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]

158 lines (157 loc) 8.63 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.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)); }