Skip to content
Merged
Show file tree
Hide file tree
Changes from 9 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
20 changes: 20 additions & 0 deletions crates/cheatcodes/assets/cheatcodes.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 5 additions & 0 deletions crates/cheatcodes/spec/src/vm.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1474,6 +1474,11 @@ interface Vm {
#[cheatcode(group = Filesystem)]
function getCode(string calldata artifactPath) external view returns (bytes memory creationBytecode);

/// Deploys a contract from an artifact file. Takes in the relative path to the json file or the path to the
/// artifact in the form of <path>:<contract>:<version> where <contract> and <version> parts are optional.
#[cheatcode(group = Filesystem)]
function deployCode(string calldata artifactPath) external returns (address deployedAddress);

/// Gets the deployed bytecode from an artifact file. Takes in the relative path to the json file or the path to the
/// artifact in the form of <path>:<contract>:<version> where <contract> and <version> parts are optional.
#[cheatcode(group = Filesystem)]
Expand Down
24 changes: 18 additions & 6 deletions crates/cheatcodes/src/evm/fork.rs
Original file line number Diff line number Diff line change
Expand Up @@ -127,28 +127,36 @@ impl Cheatcode for selectForkCall {
}

impl Cheatcode for transact_0Call {
fn apply_full<DB: DatabaseExt>(&self, ccx: &mut CheatsCtxt<DB>) -> Result {
fn apply_full_with_executor<DB: DatabaseExt, E: crate::CheatcodesExecutor>(
&self,
ccx: &mut CheatsCtxt<DB>,
executor: &mut E,
) -> Result {
let Self { txHash } = *self;
ccx.ecx.db.transact(
None,
txHash,
&mut ccx.ecx.env,
&mut ccx.ecx.journaled_state,
ccx.state,
&mut executor.get_inspector(ccx.state),
)?;
Ok(Default::default())
}
}

impl Cheatcode for transact_1Call {
fn apply_full<DB: DatabaseExt>(&self, ccx: &mut CheatsCtxt<DB>) -> Result {
fn apply_full_with_executor<DB: DatabaseExt, E: crate::CheatcodesExecutor>(
&self,
ccx: &mut CheatsCtxt<DB>,
executor: &mut E,
) -> Result {
let Self { forkId, txHash } = *self;
ccx.ecx.db.transact(
Some(forkId),
txHash,
&mut ccx.ecx.env,
&mut ccx.ecx.journaled_state,
ccx.state,
&mut executor.get_inspector(ccx.state),
)?;
Ok(Default::default())
}
Expand Down Expand Up @@ -192,7 +200,9 @@ impl Cheatcode for makePersistent_2Call {
impl Cheatcode for makePersistent_3Call {
fn apply_full<DB: DatabaseExt>(&self, ccx: &mut CheatsCtxt<DB>) -> Result {
let Self { accounts } = self;
ccx.ecx.db.extend_persistent_accounts(accounts.iter().copied());
for account in accounts {
ccx.ecx.db.add_persistent_account(*account);
}
Ok(Default::default())
}
}
Expand All @@ -208,7 +218,9 @@ impl Cheatcode for revokePersistent_0Call {
impl Cheatcode for revokePersistent_1Call {
fn apply_full<DB: DatabaseExt>(&self, ccx: &mut CheatsCtxt<DB>) -> Result {
let Self { accounts } = self;
ccx.ecx.db.remove_persistent_accounts(accounts.iter().copied());
for account in accounts {
ccx.ecx.db.remove_persistent_account(account);
}
Ok(Default::default())
}
}
Expand Down
30 changes: 29 additions & 1 deletion crates/cheatcodes/src/fs.rs
Original file line number Diff line number Diff line change
@@ -1,14 +1,16 @@
//! Implementations of [`Filesystem`](spec::Group::Filesystem) cheatcodes.

use super::string::parse;
use crate::{Cheatcode, Cheatcodes, Result, Vm::*};
use crate::{Cheatcode, Cheatcodes, CheatcodesExecutor, CheatsCtxt, Result, Vm::*};
use alloy_dyn_abi::DynSolType;
use alloy_json_abi::ContractObject;
use alloy_primitives::{Bytes, U256};
use alloy_sol_types::SolValue;
use dialoguer::{Input, Password};
use foundry_common::fs;
use foundry_config::fs_permissions::FsAccessKind;
use foundry_evm_core::backend::DatabaseExt;
use revm::interpreter::CreateInputs;
use semver::Version;
use std::{
collections::hash_map::Entry,
Expand Down Expand Up @@ -262,6 +264,32 @@ impl Cheatcode for getDeployedCodeCall {
}
}

impl Cheatcode for deployCodeCall {
fn apply_full_with_executor<DB: DatabaseExt, E: CheatcodesExecutor>(
&self,
ccx: &mut CheatsCtxt<DB>,
executor: &mut E,
) -> Result {
let Self { artifactPath: path } = self;
let bytecode = get_artifact_code(ccx.state, path, false)?;
let output = executor
.exec_create(
CreateInputs {
caller: ccx.caller,
scheme: revm::primitives::CreateScheme::Create,
value: U256::ZERO,
init_code: bytecode,
gas_limit: ccx.gas_limit,
},
ccx.state,
ccx.ecx,
)
.unwrap();

Ok(output.address.unwrap().abi_encode())
}
}

/// Returns the path to the json artifact depending on the input
///
/// Can parse following input formats:
Expand Down
118 changes: 100 additions & 18 deletions crates/cheatcodes/src/inspector.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
//! Cheatcode EVM [Inspector].
//! Cheatcode EVM inspector.

use crate::{
evm::{
Expand All @@ -24,6 +24,7 @@ use foundry_evm_core::{
abi::Vm::stopExpectSafeMemoryCall,
backend::{DatabaseExt, RevertDiagnostic},
constants::{CHEATCODE_ADDRESS, HARDHAT_CONSOLE_ADDRESS},
utils::new_evm_with_existing_context,
InspectorExt,
};
use itertools::Itertools;
Expand All @@ -32,8 +33,8 @@ use revm::{
opcode, CallInputs, CallOutcome, CallScheme, CreateInputs, CreateOutcome, Gas,
InstructionResult, Interpreter, InterpreterAction, InterpreterResult,
},
primitives::{BlockEnv, CreateScheme, TransactTo},
EvmContext, InnerEvmContext, Inspector,
primitives::{BlockEnv, CreateScheme, EVMError, TransactTo},
EvmContext, InnerEvmContext,
};
use rustc_hash::FxHashMap;
use serde_json::Value;
Expand All @@ -45,6 +46,62 @@ use std::{
path::PathBuf,
sync::Arc,
};
pub trait CheatcodesExecutor {
Comment thread
klkvr marked this conversation as resolved.
Comment thread
klkvr marked this conversation as resolved.
fn get_inspector<'a, DB: DatabaseExt>(
&'a mut self,
cheats: &'a mut Cheatcodes,
) -> impl InspectorExt<DB> + 'a;

fn exec_create<DB: DatabaseExt>(
&mut self,
inputs: CreateInputs,
cheats: &mut Cheatcodes,
ecx: &mut InnerEvmContext<DB>,
) -> Result<CreateOutcome, EVMError<DB::Error>> {
Comment thread
DaniPopes marked this conversation as resolved.
let inspector = self.get_inspector(cheats);
let error = std::mem::replace(&mut ecx.error, Ok(()));
let l1_block_info = std::mem::take(&mut ecx.l1_block_info);

let inner = revm::InnerEvmContext {
env: ecx.env.clone(),
journaled_state: std::mem::replace(
&mut ecx.journaled_state,
revm::JournaledState::new(Default::default(), Default::default()),
),
db: &mut ecx.db as &mut dyn DatabaseExt,
error,
l1_block_info,
};

let mut evm = new_evm_with_existing_context(inner, inspector);

evm.context.evm.inner.journaled_state.depth += 1;

let first_frame_or_result =
evm.handler.execution().create(&mut evm.context, Box::new(inputs))?;

let mut result = match first_frame_or_result {
revm::FrameOrResult::Frame(first_frame) => evm.run_the_loop(first_frame)?,
revm::FrameOrResult::Result(result) => result,
};

evm.handler.execution().last_frame_return(&mut evm.context, &mut result)?;

let outcome = match result {
revm::FrameResult::Call(_) | revm::FrameResult::EOFCreate(_) => unreachable!(),
revm::FrameResult::Create(create) => create,
};

evm.context.evm.inner.journaled_state.depth -= 1;

ecx.journaled_state = evm.context.evm.inner.journaled_state;
ecx.env = evm.context.evm.inner.env;
ecx.l1_block_info = evm.context.evm.inner.l1_block_info;
ecx.error = evm.context.evm.inner.error;

Ok(outcome)
}
}

macro_rules! try_or_return {
($e:expr) => {
Expand Down Expand Up @@ -255,10 +312,11 @@ impl Cheatcodes {
}

/// Decodes the input data and applies the cheatcode.
fn apply_cheatcode<DB: DatabaseExt>(
fn apply_cheatcode<DB: DatabaseExt, E: CheatcodesExecutor>(
&mut self,
ecx: &mut EvmContext<DB>,
call: &CallInputs,
executor: &mut E,
) -> Result {
// decode the cheatcode call
let decoded = Vm::VmCalls::abi_decode(&call.input, false).map_err(|e| {
Expand All @@ -285,8 +343,10 @@ impl Cheatcodes {
state: self,
ecx: &mut ecx.inner,
precompiles: &mut ecx.precompiles,
gas_limit: call.gas_limit,
caller,
},
executor,
)
}

Expand Down Expand Up @@ -348,9 +408,13 @@ impl Cheatcodes {
}
}

impl<DB: DatabaseExt> Inspector<DB> for Cheatcodes {
impl Cheatcodes {
Comment thread
DaniPopes marked this conversation as resolved.
Outdated
#[inline]
fn initialize_interp(&mut self, _interpreter: &mut Interpreter, ecx: &mut EvmContext<DB>) {
pub fn initialize_interp<DB: DatabaseExt>(
&mut self,
_: &mut Interpreter,
ecx: &mut EvmContext<DB>,
) {
// When the first interpreter is initialized we've circumvented the balance and gas checks,
// so we apply our actual block data with the correct fees and all.
if let Some(block) = self.block.take() {
Expand All @@ -362,7 +426,11 @@ impl<DB: DatabaseExt> Inspector<DB> for Cheatcodes {
}

#[inline]
fn step(&mut self, interpreter: &mut Interpreter, ecx: &mut EvmContext<DB>) {
pub fn step<DB: DatabaseExt>(
&mut self,
interpreter: &mut Interpreter,
ecx: &mut EvmContext<DB>,
) {
self.pc = interpreter.program_counter();

// `pauseGasMetering`: reset interpreter gas.
Expand Down Expand Up @@ -391,7 +459,14 @@ impl<DB: DatabaseExt> Inspector<DB> for Cheatcodes {
}
}

fn log(&mut self, _context: &mut EvmContext<DB>, log: &Log) {
pub fn step_end<DB: DatabaseExt>(
&mut self,
_interpreter: &mut Interpreter,
_context: &mut EvmContext<DB>,
) {
}

pub fn log<DB: DatabaseExt>(&mut self, _context: &mut EvmContext<DB>, log: &Log) {
if !self.expected_emits.is_empty() {
expect::handle_expect_emit(self, log);
}
Expand All @@ -406,7 +481,12 @@ impl<DB: DatabaseExt> Inspector<DB> for Cheatcodes {
}
}

fn call(&mut self, ecx: &mut EvmContext<DB>, call: &mut CallInputs) -> Option<CallOutcome> {
pub fn call<DB: DatabaseExt>(
Comment thread
DaniPopes marked this conversation as resolved.
Outdated
&mut self,
ecx: &mut EvmContext<DB>,
call: &mut CallInputs,
executor: &mut impl CheatcodesExecutor,
) -> Option<CallOutcome> {
let gas = Gas::new(call.gas_limit);

// At the root call to test function or script `run()`/`setUp()` functions, we are
Expand Down Expand Up @@ -436,7 +516,7 @@ impl<DB: DatabaseExt> Inspector<DB> for Cheatcodes {
}

if call.target_address == CHEATCODE_ADDRESS {
return match self.apply_cheatcode(ecx, call) {
return match self.apply_cheatcode(ecx, call, executor) {
Ok(retdata) => Some(CallOutcome {
result: InterpreterResult {
result: InstructionResult::Return,
Expand Down Expand Up @@ -659,7 +739,7 @@ impl<DB: DatabaseExt> Inspector<DB> for Cheatcodes {
None
}

fn call_end(
pub fn call_end<DB: DatabaseExt>(
&mut self,
ecx: &mut EvmContext<DB>,
call: &CallInputs,
Expand Down Expand Up @@ -938,7 +1018,7 @@ impl<DB: DatabaseExt> Inspector<DB> for Cheatcodes {
outcome
}

fn create(
pub fn create<DB: DatabaseExt>(
&mut self,
ecx: &mut EvmContext<DB>,
call: &mut CreateInputs,
Expand Down Expand Up @@ -1044,7 +1124,7 @@ impl<DB: DatabaseExt> Inspector<DB> for Cheatcodes {
None
}

fn create_end(
pub fn create_end<DB: DatabaseExt>(
&mut self,
ecx: &mut EvmContext<DB>,
_call: &CreateInputs,
Expand Down Expand Up @@ -1154,10 +1234,8 @@ impl<DB: DatabaseExt> Inspector<DB> for Cheatcodes {

outcome
}
}

impl<DB: DatabaseExt> InspectorExt<DB> for Cheatcodes {
fn should_use_create2_factory(
pub fn should_use_create2_factory<DB: DatabaseExt>(
&mut self,
ecx: &mut EvmContext<DB>,
inputs: &mut CreateInputs,
Expand Down Expand Up @@ -1624,11 +1702,15 @@ fn check_if_fixed_gas_limit<DB: DatabaseExt>(
}

/// Dispatches the cheatcode call to the appropriate function.
fn apply_dispatch<DB: DatabaseExt>(calls: &Vm::VmCalls, ccx: &mut CheatsCtxt<DB>) -> Result {
fn apply_dispatch<DB: DatabaseExt, E: CheatcodesExecutor>(
calls: &Vm::VmCalls,
ccx: &mut CheatsCtxt<DB>,
executor: &mut E,
) -> Result {
macro_rules! match_ {
($($variant:ident),*) => {
match calls {
$(Vm::VmCalls::$variant(cheat) => crate::Cheatcode::apply_traced(cheat, ccx),)*
$(Vm::VmCalls::$variant(cheat) => crate::Cheatcode::apply_traced(cheat, ccx, executor),)*
}
};
}
Expand Down
Loading