diff --git a/crates/cheatcodes/assets/cheatcodes.json b/crates/cheatcodes/assets/cheatcodes.json index a63b985c5c205..17e89f21cac68 100644 --- a/crates/cheatcodes/assets/cheatcodes.json +++ b/crates/cheatcodes/assets/cheatcodes.json @@ -6304,6 +6304,26 @@ "status": "stable", "safety": "safe" }, + { + "func": { + "id": "getEvmVersion", + "description": "Returns the test or script execution evm version.\n**Note:** The execution evm version is not the same as the compilation one.", + "declaration": "function getEvmVersion() external pure returns (string memory evm);", + "visibility": "external", + "mutability": "pure", + "signature": "getEvmVersion()", + "selector": "0xaa2bb222", + "selectorBytes": [ + 170, + 43, + 178, + 34 + ] + }, + "group": "evm", + "status": "stable", + "safety": "safe" + }, { "func": { "id": "getFoundryVersion", @@ -9790,6 +9810,26 @@ "status": "stable", "safety": "safe" }, + { + "func": { + "id": "setEvmVersion", + "description": "Set the exact test or script execution evm version, e.g. `berlin`, `cancun`.\n**Note:** The execution evm version is not the same as the compilation one.", + "declaration": "function setEvmVersion(string calldata evm) external;", + "visibility": "external", + "mutability": "", + "signature": "setEvmVersion(string)", + "selector": "0x43179f5a", + "selectorBytes": [ + 67, + 23, + 159, + 90 + ] + }, + "group": "evm", + "status": "stable", + "safety": "safe" + }, { "func": { "id": "setNonce", diff --git a/crates/cheatcodes/spec/src/vm.rs b/crates/cheatcodes/spec/src/vm.rs index 96eb8fcb35942..9cbb5aba016f1 100644 --- a/crates/cheatcodes/spec/src/vm.rs +++ b/crates/cheatcodes/spec/src/vm.rs @@ -609,6 +609,18 @@ interface Vm { #[cheatcode(group = Evm, safety = Unsafe)] function coolSlot(address target, bytes32 slot) external; + /// Returns the test or script execution evm version. + /// + /// **Note:** The execution evm version is not the same as the compilation one. + #[cheatcode(group = Evm, safety = Safe)] + function getEvmVersion() external pure returns (string memory evm); + + /// Set the exact test or script execution evm version, e.g. `berlin`, `cancun`. + /// + /// **Note:** The execution evm version is not the same as the compilation one. + #[cheatcode(group = Evm, safety = Safe)] + function setEvmVersion(string calldata evm) external; + // -------- Call Manipulation -------- // --- Mocks --- diff --git a/crates/cheatcodes/src/evm.rs b/crates/cheatcodes/src/evm.rs index 1431006a09d12..84301676424f5 100644 --- a/crates/cheatcodes/src/evm.rs +++ b/crates/cheatcodes/src/evm.rs @@ -21,6 +21,7 @@ use foundry_common::{ SlotInfo, }, }; +use foundry_compilers::artifacts::EvmVersion; use foundry_evm_core::{ ContextExt, backend::{DatabaseExt, RevertStateSnapshotAction}, @@ -45,6 +46,7 @@ use std::{ mod record_debug_step; use foundry_common::fmt::format_token_raw; +use foundry_config::evm_spec_id; use record_debug_step::{convert_call_trace_to_debug_step, flatten_call_trace}; use serde::Serialize; @@ -1103,6 +1105,23 @@ impl Cheatcode for stopAndReturnDebugTraceRecordingCall { } } +impl Cheatcode for setEvmVersionCall { + fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { + let Self { evm } = self; + ccx.ecx.cfg.spec = evm_spec_id( + EvmVersion::from_str(evm) + .map_err(|_| Error::from(format!("invalid evm version {evm}")))?, + ); + Ok(Default::default()) + } +} + +impl Cheatcode for getEvmVersionCall { + fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { + Ok(ccx.ecx.cfg.spec.to_string().to_lowercase().abi_encode()) + } +} + pub(super) fn get_nonce(ccx: &mut CheatsCtxt, address: &Address) -> Result { let account = ccx.ecx.journaled_state.load_account(*address)?; Ok(account.info.nonce.abi_encode()) diff --git a/crates/forge/tests/it/spec.rs b/crates/forge/tests/it/spec.rs index 99a3a5e7a98b2..67ceda82373db 100644 --- a/crates/forge/tests/it/spec.rs +++ b/crates/forge/tests/it/spec.rs @@ -1,7 +1,7 @@ //! Integration tests for EVM specifications. use crate::{config::*, test_helpers::TEST_DATA_PARIS}; -use foundry_test_utils::Filter; +use foundry_test_utils::{Filter, forgetest_init, rpc, str}; use revm::primitives::hardfork::SpecId; #[tokio::test(flavor = "multi_thread")] @@ -9,3 +9,91 @@ async fn test_shanghai_compat() { let filter = Filter::new("", "ShanghaiCompat", ".*spec"); TestConfig::with_filter(TEST_DATA_PARIS.runner(), filter).spec_id(SpecId::SHANGHAI).run().await; } + +// Test evm version switch during tests / scripts. +// +// +forgetest_init!(test_set_evm_version, |prj, cmd| { + let endpoint = rpc::next_http_archive_rpc_url(); + prj.add_test( + "TestEvmVersion.t.sol", + &r#" +import {Test} from "forge-std/Test.sol"; + +interface EvmVm { + function getEvmVersion() external pure returns (string memory evm); + function setEvmVersion(string calldata evm) external; +} + +interface ICreate2Deployer { + function computeAddress(bytes32 salt, bytes32 codeHash) external view returns (address); +} + +contract TestEvmVersion is Test { + function test_evm_version() public { + EvmVm evm = EvmVm(address(bytes20(uint160(uint256(keccak256("hevm cheat code")))))); + vm.createSelectFork(""); + + evm.setEvmVersion("istanbul"); + evm.getEvmVersion(); + + // revert with NotActivated for istanbul + vm.expectRevert(); + compute(); + + evm.setEvmVersion("shanghai"); + evm.getEvmVersion(); + compute(); + + // switch to Paris, expect revert with NotActivated + evm.setEvmVersion("paris"); + vm.expectRevert(); + compute(); + } + + function compute() internal view { + ICreate2Deployer(0x35Da41c476fA5c6De066f20556069096A1F39364).computeAddress(bytes32(0), bytes32(0)); + } +} + "#.replace("", &endpoint), + ); + + cmd.args(["test", "--mc", "TestEvmVersion", "-vvvv"]).assert_success().stdout_eq(str![[r#" +[COMPILING_FILES] with [SOLC_VERSION] +[SOLC_VERSION] [ELAPSED] +Compiler run successful! + +Ran 1 test for test/TestEvmVersion.t.sol:TestEvmVersion +[PASS] test_evm_version() ([GAS]) +Traces: + [..] TestEvmVersion::test_evm_version() + ├─ [0] VM::createSelectFork("") + │ └─ ← [Return] 0 + ├─ [0] VM::setEvmVersion("istanbul") + │ └─ ← [Return] + ├─ [0] VM::getEvmVersion() [staticcall] + │ └─ ← [Return] "istanbul" + ├─ [0] VM::expectRevert(custom error 0xf4844814) + │ └─ ← [Return] + ├─ [..] 0x35Da41c476fA5c6De066f20556069096A1F39364::computeAddress(0x0000000000000000000000000000000000000000000000000000000000000000, 0x0000000000000000000000000000000000000000000000000000000000000000) [staticcall] + │ └─ ← [NotActivated] EvmError: NotActivated + ├─ [0] VM::setEvmVersion("shanghai") + │ └─ ← [Return] + ├─ [0] VM::getEvmVersion() [staticcall] + │ └─ ← [Return] "shanghai" + ├─ [..] 0x35Da41c476fA5c6De066f20556069096A1F39364::computeAddress(0x0000000000000000000000000000000000000000000000000000000000000000, 0x0000000000000000000000000000000000000000000000000000000000000000) [staticcall] + │ └─ ← [Return] 0x0f40d7B7669e3a6683EaB25358318fd42a9F2342 + ├─ [0] VM::setEvmVersion("paris") + │ └─ ← [Return] + ├─ [0] VM::expectRevert(custom error 0xf4844814) + │ └─ ← [Return] + ├─ [..] 0x35Da41c476fA5c6De066f20556069096A1F39364::computeAddress(0x0000000000000000000000000000000000000000000000000000000000000000, 0x0000000000000000000000000000000000000000000000000000000000000000) [staticcall] + │ └─ ← [NotActivated] EvmError: NotActivated + └─ ← [Stop] + +Suite result: ok. 1 passed; 0 failed; 0 skipped; [ELAPSED] + +Ran 1 test suite [ELAPSED]: 1 tests passed, 0 failed, 0 skipped (1 total tests) + +"#]]); +}); diff --git a/testdata/cheats/Vm.sol b/testdata/cheats/Vm.sol index bf61a073d6b1d..a8ac8b5193186 100644 --- a/testdata/cheats/Vm.sol +++ b/testdata/cheats/Vm.sol @@ -308,6 +308,7 @@ interface Vm { function getDeployment(string calldata contractName) external view returns (address deployedAddress); function getDeployment(string calldata contractName, uint64 chainId) external view returns (address deployedAddress); function getDeployments(string calldata contractName, uint64 chainId) external view returns (address[] memory deployedAddresses); + function getEvmVersion() external pure returns (string memory evm); function getFoundryVersion() external view returns (string memory version); function getLabel(address account) external view returns (string memory currentLabel); function getMappingKeyAndParentOf(address target, bytes32 elementSlot) external view returns (bool found, bytes32 key, bytes32 parent); @@ -482,6 +483,7 @@ interface Vm { function setArbitraryStorage(address target, bool overwrite) external; function setBlockhash(uint256 blockNumber, bytes32 blockHash) external; function setEnv(string calldata name, string calldata value) external; + function setEvmVersion(string calldata evm) external; function setNonce(address account, uint64 newNonce) external; function setNonceUnsafe(address account, uint64 newNonce) external; function setSeed(uint256 seed) external;