UNPKG

@celo/explorer

Version:
314 lines 14.5 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.BlockExplorer = exports.newBlockExplorer = void 0; const connect_1 = require("@celo/connect"); const contractkit_1 = require("@celo/contractkit"); const proxy_1 = require("@celo/contractkit/lib/proxy"); const fixidity_1 = require("@celo/utils/lib/fixidity"); const bignumber_js_1 = __importDefault(require("bignumber.js")); const debug_1 = __importDefault(require("debug")); const base_1 = require("./base"); const sourcify_1 = require("./sourcify"); const debug = (0, debug_1.default)('kit:explorer:block'); function newBlockExplorer(kit) { return __awaiter(this, void 0, void 0, function* () { return new BlockExplorer(kit, yield (0, base_1.obtainKitContractDetails)(kit)); }); } exports.newBlockExplorer = newBlockExplorer; const getContractMappingFromDetails = (cd) => ({ details: cd, fnMapping: (0, base_1.mapFromPairs)(cd.jsonInterface.concat(proxy_1.PROXY_ABI) .filter((ad) => ad.type === 'function') .map((ad) => [ad.signature, ad])), }); const isCoreContract = (contract) => contract in contractkit_1.CeloContract; class BlockExplorer { constructor(kit, contractDetails) { this.kit = kit; this.contractDetails = contractDetails; this.proxyImplementationOverride = new Map(); this.getContractMethodAbiFromMapping = (contractMapping, selector) => { if (contractMapping === undefined) { return null; } const methodAbi = contractMapping.fnMapping.get(selector); if (methodAbi === undefined) { return null; } return { contract: contractMapping.details.address, contractName: contractMapping.details.name, abi: methodAbi, }; }; /** * @deprecated use getContractMappingWithSelector instead * Returns the contract name and ABI of the method by looking up * the contract address either in all possible contract mappings. * @param address * @param selector * @param onlyCoreContracts * @returns The contract name and ABI of the method or null if not found */ this.getContractMethodAbi = (address, selector, onlyCoreContracts = false) => __awaiter(this, void 0, void 0, function* () { if (onlyCoreContracts) { return this.getContractMethodAbiFromCore(address, selector); } const contractMapping = yield this.getContractMappingWithSelector(address, selector); if (contractMapping === undefined) { return null; } return this.getContractMethodAbiFromMapping(contractMapping, selector); }); /** * Returns the contract name and ABI of the method by looking up * the contract address but only in core contracts * @param address * @param selector * @returns The contract name and ABI of the method or null if not found */ this.getContractMethodAbiFromCore = (address, selector) => __awaiter(this, void 0, void 0, function* () { const contractMapping = yield this.getContractMappingWithSelector(address, selector, [ this.getContractMappingFromCore, ]); if (contractMapping === undefined) { return null; } return this.getContractMethodAbiFromMapping(contractMapping, selector); }); /** * @deprecated use getContractMappingWithSelector instead * Returns the contract name and ABI of the method by looking up * the contract address in Sourcify. * @param address * @param selector * @returns The contract name and ABI of the method or null if not found */ this.getContractMethodAbiFromSourcify = (address, selector) => __awaiter(this, void 0, void 0, function* () { const contractMapping = yield this.getContractMappingWithSelector(address, selector, [ this.getContractMappingFromSourcify, this.getContractMappingFromSourcifyAsProxy, ]); if (contractMapping === undefined) { return null; } return this.getContractMethodAbiFromMapping(contractMapping, selector); }); /** * @deprecated use getContractMappingWithSelector instead * Returns the contract name and ABI of the method by looking up * the selector in a list of known functions. * @param address * @param selector * @param onlyCoreContracts * @returns The contract name and ABI of the method or null if not found */ this.getContractMethodAbiFallback = (address, selector) => { // TODO(bogdan): This could be replaced with a call to 4byte.directory // or a local database of common functions. const knownFunctions = { '0x095ea7b3': 'approve(address to, uint256 value)', '0x4d49e87d': 'addLiquidity(uint256[] amounts, uint256 minLPToMint, uint256 deadline)', }; const signature = knownFunctions[selector]; if (signature) { return { abi: (0, connect_1.signatureToAbiDefinition)(signature), contract: `Unknown(${address})`, }; } return null; }; /** * Returns the ContractMapping for the contract at that address, or undefined * by looking up the contract address in the core mappings. * @param address * @returns The ContractMapping for the contract at that address, or undefined */ this.getContractMappingFromCore = (address) => __awaiter(this, void 0, void 0, function* () { return this.addressMapping.get(address); }); /** * Returns the ContractMapping for the contract at that address, or undefined * by looking up the contract address in Sourcify. * @param address * @returns The ContractMapping for the contract at that address, or undefined */ this.getContractMappingFromSourcify = (address) => __awaiter(this, void 0, void 0, function* () { const cached = this.addressMapping.get(address); if (cached) { return cached; } const metadata = yield (0, sourcify_1.fetchMetadata)(this.kit.connection, this.kit.web3.utils.toChecksumAddress(address)); const mapping = metadata === null || metadata === void 0 ? void 0 : metadata.toContractMapping(); if (mapping) { this.addressMapping.set(address, mapping); } return mapping; }); /** * Returns the ContractMapping for the contract at that address, or undefined * by looking up the contract address in Sourcify but using heuristis to treat * it as a proxy. * * This function is also included by the proxyImplementationOverrides map, * which can be used to override the implementation address for a given proxy. * This is exceptionally useful for parsing governence proposals that either * initialize a proxy or upgrade it, and then calls methods on the new implementation. * @param address * @returns The ContractMapping for the contract at that address, or undefined */ this.getContractMappingFromSourcifyAsProxy = (address) => __awaiter(this, void 0, void 0, function* () { let implAddress = yield (0, sourcify_1.tryGetProxyImplementation)(this.kit.connection, address); if (this.proxyImplementationOverride.has(address)) { implAddress = this.proxyImplementationOverride.get(address); } if (implAddress) { const contractMapping = yield this.getContractMappingFromSourcify(implAddress); if (contractMapping) { return Object.assign(Object.assign({}, contractMapping), { details: Object.assign(Object.assign({}, contractMapping.details), { address }) }); } } }); this.addressMapping = (0, base_1.mapFromPairs)(contractDetails .filter((cd) => /Proxy$/.exec(cd.name) == null) .map((cd) => [cd.address, getContractMappingFromDetails(cd)])); } updateContractDetailsMapping(name, address) { return __awaiter(this, void 0, void 0, function* () { if (isCoreContract(name)) { const contractDetails = yield (0, base_1.getContractDetailsFromContract)(this.kit, name, address); this.addressMapping.set(contractDetails.address, getContractMappingFromDetails(contractDetails)); } }); } setProxyOverride(proxyAddress, implementationAddress) { return __awaiter(this, void 0, void 0, function* () { debug('Setting proxy override for %s to %s', proxyAddress, implementationAddress); this.proxyImplementationOverride.set(proxyAddress, implementationAddress); }); } fetchBlockByHash(blockHash) { return __awaiter(this, void 0, void 0, function* () { return this.kit.connection.getBlock(blockHash); }); } fetchBlock(blockNumber) { return __awaiter(this, void 0, void 0, function* () { return this.kit.connection.getBlock(blockNumber); }); } fetchBlockRange(from, to) { return __awaiter(this, void 0, void 0, function* () { const results = []; for (let i = from; i < to; i++) { results.push(yield this.fetchBlock(i)); } return results; }); } parseBlock(block) { return __awaiter(this, void 0, void 0, function* () { const parsedTx = []; for (const tx of block.transactions) { if (typeof tx !== 'string') { const maybeKnownCall = yield this.tryParseTx(tx); if (maybeKnownCall != null) { parsedTx.push(maybeKnownCall); } } } return { block, parsedTx, }; }); } tryParseTx(tx) { return __awaiter(this, void 0, void 0, function* () { const callDetails = yield this.tryParseTxInput(tx.to, tx.input); if (!callDetails) { return null; } return { tx, callDetails, }; }); } tryParseTxInput(address, input) { return __awaiter(this, void 0, void 0, function* () { const selector = input.slice(0, 10); const contractMapping = yield this.getContractMappingWithSelector(address, selector); if (contractMapping) { const methodAbi = contractMapping.fnMapping.get(selector); return this.buildCallDetails(contractMapping.details, methodAbi, input); } return null; }); } buildCallDetails(contract, abi, input) { const encodedParameters = input.slice(10); const { args, params } = (0, connect_1.parseDecodedParams)(this.kit.connection.getAbiCoder().decodeParameters(abi.inputs, encodedParameters)); // transform numbers to big numbers in params abi.inputs.forEach((abiInput, idx) => { if (abiInput.type === 'uint256') { debug('transforming number param'); params[abiInput.name] = new bignumber_js_1.default(args[idx]); } }); // transform fixidity values to fractions in params Object.keys(params) .filter((key) => key.includes('fraction')) // TODO: come up with better enumeration .forEach((fractionKey) => { debug('transforming fixed number param'); params[fractionKey] = (0, fixidity_1.fromFixed)(params[fractionKey]); }); return { contract: contract.name, contractAddress: contract.address, isCoreContract: contract.isCore, function: abi.name, paramMap: params, argList: args, }; } /** * Uses all of the strategies available to find a contract mapping that contains * the given method selector. * @param address * @param selector * @param strategies * @returns The ContractMapping for the contract which has the function selector, or undefined */ getContractMappingWithSelector(address, selector, strategies = [ this.getContractMappingFromCore, this.getContractMappingFromSourcify, this.getContractMappingFromSourcifyAsProxy, ]) { return __awaiter(this, void 0, void 0, function* () { const mappings = yield Promise.all(strategies.map((strategy) => __awaiter(this, void 0, void 0, function* () { const contractMapping = yield strategy(address); if (contractMapping && contractMapping.fnMapping.get(selector)) { return contractMapping; } }))); return mappings.find((mapping) => mapping !== undefined); }); } } exports.BlockExplorer = BlockExplorer; //# sourceMappingURL=block-explorer.js.map