Skip to main content

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