tempo_precompiles/storage/
thread_local.rs1use alloy::primitives::{Address, B256, LogData, U256};
2use alloy_evm::{Database, EvmInternals};
3use revm::{
4 context::{Block, CfgEnv, JournalTr, Transaction, journaled_state::JournalCheckpoint},
5 state::{AccountInfo, Bytecode},
6};
7use scoped_tls::scoped_thread_local;
8use std::{cell::RefCell, fmt::Debug};
9use tempo_chainspec::hardfork::TempoHardfork;
10
11use crate::{
12 Precompile,
13 error::{Result, TempoPrecompileError},
14 storage::{PrecompileStorageProvider, evm::EvmPrecompileStorageProvider},
15};
16
17scoped_thread_local!(static STORAGE: RefCell<&mut dyn PrecompileStorageProvider>);
18
19#[derive(Debug, Default, Clone, Copy)]
34pub struct StorageCtx;
35
36impl StorageCtx {
37 pub fn enter<S, R>(storage: &mut S, f: impl FnOnce() -> R) -> R
46 where
47 S: PrecompileStorageProvider,
48 {
49 let storage: &mut dyn PrecompileStorageProvider = storage;
51 let storage_static: &mut (dyn PrecompileStorageProvider + 'static) =
52 unsafe { std::mem::transmute(storage) };
53 let cell = RefCell::new(storage_static);
54 STORAGE.set(&cell, f)
55 }
56
57 fn with_storage<F, R>(f: F) -> R
62 where
63 F: FnOnce(&mut dyn PrecompileStorageProvider) -> R,
64 {
65 assert!(
66 STORAGE.is_set(),
67 "No storage context. 'StorageCtx::enter' must be called first"
68 );
69 STORAGE.with(|cell| {
70 let mut guard = cell.borrow_mut();
73 f(&mut **guard)
74 })
75 }
76
77 fn try_with_storage<F, R>(f: F) -> Result<R>
79 where
80 F: FnOnce(&mut dyn PrecompileStorageProvider) -> Result<R>,
81 {
82 if !STORAGE.is_set() {
83 return Err(TempoPrecompileError::Fatal(
84 "No storage context. 'StorageCtx::enter' must be called first".to_string(),
85 ));
86 }
87 STORAGE.with(|cell| {
88 let mut guard = cell.borrow_mut();
91 f(&mut **guard)
92 })
93 }
94
95 pub fn with_account_info<T>(
101 &self,
102 address: Address,
103 mut f: impl FnMut(&AccountInfo) -> Result<T>,
104 ) -> Result<T> {
105 let mut result: Option<Result<T>> = None;
106 Self::try_with_storage(|s| {
107 s.with_account_info(address, &mut |info| {
108 result = Some(f(info));
109 })
110 })?;
111 result.unwrap()
112 }
113
114 pub fn chain_id(&self) -> u64 {
116 Self::with_storage(|s| s.chain_id())
117 }
118
119 pub fn timestamp(&self) -> U256 {
121 Self::with_storage(|s| s.timestamp())
122 }
123
124 pub fn beneficiary(&self) -> Address {
126 Self::with_storage(|s| s.beneficiary())
127 }
128
129 pub fn block_number(&self) -> u64 {
131 Self::with_storage(|s| s.block_number())
132 }
133
134 pub fn set_code(&mut self, address: Address, code: Bytecode) -> Result<()> {
136 Self::try_with_storage(|s| s.set_code(address, code))
137 }
138
139 pub fn sload(&self, address: Address, key: U256) -> Result<U256> {
141 Self::try_with_storage(|s| s.sload(address, key))
142 }
143
144 pub fn tload(&self, address: Address, key: U256) -> Result<U256> {
146 Self::try_with_storage(|s| s.tload(address, key))
147 }
148
149 pub fn sstore(&mut self, address: Address, key: U256, value: U256) -> Result<()> {
151 Self::try_with_storage(|s| s.sstore(address, key, value))
152 }
153
154 pub fn tstore(&mut self, address: Address, key: U256, value: U256) -> Result<()> {
156 Self::try_with_storage(|s| s.tstore(address, key, value))
157 }
158
159 pub fn emit_event(&mut self, address: Address, event: LogData) -> Result<()> {
161 Self::try_with_storage(|s| s.emit_event(address, event))
162 }
163
164 pub fn refund_gas(&mut self, gas: i64) {
166 Self::with_storage(|s| s.refund_gas(gas))
167 }
168
169 pub fn gas_used(&self) -> u64 {
171 Self::with_storage(|s| s.gas_used())
172 }
173
174 pub fn gas_refunded(&self) -> i64 {
176 Self::with_storage(|s| s.gas_refunded())
177 }
178
179 pub fn spec(&self) -> TempoHardfork {
181 Self::with_storage(|s| s.spec())
182 }
183
184 pub fn is_static(&self) -> bool {
186 Self::with_storage(|s| s.is_static())
187 }
188
189 pub fn checkpoint(&mut self) -> CheckpointGuard {
199 let checkpoint = Self::with_storage(|s| {
201 if s.spec().is_t1c() {
202 Some(s.checkpoint())
203 } else {
204 None
205 }
206 });
207
208 CheckpointGuard { checkpoint }
209 }
210
211 pub fn deduct_gas(&mut self, gas: u64) -> Result<()> {
213 Self::try_with_storage(|s| s.deduct_gas(gas))
214 }
215
216 pub fn keccak256(&self, data: &[u8]) -> Result<B256> {
220 Self::try_with_storage(|s| s.keccak256(data))
221 }
222
223 pub fn recover_signer(&self, digest: B256, v: u8, r: B256, s: B256) -> Result<Option<Address>> {
230 Self::try_with_storage(|storage| storage.recover_signer(digest, v, r, s))
231 }
232}
233
234pub struct CheckpointGuard {
251 checkpoint: Option<JournalCheckpoint>,
252}
253
254impl CheckpointGuard {
255 pub fn commit(mut self) {
257 if let Some(cp) = self.checkpoint.take() {
258 StorageCtx::with_storage(|s| s.checkpoint_commit(cp));
259 }
260 }
261}
262
263impl Drop for CheckpointGuard {
264 fn drop(&mut self) {
265 if let Some(cp) = self.checkpoint.take() {
266 StorageCtx::with_storage(|s| s.checkpoint_revert(cp));
267 }
268 }
269}
270
271impl<'evm> StorageCtx {
272 pub fn enter_evm<J, R>(
275 journal: &'evm mut J,
276 block_env: &'evm dyn Block,
277 cfg: &CfgEnv<TempoHardfork>,
278 tx_env: &'evm impl Transaction,
279 f: impl FnOnce() -> R,
280 ) -> R
281 where
282 J: JournalTr<Database: Database> + Debug,
283 {
284 let internals = EvmInternals::new(journal, block_env, cfg, tx_env);
285 let mut provider = EvmPrecompileStorageProvider::new_max_gas(internals, cfg);
286
287 Self::enter(&mut provider, f)
289 }
290
291 pub fn enter_precompile<J, P, R>(
293 journal: &'evm mut J,
294 block_env: &'evm dyn Block,
295 cfg: &CfgEnv<TempoHardfork>,
296 tx_env: &'evm impl Transaction,
297 f: impl FnOnce(P) -> R,
298 ) -> R
299 where
300 J: JournalTr<Database: Database> + Debug,
301 P: Precompile + Default,
302 {
303 Self::enter_evm(journal, block_env, cfg, tx_env, || f(P::default()))
306 }
307}
308
309#[cfg(any(test, feature = "test-utils"))]
310use crate::storage::hashmap::HashMapStorageProvider;
311
312#[cfg(any(test, feature = "test-utils"))]
313impl StorageCtx {
314 #[allow(clippy::mut_from_ref)]
319 fn as_hashmap(&self) -> &mut HashMapStorageProvider {
320 Self::with_storage(|s| {
321 unsafe {
324 extend_lifetime_mut(
325 &mut *(s as *mut dyn PrecompileStorageProvider as *mut HashMapStorageProvider),
326 )
327 }
328 })
329 }
330
331 pub fn get_account_info(&self, address: Address) -> Option<&AccountInfo> {
333 self.as_hashmap().get_account_info(address)
334 }
335
336 pub fn get_events(&self, address: Address) -> &Vec<LogData> {
338 self.as_hashmap().get_events(address)
339 }
340
341 pub fn set_nonce(&mut self, address: Address, nonce: u64) {
343 self.as_hashmap().set_nonce(address, nonce)
344 }
345
346 pub fn set_timestamp(&mut self, timestamp: U256) {
348 self.as_hashmap().set_timestamp(timestamp)
349 }
350
351 pub fn set_beneficiary(&mut self, beneficiary: Address) {
353 self.as_hashmap().set_beneficiary(beneficiary)
354 }
355
356 pub fn set_block_number(&mut self, block_number: u64) {
358 self.as_hashmap().set_block_number(block_number)
359 }
360
361 pub fn set_spec(&mut self, spec: TempoHardfork) {
363 self.as_hashmap().set_spec(spec)
364 }
365
366 pub fn clear_transient(&mut self) {
368 self.as_hashmap().clear_transient()
369 }
370
371 pub fn clear_events(&mut self, address: Address) {
376 self.as_hashmap().clear_events(address);
377 }
378
379 pub fn has_bytecode(&self, address: Address) -> Result<bool> {
381 self.with_account_info(address, |info| Ok(!info.is_empty_code_hash()))
382 }
383}
384
385#[cfg(any(test, feature = "test-utils"))]
389unsafe fn extend_lifetime_mut<'b, T: ?Sized>(r: &mut T) -> &'b mut T {
390 unsafe { &mut *(r as *mut T) }
391}
392
393#[cfg(test)]
394mod tests {
395 use super::*;
396 use alloy::primitives::U256;
397 use tempo_chainspec::hardfork::TempoHardfork;
398
399 fn t1c_storage() -> HashMapStorageProvider {
400 HashMapStorageProvider::new_with_spec(1, TempoHardfork::T1C)
401 }
402
403 #[test]
404 #[should_panic(expected = "already borrowed")]
405 fn test_reentrant_with_storage_panics() {
406 let mut storage = HashMapStorageProvider::new(1);
407 StorageCtx::enter(&mut storage, || {
408 StorageCtx::with_storage(|_| {
410 StorageCtx::with_storage(|_| ())
412 })
413 });
414 }
415
416 #[test]
417 fn test_checkpoint_commit_and_revert() {
418 let mut storage = t1c_storage();
419 let addr = Address::ZERO;
420 let key = U256::from(1);
421
422 StorageCtx::enter(&mut storage, || {
423 let mut ctx = StorageCtx;
424
425 ctx.sstore(addr, key, U256::from(42)).unwrap();
427 let guard = ctx.checkpoint();
428 ctx.sstore(addr, key, U256::from(99)).unwrap();
429 guard.commit();
430 assert_eq!(ctx.sload(addr, key).unwrap(), U256::from(99));
431
432 {
434 let _guard = ctx.checkpoint();
435 ctx.sstore(addr, key, U256::from(1)).unwrap();
436 }
437 assert_eq!(ctx.sload(addr, key).unwrap(), U256::from(99));
438 });
439 }
440
441 #[test]
442 fn test_nested_checkpoints_lifo() {
443 let mut storage = t1c_storage();
444 let addr = Address::ZERO;
445 let key = U256::from(1);
446
447 StorageCtx::enter(&mut storage, || {
448 let mut ctx = StorageCtx;
449 ctx.sstore(addr, key, U256::from(10)).unwrap();
450
451 let outer = ctx.checkpoint();
453 ctx.sstore(addr, key, U256::from(20)).unwrap();
454 let inner = ctx.checkpoint();
455 ctx.sstore(addr, key, U256::from(30)).unwrap();
456 inner.commit();
457 outer.commit();
458 assert_eq!(ctx.sload(addr, key).unwrap(), U256::from(30));
459
460 let outer = ctx.checkpoint();
462 ctx.sstore(addr, key, U256::from(40)).unwrap();
463 {
464 let _inner = ctx.checkpoint();
465 ctx.sstore(addr, key, U256::from(50)).unwrap();
466 }
467 outer.commit();
468 assert_eq!(ctx.sload(addr, key).unwrap(), U256::from(40));
469 });
470 }
471
472 #[test]
473 #[should_panic(expected = "out-of-order")]
474 fn test_nested_checkpoints_out_of_order_commit_panics() {
475 let mut storage = t1c_storage();
476
477 StorageCtx::enter(&mut storage, || {
478 let mut ctx = StorageCtx;
479
480 let outer = ctx.checkpoint();
481 let _inner = ctx.checkpoint();
482
483 outer.commit();
485 });
486 }
487
488 #[test]
489 fn test_checkpoint_noop_pre_t1c() {
490 let mut storage = HashMapStorageProvider::new(1); let addr = Address::ZERO;
492 let key = U256::from(1);
493
494 StorageCtx::enter(&mut storage, || {
495 let mut ctx = StorageCtx;
496
497 ctx.sstore(addr, key, U256::from(42)).unwrap();
498 {
499 let _guard = ctx.checkpoint(); ctx.sstore(addr, key, U256::from(99)).unwrap();
501 }
503 assert_eq!(ctx.sload(addr, key).unwrap(), U256::from(99));
505 });
506 }
507}