Skip to main content

tempo_precompiles/storage/
hashmap.rs

1use alloy::primitives::{Address, LogData, U256};
2use revm::{
3    context::journaled_state::JournalCheckpoint,
4    state::{AccountInfo, Bytecode},
5};
6use std::collections::HashMap;
7use tempo_chainspec::hardfork::TempoHardfork;
8
9use crate::{error::TempoPrecompileError, storage::PrecompileStorageProvider};
10
11/// In-memory [`PrecompileStorageProvider`] for unit tests.
12///
13/// Stores all state in `HashMap`s, avoiding the need for a real EVM context.
14pub struct HashMapStorageProvider {
15    internals: HashMap<(Address, U256), U256>,
16    transient: HashMap<(Address, U256), U256>,
17    accounts: HashMap<Address, AccountInfo>,
18    chain_id: u64,
19    timestamp: U256,
20    beneficiary: Address,
21    block_number: u64,
22    spec: TempoHardfork,
23    is_static: bool,
24    snapshots: Vec<Snapshot>,
25
26    /// Emitted events keyed by contract address.
27    pub events: HashMap<Address, Vec<LogData>>,
28}
29
30/// Snapshot of mutable state for checkpoint/revert support.
31///
32/// PERF: naive cloning strategy due to its limited usage.
33struct Snapshot {
34    internals: HashMap<(Address, U256), U256>,
35    events: HashMap<Address, Vec<LogData>>,
36}
37
38impl HashMapStorageProvider {
39    /// Creates a new provider with the given chain ID and default hardfork.
40    pub fn new(chain_id: u64) -> Self {
41        Self::new_with_spec(chain_id, TempoHardfork::default())
42    }
43
44    /// Creates a new provider with the given chain ID and hardfork spec.
45    pub fn new_with_spec(chain_id: u64, spec: TempoHardfork) -> Self {
46        Self {
47            internals: HashMap::new(),
48            transient: HashMap::new(),
49            accounts: HashMap::new(),
50            events: HashMap::new(),
51            snapshots: Vec::new(),
52            chain_id,
53            #[expect(clippy::disallowed_methods)]
54            timestamp: U256::from(
55                std::time::SystemTime::now()
56                    .duration_since(std::time::UNIX_EPOCH)
57                    .unwrap()
58                    .as_secs(),
59            ),
60            beneficiary: Address::ZERO,
61            block_number: 0,
62            spec,
63            is_static: false,
64        }
65    }
66
67    /// Returns self with the hardfork spec overridden (builder pattern).
68    pub fn with_spec(mut self, spec: TempoHardfork) -> Self {
69        self.spec = spec;
70        self
71    }
72}
73
74impl PrecompileStorageProvider for HashMapStorageProvider {
75    fn chain_id(&self) -> u64 {
76        self.chain_id
77    }
78
79    fn timestamp(&self) -> U256 {
80        self.timestamp
81    }
82
83    fn beneficiary(&self) -> Address {
84        self.beneficiary
85    }
86
87    fn block_number(&self) -> u64 {
88        self.block_number
89    }
90
91    fn set_code(&mut self, address: Address, code: Bytecode) -> Result<(), TempoPrecompileError> {
92        let account = self.accounts.entry(address).or_default();
93        account.code_hash = code.hash_slow();
94        account.code = Some(code);
95        Ok(())
96    }
97
98    fn with_account_info(
99        &mut self,
100        address: Address,
101        f: &mut dyn FnMut(&AccountInfo),
102    ) -> Result<(), TempoPrecompileError> {
103        let account = self.accounts.entry(address).or_default();
104        f(&*account);
105        Ok(())
106    }
107
108    fn sstore(
109        &mut self,
110        address: Address,
111        key: U256,
112        value: U256,
113    ) -> Result<(), TempoPrecompileError> {
114        self.internals.insert((address, key), value);
115        Ok(())
116    }
117
118    fn tstore(
119        &mut self,
120        address: Address,
121        key: U256,
122        value: U256,
123    ) -> Result<(), TempoPrecompileError> {
124        self.transient.insert((address, key), value);
125        Ok(())
126    }
127
128    fn emit_event(&mut self, address: Address, event: LogData) -> Result<(), TempoPrecompileError> {
129        self.events.entry(address).or_default().push(event);
130        Ok(())
131    }
132
133    fn sload(&mut self, address: Address, key: U256) -> Result<U256, TempoPrecompileError> {
134        Ok(self
135            .internals
136            .get(&(address, key))
137            .copied()
138            .unwrap_or(U256::ZERO))
139    }
140
141    fn tload(&mut self, address: Address, key: U256) -> Result<U256, TempoPrecompileError> {
142        Ok(self
143            .transient
144            .get(&(address, key))
145            .copied()
146            .unwrap_or(U256::ZERO))
147    }
148
149    fn deduct_gas(&mut self, _gas: u64) -> Result<(), TempoPrecompileError> {
150        Ok(())
151    }
152
153    fn refund_gas(&mut self, _gas: i64) {
154        // No-op
155    }
156
157    fn gas_used(&self) -> u64 {
158        0
159    }
160
161    fn gas_refunded(&self) -> i64 {
162        0
163    }
164
165    fn spec(&self) -> TempoHardfork {
166        self.spec
167    }
168
169    fn is_static(&self) -> bool {
170        self.is_static
171    }
172
173    fn checkpoint(&mut self) -> JournalCheckpoint {
174        let idx = self.snapshots.len();
175        self.snapshots.push(Snapshot {
176            internals: self.internals.clone(),
177            events: self.events.clone(),
178        });
179        JournalCheckpoint {
180            log_i: 0,
181            journal_i: idx,
182            selfdestructed_i: 0,
183        }
184    }
185
186    fn checkpoint_commit(&mut self, checkpoint: JournalCheckpoint) {
187        assert_eq!(
188            checkpoint.journal_i,
189            self.snapshots.len() - 1,
190            "out-of-order checkpoint commit (expected top of stack)"
191        );
192        self.snapshots.pop();
193    }
194
195    fn checkpoint_revert(&mut self, checkpoint: JournalCheckpoint) {
196        assert_eq!(
197            checkpoint.journal_i,
198            self.snapshots.len() - 1,
199            "out-of-order checkpoint revert (expected top of stack)"
200        );
201        if let Some(snapshot) = self.snapshots.drain(checkpoint.journal_i..).next() {
202            self.internals = snapshot.internals;
203            self.events = snapshot.events;
204        }
205    }
206}
207
208#[cfg(any(test, feature = "test-utils"))]
209impl HashMapStorageProvider {
210    /// Returns the account info for the given address, if it exists.
211    pub fn get_account_info(&self, address: Address) -> Option<&AccountInfo> {
212        self.accounts.get(&address)
213    }
214
215    /// Returns all emitted events for the given address.
216    pub fn get_events(&self, address: Address) -> &Vec<LogData> {
217        static EMPTY: Vec<LogData> = Vec::new();
218        self.events.get(&address).unwrap_or(&EMPTY)
219    }
220
221    /// Sets the nonce for the given address.
222    pub fn set_nonce(&mut self, address: Address, nonce: u64) {
223        let account = self.accounts.entry(address).or_default();
224        account.nonce = nonce;
225    }
226
227    /// Overrides the block timestamp.
228    pub fn set_timestamp(&mut self, timestamp: U256) {
229        self.timestamp = timestamp;
230    }
231
232    /// Overrides the block beneficiary (coinbase).
233    pub fn set_beneficiary(&mut self, beneficiary: Address) {
234        self.beneficiary = beneficiary;
235    }
236
237    /// Overrides the block number.
238    pub fn set_block_number(&mut self, block_number: u64) {
239        self.block_number = block_number;
240    }
241
242    /// Overrides the active hardfork spec.
243    pub fn set_spec(&mut self, spec: TempoHardfork) {
244        self.spec = spec;
245    }
246
247    /// Clears all transient storage (simulates a new block).
248    pub fn clear_transient(&mut self) {
249        self.transient.clear();
250    }
251
252    /// Clears all emitted events for the given address.
253    pub fn clear_events(&mut self, address: Address) {
254        let _ = self
255            .events
256            .entry(address)
257            .and_modify(|v| v.clear())
258            .or_default();
259    }
260}