Skip to content

Commit

Permalink
Expose some helpers for finding interesting steps in a trace
Browse files Browse the repository at this point in the history
  • Loading branch information
cd1m0 committed Sep 30, 2024
1 parent 61cff0b commit 84e05e4
Show file tree
Hide file tree
Showing 3 changed files with 112 additions and 106 deletions.
1 change: 1 addition & 0 deletions src/utils/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,4 @@ export * from "./pp";
export * from "./set";
export * from "./srcmap";
export * from "./test_runner";
export * from "./trace";
106 changes: 106 additions & 0 deletions src/utils/trace.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
import { bytesToHex } from "ethereum-cryptography/utils";
import { FunctionDefinition } from "solc-typed-ast";
import { StepState } from "../debug";
import { FAIL_MSG_DATA, FoundryCheatcodesAddress } from "../debug/foundry_cheatcodes";
import { bigEndianBufToNumber, wordToAddress } from "./misc";
import { flattenStack } from "./pp";

/**
* Find the last step in the non-internal code, before trace step i
*/
export function findLastNonInternalStepBeforeStepI(
trace: StepState[],
i: number
): StepState | undefined {
const stack = flattenStack(trace[i].stack);

for (let j = stack.length - 1; j >= 0; j--) {
if (stack[j].callee instanceof FunctionDefinition) {
if (j === stack.length - 1) {
return trace[i];
}

return trace[stack[j + 1].startStep - 1];
}
}

return undefined;
}

/**
* Find the last step in the non-internal code, that leads to the first revert
*/
export function findLastNonInternalStepBeforeRevert(trace: StepState[]): StepState | undefined {
let i = 0;

for (; i < trace.length; i++) {
if (trace[i].op.opcode === 0xfd) {
break;
}
}

if (i === trace.length) {
return undefined;
}

return findLastNonInternalStepBeforeStepI(trace, i);
}

/**
* Find the last step in the non-internal code, that leads to the last revert
*/
export function findLastNonInternalStepBeforeLastRevert(trace: StepState[]): StepState | undefined {
let i = trace.length - 1;

for (; i >= 0; i--) {
if (trace[i].op.opcode === 0xfd) {
break;
}
}

if (i < 0) {
return undefined;
}

return findLastNonInternalStepBeforeStepI(trace, i);
}

/**
* Find the last step before calling the foundry cheatcode fail()
*/
export function findFirstCallToFail(trace: StepState[]): StepState | undefined {
let i = 0;

for (; i < trace.length; i++) {
// Look for CALL to FoundryCheatcodesAddress with the FAIL_SELECTOR
if (trace[i].op.mnemonic === "CALL") {
const stackLen = trace[i].evmStack.length;
const addr = wordToAddress(trace[i].evmStack[stackLen - 2]);

if (!addr.equals(FoundryCheatcodesAddress)) {
continue;
}

const argOffset = bigEndianBufToNumber(trace[i].evmStack[stackLen - 4]);
const argSize = bigEndianBufToNumber(trace[i].evmStack[stackLen - 5]);

if (argSize < 4) {
continue;
}

const msgData = bytesToHex(trace[i].memory.slice(argOffset, argOffset + argSize));

if (msgData === FAIL_MSG_DATA) {
break;
}
}
}

if (i === trace.length) {
return undefined;
}

//console.error(`Error step: ${i}`);

return trace[i];
}
111 changes: 5 additions & 106 deletions test/unit/debug.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,23 +2,22 @@ import { Address } from "@ethereumjs/util";
import { bytesToHex } from "ethereum-cryptography/utils";
import expect from "expect";
import fse from "fs-extra";
import { assert, DecodedBytecodeSourceMapEntry, forAny, FunctionDefinition } from "solc-typed-ast";
import { assert, DecodedBytecodeSourceMapEntry, forAny } from "solc-typed-ast";
import {
ArtifactManager,
bigEndianBufToNumber,
ContractInfo,
decodeContractState,
FoundryTxResult,
PartialSolcOutput,
SolTxDebugger,
SourceFileInfo,
StepState,
wordToAddress
StepState
} from "../../src";
import { FAIL_MSG_DATA, FoundryCheatcodesAddress } from "../../src/debug/foundry_cheatcodes";
import { getMapKeys, getStorage, topExtFrame } from "../../src/debug/tracers/transformers";
import {
flattenStack,
findFirstCallToFail,
findLastNonInternalStepBeforeLastRevert,
findLastNonInternalStepBeforeRevert,
ppStackTrace,
ResultKind,
TestCase,
Expand All @@ -27,106 +26,6 @@ import {
} from "../../src/utils";
import { lsJson } from "../utils";

/**
* Find the last step in the non-internal code, before trace step i
*/
export function findLastNonInternalStepBeforeStepI(
trace: StepState[],
i: number
): StepState | undefined {
const stack = flattenStack(trace[i].stack);

for (let j = stack.length - 1; j >= 0; j--) {
if (stack[j].callee instanceof FunctionDefinition) {
if (j === stack.length - 1) {
return trace[i];
}

return trace[stack[j + 1].startStep - 1];
}
}

return undefined;
}

/**
* Find the last step in the non-internal code, that leads to the first revert
*/
export function findLastNonInternalStepBeforeRevert(trace: StepState[]): StepState | undefined {
let i = 0;

for (; i < trace.length; i++) {
if (trace[i].op.opcode === 0xfd) {
break;
}
}

if (i === trace.length) {
return undefined;
}

return findLastNonInternalStepBeforeStepI(trace, i);
}

/**
* Find the last step in the non-internal code, that leads to the last revert
*/
export function findLastNonInternalStepBeforeLastRevert(trace: StepState[]): StepState | undefined {
let i = trace.length - 1;

for (; i >= 0; i--) {
if (trace[i].op.opcode === 0xfd) {
break;
}
}

if (i < 0) {
return undefined;
}

return findLastNonInternalStepBeforeStepI(trace, i);
}

/**
* Find the last step before calling the foundry cheatcode fail()
*/
export function findFirstCallToFail(trace: StepState[]): StepState | undefined {
let i = 0;

for (; i < trace.length; i++) {
// Look for CALL to FoundryCheatcodesAddress with the FAIL_SELECTOR
if (trace[i].op.mnemonic === "CALL") {
const stackLen = trace[i].evmStack.length;
const addr = wordToAddress(trace[i].evmStack[stackLen - 2]);

if (!addr.equals(FoundryCheatcodesAddress)) {
continue;
}

const argOffset = bigEndianBufToNumber(trace[i].evmStack[stackLen - 4]);
const argSize = bigEndianBufToNumber(trace[i].evmStack[stackLen - 5]);

if (argSize < 4) {
continue;
}

const msgData = bytesToHex(trace[i].memory.slice(argOffset, argOffset + argSize));

if (msgData === FAIL_MSG_DATA) {
break;
}
}
}

if (i === trace.length) {
return undefined;
}

//console.error(`Error step: ${i}`);

return trace[i];
}

function checkResult(result: FoundryTxResult, step: TestStep): boolean {
switch (step.result.kind) {
case ResultKind.ContractCreated: {
Expand Down

0 comments on commit 84e05e4

Please sign in to comment.