Skip to content

Commit dbddd08

Browse files
authored
fix(anvil): return correct block number for Arbitrum fork (#7360)
* fix(anvil): return correct block number for Arbitrum fork * Backward compatibility with existing state files
1 parent a3cec87 commit dbddd08

File tree

7 files changed

+101
-20
lines changed

7 files changed

+101
-20
lines changed

crates/anvil/src/config.rs

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1026,9 +1026,6 @@ latest block number: {latest_block}"
10261026
..Default::default()
10271027
};
10281028

1029-
// apply changes such as difficulty -> prevrandao
1030-
apply_chain_and_block_specific_env_changes(env, &block);
1031-
10321029
// if not set explicitly we use the base fee of the latest block
10331030
if self.base_fee.is_none() {
10341031
if let Some(base_fee) = block.header.base_fee_per_gas {
@@ -1072,6 +1069,8 @@ latest block number: {latest_block}"
10721069
chain_id
10731070
};
10741071
let override_chain_id = self.chain_id;
1072+
// apply changes such as difficulty -> prevrandao and chain specifics for current chain id
1073+
apply_chain_and_block_specific_env_changes(env, &block);
10751074

10761075
let meta = BlockchainDbMeta::new(*env.env.clone(), eth_rpc_url.clone());
10771076
let block_chain_db = if self.fork_chain_id.is_some() {

crates/anvil/src/eth/backend/db.rs

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
//! Helper types for working with [revm](foundry_evm::revm)
22
33
use crate::{mem::state::trie_hash_db, revm::primitives::AccountInfo};
4-
use alloy_primitives::{keccak256, Address, Bytes, B256, U256};
4+
use alloy_primitives::{keccak256, Address, Bytes, B256, U256, U64};
55
use alloy_rpc_types::BlockId;
66
use anvil_core::eth::trie::KeccakHasher;
77
use foundry_common::errors::FsPathError;
@@ -126,7 +126,11 @@ pub trait Db:
126126
fn insert_block_hash(&mut self, number: U256, hash: B256);
127127

128128
/// Write all chain data to serialized bytes buffer
129-
fn dump_state(&self, at: BlockEnv) -> DatabaseResult<Option<SerializableState>>;
129+
fn dump_state(
130+
&self,
131+
at: BlockEnv,
132+
best_number: U64,
133+
) -> DatabaseResult<Option<SerializableState>>;
130134

131135
/// Deserialize and add all chain data to the backend storage
132136
fn load_state(&mut self, state: SerializableState) -> DatabaseResult<bool> {
@@ -196,7 +200,11 @@ impl<T: DatabaseRef<Error = DatabaseError> + Send + Sync + Clone + fmt::Debug> D
196200
self.block_hashes.insert(number, hash);
197201
}
198202

199-
fn dump_state(&self, _at: BlockEnv) -> DatabaseResult<Option<SerializableState>> {
203+
fn dump_state(
204+
&self,
205+
_at: BlockEnv,
206+
_best_number: U64,
207+
) -> DatabaseResult<Option<SerializableState>> {
200208
Ok(None)
201209
}
202210

@@ -329,6 +337,8 @@ pub struct SerializableState {
329337
/// Note: This is an Option for backwards compatibility: <https://github.com/foundry-rs/foundry/issues/5460>
330338
pub block: Option<BlockEnv>,
331339
pub accounts: BTreeMap<Address, SerializableAccountRecord>,
340+
/// The best block number of the state, can be different from block number (Arbitrum chain).
341+
pub best_block_number: Option<U64>,
332342
}
333343

334344
// === impl SerializableState ===

crates/anvil/src/eth/backend/mem/fork_db.rs

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ use crate::{
55
},
66
revm::primitives::AccountInfo,
77
};
8-
use alloy_primitives::{Address, B256, U256};
8+
use alloy_primitives::{Address, B256, U256, U64};
99
use alloy_rpc_types::BlockId;
1010
use foundry_evm::{
1111
backend::{DatabaseResult, RevertSnapshotAction, StateSnapshot},
@@ -32,7 +32,11 @@ impl Db for ForkedDatabase {
3232
self.inner().block_hashes().write().insert(number, hash);
3333
}
3434

35-
fn dump_state(&self, at: BlockEnv) -> DatabaseResult<Option<SerializableState>> {
35+
fn dump_state(
36+
&self,
37+
at: BlockEnv,
38+
best_number: U64,
39+
) -> DatabaseResult<Option<SerializableState>> {
3640
let mut db = self.database().clone();
3741
let accounts = self
3842
.database()
@@ -57,7 +61,11 @@ impl Db for ForkedDatabase {
5761
))
5862
})
5963
.collect::<Result<_, _>>()?;
60-
Ok(Some(SerializableState { block: Some(at), accounts }))
64+
Ok(Some(SerializableState {
65+
block: Some(at),
66+
accounts,
67+
best_block_number: Some(best_number),
68+
}))
6169
}
6270

6371
fn snapshot(&mut self) -> U256 {

crates/anvil/src/eth/backend/mem/in_memory_db.rs

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ use crate::{
88
mem::state::{state_merkle_trie_root, storage_trie_db, trie_hash_db},
99
revm::primitives::AccountInfo,
1010
};
11-
use alloy_primitives::{Address, B256, U256};
11+
use alloy_primitives::{Address, B256, U256, U64};
1212
use alloy_rpc_types::BlockId;
1313
use foundry_evm::{
1414
backend::{DatabaseResult, StateSnapshot},
@@ -32,7 +32,11 @@ impl Db for MemDb {
3232
self.inner.block_hashes.insert(number, hash);
3333
}
3434

35-
fn dump_state(&self, at: BlockEnv) -> DatabaseResult<Option<SerializableState>> {
35+
fn dump_state(
36+
&self,
37+
at: BlockEnv,
38+
best_number: U64,
39+
) -> DatabaseResult<Option<SerializableState>> {
3640
let accounts = self
3741
.inner
3842
.accounts
@@ -57,7 +61,11 @@ impl Db for MemDb {
5761
})
5862
.collect::<Result<_, _>>()?;
5963

60-
Ok(Some(SerializableState { block: Some(at), accounts }))
64+
Ok(Some(SerializableState {
65+
block: Some(at),
66+
accounts,
67+
best_block_number: Some(best_number),
68+
}))
6169
}
6270

6371
/// Creates a new snapshot
@@ -160,7 +168,7 @@ mod tests {
160168

161169
dump_db.set_storage_at(test_addr, U256::from(1234567), U256::from(1)).unwrap();
162170

163-
let state = dump_db.dump_state(Default::default()).unwrap().unwrap();
171+
let state = dump_db.dump_state(Default::default(), U64::ZERO).unwrap().unwrap();
164172

165173
let mut load_db = MemDb::default();
166174

crates/anvil/src/eth/backend/mem/mod.rs

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -495,7 +495,7 @@ impl Backend {
495495

496496
/// Returns the current best number of the chain
497497
pub fn best_number(&self) -> u64 {
498-
self.env.read().block.number.try_into().unwrap_or(u64::MAX)
498+
self.blockchain.storage.read().best_number.try_into().unwrap_or(u64::MAX)
499499
}
500500

501501
/// Sets the block number
@@ -721,7 +721,8 @@ impl Backend {
721721
/// Get the current state.
722722
pub async fn serialized_state(&self) -> Result<SerializableState, BlockchainError> {
723723
let at = self.env.read().block.clone();
724-
let state = self.db.read().await.dump_state(at)?;
724+
let best_number = self.blockchain.storage.read().best_number;
725+
let state = self.db.read().await.dump_state(at, best_number)?;
725726
state.ok_or_else(|| {
726727
RpcError::invalid_params("Dumping state not supported with the current configuration")
727728
.into()
@@ -742,7 +743,12 @@ impl Backend {
742743
pub async fn load_state(&self, state: SerializableState) -> Result<bool, BlockchainError> {
743744
// reset the block env
744745
if let Some(block) = state.block.clone() {
745-
self.env.write().block = block;
746+
self.env.write().block = block.clone();
747+
748+
// Set the current best block number.
749+
// Defaults to block number for compatibility with existing state files.
750+
self.blockchain.storage.write().best_number =
751+
state.best_block_number.unwrap_or(block.number.to::<U64>());
746752
}
747753

748754
if !self.db.write().await.load_state(state)? {
@@ -922,8 +928,9 @@ impl Backend {
922928
let ExecutedTransactions { block, included, invalid } = executed_tx;
923929
let BlockInfo { block, transactions, receipts } = block;
924930

931+
let mut storage = self.blockchain.storage.write();
925932
let header = block.header.clone();
926-
let block_number: U64 = env.block.number.to::<U64>();
933+
let block_number = storage.best_number.saturating_add(U64::from(1));
927934

928935
trace!(
929936
target: "backend",
@@ -933,7 +940,6 @@ impl Backend {
933940
transactions.iter().map(|tx| tx.transaction_hash).collect::<Vec<_>>()
934941
);
935942

936-
let mut storage = self.blockchain.storage.write();
937943
// update block metadata
938944
storage.best_number = block_number;
939945
storage.best_hash = block_hash;

crates/anvil/tests/it/fork.rs

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1123,6 +1123,55 @@ async fn test_arbitrum_fork_dev_balance() {
11231123
}
11241124
}
11251125

1126+
// <https://github.com/foundry-rs/foundry/issues/6749>
1127+
#[tokio::test(flavor = "multi_thread")]
1128+
async fn test_arbitrum_fork_block_number() {
1129+
// fork to get initial block for test
1130+
let (_, handle) = spawn(
1131+
fork_config()
1132+
.with_fork_block_number(None::<u64>)
1133+
.with_eth_rpc_url(Some("https://arb1.arbitrum.io/rpc".to_string())),
1134+
)
1135+
.await;
1136+
let provider = ethers_http_provider(&handle.http_endpoint());
1137+
let initial_block_number = provider.get_block_number().await.unwrap().as_u64();
1138+
1139+
// fork again at block number returned by `eth_blockNumber`
1140+
// if wrong block number returned (e.g. L1) then fork will fail with error code -32000: missing
1141+
// trie node
1142+
let (api, _) = spawn(
1143+
fork_config()
1144+
.with_fork_block_number(Some(initial_block_number))
1145+
.with_eth_rpc_url(Some("https://arb1.arbitrum.io/rpc".to_string())),
1146+
)
1147+
.await;
1148+
let block_number = api.block_number().unwrap().to::<u64>();
1149+
assert_eq!(block_number, initial_block_number);
1150+
1151+
// take snapshot at initial block number
1152+
let snapshot = api.evm_snapshot().await.unwrap();
1153+
1154+
// mine new block and check block number returned by `eth_blockNumber`
1155+
api.mine_one().await;
1156+
let block_number = api.block_number().unwrap().to::<u64>();
1157+
assert_eq!(block_number, initial_block_number + 1);
1158+
1159+
// revert to recorded snapshot and check block number
1160+
assert!(api.evm_revert(snapshot).await.unwrap());
1161+
let block_number = api.block_number().unwrap().to::<u64>();
1162+
assert_eq!(block_number, initial_block_number);
1163+
1164+
// reset fork to different block number and compare with block returned by `eth_blockNumber`
1165+
api.anvil_reset(Some(Forking {
1166+
json_rpc_url: Some("https://arb1.arbitrum.io/rpc".to_string()),
1167+
block_number: Some(initial_block_number - 2),
1168+
}))
1169+
.await
1170+
.unwrap();
1171+
let block_number = api.block_number().unwrap().to::<u64>();
1172+
assert_eq!(block_number, initial_block_number - 2);
1173+
}
1174+
11261175
// <https://github.com/foundry-rs/foundry/issues/7023>
11271176
#[tokio::test(flavor = "multi_thread")]
11281177
async fn test_fork_execution_reverted() {

crates/evm/core/src/utils.rs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,9 @@ pub use crate::ic::*;
1515

1616
/// Depending on the configured chain id and block number this should apply any specific changes
1717
///
18-
/// This checks for:
19-
/// - prevrandao mixhash after merge
18+
/// - checks for prevrandao mixhash after merge
19+
/// - applies chain specifics: on Arbitrum `block.number` is the L1 block
20+
/// Should be called with proper chain id (retrieved from provider if not provided).
2021
pub fn apply_chain_and_block_specific_env_changes(env: &mut revm::primitives::Env, block: &Block) {
2122
if let Ok(chain) = NamedChain::try_from(env.cfg.chain_id) {
2223
let block_number = block.header.number.unwrap_or_default();

0 commit comments

Comments
 (0)