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

enha: add snapshot tests for external primitives #2007

Merged
merged 9 commits into from
Feb 11, 2025
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
62 changes: 61 additions & 1 deletion src/eth/primitives/external_block.rs
Original file line number Diff line number Diff line change
@@ -1,14 +1,24 @@
use alloy_eips::eip4895::Withdrawals;
use alloy_primitives::Bloom;
use alloy_primitives::Bytes;
use alloy_primitives::B256;
use alloy_primitives::B64;
use alloy_primitives::U256;
use fake::Dummy;
use fake::Fake;
use fake::Faker;
use serde::Deserialize;

use crate::alias::AlloyBlockExternalTransaction;
use crate::alias::JsonValue;
use crate::eth::primitives::external_transaction::ExternalTransaction;
use crate::eth::primitives::Address;
use crate::eth::primitives::BlockNumber;
use crate::eth::primitives::Hash;
use crate::eth::primitives::UnixTime;
use crate::log_and_err;

#[derive(Debug, Clone, derive_more::Deref, derive_more::DerefMut, serde::Deserialize, serde::Serialize)]
#[derive(Debug, Clone, PartialEq, derive_more::Deref, derive_more::DerefMut, serde::Deserialize, serde::Serialize)]
#[serde(transparent)]
pub struct ExternalBlock(#[deref] pub AlloyBlockExternalTransaction);

Expand Down Expand Up @@ -36,9 +46,59 @@ impl ExternalBlock {
}
}

impl Dummy<Faker> for ExternalBlock {
fn dummy_with_rng<R: rand_core::RngCore + ?Sized>(faker: &Faker, rng: &mut R) -> Self {
let mut addr_bytes = [0u8; 20];
let mut hash_bytes = [0u8; 32];
let mut nonce_bytes = [0u8; 8];
rng.fill_bytes(&mut addr_bytes);
rng.fill_bytes(&mut hash_bytes);
rng.fill_bytes(&mut nonce_bytes);

let transaction: ExternalTransaction = faker.fake_with_rng(rng);

let block = alloy_rpc_types_eth::Block {
header: alloy_rpc_types_eth::Header {
hash: B256::from_slice(&hash_bytes),
inner: alloy_consensus::Header {
parent_hash: B256::from_slice(&hash_bytes),
ommers_hash: B256::from_slice(&hash_bytes),
beneficiary: alloy_primitives::Address::from_slice(&addr_bytes),
state_root: B256::from_slice(&hash_bytes),
transactions_root: B256::from_slice(&hash_bytes),
receipts_root: B256::from_slice(&hash_bytes),
withdrawals_root: Some(B256::from_slice(&hash_bytes)),
number: rng.next_u64(),
gas_used: rng.next_u64(),
gas_limit: rng.next_u64(),
extra_data: Bytes::default(),
logs_bloom: Bloom::default(),
timestamp: rng.next_u64(),
difficulty: U256::from(rng.next_u64()),
mix_hash: B256::from_slice(&hash_bytes),
nonce: B64::from_slice(&nonce_bytes),
base_fee_per_gas: Some(rng.next_u64()),
blob_gas_used: None,
excess_blob_gas: None,
parent_beacon_block_root: None,
requests_hash: None,
},
total_difficulty: Some(U256::from(rng.next_u64())),
size: Some(U256::from(rng.next_u64())),
},
uncles: vec![B256::from_slice(&hash_bytes)],
transactions: alloy_rpc_types_eth::BlockTransactions::Full(vec![transaction]),
withdrawals: Some(Withdrawals::default()),
};

ExternalBlock(block)
}
}

// -----------------------------------------------------------------------------
// Conversions: Other -> Self
// -----------------------------------------------------------------------------

