diff --git a/crates/cheatcodes/assets/cheatcodes.json b/crates/cheatcodes/assets/cheatcodes.json index 50189117bd26a..723c01b3ef52c 100644 --- a/crates/cheatcodes/assets/cheatcodes.json +++ b/crates/cheatcodes/assets/cheatcodes.json @@ -625,6 +625,22 @@ "description": "The data to use to match encountered reverts" } ] + }, + { + "name": "AccessListItem", + "description": "An EIP-2930 access list item.", + "fields": [ + { + "name": "target", + "ty": "address", + "description": "The address to be added in access list." + }, + { + "name": "storageKeys", + "ty": "bytes32[]", + "description": "The storage keys to be added in access list." + } + ] } ], "cheatcodes": [ @@ -688,6 +704,26 @@ "status": "internal", "safety": "unsafe" }, + { + "func": { + "id": "accessList", + "description": "Utility cheatcode to set an EIP-2930 access list for all subsequent transactions.", + "declaration": "function accessList(AccessListItem[] calldata accessList) external;", + "visibility": "external", + "mutability": "", + "signature": "accessList((address,bytes32[])[])", + "selector": "0x743e4cb7", + "selectorBytes": [ + 116, + 62, + 76, + 183 + ] + }, + "group": "evm", + "status": "experimental", + "safety": "unsafe" + }, { "func": { "id": "accesses", @@ -3448,6 +3484,26 @@ "status": "stable", "safety": "unsafe" }, + { + "func": { + "id": "cold", + "description": "Utility cheatcode to mark specific storage slot as cold, simulating no prior read.", + "declaration": "function cold(address target, bytes32 slot) external;", + "visibility": "external", + "mutability": "", + "signature": "cold(address,bytes32)", + "selector": "0x40a4035f", + "selectorBytes": [ + 64, + 164, + 3, + 95 + ] + }, + "group": "evm", + "status": "experimental", + "safety": "unsafe" + }, { "func": { "id": "computeCreate2Address_0", @@ -6614,6 +6670,26 @@ "status": "stable", "safety": "unsafe" }, + { + "func": { + "id": "noAccessList", + "description": "Utility cheatcode to remove any EIP-2930 access list set by `accessList` cheatcode.", + "declaration": "function noAccessList() external;", + "visibility": "external", + "mutability": "", + "signature": "noAccessList()", + "selector": "0x238ad778", + "selectorBytes": [ + 35, + 138, + 215, + 120 + ] + }, + "group": "evm", + "status": "experimental", + "safety": "unsafe" + }, { "func": { "id": "parseAddress", @@ -10460,6 +10536,26 @@ "status": "stable", "safety": "safe" }, + { + "func": { + "id": "warm", + "description": "Utility cheatcode to mark specific storage slot as warm, simulating a prior read.", + "declaration": "function warm(address target, bytes32 slot) external;", + "visibility": "external", + "mutability": "", + "signature": "warm(address,bytes32)", + "selector": "0x289eb2d6", + "selectorBytes": [ + 40, + 158, + 178, + 214 + ] + }, + "group": "evm", + "status": "experimental", + "safety": "unsafe" + }, { "func": { "id": "warp", diff --git a/crates/cheatcodes/spec/src/lib.rs b/crates/cheatcodes/spec/src/lib.rs index 6dd6ee769f6b9..0a1e81c6f60d0 100644 --- a/crates/cheatcodes/spec/src/lib.rs +++ b/crates/cheatcodes/spec/src/lib.rs @@ -90,6 +90,7 @@ impl Cheatcodes<'static> { Vm::BroadcastTxSummary::STRUCT.clone(), Vm::SignedDelegation::STRUCT.clone(), Vm::PotentialRevert::STRUCT.clone(), + Vm::AccessListItem::STRUCT.clone(), ]), enums: Cow::Owned(vec![ Vm::CallerMode::ENUM.clone(), diff --git a/crates/cheatcodes/spec/src/vm.rs b/crates/cheatcodes/spec/src/vm.rs index 1891cc45f4709..0b7cf87b2f7a2 100644 --- a/crates/cheatcodes/spec/src/vm.rs +++ b/crates/cheatcodes/spec/src/vm.rs @@ -223,6 +223,14 @@ interface Vm { bool reverted; } + /// An EIP-2930 access list item. + struct AccessListItem { + /// The address to be added in access list. + address target; + /// The storage keys to be added in access list. + bytes32[] storageKeys; + } + /// The result of a `stopAndReturnStateDiff` call. struct AccountAccess { /// The chain and fork the access occurred. @@ -544,6 +552,22 @@ interface Vm { #[cheatcode(group = Evm, safety = Unsafe, status = Experimental)] function cool(address target) external; + /// Utility cheatcode to set an EIP-2930 access list for all subsequent transactions. + #[cheatcode(group = Evm, safety = Unsafe, status = Experimental)] + function accessList(AccessListItem[] calldata accessList) external; + + /// Utility cheatcode to remove any EIP-2930 access list set by `accessList` cheatcode. + #[cheatcode(group = Evm, safety = Unsafe, status = Experimental)] + function noAccessList() external; + + /// Utility cheatcode to mark specific storage slot as warm, simulating a prior read. + #[cheatcode(group = Evm, safety = Unsafe, status = Experimental)] + function warm(address target, bytes32 slot) external; + + /// Utility cheatcode to mark specific storage slot as cold, simulating no prior read. + #[cheatcode(group = Evm, safety = Unsafe, status = Experimental)] + function cold(address target, bytes32 slot) external; + // -------- Call Manipulation -------- // --- Mocks --- diff --git a/crates/cheatcodes/src/evm.rs b/crates/cheatcodes/src/evm.rs index b7469ba8e6839..e4ae83698834a 100644 --- a/crates/cheatcodes/src/evm.rs +++ b/crates/cheatcodes/src/evm.rs @@ -16,6 +16,7 @@ use foundry_evm_core::{ constants::{CALLER, CHEATCODE_ADDRESS, HARDHAT_CONSOLE_ADDRESS, TEST_CONTRACT_ADDRESS}, }; use foundry_evm_traces::StackSnapshotType; +use itertools::Itertools; use rand::Rng; use revm::primitives::{Account, Bytecode, SpecId, KECCAK_EMPTY}; use std::{ @@ -585,6 +586,48 @@ impl Cheatcode for coolCall { } } +impl Cheatcode for accessListCall { + fn apply(&self, state: &mut Cheatcodes) -> Result { + let Self { accessList } = self; + let access_list = accessList + .iter() + .map(|item| { + let keys = item.storageKeys.iter().map(|key| B256::from(*key)).collect_vec(); + alloy_rpc_types::AccessListItem { address: item.target, storage_keys: keys } + }) + .collect_vec(); + state.access_list = Some(alloy_rpc_types::AccessList::from(access_list)); + Ok(Default::default()) + } +} + +impl Cheatcode for noAccessListCall { + fn apply(&self, state: &mut Cheatcodes) -> Result { + let Self {} = self; + // Set to empty option in order to override previous applied access list. + if state.access_list.is_some() { + state.access_list = Some(alloy_rpc_types::AccessList::default()); + } + Ok(Default::default()) + } +} + +impl Cheatcode for warmCall { + fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { + let Self { target, slot } = *self; + set_cold_slot(ccx, target, slot.into(), false); + Ok(Default::default()) + } +} + +impl Cheatcode for coldCall { + fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { + let Self { target, slot } = *self; + set_cold_slot(ccx, target, slot.into(), true); + Ok(Default::default()) + } +} + impl Cheatcode for readCallersCall { fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { let Self {} = self; @@ -1195,3 +1238,12 @@ fn get_recorded_state_diffs(state: &mut Cheatcodes) -> BTreeMap
, + /// Additional, user configurable context this Inspector has access to when inspecting a call. pub config: Arc