Skip to content

Commit 7e46a70

Browse files
committed
Better EIP-7702 ergonomics (rust-ethereum#325)
* feat: ✨ make authorizing_address in authorization list optional * test: ✅ test None authorizing_address * style: 🎨 fmt
1 parent 06b7b59 commit 7e46a70

File tree

3 files changed

+133
-10
lines changed

3 files changed

+133
-10
lines changed

gasometer/src/lib.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -335,7 +335,7 @@ impl<'config> Gasometer<'config> {
335335
pub fn call_transaction_cost(
336336
data: &[u8],
337337
access_list: &[(H160, Vec<H256>)],
338-
authorization_list: &[(U256, H160, U256, H160)],
338+
authorization_list: &[(U256, H160, U256, Option<H160>)],
339339
) -> TransactionCost {
340340
let zero_data_len = data.iter().filter(|v| **v == 0).count();
341341
let non_zero_data_len = data.len() - zero_data_len;
@@ -358,7 +358,7 @@ pub fn call_transaction_cost(
358358
pub fn create_transaction_cost(
359359
data: &[u8],
360360
access_list: &[(H160, Vec<H256>)],
361-
authorization_list: &[(U256, H160, U256, H160)],
361+
authorization_list: &[(U256, H160, U256, Option<H160>)],
362362
) -> TransactionCost {
363363
let zero_data_len = data.iter().filter(|v| **v == 0).count();
364364
let non_zero_data_len = data.len() - zero_data_len;

src/executor/stack/executor.rs

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -423,7 +423,7 @@ impl<'config, 'precompiles, S: StackState<'config>, P: PrecompileSet>
423423
&mut self,
424424
init_code: &[u8],
425425
access_list: &[(H160, Vec<H256>)],
426-
authorization_list: &[(U256, H160, U256, H160)],
426+
authorization_list: &[(U256, H160, U256, Option<H160>)],
427427
) -> Result<(), ExitError> {
428428
let transaction_cost =
429429
gasometer::create_transaction_cost(init_code, access_list, authorization_list);
@@ -455,7 +455,7 @@ impl<'config, 'precompiles, S: StackState<'config>, P: PrecompileSet>
455455
init_code: Vec<u8>,
456456
gas_limit: u64,
457457
access_list: Vec<(H160, Vec<H256>)>, // See EIP-2930
458-
authorization_list: Vec<(U256, H160, U256, H160)>, // See EIP-7702
458+
authorization_list: Vec<(U256, H160, U256, Option<H160>)>, // See EIP-7702
459459
) -> (ExitReason, Vec<u8>) {
460460
event!(TransactCreate {
461461
caller,
@@ -513,7 +513,7 @@ impl<'config, 'precompiles, S: StackState<'config>, P: PrecompileSet>
513513
salt: H256,
514514
gas_limit: u64,
515515
access_list: Vec<(H160, Vec<H256>)>, // See EIP-2930
516-
authorization_list: Vec<(U256, H160, U256, H160)>, // See EIP-7702
516+
authorization_list: Vec<(U256, H160, U256, Option<H160>)>, // See EIP-7702
517517
) -> (ExitReason, Vec<u8>) {
518518
if let Some(limit) = self.config.max_initcode_size {
519519
if init_code.len() > limit {
@@ -581,7 +581,7 @@ impl<'config, 'precompiles, S: StackState<'config>, P: PrecompileSet>
581581
init_code: Vec<u8>,
582582
gas_limit: u64,
583583
access_list: Vec<(H160, Vec<H256>)>, // See EIP-2930
584-
authorization_list: Vec<(U256, H160, U256, H160)>, // See EIP-7702
584+
authorization_list: Vec<(U256, H160, U256, Option<H160>)>, // See EIP-7702
585585
contract_address: H160,
586586
) -> (ExitReason, Vec<u8>) {
587587
event!(TransactCreate {
@@ -647,7 +647,7 @@ impl<'config, 'precompiles, S: StackState<'config>, P: PrecompileSet>
647647
data: Vec<u8>,
648648
gas_limit: u64,
649649
access_list: Vec<(H160, Vec<H256>)>,
650-
authorization_list: Vec<(U256, H160, U256, H160)>,
650+
authorization_list: Vec<(U256, H160, U256, Option<H160>)>,
651651
) -> (ExitReason, Vec<u8>) {
652652
event!(TransactCall {
653653
caller,
@@ -794,7 +794,7 @@ impl<'config, 'precompiles, S: StackState<'config>, P: PrecompileSet>
794794
/// This processes the authorization tuples and applies delegations as specified
795795
pub fn initialize_with_authorization_list(
796796
&mut self,
797-
authorization_list: Vec<(U256, H160, U256, H160)>,
797+
authorization_list: Vec<(U256, H160, U256, Option<H160>)>,
798798
) -> Result<(), ExitError> {
799799
for authorization in authorization_list {
800800
let (chain_id, delegation_address, nonce, authorizing_address) = authorization;
@@ -809,6 +809,11 @@ impl<'config, 'precompiles, S: StackState<'config>, P: PrecompileSet>
809809
continue;
810810
}
811811

812+
// Skip if authorizing_address is None
813+
let Some(authorizing_address) = authorizing_address else {
814+
continue;
815+
};
816+
812817
// Add authority to accessed_addresses, as defined in EIP-2929
813818
if self.config.increase_state_access_gas {
814819
self.state

tests/eip7702.rs

Lines changed: 120 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,13 @@ fn create_authorization(
1616
delegation_address: H160,
1717
nonce: U256,
1818
authorizing_address: H160,
19-
) -> (U256, H160, U256, H160) {
20-
(chain_id, delegation_address, nonce, authorizing_address)
19+
) -> (U256, H160, U256, Option<H160>) {
20+
(
21+
chain_id,
22+
delegation_address,
23+
nonce,
24+
Some(authorizing_address),
25+
)
2126
}
2227

2328
/// Create a test vicinity for EIP-7702 tests
@@ -4075,3 +4080,116 @@ fn test_12_2_signature_replay_protection() {
40754080
// Nonce should still be 1 (no increment from failed authorization)
40764081
assert_eq!(executor.state().basic(authorizing).nonce, U256::from(1));
40774082
}
4083+
4084+
#[test]
4085+
fn test_none_authorizing_address_rejection() {
4086+
// Test: Authorization with None authorizing_address should be skipped
4087+
// We compare against a valid authorization to show the difference
4088+
let caller = H160::from_slice(&[1u8; 20]);
4089+
let implementation = H160::from_slice(&[2u8; 20]);
4090+
let authorizing = H160::from_slice(&[3u8; 20]);
4091+
let target = H160::from_slice(&[4u8; 20]);
4092+
4093+
let config = Config::pectra();
4094+
4095+
let mut state = BTreeMap::new();
4096+
state.insert(
4097+
caller,
4098+
evm::backend::MemoryAccount {
4099+
nonce: U256::zero(),
4100+
balance: U256::from(10_000_000),
4101+
storage: BTreeMap::new(),
4102+
code: Vec::new(),
4103+
},
4104+
);
4105+
4106+
// Create implementation account with code
4107+
state.insert(
4108+
implementation,
4109+
evm::backend::MemoryAccount {
4110+
nonce: U256::zero(),
4111+
balance: U256::zero(),
4112+
storage: BTreeMap::new(),
4113+
code: vec![0x60, 0x42, 0x60, 0x00, 0x52, 0x60, 0x20, 0x60, 0x00, 0xf3],
4114+
},
4115+
);
4116+
4117+
// Create empty authorizing account (suitable for delegation)
4118+
state.insert(
4119+
authorizing,
4120+
evm::backend::MemoryAccount {
4121+
nonce: U256::zero(),
4122+
balance: U256::zero(),
4123+
storage: BTreeMap::new(),
4124+
code: Vec::new(),
4125+
},
4126+
);
4127+
4128+
let vicinity = create_test_vicinity();
4129+
let mut backend = MemoryBackend::new(&vicinity, state);
4130+
4131+
// Test 1: Transaction with None authorizing_address
4132+
{
4133+
let metadata = evm::executor::stack::StackSubstateMetadata::new(1000000, &config);
4134+
let state = evm::executor::stack::MemoryStackState::new(metadata, &mut backend);
4135+
let precompiles = BTreeMap::new();
4136+
let mut executor = StackExecutor::new_with_precompiles(state, &config, &precompiles);
4137+
4138+
let authorization_with_none = (
4139+
U256::from(1), // chain_id matches vicinity
4140+
implementation, // delegation_address
4141+
U256::zero(), // nonce
4142+
None, // authorizing_address is None
4143+
);
4144+
4145+
let (exit_reason, _) = executor.transact_call(
4146+
caller,
4147+
target,
4148+
U256::zero(),
4149+
Vec::new(),
4150+
100_000,
4151+
Vec::new(),
4152+
vec![authorization_with_none],
4153+
);
4154+
4155+
assert_eq!(exit_reason, ExitReason::Succeed(evm::ExitSucceed::Stopped));
4156+
4157+
// Authorizing account should be unchanged (no delegation applied)
4158+
let authorizing_basic = executor.state().basic(authorizing);
4159+
assert_eq!(authorizing_basic.nonce, U256::zero());
4160+
assert!(executor.code(authorizing).is_empty());
4161+
}
4162+
4163+
// Test 2: Same transaction but with valid authorizing_address for comparison
4164+
{
4165+
let metadata = evm::executor::stack::StackSubstateMetadata::new(1000000, &config);
4166+
let state = evm::executor::stack::MemoryStackState::new(metadata, &mut backend);
4167+
let precompiles = BTreeMap::new();
4168+
let mut executor = StackExecutor::new_with_precompiles(state, &config, &precompiles);
4169+
4170+
let authorization_with_some = (
4171+
U256::from(1), // chain_id matches vicinity
4172+
implementation, // delegation_address
4173+
U256::zero(), // nonce
4174+
Some(authorizing), // authorizing_address is Some
4175+
);
4176+
4177+
let (exit_reason, _) = executor.transact_call(
4178+
caller,
4179+
target,
4180+
U256::zero(),
4181+
Vec::new(),
4182+
100_000,
4183+
Vec::new(),
4184+
vec![authorization_with_some],
4185+
);
4186+
4187+
assert_eq!(exit_reason, ExitReason::Succeed(evm::ExitSucceed::Stopped));
4188+
4189+
// Authorizing account should now have delegation designator
4190+
let authorizing_basic = executor.state().basic(authorizing);
4191+
assert_eq!(authorizing_basic.nonce, U256::from(1)); // Nonce incremented
4192+
let expected_delegation = evm_core::create_delegation_designator(implementation);
4193+
assert_eq!(executor.code(authorizing), expected_delegation);
4194+
}
4195+
}

0 commit comments

Comments
 (0)