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);
+ }
+}