Repository: lambdaclass/ethrex (rust)

Issue:
Add support to run EF spec tests
See: 
- https://github.com/ethereum/execution-spec-tests
- https://ethereum.github.io/execution-spec-tests/main/consuming_tests/blockchain_test/
- Reference implementation: https://github.com/bluealloy/revm/blob/main/bins/revme/src/cmd/statetest/runner.rs#L240

Pull request:
test: add boilerplate to run EF blockchain tests
**Motivation**
We want to be able to run the test suite from EF

**Description**
This is a first implementation that adds support to parse a simple test vector with one block and one transaction.

Pending work:
- Add a script to download all test vectors, and filter out ones previous to Cancun.
- Extend `ef_tests` types to succesfully parse all cancun blockchain tests.
- Add logic to skip tests that are not supported yet.
- Call EVM inside `core` instead of the one inside `ef_tests` once it's ready. This will require to use types from `core` and be able to go from `ef_tests` _into_ `core` types.

Closes #43



Patch:
diff --git a/Cargo.toml b/Cargo.toml
index 8f235a30c..890685b8c 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -7,6 +7,7 @@ members = [
     "crates/net",
     "crates/rpc",
     "crates/storage",
+    "ef_tests",
 ]
 resolver = "2"
 
@@ -24,6 +25,7 @@ ethrex-storage = { path = "./crates/storage" }
 
 tracing = "0.1"
 tracing-subscriber = "0.3.0"
+
 serde = { version = "1.0.203", features = ["derive"] }
 serde_json = "1.0.117"
 libmdbx = { version = "0.5.0", features = ["orm"] }


