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