Skip to content

Commit f551baf

Browse files
authored
feat(EIP-7702): devnet-4 changes (#1821)
* feat(EIP-7702): devnet-4 changes * add new test suite * fix test * check parity in revme, remove invalid auth tx tests * fix clippy
1 parent 4f09399 commit f551baf

File tree

95 files changed

+4284
-1857
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

95 files changed

+4284
-1857
lines changed

bins/revme/src/cmd/statetest/models/mod.rs

+8-1
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,9 @@ use deserializer::*;
66
pub use eip7702::TxEip7702;
77
pub use spec::SpecName;
88

9-
use revm::primitives::{AccessList, Address, AuthorizationList, Bytes, HashMap, B256, U256};
9+
use revm::primitives::{
10+
alloy_primitives::Parity, AccessList, Address, AuthorizationList, Bytes, HashMap, B256, U256,
11+
};
1012
use serde::{Deserialize, Serialize};
1113
use std::collections::BTreeMap;
1214

@@ -60,6 +62,11 @@ impl Test {
6062
if txbytes.first() == Some(&0x04) {
6163
let mut txbytes = &txbytes[1..];
6264
let tx = TxEip7702::decode(&mut txbytes)?;
65+
if let Parity::Eip155(parity) = tx.signature.v() {
66+
if parity < u8::MAX as u64 {
67+
return Err(alloy_rlp::Error::Custom("Invalid parity value"));
68+
}
69+
}
6370
return Ok(Some(
6471
AuthorizationList::Signed(tx.authorization_list).into_recovered(),
6572
));

crates/primitives/src/eip7702.rs

+2-2
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ pub use bytecode::{
1111
};
1212

1313
// Base cost of updating authorized account.
14-
pub const PER_AUTH_BASE_COST: u64 = 2500;
14+
pub const PER_AUTH_BASE_COST: u64 = 12500;
1515

1616
/// Cost of creating authorized account that was previously empty.
1717
pub const PER_EMPTY_ACCOUNT_COST: u64 = 25000;
@@ -20,7 +20,7 @@ pub const PER_EMPTY_ACCOUNT_COST: u64 = 25000;
2020
/// to EIP-2 should have an S value less than or equal to this.
2121
///
2222
/// `57896044618658097711785492504343953926418782139537452191302581570759080747168`
23-
const SECP256K1N_HALF: U256 = U256::from_be_bytes([
23+
pub const SECP256K1N_HALF: U256 = U256::from_be_bytes([
2424
0x7F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
2525
0x5D, 0x57, 0x6E, 0x73, 0x57, 0xA4, 0x50, 0x1D, 0xDF, 0xE9, 0x2F, 0x46, 0x68, 0x1B, 0x20, 0xA0,
2626
]);

crates/primitives/src/eip7702/authorization_list.rs

+14-36
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,12 @@
11
pub use alloy_eip7702::{Authorization, SignedAuthorization};
22
pub use alloy_primitives::{Parity, Signature};
33

4-
use super::SECP256K1N_HALF;
54
use crate::Address;
65
use core::{fmt, ops::Deref};
76
use std::{boxed::Box, vec::Vec};
87

8+
use super::SECP256K1N_HALF;
9+
910
/// Authorization list for EIP-7702 transaction type.
1011
#[derive(Clone, Debug, Eq, PartialEq)]
1112
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
@@ -35,40 +36,6 @@ impl AuthorizationList {
3536
}
3637
}
3738

38-
/// Returns true if the authorization list is valid.
39-
pub fn is_valid(&self, _chain_id: u64) -> Result<(), InvalidAuthorization> {
40-
let validate = |auth: &SignedAuthorization| -> Result<(), InvalidAuthorization> {
41-
// TODO Eip7702. Check chain_id
42-
// Pending: https://github.com/ethereum/EIPs/pull/8833/files
43-
// let auth_chain_id: u64 = auth.chain_id().try_into().unwrap_or(u64::MAX);
44-
// if auth_chain_id != 0 && auth_chain_id != chain_id {
45-
// return Err(InvalidAuthorization::InvalidChainId);
46-
// }
47-
48-
// Check y_parity, Parity::Parity means that it was 0 or 1.
49-
if !matches!(auth.signature().v(), Parity::Parity(_)) {
50-
return Err(InvalidAuthorization::InvalidYParity);
51-
}
52-
53-
// Check s-value
54-
if auth.signature().s() > SECP256K1N_HALF {
55-
return Err(InvalidAuthorization::Eip2InvalidSValue);
56-
}
57-
58-
Ok(())
59-
};
60-
61-
match self {
62-
Self::Signed(signed) => signed.iter().try_for_each(validate)?,
63-
Self::Recovered(recovered) => recovered
64-
.iter()
65-
.map(|recovered| &recovered.inner)
66-
.try_for_each(validate)?,
67-
};
68-
69-
Ok(())
70-
}
71-
7239
/// Return empty authorization list.
7340
pub fn empty() -> Self {
7441
Self::Recovered(Vec::new())
@@ -114,7 +81,18 @@ impl RecoveredAuthorization {
11481
/// Get the `authority` for the authorization.
11582
///
11683
/// If this is `None`, then the authority could not be recovered.
117-
pub const fn authority(&self) -> Option<Address> {
84+
pub fn authority(&self) -> Option<Address> {
85+
let signature = self.inner.signature();
86+
87+
// Check s-value
88+
if signature.s() > SECP256K1N_HALF {
89+
return None;
90+
}
91+
92+
// Check y_parity, Parity::Parity means that it was 0 or 1.
93+
if !matches!(signature.v(), Parity::Parity(_)) {
94+
return None;
95+
}
11896
self.authority
11997
}
12098

crates/primitives/src/env.rs

-3
Original file line numberDiff line numberDiff line change
@@ -202,9 +202,6 @@ impl Env {
202202
return Err(InvalidTransaction::EmptyAuthorizationList);
203203
}
204204

205-
// Check validity of authorization_list
206-
auth_list.is_valid(self.cfg.chain_id)?;
207-
208205
// Check if other fields are unset.
209206
if self.tx.max_fee_per_blob_gas.is_some() || !self.tx.blob_hashes.is_empty() {
210207
return Err(InvalidTransaction::AuthorizationListInvalidFields);

crates/revm/benches/bench.rs

+2-2
Original file line numberDiff line numberDiff line change
@@ -82,7 +82,7 @@ fn transfer(c: &mut Criterion) {
8282
}
8383

8484
fn bench_transact<EXT>(g: &mut BenchmarkGroup<'_, WallTime>, evm: &mut Evm<'_, EXT, BenchmarkDB>) {
85-
let state = match evm.context.evm.db.0 {
85+
let state = match evm.context.evm.db.bytecode {
8686
Bytecode::LegacyRaw(_) => "raw",
8787
Bytecode::LegacyAnalyzed(_) => "analysed",
8888
Bytecode::Eof(_) => "eof",
@@ -96,7 +96,7 @@ fn bench_eval(g: &mut BenchmarkGroup<'_, WallTime>, evm: &mut Evm<'static, (), B
9696
g.bench_function("eval", |b| {
9797
let contract = Contract {
9898
input: evm.context.evm.env.tx.data.clone(),
99-
bytecode: to_analysed(evm.context.evm.db.0.clone()),
99+
bytecode: to_analysed(evm.context.evm.db.bytecode.clone()),
100100
..Default::default()
101101
};
102102
let mut shared_memory = SharedMemory::new();

crates/revm/src/db/in_memory_db.rs

+27-6
Original file line numberDiff line numberDiff line change
@@ -360,28 +360,49 @@ impl AccountState {
360360
///
361361
/// Any other address will return an empty account.
362362
#[derive(Debug, Default, Clone)]
363-
pub struct BenchmarkDB(pub Bytecode, B256);
363+
pub struct BenchmarkDB {
364+
pub bytecode: Bytecode,
365+
pub hash: B256,
366+
pub target: Address,
367+
pub caller: Address,
368+
}
364369

365370
impl BenchmarkDB {
371+
/// Create a new benchmark database with the given bytecode.
366372
pub fn new_bytecode(bytecode: Bytecode) -> Self {
367373
let hash = bytecode.hash_slow();
368-
Self(bytecode, hash)
374+
Self {
375+
bytecode,
376+
hash,
377+
target: Address::ZERO,
378+
caller: Address::with_last_byte(1),
379+
}
380+
}
381+
382+
/// Change the caller address for the benchmark.
383+
pub fn with_caller(self, caller: Address) -> Self {
384+
Self { caller, ..self }
385+
}
386+
387+
/// Change the target address for the benchmark.
388+
pub fn with_target(self, target: Address) -> Self {
389+
Self { target, ..self }
369390
}
370391
}
371392

372393
impl Database for BenchmarkDB {
373394
type Error = Infallible;
374395
/// Get basic account information.
375396
fn basic(&mut self, address: Address) -> Result<Option<AccountInfo>, Self::Error> {
376-
if address == Address::ZERO {
397+
if address == self.target {
377398
return Ok(Some(AccountInfo {
378399
nonce: 1,
379400
balance: U256::from(10000000),
380-
code: Some(self.0.clone()),
381-
code_hash: self.1,
401+
code: Some(self.bytecode.clone()),
402+
code_hash: self.hash,
382403
}));
383404
}
384-
if address == Address::with_last_byte(1) {
405+
if address == self.caller {
385406
return Ok(Some(AccountInfo {
386407
nonce: 0,
387408
balance: U256::from(10000000),

crates/revm/src/evm.rs

+2-2
Original file line numberDiff line numberDiff line change
@@ -402,15 +402,15 @@ mod tests {
402402

403403
#[test]
404404
fn sanity_eip7702_tx() {
405-
let delegate = address!("0000000000000000000000000000000000000000");
406405
let caller = address!("0000000000000000000000000000000000000001");
406+
let delegate = address!("0000000000000000000000000000000000000002");
407407
let auth = address!("0000000000000000000000000000000000000100");
408408

409409
let bytecode = Bytecode::new_legacy([PUSH1, 0x01, PUSH1, 0x01, SSTORE].into());
410410

411411
let mut evm = Evm::builder()
412412
.with_spec_id(SpecId::PRAGUE)
413-
.with_db(BenchmarkDB::new_bytecode(bytecode))
413+
.with_db(BenchmarkDB::new_bytecode(bytecode).with_target(delegate))
414414
.modify_tx_env(|tx| {
415415
tx.authorization_list = Some(
416416
vec![RecoveredAuthorization::new_unchecked(

crates/revm/src/handler/mainnet/pre_execution.rs

+28-16
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ use crate::{
88
db::Database,
99
eip7702, Account, Bytecode, EVMError, Env, Spec,
1010
SpecId::{CANCUN, PRAGUE, SHANGHAI},
11-
TxKind, BLOCKHASH_STORAGE_ADDRESS, U256,
11+
TxKind, BLOCKHASH_STORAGE_ADDRESS, KECCAK_EMPTY, U256,
1212
},
1313
Context, ContextPrecompiles,
1414
};
@@ -114,51 +114,63 @@ pub fn apply_eip7702_auth_list<SPEC: Spec, EXT, DB: Database>(
114114

115115
let mut refunded_accounts = 0;
116116
for authorization in authorization_list.recovered_iter() {
117-
// 1. recover authority and authorized addresses.
118-
// authority = ecrecover(keccak(MAGIC || rlp([chain_id, address, nonce])), y_parity, r, s]
119-
let Some(authority) = authorization.authority() else {
120-
continue;
121-
};
122-
123-
// 2. Verify the chain id is either 0 or the chain's current ID.
117+
// 1. Verify the chain id is either 0 or the chain's current ID.
124118
if !authorization.chain_id().is_zero()
125119
&& authorization.chain_id() != U256::from(context.evm.inner.env.cfg.chain_id)
126120
{
127121
continue;
128122
}
129123

124+
// 2. Verify the `nonce` is less than `2**64 - 1`.
125+
if authorization.nonce() == u64::MAX {
126+
continue;
127+
}
128+
129+
// recover authority and authorized addresses.
130+
// 3. `authority = ecrecover(keccak(MAGIC || rlp([chain_id, address, nonce])), y_parity, r, s]`
131+
let Some(authority) = authorization.authority() else {
132+
continue;
133+
};
134+
130135
// warm authority account and check nonce.
131-
// 3. Add authority to accessed_addresses (as defined in EIP-2929.)
136+
// 4. Add `authority` to `accessed_addresses` (as defined in [EIP-2929](./eip-2929.md).)
132137
let mut authority_acc = context
133138
.evm
134139
.inner
135140
.journaled_state
136141
.load_code(authority, &mut context.evm.inner.db)?;
137142

138-
// 4. Verify the code of authority is either empty or already delegated.
143+
// 5. Verify the code of `authority` is either empty or already delegated.
139144
if let Some(bytecode) = &authority_acc.info.code {
140145
// if it is not empty and it is not eip7702
141146
if !bytecode.is_empty() && !bytecode.is_eip7702() {
142147
continue;
143148
}
144149
}
145150

146-
// 5. Verify the nonce of authority is equal to nonce.
151+
// 6. Verify the nonce of `authority` is equal to `nonce`. In case `authority` does not exist in the trie, verify that `nonce` is equal to `0`.
147152
if authorization.nonce() != authority_acc.info.nonce {
148153
continue;
149154
}
150155

151-
// 6. Refund the sender PER_EMPTY_ACCOUNT_COST - PER_AUTH_BASE_COST gas if authority exists in the trie.
156+
// 7. Add `PER_EMPTY_ACCOUNT_COST - PER_AUTH_BASE_COST` gas to the global refund counter if `authority` exists in the trie.
152157
if !authority_acc.is_empty() {
153158
refunded_accounts += 1;
154159
}
155160

156-
// 7. Set the code of authority to be 0xef0100 || address. This is a delegation designation.
157-
let bytecode = Bytecode::new_eip7702(authorization.address);
158-
authority_acc.info.code_hash = bytecode.hash_slow();
161+
// 8. Set the code of `authority` to be `0xef0100 || address`. This is a delegation designation.
162+
// * As a special case, if `address` is `0x0000000000000000000000000000000000000000` do not write the designation. Clear the accounts code and reset the account's code hash to the empty hash `0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470`.
163+
let (bytecode, hash) = if authorization.address.is_zero() {
164+
(Bytecode::default(), KECCAK_EMPTY)
165+
} else {
166+
let bytecode = Bytecode::new_eip7702(authorization.address);
167+
let hash = bytecode.hash_slow();
168+
(bytecode, hash)
169+
};
170+
authority_acc.info.code_hash = hash;
159171
authority_acc.info.code = Some(bytecode);
160172

161-
// 8. Increase the nonce of authority by one.
173+
// 9. Increase the nonce of `authority` by one.
162174
authority_acc.info.nonce = authority_acc.info.nonce.saturating_add(1);
163175
authority_acc.mark_touch();
164176
}

tests/prague_suite/state_tests/prague/eip2537_bls_12_381_precompiles/bls12_g1add/call_types.json

+3-3
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,7 @@
5959
"comment": "`execution-spec-tests` generated test",
6060
"filling-transition-tool": "ethereumjs t8n v1",
6161
"description": "Test function documentation:\n\n Test the BLS12_G1ADD precompile using different call types.",
62-
"url": "https://github.com/ethereum/execution-spec-tests/blob/pectra-devnet-3@v1.4.0/tests/prague/eip2537_bls_12_381_precompiles/test_bls12_g1add.py#L204",
62+
"url": "https://github.com/ethereum/execution-spec-tests/blob/pectra-devnet-4@v1.0.0/tests/prague/eip2537_bls_12_381_precompiles/test_bls12_g1add.py#L204",
6363
"reference-spec": "https://github.com/ethereum/EIPs/blob/master/EIPS/eip-2537.md",
6464
"reference-spec-version": "cd0f016ad0c4c68b8b1f5c502ef61ab9353b6e5e"
6565
}
@@ -124,7 +124,7 @@
124124
"comment": "`execution-spec-tests` generated test",
125125
"filling-transition-tool": "ethereumjs t8n v1",
126126
"description": "Test function documentation:\n\n Test the BLS12_G1ADD precompile using different call types.",
127-
"url": "https://github.com/ethereum/execution-spec-tests/blob/pectra-devnet-3@v1.4.0/tests/prague/eip2537_bls_12_381_precompiles/test_bls12_g1add.py#L204",
127+
"url": "https://github.com/ethereum/execution-spec-tests/blob/pectra-devnet-4@v1.0.0/tests/prague/eip2537_bls_12_381_precompiles/test_bls12_g1add.py#L204",
128128
"reference-spec": "https://github.com/ethereum/EIPs/blob/master/EIPS/eip-2537.md",
129129
"reference-spec-version": "cd0f016ad0c4c68b8b1f5c502ef61ab9353b6e5e"
130130
}
@@ -189,7 +189,7 @@
189189
"comment": "`execution-spec-tests` generated test",
190190
"filling-transition-tool": "ethereumjs t8n v1",
191191
"description": "Test function documentation:\n\n Test the BLS12_G1ADD precompile using different call types.",
192-
"url": "https://github.com/ethereum/execution-spec-tests/blob/pectra-devnet-3@v1.4.0/tests/prague/eip2537_bls_12_381_precompiles/test_bls12_g1add.py#L204",
192+
"url": "https://github.com/ethereum/execution-spec-tests/blob/pectra-devnet-4@v1.0.0/tests/prague/eip2537_bls_12_381_precompiles/test_bls12_g1add.py#L204",
193193
"reference-spec": "https://github.com/ethereum/EIPs/blob/master/EIPS/eip-2537.md",
194194
"reference-spec-version": "cd0f016ad0c4c68b8b1f5c502ef61ab9353b6e5e"
195195
}

tests/prague_suite/state_tests/prague/eip2537_bls_12_381_precompiles/bls12_g1add/gas.json

+2-2
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,7 @@
5959
"comment": "`execution-spec-tests` generated test",
6060
"filling-transition-tool": "ethereumjs t8n v1",
6161
"description": "Test function documentation:\n\n Test the BLS12_G1ADD precompile gas requirements.",
62-
"url": "https://github.com/ethereum/execution-spec-tests/blob/pectra-devnet-3@v1.4.0/tests/prague/eip2537_bls_12_381_precompiles/test_bls12_g1add.py#L170",
62+
"url": "https://github.com/ethereum/execution-spec-tests/blob/pectra-devnet-4@v1.0.0/tests/prague/eip2537_bls_12_381_precompiles/test_bls12_g1add.py#L170",
6363
"reference-spec": "https://github.com/ethereum/EIPs/blob/master/EIPS/eip-2537.md",
6464
"reference-spec-version": "cd0f016ad0c4c68b8b1f5c502ef61ab9353b6e5e"
6565
}
@@ -124,7 +124,7 @@
124124
"comment": "`execution-spec-tests` generated test",
125125
"filling-transition-tool": "ethereumjs t8n v1",
126126
"description": "Test function documentation:\n\n Test the BLS12_G1ADD precompile gas requirements.",
127-
"url": "https://github.com/ethereum/execution-spec-tests/blob/pectra-devnet-3@v1.4.0/tests/prague/eip2537_bls_12_381_precompiles/test_bls12_g1add.py#L170",
127+
"url": "https://github.com/ethereum/execution-spec-tests/blob/pectra-devnet-4@v1.0.0/tests/prague/eip2537_bls_12_381_precompiles/test_bls12_g1add.py#L170",
128128
"reference-spec": "https://github.com/ethereum/EIPs/blob/master/EIPS/eip-2537.md",
129129
"reference-spec-version": "cd0f016ad0c4c68b8b1f5c502ef61ab9353b6e5e"
130130
}

0 commit comments

Comments
 (0)