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
6#[allow(unused_imports)]
7#[macro_use]
8extern crate alloc;
9
10#[cfg(not(feature = "std"))]
11use alloc::{string::String, vec::Vec};
12
13use alloy_primitives::{Address, B256, U256};
14use alloy_rlp::{RlpDecodable, RlpDecodableWrapper, RlpEncodable, RlpEncodableWrapper};
15use core::{mem, ops::Deref};
16/// A list of addresses and storage keys that the transaction plans to access.
17/// Accesses outside the list are possible, but become more expensive.
18#[derive(Clone, Debug, Default, PartialEq, Eq, Hash, RlpDecodable, RlpEncodable)]
19#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
20#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
21#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))]
22pub struct AccessListItem {
23    /// Account addresses that would be loaded at the start of execution
24    pub address: Address,
25    /// Keys of storage that would be loaded at the start of execution
26    pub storage_keys: Vec<B256>,
27}
28
29impl AccessListItem {
30    /// Calculates a heuristic for the in-memory size of the [AccessListItem].
31    #[inline]
32    pub fn size(&self) -> usize {
33        mem::size_of::<Address>() + self.storage_keys.capacity() * mem::size_of::<B256>()
34    }
35}
36
37/// AccessList as defined in EIP-2930
38#[derive(Clone, Debug, Default, PartialEq, Eq, Hash, RlpDecodableWrapper, RlpEncodableWrapper)]
39#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
40#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
41pub struct AccessList(pub Vec<AccessListItem>);
42
43impl From<Vec<AccessListItem>> for AccessList {
44    fn from(list: Vec<AccessListItem>) -> Self {
45        Self(list)
46    }
47}
48
49impl From<AccessList> for Vec<AccessListItem> {
50    fn from(this: AccessList) -> Self {
51        this.0
52    }
53}
54
55impl Deref for AccessList {
56    type Target = Vec<AccessListItem>;
57
58    fn deref(&self) -> &Self::Target {
59        &self.0
60    }
61}
62
63impl AccessList {
64    /// Converts the list into a vec, expected by revm
65    pub fn flattened(&self) -> Vec<(Address, Vec<U256>)> {
66        self.flatten().collect()
67    }
68
69    /// Consumes the type and converts the list into a vec, expected by revm
70    pub fn into_flattened(self) -> Vec<(Address, Vec<U256>)> {
71        self.into_flatten().collect()
72    }
73
74    /// Consumes the type and returns an iterator over the list's addresses and storage keys.
75    pub fn into_flatten(self) -> impl Iterator<Item = (Address, Vec<U256>)> {
76        self.0.into_iter().map(|item| {
77            (
78                item.address,
79                item.storage_keys.into_iter().map(|slot| U256::from_be_bytes(slot.0)).collect(),
80            )
81        })
82    }
83
84    /// Returns an iterator over the list's addresses and storage keys.
85    pub fn flatten(&self) -> impl Iterator<Item = (Address, Vec<U256>)> + '_ {
86        self.0.iter().map(|item| {
87            (
88                item.address,
89                item.storage_keys.iter().map(|slot| U256::from_be_bytes(slot.0)).collect(),
90            )
91        })
92    }
93
94    /// Returns the position of the given address in the access list, if present.
95    fn index_of_address(&self, address: Address) -> Option<usize> {
96        self.iter().position(|item| item.address == address)
97    }
98
99    /// Checks if a specific storage slot within an account is present in the access list.
100    ///
101    /// Returns a tuple with flags for the presence of the account and the slot.
102    pub fn contains_storage(&self, address: Address, slot: B256) -> (bool, bool) {
103        self.index_of_address(address)
104            .map_or((false, false), |idx| (true, self.contains_storage_key_at_index(slot, idx)))
105    }
106
107    /// Checks if the access list contains the specified address.
108    pub fn contains_address(&self, address: Address) -> bool {
109        self.iter().any(|item| item.address == address)
110    }
111
112    /// Checks if the storage keys at the given index within an account are present in the access
113    /// list.
114    fn contains_storage_key_at_index(&self, slot: B256, index: usize) -> bool {
115        self.get(index).map_or(false, |entry| {
116            entry.storage_keys.iter().any(|storage_key| *storage_key == slot)
117        })
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}