Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: Cleanup and document op-test-vectors #31

Merged
merged 1 commit into from
Jul 24, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 25 additions & 0 deletions book/src/format.md
Original file line number Diff line number Diff line change
@@ -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}}
3 changes: 2 additions & 1 deletion crates/op-test-vectors/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,15 @@ homepage.workspace = true
version.workspace = true
authors.workspace = true
edition.workspace = true
exclude = ["src/testdata"]

[dependencies]
op-alloy-rpc-types.workspace = true
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
7 changes: 7 additions & 0 deletions crates/op-test-vectors/README.md
Original file line number Diff line number Diff line change
@@ -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
7 changes: 7 additions & 0 deletions crates/op-test-vectors/src/derivation.rs
Original file line number Diff line number Diff line change
@@ -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 {}
196 changes: 114 additions & 82 deletions crates/op-test-vectors/src/execution.rs
Original file line number Diff line number Diff line change
@@ -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<Address, AccountState>,
/// The expected state of the accounts after running the transactions, also called the
/// "post-state".
pub out_alloc: HashMap<Address, AccountState>,
/// Transactions to execute.
#[serde(rename = "txs")]
pub transactions: Vec<TypedTransaction>,
/// 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<HashMap<U256, B256>>,
}

impl From<Block> 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<ExecutionReceipt>,
}

/// 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<Address>,
/// 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<Log>,
}

impl From<Block> 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<TransactionReceipt<TypedReceipt<Log>>> for ExecutionReceipt {
type Error = eyre::Error;

impl From<TransactionReceipt<TypedReceipt<Log>>> for ExecutionReceipt {
fn from(receipt: TransactionReceipt<TypedReceipt<Log>>) -> ExecutionReceipt {
ExecutionReceipt {
fn try_from(receipt: TransactionReceipt<TypedReceipt<Log>>) -> eyre::Result<Self> {
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::<ExecutionEnvironment>(expected_env)?;
let serialized_env = serde_json::to_string(&env)?;

assert_eq!(
serde_json::from_str::<Value>(expected_env)?,
serde_json::from_str::<Value>(&serialized_env)?
);
fn test_serialize_execution_environment() {
let expected_env = include_str!("./testdata/environment.json");
let env = serde_json::from_str::<ExecutionEnvironment>(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::<Value>(&serialized_env).expect("failed to parse serialized environment");
let expected_value = serde_json::from_str::<Value>(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::<ExecutionResult>(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::<Value>(&serialized_result).expect("failed to parse serialized result");
let expected_value = serde_json::from_str::<Value>(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<TypedReceipt<Log>> = 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::<ExecutionResult>(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<TypedReceipt<Log>> = 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::<Value>(expected_result)?,
serde_json::from_str::<Value>(&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<TypedReceipt<Log>> = 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<TypedReceipt<Log>> = 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());
}
}
6 changes: 6 additions & 0 deletions crates/op-test-vectors/src/lib.rs
Original file line number Diff line number Diff line change
@@ -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;
11 changes: 11 additions & 0 deletions crates/op-test-vectors/src/testdata/environment.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
{
"currentCoinbase" : "0x2adc25665018aa1fe0e6bc666dac8fc2697ff9ba",
"currentDifficulty" : "0x20000",
"currentGasLimit" : "0x5f5e100",
"currentNumber" : "0x1",
"currentTimestamp" : "0x3e8",
"previousHash" : "0xe729de3fec21e30bea3d56adb01ed14bc107273c2775f9355afb10f594a10d9e",
"blockHashes" : {
"0x0" : "0xe729de3fec21e30bea3d56adb01ed14bc107273c2775f9355afb10f594a10d9e"
}
}
22 changes: 22 additions & 0 deletions crates/op-test-vectors/src/testdata/result.json
Original file line number Diff line number Diff line change
@@ -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"
}
]
}
18 changes: 18 additions & 0 deletions crates/op-test-vectors/src/testdata/tx_receipt.json
Original file line number Diff line number Diff line change
@@ -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"
}
Loading