diff --git a/crates/ev-revm/src/deploy.rs b/crates/ev-revm/src/deploy.rs index 620697f..d516d8f 100644 --- a/crates/ev-revm/src/deploy.rs +++ b/crates/ev-revm/src/deploy.rs @@ -80,3 +80,60 @@ 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()); + } + + #[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 e6f87d8..0751e43 100644 --- a/crates/ev-revm/src/handler.rs +++ b/crates/ev-revm/src/handler.rs @@ -1334,6 +1334,103 @@ 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:?}" + ); + } + + #[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 b39c75c..7d7eeb6 100644 --- a/crates/node/src/txpool.rs +++ b/crates/node/src/txpool.rs @@ -809,6 +809,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 that non-sponsored `EvNode` transactions with non-zero call values /// require the sender to cover both gas and value. #[test] @@ -881,4 +903,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:?}" + ); + } }