Skip to content

Commit dd317d3

Browse files
authored
fix(anvil): v1.2 state load compatibility (#11179)
* add backwards compat for v1.2 anvil state dumps * nits * rename to _compat * nits * clean up * clean up * clean up, introduce LegacyBlockEnv * fix tests
1 parent 5b96dda commit dd317d3

File tree

2 files changed

+574
-10
lines changed

2 files changed

+574
-10
lines changed

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

Lines changed: 129 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,11 @@
11
//! Helper types for working with [revm](foundry_evm::revm)
22
3-
use crate::mem::storage::MinedTransaction;
3+
use std::{
4+
collections::BTreeMap,
5+
fmt::{self, Debug},
6+
path::Path,
7+
};
8+
49
use alloy_consensus::Header;
510
use alloy_primitives::{Address, B256, Bytes, U256, keccak256, map::HashMap};
611
use alloy_rpc_types::BlockId;
@@ -16,19 +21,18 @@ use revm::{
1621
Database, DatabaseCommit,
1722
bytecode::Bytecode,
1823
context::BlockEnv,
24+
context_interface::block::BlobExcessGasAndPrice,
1925
database::{CacheDB, DatabaseRef, DbAccount},
20-
primitives::KECCAK_EMPTY,
26+
primitives::{KECCAK_EMPTY, eip4844::BLOB_BASE_FEE_UPDATE_FRACTION_PRAGUE},
2127
state::AccountInfo,
2228
};
2329
use serde::{
2430
Deserialize, Deserializer, Serialize,
25-
de::{MapAccess, Visitor},
26-
};
27-
use std::{
28-
collections::BTreeMap,
29-
fmt::{self, Debug},
30-
path::Path,
31+
de::{Error as DeError, MapAccess, Visitor},
3132
};
33+
use serde_json::Value;
34+
35+
use crate::mem::storage::MinedTransaction;
3236

3337
/// Helper trait get access to the full state data of the database
3438
pub trait MaybeFullDatabase: DatabaseRef<Error = DatabaseError> + Debug {
@@ -385,14 +389,131 @@ impl MaybeFullDatabase for StateDb {
385389
}
386390
}
387391

392+
/// Legacy block environment from before v1.3.
393+
#[derive(Debug, Deserialize)]
394+
#[serde(rename_all = "snake_case")]
395+
pub struct LegacyBlockEnv {
396+
pub number: Option<StringOrU64>,
397+
#[serde(alias = "coinbase")]
398+
pub beneficiary: Option<Address>,
399+
pub timestamp: Option<StringOrU64>,
400+
pub gas_limit: Option<StringOrU64>,
401+
pub basefee: Option<StringOrU64>,
402+
pub difficulty: Option<StringOrU64>,
403+
pub prevrandao: Option<B256>,
404+
pub blob_excess_gas_and_price: Option<LegacyBlobExcessGasAndPrice>,
405+
}
406+
407+
/// Legacy blob excess gas and price structure from before v1.3.
408+
#[derive(Debug, Deserialize)]
409+
pub struct LegacyBlobExcessGasAndPrice {
410+
pub excess_blob_gas: u64,
411+
pub blob_gasprice: u64,
412+
}
413+
414+
/// Legacy string or u64 type from before v1.3.
415+
#[derive(Debug, Deserialize)]
416+
#[serde(untagged)]
417+
pub enum StringOrU64 {
418+
Hex(String),
419+
Dec(u64),
420+
}
421+
422+
impl StringOrU64 {
423+
pub fn to_u64(&self) -> Option<u64> {
424+
match self {
425+
Self::Dec(n) => Some(*n),
426+
Self::Hex(s) => s.strip_prefix("0x").and_then(|s| u64::from_str_radix(s, 16).ok()),
427+
}
428+
}
429+
430+
pub fn to_u256(&self) -> Option<U256> {
431+
match self {
432+
Self::Dec(n) => Some(U256::from(*n)),
433+
Self::Hex(s) => s.strip_prefix("0x").and_then(|s| U256::from_str_radix(s, 16).ok()),
434+
}
435+
}
436+
}
437+
438+
/// Converts a `LegacyBlockEnv` to a `BlockEnv`, handling the conversion of legacy fields.
439+
impl TryFrom<LegacyBlockEnv> for BlockEnv {
440+
type Error = &'static str;
441+
442+
fn try_from(legacy: LegacyBlockEnv) -> Result<Self, Self::Error> {
443+
Ok(Self {
444+
number: legacy.number.and_then(|v| v.to_u256()).unwrap_or(U256::ZERO),
445+
beneficiary: legacy.beneficiary.unwrap_or(Address::ZERO),
446+
timestamp: legacy.timestamp.and_then(|v| v.to_u256()).unwrap_or(U256::ONE),
447+
gas_limit: legacy.gas_limit.and_then(|v| v.to_u64()).unwrap_or(u64::MAX),
448+
basefee: legacy.basefee.and_then(|v| v.to_u64()).unwrap_or(0),
449+
difficulty: legacy.difficulty.and_then(|v| v.to_u256()).unwrap_or(U256::ZERO),
450+
prevrandao: legacy.prevrandao.or(Some(B256::ZERO)),
451+
blob_excess_gas_and_price: legacy
452+
.blob_excess_gas_and_price
453+
.map(|v| BlobExcessGasAndPrice::new(v.excess_blob_gas, v.blob_gasprice))
454+
.or_else(|| {
455+
Some(BlobExcessGasAndPrice::new(0, BLOB_BASE_FEE_UPDATE_FRACTION_PRAGUE))
456+
}),
457+
})
458+
}
459+
}
460+
461+
/// Custom deserializer for `BlockEnv` that handles both v1.2 and v1.3+ formats.
462+
fn deserialize_block_env_compat<'de, D>(deserializer: D) -> Result<Option<BlockEnv>, D::Error>
463+
where
464+
D: Deserializer<'de>,
465+
{
466+
let value: Option<Value> = Option::deserialize(deserializer)?;
467+
let Some(value) = value else {
468+
return Ok(None);
469+
};
470+
471+
if let Ok(env) = BlockEnv::deserialize(&value) {
472+
return Ok(Some(env));
473+
}
474+
475+
let legacy: LegacyBlockEnv = serde_json::from_value(value).map_err(|e| {
476+
D::Error::custom(format!("Legacy deserialization of `BlockEnv` failed: {e}"))
477+
})?;
478+
479+
Ok(Some(BlockEnv::try_from(legacy).map_err(D::Error::custom)?))
480+
}
481+
482+
/// Custom deserializer for `best_block_number` that handles both v1.2 and v1.3+ formats.
483+
fn deserialize_best_block_number_compat<'de, D>(deserializer: D) -> Result<Option<u64>, D::Error>
484+
where
485+
D: Deserializer<'de>,
486+
{
487+
let value: Option<Value> = Option::deserialize(deserializer)?;
488+
let Some(value) = value else {
489+
return Ok(None);
490+
};
491+
492+
let number = match value {
493+
Value::Number(n) => n.as_u64(),
494+
Value::String(s) => {
495+
if let Some(s) = s.strip_prefix("0x") {
496+
u64::from_str_radix(s, 16).ok()
497+
} else {
498+
s.parse().ok()
499+
}
500+
}
501+
_ => None,
502+
};
503+
504+
Ok(number)
505+
}
506+
388507
#[derive(Clone, Debug, Default, Serialize, Deserialize)]
389508
pub struct SerializableState {
390509
/// The block number of the state
391510
///
392511
/// Note: This is an Option for backwards compatibility: <https://github.com/foundry-rs/foundry/issues/5460>
512+
#[serde(deserialize_with = "deserialize_block_env_compat")]
393513
pub block: Option<BlockEnv>,
394514
pub accounts: BTreeMap<Address, SerializableAccountRecord>,
395515
/// The best block number of the state, can be different from block number (Arbitrum chain).
516+
#[serde(deserialize_with = "deserialize_best_block_number_compat")]
396517
pub best_block_number: Option<u64>,
397518
#[serde(default)]
398519
pub blocks: Vec<SerializableBlock>,

0 commit comments

Comments
 (0)