impl TryFrom<JsonValue> for ExternalBlock {
type Error = anyhow::Error;

Expand Down
19 changes: 18 additions & 1 deletion src/eth/primitives/external_block_with_receipts.rs
Original file line number Diff line number Diff line change
@@ -1,16 +1,33 @@
use fake::Dummy;
use fake::Faker;
use serde::Deserialize;
use serde::Serialize;

use crate::alias::JsonValue;
use crate::eth::primitives::ExternalBlock;
use crate::eth::primitives::ExternalReceipt;
use crate::log_and_err;

#[derive(Debug, Clone, Deserialize)]
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize)]
pub struct ExternalBlockWithReceipts {
pub block: ExternalBlock,
pub receipts: Vec<ExternalReceipt>,
}

impl Dummy<Faker> for ExternalBlockWithReceipts {
fn dummy_with_rng<R: rand_core::RngCore + ?Sized>(faker: &Faker, rng: &mut R) -> Self {
let block = ExternalBlock::dummy_with_rng(faker, rng);

let receipts = match &block.transactions {
alloy_rpc_types_eth::BlockTransactions::Full(txs) => txs.iter().map(|_| ExternalReceipt::dummy_with_rng(faker, rng)).collect(),
alloy_rpc_types_eth::BlockTransactions::Hashes(_) => Vec::new(),
alloy_rpc_types_eth::BlockTransactions::Uncle => Vec::new(),
};

Self { block, receipts }
}
}

// -----------------------------------------------------------------------------
// Conversions: Other -> Self
// -----------------------------------------------------------------------------
Expand Down
61 changes: 60 additions & 1 deletion src/eth/primitives/external_receipt.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,10 @@
use alloy_consensus::ReceiptEnvelope;
use alloy_primitives::Bloom;
use alloy_primitives::Bytes;
use alloy_primitives::B256;
use ethereum_types::U256;
use fake::Dummy;
use fake::Faker;
use serde::Deserialize;

use crate::alias::AlloyReceipt;
Expand All @@ -8,7 +14,7 @@ use crate::eth::primitives::Hash;
use crate::eth::primitives::Wei;
use crate::log_and_err;

#[derive(Debug, Clone, derive_more::Deref, serde::Serialize)]
#[derive(Debug, Clone, PartialEq, derive_more::Deref, serde::Serialize)]
#[serde(transparent)]
pub struct ExternalReceipt(#[deref] pub AlloyReceipt);

Expand Down Expand Up @@ -43,6 +49,58 @@ impl ExternalReceipt {
}
}

impl Dummy<Faker> for ExternalReceipt {
fn dummy_with_rng<R: rand_core::RngCore + ?Sized>(_faker: &Faker, rng: &mut R) -> Self {
let mut addr_bytes = [0u8; 20];
let mut hash_bytes = [0u8; 32];
rng.fill_bytes(&mut addr_bytes);
rng.fill_bytes(&mut hash_bytes);

let log = alloy_rpc_types_eth::Log {
inner: alloy_primitives::Log {
address: alloy_primitives::Address::from_slice(&addr_bytes),
data: alloy_primitives::LogData::new_unchecked(vec![B256::from_slice(&hash_bytes)], Bytes::default()),
},
block_hash: Some(B256::from_slice(&hash_bytes)),
block_number: Some(rng.next_u64()),
transaction_hash: Some(B256::from_slice(&hash_bytes)),
transaction_index: Some(rng.next_u64()),
log_index: Some(rng.next_u64()),
removed: false,
block_timestamp: Some(rng.next_u64()),
};

let receipt = alloy_consensus::Receipt {
status: alloy_consensus::Eip658Value::Eip658(true),
cumulative_gas_used: rng.next_u64(),
logs: vec![log],
};

let receipt_envelope = ReceiptEnvelope::Legacy(alloy_consensus::ReceiptWithBloom {
receipt,
logs_bloom: Bloom::default(),
});

let receipt = alloy_rpc_types_eth::TransactionReceipt {
inner: receipt_envelope,
transaction_hash: B256::from_slice(&hash_bytes),
transaction_index: Some(rng.next_u64()),
block_hash: Some(B256::from_slice(&hash_bytes)),
block_number: Some(rng.next_u64()),
from: alloy_primitives::Address::from_slice(&addr_bytes),
to: Some(alloy_primitives::Address::from_slice(&addr_bytes)),
contract_address: None,
gas_used: rng.next_u64(),
effective_gas_price: rng.next_u64() as u128,
blob_gas_used: None,
blob_gas_price: None,
authorization_list: None,
};

ExternalReceipt(receipt)
}
}

