tempo_precompiles/storage_credits/
mod.rs1pub mod accounting;
4pub mod dispatch;
5
6pub use accounting::{StorageCreditsBackend, StorageCreditsErr, sstore_storage_credits};
7
8use crate::{
9 STORAGE_CREDITS_ADDRESS,
10 error::{Result, TempoPrecompileError},
11 storage::{Handler, LayoutCtx, StorableType},
12};
13use alloy::primitives::{Address, U256};
14use tempo_contracts::precompiles::{IStorageCredits::Mode, StorageCreditsError};
15use tempo_precompiles_macros::{Storable, contract};
16
17#[repr(u8)]
18#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Storable)]
19pub enum CreditMode {
20 #[default]
21 Refund,
22 Preserve,
23 Direct,
24}
25
26#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
28pub struct TransientState {
29 pub budget: u64,
31 pub mode: CreditMode,
33 pub pending_refunds: u64,
35}
36
37impl TryFrom<U256> for TransientState {
38 type Error = TempoPrecompileError;
39
40 #[inline]
41 fn try_from(value: U256) -> Result<Self> {
42 let limbs = value.as_limbs();
43 Ok(Self {
44 budget: limbs[0],
45 mode: (limbs[1] as u8).try_into()?,
46 pending_refunds: limbs[3],
47 })
48 }
49}
50
51impl From<TransientState> for U256 {
52 #[inline]
53 fn from(value: TransientState) -> Self {
54 Self::from_limbs([value.budget, value.mode as u64, 0, value.pending_refunds])
55 }
56}
57
58#[contract(addr = STORAGE_CREDITS_ADDRESS)]
72pub struct StorageCredits {}
73
74impl StorageCredits {
75 pub fn initialize(&mut self) -> Result<()> {
76 self.__initialize()
77 }
78
79 pub fn balance_of(&self, account: Address) -> Result<u64> {
80 u64::handle(Self::slot(account), LayoutCtx::FULL, self.address).read()
81 }
82
83 pub fn mode_of(&self, account: Address) -> Result<CreditMode> {
84 self.credit_state_of(account).map(|state| state.mode)
85 }
86
87 pub fn budget_of(&self, account: Address) -> Result<u64> {
88 self.credit_state_of(account).map(|state| state.budget)
89 }
90
91 pub fn set_mode(&mut self, msg_sender: Address, mode: Mode) -> Result<()> {
92 let mode = CreditMode::try_from(mode)?;
93 let budget = if matches!(mode, CreditMode::Direct) {
94 u64::MAX
95 } else {
96 0
97 };
98
99 self.write_mode_with_budget(msg_sender, mode, budget)
100 }
101
102 pub fn set_budget(&mut self, msg_sender: Address, credit_budget: u64) -> Result<()> {
103 self.write_mode_with_budget(msg_sender, CreditMode::Direct, credit_budget)
104 }
105
106 fn write_mode_with_budget(
107 &mut self,
108 msg_sender: Address,
109 mode: CreditMode,
110 budget: u64,
111 ) -> Result<()> {
112 let mut state = self.credit_state_of(msg_sender)?;
113 state.mode = mode;
114 state.budget = budget;
115 self.write_credit_state_of(msg_sender, state)
116 }
117
118 #[inline]
119 pub fn slot(account: Address) -> U256 {
120 U256::from_be_bytes(account.into_word().0)
121 }
122
123 #[inline]
124 fn credit_state_of(&self, account: Address) -> Result<TransientState> {
125 U256::handle(Self::slot(account), LayoutCtx::FULL, self.address)
126 .t_read()?
127 .try_into()
128 }
129
130 #[inline]
131 fn write_credit_state_of(&mut self, account: Address, state: TransientState) -> Result<()> {
132 U256::handle(Self::slot(account), LayoutCtx::FULL, self.address).t_write(state.into())
133 }
134}
135
136impl TryFrom<u8> for CreditMode {
137 type Error = TempoPrecompileError;
138
139 fn try_from(value: u8) -> Result<Self> {
140 match value {
141 0 => Ok(Self::Refund),
142 1 => Ok(Self::Preserve),
143 2 => Ok(Self::Direct),
144 _ => Err(StorageCreditsError::invalid_mode().into()),
145 }
146 }
147}
148
149impl TryFrom<Mode> for CreditMode {
150 type Error = TempoPrecompileError;
151
152 fn try_from(mode: Mode) -> Result<Self> {
153 match mode {
154 Mode::Refund => Ok(Self::Refund),
155 Mode::Preserve => Ok(Self::Preserve),
156 Mode::Direct => Ok(Self::Direct),
157 _ => Err(StorageCreditsError::invalid_mode().into()),
158 }
159 }
160}
161
162impl From<CreditMode> for Mode {
163 fn from(mode: CreditMode) -> Self {
164 match mode {
165 CreditMode::Refund => Self::Refund,
166 CreditMode::Preserve => Self::Preserve,
167 CreditMode::Direct => Self::Direct,
168 }
169 }
170}
171
172#[cfg(test)]
173mod tests {
174 use super::*;
175 use crate::storage::{StorageCtx, hashmap::HashMapStorageProvider};
176
177 #[test]
178 fn test_set_mode_budget_semantics() -> eyre::Result<()> {
179 let account = Address::repeat_byte(0x11);
180 let mut storage = HashMapStorageProvider::new(1);
181
182 StorageCtx::enter(&mut storage, || {
183 let mut credits = StorageCredits::new();
184
185 assert_eq!(credits.mode_of(account)?, CreditMode::Refund);
186 assert_eq!(credits.budget_of(account)?, 0);
187
188 credits.set_mode(account, Mode::Direct)?;
189 assert_eq!(credits.mode_of(account)?, CreditMode::Direct);
190 assert_eq!(credits.budget_of(account)?, u64::MAX);
191
192 credits.set_mode(account, Mode::Preserve)?;
193 assert_eq!(credits.mode_of(account)?, CreditMode::Preserve);
194 assert_eq!(credits.budget_of(account)?, 0);
195
196 credits.set_mode(account, Mode::Refund)?;
197 assert_eq!(credits.mode_of(account)?, CreditMode::Refund);
198 assert_eq!(credits.budget_of(account)?, 0);
199
200 Ok(())
201 })
202 }
203
204 #[test]
205 fn test_set_budget_zero_stays_direct_with_zero_budget() -> eyre::Result<()> {
206 let account = Address::repeat_byte(0x12);
207 let mut storage = HashMapStorageProvider::new(1);
208
209 StorageCtx::enter(&mut storage, || {
210 let mut credits = StorageCredits::new();
211
212 credits.set_budget(account, 2)?;
213 assert_eq!(credits.mode_of(account)?, CreditMode::Direct);
214 assert_eq!(credits.budget_of(account)?, 2);
215
216 credits.set_budget(account, 0)?;
217 assert_eq!(credits.mode_of(account)?, CreditMode::Direct);
218 assert_eq!(credits.budget_of(account)?, 0);
219
220 Ok(())
221 })
222 }
223}