From 0f9bfb6abb20ba64ce6534191afe8df3e6cfca52 Mon Sep 17 00:00:00 2001 From: grandizzy Date: Wed, 8 Oct 2025 09:14:56 +0300 Subject: [PATCH 1/4] feat(cheatcodes): add setEvmVersion / getEvmVersion --- crates/cheatcodes/assets/cheatcodes.json | 40 +++++++++++ crates/cheatcodes/spec/src/vm.rs | 8 +++ crates/cheatcodes/src/evm.rs | 19 +++++ crates/forge/tests/it/spec.rs | 90 +++++++++++++++++++++++- testdata/cheats/Vm.sol | 2 + 5 files changed, 158 insertions(+), 1 deletion(-) diff --git a/crates/cheatcodes/assets/cheatcodes.json b/crates/cheatcodes/assets/cheatcodes.json index a63b985c5c205..b3780d6b8a563 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 execution evm version.", + "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 execution evm version.", + "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..6c05818025503 100644 --- a/crates/cheatcodes/spec/src/vm.rs +++ b/crates/cheatcodes/spec/src/vm.rs @@ -609,6 +609,14 @@ interface Vm { #[cheatcode(group = Evm, safety = Unsafe)] function coolSlot(address target, bytes32 slot) external; + /// Returns the execution evm version. + #[cheatcode(group = Evm, safety = Safe)] + function getEvmVersion() external pure returns (string memory evm); + + /// Set the execution evm version. + #[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..2ffb90dee4687 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), + ); + // Tests should fail and record counterexample with value 2. + 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; From 0838c5421b44208623f60558227e380bdf601e88 Mon Sep 17 00:00:00 2001 From: grandizzy Date: Wed, 8 Oct 2025 15:56:36 +0300 Subject: [PATCH 2/4] Add better explanation re evm version --- crates/cheatcodes/assets/cheatcodes.json | 4 ++-- crates/cheatcodes/spec/src/vm.rs | 8 ++++++-- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/crates/cheatcodes/assets/cheatcodes.json b/crates/cheatcodes/assets/cheatcodes.json index b3780d6b8a563..e422bbd38cac1 100644 --- a/crates/cheatcodes/assets/cheatcodes.json +++ b/crates/cheatcodes/assets/cheatcodes.json @@ -6307,7 +6307,7 @@ { "func": { "id": "getEvmVersion", - "description": "Returns the execution evm version.", + "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", @@ -9813,7 +9813,7 @@ { "func": { "id": "setEvmVersion", - "description": "Set the execution evm version.", + "description": "Set the test or script execution evm version.\n**Note:** The execution evm version is not the same as the compilation one.", "declaration": "function setEvmVersion(string calldata evm) external;", "visibility": "external", "mutability": "", diff --git a/crates/cheatcodes/spec/src/vm.rs b/crates/cheatcodes/spec/src/vm.rs index 6c05818025503..bd655f0fd4743 100644 --- a/crates/cheatcodes/spec/src/vm.rs +++ b/crates/cheatcodes/spec/src/vm.rs @@ -609,11 +609,15 @@ interface Vm { #[cheatcode(group = Evm, safety = Unsafe)] function coolSlot(address target, bytes32 slot) external; - /// Returns the execution evm version. + /// 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 execution evm version. + /// Set 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 setEvmVersion(string calldata evm) external; From 50dd1838ed26da0503df5fc8186301a3b4f27406 Mon Sep 17 00:00:00 2001 From: grandizzy Date: Wed, 8 Oct 2025 19:37:51 +0300 Subject: [PATCH 3/4] Nit --- crates/forge/tests/it/spec.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/forge/tests/it/spec.rs b/crates/forge/tests/it/spec.rs index 2ffb90dee4687..67ceda82373db 100644 --- a/crates/forge/tests/it/spec.rs +++ b/crates/forge/tests/it/spec.rs @@ -57,7 +57,7 @@ contract TestEvmVersion is Test { } "#.replace("", &endpoint), ); - // Tests should fail and record counterexample with value 2. + cmd.args(["test", "--mc", "TestEvmVersion", "-vvvv"]).assert_success().stdout_eq(str![[r#" [COMPILING_FILES] with [SOLC_VERSION] [SOLC_VERSION] [ELAPSED] From 26cc8537b52139d1600a1853624f3416b47705ae Mon Sep 17 00:00:00 2001 From: grandizzy Date: Thu, 9 Oct 2025 17:29:38 +0300 Subject: [PATCH 4/4] Update cheatcode note to reflect exact evm version needed --- crates/cheatcodes/assets/cheatcodes.json | 2 +- crates/cheatcodes/spec/src/vm.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/cheatcodes/assets/cheatcodes.json b/crates/cheatcodes/assets/cheatcodes.json index e422bbd38cac1..17e89f21cac68 100644 --- a/crates/cheatcodes/assets/cheatcodes.json +++ b/crates/cheatcodes/assets/cheatcodes.json @@ -9813,7 +9813,7 @@ { "func": { "id": "setEvmVersion", - "description": "Set the test or script execution evm version.\n**Note:** The execution evm version is not the same as the compilation one.", + "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": "", diff --git a/crates/cheatcodes/spec/src/vm.rs b/crates/cheatcodes/spec/src/vm.rs index bd655f0fd4743..9cbb5aba016f1 100644 --- a/crates/cheatcodes/spec/src/vm.rs +++ b/crates/cheatcodes/spec/src/vm.rs @@ -615,7 +615,7 @@ interface Vm { #[cheatcode(group = Evm, safety = Safe)] function getEvmVersion() external pure returns (string memory evm); - /// Set the test or script execution evm version. + /// 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)]