Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
177 changes: 177 additions & 0 deletions crates/forge/tests/it/revive/cheats_individual.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,177 @@
//! Individual cheatcode tests for pallet-revive
//! Each test runs a specific cheatcode file for easier debugging

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;

// Internal macro that accepts directory parameter
macro_rules! revive_cheat_test_with_dir {
($test_name:ident, $file_pattern:expr, $dir:expr) => {
#[rstest]
#[case::evm(ReviveRuntimeMode::Evm)]
#[tokio::test(flavor = "multi_thread")]
async fn $test_name(#[case] runtime_mode: ReviveRuntimeMode) {
let filter = Filter::new(".*", ".*", &format!(".*/{}/{}.*", $dir, $file_pattern));
Copy link

Choose a reason for hiding this comment

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

shouldn't this be */{}/{}.t.* to correctly grab tests by their filename?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

yes there is a bug


let runner = TEST_DATA_REVIVE.runner_revive_with(runtime_mode, |config| {
use foundry_config::{FsPermissions, fs_permissions::PathPermission};
config.fs_permissions = FsPermissions::new(vec![PathPermission::read_write("./")]);
});

TestConfig::with_filter(runner, filter).spec_id(SpecId::PRAGUE).run().await;
}
};
}

// Public macro for revive-specific tests (default)
macro_rules! revive_cheat_test {
($test_name:ident, $file_pattern:expr) => {
revive_cheat_test_with_dir!($test_name, $file_pattern, "revive");
};
}

// Public macro for original cheatcode tests
macro_rules! revive_cheat_test_original {
($test_name:ident, $file_pattern:expr) => {
revive_cheat_test_with_dir!($test_name, $file_pattern, "cheats");
};
}

revive_cheat_test_original!(test_nonce, "Nonce");
revive_cheat_test!(test_coinbase, "CoinBase");
revive_cheat_test!(test_warp, "Warp");
// TOFIX Fails blockhash
revive_cheat_test!(test_roll, "Roll");
revive_cheat_test!(test_chainid, "ChainId");
revive_cheat_test!(test_deal, "Deal");
revive_cheat_test!(test_get_block_timestamp, "GetBlockTimestamp");
revive_cheat_test!(test_get_block_number, "getBlockNumber");
revive_cheat_test_original!(test_expect_emit, "ExpectEmit");
// TOFIX
revive_cheat_test_original!(test_expect_revert, "ExpectRevert");
// TOFIX
revive_cheat_test_original!(test_expect_call, "ExpectCall");
// vm.fee() doesn't work correctly in revive mode
// revive_cheat_test!(test_fee, "Fee");
// vm.prevrandao() doesn't work correctly in revive mode
// revive_cheat_test!(test_prevrandao, "Prevrandao");
revive_cheat_test_original!(test_load, "Load");
// Not implemented
// revive_cheat_test_original!(test_access_list, "AccessList");
revive_cheat_test_original!(test_addr, "Addr");
// Not implemented vm.setArbitraryStorage
// revive_cheat_test_original!(test_arbitrary_storage, "ArbitraryStorage");
// SKIP it should not affect pallet-revive execution
// revive_cheat_test_original!(test_assert, "Assert");
// SKIP it should not affect pallet-revive execution
// revive_cheat_test!(test_assume, "Assume");
revive_cheat_test_original!(test_assume_no_revert, "AssumeNoRevert");
// vm.attachBlob vm.broadcast does not work
revive_cheat_test_original!(test_attach_blob, "AttachBlob");
// vm.attachDelegation vm.broadcast does not work
// revive_cheat_test_original!(test_attach_delegation, "AttachDelegation");
// SKIP it should not affect pallet-revive execution
// revive_cheat_test_original!(test_base64, "Base64");
// Compilation error
// revive_cheat_test!(test_blob_base_fee, "BlobBaseFee");
revive_cheat_test_original!(test_blobhashes, "Blobhashes");
// vm.broadcast does not work
// revive_cheat_test_original!(test_broadcast, "Broadcast");
// vm.broadcastRawTransaction does not work
// revive_cheat_test_original!(test_broadcast_raw_transaction, "BroadcastRawTransaction");
// vm.cloneAccount maybly ised for forks - skip it
//revive_cheat_test_original!(test_clone_account, "CloneAccount");
// not supported in polkadot
// revive_cheat_test_original!(test_cool, "Cool");
// vm.copyStorage vm.setArbitraryStorage not implemented
//revive_cheat_test_original!(test_copy_storage, "CopyStorage");
// vm.deployCode not implemented
revive_cheat_test_original!(test_deploy_code, "DeployCode");
// SKIP it should not affect pallet-revive execution
// revive_cheat_test_original!(test_derive, "Derive");
// SKIP it should not affect pallet-revive execution
// revive_cheat_test_original!(test_ens_namehash, "EnsNamehash");
// SKIP it should not affect pallet-revive execution
// revive_cheat_test_original!(test_env, "Env");
// EXTCODECOPY compilation issue
revive_cheat_test_original!(test_etch, "Etch");
Copy link

