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