From 24a81e7a03554cf734ceedaea728c1c3f1f7c0f6 Mon Sep 17 00:00:00 2001 From: dylan Date: Tue, 10 Feb 2026 12:23:34 -0700 Subject: [PATCH 1/3] tests: adds builder simulation test harness - adds a builder test harness for simulating blocks that doesn't require network access and doesn't use alloy for test setup - adds test scenario helpers for future setup and use --- .claude/CLAUDE.md | 25 ++- src/test_utils/block.rs | 172 ++++++++++++++++++++ src/test_utils/db.rs | 143 +++++++++++++++++ src/test_utils/env.rs | 170 ++++++++++++++++++++ src/{test_utils.rs => test_utils/mod.rs} | 24 ++- src/test_utils/scenarios.rs | 192 ++++++++++++++++++++++ src/test_utils/tx.rs | 193 +++++++++++++++++++++++ 7 files changed, 916 insertions(+), 3 deletions(-) create mode 100644 src/test_utils/block.rs create mode 100644 src/test_utils/db.rs create mode 100644 src/test_utils/env.rs rename src/{test_utils.rs => test_utils/mod.rs} (84%) create mode 100644 src/test_utils/scenarios.rs create mode 100644 src/test_utils/tx.rs diff --git a/.claude/CLAUDE.md b/.claude/CLAUDE.md index d2aacee..b11f832 100644 --- a/.claude/CLAUDE.md +++ b/.claude/CLAUDE.md @@ -17,6 +17,14 @@ make clippy # Lint with warnings denied Always lint before committing. The Makefile provides shortcuts (`make fmt`, `make clippy`, `make test`) +### Running Individual Tests + +```bash +cargo test test_name # Run specific test by name +cargo test --test test_file_name # Run all tests in a specific test file +cargo test -- --ignored # Run ignored integration tests (require network) +``` + ## Architecture Five actor tasks communicate via tokio channels: @@ -123,7 +131,22 @@ src/ ### GitHub - Fresh branches off `main` for PRs. Descriptive branch names. -- AI-authored GitHub comments must include `**[Claude Code]**` header. +- AI-authored GitHub comments must include `**[Claude Code]**` header. Minimum: 1.85, Edition: 2024 + +## Testing + +### Integration Tests + +Most tests in `tests/` are marked `#[ignore]` and require network access (real RPC endpoints or Anvil). + +### Simulation Harness (Offline Tests) + +`src/test_utils/` provides a testing harness for offline simulation testing: + +- `TestDbBuilder` - Create in-memory EVM state +- `TestSimEnvBuilder` - Create `RollupEnv`/`HostEnv` without RPC +- `TestBlockBuildBuilder` - Build blocks with `BlockBuild` +- `basic_scenario()`, `gas_limit_scenario()` - Pre-configured test scenarios ## Local Development diff --git a/src/test_utils/block.rs b/src/test_utils/block.rs new file mode 100644 index 0000000..bc2943a --- /dev/null +++ b/src/test_utils/block.rs @@ -0,0 +1,172 @@ +//! Test utilities for block building and simulation. +//! This module provides builders for creating `BlockBuild` instances +//! for testing block simulation. + +use super::{ + db::TestDb, + env::{TestHostEnv, TestRollupEnv, TestSimEnvBuilder}, +}; +use signet_sim::{BlockBuild, BuiltBlock, SimCache}; +use std::time::{Duration, Instant}; +use trevm::revm::inspector::NoOpInspector; + +/// Test block builder type using in-memory databases. +pub type TestBlockBuild = BlockBuild; + +/// Builder for creating test `BlockBuild` instances. +/// Configures all the parameters needed for block simulation +/// and provides sensible defaults for testing scenarios. +#[derive(Debug)] +pub struct TestBlockBuildBuilder { + rollup_env: Option, + host_env: Option, + sim_env_builder: Option, + sim_cache: SimCache, + deadline_duration: Duration, + concurrency_limit: usize, + max_gas: u64, + max_host_gas: u64, +} + +impl Default for TestBlockBuildBuilder { + fn default() -> Self { + Self::new() + } +} + +impl TestBlockBuildBuilder { + /// Create a new test block build builder with sensible defaults. + /// Default values: + /// - Deadline: 2 seconds + /// - Concurrency limit: 4 + /// - Max gas: 3,000,000,000 (3 billion) + /// - Max host gas: 24,000,000 + pub fn new() -> Self { + Self { + rollup_env: None, + host_env: None, + sim_env_builder: Some(TestSimEnvBuilder::new()), + sim_cache: SimCache::new(), + deadline_duration: Duration::from_secs(2), + concurrency_limit: 4, + max_gas: 3_000_000_000, + max_host_gas: 24_000_000, + } + } + + /// Set the simulation environment builder. + /// The environments will be built from this builder when `build()` is called. + pub fn with_sim_env_builder(mut self, builder: TestSimEnvBuilder) -> Self { + self.sim_env_builder = Some(builder); + self.rollup_env = None; + self.host_env = None; + self + } + + /// Set the rollup environment directly. + pub fn with_rollup_env(mut self, env: TestRollupEnv) -> Self { + self.rollup_env = Some(env); + self + } + + /// Set the host environment directly. + pub fn with_host_env(mut self, env: TestHostEnv) -> Self { + self.host_env = Some(env); + self + } + + /// Set the simulation cache. + pub fn with_cache(mut self, cache: SimCache) -> Self { + self.sim_cache = cache; + self + } + + /// Set the deadline duration from now. + pub fn with_deadline(mut self, duration: Duration) -> Self { + self.deadline_duration = duration; + self + } + + /// Set the concurrency limit for parallel simulation. + pub fn with_concurrency(mut self, limit: usize) -> Self { + self.concurrency_limit = limit; + self + } + + /// Set the maximum gas limit for the rollup block. + pub fn with_max_gas(mut self, gas: u64) -> Self { + self.max_gas = gas; + self + } + + /// Set the maximum gas limit for host transactions. + pub fn with_max_host_gas(mut self, gas: u64) -> Self { + self.max_host_gas = gas; + self + } + + /// Build the test `BlockBuild` instance. + /// This creates a `BlockBuild` ready for simulation. + /// Call `.build().await` on the result to execute the simulation and get a `BuiltBlock`. + pub fn build(self) -> TestBlockBuild { + let (rollup_env, host_env) = match (self.rollup_env, self.host_env) { + (Some(rollup), Some(host)) => (rollup, host), + _ => { + let builder = self.sim_env_builder.unwrap_or_default(); + builder.build() + } + }; + + let finish_by = Instant::now() + self.deadline_duration; + + BlockBuild::new( + rollup_env, + host_env, + finish_by, + self.concurrency_limit, + self.sim_cache, + self.max_gas, + self.max_host_gas, + ) + } +} + +/// Convenience function to quickly build a block with a cache and optional configuration. +/// This is useful for simple test cases where you just want to simulate +/// some transactions quickly. +pub async fn quick_build_block(cache: SimCache, deadline: Duration) -> BuiltBlock { + TestBlockBuildBuilder::new() + .with_cache(cache) + .with_deadline(deadline) + .build() + .build() + .await +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_block_build_builder_defaults() { + let builder = TestBlockBuildBuilder::new(); + assert_eq!(builder.deadline_duration, Duration::from_secs(2)); + assert_eq!(builder.concurrency_limit, 4); + assert_eq!(builder.max_gas, 3_000_000_000); + assert_eq!(builder.max_host_gas, 24_000_000); + } + + #[test] + fn test_block_build_builder_custom_values() { + let builder = TestBlockBuildBuilder::new() + .with_deadline(Duration::from_secs(5)) + .with_concurrency(8) + .with_max_gas(1_000_000_000) + .with_max_host_gas(10_000_000); + + assert_eq!(builder.deadline_duration, Duration::from_secs(5)); + assert_eq!(builder.concurrency_limit, 8); + assert_eq!(builder.max_gas, 1_000_000_000); + assert_eq!(builder.max_host_gas, 10_000_000); + } +} diff --git a/src/test_utils/db.rs b/src/test_utils/db.rs new file mode 100644 index 0000000..03797a2 --- /dev/null +++ b/src/test_utils/db.rs @@ -0,0 +1,143 @@ +//! Test database utilities for in-memory EVM state. +//! This module provides an in-memory database implementation that can be used +//! for testing block simulation without requiring network access. + +use alloy::primitives::{Address, B256, U256}; +use trevm::revm::{ + database::{CacheDB, EmptyDB}, + state::AccountInfo, +}; + +/// In-memory database for testing (no network access required). +/// This is a type alias for revm's `CacheDB`, which stores all +/// blockchain state in memory. It implements `DatabaseRef` and can be used +/// with `RollupEnv` and `HostEnv` for offline simulation testing. +pub type TestDb = CacheDB; + +/// Builder for creating pre-populated test databases. +/// Use this builder to set up blockchain state (accounts, contracts, storage) +/// before running simulations. +#[derive(Debug)] +pub struct TestDbBuilder { + db: TestDb, +} + +impl Default for TestDbBuilder { + fn default() -> Self { + Self::new() + } +} + +impl TestDbBuilder { + /// Create a new empty test database builder. + pub fn new() -> Self { + Self { + db: CacheDB::new(EmptyDB::default()), + } + } + + /// Add an account with the specified balance and nonce. + /// + /// # Arguments + /// + /// * `address` - The account address + /// * `balance` - The account balance in wei + /// * `nonce` - The account nonce (transaction count) + pub fn with_account(mut self, address: Address, balance: U256, nonce: u64) -> Self { + self.db.insert_account_info( + address, + AccountInfo { + balance, + nonce, + ..Default::default() + }, + ); + self + } + + /// Set a storage slot for an account. + /// + /// # Arguments + /// + /// * `address` - The account address + /// * `slot` - The storage slot index + /// * `value` - The value to store + pub fn with_storage(mut self, address: Address, slot: U256, value: U256) -> Self { + // Ensure the account exists before setting storage + if self.db.cache.accounts.get(&address).is_none() { + self.db.insert_account_info(address, AccountInfo::default()); + } + let _ = self.db.insert_account_storage(address, slot, value); + self + } + + /// Insert a block hash for a specific block number. + /// + /// This is useful for testing contracts that use the BLOCKHASH opcode. + /// + /// # Arguments + /// + /// * `number` - The block number + /// * `hash` - The block hash + pub fn with_block_hash(mut self, number: u64, hash: B256) -> Self { + self.db.cache.block_hashes.insert(U256::from(number), hash); + self + } + + /// Build the test database. + pub fn build(self) -> TestDb { + self.db + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_db_builder_creates_empty_db() { + let db = TestDbBuilder::new().build(); + assert!(db.cache.accounts.is_empty()); + } + + #[test] + fn test_db_builder_adds_account() { + let address = Address::repeat_byte(0x01); + let balance = U256::from(1000u64); + let nonce = 5u64; + + let db = TestDbBuilder::new() + .with_account(address, balance, nonce) + .build(); + + let account = db.cache.accounts.get(&address).unwrap(); + assert_eq!(account.info.balance, balance); + assert_eq!(account.info.nonce, nonce); + } + + #[test] + fn test_db_builder_adds_storage() { + let address = Address::repeat_byte(0x01); + let slot = U256::from(42u64); + let value = U256::from(123u64); + + let db = TestDbBuilder::new() + .with_storage(address, slot, value) + .build(); + + let account = db.cache.accounts.get(&address).unwrap(); + let stored = account.storage.get(&slot).unwrap(); + assert_eq!(*stored, value); + } + + #[test] + fn test_db_builder_adds_block_hash() { + let number = 100u64; + let hash = B256::repeat_byte(0xab); + + let db = TestDbBuilder::new().with_block_hash(number, hash).build(); + + let stored = db.cache.block_hashes.get(&U256::from(number)).unwrap(); + assert_eq!(*stored, hash); + } +} diff --git a/src/test_utils/env.rs b/src/test_utils/env.rs new file mode 100644 index 0000000..4799688 --- /dev/null +++ b/src/test_utils/env.rs @@ -0,0 +1,170 @@ +//! Test simulation environment utilities. +//! This module provides builders for creating `RollupEnv` and `HostEnv` +//! instances with in-memory databases for offline testing. + +use super::db::{TestDb, TestDbBuilder}; +use crate::tasks::block::cfg::SignetCfgEnv; +use alloy::primitives::{Address, B256, U256}; +use signet_constants::SignetSystemConstants; +use signet_sim::{HostEnv, RollupEnv}; +use trevm::revm::{context::BlockEnv, context_interface::block::BlobExcessGasAndPrice, inspector::NoOpInspector}; + +/// Test rollup environment using in-memory database. +pub type TestRollupEnv = RollupEnv; + +/// Test host environment using in-memory database. +pub type TestHostEnv = HostEnv; + +/// Builder for creating test simulation environments. +/// This allows you to configure both rollup and host environments +/// with pre-populated in-memory databases for testing block simulation +/// without network access. +#[derive(Debug)] +pub struct TestSimEnvBuilder { + rollup_db: TestDb, + host_db: TestDb, + rollup_block_env: BlockEnv, + host_block_env: BlockEnv, + constants: SignetSystemConstants, +} + +impl Default for TestSimEnvBuilder { + fn default() -> Self { + Self::new() + } +} + +impl TestSimEnvBuilder { + /// Create a new builder with default Parmigiana constants and empty databases. + pub fn new() -> Self { + let default_block_env = Self::default_block_env(); + Self { + rollup_db: TestDbBuilder::new().build(), + host_db: TestDbBuilder::new().build(), + rollup_block_env: default_block_env.clone(), + host_block_env: default_block_env, + constants: SignetSystemConstants::parmigiana(), + } + } + + /// Create a default block environment suitable for testing. + fn default_block_env() -> BlockEnv { + BlockEnv { + number: U256::from(100u64), + beneficiary: Address::repeat_byte(0x01), + timestamp: U256::from(1700000000u64), + gas_limit: 3_000_000_000, + basefee: 1_000_000_000, // 1 gwei + difficulty: U256::ZERO, + prevrandao: Some(B256::random()), + blob_excess_gas_and_price: Some(BlobExcessGasAndPrice { + excess_blob_gas: 0, + blob_gasprice: 0, + }), + } + } + + /// Set the rollup database. + pub fn with_rollup_db(mut self, db: TestDb) -> Self { + self.rollup_db = db; + self + } + + /// Set the host database. + pub fn with_host_db(mut self, db: TestDb) -> Self { + self.host_db = db; + self + } + + /// Set the rollup block environment. + pub fn with_rollup_block_env(mut self, env: BlockEnv) -> Self { + self.rollup_block_env = env; + self + } + + /// Set the host block environment. + pub fn with_host_block_env(mut self, env: BlockEnv) -> Self { + self.host_block_env = env; + self + } + + /// Set both rollup and host block environments to the same value. + pub fn with_block_env(mut self, env: BlockEnv) -> Self { + self.rollup_block_env = env.clone(); + self.host_block_env = env; + self + } + + /// Set the system constants. + pub fn with_constants(mut self, constants: SignetSystemConstants) -> Self { + self.constants = constants; + self + } + + /// Build the test RollupEnv. + pub fn build_rollup_env(&self) -> TestRollupEnv { + let timestamp = self.rollup_block_env.timestamp.to::(); + let cfg = SignetCfgEnv::new(self.constants.ru_chain_id(), timestamp); + RollupEnv::new( + self.rollup_db.clone(), + self.constants.clone(), + &cfg, + &self.rollup_block_env, + ) + } + + /// Build the test HostEnv. + pub fn build_host_env(&self) -> TestHostEnv { + let timestamp = self.host_block_env.timestamp.to::(); + let cfg = SignetCfgEnv::new(self.constants.host_chain_id(), timestamp); + HostEnv::new( + self.host_db.clone(), + self.constants.clone(), + &cfg, + &self.host_block_env, + ) + } + + /// Build both environments as a tuple. + pub fn build(self) -> (TestRollupEnv, TestHostEnv) { + let rollup = self.build_rollup_env(); + let host = self.build_host_env(); + (rollup, host) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_sim_env_builder_creates_environments() { + let builder = TestSimEnvBuilder::new(); + let (rollup_env, host_env) = builder.build(); + + // Verify environments were created (we can't easily inspect internals, + // but we can verify they don't panic during creation) + let _ = rollup_env; + let _ = host_env; + } + + #[test] + fn test_sim_env_builder_with_custom_block_env() { + let custom_env = BlockEnv { + number: U256::from(500u64), + beneficiary: Address::repeat_byte(0x42), + timestamp: U256::from(1800000000u64), + gas_limit: 5_000_000_000, + basefee: 2_000_000_000, + difficulty: U256::ZERO, + prevrandao: Some(B256::repeat_byte(0x11)), + blob_excess_gas_and_price: None, + }; + + let builder = TestSimEnvBuilder::new().with_block_env(custom_env); + let (rollup_env, host_env) = builder.build(); + + let _ = rollup_env; + let _ = host_env; + } +} diff --git a/src/test_utils.rs b/src/test_utils/mod.rs similarity index 84% rename from src/test_utils.rs rename to src/test_utils/mod.rs index 51ce082..ca84b61 100644 --- a/src/test_utils.rs +++ b/src/test_utils/mod.rs @@ -1,4 +1,25 @@ //! Test utilities for testing builder tasks +//! +//! This module provides utilities for testing the block builder without +//! requiring network access or full chain state setup. + +mod block; +mod db; +mod env; +mod scenarios; +mod tx; + +// Re-export test harness components +pub use block::{quick_build_block, TestBlockBuild, TestBlockBuildBuilder}; +pub use db::{TestDb, TestDbBuilder}; +pub use env::{TestHostEnv, TestRollupEnv, TestSimEnvBuilder}; +pub use scenarios::{ + basic_scenario, custom_funded_scenario, funded_test_db, gas_limit_scenario, + priority_ordering_scenario, test_block_env as scenarios_test_block_env, DEFAULT_BALANCE, + DEFAULT_BASEFEE, +}; +pub use tx::{create_call_tx, create_transfer_tx, TestAccounts}; + use crate::config::BuilderConfig; use alloy::{ consensus::{SignableTransaction, TxEip1559, TxEnvelope}, @@ -15,7 +36,6 @@ use init4_bin_base::{ utils::{calc::SlotCalculator, provider::ProviderConfig}, }; use signet_constants::SignetSystemConstants; -use std::env; use std::str::FromStr; use trevm::revm::{context::BlockEnv, context_interface::block::BlobExcessGasAndPrice}; @@ -35,7 +55,7 @@ pub fn setup_test_config() -> &'static BuilderConfig { flashbots_endpoint: "https://relay-sepolia.flashbots.net:443".parse().unwrap(), quincey_url: "http://localhost:8080".into(), sequencer_key: None, - builder_key: env::var("SEPOLIA_ETH_PRIV_KEY") + builder_key: std::env::var("SEPOLIA_ETH_PRIV_KEY") .unwrap_or_else(|_| B256::repeat_byte(0x42).to_string()), builder_port: 8080, builder_rewards_address: Address::default(), diff --git a/src/test_utils/scenarios.rs b/src/test_utils/scenarios.rs new file mode 100644 index 0000000..0d612db --- /dev/null +++ b/src/test_utils/scenarios.rs @@ -0,0 +1,192 @@ +//! Pre-configured test scenarios. +//! This module provides ready-to-use test scenarios that configure databases, +//! environments, and builders for common testing needs. + +use super::{ + block::TestBlockBuildBuilder, + db::TestDbBuilder, + env::TestSimEnvBuilder, + tx::TestAccounts, +}; +use alloy::primitives::{Address, B256, U256}; +use signet_sim::SimCache; +use trevm::revm::{context::BlockEnv, context_interface::block::BlobExcessGasAndPrice}; + +/// Default test balance: 100 ETH in wei. +pub const DEFAULT_BALANCE: u128 = 100_000_000_000_000_000_000; + +/// Default basefee: 1 gwei. +pub const DEFAULT_BASEFEE: u64 = 1_000_000_000; + +/// Create a test database with pre-funded accounts. +/// +/// # Arguments +/// +/// * `accounts` - The test accounts to fund +/// * `balance` - The balance to give each account +pub fn funded_test_db(accounts: &TestAccounts, balance: U256) -> TestDbBuilder { + TestDbBuilder::new() + .with_account(accounts.alice_address(), balance, 0) + .with_account(accounts.bob_address(), balance, 0) + .with_account(accounts.charlie_address(), balance, 0) +} + +/// Create a standard test block environment. +/// +/// # Arguments +/// +/// * `block_number` - The block number +/// * `basefee` - The base fee per gas +/// * `timestamp` - The block timestamp +/// * `gas_limit` - The block gas limit +pub fn test_block_env(block_number: u64, basefee: u64, timestamp: u64, gas_limit: u64) -> BlockEnv { + BlockEnv { + number: U256::from(block_number), + beneficiary: Address::repeat_byte(0x01), + timestamp: U256::from(timestamp), + gas_limit, + basefee, + difficulty: U256::ZERO, + prevrandao: Some(B256::random()), + blob_excess_gas_and_price: Some(BlobExcessGasAndPrice { + excess_blob_gas: 0, + blob_gasprice: 0, + }), + } +} + +/// Basic multi-transaction block building scenario. +/// +/// Creates a scenario with: +/// - Three pre-funded test accounts (100 ETH each) +/// - Default block environment +/// - Empty SimCache ready for transactions +/// +/// # Returns +/// +/// A tuple of (builder, accounts, cache) ready for adding transactions and building. +pub fn basic_scenario() -> (TestBlockBuildBuilder, TestAccounts, SimCache) { + let accounts = TestAccounts::new(); + let balance = U256::from(DEFAULT_BALANCE); + + let rollup_db = funded_test_db(&accounts, balance).build(); + let host_db = funded_test_db(&accounts, balance).build(); + + let block_env = test_block_env(100, DEFAULT_BASEFEE, 1700000000, 3_000_000_000); + + let sim_env = TestSimEnvBuilder::new() + .with_rollup_db(rollup_db) + .with_host_db(host_db) + .with_block_env(block_env); + + let cache = SimCache::new(); + + let builder = TestBlockBuildBuilder::new().with_sim_env_builder(sim_env); + + (builder, accounts, cache) +} + +/// Scenario for testing transaction priority ordering. +/// +/// Similar to `basic_scenario` but with a longer deadline to ensure +/// all transactions can be processed and ordered. +/// +/// # Returns +/// +/// A tuple of (builder, accounts, cache) configured for priority testing. +pub fn priority_ordering_scenario() -> (TestBlockBuildBuilder, TestAccounts, SimCache) { + let (builder, accounts, cache) = basic_scenario(); + + // Use a longer deadline for ordering tests + let builder = builder.with_deadline(std::time::Duration::from_secs(5)); + + (builder, accounts, cache) +} + +/// Scenario for testing gas limit enforcement. +/// +/// Creates a scenario with a configurable gas limit to test that +/// block building respects the maximum gas constraint. +/// +/// # Arguments +/// +/// * `max_gas` - The maximum gas limit for the block +/// +/// # Returns +/// +/// A tuple of (builder, accounts, cache) with the specified gas limit. +pub fn gas_limit_scenario(max_gas: u64) -> (TestBlockBuildBuilder, TestAccounts, SimCache) { + let (builder, accounts, cache) = basic_scenario(); + + let builder = builder.with_max_gas(max_gas); + + (builder, accounts, cache) +} + +/// Scenario with custom funded addresses. +/// +/// Use this when you need specific addresses to be funded +/// (e.g., for testing contract interactions). +/// +/// # Arguments +/// +/// * `addresses` - List of (address, balance, nonce) tuples to fund +/// +/// # Returns +/// +/// A TestBlockBuildBuilder configured with the funded accounts. +pub fn custom_funded_scenario( + addresses: &[(Address, U256, u64)], +) -> (TestBlockBuildBuilder, SimCache) { + let mut db_builder = TestDbBuilder::new(); + + for (addr, balance, nonce) in addresses { + db_builder = db_builder.with_account(*addr, *balance, *nonce); + } + + let db = db_builder.build(); + + let sim_env = TestSimEnvBuilder::new() + .with_rollup_db(db.clone()) + .with_host_db(db); + + let cache = SimCache::new(); + let builder = TestBlockBuildBuilder::new().with_sim_env_builder(sim_env); + + (builder, cache) +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_basic_scenario_creates_funded_accounts() { + let (_, accounts, _) = basic_scenario(); + + // Verify accounts are created with unique addresses + let addresses = accounts.addresses(); + assert_eq!(addresses.len(), 3); + assert_ne!(addresses[0], addresses[1]); + } + + #[test] + fn test_gas_limit_scenario_creates_builder() { + // Verify gas limit scenario creates a valid builder + let (_, _, _) = gas_limit_scenario(100_000); + // Builder creation succeeds (gas limit is applied internally) + } + + #[test] + fn test_custom_funded_scenario() { + let addr1 = Address::repeat_byte(0x11); + let addr2 = Address::repeat_byte(0x22); + + let (_, _) = custom_funded_scenario(&[ + (addr1, U256::from(1000u64), 5), + (addr2, U256::from(2000u64), 10), + ]); + + // Scenario creation succeeds + } +} diff --git a/src/test_utils/tx.rs b/src/test_utils/tx.rs new file mode 100644 index 0000000..c80f708 --- /dev/null +++ b/src/test_utils/tx.rs @@ -0,0 +1,193 @@ +//! Test transaction utilities. +//! This module provides helpers for creating test transactions and accounts +//! for use in simulation tests. + +use alloy::{ + consensus::{SignableTransaction, TxEip1559, TxEnvelope, transaction::Recovered, transaction::SignerRecoverable}, + primitives::{Address, TxKind, U256}, + signers::{SignerSync, local::PrivateKeySigner}, +}; +use eyre::Result; + +/// Pre-funded test accounts for simulation testing. +/// These accounts can be used to create and sign test transactions. +/// Use `TestDbBuilder` to fund these accounts in the test database. +#[derive(Debug, Clone)] +pub struct TestAccounts { + /// First test account (Alice) + pub alice: PrivateKeySigner, + /// Second test account (Bob) + pub bob: PrivateKeySigner, + /// Third test account (Charlie) + pub charlie: PrivateKeySigner, +} + +impl Default for TestAccounts { + fn default() -> Self { + Self::new() + } +} + +impl TestAccounts { + /// Create new random test accounts. + pub fn new() -> Self { + Self { + alice: PrivateKeySigner::random(), + bob: PrivateKeySigner::random(), + charlie: PrivateKeySigner::random(), + } + } + + /// Get all account addresses. + pub fn addresses(&self) -> Vec
{ + vec![ + self.alice.address(), + self.bob.address(), + self.charlie.address(), + ] + } + + /// Get Alice's address. + pub fn alice_address(&self) -> Address { + self.alice.address() + } + + /// Get Bob's address. + pub fn bob_address(&self) -> Address { + self.bob.address() + } + + /// Get Charlie's address. + pub fn charlie_address(&self) -> Address { + self.charlie.address() + } +} + +/// Create a signed EIP-1559 transfer transaction. +/// +/// # Arguments +/// +/// * `signer` - The account signing the transaction +/// * `to` - The recipient address +/// * `value` - The amount of wei to transfer +/// * `nonce` - The sender's nonce (transaction count) +/// * `chain_id` - The chain ID (e.g., signet_constants::parmigiana::RU_CHAIN_ID) +/// * `max_priority_fee_per_gas` - The priority fee (tip) per gas unit +/// +/// # Returns +/// +/// A recovered transaction envelope ready to be added to a SimCache. +pub fn create_transfer_tx( + signer: &PrivateKeySigner, + to: Address, + value: U256, + nonce: u64, + chain_id: u64, + max_priority_fee_per_gas: u128, +) -> Result> { + let tx = TxEip1559 { + chain_id, + nonce, + max_fee_per_gas: 100_000_000_000, // 100 gwei max fee + max_priority_fee_per_gas, + gas_limit: 21_000, // Standard transfer gas + to: TxKind::Call(to), + value, + ..Default::default() + }; + + let signature = signer.sign_hash_sync(&tx.signature_hash())?; + let signed = TxEnvelope::Eip1559(tx.into_signed(signature)); + let recovered = signed.try_into_recovered()?; + Ok(recovered) +} + +/// Create a signed EIP-1559 contract call transaction. +/// +/// # Arguments +/// +/// * `signer` - The account signing the transaction +/// * `to` - The contract address +/// * `input` - The calldata for the contract call +/// * `value` - The amount of wei to send with the call +/// * `nonce` - The sender's nonce +/// * `chain_id` - The chain ID +/// * `gas_limit` - The gas limit for the transaction +/// * `max_priority_fee_per_gas` - The priority fee per gas unit +pub fn create_call_tx( + signer: &PrivateKeySigner, + to: Address, + input: alloy::primitives::Bytes, + value: U256, + nonce: u64, + chain_id: u64, + gas_limit: u64, + max_priority_fee_per_gas: u128, +) -> Result> { + let tx = TxEip1559 { + chain_id, + nonce, + max_fee_per_gas: 100_000_000_000, + max_priority_fee_per_gas, + gas_limit, + to: TxKind::Call(to), + value, + input, + ..Default::default() + }; + + let signature = signer.sign_hash_sync(&tx.signature_hash())?; + let signed = TxEnvelope::Eip1559(tx.into_signed(signature)); + let recovered = signed.try_into_recovered()?; + Ok(recovered) +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_accounts_creates_unique_addresses() { + let accounts = TestAccounts::new(); + let addresses = accounts.addresses(); + + assert_eq!(addresses.len(), 3); + assert_ne!(addresses[0], addresses[1]); + assert_ne!(addresses[1], addresses[2]); + assert_ne!(addresses[0], addresses[2]); + } + + #[test] + fn test_create_transfer_tx_succeeds() { + let accounts = TestAccounts::new(); + let tx = create_transfer_tx( + &accounts.alice, + accounts.bob_address(), + U256::from(1_000_000_000_000_000_000u128), // 1 ETH + 0, + 1, // Mainnet chain ID for testing + 10_000, + ); + + assert!(tx.is_ok()); + let recovered = tx.unwrap(); + assert_eq!(recovered.signer(), accounts.alice_address()); + } + + #[test] + fn test_create_call_tx_succeeds() { + let accounts = TestAccounts::new(); + let tx = create_call_tx( + &accounts.alice, + accounts.bob_address(), + alloy::primitives::Bytes::from_static(&[0x12, 0x34]), + U256::ZERO, + 0, + 1, + 50_000, + 10_000, + ); + + assert!(tx.is_ok()); + } +} From df7a31dc58edace03274db7d055189ad6c9511be Mon Sep 17 00:00:00 2001 From: dylan Date: Tue, 10 Feb 2026 12:38:52 -0700 Subject: [PATCH 2/3] fmt --- src/test_utils/block.rs | 9 ++------- src/test_utils/db.rs | 21 ++++----------------- src/test_utils/env.rs | 18 +++++------------- src/test_utils/mod.rs | 9 ++++----- src/test_utils/scenarios.rs | 9 ++------- src/test_utils/tx.rs | 11 +++++------ 6 files changed, 22 insertions(+), 55 deletions(-) diff --git a/src/test_utils/block.rs b/src/test_utils/block.rs index bc2943a..e634890 100644 --- a/src/test_utils/block.rs +++ b/src/test_utils/block.rs @@ -106,7 +106,7 @@ impl TestBlockBuildBuilder { } /// Build the test `BlockBuild` instance. - /// This creates a `BlockBuild` ready for simulation. + /// This creates a `BlockBuild` ready for simulation. /// Call `.build().await` on the result to execute the simulation and get a `BuiltBlock`. pub fn build(self) -> TestBlockBuild { let (rollup_env, host_env) = match (self.rollup_env, self.host_env) { @@ -135,12 +135,7 @@ impl TestBlockBuildBuilder { /// This is useful for simple test cases where you just want to simulate /// some transactions quickly. pub async fn quick_build_block(cache: SimCache, deadline: Duration) -> BuiltBlock { - TestBlockBuildBuilder::new() - .with_cache(cache) - .with_deadline(deadline) - .build() - .build() - .await + TestBlockBuildBuilder::new().with_cache(cache).with_deadline(deadline).build().build().await } #[cfg(test)] diff --git a/src/test_utils/db.rs b/src/test_utils/db.rs index 03797a2..6a41d96 100644 --- a/src/test_utils/db.rs +++ b/src/test_utils/db.rs @@ -31,9 +31,7 @@ impl Default for TestDbBuilder { impl TestDbBuilder { /// Create a new empty test database builder. pub fn new() -> Self { - Self { - db: CacheDB::new(EmptyDB::default()), - } + Self { db: CacheDB::new(EmptyDB::default()) } } /// Add an account with the specified balance and nonce. @@ -44,14 +42,7 @@ impl TestDbBuilder { /// * `balance` - The account balance in wei /// * `nonce` - The account nonce (transaction count) pub fn with_account(mut self, address: Address, balance: U256, nonce: u64) -> Self { - self.db.insert_account_info( - address, - AccountInfo { - balance, - nonce, - ..Default::default() - }, - ); + self.db.insert_account_info(address, AccountInfo { balance, nonce, ..Default::default() }); self } @@ -106,9 +97,7 @@ mod tests { let balance = U256::from(1000u64); let nonce = 5u64; - let db = TestDbBuilder::new() - .with_account(address, balance, nonce) - .build(); + let db = TestDbBuilder::new().with_account(address, balance, nonce).build(); let account = db.cache.accounts.get(&address).unwrap(); assert_eq!(account.info.balance, balance); @@ -121,9 +110,7 @@ mod tests { let slot = U256::from(42u64); let value = U256::from(123u64); - let db = TestDbBuilder::new() - .with_storage(address, slot, value) - .build(); + let db = TestDbBuilder::new().with_storage(address, slot, value).build(); let account = db.cache.accounts.get(&address).unwrap(); let stored = account.storage.get(&slot).unwrap(); diff --git a/src/test_utils/env.rs b/src/test_utils/env.rs index 4799688..01dece7 100644 --- a/src/test_utils/env.rs +++ b/src/test_utils/env.rs @@ -7,7 +7,9 @@ use crate::tasks::block::cfg::SignetCfgEnv; use alloy::primitives::{Address, B256, U256}; use signet_constants::SignetSystemConstants; use signet_sim::{HostEnv, RollupEnv}; -use trevm::revm::{context::BlockEnv, context_interface::block::BlobExcessGasAndPrice, inspector::NoOpInspector}; +use trevm::revm::{ + context::BlockEnv, context_interface::block::BlobExcessGasAndPrice, inspector::NoOpInspector, +}; /// Test rollup environment using in-memory database. pub type TestRollupEnv = RollupEnv; @@ -105,24 +107,14 @@ impl TestSimEnvBuilder { pub fn build_rollup_env(&self) -> TestRollupEnv { let timestamp = self.rollup_block_env.timestamp.to::(); let cfg = SignetCfgEnv::new(self.constants.ru_chain_id(), timestamp); - RollupEnv::new( - self.rollup_db.clone(), - self.constants.clone(), - &cfg, - &self.rollup_block_env, - ) + RollupEnv::new(self.rollup_db.clone(), self.constants.clone(), &cfg, &self.rollup_block_env) } /// Build the test HostEnv. pub fn build_host_env(&self) -> TestHostEnv { let timestamp = self.host_block_env.timestamp.to::(); let cfg = SignetCfgEnv::new(self.constants.host_chain_id(), timestamp); - HostEnv::new( - self.host_db.clone(), - self.constants.clone(), - &cfg, - &self.host_block_env, - ) + HostEnv::new(self.host_db.clone(), self.constants.clone(), &cfg, &self.host_block_env) } /// Build both environments as a tuple. diff --git a/src/test_utils/mod.rs b/src/test_utils/mod.rs index ca84b61..e3ce239 100644 --- a/src/test_utils/mod.rs +++ b/src/test_utils/mod.rs @@ -10,15 +10,14 @@ mod scenarios; mod tx; // Re-export test harness components -pub use block::{quick_build_block, TestBlockBuild, TestBlockBuildBuilder}; +pub use block::{TestBlockBuild, TestBlockBuildBuilder, quick_build_block}; pub use db::{TestDb, TestDbBuilder}; pub use env::{TestHostEnv, TestRollupEnv, TestSimEnvBuilder}; pub use scenarios::{ - basic_scenario, custom_funded_scenario, funded_test_db, gas_limit_scenario, - priority_ordering_scenario, test_block_env as scenarios_test_block_env, DEFAULT_BALANCE, - DEFAULT_BASEFEE, + DEFAULT_BALANCE, DEFAULT_BASEFEE, basic_scenario, custom_funded_scenario, funded_test_db, + gas_limit_scenario, priority_ordering_scenario, test_block_env as scenarios_test_block_env, }; -pub use tx::{create_call_tx, create_transfer_tx, TestAccounts}; +pub use tx::{TestAccounts, create_call_tx, create_transfer_tx}; use crate::config::BuilderConfig; use alloy::{ diff --git a/src/test_utils/scenarios.rs b/src/test_utils/scenarios.rs index 0d612db..4eec47a 100644 --- a/src/test_utils/scenarios.rs +++ b/src/test_utils/scenarios.rs @@ -3,10 +3,7 @@ //! environments, and builders for common testing needs. use super::{ - block::TestBlockBuildBuilder, - db::TestDbBuilder, - env::TestSimEnvBuilder, - tx::TestAccounts, + block::TestBlockBuildBuilder, db::TestDbBuilder, env::TestSimEnvBuilder, tx::TestAccounts, }; use alloy::primitives::{Address, B256, U256}; use signet_sim::SimCache; @@ -146,9 +143,7 @@ pub fn custom_funded_scenario( let db = db_builder.build(); - let sim_env = TestSimEnvBuilder::new() - .with_rollup_db(db.clone()) - .with_host_db(db); + let sim_env = TestSimEnvBuilder::new().with_rollup_db(db.clone()).with_host_db(db); let cache = SimCache::new(); let builder = TestBlockBuildBuilder::new().with_sim_env_builder(sim_env); diff --git a/src/test_utils/tx.rs b/src/test_utils/tx.rs index c80f708..95810c1 100644 --- a/src/test_utils/tx.rs +++ b/src/test_utils/tx.rs @@ -3,7 +3,10 @@ //! for use in simulation tests. use alloy::{ - consensus::{SignableTransaction, TxEip1559, TxEnvelope, transaction::Recovered, transaction::SignerRecoverable}, + consensus::{ + SignableTransaction, TxEip1559, TxEnvelope, transaction::Recovered, + transaction::SignerRecoverable, + }, primitives::{Address, TxKind, U256}, signers::{SignerSync, local::PrivateKeySigner}, }; @@ -40,11 +43,7 @@ impl TestAccounts { /// Get all account addresses. pub fn addresses(&self) -> Vec
{ - vec![ - self.alice.address(), - self.bob.address(), - self.charlie.address(), - ] + vec![self.alice.address(), self.bob.address(), self.charlie.address()] } /// Get Alice's address. From 717194a89c20f21137224e56c4a6737f5f4a3347 Mon Sep 17 00:00:00 2001 From: dylan Date: Tue, 10 Feb 2026 12:44:43 -0700 Subject: [PATCH 3/3] chore: update claude workflows to run fmt and clippy --- .claude/CLAUDE.md | 4 ++++ .gitignore | 1 + src/test_utils/block.rs | 8 ++++---- src/test_utils/db.rs | 2 +- src/test_utils/env.rs | 6 +++--- src/test_utils/tx.rs | 7 ++++--- 6 files changed, 17 insertions(+), 11 deletions(-) diff --git a/.claude/CLAUDE.md b/.claude/CLAUDE.md index b11f832..24e44d3 100644 --- a/.claude/CLAUDE.md +++ b/.claude/CLAUDE.md @@ -148,6 +148,10 @@ Most tests in `tests/` are marked `#[ignore]` and require network access (real R - `TestBlockBuildBuilder` - Build blocks with `BlockBuild` - `basic_scenario()`, `gas_limit_scenario()` - Pre-configured test scenarios +## Workflow + +After completing a set of changes, always run `make fmt` and `make clippy` and fix any issues before committing. + ## Local Development For local SDK development, uncomment the `[patch.crates-io]` section in Cargo.toml to point to local signet-sdk paths. diff --git a/.gitignore b/.gitignore index 2aab883..c716535 100644 --- a/.gitignore +++ b/.gitignore @@ -23,6 +23,7 @@ target/ # VSCode debug launcher .vscode/launch.json +# Claude configs .claude/*.local.* .claude/settings.json CLAUDE.local.md diff --git a/src/test_utils/block.rs b/src/test_utils/block.rs index e634890..e330b97 100644 --- a/src/test_utils/block.rs +++ b/src/test_utils/block.rs @@ -82,25 +82,25 @@ impl TestBlockBuildBuilder { } /// Set the deadline duration from now. - pub fn with_deadline(mut self, duration: Duration) -> Self { + pub const fn with_deadline(mut self, duration: Duration) -> Self { self.deadline_duration = duration; self } /// Set the concurrency limit for parallel simulation. - pub fn with_concurrency(mut self, limit: usize) -> Self { + pub const fn with_concurrency(mut self, limit: usize) -> Self { self.concurrency_limit = limit; self } /// Set the maximum gas limit for the rollup block. - pub fn with_max_gas(mut self, gas: u64) -> Self { + pub const fn with_max_gas(mut self, gas: u64) -> Self { self.max_gas = gas; self } /// Set the maximum gas limit for host transactions. - pub fn with_max_host_gas(mut self, gas: u64) -> Self { + pub const fn with_max_host_gas(mut self, gas: u64) -> Self { self.max_host_gas = gas; self } diff --git a/src/test_utils/db.rs b/src/test_utils/db.rs index 6a41d96..93315c6 100644 --- a/src/test_utils/db.rs +++ b/src/test_utils/db.rs @@ -55,7 +55,7 @@ impl TestDbBuilder { /// * `value` - The value to store pub fn with_storage(mut self, address: Address, slot: U256, value: U256) -> Self { // Ensure the account exists before setting storage - if self.db.cache.accounts.get(&address).is_none() { + if !self.db.cache.accounts.contains_key(&address) { self.db.insert_account_info(address, AccountInfo::default()); } let _ = self.db.insert_account_storage(address, slot, value); diff --git a/src/test_utils/env.rs b/src/test_utils/env.rs index 01dece7..fd6bfaa 100644 --- a/src/test_utils/env.rs +++ b/src/test_utils/env.rs @@ -79,13 +79,13 @@ impl TestSimEnvBuilder { } /// Set the rollup block environment. - pub fn with_rollup_block_env(mut self, env: BlockEnv) -> Self { + pub const fn with_rollup_block_env(mut self, env: BlockEnv) -> Self { self.rollup_block_env = env; self } /// Set the host block environment. - pub fn with_host_block_env(mut self, env: BlockEnv) -> Self { + pub const fn with_host_block_env(mut self, env: BlockEnv) -> Self { self.host_block_env = env; self } @@ -98,7 +98,7 @@ impl TestSimEnvBuilder { } /// Set the system constants. - pub fn with_constants(mut self, constants: SignetSystemConstants) -> Self { + pub const fn with_constants(mut self, constants: SignetSystemConstants) -> Self { self.constants = constants; self } diff --git a/src/test_utils/tx.rs b/src/test_utils/tx.rs index 95810c1..eeb856a 100644 --- a/src/test_utils/tx.rs +++ b/src/test_utils/tx.rs @@ -47,17 +47,17 @@ impl TestAccounts { } /// Get Alice's address. - pub fn alice_address(&self) -> Address { + pub const fn alice_address(&self) -> Address { self.alice.address() } /// Get Bob's address. - pub fn bob_address(&self) -> Address { + pub const fn bob_address(&self) -> Address { self.bob.address() } /// Get Charlie's address. - pub fn charlie_address(&self) -> Address { + pub const fn charlie_address(&self) -> Address { self.charlie.address() } } @@ -113,6 +113,7 @@ pub fn create_transfer_tx( /// * `chain_id` - The chain ID /// * `gas_limit` - The gas limit for the transaction /// * `max_priority_fee_per_gas` - The priority fee per gas unit +#[allow(clippy::too_many_arguments)] pub fn create_call_tx( signer: &PrivateKeySigner, to: Address,