tempo_precompiles/storage/
thread_local.rs1use alloy::{
2 primitives::{Address, B256, Bytes, LogData, U256},
3 sol_types::SolInterface,
4};
5use alloy_evm::{Database, EvmInternals};
6use revm::{
7 context::{
8 Block, CfgEnv, ContextTr, JournalTr, Transaction, journaled_state::JournalCheckpoint,
9 },
10 precompile::{PrecompileHalt, PrecompileOutput, PrecompileResult},
11 state::{AccountInfo, Bytecode},
12};
13use scoped_tls::scoped_thread_local;
14use std::{cell::RefCell, fmt::Debug};
15use tempo_chainspec::hardfork::TempoHardfork;
16
17use crate::{
18 Precompile,
19 error::{Result, TempoPrecompileError},
20 storage::{PrecompileStorageProvider, evm::EvmPrecompileStorageProvider},
21};
22
23scoped_thread_local!(static STORAGE: RefCell<&mut dyn PrecompileStorageProvider>);
24
25#[derive(Debug, Default, Clone, Copy)]
40pub struct StorageCtx;
41
42impl StorageCtx {
43 pub fn enter<S, R>(storage: &mut S, f: impl FnOnce() -> R) -> R
52 where
53 S: PrecompileStorageProvider,
54 {
55 let storage: &mut dyn PrecompileStorageProvider = storage;
57 let storage_static: &mut (dyn PrecompileStorageProvider + 'static) =
58 unsafe { std::mem::transmute(storage) };
59 let cell = RefCell::new(storage_static);
60 STORAGE.set(&cell, f)
61 }
62
63 fn with_storage<F, R>(f: F) -> R
68 where
69 F: FnOnce(&mut dyn PrecompileStorageProvider) -> R,
70 {
71 assert!(
72 STORAGE.is_set(),
73 "No storage context. 'StorageCtx::enter' must be called first"
74 );
75 STORAGE.with(|cell| {
76 let mut guard = cell.borrow_mut();
79 f(&mut **guard)
80 })
81 }
82
83 fn try_with_storage<F, R>(f: F) -> Result<R>
85 where
86 F: FnOnce(&mut dyn PrecompileStorageProvider) -> Result<R>,
87 {
88 if !STORAGE.is_set() {
89 return Err(TempoPrecompileError::Fatal(
90 "No storage context. 'StorageCtx::enter' must be called first".to_string(),
91 ));
92 }
93 STORAGE.with(|cell| {
94 let mut guard = cell.borrow_mut();
97 f(&mut **guard)
98 })
99 }
100
101 pub fn with_account_info<T>(
107 &self,
108 address: Address,
109 mut f: impl FnMut(&AccountInfo) -> Result<T>,
110 ) -> Result<T> {
111 let mut result: Option<Result<T>> = None;
112 Self::try_with_storage(|s| {
113 s.with_account_info(address, &mut |info| {
114 result = Some(f(info));
115 })
116 })?;
117 result.unwrap()
118 }
119
120 pub fn chain_id(&self) -> u64 {
122 Self::with_storage(|s| s.chain_id())
123 }
124
125 pub fn timestamp(&self) -> U256 {
127 Self::with_storage(|s| s.timestamp())
128 }
129
130 pub fn beneficiary(&self) -> Address {
132 Self::with_storage(|s| s.beneficiary())
133 }
134
135 pub fn block_number(&self) -> u64 {
137 Self::with_storage(|s| s.block_number())
138 }
139
140 pub fn set_code(&mut self, address: Address, code: Bytecode) -> Result<()> {
142 Self::try_with_storage(|s| s.set_code(address, code))
143 }
144
145 pub fn sload(&self, address: Address, key: U256) -> Result<U256> {
147 Self::try_with_storage(|s| s.sload(address, key))
148 }
149
150 pub fn tload(&self, address: Address, key: U256) -> Result<U256> {
152 Self::try_with_storage(|s| s.tload(address, key))
153 }
154
155 pub fn sstore(&mut self, address: Address, key: U256, value: U256) -> Result<()> {
157 Self::try_with_storage(|s| s.sstore(address, key, value))
158 }
159
160 pub fn tstore(&mut self, address: Address, key: U256, value: U256) -> Result<()> {
162 Self::try_with_storage(|s| s.tstore(address, key, value))
163 }
164
165 pub fn emit_event(&mut self, address: Address, event: LogData) -> Result<()> {
167 Self::try_with_storage(|s| s.emit_event(address, event))
168 }
169
170 pub fn refund_gas(&mut self, gas: i64) {
172 Self::with_storage(|s| s.refund_gas(gas))
173 }
174
175 pub fn gas_limit(&self) -> u64 {
177 Self::with_storage(|s| s.gas_limit())
178 }
179
180 pub fn gas_used(&self) -> u64 {
182 Self::with_storage(|s| s.gas_used())
183 }
184
185 pub fn state_gas_used(&self) -> u64 {
187 Self::with_storage(|s| s.state_gas_used())
188 }
189
190 pub fn gas_refunded(&self) -> i64 {
192 Self::with_storage(|s| s.gas_refunded())
193 }
194
195 pub fn reservoir(&self) -> u64 {
197 Self::with_storage(|s| s.reservoir())
198 }
199
200 pub fn spec(&self) -> TempoHardfork {
202 Self::with_storage(|s| s.spec())
203 }
204
205 pub fn amsterdam_eip8037_enabled(&self) -> bool {
208 Self::with_storage(|s| s.amsterdam_eip8037_enabled())
209 }
210
211 pub fn is_static(&self) -> bool {
213 Self::with_storage(|s| s.is_static())
214 }
215
216 pub fn set_tip1060_storage_credits(&mut self, enabled: bool) {
218 Self::with_storage(|s| s.set_tip1060_storage_credits(enabled))
219 }
220
221 pub fn checkpoint(&mut self) -> CheckpointGuard {
231 let checkpoint = Self::with_storage(|s| {
233 if s.spec().is_t1c() {
234 Some(s.checkpoint())
235 } else {
236 None
237 }
238 });
239
240 CheckpointGuard { checkpoint }
241 }
242
243 pub fn deduct_gas(&mut self, gas: u64) -> Result<()> {
245 Self::try_with_storage(|s| s.deduct_gas(gas))
246 }
247
248 pub fn keccak256(&self, data: &[u8]) -> Result<B256> {
252 Self::try_with_storage(|s| s.keccak256(data))
253 }
254
255 pub fn recover_signer(&self, digest: B256, v: u8, r: B256, s: B256) -> Result<Option<Address>> {
262 Self::try_with_storage(|storage| storage.recover_signer(digest, v, r, s))
263 }
264
265 pub fn success_output(&self, output: Bytes) -> PrecompileOutput {
267 PrecompileOutput::new(self.gas_used(), output, self.reservoir())
268 }
269
270 pub fn abi_success(&self, output: impl SolInterface) -> PrecompileOutput {
272 self.success_output(output.abi_encode().into())
273 }
274
275 pub fn revert_output(&self, output: Bytes) -> PrecompileOutput {
277 PrecompileOutput::revert(self.gas_used(), output, self.reservoir())
278 }
279
280 pub fn abi_revert(&self, error: impl SolInterface) -> PrecompileOutput {
282 self.revert_output(error.abi_encode().into())
283 }
284
285 pub fn halt_output(&self, halt: PrecompileHalt) -> PrecompileOutput {
287 PrecompileOutput::halt(halt, self.reservoir())
288 }
289
290 pub fn error_result(&self, error: impl Into<TempoPrecompileError>) -> PrecompileResult {
292 error
293 .into()
294 .into_precompile_result(self.gas_used(), self.reservoir())
295 }
296}
297
298pub struct CheckpointGuard {
315 checkpoint: Option<JournalCheckpoint>,
316}
317
318impl CheckpointGuard {
319 pub fn commit(mut self) {
321 if let Some(cp) = self.checkpoint.take() {
322 StorageCtx::with_storage(|s| s.checkpoint_commit(cp));
323 }
324 }
325}
326
327impl Drop for CheckpointGuard {
328 fn drop(&mut self) {
329 if let Some(cp) = self.checkpoint.take() {
330 StorageCtx::with_storage(|s| s.checkpoint_revert(cp));
331 }
332 }
333}
334
335impl<'evm> StorageCtx {
336 pub fn enter_evm<J, R>(
339 journal: &'evm mut J,
340 block_env: &'evm dyn Block,
341 cfg: &CfgEnv<TempoHardfork>,
342 tx_env: &'evm impl Transaction,
343 f: impl FnOnce() -> R,
344 ) -> R
345 where
346 J: JournalTr<Database: Database> + Debug,
347 {
348 let internals = EvmInternals::new(journal, block_env, cfg, tx_env);
349 let mut provider = EvmPrecompileStorageProvider::new_max_gas(internals, cfg);
350
351 Self::enter(&mut provider, f)
353 }
354
355 pub fn enter_evm_without_tip1060_accounting<J, R>(
361 journal: &'evm mut J,
362 block_env: &'evm dyn Block,
363 cfg: &CfgEnv<TempoHardfork>,
364 tx_env: &'evm impl Transaction,
365 f: impl FnOnce() -> R,
366 ) -> R
367 where
368 J: JournalTr<Database: Database> + Debug,
369 {
370 let internals = EvmInternals::new(journal, block_env, cfg, tx_env);
371 let mut provider = EvmPrecompileStorageProvider::new_max_gas(internals, cfg);
372 provider.set_tip1060_storage_credits(false);
373
374 Self::enter(&mut provider, f)
375 }
376
377 pub fn enter_ctx<C, R>(ctx: &mut C, f: impl FnOnce() -> R) -> R
380 where
381 C: ContextTr<Cfg = CfgEnv<TempoHardfork>, Journal: Debug, Db: Database>,
382 {
383 let (tx, block, cfg, journal) = ctx.tx_block_cfg_journal_mut();
384 Self::enter_evm(journal, block, cfg, tx, f)
385 }
386
387 pub fn enter_ctx_with_gas_limit<C, R>(
390 ctx: &mut C,
391 gas_limit: u64,
392 reservoir: u64,
393 f: impl FnOnce() -> R,
394 ) -> (R, u64)
395 where
396 C: ContextTr<Cfg = CfgEnv<TempoHardfork>, Journal: Debug, Db: Database>,
397 {
398 let (tx, block, cfg, journal) = ctx.tx_block_cfg_journal_mut();
399 let internals = EvmInternals::new(journal, block, cfg, tx);
400 let mut provider =
401 EvmPrecompileStorageProvider::new_with_gas_limit(internals, cfg, gas_limit, reservoir);
402 let result = Self::enter(&mut provider, f);
403 let gas_used = provider.gas_used();
404 (result, gas_used)
405 }
406
407 pub fn enter_precompile<J, P, R>(
409 journal: &'evm mut J,
410 block_env: &'evm dyn Block,
411 cfg: &CfgEnv<TempoHardfork>,
412 tx_env: &'evm impl Transaction,
413 f: impl FnOnce(P) -> R,
414 ) -> R
415 where
416 J: JournalTr<Database: Database> + Debug,
417 P: Precompile + Default,
418 {
419 Self::enter_evm(journal, block_env, cfg, tx_env, || f(P::default()))
422 }
423}
424
425#[cfg(any(test, feature = "test-utils"))]
426use crate::storage::hashmap::HashMapStorageProvider;
427
428#[cfg(any(test, feature = "test-utils"))]
429impl StorageCtx {
430 #[allow(clippy::mut_from_ref)]
435 fn as_hashmap(&self) -> &mut HashMapStorageProvider {
436 Self::with_storage(|s| {
437 unsafe {
440 extend_lifetime_mut(
441 &mut *(s as *mut dyn PrecompileStorageProvider as *mut HashMapStorageProvider),
442 )
443 }
444 })
445 }
446
447 pub fn get_account_info(&self, address: Address) -> Option<&AccountInfo> {
449 self.as_hashmap().get_account_info(address)
450 }
451
452 pub fn get_events(&self, address: Address) -> &Vec<LogData> {
454 self.as_hashmap().get_events(address)
455 }
456
457 pub fn set_nonce(&mut self, address: Address, nonce: u64) {
459 self.as_hashmap().set_nonce(address, nonce)
460 }
461
462 pub fn set_timestamp(&mut self, timestamp: U256) {
464 self.as_hashmap().set_timestamp(timestamp)
465 }
466
467 pub fn set_beneficiary(&mut self, beneficiary: Address) {
469 self.as_hashmap().set_beneficiary(beneficiary)
470 }
471
472 pub fn set_block_number(&mut self, block_number: u64) {
474 self.as_hashmap().set_block_number(block_number)
475 }
476
477 pub fn set_spec(&mut self, spec: TempoHardfork) {
479 self.as_hashmap().set_spec(spec)
480 }
481
482 pub fn clear_transient(&mut self) {
484 self.as_hashmap().clear_transient()
485 }
486
487 pub fn clear_events(&mut self, address: Address) {
492 self.as_hashmap().clear_events(address);
493 }
494
495 pub fn counter_sload(&self) -> u64 {
497 self.as_hashmap().counter_sload()
498 }
499
500 pub fn counter_sstore(&self) -> u64 {
502 self.as_hashmap().counter_sstore()
503 }
504
505 pub fn reset_counters(&mut self) {
507 self.as_hashmap().reset_counters()
508 }
509
510 pub fn has_bytecode(&self, address: Address) -> Result<bool> {
512 self.with_account_info(address, |info| Ok(!info.is_empty_code_hash()))
513 }
514}
515
516#[cfg(any(test, feature = "test-utils"))]
520unsafe fn extend_lifetime_mut<'b, T: ?Sized>(r: &mut T) -> &'b mut T {
521 unsafe { &mut *(r as *mut T) }
522}
523
524#[cfg(test)]
525mod tests {
526 use super::*;
527 use alloy::primitives::U256;
528 use tempo_chainspec::hardfork::TempoHardfork;
529
530 fn t1c_storage() -> HashMapStorageProvider {
531 HashMapStorageProvider::new_with_spec(1, TempoHardfork::T1C)
532 }
533
534 #[test]
535 #[should_panic(expected = "already borrowed")]
536 fn test_reentrant_with_storage_panics() {
537 let mut storage = HashMapStorageProvider::new(1);
538 StorageCtx::enter(&mut storage, || {
539 StorageCtx::with_storage(|_| {
541 StorageCtx::with_storage(|_| ())
543 })
544 });
545 }
546
547 #[test]
548 fn test_checkpoint_commit_and_revert() {
549 let mut storage = t1c_storage();
550 let addr = Address::ZERO;
551 let key = U256::from(1);
552
553 StorageCtx::enter(&mut storage, || {
554 let mut ctx = StorageCtx;
555
556 ctx.sstore(addr, key, U256::from(42)).unwrap();
558 let guard = ctx.checkpoint();
559 ctx.sstore(addr, key, U256::from(99)).unwrap();
560 guard.commit();
561 assert_eq!(ctx.sload(addr, key).unwrap(), U256::from(99));
562
563 {
565 let _guard = ctx.checkpoint();
566 ctx.sstore(addr, key, U256::from(1)).unwrap();
567 }
568 assert_eq!(ctx.sload(addr, key).unwrap(), U256::from(99));
569 });
570 }
571
572 #[test]
573 fn test_nested_checkpoints_lifo() {
574 let mut storage = t1c_storage();
575 let addr = Address::ZERO;
576 let key = U256::from(1);
577
578 StorageCtx::enter(&mut storage, || {
579 let mut ctx = StorageCtx;
580 ctx.sstore(addr, key, U256::from(10)).unwrap();
581
582 let outer = ctx.checkpoint();
584 ctx.sstore(addr, key, U256::from(20)).unwrap();
585 let inner = ctx.checkpoint();
586 ctx.sstore(addr, key, U256::from(30)).unwrap();
587 inner.commit();
588 outer.commit();
589 assert_eq!(ctx.sload(addr, key).unwrap(), U256::from(30));
590
591 let outer = ctx.checkpoint();
593 ctx.sstore(addr, key, U256::from(40)).unwrap();
594 {
595 let _inner = ctx.checkpoint();
596 ctx.sstore(addr, key, U256::from(50)).unwrap();
597 }
598 outer.commit();
599 assert_eq!(ctx.sload(addr, key).unwrap(), U256::from(40));
600 });
601 }
602
603 #[test]
604 #[should_panic(expected = "out-of-order")]
605 fn test_nested_checkpoints_out_of_order_commit_panics() {
606 let mut storage = t1c_storage();
607
608 StorageCtx::enter(&mut storage, || {
609 let mut ctx = StorageCtx;
610
611 let outer = ctx.checkpoint();
612 let _inner = ctx.checkpoint();
613
614 outer.commit();
616 });
617 }
618
619 #[test]
620 fn test_checkpoint_noop_pre_t1c() {
621 let mut storage = HashMapStorageProvider::new(1); let addr = Address::ZERO;
623 let key = U256::from(1);
624
625 StorageCtx::enter(&mut storage, || {
626 let mut ctx = StorageCtx;
627
628 ctx.sstore(addr, key, U256::from(42)).unwrap();
629 {
630 let _guard = ctx.checkpoint(); ctx.sstore(addr, key, U256::from(99)).unwrap();
632 }
634 assert_eq!(ctx.sload(addr, key).unwrap(), U256::from(99));
636 });
637 }
638}