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..ef34f8164978d 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,22 @@ impl DatabaseExt for Backend { self.inner.cheatcode_access_accounts.contains(account) } + fn cached_accounts(&self) -> Vec
{ + 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) { 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..f3f2029c0d566 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/forge/tests/it/test_helpers.rs b/crates/forge/tests/it/test_helpers.rs index 5b714f887b37f..f5ae26eb5e82f 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; + 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 e4350caa33c1c..e950fc7181dce 100644 --- a/crates/revive-strategy/src/cheatcodes/mod.rs +++ b/crates/revive-strategy/src/cheatcodes/mod.rs @@ -448,7 +448,7 @@ impl CheatcodeInspectorStrategyRunner for PvmCheatcodeInspectorStrategyRunner { } 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)?; cheatcode.dyn_apply(ccx, executor) } @@ -564,7 +564,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"); } @@ -645,7 +645,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 @@ -654,7 +654,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; @@ -677,9 +681,20 @@ 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 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() + ); + } + + 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 8d6723c5091ef..006df58c7537e 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, AccountId32Mapper, AccountInfo, AddressMapper, BalanceOf, BytecodeType, ContractInfo, @@ -146,17 +146,8 @@ 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 origin_address = H160::from_slice(ecx.tx.caller.as_slice()); - let origin_account = - AccountId32Mapper::::to_fallback_account_id(&origin_address); - let target_address = H160::from_slice(target.as_slice()); let target_account = AccountId32Mapper::::to_fallback_account_id(&target_address); @@ -165,7 +156,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, &mut ResourceMeter::new(pallet_revive::TransactionLimits::WeightAndDeposit { diff --git a/testdata/default/revive/EvmToReviveMigration.t.sol b/testdata/default/revive/EvmToReviveMigration.t.sol index a5931796dd281..268a2580b01f9 100644 --- a/testdata/default/revive/EvmToReviveMigration.t.sol +++ b/testdata/default/revive/EvmToReviveMigration.t.sol @@ -369,3 +369,18 @@ 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); + } +}