diff --git a/src/payload/ext/span.rs b/src/payload/ext/span.rs index a6bdedb..cf53bfc 100644 --- a/src/payload/ext/span.rs +++ b/src/payload/ext/span.rs @@ -1,7 +1,5 @@ use crate::{ - alloy::primitives::TxHash, - prelude::*, - reth::primitives::Recovered, + alloy::primitives::TxHash, prelude::*, reth::primitives::Recovered, }; /// Quality of Life extensions for the `Span` type. @@ -106,3 +104,121 @@ impl SpanExt

for Span

{ } } } + +#[cfg(test)] +mod span_ext_tests { + use crate::{ + payload::{Span, SpanExt}, + prelude::{BlockContext, Checkpoint, Ethereum}, + test_utils::BlockContextMocked, + }; + + use { + alloy::{ + consensus::{EthereumTxEnvelope, TxEip4844}, + network::{TransactionBuilder, TxSignerSync}, + primitives::{Address, U256}, + signers::local::PrivateKeySigner, + }, + rblib::{alloy, reth, test_utils::FundedAccounts}, + reth::{ + ethereum::{TransactionSigned, primitives::SignedTransaction}, + primitives::Recovered, + rpc::types::TransactionRequest, + }, + }; + + fn transfer_tx( + signer: &PrivateKeySigner, + nonce: u64, + value: U256, + ) -> Recovered> { + let mut tx = TransactionRequest::default() + .with_nonce(nonce) + .with_to(Address::random()) + .value(value) + .with_gas_price(1_000_000_000) + .with_gas_limit(21_000) + .with_max_priority_fee_per_gas(1_000_000) + .with_max_fee_per_gas(2_000_000) + .build_unsigned() + .expect("valid transaction request"); + + let sig = signer + .sign_transaction_sync(&mut tx) + .expect("signing should succeed"); + + TransactionSigned::new_unhashed(tx.into(), sig) + .with_signer(signer.address()) + } + + #[test] + fn gas_and_blob_gas_used_sum_correctly() { + let (block, _) = BlockContext::::mocked(); + + let root = block.start(); + let c1: Checkpoint = root.barrier(); + let c2: Checkpoint = c1.barrier(); + + let span = Span::::between(&root, &c2).unwrap(); + + assert_eq!(span.gas_used(), 0); + assert_eq!(span.blob_gas_used(), 0); + } + + #[test] + fn contains_transaction_hash_works() { + let (block, _) = BlockContext::::mocked(); + + let root = block.start(); + let c1: Checkpoint = root.barrier(); + + let tx1 = transfer_tx(&FundedAccounts::signer(0), 0, U256::from(50_000u64)); + + let c2 = c1.apply(tx1.clone()).unwrap(); + + let span = Span::::between(&root, &c2).unwrap(); + + assert!(span.contains(*tx1.hash())); + } + + #[test] + fn split_at_produces_valid_halves() { + let (block, _) = BlockContext::::mocked(); + + let root = block.start(); + let c1: Checkpoint = root.barrier(); + let c2: Checkpoint = c1.barrier(); + + let span = Span::::between(&root, &c2).unwrap(); + + let (left, right) = span.split_at(1); + assert_eq!(left.len(), 1); + assert_eq!(right.len(), 2); + + let (left_all, right_empty) = span.split_at(10); + assert_eq!(left_all.len(), span.len()); + assert!(right_empty.is_empty()); + } + + #[test] + fn take_and_skip_are_consistent_with_split_at() { + let (block, _) = BlockContext::::mocked(); + + let root = block.start(); + let c1: Checkpoint = root.barrier(); + let c2: Checkpoint = c1.barrier(); + + let span = Span::::between(&root, &c2).unwrap(); + + let take_1 = span.take(1); + let skip_1 = span.skip(1); + + assert_eq!(take_1.len(), 1); + assert_eq!(skip_1.len(), 2); + + let (left, right) = span.split_at(1); + assert_eq!(take_1.len(), left.len()); + assert_eq!(skip_1.len(), right.len()); + } +} diff --git a/src/payload/span.rs b/src/payload/span.rs index 9393e50..442bd7c 100644 --- a/src/payload/span.rs +++ b/src/payload/span.rs @@ -181,3 +181,72 @@ impl Display for Span

{ ) } } + +#[cfg(test)] +mod tests { + use crate::{ + payload::Span, + prelude::{BlockContext, Checkpoint, Ethereum}, + test_utils::BlockContextMocked, + }; + + #[test] + fn span_between_linear_history() { + let (block, _) = BlockContext::::mocked(); + + let checkpoint = block.start(); + let c1: Checkpoint = checkpoint.barrier(); + let c2: Checkpoint = c1.barrier(); + + // Direct ancestor-descendant pair + let span = Span::::between(&checkpoint, &c2).unwrap(); + assert_eq!(span.len(), 3); + assert_eq!(span.first().unwrap(), &checkpoint); + assert_eq!(span.len(), 2); + assert_eq!(span.last().unwrap(), &c2); + } + + #[test] + fn test_empty_span() { + let (block, _) = BlockContext::::mocked(); + + let checkpoint = block.start(); + + let span = Span::::between(&checkpoint, &checkpoint).unwrap(); + + assert_eq!(span.len(), 1); + } + + #[test] + fn test_between_nonlinear_history() { + let (block, _) = BlockContext::::mocked(); + + let checkpoint1 = block.start(); + let checkpoint2 = block.start(); + + let result = Span::between(&checkpoint1, &checkpoint2); + assert!(result.is_err()); + } + + #[test] + fn into_iter_and_iterators_work() { + let (block, _) = BlockContext::::mocked(); + + let root = block.start(); + let c1: Checkpoint = root.barrier(); + let c2: Checkpoint = c1.barrier(); + + let span = Span::::between(&root, &c2).unwrap(); + + // iter() + let ids_from_iter: Vec<_> = span.iter().map(|c| c.to_owned()).collect(); + assert_eq!(ids_from_iter.len(), 3); + assert_eq!(ids_from_iter[0], root); + assert_eq!(ids_from_iter[2], c2); + + // into_iter() + let ids_from_into_iter: Vec<_> = + span.clone().into_iter().map(|c| c).collect(); + assert_eq!(ids_from_iter, ids_from_into_iter); + } +}