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]
221 lines (220 loc) • 10.9 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.formatInt = exports.formatHex = 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) => `${(0, viem_1.slice)(address, 0, 4)}…${(0, viem_1.slice)(address, -2).slice(2)}`;
exports.formatAddress = formatAddress;
const formatHex = (hex) => {
if (hex === viem_1.zeroHash)
return "bytes(0)";
return (0, viem_1.size)(hex) > 8 ? `${(0, viem_1.slice)(hex, 0, 4)}…${(0, viem_1.slice)(hex, -1).slice(2)}` : hex;
};
exports.formatHex = formatHex;
const formatInt = (value) => {
for (let i = 32n; i <= 256n; i++)
if (BigInt(value) === 2n ** i - 1n)
return `2 ** ${i} - 1`;
return String(value);
};
exports.formatInt = formatInt;
const formatArg = (arg, level, config) => {
if (Array.isArray(arg)) {
const { length } = arg;
const wrapLines = length > 5 || arg.some((a) => Array.isArray(a));
const formattedArr = arg
.map((arg, i) => `${wrapLines ? `\n${(0, exports.getIndentLevel)(level + 1)}` : ""}${grey((0, exports.formatArg)(arg, level + 1, config))}${i !== length - 1 || wrapLines ? "," : ""}`)
.join(wrapLines ? "" : " ");
if (!wrapLines)
return `[${formattedArr}]`;
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, config))},`)
.join("");
return `{${formattedObj ? `${formattedObj}\n` : ""}${(0, exports.getIndentLevel)(level)}}`;
}
case "string":
if (config.fullArgs)
return grey(arg);
return grey((0, viem_1.isAddress)(arg, { strict: false })
? (0, exports.formatAddress)(arg)
: (0, viem_1.isHex)(arg)
? (0, exports.formatHex)(arg)
: arg);
case "bigint":
case "number":
if (config.fullArgs)
return grey(String(arg));
return grey((0, exports.formatInt)(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] ??
(trace.input === "0x" ? "receive()" : undefined);
if (!signature)
return trace.input;
const functionName = signature.split("(")[0];
let args;
try {
({ args } = (0, viem_1.decodeFunctionData)({
abi: (0, viem_1.parseAbi)(
// @ts-ignore
[`function ${signature}`]),
data: trace.input,
}));
}
catch {
// Decoding function data can fail for many reasons, including invalid ABI.
}
const value = BigInt(trace.value ?? "0x0");
const formattedArgs = args
?.map((arg) => (0, exports.formatArg)(arg, level, config))
.join(", ");
const error = trace.revertReason || trace.error;
let returnValue = error || trace.output;
try {
if (error == null) {
const functionAbi = viem_1.erc20Abi
.concat(viem_1.erc721Abi)
.concat(viem_1.erc1155Abi)
.concat(viem_1.erc4626Abi)
.concat(viem_1.multicall3Abi)
.find((abi) => abi.type === "function" && abi.name === functionName);
if (functionAbi != null) {
const decodedOutputs = (0, viem_1.decodeAbiParameters)(functionAbi.outputs, trace.output);
returnValue = decodedOutputs
.map((arg) => (0, exports.formatArg)(arg, level, config))
.join(", ");
}
}
}
catch { }
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 ?? ""})${returnValue ? (error ? red : grey)(` -> ${returnValue}`) : ""}`;
};
exports.formatCallSignature = formatCallSignature;
const formatCallLog = (log, level, signatures, config) => {
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, config))
.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 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)}${trace.logs
? `\n${trace.logs.map((log) => (0, exports.formatCallLog)(log, level, signatures, config))}`
: ""}
${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));
}