Skip to main content

tempo_precompiles/storage/
mod.rs

1//! EVM storage abstraction layer for Tempo precompile contracts.
2//!
3//! Provides traits and types for reading/writing contract state from EVM storage,
4//! including persistent (SLOAD/SSTORE) and transient (TLOAD/TSTORE) operations.
5
6pub mod evm;
7pub mod hashmap;
8
9pub mod thread_local;
10use alloy::primitives::keccak256;
11pub use thread_local::{CheckpointGuard, StorageCtx};
12
13mod types;
14pub use types::*;
15
16pub mod packing;
17pub use packing::FieldLocation;
18pub use types::mapping as slots;
19
20use alloy::primitives::{Address, B256, LogData, Signature, U256};
21use revm::{
22    context::journaled_state::JournalCheckpoint,
23    interpreter::gas::{KECCAK256, KECCAK256WORD},
24    state::{AccountInfo, Bytecode},
25};
26use tempo_chainspec::hardfork::TempoHardfork;
27
28use crate::error::{Result, TempoPrecompileError};
29
30/// Low-level storage provider for interacting with the EVM.
31///
32/// # Implementations
33///
34/// - `EvmPrecompileStorageProvider` - Production EVM storage
35/// - `HashMapStorageProvider` - Test storage
36///
37/// # Sync with `[StorageCtx]`
38///
39/// `StorageCtx` mirrors these methods with split mutability for read (staticcall) vs write (call).
40/// When adding new methods here, remember to add corresponding methods to `StorageCtx`.
41pub trait PrecompileStorageProvider {
42    /// Returns the chain ID.
43    fn chain_id(&self) -> u64;
44
45    /// Returns the current block timestamp.
46    fn timestamp(&self) -> U256;
47
48    /// Returns the current block beneficiary (coinbase).
49    fn beneficiary(&self) -> Address;
50
51    /// Returns the current block number.
52    fn block_number(&self) -> u64;
53
54    /// Sets the bytecode at the given address.
55    fn set_code(&mut self, address: Address, code: Bytecode) -> Result<()>;
56
57    /// Executes a closure with access to the account info for the given address.
58    fn with_account_info(
59        &mut self,
60        address: Address,
61        f: &mut dyn FnMut(&AccountInfo),
62    ) -> Result<()>;
63
64    /// Performs an SLOAD operation (persistent storage read).
65    fn sload(&mut self, address: Address, key: U256) -> Result<U256>;
66
67    /// Performs a TLOAD operation (transient storage read).
68    fn tload(&mut self, address: Address, key: U256) -> Result<U256>;
69
70    /// Performs an SSTORE operation (persistent storage write).
71    fn sstore(&mut self, address: Address, key: U256, value: U256) -> Result<()>;
72
73    /// Performs a TSTORE operation (transient storage write).
74    fn tstore(&mut self, address: Address, key: U256, value: U256) -> Result<()>;
75
76    /// Emits an event from the given contract address.
77    fn emit_event(&mut self, address: Address, event: LogData) -> Result<()>;
78
79    /// Deducts gas from the remaining gas and returns an error if insufficient.
80    fn deduct_gas(&mut self, gas: u64) -> Result<()>;
81
82    /// Add refund to the refund gas counter.
83    fn refund_gas(&mut self, gas: i64);
84
85    /// Returns the gas limit for this precompile call.
86    fn gas_limit(&self) -> u64;
87
88    /// Returns the gas used so far.
89    fn gas_used(&self) -> u64;
90
91    /// Returns the state-creating gas used so far (cold SSTORE zero->non-zero, code deposit).
92    fn state_gas_used(&self) -> u64;
93
94    /// Returns the gas refunded so far.
95    fn gas_refunded(&self) -> i64;
96
97    /// Returns the state gas reservoir.
98    fn reservoir(&self) -> u64;
99
100    /// Returns the currently active hardfork.
101    fn spec(&self) -> TempoHardfork;
102
103    /// Mirrors `CfgEnv::enable_amsterdam_eip8037`. Used by precompiles to gate the TIP-1016
104    /// regular/state gas split independently of the active hardfork.
105    fn amsterdam_eip8037_enabled(&self) -> bool;
106
107    /// Returns whether the current call context is static.
108    fn is_static(&self) -> bool;
109
110    /// Creates a new journal checkpoint so that all subsequent state-changing
111    /// operations can be atomically committed ([`checkpoint_commit`](Self::checkpoint_commit))
112    /// or reverted ([`checkpoint_revert`](Self::checkpoint_revert)).
113    ///
114    /// Prefer [`StorageCtx::checkpoint`] which returns a [`CheckpointGuard`] that
115    /// auto-reverts on drop and is hardfork-aware (no-op pre-T1C).
116    fn checkpoint(&mut self) -> JournalCheckpoint;
117
118    /// Commits all state changes since the given checkpoint.
119    ///
120    /// Prefer [`CheckpointGuard::commit`].
121    fn checkpoint_commit(&mut self, checkpoint: JournalCheckpoint);
122
123    /// Reverts all state changes back to the given checkpoint.
124    ///
125    /// Prefer [`CheckpointGuard`] (auto-reverts on drop).
126    fn checkpoint_revert(&mut self, checkpoint: JournalCheckpoint);
127
128    /// Computes keccak256 and charges the appropriate gas.
129    ///
130    /// Implementations should use this over naked `keccak256` call to ensure gas is accounted for.
131    fn keccak256(&mut self, data: &[u8]) -> Result<B256> {
132        let num_words =
133            u64::try_from(data.len().div_ceil(32)).map_err(|_| TempoPrecompileError::OutOfGas)?;
134        let price = KECCAK256WORD
135            .checked_mul(num_words)
136            .and_then(|w| w.checked_add(KECCAK256))
137            .ok_or(TempoPrecompileError::OutOfGas)?;
138        self.deduct_gas(price)?;
139        Ok(keccak256(data))
140    }
141
142    /// Recovers the signer address from an ECDSA signature and charges ecrecover gas.
143    /// As per [TIP-1004], it only accepts `v` values of `27` or `28` (no `0`/`1` normalization).
144    ///
145    /// Returns `Ok(None)` on invalid signatures; callers map to domain-specific errors.
146    ///
147    /// [TIP-1004]: <https://github.com/tempoxyz/tempo/blob/main/tips/tip-1004.md#signature-validation>
148    fn recover_signer(&mut self, digest: B256, v: u8, r: B256, s: B256) -> Result<Option<Address>> {
149        self.deduct_gas(crate::ECRECOVER_GAS)?;
150
151        if v != 27 && v != 28 {
152            return Ok(None);
153        }
154
155        let parity = v == 28;
156        let sig = Signature::from_scalars_and_parity(r, s, parity);
157        let recovered = alloy::consensus::crypto::secp256k1::recover_signer(&sig, digest);
158
159        Ok(recovered.ok().filter(|addr| !addr.is_zero()))
160    }
161}
162
163/// Storage operations for a given (contract) address.
164///
165/// Abstracts over persistent storage (SLOAD/SSTORE) and transient storage (TLOAD/TSTORE).
166/// Implementors must route to the appropriate opcode.
167pub trait StorageOps {
168    /// Stores a value at the provided slot.
169    fn store(&mut self, slot: U256, value: U256) -> Result<()>;
170    /// Loads a value from the provided slot.
171    fn load(&self, slot: U256) -> Result<U256>;
172}
173
174/// Trait providing access to a contract's address.
175///
176/// Automatically implemented by the `#[contract]` macro.
177pub trait ContractStorage {
178    /// Contract address.
179    fn address(&self) -> Address;
180
181    /// Contract storage accessor.
182    fn storage(&self) -> &StorageCtx;
183
184    /// Contract storage mutable accessor.
185    fn storage_mut(&mut self) -> &mut StorageCtx;
186
187    /// Returns true if the contract has been initialized (has bytecode deployed).
188    fn is_initialized(&self) -> Result<bool> {
189        self.storage()
190            .with_account_info(self.address(), |info| Ok(!info.is_empty_code_hash()))
191    }
192}