UNPKG

hardhat

Version:

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

256 lines (211 loc) 6.87 kB
import { equalsBytes } from "@nomicfoundation/ethereumjs-util"; import { ReturnData } from "../provider/return-data"; import { ExitCode } from "../provider/vm/exit"; import { ErrorInferrer, instructionToCallstackStackTraceEntry, SubmessageData, } from "./error-inferrer"; import { adjustStackTrace, stackTraceMayRequireAdjustments, } from "./mapped-inlined-internal-functions-heuristics"; import { DecodedCallMessageTrace, DecodedCreateMessageTrace, DecodedEvmMessageTrace, EvmMessageTrace, EvmStep, isCreateTrace, isDecodedCallTrace, isDecodedCreateTrace, isEvmStep, isPrecompileTrace, MessageTrace, PrecompileMessageTrace, } from "./message-trace"; import { Instruction, JumpType } from "./model"; import { Opcode } from "./opcodes"; import { SolidityStackTrace, SolidityStackTraceEntry, StackTraceEntryType, } from "./solidity-stack-trace"; export class SolidityTracer { private _errorInferrer = new ErrorInferrer(); public getStackTrace( maybeDecodedMessageTrace: MessageTrace ): SolidityStackTrace { if (!maybeDecodedMessageTrace.exit.isError()) { return []; } if (isPrecompileTrace(maybeDecodedMessageTrace)) { return this._getPrecompileMessageStackTrace(maybeDecodedMessageTrace); } if (isDecodedCreateTrace(maybeDecodedMessageTrace)) { return this._getCreateMessageStackTrace(maybeDecodedMessageTrace); } if (isDecodedCallTrace(maybeDecodedMessageTrace)) { return this._getCallMessageStackTrace(maybeDecodedMessageTrace); } return this._getUnrecognizedMessageStackTrace(maybeDecodedMessageTrace); } private _getCallMessageStackTrace( trace: DecodedCallMessageTrace ): SolidityStackTrace { const inferredError = this._errorInferrer.inferBeforeTracingCallMessage(trace); if (inferredError !== undefined) { return inferredError; } return this._traceEvmExecution(trace); } private _getUnrecognizedMessageStackTrace( trace: EvmMessageTrace ): SolidityStackTrace { const subtrace = this._getLastSubtrace(trace); if (subtrace !== undefined) { // This is not a very exact heuristic, but most of the time it will be right, as solidity // reverts if a call fails, and most contracts are in solidity if ( subtrace.exit.isError() && equalsBytes(trace.returnData, subtrace.returnData) ) { let unrecognizedEntry: SolidityStackTraceEntry; if (isCreateTrace(trace)) { unrecognizedEntry = { type: StackTraceEntryType.UNRECOGNIZED_CREATE_CALLSTACK_ENTRY, }; } else { unrecognizedEntry = { type: StackTraceEntryType.UNRECOGNIZED_CONTRACT_CALLSTACK_ENTRY, address: trace.address, }; } return [unrecognizedEntry, ...this.getStackTrace(subtrace)]; } } if (trace.exit.kind === ExitCode.CODESIZE_EXCEEDS_MAXIMUM) { return [ { type: StackTraceEntryType.CONTRACT_TOO_LARGE_ERROR, }, ]; } const isInvalidOpcodeError = trace.exit.kind === ExitCode.INVALID_OPCODE; if (isCreateTrace(trace)) { return [ { type: StackTraceEntryType.UNRECOGNIZED_CREATE_ERROR, message: new ReturnData(trace.returnData), isInvalidOpcodeError, }, ]; } return [ { type: StackTraceEntryType.UNRECOGNIZED_CONTRACT_ERROR, address: trace.address, message: new ReturnData(trace.returnData), isInvalidOpcodeError, }, ]; } private _getCreateMessageStackTrace( trace: DecodedCreateMessageTrace ): SolidityStackTrace { const inferredError = this._errorInferrer.inferBeforeTracingCreateMessage(trace); if (inferredError !== undefined) { return inferredError; } return this._traceEvmExecution(trace); } private _getPrecompileMessageStackTrace( trace: PrecompileMessageTrace ): SolidityStackTrace { return [ { type: StackTraceEntryType.PRECOMPILE_ERROR, precompile: trace.precompile, }, ]; } private _traceEvmExecution( trace: DecodedEvmMessageTrace ): SolidityStackTrace { const stackTrace = this._rawTraceEvmExecution(trace); if (stackTraceMayRequireAdjustments(stackTrace, trace)) { return adjustStackTrace(stackTrace, trace); } return stackTrace; } private _rawTraceEvmExecution( trace: DecodedEvmMessageTrace ): SolidityStackTrace { const stacktrace: SolidityStackTrace = []; let subtracesSeen = 0; // There was a jump into a function according to the sourcemaps let jumpedIntoFunction = false; const functionJumpdests: Instruction[] = []; let lastSubmessageData: SubmessageData | undefined; for (let stepIndex = 0; stepIndex < trace.steps.length; stepIndex++) { const step = trace.steps[stepIndex]; const nextStep = trace.steps[stepIndex + 1]; if (isEvmStep(step)) { const inst = trace.bytecode.getInstruction(step.pc); if ( inst.jumpType === JumpType.INTO_FUNCTION && nextStep !== undefined ) { const nextEvmStep = nextStep as EvmStep; // A jump can't be followed by a subtrace const nextInst = trace.bytecode.getInstruction(nextEvmStep.pc); if (nextInst !== undefined && nextInst.opcode === Opcode.JUMPDEST) { stacktrace.push( instructionToCallstackStackTraceEntry(trace.bytecode, inst) ); if (nextInst.location !== undefined) { jumpedIntoFunction = true; } functionJumpdests.push(nextInst); } } else if (inst.jumpType === JumpType.OUTOF_FUNCTION) { stacktrace.pop(); functionJumpdests.pop(); } } else { subtracesSeen += 1; // If there are more subtraces, this one didn't terminate the execution if (subtracesSeen < trace.numberOfSubtraces) { continue; } const submessageTrace = this.getStackTrace(step); lastSubmessageData = { messageTrace: step, stepIndex, stacktrace: submessageTrace, }; } } const stacktraceWithInferredError = this._errorInferrer.inferAfterTracing( trace, stacktrace, functionJumpdests, jumpedIntoFunction, lastSubmessageData ); return this._errorInferrer.filterRedundantFrames( stacktraceWithInferredError ); } private _getLastSubtrace(trace: EvmMessageTrace): MessageTrace | undefined { if (trace.numberOfSubtraces < 1) { return undefined; } let i = trace.steps.length - 1; while (isEvmStep(trace.steps[i])) { i -= 1; } return trace.steps[i] as MessageTrace; } }