Test patch:
diff --git a/ef_tests/Cargo.toml b/ef_tests/Cargo.toml
new file mode 100644
index 000000000..4f7db70e0
--- /dev/null
+++ b/ef_tests/Cargo.toml
@@ -0,0 +1,10 @@
+[package]
+name = "ef_tests"
+version.workspace = true
+edition.workspace = true
+
+[dependencies]
+revm = { version = "9.0.0", features = ["serde", "std", "serde-json"] }
+ethrex-core.workspace = true
+serde.workspace = true
+serde_json.workspace = true
diff --git a/ef_tests/src/evm.rs b/ef_tests/src/evm.rs
new file mode 100644
index 000000000..5c5f29c21
--- /dev/null
+++ b/ef_tests/src/evm.rs
@@ -0,0 +1,94 @@
+use std::{collections::HashMap, io::stderr};
+
+use ethrex_core::{Address, U256};
+use revm::{
+    inspector_handle_register,
+    inspectors::TracerEip3155,
+    primitives::{
+        keccak256, AccountInfo, Bytecode, Env, ExecutionResult, FixedBytes, SpecId, TransactTo,
+        U256 as AlloyU256,
+    },
+    Evm,
+};
+
+use crate::types::{Account, Header, Transaction};
+
+pub fn execute_transaction(
+    block: &Header,
+    transaction: &Transaction,
+    pre: HashMap<Address, Account>,
+) -> ExecutionResult {
+    let mut env = Box::<Env>::default();
+
+    env.block.number = to_alloy_bytes(block.number);
+    env.block.coinbase = block.coinbase.to_fixed_bytes().into();
+    env.block.timestamp = to_alloy_bytes(block.timestamp);
+    env.block.gas_limit = to_alloy_bytes(block.gas_limit);
+    env.block.basefee = AlloyU256::ZERO;
+    env.block.difficulty = AlloyU256::ZERO;
+    env.block.prevrandao = Some(block.mix_hash.as_fixed_bytes().into());
+
+    env.tx.caller = transaction.sender.to_fixed_bytes().into();
+
+    env.tx.gas_price = to_alloy_bytes(
+        transaction
+            .gas_price
+            .or(transaction.max_fee_per_gas)
+            .unwrap_or_default(),
+    );
+    env.tx.gas_priority_fee = transaction.max_priority_fee_per_gas.map(to_alloy_bytes);
+
+    let spec_id = SpecId::CANCUN;
+
+    env.tx.gas_limit = transaction.gas_limit.as_u64();
+
+    env.tx.data = transaction.data.clone();
+    env.tx.value = to_alloy_bytes(transaction.value);
+
+    env.tx.transact_to = TransactTo::Call(transaction.to.to_fixed_bytes().into());
+
+    let mut cache_state = revm::CacheState::new(false);
+    for (address, info) in pre {
+        let acc_info = AccountInfo {
+            balance: to_alloy_bytes(info.balance),
+            code_hash: keccak256(&info.code),
+            code: Some(Bytecode::new_raw(info.code)),
+            nonce: info.nonce.as_u64(),
+        };
+
+        let mut storage = HashMap::new();
+        for (k, v) in info.storage {
+            storage.insert(to_alloy_bytes(k), to_alloy_bytes(v));
+        }
+
+        cache_state.insert_account_with_storage(address.to_fixed_bytes().into(), acc_info, storage);
+    }
+
+    let cache = cache_state.clone();
+    let mut state = revm::db::State::builder()
+        .with_cached_prestate(cache)
+        .with_bundle_update()
+        .build();
+    let evm = Evm::builder()
+        .with_db(&mut state)
+        .modify_env(|e| e.clone_from(&env))
+        .with_spec_id(spec_id)
+        .build();
+
+    let mut evm = evm
+        .modify()
+        .reset_handler_with_external_context(
+            TracerEip3155::new(Box::new(stderr())).without_summary(),
+        )
+        .append_handler_register(inspector_handle_register)
+        .build();
+
+    evm.transact_commit().unwrap()
+}
+
+fn to_alloy_bytes(eth_byte: U256) -> AlloyU256 {
+    let mut bytes = [0u8; 32];
+    eth_byte.to_big_endian(&mut bytes);
+    let fixed_bytes: FixedBytes<32> = bytes.into();
+    fixed_bytes.into()
+}
diff --git a/ef_tests/src/lib.rs b/ef_tests/src/lib.rs
new file mode 100644
index 000000000..1dc5a8894
--- /dev/null
+++ b/ef_tests/src/lib.rs
@@ -0,0 +1,2 @@
+pub mod evm;
+pub mod types;
diff --git a/ef_tests/src/types.rs b/ef_tests/src/types.rs
new file mode 100644
index 000000000..4f7ca078a
--- /dev/null
+++ b/ef_tests/src/types.rs
@@ -0,0 +1,114 @@
+use ethrex_core::{Address, Bloom, H256, U256, U64};
+
+use revm::primitives::Bytes;
+use serde::{Deserialize, Serialize};
+use std::collections::HashMap;
+
+#[derive(Debug, Deserialize)]
+#[serde(rename_all = "camelCase", deny_unknown_fields)]
+pub struct TestUnit {
+    #[serde(default, rename = "_info")]
+    pub info: Option<serde_json::Value>,
+    pub blocks: Vec<Block>,
+    pub genesis_block_header: Header,
+    #[serde(rename = "genesisRLP")]
+    pub genesis_rlp: serde_json::Value,
+    pub lastblockhash: serde_json::Value,
+    pub network: serde_json::Value,
+    pub post_state: serde_json::Value,
+    pub pre: HashMap<Address, Account>,
+    pub seal_engine: serde_json::Value,
+}
+
+#[derive(Debug, PartialEq, Eq, Serialize, Deserialize, Clone)]
+pub struct Account {
+    pub balance: U256,
+    pub code: Bytes,
+    pub nonce: U256,
+    pub storage: HashMap<U256, U256>,
+}
+
+#[derive(Debug, PartialEq, Eq, Deserialize)]
+#[serde(rename_all = "camelCase", deny_unknown_fields)]
+pub struct Env {
+    pub current_coinbase: Address,
+    pub current_difficulty: U256,
+    pub current_gas_limit: U256,
+    pub current_number: U256,
+    pub current_timestamp: U256,
+    pub current_base_fee: Option<U256>,
+    pub previous_hash: Option<H256>,
+    pub current_random: Option<H256>,
+    pub current_beacon_root: Option<H256>,
+    pub current_withdrawals_root: Option<H256>,
+    pub parent_blob_gas_used: Option<U256>,
+    pub parent_excess_blob_gas: Option<U256>,
+    pub current_excess_blob_gas: Option<U256>,
+}
+
+#[derive(Debug, PartialEq, Eq, Serialize, Deserialize, Clone)]
+#[serde(rename_all = "camelCase", deny_unknown_fields)]
+pub struct AccessListItem {
+    pub address: Address,
+    pub storage_keys: Vec<H256>,
+}
+
+pub type AccessList = Vec<AccessListItem>;
+
+#[derive(Debug, PartialEq, Eq, Clone, Deserialize)]
+#[serde(rename_all = "camelCase")]
+pub struct Header {
+    pub bloom: Bloom,
+    pub coinbase: Address,
+    pub difficulty: U256,
+    pub extra_data: Bytes,
+    pub gas_limit: U256,
+    pub gas_used: U256,
+    pub hash: H256,
+    pub mix_hash: H256,
+    pub nonce: U64,
+    pub number: U256,
+    pub parent_hash: H256,
+    pub receipt_trie: H256,
+    pub state_root: H256,
+    pub timestamp: U256,
+    pub transactions_trie: H256,
+    pub uncle_hash: H256,
+    pub base_fee_per_gas: Option<U256>,
+    pub withdrawals_root: Option<H256>,
+    pub blob_gas_used: Option<U256>,
+    pub excess_blob_gas: Option<U256>,
+    pub parent_beacon_block_root: Option<H256>,
+    pub requests_root: Option<H256>,
+}
+
+#[derive(Debug, PartialEq, Eq, Deserialize, Clone)]
+#[serde(rename_all = "camelCase")]
+pub struct Block {
+    pub block_header: Option<Header>,
+    pub rlp: Bytes,
+    pub transactions: Option<Vec<Transaction>>,
+    pub uncle_headers: Option<Vec<Header>>,
+}
+
+#[derive(Debug, PartialEq, Eq, Deserialize, Clone)]
+#[serde(rename_all = "camelCase")]
+pub struct Transaction {
+    #[serde(rename = "type")]
+    pub transaction_type: Option<U256>,
+    pub data: Bytes,
+    pub gas_limit: U256,
+    pub gas_price: Option<U256>,
+    pub nonce: U256,
+    pub r: U256,
+    pub s: U256,
+    pub v: U256,
+    pub value: U256,
+    pub chain_id: Option<U256>,
+    pub access_list: Option<AccessList>,
+    pub max_fee_per_gas: Option<U256>,
+    pub max_priority_fee_per_gas: Option<U256>,
+    pub hash: Option<H256>,
+    pub sender: Address,
+    pub to: Address,
+}
diff --git a/ef_tests/tests/tests.rs b/ef_tests/tests/tests.rs
new file mode 100644
index 000000000..ed9c64e74
--- /dev/null
+++ b/ef_tests/tests/tests.rs
@@ -0,0 +1,36 @@
+use ::ef_tests::{evm::execute_transaction, types::TestUnit};
+
+fn execute_test(test: TestUnit) {
+    // TODO: Add support for multiple blocks and multiple transactions per block.
+    let transaction = test
+        .blocks
+        .first()
+        .unwrap()
+        .transactions
+        .as_ref()
+        .unwrap()
+        .first()
+        .unwrap();
+    execute_transaction(&test.genesis_block_header, transaction, test.pre);
+}
+
+#[cfg(test)]
+mod ef_tests {
+    use std::collections::HashMap;
+
+    use ef_tests::types::TestUnit;
+
+    use crate::execute_test;
+
+    #[test]
+    fn add11_test() {
+        let s: String =
+            std::fs::read_to_string("./vectors/add11.json").expect("Unable to read file");
+        let tests: HashMap<String, TestUnit> =
+            serde_json::from_str(&s).expect("Unable to parse JSON");
+
+        for (_k, test) in tests {
+            execute_test(test)
+        }
+    }
+}
diff --git a/ef_tests/vectors/add11.json b/ef_tests/vectors/add11.json
new file mode 100644
index 000000000..79588f281
--- /dev/null
+++ b/ef_tests/vectors/add11.json
@@ -0,0 +1,141 @@
+{
+    "add11_d0g0v0_Cancun": {
+        "_info": {
+            "comment": "A test for (add 1 1) opcode result",
+            "filling-rpc-server": "evm version 1.14.4-unstable-3d8028a6-20240513",
+            "filling-tool-version": "retesteth-0.3.2-cancun+commit.cae6bc33.Linux.g++",
+            "generatedTestHash": "fa7502b45916d7f90cfeb1900c654d8cc2ce9ed77e7bb78953aff256e182136f",
+            "lllcversion": "Version: 0.5.14-develop.2023.7.11+commit.c58ab2c6.mod.Linux.g++",
+            "solidity": "Version: 0.8.21+commit.d9974bed.Linux.g++",
+            "source": "src/GeneralStateTestsFiller/stExample/add11Filler.json",
+            "sourceHash": "0ac8f1fbb199f7961a8399d6fa097d8cb7ff121df31e06ad7e8d2a93c2d4c9a6"
+        },
+        "blocks": [
+            {
+                "blockHeader": {
+                    "baseFeePerGas": "0x0a",
+                    "blobGasUsed": "0x00",
+                    "bloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000",
+                    "coinbase": "0x2adc25665018aa1fe0e6bc666dac8fc2697ff9ba",
+                    "difficulty": "0x00",
+                    "excessBlobGas": "0x00",
+                    "extraData": "0x00",
+                    "gasLimit": "0xff112233445566",
+                    "gasUsed": "0xa868",
+                    "hash": "0x294115a365c9113463fc5f85f09a77deb7b4fc56bc9d2b9b92dcaf8d04b2f26f",
+                    "mixHash": "0x0000000000000000000000000000000000000000000000000000000000020000",
+                    "nonce": "0x0000000000000000",
+                    "number": "0x01",
+                    "parentBeaconBlockRoot": "0x0000000000000000000000000000000000000000000000000000000000000000",
+                    "parentHash": "0x4b752ac4fc8dadaa1b82eb79c94f60e0daedf71cf36ac1e441bff7f2c227e4c7",
+                    "receiptTrie": "0x06f890d54ec65d8650b6c73eefd1fbc39f78b5b25f4e1ec10885c9f29f84ee98",
+                    "stateRoot": "0x18571670421257919d0e116b68c9c6223a0872bb493f53fc5b2dffd850c7200f",
+                    "timestamp": "0x03e8",
+                    "transactionsTrie": "0xf91abed7e00f88cadedc98279f8fe12e181da598fdf28c61aa18908e2e32d531",
+                    "uncleHash": "0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347",
+                    "withdrawalsRoot": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421"
+                },
+                "rlp": "0xf902a9f9023da04b752ac4fc8dadaa1b82eb79c94f60e0daedf71cf36ac1e441bff7f2c227e4c7a01dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347942adc25665018aa1fe0e6bc666dac8fc2697ff9baa018571670421257919d0e116b68c9c6223a0872bb493f53fc5b2dffd850c7200fa0f91abed7e00f88cadedc98279f8fe12e181da598fdf28c61aa18908e2e32d531a006f890d54ec65d8650b6c73eefd1fbc39f78b5b25f4e1ec10885c9f29f84ee98b9010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800187ff11223344556682a8688203e800a000000000000000000000000000000000000000000000000000000000000200008800000000000000000aa056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b4218080a00000000000000000000000000000000000000000000000000000000000000000f865f863800a83061a8094095e7baea6a6c7c4c2dfeb977efac326af552d87830186a0801ba0ffb600e63115a7362e7811894a91d8ba4330e526f22121c994c4692035dfdfd5a06198379fcac8de3dbfac48b165df4bf88e2088f294b61efb9a65fe2281c76e16c0c0",
+                "transactions": [
+                    {
+                        "data": "0x",
+                        "gasLimit": "0x061a80",
+                        "gasPrice": "0x0a",
+                        "nonce": "0x00",
+                        "r": "0xffb600e63115a7362e7811894a91d8ba4330e526f22121c994c4692035dfdfd5",
+                        "s": "0x6198379fcac8de3dbfac48b165df4bf88e2088f294b61efb9a65fe2281c76e16",
+                        "sender": "0xa94f5374fce5edbc8e2a8697c15331677e6ebf0b",
+                        "to": "0x095e7baea6a6c7c4c2dfeb977efac326af552d87",
+                        "v": "0x1b",
+                        "value": "0x0186a0"
+                    }
+                ],
+                "uncleHeaders": [],
+                "withdrawals": []
+            }
+        ],
+        "genesisBlockHeader": {
+            "baseFeePerGas": "0x0b",
+            "blobGasUsed": "0x00",
+            "bloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000",
+            "coinbase": "0x2adc25665018aa1fe0e6bc666dac8fc2697ff9ba",
+            "difficulty": "0x00",
+            "excessBlobGas": "0x060000",
+            "extraData": "0x00",
+            "gasLimit": "0xff112233445566",
+            "gasUsed": "0x00",
+            "hash": "0x4b752ac4fc8dadaa1b82eb79c94f60e0daedf71cf36ac1e441bff7f2c227e4c7",
+            "mixHash": "0x0000000000000000000000000000000000000000000000000000000000020000",
+            "nonce": "0x0000000000000000",
+            "number": "0x00",
+            "parentBeaconBlockRoot": "0x0000000000000000000000000000000000000000000000000000000000000000",
+            "parentHash": "0x0000000000000000000000000000000000000000000000000000000000000000",
+            "receiptTrie": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421",
+            "stateRoot": "0x0f06118fcfe149aa3916d754c8747a35f9241cd946b63f8eb40ce66fdac5ce5a",
+            "timestamp": "0x00",
+            "transactionsTrie": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421",
+            "uncleHash": "0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347",
+            "withdrawalsRoot": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421"
+        },
+        "genesisRLP": "0xf90242f9023ca00000000000000000000000000000000000000000000000000000000000000000a01dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347942adc25665018aa1fe0e6bc666dac8fc2697ff9baa00f06118fcfe149aa3916d754c8747a35f9241cd946b63f8eb40ce66fdac5ce5aa056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421a056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421b9010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000808087ff112233445566808000a000000000000000000000000000000000000000000000000000000000000200008800000000000000000ba056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b4218083060000a00000000000000000000000000000000000000000000000000000000000000000c0c0c0",
+        "lastblockhash": "0x294115a365c9113463fc5f85f09a77deb7b4fc56bc9d2b9b92dcaf8d04b2f26f",
+        "network": "Cancun",
+        "postState": {
+            "0x000f3df6d732807ef1319fb7b8bb8522d0beac02": {
+                "balance": "0x00",
+                "code": "0x3373fffffffffffffffffffffffffffffffffffffffe14604d57602036146024575f5ffd5b5f35801560495762001fff810690815414603c575f5ffd5b62001fff01545f5260205ff35b5f5ffd5b62001fff42064281555f359062001fff015500",
+                "nonce": "0x01",
+                "storage": {
+                    "0x03e8": "0x03e8"
+                }
+            },
+            "0x095e7baea6a6c7c4c2dfeb977efac326af552d87": {
+                "balance": "0x0de0b6b3a76586a0",
+                "code": "0x600160010160005500",
+                "nonce": "0x00",
+                "storage": {
+                    "0x00": "0x02"
+                }
+            },
+            "0x2adc25665018aa1fe0e6bc666dac8fc2697ff9ba": {
+                "balance": "0x00",
+                "code": "0x",
+                "nonce": "0x01",
+                "storage": {}
+            },
+            "0xa94f5374fce5edbc8e2a8697c15331677e6ebf0b": {
+                "balance": "0x0de0b6b3a75be550",
+                "code": "0x",
+                "nonce": "0x01",
+                "storage": {}
+            }
+        },
+        "pre": {
+            "0x000f3df6d732807ef1319fb7b8bb8522d0beac02": {
+                "balance": "0x00",
+                "code": "0x3373fffffffffffffffffffffffffffffffffffffffe14604d57602036146024575f5ffd5b5f35801560495762001fff810690815414603c575f5ffd5b62001fff01545f5260205ff35b5f5ffd5b62001fff42064281555f359062001fff015500",
+                "nonce": "0x01",
+                "storage": {}
+            },
+            "0x095e7baea6a6c7c4c2dfeb977efac326af552d87": {
+                "balance": "0x0de0b6b3a7640000",
+                "code": "0x600160010160005500",
+                "nonce": "0x00",
+                "storage": {}
+            },
+            "0x2adc25665018aa1fe0e6bc666dac8fc2697ff9ba": {
+                "balance": "0x00",
+                "code": "0x",
+                "nonce": "0x01",
+                "storage": {}
+            },
+            "0xa94f5374fce5edbc8e2a8697c15331677e6ebf0b": {
+                "balance": "0x0de0b6b3a7640000",
+                "code": "0x",
+                "nonce": "0x00",
+                "storage": {}
+            }
+        },
+        "sealEngine": "NoProof"
+    }
+}