Skip to content

Commit

Permalink
Merge pull request #42 from Consensys/remove_requires
Browse files Browse the repository at this point in the history
Fix brokenness post the bump to ethereumjs
  • Loading branch information
cd1m0 authored Aug 17, 2024
2 parents 6f11c12 + 85c2e89 commit cd3c399
Show file tree
Hide file tree
Showing 7 changed files with 219 additions and 93 deletions.
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

0 comments on commit cd3c399

Please sign in to comment.