From ef5340b695e151be66805867c9445dd213fdece5 Mon Sep 17 00:00:00 2001 From: chatton Date: Thu, 26 Feb 2026 12:45:54 +0000 Subject: [PATCH 1/3] fix: fix allow list logic --- crates/ev-revm/src/deploy.rs | 21 +++++++++++ crates/ev-revm/src/handler.rs | 36 +++++++++++++++--- crates/node/src/txpool.rs | 71 +++++++++++++++++++++++------------ 3 files changed, 100 insertions(+), 28 deletions(-) diff --git a/crates/ev-revm/src/deploy.rs b/crates/ev-revm/src/deploy.rs index 620697f..6cf0202 100644 --- a/crates/ev-revm/src/deploy.rs +++ b/crates/ev-revm/src/deploy.rs @@ -80,3 +80,24 @@ pub fn check_deploy_allowed( Err(DeployCheckError::NotAllowed) } } + +#[cfg(test)] +mod tests { + use super::*; + use alloy_primitives::address; + + #[test] + fn empty_allowlist_allows_any_caller() { + let settings = DeployAllowlistSettings::new(vec![], 0); + let caller = address!("0x00000000000000000000000000000000000000aa"); + assert!(settings.is_allowed(caller)); + } + + #[test] + fn check_deploy_allowed_with_empty_settings_allows() { + let settings = DeployAllowlistSettings::new(vec![], 0); + let caller = address!("0x00000000000000000000000000000000000000bb"); + let result = check_deploy_allowed(Some(&settings), caller, true, 0); + assert!(result.is_ok()); + } +} diff --git a/crates/ev-revm/src/handler.rs b/crates/ev-revm/src/handler.rs index e6f87d8..32fd52f 100644 --- a/crates/ev-revm/src/handler.rs +++ b/crates/ev-revm/src/handler.rs @@ -58,12 +58,13 @@ impl EvHandler { self.redirect } - const fn deploy_allowlist_for_block( - &self, - block_number: u64, - ) -> Option<&DeployAllowlistSettings> { + fn deploy_allowlist_for_block(&self, block_number: u64) -> Option<&DeployAllowlistSettings> { match self.deploy_allowlist.as_ref() { - Some(settings) if settings.is_active(block_number) => Some(settings), + Some(settings) + if settings.is_active(block_number) && !settings.allowlist().is_empty() => + { + Some(settings) + } _ => None, } } @@ -1334,6 +1335,31 @@ mod tests { assert!(matches!(result, Err(EVMError::Custom(_)))); } + #[test] + fn allow_deploy_when_allowlist_is_empty() { + let caller = address!("0x00000000000000000000000000000000000000cc"); + let allowlist = DeployAllowlistSettings::new(vec![], 0); + + let mut ctx = Context::mainnet().with_db(EmptyDB::default()); + ctx.block.number = U256::from(1); + ctx.cfg.spec = SpecId::CANCUN; + ctx.cfg.disable_nonce_check = true; + ctx.tx.caller = caller; + ctx.tx.kind = TxKind::Create; + ctx.tx.gas_limit = 1_000_000; + // gas_price=0 so no balance is required + ctx.tx.gas_price = 0; + + let mut evm = EvEvm::new(ctx, NoOpInspector, None); + let handler: TestHandler = EvHandler::new(None, Some(allowlist)); + + let result = handler.validate_against_state_and_deduct_caller(&mut evm); + assert!( + result.is_ok(), + "empty allowlist should allow any caller to deploy, got: {result:?}" + ); + } + fn setup_evm(redirect: BaseFeeRedirect, beneficiary: Address) -> (TestEvm, TestHandler) { let mut ctx = Context::mainnet().with_db(EmptyDB::default()); ctx.block.basefee = BASE_FEE; diff --git a/crates/node/src/txpool.rs b/crates/node/src/txpool.rs index efeff56..a1d6fa2 100644 --- a/crates/node/src/txpool.rs +++ b/crates/node/src/txpool.rs @@ -404,31 +404,34 @@ where Client: StateProviderFactory, { // Unified deploy allowlist check (covers both Ethereum and EvNode txs). + // empty allowlist = permissionless, skip enforcement if let Some(settings) = &self.deploy_allowlist { - let is_top_level_create = match pooled.transaction().inner() { - EvTxEnvelope::Ethereum(tx) => alloy_consensus::Transaction::is_create(tx), - EvTxEnvelope::EvNode(ref signed) => { - let tx = signed.tx(); - tx.calls.first().map(|c| c.to.is_create()).unwrap_or(false) + if !settings.allowlist().is_empty() { + let is_top_level_create = match pooled.transaction().inner() { + EvTxEnvelope::Ethereum(tx) => alloy_consensus::Transaction::is_create(tx), + EvTxEnvelope::EvNode(ref signed) => { + let tx = signed.tx(); + tx.calls.first().map(|c| c.to.is_create()).unwrap_or(false) + } + }; + let caller = pooled.transaction().signer(); + let block_number = self.inner.client().best_block_number().map_err( + |err: reth_provider::ProviderError| { + InvalidPoolTransactionError::other(EvTxPoolError::StateProvider( + err.to_string(), + )) + }, + )?; + if let Err(_e) = ev_revm::deploy::check_deploy_allowed( + Some(settings), + caller, + is_top_level_create, + block_number, + ) { + return Err(InvalidPoolTransactionError::other( + EvTxPoolError::DeployNotAllowed, + )); } - }; - let caller = pooled.transaction().signer(); - let block_number = self.inner.client().best_block_number().map_err( - |err: reth_provider::ProviderError| { - InvalidPoolTransactionError::other(EvTxPoolError::StateProvider( - err.to_string(), - )) - }, - )?; - if let Err(_e) = ev_revm::deploy::check_deploy_allowed( - Some(settings), - caller, - is_top_level_create, - block_number, - ) { - return Err(InvalidPoolTransactionError::other( - EvTxPoolError::DeployNotAllowed, - )); } } @@ -797,6 +800,28 @@ mod tests { ); } + #[test] + fn evnode_create_allowed_when_allowlist_is_empty() { + let settings = ev_revm::deploy::DeployAllowlistSettings::new(vec![], 0); + let validator = create_test_validator(Some(settings)); + + let gas_limit = 200_000u64; + let max_fee_per_gas = 1_000_000_000u128; + let signed_tx = create_non_sponsored_evnode_create_tx(gas_limit, max_fee_per_gas); + + let signer = Address::from([0x33u8; 20]); + let pooled = create_pooled_tx(signed_tx, signer); + + let sender_balance = *pooled.cost() + U256::from(1); + let mut state: Option> = None; + + let result = validator.validate_evnode(&pooled, sender_balance, &mut state); + assert!( + result.is_ok(), + "empty allowlist should allow any caller to deploy, got: {result:?}" + ); + } + /// Tests pool-level deploy allowlist rejection for `EvNode` CREATE when caller not allowlisted. #[test] fn evnode_create_rejected_when_not_allowlisted() { From 65874969648d4286b60ebd609ded2a60284b6894 Mon Sep 17 00:00:00 2001 From: chatton Date: Thu, 26 Feb 2026 12:53:29 +0000 Subject: [PATCH 2/3] test: adding additional test coverage --- crates/ev-revm/src/deploy.rs | 36 ++++++++++++++++++ crates/ev-revm/src/handler.rs | 72 +++++++++++++++++++++++++++++++++++ crates/node/src/txpool.rs | 67 ++++++++++++++++++++++++++++++++ 3 files changed, 175 insertions(+) diff --git a/crates/ev-revm/src/deploy.rs b/crates/ev-revm/src/deploy.rs index 6cf0202..d516d8f 100644 --- a/crates/ev-revm/src/deploy.rs +++ b/crates/ev-revm/src/deploy.rs @@ -100,4 +100,40 @@ mod tests { let result = check_deploy_allowed(Some(&settings), caller, true, 0); assert!(result.is_ok()); } + + #[test] + fn check_deploy_allowed_with_none_settings_allows() { + let caller = address!("0x00000000000000000000000000000000000000cc"); + let result = check_deploy_allowed(None, caller, true, 0); + assert!(result.is_ok()); + } + + #[test] + fn allowlisted_caller_is_allowed() { + let caller = address!("0x00000000000000000000000000000000000000aa"); + let settings = DeployAllowlistSettings::new(vec![caller], 0); + assert!(settings.is_allowed(caller)); + let result = check_deploy_allowed(Some(&settings), caller, true, 0); + assert!(result.is_ok()); + } + + #[test] + fn non_allowlisted_caller_is_denied() { + let allowed = address!("0x00000000000000000000000000000000000000aa"); + let caller = address!("0x00000000000000000000000000000000000000bb"); + let settings = DeployAllowlistSettings::new(vec![allowed], 0); + assert!(!settings.is_allowed(caller)); + let result = check_deploy_allowed(Some(&settings), caller, true, 0); + assert_eq!(result, Err(DeployCheckError::NotAllowed)); + } + + #[test] + fn call_tx_always_allowed_regardless_of_allowlist() { + let allowed = address!("0x00000000000000000000000000000000000000aa"); + let caller = address!("0x00000000000000000000000000000000000000bb"); + let settings = DeployAllowlistSettings::new(vec![allowed], 0); + // caller is not in the allowlist, but is_top_level_create=false so it's allowed + let result = check_deploy_allowed(Some(&settings), caller, false, 0); + assert!(result.is_ok()); + } } diff --git a/crates/ev-revm/src/handler.rs b/crates/ev-revm/src/handler.rs index 32fd52f..e154606 100644 --- a/crates/ev-revm/src/handler.rs +++ b/crates/ev-revm/src/handler.rs @@ -1360,6 +1360,78 @@ mod tests { ); } + #[test] + fn allow_deploy_when_allowlist_is_none() { + let caller = address!("0x00000000000000000000000000000000000000dd"); + + let mut ctx = Context::mainnet().with_db(EmptyDB::default()); + ctx.block.number = U256::from(1); + ctx.cfg.spec = SpecId::CANCUN; + ctx.cfg.disable_nonce_check = true; + ctx.tx.caller = caller; + ctx.tx.kind = TxKind::Create; + ctx.tx.gas_limit = 1_000_000; + ctx.tx.gas_price = 0; + + let mut evm = EvEvm::new(ctx, NoOpInspector, None); + let handler: TestHandler = EvHandler::new(None, None); + + let result = handler.validate_against_state_and_deduct_caller(&mut evm); + assert!( + result.is_ok(), + "no allowlist configured should allow any caller to deploy, got: {result:?}" + ); + } + + #[test] + fn allow_deploy_for_allowlisted_caller() { + let caller = address!("0x00000000000000000000000000000000000000ee"); + let allowlist = DeployAllowlistSettings::new(vec![caller], 0); + + let mut ctx = Context::mainnet().with_db(EmptyDB::default()); + ctx.block.number = U256::from(1); + ctx.cfg.spec = SpecId::CANCUN; + ctx.cfg.disable_nonce_check = true; + ctx.tx.caller = caller; + ctx.tx.kind = TxKind::Create; + ctx.tx.gas_limit = 1_000_000; + ctx.tx.gas_price = 0; + + let mut evm = EvEvm::new(ctx, NoOpInspector, None); + let handler: TestHandler = EvHandler::new(None, Some(allowlist)); + + let result = handler.validate_against_state_and_deduct_caller(&mut evm); + assert!( + result.is_ok(), + "allowlisted caller should be allowed to deploy, got: {result:?}" + ); + } + + #[test] + fn call_tx_allowed_for_non_allowlisted_caller() { + let allowed = address!("0x00000000000000000000000000000000000000aa"); + let caller = address!("0x00000000000000000000000000000000000000ff"); + let allowlist = DeployAllowlistSettings::new(vec![allowed], 0); + + let mut ctx = Context::mainnet().with_db(EmptyDB::default()); + ctx.block.number = U256::from(1); + ctx.cfg.spec = SpecId::CANCUN; + ctx.cfg.disable_nonce_check = true; + ctx.tx.caller = caller; + ctx.tx.kind = TxKind::Call(Address::ZERO); + ctx.tx.gas_limit = 1_000_000; + ctx.tx.gas_price = 0; + + let mut evm = EvEvm::new(ctx, NoOpInspector, None); + let handler: TestHandler = EvHandler::new(None, Some(allowlist)); + + let result = handler.validate_against_state_and_deduct_caller(&mut evm); + assert!( + result.is_ok(), + "CALL tx should be allowed regardless of allowlist, got: {result:?}" + ); + } + fn setup_evm(redirect: BaseFeeRedirect, beneficiary: Address) -> (TestEvm, TestHandler) { let mut ctx = Context::mainnet().with_db(EmptyDB::default()); ctx.block.basefee = BASE_FEE; diff --git a/crates/node/src/txpool.rs b/crates/node/src/txpool.rs index a1d6fa2..442664b 100644 --- a/crates/node/src/txpool.rs +++ b/crates/node/src/txpool.rs @@ -846,4 +846,71 @@ mod tests { assert!(matches!(err, InvalidPoolTransactionError::Other(_))); } } + + #[test] + fn evnode_create_allowed_when_allowlist_is_none() { + let validator = create_test_validator(None); + + let gas_limit = 200_000u64; + let max_fee_per_gas = 1_000_000_000u128; + let signed_tx = create_non_sponsored_evnode_create_tx(gas_limit, max_fee_per_gas); + + let signer = Address::from([0x44u8; 20]); + let pooled = create_pooled_tx(signed_tx, signer); + + let sender_balance = *pooled.cost() + U256::from(1); + let mut state: Option> = None; + + let result = validator.validate_evnode(&pooled, sender_balance, &mut state); + assert!( + result.is_ok(), + "no allowlist configured should allow any caller to deploy, got: {result:?}" + ); + } + + #[test] + fn evnode_create_allowed_for_allowlisted_caller() { + let signer = Address::from([0x55u8; 20]); + let settings = ev_revm::deploy::DeployAllowlistSettings::new(vec![signer], 0); + let validator = create_test_validator(Some(settings)); + + let gas_limit = 200_000u64; + let max_fee_per_gas = 1_000_000_000u128; + let signed_tx = create_non_sponsored_evnode_create_tx(gas_limit, max_fee_per_gas); + + let pooled = create_pooled_tx(signed_tx, signer); + + let sender_balance = *pooled.cost() + U256::from(1); + let mut state: Option> = None; + + let result = validator.validate_evnode(&pooled, sender_balance, &mut state); + assert!( + result.is_ok(), + "allowlisted caller should be allowed to deploy, got: {result:?}" + ); + } + + #[test] + fn evnode_call_allowed_for_non_allowlisted_caller() { + let allowed = Address::from([0x11u8; 20]); + let settings = ev_revm::deploy::DeployAllowlistSettings::new(vec![allowed], 0); + let validator = create_test_validator(Some(settings)); + + let gas_limit = 21_000u64; + let max_fee_per_gas = 1_000_000_000u128; + // CALL tx, not CREATE + let signed_tx = create_non_sponsored_evnode_tx(gas_limit, max_fee_per_gas); + + let signer = Address::from([0x66u8; 20]); // not allowlisted + let pooled = create_pooled_tx(signed_tx, signer); + + let sender_balance = *pooled.cost() + U256::from(1); + let mut state: Option> = None; + + let result = validator.validate_evnode(&pooled, sender_balance, &mut state); + assert!( + result.is_ok(), + "CALL tx should be allowed regardless of allowlist, got: {result:?}" + ); + } } From 011dd2ee6b22ba3bf37e3b394a4bcfb80adf44d2 Mon Sep 17 00:00:00 2001 From: chatton Date: Thu, 26 Feb 2026 13:00:45 +0000 Subject: [PATCH 3/3] chore: revert code logic, keep tests --- crates/ev-revm/src/handler.rs | 11 ++++---- crates/node/src/txpool.rs | 49 ++++++++++++++++------------------- 2 files changed, 28 insertions(+), 32 deletions(-) diff --git a/crates/ev-revm/src/handler.rs b/crates/ev-revm/src/handler.rs index e154606..0751e43 100644 --- a/crates/ev-revm/src/handler.rs +++ b/crates/ev-revm/src/handler.rs @@ -58,13 +58,12 @@ impl EvHandler { self.redirect } - fn deploy_allowlist_for_block(&self, block_number: u64) -> Option<&DeployAllowlistSettings> { + const fn deploy_allowlist_for_block( + &self, + block_number: u64, + ) -> Option<&DeployAllowlistSettings> { match self.deploy_allowlist.as_ref() { - Some(settings) - if settings.is_active(block_number) && !settings.allowlist().is_empty() => - { - Some(settings) - } + Some(settings) if settings.is_active(block_number) => Some(settings), _ => None, } } diff --git a/crates/node/src/txpool.rs b/crates/node/src/txpool.rs index 442664b..8eeb831 100644 --- a/crates/node/src/txpool.rs +++ b/crates/node/src/txpool.rs @@ -404,34 +404,31 @@ where Client: StateProviderFactory, { // Unified deploy allowlist check (covers both Ethereum and EvNode txs). - // empty allowlist = permissionless, skip enforcement if let Some(settings) = &self.deploy_allowlist { - if !settings.allowlist().is_empty() { - let is_top_level_create = match pooled.transaction().inner() { - EvTxEnvelope::Ethereum(tx) => alloy_consensus::Transaction::is_create(tx), - EvTxEnvelope::EvNode(ref signed) => { - let tx = signed.tx(); - tx.calls.first().map(|c| c.to.is_create()).unwrap_or(false) - } - }; - let caller = pooled.transaction().signer(); - let block_number = self.inner.client().best_block_number().map_err( - |err: reth_provider::ProviderError| { - InvalidPoolTransactionError::other(EvTxPoolError::StateProvider( - err.to_string(), - )) - }, - )?; - if let Err(_e) = ev_revm::deploy::check_deploy_allowed( - Some(settings), - caller, - is_top_level_create, - block_number, - ) { - return Err(InvalidPoolTransactionError::other( - EvTxPoolError::DeployNotAllowed, - )); + let is_top_level_create = match pooled.transaction().inner() { + EvTxEnvelope::Ethereum(tx) => alloy_consensus::Transaction::is_create(tx), + EvTxEnvelope::EvNode(ref signed) => { + let tx = signed.tx(); + tx.calls.first().map(|c| c.to.is_create()).unwrap_or(false) } + }; + let caller = pooled.transaction().signer(); + let block_number = self.inner.client().best_block_number().map_err( + |err: reth_provider::ProviderError| { + InvalidPoolTransactionError::other(EvTxPoolError::StateProvider( + err.to_string(), + )) + }, + )?; + if let Err(_e) = ev_revm::deploy::check_deploy_allowed( + Some(settings), + caller, + is_top_level_create, + block_number, + ) { + return Err(InvalidPoolTransactionError::other( + EvTxPoolError::DeployNotAllowed, + )); } }