UNPKG

hardhat

Version:

Hardhat is an extensible developer tool that helps smart contract developers increase productivity by reliably bringing together the tools they want.

221 lines (180 loc) 6.07 kB
import { bytesToHex as bufferToHex } from "@nomicfoundation/ethereumjs-util"; import chalk from "chalk"; import { CallMessageTrace, CreateMessageTrace, isCreateTrace, isEvmStep, isPrecompileTrace, MessageTrace, PrecompileMessageTrace, } from "./message-trace"; import { JumpType } from "./model"; import { isJump, isPush, Opcode } from "./opcodes"; import { SolidityStackTrace, SourceReference, StackTraceEntryType, } from "./solidity-stack-trace"; const MARGIN_SPACE = 6; export function printMessageTrace(trace: MessageTrace, depth = 0) { console.log(""); if (isCreateTrace(trace)) { printCreateTrace(trace, depth); } else if (isPrecompileTrace(trace)) { printPrecompileTrace(trace, depth); } else { printCallTrace(trace, depth); } console.log(""); } export function printCreateTrace(trace: CreateMessageTrace, depth: number) { const margin = "".padStart(depth * MARGIN_SPACE); console.log(`${margin}Create trace`); if (trace.bytecode !== undefined) { console.log( `${margin} deploying contract: ${trace.bytecode.contract.location.file.sourceName}:${trace.bytecode.contract.name}` ); console.log(`${margin} code: ${bufferToHex(trace.code)}`); } else { console.log( `${margin} unrecognized deployment code: ${bufferToHex(trace.code)}` ); } console.log(`${margin} value: ${trace.value.toString(10)}`); if (trace.deployedContract !== undefined) { console.log( `${margin} contract address: ${bufferToHex(trace.deployedContract)}` ); } if (trace.exit.isError()) { console.log(`${margin} error: ${trace.exit.getReason()}`); // The return data is the deployed-bytecode if there was no error, so we don't show it console.log(`${margin} returnData: ${bufferToHex(trace.returnData)}`); } traceSteps(trace, depth); } export function printPrecompileTrace( trace: PrecompileMessageTrace, depth: number ) { const margin = "".padStart(depth * MARGIN_SPACE); console.log(`${margin}Precompile trace`); console.log(`${margin} precompile number: ${trace.precompile}`); console.log(`${margin} value: ${trace.value.toString(10)}`); console.log(`${margin} calldata: ${bufferToHex(trace.calldata)}`); if (trace.exit.isError()) { console.log(`${margin} error: ${trace.exit.getReason()}`); } console.log(`${margin} returnData: ${bufferToHex(trace.returnData)}`); } export function printCallTrace(trace: CallMessageTrace, depth: number) { const margin = "".padStart(depth * MARGIN_SPACE); console.log(`${margin}Call trace`); if (trace.bytecode !== undefined) { console.log( `${margin} calling contract: ${trace.bytecode.contract.location.file.sourceName}:${trace.bytecode.contract.name}` ); } else { console.log( `${margin} unrecognized contract code: ${bufferToHex(trace.code)}` ); console.log(`${margin} contract: ${bufferToHex(trace.address)}`); } console.log(`${margin} value: ${trace.value.toString(10)}`); console.log(`${margin} calldata: ${bufferToHex(trace.calldata)}`); if (trace.exit.isError()) { console.log(`${margin} error: ${trace.exit.getReason()}`); } console.log(`${margin} returnData: ${bufferToHex(trace.returnData)}`); traceSteps(trace, depth); } function traceSteps( trace: CreateMessageTrace | CallMessageTrace, depth: number ) { const margin = "".padStart(depth * MARGIN_SPACE); console.log(`${margin} steps:`); console.log(""); for (const step of trace.steps) { if (isEvmStep(step)) { const pc = step.pc.toString(10).padStart(3, "0").padStart(5); if (trace.bytecode !== undefined) { const inst = trace.bytecode.getInstruction(step.pc); let location: string = ""; if (inst.location !== undefined) { location += inst.location.file.sourceName; const func = inst.location.getContainingFunction(); if (func !== undefined) { location += `:${ func.contract?.name ?? func.location.file.sourceName }:${func.name}`; } location += ` - ${inst.location.offset}:${inst.location.length}`; } if (isJump(inst.opcode)) { const jump = inst.jumpType !== JumpType.NOT_JUMP ? chalk.bold(`(${JumpType[inst.jumpType]})`) : ""; console.log( `${margin} ${pc} ${Opcode[inst.opcode]} ${jump}`.padEnd(50), location ); } else if (isPush(inst.opcode)) { console.log( `${margin} ${pc} ${Opcode[inst.opcode]} ${bufferToHex( inst.pushData! )}`.padEnd(50), location ); } else { console.log( `${margin} ${pc} ${Opcode[inst.opcode]}`.padEnd(50), location ); } } else { console.log(`${margin} ${pc}`); } } else { printMessageTrace(step, depth + 1); } } } function flattenSourceReference(sourceReference?: SourceReference) { if (sourceReference === undefined) { return undefined; } return { ...sourceReference, file: sourceReference.sourceName, }; } export function printStackTrace(trace: SolidityStackTrace) { const withDecodedMessages = trace.map((entry) => entry.type === StackTraceEntryType.REVERT_ERROR ? { ...entry, message: entry.message.decodeError() } : entry ); const withHexAddress = withDecodedMessages.map((entry) => "address" in entry ? { ...entry, address: bufferToHex(entry.address) } : entry ); const withTextualType = withHexAddress.map((entry) => ({ ...entry, type: StackTraceEntryType[entry.type], })); const withFlattenedSourceReferences = withTextualType.map((entry) => ({ ...entry, sourceReference: flattenSourceReference(entry.sourceReference), })); console.log( JSON.stringify( withFlattenedSourceReferences, (key, value) => (typeof value === "bigint" ? value.toString() : value), 2 ) ); }