Skip to main content

ef_tests/
models.rs

1//! Shared models for <https://github.com/ethereum/tests>
2
3use crate::{assert::assert_equal, Error};
4use alloy_consensus::Header as RethHeader;
5use alloy_eips::eip4895::Withdrawals;
6use alloy_genesis::GenesisAccount;
7use alloy_primitives::{keccak256, Address, Bloom, Bytes, B256, B64, U256};
8use reth_chainspec::{ChainSpec, ChainSpecBuilder, EthereumHardfork, ForkCondition};
9use reth_db_api::{cursor::DbDupCursorRO, tables, transaction::DbTx};
10use reth_primitives_traits::SealedHeader;
11use revm::primitives::HashMap;
12use serde::Deserialize;
13use std::{
14    collections::BTreeMap,
15    ops::Deref,
16    sync::{Arc, OnceLock, RwLock},
17};
18
19/// The definition of a blockchain test.
20#[derive(Debug, PartialEq, Eq, Deserialize)]
21#[serde(rename_all = "camelCase")]
22pub struct BlockchainTest {
23    /// Genesis block header.
24    pub genesis_block_header: Header,
25    /// RLP encoded genesis block.
26    #[serde(rename = "genesisRLP")]
27    pub genesis_rlp: Option<Bytes>,
28    /// Block data.
29    pub blocks: Vec<Block>,
30    /// The expected post state.
31    pub post_state: Option<BTreeMap<Address, Account>>,
32    /// The test pre-state.
33    pub pre: State,
34    /// Hash of the best block.
35    pub lastblockhash: B256,
36    /// Network spec.
37    pub network: ForkSpec,
38    #[serde(default)]
39    /// Engine spec.
40    pub seal_engine: SealEngine,
41}
42
43/// A block header in an Ethereum blockchain test.
44#[derive(Debug, PartialEq, Eq, Clone, Deserialize, Default)]
45#[serde(rename_all = "camelCase")]
46pub struct Header {
47    /// Bloom filter.
48    pub bloom: Bloom,
49    /// Coinbase.
50    pub coinbase: Address,
51    /// Difficulty.
52    pub difficulty: U256,
53    /// Extra data.
54    pub extra_data: Bytes,
55    /// Gas limit.
56    pub gas_limit: U256,
57    /// Gas used.
58    pub gas_used: U256,
59    /// Block Hash.
60    pub hash: B256,
61    /// Mix hash.
62    pub mix_hash: B256,
63    /// Seal nonce.
64    pub nonce: B64,
65    /// Block number.
66    pub number: U256,
67    /// Parent hash.
68    pub parent_hash: B256,
69    /// Receipt trie.
70    pub receipt_trie: B256,
71    /// State root.
72    pub state_root: B256,
73    /// Timestamp.
74    pub timestamp: U256,
75    /// Transactions trie.
76    pub transactions_trie: B256,
77    /// Uncle hash.
78    pub uncle_hash: B256,
79    /// Base fee per gas.
80    pub base_fee_per_gas: Option<U256>,
81    /// Withdrawals root.
82    pub withdrawals_root: Option<B256>,
83    /// Blob gas used.
84    pub blob_gas_used: Option<U256>,
85    /// Excess blob gas.
86    pub excess_blob_gas: Option<U256>,
87    /// Parent beacon block root.
88    pub parent_beacon_block_root: Option<B256>,
89    /// Requests root.
90    pub requests_hash: Option<B256>,
91    /// Target blobs per block.
92    pub target_blobs_per_block: Option<U256>,
93}
94
95impl From<Header> for SealedHeader {
96    fn from(value: Header) -> Self {
97        let header = RethHeader {
98            base_fee_per_gas: value.base_fee_per_gas.map(|v| v.to::<u64>()),
99            beneficiary: value.coinbase,
100            difficulty: value.difficulty,
101            extra_data: value.extra_data,
102            gas_limit: value.gas_limit.to::<u64>(),
103            gas_used: value.gas_used.to::<u64>(),
104            mix_hash: value.mix_hash,
105            nonce: u64::from_be_bytes(value.nonce.0).into(),
106            number: value.number.to::<u64>(),
107            timestamp: value.timestamp.to::<u64>(),
108            transactions_root: value.transactions_trie,
109            receipts_root: value.receipt_trie,
110            ommers_hash: value.uncle_hash,
111            state_root: value.state_root,
112            parent_hash: value.parent_hash,
113            logs_bloom: value.bloom,
114            withdrawals_root: value.withdrawals_root,
115            blob_gas_used: value.blob_gas_used.map(|v| v.to::<u64>()),
116            excess_blob_gas: value.excess_blob_gas.map(|v| v.to::<u64>()),
117            parent_beacon_block_root: value.parent_beacon_block_root,
118            requests_hash: value.requests_hash,
119            block_access_list_hash: None,
120            slot_number: None,
121        };
122        Self::new(header, value.hash)
123    }
124}
125
126/// A block in an Ethereum blockchain test.
127#[derive(Debug, PartialEq, Eq, Deserialize, Default)]
128#[serde(rename_all = "camelCase")]
129pub struct Block {
130    /// Block header.
131    pub block_header: Option<Header>,
132    /// RLP encoded block bytes
133    pub rlp: Bytes,
134    /// If the execution of the block should fail,
135    /// `expect_exception` is `Some`.
136    /// Its contents detail the reason for the failure.
137    pub expect_exception: Option<String>,
138    /// Transactions
139    pub transactions: Option<Vec<Transaction>>,
140    /// Uncle/ommer headers
141    pub uncle_headers: Option<Vec<Header>>,
142    /// Transaction Sequence
143    pub transaction_sequence: Option<Vec<TransactionSequence>>,
144    /// Withdrawals
145    pub withdrawals: Option<Withdrawals>,
146}
147
148/// Transaction sequence in block
149#[derive(Debug, PartialEq, Eq, Deserialize, Default)]
150#[serde(deny_unknown_fields)]
151#[serde(rename_all = "camelCase")]
152pub struct TransactionSequence {
153    exception: String,
154    raw_bytes: Bytes,
155    valid: String,
156}
157
158/// Ethereum blockchain test data state.
159#[derive(Clone, Debug, Eq, PartialEq, Deserialize, Default)]
160pub struct State(BTreeMap<Address, Account>);
161
162impl State {
163    /// Return state as genesis state.
164    pub fn into_genesis_state(self) -> BTreeMap<Address, GenesisAccount> {
165        self.0
166            .into_iter()
167            .map(|(address, account)| {
168                let storage = account
169                    .storage
170                    .iter()
171                    .filter(|(_, v)| !v.is_zero())
172                    .map(|(k, v)| {
173                        (
174                            B256::from_slice(&k.to_be_bytes::<32>()),
175                            B256::from_slice(&v.to_be_bytes::<32>()),
176                        )
177                    })
178                    .collect();
179                let account = GenesisAccount {
180                    balance: account.balance,
181                    nonce: Some(account.nonce.try_into().unwrap()),
182                    code: Some(account.code).filter(|c| !c.is_empty()),
183                    storage: Some(storage),
184                    private_key: None,
185                };
186                (address, account)
187            })
188            .collect::<BTreeMap<_, _>>()
189    }
190}
191
192impl Deref for State {
193    type Target = BTreeMap<Address, Account>;
194
195    fn deref(&self) -> &Self::Target {
196        &self.0
197    }
198}
199
200/// An account.
201#[derive(Debug, PartialEq, Eq, Deserialize, Clone, Default)]
202#[serde(deny_unknown_fields)]
203pub struct Account {
204    /// Balance.
205    pub balance: U256,
206    /// Code.
207    pub code: Bytes,
208    /// Nonce.
209    pub nonce: U256,
210    /// Storage.
211    pub storage: BTreeMap<U256, U256>,
212}
213
214impl Account {
215    /// Check that the account matches what is in the database.
216    ///
217    /// In case of a mismatch, `Err(Error::Assertion)` is returned.
218    pub fn assert_db(&self, address: Address, tx: &impl DbTx) -> Result<(), Error> {
219        let account =
220            tx.get_by_encoded_key::<tables::PlainAccountState>(&address)?.ok_or_else(|| {
221                Error::Assertion(format!(
222                    "Expected account ({address}) is missing from DB: {self:?}"
223                ))
224            })?;
225
226        assert_equal(self.balance, account.balance, "Balance does not match")?;
227        assert_equal(self.nonce.to(), account.nonce, "Nonce does not match")?;
228
229        if let Some(bytecode_hash) = account.bytecode_hash {
230            assert_equal(keccak256(&self.code), bytecode_hash, "Bytecode does not match")?;
231        } else {
232            assert_equal(
233                self.code.is_empty(),
234                true,
235                "Expected empty bytecode, got bytecode in db.",
236            )?;
237        }
238
239        let mut storage_cursor = tx.cursor_dup_read::<tables::PlainStorageState>()?;
240        for (slot, value) in &self.storage {
241            if let Some(entry) =
242                storage_cursor.seek_by_key_subkey(address, B256::new(slot.to_be_bytes()))?
243            {
244                if U256::from_be_bytes(entry.key.0) == *slot {
245                    assert_equal(
246                        *value,
247                        entry.value,
248                        &format!("Storage for slot {slot:?} does not match"),
249                    )?;
250                } else {
251                    return Err(Error::Assertion(format!(
252                        "Slot {slot:?} is missing from the database. Expected {value:?}"
253                    )))
254                }
255            } else {
256                return Err(Error::Assertion(format!(
257                    "Slot {slot:?} is missing from the database. Expected {value:?}"
258                )))
259            }
260        }
261
262        Ok(())
263    }
264}
265
266/// Fork specification.
267#[derive(Debug, PartialEq, Eq, PartialOrd, Hash, Ord, Clone, Copy, Deserialize)]
268pub enum ForkSpec {
269    /// Frontier
270    Frontier,
271    /// Frontier to Homestead
272    FrontierToHomesteadAt5,
273    /// Homestead
274    Homestead,
275    /// Homestead to DAO
276    HomesteadToDaoAt5,
277    /// Homestead to Tangerine
278    HomesteadToEIP150At5,
279    /// Tangerine
280    EIP150,
281    /// Spurious Dragon
282    EIP158, // EIP-161: State trie clearing
283    /// Spurious Dragon to Byzantium
284    EIP158ToByzantiumAt5,
285    /// Byzantium
286    Byzantium,
287    /// Byzantium to Constantinople
288    ByzantiumToConstantinopleAt5, // SKIPPED
289    /// Byzantium to Constantinople
290    ByzantiumToConstantinopleFixAt5,
291    /// Constantinople
292    Constantinople, // SKIPPED
293    /// Constantinople fix
294    ConstantinopleFix,
295    /// Istanbul
296    Istanbul,
297    /// Berlin
298    Berlin,
299    /// Berlin to London
300    BerlinToLondonAt5,
301    /// London
302    London,
303    /// Paris aka The Merge
304    #[serde(alias = "Paris")]
305    Merge,
306    /// Paris to Shanghai at time 15k
307    ParisToShanghaiAtTime15k,
308    /// Shanghai
309    Shanghai,
310    /// Shanghai to Cancun at time 15k
311    ShanghaiToCancunAtTime15k,
312    /// Merge EOF test
313    #[serde(alias = "Merge+3540+3670")]
314    MergeEOF,
315    /// After Merge Init Code test
316    #[serde(alias = "Merge+3860")]
317    MergeMeterInitCode,
318    /// After Merge plus new PUSH0 opcode
319    #[serde(alias = "Merge+3855")]
320    MergePush0,
321    /// Cancun
322    Cancun,
323    /// Cancun to Prague at time 15k
324    CancunToPragueAtTime15k,
325    /// Prague
326    Prague,
327    /// Osaka
328    Osaka,
329}
330
331impl ForkSpec {
332    /// Converts this EF fork spec to a Reth [`ChainSpec`].
333    pub fn to_chain_spec(self) -> Arc<ChainSpec> {
334        static MAP: OnceLock<RwLock<HashMap<ForkSpec, Arc<ChainSpec>>>> = OnceLock::new();
335        let map = MAP.get_or_init(Default::default);
336        if let Some(r) = map.read().unwrap().get(&self) {
337            return r.clone();
338        }
339        map.write()
340            .unwrap()
341            .entry(self)
342            .or_insert_with(|| Arc::new(self.to_chain_spec_inner()))
343            .clone()
344    }
345
346    fn to_chain_spec_inner(self) -> ChainSpec {
347        let spec_builder = ChainSpecBuilder::mainnet().reset();
348
349        match self {
350            Self::Frontier => spec_builder.frontier_activated(),
351            Self::FrontierToHomesteadAt5 => spec_builder
352                .frontier_activated()
353                .with_fork(EthereumHardfork::Homestead, ForkCondition::Block(5)),
354            Self::Homestead => spec_builder.homestead_activated(),
355            Self::HomesteadToDaoAt5 => spec_builder
356                .homestead_activated()
357                .with_fork(EthereumHardfork::Dao, ForkCondition::Block(5)),
358            Self::HomesteadToEIP150At5 => spec_builder
359                .homestead_activated()
360                .with_fork(EthereumHardfork::Tangerine, ForkCondition::Block(5)),
361            Self::EIP150 => spec_builder.tangerine_whistle_activated(),
362            Self::EIP158 => spec_builder.spurious_dragon_activated(),
363            Self::EIP158ToByzantiumAt5 => spec_builder
364                .spurious_dragon_activated()
365                .with_fork(EthereumHardfork::Byzantium, ForkCondition::Block(5)),
366            Self::Byzantium => spec_builder.byzantium_activated(),
367            Self::ByzantiumToConstantinopleAt5 => spec_builder
368                .byzantium_activated()
369                .with_fork(EthereumHardfork::Constantinople, ForkCondition::Block(5)),
370            Self::ByzantiumToConstantinopleFixAt5 => spec_builder
371                .byzantium_activated()
372                .with_fork(EthereumHardfork::Petersburg, ForkCondition::Block(5)),
373            Self::Constantinople => spec_builder.constantinople_activated(),
374            Self::ConstantinopleFix => spec_builder.petersburg_activated(),
375            Self::Istanbul => spec_builder.istanbul_activated(),
376            Self::Berlin => spec_builder.berlin_activated(),
377            Self::BerlinToLondonAt5 => spec_builder
378                .berlin_activated()
379                .with_fork(EthereumHardfork::London, ForkCondition::Block(5)),
380            Self::London => spec_builder.london_activated(),
381            Self::Merge | Self::MergeEOF | Self::MergeMeterInitCode | Self::MergePush0 => {
382                spec_builder.paris_activated()
383            }
384            Self::ParisToShanghaiAtTime15k => spec_builder
385                .paris_activated()
386                .with_fork(EthereumHardfork::Shanghai, ForkCondition::Timestamp(15_000)),
387            Self::Shanghai => spec_builder.shanghai_activated(),
388            Self::ShanghaiToCancunAtTime15k => spec_builder
389                .shanghai_activated()
390                .with_fork(EthereumHardfork::Cancun, ForkCondition::Timestamp(15_000)),
391            Self::Cancun => spec_builder.cancun_activated(),
392            Self::CancunToPragueAtTime15k => spec_builder
393                .cancun_activated()
394                .with_fork(EthereumHardfork::Prague, ForkCondition::Timestamp(15_000)),
395            Self::Prague => spec_builder.prague_activated(),
396            Self::Osaka => spec_builder.osaka_activated(),
397        }
398        .build()
399    }
400}
401
402/// Possible seal engines.
403#[derive(Debug, PartialEq, Eq, Default, Deserialize)]
404pub enum SealEngine {
405    /// No consensus checks.
406    #[default]
407    NoProof,
408}
409
410/// Ethereum blockchain test transaction data.
411#[derive(Debug, PartialEq, Eq, Deserialize)]
412#[serde(rename_all = "camelCase")]
413pub struct Transaction {
414    /// Transaction type
415    #[serde(rename = "type")]
416    pub transaction_type: Option<U256>,
417    /// Data.
418    pub data: Bytes,
419    /// Gas limit.
420    pub gas_limit: U256,
421    /// Gas price.
422    pub gas_price: Option<U256>,
423    /// Nonce.
424    pub nonce: U256,
425    /// Signature r part.
426    pub r: U256,
427    /// Signature s part.
428    pub s: U256,
429    /// Parity bit.
430    pub v: U256,
431    /// Transaction value.
432    pub value: U256,
433    /// Chain ID.
434    pub chain_id: Option<U256>,
435    /// Access list.
436    pub access_list: Option<AccessList>,
437    /// Max fee per gas.
438    pub max_fee_per_gas: Option<U256>,
439    /// Max priority fee per gas
440    pub max_priority_fee_per_gas: Option<U256>,
441    /// Transaction hash.
442    pub hash: Option<B256>,
443}
444
445/// Access list item
446#[derive(Debug, PartialEq, Eq, Deserialize, Clone)]
447#[serde(rename_all = "camelCase")]
448pub struct AccessListItem {
449    /// Account address
450    pub address: Address,
451    /// Storage key.
452    pub storage_keys: Vec<B256>,
453}
454
455/// Access list.
456pub type AccessList = Vec<AccessListItem>;
457
458#[cfg(test)]
459mod tests {
460    use super::*;
461
462    #[test]
463    fn header_deserialize() {
464        let test = r#"{
465            "baseFeePerGas" : "0x0a",
466            "bloom" : "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000",
467            "coinbase" : "0x2adc25665018aa1fe0e6bc666dac8fc2697ff9ba",
468            "difficulty" : "0x020000",
469            "extraData" : "0x00",
470            "gasLimit" : "0x10000000000000",
471            "gasUsed" : "0x10000000000000",
472            "hash" : "0x7ebfee2a2c785fef181b8ffd92d4a48a0660ec000f465f309757e3f092d13882",
473            "mixHash" : "0x0000000000000000000000000000000000000000000000000000000000000000",
474            "nonce" : "0x0000000000000000",
475            "number" : "0x01",
476            "parentHash" : "0xa8f2eb2ea9dccbf725801eef5a31ce59bada431e888dfd5501677cc4365dc3be",
477            "receiptTrie" : "0xbdd943f5c62ae0299324244a0f65524337ada9817e18e1764631cc1424f3a293",
478            "stateRoot" : "0xc9c6306ee3e5acbaabe8e2fa28a10c12e27bad1d1aacc271665149f70519f8b0",
479            "timestamp" : "0x03e8",
480            "transactionsTrie" : "0xf5893b055ca05e4f14d1792745586a1376e218180bd56bd96b2b024e1dc78300",
481            "uncleHash" : "0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347"
482        }"#;
483        let res = serde_json::from_str::<Header>(test);
484        assert!(res.is_ok(), "Failed to deserialize Header with error: {res:?}");
485    }
486
487    #[test]
488    fn transaction_deserialize() {
489        let test = r#"[
490            {
491                "accessList" : [
492                ],
493                "chainId" : "0x01",
494                "data" : "0x693c61390000000000000000000000000000000000000000000000000000000000000000",
495                "gasLimit" : "0x10000000000000",
496                "maxFeePerGas" : "0x07d0",
497                "maxPriorityFeePerGas" : "0x00",
498                "nonce" : "0x01",
499                "r" : "0x5fecc3972a35c9e341b41b0c269d9a7325e13269fb01c2f64cbce1046b3441c8",
500                "s" : "0x7d4d0eda0e4ebd53c5d0b6fc35c600b317f8fa873b3963ab623ec9cec7d969bd",
501                "sender" : "0xa94f5374fce5edbc8e2a8697c15331677e6ebf0b",
502                "to" : "0xcccccccccccccccccccccccccccccccccccccccc",
503                "type" : "0x02",
504                "v" : "0x01",
505                "value" : "0x00"
506            }
507        ]"#;
508
509        let res = serde_json::from_str::<Vec<Transaction>>(test);
510        assert!(res.is_ok(), "Failed to deserialize transaction with error: {res:?}");
511    }
512}