Skip to content

Commit 2c0f4b8

Browse files
authored
Merge branch 'master' into mablr/tracing-spec-authority-master
2 parents b8b5a52 + f3e8056 commit 2c0f4b8

10 files changed

Lines changed: 240 additions & 0 deletions

File tree

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
---
2+
forge: minor
3+
foundry-cheatcodes: minor
4+
foundry-cheatcodes-spec: minor
5+
foundry-evm-core: minor
6+
---
7+
8+
Added `vm.getSlotNumber()` and `vm.rollSlot(uint64)` to read and set the Amsterdam block slot number.

crates/cheatcodes/assets/cheatcodes.json

Lines changed: 40 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

crates/cheatcodes/spec/src/vm.rs

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -591,6 +591,20 @@ interface Vm {
591591
#[cheatcode(group = Evm, safety = Safe)]
592592
function getBlockNumber() external view returns (uint256 height);
593593

594+
/// Sets `block.slotnum` without changing the block number or timestamp.
595+
/// Not available on EVM versions before Amsterdam.
596+
/// If used on unsupported EVM versions it will revert.
597+
#[cheatcode(group = Evm, safety = Unsafe)]
598+
function rollSlot(uint64 newSlotNumber) external;
599+
600+
/// Gets the current `block.slotnum`.
601+
/// Use this instead of `block.slotnum` after `vm.rollSlot`, as the compiler assumes
602+
/// `block.slotnum` is constant across a transaction and may optimize repeated reads away.
603+
/// Not available on EVM versions before Amsterdam.
604+
/// If used on unsupported EVM versions it will revert.
605+
#[cheatcode(group = Evm, safety = Safe)]
606+
function getSlotNumber() external view returns (uint64 slotNumber);
607+
594608
/// Sets `tx.gasprice`.
595609
#[cheatcode(group = Evm, safety = Unsafe)]
596610
function txGasPrice(uint256 newGasPrice) external;

crates/cheatcodes/src/evm.rs

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -707,6 +707,29 @@ impl Cheatcode for getBlockNumberCall {
707707
}
708708
}
709709

