diff --git a/book/src/format.md b/book/src/format.md
index e69de29..67c2d05 100644
--- a/book/src/format.md
+++ b/book/src/format.md
@@ -0,0 +1,25 @@
+# Test Fixture Format
+
+There are two primary types of test fixtures: Derivation and Execution.
+Test fixtures are static JSON files that live in the [fixtures][fixtures] directory.
+
+## Execution Test Fixtures
+
+Execution test fixtures live inside the `fixtures/execution/` directory.
+Each JSON file in this directory contains the JSON-serialized
+[`ExecutionFixture`][exec-fixture] object which is defined in Rust
+in the [op-test-vectors][op-test-vectors] crate.
+
+The `ExecutionFixture` holds everything needed to test execution of the OP Stack.
+It's composed of the following.
+- An `ExecutionEnvironment`, which is used to setup the execution client's environment.
+- An initial set of addresses and their states, also called the "pre-state".
+- A final set of addresses and their states, also called the "post-state".
+- A list of transactions to execute in the environment.
+- The result of executing all the transactions.
+
+## Derivation Test Fixtures
+
+// TODO
+
+{{#include ../links.md}}
diff --git a/crates/op-test-vectors/Cargo.toml b/crates/op-test-vectors/Cargo.toml
index 2391ed9..f268c54 100644
--- a/crates/op-test-vectors/Cargo.toml
+++ b/crates/op-test-vectors/Cargo.toml
@@ -7,6 +7,7 @@ homepage.workspace = true
version.workspace = true
authors.workspace = true
edition.workspace = true
+exclude = ["src/testdata"]
[dependencies]
op-alloy-rpc-types.workspace = true
@@ -14,7 +15,7 @@ op-alloy-consensus.workspace = true
alloy.workspace = true
serde.workspace = true
anvil-core.workspace = true
+color-eyre.workspace = true
[dev-dependencies]
-color-eyre.workspace = true
serde_json.workspace = true
diff --git a/crates/op-test-vectors/README.md b/crates/op-test-vectors/README.md
new file mode 100644
index 0000000..2c77d4b
--- /dev/null
+++ b/crates/op-test-vectors/README.md
@@ -0,0 +1,7 @@
+# OP Test Vectors
+
+Rust test fixture type definitions for the OP Stack.
+
+There are two primary test fixture types in this crate:
+- execution
+- derivation
diff --git a/crates/op-test-vectors/src/derivation.rs b/crates/op-test-vectors/src/derivation.rs
index c2f0b3b..35a62c3 100644
--- a/crates/op-test-vectors/src/derivation.rs
+++ b/crates/op-test-vectors/src/derivation.rs
@@ -1 +1,8 @@
+//! Module containing the derivation test fixture.
+
+use serde::{Deserialize, Serialize};
+
+/// The derivation fixture is the top-level object that contains
+/// everything needed to run a derivation test.
+#[derive(Serialize, Deserialize, Debug, Default)]
pub struct DerivationFixture {}
diff --git a/crates/op-test-vectors/src/execution.rs b/crates/op-test-vectors/src/execution.rs
index 9176718..bdfd66e 100644
--- a/crates/op-test-vectors/src/execution.rs
+++ b/crates/op-test-vectors/src/execution.rs
@@ -1,157 +1,189 @@
-use std::collections::HashMap;
+//! Module containing the execution test fixture.
+use std::collections::HashMap;
use alloy::primitives::{Address, Bloom, B256, U256};
use alloy::rpc::types::trace::geth::AccountState;
use alloy::rpc::types::{Log, TransactionReceipt};
use anvil_core::eth::block::Block;
use anvil_core::eth::transaction::{TypedReceipt, TypedTransaction};
use serde::{Deserialize, Serialize};
+use color_eyre::eyre;
+/// The execution fixture is the top-level object that contains
+/// everything needed to run an execution test.
#[derive(Serialize, Deserialize, Debug, Default)]
pub struct ExecutionFixture {
+ /// The execution environment sets up the current block context.
pub env: ExecutionEnvironment,
+ /// The initial state of the accounts before running the transactions, also called the
+ /// "pre-state".
pub alloc: HashMap
,
+ /// The expected state of the accounts after running the transactions, also called the
+ /// "post-state".
pub out_alloc: HashMap,
+ /// Transactions to execute.
#[serde(rename = "txs")]
pub transactions: Vec,
+ /// The expected result after executing transactions.
pub result: ExecutionResult,
}
+/// The execution environment is the initial state of the execution context.
+/// It's used to set the execution environment current block information.
#[derive(Serialize, Deserialize, Debug, Default)]
#[serde(rename_all = "camelCase")]
pub struct ExecutionEnvironment {
+ /// The current block coinbase.
pub current_coinbase: Address,
+ /// The current block difficulty.
pub current_difficulty: U256,
+ /// The current block gas limit.
pub current_gas_limit: U256,
+ /// The previous block hash.
pub previous_hash: B256,
+ /// The current block number.
pub current_number: U256,
+ /// The current block timestamp.
pub current_timestamp: U256,
+ /// The block hashes of the previous blocks.
#[serde(skip_serializing_if = "Option::is_none")]
pub block_hashes: Option>,
}
+impl From for ExecutionEnvironment {
+ fn from(block: Block) -> Self {
+ Self {
+ current_coinbase: block.header.beneficiary,
+ current_difficulty: block.header.difficulty,
+ current_gas_limit: U256::from(block.header.gas_limit),
+ previous_hash: block.header.parent_hash,
+ current_number: U256::from(block.header.number),
+ current_timestamp: U256::from(block.header.timestamp),
+ block_hashes: None,
+ }
+ }
+}
+
+/// The execution result is the expected result after running the transactions
+/// in the execution environment over the pre-state.
#[derive(Serialize, Deserialize, Debug, Default)]
#[serde(rename_all = "camelCase")]
pub struct ExecutionResult {
+ /// The state root.
pub state_root: B256,
+ /// The transaction root.
pub tx_root: B256,
+ /// The receipt root.
pub receipt_root: B256,
+ /// The logs hash.
pub logs_hash: B256,
+ /// The logs bloom.
pub logs_bloom: Bloom,
+ /// A list of execution receipts for each executed transaction.
pub receipts: Vec,
}
+/// An execution receipt is the result of running a transaction in the execution environment.
#[derive(Serialize, Deserialize, Debug)]
#[serde(rename_all = "camelCase")]
pub struct ExecutionReceipt {
+ /// The state root.
pub root: B256,
+ /// The hash of the transaction.
pub transaction_hash: B256,
- pub contract_address: Address,
+ /// The contract address that the transaction created.
+ #[serde(skip_serializing_if = "Option::is_none")]
+ pub contract_address: Option,
+ /// The gas used by the transaction.
pub gas_used: U256,
+ /// The block hash.
pub block_hash: B256,
+ /// The transaction index.
pub transaction_index: U256,
+ /// The inner log receipt.
#[serde(flatten)]
pub inner: TypedReceipt,
}
-impl From for ExecutionEnvironment {
- fn from(block: Block) -> Self {
- Self {
- current_coinbase: block.header.beneficiary,
- current_difficulty: block.header.difficulty,
- current_gas_limit: U256::from(block.header.gas_limit),
- previous_hash: block.header.parent_hash,
- current_number: U256::from(block.header.number),
- current_timestamp: U256::from(block.header.timestamp),
- block_hashes: None,
- }
- }
-}
+impl TryFrom>> for ExecutionReceipt {
+ type Error = eyre::Error;
-impl From>> for ExecutionReceipt {
- fn from(receipt: TransactionReceipt>) -> ExecutionReceipt {
- ExecutionReceipt {
+ fn try_from(receipt: TransactionReceipt>) -> eyre::Result {
+ Ok(Self {
transaction_hash: receipt.transaction_hash,
- root: receipt.state_root.unwrap_or_default(),
- contract_address: receipt.contract_address.unwrap_or_default(),
+ root: receipt.state_root.ok_or_else(|| eyre::eyre!("missing state root"))?,
+ contract_address: receipt.contract_address,
gas_used: U256::from(receipt.gas_used),
- block_hash: receipt.block_hash.unwrap_or_default(),
- transaction_index: U256::from(receipt.transaction_index.unwrap_or_default()),
+ block_hash: receipt.block_hash.ok_or_else(|| eyre::eyre!("missing block hash"))?,
+ transaction_index: U256::from(receipt.transaction_index.ok_or_else(|| eyre::eyre!("missing transaction index"))?),
inner: receipt.inner,
- }
+ })
}
}
#[cfg(test)]
mod tests {
-
- use super::ExecutionEnvironment;
- use crate::execution::ExecutionResult;
- use color_eyre::eyre;
+ use super::*;
use serde_json::Value;
#[test]
- fn test_serialize_execution_environment() -> eyre::Result<()> {
- let expected_env = r#"
- {
- "currentCoinbase" : "0x2adc25665018aa1fe0e6bc666dac8fc2697ff9ba",
- "currentDifficulty" : "0x20000",
- "currentGasLimit" : "0x5f5e100",
- "currentNumber" : "0x1",
- "currentTimestamp" : "0x3e8",
- "previousHash" : "0xe729de3fec21e30bea3d56adb01ed14bc107273c2775f9355afb10f594a10d9e",
- "blockHashes" : {
- "0x0" : "0xe729de3fec21e30bea3d56adb01ed14bc107273c2775f9355afb10f594a10d9e"
- }
- }
- "#;
-
- let env = serde_json::from_str::(expected_env)?;
- let serialized_env = serde_json::to_string(&env)?;
-
- assert_eq!(
- serde_json::from_str::(expected_env)?,
- serde_json::from_str::(&serialized_env)?
- );
+ fn test_serialize_execution_environment() {
+ let expected_env = include_str!("./testdata/environment.json");
+ let env = serde_json::from_str::(expected_env).expect("failed to parse environment");
+ let serialized_env = serde_json::to_string(&env).expect("failed to serialize environment");
+ let serialized_value = serde_json::from_str::(&serialized_env).expect("failed to parse serialized environment");
+ let expected_value = serde_json::from_str::(expected_env).expect("failed to parse expected environment");
+ assert_eq!(serialized_value, expected_value);
+ }
- Ok(())
+ #[test]
+ fn test_serialize_execution_result() {
+ let expected_result = include_str!("./testdata/result.json");
+ let execution_result = serde_json::from_str::(expected_result).expect("failed to parse result");
+ let serialized_result = serde_json::to_string(&execution_result).expect("failed to serialize result");
+ let serialized_value = serde_json::from_str::(&serialized_result).expect("failed to parse serialized result");
+ let expected_value = serde_json::from_str::(expected_result).expect("failed to parse expected result");
+ assert_eq!(serialized_value, expected_value);
}
#[test]
- fn test_serialize_execution_result() -> eyre::Result<()> {
- let expected_result = r#"
- {
- "stateRoot": "0x1c99b01120e7a2fa1301b3505f20100e72362e5ac3f96854420e56ba8984d716",
- "txRoot": "0xb5eee60b45801179cbde3781b9a5dee9b3111554618c9cda3d6f7e351fd41e0b",
- "receiptRoot": "0x86ceb80cb6bef8fe4ac0f1c99409f67cb2554c4432f374e399b94884eb3e6562",
- "logsHash": "0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347",
- "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000",
- "receipts": [
- {
- "root": "0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347",
- "status": "0x1",
- "type": "0x0",
- "cumulativeGasUsed": "0xa878",
- "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000",
- "logs": [],
- "transactionHash": "0x4e6549e2276d1bc256b2a56ead2d9705a51a8bf54e3775fbd2e98c91fb0e4494",
- "contractAddress": "0x0000000000000000000000000000000000000000",
- "gasUsed": "0xa878",
- "blockHash": "0x0000000000000000000000000000000000000000000000000000000000000000",
- "transactionIndex": "0x0"
- }
- ]
- }
- "#;
+ fn test_exec_receipt_try_from_tx_receipt() {
+ let tx_receipt_str = include_str!("./testdata/tx_receipt.json");
+ let tx_receipt: TransactionReceipt> = serde_json::from_str(tx_receipt_str).expect("failed to parse tx receipt");
+ let exec_receipt = ExecutionReceipt::try_from(tx_receipt.clone()).expect("failed to convert tx receipt to exec receipt");
+ assert_eq!(exec_receipt.transaction_hash, tx_receipt.transaction_hash);
+ assert_eq!(exec_receipt.root, tx_receipt.state_root.unwrap());
+ assert_eq!(exec_receipt.contract_address, tx_receipt.contract_address);
+ assert_eq!(exec_receipt.gas_used, U256::from(tx_receipt.gas_used));
+ assert_eq!(exec_receipt.block_hash, tx_receipt.block_hash.unwrap());
+ assert_eq!(exec_receipt.transaction_index, U256::from(tx_receipt.transaction_index.unwrap()));
+ assert_eq!(exec_receipt.inner, tx_receipt.inner);
+ }
- let execution_result = serde_json::from_str::(expected_result)?;
- let serialized_result = serde_json::to_string(&execution_result)?;
+ #[test]
+ fn test_exec_receipt_try_from_missing_root() {
+ let tx_receipt_str = include_str!("./testdata/tx_receipt.json");
+ let mut tx_receipt: TransactionReceipt> = serde_json::from_str(tx_receipt_str).expect("failed to parse tx receipt");
+ tx_receipt.state_root = None;
+ let exec_receipt = ExecutionReceipt::try_from(tx_receipt);
+ assert!(exec_receipt.is_err());
+ }
- assert_eq!(
- serde_json::from_str::(expected_result)?,
- serde_json::from_str::(&serialized_result)?
- );
+ #[test]
+ fn test_exec_receipt_try_from_missing_block_hash() {
+ let tx_receipt_str = include_str!("./testdata/tx_receipt.json");
+ let mut tx_receipt: TransactionReceipt> = serde_json::from_str(tx_receipt_str).expect("failed to parse tx receipt");
+ tx_receipt.block_hash = None;
+ let exec_receipt = ExecutionReceipt::try_from(tx_receipt);
+ assert!(exec_receipt.is_err());
+ }
- Ok(())
+ #[test]
+ fn test_exec_receipt_try_from_missing_tx_index() {
+ let tx_receipt_str = include_str!("./testdata/tx_receipt.json");
+ let mut tx_receipt: TransactionReceipt> = serde_json::from_str(tx_receipt_str).expect("failed to parse tx receipt");
+ tx_receipt.transaction_index = None;
+ let exec_receipt = ExecutionReceipt::try_from(tx_receipt);
+ assert!(exec_receipt.is_err());
}
}
diff --git a/crates/op-test-vectors/src/lib.rs b/crates/op-test-vectors/src/lib.rs
index e1745d8..35d85de 100644
--- a/crates/op-test-vectors/src/lib.rs
+++ b/crates/op-test-vectors/src/lib.rs
@@ -1,2 +1,8 @@
+#![doc = include_str!("../README.md")]
+#![warn(missing_debug_implementations, missing_docs, unreachable_pub, rustdoc::all)]
+#![deny(unused_must_use, rust_2018_idioms)]
+#![cfg_attr(docsrs, feature(doc_cfg, doc_auto_cfg))]
+
pub mod derivation;
+
pub mod execution;
diff --git a/crates/op-test-vectors/src/testdata/environment.json b/crates/op-test-vectors/src/testdata/environment.json
new file mode 100644
index 0000000..5dca82e
--- /dev/null
+++ b/crates/op-test-vectors/src/testdata/environment.json
@@ -0,0 +1,11 @@
+{
+ "currentCoinbase" : "0x2adc25665018aa1fe0e6bc666dac8fc2697ff9ba",
+ "currentDifficulty" : "0x20000",
+ "currentGasLimit" : "0x5f5e100",
+ "currentNumber" : "0x1",
+ "currentTimestamp" : "0x3e8",
+ "previousHash" : "0xe729de3fec21e30bea3d56adb01ed14bc107273c2775f9355afb10f594a10d9e",
+ "blockHashes" : {
+ "0x0" : "0xe729de3fec21e30bea3d56adb01ed14bc107273c2775f9355afb10f594a10d9e"
+ }
+ }
diff --git a/crates/op-test-vectors/src/testdata/result.json b/crates/op-test-vectors/src/testdata/result.json
new file mode 100644
index 0000000..7fdd264
--- /dev/null
+++ b/crates/op-test-vectors/src/testdata/result.json
@@ -0,0 +1,22 @@
+{
+ "stateRoot": "0x1c99b01120e7a2fa1301b3505f20100e72362e5ac3f96854420e56ba8984d716",
+ "txRoot": "0xb5eee60b45801179cbde3781b9a5dee9b3111554618c9cda3d6f7e351fd41e0b",
+ "receiptRoot": "0x86ceb80cb6bef8fe4ac0f1c99409f67cb2554c4432f374e399b94884eb3e6562",
+ "logsHash": "0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347",
+ "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000",
+ "receipts": [
+ {
+ "root": "0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347",
+ "status": "0x1",
+ "type": "0x0",
+ "cumulativeGasUsed": "0xa878",
+ "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000",
+ "logs": [],
+ "transactionHash": "0x4e6549e2276d1bc256b2a56ead2d9705a51a8bf54e3775fbd2e98c91fb0e4494",
+ "contractAddress": "0x0000000000000000000000000000000000000000",
+ "gasUsed": "0xa878",
+ "blockHash": "0x0000000000000000000000000000000000000000000000000000000000000000",
+ "transactionIndex": "0x0"
+ }
+ ]
+}
diff --git a/crates/op-test-vectors/src/testdata/tx_receipt.json b/crates/op-test-vectors/src/testdata/tx_receipt.json
new file mode 100644
index 0000000..06d4116
--- /dev/null
+++ b/crates/op-test-vectors/src/testdata/tx_receipt.json
@@ -0,0 +1,18 @@
+{
+ "root": "0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347",
+ "type": "0x0",
+ "status": "0x1",
+ "cumulativeGasUsed": "0xa878",
+ "logs": [],
+ "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000",
+ "transactionHash": "0x4e6549e2276d1bc256b2a56ead2d9705a51a8bf54e3775fbd2e98c91fb0e4494",
+ "transactionIndex": "0x0",
+ "blockHash": "0x0000000000000000000000000000000000000000000000000000000000000000",
+ "blockNumber": "0x0",
+ "gasUsed": "0xa878",
+ "effectiveGasPrice": "0x0",
+ "blobGasUsed": "0xa878",
+ "blobGasPrice": "0x0",
+ "from": "0xbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb",
+ "to": "0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
+}
diff --git a/crates/opt8n/src/opt8n.rs b/crates/opt8n/src/opt8n.rs
index 2952df8..b7f99e9 100644
--- a/crates/opt8n/src/opt8n.rs
+++ b/crates/opt8n/src/opt8n.rs
@@ -201,7 +201,7 @@ impl Opt8n {
for tx in &transactions {
if let Some(receipt) = self.eth_api.backend.transaction_receipt(tx.hash()).await? {
- receipts.push(receipt.into());
+ receipts.push(receipt.try_into()?);
}
self.execution_fixture.transactions.push(tx.to_owned());
}