UNPKG

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
"use strict"; 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)); }