Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
57 changes: 57 additions & 0 deletions crates/ev-revm/src/deploy.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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());
}
}
97 changes: 97 additions & 0 deletions crates/ev-revm/src/handler.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
89 changes: 89 additions & 0 deletions crates/node/src/txpool.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<Box<dyn AccountInfoReader + Send>> = 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]
Expand Down Expand Up @@ -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<Box<dyn AccountInfoReader + Send>> = 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<Box<dyn AccountInfoReader + Send>> = 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<Box<dyn AccountInfoReader + Send>> = 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:?}"
);
}
}