// -----------------------------------------------------------------------------
// Serialization / Deserialization
// -----------------------------------------------------------------------------
Expand Down Expand Up @@ -91,6 +149,7 @@ impl TryFrom<JsonValue> for ExternalReceipt {

#[cfg(test)]
mod tests {

use alloy_consensus::TxType;

use super::*;
Expand Down
17 changes: 16 additions & 1 deletion src/eth/primitives/external_receipts.rs
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
use std::collections::HashMap;

use anyhow::anyhow;
use fake::Dummy;
use fake::Faker;

use crate::eth::primitives::ExternalReceipt;
use crate::eth::primitives::Hash;

/// A collection of [`ExternalReceipt`].
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)]
pub struct ExternalReceipts(HashMap<Hash, ExternalReceipt>);

impl ExternalReceipts {
Expand All @@ -22,6 +24,19 @@ impl ExternalReceipts {
}
}

impl Dummy<Faker> for ExternalReceipts {
fn dummy_with_rng<R: rand_core::RngCore + ?Sized>(faker: &Faker, rng: &mut R) -> Self {
let count = (rng.next_u32() % 5 + 1) as usize;
let receipts = (0..count).map(|_| ExternalReceipt::dummy_with_rng(faker, rng)).collect::<Vec<_>>();

Self::from(receipts)
}
}

// -----------------------------------------------------------------------------
// Conversions: Other -> Self
// -----------------------------------------------------------------------------

