diff --git a/crates/cheatcodes/assets/cheatcodes.json b/crates/cheatcodes/assets/cheatcodes.json index 26d9dd2e17bc..7a02c6ad64fa 100644 --- a/crates/cheatcodes/assets/cheatcodes.json +++ b/crates/cheatcodes/assets/cheatcodes.json @@ -2971,6 +2971,26 @@ "status": "stable", "safety": "safe" }, + { + "func": { + "id": "blobBaseFee", + "description": "Sets `block.blobbasefee`", + "declaration": "function blobBaseFee(uint256 newBlobBaseFee) external;", + "visibility": "external", + "mutability": "", + "signature": "blobBaseFee(uint256)", + "selector": "0x6d315d7e", + "selectorBytes": [ + 109, + 49, + 93, + 126 + ] + }, + "group": "evm", + "status": "stable", + "safety": "unsafe" + }, { "func": { "id": "breakpoint_0", @@ -4651,6 +4671,26 @@ "status": "stable", "safety": "safe" }, + { + "func": { + "id": "getBlobBaseFee", + "description": "Gets the current `block.blobbasefee`.\nYou should use this instead of `block.blobbasefee` if you use `vm.blobBaseFee`, as `block.blobbasefee` is assumed to be constant across a transaction,\nand as a result will get optimized out by the compiler.\nSee https://github.com/foundry-rs/foundry/issues/6180", + "declaration": "function getBlobBaseFee() external view returns (uint256 blobBaseFee);", + "visibility": "external", + "mutability": "view", + "signature": "getBlobBaseFee()", + "selector": "0x1f6d6ef7", + "selectorBytes": [ + 31, + 109, + 110, + 247 + ] + }, + "group": "evm", + "status": "stable", + "safety": "safe" + }, { "func": { "id": "getBlockNumber", diff --git a/crates/cheatcodes/spec/src/vm.rs b/crates/cheatcodes/spec/src/vm.rs index e7ac87da11a1..ae93475b2bf3 100644 --- a/crates/cheatcodes/spec/src/vm.rs +++ b/crates/cheatcodes/spec/src/vm.rs @@ -402,6 +402,17 @@ interface Vm { #[cheatcode(group = Evm, safety = Safe)] function getBlockTimestamp() external view returns (uint256 timestamp); + /// Sets `block.blobbasefee` + #[cheatcode(group = Evm, safety = Unsafe)] + function blobBaseFee(uint256 newBlobBaseFee) external; + + /// Gets the current `block.blobbasefee`. + /// You should use this instead of `block.blobbasefee` if you use `vm.blobBaseFee`, as `block.blobbasefee` is assumed to be constant across a transaction, + /// and as a result will get optimized out by the compiler. + /// See https://github.com/foundry-rs/foundry/issues/6180 + #[cheatcode(group = Evm, safety = Safe)] + function getBlobBaseFee() external view returns (uint256 blobBaseFee); + // -------- Account State -------- /// Sets an address' balance. diff --git a/crates/cheatcodes/src/evm.rs b/crates/cheatcodes/src/evm.rs index b8f55b37c2f7..d9c0f5cff09e 100644 --- a/crates/cheatcodes/src/evm.rs +++ b/crates/cheatcodes/src/evm.rs @@ -328,6 +328,26 @@ impl Cheatcode for getBlockTimestampCall { } } +impl Cheatcode for blobBaseFeeCall { + fn apply_full(&self, ccx: &mut CheatsCtxt) -> Result { + let Self { newBlobBaseFee } = self; + ensure!( + ccx.ecx.spec_id() >= SpecId::CANCUN, + "`blobBaseFee` is not supported before the Cancun hard fork; \ + see EIP-4844: https://eips.ethereum.org/EIPS/eip-4844" + ); + ccx.ecx.env.block.set_blob_excess_gas_and_price((*newBlobBaseFee).to()); + Ok(Default::default()) + } +} + +impl Cheatcode for getBlobBaseFeeCall { + fn apply_full(&self, ccx: &mut CheatsCtxt) -> Result { + let Self {} = self; + Ok(ccx.ecx.env.block.get_blob_excess_gas().unwrap_or(0).abi_encode()) + } +} + impl Cheatcode for dealCall { fn apply_full(&self, ccx: &mut CheatsCtxt) -> Result { let Self { account: address, newBalance: new_balance } = *self; diff --git a/crates/forge/tests/it/cheats.rs b/crates/forge/tests/it/cheats.rs index 42113cdc770d..47d6ebbb9ec5 100644 --- a/crates/forge/tests/it/cheats.rs +++ b/crates/forge/tests/it/cheats.rs @@ -2,7 +2,10 @@ use crate::{ config::*, - test_helpers::{ForgeTestData, RE_PATH_SEPARATOR, TEST_DATA_DEFAULT, TEST_DATA_MULTI_VERSION}, + test_helpers::{ + ForgeTestData, RE_PATH_SEPARATOR, TEST_DATA_CANCUN, TEST_DATA_DEFAULT, + TEST_DATA_MULTI_VERSION, + }, }; use foundry_config::{fs_permissions::PathPermission, FsPermissions}; use foundry_test_utils::Filter; @@ -50,3 +53,8 @@ async fn test_cheats_local_default_isolated() { async fn test_cheats_local_multi_version() { test_cheats_local(&TEST_DATA_MULTI_VERSION).await } + +#[tokio::test(flavor = "multi_thread")] +async fn test_cheats_local_cancun() { + test_cheats_local(&TEST_DATA_CANCUN).await +} diff --git a/crates/forge/tests/it/test_helpers.rs b/crates/forge/tests/it/test_helpers.rs index 6fc8a3745f15..204df223b860 100644 --- a/crates/forge/tests/it/test_helpers.rs +++ b/crates/forge/tests/it/test_helpers.rs @@ -2,8 +2,8 @@ use alloy_primitives::U256; use forge::{ - inspectors::CheatsConfig, MultiContractRunner, MultiContractRunnerBuilder, TestOptions, - TestOptionsBuilder, + inspectors::CheatsConfig, revm::primitives::SpecId, MultiContractRunner, + MultiContractRunnerBuilder, TestOptions, TestOptionsBuilder, }; use foundry_compilers::{ artifacts::{Libraries, Settings}, @@ -46,6 +46,11 @@ impl fmt::Display for ForgeTestProfile { } impl ForgeTestProfile { + /// Returns true if the profile is Cancun. + pub fn is_cancun(&self) -> bool { + matches!(self, Self::Cancun) + } + pub fn root(&self) -> PathBuf { PathBuf::from(TESTDATA) } @@ -147,7 +152,7 @@ impl ForgeTestProfile { "fork/Fork.t.sol:DssExecLib:0xfD88CeE74f7D78697775aBDAE53f9Da1559728E4".to_string(), ]; - if matches!(self, Self::Cancun) { + if self.is_cancun() { config.evm_version = EvmVersion::Cancun; } @@ -162,6 +167,7 @@ pub struct ForgeTestData { pub test_opts: TestOptions, pub evm_opts: EvmOpts, pub config: Config, + pub profile: ForgeTestProfile, } impl ForgeTestData { @@ -175,15 +181,20 @@ impl ForgeTestData { let config = profile.config(); let evm_opts = profile.evm_opts(); - Self { project, output, test_opts, evm_opts, config } + Self { project, output, test_opts, evm_opts, config, profile } } /// Builds a base runner pub fn base_runner(&self) -> MultiContractRunnerBuilder { init_tracing(); - MultiContractRunnerBuilder::default() + let mut runner = MultiContractRunnerBuilder::default() .sender(self.evm_opts.sender) - .with_test_options(self.test_opts.clone()) + .with_test_options(self.test_opts.clone()); + if self.profile.is_cancun() { + runner = runner.evm_spec(SpecId::CANCUN); + } + + runner } /// Builds a non-tracing runner diff --git a/testdata/cancun/cheats/BlobBaseFee.t.sol b/testdata/cancun/cheats/BlobBaseFee.t.sol new file mode 100644 index 000000000000..54fbc8f7f061 --- /dev/null +++ b/testdata/cancun/cheats/BlobBaseFee.t.sol @@ -0,0 +1,14 @@ +// SPDX-License-Identifier: MIT OR Apache-2.0 +pragma solidity ^0.8.25; + +import "ds-test/test.sol"; +import "cheats/Vm.sol"; + +contract BlobBaseFeeTest is DSTest { + Vm constant vm = Vm(HEVM_ADDRESS); + + function test_blob_base_fee() public { + vm.blobBaseFee(6969); + assertEq(vm.getBlobBaseFee(), 6969); + } +} diff --git a/testdata/cheats/Vm.sol b/testdata/cheats/Vm.sol index 8f799c9f6936..1d0b6118b1a3 100644 --- a/testdata/cheats/Vm.sol +++ b/testdata/cheats/Vm.sol @@ -144,6 +144,7 @@ interface Vm { function assertTrue(bool condition) external pure; function assertTrue(bool condition, string calldata error) external pure; function assume(bool condition) external pure; + function blobBaseFee(uint256 newBlobBaseFee) external; function breakpoint(string calldata char) external; function breakpoint(string calldata char, bool value) external; function broadcast() external; @@ -228,6 +229,7 @@ interface Vm { function fee(uint256 newBasefee) external; function ffi(string[] calldata commandInput) external returns (bytes memory result); function fsMetadata(string calldata path) external view returns (FsMetadata memory metadata); + function getBlobBaseFee() external view returns (uint256 blobBaseFee); function getBlockNumber() external view returns (uint256 height); function getBlockTimestamp() external view returns (uint256 timestamp); function getCode(string calldata artifactPath) external view returns (bytes memory creationBytecode);