From d79312b8e958d29f359add5b6c8e3718fb552df7 Mon Sep 17 00:00:00 2001 From: dylan Date: Tue, 10 Feb 2026 12:23:34 -0700 Subject: [PATCH 01/10] 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} (86%) 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 d2aacee2..b11f8328 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 00000000..bc2943a0 --- /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 00000000..03797a2e --- /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 00000000..47996883 --- /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 86% rename from src/test_utils.rs rename to src/test_utils/mod.rs index 1a2c9e53..ee8f3fea 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}, @@ -20,7 +41,6 @@ use init4_bin_base::{ }, }; use signet_constants::SignetSystemConstants; -use std::env; use trevm::revm::{context::BlockEnv, context_interface::block::BlobExcessGasAndPrice}; /// Set up a block builder with test values @@ -40,7 +60,7 @@ pub fn setup_test_config() -> &'static BuilderConfig { submit_endpoints: vec!["https://relay-sepolia.flashbots.net:443".to_string()], 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 00000000..0d612db6 --- /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 00000000..c80f7088 --- /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 6a961cd11a67110accf22941a372570475aecd3d Mon Sep 17 00:00:00 2001 From: dylan Date: Tue, 10 Feb 2026 12:38:52 -0700 Subject: [PATCH 02/10] 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 bc2943a0..e6348904 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 03797a2e..6a41d962 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 47996883..01dece7e 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 ee8f3fea..500c4121 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 0d612db6..4eec47a6 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 c80f7088..95810c1a 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 d5485449dae2aae52c2f74dcdf2bec54559181b6 Mon Sep 17 00:00:00 2001 From: dylan Date: Tue, 10 Feb 2026 12:44:43 -0700 Subject: [PATCH 03/10] 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 b11f8328..24e44d3a 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 2aab8832..c716535f 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 e6348904..e330b970 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 6a41d962..93315c6f 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 01dece7e..fd6bfaae 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 95810c1a..eeb856ad 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, From 14a1bcd536fc4d95b28c83835f7e7476681549da Mon Sep 17 00:00:00 2001 From: dylan Date: Wed, 25 Feb 2026 15:58:09 -0700 Subject: [PATCH 04/10] fmt and clippy --- src/test_utils/block.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/test_utils/block.rs b/src/test_utils/block.rs index e330b970..7edac228 100644 --- a/src/test_utils/block.rs +++ b/src/test_utils/block.rs @@ -7,7 +7,8 @@ use super::{ env::{TestHostEnv, TestRollupEnv, TestSimEnvBuilder}, }; use signet_sim::{BlockBuild, BuiltBlock, SimCache}; -use std::time::{Duration, Instant}; +use std::time::Duration; +use tokio::time::Instant; use trevm::revm::inspector::NoOpInspector; /// Test block builder type using in-memory databases. From 3217a12be69f1440e93b6fc3bb7d2b40a7bc3adb Mon Sep 17 00:00:00 2001 From: dylan Date: Tue, 10 Mar 2026 23:31:36 -0600 Subject: [PATCH 05/10] fix: add state source type support --- src/test_utils/block.rs | 20 +++++++++++++------- src/test_utils/db.rs | 32 ++++++++++++++++++++++++++++++++ src/test_utils/env.rs | 17 ++++++++++++++++- src/test_utils/mod.rs | 2 +- 4 files changed, 62 insertions(+), 9 deletions(-) diff --git a/src/test_utils/block.rs b/src/test_utils/block.rs index 7edac228..c39a1f4a 100644 --- a/src/test_utils/block.rs +++ b/src/test_utils/block.rs @@ -3,7 +3,7 @@ //! for testing block simulation. use super::{ - db::TestDb, + db::{TestDb, TestStateSource}, env::{TestHostEnv, TestRollupEnv, TestSimEnvBuilder}, }; use signet_sim::{BlockBuild, BuiltBlock, SimCache}; @@ -12,7 +12,8 @@ use tokio::time::Instant; use trevm::revm::inspector::NoOpInspector; /// Test block builder type using in-memory databases. -pub type TestBlockBuild = BlockBuild; +pub type TestBlockBuild = + BlockBuild; /// Builder for creating test `BlockBuild` instances. /// Configures all the parameters needed for block simulation @@ -110,12 +111,15 @@ impl TestBlockBuildBuilder { /// 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 sim_env_builder = self.sim_env_builder.unwrap_or_default(); + + let (rollup_env, host_env, ru_source, host_source) = match (self.rollup_env, self.host_env) + { + (Some(rollup), Some(host)) => { + let (ru_source, host_source) = sim_env_builder.build_state_sources(); + (rollup, host, ru_source, host_source) } + _ => sim_env_builder.build_with_sources(), }; let finish_by = Instant::now() + self.deadline_duration; @@ -128,6 +132,8 @@ impl TestBlockBuildBuilder { self.sim_cache, self.max_gas, self.max_host_gas, + ru_source, + host_source, ) } } diff --git a/src/test_utils/db.rs b/src/test_utils/db.rs index 93315c6f..df5764b4 100644 --- a/src/test_utils/db.rs +++ b/src/test_utils/db.rs @@ -3,8 +3,11 @@ //! for testing block simulation without requiring network access. use alloy::primitives::{Address, B256, U256}; +use signet_sim::AcctInfo; use trevm::revm::{ database::{CacheDB, EmptyDB}, + database_interface::DatabaseRef, + primitives::KECCAK_EMPTY, state::AccountInfo, }; @@ -14,6 +17,35 @@ use trevm::revm::{ /// with `RollupEnv` and `HostEnv` for offline simulation testing. pub type TestDb = CacheDB; +/// A [`StateSource`] backed by a [`TestDb`] for offline testing. +/// +/// This wraps an in-memory database and implements [`signet_sim::StateSource`] +/// so it can be used as the async state source parameter in [`BlockBuild::new`]. +/// +/// [`StateSource`]: signet_sim::StateSource +/// [`BlockBuild::new`]: signet_sim::BlockBuild::new +#[derive(Debug, Clone)] +pub struct TestStateSource { + db: TestDb, +} + +impl TestStateSource { + /// Create a new [`TestStateSource`] from a [`TestDb`]. + pub const fn new(db: TestDb) -> Self { + Self { db } + } +} + +impl signet_sim::StateSource for TestStateSource { + type Error = ::Error; + + async fn account_details(&self, address: &Address) -> Result { + let info = self.db.basic_ref(*address)?.unwrap_or_default(); + let has_code = info.code_hash() != KECCAK_EMPTY; + Ok(AcctInfo { nonce: info.nonce, balance: info.balance, has_code }) + } +} + /// Builder for creating pre-populated test databases. /// Use this builder to set up blockchain state (accounts, contracts, storage) /// before running simulations. diff --git a/src/test_utils/env.rs b/src/test_utils/env.rs index fd6bfaae..3a860ed7 100644 --- a/src/test_utils/env.rs +++ b/src/test_utils/env.rs @@ -2,7 +2,7 @@ //! This module provides builders for creating `RollupEnv` and `HostEnv` //! instances with in-memory databases for offline testing. -use super::db::{TestDb, TestDbBuilder}; +use super::db::{TestDb, TestDbBuilder, TestStateSource}; use crate::tasks::block::cfg::SignetCfgEnv; use alloy::primitives::{Address, B256, U256}; use signet_constants::SignetSystemConstants; @@ -117,12 +117,27 @@ impl TestSimEnvBuilder { HostEnv::new(self.host_db.clone(), self.constants.clone(), &cfg, &self.host_block_env) } + /// Build [`TestStateSource`] instances from the current databases. + pub fn build_state_sources(&self) -> (TestStateSource, TestStateSource) { + (TestStateSource::new(self.rollup_db.clone()), TestStateSource::new(self.host_db.clone())) + } + /// 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) } + + /// Build environments and state sources together. + pub fn build_with_sources( + &self, + ) -> (TestRollupEnv, TestHostEnv, TestStateSource, TestStateSource) { + let rollup = self.build_rollup_env(); + let host = self.build_host_env(); + let (ru_source, host_source) = self.build_state_sources(); + (rollup, host, ru_source, host_source) + } } #[cfg(test)] diff --git a/src/test_utils/mod.rs b/src/test_utils/mod.rs index 500c4121..05761edd 100644 --- a/src/test_utils/mod.rs +++ b/src/test_utils/mod.rs @@ -11,7 +11,7 @@ mod tx; // Re-export test harness components pub use block::{TestBlockBuild, TestBlockBuildBuilder, quick_build_block}; -pub use db::{TestDb, TestDbBuilder}; +pub use db::{TestDb, TestDbBuilder, TestStateSource}; pub use env::{TestHostEnv, TestRollupEnv, TestSimEnvBuilder}; pub use scenarios::{ DEFAULT_BALANCE, DEFAULT_BASEFEE, basic_scenario, custom_funded_scenario, funded_test_db, From c28a81f8439bb56599ea2ba3477429105c6711d1 Mon Sep 17 00:00:00 2001 From: dylan Date: Tue, 24 Mar 2026 21:16:26 -0600 Subject: [PATCH 06/10] refactor: pull integration tests back into tests/ and gate test_utils behind`#[cfg(test)]` --- Cargo.toml | 23 +++++++++++++ Makefile | 6 +++- src/lib.rs | 2 +- src/test_utils/block.rs | 65 ++++++++++++++++++++----------------- src/test_utils/db.rs | 12 +++++-- src/test_utils/env.rs | 11 ++++++- src/test_utils/mod.rs | 1 + tests/block_builder_test.rs | 3 -- tests/bundle_poller_test.rs | 1 - tests/cache.rs | 3 -- tests/env.rs | 3 -- tests/tx_poller_test.rs | 1 - 12 files changed, 85 insertions(+), 46 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 408cedcf..7db0ba97 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -10,6 +10,9 @@ license = "Apache-2.0 OR MIT" homepage = "https://github.com/init4tech/builder" repository = "https://github.com/init4tech/builder" +[features] +test-utils = [] + [lib] name = "builder" @@ -17,6 +20,26 @@ name = "builder" name = "zenith-builder-example" path = "bin/builder.rs" +[[test]] +name = "block_builder_test" +required-features = ["test-utils"] + +[[test]] +name = "bundle_poller_test" +required-features = ["test-utils"] + +[[test]] +name = "cache" +required-features = ["test-utils"] + +[[test]] +name = "env" +required-features = ["test-utils"] + +[[test]] +name = "tx_poller_test" +required-features = ["test-utils"] + [dependencies] init4-bin-base = { version = "0.18.0-rc.13", features = ["perms", "aws", "pylon"] } diff --git a/Makefile b/Makefile index db935850..0ce5a089 100644 --- a/Makefile +++ b/Makefile @@ -16,7 +16,7 @@ PROFILE ?= dev # override with `make PROFILE=release build` CLIPPY_FLAGS ?= $(TARGETS) $(FEATURES) --workspace --profile dev -- -D warnings FMT_FLAGS ?= --all -.PHONY: build release run test clean fmt clippy default +.PHONY: build release run test test-all clean fmt clippy default default: build @@ -34,6 +34,10 @@ run: test: $(CARGO) test +# NB: requires auth and server configuration +test-all: + $(CARGO) test --features test-utils + clean: $(CARGO) clean diff --git a/src/lib.rs b/src/lib.rs index dfb7245d..c5f48db0 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -31,7 +31,7 @@ pub mod tasks; /// Utilities. pub mod utils; -/// Test utilitites +#[cfg(any(test, feature = "test-utils"))] pub mod test_utils; // Anonymous imports suppress warnings about unused crate dependencies. diff --git a/src/test_utils/block.rs b/src/test_utils/block.rs index c39a1f4a..2ce13f09 100644 --- a/src/test_utils/block.rs +++ b/src/test_utils/block.rs @@ -20,16 +20,30 @@ pub type TestBlockBuild = /// and provides sensible defaults for testing scenarios. #[derive(Debug)] pub struct TestBlockBuildBuilder { - rollup_env: Option, - host_env: Option, - sim_env_builder: Option, + /// The test environment configuration for the block build. + env: TestBlockBuildEnv, + /// The simulation cache to use for the block build. sim_cache: SimCache, + /// The duration from now until the block build should finish. deadline_duration: Duration, + /// The concurrency limit for parallel simulation. concurrency_limit: usize, + /// The maximum gas limit for the rollup block. max_gas: u64, + /// The maximum gas limit for host transactions. max_host_gas: u64, } +/// Internal enum to manage the environment configuration for the block build. +#[derive(Debug)] +#[allow(clippy::large_enum_variant)] +enum TestBlockBuildEnv { + /// A builder that will create the environments when `into_block_build()` is called. + Builder(TestSimEnvBuilder), + /// A pair of already built environments to use directly. + Built { rollup: TestRollupEnv, host: TestHostEnv }, +} + impl Default for TestBlockBuildBuilder { fn default() -> Self { Self::new() @@ -37,7 +51,7 @@ impl Default for TestBlockBuildBuilder { } impl TestBlockBuildBuilder { - /// Create a new test block build builder with sensible defaults. + /// Create a new test block build builder with test-focused defaults. /// Default values: /// - Deadline: 2 seconds /// - Concurrency limit: 4 @@ -45,9 +59,8 @@ impl TestBlockBuildBuilder { /// - Max host gas: 24,000,000 pub fn new() -> Self { Self { - rollup_env: None, - host_env: None, - sim_env_builder: Some(TestSimEnvBuilder::new()), + // Default to building fresh test environments unless the caller injects a pair. + env: TestBlockBuildEnv::Builder(TestSimEnvBuilder::new()), sim_cache: SimCache::new(), deadline_duration: Duration::from_secs(2), concurrency_limit: 4, @@ -57,23 +70,15 @@ impl TestBlockBuildBuilder { } /// Set the simulation environment builder. - /// The environments will be built from this builder when `build()` is called. + /// The environments will be built from this builder when `into_block_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.env = TestBlockBuildEnv::Builder(builder); 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); + /// Set both environments directly so the block build uses a consistent pair. + pub fn with_envs(mut self, rollup: TestRollupEnv, host: TestHostEnv) -> Self { + self.env = TestBlockBuildEnv::Built { rollup, host }; self } @@ -110,18 +115,18 @@ impl TestBlockBuildBuilder { /// 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 sim_env_builder = self.sim_env_builder.unwrap_or_default(); - - let (rollup_env, host_env, ru_source, host_source) = match (self.rollup_env, self.host_env) - { - (Some(rollup), Some(host)) => { - let (ru_source, host_source) = sim_env_builder.build_state_sources(); + pub async fn into_block_build(self) -> BuiltBlock { + // Keep the async state sources aligned with whichever environment pair we use. + let (rollup_env, host_env, ru_source, host_source) = match self.env { + TestBlockBuildEnv::Builder(sim_env_builder) => sim_env_builder.build_with_sources(), + TestBlockBuildEnv::Built { rollup, host } => { + let ru_source = TestStateSource::from_inner_db(rollup.db().clone()); + let host_source = TestStateSource::from_inner_db(host.db().clone()); (rollup, host, ru_source, host_source) } - _ => sim_env_builder.build_with_sources(), }; + // Convert the relative deadline into the absolute instant expected by `BlockBuild`. let finish_by = Instant::now() + self.deadline_duration; BlockBuild::new( @@ -135,6 +140,8 @@ impl TestBlockBuildBuilder { ru_source, host_source, ) + .build() + .await } } @@ -142,7 +149,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).into_block_build().await } #[cfg(test)] diff --git a/src/test_utils/db.rs b/src/test_utils/db.rs index df5764b4..4c3b87ad 100644 --- a/src/test_utils/db.rs +++ b/src/test_utils/db.rs @@ -3,7 +3,8 @@ //! for testing block simulation without requiring network access. use alloy::primitives::{Address, B256, U256}; -use signet_sim::AcctInfo; +use signet_sim::{AcctInfo, InnerDb}; +use std::sync::Arc; use trevm::revm::{ database::{CacheDB, EmptyDB}, database_interface::DatabaseRef, @@ -26,12 +27,17 @@ pub type TestDb = CacheDB; /// [`BlockBuild::new`]: signet_sim::BlockBuild::new #[derive(Debug, Clone)] pub struct TestStateSource { - db: TestDb, + db: InnerDb, } impl TestStateSource { /// Create a new [`TestStateSource`] from a [`TestDb`]. - pub const fn new(db: TestDb) -> Self { + pub fn new(db: TestDb) -> Self { + Self { db: Arc::new(CacheDB::new(db)) } + } + + /// Create a new [`TestStateSource`] from an environment database handle. + pub const fn from_inner_db(db: InnerDb) -> Self { Self { db } } } diff --git a/src/test_utils/env.rs b/src/test_utils/env.rs index 3a860ed7..d1d53ef6 100644 --- a/src/test_utils/env.rs +++ b/src/test_utils/env.rs @@ -23,10 +23,15 @@ pub type TestHostEnv = HostEnv; /// without network access. #[derive(Debug)] pub struct TestSimEnvBuilder { + /// The in-memory database for the rollup environment. rollup_db: TestDb, + /// The in-memory database for the host environment. host_db: TestDb, + /// The block environment configuration for the rollup. rollup_block_env: BlockEnv, + /// The block environment configuration for the host. host_block_env: BlockEnv, + /// The unified system constants to use for both environments. constants: SignetSystemConstants, } @@ -39,6 +44,7 @@ impl Default for TestSimEnvBuilder { impl TestSimEnvBuilder { /// Create a new builder with default Parmigiana constants and empty databases. pub fn new() -> Self { + // Share one default block template so the rollup and host builders start aligned. let default_block_env = Self::default_block_env(); Self { rollup_db: TestDbBuilder::new().build(), @@ -54,7 +60,7 @@ impl TestSimEnvBuilder { BlockEnv { number: U256::from(100u64), beneficiary: Address::repeat_byte(0x01), - timestamp: U256::from(1700000000u64), + timestamp: U256::from(1_700_000_000u64), gas_limit: 3_000_000_000, basefee: 1_000_000_000, // 1 gwei difficulty: U256::ZERO, @@ -119,6 +125,7 @@ impl TestSimEnvBuilder { /// Build [`TestStateSource`] instances from the current databases. pub fn build_state_sources(&self) -> (TestStateSource, TestStateSource) { + // Match the environment internals so explicit env injection can reuse the same source type. (TestStateSource::new(self.rollup_db.clone()), TestStateSource::new(self.host_db.clone())) } @@ -146,6 +153,7 @@ mod tests { #[test] fn test_sim_env_builder_creates_environments() { + // The default builder should produce both environments without any extra setup. let builder = TestSimEnvBuilder::new(); let (rollup_env, host_env) = builder.build(); @@ -157,6 +165,7 @@ mod tests { #[test] fn test_sim_env_builder_with_custom_block_env() { + // Override the shared block env to verify both environments accept custom settings. let custom_env = BlockEnv { number: U256::from(500u64), beneficiary: Address::repeat_byte(0x42), diff --git a/src/test_utils/mod.rs b/src/test_utils/mod.rs index 05761edd..2e708e32 100644 --- a/src/test_utils/mod.rs +++ b/src/test_utils/mod.rs @@ -2,6 +2,7 @@ //! //! This module provides utilities for testing the block builder without //! requiring network access or full chain state setup. +#![allow(dead_code, unreachable_pub, unused_imports)] mod block; mod db; diff --git a/tests/block_builder_test.rs b/tests/block_builder_test.rs index 38db7772..ff59940d 100644 --- a/tests/block_builder_test.rs +++ b/tests/block_builder_test.rs @@ -1,7 +1,5 @@ //! Tests for the block building task. -#![recursion_limit = "256"] - use alloy::{ consensus::transaction::SignerRecoverable, eips::BlockId, node_bindings::Anvil, primitives::U256, providers::Provider, signers::local::PrivateKeySigner, @@ -21,7 +19,6 @@ use std::time::{Duration, Instant}; /// This test sets up a simulated environment using Anvil, creates a block builder, /// and verifies that the block builder can successfully build a block containing /// transactions from multiple senders. -#[ignore = "integration test"] #[tokio::test(flavor = "multi_thread", worker_threads = 2)] async fn test_handle_build() { setup_logging(); diff --git a/tests/bundle_poller_test.rs b/tests/bundle_poller_test.rs index 440e0572..2c5aa588 100644 --- a/tests/bundle_poller_test.rs +++ b/tests/bundle_poller_test.rs @@ -1,7 +1,6 @@ use builder::test_utils::{setup_logging, setup_test_config}; use eyre::Result; -#[ignore = "integration test"] #[tokio::test] async fn test_bundle_poller_roundtrip() -> Result<()> { setup_logging(); diff --git a/tests/cache.rs b/tests/cache.rs index 9173ad08..e2f62e37 100644 --- a/tests/cache.rs +++ b/tests/cache.rs @@ -1,5 +1,3 @@ -#![recursion_limit = "256"] - use builder::{ tasks::{cache::CacheTasks, env::EnvTask}, test_utils::{setup_logging, setup_test_config}, @@ -7,7 +5,6 @@ use builder::{ use init4_bin_base::deps::tracing::warn; use std::time::Duration; -#[ignore = "integration test. This test will take >12 seconds to run, and requires Authz configuration env vars."] #[tokio::test] async fn test_bundle_poller_roundtrip() -> eyre::Result<()> { setup_logging(); diff --git a/tests/env.rs b/tests/env.rs index 89fa6a04..f7d5aa12 100644 --- a/tests/env.rs +++ b/tests/env.rs @@ -1,11 +1,8 @@ -#![recursion_limit = "256"] - use builder::{ tasks::env::EnvTask, test_utils::{setup_logging, setup_test_config}, }; -#[ignore = "integration test. This test will take between 0 and 12 seconds to run."] #[tokio::test] async fn test_bundle_poller_roundtrip() -> eyre::Result<()> { setup_logging(); diff --git a/tests/tx_poller_test.rs b/tests/tx_poller_test.rs index bca37e3c..c3d5f3b0 100644 --- a/tests/tx_poller_test.rs +++ b/tests/tx_poller_test.rs @@ -5,7 +5,6 @@ use builder::{ }; use eyre::{Ok, Result}; -#[ignore = "integration test"] #[tokio::test] async fn test_tx_roundtrip() -> Result<()> { setup_logging(); From 82d34f313f24811accad5738bbe8d7c391e2257a Mon Sep 17 00:00:00 2001 From: dylan Date: Tue, 24 Mar 2026 22:00:55 -0600 Subject: [PATCH 07/10] docs: update docs to match testing changes --- .claude/CLAUDE.md | 14 +++++++------- README.md | 17 +++++++++++++++++ 2 files changed, 24 insertions(+), 7 deletions(-) diff --git a/.claude/CLAUDE.md b/.claude/CLAUDE.md index 24e44d3a..f684e302 100644 --- a/.claude/CLAUDE.md +++ b/.claude/CLAUDE.md @@ -10,7 +10,8 @@ Single-crate Rust application (not a workspace) that builds Signet rollup blocks make # Debug build make release # Optimized release build make run # Run zenith-builder-example binary -make test # Run all tests +make test # Run unit tests +make test-all # Run unit + integration tests (requires network) make fmt # Format code make clippy # Lint with warnings denied ``` @@ -20,9 +21,8 @@ Always lint before committing. The Makefile provides shortcuts (`make fmt`, `mak ### 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) +cargo test test_name # Run specific unit test by name +cargo test --features test-utils --test test_file_name # Run a specific integration test ``` ## Architecture @@ -49,7 +49,7 @@ src/ service.rs - Axum /healthcheck endpoint macros.rs - span_scoped!, span_debug/info/warn/error!, res/opt_unwrap_or_continue! utils.rs - Signature extraction, gas population helpers - test_utils.rs - setup_test_config, new_signed_tx, test_block_env helpers + test_utils/ - Test harness (cfg-gated via test-utils feature) tasks/ mod.rs - Module re-exports env.rs - EnvTask, SimEnv, Environment types @@ -137,11 +137,11 @@ src/ ### Integration Tests -Most tests in `tests/` are marked `#[ignore]` and require network access (real RPC endpoints or Anvil). +Integration tests live in `tests/` and are gated behind the `test-utils` Cargo feature via `required-features` in `Cargo.toml`. They are not compiled by `cargo test` alone — use `make test-all` or `cargo test --features test-utils` to build and run them. They require network access (real RPC endpoints or Anvil). ### Simulation Harness (Offline Tests) -`src/test_utils/` provides a testing harness for offline simulation testing: +`src/test_utils/` provides a testing harness for offline simulation testing, gated with `#[cfg(any(test, feature = "test-utils"))]`: - `TestDbBuilder` - Create in-memory EVM state - `TestSimEnvBuilder` - Create `RollupEnv`/`HostEnv` without RPC diff --git a/README.md b/README.md index a6dbcd1e..42a829c2 100644 --- a/README.md +++ b/README.md @@ -166,6 +166,23 @@ The previous header's basefee is tracked through the build loop and used for gas ## ✅ Testing +### Unit Tests + +```bash +make test +``` + +### Integration Tests + +Integration tests require network access (RPC endpoints or Anvil) and are gated behind the `test-utils` Cargo feature. They are not compiled by default. + +```bash +make test-all # Run all tests (unit + integration) +cargo test --features test-utils --test block_builder_test # Run a specific integration test +``` + +### Deployment Verification + 1. Build the Docker image: ```bash From 9d9a90eeebccea4604b3e5aa2e1bc0e6d43dd450 Mon Sep 17 00:00:00 2001 From: dylan Date: Wed, 25 Mar 2026 09:55:26 -0600 Subject: [PATCH 08/10] fmt + clippy recrusion limits fix --- tests/block_builder_test.rs | 1 + tests/cache.rs | 2 ++ tests/env.rs | 2 ++ 3 files changed, 5 insertions(+) diff --git a/tests/block_builder_test.rs b/tests/block_builder_test.rs index ff59940d..41aebe81 100644 --- a/tests/block_builder_test.rs +++ b/tests/block_builder_test.rs @@ -1,3 +1,4 @@ +#![recursion_limit = "256"] //! Tests for the block building task. use alloy::{ diff --git a/tests/cache.rs b/tests/cache.rs index e2f62e37..903a86f9 100644 --- a/tests/cache.rs +++ b/tests/cache.rs @@ -1,3 +1,5 @@ +#![recursion_limit = "256"] + use builder::{ tasks::{cache::CacheTasks, env::EnvTask}, test_utils::{setup_logging, setup_test_config}, diff --git a/tests/env.rs b/tests/env.rs index f7d5aa12..103e9484 100644 --- a/tests/env.rs +++ b/tests/env.rs @@ -1,3 +1,5 @@ +#![recursion_limit = "256"] + use builder::{ tasks::env::EnvTask, test_utils::{setup_logging, setup_test_config}, From 7321aa65a170274fc5205a74dcd54d6fd09a3643 Mon Sep 17 00:00:00 2001 From: dylan Date: Thu, 26 Mar 2026 22:59:45 -0600 Subject: [PATCH 09/10] cleanup: gate individual test files behind test-utils --- Cargo.toml | 20 -------------------- tests/block_builder_test.rs | 1 + tests/bundle_poller_test.rs | 2 ++ tests/cache.rs | 1 + tests/env.rs | 1 + tests/tx_poller_test.rs | 2 ++ 6 files changed, 7 insertions(+), 20 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 7db0ba97..99008871 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -20,26 +20,6 @@ name = "builder" name = "zenith-builder-example" path = "bin/builder.rs" -[[test]] -name = "block_builder_test" -required-features = ["test-utils"] - -[[test]] -name = "bundle_poller_test" -required-features = ["test-utils"] - -[[test]] -name = "cache" -required-features = ["test-utils"] - -[[test]] -name = "env" -required-features = ["test-utils"] - -[[test]] -name = "tx_poller_test" -required-features = ["test-utils"] - [dependencies] init4-bin-base = { version = "0.18.0-rc.13", features = ["perms", "aws", "pylon"] } diff --git a/tests/block_builder_test.rs b/tests/block_builder_test.rs index 41aebe81..7992912e 100644 --- a/tests/block_builder_test.rs +++ b/tests/block_builder_test.rs @@ -1,3 +1,4 @@ +#![cfg(feature = "test-utils")] #![recursion_limit = "256"] //! Tests for the block building task. diff --git a/tests/bundle_poller_test.rs b/tests/bundle_poller_test.rs index 2c5aa588..9acfe483 100644 --- a/tests/bundle_poller_test.rs +++ b/tests/bundle_poller_test.rs @@ -1,3 +1,5 @@ +#![cfg(feature = "test-utils")] + use builder::test_utils::{setup_logging, setup_test_config}; use eyre::Result; diff --git a/tests/cache.rs b/tests/cache.rs index 903a86f9..d37cd7e9 100644 --- a/tests/cache.rs +++ b/tests/cache.rs @@ -1,3 +1,4 @@ +#![cfg(feature = "test-utils")] #![recursion_limit = "256"] use builder::{ diff --git a/tests/env.rs b/tests/env.rs index 103e9484..06f78f05 100644 --- a/tests/env.rs +++ b/tests/env.rs @@ -1,3 +1,4 @@ +#![cfg(feature = "test-utils")] #![recursion_limit = "256"] use builder::{ diff --git a/tests/tx_poller_test.rs b/tests/tx_poller_test.rs index c3d5f3b0..d607d8c6 100644 --- a/tests/tx_poller_test.rs +++ b/tests/tx_poller_test.rs @@ -1,3 +1,5 @@ +#![cfg(feature = "test-utils")] + use alloy::{primitives::U256, signers::local::PrivateKeySigner}; use builder::{ tasks::cache::TxPoller, From 9cb5717477bd92f09b56211dc643b668264ab60f Mon Sep 17 00:00:00 2001 From: dylan Date: Thu, 26 Mar 2026 23:03:54 -0600 Subject: [PATCH 10/10] cleanup: remove redundant rust version info --- .claude/CLAUDE.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.claude/CLAUDE.md b/.claude/CLAUDE.md index f684e302..c1497f36 100644 --- a/.claude/CLAUDE.md +++ b/.claude/CLAUDE.md @@ -131,7 +131,7 @@ src/ ### GitHub - Fresh branches off `main` for PRs. Descriptive branch names. -- AI-authored GitHub comments must include `**[Claude Code]**` header. Minimum: 1.85, Edition: 2024 +- AI-authored GitHub comments must include `**[Claude Code]**` header. ## Testing