Skip to content

Commit 63072be

Browse files
authored
refactor: inject call to CREATE2 factory through custom revm handler (foundry-rs#7653)
* wip * wip * add docs * clippy * update doc * simplify logic * review fixes * doc * review fixes * fix
1 parent 19871fc commit 63072be

File tree

9 files changed

+231
-158
lines changed

9 files changed

+231
-158
lines changed

crates/anvil/src/eth/backend/mem/inspector.rs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,13 +6,14 @@ use foundry_evm::{
66
call_inspectors,
77
decode::decode_console_logs,
88
inspectors::{LogCollector, TracingInspector},
9-
revm,
109
revm::{
10+
self,
1111
interpreter::{CallInputs, CallOutcome, CreateInputs, CreateOutcome, Interpreter},
1212
primitives::U256,
1313
EvmContext,
1414
},
1515
traces::TracingInspectorConfig,
16+
InspectorExt,
1617
};
1718

1819
/// The [`revm::Inspector`] used when transacting in the evm
@@ -136,6 +137,8 @@ impl<DB: Database> revm::Inspector<DB> for Inspector {
136137
}
137138
}
138139

140+
impl<DB: Database> InspectorExt<DB> for Inspector {}
141+
139142
/// Prints all the logs
140143
#[inline]
141144
pub fn print_logs(logs: &[Log]) {

crates/anvil/src/eth/backend/mem/mod.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,7 @@ use foundry_evm::{
7676
},
7777
},
7878
utils::new_evm_with_inspector_ref,
79+
InspectorExt,
7980
};
8081
use futures::channel::mpsc::{unbounded, UnboundedSender};
8182
use parking_lot::{Mutex, RwLock};
@@ -819,7 +820,7 @@ impl Backend {
819820
) -> revm::Evm<'_, I, WrapDatabaseRef<DB>>
820821
where
821822
DB: revm::DatabaseRef,
822-
I: revm::Inspector<WrapDatabaseRef<DB>>,
823+
I: InspectorExt<WrapDatabaseRef<DB>>,
823824
{
824825
let mut evm = new_evm_with_inspector_ref(db, env, inspector);
825826
if let Some(ref factory) = self.precompile_factory {

crates/cheatcodes/src/inspector.rs

Lines changed: 33 additions & 138 deletions
Original file line numberDiff line numberDiff line change
@@ -21,8 +21,9 @@ use alloy_sol_types::{SolInterface, SolValue};
2121
use foundry_common::{evm::Breakpoints, provider::alloy::RpcUrl, SELECTOR_LEN};
2222
use foundry_evm_core::{
2323
abi::Vm::stopExpectSafeMemoryCall,
24-
backend::{DatabaseError, DatabaseExt, RevertDiagnostic},
25-
constants::{CHEATCODE_ADDRESS, DEFAULT_CREATE2_DEPLOYER, HARDHAT_CONSOLE_ADDRESS},
24+
backend::{DatabaseExt, RevertDiagnostic},
25+
constants::{CHEATCODE_ADDRESS, HARDHAT_CONSOLE_ADDRESS},
26+
InspectorExt,
2627
};
2728
use itertools::Itertools;
2829
use revm::{
@@ -1327,22 +1328,19 @@ impl<DB: DatabaseExt> Inspector<DB> for Cheatcodes {
13271328
ecx.env.tx.caller = broadcast.new_origin;
13281329

13291330
if ecx.journaled_state.depth() == broadcast.depth {
1330-
let (bytecode, to, nonce) = process_broadcast_create(
1331-
broadcast.new_origin,
1332-
call.init_code.clone(),
1333-
ecx,
1334-
call,
1335-
);
1331+
call.caller = broadcast.new_origin;
13361332
let is_fixed_gas_limit = check_if_fixed_gas_limit(ecx, call.gas_limit);
13371333

1334+
let account = &ecx.journaled_state.state()[&broadcast.new_origin];
1335+
13381336
self.broadcastable_transactions.push_back(BroadcastableTransaction {
13391337
rpc: ecx.db.active_fork_url(),
13401338
transaction: TransactionRequest {
13411339
from: Some(broadcast.new_origin),
1342-
to,
1340+
to: None,
13431341
value: Some(call.value),
1344-
input: TransactionInput::new(bytecode),
1345-
nonce: Some(nonce),
1342+
input: TransactionInput::new(call.init_code.clone()),
1343+
nonce: Some(account.info.nonce),
13461344
gas: if is_fixed_gas_limit {
13471345
Some(call.gas_limit as u128)
13481346
} else {
@@ -1351,6 +1349,7 @@ impl<DB: DatabaseExt> Inspector<DB> for Cheatcodes {
13511349
..Default::default()
13521350
},
13531351
});
1352+
13541353
let kind = match call.scheme {
13551354
CreateScheme::Create => "create",
13561355
CreateScheme::Create2 { .. } => "create2",
@@ -1360,29 +1359,6 @@ impl<DB: DatabaseExt> Inspector<DB> for Cheatcodes {
13601359
}
13611360
}
13621361

1363-
// Apply the Create2 deployer
1364-
if self.broadcast.is_some() || self.config.always_use_create_2_factory {
1365-
match apply_create2_deployer(
1366-
ecx,
1367-
call,
1368-
self.prank.as_ref(),
1369-
self.broadcast.as_ref(),
1370-
self.recorded_account_diffs_stack.as_mut(),
1371-
) {
1372-
Ok(_) => {}
1373-
Err(err) => {
1374-
return Some(CreateOutcome {
1375-
result: InterpreterResult {
1376-
result: InstructionResult::Revert,
1377-
output: Error::encode(err),
1378-
gas,
1379-
},
1380-
address: None,
1381-
})
1382-
}
1383-
};
1384-
}
1385-
13861362
// allow cheatcodes from the address of the new contract
13871363
// Compute the address *after* any possible broadcast updates, so it's based on the updated
13881364
// call inputs
@@ -1526,6 +1502,29 @@ impl<DB: DatabaseExt> Inspector<DB> for Cheatcodes {
15261502
}
15271503
}
15281504

1505+
impl<DB: DatabaseExt> InspectorExt<DB> for Cheatcodes {
1506+
fn should_use_create2_factory(
1507+
&mut self,
1508+
ecx: &mut EvmContext<DB>,
1509+
inputs: &mut CreateInputs,
1510+
) -> bool {
1511+
if let CreateScheme::Create2 { .. } = inputs.scheme {
1512+
let target_depth = if let Some(prank) = &self.prank {
1513+
prank.depth
1514+
} else if let Some(broadcast) = &self.broadcast {
1515+
broadcast.depth
1516+
} else {
1517+
1
1518+
};
1519+
1520+
ecx.journaled_state.depth() == target_depth &&
1521+
(self.broadcast.is_some() || self.config.always_use_create_2_factory)
1522+
} else {
1523+
false
1524+
}
1525+
}
1526+
}
1527+
15291528
/// Helper that expands memory, stores a revert string pertaining to a disallowed memory write,
15301529
/// and sets the return range to the revert string's location in memory.
15311530
///
@@ -1554,110 +1553,6 @@ fn disallowed_mem_write(
15541553
};
15551554
}
15561555

1557-
/// Applies the default CREATE2 deployer for contract creation.
1558-
///
1559-
/// This function is invoked during the contract creation process and updates the caller of the
1560-
/// contract creation transaction to be the `DEFAULT_CREATE2_DEPLOYER` if the `CreateScheme` is
1561-
/// `Create2` and the current execution depth matches the depth at which the `prank` or `broadcast`
1562-
/// was started, or the default depth of 1 if no prank or broadcast is currently active.
1563-
///
1564-
/// Returns a `DatabaseError::MissingCreate2Deployer` if the `DEFAULT_CREATE2_DEPLOYER` account is
1565-
/// not found or if it does not have any associated bytecode.
1566-
fn apply_create2_deployer<DB: DatabaseExt>(
1567-
ecx: &mut InnerEvmContext<DB>,
1568-
call: &mut CreateInputs,
1569-
prank: Option<&Prank>,
1570-
broadcast: Option<&Broadcast>,
1571-
diffs_stack: Option<&mut Vec<Vec<AccountAccess>>>,
1572-
) -> Result<(), DB::Error> {
1573-
if let CreateScheme::Create2 { salt } = call.scheme {
1574-
let mut base_depth = 1;
1575-
if let Some(prank) = &prank {
1576-
base_depth = prank.depth;
1577-
} else if let Some(broadcast) = &broadcast {
1578-
base_depth = broadcast.depth;
1579-
}
1580-
1581-
// If the create scheme is Create2 and the depth equals the broadcast/prank/default
1582-
// depth, then use the default create2 factory as the deployer
1583-
if ecx.journaled_state.depth() == base_depth {
1584-
// Record the call to the create2 factory in the state diff
1585-
if let Some(recorded_account_diffs_stack) = diffs_stack {
1586-
let calldata = [&salt.to_be_bytes::<32>()[..], &call.init_code[..]].concat();
1587-
recorded_account_diffs_stack.push(vec![AccountAccess {
1588-
chainInfo: crate::Vm::ChainInfo {
1589-
forkId: ecx.db.active_fork_id().unwrap_or_default(),
1590-
chainId: U256::from(ecx.env.cfg.chain_id),
1591-
},
1592-
accessor: call.caller,
1593-
account: DEFAULT_CREATE2_DEPLOYER,
1594-
kind: crate::Vm::AccountAccessKind::Call,
1595-
initialized: true,
1596-
oldBalance: U256::ZERO, // updated on create_end
1597-
newBalance: U256::ZERO, // updated on create_end
1598-
value: call.value,
1599-
data: calldata.into(),
1600-
reverted: false,
1601-
deployedCode: Bytes::new(), // updated on create_end
1602-
storageAccesses: vec![], // updated on create_end
1603-
depth: ecx.journaled_state.depth(),
1604-
}])
1605-
}
1606-
1607-
// Sanity checks for our CREATE2 deployer
1608-
// TODO: use ecx.load_account
1609-
let info =
1610-
&ecx.journaled_state.load_account(DEFAULT_CREATE2_DEPLOYER, &mut ecx.db)?.0.info;
1611-
match &info.code {
1612-
Some(code) if code.is_empty() => return Err(DatabaseError::MissingCreate2Deployer),
1613-
None if ecx.db.code_by_hash(info.code_hash)?.is_empty() => {
1614-
return Err(DatabaseError::MissingCreate2Deployer)
1615-
}
1616-
_ => {}
1617-
}
1618-
1619-
call.caller = DEFAULT_CREATE2_DEPLOYER;
1620-
}
1621-
}
1622-
Ok(())
1623-
}
1624-
1625-
/// Processes the creation of a new contract when broadcasting, preparing the necessary data for the
1626-
/// transaction to deploy the contract.
1627-
///
1628-
/// Returns the transaction calldata and the target address.
1629-
///
1630-
/// If the CreateScheme is Create, then this function returns the input bytecode without
1631-
/// modification and no address since it will be filled in later. If the CreateScheme is Create2,
1632-
/// then this function returns the calldata for the call to the create2 deployer which must be the
1633-
/// salt and init code concatenated.
1634-
fn process_broadcast_create<DB: DatabaseExt>(
1635-
broadcast_sender: Address,
1636-
bytecode: Bytes,
1637-
ecx: &mut InnerEvmContext<DB>,
1638-
call: &mut CreateInputs,
1639-
) -> (Bytes, Option<Address>, u64) {
1640-
call.caller = broadcast_sender;
1641-
match call.scheme {
1642-
CreateScheme::Create => {
1643-
(bytecode, None, ecx.journaled_state.account(broadcast_sender).info.nonce)
1644-
}
1645-
CreateScheme::Create2 { salt } => {
1646-
// We have to increment the nonce of the user address, since this create2 will be done
1647-
// by the create2_deployer
1648-
let account = ecx.journaled_state.state().get_mut(&broadcast_sender).unwrap();
1649-
let prev = account.info.nonce;
1650-
// Touch account to ensure that incremented nonce is committed
1651-
account.mark_touch();
1652-
account.info.nonce += 1;
1653-
debug!(target: "cheatcodes", address=%broadcast_sender, nonce=prev+1, prev, "incremented nonce in create2");
1654-
// Proxy deployer requires the data to be `salt ++ init_code`
1655-
let calldata = [&salt.to_be_bytes::<32>()[..], &bytecode[..]].concat();
1656-
(calldata.into(), Some(DEFAULT_CREATE2_DEPLOYER), prev)
1657-
}
1658-
}
1659-
}
1660-
16611556
// Determines if the gas limit on a given call was manually set in the script and should therefore
16621557
// not be overwritten by later estimations
16631558
fn check_if_fixed_gas_limit<DB: DatabaseExt>(

crates/evm/core/src/backend/cow.rs

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ use crate::{
66
RevertSnapshotAction,
77
},
88
fork::{CreateFork, ForkId},
9+
InspectorExt,
910
};
1011
use alloy_genesis::GenesisAccount;
1112
use alloy_primitives::{Address, B256, U256};
@@ -16,7 +17,7 @@ use revm::{
1617
Account, AccountInfo, Bytecode, Env, EnvWithHandlerCfg, HashMap as Map, ResultAndState,
1718
SpecId,
1819
},
19-
Database, DatabaseCommit, Inspector, JournaledState,
20+
Database, DatabaseCommit, JournaledState,
2021
};
2122
use std::{borrow::Cow, collections::BTreeMap};
2223

@@ -58,7 +59,7 @@ impl<'a> CowBackend<'a> {
5859
///
5960
/// Note: in case there are any cheatcodes executed that modify the environment, this will
6061
/// update the given `env` with the new values.
61-
pub fn inspect<'b, I: Inspector<&'b mut Self>>(
62+
pub fn inspect<'b, I: InspectorExt<&'b mut Self>>(
6263
&'b mut self,
6364
env: &mut EnvWithHandlerCfg,
6465
inspector: I,
@@ -176,7 +177,7 @@ impl<'a> DatabaseExt for CowBackend<'a> {
176177
self.backend_mut(env).roll_fork_to_transaction(id, transaction, env, journaled_state)
177178
}
178179

179-
fn transact<I: Inspector<Backend>>(
180+
fn transact<I: InspectorExt<Backend>>(
180181
&mut self,
181182
id: Option<LocalForkId>,
182183
transaction: B256,

crates/evm/core/src/backend/mod.rs

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ use crate::{
55
fork::{CreateFork, ForkId, MultiFork, SharedBackend},
66
snapshot::Snapshots,
77
utils::configure_tx_env,
8+
InspectorExt,
89
};
910
use alloy_genesis::GenesisAccount;
1011
use alloy_primitives::{b256, keccak256, Address, B256, U256};
@@ -19,7 +20,7 @@ use revm::{
1920
Account, AccountInfo, Bytecode, CreateScheme, Env, EnvWithHandlerCfg, HashMap as Map, Log,
2021
ResultAndState, SpecId, State, StorageSlot, TransactTo, KECCAK_EMPTY,
2122
},
22-
Database, DatabaseCommit, Inspector, JournaledState,
23+
Database, DatabaseCommit, JournaledState,
2324
};
2425
use std::{
2526
collections::{BTreeMap, HashMap, HashSet},
@@ -188,7 +189,7 @@ pub trait DatabaseExt: Database<Error = DatabaseError> {
188189
) -> eyre::Result<()>;
189190

190191
/// Fetches the given transaction for the fork and executes it, committing the state in the DB
191-
fn transact<I: Inspector<Backend>>(
192+
fn transact<I: InspectorExt<Backend>>(
192193
&mut self,
193194
id: Option<LocalForkId>,
194195
transaction: B256,
@@ -780,7 +781,7 @@ impl Backend {
780781
///
781782
/// Note: in case there are any cheatcodes executed that modify the environment, this will
782783
/// update the given `env` with the new values.
783-
pub fn inspect<'a, I: Inspector<&'a mut Self>>(
784+
pub fn inspect<'a, I: InspectorExt<&'a mut Self>>(
784785
&'a mut self,
785786
env: &mut EnvWithHandlerCfg,
786787
inspector: I,
@@ -1228,7 +1229,7 @@ impl DatabaseExt for Backend {
12281229
Ok(())
12291230
}
12301231

1231-
fn transact<I: Inspector<Backend>>(
1232+
fn transact<I: InspectorExt<Backend>>(
12321233
&mut self,
12331234
maybe_id: Option<LocalForkId>,
12341235
transaction: B256,
@@ -1868,7 +1869,7 @@ fn update_env_block(env: &mut Env, fork_block: u64, block: &Block) {
18681869

18691870
/// Executes the given transaction and commits state changes to the database _and_ the journaled
18701871
/// state, with an optional inspector
1871-
fn commit_transaction<I: Inspector<Backend>>(
1872+
fn commit_transaction<I: InspectorExt<Backend>>(
18721873
tx: WithOtherFields<Transaction>,
18731874
mut env: EnvWithHandlerCfg,
18741875
journaled_state: &mut JournaledState,

crates/evm/core/src/lib.rs

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,10 @@
44
55
#![warn(unused_crate_dependencies)]
66

7+
use auto_impl::auto_impl;
8+
use revm::{inspectors::NoOpInspector, interpreter::CreateInputs, Database, EvmContext, Inspector};
9+
use revm_inspectors::access_list::AccessListInspector;
10+
711
#[macro_use]
812
extern crate tracing;
913

@@ -19,3 +23,23 @@ pub mod opcodes;
1923
pub mod opts;
2024
pub mod snapshot;
2125
pub mod utils;
26+
27+
/// An extension trait that allows us to add additional hooks to Inspector for later use in
28+
/// handlers.
29+
#[auto_impl(&mut, Box)]
30+
pub trait InspectorExt<DB: Database>: Inspector<DB> {
31+
/// Determines whether the `DEFAULT_CREATE2_DEPLOYER` should be used for a CREATE2 frame.
32+
///
33+
/// If this function returns true, we'll replace CREATE2 frame with a CALL frame to CREATE2
34+
/// factory.
35+
fn should_use_create2_factory(
36+
&mut self,
37+
_context: &mut EvmContext<DB>,
38+
_inputs: &mut CreateInputs,
39+
) -> bool {
40+
false
41+
}
42+
}
43+
44+
impl<DB: Database> InspectorExt<DB> for NoOpInspector {}
45+
impl<DB: Database> InspectorExt<DB> for AccessListInspector {}

0 commit comments

Comments
 (0)