alloy_eip2930/
lib.rs

1//! [EIP-2930] types.
2//!
3//! [EIP-2930]: https://eips.ethereum.org/EIPS/eip-2930
4#![cfg_attr(not(feature = "std"), no_std)]
5
6extern crate alloc;
7
8use alloc::{string::String, vec::Vec};
9use alloy_primitives::{Address, B256, U256};
10use alloy_rlp::{RlpDecodable, RlpDecodableWrapper, RlpEncodable, RlpEncodableWrapper};
11use core::{mem, ops::Deref};
12
13/// A list of addresses and storage keys that the transaction plans to access.
14/// Accesses outside the list are possible, but become more expensive.
15#[derive(Clone, Debug, Default, PartialEq, Eq, Hash, RlpDecodable, RlpEncodable)]
16#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
17#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
18#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))]
19pub struct AccessListItem {
20    /// Account addresses that would be loaded at the start of execution
21    pub address: Address,
22    /// Keys of storage that would be loaded at the start of execution
23    pub storage_keys: Vec<B256>,
24}
25
26impl AccessListItem {
27    /// Calculates a heuristic for the in-memory size of the [AccessListItem].
28    #[inline]
29    pub fn size(&self) -> usize {
30        mem::size_of::<Address>() + self.storage_keys.capacity() * mem::size_of::<B256>()
31    }
32}
33
34/// AccessList as defined in EIP-2930
35#[derive(Clone, Debug, Default, PartialEq, Eq, Hash, RlpDecodableWrapper, RlpEncodableWrapper)]
36#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
37#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
38pub struct AccessList(pub Vec<AccessListItem>);
39
40impl From<Vec<AccessListItem>> for AccessList {
41    fn from(list: Vec<AccessListItem>) -> Self {
42        Self(list)
43    }
44}
45
46impl From<AccessList> for Vec<AccessListItem> {
47    fn from(this: AccessList) -> Self {
48        this.0
49    }
50}
51
52impl Deref for AccessList {
53    type Target = Vec<AccessListItem>;
54
55    fn deref(&self) -> &Self::Target {
56        &self.0
57    }
58}
59
60impl AccessList {
61    /// Converts the list into a vec, expected by revm
62    pub fn flattened(&self) -> Vec<(Address, Vec<U256>)> {
63        self.flatten().collect()
64    }
65
66    /// Consumes the type and converts the list into a vec, expected by revm
67    pub fn into_flattened(self) -> Vec<(Address, Vec<U256>)> {
68        self.into_flatten().collect()
69    }
70
71    /// Consumes the type and returns an iterator over the list's addresses and storage keys.
72    pub fn into_flatten(self) -> impl Iterator<Item = (Address, Vec<U256>)> {
73        self.0.into_iter().map(|item| {
74            (
75                item.address,
76                item.storage_keys.into_iter().map(|slot| U256::from_be_bytes(slot.0)).collect(),
77            )
78        })
79    }
80
81    /// Returns an iterator over the list's addresses and storage keys.
82    pub fn flatten(&self) -> impl Iterator<Item = (Address, Vec<U256>)> + '_ {
83        self.0.iter().map(|item| {
84            (
85                item.address,
86                item.storage_keys.iter().map(|slot| U256::from_be_bytes(slot.0)).collect(),
87            )
88        })
89    }
90
91    /// Returns the position of the given address in the access list, if present.
92    fn index_of_address(&self, address: Address) -> Option<usize> {
93        self.iter().position(|item| item.address == address)
94    }
95
96    /// Returns the total number of storage keys in this access list.
97    pub fn storage_keys_count(&self) -> usize {
98        self.iter().map(|i| i.storage_keys.len()).sum::<usize>()
99    }
100
101    /// Checks if a specific storage slot within an account is present in the access list.
102    ///
103    /// Returns a tuple with flags for the presence of the account and the slot.
104    pub fn contains_storage(&self, address: Address, slot: B256) -> (bool, bool) {
105        self.index_of_address(address)
106            .map_or((false, false), |idx| (true, self.contains_storage_key_at_index(slot, idx)))
107    }
108
109    /// Checks if the access list contains the specified address.
110    pub fn contains_address(&self, address: Address) -> bool {
111        self.iter().any(|item| item.address == address)
112    }
113
114    /// Checks if the storage keys at the given index within an account are present in the access
115    /// list.
116    fn contains_storage_key_at_index(&self, slot: B256, index: usize) -> bool {
117        self.get(index).is_some_and(|entry| entry.storage_keys.contains(&slot))
118    }
119
120    /// Adds an address to the access list and returns `true` if the operation results in a change,
121    /// indicating that the address was not previously present.
122    pub fn add_address(&mut self, address: Address) -> bool {
123        !self.contains_address(address) && {
124            self.0.push(AccessListItem { address, storage_keys: Vec::new() });
125            true
126        }
127    }
128
129    /// Calculates a heuristic for the in-memory size of the [AccessList].
130    #[inline]
131    pub fn size(&self) -> usize {
132        // take into account capacity
133        self.0.iter().map(AccessListItem::size).sum::<usize>()
134            + self.0.capacity() * mem::size_of::<AccessListItem>()
135    }
136}
137
138/// Access list with gas used appended.
139#[derive(Clone, Debug, Default, PartialEq, Eq)]
140#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
141#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))]
142pub struct AccessListWithGasUsed {
143    /// List with accounts accessed during transaction.
144    pub access_list: AccessList,
145    /// Estimated gas used with access list.
146    pub gas_used: U256,
147}
148
149/// `AccessListResult` for handling errors from `eth_createAccessList`
150#[derive(Clone, Debug, Default, PartialEq, Eq)]
151#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
152#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))]
153pub struct AccessListResult {
154    /// List with accounts accessed during transaction.
155    pub access_list: AccessList,
156    /// Estimated gas used with access list.
157    pub gas_used: U256,
158    /// Optional error message if the transaction failed.
159    #[cfg_attr(feature = "serde", serde(default, skip_serializing_if = "Option::is_none"))]
160    pub error: Option<String>,
161}
162
163impl AccessListResult {
164    /// Ensures the result is OK, returning [`AccessListWithGasUsed`] if so, or an error message if
165    /// not.
166    pub fn ensure_ok(self) -> Result<AccessListWithGasUsed, String> {
167        match self.error {
168            Some(err) => Err(err),
169            None => {
170                Ok(AccessListWithGasUsed { access_list: self.access_list, gas_used: self.gas_used })
171            }
172        }
173    }
174
175    /// Checks if there is an error in the result.
176    #[inline]
177    pub const fn is_err(&self) -> bool {
178        self.error.is_some()
179    }
180}
181
182#[cfg(all(test, feature = "serde"))]
183mod tests {
184    use super::*;
185
186    #[test]
187    fn access_list_serde() {
188        let list = AccessList(vec![
189            AccessListItem { address: Address::ZERO, storage_keys: vec![B256::ZERO] },
190            AccessListItem { address: Address::ZERO, storage_keys: vec![B256::ZERO] },
191        ]);
192        let json = serde_json::to_string(&list).unwrap();
193        let list2 = serde_json::from_str::<AccessList>(&json).unwrap();
194        assert_eq!(list, list2);
195    }
196
197    #[test]
198    fn access_list_with_gas_used() {
199        let list = AccessListResult {
200            access_list: AccessList(vec![
201                AccessListItem { address: Address::ZERO, storage_keys: vec![B256::ZERO] },
202                AccessListItem { address: Address::ZERO, storage_keys: vec![B256::ZERO] },
203            ]),
204            gas_used: U256::from(100),
205            error: None,
206        };
207        let json = serde_json::to_string(&list).unwrap();
208        let list2 = serde_json::from_str(&json).unwrap();
209        assert_eq!(list, list2);
210    }
211}