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    /// Checks if a specific storage slot within an account is present in the access list.
97    ///
98    /// Returns a tuple with flags for the presence of the account and the slot.
99    pub fn contains_storage(&self, address: Address, slot: B256) -> (bool, bool) {
100        self.index_of_address(address)
101            .map_or((false, false), |idx| (true, self.contains_storage_key_at_index(slot, idx)))
102    }
103
104    /// Checks if the access list contains the specified address.
105    pub fn contains_address(&self, address: Address) -> bool {
106        self.iter().any(|item| item.address == address)
107    }
108
109    /// Checks if the storage keys at the given index within an account are present in the access
110    /// list.
111    fn contains_storage_key_at_index(&self, slot: B256, index: usize) -> bool {
112        self.get(index).is_some_and(|entry| entry.storage_keys.contains(&slot))
113    }
114
115    /// Adds an address to the access list and returns `true` if the operation results in a change,
116    /// indicating that the address was not previously present.
117    pub fn add_address(&mut self, address: Address) -> bool {
118        !self.contains_address(address) && {
119            self.0.push(AccessListItem { address, storage_keys: Vec::new() });
120            true
121        }
122    }
123
124    /// Calculates a heuristic for the in-memory size of the [AccessList].
125    #[inline]
126    pub fn size(&self) -> usize {
127        // take into account capacity
128        self.0.iter().map(AccessListItem::size).sum::<usize>()
129            + self.0.capacity() * mem::size_of::<AccessListItem>()
130    }
131}
132
133/// Access list with gas used appended.
134#[derive(Clone, Debug, Default, PartialEq, Eq)]
135#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
136#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))]
137pub struct AccessListWithGasUsed {
138    /// List with accounts accessed during transaction.
139    pub access_list: AccessList,
140    /// Estimated gas used with access list.
141    pub gas_used: U256,
142}
143
144/// `AccessListResult` for handling errors from `eth_createAccessList`
145#[derive(Clone, Debug, Default, PartialEq, Eq)]
146#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
147#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))]
148pub struct AccessListResult {
149    /// List with accounts accessed during transaction.
150    pub access_list: AccessList,
151    /// Estimated gas used with access list.
152    pub gas_used: U256,
153    /// Optional error message if the transaction failed.
154    #[cfg_attr(feature = "serde", serde(default, skip_serializing_if = "Option::is_none"))]
155    pub error: Option<String>,
156}
157
158impl AccessListResult {
159    /// Ensures the result is OK, returning [`AccessListWithGasUsed`] if so, or an error message if
160    /// not.
161    pub fn ensure_ok(self) -> Result<AccessListWithGasUsed, String> {
162        match self.error {
163            Some(err) => Err(err),
164            None => {
165                Ok(AccessListWithGasUsed { access_list: self.access_list, gas_used: self.gas_used })
166            }
167        }
168    }
169
170    /// Checks if there is an error in the result.
171    #[inline]
172    pub const fn is_err(&self) -> bool {
173        self.error.is_some()
174    }
175}
176
177#[cfg(all(test, feature = "serde"))]
178mod tests {
179    use super::*;
180
181    #[test]
182    fn access_list_serde() {
183        let list = AccessList(vec![
184            AccessListItem { address: Address::ZERO, storage_keys: vec![B256::ZERO] },
185            AccessListItem { address: Address::ZERO, storage_keys: vec![B256::ZERO] },
186        ]);
187        let json = serde_json::to_string(&list).unwrap();
188        let list2 = serde_json::from_str::<AccessList>(&json).unwrap();
189        assert_eq!(list, list2);
190    }
191
192    #[test]
193    fn access_list_with_gas_used() {
194        let list = AccessListResult {
195            access_list: AccessList(vec![
196                AccessListItem { address: Address::ZERO, storage_keys: vec![B256::ZERO] },
197                AccessListItem { address: Address::ZERO, storage_keys: vec![B256::ZERO] },
198            ]),
199            gas_used: U256::from(100),
200            error: None,
201        };
202        let json = serde_json::to_string(&list).unwrap();
203        let list2 = serde_json::from_str(&json).unwrap();
204        assert_eq!(list, list2);
205    }
206}