impl From<Vec<ExternalReceipt>> for ExternalReceipts {
fn from(receipts: Vec<ExternalReceipt>) -> Self {
let mut receipts_by_hash = HashMap::with_capacity(receipts.len());
Expand Down
55 changes: 54 additions & 1 deletion src/eth/primitives/external_transaction.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,24 @@
use alloy_consensus::Signed;
use alloy_consensus::TxEnvelope;
use alloy_consensus::TxLegacy;
use alloy_primitives::Bytes;
use alloy_primitives::PrimitiveSignature;
use alloy_primitives::TxKind;
use anyhow::Context;
use anyhow::Result;
use ethereum_types::U256;
use fake::Dummy;
use fake::Fake;
use fake::Faker;

use crate::alias::AlloyTransaction;
use crate::eth::primitives::signature_component::SignatureComponent;
use crate::eth::primitives::Address;
use crate::eth::primitives::BlockNumber;
use crate::eth::primitives::Hash;
#[derive(Debug, Clone, derive_more::Deref, serde::Deserialize, serde::Serialize)]
use crate::eth::primitives::Wei;

#[derive(Debug, Clone, PartialEq, derive_more::Deref, serde::Deserialize, serde::Serialize)]
#[serde(transparent)]
pub struct ExternalTransaction(#[deref] pub AlloyTransaction);

Expand All @@ -20,6 +34,45 @@ impl ExternalTransaction {
}
}

impl Dummy<Faker> for ExternalTransaction {
fn dummy_with_rng<R: rand_core::RngCore + ?Sized>(faker: &Faker, rng: &mut R) -> Self {
let from: Address = faker.fake_with_rng(rng);
let block_hash: Hash = faker.fake_with_rng(rng);

let gas_price: Wei = Wei::from(rng.next_u64());
let value: Wei = Wei::from(rng.next_u64());

let tx = TxLegacy {
chain_id: Some(1),
nonce: rng.next_u64(),
gas_price: gas_price.into(),
gas_limit: rng.next_u64(),
to: TxKind::Call(from.into()),
value: value.into(),
input: Bytes::default(),
};

let r = U256::from(rng.next_u64());
let s = U256::from(rng.next_u64());
let v = rng.next_u64() % 2 == 0;
let signature = PrimitiveSignature::new(SignatureComponent(r).into(), SignatureComponent(s).into(), v);

let hash: Hash = faker.fake_with_rng(rng);
let inner_tx = TxEnvelope::Legacy(Signed::new_unchecked(tx, signature, hash.into()));

let inner = alloy_rpc_types_eth::Transaction {
inner: inner_tx,
block_hash: Some(block_hash.into()),
block_number: Some(rng.next_u64()),
transaction_index: Some(rng.next_u64()),
from: from.into(),
effective_gas_price: Some(gas_price.as_u128()),
};

ExternalTransaction(inner)
}
}

// -----------------------------------------------------------------------------
// Conversions: Other -> Self
// -----------------------------------------------------------------------------
Expand Down
7 changes: 7 additions & 0 deletions src/eth/primitives/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,7 @@ pub use wei::Wei;
#[cfg(test)]
mod tests {
use super::*;
use crate::gen_test_json;
use crate::gen_test_serde;

type TransactionExecutionValueChangeBytes = ExecutionValueChange<Bytes>;
Expand All @@ -142,6 +143,12 @@ mod tests {
// gen_test_serde!(TransactionExecution);
// gen_test_serde!(TransactionStage);

gen_test_json!(ExternalBlock);
gen_test_json!(ExternalBlockWithReceipts);
gen_test_json!(ExternalReceipt);
gen_test_json!(ExternalReceipts);
gen_test_json!(ExternalTransaction);

gen_test_serde!(Account);
gen_test_serde!(Address);
gen_test_serde!(Block);
Expand Down
52 changes: 52 additions & 0 deletions src/ext.rs
Original file line number Diff line number Diff line change
Expand Up @@ -346,6 +346,58 @@ macro_rules! gen_test_serde {
};
}

/// Generates a unit test that verifies JSON serialization/deserialization compatibility using snapshots.
#[macro_export]
macro_rules! gen_test_json {
($type:ty) => {
paste::paste! {
#[test]
fn [<test_ $type:snake _json_snapshot>]() -> anyhow::Result<()> {
use anyhow::bail;
use std::path::Path;
use std::{env, fs};

let expected: $type = $crate::utils::test_utils::fake_first::<$type>();
let expected_json = serde_json::to_string_pretty(&expected)?;
let snapshot_parent_path = "tests/fixtures/primitives";
let snapshot_name = format!("{}.json", stringify!($type));
let snapshot_path = format!("{}/{}", snapshot_parent_path, snapshot_name);

// WARNING: If you need to update snapshots (DANGEROUS_UPDATE_SNAPSHOTS=1), you have likely
// broken backwards compatibility! Make sure this is intentional.
if !Path::new(&snapshot_path).exists() {
if env::var("DANGEROUS_UPDATE_SNAPSHOTS").is_ok() {
fs::create_dir_all(snapshot_parent_path)?;
fs::write(&snapshot_path, &expected_json)?;
} else {
bail!("snapshot file at '{snapshot_path}' doesn't exist and DANGEROUS_UPDATE_SNAPSHOTS is not set");
}
}

// Read and attempt to deserialize the snapshot
let snapshot_content = fs::read_to_string(&snapshot_path)?;
let deserialized = match serde_json::from_str::<$type>(&snapshot_content) {
Ok(value) => value,
Err(e) => {
bail!("Failed to deserialize snapshot:\nError: {}\n\nExpected JSON:\n{}\n\nActual JSON from snapshot:\n{}",
e, expected_json, snapshot_content);
}
};

// Compare the values
assert_eq!(
expected, deserialized,
"\nDeserialized value doesn't match expected:\n\nExpected JSON:\n{}\n\nDeserialized JSON:\n{}",
expected_json,
serde_json::to_string_pretty(&deserialized)?
);

Ok(())
}
}
};
}

/// Generates unit test that checks that bincode's serialization and deserialization are compatible
#[macro_export]
macro_rules! gen_test_bincode {
Expand Down
Loading
Loading