diff --git a/crates/cheatcodes/src/evm.rs b/crates/cheatcodes/src/evm.rs index 243cddc261cf9..1682d263bede5 100644 --- a/crates/cheatcodes/src/evm.rs +++ b/crates/cheatcodes/src/evm.rs @@ -397,7 +397,6 @@ impl Cheatcode for difficultyCall { impl Cheatcode for feeCall { fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { let Self { newBasefee } = self; - ensure!(*newBasefee <= U256::from(u64::MAX), "base fee must be less than 2^64 - 1"); ccx.ecx.block.basefee = newBasefee.saturating_to(); Ok(Default::default()) } diff --git a/crates/cli/src/utils/mod.rs b/crates/cli/src/utils/mod.rs index 1591f288c9e03..afb9e84a40c56 100644 --- a/crates/cli/src/utils/mod.rs +++ b/crates/cli/src/utils/mod.rs @@ -239,9 +239,9 @@ pub fn enable_paint() { /// pub fn install_crypto_provider() { // https://github.com/snapview/tokio-tungstenite/issues/353 - rustls::crypto::ring::default_provider() - .install_default() - .expect("Failed to install default rustls crypto provider"); + // Only one provider can be installed globally per process. Multiple tests running in parallel + // will fail if this is not ignored. + let _ = rustls::crypto::ring::default_provider().install_default(); } /// Useful extensions to [`std::process::Command`]. diff --git a/crates/forge/tests/cli/revive_vm.rs b/crates/forge/tests/cli/revive_vm.rs index 9b5532e67c4a3..b3ed0721f3ba6 100644 --- a/crates/forge/tests/cli/revive_vm.rs +++ b/crates/forge/tests/cli/revive_vm.rs @@ -227,6 +227,105 @@ Ran 1 test suite [ELAPSED]: 1 tests passed, 0 failed, 0 skipped (1 total tests) "#]]); }); +forgetest!(fee_revive, |prj, cmd| { + prj.insert_ds_test(); + prj.insert_vm(); + prj.insert_console(); + prj.add_source( + "Fee.t.sol", + r#" +import "./test.sol"; +import "./Vm.sol"; +import {console} from "./console.sol"; + +contract Fee is DSTest { + Vm constant vm = Vm(HEVM_ADDRESS); + + function test_Fee() public { + vm.pvm(true); + uint256 original = block.basefee; + vm.fee(25 gwei); + uint256 newValue = block.basefee; + assert(original != newValue); + assertEq(newValue, 25 gwei); + } +} +"#, + ) + .unwrap(); + + let res = cmd.args(["test", "--resolc", "-vvv"]).assert_success(); + res.stderr_eq(str![""]).stdout_eq(str![[r#" +[COMPILING_FILES] with [SOLC_VERSION] +[SOLC_VERSION] [ELAPSED] +Compiler run successful! +[COMPILING_FILES] with [RESOLC_VERSION] +[RESOLC_VERSION] [ELAPSED] +Compiler run successful! + +Ran 1 test for src/Fee.t.sol:Fee +[PASS] test_Fee() ([GAS]) +Suite result: ok. 1 passed; 0 failed; 0 skipped; [ELAPSED] + +Ran 1 test suite [ELAPSED]: 1 tests passed, 0 failed, 0 skipped (1 total tests) + +"#]]); +}); + +forgetest!(fee_revive_clamping, |prj, cmd| { + prj.insert_ds_test(); + prj.insert_vm(); + prj.insert_console(); + prj.add_source( + "FeeClamp.t.sol", + r#" +import "./test.sol"; +import "./Vm.sol"; +import {console} from "./console.sol"; + +contract FeeClamp is DSTest { + Vm constant vm = Vm(HEVM_ADDRESS); + + function min(uint256 a, uint256 b) internal pure returns (uint256) { + return a < b ? a : b; + } + + function test_FeeClamp() public { + vm.pvm(true); + vm.fee(100 gwei); + uint256 original = block.basefee; + assertEq(original, 100 gwei); + + vm.fee(200 gwei); + uint256 raised = block.basefee; + assertEq(raised, 200 gwei); + + uint256 effective = min(block.basefee, 150 gwei); + assertEq(effective, 150 gwei); + } +} +"#, + ) + .unwrap(); + + let res = cmd.args(["test", "--resolc", "-vvv"]).assert_success(); + res.stderr_eq(str![""]).stdout_eq(str![[r#" +[COMPILING_FILES] with [SOLC_VERSION] +[SOLC_VERSION] [ELAPSED] +Compiler run successful! +[COMPILING_FILES] with [RESOLC_VERSION] +[RESOLC_VERSION] [ELAPSED] +Compiler run successful! + +Ran 1 test for src/FeeClamp.t.sol:FeeClamp +[PASS] test_FeeClamp() ([GAS]) +Suite result: ok. 1 passed; 0 failed; 0 skipped; [ELAPSED] + +Ran 1 test suite [ELAPSED]: 1 tests passed, 0 failed, 0 skipped (1 total tests) + +"#]]); +}); + forgetest!(deal, |prj, cmd| { prj.insert_ds_test(); prj.insert_vm(); diff --git a/crates/revive-strategy/src/cheatcodes/mod.rs b/crates/revive-strategy/src/cheatcodes/mod.rs index 1d6472c566b24..8efc9ad705fb9 100644 --- a/crates/revive-strategy/src/cheatcodes/mod.rs +++ b/crates/revive-strategy/src/cheatcodes/mod.rs @@ -6,8 +6,8 @@ use foundry_cheatcodes::{ CheatcodeInspectorStrategyContext, CheatcodeInspectorStrategyRunner, CheatsConfig, CheatsCtxt, CommonCreateInput, DealRecord, Ecx, Error, EvmCheatcodeInspectorStrategyRunner, Result, Vm::{ - dealCall, getNonce_0Call, loadCall, pvmCall, rollCall, setNonceCall, setNonceUnsafeCall, - storeCall, warpCall, + dealCall, feeCall, getNonce_0Call, loadCall, pvmCall, rollCall, setNonceCall, + setNonceUnsafeCall, storeCall, warpCall, }, journaled_account, precompile_error, }; @@ -208,6 +208,11 @@ fn set_timestamp(new_timestamp: U256, ecx: Ecx<'_, '_, '_>) { }); } +fn set_basefee(new_basefee: U256, ecx: Ecx<'_, '_, '_>) { + let as_u64 = new_basefee.try_into().unwrap_or(u64::MAX); + ecx.block.basefee = as_u64; +} + /// Implements [CheatcodeInspectorStrategyRunner] for PVM. #[derive(Debug, Default, Clone)] pub struct PvmCheatcodeInspectorStrategyRunner; @@ -381,6 +386,14 @@ impl CheatcodeInspectorStrategyRunner for PvmCheatcodeInspectorStrategyRunner { Ok(Default::default()) } + t if using_pvm && is::(t) => { + let &feeCall { newBasefee } = cheatcode.as_any().downcast_ref().unwrap(); + + tracing::info!(cheatcode = ?cheatcode.as_debug() , using_pvm = ?using_pvm); + set_basefee(newBasefee, ccx.ecx); + + Ok(Default::default()) + } t if using_pvm && is::(t) => { tracing::info!(cheatcode = ?cheatcode.as_debug() , using_pvm = ?using_pvm); let &loadCall { target, slot } = cheatcode.as_any().downcast_ref().unwrap(); @@ -713,6 +726,18 @@ impl foundry_cheatcodes::CheatcodeInspectorStrategyExt for PvmCheatcodeInspector return None; } + let basefee_u64 = ecx.block.basefee; + + // tx cap is ecx.tx.gas_price (max_fee_per_gas) or u128::MAX if it's not set + let mut tx_cap_u128 = ecx.tx.gas_price; + if ecx.tx.gas_priority_fee.is_none() && tx_cap_u128 == 0 { + tx_cap_u128 = u128::MAX; + } + + let tx_cap_u64 = tx_cap_u128.min(u128::from(u64::MAX)) as u64; + let clamped_basefee_u64 = basefee_u64.min(tx_cap_u64); + let block_basefee = sp_core::U256::from(clamped_basefee_u64); + if let Some(CreateScheme::Create) = input.scheme() { let caller = input.caller(); let nonce = ecx @@ -779,7 +804,11 @@ impl foundry_cheatcodes::CheatcodeInspectorStrategyExt for PvmCheatcodeInspector code, data, salt, - ExecConfig::new_substrate_tx(), + { + let mut config = ExecConfig::new_substrate_tx(); + config.effective_gas_price = Some(block_basefee); + config + }, ) }) }) @@ -849,6 +878,18 @@ impl foundry_cheatcodes::CheatcodeInspectorStrategyExt for PvmCheatcodeInspector return None; } + let basefee_u64 = ecx.block.basefee; + + // tx cap is ecx.tx.gas_price (max_fee_per_gas) or u128::MAX if it's not set + let mut tx_cap_u128 = ecx.tx.gas_price; + if ecx.tx.gas_priority_fee.is_none() && tx_cap_u128 == 0 { + tx_cap_u128 = u128::MAX; + } + + let tx_cap_u64 = tx_cap_u128.min(u128::from(u64::MAX)) as u64; + let clamped_basefee_u64 = basefee_u64.min(tx_cap_u64); + let block_basefee = sp_core::U256::from(clamped_basefee_u64); + if ecx .journaled_state .database @@ -885,7 +926,11 @@ impl foundry_cheatcodes::CheatcodeInspectorStrategyExt for PvmCheatcodeInspector // TODO: fixing. BalanceOf::::MAX, call.input.bytes(ecx).to_vec(), - ExecConfig::new_substrate_tx(), + { + let mut config = ExecConfig::new_substrate_tx(); + config.effective_gas_price = Some(block_basefee); + config + }, ) }) }) diff --git a/testdata/default/cheats/Fee.t.sol b/testdata/default/cheats/Fee.t.sol index 120627c0004e9..9bcd5e8f5a6b7 100644 --- a/testdata/default/cheats/Fee.t.sol +++ b/testdata/default/cheats/Fee.t.sol @@ -16,4 +16,115 @@ contract FeeTest is DSTest { vm.fee(fee); assertEq(block.basefee, fee, "fee failed"); } + + function test_SetFeeOnce() public { + vm.pvm(true); + uint256 before = block.basefee; + vm.fee(25 gwei); + uint256 afterFee = block.basefee; + assertTrue(before != afterFee); + assertEq(afterFee, 25 gwei); + } + + function test_SetFeeMultipleTimes() public { + vm.pvm(true); + vm.fee(10 gwei); + assertEq(block.basefee, 10 gwei); + vm.fee(50 gwei); + assertEq(block.basefee, 50 gwei); + vm.fee(1 gwei); + assertEq(block.basefee, 1 gwei); + } +} + +contract FeePersistenceTest is DSTest { + Vm constant vm = Vm(HEVM_ADDRESS); + + function test_FeePersistsAcrossCalls() public { + vm.pvm(true); + vm.fee(30 gwei); + assertEq(block.basefee, 30 gwei); + + // simulate another call + helper(); + } + + function helper() internal { + assertEq(block.basefee, 30 gwei); + } +} + +contract FeeClampTest is DSTest { + Vm constant vm = Vm(HEVM_ADDRESS); + + function test_ClampLowerThanBasefee() public { + vm.pvm(true); + vm.fee(200 gwei); + uint256 clamped = min(block.basefee, 150 gwei); + assertEq(clamped, 150 gwei); + } + + function test_ClampHigherThanBasefee() public { + vm.pvm(true); + vm.fee(100 gwei); + uint256 clamped = min(block.basefee, 500 gwei); + assertEq(clamped, 100 gwei); + } + + function min(uint256 a, uint256 b) internal pure returns (uint256) { + return a < b ? a : b; + } +} + +contract FeeEdgeCases is DSTest { + Vm constant vm = Vm(HEVM_ADDRESS); + + function test_ZeroBasefee() public { + vm.pvm(true); + vm.fee(0); + assertEq(block.basefee, 0); + } + + function test_MaxBasefee() public { + vm.pvm(true); + uint256 max = type(uint256).max; + vm.fee(max); + assertEq(block.basefee, type(uint64).max); + } +} + +contract FeeCrossMode is DSTest { + Vm constant vm = Vm(HEVM_ADDRESS); + + function test_FeeConsistencyAcrossModes() public { + vm.pvm(false); + vm.fee(42 gwei); + uint256 evmFee = block.basefee; + + vm.pvm(true); + uint256 pvmFee = block.basefee; + + assertEq(evmFee, pvmFee); + } +} + +contract BasefeeConsumer { + function readBasefee() external view returns (uint256) { + return block.basefee; + } +} + +contract FeeExternalTest is DSTest { + Vm constant vm = Vm(HEVM_ADDRESS); + + function test_BasefeeVisibleInDeployedContract() public { + vm.pvm(true); + vm.fee(42 gwei); + + // try calling block.basefee from a contract that's deployed on pvm side + BasefeeConsumer consumer = new BasefeeConsumer(); + uint256 observed = consumer.readBasefee(); + + assertEq(observed, 42 gwei, "deployed contract should see updated basefee"); + } }