@nomyx/hardhat-adminui
Version:
A comprehensive Hardhat plugin providing a web-based admin UI for deployed smart contracts with Diamond proxy support, contract interaction, event monitoring, and deployment dashboard.
264 lines (263 loc) • 10.2 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.FunctionSignatureDecoder = void 0;
const ethers_1 = require("ethers");
/**
* Common function signatures for popular smart contract functions
* These are commonly used functions across DeFi and other protocols
*/
const COMMON_FUNCTION_SIGNATURES = {
'0xa9059cbb': 'transfer(address,uint256)',
'0x23b872dd': 'transferFrom(address,address,uint256)',
'0x095ea7b3': 'approve(address,uint256)',
'0x70a08231': 'balanceOf(address)',
'0xdd62ed3e': 'allowance(address,address)',
'0x18160ddd': 'totalSupply()',
'0x06fdde03': 'name()',
'0x95d89b41': 'symbol()',
'0x313ce567': 'decimals()',
'0x40c10f19': 'mint(address,uint256)',
'0x42966c68': 'burn(uint256)',
'0x79cc6790': 'burnFrom(address,uint256)',
'0x8da5cb5b': 'owner()',
'0x715018a6': 'renounceOwnership()',
'0xf2fde38b': 'transferOwnership(address)',
'0x5c975abb': 'paused()',
'0x8456cb59': 'pause()',
'0x3f4ba83a': 'unpause()',
'0xd547741f': 'revokeRole(bytes32,address)',
'0x2f2ff15d': 'grantRole(bytes32,address)',
'0x91d14854': 'hasRole(bytes32,address)',
'0x248a9ca3': 'getRoleAdmin(bytes32)',
'0x02fe5305': 'setApprovalForAll(address,bool)',
'0xe985e9c5': 'isApprovedForAll(address,address)',
'0x6352211e': 'ownerOf(uint256)',
'0x081812fc': 'getApproved(uint256)',
'0xb88d4fde': 'tokenURI(uint256)',
'0x2d1a12f2': 'uri(uint256)',
'0x4e1273f4': 'balanceOfBatch(address[],uint256[])',
'0xf242432a': 'safeTransferFrom(address,address,uint256,uint256,bytes)',
'0x2eb2c2d6': 'safeBatchTransferFrom(address,address,uint256[],uint256[],bytes)',
};
/**
* Common event signatures
*/
const COMMON_EVENT_SIGNATURES = {
'0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef': 'Transfer(address,address,uint256)',
'0x8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925': 'Approval(address,address,uint256)',
'0x17307eab39ab6107e8899845ad3d59bd9653f200f220920489ca2b5937696c31': 'ApprovalForAll(address,address,bool)',
'0x8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e0': 'OwnershipTransferred(address,address)',
'0x2f8788117e7eff1d82e926ec794901d17c78024a50270940304540a733656f0d': 'RoleAdminChanged(bytes32,bytes32,bytes32)',
'0x2f8ba45dc4ee0a77c5a46c269a4c32b04b3a8bcfc32ee9abee60edbfe0c9476f': 'RoleGranted(bytes32,address,address)',
'0xf6391f5c32d9c69d2a47ea670b442974b53935d1edc7fd64eb21e047a839171b': 'RoleRevoked(bytes32,address,address)',
'0x62e78cea01bee320cd4e420270b5ea74000d11b0c9f74754ebdbfc544b05a258': 'Paused(address)',
'0x5db9ee0a495bf2e6ff9c91a7834c1ba4fdd244a5e8aa4e537bd38aeae4b073aa': 'Unpaused(address)',
};
class FunctionSignatureDecoder {
constructor(provider) {
this.cache = new Map();
this.eventCache = new Map();
this.provider = provider;
}
/**
* Decode a function call using ABI or known signatures
*/
async decodeFunction(data, contractAbi) {
if (!data || data.length < 10)
return null;
const selector = data.slice(0, 10);
// Check cache first
const cacheKey = `${selector}:${data}`;
if (this.cache.has(cacheKey)) {
return this.cache.get(cacheKey);
}
let decoded = null;
// Try to decode using provided ABI first
if (contractAbi) {
try {
const result = contractAbi.parseTransaction({ data });
decoded = {
name: result.name,
signature: result.signature,
selector,
inputs: result.args.map((arg, index) => ({
name: result.functionFragment.inputs[index].name || `param${index}`,
type: result.functionFragment.inputs[index].type,
value: this.formatValue(arg, result.functionFragment.inputs[index].type)
}))
};
}
catch (error) {
console.log('Failed to decode with ABI:', error);
}
}
// Fall back to known signatures
if (!decoded && COMMON_FUNCTION_SIGNATURES[selector]) {
const signature = COMMON_FUNCTION_SIGNATURES[selector];
const name = signature.split('(')[0];
decoded = {
name,
signature,
selector,
inputs: await this.decodeInputsFromSignature(data, signature)
};
}
// If still no result, provide basic info
if (!decoded) {
decoded = {
name: 'Unknown Function',
signature: `unknown(${selector})`,
selector,
inputs: [{
name: 'data',
type: 'bytes',
value: data.slice(10) || '0x'
}]
};
}
// Cache the result
this.cache.set(cacheKey, decoded);
return decoded;
}
/**
* Decode event logs using ABI or known signatures
*/
async decodeEvent(log, contractAbi) {
if (!log.topics || log.topics.length === 0)
return null;
const eventTopic = log.topics[0];
const cacheKey = `${eventTopic}:${log.data}:${log.topics.join(',')}`;
// Check cache first
if (this.eventCache.has(cacheKey)) {
return this.eventCache.get(cacheKey);
}
let decoded = null;
// Try to decode using provided ABI first
if (contractAbi) {
try {
const result = contractAbi.parseLog(log);
decoded = {
name: result.name,
signature: result.signature,
topics: log.topics,
inputs: result.args.map((arg, index) => ({
name: result.eventFragment.inputs[index].name || `param${index}`,
type: result.eventFragment.inputs[index].type,
value: this.formatValue(arg, result.eventFragment.inputs[index].type),
indexed: result.eventFragment.inputs[index].indexed
}))
};
}
catch (error) {
console.log('Failed to decode event with ABI:', error);
}
}
// Fall back to known event signatures
if (!decoded && COMMON_EVENT_SIGNATURES[eventTopic]) {
const signature = COMMON_EVENT_SIGNATURES[eventTopic];
const name = signature.split('(')[0];
decoded = {
name,
signature,
topics: log.topics,
inputs: await this.decodeEventInputsFromSignature(log, signature)
};
}
// If still no result, provide basic info
if (!decoded) {
decoded = {
name: 'Unknown Event',
signature: `UnknownEvent(${eventTopic})`,
topics: log.topics,
inputs: [{
name: 'data',
type: 'bytes',
value: log.data || '0x',
indexed: false
}]
};
}
// Cache the result
this.eventCache.set(cacheKey, decoded);
return decoded;
}
/**
* Decode function inputs from signature
*/
async decodeInputsFromSignature(data, signature) {
try {
const iface = new ethers_1.ethers.utils.Interface([`function ${signature}`]);
const result = iface.parseTransaction({ data });
return result.args.map((arg, index) => ({
name: result.functionFragment.inputs[index].name || `param${index}`,
type: result.functionFragment.inputs[index].type,
value: this.formatValue(arg, result.functionFragment.inputs[index].type)
}));
}
catch (error) {
return [{
name: 'rawData',
type: 'bytes',
value: data.slice(10) || '0x'
}];
}
}
/**
* Decode event inputs from signature
*/
async decodeEventInputsFromSignature(log, signature) {
try {
const iface = new ethers_1.ethers.utils.Interface([`event ${signature}`]);
const result = iface.parseLog(log);
return result.args.map((arg, index) => ({
name: result.eventFragment.inputs[index].name || `param${index}`,
type: result.eventFragment.inputs[index].type,
value: this.formatValue(arg, result.eventFragment.inputs[index].type),
indexed: result.eventFragment.inputs[index].indexed
}));
}
catch (error) {
return [{
name: 'rawData',
type: 'bytes',
value: log.data || '0x',
indexed: false
}];
}
}
/**
* Format values for display
*/
formatValue(value, type) {
if (ethers_1.ethers.BigNumber.isBigNumber(value)) {
// For display purposes, convert to string
if (type.includes('uint') || type.includes('int')) {
return value.toString();
}
}
if (Array.isArray(value)) {
return value.map(v => this.formatValue(v, type.replace('[]', '')));
}
return value;
}
/**
* Add custom function signature to the decoder
*/
addFunctionSignature(selector, signature) {
COMMON_FUNCTION_SIGNATURES[selector] = signature;
}
/**
* Add custom event signature to the decoder
*/
addEventSignature(topic, signature) {
COMMON_EVENT_SIGNATURES[topic] = signature;
}
/**
* Clear caches
*/
clearCache() {
this.cache.clear();
this.eventCache.clear();
}
}
exports.FunctionSignatureDecoder = FunctionSignatureDecoder;