revm_context/journal/
warm_addresses.rs

1//! This module contains [`WarmAddresses`] struct that stores addresses that are warm loaded.
2//!
3//! It is used to optimize access to precompile addresses.
4
5use bitvec::{bitvec, vec::BitVec};
6use primitives::{short_address, Address, HashMap, HashSet, StorageKey, SHORT_ADDRESS_CAP};
7
8/// Stores addresses that are warm loaded. Contains precompiles and coinbase address.
9///
10/// It contains precompiles addresses that are not changed frequently and AccessList that
11/// is changed per transaction.
12///
13/// [WarmAddresses::precompiles] will always contain all precompile addresses.
14///
15/// As precompiles addresses are usually very small, precompile_short_addresses will
16/// contain bitset of shrunk precompile address.
17#[derive(Debug, Clone, PartialEq, Eq)]
18#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
19pub struct WarmAddresses {
20    /// Set of warm loaded precompile addresses.
21    precompile_set: HashSet<Address>,
22    /// Bit vector of precompile short addresses. If address is shorter than [`SHORT_ADDRESS_CAP`] it
23    /// will be stored in this bit vector for faster access.
24    precompile_short_addresses: BitVec,
25    /// `true` if all precompiles are short addresses.
26    precompile_all_short_addresses: bool,
27    /// Coinbase address.
28    coinbase: Option<Address>,
29    /// Access list
30    access_list: HashMap<Address, HashSet<StorageKey>>,
31}
32
33impl Default for WarmAddresses {
34    fn default() -> Self {
35        Self::new()
36    }
37}
38
39impl WarmAddresses {
40    /// Create a new warm addresses instance.
41    #[inline]
42    pub fn new() -> Self {
43        Self {
44            precompile_set: HashSet::default(),
45            precompile_short_addresses: bitvec![0; SHORT_ADDRESS_CAP],
46            precompile_all_short_addresses: true,
47            coinbase: None,
48            access_list: HashMap::default(),
49        }
50    }
51
52    /// Returns the precompile addresses.
53    #[inline]
54    pub fn precompiles(&self) -> &HashSet<Address> {
55        &self.precompile_set
56    }
57
58    /// Returns the coinbase address.
59    #[inline]
60    pub fn coinbase(&self) -> Option<Address> {
61        self.coinbase
62    }
63
64    /// Set the precompile addresses and short addresses.
65    #[inline]
66    pub fn set_precompile_addresses(&mut self, addresses: HashSet<Address>) {
67        self.precompile_short_addresses.fill(false);
68
69        let mut all_short_addresses = true;
70        for address in addresses.iter() {
71            if let Some(short_address) = short_address(address) {
72                self.precompile_short_addresses.set(short_address, true);
73            } else {
74                all_short_addresses = false;
75            }
76        }
77
78        self.precompile_all_short_addresses = all_short_addresses;
79        self.precompile_set = addresses;
80    }
81
82    /// Set the coinbase address.
83    #[inline]
84    pub fn set_coinbase(&mut self, address: Address) {
85        self.coinbase = Some(address);
86    }
87
88    /// Set the access list.
89    #[inline]
90    pub fn set_access_list(&mut self, access_list: HashMap<Address, HashSet<StorageKey>>) {
91        self.access_list = access_list;
92    }
93
94    /// Clear the coinbase address.
95    #[inline]
96    pub fn clear_coinbase(&mut self) {
97        self.coinbase = None;
98    }
99
100    /// Clear the coinbase and access list.
101    #[inline]
102    pub fn clear_coinbase_and_access_list(&mut self) {
103        self.coinbase = None;
104        self.access_list.clear();
105    }
106
107    /// Returns true if the address is warm loaded.
108    #[inline]
109    pub fn is_warm(&self, address: &Address) -> bool {
110        // check if it is coinbase
111        if Some(*address) == self.coinbase {
112            return true;
113        }
114
115        // if it is part of access list.
116        if self.access_list.contains_key(address) {
117            return true;
118        }
119
120        // if there are no precompiles, it is cold loaded and bitvec is not set.
121        if self.precompile_set.is_empty() {
122            return false;
123        }
124
125        // check if it is short precompile address
126        if let Some(short_address) = short_address(address) {
127            return self.precompile_short_addresses[short_address];
128        }
129
130        if !self.precompile_all_short_addresses {
131            // in the end check if it is inside precompile set
132            return self.precompile_set.contains(address);
133        }
134
135        false
136    }
137
138    /// Returns true if the storage is warm loaded.
139    #[inline]
140    pub fn is_storage_warm(&self, address: &Address, key: &StorageKey) -> bool {
141        if let Some(access_list) = self.access_list.get(address) {
142            return access_list.contains(key);
143        }
144
145        false
146    }
147
148    /// Returns true if the address is cold loaded.
149    #[inline]
150    pub fn is_cold(&self, address: &Address) -> bool {
151        !self.is_warm(address)
152    }
153}
154
155#[cfg(test)]
156mod tests {
157    use super::*;
158    use primitives::{address, Address};
159
160    #[test]
161    fn test_initialization() {
162        let warm_addresses = WarmAddresses::new();
163        assert!(warm_addresses.precompile_set.is_empty());
164        assert_eq!(
165            warm_addresses.precompile_short_addresses.len(),
166            SHORT_ADDRESS_CAP
167        );
168        assert!(!warm_addresses.precompile_short_addresses.any());
169        assert!(warm_addresses.coinbase.is_none());
170
171        // Test Default trait
172        let default_addresses = WarmAddresses::default();
173        assert_eq!(warm_addresses, default_addresses);
174    }
175
176    #[test]
177    fn test_coinbase_management() {
178        let mut warm_addresses = WarmAddresses::new();
179        let coinbase_addr = address!("1234567890123456789012345678901234567890");
180
181        // Test setting coinbase
182        warm_addresses.set_coinbase(coinbase_addr);
183        assert_eq!(warm_addresses.coinbase, Some(coinbase_addr));
184        assert!(warm_addresses.is_warm(&coinbase_addr));
185
186        // Test clearing coinbase
187        warm_addresses.clear_coinbase_and_access_list();
188        assert!(warm_addresses.coinbase.is_none());
189        assert!(!warm_addresses.is_warm(&coinbase_addr));
190    }
191
192    #[test]
193    fn test_short_address_precompiles() {
194        let mut warm_addresses = WarmAddresses::new();
195
196        // Create short addresses (18 leading zeros, last 2 bytes < 300)
197        let mut bytes1 = [0u8; 20];
198        bytes1[19] = 1u8;
199        let short_addr1 = Address::from(bytes1);
200
201        let mut bytes2 = [0u8; 20];
202        bytes2[19] = 5u8;
203        let short_addr2 = Address::from(bytes2);
204
205        let mut precompiles = HashSet::default();
206        precompiles.insert(short_addr1);
207        precompiles.insert(short_addr2);
208
209        warm_addresses.set_precompile_addresses(precompiles.clone());
210
211        // Verify storage
212        assert_eq!(warm_addresses.precompile_set, precompiles);
213        assert_eq!(
214            warm_addresses.precompile_short_addresses.len(),
215            SHORT_ADDRESS_CAP
216        );
217
218        // Verify bitvec optimization
219        assert!(warm_addresses.precompile_short_addresses[1]);
220        assert!(warm_addresses.precompile_short_addresses[5]);
221        assert!(!warm_addresses.precompile_short_addresses[0]);
222
223        // Verify warmth detection
224        assert!(warm_addresses.is_warm(&short_addr1));
225        assert!(warm_addresses.is_warm(&short_addr2));
226
227        // Test non-existent short address
228        let mut other_bytes = [0u8; 20];
229        other_bytes[19] = 20u8;
230        let other_short_addr = Address::from(other_bytes);
231        assert!(!warm_addresses.is_warm(&other_short_addr));
232    }
233
234    #[test]
235    fn test_regular_address_precompiles() {
236        let mut warm_addresses = WarmAddresses::new();
237
238        // Create non-short addresses
239        let regular_addr = address!("1234567890123456789012345678901234567890");
240        let mut bytes = [0u8; 20];
241        bytes[18] = 1u8;
242        bytes[19] = 44u8; // 300
243        let boundary_addr = Address::from(bytes);
244
245        let mut precompiles = HashSet::default();
246        precompiles.insert(regular_addr);
247        precompiles.insert(boundary_addr);
248
249        warm_addresses.set_precompile_addresses(precompiles.clone());
250
251        // Verify storage
252        assert_eq!(warm_addresses.precompile_set, precompiles);
253        assert!(!warm_addresses.precompile_short_addresses.any());
254
255        // Verify warmth detection
256        assert!(warm_addresses.is_warm(&regular_addr));
257        assert!(warm_addresses.is_warm(&boundary_addr));
258
259        // Test non-existent regular address
260        let other_addr = address!("0987654321098765432109876543210987654321");
261        assert!(!warm_addresses.is_warm(&other_addr));
262    }
263
264    #[test]
265    fn test_mixed_address_types() {
266        let mut warm_addresses = WarmAddresses::new();
267
268        let mut short_bytes = [0u8; 20];
269        short_bytes[19] = 7u8;
270        let short_addr = Address::from(short_bytes);
271        let regular_addr = address!("1234567890123456789012345678901234567890");
272
273        let mut precompiles = HashSet::default();
274        precompiles.insert(short_addr);
275        precompiles.insert(regular_addr);
276
277        warm_addresses.set_precompile_addresses(precompiles);
278
279        // Both types should be warm
280        assert!(warm_addresses.is_warm(&short_addr));
281        assert!(warm_addresses.is_warm(&regular_addr));
282
283        // Verify short address optimization is used
284        assert!(warm_addresses.precompile_short_addresses[7]);
285        assert!(!warm_addresses.precompile_short_addresses[8]);
286    }
287
288    #[test]
289    fn test_short_address_boundary() {
290        let mut warm_addresses = WarmAddresses::new();
291
292        // Address at boundary (SHORT_ADDRESS_CAP - 1)
293        let mut boundary_bytes = [0u8; 20];
294        let boundary_val = (SHORT_ADDRESS_CAP - 1) as u16;
295        boundary_bytes[18] = (boundary_val >> 8) as u8;
296        boundary_bytes[19] = boundary_val as u8;
297        let boundary_addr = Address::from(boundary_bytes);
298
299        let mut precompiles = HashSet::default();
300        precompiles.insert(boundary_addr);
301
302        warm_addresses.set_precompile_addresses(precompiles);
303
304        assert!(warm_addresses.is_warm(&boundary_addr));
305        assert!(warm_addresses.precompile_short_addresses[SHORT_ADDRESS_CAP - 1]);
306    }
307}