UNPKG

@celo/explorer

Version:
224 lines (223 loc) 9.29 kB
"use strict"; var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } return new (P || (P = Promise))(function (resolve, reject) { function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } step((generator = generator.apply(thisArg, _arguments || [])).next()); }); }; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.tryGetProxyImplementation = exports.fetchMetadata = exports.Metadata = exports.formatAbiInputType = void 0; const cross_fetch_1 = __importDefault(require("cross-fetch")); const viem_1 = require("viem"); const base_1 = require("./base"); /** * Convert an ABI item to a function signature string like `transfer(address,uint256)`. * Replaces the former `_jsonInterfaceMethodToString` helper. */ function abiItemToSignatureString(item) { if (item.type === 'function' || item.type === 'constructor' || item.type === 'event') { const inputTypes = (item.inputs || []).map((input) => formatAbiInputType(input)); return `${item.name || ''}(${inputTypes.join(',')})`; } return item.name || ''; } function formatAbiInputType(input) { if (input.type === 'tuple' && input.components) { return `(${input.components.map((c) => formatAbiInputType(c)).join(',')})`; } if (input.type.startsWith('tuple[') && input.components) { const suffix = input.type.slice(5); // e.g. '[]' or '[3]' return `(${input.components.map((c) => formatAbiInputType(c)).join(',')})${suffix}`; } return input.type; } exports.formatAbiInputType = formatAbiInputType; const PROXY_IMPLEMENTATION_GETTERS = [ '_getImplementation', 'getImplementation', '_implementation', 'implementation', ]; // Position of the implementation in the UUPS proxy smart contract storage // https://github.com/OpenZeppelin/openzeppelin-contracts/blob/8b4b7b8d041c62a84e2c23d7f6e1f0d9e0fc1f20/contracts/proxy/ERC1967/ERC1967Utils.sol#L35 const PROXY_IMPLEMENTATION_POSITION_UUPS = '0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc'; const PROXY_ABI = PROXY_IMPLEMENTATION_GETTERS.map((funcName) => ({ constant: true, inputs: [], name: funcName, outputs: [ { internalType: 'address', name: 'implementation', type: 'address', }, ], payable: false, stateMutability: 'view', type: 'function', })); /** * Wrapper class for a metadata.json response from sourcify. * Because the response's true structure is unknown this wrapper implements * light runtime verification. */ class Metadata { constructor(_connection, address, response) { this.abi = null; this.contractName = null; this.fnMapping = new Map(); this.response = response; this.address = address; } set response(value) { if (typeof value === 'object' && typeof value.output === 'object' && 'abi' in value.output && Array.isArray(value.output.abi) && value.output.abi.length > 0) { this.abi = value.output.abi; this.fnMapping = (0, base_1.mapFromPairs)((this.abi || []) .filter((item) => item.type === 'function') .map((item) => { const sig = `${item.name}(${(item.inputs || []).map((i) => formatAbiInputType(i)).join(',')})`; const signature = (0, viem_1.toFunctionSelector)(sig); return Object.assign(Object.assign({}, item), { signature }); }) .map((item) => [item.signature, item])); } if (typeof value === 'object' && typeof value.settings === 'object' && typeof value.settings.compilationTarget === 'object' && Object.values(value.settings.compilationTarget).length > 0) { // XXX: Not sure when there are multiple compilationTargets and what should // happen then but defaulting to this for now. const contracts = Object.values(value.settings.compilationTarget); this.contractName = contracts[0]; } } /** * Turn the ABI into a mapping of function selectors to ABI items. */ toContractMapping() { return { details: { name: this.contractName || 'Unknown', address: this.address, jsonInterface: this.abi || [], isCore: false, }, fnMapping: this.fnMapping, }; } /** * Find the AbiItem for a given function selector * @param selector the 4-byte selector of the function call * @returns an AbiItem if found or null */ abiForSelector(selector) { var _a; return (((_a = this.abi) === null || _a === void 0 ? void 0 : _a.find((item) => { return (item.type === 'function' && (0, viem_1.toFunctionSelector)(`${item.name}(${(item.inputs || []).map((i) => formatAbiInputType(i)).join(',')})`) === selector); })) || null); } /** * Find the AbiItem for methods that match the provided method name. * The function can return more than one AbiItem if the query string * provided doesn't contain arguments as there can be multiple * definitions with different arguments. * @param method name of the method to lookup * @returns and array of AbiItems matching the query */ abiForMethod(query) { var _a, _b; if (query.indexOf('(') >= 0) { // Method is a full call signature with arguments return (((_a = this.abi) === null || _a === void 0 ? void 0 : _a.filter((item) => { return item.type === 'function' && abiItemToSignatureString(item) === query; })) || []); } else { // Method is only method name return (((_b = this.abi) === null || _b === void 0 ? void 0 : _b.filter((item) => { return item.type === 'function' && item.name === query; })) || []); } } } exports.Metadata = Metadata; const SOURCIFY_SERVER_URL = 'https://sourcify.dev/server'; /** * Fetch the contract metadata from Sourcify and instantiate a Metadata wrapper * class around it. Uses the Sourcify v2 API (the v1 repo API has been sunset). * @param connection @celo/connect instance * @param contract the address of the contract to query * @param strict only allow exact matches https://docs.sourcify.dev/docs/exact-match-vs-match/ * @returns Metadata or null */ function fetchMetadata(connection, contract, strict = false) { return __awaiter(this, void 0, void 0, function* () { const chainID = yield connection.viemClient.getChainId(); const resp = yield (0, cross_fetch_1.default)(`${SOURCIFY_SERVER_URL}/v2/contract/${chainID}/${contract}?fields=metadata`); // 404 (and any non-2xx) means the contract isn't verified on Sourcify. if (!resp.ok) { return null; } const data = (yield resp.json()); if (!data || !data.match || !data.metadata) { return null; } if (strict && data.match !== 'exact_match') { return null; } return new Metadata(connection, contract, data.metadata); }); } exports.fetchMetadata = fetchMetadata; /** * Use heuristics to determine if the contract can be a proxy * and extract the implementation. * Available scenarios: * - _getImplementation() exists * - getImplementation() exists * - _implementation() exists * - implementation() exists * @param connection @celo/connect instance * @param contract the address of the contract to query * @returns the implementation address or null */ function tryGetProxyImplementation(connection, contract) { return __awaiter(this, void 0, void 0, function* () { const proxyContract = connection.getCeloContract(PROXY_ABI, contract); for (const fn of PROXY_IMPLEMENTATION_GETTERS) { try { const result = yield proxyContract.read[fn](); return result; } catch (_a) { continue; } } try { const hexValue = yield connection.viemClient.getStorageAt({ address: contract, slot: PROXY_IMPLEMENTATION_POSITION_UUPS, }); if (!hexValue) { return undefined; } // checksum to match map keys populated from registry.addressMapping() return (0, viem_1.getAddress)('0x' + hexValue.slice(-40)); } catch (_b) { return undefined; } }); } exports.tryGetProxyImplementation = tryGetProxyImplementation;