UNPKG

@celo/explorer

Version:
210 lines 8.69 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 = void 0; const cross_fetch_1 = __importDefault(require("cross-fetch")); const base_1 = require("./base"); 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.abiCoder = connection.getAbiCoder(); this.response = response; // XXX: For some reason this isn't exported as it should be // @ts-ignore this.jsonInterfaceMethodToString = connection.web3.utils._jsonInterfaceMethodToString; 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 signature = this.abiCoder.encodeFunctionSignature(item); 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' && this.abiCoder.encodeFunctionSignature(item) === 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' && this.jsonInterfaceMethodToString(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; /** * Fetch the sourcify response and instantiate a Metadata wrapper class around it. * Try a full_match but fallback to partial_match when not strict. * @param connection @celo/connect instance * @param contract the address of the contract to query * @param strict only allow full matches https://docs.sourcify.dev/docs/full-vs-partial-match/ * @returns Metadata or null */ function fetchMetadata(connection, contract, strict = false) { return __awaiter(this, void 0, void 0, function* () { const fullMatchMetadata = yield querySourcify(connection, 'full_match', contract); if (fullMatchMetadata !== null) { return fullMatchMetadata; } else if (strict) { return null; } else { return querySourcify(connection, 'partial_match', contract); } }); } exports.fetchMetadata = fetchMetadata; /** * Fetch the sourcify response and instantiate a Metadata wrapper class around it. * @param connection @celo/connect instance * @param matchType what type of match to query for https://docs.sourcify.dev/docs/full-vs-partial-match/ * @param contract the address of the contract to query * @returns Metadata or null */ function querySourcify(connection, matchType, contract) { return __awaiter(this, void 0, void 0, function* () { const chainID = yield connection.chainId(); const resp = yield (0, cross_fetch_1.default)(`https://repo.sourcify.dev/contracts/${matchType}/${chainID}/${contract}/metadata.json`); if (resp.ok) { return new Metadata(connection, contract, yield resp.json()); } return null; }); } /** * 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 = new connection.web3.eth.Contract(PROXY_ABI, contract); for (const fn of PROXY_IMPLEMENTATION_GETTERS) { try { return yield new Promise((resolve, reject) => { proxyContract.methods[fn]().call().then(resolve).catch(reject); }); } catch (_a) { continue; } } try { const hexValue = yield connection.web3.eth.getStorageAt(contract, PROXY_IMPLEMENTATION_POSITION_UUPS); const address = connection.web3.utils.toChecksumAddress('0x' + hexValue.slice(-40)); return address; } catch (_b) { return undefined; } }); } exports.tryGetProxyImplementation = tryGetProxyImplementation; //# sourceMappingURL=sourcify.js.map