refactor: move ef tests to own testing crate (#2847)

This commit is contained in:
Bjerg
2023-05-26 04:02:08 +02:00
committed by GitHub
parent a441c136e3
commit cb829be089
18 changed files with 715 additions and 521 deletions

View File

@ -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

35
Cargo.lock generated
View File

@ -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",
]

View File

@ -42,6 +42,7 @@ members = [
"crates/tasks",
"crates/transaction-pool",
"crates/trie",
"testing/ef-tests"
]
default-members = ["bin/reth"]

View File

@ -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),

View File

@ -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;

View File

@ -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<PathBuf>,
}
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(())
}
}
}

View File

@ -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<eyre::Result<TestOutcome>> for TestOutcome {
fn from(v: eyre::Result<TestOutcome>) -> 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<TestOutcome> {
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::<tables::PlainAccountState>(
address,
RethAccount {
balance: account.balance.0,
nonce: account.nonce.0.to::<u64>(),
bytecode_hash: code_hash,
},
)?;
if let Some(code_hash) = code_hash {
tx.put::<tables::Bytecodes>(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::<tables::PlainStorageState>(
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::<tables::PlainStorageState>()?;
let walker = cursor.first()?.map(|first| cursor.walk(Some(first.0))).transpose()?;
Ok(walker.map(|mut walker| {
let mut map: HashMap<Address, HashMap<U256, U256>> = 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::<tables::PlainStorageState>()?;
let walker = cursor.first()?.map(|first| cursor.walk(Some(first.0))).transpose()?;
let storage = walker.map(|mut walker| {
let mut map: HashMap<Address, HashMap<U256, U256>> = 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::<tables::PlainAccountState>(*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::<u64>() != 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)
}

1
testing/ef-tests/.gitignore vendored Normal file
View File

@ -0,0 +1 @@
ethereum-tests

View File

@ -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"

View File

@ -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<T>(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(())
}

View File

@ -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 <https://ethereum-tests.readthedocs.io/> 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 <https://ethereum-tests.readthedocs.io/>.
fn load(path: &Path) -> Result<Self, Error>;
/// Run the test.
fn run(&self) -> Result<(), Error>;
}
/// A container for multiple test cases.
#[derive(Debug)]
pub struct Cases<T> {
/// The contained test cases and the path to each test.
pub test_cases: Vec<(PathBuf, T)>,
}
impl<T: Case> Cases<T> {
/// Run the contained test cases.
pub fn run(&self) -> Vec<CaseResult> {
self.test_cases.iter().map(|(path, case)| CaseResult::new(path, case, case.run())).collect()
}
}

View File

@ -0,0 +1,190 @@
//! Test runners for `BlockchainTests` in <https://github.com/ethereum/tests>
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<String, BlockchainTest>,
skip: bool,
}
impl Case for BlockchainTestCase {
fn load(path: &Path) -> Result<Self, Error> {
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
}

View File

@ -0,0 +1,3 @@
//! Specific test case handler implementations.
pub mod blockchain_test;

View File

@ -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;

View File

@ -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 <https://github.com/ethereum/tests>
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<String, BlockchainTestData>);
/// 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<Header> 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<Vec<Withdrawal>>,
}
/// Transaction Sequence in block
impl Block {
/// Write the block to the database.
pub fn write_to_db<'a, Tx>(&self, tx: &'a Tx) -> Result<BlockNumber, Error>
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<Address, Account>);
pub struct State(BTreeMap<Address, Account>);
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::<tables::PlainAccountState>(
address,
RethAccount {
balance: account.balance.0,
nonce: account.nonce.0.to::<u64>(),
bytecode_hash: code_hash,
},
)?;
if let Some(code_hash) = code_hash {
tx.put::<tables::Bytecodes>(code_hash, Bytecode::new_raw(account.code.0.clone()))?;
}
account.storage.iter().try_for_each(|(k, v)| {
tx.put::<tables::PlainStorageState>(
address,
StorageEntry { key: H256::from_slice(&k.0.to_be_bytes::<32>()), value: v.0 },
)
})?;
}
Ok(())
}
}
impl Deref for State {
type Target = BTreeMap<Address, Account>;
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<JsonU256, JsonU256>,
}
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::<tables::PlainAccountState>(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::<tables::PlainStorageState>()?;
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>(test);
assert!(res.is_ok(), "Failed to deserialize BlockchainTestData with error: {res:?}");
}
#[test]
fn header_deserialize() {
let test = r#"{

View File

@ -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);
}
}

View File

@ -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<PathBuf> {
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::<_>()
}

View File

@ -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