Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix brokenness post the bump to ethereumjs #42

Merged
merged 1 commit into from
Aug 17, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
118 changes: 49 additions & 69 deletions src/debug/foundry_cheatcodes.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { EVM, EVMInterface, ExecResult, PrecompileInput } from "@ethereumjs/evm";
import { EVM, EvmError, ExecResult, Message, PrecompileInput } from "@ethereumjs/evm";
import {
Account,
Address,
Expand All @@ -8,60 +8,43 @@ import {
} from "@ethereumjs/util";
import { keccak256 } from "ethereum-cryptography/keccak.js";
import { bytesToHex, concatBytes, equalsBytes, utf8ToBytes } from "ethereum-cryptography/utils";
import EventEmitter from "events";
import { bigEndianBufToBigint, bigIntToBuf } from "../utils";

const EVM_MOD = require("@ethereumjs/evm/dist/cjs/evm");
const EvmErrorResult = EVM_MOD.EvmErrorResult;

const EXCEPTION_MOD = require("@ethereumjs/evm/dist/cjs/exceptions");
const ERROR = EXCEPTION_MOD.ERROR;
const EvmError = EXCEPTION_MOD.EvmError;

/// require("@ethereumjs/evm/dist/cjs/interpreter").Env
type Env = any;
/// require("@ethereumjs/evm/dist/cjs/interpreter").InterpreterOpts
type InterpreterOpts = any;
/// require("@ethereumjs/evm/dist/cjs/interpreter").InterpreterResult
type InterpreterResult = any;

const INTERPRETER_MOD = require("@ethereumjs/evm/dist/cjs/interpreter");
const Interpreter = INTERPRETER_MOD.Interpreter;
import { ERROR, EvmErrorResult } from "../utils/ethereumjs_internal/exceptions";

/// require("@ethereumjs/evm/dist/cjs/precompiles").PrecompileFunc
type PrecompileFunc = any;
/// require("@ethereumjs/evm/dist/cjs/precompiles").RunState
type RunState = any;
/*
* Hotpatch Interpreter.run so we can keep track of the runtime relationships between EEIs.
* We use this to track when one call context is a child of another, which helps us scope pranks
*/
const oldRun = Interpreter.prototype.run;
/**
* Each test/cases/debug session is associated with a unique VM. And there are
* multiple interpreter instances per VM. We keep 1 event emitter per VM, so
* that after we are done working with some VM, we don't unnecessarily invoke
* its callbacks.
*/
export const interpRunListeners = new Map<EVM, EventEmitter>();
export const foundryCtxMap = new Map<EVM, FoundryContext>();

const oldRunMsgFun = (EVM.prototype as any).runInterpreter;

Interpreter.prototype.run = async function (
code: Uint8Array,
opts?: InterpreterOpts
): Promise<InterpreterResult> {
const vm = this._evm;
const emitter = interpRunListeners.get(vm);
(EVM.prototype as any).runInterpreter = async function hookedRunInterpreter(
message: Message,
opts: any
): Promise<ExecResult> {
const ctx = foundryCtxMap.get(this);

if (emitter) emitter.emit("beforeInterpRun", this);
const res = oldRun.bind(this)(code, opts);
if (ctx) {
ctx.beforeInterpRunCB(message);
}

const wrappedPromise = res.then((interpRes: InterpreterResult) => {
if (emitter) emitter.emit("afterInterpRun", this);
const res = oldRunMsgFun.bind(this)(message, opts);

return interpRes;
const wrappedRes = res.then((res: ExecResult) => {
if (ctx) {
ctx.afterInterpRunCB();
}
return res;
});

return wrappedPromise;
return wrappedRes;
};

const { secp256k1 } = require("ethereum-cryptography/secp256k1");
Expand Down Expand Up @@ -102,6 +85,12 @@ export const FAIL_MSG_DATA = bytesToHex(
)
);

interface PrankCallFrame extends Message {
expectedRevertDesc: RevertMatch | undefined;
pendingPrank: FoundryPrank | undefined;
pranks: FoundryPrank[] | undefined;
}

export interface FoundryPrank {
sender: Address;
origin: Address | undefined;
Expand Down Expand Up @@ -188,15 +177,15 @@ export class FoundryContext {
return undefined;
}

(this.getEnv() as any).expectedRevertDesc = match;
this.getEnv().expectedRevertDesc = match;
}

public getExpectedRevert(): RevertMatch {
if (this.envStack.length === 0) {
return undefined;
}

return (this.getEnv() as any).expectedRevertDesc;
return this.getEnv().expectedRevertDesc;
}

/**
Expand All @@ -205,10 +194,10 @@ export class FoundryContext {
* objects, as there is a unique EEI object for each external call in the
* trace, and pranks are scoped to external calls.
*/
private envStack: Env[] = [];
private envStack: PrankCallFrame[] = [];

// Get the current (topmost) EEI object
public getEnv(): Env {
public getEnv(): PrankCallFrame {
return this.envStack[this.envStack.length - 1];
}

Expand All @@ -218,7 +207,7 @@ export class FoundryContext {
return undefined;
}

return (this.getEnv() as any).pendingPrank;
return this.getEnv().pendingPrank;
}

// Set the pending prank for the current call frame
Expand All @@ -227,16 +216,16 @@ export class FoundryContext {
return undefined;
}

(this.getEnv() as any).pendingPrank = prank;
this.getEnv().pendingPrank = prank;
}

/**
* Get the set of pranks attached to the callframe related to eei.
* Note that for flexibility we allow more than 1 prank, but in practice
* foundry restricts this to only 1 prank at a time.
*/
private getPranks(eei: Env): FoundryPrank[] {
const pranks = (eei as any).pranks;
private getPranks(frame: PrankCallFrame): FoundryPrank[] {
const pranks = frame.pranks;

if (pranks === undefined) {
return [];
Expand All @@ -248,20 +237,20 @@ export class FoundryContext {
/**
* Add a prank to a call frame identified by `eei`.
*/
public addPrank(eei: Env, prank: FoundryPrank): void {
const pranks = this.getPranks(eei);
public addPrank(frame: PrankCallFrame, prank: FoundryPrank): void {
const pranks = this.getPranks(frame);
pranks.push(prank);

(eei as any).pranks = pranks;
frame.pranks = pranks;
}

/**
* Clear all active and pending pranks.
*/
public clearPranks(): void {
for (let i = this.envStack.length - 1; i >= 0; i--) {
(this.envStack[i] as any).pranks = undefined;
(this.envStack[i] as any).pendingPrank = undefined;
this.envStack[i].pranks = undefined;
this.envStack[i].pendingPrank = undefined;
}
}

Expand Down Expand Up @@ -303,19 +292,18 @@ export class FoundryContext {
* Callback from the hooks in the interpreter to keep track of the
* eei stack
*/
beforeInterpRunCB(interp: typeof Interpreter): void {
const env = interp._env;
beforeInterpRunCB(msg: Message): void {
const pendingPrank = this.getPendingPrank();

if (pendingPrank) {
this.addPrank(env, pendingPrank);
this.addPrank(msg as PrankCallFrame, pendingPrank);

if (pendingPrank.once) {
this.setPendingPrank(undefined);
}
}

this.envStack.push(env);
this.envStack.push(msg as PrankCallFrame);
}

/**
Expand All @@ -327,14 +315,6 @@ export class FoundryContext {
}
}

export function getFoundryCtx(evm: EVMInterface): FoundryContext {
return (evm as any)._foundryCtx;
}

export function setFoundryCtx(evm: EVMInterface, ctx: FoundryContext): void {
(evm as any)._foundryCtx = ctx;
}

export function makeFoundryCheatcodePrecompile(): [PrecompileFunc, FoundryContext] {
const ctx = new FoundryContext();

Expand Down Expand Up @@ -464,7 +444,7 @@ export function makeFoundryCheatcodePrecompile(): [PrecompileFunc, FoundryContex
if (equalsBytes(selector, PRANK_SELECTOR01)) {
// Foundry doesn't allow multiple concurrent pranks
if (ctx.getPendingPrank() !== undefined) {
return EvmErrorResult(new EvmError(ERROR.REVERT), 0n);
return EvmErrorResult(new EvmError(ERROR.REVERT as any), 0n);
}

ctx.setPendingPrank({
Expand All @@ -483,7 +463,7 @@ export function makeFoundryCheatcodePrecompile(): [PrecompileFunc, FoundryContex
if (equalsBytes(selector, PRANK_SELECTOR02)) {
// Foundry doesn't allow multiple concurrent pranks
if (ctx.getPendingPrank() !== undefined) {
return EvmErrorResult(new EvmError(ERROR.REVERT), 0n);
return EvmErrorResult(new EvmError(ERROR.REVERT as any), 0n);
}

ctx.setPendingPrank({
Expand All @@ -507,7 +487,7 @@ export function makeFoundryCheatcodePrecompile(): [PrecompileFunc, FoundryContex
if (equalsBytes(selector, START_PRANK_SELECTOR01)) {
// Foundry doesn't allow multiple concurrent pranks
if (ctx.getPendingPrank() !== undefined) {
return EvmErrorResult(new EvmError(ERROR.REVERT), 0n);
return EvmErrorResult(new EvmError(ERROR.REVERT as any), 0n);
}

ctx.setPendingPrank({
Expand All @@ -527,7 +507,7 @@ export function makeFoundryCheatcodePrecompile(): [PrecompileFunc, FoundryContex
if (equalsBytes(selector, START_PRANK_SELECTOR02)) {
// Foundry doesn't allow multiple concurrent pranks
if (ctx.getPendingPrank() !== undefined) {
return EvmErrorResult(new EvmError(ERROR.REVERT), 0n);
return EvmErrorResult(new EvmError(ERROR.REVERT as any), 0n);
}

ctx.setPendingPrank({
Expand Down Expand Up @@ -572,7 +552,7 @@ export function makeFoundryCheatcodePrecompile(): [PrecompileFunc, FoundryContex
if (equalsBytes(selector, EXPECT_REVERT_SELECTOR02)) {
//console.error(`vm.expectRevert(bytes4);`);
if (input.data.length < 8) {
return EvmErrorResult(new EvmError(ERROR.REVERT), 0n);
return EvmErrorResult(new EvmError(ERROR.REVERT as any), 0n);
}

const selector = input.data.slice(4, 8);
Expand All @@ -586,13 +566,13 @@ export function makeFoundryCheatcodePrecompile(): [PrecompileFunc, FoundryContex

if (equalsBytes(selector, EXPECT_REVERT_SELECTOR03)) {
if (input.data.length < 68) {
return EvmErrorResult(new EvmError(ERROR.REVERT), 0n);
return EvmErrorResult(new EvmError(ERROR.REVERT as any), 0n);
}

const len = Number(bigEndianBufToBigint(input.data.slice(36, 68)));

if (input.data.length < 68 + len) {
return EvmErrorResult(new EvmError(ERROR.REVERT), 0n);
return EvmErrorResult(new EvmError(ERROR.REVERT as any), 0n);
}

const bytes = input.data.slice(68, 68 + len);
Expand Down
18 changes: 6 additions & 12 deletions src/debug/opcode_interposing.ts
Original file line number Diff line number Diff line change
@@ -1,21 +1,15 @@
import { Common } from "@ethereumjs/common";
import { Address, bigIntToBytes, setLengthLeft } from "@ethereumjs/util";
import { bigEndianBufToBigint } from "../utils";
import { createAddressFromStackBigInt, trap, writeCallOutput } from "../utils/ethereumjs_internal";
import { ERROR } from "../utils/ethereumjs_internal/exceptions";
import {
FoundryCheatcodesAddress,
FoundryContext,
RevertMatch,
returnStateMatchesRevert
} from "./foundry_cheatcodes";

const EXCEPTION_MOD = require("@ethereumjs/evm/dist/cjs/exceptions");
const ERROR = EXCEPTION_MOD.ERROR;

const OPCODES_MOD = require("@ethereumjs/evm/dist/cjs/opcodes");
const addresstoBytes = OPCODES_MOD.addresstoBytes;
const trap = OPCODES_MOD.trap;
const writeCallOutput = OPCODES_MOD.writeCallOutput;

/// require(@ethereumjs/evm/dist/cjs/types).AddOpcode
type AddOpcode = any;
/// require(@ethereumjs/evm/dist/cjs/types).Opcode
Expand Down Expand Up @@ -167,7 +161,7 @@ export function foundryInterposedOps(opcodes: any, foundryCtx: FoundryContext):
const [, toAddr, value, inOffset, inLength, outOffset, outLength] =
runState.stack.popN(7);

const toAddress = new Address(addresstoBytes(toAddr));
const toAddress = createAddressFromStackBigInt(toAddr);

const expectedRevert = getExpectedRevert(foundryCtx, toAddress);

Expand Down Expand Up @@ -202,7 +196,7 @@ export function foundryInterposedOps(opcodes: any, foundryCtx: FoundryContext):
async function (runState: RunState) {
const [, toAddr, value, inOffset, inLength, outOffset, outLength] =
runState.stack.popN(7);
const toAddress = new Address(addresstoBytes(toAddr));
const toAddress = createAddressFromStackBigInt(toAddr);
const expectedRevert = getExpectedRevert(foundryCtx, toAddress);

const gasLimit = runState.messageGasLimit!;
Expand All @@ -224,7 +218,7 @@ export function foundryInterposedOps(opcodes: any, foundryCtx: FoundryContext):
async function (runState: RunState) {
const value = runState.interpreter.getCallValue();
const [, toAddr, inOffset, inLength, outOffset, outLength] = runState.stack.popN(6);
const toAddress = new Address(addresstoBytes(toAddr));
const toAddress = createAddressFromStackBigInt(toAddr);
const expectedRevert = getExpectedRevert(foundryCtx, toAddress);

let data = new Uint8Array(0);
Expand All @@ -251,7 +245,7 @@ export function foundryInterposedOps(opcodes: any, foundryCtx: FoundryContext):
async function (runState: RunState) {
const value = BigInt(0);
const [, toAddr, inOffset, inLength, outOffset, outLength] = runState.stack.popN(6);
const toAddress = new Address(addresstoBytes(toAddr));
const toAddress = createAddressFromStackBigInt(toAddr);
const expectedRevert = getExpectedRevert(foundryCtx, toAddress);

const gasLimit = runState.messageGasLimit!;
Expand Down
15 changes: 4 additions & 11 deletions src/debug/sol_debugger.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@ import { Address, setLengthLeft } from "@ethereumjs/util";
import { RunTxResult, VM } from "@ethereumjs/vm";
import { bytesToHex, hexToBytes } from "ethereum-cryptography/utils";
import { ASTNode, FunctionDefinition, TypeNode, VariableDeclaration, assert } from "solc-typed-ast";
import { EventEmitter } from "stream";
import {
DecodedBytecodeSourceMapEntry,
HexString,
Expand All @@ -27,9 +26,8 @@ import { ContractInfo, IArtifactManager, getOffsetSrc } from "./artifact_manager
import { isCalldataType2Slots } from "./decoding";
import {
FoundryCheatcodesAddress,
interpRunListeners,
makeFoundryCheatcodePrecompile,
setFoundryCtx
foundryCtxMap,
makeFoundryCheatcodePrecompile
} from "./foundry_cheatcodes";
import { foundryInterposedOps } from "./opcode_interposing";
import { OPCODES, changesMemory, createsContract, getOpInfo, increasesDepth } from "./opcodes";
Expand Down Expand Up @@ -492,12 +490,7 @@ export class SolTxDebugger {
};

const res = await EVM.create(optsCopy);

const emitter = new EventEmitter();
emitter.on("beforeInterpRun", foundryCtx.beforeInterpRunCB.bind(foundryCtx));
emitter.on("afterInterpRun", foundryCtx.afterInterpRunCB.bind(foundryCtx));
interpRunListeners.set(res, emitter);
setFoundryCtx(res, foundryCtx);
foundryCtxMap.set(res, foundryCtx);
return res;
}

Expand All @@ -512,7 +505,7 @@ export class SolTxDebugger {
const evm = vmToEVMMap.get(vm);

if (evm) {
interpRunListeners.delete(evm);
foundryCtxMap.delete(evm);
}

vmToEVMMap.delete(vm);
Expand Down
Loading
Loading