UNPKG

hardhat-tracer

Version:

Hardhat Tracer plugin

181 lines 8.1 kB
"use strict"; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.Decoder = void 0; const debug_1 = __importDefault(require("debug")); const ethers_1 = require("ethers"); const utils_1 = require("ethers/lib/utils"); const debug = (0, debug_1.default)("hardhat-tracer:decoder"); class Decoder { constructor(artifacts, cache, use4bytesDirectory) { this.cache = cache; this.use4bytesDirectory = use4bytesDirectory; this.functionFragmentsBySelector = new Map(); this.errorFragmentsBySelector = new Map(); this.eventFragmentsByTopic0 = new Map(); debug("Decoder constructor"); this.ready = this._updateArtifacts(artifacts); } async updateArtifacts(artifacts) { this.ready = this._updateArtifacts(artifacts); } async _updateArtifacts(artifacts) { debug("_updateArtifacts called"); const names = await artifacts.getAllFullyQualifiedNames(); const everyArtifact = await Promise.all(names.map((name) => artifacts.readArtifact(name))); for (let i = 0; i < names.length; i++) { const name = names[i]; const artifact = everyArtifact[i]; const iface = new ethers_1.ethers.utils.Interface(artifact.abi); copyFragments(name, iface.functions, this.functionFragmentsBySelector); copyFragments(name, iface.errors, this.errorFragmentsBySelector); copyFragments(name, iface.events, this.eventFragmentsByTopic0); } // common errors, these are in function format because Ethers.js does not accept them as errors const commonErrors = [ "function Error(string reason)", "function Panic(uint256 code)", ]; const commonErrorsIface = new utils_1.Interface(commonErrors); copyFragments("", commonErrorsIface.functions, this.errorFragmentsBySelector); debug("_updateArtifacts finished"); } async decode(inputData, returnData) { await this.ready; try { debug("try decoding as a function"); return decode(inputData, returnData, "function", this.functionFragmentsBySelector, this.cache, this.use4bytesDirectory); } catch (e) { debug("decoding as a function errored %s", e.message); } debug("decode as error"); return decode(inputData, returnData, "error", this.errorFragmentsBySelector, this.cache, this.use4bytesDirectory); } async decodeFunction(inputData, returnData) { await this.ready; return decode(inputData, returnData, "function", this.functionFragmentsBySelector, this.cache, this.use4bytesDirectory); } async decodeError(revertData) { await this.ready; const { fragment, inputResult, contractName } = await decode(revertData, "0x", "error", this.errorFragmentsBySelector, this.cache, this.use4bytesDirectory); return { fragment, revertResult: inputResult, contractName }; } async decodeEvent(topics, data) { await this.ready; if (topics.length === 0) { throw new Error("[hardhat-tracer]: No topics, cannot decode"); } const topic0 = topics[0]; const fragments = this.eventFragmentsByTopic0.get(topic0); if (fragments) { for (const { contractName, fragment } of fragments) { try { const iface = new ethers_1.ethers.utils.Interface([fragment]); const result = iface.parseLog({ data, topics }); return { fragment, result: result.args, contractName }; } catch { } } } throw decodeError(topic0); } } exports.Decoder = Decoder; function copyFragments(contractName, fragments, mapping) { for (const fragment of Object.values(fragments)) { addFragmentToMapping(contractName, fragment, mapping); } } function addFragmentToMapping(contractName, fragment, mapping) { const selector = utils_1.EventFragment.isEventFragment(fragment) ? ethers_1.ethers.utils.Interface.getEventTopic(fragment) : ethers_1.ethers.utils.Interface.getSighash(fragment); let fragments = mapping.get(selector); if (!fragments) { mapping.set(selector, (fragments = [])); } // TODO while adding, see if we already have a same signature fragment fragments.push({ contractName, fragment }); } async function decode(inputData, returnData, type, mapping, cache, use4bytesDirectory) { const selector = inputData.slice(0, 10); // console.log("selector", selector); // if we have a local fragment for this selector, try using it const fragments = mapping.get(selector); if (fragments) { for (const { fragment, contractName } of fragments) { try { const iface = new utils_1.Interface([fragment]); if (type === "function") { const inputResult = iface.decodeFunctionData(inputData.slice(0, 10), inputData); let returnResult; try { returnResult = iface.decodeFunctionResult(inputData.slice(0, 10), returnData); } catch { } return { fragment, inputResult, returnResult, contractName }; } else if (type === "error") { const inputResult = iface.decodeErrorResult(fragment, inputData); return { fragment, inputResult, contractName }; } } catch { } } } // we couldn't decode it using local ABI, try 4byte.directory // currently only supports function calls if (use4bytesDirectory && type === "function") { try { debug("decode using 4byte.directory"); const { fragment, inputResult } = await decodeUsing4byteDirectory(selector, inputData, mapping, cache); return { fragment, inputResult }; } catch (e) { debug("decoding using 4byte.directory errored %s", e.message); } } // we couldn't decode it after even using 4byte.directory, give up throw decodeError(selector); } async function decodeUsing4byteDirectory(selector, inputData, mapping, cache) { let responseResults; const cacheVal = cache.fourByteDir.get(selector); if (cacheVal) { responseResults = cacheVal; } else { const response = await (0, utils_1.fetchJson)("https://www.4byte.directory/api/v1/signatures/?hex_signature=" + selector); responseResults = response.results; cache.fourByteDir.set(selector, responseResults); cache.save(); } // sort the results to prefer the oldest entry responseResults.sort((a, b) => new Date(a.created_at) < new Date(b.created_at) ? -1 : 1); for (const result of responseResults) { try { const iface = new utils_1.Interface(["function " + result.text_signature]); const inputResult = iface.decodeFunctionData(inputData.slice(0, 10), inputData); // there's some weird Node.js bug, error from above line doesn't get catched by try/catch // the following line looks inside inputResult, so Node.js has to resolve it. for (const _ of inputResult) { } // cache the fragment for next time (within the same run) const fragment = iface.getFunction(result.text_signature); // console.log(fragment); addFragmentToMapping("", fragment, mapping); return { fragment, inputResult }; } catch (E) { // console.log("error xyzzz", E); } } throw decodeError(selector); } function decodeError(selector) { return new Error(`Could not decode data for selector ${selector}, no ABI available.`); } //# sourceMappingURL=decoder.js.map