diff --git a/.github/workflows/unit.yml b/.github/workflows/unit.yml index edaa52bbb..9b6038301 100644 --- a/.github/workflows/unit.yml +++ b/.github/workflows/unit.yml @@ -58,14 +58,15 @@ jobs: RUST_LOG: info,sync=error steps: - name: Checkout sources - uses: actions/checkout@v2 + uses: actions/checkout@v3 - name: Checkout ethereum/tests - uses: actions/checkout@v2 + uses: actions/checkout@v3 with: repository: ethereum/tests - path: ethtests + path: testing/ef-tests/ethereum-tests submodules: recursive + depth: 1 - name: Install toolchain uses: actions-rs/toolchain@v1 @@ -73,13 +74,14 @@ jobs: toolchain: stable profile: minimal override: true - - uses: Swatinem/rust-cache@v1 with: cache-on-failure: true + - name: Install latest nextest release + uses: taiki-e/install-action@nextest - name: Run Ethereum tests - run: cargo run --release -- test-chain ethtests/BlockchainTests/GeneralStateTests/ + run: cargo nextest run --release -p ef-tests --features ef-tests doc-test: name: rustdoc diff --git a/Cargo.lock b/Cargo.lock index 64e0e2ee0..cec94c78e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1544,6 +1544,24 @@ dependencies = [ "syn 1.0.109", ] +[[package]] +name = "ef-tests" +version = "0.1.0" +dependencies = [ + "reth-db", + "reth-interfaces", + "reth-primitives", + "reth-provider", + "reth-revm", + "reth-rlp", + "reth-stages", + "serde", + "serde_json", + "thiserror", + "tokio", + "walkdir", +] + [[package]] name = "either" version = "1.8.1" @@ -5980,18 +5998,18 @@ checksum = "cd0b0ec5f1c1ca621c432a25813d8d60c88abe6d3e08a3eb9cf37d97a0fe3d73" [[package]] name = "serde" -version = "1.0.160" +version = "1.0.163" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bb2f3770c8bce3bcda7e149193a069a0f4365bda1fa5cd88e03bca26afc1216c" +checksum = "2113ab51b87a539ae008b5c6c02dc020ffa39afd2d83cffcb3f4eb2722cebec2" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.160" +version = "1.0.163" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "291a097c63d8497e00160b166a967a4a79c64f3facdd01cbd7502231688d77df" +checksum = "8c805777e3930c8883389c602315a24224bcc738b63905ef87cd1420353ea93e" dependencies = [ "proc-macro2 1.0.56", "quote 1.0.26", @@ -6000,9 +6018,9 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.94" +version = "1.0.96" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1c533a59c9d8a93a09c6ab31f0fd5e5f4dd1b8fc9434804029839884765d04ea" +checksum = "057d394a50403bcac12672b2b18fb387ab6d289d957dab67dd201875391e52f1" dependencies = [ "itoa", "ryu", @@ -7337,12 +7355,11 @@ dependencies = [ [[package]] name = "walkdir" -version = "2.3.2" +version = "2.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "808cf2735cd4b6866113f648b791c6adc5714537bc222d9347bb203386ffda56" +checksum = "36df944cda56c7d8d8b7496af378e6b16de9284591917d307c9b4d313c44e698" dependencies = [ "same-file", - "winapi", "winapi-util", ] diff --git a/Cargo.toml b/Cargo.toml index 1e8ec2035..d240927db 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -42,6 +42,7 @@ members = [ "crates/tasks", "crates/transaction-pool", "crates/trie", + "testing/ef-tests" ] default-members = ["bin/reth"] diff --git a/bin/reth/src/cli.rs b/bin/reth/src/cli.rs index 29d27fe5b..d309b6e8a 100644 --- a/bin/reth/src/cli.rs +++ b/bin/reth/src/cli.rs @@ -4,7 +4,7 @@ use crate::{ dirs::{LogsDir, PlatformPath}, merkle_debug, node, p2p, runner::CliRunner, - stage, test_eth_chain, test_vectors, + stage, test_vectors, version::{LONG_VERSION, SHORT_VERSION}, }; use clap::{ArgAction, Args, Parser, Subcommand}; @@ -35,7 +35,6 @@ pub fn run() -> eyre::Result<()> { Commands::Stage(command) => runner.run_blocking_until_ctrl_c(command.execute()), Commands::P2P(command) => runner.run_until_ctrl_c(command.execute()), Commands::TestVectors(command) => runner.run_until_ctrl_c(command.execute()), - Commands::TestEthChain(command) => runner.run_until_ctrl_c(command.execute()), Commands::Config(command) => runner.run_until_ctrl_c(command.execute()), Commands::MerkleDebug(command) => runner.run_until_ctrl_c(command.execute()), } @@ -62,9 +61,6 @@ pub enum Commands { /// P2P Debugging utilities #[command(name = "p2p")] P2P(p2p::Command), - /// Run Ethereum blockchain tests - #[command(name = "test-chain")] - TestEthChain(test_eth_chain::Command), /// Generate Test Vectors #[command(name = "test-vectors")] TestVectors(test_vectors::Command), diff --git a/bin/reth/src/lib.rs b/bin/reth/src/lib.rs index 268134edd..14d31cefd 100644 --- a/bin/reth/src/lib.rs +++ b/bin/reth/src/lib.rs @@ -19,7 +19,6 @@ pub mod p2p; pub mod prometheus_exporter; pub mod runner; pub mod stage; -pub mod test_eth_chain; pub mod test_vectors; pub mod utils; pub mod version; diff --git a/bin/reth/src/test_eth_chain/mod.rs b/bin/reth/src/test_eth_chain/mod.rs deleted file mode 100644 index b36b3fa4a..000000000 --- a/bin/reth/src/test_eth_chain/mod.rs +++ /dev/null @@ -1,60 +0,0 @@ -//! Command for running Ethereum chain tests. - -use clap::Parser; -use eyre::eyre; -use futures::{stream::FuturesUnordered, StreamExt}; -use std::path::PathBuf; -use tracing::{error, info, warn}; -/// Models for parsing JSON blockchain tests -pub mod models; -/// Ethereum blockhain test runner -pub mod runner; - -use runner::TestOutcome; - -/// Execute Ethereum blockchain tests by specifying path to json files -#[derive(Debug, Parser)] -pub struct Command { - /// Path to Ethereum JSON test files - path: Vec, -} - -impl Command { - /// Execute the command - pub async fn execute(self) -> eyre::Result<()> { - let mut futs: FuturesUnordered<_> = self - .path - .iter() - .flat_map(|item| reth_staged_sync::utils::find_all_files_with_postfix(item, ".json")) - .map(|file| async { (runner::run_test(file.clone()).await, file) }) - .collect(); - - let mut failed = 0; - let mut passed = 0; - let mut skipped = 0; - while let Some((result, file)) = futs.next().await { - match TestOutcome::from(result) { - TestOutcome::Passed => { - info!(target: "reth::cli", "[+] Test {file:?} passed."); - passed += 1; - } - TestOutcome::Skipped => { - warn!(target: "reth::cli", "[=] Test {file:?} skipped."); - skipped += 1; - } - TestOutcome::Failed(error) => { - error!(target: "reth::cli", "Test {file:?} failed:\n{error}"); - failed += 1; - } - } - } - - info!(target: "reth::cli", "{passed}/{0} tests passed, {skipped}/{0} skipped, {failed}/{0} failed.\n", failed + passed + skipped); - - if failed != 0 { - Err(eyre!("Failed {failed} tests")) - } else { - Ok(()) - } - } -} diff --git a/bin/reth/src/test_eth_chain/runner.rs b/bin/reth/src/test_eth_chain/runner.rs deleted file mode 100644 index 73e113bee..000000000 --- a/bin/reth/src/test_eth_chain/runner.rs +++ /dev/null @@ -1,303 +0,0 @@ -use super::models::Test; -use crate::test_eth_chain::models::{ForkSpec, RootOrState}; -use eyre::eyre; -use reth_db::{ - cursor::DbCursorRO, - database::Database, - mdbx::test_utils::create_test_rw_db, - tables, - transaction::{DbTx, DbTxMut}, - DatabaseError as DbError, -}; -use reth_primitives::{ - keccak256, Account as RethAccount, Address, Bytecode, ChainSpec, JsonU256, SealedBlock, - SealedHeader, StageCheckpoint, StorageEntry, H256, U256, -}; -use reth_provider::Transaction; -use reth_rlp::Decodable; -use reth_stages::{stages::ExecutionStage, ExecInput, Stage, StageId}; -use std::{ - collections::HashMap, - ffi::OsStr, - path::{Path, PathBuf}, - sync::Arc, -}; -use tracing::{debug, trace}; - -/// The outcome of a test. -#[derive(Debug)] -pub enum TestOutcome { - /// The test was skipped. - Skipped, - /// The test passed. - Passed, - /// The test failed. - Failed(eyre::Report), -} - -impl From> for TestOutcome { - fn from(v: eyre::Result) -> TestOutcome { - match v { - Ok(outcome) => outcome, - Err(err) => TestOutcome::Failed(err), - } - } -} - -/// Tests are test edge cases that are not possible to happen on mainnet, so we are skipping them. -pub fn should_skip(path: &Path) -> bool { - // funky test with `bigint 0x00` value in json :) not possible to happen on mainnet and require - // custom json parser. https://github.com/ethereum/tests/issues/971 - if path.file_name() == Some(OsStr::new("ValueOverflow.json")) { - return true - } - // txbyte is of type 02 and we dont parse tx bytes for this test to fail. - if path.file_name() == Some(OsStr::new("typeTwoBerlin.json")) { - return true - } - // Test checks if nonce overflows. We are handling this correctly but we are not parsing - // exception in testsuite There are more nonce overflow tests that are in internal - // call/create, and those tests are passing and are enabled. - if path.file_name() == Some(OsStr::new("CreateTransactionHighNonce.json")) { - return true - } - - // Test check if gas price overflows, we handle this correctly but does not match tests specific - // exception. - if path.file_name() == Some(OsStr::new("HighGasPrice.json")) { - return true - } - - // Skip test where basefee/accesslist/difficulty is present but it shouldn't be supported in - // London/Berlin/TheMerge. https://github.com/ethereum/tests/blob/5b7e1ab3ffaf026d99d20b17bb30f533a2c80c8b/GeneralStateTests/stExample/eip1559.json#L130 - // It is expected to not execute these tests. - if path.file_name() == Some(OsStr::new("accessListExample.json")) || - path.file_name() == Some(OsStr::new("basefeeExample.json")) || - path.file_name() == Some(OsStr::new("eip1559.json")) || - path.file_name() == Some(OsStr::new("mergeTest.json")) - { - return true - } - - // These tests are passing, but they take a lot of time to execute so we are going to skip them. - if path.file_name() == Some(OsStr::new("loopExp.json")) || - path.file_name() == Some(OsStr::new("Call50000_sha256.json")) || - path.file_name() == Some(OsStr::new("static_Call50000_sha256.json")) || - path.file_name() == Some(OsStr::new("loopMul.json")) || - path.file_name() == Some(OsStr::new("CALLBlake2f_MaxRounds.json")) || - path.file_name() == Some(OsStr::new("shiftCombinations.json")) - { - return true - } - - // Ignore outdated EOF tests that haven't been updated for Cancun yet. - let eof_path = Path::new("EIPTests").join("stEOF"); - if path.to_string_lossy().contains(&*eof_path.to_string_lossy()) { - return true - } - - false -} - -/// Run one JSON-encoded Ethereum blockchain test at the specified path. -pub async fn run_test(path: PathBuf) -> eyre::Result { - let path = path.as_path(); - let json_file = std::fs::read(path)?; - let suites: Test = serde_json::from_reader(&*json_file)?; - - if should_skip(path) { - return Ok(TestOutcome::Skipped) - } - - debug!(target: "reth::cli", ?path, "Running test suite"); - - for (name, suite) in suites.0 { - if matches!( - suite.network, - ForkSpec::ByzantiumToConstantinopleAt5 | - ForkSpec::Constantinople | - ForkSpec::ConstantinopleFix | - ForkSpec::MergeEOF | - ForkSpec::MergeMeterInitCode | - ForkSpec::MergePush0 | - ForkSpec::Unknown - ) { - continue - } - - let pre_state = suite.pre.0; - - debug!(target: "reth::cli", name, network = ?suite.network, "Running test"); - - let chain_spec: ChainSpec = suite.network.into(); - - // Create db and acquire transaction - let db = create_test_rw_db(); - let tx = db.tx_mut()?; - - // insert genesis - let header: SealedHeader = suite.genesis_block_header.into(); - let genesis_block = SealedBlock { header, body: vec![], ommers: vec![], withdrawals: None }; - reth_provider::insert_canonical_block(&tx, genesis_block, None)?; - - let mut last_block = None; - suite.blocks.iter().try_for_each(|block| -> eyre::Result<()> { - let decoded = SealedBlock::decode(&mut block.rlp.as_ref())?; - last_block = Some(decoded.number); - reth_provider::insert_canonical_block(&tx, decoded, None)?; - Ok(()) - })?; - - pre_state.into_iter().try_for_each(|(address, account)| -> eyre::Result<()> { - let has_code = !account.code.is_empty(); - let code_hash = has_code.then(|| keccak256(&account.code)); - tx.put::( - address, - RethAccount { - balance: account.balance.0, - nonce: account.nonce.0.to::(), - bytecode_hash: code_hash, - }, - )?; - if let Some(code_hash) = code_hash { - tx.put::(code_hash, Bytecode::new_raw(account.code.0))?; - } - account.storage.iter().try_for_each(|(k, v)| { - trace!(target: "reth::cli", ?address, key = ?k.0, value = ?v.0, "Update storage"); - tx.put::( - address, - StorageEntry { key: H256::from_slice(&k.0.to_be_bytes::<32>()), value: v.0 }, - ) - })?; - - Ok(()) - })?; - - // Commit the pre suite state - tx.commit()?; - - let storage = db.view(|tx| -> Result<_, DbError> { - let mut cursor = tx.cursor_dup_read::()?; - let walker = cursor.first()?.map(|first| cursor.walk(Some(first.0))).transpose()?; - Ok(walker.map(|mut walker| { - let mut map: HashMap> = HashMap::new(); - while let Some(Ok((address, slot))) = walker.next() { - let key = U256::from_be_bytes(slot.key.0); - map.entry(address).or_default().insert(key, slot.value); - } - map - })) - })??; - trace!(target: "reth::cli", ?storage, "Pre-state"); - - // Initialize the execution stage - // Hardcode the chain_id to Ethereum 1. - let factory = reth_revm::Factory::new(Arc::new(chain_spec)); - let mut stage = ExecutionStage::new_with_factory(factory); - - // Call execution stage - let input = ExecInput { - previous_stage: last_block.map(|b| (StageId(""), StageCheckpoint::new(b))), - checkpoint: None, - }; - { - let mut transaction = Transaction::new(db.as_ref())?; - - // ignore error - let _ = stage.execute(&mut transaction, input).await; - transaction.commit()?; - } - - // Validate post state - match suite.post_state { - Some(RootOrState::Root(root)) => { - debug!(target: "reth::cli", "Post-state root: #{root:?}") - } - Some(RootOrState::State(state)) => db.view(|tx| -> eyre::Result<()> { - let mut cursor = tx.cursor_dup_read::()?; - let walker = cursor.first()?.map(|first| cursor.walk(Some(first.0))).transpose()?; - let storage = walker.map(|mut walker| { - let mut map: HashMap> = HashMap::new(); - while let Some(Ok((address, slot))) = walker.next() { - let key = U256::from_be_bytes(slot.key.0); - map.entry(address).or_default().insert(key, slot.value); - } - map - }); - tracing::trace!("Our storage:{:?}", storage); - for (address, test_account) in state.iter() { - // check account - let our_account = - tx.get::(*address)?.ok_or_else(|| { - eyre!("Account is missing: {address} expected: {:?}", test_account) - })?; - if test_account.balance.0 != our_account.balance { - return Err(eyre!( - "Account {address} balance diff, expected {} got {}", - test_account.balance.0, - our_account.balance - )) - } - if test_account.nonce.0.to::() != our_account.nonce { - return Err(eyre!( - "Account {address} nonce diff, expected {} got {}", - test_account.nonce.0, - our_account.nonce - )) - } - if let Some(our_bytecode) = our_account.bytecode_hash { - let test_bytecode = keccak256(test_account.code.as_ref()); - if our_bytecode != test_bytecode { - return Err(eyre!( - "Account {address} bytecode diff, expected: {} got: {:?}", - test_account.code, - our_account.bytecode_hash - )) - } - } else if !test_account.code.is_empty() { - return Err(eyre!( - "Account {address} bytecode diff, expected {} got empty bytecode", - test_account.code, - )) - } - - // get walker if present - if let Some(storage) = storage.as_ref() { - // iterate over storages - for (JsonU256(key), JsonU256(value)) in test_account.storage.iter() { - let our_value = storage - .get(address) - .ok_or_else(|| { - eyre!( - "Missing storage from test {storage:?} got {:?}", - test_account.storage - ) - })? - .get(key) - .ok_or_else(|| { - eyre!( - "Slot is missing from table {storage:?} got:{:?}", - test_account.storage - ) - })?; - if value != our_value { - return Err(eyre!( - "Storage diff we got {address}: {storage:?} but expect: {:?}", - test_account.storage - )) - } - } - } else if !test_account.storage.is_empty() { - return Err(eyre!( - "Walker is not present, but storage is not empty.{:?}", - test_account.storage - )) - } - } - Ok(()) - })??, - None => debug!(target: "reth::cli", "No post-state"), - } - } - Ok(TestOutcome::Passed) -} diff --git a/testing/ef-tests/.gitignore b/testing/ef-tests/.gitignore new file mode 100644 index 000000000..eae5bd973 --- /dev/null +++ b/testing/ef-tests/.gitignore @@ -0,0 +1 @@ +ethereum-tests \ No newline at end of file diff --git a/testing/ef-tests/Cargo.toml b/testing/ef-tests/Cargo.toml new file mode 100644 index 000000000..7bccc92b0 --- /dev/null +++ b/testing/ef-tests/Cargo.toml @@ -0,0 +1,25 @@ +[package] +name = "ef-tests" +version = "0.1.0" +edition = "2021" +license = "MIT OR Apache-2.0" +repository = "https://github.com/paradigmxyz/reth" +readme = "README.md" +description = "Staged syncing primitives used in reth." + +[features] +ef-tests = [] + +[dependencies] +reth-primitives = { path = "../../crates/primitives" } +reth-db = { path = "../../crates/storage/db", features = ["mdbx", "test-utils"] } +reth-provider = { path = "../../crates/storage/provider" } +reth-stages = { path = "../../crates/stages" } +reth-rlp = { path = "../../crates/rlp" } +reth-interfaces = { path = "../../crates/interfaces" } +reth-revm = { path = "../../crates/revm" } +tokio = "1.28.1" +walkdir = "2.3.3" +serde = "1.0.163" +serde_json = "1.0.96" +thiserror = "1.0.40" \ No newline at end of file diff --git a/testing/ef-tests/src/assert.rs b/testing/ef-tests/src/assert.rs new file mode 100644 index 000000000..8db027f6c --- /dev/null +++ b/testing/ef-tests/src/assert.rs @@ -0,0 +1,16 @@ +//! Various assertion helpers. + +use crate::Error; +use std::fmt::Debug; + +/// A helper like `assert_eq!` that instead returns `Err(Error::Assertion)` on failure. +pub fn assert_equal(left: T, right: T, msg: &str) -> Result<(), Error> +where + T: Eq + Debug, +{ + if left != right { + return Err(Error::Assertion(format!("{msg}. Left {:?}, right {:?}", left, right))) + } + + Ok(()) +} diff --git a/testing/ef-tests/src/case.rs b/testing/ef-tests/src/case.rs new file mode 100644 index 000000000..7258dafb2 --- /dev/null +++ b/testing/ef-tests/src/case.rs @@ -0,0 +1,39 @@ +//! Test case definitions + +use crate::result::{CaseResult, Error}; +use std::{ + fmt::Debug, + path::{Path, PathBuf}, +}; + +/// A single test case, capable of loading a JSON description of itself and running it. +/// +/// See for test specs. +pub trait Case: Debug + Sync + Sized { + /// A description of the test. + fn description(&self) -> String { + "no description".to_string() + } + + /// Load the test from the given file path. + /// + /// The file can be assumed to be a valid EF test case as described on . + fn load(path: &Path) -> Result; + + /// Run the test. + fn run(&self) -> Result<(), Error>; +} + +/// A container for multiple test cases. +#[derive(Debug)] +pub struct Cases { + /// The contained test cases and the path to each test. + pub test_cases: Vec<(PathBuf, T)>, +} + +impl Cases { + /// Run the contained test cases. + pub fn run(&self) -> Vec { + self.test_cases.iter().map(|(path, case)| CaseResult::new(path, case, case.run())).collect() + } +} diff --git a/testing/ef-tests/src/cases/blockchain_test.rs b/testing/ef-tests/src/cases/blockchain_test.rs new file mode 100644 index 000000000..3e52403a6 --- /dev/null +++ b/testing/ef-tests/src/cases/blockchain_test.rs @@ -0,0 +1,190 @@ +//! Test runners for `BlockchainTests` in + +use crate::{ + models::{BlockchainTest, ForkSpec, RootOrState}, + Case, Error, Suite, +}; +use reth_db::mdbx::test_utils::create_test_rw_db; +use reth_primitives::{BlockBody, SealedBlock, StageCheckpoint}; +use reth_provider::Transaction; +use reth_stages::{stages::ExecutionStage, ExecInput, Stage, StageId}; +use std::{collections::BTreeMap, ffi::OsStr, fs, ops::Deref, path::Path, sync::Arc}; + +/// A handler for the blockchain test suite. +#[derive(Debug)] +pub struct BlockchainTests { + suite: String, +} + +impl BlockchainTests { + /// Create a new handler for a subset of the blockchain test suite. + pub fn new(suite: String) -> Self { + Self { suite } + } +} + +impl Suite for BlockchainTests { + type Case = BlockchainTestCase; + + fn suite_name(&self) -> String { + format!("BlockchainTests/{}", self.suite) + } +} + +/// An Ethereum blockchain test. +#[derive(Debug, PartialEq, Eq)] +pub struct BlockchainTestCase { + tests: BTreeMap, + skip: bool, +} + +impl Case for BlockchainTestCase { + fn load(path: &Path) -> Result { + Ok(BlockchainTestCase { + tests: fs::read_to_string(path) + .map_err(|e| Error::Io { path: path.into(), error: e.to_string() }) + .and_then(|s| { + serde_json::from_str(&s).map_err(|e| Error::CouldNotDeserialize { + path: path.into(), + error: e.to_string(), + }) + })?, + skip: should_skip(path), + }) + } + + // TODO: Clean up + fn run(&self) -> Result<(), Error> { + if self.skip { + return Err(Error::Skipped) + } + + for case in self.tests.values() { + if matches!( + case.network, + ForkSpec::ByzantiumToConstantinopleAt5 | + ForkSpec::Constantinople | + ForkSpec::ConstantinopleFix | + ForkSpec::MergeEOF | + ForkSpec::MergeMeterInitCode | + ForkSpec::MergePush0 | + ForkSpec::Unknown + ) { + continue + } + + // Create the database + let db = create_test_rw_db(); + let mut transaction = Transaction::new(db.as_ref())?; + + // Insert test state + reth_provider::insert_canonical_block( + transaction.deref(), + SealedBlock::new(case.genesis_block_header.clone().into(), BlockBody::default()), + None, + )?; + case.pre.write_to_db(transaction.deref())?; + + let mut last_block = None; + for block in case.blocks.iter() { + last_block = Some(block.write_to_db(transaction.deref())?); + } + + // Call execution stage + { + let mut stage = ExecutionStage::new_with_factory(reth_revm::Factory::new( + Arc::new(case.network.clone().into()), + )); + + tokio::runtime::Builder::new_current_thread() + .build() + .expect("Could not build tokio RT") + .block_on(async { + // ignore error + let _ = stage + .execute( + &mut transaction, + ExecInput { + previous_stage: last_block + .map(|b| (StageId("Dummy"), StageCheckpoint::new(b))), + checkpoint: None, + }, + ) + .await; + }); + } + + // Validate post state + match &case.post_state { + Some(RootOrState::Root(root)) => { + // TODO: We should really check the state root here... + println!("Post-state root: #{root:?}") + } + Some(RootOrState::State(state)) => { + for (&address, account) in state.iter() { + account.assert_db(address, transaction.deref())?; + } + } + None => println!("No post-state"), + } + + transaction.close(); + } + Ok(()) + } +} + +/// Tests are test edge cases that are not possible to happen on mainnet, so we are skipping them. +pub fn should_skip(path: &Path) -> bool { + // funky test with `bigint 0x00` value in json :) not possible to happen on mainnet and require + // custom json parser. https://github.com/ethereum/tests/issues/971 + if path.file_name() == Some(OsStr::new("ValueOverflow.json")) { + return true + } + // txbyte is of type 02 and we dont parse tx bytes for this test to fail. + if path.file_name() == Some(OsStr::new("typeTwoBerlin.json")) { + return true + } + // Test checks if nonce overflows. We are handling this correctly but we are not parsing + // exception in testsuite There are more nonce overflow tests that are in internal + // call/create, and those tests are passing and are enabled. + if path.file_name() == Some(OsStr::new("CreateTransactionHighNonce.json")) { + return true + } + + // Test check if gas price overflows, we handle this correctly but does not match tests specific + // exception. + if path.file_name() == Some(OsStr::new("HighGasPrice.json")) { + return true + } + + // Skip test where basefee/accesslist/difficulty is present but it shouldn't be supported in + // London/Berlin/TheMerge. https://github.com/ethereum/tests/blob/5b7e1ab3ffaf026d99d20b17bb30f533a2c80c8b/GeneralStateTests/stExample/eip1559.json#L130 + // It is expected to not execute these tests. + if path.file_name() == Some(OsStr::new("accessListExample.json")) || + path.file_name() == Some(OsStr::new("basefeeExample.json")) || + path.file_name() == Some(OsStr::new("eip1559.json")) || + path.file_name() == Some(OsStr::new("mergeTest.json")) + { + return true + } + + // These tests are passing, but they take a lot of time to execute so we are going to skip them. + if path.file_name() == Some(OsStr::new("loopExp.json")) || + path.file_name() == Some(OsStr::new("Call50000_sha256.json")) || + path.file_name() == Some(OsStr::new("static_Call50000_sha256.json")) || + path.file_name() == Some(OsStr::new("loopMul.json")) || + path.file_name() == Some(OsStr::new("CALLBlake2f_MaxRounds.json")) || + path.file_name() == Some(OsStr::new("shiftCombinations.json")) + { + return true + } + + // Ignore outdated EOF tests that haven't been updated for Cancun yet. + let eof_path = Path::new("EIPTests").join("stEOF"); + if path.to_string_lossy().contains(&*eof_path.to_string_lossy()) { + return true + } + + false +} diff --git a/testing/ef-tests/src/cases/mod.rs b/testing/ef-tests/src/cases/mod.rs new file mode 100644 index 000000000..cfde4acf0 --- /dev/null +++ b/testing/ef-tests/src/cases/mod.rs @@ -0,0 +1,3 @@ +//! Specific test case handler implementations. + +pub mod blockchain_test; diff --git a/testing/ef-tests/src/lib.rs b/testing/ef-tests/src/lib.rs new file mode 100644 index 000000000..3360af566 --- /dev/null +++ b/testing/ef-tests/src/lib.rs @@ -0,0 +1,20 @@ +#![warn(missing_debug_implementations, missing_docs, unreachable_pub)] +#![deny(unused_must_use, rust_2018_idioms)] +#![doc(test( + no_crate_inject, + attr(deny(warnings, rust_2018_idioms), allow(dead_code, unused_variables)) +))] + +//! Abstractions and runners for EF tests. + +pub mod case; +pub mod result; +pub mod suite; + +pub mod assert; +pub mod cases; +pub mod models; + +pub use case::{Case, Cases}; +pub use result::{CaseResult, Error}; +pub use suite::Suite; diff --git a/bin/reth/src/test_eth_chain/models.rs b/testing/ef-tests/src/models.rs similarity index 52% rename from bin/reth/src/test_eth_chain/models.rs rename to testing/ef-tests/src/models.rs index 25b9731ae..31a9612f0 100644 --- a/bin/reth/src/test_eth_chain/models.rs +++ b/testing/ef-tests/src/models.rs @@ -1,18 +1,24 @@ -use reth_primitives::{ - Address, BigEndianHash, Bloom, Bytes, ChainSpec, ChainSpecBuilder, Header as RethHeader, - JsonU256, SealedHeader, Withdrawal, H160, H256, H64, +//! Shared models for + +use crate::{assert::assert_equal, Error}; +use reth_db::{ + cursor::DbDupCursorRO, + tables, + transaction::{DbTx, DbTxMut}, }; +use reth_primitives::{ + keccak256, Account as RethAccount, Address, BigEndianHash, BlockNumber, Bloom, Bytecode, Bytes, + ChainSpec, ChainSpecBuilder, Header as RethHeader, JsonU256, SealedBlock, SealedHeader, + StorageEntry, Withdrawal, H160, H256, H64, U256, +}; +use reth_rlp::Decodable; use serde::{self, Deserialize}; -use std::collections::BTreeMap; +use std::{collections::BTreeMap, ops::Deref}; -/// An Ethereum blockchain test. -#[derive(Debug, PartialEq, Eq, Deserialize)] -pub struct Test(pub BTreeMap); - -/// Ethereum test data. +/// The definition of a blockchain test. #[derive(Debug, PartialEq, Eq, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct BlockchainTestData { +pub struct BlockchainTest { /// Genesis block header. pub genesis_block_header: Header, /// RLP encoded genesis block. @@ -34,7 +40,7 @@ pub struct BlockchainTestData { } /// A block header in an Ethereum blockchain test. -#[derive(Debug, PartialEq, Eq, Deserialize)] +#[derive(Debug, PartialEq, Eq, Clone, Deserialize)] #[serde(rename_all = "camelCase")] pub struct Header { /// Bloom filter. @@ -102,7 +108,6 @@ impl From
for SealedHeader { /// A block in an Ethereum blockchain test. #[derive(Debug, PartialEq, Eq, Deserialize)] -#[serde(deny_unknown_fields)] #[serde(rename_all = "camelCase")] pub struct Block { /// Block header. @@ -119,7 +124,20 @@ pub struct Block { pub withdrawals: Option>, } -/// Transaction Sequence in block +impl Block { + /// Write the block to the database. + pub fn write_to_db<'a, Tx>(&self, tx: &'a Tx) -> Result + where + Tx: DbTx<'a> + DbTxMut<'a>, + { + let decoded = SealedBlock::decode(&mut self.rlp.as_ref())?; + let block_number = decoded.number; + reth_provider::insert_canonical_block(tx, decoded, None)?; + Ok(block_number) + } +} + +/// Transaction sequence in block #[derive(Debug, PartialEq, Eq, Deserialize)] #[serde(deny_unknown_fields)] #[serde(rename_all = "camelCase")] @@ -131,7 +149,47 @@ pub struct TransactionSequence { /// Ethereum blockchain test data state. #[derive(Clone, Debug, Eq, PartialEq, Deserialize)] -pub struct State(pub BTreeMap); +pub struct State(BTreeMap); + +impl State { + /// Write the state to the database. + pub fn write_to_db<'a, Tx>(&self, tx: &'a Tx) -> Result<(), Error> + where + Tx: DbTxMut<'a>, + { + for (&address, account) in self.0.iter() { + let has_code = !account.code.is_empty(); + let code_hash = has_code.then(|| keccak256(&account.code)); + tx.put::( + address, + RethAccount { + balance: account.balance.0, + nonce: account.nonce.0.to::(), + bytecode_hash: code_hash, + }, + )?; + if let Some(code_hash) = code_hash { + tx.put::(code_hash, Bytecode::new_raw(account.code.0.clone()))?; + } + account.storage.iter().try_for_each(|(k, v)| { + tx.put::( + address, + StorageEntry { key: H256::from_slice(&k.0.to_be_bytes::<32>()), value: v.0 }, + ) + })?; + } + + Ok(()) + } +} + +impl Deref for State { + type Target = BTreeMap; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} /// Merkle root hash or storage accounts. #[derive(Clone, Debug, PartialEq, Eq, Deserialize)] @@ -157,8 +215,62 @@ pub struct Account { pub storage: BTreeMap, } +impl Account { + /// Check that the account matches what is in the database. + /// + /// In case of a mismatch, `Err(Error::Assertion)` is returned. + pub fn assert_db<'a, Tx>(&self, address: Address, tx: &'a Tx) -> Result<(), Error> + where + Tx: DbTx<'a>, + { + let account = tx.get::(address)?.ok_or_else(|| { + Error::Assertion(format!("Account is missing ({address}) expected: {:?}", self)) + })?; + + assert_equal(self.balance.into(), account.balance, "Balance does not match")?; + assert_equal(self.nonce.0.to(), account.nonce, "Nonce does not match")?; + + if let Some(bytecode_hash) = account.bytecode_hash { + assert_equal(keccak256(&self.code), bytecode_hash, "Bytecode does not match")?; + } else { + assert_equal( + self.code.is_empty(), + true, + "Expected empty bytecode, got bytecode in db.", + )?; + } + + let mut storage_cursor = tx.cursor_dup_read::()?; + for (slot, value) in self.storage.iter() { + if let Some(entry) = + storage_cursor.seek_by_key_subkey(address, H256(slot.0.to_be_bytes()))? + { + if U256::from_be_bytes(entry.key.0) == slot.0 { + assert_equal( + value.0, + entry.value, + &format!("Storage for slot {:?} does not match", slot), + )?; + } else { + return Err(Error::Assertion(format!( + "Slot {:?} is missing from the database. Expected {:?}", + slot, value + ))) + } + } else { + return Err(Error::Assertion(format!( + "Slot {:?} is missing from the database. Expected {:?}", + slot, value + ))) + } + } + + Ok(()) + } +} + /// Fork specification. -#[derive(Debug, PartialEq, Eq, PartialOrd, Hash, Ord, Deserialize)] +#[derive(Debug, PartialEq, Eq, PartialOrd, Hash, Ord, Clone, Deserialize)] pub enum ForkSpec { /// Frontier Frontier, @@ -310,129 +422,6 @@ mod test { use super::*; use serde_json; - #[test] - fn blockchain_test_deserialize() { - let test = r#"{ - "evmBytecode_d0g0v0_Berlin" : { - "_info" : { - "comment" : "", - "filling-rpc-server" : "evm version 1.10.18-unstable-53304ff6-20220503", - "filling-tool-version" : "retesteth-0.2.2-testinfo+commit.05e0b8ca.Linux.g++", - "generatedTestHash" : "0951de8d9e6b2a08e57234f57ef719a17aee9d7e9d7e852e454a641028b791a9", - "lllcversion" : "Version: 0.5.14-develop.2021.11.27+commit.401d5358.Linux.g++", - "solidity" : "Version: 0.8.5+commit.a4f2e591.Linux.g++", - "source" : "src/GeneralStateTestsFiller/stBugs/evmBytecodeFiller.json", - "sourceHash" : "6ced7b43100305d1cc5aee48344c0eab6002940358e2c126279ef8444c2dea5a" - }, - "blocks" : [ - { - "blockHeader" : { - "bloom" : "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", - "coinbase" : "0x1000000000000000000000000000000000000000", - "difficulty" : "0x020000", - "extraData" : "0x00", - "gasLimit" : "0x54a60a4202e088", - "gasUsed" : "0x01d4c0", - "hash" : "0xb8152a06d2018ed9a1eb1eb6e1fe8c3478d8eae5b04d743bf4a1ec699510cfe5", - "mixHash" : "0x0000000000000000000000000000000000000000000000000000000000000000", - "nonce" : "0x0000000000000000", - "number" : "0x01", - "parentHash" : "0xb835c89a42605cfcc542381145b83c826caf10823b81af0f45091040a67e6601", - "receiptTrie" : "0x0ef77336cf7bfbd2c500dcefe7b48d0ef7896d38f6373fbeb301ea4dac3746a7", - "stateRoot" : "0x27bf1aca92967ecd83e11c52887203bbdcab73a27fe07e814cf749fa50483a53", - "timestamp" : "0x03e8", - "transactionsTrie" : "0x9008a2d4af552fea9b45675cd2af6d4117303b57da25b28438ccd1f6bad6828d", - "uncleHash" : "0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347" - }, - "rlp" : "0xf90264f901fca0b835c89a42605cfcc542381145b83c826caf10823b81af0f45091040a67e6601a01dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347941000000000000000000000000000000000000000a027bf1aca92967ecd83e11c52887203bbdcab73a27fe07e814cf749fa50483a53a09008a2d4af552fea9b45675cd2af6d4117303b57da25b28438ccd1f6bad6828da00ef77336cf7bfbd2c500dcefe7b48d0ef7896d38f6373fbeb301ea4dac3746a7b901000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000083020000018754a60a4202e0888301d4c08203e800a00000000000000000000000000000000000000000000000000000000000000000880000000000000000f862f860800a8301d4c094b94f5374fce5edbc8e2a8697c15331677e6ebf0b80801ca0f3b41c283c02ed98318dc9cac3f0ddc3de2f2f7853a03299a46f22a7c6726c3aa0396ccb5968a532ea070924408625d3e36d4db21bfbd0cb070ba9e1fe9dba58abc0", - "transactions" : [ - { - "data" : "0x", - "gasLimit" : "0x01d4c0", - "gasPrice" : "0x0a", - "nonce" : "0x00", - "r" : "0xf3b41c283c02ed98318dc9cac3f0ddc3de2f2f7853a03299a46f22a7c6726c3a", - "s" : "0x396ccb5968a532ea070924408625d3e36d4db21bfbd0cb070ba9e1fe9dba58ab", - "sender" : "0xa94f5374fce5edbc8e2a8697c15331677e6ebf0b", - "to" : "0xb94f5374fce5edbc8e2a8697c15331677e6ebf0b", - "v" : "0x1c", - "value" : "0x00" - } - ], - "uncleHeaders" : [ - ], - "withdrawals" : [ - ] - } - ], - "genesisBlockHeader" : { - "bloom" : "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", - "coinbase" : "0x1000000000000000000000000000000000000000", - "difficulty" : "0x020000", - "extraData" : "0x00", - "gasLimit" : "0x54a60a4202e088", - "gasUsed" : "0x00", - "hash" : "0xb835c89a42605cfcc542381145b83c826caf10823b81af0f45091040a67e6601", - "mixHash" : "0x0000000000000000000000000000000000000000000000000000000000000000", - "nonce" : "0x0000000000000000", - "number" : "0x00", - "parentHash" : "0x0000000000000000000000000000000000000000000000000000000000000000", - "receiptTrie" : "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", - "stateRoot" : "0x642a369a4a9dbf57d83ba05413910a5dd2cff93858c68e9e8293a8fffeae8660", - "timestamp" : "0x00", - "transactionsTrie" : "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", - "uncleHash" : "0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347" - }, - "genesisRLP" : "0xf901fcf901f7a00000000000000000000000000000000000000000000000000000000000000000a01dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347941000000000000000000000000000000000000000a0642a369a4a9dbf57d83ba05413910a5dd2cff93858c68e9e8293a8fffeae8660a056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421a056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421b901000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000083020000808754a60a4202e088808000a00000000000000000000000000000000000000000000000000000000000000000880000000000000000c0c0", - "lastblockhash" : "0xb8152a06d2018ed9a1eb1eb6e1fe8c3478d8eae5b04d743bf4a1ec699510cfe5", - "network" : "Berlin", - "postState" : { - "0x1000000000000000000000000000000000000000" : { - "balance" : "0x1bc16d674eda4f80", - "code" : "0x", - "nonce" : "0x00", - "storage" : { - } - }, - "0xa94f5374fce5edbc8e2a8697c15331677e6ebf0b" : { - "balance" : "0x38beec8feeb7d618", - "code" : "0x", - "nonce" : "0x01", - "storage" : { - } - }, - "0xb94f5374fce5edbc8e2a8697c15331677e6ebf0b" : { - "balance" : "0x00", - "code" : "0x67ffffffffffffffff600160006000fb", - "nonce" : "0x3f", - "storage" : { - } - } - }, - "pre" : { - "0xa94f5374fce5edbc8e2a8697c15331677e6ebf0b" : { - "balance" : "0x38beec8feeca2598", - "code" : "0x", - "nonce" : "0x00", - "storage" : { - } - }, - "0xb94f5374fce5edbc8e2a8697c15331677e6ebf0b" : { - "balance" : "0x00", - "code" : "0x67ffffffffffffffff600160006000fb", - "nonce" : "0x3f", - "storage" : { - } - } - }, - "sealEngine" : "NoProof" - } - }"#; - - let res = serde_json::from_str::(test); - assert!(res.is_ok(), "Failed to deserialize BlockchainTestData with error: {res:?}"); - } - #[test] fn header_deserialize() { let test = r#"{ diff --git a/testing/ef-tests/src/result.rs b/testing/ef-tests/src/result.rs new file mode 100644 index 000000000..5aaf58a9e --- /dev/null +++ b/testing/ef-tests/src/result.rs @@ -0,0 +1,123 @@ +//! Test results and errors + +use crate::Case; +use reth_db::DatabaseError; +use std::path::{Path, PathBuf}; +use thiserror::Error; + +/// Test errors +/// +/// # Note +/// +/// `Error::Skipped` should not be treated as a test failure. +#[derive(Error, Debug, Clone)] +#[non_exhaustive] +pub enum Error { + /// The test was skipped + #[error("Test was skipped")] + Skipped, + /// An IO error occurred + #[error("An error occurred interacting with the file system at {path}: {error}")] + Io { + /// The path to the file or directory + path: PathBuf, + /// The specific error + error: String, + }, + /// A deserialization error occurred + #[error("An error occurred deserializing the test at {path}: {error}")] + CouldNotDeserialize { + /// The path to the file we wanted to deserialize + path: PathBuf, + /// The specific error + error: String, + }, + /// A database error occurred. + #[error(transparent)] + Database(#[from] DatabaseError), + /// A test assertion failed. + #[error("Test failed: {0}")] + Assertion(String), + /// An error internally in reth occurred. + #[error("Test failed: {0}")] + RethError(#[from] reth_interfaces::Error), + /// An error occurred while decoding RLP. + #[error("An error occurred deserializing RLP")] + RlpDecodeError(#[from] reth_rlp::DecodeError), +} + +/// The result of running a test. +#[derive(Debug)] +pub struct CaseResult { + /// A description of the test. + pub desc: String, + /// The full path to the test. + pub path: PathBuf, + /// The result of the test. + pub result: Result<(), Error>, +} + +impl CaseResult { + /// Create a new test result. + pub fn new(path: &Path, case: &impl Case, result: Result<(), Error>) -> Self { + CaseResult { desc: case.description(), path: path.into(), result } + } +} + +/// Assert that all the given tests passed and print the results to stdout. +pub(crate) fn assert_tests_pass(suite_name: &str, path: &Path, results: &[CaseResult]) { + let (passed, failed, skipped) = categorize_results(results); + + print_results(suite_name, path, &passed, &failed, &skipped); + + if !failed.is_empty() { + panic!("Some tests failed (see above)"); + } +} + +/// Categorize test results into `(passed, failed, skipped)`. +pub(crate) fn categorize_results( + results: &[CaseResult], +) -> (Vec<&CaseResult>, Vec<&CaseResult>, Vec<&CaseResult>) { + let mut passed = Vec::new(); + let mut failed = Vec::new(); + let mut skipped = Vec::new(); + + for case in results { + match case.result.as_ref().err() { + Some(Error::Skipped) => skipped.push(case), + Some(_) => failed.push(case), + None => passed.push(case), + } + } + + (passed, failed, skipped) +} + +/// Display the given test results to stdout. +pub(crate) fn print_results( + suite_name: &str, + path: &Path, + passed: &[&CaseResult], + failed: &[&CaseResult], + skipped: &[&CaseResult], +) { + println!("Suite: {suite_name} (at {})", path.display()); + println!( + "Ran {} tests ({} passed, {} failed, {} skipped)", + passed.len() + failed.len() + skipped.len(), + passed.len(), + failed.len(), + skipped.len() + ); + + for case in skipped { + println!("[S] Case {} skipped", case.path.display()); + } + + for case in failed { + let error = case.result.clone().unwrap_err(); + + println!("[!] Case {} failed (description: {}): {}", case.path.display(), case.desc, error); + } +} diff --git a/testing/ef-tests/src/suite.rs b/testing/ef-tests/src/suite.rs new file mode 100644 index 000000000..25b15c10f --- /dev/null +++ b/testing/ef-tests/src/suite.rs @@ -0,0 +1,57 @@ +//! Abstractions for groups of tests. + +use crate::{ + case::{Case, Cases}, + result::assert_tests_pass, +}; +use std::path::{Path, PathBuf}; +use walkdir::{DirEntry, WalkDir}; + +/// A collection of tests. +pub trait Suite { + /// The type of test cases in this suite. + type Case: Case; + + /// The name of the test suite used to locate the individual test cases. + /// + /// # Example + /// + /// - `GeneralStateTests` + /// - `BlockchainTests/InvalidBlocks` + /// - `BlockchainTests/TransitionTests` + fn suite_name(&self) -> String; + + /// Load an run each contained test case. + /// + /// # Note + /// + /// This recursively finds every test description in the resulting path. + fn run(&self) { + let suite_path = PathBuf::from(env!("CARGO_MANIFEST_DIR")) + .join("ethereum-tests") + .join(self.suite_name()); + + // todo: assert that the path exists + let test_cases = find_all_files_with_extension(&suite_path, ".json") + .into_iter() + .map(|test_case_path| { + let case = Self::Case::load(&test_case_path).expect("test case should load"); + (test_case_path, case) + }) + .collect(); + + let results = Cases { test_cases }.run(); + + assert_tests_pass(&self.suite_name(), &suite_path, &results); + } +} + +/// Recursively find all files with a given extension. +fn find_all_files_with_extension(path: &Path, extension: &str) -> Vec { + WalkDir::new(path) + .into_iter() + .filter_map(Result::ok) + .filter(|e| e.file_name().to_string_lossy().ends_with(extension)) + .map(DirEntry::into_path) + .collect::<_>() +} diff --git a/testing/ef-tests/tests/tests.rs b/testing/ef-tests/tests/tests.rs new file mode 100644 index 000000000..afbaa2e79 --- /dev/null +++ b/testing/ef-tests/tests/tests.rs @@ -0,0 +1,79 @@ +#![cfg(feature = "ef-tests")] + +use ef_tests::{cases::blockchain_test::BlockchainTests, suite::Suite}; + +macro_rules! general_state_test { + ($test_name:ident, $dir:ident) => { + #[test] + fn $test_name() { + BlockchainTests::new(format!("GeneralStateTests/{}", stringify!($dir))).run(); + } + }; +} + +mod general_state_tests { + use super::*; + + general_state_test!(shanghai, Shanghai); + general_state_test!(st_args_zero_one_balance, stArgsZeroOneBalance); + general_state_test!(st_attack, stAttackTest); + general_state_test!(st_bad_opcode, stBadOpcode); + general_state_test!(st_bugs, stBugs); + general_state_test!(st_call_codes, stCallCodes); + general_state_test!(st_call_create_call_code, stCallCreateCallCodeTest); + general_state_test!( + st_call_delegate_codes_call_code_homestead, + stCallDelegateCodesCallCodeHomestead + ); + general_state_test!(st_call_delegate_codes_homestead, stCallDelegateCodesHomestead); + general_state_test!(st_chain_id, stChainId); + general_state_test!(st_code_copy_test, stCodeCopyTest); + general_state_test!(st_code_size_limit, stCodeSizeLimit); + general_state_test!(st_create2, stCreate2); + general_state_test!(st_create, stCreateTest); + general_state_test!(st_delegate_call_test_homestead, stDelegatecallTestHomestead); + general_state_test!(st_eip150_gas_prices, stEIP150singleCodeGasPrices); + general_state_test!(st_eip150, stEIP150Specific); + general_state_test!(st_eip158, stEIP158Specific); + general_state_test!(st_eip1559, stEIP1559); + general_state_test!(st_eip2930, stEIP2930); + general_state_test!(st_eip3607, stEIP3607); + general_state_test!(st_example, stExample); + general_state_test!(st_ext_codehash, stExtCodeHash); + general_state_test!(st_homestead, stHomesteadSpecific); + general_state_test!(st_init_code, stInitCodeTest); + general_state_test!(st_log, stLogTests); + general_state_test!(st_mem_expanding_eip150_calls, stMemExpandingEIP150Calls); + general_state_test!(st_memory_stress, stMemoryStressTest); + general_state_test!(st_memory, stMemoryTest); + general_state_test!(st_non_zero_calls, stNonZeroCallsTest); + general_state_test!(st_precompiles, stPreCompiledContracts); + general_state_test!(st_precompiles2, stPreCompiledContracts2); + general_state_test!(st_quadratic_complexity, stQuadraticComplexityTest); + general_state_test!(st_random, stRandom); + general_state_test!(st_random2, stRandom2); + general_state_test!(st_recursive_create, stRecursiveCreate); + general_state_test!(st_refund, stRefundTest); + general_state_test!(st_return, stReturnDataTest); + general_state_test!(st_revert, stRevertTest); + general_state_test!(st_self_balance, stSelfBalance); + general_state_test!(st_shift, stShift); + general_state_test!(st_sload, stSLoadTest); + general_state_test!(st_solidity, stSolidityTest); + general_state_test!(st_special, stSpecialTest); + general_state_test!(st_sstore, stSStoreTest); + general_state_test!(st_stack, stStackTests); + general_state_test!(st_static_call, stStaticCall); + general_state_test!(st_static_flag, stStaticFlagEnabled); + general_state_test!(st_system_operations, stSystemOperationsTest); + general_state_test!(st_time_consuming, stTimeConsuming); + general_state_test!(st_transaction, stTransactionTest); + general_state_test!(st_wallet, stWalletTest); + general_state_test!(st_zero_calls_revert, stZeroCallsRevert); + general_state_test!(st_zero_calls, stZeroCallsTest); + general_state_test!(st_zero_knowledge, stZeroKnowledge); + general_state_test!(st_zero_knowledge2, stZeroKnowledge2); + general_state_test!(vm_tests, VMTests); +} + +// TODO: Add ValidBlocks and InvalidBlocks tests