hardhat-tracer
Version:
Hardhat Tracer plugin
181 lines • 8.1 kB
JavaScript
;
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