1mod mock_handler;
2
3use alloy_primitives::{Address, B256, Bytes, Log, hex, ruint::aliases::U256};
4use alloy_rpc_types::BlobTransactionSidecar;
5use alloy_sol_types::SolValue;
6use foundry_cheatcodes::{
7 Broadcast, BroadcastableTransactions, CheatcodeInspectorStrategy,
8 CheatcodeInspectorStrategyContext, CheatcodeInspectorStrategyRunner, CheatsConfig, CheatsCtxt,
9 CommonCreateInput, DynCheatcode, Ecx, EvmCheatcodeInspectorStrategyRunner, Result,
10 Vm::{
11 AccountAccessKind, chainIdCall, coinbaseCall, dealCall, etchCall, getNonce_0Call, loadCall,
12 polkadot_0Call, polkadot_1Call, polkadotSkipCall, resetNonceCall,
13 revertToStateAndDeleteCall, revertToStateCall, rollCall, setBlockhashCall, setNonceCall,
14 setNonceUnsafeCall, snapshotStateCall, storeCall, warpCall,
15 },
16 journaled_account, precompile_error,
17};
18
19use foundry_compilers::resolc::dual_compiled_contracts::DualCompiledContracts;
20use foundry_evm::constants::CHEATCODE_ADDRESS;
21use revive_env::{Runtime, System, Timestamp};
22use std::{
23 any::{Any, TypeId},
24 sync::Arc,
25};
26use tracing::warn;
27
28use alloy_eips::eip7702::SignedAuthorization;
29use polkadot_sdk::{
30 pallet_revive::{
31 self, AccountId32Mapper, AccountInfo, AddressMapper, BytecodeType, Code, ContractInfo,
32 DebugSettings, ExecConfig, Executable, Pallet, ResourceMeter, evm::CallTrace,
33 },
34 polkadot_sdk_frame::prelude::OriginFor,
35 sp_core::{self, H160},
36 sp_weights::Weight,
37};
38
39use crate::{
40 cheatcodes::mock_handler::MockHandlerImpl,
41 state::TestEnv,
42 tracing::{Tracer, storage_tracer::AccountAccess},
43};
44use foundry_cheatcodes::Vm::{AccountAccess as FAccountAccess, ChainInfo};
45use polkadot_sdk::pallet_revive::tracing::Tracing;
46
47use revm::{
48 bytecode::opcode as op,
49 context::{CreateScheme, JournalTr},
50 interpreter::{
51 CallInputs, CallOutcome, CallScheme, CreateOutcome, Gas, InstructionResult, Interpreter,
52 InterpreterResult, interpreter_types::Jumps,
53 },
54 state::Bytecode,
55};
56pub trait PvmCheatcodeInspectorStrategyBuilder {
57 fn new_pvm(
58 dual_compiled_contracts: DualCompiledContracts,
59 runtime_mode: crate::ReviveRuntimeMode,
60 externalities: TestEnv,
61 ) -> Self;
62}
63impl PvmCheatcodeInspectorStrategyBuilder for CheatcodeInspectorStrategy {
64 fn new_pvm(
66 dual_compiled_contracts: DualCompiledContracts,
67 runtime_mode: crate::ReviveRuntimeMode,
68 externalities: TestEnv,
69 ) -> Self {
70 Self {
71 runner: &PvmCheatcodeInspectorStrategyRunner,
72 context: Box::new(PvmCheatcodeInspectorStrategyContext::new(
73 dual_compiled_contracts,
74 runtime_mode,
75 externalities,
76 )),
77 }
78 }
79}
80
81#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
83pub enum ReviveStartupMigration {
84 Defer,
87 #[default]
90 Allow,
91 Done,
94}
95
96impl ReviveStartupMigration {
97 pub fn is_allowed(&self) -> bool {
99 matches!(self, Self::Allow)
100 }
101
102 pub fn allow(&mut self) {
104 *self = Self::Allow;
105 }
106
107 pub fn done(&mut self) {
109 *self = Self::Done;
110 }
111}
112#[derive(Debug, Default, Clone)]
114pub struct PvmCheatcodeInspectorStrategyContext {
115 pub using_revive: bool,
117 pub skip_revive: bool,
119 pub skip_revive_addresses: std::collections::HashSet<Address>,
124 pub record_next_create_address: bool,
126 pub revive_startup_migration: ReviveStartupMigration,
128 pub dual_compiled_contracts: DualCompiledContracts,
129 pub runtime_mode: crate::ReviveRuntimeMode,
131 pub remove_recorded_access_at: Option<usize>,
132 pub externalities: TestEnv,
133}
134
135impl PvmCheatcodeInspectorStrategyContext {
136 pub fn new(
137 dual_compiled_contracts: DualCompiledContracts,
138 runtime_mode: crate::ReviveRuntimeMode,
139 externalities: TestEnv,
140 ) -> Self {
141 Self {
142 using_revive: false,
144 skip_revive: false,
145 skip_revive_addresses: Default::default(),
146 record_next_create_address: Default::default(),
147 revive_startup_migration: ReviveStartupMigration::Defer,
149 dual_compiled_contracts,
150 runtime_mode,
151 remove_recorded_access_at: None,
152 externalities,
153 }
154 }
155}
156
157impl CheatcodeInspectorStrategyContext for PvmCheatcodeInspectorStrategyContext {
158 fn new_cloned(&self) -> Box<dyn CheatcodeInspectorStrategyContext> {
159 Box::new(self.clone())
160 }
161
162 fn as_any_mut(&mut self) -> &mut dyn Any {
163 self
164 }
165
166 fn as_any_ref(&self) -> &dyn Any {
167 self
168 }
169}
170
171#[derive(Debug, Default, Clone)]
173pub struct PvmCheatcodeInspectorStrategyRunner;
174
175impl PvmCheatcodeInspectorStrategyRunner {
176 fn append_recorded_accesses(
177 &self,
178 state: &mut foundry_cheatcodes::Cheatcodes,
179 ecx: Ecx<'_, '_, '_>,
180 account_accesses: Vec<AccountAccess>,
181 ) {
182 let ctx: &mut PvmCheatcodeInspectorStrategyContext =
183 get_context_ref_mut(state.strategy.context.as_mut());
184 if state.recording_accesses {
186 for record in &account_accesses {
187 for r in &record.storage_accesses {
188 if !r.isWrite {
189 state.accesses.record_read(
190 Address::from(record.account.0),
191 alloy_primitives::U256::from_be_slice(r.slot.clone().as_slice()),
192 );
193 } else {
194 state.accesses.record_write(
195 Address::from(record.account.0),
196 alloy_primitives::U256::from_be_slice(r.slot.clone().as_slice()),
197 );
198 }
199 }
200 }
201 }
202
203 if let Some(recorded_account_diffs_stack) = state.recorded_account_diffs_stack.as_mut() {
204 let stack_insert_index = if recorded_account_diffs_stack.len() > 1 {
221 recorded_account_diffs_stack
222 .get(recorded_account_diffs_stack.len() - 2)
223 .map_or(0, Vec::len)
224 } else {
225 recorded_account_diffs_stack.first().map_or(0, |v| v.len().saturating_sub(1))
227 };
228
229 if let Some(last) = recorded_account_diffs_stack.last_mut() {
230 ctx.remove_recorded_access_at = Some(stack_insert_index);
231 for record in account_accesses {
232 let _ = ecx
233 .journaled_state
234 .load_account(Address::from(record.account.0))
235 .expect("failed to load account");
236
237 let access = FAccountAccess {
238 chainInfo: ChainInfo {
239 forkId: ecx
240 .journaled_state
241 .database
242 .active_fork_id()
243 .unwrap_or_default(),
244 chainId: U256::from(ecx.cfg.chain_id),
245 },
246 accessor: Address::from(record.accessor.0),
247 account: Address::from(record.account.0),
248 kind: record.kind,
249 initialized: record.initialized,
250 oldBalance: U256::from_limbs(record.old_balance.0),
251 newBalance: ecx
252 .journaled_state
253 .account(Address::from(record.account.0))
254 .info
255 .balance,
256 value: U256::from_limbs(record.value.0),
257 data: record.data.clone(),
258 reverted: record.reverted,
259 deployedCode: if matches!(record.kind, AccountAccessKind::Create) {
260 ctx.externalities
261 .execute_with(|| {
262 Some(Bytes::from(Pallet::<Runtime>::code(&H160::from(
263 record.account.0,
264 ))))
265 })
266 .unwrap_or_else(|| Bytes::from(record.data.0.to_vec()))
267 } else {
268 Bytes::new()
269 },
270 storageAccesses: record.storage_accesses,
271 depth: record.depth,
272 };
273 last.push(access);
274 }
275 }
276 }
277 }
278}
279
280impl CheatcodeInspectorStrategyRunner for PvmCheatcodeInspectorStrategyRunner {
281 fn apply_full(
282 &self,
283 cheatcode: &dyn foundry_cheatcodes::DynCheatcode,
284 ccx: &mut CheatsCtxt<'_, '_, '_, '_>,
285 executor: &mut dyn foundry_cheatcodes::CheatcodesExecutor,
286 ) -> Result {
287 fn is<T: std::any::Any>(t: TypeId) -> bool {
288 TypeId::of::<T>() == t
289 }
290 let ctx: &mut PvmCheatcodeInspectorStrategyContext =
291 get_context_ref_mut(ccx.state.strategy.context.as_mut());
292 let using_revive = ctx.using_revive;
293 match cheatcode.as_any().type_id() {
294 t if is::<polkadot_0Call>(t) => {
295 tracing::info!(cheatcode = ?cheatcode.as_debug(), using_revive = ?using_revive);
297 let polkadot_0Call { enable, backend } = cheatcode.as_any().downcast_ref().unwrap();
298 let ctx: &mut PvmCheatcodeInspectorStrategyContext =
299 get_context_ref_mut(ccx.state.strategy.context.as_mut());
300 handle_polkadot_call(ctx, ccx.ecx, *enable, backend)
301 }
302 t if is::<polkadot_1Call>(t) => {
303 tracing::info!(cheatcode = ?cheatcode.as_debug(), using_revive = ?using_revive);
305 let polkadot_1Call { enable } = cheatcode.as_any().downcast_ref().unwrap();
306 let ctx: &mut PvmCheatcodeInspectorStrategyContext =
307 get_context_ref_mut(ccx.state.strategy.context.as_mut());
308 handle_polkadot_call(ctx, ccx.ecx, *enable, "")
309 }
310 t if is::<polkadotSkipCall>(t) => {
311 tracing::info!(cheatcode = ?cheatcode.as_debug(), using_revive = ?using_revive);
312 let polkadotSkipCall { .. } = cheatcode.as_any().downcast_ref().unwrap();
313 let ctx = get_context_ref_mut(ccx.state.strategy.context.as_mut());
314 ctx.skip_revive = true;
315 Ok(Default::default())
316 }
317 t if using_revive && is::<dealCall>(t) => {
318 tracing::info!(cheatcode = ?cheatcode.as_debug() , using_revive = ?using_revive);
319 let dealCall { account, newBalance } = cheatcode.as_any().downcast_ref().unwrap();
320
321 let clamped_balance = ctx.externalities.set_balance(*account, *newBalance);
322 let clamped_deal = dealCall { account: *account, newBalance: clamped_balance };
323 clamped_deal.dyn_apply(ccx, executor)
324 }
325 t if using_revive && is::<setNonceCall>(t) => {
326 tracing::info!(cheatcode = ?cheatcode.as_debug() , using_revive = ?using_revive);
327
328 let &setNonceCall { account, newNonce } =
329 cheatcode.as_any().downcast_ref().unwrap();
330 ctx.externalities.set_nonce(account, newNonce);
331
332 cheatcode.dyn_apply(ccx, executor)
333 }
334 t if using_revive && is::<setNonceUnsafeCall>(t) => {
335 tracing::info!(cheatcode = ?cheatcode.as_debug() , using_revive = ?using_revive);
336
337 let &setNonceUnsafeCall { account, newNonce } =
338 cheatcode.as_any().downcast_ref().unwrap();
339 ctx.externalities.set_nonce(account, newNonce);
340
341 cheatcode.dyn_apply(ccx, executor)
342 }
343 t if using_revive && is::<resetNonceCall>(t) => {
344 tracing::info!(cheatcode = ?cheatcode.as_debug() , using_revive = ?using_revive);
345 let &resetNonceCall { account } = cheatcode.as_any().downcast_ref().unwrap();
346 ctx.externalities.set_nonce(account, 0);
347 cheatcode.dyn_apply(ccx, executor)
348 }
349 t if using_revive && is::<getNonce_0Call>(t) => {
350 tracing::info!(cheatcode = ?cheatcode.as_debug() , using_revive = ?using_revive);
351 let &getNonce_0Call { account } = cheatcode.as_any().downcast_ref().unwrap();
352 let ctx = get_context_ref_mut(ccx.state.strategy.context.as_mut());
353 let nonce = ctx.externalities.get_nonce(account);
354 Ok(u64::from(nonce).abi_encode())
355 }
356 t if using_revive && is::<rollCall>(t) => {
357 let &rollCall { newHeight } = cheatcode.as_any().downcast_ref().unwrap();
358 let clamped_height =
359 ctx.externalities.roll(newHeight, &mut *ccx.ecx.journaled_state.database);
360
361 rollCall { newHeight: clamped_height }.dyn_apply(ccx, executor)
362 }
363 t if using_revive && is::<snapshotStateCall>(t) => {
364 ctx.externalities.start_snapshotting();
365 cheatcode.dyn_apply(ccx, executor)
366 }
367 t if using_revive && is::<revertToStateAndDeleteCall>(t) => {
368 let &revertToStateAndDeleteCall { snapshotId } =
369 cheatcode.as_any().downcast_ref().unwrap();
370
371 ctx.externalities.revert(snapshotId.try_into().unwrap());
372 cheatcode.dyn_apply(ccx, executor)
373 }
374 t if using_revive && is::<revertToStateCall>(t) => {
375 let &revertToStateCall { snapshotId } = cheatcode.as_any().downcast_ref().unwrap();
376
377 ctx.externalities.revert(snapshotId.try_into().unwrap());
378 cheatcode.dyn_apply(ccx, executor)
379 }
380 t if using_revive && is::<warpCall>(t) => {
381 let &warpCall { newTimestamp } = cheatcode.as_any().downcast_ref().unwrap();
382
383 let clamped_timestamp = ctx.externalities.set_timestamp(newTimestamp);
384
385 warpCall { newTimestamp: clamped_timestamp }.dyn_apply(ccx, executor)
386 }
387
388 t if using_revive && is::<chainIdCall>(t) => {
389 let &chainIdCall { newChainId } = cheatcode.as_any().downcast_ref().unwrap();
390
391 tracing::info!(cheatcode = ?cheatcode.as_debug() , using_revive = ?using_revive);
392 ctx.externalities.set_chain_id(newChainId.to());
393
394 cheatcode.dyn_apply(ccx, executor)
395 }
396 t if using_revive && is::<coinbaseCall>(t) => {
397 let &coinbaseCall { newCoinbase } = cheatcode.as_any().downcast_ref().unwrap();
398
399 tracing::info!(cheatcode = ?cheatcode.as_debug() , using_revive = ?using_revive);
400 ctx.externalities.set_block_author(newCoinbase);
401
402 cheatcode.dyn_apply(ccx, executor)
403 }
404 t if is::<setBlockhashCall>(t) => {
405 let &setBlockhashCall { blockNumber, blockHash } =
406 cheatcode.as_any().downcast_ref().unwrap();
407
408 tracing::info!(cheatcode = ?cheatcode.as_debug(), using_revive = ?using_revive);
409 let u64_max: U256 = U256::from(u64::MAX);
410 let clamped_block_number = if blockNumber > u64_max {
411 tracing::warn!(
412 blockNumber = ?blockNumber,
413 max = ?u64_max,
414 "Block number exceeds u64::MAX. Clamping to u64::MAX."
415 );
416 u64_max
417 } else {
418 blockNumber
419 };
420
421 let current_block = ctx.externalities.get_block_number();
423 if clamped_block_number > current_block {
424 return Err(foundry_cheatcodes::Error::from(
425 "block number must be less than or equal to the current block number",
426 ));
427 }
428
429 ctx.externalities.set_blockhash(clamped_block_number.to(), blockHash);
430
431 setBlockhashCall { blockNumber: clamped_block_number, blockHash }
432 .dyn_apply(ccx, executor)
433 }
434 t if using_revive && is::<etchCall>(t) => {
435 let etchCall { target, newRuntimeBytecode } =
436 cheatcode.as_any().downcast_ref().unwrap();
437
438 if ccx.is_precompile(target) {
439 return Err(precompile_error(target));
440 }
441
442 let ctx = get_context_ref_mut(ccx.state.strategy.context.as_mut());
443 ctx.externalities.etch_call(target, newRuntimeBytecode)?;
444
445 cheatcode.dyn_apply(ccx, executor)
446 }
447
448 t if is::<etchCall>(t) => {
449 let etchCall { target, newRuntimeBytecode: _ } =
450 cheatcode.as_any().downcast_ref().unwrap();
451 ccx.ecx.journaled_state.database.add_persistent_account(*target);
455
456 cheatcode.dyn_apply(ccx, executor)
457 }
458 t if using_revive && is::<loadCall>(t) => {
459 tracing::info!(cheatcode = ?cheatcode.as_debug() , using_revive = ?using_revive);
460 let &loadCall { target, slot } = cheatcode.as_any().downcast_ref().unwrap();
461
462 if ccx
464 .ecx
465 .journaled_state
466 .database
467 .get_test_contract_address()
468 .map(|addr| target == addr)
469 .unwrap_or_default()
470 {
471 cheatcode.dyn_apply(ccx, executor)
472 } else {
473 let ctx = get_context_ref_mut(ccx.state.strategy.context.as_mut());
474 let storage_value = ctx.externalities.get_storage(target, slot)?;
475 let result = storage_value.map(|b| B256::from_slice(&b)).unwrap_or(B256::ZERO);
476 Ok(result.abi_encode())
477 }
478 }
479 t if using_revive && is::<storeCall>(t) => {
480 tracing::info!(cheatcode = ?cheatcode.as_debug() , using_revive = ?using_revive);
481 let &storeCall { target, slot, value } = cheatcode.as_any().downcast_ref().unwrap();
482 if ccx.is_precompile(&target) {
483 return Err(precompile_error(&target));
484 }
485 let ctx = get_context_ref_mut(ccx.state.strategy.context.as_mut());
486 if target != CHEATCODE_ADDRESS {
487 ctx.externalities.set_storage(target, slot, value)?;
488 }
489 cheatcode.dyn_apply(ccx, executor)
490 }
491 _ => cheatcode.dyn_apply(ccx, executor),
493 }
494 }
495
496 fn base_contract_deployed(&self, ctx: &mut dyn CheatcodeInspectorStrategyContext) {
497 let ctx = get_context_ref_mut(ctx);
498
499 tracing::debug!("allowing startup PVM migration");
500 ctx.revive_startup_migration.allow();
501 }
502
503 fn record_broadcastable_create_transactions(
504 &self,
505 _ctx: &mut dyn CheatcodeInspectorStrategyContext,
506 config: Arc<CheatsConfig>,
507 input: &dyn CommonCreateInput,
508 ecx_inner: Ecx<'_, '_, '_>,
509 broadcast: &Broadcast,
510 broadcastable_transactions: &mut BroadcastableTransactions,
511 ) {
512 EvmCheatcodeInspectorStrategyRunner.record_broadcastable_create_transactions(
515 _ctx,
516 config,
517 input,
518 ecx_inner,
519 broadcast,
520 broadcastable_transactions,
521 );
522 }
523
524 fn record_broadcastable_call_transactions(
525 &self,
526 _ctx: &mut dyn CheatcodeInspectorStrategyContext,
527 config: Arc<CheatsConfig>,
528 call: &CallInputs,
529 ecx_inner: Ecx<'_, '_, '_>,
530 broadcast: &Broadcast,
531 broadcastable_transactions: &mut BroadcastableTransactions,
532 active_delegations: Vec<SignedAuthorization>,
533 active_blob_sidecar: Option<BlobTransactionSidecar>,
534 ) {
535 EvmCheatcodeInspectorStrategyRunner.record_broadcastable_call_transactions(
538 _ctx,
539 config,
540 call,
541 ecx_inner,
542 broadcast,
543 broadcastable_transactions,
544 active_delegations,
545 active_blob_sidecar,
546 );
547 }
548
549 fn post_initialize_interp(
550 &self,
551 ctx: &mut dyn CheatcodeInspectorStrategyContext,
552 _interpreter: &mut Interpreter,
553 ecx: Ecx<'_, '_, '_>,
554 ) {
555 let ctx = get_context_ref_mut(ctx);
556
557 if ctx.revive_startup_migration.is_allowed() && !ctx.using_revive {
558 tracing::info!("startup pallet-revive migration initiated");
559 select_revive(ctx, ecx, true);
560 ctx.revive_startup_migration.done();
561 tracing::info!("startup pallet-revive migration completed");
562 }
563 }
564
565 fn pre_step_end(
566 &self,
567 ctx: &mut dyn CheatcodeInspectorStrategyContext,
568 interpreter: &mut Interpreter,
569 _ecx: Ecx<'_, '_, '_>,
570 ) -> bool {
571 let ctx = get_context_ref_mut(ctx);
572
573 if !ctx.using_revive {
574 return false;
575 }
576
577 let address = match interpreter.bytecode.opcode() {
578 op::SELFBALANCE => interpreter.input.target_address,
579 op::BALANCE => {
580 if interpreter.stack.is_empty() {
581 return true;
582 }
583
584 Address::from_word(B256::from(unsafe { interpreter.stack.pop_unsafe() }))
585 }
586 _ => return true,
587 };
588
589 let balance = ctx.externalities.get_balance(address);
590 tracing::info!(operation = "get_balance" , using_revive = ?ctx.using_revive, target = ?address, balance = ?balance);
591
592 if interpreter.stack.push(balance) {
594 interpreter.bytecode.relative_jump(1);
595 } else {
596 }
598
599 false }
601}
602
603fn handle_polkadot_call(
604 ctx: &mut PvmCheatcodeInspectorStrategyContext,
605 data: Ecx<'_, '_, '_>,
606 enable: bool,
607 backend: &str,
608) -> Result {
609 if enable {
610 let target_mode = match backend.to_lowercase().as_str() {
613 "" => ctx.runtime_mode, "evm" => crate::ReviveRuntimeMode::Evm,
615 "pvm" => {
616 tracing::warn!(
617 "Using 'pvm' backend is an experimental feature and may lead to unexpected behavior in tests."
618 );
619 crate::ReviveRuntimeMode::Pvm
620 }
621 _ => return Err(format!("Invalid backend: '{backend}'. Use 'evm' or 'pvm'").into()),
622 };
623
624 let is_backend_switch = target_mode != ctx.runtime_mode && ctx.using_revive;
626
627 if is_backend_switch
630 && ctx.dual_compiled_contracts.is_empty()
631 && target_mode == crate::ReviveRuntimeMode::Pvm
632 {
633 return Err(
634 "Backend switching to PVM requires running tests with --polkadot and --resolc flags".into(),
635 );
636 }
637 ctx.runtime_mode = target_mode;
638 if !is_backend_switch {
639 select_revive(ctx, data, false);
641 }
642 } else if ctx.using_revive {
643 select_evm(ctx, data);
645 }
646 Ok(Default::default())
647}
648
649fn select_revive(
650 ctx: &mut PvmCheatcodeInspectorStrategyContext,
651 data: Ecx<'_, '_, '_>,
652 migrate_all: bool,
653) {
654 if ctx.using_revive {
655 tracing::info!("already using pallet-revive");
656 return;
657 }
658
659 tracing::info!("switching to pallet-revive ({} mode)", ctx.runtime_mode);
660 ctx.using_revive = true;
661
662 let block_number = data.block.number;
663 let timestamp = data.block.timestamp;
664 ctx.externalities.set_timestamp(timestamp);
665 ctx.externalities.roll(block_number, &mut *data.journaled_state.database);
666
667 ctx.externalities.execute_with(||{
668 if data.cfg.limit_contract_code_size == Some(usize::MAX) {
670 let debug_settings = DebugSettings::new(true, true, true);
671 debug_settings.write_to_storage::<Runtime>();
672 }
673 <revive_env::Runtime as polkadot_sdk::pallet_revive::Config>::ChainId::set(
674 &data.cfg.chain_id,
675 );
676 let test_contract_addr = data.journaled_state.database.get_test_contract_address();
677 let mut accounts = data.journaled_state.database.persistent_accounts().clone();
678 accounts.insert(data.tx.caller);
679
680 if migrate_all {
681 accounts.extend(
683 data.journaled_state
684 .database
685 .cached_accounts()
686 );
687 }
688
689 for address in accounts {
690 tracing::info!("Migrating account {:?} (is_test_contract: {})", address, test_contract_addr == Some(address));
691 let acc = data.journaled_state.load_account(address).expect("failed to load account");
692
693 let amount = acc.info.balance;
694 let nonce = acc.info.nonce;
695 let account = H160::from_slice(address.as_slice());
696 let account_id =
697 AccountId32Mapper::<Runtime>::to_fallback_account_id(&account);
698
699 let u128_max: U256 = U256::from(u128::MAX);
700 let clamped_amount = if amount > u128_max {
701 tracing::info!(
702 address = ?address,
703 requested = ?amount,
704 actual = ?u128_max,
705 "Migration: balance exceeds u128::MAX, clamping to u128::MAX. \
706 pallet-revive uses u128 for balances."
707 );
708 u128_max
709 } else {
710 amount
711 };
712
713 let amount_pvm = sp_core::U256::from_little_endian(&clamped_amount.as_le_bytes());
714 Pallet::<Runtime>::set_evm_balance(&account, amount_pvm)
715 .expect("failed to set evm balance");
716
717 polkadot_sdk::frame_system::Account::<Runtime>::mutate(&account_id, |a| {
718 a.nonce = nonce.min(u32::MAX.into()).try_into().expect("shouldn't happen");
719 });
720
721 if let Some(bytecode) = acc.info.code.as_ref() {
722 let account_h160 = H160::from_slice(address.as_slice());
723
724 if AccountInfo::<Runtime>::load_contract(&account_h160).is_none() {
726 if let Some((_, contract)) = ctx.dual_compiled_contracts
728 .find_by_evm_deployed_bytecode_with_immutables(bytecode.original_byte_slice())
729 {
730 let effective_runtime_mode = if test_contract_addr == Some(address) {
733 crate::ReviveRuntimeMode::Evm
734 } else {
735 ctx.runtime_mode
736 };
737 let (code_bytes, immutable_data, code_type) = match effective_runtime_mode {
738 crate::ReviveRuntimeMode::Pvm => {
739 let immutable_data = contract.evm_immutable_references
740 .as_ref()
741 .map(|immutable_refs| {
742 let evm_bytecode = bytecode.original_byte_slice();
743
744 immutable_refs
746 .values().filter_map(|offsets| offsets.first())
747 .flat_map(|offset| {
748 let start = offset.start as usize;
749 let end = start + offset.length as usize;
750 evm_bytecode.get(start..end).unwrap_or_else(|| panic!("Immutable offset out of bounds: address={:?}, offset={}..{}, bytecode_len={}",
751 address, start, end, evm_bytecode.len())).iter().rev()
752 })
753 .copied()
754 .collect::<Vec<u8>>()
755 });
756 (contract.resolc_deployed_bytecode.as_bytes().map(|b| b.to_vec()),immutable_data, BytecodeType::Pvm)
757 },
758 crate::ReviveRuntimeMode::Evm => {
759 (Some(bytecode.bytecode().to_vec()), None, BytecodeType::Evm)
760 },
761 };
762
763 if let Some(code_bytes) = code_bytes {
764 let upload_result = Pallet::<Runtime>::try_upload_code(
765 Pallet::<Runtime>::account_id(),
766 code_bytes.clone(),
767 code_type,
768 &mut ResourceMeter::new(pallet_revive::TransactionLimits::WeightAndDeposit {
769 weight_limit: Weight::from_parts(10_000_000_000_000, 100_000_000),
770 deposit_limit: 100_000_000_000_000,
771 })
772 .unwrap(),
773 &ExecConfig::new_substrate_tx(),
774 );
775 match upload_result {
776 Ok(upload_res) => {
777 let code_hash = upload_res.code_hash().to_owned();
778 let contract_info = ContractInfo::<Runtime>::new(&account_h160, nonce as u32, code_hash)
779 .expect("Failed to create contract info");
780 AccountInfo::<Runtime>::insert_contract(&account_h160, contract_info);
781 if let Some(data) = immutable_data.and_then(|immutables| immutables.try_into().ok())
782 {
783 Pallet::<Runtime>::set_immutables(account_h160, data).expect("Failed to migrate immutables");
784 }
785 }
786 Err(err) => {
787 tracing::warn!(
788 address = ?address,
789 runtime_mode = ?ctx.runtime_mode,
790 bytecode_len = code_bytes.len(),
791 error = ?err,
792 "Failed to upload bytecode to pallet-revive, skipping migration"
793 );
794 }
795 }
796 } else {
797 tracing::info!(
798 address = ?address,
799 "no PVM equivalent found for EVM bytecode, skipping migration"
800 );
801 }
802 } else {
803 tracing::info!("Setting evm bytecode stored in account {:?} balance: {:?}", address, amount);
804 let code_bytes = bytecode.original_byte_slice().to_vec();
806 let upload_result = Pallet::<Runtime>::try_upload_code(
807 Pallet::<Runtime>::account_id(),
808 code_bytes.clone(),
809 BytecodeType::Evm,
810 &mut ResourceMeter::new(pallet_revive::TransactionLimits::WeightAndDeposit {
811 weight_limit: Weight::from_parts(10_000_000_000_000, 100_000_000),
812 deposit_limit: 100_000_000_000_000,
813 })
814 .unwrap(),
815 &ExecConfig::new_substrate_tx_without_bump(),
816 );
817 match upload_result {
818 Ok(upload_res) => {
819 let code_hash = upload_res.code_hash();
820 let contract_info = ContractInfo::<Runtime>::new(&account_h160, nonce as u32, code_hash.to_owned())
821 .expect("Failed to create contract info");
822 AccountInfo::<Runtime>::insert_contract(&account_h160, contract_info);
823 }
824 Err(err) => {
825 tracing::warn!(
826 address = ?address,
827 runtime_mode = ?ctx.runtime_mode,
828 bytecode_len = code_bytes.len(),
829 error = ?err,
830 "Failed to upload bytecode to pallet-revive, skipping migration"
831 );
832 }
833 }
834 }
835 }
836 if AccountInfo::<Runtime>::load_contract(&account_h160).is_some() {
837 migrate_contract_storage(data, address, account_h160);
838 }
839 }
840 }
841 });
842}
843
844fn migrate_contract_storage(data: Ecx<'_, '_, '_>, address: Address, account_h160: H160) {
854 use std::collections::HashSet;
855
856 let mut migrated_slots: HashSet<U256> = HashSet::new();
858
859 if let Some(account_state) = data.journaled_state.state.get(&address) {
861 for (slot, storage_slot) in &account_state.storage {
862 migrated_slots.insert(*slot);
863
864 let slot_bytes = slot.to_be_bytes::<32>();
865 let value = storage_slot.present_value;
866
867 if !value.is_zero() {
868 let _ = Pallet::<Runtime>::set_storage(
869 account_h160,
870 slot_bytes,
871 Some(value.to_be_bytes::<32>().to_vec()),
872 );
873 } else {
874 let _ = Pallet::<Runtime>::set_storage(account_h160, slot_bytes, None);
876 }
877 }
878 }
879
880 if let Some(cached_storage) = data.journaled_state.database.cached_storage(address) {
882 for (slot, value) in cached_storage {
883 if migrated_slots.contains(&slot) {
885 continue;
886 }
887
888 let slot_bytes = slot.to_be_bytes::<32>();
889 if !value.is_zero() {
890 let _ = Pallet::<Runtime>::set_storage(
891 account_h160,
892 slot_bytes,
893 Some(value.to_be_bytes::<32>().to_vec()),
894 );
895 } else {
896 let _ = Pallet::<Runtime>::set_storage(account_h160, slot_bytes, None);
898 }
899 }
900 }
901}
902
903fn select_evm(ctx: &mut PvmCheatcodeInspectorStrategyContext, data: Ecx<'_, '_, '_>) {
904 if !ctx.using_revive {
905 tracing::info!("already using REVM");
906 return;
907 }
908
909 tracing::info!("switching from pallet-revive back to REVM");
910 ctx.using_revive = false;
911
912 ctx.externalities.execute_with(|| {
913 let block_number = System::block_number();
914 let timestamp = Timestamp::get();
915
916 data.block.number = U256::from(block_number);
917 data.block.timestamp = U256::from(timestamp / 1000);
918
919 let test_contract = data.journaled_state.database.get_test_contract_address();
920 let persistent_accounts = data.journaled_state.database.persistent_accounts().clone();
921 for address in persistent_accounts.into_iter().chain([data.tx.caller]) {
922 let account_evm = H160::from_slice(address.as_slice());
923 let pallet_evm_nonce = Pallet::<Runtime>::evm_nonce(&account_evm);
924 let pallet_evm_balance = Pallet::<Runtime>::evm_balance(&account_evm);
925 let amount_evm = U256::from_limbs(pallet_evm_balance.0);
926 let account = journaled_account(data, address).expect("failed to load account");
927 account.info.balance = amount_evm;
928 account.info.nonce = pallet_evm_nonce as u64;
929
930 if test_contract != Some(address)
932 && let Some(info) = AccountInfo::<Runtime>::load_contract(&account_evm)
933 {
934 let hash = hex::encode(info.code_hash);
935
936 let bytecode_result = ctx
940 .dual_compiled_contracts
941 .find_by_resolc_bytecode_hash(hash.clone())
942 .and_then(|(_, contract)| {
943 contract.evm_deployed_bytecode.as_bytes().map(|evm_bytecode| {
944 (contract.evm_bytecode_hash, Bytecode::new_raw(evm_bytecode.clone()))
945 })
946 })
947 .or_else(|| {
948 ctx.dual_compiled_contracts.find_by_evm_bytecode_hash(hash).and_then(
949 |(_, contract)| {
950 contract.evm_deployed_bytecode.as_bytes().map(|evm_bytecode| {
951 (
952 contract.evm_bytecode_hash,
953 Bytecode::new_raw(evm_bytecode.clone()),
954 )
955 })
956 },
957 )
958 });
959
960 if let Some((code_hash, bytecode)) = bytecode_result {
961 account.info.code_hash = code_hash;
962 account.info.code = Some(bytecode);
963 } else {
964 tracing::info!(
965 address = ?address,
966 "no EVM equivalent found for PVM bytecode, skipping migration"
967 );
968 }
969 }
970 }
971 });
972}
973
974impl foundry_cheatcodes::CheatcodeInspectorStrategyExt for PvmCheatcodeInspectorStrategyRunner {
975 fn is_pvm_enabled(&self, state: &mut foundry_cheatcodes::Cheatcodes) -> bool {
976 let ctx = get_context_ref_mut(state.strategy.context.as_mut());
977
978 ctx.using_revive
979 }
980
981 fn revive_try_create(
986 &self,
987 state: &mut foundry_cheatcodes::Cheatcodes,
988 ecx: Ecx<'_, '_, '_>,
989 input: &dyn CommonCreateInput,
990 executor: &mut dyn foundry_cheatcodes::CheatcodesExecutor,
991 ) -> Option<CreateOutcome> {
992 let mock_handler =
993 MockHandlerImpl::new(&ecx, &input.caller(), &ecx.tx.caller, None, None, state);
994
995 let ctx: &mut PvmCheatcodeInspectorStrategyContext =
996 get_context_ref_mut(state.strategy.context.as_mut());
997
998 if !ctx.using_revive {
999 return None;
1000 }
1001
1002 if ctx.skip_revive {
1003 ctx.skip_revive = false; ctx.record_next_create_address = true;
1005 tracing::info!("running create in EVM, instead of pallet-revive (skipped)");
1006 return None;
1007 }
1008
1009 if let Some(CreateScheme::Create) = input.scheme() {
1010 let caller = input.caller();
1011 let nonce = ecx
1012 .journaled_state
1013 .load_account(input.caller())
1014 .expect("to load caller account")
1015 .info
1016 .nonce;
1017 let address = caller.create(nonce);
1018 if ecx
1019 .journaled_state
1020 .database
1021 .get_test_contract_address()
1022 .map(|addr| address == addr)
1023 .unwrap_or_default()
1024 {
1025 tracing::info!(
1026 "running create in EVM, instead of PVM (Test Contract) {:#?}",
1027 address
1028 );
1029 return None;
1030 }
1031 }
1032
1033 let init_code = input.init_code();
1034
1035 let (code_bytes, constructor_args) = match ctx.runtime_mode {
1037 crate::ReviveRuntimeMode::Pvm => {
1038 tracing::info!("running create in PVM mode with PVM bytecode");
1040 let find_contract = ctx
1041 .dual_compiled_contracts
1042 .find_bytecode(&init_code.0)
1043 .unwrap_or_else(|| panic!("failed finding contract for {init_code:?}"));
1044 let constructor_args = find_contract.constructor_args();
1045 let contract = find_contract.contract();
1046 (contract.resolc_bytecode.as_bytes().unwrap().to_vec(), constructor_args.to_vec())
1047 }
1048 crate::ReviveRuntimeMode::Evm => {
1049 tracing::info!("running create in EVM mode with EVM bytecode");
1051 (init_code.0.to_vec(), vec![])
1052 }
1053 };
1054
1055 let u128_max: U256 = U256::from(u128::MAX);
1056 if input.value() > u128_max {
1057 tracing::warn!(
1058 caller = ?input.caller(),
1059 value = ?input.value(),
1060 max = ?u128_max,
1061 "Create value exceeds u128::MAX. pallet-revive uses u128 for balances."
1062 );
1063 return Some(CreateOutcome {
1064 result: InterpreterResult {
1065 result: InstructionResult::Revert,
1066 output: Bytes::from_iter(
1067 format!(
1068 "Create value {} exceeds u128::MAX ({}). pallet-revive uses u128 for balances.",
1069 input.value(),
1070 u128::MAX
1071 ).as_bytes(),
1072 ),
1073 gas: Gas::new(input.gas_limit()),
1074 },
1075 address: None,
1076 });
1077 }
1078
1079 let gas_price_pvm =
1080 sp_core::U256::from_little_endian(&U256::from(ecx.tx.gas_price).as_le_bytes());
1081 let mut tracer = Tracer::new(state.expected_calls.clone(), state.expected_creates.clone());
1082 let caller_h160 = H160::from_slice(input.caller().as_slice());
1083 let eth_deals = &state.eth_deals;
1084
1085 let res = ctx.externalities.execute_with(|| {
1086 tracer.watch_address(&caller_h160);
1087
1088 tracer.trace(|| {
1089 let exists = AccountInfo::<Runtime>::load_contract(&caller_h160).is_some();
1090 let origin_account_id =
1091 AccountId32Mapper::<Runtime>::to_fallback_account_id(&caller_h160);
1092 let origin = OriginFor::<Runtime>::signed(origin_account_id.clone());
1093 let evm_value = sp_core::U256::from_little_endian(&input.value().as_le_bytes());
1094 MockHandlerImpl::fund_pranked_accounts(input.caller(), eth_deals);
1095 if !exists {
1096 let nonce = ecx
1097 .journaled_state
1098 .load_account(input.caller())
1099 .expect("to load caller account")
1100 .info
1101 .nonce;
1102 polkadot_sdk::frame_system::Account::<Runtime>::mutate(
1103 &origin_account_id,
1104 |a| {
1105 a.nonce =
1106 nonce.min(u32::MAX.into()).try_into().expect("shouldn't happen");
1107 },
1108 );
1109 }
1110 System::inc_account_nonce(&origin_account_id);
1111 let code = Code::Upload(code_bytes.clone());
1112 let data = constructor_args;
1113 let salt = match input.scheme() {
1114 Some(CreateScheme::Create2 { salt }) => Some(
1115 salt.as_limbs()
1116 .iter()
1117 .flat_map(|&x| x.to_le_bytes())
1118 .collect::<Vec<u8>>()
1119 .try_into()
1120 .unwrap(),
1121 ),
1122 _ => None,
1123 };
1124
1125 let exec_config = ExecConfig {
1126 bump_nonce: false,
1134 collect_deposit_from_hold: None,
1135 effective_gas_price: Some(gas_price_pvm),
1136 mock_handler: Some(Box::new(mock_handler.clone())),
1137 is_dry_run: None,
1138 };
1139
1140 Pallet::<Runtime>::bare_instantiate(
1141 origin,
1142 evm_value,
1143 pallet_revive::TransactionLimits::WeightAndDeposit {
1144 weight_limit: Weight::from_parts(10_000_000_000_000, 100_000_000),
1145 deposit_limit: 100_000_000_000_000,
1146 },
1147 code,
1148 data,
1149 salt,
1150 exec_config,
1151 )
1152 })
1153 });
1154 let mut gas = Gas::new(input.gas_limit());
1155
1156 post_exec(state, ecx, executor, &mut tracer, false);
1157 self.append_recorded_accesses(state, ecx, tracer.get_recorded_accesses());
1158
1159 mock_handler.update_state_mocks(state);
1160
1161 match &res.result {
1162 Ok(result) => {
1163 if !state.gas_metering.paused {
1166 let _ =
1167 gas.record_cost(res.gas_consumed.min(u64::MAX.into()).try_into().unwrap());
1168 }
1169
1170 let outcome = if result.result.did_revert() {
1171 CreateOutcome {
1172 result: InterpreterResult {
1173 result: InstructionResult::Revert,
1174 output: result.result.data.clone().into(),
1175 gas,
1176 },
1177 address: None,
1178 }
1179 } else {
1180 CreateOutcome {
1181 result: InterpreterResult {
1182 result: InstructionResult::Return,
1183 output: code_bytes.into(),
1184 gas,
1185 },
1186 address: Some(Address::from_slice(result.addr.as_bytes())),
1187 }
1188 };
1189
1190 Some(outcome)
1191 }
1192 Err(e) => {
1193 tracing::error!("Contract creation failed: {e:#?}");
1194 Some(CreateOutcome {
1195 result: InterpreterResult {
1196 result: InstructionResult::Revert,
1197 output: Bytes::from_iter(
1198 format!("Contract creation failed: {e:#?}").as_bytes(),
1199 ),
1200 gas,
1201 },
1202 address: None,
1203 })
1204 }
1205 }
1206 }
1207
1208 fn revive_try_call(
1213 &self,
1214 state: &mut foundry_cheatcodes::Cheatcodes,
1215 ecx: Ecx<'_, '_, '_>,
1216 call: &CallInputs,
1217 executor: &mut dyn foundry_cheatcodes::CheatcodesExecutor,
1218 ) -> Option<CallOutcome> {
1219 let ctx = get_context_ref_mut(state.strategy.context.as_mut());
1220 let target_address = match call.scheme {
1221 CallScheme::DelegateCall => Some(call.target_address),
1222 _ => None,
1223 };
1224
1225 if !ctx.using_revive {
1226 return None;
1227 }
1228
1229 if ctx.skip_revive || ctx.skip_revive_addresses.contains(&call.target_address) {
1230 ctx.skip_revive = false; tracing::info!("running call in EVM, instead of pallet-revive (skipped)");
1232 return None;
1233 }
1234
1235 if ecx
1236 .journaled_state
1237 .database
1238 .get_test_contract_address()
1239 .map(|addr| call.bytecode_address == addr || call.target_address == addr)
1240 .unwrap_or_default()
1241 {
1242 tracing::info!(
1243 "running call in EVM, instead of pallet-revive (Test Contract) {:#?}",
1244 call.bytecode_address
1245 );
1246 return None;
1247 }
1248
1249 tracing::info!("running call on pallet-revive with {} {:#?}", ctx.runtime_mode, call);
1250
1251 let u128_max: U256 = U256::from(u128::MAX);
1252 let call_value = call.call_value();
1253 if call_value > u128_max {
1254 tracing::warn!(
1255 caller = ?call.caller,
1256 target = ?call.target_address,
1257 value = ?call_value,
1258 max = ?u128_max,
1259 "Call value exceeds u128::MAX. pallet-revive uses u128 for balances."
1260 );
1261 return Some(CallOutcome {
1262 result: InterpreterResult {
1263 result: InstructionResult::Revert,
1264 output: Bytes::from_iter(
1265 format!(
1266 "Call value {} exceeds u128::MAX ({}). pallet-revive uses u128 for balances.",
1267 call_value,
1268 u128::MAX
1269 ).as_bytes(),
1270 ),
1271 gas: Gas::new(call.gas_limit),
1272 },
1273 memory_offset: call.return_memory_offset.clone(),
1274 });
1275 }
1276
1277 let gas_price_pvm =
1278 sp_core::U256::from_little_endian(&U256::from(ecx.tx.gas_price).as_le_bytes());
1279 let mock_handler = MockHandlerImpl::new(
1280 &ecx,
1281 &call.caller,
1282 &ecx.tx.caller,
1283 target_address.as_ref(),
1284 Some(&call.bytecode_address),
1285 state,
1286 );
1287 let ctx = get_context_ref_mut(state.strategy.context.as_mut());
1288
1289 let should_bump_nonce = !call.is_static;
1291 let caller_h160 = H160::from_slice(call.caller.as_slice());
1292
1293 let mut tracer = Tracer::new(state.expected_calls.clone(), state.expected_creates.clone());
1294 let eth_deals = &state.eth_deals;
1295 let res = ctx.externalities.execute_with(|| {
1296 tracer.watch_address(&caller_h160);
1298
1299 tracer.trace(|| {
1300 let origin = OriginFor::<Runtime>::signed(
1301 AccountId32Mapper::<Runtime>::to_fallback_account_id(&caller_h160),
1302 );
1303 MockHandlerImpl::fund_pranked_accounts(call.caller, eth_deals);
1304
1305 let evm_value = sp_core::U256::from_little_endian(&call.call_value().as_le_bytes());
1306 let target = H160::from_slice(call.target_address.as_slice());
1307 let exec_config = ExecConfig {
1308 bump_nonce: false, collect_deposit_from_hold: None,
1310 effective_gas_price: Some(gas_price_pvm),
1311 mock_handler: Some(Box::new(mock_handler.clone())),
1312 is_dry_run: None,
1313 };
1314 if should_bump_nonce {
1315 System::inc_account_nonce(
1316 AccountId32Mapper::<Runtime>::to_fallback_account_id(&caller_h160),
1317 );
1318 }
1319 Pallet::<Runtime>::bare_call(
1320 origin,
1321 target,
1322 evm_value,
1323 pallet_revive::TransactionLimits::WeightAndDeposit {
1324 weight_limit: Weight::from_parts(10_000_000_000_000, 100_000_000),
1325 deposit_limit: if call.is_static { 0 } else { 100_000_000_000_000 },
1326 },
1327 call.input.bytes(ecx).to_vec(),
1328 exec_config,
1329 )
1330 })
1331 });
1332 mock_handler.update_state_mocks(state);
1333 let mut gas = Gas::new(call.gas_limit);
1334
1335 post_exec(state, ecx, executor, &mut tracer, call.is_static);
1336 self.append_recorded_accesses(state, ecx, tracer.get_recorded_accesses());
1337
1338 match res.result {
1339 Ok(result) => {
1340 if !state.gas_metering.paused {
1343 let _ =
1344 gas.record_cost(res.gas_consumed.min(u64::MAX.into()).try_into().unwrap());
1345 }
1346
1347 let outcome = if result.did_revert() {
1348 tracing::info!("Contract call reverted");
1349 CallOutcome {
1350 result: InterpreterResult {
1351 result: InstructionResult::Revert,
1352 output: result.data.into(),
1353 gas,
1354 },
1355 memory_offset: call.return_memory_offset.clone(),
1356 }
1357 } else if result.data.is_empty() {
1358 CallOutcome {
1359 result: InterpreterResult {
1360 result: InstructionResult::Stop,
1361 output: result.data.into(),
1362 gas,
1363 },
1364 memory_offset: call.return_memory_offset.clone(),
1365 }
1366 } else {
1367 CallOutcome {
1368 result: InterpreterResult {
1369 result: InstructionResult::Return,
1370 output: result.data.into(),
1371 gas,
1372 },
1373 memory_offset: call.return_memory_offset.clone(),
1374 }
1375 };
1376
1377 Some(outcome)
1378 }
1379 Err(e) => {
1380 tracing::info!("Contract call failed: {e:#?}");
1381 Some(CallOutcome {
1382 result: InterpreterResult {
1383 result: InstructionResult::Revert,
1384 output: Bytes::from_iter(
1385 format!("Contract call failed: {e:#?}").as_bytes(),
1386 ),
1387 gas,
1388 },
1389 memory_offset: call.return_memory_offset.clone(),
1390 })
1391 }
1392 }
1393 }
1394
1395 fn revive_remove_duplicate_account_access(&self, state: &mut foundry_cheatcodes::Cheatcodes) {
1396 let ctx = get_context_ref_mut(state.strategy.context.as_mut());
1397
1398 if let Some(index) = ctx.remove_recorded_access_at.take()
1399 && let Some(recorded_account_diffs_stack) = state.recorded_account_diffs_stack.as_mut()
1400 && let Some(last) = recorded_account_diffs_stack.last_mut()
1401 {
1402 if index < last.len() {
1405 let removed = last.remove(index);
1406 if let Some(entry) = last.get_mut(index) {
1407 entry.initialized = removed.initialized;
1408 }
1409 } else {
1410 warn!(index, len = last.len(), "skipping duplicate access removal: out of bounds");
1411 }
1412 }
1413 }
1414 fn revive_call_end(
1415 &self,
1416 state: &mut foundry_cheatcodes::Cheatcodes,
1417 ecx: Ecx<'_, '_, '_>,
1418 call: &CallInputs,
1419 ) {
1420 let ctx = get_context_ref_mut(state.strategy.context.as_mut());
1421
1422 if ctx.using_revive
1424 && ecx
1425 .journaled_state
1426 .database
1427 .get_test_contract_address()
1428 .map(|addr| call.bytecode_address != addr || call.target_address != addr)
1429 .unwrap_or(true)
1430 {
1431 return;
1432 }
1433
1434 apply_revm_storage_diff(ctx, ecx, call.target_address);
1435 }
1436
1437 fn revive_record_create_address(
1438 &self,
1439 state: &mut foundry_cheatcodes::Cheatcodes,
1440 outcome: &CreateOutcome,
1441 ) {
1442 let ctx = get_context_ref_mut(state.strategy.context.as_mut());
1443
1444 if ctx.record_next_create_address {
1445 ctx.record_next_create_address = false;
1446 if let Some(address) = outcome.address {
1447 ctx.skip_revive_addresses.insert(address);
1448 tracing::info!(
1449 "recorded address {:?} for skip execution in the pallet-revive",
1450 address
1451 );
1452 }
1453 }
1454 }
1455}
1456
1457fn post_exec(
1458 state: &mut foundry_cheatcodes::Cheatcodes,
1459 ecx: Ecx<'_, '_, '_>,
1460 executor: &mut dyn foundry_cheatcodes::CheatcodesExecutor,
1461 tracer: &mut Tracer,
1462 is_static_call: bool,
1463) {
1464 let ctx = &mut get_context_ref_mut(state.strategy.context.as_mut());
1465
1466 let externalities = &mut ctx.externalities;
1467 let dual_compiled_contracts = &ctx.dual_compiled_contracts;
1468 let (call_traces, create_traces) = externalities.execute_with(|| {
1469 tracer.apply_prestate_trace(ecx);
1470 (tracer.collect_call_traces(), tracer.create_tracer.finalize(dual_compiled_contracts))
1471 });
1472 if let Some(traces) = call_traces
1473 && !is_static_call
1474 {
1475 let mut logs: Vec<(u32, Log)> = vec![];
1476 logs.sort_by(|a, b| a.0.cmp(&b.0));
1477 if !state.expected_emits.is_empty() || state.recorded_logs.is_some() {
1478 logs = collect_logs(&traces);
1479 }
1480 if !state.expected_emits.is_empty() {
1481 logs.clone().into_iter().for_each(|(_, log)| {
1482 foundry_cheatcodes::handle_expect_emit(state, &log, &mut Default::default());
1483 })
1484 }
1485 if let Some(records) = &mut state.recorded_logs {
1486 records.extend(logs.iter().map(|(_, log)| foundry_cheatcodes::Vm::Log {
1487 data: log.data.data.clone(),
1488 emitter: log.address,
1489 topics: log.topics().to_owned(),
1490 }));
1491 };
1492 executor.trace_revive(state, ecx, Box::new(traces));
1493 }
1494
1495 if let Some(expected_revert) = &mut state.expected_revert {
1496 expected_revert.max_depth = std::cmp::max(
1497 ecx.journaled_state.depth() + tracer.revert_tracer.max_depth,
1498 expected_revert.max_depth,
1499 );
1500 expected_revert.reverted_by = tracer.revert_tracer.has_reverted.map(|x| Address::from(x.0));
1501 }
1502 state.expected_calls = tracer.expect_call_tracer.data.clone();
1503 state.expected_creates = create_traces;
1504}
1505
1506struct LogWithIndex {
1507 log: CallTrace,
1508 index: Vec<(Log, u32)>,
1509}
1510
1511impl From<CallTrace> for LogWithIndex {
1512 fn from(value: CallTrace) -> Self {
1513 Self { log: value, index: vec![] }
1514 }
1515}
1516
1517fn assign_indexes(trace: &mut LogWithIndex, mut index: u32) -> (u32, Vec<(Log, u32)>) {
1518 let mut sub_call_index = 0;
1519 for (i, _) in trace.log.logs.clone().iter().enumerate() {
1520 while sub_call_index < trace.log.logs[i].position {
1521 let (new_index, logs) =
1522 assign_indexes(&mut trace.log.calls[sub_call_index as usize].clone().into(), index);
1523 index = new_index;
1524 trace.index.extend(logs.into_iter());
1525 sub_call_index += 1;
1526 }
1527 let log = trace.log.logs[i].clone();
1528 trace.index.push((
1529 Log::new_unchecked(
1530 Address::from(log.address.0),
1531 log.topics.iter().map(|x| U256::from_be_slice(x.as_bytes()).into()).collect(),
1532 Bytes::from(log.data.0),
1533 ),
1534 index,
1535 ));
1536 index += 1;
1537 }
1538 while (sub_call_index as usize) < trace.log.calls.len() {
1539 let (new_index, logs) =
1540 assign_indexes(&mut trace.log.calls[sub_call_index as usize].clone().into(), index);
1541 index = new_index;
1542 trace.index.extend(logs.into_iter());
1543 sub_call_index += 1;
1544 }
1545 (index, trace.index.clone())
1546}
1547
1548fn collect_logs(trace: &CallTrace) -> Vec<(u32, Log)> {
1549 let (_, mut l) = assign_indexes(&mut trace.clone().into(), 0);
1550 l.sort_by(|a, b| a.1.cmp(&b.1));
1551 l.into_iter().map(|x| (x.1, x.0)).collect()
1552}
1553
1554fn get_context_ref_mut(
1555 ctx: &mut dyn CheatcodeInspectorStrategyContext,
1556) -> &mut PvmCheatcodeInspectorStrategyContext {
1557 ctx.as_any_mut().downcast_mut().expect("expected PvmCheatcodeInspectorStrategyContext")
1558}
1559
1560fn apply_revm_storage_diff(
1561 ctx: &mut PvmCheatcodeInspectorStrategyContext,
1562 ecx: Ecx<'_, '_, '_>,
1563 address: Address,
1564) {
1565 let Some(account_state) = ecx.journaled_state.state.get(&address) else {
1566 return;
1567 };
1568
1569 let h160_address = H160::from_slice(address.as_slice());
1570
1571 let contract_exists = ctx
1573 .externalities
1574 .execute_with(|| AccountInfo::<Runtime>::load_contract(&h160_address).is_some());
1575
1576 if !contract_exists {
1577 return;
1578 }
1579
1580 ctx.externalities.execute_with(|| {
1581 for (slot, storage_slot) in &account_state.storage {
1582 if storage_slot.is_changed() {
1583 let slot_bytes = slot.to_be_bytes::<32>();
1584 let new_value = storage_slot.present_value;
1585
1586 if !new_value.is_zero() {
1587 let _ = Pallet::<Runtime>::set_storage(
1588 h160_address,
1589 slot_bytes,
1590 Some(new_value.to_be_bytes::<32>().to_vec()),
1591 );
1592 } else {
1593 let _ = Pallet::<Runtime>::set_storage(h160_address, slot_bytes, None);
1594 }
1595 }
1596 }
1597 });
1598}