diff --git a/crates/primitives/src/transaction/access_list.rs b/crates/primitives/src/transaction/access_list.rs index c15646b4b..acaf132c4 100644 --- a/crates/primitives/src/transaction/access_list.rs +++ b/crates/primitives/src/transaction/access_list.rs @@ -1,202 +1,74 @@ -use crate::{Address, B256, U256}; -use alloy_rlp::{RlpDecodable, RlpDecodableWrapper, RlpEncodable, RlpEncodableWrapper}; -use reth_codecs::{main_codec, Compact}; -use std::{ - mem, - ops::{Deref, DerefMut}, -}; +//! [EIP-2930](https://eips.ethereum.org/EIPS/eip-2930): Access List types -/// Represents a list of addresses and storage keys that a transaction plans to access. -/// -/// Accesses outside this list incur higher costs due to gas charging. -/// -/// This structure is part of [EIP-2930](https://eips.ethereum.org/EIPS/eip-2930), introducing an optional access list for Ethereum transactions. -/// -/// The access list allows pre-specifying and pre-paying for accounts and storage -/// slots, mitigating risks introduced by [EIP-2929](https://eips.ethereum.org/EIPS/e). -#[main_codec(rlp)] -#[derive(Clone, Debug, PartialEq, Eq, Hash, Default, RlpDecodable, RlpEncodable)] -#[serde(rename_all = "camelCase")] -pub struct AccessListItem { - /// Account address that would be loaded at the start of execution - pub address: Address, - /// The storage keys to be loaded at the start of execution. - /// - /// Each key is a 32-byte value representing a specific storage slot. - #[cfg_attr( - any(test, feature = "arbitrary"), - proptest( - strategy = "proptest::collection::vec(proptest::arbitrary::any::(), 0..=20)" - ) - )] - pub storage_keys: Vec, -} - -impl AccessListItem { - /// Calculates a heuristic for the in-memory size of the [AccessListItem]. - #[inline] - pub fn size(&self) -> usize { - mem::size_of::
() + self.storage_keys.capacity() * mem::size_of::() - } -} - -/// AccessList as defined in [EIP-2930](https://eips.ethereum.org/EIPS/eip-2930) -#[main_codec(rlp)] -#[derive(Clone, Debug, PartialEq, Eq, Hash, Default, RlpDecodableWrapper, RlpEncodableWrapper)] -pub struct AccessList( - #[cfg_attr( - any(test, feature = "arbitrary"), - proptest( - strategy = "proptest::collection::vec(proptest::arbitrary::any::(), 0..=20)" - ) - )] - pub Vec, -); - -impl AccessList { - /// Converts the list into a vec, expected by revm - pub fn flattened(&self) -> Vec<(Address, Vec)> { - self.flatten().collect() - } - - /// Consumes the type and converts the list into a vec, expected by revm - pub fn into_flattened(self) -> Vec<(Address, Vec)> { - self.into_flatten().collect() - } - - /// Consumes the type and returns an iterator over the list's addresses and storage keys. - pub fn into_flatten(self) -> impl Iterator)> { - self.0.into_iter().map(|item| { - ( - item.address, - item.storage_keys.into_iter().map(|slot| U256::from_be_bytes(slot.0)).collect(), - ) - }) - } - - /// Returns an iterator over the list's addresses and storage keys. - pub fn flatten(&self) -> impl Iterator)> + '_ { - self.iter().map(|item| { - ( - item.address, - item.storage_keys.iter().map(|slot| U256::from_be_bytes(slot.0)).collect(), - ) - }) - } - - /// Calculates a heuristic for the in-memory size of the [AccessList]. - #[inline] - pub fn size(&self) -> usize { - // take into account capacity - self.iter().map(AccessListItem::size).sum::() + - self.capacity() * mem::size_of::() - } - - /// Returns the position of the given address in the access list, if present. - pub fn index_of_address(&self, address: Address) -> Option { - self.iter().position(|item| item.address == address) - } - - /// Checks if a specific storage slot within an account is present in the access list. - /// - /// Returns a tuple with flags for the presence of the account and the slot. - pub fn contains(&self, address: Address, slot: B256) -> (bool, bool) { - self.index_of_address(address) - .map_or((false, false), |idx| (true, self.contains_storage_key_at_index(slot, idx))) - } - - /// Checks if the access list contains the specified address. - pub fn contains_address(&self, address: Address) -> bool { - self.iter().any(|item| item.address == address) - } - - /// Checks if the storage keys at the given index within an account are present in the access - /// list. - pub fn contains_storage_key_at_index(&self, slot: B256, index: usize) -> bool { - self.get(index).map_or(false, |entry| { - entry.storage_keys.iter().any(|storage_key| *storage_key == slot) - }) - } - - /// Adds an address to the access list and returns `true` if the operation results in a change, - /// indicating that the address was not previously present. - pub fn add_address(&mut self, address: Address) -> bool { - !self.contains_address(address) && { - self.push(AccessListItem { address, storage_keys: Vec::new() }); - true - } - } -} - -impl Deref for AccessList { - type Target = Vec; - - fn deref(&self) -> &Self::Target { - &self.0 - } -} - -impl DerefMut for AccessList { - fn deref_mut(&mut self) -> &mut Self::Target { - &mut self.0 - } -} - -impl From for AccessList { - #[inline] - fn from(value: reth_rpc_types::AccessList) -> Self { - AccessList( - value - .0 - .into_iter() - .map(|item| AccessListItem { - address: item.address, - storage_keys: item.storage_keys, - }) - .collect(), - ) - } -} - -impl From for reth_rpc_types::AccessList { - #[inline] - fn from(value: AccessList) -> Self { - reth_rpc_types::AccessList( - value - .0 - .into_iter() - .map(|item| reth_rpc_types::AccessListItem { - address: item.address, - storage_keys: item.storage_keys, - }) - .collect(), - ) - } -} +/// Re-export from `alloy_eips`. +#[doc(inline)] +pub use alloy_eips::eip2930::{AccessList, AccessListItem}; #[cfg(test)] mod tests { use super::*; - + use crate::{Address, B256}; + use alloy_rlp::{RlpDecodable, RlpDecodableWrapper, RlpEncodable, RlpEncodableWrapper}; use proptest::proptest; + use reth_codecs::{main_codec, Compact}; + + /// This type is kept for compatibility tests after the codec support was added to alloy-eips + /// AccessList type natively + #[main_codec(rlp)] + #[derive( + Clone, Debug, PartialEq, Eq, Hash, Default, RlpDecodableWrapper, RlpEncodableWrapper, + )] + struct RethAccessList( + #[proptest( + strategy = "proptest::collection::vec(proptest::arbitrary::any::(), 0..=20)" + )] + Vec, + ); + + impl PartialEq for RethAccessList { + fn eq(&self, other: &AccessList) -> bool { + self.0.iter().zip(other.iter()).all(|(a, b)| a == b) + } + } + + // This + #[main_codec(rlp)] + #[derive(Clone, Debug, PartialEq, Eq, Hash, Default, RlpDecodable, RlpEncodable)] + #[serde(rename_all = "camelCase")] + struct RethAccessListItem { + /// Account address that would be loaded at the start of execution + address: Address, + /// The storage keys to be loaded at the start of execution. + /// + /// Each key is a 32-byte value representing a specific storage slot. + #[proptest( + strategy = "proptest::collection::vec(proptest::arbitrary::any::(), 0..=20)" + )] + storage_keys: Vec, + } + + impl PartialEq for RethAccessListItem { + fn eq(&self, other: &AccessListItem) -> bool { + self.address == other.address && self.storage_keys == other.storage_keys + } + } proptest!( #[test] - fn test_roundtrip_accesslist_conversion(access_list: AccessList) { + fn test_roundtrip_accesslist_compat(access_list: RethAccessList) { // Convert access_list to buffer and then create alloy_access_list from buffer and // compare - let mut compacted_access_list = Vec::::new(); - let len = access_list.clone().to_compact(&mut compacted_access_list); + let mut compacted_reth_access_list = Vec::::new(); + let len = access_list.clone().to_compact(&mut compacted_reth_access_list); - let alloy_access_list = AccessList::from_compact(&compacted_access_list, len).0; + // decode the compacted buffer to AccessList + let alloy_access_list = AccessList::from_compact(&compacted_reth_access_list, len).0; assert_eq!(access_list, alloy_access_list); - // Create alloy_access_list from access_list and then convert it to buffer and compare - // compacted_alloy_access_list and compacted_access_list - let alloy_access_list = AccessList(access_list.0); let mut compacted_alloy_access_list = Vec::::new(); - let _len = alloy_access_list.to_compact(&mut compacted_alloy_access_list); - assert_eq!(compacted_access_list, compacted_alloy_access_list); + let alloy_len = alloy_access_list.to_compact(&mut compacted_alloy_access_list); + assert_eq!(len, alloy_len); + assert_eq!(compacted_reth_access_list, compacted_alloy_access_list); } ); } diff --git a/crates/primitives/src/transaction/mod.rs b/crates/primitives/src/transaction/mod.rs index 2d3d664b2..7c57495fe 100644 --- a/crates/primitives/src/transaction/mod.rs +++ b/crates/primitives/src/transaction/mod.rs @@ -652,7 +652,7 @@ impl TryFrom for Transaction { to: tx.to.map_or(TransactionKind::Create, TransactionKind::Call), value: tx.value, input: tx.input, - access_list: tx.access_list.ok_or(ConversionError::MissingAccessList)?.into(), + access_list: tx.access_list.ok_or(ConversionError::MissingAccessList)?, gas_price: tx.gas_price.ok_or(ConversionError::MissingGasPrice)?, })) } @@ -673,7 +673,7 @@ impl TryFrom for Transaction { .map_err(|_| ConversionError::Eip2718Error(RlpError::Overflow.into()))?, to: tx.to.map_or(TransactionKind::Create, TransactionKind::Call), value: tx.value, - access_list: tx.access_list.ok_or(ConversionError::MissingAccessList)?.into(), + access_list: tx.access_list.ok_or(ConversionError::MissingAccessList)?, input: tx.input, })) } @@ -694,7 +694,7 @@ impl TryFrom for Transaction { .map_err(|_| ConversionError::Eip2718Error(RlpError::Overflow.into()))?, to: tx.to.map_or(TransactionKind::Create, TransactionKind::Call), value: tx.value, - access_list: tx.access_list.ok_or(ConversionError::MissingAccessList)?.into(), + access_list: tx.access_list.ok_or(ConversionError::MissingAccessList)?, input: tx.input, blob_versioned_hashes: tx .blob_versioned_hashes diff --git a/crates/rpc/rpc-types-compat/src/transaction/typed.rs b/crates/rpc/rpc-types-compat/src/transaction/typed.rs index a14898195..cc90c626e 100644 --- a/crates/rpc/rpc-types-compat/src/transaction/typed.rs +++ b/crates/rpc/rpc-types-compat/src/transaction/typed.rs @@ -28,7 +28,7 @@ pub fn to_primitive_transaction( to: to_primitive_transaction_kind(tx.kind), value: tx.value, input: tx.input, - access_list: tx.access_list.into(), + access_list: tx.access_list, }), TypedTransactionRequest::EIP1559(tx) => Transaction::Eip1559(TxEip1559 { chain_id: tx.chain_id, @@ -38,7 +38,7 @@ pub fn to_primitive_transaction( to: to_primitive_transaction_kind(tx.kind), value: tx.value, input: tx.input, - access_list: tx.access_list.into(), + access_list: tx.access_list, max_priority_fee_per_gas: tx.max_priority_fee_per_gas.to(), }), TypedTransactionRequest::EIP4844(tx) => Transaction::Eip4844(TxEip4844 { @@ -49,7 +49,7 @@ pub fn to_primitive_transaction( max_priority_fee_per_gas: tx.max_priority_fee_per_gas.to(), to: to_primitive_transaction_kind(tx.kind), value: tx.value, - access_list: tx.access_list.into(), + access_list: tx.access_list, blob_versioned_hashes: tx.blob_versioned_hashes, max_fee_per_blob_gas: tx.max_fee_per_blob_gas.to(), input: tx.input,