From c9c4f98933e844a84c7f74bf4d1eedb11f455390 Mon Sep 17 00:00:00 2001 From: Sebastian Miasojed Date: Wed, 14 Jan 2026 15:07:41 +0100 Subject: [PATCH 1/6] Add initial sync between REVM and pallet-revive --- crates/evm/core/src/backend/cow.rs | 4 ++++ crates/evm/core/src/backend/mod.rs | 7 ++++++ crates/forge/tests/it/revive/migration.rs | 11 +++++++++ crates/revive-strategy/src/cheatcodes/mod.rs | 23 +++++++++++++++---- .../default/revive/EvmToReviveMigration.t.sol | 14 +++++++++++ 5 files changed, 54 insertions(+), 5 deletions(-) diff --git a/crates/evm/core/src/backend/cow.rs b/crates/evm/core/src/backend/cow.rs index 50182fc8242aa..48f1667e22bb5 100644 --- a/crates/evm/core/src/backend/cow.rs +++ b/crates/evm/core/src/backend/cow.rs @@ -287,6 +287,10 @@ impl DatabaseExt for CowBackend<'_> { self.backend.has_cheatcode_access(account) } + fn cached_accounts(&self) -> Vec
{ + self.backend.cached_accounts() + } + fn set_blockhash(&mut self, block_number: U256, block_hash: B256) { self.backend.to_mut().set_blockhash(block_number, block_hash); } diff --git a/crates/evm/core/src/backend/mod.rs b/crates/evm/core/src/backend/mod.rs index d278e856b357b..7c12cece0438b 100644 --- a/crates/evm/core/src/backend/mod.rs +++ b/crates/evm/core/src/backend/mod.rs @@ -353,6 +353,9 @@ pub trait DatabaseExt: Database + DatabaseCommit + Debug /// Returns `true` if the given account is allowed to execute cheatcodes fn has_cheatcode_access(&self, account: &Address) -> bool; + /// Returns all accounts in the memory database cache + fn cached_accounts(&self) -> Vec
; + /// Ensures that `account` is allowed to execute cheatcodes /// /// Returns an error if [`Self::has_cheatcode_access`] returns `false` @@ -1516,6 +1519,10 @@ impl DatabaseExt for Backend { self.inner.cheatcode_access_accounts.contains(account) } + fn cached_accounts(&self) -> Vec
{ + self.mem_db.cache.accounts.keys().copied().collect() + } + fn set_blockhash(&mut self, block_number: U256, block_hash: B256) { if let Some(db) = self.active_fork_db_mut() { db.cache.block_hashes.insert(block_number.saturating_to(), block_hash); diff --git a/crates/forge/tests/it/revive/migration.rs b/crates/forge/tests/it/revive/migration.rs index 311b21ac0811b..fc3adbefc9c20 100644 --- a/crates/forge/tests/it/revive/migration.rs +++ b/crates/forge/tests/it/revive/migration.rs @@ -151,3 +151,14 @@ async fn test_contract_deployment_in_different_modes(#[case] runtime_mode: Reviv ); TestConfig::with_filter(runner, filter).spec_id(SpecId::PRAGUE).run().await; } + +#[rstest] +//#[case::pvm(ReviveRuntimeMode::Pvm)] +#[case::evm(ReviveRuntimeMode::Evm)] +#[tokio::test(flavor = "multi_thread")] +async fn test_initial_contract_deployment(#[case] runtime_mode: ReviveRuntimeMode) { + let runner = TEST_DATA_REVIVE.runner_revive(runtime_mode); + let filter = + Filter::new("testInitialContractMigration", "InitialMigrationTest", ".*/revive/.*"); + TestConfig::with_filter(runner, filter).spec_id(SpecId::PRAGUE).run().await; +} diff --git a/crates/revive-strategy/src/cheatcodes/mod.rs b/crates/revive-strategy/src/cheatcodes/mod.rs index ffad9796329fb..f4679145df32e 100644 --- a/crates/revive-strategy/src/cheatcodes/mod.rs +++ b/crates/revive-strategy/src/cheatcodes/mod.rs @@ -560,7 +560,7 @@ impl CheatcodeInspectorStrategyRunner for PvmCheatcodeInspectorStrategyRunner { if ctx.revive_startup_migration.is_allowed() && !ctx.using_revive { tracing::info!("startup pallet-revive migration initiated"); - select_revive(ctx, ecx); + select_revive(ctx, ecx, true); ctx.revive_startup_migration.done(); tracing::info!("startup pallet-revive migration completed"); } @@ -641,7 +641,7 @@ fn handle_polkadot_call( ctx.runtime_mode = target_mode; if !is_backend_switch { // Migrate to the target mode (from standard EVM to Polkadot) - select_revive(ctx, data); + select_revive(ctx, data, false); } } else if ctx.using_revive { // Switching BACK to Foundry EVM @@ -650,7 +650,11 @@ fn handle_polkadot_call( Ok(Default::default()) } -fn select_revive(ctx: &mut PvmCheatcodeInspectorStrategyContext, data: Ecx<'_, '_, '_>) { +fn select_revive( + ctx: &mut PvmCheatcodeInspectorStrategyContext, + data: Ecx<'_, '_, '_>, + migrate_all: bool, +) { if ctx.using_revive { tracing::info!("already using pallet-revive"); return; @@ -673,9 +677,18 @@ fn select_revive(ctx: &mut PvmCheatcodeInspectorStrategyContext, data: Ecx<'_, ' ::ChainId::set( &data.cfg.chain_id, ); - let persistent_accounts = data.journaled_state.database.persistent_accounts().clone(); let test_contract_addr = data.journaled_state.database.get_test_contract_address(); - for address in persistent_accounts.into_iter().chain([data.tx.caller]) { + + let accounts_to_migrate: Vec
= if migrate_all { + // Migrate all accounts (including contract-level deployments) + // Get accounts from the database cache (includes all created/modified accounts) + data.journaled_state.database.cached_accounts() + } else { + let persistent_accounts = data.journaled_state.database.persistent_accounts().clone(); + persistent_accounts.into_iter().chain([data.tx.caller]).collect() + }; + + for address in accounts_to_migrate { tracing::info!("Migrating account {:?} (is_test_contract: {})", address, test_contract_addr == Some(address)); let acc = data.journaled_state.load_account(address).expect("failed to load account"); let amount = acc.data.info.balance; diff --git a/testdata/default/revive/EvmToReviveMigration.t.sol b/testdata/default/revive/EvmToReviveMigration.t.sol index a5931796dd281..c5a6caf3da1b1 100644 --- a/testdata/default/revive/EvmToReviveMigration.t.sol +++ b/testdata/default/revive/EvmToReviveMigration.t.sol @@ -369,3 +369,17 @@ contract EvmReviveMigrationTest is DSTest { assertEq(evmContract.get(), 250, "EVM contract should update in PVM mode"); } } +contract InitialMigrationTest is DSTest { + Vm constant vm = Vm(address(uint160(uint256(keccak256("hevm cheat code"))))); + SimpleStorage storageContract = new SimpleStorage(); + + function setUp() public { + storageContract.set(42); + uint256 val = storageContract.get(); + assertEq(val, 42); + } + + function testInitialContractMigration() public { + assertEq(storageContract.get(), 42); + } +} From cedd99af22e1aa22693bc35429d6f9482ae549d6 Mon Sep 17 00:00:00 2001 From: Sebastian Miasojed Date: Thu, 15 Jan 2026 15:05:24 +0100 Subject: [PATCH 2/6] Fixes --- crates/evm/core/src/backend/mod.rs | 12 +++++++++++- crates/forge/tests/it/test_helpers.rs | 2 ++ crates/revive-strategy/src/state.rs | 5 +---- testdata/forge-std-rev | 2 +- 4 files changed, 15 insertions(+), 6 deletions(-) diff --git a/crates/evm/core/src/backend/mod.rs b/crates/evm/core/src/backend/mod.rs index 7c12cece0438b..a2ea41d1e0c08 100644 --- a/crates/evm/core/src/backend/mod.rs +++ b/crates/evm/core/src/backend/mod.rs @@ -1520,7 +1520,17 @@ impl DatabaseExt for Backend { } fn cached_accounts(&self) -> Vec
{ - self.mem_db.cache.accounts.keys().copied().collect() + self.mem_db.cache.accounts + .iter() + .filter_map(|(addr, acc)| { + // Only include accounts with non-empty bytecode (actual contracts) + if acc.info.code.as_ref().is_some_and(|c| !c.is_empty()) { + Some(*addr) + } else { + None + } + }) + .collect() } fn set_blockhash(&mut self, block_number: U256, block_hash: B256) { diff --git a/crates/forge/tests/it/test_helpers.rs b/crates/forge/tests/it/test_helpers.rs index 5b714f887b37f..058391658c30b 100644 --- a/crates/forge/tests/it/test_helpers.rs +++ b/crates/forge/tests/it/test_helpers.rs @@ -211,6 +211,8 @@ impl ForgeTestData { // Create resolc config with resolc compilation enabled let mut resolc_config = (*config).clone(); resolc_config.polkadot.resolc_compile = true; + // Set resolc version to 0.3.0 + resolc_config.polkadot.resolc = Some(foundry_config::SolcReq::Version("0.4.1".parse().unwrap())); let mut resolc_project = resolc_config.project().unwrap(); // Filter files compatible with resolc diff --git a/crates/revive-strategy/src/state.rs b/crates/revive-strategy/src/state.rs index f6d5bcba64767..8323a056d226e 100644 --- a/crates/revive-strategy/src/state.rs +++ b/crates/revive-strategy/src/state.rs @@ -148,9 +148,6 @@ impl TestEnv { ecx: Ecx<'_, '_, '_>, ) -> Result { self.0.lock().unwrap().externalities.execute_with(|| { - let origin_address = H160::from_slice(ecx.tx.caller.as_slice()); - let origin_account = AccountId::to_fallback_account_id(&origin_address); - let target_address = H160::from_slice(target.as_slice()); let target_account = AccountId::to_fallback_account_id(&target_address); @@ -158,7 +155,7 @@ impl TestEnv { let code_type = if code.starts_with(b"PVM\0") { BytecodeType::Pvm } else { BytecodeType::Evm }; let contract_blob = Pallet::::try_upload_code( - origin_account, + Pallet::::account_id(), code, code_type, BalanceOf::::MAX, diff --git a/testdata/forge-std-rev b/testdata/forge-std-rev index 213471e492846..7e643990d82d5 100644 --- a/testdata/forge-std-rev +++ b/testdata/forge-std-rev @@ -1 +1 @@ -1801b0541f4fda118a10798fd3486bb7051c5dd6 \ No newline at end of file +7117c90c8cf6c68e5acce4f09a6b24715cea4de6 \ No newline at end of file From 811e9032815d139c5ba1d6268bb1edb88e10d3d8 Mon Sep 17 00:00:00 2001 From: Sebastian Miasojed Date: Thu, 15 Jan 2026 16:37:18 +0100 Subject: [PATCH 3/6] Remove comment --- crates/forge/tests/it/test_helpers.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/crates/forge/tests/it/test_helpers.rs b/crates/forge/tests/it/test_helpers.rs index 058391658c30b..f8ef8f3978762 100644 --- a/crates/forge/tests/it/test_helpers.rs +++ b/crates/forge/tests/it/test_helpers.rs @@ -211,7 +211,6 @@ impl ForgeTestData { // Create resolc config with resolc compilation enabled let mut resolc_config = (*config).clone(); resolc_config.polkadot.resolc_compile = true; - // Set resolc version to 0.3.0 resolc_config.polkadot.resolc = Some(foundry_config::SolcReq::Version("0.4.1".parse().unwrap())); let mut resolc_project = resolc_config.project().unwrap(); From 3797e95685eaeb992ea34c7dfa1b70592d1d447b Mon Sep 17 00:00:00 2001 From: Sebastian Miasojed Date: Thu, 15 Jan 2026 20:30:13 +0100 Subject: [PATCH 4/6] Fix tests --- crates/evm/core/src/backend/mod.rs | 4 +++- crates/forge/tests/it/test_helpers.rs | 3 ++- crates/revive-strategy/src/cheatcodes/mod.rs | 24 +++++++++++--------- crates/revive-strategy/src/state.rs | 9 ++------ 4 files changed, 20 insertions(+), 20 deletions(-) diff --git a/crates/evm/core/src/backend/mod.rs b/crates/evm/core/src/backend/mod.rs index a2ea41d1e0c08..ef34f8164978d 100644 --- a/crates/evm/core/src/backend/mod.rs +++ b/crates/evm/core/src/backend/mod.rs @@ -1520,7 +1520,9 @@ impl DatabaseExt for Backend { } fn cached_accounts(&self) -> Vec
{ - self.mem_db.cache.accounts + self.mem_db + .cache + .accounts .iter() .filter_map(|(addr, acc)| { // Only include accounts with non-empty bytecode (actual contracts) diff --git a/crates/forge/tests/it/test_helpers.rs b/crates/forge/tests/it/test_helpers.rs index f8ef8f3978762..f5ae26eb5e82f 100644 --- a/crates/forge/tests/it/test_helpers.rs +++ b/crates/forge/tests/it/test_helpers.rs @@ -211,7 +211,8 @@ impl ForgeTestData { // Create resolc config with resolc compilation enabled let mut resolc_config = (*config).clone(); resolc_config.polkadot.resolc_compile = true; - resolc_config.polkadot.resolc = Some(foundry_config::SolcReq::Version("0.4.1".parse().unwrap())); + resolc_config.polkadot.resolc = + Some(foundry_config::SolcReq::Version("0.4.1".parse().unwrap())); let mut resolc_project = resolc_config.project().unwrap(); // Filter files compatible with resolc diff --git a/crates/revive-strategy/src/cheatcodes/mod.rs b/crates/revive-strategy/src/cheatcodes/mod.rs index f4679145df32e..c6b375b45e271 100644 --- a/crates/revive-strategy/src/cheatcodes/mod.rs +++ b/crates/revive-strategy/src/cheatcodes/mod.rs @@ -445,7 +445,7 @@ impl CheatcodeInspectorStrategyRunner for PvmCheatcodeInspectorStrategyRunner { cheatcode.as_any().downcast_ref().unwrap(); let ctx = get_context_ref_mut(ccx.state.strategy.context.as_mut()); - ctx.externalities.etch_call(target, newRuntimeBytecode, ccx.ecx)?; + ctx.externalities.etch_call(target, newRuntimeBytecode)?; Ok(Default::default()) } @@ -678,17 +678,19 @@ fn select_revive( &data.cfg.chain_id, ); let test_contract_addr = data.journaled_state.database.get_test_contract_address(); + let mut accounts = data.journaled_state.database.persistent_accounts().clone(); + accounts.insert(data.tx.caller); + + if migrate_all { + // Migrate all cached contracts + accounts.extend( + data.journaled_state + .database + .cached_accounts() + ); + } - let accounts_to_migrate: Vec
= if migrate_all { - // Migrate all accounts (including contract-level deployments) - // Get accounts from the database cache (includes all created/modified accounts) - data.journaled_state.database.cached_accounts() - } else { - let persistent_accounts = data.journaled_state.database.persistent_accounts().clone(); - persistent_accounts.into_iter().chain([data.tx.caller]).collect() - }; - - for address in accounts_to_migrate { + for address in accounts { tracing::info!("Migrating account {:?} (is_test_contract: {})", address, test_contract_addr == Some(address)); let acc = data.journaled_state.load_account(address).expect("failed to load account"); let amount = acc.data.info.balance; diff --git a/crates/revive-strategy/src/state.rs b/crates/revive-strategy/src/state.rs index 8323a056d226e..8d5707b986f46 100644 --- a/crates/revive-strategy/src/state.rs +++ b/crates/revive-strategy/src/state.rs @@ -1,5 +1,5 @@ use alloy_primitives::{Address, B256, Bytes, FixedBytes, U256}; -use foundry_cheatcodes::{Ecx, Error, Result}; +use foundry_cheatcodes::{Error, Result}; use polkadot_sdk::{ pallet_revive::{ self, AccountInfo, AddressMapper, BalanceOf, BytecodeType, ContractInfo, ExecConfig, @@ -141,12 +141,7 @@ impl TestEnv { }); } - pub fn etch_call( - &mut self, - target: &Address, - new_runtime_code: &Bytes, - ecx: Ecx<'_, '_, '_>, - ) -> Result { + pub fn etch_call(&mut self, target: &Address, new_runtime_code: &Bytes) -> Result { self.0.lock().unwrap().externalities.execute_with(|| { let target_address = H160::from_slice(target.as_slice()); let target_account = AccountId::to_fallback_account_id(&target_address); From 5263084b0dda415a85e58db5138cbe817a6df53b Mon Sep 17 00:00:00 2001 From: Sebastian Miasojed Date: Thu, 15 Jan 2026 20:55:43 +0100 Subject: [PATCH 5/6] Fmt --- crates/forge/tests/it/revive/migration.rs | 2 +- crates/revive-strategy/src/cheatcodes/mod.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/forge/tests/it/revive/migration.rs b/crates/forge/tests/it/revive/migration.rs index fc3adbefc9c20..f3f2029c0d566 100644 --- a/crates/forge/tests/it/revive/migration.rs +++ b/crates/forge/tests/it/revive/migration.rs @@ -153,7 +153,7 @@ async fn test_contract_deployment_in_different_modes(#[case] runtime_mode: Reviv } #[rstest] -//#[case::pvm(ReviveRuntimeMode::Pvm)] +#[case::pvm(ReviveRuntimeMode::Pvm)] #[case::evm(ReviveRuntimeMode::Evm)] #[tokio::test(flavor = "multi_thread")] async fn test_initial_contract_deployment(#[case] runtime_mode: ReviveRuntimeMode) { diff --git a/crates/revive-strategy/src/cheatcodes/mod.rs b/crates/revive-strategy/src/cheatcodes/mod.rs index c6b375b45e271..10b6ef8aa3625 100644 --- a/crates/revive-strategy/src/cheatcodes/mod.rs +++ b/crates/revive-strategy/src/cheatcodes/mod.rs @@ -689,7 +689,7 @@ fn select_revive( .cached_accounts() ); } - + for address in accounts { tracing::info!("Migrating account {:?} (is_test_contract: {})", address, test_contract_addr == Some(address)); let acc = data.journaled_state.load_account(address).expect("failed to load account"); From a734ce21b407e073f7ac5ad0e00e8badefddd0c2 Mon Sep 17 00:00:00 2001 From: Sebastian Miasojed Date: Fri, 16 Jan 2026 15:09:09 +0100 Subject: [PATCH 6/6] Fmt --- testdata/default/revive/EvmToReviveMigration.t.sol | 1 + 1 file changed, 1 insertion(+) diff --git a/testdata/default/revive/EvmToReviveMigration.t.sol b/testdata/default/revive/EvmToReviveMigration.t.sol index c5a6caf3da1b1..268a2580b01f9 100644 --- a/testdata/default/revive/EvmToReviveMigration.t.sol +++ b/testdata/default/revive/EvmToReviveMigration.t.sol @@ -369,6 +369,7 @@ contract EvmReviveMigrationTest is DSTest { assertEq(evmContract.get(), 250, "EVM contract should update in PVM mode"); } } + contract InitialMigrationTest is DSTest { Vm constant vm = Vm(address(uint160(uint256(keccak256("hevm cheat code"))))); SimpleStorage storageContract = new SimpleStorage();