diff --git a/Cargo.lock b/Cargo.lock index 9c4746f0b38..59d91b9bc01 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -6235,7 +6235,7 @@ dependencies = [ [[package]] name = "op-revm" version = "11.1.1" -source = "git+https://github.com/scroll-tech/revm?tag=scroll-v91#10e11b985ed28bd383e624539868bcc3f613d77c" +source = "git+https://github.com/scroll-tech/revm?tag=scroll-v91.1#3992cf8991a8738c565d45960efb60a2cabd4c02" dependencies = [ "auto_impl", "revm", @@ -10732,6 +10732,7 @@ dependencies = [ "reth-transaction-pool", "revm-scroll", "scroll-alloy-consensus", + "scroll-alloy-evm", "tracing", ] @@ -11276,7 +11277,7 @@ dependencies = [ [[package]] name = "revm" version = "30.1.1" -source = "git+https://github.com/scroll-tech/revm?tag=scroll-v91#10e11b985ed28bd383e624539868bcc3f613d77c" +source = "git+https://github.com/scroll-tech/revm?tag=scroll-v91.1#3992cf8991a8738c565d45960efb60a2cabd4c02" dependencies = [ "revm-bytecode", "revm-context", @@ -11294,7 +11295,7 @@ dependencies = [ [[package]] name = "revm-bytecode" version = "7.0.1" -source = "git+https://github.com/scroll-tech/revm?tag=scroll-v91#10e11b985ed28bd383e624539868bcc3f613d77c" +source = "git+https://github.com/scroll-tech/revm?tag=scroll-v91.1#3992cf8991a8738c565d45960efb60a2cabd4c02" dependencies = [ "bitvec", "phf 0.13.1", @@ -11305,7 +11306,7 @@ dependencies = [ [[package]] name = "revm-context" version = "10.1.1" -source = "git+https://github.com/scroll-tech/revm?tag=scroll-v91#10e11b985ed28bd383e624539868bcc3f613d77c" +source = "git+https://github.com/scroll-tech/revm?tag=scroll-v91.1#3992cf8991a8738c565d45960efb60a2cabd4c02" dependencies = [ "bitvec", "cfg-if", @@ -11321,7 +11322,7 @@ dependencies = [ [[package]] name = "revm-context-interface" version = "11.1.1" -source = "git+https://github.com/scroll-tech/revm?tag=scroll-v91#10e11b985ed28bd383e624539868bcc3f613d77c" +source = "git+https://github.com/scroll-tech/revm?tag=scroll-v91.1#3992cf8991a8738c565d45960efb60a2cabd4c02" dependencies = [ "alloy-eip2930", "alloy-eip7702", @@ -11336,7 +11337,7 @@ dependencies = [ [[package]] name = "revm-database" version = "9.0.1" -source = "git+https://github.com/scroll-tech/revm?tag=scroll-v91#10e11b985ed28bd383e624539868bcc3f613d77c" +source = "git+https://github.com/scroll-tech/revm?tag=scroll-v91.1#3992cf8991a8738c565d45960efb60a2cabd4c02" dependencies = [ "alloy-eips", "revm-bytecode", @@ -11349,7 +11350,7 @@ dependencies = [ [[package]] name = "revm-database-interface" version = "8.0.2" -source = "git+https://github.com/scroll-tech/revm?tag=scroll-v91#10e11b985ed28bd383e624539868bcc3f613d77c" +source = "git+https://github.com/scroll-tech/revm?tag=scroll-v91.1#3992cf8991a8738c565d45960efb60a2cabd4c02" dependencies = [ "auto_impl", "either", @@ -11361,7 +11362,7 @@ dependencies = [ [[package]] name = "revm-handler" version = "11.1.1" -source = "git+https://github.com/scroll-tech/revm?tag=scroll-v91#10e11b985ed28bd383e624539868bcc3f613d77c" +source = "git+https://github.com/scroll-tech/revm?tag=scroll-v91.1#3992cf8991a8738c565d45960efb60a2cabd4c02" dependencies = [ "auto_impl", "derive-where", @@ -11379,7 +11380,7 @@ dependencies = [ [[package]] name = "revm-inspector" version = "11.1.1" -source = "git+https://github.com/scroll-tech/revm?tag=scroll-v91#10e11b985ed28bd383e624539868bcc3f613d77c" +source = "git+https://github.com/scroll-tech/revm?tag=scroll-v91.1#3992cf8991a8738c565d45960efb60a2cabd4c02" dependencies = [ "auto_impl", "either", @@ -11416,7 +11417,7 @@ dependencies = [ [[package]] name = "revm-interpreter" version = "27.0.1" -source = "git+https://github.com/scroll-tech/revm?tag=scroll-v91#10e11b985ed28bd383e624539868bcc3f613d77c" +source = "git+https://github.com/scroll-tech/revm?tag=scroll-v91.1#3992cf8991a8738c565d45960efb60a2cabd4c02" dependencies = [ "revm-bytecode", "revm-context-interface", @@ -11428,7 +11429,7 @@ dependencies = [ [[package]] name = "revm-precompile" version = "28.1.1" -source = "git+https://github.com/scroll-tech/revm?tag=scroll-v91#10e11b985ed28bd383e624539868bcc3f613d77c" +source = "git+https://github.com/scroll-tech/revm?tag=scroll-v91.1#3992cf8991a8738c565d45960efb60a2cabd4c02" dependencies = [ "ark-bls12-381", "ark-bn254", @@ -11452,7 +11453,7 @@ dependencies = [ [[package]] name = "revm-primitives" version = "21.0.1" -source = "git+https://github.com/scroll-tech/revm?tag=scroll-v91#10e11b985ed28bd383e624539868bcc3f613d77c" +source = "git+https://github.com/scroll-tech/revm?tag=scroll-v91.1#3992cf8991a8738c565d45960efb60a2cabd4c02" dependencies = [ "alloy-primitives", "num_enum", @@ -11463,7 +11464,7 @@ dependencies = [ [[package]] name = "revm-scroll" version = "0.1.0" -source = "git+https://github.com/scroll-tech/scroll-revm?tag=scroll-v91#a1ac004adf0019d9926defc4e31e6a76a7e558f7" +source = "git+https://github.com/scroll-tech/scroll-revm?tag=scroll-v91.1#9c2a41996a216e3b69951db987d39e2f3fcfa148" dependencies = [ "auto_impl", "enumn", @@ -11477,7 +11478,7 @@ dependencies = [ [[package]] name = "revm-state" version = "8.0.1" -source = "git+https://github.com/scroll-tech/revm?tag=scroll-v91#10e11b985ed28bd383e624539868bcc3f613d77c" +source = "git+https://github.com/scroll-tech/revm?tag=scroll-v91.1#3992cf8991a8738c565d45960efb60a2cabd4c02" dependencies = [ "bitflags 2.9.4", "revm-bytecode", diff --git a/Cargo.toml b/Cargo.toml index fb12f7372c5..eef1434b6f5 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -487,18 +487,18 @@ reth-ress-protocol = { path = "crates/ress/protocol" } reth-ress-provider = { path = "crates/ress/provider" } # revm -revm = { git = "https://github.com/scroll-tech/revm", tag = "scroll-v91", default-features = false, features = ["enable_eip7702", "enable_eip7623"] } -revm-bytecode = { git = "https://github.com/scroll-tech/revm", tag = "scroll-v91", default-features = false } -revm-database = { git = "https://github.com/scroll-tech/revm", tag = "scroll-v91", default-features = false } -revm-state = { git = "https://github.com/scroll-tech/revm", tag = "scroll-v91", default-features = false } -revm-primitives = { git = "https://github.com/scroll-tech/revm", tag = "scroll-v91", default-features = false } -revm-interpreter = { git = "https://github.com/scroll-tech/revm", tag = "scroll-v91", default-features = false } -revm-inspector = { git = "https://github.com/scroll-tech/revm", tag = "scroll-v91", default-features = false } -revm-context = { git = "https://github.com/scroll-tech/revm", tag = "scroll-v91", default-features = false } -revm-context-interface = { git = "https://github.com/scroll-tech/revm", tag = "scroll-v91", default-features = false } -revm-database-interface = { git = "https://github.com/scroll-tech/revm", tag = "scroll-v91", default-features = false } -op-revm = { git = "https://github.com/scroll-tech/revm", tag = "scroll-v91", default-features = false } -revm-scroll = { git = "https://github.com/scroll-tech/scroll-revm", tag = "scroll-v91", default-features = false } +revm = { git = "https://github.com/scroll-tech/revm", tag = "scroll-v91.1", default-features = false, features = ["enable_eip7702", "enable_eip7623"] } +revm-bytecode = { git = "https://github.com/scroll-tech/revm", tag = "scroll-v91.1", default-features = false } +revm-database = { git = "https://github.com/scroll-tech/revm", tag = "scroll-v91.1", default-features = false } +revm-state = { git = "https://github.com/scroll-tech/revm", tag = "scroll-v91.1", default-features = false } +revm-primitives = { git = "https://github.com/scroll-tech/revm", tag = "scroll-v91.1", default-features = false } +revm-interpreter = { git = "https://github.com/scroll-tech/revm", tag = "scroll-v91.1", default-features = false } +revm-inspector = { git = "https://github.com/scroll-tech/revm", tag = "scroll-v91.1", default-features = false } +revm-context = { git = "https://github.com/scroll-tech/revm", tag = "scroll-v91.1", default-features = false } +revm-context-interface = { git = "https://github.com/scroll-tech/revm", tag = "scroll-v91.1", default-features = false } +revm-database-interface = { git = "https://github.com/scroll-tech/revm", tag = "scroll-v91.1", default-features = false } +op-revm = { git = "https://github.com/scroll-tech/revm", tag = "scroll-v91.1", default-features = false } +revm-scroll = { git = "https://github.com/scroll-tech/scroll-revm", tag = "scroll-v91.1", default-features = false } revm-inspectors = "0.31.0" # eth @@ -771,8 +771,8 @@ walkdir = "2.3.3" vergen-git2 = "1.0.5" [patch.crates-io] -revm = { git = "https://github.com/scroll-tech/revm", tag = "scroll-v91" } -op-revm = { git = "https://github.com/scroll-tech/revm", tag = "scroll-v91" } +revm = { git = "https://github.com/scroll-tech/revm", tag = "scroll-v91.1" } +op-revm = { git = "https://github.com/scroll-tech/revm", tag = "scroll-v91.1" } # alloy-consensus = { git = "https://github.com/alloy-rs/alloy", rev = "cfb13aa" } # alloy-contract = { git = "https://github.com/alloy-rs/alloy", rev = "cfb13aa" } # alloy-eips = { git = "https://github.com/alloy-rs/alloy", rev = "cfb13aa" } diff --git a/crates/scroll/node/src/builder/pool.rs b/crates/scroll/node/src/builder/pool.rs index 27079e78d14..35950076bb4 100644 --- a/crates/scroll/node/src/builder/pool.rs +++ b/crates/scroll/node/src/builder/pool.rs @@ -24,14 +24,21 @@ use scroll_alloy_hardforks::ScrollHardforks; pub struct ScrollPoolBuilder { /// Enforced overrides that are applied to the pool config. pub pool_config_overrides: PoolBuilderConfigOverrides, - + /// Require L1 data fee buffer in balance check. + /// When enabled, validates balance >= `L2_cost` + 2*`L1_cost` but only charges `L2_cost` + + /// 1*`L1_cost`. + pub require_l1_data_fee_buffer: bool, /// Marker for the pooled transaction type. _pd: core::marker::PhantomData, } impl Default for ScrollPoolBuilder { fn default() -> Self { - Self { pool_config_overrides: Default::default(), _pd: Default::default() } + Self { + pool_config_overrides: Default::default(), + require_l1_data_fee_buffer: false, + _pd: Default::default(), + } } } @@ -44,6 +51,14 @@ impl ScrollPoolBuilder { self.pool_config_overrides = pool_config_overrides; self } + + /// Sets the require L1 data fee buffer flag. + /// When enabled, validates balance >= `L2_cost` + 2*`L1_cost` but only charges `L2_cost` + + /// 1*`L1_cost`. This matches geth's block validation behavior. + pub const fn with_require_l1_data_fee_buffer(mut self, require: bool) -> Self { + self.require_l1_data_fee_buffer = require; + self + } } impl PoolBuilder for ScrollPoolBuilder @@ -58,7 +73,7 @@ where type Pool = ScrollTransactionPool; async fn build_pool(self, ctx: &BuilderContext) -> eyre::Result { - let Self { pool_config_overrides, .. } = self; + let Self { pool_config_overrides, require_l1_data_fee_buffer, .. } = self; let data_dir = ctx.config().datadir(); let blob_store = DiskFileBlobStore::open(data_dir.blobstore(), Default::default())?; @@ -81,6 +96,7 @@ where // In --dev mode we can't require gas fees because we're unable to decode // the L1 block info .require_l1_data_gas_fee(!ctx.config().dev.dev) + .require_l1_data_fee_buffer(require_l1_data_fee_buffer) }); let transaction_pool = reth_transaction_pool::Pool::new( @@ -379,4 +395,111 @@ mod tests { // drop all validation tasks. drop(manager); } + + #[test] + fn test_pool_builder_with_require_l1_data_fee_buffer() { + // Test that the builder method correctly sets the flag + let pool_builder = ScrollPoolBuilder::::default(); + assert!(!pool_builder.require_l1_data_fee_buffer); + + let pool_builder = pool_builder.with_require_l1_data_fee_buffer(true); + assert!(pool_builder.require_l1_data_fee_buffer); + + let pool_builder = pool_builder.with_require_l1_data_fee_buffer(false); + assert!(!pool_builder.require_l1_data_fee_buffer); + } + + #[tokio::test] + async fn test_l1_data_fee_buffer_validation() { + // Test that the L1 data fee buffer feature correctly validates transactions: + // - With buffer enabled: rejects when balance < L2_cost + 2*L1_cost + // - With buffer disabled: accepts when balance >= L2_cost + 1*L1_cost + // + // Both scenarios use identical setup to prove only the buffer flag differs. + + // Shared test constants + let signer: alloy_primitives::Address = Default::default(); + let balance = U256::from(500_000_000_000_000u64); // 500 Twei + let gas_limit = 55_000u64; + let gas_price = 7u128; + let tx_input = Bytes::from(random_iter::().take(100).collect::>()); + + // Helper to create a client with identical state + let client = + MockEthProvider::::new().with_chain_spec(SCROLL_DEV.clone()); + let hash = B256::random(); + client.add_header(hash, Header::default()); + client.add_block(hash, ScrollBlock::default()); + // Balance covers L2_cost + 1*L1_cost but NOT L2_cost + 2*L1_cost + // With u32::MAX storage values, L1 cost is ~483 Twei. + // max L2_cost = 55,000 * 7 = 385,000 Wei. + client.add_account(signer, ExtendedAccount::new(0, balance)); + client.add_account( + L1_GAS_PRICE_ORACLE_ADDRESS, + ExtendedAccount::new(0, U256::ZERO).extend_storage( + (0u8..8).map(|k| (B256::from(U256::from(k)), U256::from(u32::MAX))), + ), + ); + + // Helper to create a transaction with identical parameters + let tx = ScrollTxEnvelope::Legacy(Signed::new_unchecked( + TxLegacy { gas_limit, gas_price, input: tx_input.clone(), ..Default::default() }, + Signature::new(U256::ZERO, U256::ZERO, false), + Default::default(), + )); + let tx = ScrollPooledTransaction::new(Recovered::new_unchecked(tx, signer), 200); + + let handle = tokio::runtime::Handle::current(); + let manager = TaskManager::new(handle); + + // Test 1: With L1 data fee buffer ENABLED - should reject (requires 2x L1 cost) + let validator = TransactionValidationTaskExecutor::eth_builder(client.clone()) + .no_eip4844() + .build_with_tasks(manager.executor(), NoopBlobStore::default()) + .map(|validator| { + ScrollTransactionValidator::new(validator) + .require_l1_data_gas_fee(true) + .require_l1_data_fee_buffer(true) + }); + + let pool = ScrollTransactionPool::new( + validator, + CoinbaseTipOrdering::::default(), + NoopBlobStore::default(), + PoolConfig::default(), + ); + + let err = pool.add_transaction(TransactionOrigin::Local, tx.clone()).await.unwrap_err(); + assert!(matches!( + err.kind, + PoolErrorKind::InvalidTransaction( + InvalidPoolTransactionError::Consensus(InvalidTransactionError::InsufficientFunds(GotExpectedBoxed(expected))) + ) if *expected == GotExpected{ got: balance, expected: U256::from(967347259159872u64) } + )); + + // Test 2: With L1 data fee buffer DISABLED - should accept (only requires 1x L1 cost) + let validator = TransactionValidationTaskExecutor::eth_builder(client) + .no_eip4844() + .build_with_tasks(manager.executor(), NoopBlobStore::default()) + .map(|validator| { + ScrollTransactionValidator::new(validator) + .require_l1_data_gas_fee(true) + .require_l1_data_fee_buffer(false) + }); + + let pool = ScrollTransactionPool::new( + validator, + CoinbaseTipOrdering::::default(), + NoopBlobStore::default(), + PoolConfig::default(), + ); + + let result = pool.add_transaction(TransactionOrigin::Local, tx).await; + assert!( + result.is_ok(), + "Expected transaction to be accepted without buffer, got: {result:?}" + ); + + drop(manager); + } } diff --git a/crates/scroll/openvm-compat/Cargo.toml b/crates/scroll/openvm-compat/Cargo.toml index 4cf2cf3044e..3a5da3f0f70 100644 --- a/crates/scroll/openvm-compat/Cargo.toml +++ b/crates/scroll/openvm-compat/Cargo.toml @@ -28,4 +28,4 @@ scroll-alloy-consensus = { path = "../alloy/consensus", default-features = false scroll-alloy-rpc-types = { path = "../alloy/rpc-types", default-features = false } [patch.crates-io] -revm = { git = "https://github.com/scroll-tech/revm", tag = "scroll-v91" } +revm = { git = "https://github.com/scroll-tech/revm", tag = "scroll-v91.1" } diff --git a/crates/scroll/txpool/Cargo.toml b/crates/scroll/txpool/Cargo.toml index ede06079cf9..888e7354bdc 100644 --- a/crates/scroll/txpool/Cargo.toml +++ b/crates/scroll/txpool/Cargo.toml @@ -45,3 +45,4 @@ tracing.workspace = true [dev-dependencies] reth-scroll-chainspec.workspace = true reth-provider = { workspace = true, features = ["test-utils"] } +scroll-alloy-evm.workspace = true diff --git a/crates/scroll/txpool/src/transaction.rs b/crates/scroll/txpool/src/transaction.rs index e0fdf7b66c1..f68e9144ce2 100644 --- a/crates/scroll/txpool/src/transaction.rs +++ b/crates/scroll/txpool/src/transaction.rs @@ -223,17 +223,18 @@ impl ScrollTransaction for ScrollPooledTransact #[cfg(test)] mod tests { use crate::{ScrollPooledTransaction, ScrollTransactionValidator}; - use alloy_consensus::{transaction::Recovered, Signed}; + use alloy_consensus::{transaction::Recovered, Header, Signed, TxLegacy}; use alloy_eips::eip2718::Encodable2718; - use alloy_primitives::Signature; - use reth_provider::test_utils::MockEthProvider; - use reth_scroll_chainspec::SCROLL_MAINNET; - use reth_scroll_primitives::ScrollTransactionSigned; + use alloy_primitives::{private::rand::random_iter, Bytes, Signature, B256, U256}; + use reth_provider::test_utils::{ExtendedAccount, MockEthProvider}; + use reth_scroll_chainspec::{SCROLL_DEV, SCROLL_MAINNET}; + use reth_scroll_primitives::{ScrollBlock, ScrollPrimitives, ScrollTransactionSigned}; use reth_transaction_pool::{ blobstore::InMemoryBlobStore, validate::EthTransactionValidatorBuilder, TransactionOrigin, TransactionValidationOutcome, }; - use scroll_alloy_consensus::{ScrollTypedTransaction, TxL1Message}; + use scroll_alloy_consensus::{ScrollTxEnvelope, ScrollTypedTransaction, TxL1Message}; + use scroll_alloy_evm::gas_price_oracle::L1_GAS_PRICE_ORACLE_ADDRESS; #[test] fn validate_scroll_transaction() { let client = MockEthProvider::default().with_chain_spec(SCROLL_MAINNET.clone()); @@ -260,4 +261,102 @@ mod tests { }; assert_eq!(err.to_string(), "transaction type not supported"); } + + /// Helper function to create a test client with L1 gas price oracle storage configured. + /// Uses `u32::MAX` for storage values to produce significant L1 fees (similar to existing + /// tests). + fn create_test_client_with_l1_oracle( + signer: alloy_primitives::Address, + balance: U256, + ) -> MockEthProvider> + { + let client = + MockEthProvider::::new().with_chain_spec(SCROLL_DEV.clone()); + let hash = B256::random(); + + // Load a header, block, signer and the L1_GAS_PRICE_ORACLE_ADDRESS storage. + client.add_header(hash, Header::default()); + client.add_block(hash, ScrollBlock::default()); + client.add_account(signer, ExtendedAccount::new(0, balance)); + // Use u32::MAX for storage values (same as test_validate_one_rollup_fee_exceeds_balance) + // This produces L1 fees around 483 trillion wei per the existing test + client.add_account( + L1_GAS_PRICE_ORACLE_ADDRESS, + ExtendedAccount::new(0, U256::ZERO).extend_storage( + (0u8..8).map(|k| (B256::from(U256::from(k)), U256::from(u32::MAX))), + ), + ); + client + } + + /// Creates a test legacy transaction with random input data. + fn create_test_legacy_tx(signer: alloy_primitives::Address) -> ScrollPooledTransaction { + let tx = ScrollTxEnvelope::Legacy(Signed::new_unchecked( + TxLegacy { + gas_limit: 55_000, // Need higher gas for tx with input data + gas_price: 7, + input: Bytes::from(random_iter::().take(100).collect::>()), + ..Default::default() + }, + Signature::new(U256::ZERO, U256::ZERO, false), + Default::default(), + )); + ScrollPooledTransaction::new(Recovered::new_unchecked(tx, signer), 200) + } + + #[test] + fn test_validate_with_l1_data_fee_buffer() { + // This test verifies that when the L1 data fee buffer is enabled, + // a transaction that would pass without buffer fails because it requires 2x L1 cost. + let signer = Default::default(); + + // Balance covers L2_cost + 1*L1_cost but NOT L2_cost + 2*L1_cost + // With u32::MAX storage values, L1 cost is ~483 Twei (per existing test) + // L2_cost = 55,000 * 7 = 385,000 wei + // L2_cost + 1*L1_cost ≈ 483 Twei + // L2_cost + 2*L1_cost ≈ 967 Twei + // Balance of 500 Twei is between these values + let balance = U256::from(500_000_000_000_000u64); + let client = create_test_client_with_l1_oracle(signer, balance); + + let validator = EthTransactionValidatorBuilder::new(client.clone()) + .no_shanghai() + .no_cancun() + .build(InMemoryBlobStore::default()); + let buffer_validator = ScrollTransactionValidator::new(validator) + .require_l1_data_gas_fee(true) + .require_l1_data_fee_buffer(true); + + let validator = EthTransactionValidatorBuilder::new(client) + .no_shanghai() + .no_cancun() + .build(InMemoryBlobStore::default()); + let no_buffer_validator = ScrollTransactionValidator::new(validator) + .require_l1_data_gas_fee(true) + .require_l1_data_fee_buffer(false); + + let pool_tx = create_test_legacy_tx(signer); + let outcome = buffer_validator.validate_one(TransactionOrigin::External, pool_tx.clone()); + + // Should fail with InsufficientFunds because buffer requires 2x L1 cost + match &outcome { + TransactionValidationOutcome::Invalid(_, err) => { + let err_str = err.to_string().to_lowercase(); + assert!( + err_str.contains("not have enough funds") || err_str.contains("insufficient"), + "Expected InsufficientFunds error, got: {err}" + ); + } + _ => panic!("Expected Invalid outcome with buffer enabled, got: {outcome:?}"), + } + + // Now validate the same transaction without buffer, which should succeed + let outcome = no_buffer_validator.validate_one(TransactionOrigin::External, pool_tx); + + // Should succeed because without buffer, only 1x L1 cost is required + assert!( + matches!(outcome, TransactionValidationOutcome::Valid { .. }), + "Expected Valid outcome without buffer, got: {outcome:?}" + ); + } } diff --git a/crates/scroll/txpool/src/validator.rs b/crates/scroll/txpool/src/validator.rs index 65cead657ee..8d8c1670f0c 100644 --- a/crates/scroll/txpool/src/validator.rs +++ b/crates/scroll/txpool/src/validator.rs @@ -45,6 +45,10 @@ pub struct ScrollTransactionValidator { /// If true, ensure that the transaction's sender has enough balance to cover the L1 gas fee /// derived from the tracked L1 block info. require_l1_data_gas_fee: bool, + /// If true, require additional L1 data fee buffer in balance check. + /// When enabled, validates balance >= `L2_cost` + 2*`L1_cost` but only charges `L2_cost` + + /// 1*`L1_cost`. + require_l1_data_fee_buffer: bool, } impl ScrollTransactionValidator { @@ -82,6 +86,18 @@ impl ScrollTransactionValidator { pub const fn requires_l1_data_gas_fee(&self) -> bool { self.require_l1_data_gas_fee } + + /// Enable L1 data fee buffer check (disabled by default). + /// When enabled, validates balance >= `L2_cost` + 2*`L1_cost` but only charges `L2_cost` + + /// 1*`L1_cost`. + pub fn require_l1_data_fee_buffer(self, require: bool) -> Self { + Self { require_l1_data_fee_buffer: require, ..self } + } + + /// Returns whether this validator requires the L1 data fee buffer check. + pub const fn requires_l1_data_fee_buffer(&self) -> bool { + self.require_l1_data_fee_buffer + } } impl ScrollTransactionValidator @@ -108,7 +124,12 @@ where inner: EthTransactionValidator, block_info: ScrollL1BlockInfo, ) -> Self { - Self { inner, block_info: Arc::new(block_info), require_l1_data_gas_fee: true } + Self { + inner, + block_info: Arc::new(block_info), + require_l1_data_gas_fee: true, + require_l1_data_fee_buffer: false, + } } /// Update the L1 block info for the given header and system transaction, if any. @@ -184,7 +205,7 @@ where let compression_ratio = compute_compression_ratio(valid_tx.transaction().input()); let compressed_size = compute_compressed_size(&encoded); - let cost_addition = match l1_block_info.l1_tx_data_fee( + let l1_data_fee = match l1_block_info.l1_tx_data_fee( self.chain_spec(), self.block_timestamp(), self.block_number(), @@ -198,14 +219,21 @@ where } }; // Check rollup fee is under u64::MAX. - if cost_addition >= MAX_ROLLUP_FEE { + if l1_data_fee >= MAX_ROLLUP_FEE { return TransactionValidationOutcome::Invalid( valid_tx.into_transaction(), InvalidTransactionError::GasUintOverflow.into(), ) } - let cost = valid_tx.transaction().cost().saturating_add(cost_addition); + let cost = valid_tx.transaction().cost().saturating_add(l1_data_fee); + + // Optionally add buffer (1 additional unit of L1 data fee) + let cost = if self.requires_l1_data_fee_buffer() { + cost.saturating_add(l1_data_fee) + } else { + cost + }; // Checks for max cost if cost > balance {