Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
68 commits
Select commit Hold shift + click to select a range
2146fc5
add vm.prank/startPrank/stopPrank support
alexggh Oct 2, 2025
51804f5
add prank testsuite
alexggh Oct 2, 2025
ad700fb
mocked functions working something
alexggh Oct 3, 2025
08e63fd
add support for mocks
alexggh Oct 7, 2025
aaa472f
add mock
alexggh Oct 8, 2025
f7145b4
remove unneeded changes
alexggh Oct 8, 2025
8608a99
remove unneeded file
alexggh Oct 8, 2025
50e8f43
compile against latest branch
alexggh Oct 20, 2025
0b39a22
rebased on master
alexggh Oct 20, 2025
4158429
Merge remote-tracking branch 'origin/master' into re_merge_prank
alexggh Oct 20, 2025
effbecb
add cheatcode testsuite
alexggh Oct 21, 2025
b005fa0
prank latest
alexggh Oct 23, 2025
80fa8d8
Merge remote-tracking branch 'origin/master' into alexggh/review_pran…
alexggh Oct 27, 2025
e15127f
fixup migration
alexggh Oct 27, 2025
ad1253d
fix build
alexggh Oct 27, 2025
f515d42
add more tests
alexggh Oct 28, 2025
4d1f528
Merge remote-tracking branch 'origin/master' into alexggh/review_pran…
alexggh Oct 28, 2025
3cb2315
fixup tests
alexggh Oct 24, 2025
a629145
switch to master
alexggh Oct 28, 2025
676b2f8
make cargo fmt happy
alexggh Oct 28, 2025
681472c
make clippy happy
alexggh Oct 28, 2025
ed7b936
make forge fmt happy
alexggh Oct 28, 2025
e29737b
fix prank calls
alexggh Oct 29, 2025
9d35e48
Merge remote-tracking branch 'origin/master' into alexggh/review_pran…
alexggh Nov 4, 2025
fedcc8e
Merge remote-tracking branch 'origin/master' into alexggh/review_pran…
alexggh Nov 5, 2025
ee9c3d3
replace with template
alexggh Nov 5, 2025
8d11b21
fixup typo
alexggh Nov 5, 2025
d0a562d
fix extra
alexggh Nov 5, 2025
1da5f68
make tests consume less resources
alexggh Nov 5, 2025
5855f2b
refactor externalities usage
pkhry Nov 5, 2025
5b0c610
Merge remote-tracking branch 'origin/master' into pkhry/refactor_exte…
pkhry Nov 17, 2025
6ea0776
fix beforeTestSetups
pkhry Nov 17, 2025
0707b67
accidental changes
pkhry Nov 18, 2025
5b25129
Merge remote-tracking branch 'origin/master' into pkhry/refactor_exte…
pkhry Nov 18, 2025
998fccf
add invariant test
pkhry Nov 18, 2025
821791e
Merge remote-tracking branch 'origin/master' into pkhry/refactor_exte…
pkhry Nov 18, 2025
c8b6d02
fixup merge
pkhry Nov 18, 2025
08c99c4
opt
pkhry Nov 19, 2025
0da1fe5
Merge remote-tracking branch 'origin/master' into pkhry/refactor_exte…
pkhry Nov 19, 2025
0662bd4
upd tests
pkhry Nov 19, 2025
9c4facb
remove revive_call_end as redundant
pkhry Nov 19, 2025
01f9d8e
caching fix (#207)
pkhry Nov 19, 2025
bbc4cb2
tests fixup
pkhry Nov 20, 2025
52a37e6
commit suggestions
pkhry Nov 20, 2025
48ccc2f
review comment
pkhry Nov 20, 2025
cf0f776
fix storage migration
pkhry Nov 20, 2025
1908168
uncommit lockfile
pkhry Nov 20, 2025
3886d81
add extra clause to test
pkhry Nov 20, 2025
fa3018b
gitignore
pkhry Nov 21, 2025
8dc7d09
Merge branch 'master' into pkhry/refactor_externalities_usage
pkhry Nov 21, 2025
05b0a46
fix select_evm()
pkhry Nov 21, 2025
493e263
update compilers
pkhry Nov 21, 2025
d7eaf3b
ci checks
pkhry Nov 21, 2025
9c87260
merge branch origin/main
pkhry Nov 24, 2025
cb04fdf
clippy
pkhry Nov 24, 2025
62205eb
fixup merge
pkhry Nov 24, 2025
b153c11
revert removal of call_end
pkhry Nov 24, 2025
ce46d9a
snapshotting cheats
pkhry Nov 21, 2025
bb6f468
refactor transactions
pkhry Nov 21, 2025
70e30d9
clippy
pkhry Nov 21, 2025
f01d036
merge branch "origin/master"
pkhry Nov 25, 2025
f872f78
fmt
pkhry Nov 25, 2025
253ba1f
Merge branch 'master' into pkhry/snapshotting_cheats
pkhry Nov 28, 2025
ca7a009
Merge remote-tracking branch 'origin/master' into pkhry/snapshotting_…
pkhry Nov 30, 2025
f59d42d
Merge branch 'master' into pkhry/snapshotting_cheats
pkhry Dec 1, 2025
002f2c8
fixup tests
pkhry Dec 1, 2025
f824d00
Merge branch 'master' into pkhry/snapshotting_cheats
pkhry Dec 1, 2025
fb0c5a5
rm accidental change
pkhry Dec 1, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

14 changes: 6 additions & 8 deletions crates/forge/src/runner.rs
Original file line number Diff line number Diff line change
Expand Up @@ -544,13 +544,15 @@ impl<'a> FunctionRunner<'a> {
/// State modifications of before test txes and unit test function call are discarded after
/// test ends, similar to `eth_call`.
fn run_unit_test(mut self, func: &Function) -> TestResult {
let binding = self.executor.clone().into_owned();
self.executor = Cow::Owned(binding);
// Prepare unit test execution.
self.executor.strategy.runner.start_transaction(self.executor.strategy.context.as_ref());
if self.prepare_test(func).is_err() {
self.executor
.strategy
.runner
.rollback_transaction(self.executor.strategy.context.as_ref());
return self.result;
}
self.executor.strategy.runner.start_transaction(self.executor.strategy.context.as_ref());

// Run current unit test.
let (mut raw_call_result, reason) = match self.executor.call(
Expand Down Expand Up @@ -580,11 +582,11 @@ impl<'a> FunctionRunner<'a> {
return self.result;
}
};
self.executor.strategy.runner.rollback_transaction(self.executor.strategy.context.as_ref());

let success =
self.executor.is_raw_call_mut_success(self.address, &mut raw_call_result, false);
self.result.single_result(success, reason, raw_call_result);
self.executor.strategy.runner.rollback_transaction(self.executor.strategy.context.as_ref());

self.result
}
Expand All @@ -598,8 +600,6 @@ impl<'a> FunctionRunner<'a> {
/// - `bool[] public fixtureSwap = [true, false]` The `table_test` is then called with the pair
/// of args `(2, true)` and `(5, false)`.
fn run_table_test(mut self, func: &Function) -> TestResult {
let binding = self.executor.clone().into_owned();
self.executor = Cow::Owned(binding); // Prepare unit test execution.
if self.prepare_test(func).is_err() {
return self.result;
}
Expand Down Expand Up @@ -729,8 +729,6 @@ impl<'a> FunctionRunner<'a> {
identified_contracts: &ContractsByAddress,
test_bytecode: &Bytes,
) -> TestResult {
let binding = self.executor.clone().into_owned();
self.executor = Cow::Owned(binding);
// First, run the test normally to see if it needs to be skipped.
if let Err(EvmError::Skip(reason)) = self.executor.call(
self.sender,
Expand Down
16 changes: 16 additions & 0 deletions crates/forge/tests/it/revive/cheat_snapshot.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
use crate::{config::*, test_helpers::TEST_DATA_REVIVE};
use foundry_test_utils::Filter;
use revive_strategy::ReviveRuntimeMode;
use revm::primitives::hardfork::SpecId;
use rstest::rstest;

#[rstest]
#[case::pvm(ReviveRuntimeMode::Pvm)]
#[case::evm(ReviveRuntimeMode::Evm)]
#[tokio::test(flavor = "multi_thread")]
async fn test_snapshot_cheats(#[case] runtime_mode: ReviveRuntimeMode) {
let runner: forge::MultiContractRunner = TEST_DATA_REVIVE.runner_revive(runtime_mode);
let filter = Filter::new(".*", "StateSnapshotTest", ".*/revive/.*");

TestConfig::with_filter(runner, filter).spec_id(SpecId::PRAGUE).run().await;
}
1 change: 1 addition & 0 deletions crates/forge/tests/it/revive/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ pub mod cheat_mock_call;
pub mod cheat_mock_calls;
pub mod cheat_mock_functions;
pub mod cheat_prank;
mod cheat_snapshot;
pub mod cheat_store;
pub mod cheats_individual;
pub mod migration;
Expand Down
20 changes: 19 additions & 1 deletion crates/revive-strategy/src/cheatcodes/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,8 @@ use foundry_cheatcodes::{
CommonCreateInput, Ecx, EvmCheatcodeInspectorStrategyRunner, Result,
Vm::{
chainIdCall, coinbaseCall, dealCall, etchCall, getNonce_0Call, loadCall, pvmCall,
resetNonceCall, rollCall, setNonceCall, setNonceUnsafeCall, storeCall, warpCall,
resetNonceCall, revertToStateAndDeleteCall, revertToStateCall, rollCall, setNonceCall,
setNonceUnsafeCall, snapshotStateCall, storeCall, warpCall,
},
journaled_account, precompile_error,
};
Expand Down Expand Up @@ -319,6 +320,23 @@ impl CheatcodeInspectorStrategyRunner for PvmCheatcodeInspectorStrategyRunner {

cheatcode.dyn_apply(ccx, executor)
}
t if using_pvm && is::<snapshotStateCall>(t) => {
ctx.externalities.start_snapshotting();
cheatcode.dyn_apply(ccx, executor)
}
t if using_pvm && is::<revertToStateAndDeleteCall>(t) => {
let &revertToStateAndDeleteCall { snapshotId } =
cheatcode.as_any().downcast_ref().unwrap();

ctx.externalities.revert(snapshotId.try_into().unwrap());
cheatcode.dyn_apply(ccx, executor)
}
t if using_pvm && is::<revertToStateCall>(t) => {
let &revertToStateCall { snapshotId } = cheatcode.as_any().downcast_ref().unwrap();

ctx.externalities.revert(snapshotId.try_into().unwrap());
cheatcode.dyn_apply(ccx, executor)
}
t if using_pvm && is::<warpCall>(t) => {
let &warpCall { newTimestamp } = cheatcode.as_any().downcast_ref().unwrap();

Expand Down
6 changes: 3 additions & 3 deletions crates/revive-strategy/src/executor/runner.rs
Original file line number Diff line number Diff line change
Expand Up @@ -157,12 +157,12 @@ impl ExecutorStrategyExt for ReviveExecutorStrategyRunner {
fn start_transaction(&self, ctx: &dyn ExecutorStrategyContext) {
let ctx = get_context_ref(ctx);
let mut externalities = ctx.externalties.0.lock().unwrap();
externalities.ext().storage_start_transaction();
externalities.externalities.ext().storage_start_transaction();
}

fn rollback_transaction(&self, ctx: &dyn ExecutorStrategyContext) {
let ctx = get_context_ref(ctx);
let mut externalities = ctx.externalties.0.lock().unwrap();
externalities.ext().storage_rollback_transaction().unwrap();
let mut state = ctx.externalties.0.lock().unwrap();
let _ = state.externalities.ext().storage_rollback_transaction();
}
}
63 changes: 46 additions & 17 deletions crates/revive-strategy/src/state.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,22 +6,31 @@ use polkadot_sdk::{
Executable, Pallet,
},
sp_core::{self, H160},
sp_externalities::Externalities,
sp_io::TestExternalities,
};
use revive_env::{AccountId, BlockAuthor, ExtBuilder, Runtime, System, Timestamp};
use std::{
fmt::Debug,
sync::{Arc, Mutex},
};
pub struct TestEnv(pub Arc<Mutex<TestExternalities>>);

impl Default for TestEnv {
pub(crate) struct Inner {
pub externalities: TestExternalities,
pub depth: usize,
}

#[derive(Default)]
pub struct TestEnv(pub(crate) Arc<Mutex<Inner>>);

impl Default for Inner {
fn default() -> Self {
Self(Arc::new(Mutex::new(
ExtBuilder::default()
Self {
externalities: ExtBuilder::default()
.balance_genesis_config(vec![(H160::from_low_u64_be(1), 1000)])
.build(),
)))
depth: 0,
}
}
}

Expand All @@ -33,9 +42,10 @@ impl Debug for TestEnv {

impl Clone for TestEnv {
fn clone(&self) -> Self {
let mut externalities = ExtBuilder::default().build();
externalities.backend = self.0.lock().unwrap().as_backend();
Self(Arc::new(Mutex::new(externalities)))
let mut inner: Inner = Default::default();
inner.externalities.backend = self.0.lock().unwrap().externalities.as_backend();
inner.depth = self.0.lock().unwrap().depth;
Self(Arc::new(Mutex::new(inner)))
}
}

Expand All @@ -44,20 +54,36 @@ impl TestEnv {
Self(self.0.clone())
}

pub fn start_snapshotting(&mut self) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why you have not decided to just copy backend like this:

pub fn save_revive_snapshot(snapshot_id: U256) {
    TEST_EXTERNALITIES.with_borrow_mut(|test_ext| {
        let backend = test_ext.as_backend();
        REVIVE_SNAPSHOTS.with_borrow_mut(|snapshots| {
            snapshots.insert(snapshot_id, backend);
        });
    });
}

And revert it like this

pub fn revert_revive_snapshot(snapshot_id: U256) -> bool {
    REVIVE_SNAPSHOTS.with_borrow_mut(|snapshots| {
        if let Some(backend) = snapshots.get(&snapshot_id).cloned() {
            let mut test_externalities = ExtBuilder::default().build();
            let mut backend_copy = backend;
            std::mem::swap(&mut test_externalities.backend, &mut backend_copy);
            TEST_EXTERNALITIES.set(test_externalities);
            true
        } else {
            false
        }
    })
}

Of course this implementation is based on old code base, but when I have implemented it the changes were very minimal.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

transaction commit and rollback are much more faster

Copy link
Collaborator

@smiasojed smiasojed Nov 24, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why? It is mem swap/copy, Rollbacks you are doing in loops. Do you have any numbers?
I see that as_backend could be slow, is there an option to use into_raw_snapshot

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yeah, i'll post the comparative changes today. for fuzz tests for example executor.clone was very much noticeable in the .trace output compared to transaction_start and transaction_rollback

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why? It is mem swap/copy, Rollbacks you are doing in loops. Do you have any numbers?
I see that as_backend could be slow, is there an option to use into_raw_snapshot

it was even slower than as_backend(), in the thread local impl but i'll add benches today to an issue and tag you so that it's more visible

let mut state = self.0.lock().unwrap();
state.depth += 1;
state.externalities.ext().storage_start_transaction();
}

pub fn revert(&mut self, depth: usize) {
let mut state = self.0.lock().unwrap();
while state.depth > depth + 1 {
state.externalities.ext().storage_rollback_transaction().unwrap();
state.depth -= 1;
}
state.externalities.ext().storage_rollback_transaction().unwrap();
state.externalities.ext().storage_start_transaction();
}

pub fn execute_with<R, F: FnOnce() -> R>(&mut self, f: F) -> R {
self.0.lock().unwrap().execute_with(f)
self.0.lock().unwrap().externalities.execute_with(f)
}

pub fn get_nonce(&mut self, account: Address) -> u32 {
self.0.lock().unwrap().execute_with(|| {
self.0.lock().unwrap().externalities.execute_with(|| {
System::account_nonce(AccountId::to_fallback_account_id(&H160::from_slice(
account.as_slice(),
)))
})
}

pub fn set_nonce(&mut self, address: Address, nonce: u64) {
self.0.lock().unwrap().execute_with(|| {
self.0.lock().unwrap().externalities.execute_with(|| {
let account_id =
AccountId::to_fallback_account_id(&H160::from_slice(address.as_slice()));

Expand All @@ -69,7 +95,7 @@ impl TestEnv {

pub fn set_chain_id(&mut self, new_chain_id: u64) {
// Set chain id in pallet-revive runtime.
self.0.lock().unwrap().execute_with(|| {
self.0.lock().unwrap().externalities.execute_with(|| {
<revive_env::Runtime as polkadot_sdk::pallet_revive::Config>::ChainId::set(
&new_chain_id,
);
Expand All @@ -78,14 +104,14 @@ impl TestEnv {

pub fn set_block_number(&mut self, new_height: U256) {
// Set block number in pallet-revive runtime.
self.0.lock().unwrap().execute_with(|| {
self.0.lock().unwrap().externalities.execute_with(|| {
System::set_block_number(new_height.try_into().expect("Block number exceeds u64"));
});
}

pub fn set_timestamp(&mut self, new_timestamp: U256) {
// Set timestamp in pallet-revive runtime (milliseconds).
self.0.lock().unwrap().execute_with(|| {
self.0.lock().unwrap().externalities.execute_with(|| {
let timestamp_ms = new_timestamp.saturating_to::<u64>().saturating_mul(1000);
Timestamp::set_timestamp(timestamp_ms);
});
Expand All @@ -97,7 +123,7 @@ impl TestEnv {
new_runtime_code: &Bytes,
ecx: Ecx<'_, '_, '_>,
) -> Result {
self.0.lock().unwrap().execute_with(|| {
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);

Expand Down Expand Up @@ -153,6 +179,7 @@ impl TestEnv {
self.0
.lock()
.unwrap()
.externalities
.execute_with(|| {
pallet_revive::Pallet::<Runtime>::get_storage(target_address_h160, slot.into())
})
Expand All @@ -169,6 +196,7 @@ impl TestEnv {
self.0
.lock()
.unwrap()
.externalities
.execute_with(|| {
pallet_revive::Pallet::<Runtime>::set_storage(
target_address_h160,
Expand All @@ -184,7 +212,7 @@ impl TestEnv {
let amount_pvm =
sp_core::U256::from_little_endian(&amount.as_le_bytes()).min(u128::MAX.into());

self.0.lock().unwrap().execute_with(|| {
self.0.lock().unwrap().externalities.execute_with(|| {
let h160_addr = H160::from_slice(address.as_slice());
pallet_revive::Pallet::<Runtime>::set_evm_balance(&h160_addr, amount_pvm)
.expect("failed to set evm balance");
Expand All @@ -195,6 +223,7 @@ impl TestEnv {
self.0
.lock()
.unwrap()
.externalities
.execute_with(|| {
let h160_addr = H160::from_slice(address.as_slice());
pallet_revive::Pallet::<Runtime>::evm_balance(&h160_addr)
Expand All @@ -204,7 +233,7 @@ impl TestEnv {
}

pub fn set_block_author(&mut self, new_author: Address) {
self.0.lock().unwrap().execute_with(|| {
self.0.lock().unwrap().externalities.execute_with(|| {
let account_id32 =
AccountId::to_fallback_account_id(&H160::from_slice(new_author.as_slice()));
BlockAuthor::set(&account_id32);
Expand Down
Loading
Loading