viem-tracer
Version:
[![npm package][npm-img]][npm-url] [![Build Status][build-img]][build-url] [![Downloads][downloads-img]][downloads-url] [![Issues][issues-img]][issues-url] [![Commitizen Friendly][commitizen-img]][commitizen-url] [![Semantic Release][semantic-release-img]
158 lines (157 loc) • 8.63 kB
JavaScript
;
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.formatCallTrace = exports.formatCallLog = exports.formatCallSignature = exports.formatArg = exports.formatAddress = exports.getIndentLevel = exports.getCallTraceUnknownEventSelectors = exports.getCallTraceUnknownFunctionSelectors = exports.getSelector = exports.loadSignaturesCache = exports.getSignaturesCachePath = void 0;
exports.formatFullTrace = formatFullTrace;
const node_fs_1 = require("node:fs");
const node_os_1 = require("node:os");
const node_path_1 = require("node:path");
const safe_1 = __importDefault(require("colors/safe"));
const viem_1 = require("viem");
// The requested module 'colors/safe.js' is a CommonJS module, which may not support all module.exports as named exports.
// CommonJS modules can always be imported via the default export, for example using:
const { bold, cyan, grey, red, white, yellow, green, dim, magenta } = safe_1.default;
const getSignaturesCachePath = () => (0, node_path_1.join)((0, node_os_1.homedir)(), ".foundry", "cache", "signatures");
exports.getSignaturesCachePath = getSignaturesCachePath;
const loadSignaturesCache = () => {
try {
return JSON.parse((0, node_fs_1.readFileSync)((0, exports.getSignaturesCachePath)(), { encoding: "utf8" }));
}
catch { }
return { events: {}, functions: {} };
};
exports.loadSignaturesCache = loadSignaturesCache;
const getSelector = (input) => (0, viem_1.slice)(input, 0, 4);
exports.getSelector = getSelector;
const getCallTraceUnknownFunctionSelectors = (trace, signatures) => {
const rest = (trace.calls ?? [])
.flatMap((subtrace) => (0, exports.getCallTraceUnknownFunctionSelectors)(subtrace, signatures))
.filter(Boolean);
if (trace.input) {
const inputSelector = (0, exports.getSelector)(trace.input);
if (!signatures.functions[inputSelector])
rest.push(inputSelector);
}
return rest.join(",");
};
exports.getCallTraceUnknownFunctionSelectors = getCallTraceUnknownFunctionSelectors;
const getCallTraceUnknownEventSelectors = (trace, signatures) => {
const rest = (trace.calls ?? [])
.flatMap((subtrace) => (0, exports.getCallTraceUnknownEventSelectors)(subtrace, signatures))
.filter(Boolean);
if (trace.logs) {
for (const log of trace.logs) {
const selector = log.topics[0];
if (!signatures.events[selector])
rest.push(selector);
}
}
return rest.join(",");
};
exports.getCallTraceUnknownEventSelectors = getCallTraceUnknownEventSelectors;
const getIndentLevel = (level, index = false) => `${" ".repeat(level - 1)}${index ? cyan(`${level - 1} ↳ `) : " "}`;
exports.getIndentLevel = getIndentLevel;
const formatAddress = (address) => `${address.slice(0, 8)}…${address.slice(0, 4)}`;
exports.formatAddress = formatAddress;
const formatArg = (arg, level) => {
if (Array.isArray(arg)) {
const formattedArr = arg.map((arg) => `\n${(0, exports.getIndentLevel)(level + 1)}${grey((0, exports.formatArg)(arg, level + 1))},`).join("");
return `[${formattedArr ? `${formattedArr}\n` : ""}${(0, exports.getIndentLevel)(level)}]`;
}
switch (typeof arg) {
case "object": {
if (arg == null)
return "";
const formattedObj = Object.entries(arg)
.map(([key, value]) => `\n${(0, exports.getIndentLevel)(level + 1)}${key}: ${grey((0, exports.formatArg)(value, level + 1))},`)
.join("");
return `{${formattedObj ? `${formattedObj}\n` : ""}${(0, exports.getIndentLevel)(level)}}`;
}
case "string":
return grey((0, viem_1.isAddress)(arg, { strict: false }) ? (0, exports.formatAddress)(arg) : arg);
default:
return grey(String(arg));
}
};
exports.formatArg = formatArg;
const formatCallSignature = (trace, config, level, signatures) => {
const selector = (0, exports.getSelector)(trace.input);
const signature = signatures.functions[selector];
if (!signature)
return trace.input;
const { functionName, args } = (0, viem_1.decodeFunctionData)({
abi: (0, viem_1.parseAbi)(
// @ts-ignore
[`function ${signature}`]),
data: trace.input,
});
const value = BigInt(trace.value ?? "0x0");
const formattedArgs = args?.map((arg) => (0, exports.formatArg)(arg, level)).join(", ");
return `${bold((trace.revertReason || trace.error ? red : green)(functionName))}${value !== 0n ? grey(`{ ${white((0, viem_1.formatEther)(value))} ETH }`) : ""}${config.gas ? grey(`[ ${dim(magenta(Number(trace.gasUsed).toLocaleString()))} / ${dim(magenta(Number(trace.gas).toLocaleString()))} ]`) : ""}(${formattedArgs ?? ""})`;
};
exports.formatCallSignature = formatCallSignature;
const formatCallLog = (log, level, signatures) => {
const selector = log.topics[0];
const signature = signatures.events[selector];
if (!signature)
return (0, viem_1.concatHex)(log.topics);
const { eventName, args } = (0, viem_1.decodeEventLog)({
abi: (0, viem_1.parseAbi)(
// @ts-ignore
[`event ${signature}`]),
data: (0, viem_1.concatHex)(log.topics.slice(1).concat(log.data)),
topics: log.topics,
strict: false,
});
const formattedArgs = args?.map((arg) => (0, exports.formatArg)(arg, level)).join(", ");
return `${(0, exports.getIndentLevel)(level + 1, true)}${yellow("LOG")} ${eventName}(${formattedArgs ?? ""})`;
};
exports.formatCallLog = formatCallLog;
const formatCallTrace = (trace, config = {}, signatures = (0, exports.loadSignaturesCache)(), level = 1) => {
const rest = (trace.calls ?? [])
.map((subtrace) => (0, exports.formatCallTrace)(subtrace, config, signatures, level + 1))
.join("\n");
const error = trace.revertReason || trace.error;
const returnValue = error || trace.output;
const indentLevel = (0, exports.getIndentLevel)(level, true);
return `${level === 1 ? `${indentLevel}${cyan("FROM")} ${grey(trace.from)}\n` : ""}${indentLevel}${yellow(trace.type)} ${trace.from === trace.to ? grey("self") : `(${white(trace.to)})`}.${(0, exports.formatCallSignature)(trace, config, level, signatures)}${returnValue ? (error ? red : grey)(` -> ${returnValue}`) : ""}${trace.logs ? `\n${trace.logs.map((log) => (0, exports.formatCallLog)(log, level, signatures))}` : ""}
${config.raw ? `${grey(JSON.stringify(trace))}\n` : ""}${rest}`;
};
exports.formatCallTrace = formatCallTrace;
async function formatFullTrace(trace, config, signatures = (0, exports.loadSignaturesCache)()) {
const unknownFunctionSelectors = (0, exports.getCallTraceUnknownFunctionSelectors)(trace, signatures);
const unknownEventSelectors = (0, exports.getCallTraceUnknownEventSelectors)(trace, signatures);
if (unknownFunctionSelectors || unknownEventSelectors) {
const searchParams = new URLSearchParams({ filter: "false" });
if (unknownFunctionSelectors)
searchParams.append("function", unknownFunctionSelectors);
if (unknownEventSelectors)
searchParams.append("event", unknownEventSelectors);
const lookupRes = await fetch(`https://api.openchain.xyz/signature-database/v1/lookup?${searchParams.toString()}`);
const lookup = await lookupRes.json();
if (lookup.ok) {
Object.entries(lookup.result.function).map(([sig, results]) => {
const match = results.find(({ filtered }) => !filtered)?.name;
if (!match)
return;
signatures.functions[sig] = match;
});
Object.entries(lookup.result.event).map(([sig, results]) => {
const match = results.find(({ filtered }) => !filtered)?.name;
if (!match)
return;
signatures.events[sig] = match;
});
const path = (0, exports.getSignaturesCachePath)();
if (!(0, node_fs_1.existsSync)(path))
(0, node_fs_1.mkdirSync)((0, node_path_1.dirname)(path), { recursive: true });
(0, node_fs_1.writeFileSync)(path, JSON.stringify(signatures));
}
else {
console.warn(`Failed to fetch signatures for unknown selectors: ${unknownFunctionSelectors},${unknownEventSelectors}`, lookup.error, "\n");
}
}
return white((0, exports.formatCallTrace)(trace, config, signatures));
}