Choose a reason for hiding this comment

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

@smiasojed etch, prank, mock tests were already ported by me and passing here:

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

yes I saw it, I will clean this up. But anyway original etch is failing - probably issue with EXTCODECOPY - but it should not affect our EVM path.

Copy link

@alexggh alexggh Nov 27, 2025

Choose a reason for hiding this comment

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

yeah EXTCODECOPY compilation was the reason I modified. the test slightly.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

I think we should not error out in this case, because it means that we do not have pvm code which is fine for evm path. @pkhry WDYT?

Copy link

Choose a reason for hiding this comment

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

error out

Copy link
Collaborator Author

@smiasojed smiasojed Nov 27, 2025

Choose a reason for hiding this comment

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

It means that if any project uses opcodes not supported in PVM, the EVM tests will fail. I’m not sure if this is the right approach.

Copy link

Choose a reason for hiding this comment

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

It means that if any project uses opcodes not supported in PVM, the EVM tests will fail. I’m not sure if this is the right approach.

then just disable pvm for now i guess

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

or if compilation for pvm fails just skip this bytecode in dualcompiledcontract - the user will not be able to switch to pvm in test

revive_cheat_test_original!(test_expect_create, "ExpectCreate");
// revive_cheat_test_original!(test_ffi, "Ffi");
// fork cheatcodes not supported
// revive_cheat_test_original!(test_fork, "Fork");
// fork cheatcodes not supported
// revive_cheat_test_original!(test_fork2, "Fork2");
// revive_cheat_test_original!(test_fs, "Fs");
revive_cheat_test!(test_get_artifact_path, "GetArtifactPath");
revive_cheat_test_original!(test_get_chain, "GetChain");
revive_cheat_test_original!(test_get_code, "GetCode");
revive_cheat_test!(test_get_deployed_code, "GetDeployedCode");
// SKIP it should not affect pallet-revive execution
// revive_cheat_test_original!(test_get_foundry_version, "GetFoundryVersion");
revive_cheat_test_original!(test_get_label, "GetLabel");
revive_cheat_test_original!(test_get_nonce, "GetNonce");
// Implement test to work without fork
revive_cheat_test!(test_get_raw_block_header, "GetRawBlockHeader");
// SKIP it should not affect pallet-revive execution
// revive_cheat_test_original!(test_json, "Json");
// revive_cheat_test_original!(test_label, "Label");
// TODO: check if it is needed
// revive_cheat_test_original!(test_mapping, "Mapping");
// TODO: check if it is needed
// revive_cheat_test_original!(test_mem_safety, "MemSafety");
// SKIP it should not affect pallet-revive execution
// revive_cheat_test_original!(test_parse, "Parse");
// SKIP it should not affect pallet-revive execution
// revive_cheat_test_original!(test_project_root, "ProjectRoot");
// TODO: check if it is needed
// revive_cheat_test_original!(test_prompt, "Prompt");
// SKIP it should not affect pallet-revive execution
// revive_cheat_test_original!(test_random_address, "RandomAddress");
// SKIP it should not affect pallet-revive execution
// revive_cheat_test_original!(test_random_bytes, "RandomBytes");
// SKIP it should not affect pallet-revive execution
// revive_cheat_test_original!(test_random_cheatcodes, "RandomCheatcodes");
// SKIP it should not affect pallet-revive execution
// revive_cheat_test_original!(test_random_uint, "RandomUint");
// TODO: check if it is needed
revive_cheat_test_original!(test_read_callers, "ReadCallers");
revive_cheat_test_original!(test_record, "Record");
revive_cheat_test_original!(test_record_account_accesses, "RecordAccountAccesses");
revive_cheat_test_original!(test_record_debug_trace, "RecordDebugTrace");
revive_cheat_test_original!(test_record_logs, "RecordLogs");
// SKIP it should not affect pallet-revive execution
// revive_cheat_test_original!(test_remember, "Remember");
revive_cheat_test_original!(test_reset_nonce, "ResetNonce");
// SKIP it should not affect pallet-revive execution
// revive_cheat_test_original!(test_rpc_urls, "RpcUrls");
revive_cheat_test_original!(test_seed, "Seed");
revive_cheat_test!(test_set_blockhash, "SetBlockhash");
revive_cheat_test_original!(test_set_blockhash2, "SetBlockhash");
revive_cheat_test_original!(test_set_nonce, "SetNonce");
revive_cheat_test_original!(test_set_nonce_unsafe, "SetNonceUnsafe");
revive_cheat_test_original!(test_setup, "Setup");
// revive_cheat_test_original!(test_shuffle, "Shuffle");
// SKIP it should not affect pallet-revive execution
// revive_cheat_test_original!(test_sign, "Sign");
// SKIP it should not affect pallet-revive execution
// revive_cheat_test_original!(test_sign_p256, "SignP256");
revive_cheat_test_original!(test_skip, "Skip");
// revive_cheat_test_original!(test_sleep, "Sleep");
// revive_cheat_test_original!(test_sort, "Sort");
revive_cheat_test_original!(test_state_snapshots, "StateSnapshots");
// Will not work at itt check Gas nd vm.cool
revive_cheat_test_original!(test_storage_slot_state, "StorageSlotState");
// SKIP it should not affect pallet-revive execution
// revive_cheat_test_original!(test_string_utils, "StringUtils");
// revive_cheat_test_original!(test_to_string, "ToString");
// revive_cheat_test_original!(test_toml, "Toml");
revive_cheat_test!(test_chainid2, "Travel");
// revive_cheat_test_original!(test_try_ffi, "TryFfi");
// revive_cheat_test_original!(test_unix_time, "UnixTime");
// revive_cheat_test_original!(test_wallet, "Wallet");
revive_cheat_test_original!(test_dump_state, "dumpState");
// revive_cheat_test_original!(test_load_allocs, "loadAllocs");
revive_cheat_test_original!(test_gas_metering, "GasMetering");
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 @@ -7,5 +7,6 @@ pub mod cheat_mock_calls;
pub mod cheat_mock_functions;
pub mod cheat_prank;
pub mod cheat_store;
pub mod cheats_individual;
pub mod migration;
pub mod tx_gas_price;
9 changes: 9 additions & 0 deletions crates/forge/tests/it/test_helpers.rs
Original file line number Diff line number Diff line change
Expand Up @@ -329,7 +329,16 @@ impl ForgeTestData {

/// Builds a runner with revive strategy for polkadot/substrate testing
pub fn runner_revive(&self, runtime_mode: ReviveRuntimeMode) -> MultiContractRunner {
self.runner_revive_with(runtime_mode, |_| {})
}

pub fn runner_revive_with(
&self,
runtime_mode: ReviveRuntimeMode,
modify: impl FnOnce(&mut Config),
) -> MultiContractRunner {
let mut config = (*self.config).clone();
modify(&mut config);
config.rpc_endpoints = rpc_endpoints();
config.allow_paths.push(manifest_root().to_path_buf());
if config.fs_permissions.is_empty() {
Expand Down
2 changes: 1 addition & 1 deletion crates/revive-env/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ use polkadot_sdk::{
sp_tracing,
};

pub use crate::runtime::{AccountId, Balance, Runtime, System, Timestamp};
pub use crate::runtime::{AccountId, Balance, BlockAuthor, Runtime, System, Timestamp};

mod runtime;

Expand Down
5 changes: 4 additions & 1 deletion crates/revive-env/src/runtime.rs
Original file line number Diff line number Diff line change
Expand Up @@ -92,13 +92,16 @@ impl pallet_revive::Config for Runtime {

parameter_types! {
pub storage ChainId: u64 = 420_420_420;
pub storage BlockAuthor: AccountId32 = {
[[0xff; 20].as_slice(), [0xee; 12].as_slice()].concat().as_slice().try_into().unwrap()
};
}

impl FindAuthor<<Self as frame_system::Config>::AccountId> for Runtime {
fn find_author<'a, I>(_digests: I) -> Option<<Self as frame_system::Config>::AccountId>
where
I: 'a + IntoIterator<Item = (frame_support::ConsensusEngineId, &'a [u8])>,
{
Some([[0xff; 20].as_slice(), [0xee; 12].as_slice()].concat().as_slice().try_into().unwrap())
Some(BlockAuthor::get())
}
}
74 changes: 50 additions & 24 deletions crates/revive-strategy/src/cheatcodes/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,8 @@ use foundry_cheatcodes::{
CheatcodeInspectorStrategyContext, CheatcodeInspectorStrategyRunner, CheatsConfig, CheatsCtxt,
CommonCreateInput, Ecx, EvmCheatcodeInspectorStrategyRunner, Result,
Vm::{
chainIdCall, dealCall, etchCall, getNonce_0Call, loadCall, pvmCall, resetNonceCall,
rollCall, setNonceCall, setNonceUnsafeCall, storeCall, warpCall,
chainIdCall, coinbaseCall, dealCall, etchCall, getNonce_0Call, loadCall, pvmCall,
resetNonceCall, rollCall, setNonceCall, setNonceUnsafeCall, storeCall, warpCall,
},
journaled_account, precompile_error,
};
Expand Down Expand Up @@ -153,6 +153,7 @@ impl CheatcodeInspectorStrategyContext for PvmCheatcodeInspectorStrategyContext
self
}
}

/// Implements [CheatcodeInspectorStrategyRunner] for PVM.
#[derive(Debug, Default, Clone)]
pub struct PvmCheatcodeInspectorStrategyRunner;
Expand Down Expand Up @@ -301,7 +302,15 @@ impl CheatcodeInspectorStrategyRunner for PvmCheatcodeInspectorStrategyRunner {
t if using_pvm && is::<resetNonceCall>(t) => {
tracing::info!(cheatcode = ?cheatcode.as_debug() , using_pvm = ?using_pvm);
let &resetNonceCall { account } = cheatcode.as_any().downcast_ref().unwrap();
ctx.externalities.set_nonce(account, 0);

// EOA nonces start at 0, contract nonces start at 1
let nonce = if ctx.externalities.is_contract(account) {
1u64 // Contract
} else {
0u64 // EOA
};

ctx.externalities.set_nonce(account, nonce);
cheatcode.dyn_apply(ccx, executor)
}
t if using_pvm && is::<getNonce_0Call>(t) => {
Expand Down Expand Up @@ -335,6 +344,14 @@ impl CheatcodeInspectorStrategyRunner for PvmCheatcodeInspectorStrategyRunner {

cheatcode.dyn_apply(ccx, executor)
}
t if using_pvm && is::<coinbaseCall>(t) => {
let &coinbaseCall { newCoinbase } = cheatcode.as_any().downcast_ref().unwrap();

tracing::info!(cheatcode = ?cheatcode.as_debug() , using_pvm = ?using_pvm);
ctx.externalities.set_block_author(newCoinbase);

cheatcode.dyn_apply(ccx, executor)
}
t if using_pvm && is::<etchCall>(t) => {
let etchCall { target, newRuntimeBytecode } =
cheatcode.as_any().downcast_ref().unwrap();
Expand Down Expand Up @@ -617,7 +634,7 @@ fn select_revive(ctx: &mut PvmCheatcodeInspectorStrategyContext, data: Ecx<'_, '
code_bytes.clone(),
BytecodeType::Evm,
u64::MAX.into(),
&ExecConfig::new_substrate_tx(),
&ExecConfig::new_substrate_tx_without_bump(),
);
match upload_result {
Ok(_) => {
Expand Down Expand Up @@ -807,24 +824,12 @@ impl foundry_cheatcodes::CheatcodeInspectorStrategyExt for PvmCheatcodeInspector
let mut tracer = Tracer::new(true);
let res = ctx.externalities.execute_with(|| {
tracer.trace(|| {
let origin = OriginFor::<Runtime>::signed(AccountId::to_fallback_account_id(
&H160::from_slice(input.caller().as_slice()),
));
let caller_h160 = H160::from_slice(input.caller().as_slice());
let origin_account_id = AccountId::to_fallback_account_id(&caller_h160);
let origin = OriginFor::<Runtime>::signed(origin_account_id.clone());
let evm_value = sp_core::U256::from_little_endian(&input.value().as_le_bytes());
mock_handler.fund_pranked_accounts(input.caller());

// Pre-Dispatch Increments the nonce of the origin, so let's make sure we do
// that here too to replicate the same address generation.
System::inc_account_nonce(AccountId::to_fallback_account_id(&H160::from_slice(
input.caller().as_slice(),
)));
let exec_config = ExecConfig {
bump_nonce: true,
collect_deposit_from_hold: None,
effective_gas_price: Some(gas_price_pvm),
mock_handler: Some(Box::new(mock_handler.clone())),
is_dry_run: None,
};
System::inc_account_nonce(&origin_account_id);
let code = Code::Upload(code_bytes.clone());
let data = constructor_args;
let salt = match input.scheme() {
Expand All @@ -839,6 +844,21 @@ impl foundry_cheatcodes::CheatcodeInspectorStrategyExt for PvmCheatcodeInspector
_ => None,
};

let exec_config = ExecConfig {
// IMPORTANT: Do NOT bump nonce here!
// When calling bare_instantiate directly (not through dispatch), the nonce
// has NOT been incremented pre-dispatch. Setting bump_nonce=true would cause
// pallet-revive to increment the nonce AFTER computing the CREATE address,
// but the address computation subtracts 1 from the nonce assuming it was
// already incremented. This causes all deployments to use nonce-1 for
// address computation, resulting in duplicate addresses.
bump_nonce: false,
collect_deposit_from_hold: None,
effective_gas_price: Some(gas_price_pvm),
mock_handler: Some(Box::new(mock_handler.clone())),
is_dry_run: None,
};

Pallet::<Runtime>::bare_instantiate(
origin,
evm_value,
Expand Down Expand Up @@ -955,23 +975,29 @@ impl foundry_cheatcodes::CheatcodeInspectorStrategyExt for PvmCheatcodeInspector

let ctx = get_context_ref_mut(state.strategy.context.as_mut());

// Get nonce before execute_with closure
let should_bump_nonce = !call.is_static;
let caller_h160 = H160::from_slice(call.caller.as_slice());

let mut tracer = Tracer::new(true);
let res = ctx.externalities.execute_with(|| {
tracer.trace(|| {
let origin = OriginFor::<Runtime>::signed(AccountId::to_fallback_account_id(
&H160::from_slice(call.caller.as_slice()),
));
let origin =
OriginFor::<Runtime>::signed(AccountId::to_fallback_account_id(&caller_h160));
mock_handler.fund_pranked_accounts(call.caller);

let evm_value = sp_core::U256::from_little_endian(&call.call_value().as_le_bytes());
let target = H160::from_slice(call.target_address.as_slice());
let exec_config = ExecConfig {
bump_nonce: true,
bump_nonce: false, // only works for contructors
collect_deposit_from_hold: None,
effective_gas_price: Some(gas_price_pvm),
mock_handler: Some(Box::new(mock_handler.clone())),
is_dry_run: None,
};
if should_bump_nonce {
System::inc_account_nonce(AccountId::to_fallback_account_id(&caller_h160));
}
Pallet::<Runtime>::bare_call(
origin,
target,
Expand Down
Loading