710+
impl Cheatcode for rollSlotCall {
711+
fn apply_stateful<FEN: FoundryEvmNetwork>(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result {
712+
ensure!(
713+
ccx.ecx.cfg().spec().into() >= SpecId::AMSTERDAM,
714+
"`rollSlot` is not supported before the Amsterdam hard fork; \
715+
see EIP-7843: https://eips.ethereum.org/EIPS/eip-7843"
716+
);
717+
ccx.ecx.block_mut().set_slot_num(self.newSlotNumber);
718+
Ok(Default::default())
719+
}
720+
}
721+
722+
impl Cheatcode for getSlotNumberCall {
723+
fn apply_stateful<FEN: FoundryEvmNetwork>(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result {
724+
ensure!(
725+
ccx.ecx.cfg().spec().into() >= SpecId::AMSTERDAM,
726+
"`getSlotNumber` is not supported before the Amsterdam hard fork; \
727+
see EIP-7843: https://eips.ethereum.org/EIPS/eip-7843"
728+
);
729+
Ok(ccx.ecx.block().slot_num().abi_encode())
730+
}
731+
}
732+
710733
impl Cheatcode for txGasPriceCall {
711734
fn apply_stateful<FEN: FoundryEvmNetwork>(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result {
712735
let Self { newGasPrice } = self;

crates/evm/core/src/backend/mod.rs

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3209,6 +3209,7 @@ fn update_env_block<N: Network, SPEC: Into<SpecId> + Copy, BLOCK: FoundryBlock>(
32093209
block_env.set_basefee(header.base_fee_per_gas().unwrap_or_default());
32103210
block_env.set_gas_limit(header.gas_limit());
32113211
block_env.set_number(U256::from(header.number()));
3212+
block_env.set_slot_num(header.slot_number().unwrap_or_default());
32123213

32133214
if let Some(excess_blob_gas) = header.excess_blob_gas() {
32143215
evm_env.block_env.set_blob_excess_gas_and_price(
@@ -3898,6 +3899,29 @@ mod tests {
38983899
assert_eq!(parent.after_transaction(block, parent_block.hash, 0, 0), None);
38993900
}
39003901

3902+
#[test]
3903+
fn fork_block_env_updates_slot_number() {
3904+
let mut evm_env =
3905+
EvmEnv::new(revm::context::CfgEnv::<SpecId>::default(), BlockEnv::default());
3906+
for slot_number in [Some(42), Some(u64::MAX), None, Some(0)] {
3907+
let header = AnyHeader { slot_number, ..Default::default() };
3908+
let block = AnyRpcBlock::new(
3909+
Block::new(
3910+
AnyRpcHeader::from_sealed(header.seal(B256::ZERO)),
3911+
BlockTransactions::Full(Vec::new()),
3912+
)
3913+
.into(),
3914+
);
3915+
update_env_block::<AnyNetwork, _, _>(
3916+
&mut evm_env,
3917+
&block,
3918+
NamedChain::Mainnet as u64,
3919+
NetworkConfigs::default(),
3920+
);
3921+
assert_eq!(evm_env.block_env.slot_num, slot_number.unwrap_or_default());
3922+
}
3923+
}
3924+
39013925
#[test]
39023926
fn fork_replay_block_env_preserves_arbitrum_l1_number() {
39033927
let header = AnyHeader { number: 75_219_831, ..Default::default() };

crates/evm/core/src/env.rs

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,9 @@ pub trait FoundryBlock: Block {
2929
/// Sets the block number.
3030
fn set_number(&mut self, number: U256);
3131

32+
/// Sets the slot number.
33+
fn set_slot_num(&mut self, slot_num: u64);
34+
3235
/// Sets the beneficiary (coinbase) address.
3336
fn set_beneficiary(&mut self, beneficiary: Address);
3437

@@ -70,6 +73,10 @@ impl FoundryBlock for BlockEnv {
7073
self.number = number;
7174
}
7275

76+
fn set_slot_num(&mut self, slot_num: u64) {
77+
self.slot_num = slot_num;
78+
}
79+
7380
fn set_beneficiary(&mut self, beneficiary: Address) {
7481
self.beneficiary = beneficiary;
7582
}
@@ -108,6 +115,10 @@ impl FoundryBlock for TempoBlockEnv {
108115
self.inner.set_number(number);
109116
}
110117

118+
fn set_slot_num(&mut self, slot_num: u64) {
119+
self.inner.set_slot_num(slot_num);
120+
}
121+
111122
fn set_beneficiary(&mut self, beneficiary: Address) {
112123
self.inner.set_beneficiary(beneficiary);
113124
}

crates/evm/core/src/utils.rs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ pub const fn cold_path() {
2121
pub fn block_env_from_header<BLOCK: FoundryBlock + Default>(header: &impl BlockHeader) -> BLOCK {
2222
let mut block = BLOCK::default();
2323
block.set_number(U256::from(header.number()));
24+
block.set_slot_num(header.slot_number().unwrap_or_default());
2425
block.set_beneficiary(header.beneficiary());
2526
block.set_timestamp(U256::from(header.timestamp()));
2627
block.set_difficulty(header.difficulty());
@@ -235,6 +236,15 @@ mod tests {
235236
use alloy_rpc_types::{Block, BlockTransactions};
236237
use revm::context::{BlockEnv, CfgEnv};
237238

239+
#[test]
240+
fn block_env_preserves_slot_number() {
241+
for slot_number in [None, Some(0), Some(42), Some(u64::MAX)] {
242+
let header = AnyHeader { slot_number, ..Default::default() };
243+
let block = block_env_from_header::<BlockEnv>(&header);
244+
assert_eq!(block.slot_num, slot_number.unwrap_or_default());
245+
}
246+
}
247+
238248
#[test]
239249
fn block_normalization_uses_source_chain() {
240250
let header = AnyHeader { number: 500, ..Default::default() };

crates/forge/tests/cli/svm.rs

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -227,3 +227,44 @@ contract StateGasTest is Test {
227227
});
228228
cmd.forge_fuse().args(args).assert_success();
229229
});
230+
231+
forgetest_init!(can_test_slot_number_amsterdam, |prj, cmd| {
232+
prj.add_test(
233+
"SlotNumber.t.sol",
234+
r#"
235+
pragma solidity =0.8.37;
236+
237+
import "forge-std/Test.sol";
238+
239+
interface VmSlot {
240+
function getSlotNumber() external view returns (uint64);
241+
function rollSlot(uint64 newSlotNumber) external;
242+
}
243+
244+
contract SlotNumberTest is Test {
245+
VmSlot constant slots = VmSlot(address(vm));
246+
247+
function readSlot() external view returns (uint256) {
248+
return block.slotnum;
249+
}
250+
251+
function testSlotNumber(uint64 first, uint64 second) public {
252+
uint256 number = vm.getBlockNumber();
253+
uint256 timestamp = vm.getBlockTimestamp();
254+
slots.rollSlot(first);
255+
assertEq(slots.getSlotNumber(), first);
256+
assertEq(this.readSlot(), first);
257+
slots.rollSlot(second);
258+
assertEq(slots.getSlotNumber(), second);
259+
assertEq(this.readSlot(), second);
260+
assertEq(vm.getBlockNumber(), number);
261+
assertEq(vm.getBlockTimestamp(), timestamp);
262+
}
263+
}
264+
"#,
265+
);
266+
let args = ["test", "--use", "0.8.37", "--evm-version", "amsterdam", "--experimental"];
267+
cmd.args(args).assert_success();
268+
cmd.forge_fuse().args(args).args(["--optimize", "--via-ir"]).assert_success();
269+
cmd.forge_fuse().args(args).arg("--isolate").assert_success();
270+
});
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
// SPDX-License-Identifier: MIT OR Apache-2.0
2+
pragma solidity ^0.8.18;
3+
4+
import "utils/Test.sol";
5+
6+
contract SlotNumberTest is Test {
7+
address constant SLOT_READER = address(0x1234);
8+
9+
function setUp() public {
10+
vm.setEvmVersion("amsterdam");
11+
// SLOTNUM; MSTORE(0); RETURN(0, 32).
12+
vm.etch(SLOT_READER, hex"4b60005260206000f3");
13+
}
14+
15+
function readSlotNumber() internal view returns (uint256) {
16+
(bool success, bytes memory result) = SLOT_READER.staticcall("");
17+
require(success, "SLOTNUM failed");
18+
return abi.decode(result, (uint256));
19+
}
20+
21+
function testRollSlot() public {
22+
uint256 number = vm.getBlockNumber();
23+
uint256 timestamp = vm.getBlockTimestamp();
24+
assertEq(vm.getSlotNumber(), readSlotNumber());
25+
vm.rollSlot(100);
26+
assertEq(vm.getSlotNumber(), 100);
27+
assertEq(readSlotNumber(), 100);
28+
vm.rollSlot(0);
29+
assertEq(vm.getSlotNumber(), 0);
30+
assertEq(readSlotNumber(), 0);
31+
vm.rollSlot(type(uint64).max);
32+
assertEq(vm.getSlotNumber(), type(uint64).max);
33+
assertEq(readSlotNumber(), type(uint64).max);
34+
assertEq(vm.getBlockNumber(), number);
35+
assertEq(vm.getBlockTimestamp(), timestamp);
36+
}
37+
38+
function testRollSlotFuzzed(uint64 first, uint64 second) public {
39+
vm.rollSlot(first);
40+
assertEq(vm.getSlotNumber(), first);
41+
assertEq(readSlotNumber(), first);
42+
vm.rollSlot(second);
43+
assertEq(vm.getSlotNumber(), second);
44+
assertEq(readSlotNumber(), second);
45+
}
46+
47+
function testRollSlotSnapshot() public {
48+
vm.rollSlot(42);
49+
uint256 snapshot = vm.snapshotState();
50+
vm.rollSlot(100);
51+
assertTrue(vm.revertToState(snapshot));
52+
assertEq(vm.getSlotNumber(), 42);
53+
assertEq(readSlotNumber(), 42);
54+
}
55+
56+
function testSlotNumberBeforeAmsterdam() public {
57+
vm.setEvmVersion("osaka");
58+
vm._expectCheatcodeRevert(
59+
"vm.rollSlot: `rollSlot` is not supported before the Amsterdam hard fork; see EIP-7843: https://eips.ethereum.org/EIPS/eip-7843"
60+
);
61+
vm.rollSlot(1);
62+
vm._expectCheatcodeRevert(
63+
"vm.getSlotNumber: `getSlotNumber` is not supported before the Amsterdam hard fork; see EIP-7843: https://eips.ethereum.org/EIPS/eip-7843"
64+
);
65+
vm.getSlotNumber();
66+
}
67+
}

testdata/utils/Vm.sol

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -339,6 +339,7 @@ interface Vm {
339339
function getRecordedLogs() external view returns (Log[] memory logs);
340340
function getRecordedLogsJson() external view returns (string memory logsJson);
341341
function getSelectors(string calldata artifactPath) external view returns (bytes4[] memory selectors);
342+
function getSlotNumber() external view returns (uint64 slotNumber);
342343
function getStateDiff() external view returns (string memory diff);
343344
function getStateDiffJson() external view returns (string memory diff);
344345
function getStorageAccesses() external view returns (StorageAccess[] memory storageAccesses);
@@ -513,6 +514,7 @@ interface Vm {
513514
function rollFork(bytes32 txHash) external;
514515
function rollFork(uint256 forkId, uint256 blockNumber) external;
515516
function rollFork(uint256 forkId, bytes32 txHash) external;
517+
function rollSlot(uint64 newSlotNumber) external;
516518
function rpcJson(string calldata method, string calldata params) external returns (string memory data);
517519
function rpcJson(string calldata urlOrAlias, string calldata method, string calldata params) external returns (string memory data);
518520
function rpcUrl(string calldata rpcAlias) external view returns (string memory json);

0 commit comments

Comments
 (0)