linera_alloy_eips/
eip2930.rs

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