tempo_precompiles/tip1060_storage_credits/
gas_state.rs1use super::{CreditMode, TIP1060StorageCredits, TransientState};
12use crate::storage::FromWord;
13use alloy::primitives::{Address, IntoLogData, LogData, U256};
14use revm::{
15 context_interface::cfg::GasParams,
16 interpreter::{InstructionResult, SStoreResult, StateLoad, gas::GasTracker},
17};
18use tempo_chainspec::constants::gas::STORAGE_CREDIT_VALUE;
19use tempo_contracts::precompiles::{STORAGE_CREDITS_ADDRESS, TIP1060StorageCreditsEvent};
20
21pub trait StorageCreditsError: Sized {
23 fn out_of_gas() -> Self;
24 fn fatal_external() -> Self;
25}
26
27impl StorageCreditsError for InstructionResult {
28 fn out_of_gas() -> Self {
29 Self::OutOfGas
30 }
31
32 fn fatal_external() -> Self {
33 Self::FatalExternalError
34 }
35}
36
37pub trait StorageCreditsBackend {
39 type Error: StorageCreditsError;
40
41 fn gas_params(&self) -> &GasParams;
43
44 fn gas_tracker(&mut self) -> &mut GasTracker;
46
47 #[inline]
49 fn charge_gas(&mut self, cost: u64) -> Result<(), Self::Error> {
50 self.gas_tracker()
51 .record_regular_cost(cost)
52 .then_some(())
53 .ok_or_else(Self::Error::out_of_gas)
54 }
55
56 fn sload(
58 &mut self,
59 address: Address,
60 key: U256,
61 skip_cold_load: bool,
62 ) -> Result<StateLoad<U256>, Self::Error>;
63
64 fn sstore(
66 &mut self,
67 address: Address,
68 key: U256,
69 value: U256,
70 skip_cold_load: bool,
71 ) -> Result<StateLoad<SStoreResult>, Self::Error>;
72
73 fn tload(&mut self, address: Address, key: U256) -> U256;
75
76 fn tstore(&mut self, address: Address, key: U256, value: U256);
78
79 fn emit_event(&mut self, address: Address, event: LogData) -> Result<(), Self::Error>;
81}
82
83#[inline]
84fn emit_mode_updated<B: StorageCreditsBackend>(
85 backend: &mut B,
86 account: Address,
87 new_mode: CreditMode,
88) -> Result<(), B::Error> {
89 let event = TIP1060StorageCreditsEvent::mode_updated(account, new_mode.into());
90 backend.emit_event(STORAGE_CREDITS_ADDRESS, event.into_log_data())
91}
92
93#[inline]
94fn store_credit_state<B: StorageCreditsBackend>(
95 backend: &mut B,
96 key: U256,
97 state: TransientState,
98) -> Result<(), B::Error> {
99 backend.tstore(STORAGE_CREDITS_ADDRESS, key, state.into());
100 Ok(())
101}
102
103pub fn sstore_storage_credits<B: StorageCreditsBackend>(
107 backend: &mut B,
108 owner: Address,
109 caller_state_load: &StateLoad<SStoreResult>,
110) -> Result<(), B::Error> {
111 let values = &caller_state_load.data;
112
113 if values.is_present_zero() == values.is_new_zero() {
116 return Ok(());
117 }
118
119 if owner == STORAGE_CREDITS_ADDRESS {
122 return Ok(());
123 }
124
125 let warm_storage_read_cost = backend.gas_params().warm_storage_read_cost();
127 backend.charge_gas(warm_storage_read_cost)?;
128
129 let account_slot = TIP1060StorageCredits::slot(owner);
130 let additional_cold_cost = backend.gas_params().cold_storage_additional_cost();
131 let skip_cold = backend.gas_tracker().remaining() < additional_cold_cost;
132 let storage_credit_state_load =
133 backend.sload(STORAGE_CREDITS_ADDRESS, account_slot, skip_cold)?;
134 if storage_credit_state_load.is_cold {
135 backend.charge_gas(additional_cold_cost)?;
136 }
137
138 let mut credit =
139 u64::from_word(storage_credit_state_load.data).map_err(|_| B::Error::fatal_external())?;
140
141 let mut was_changed = false;
142 if values.is_new_zero() {
143 credit = credit.saturating_add(1);
145 was_changed = true;
146 } else {
147 let mut transient_state: TransientState = backend
151 .tload(STORAGE_CREDITS_ADDRESS, account_slot)
152 .try_into()
153 .map_err(|_| B::Error::fatal_external())?;
154
155 match transient_state.mode {
156 CreditMode::Direct if credit > 0 && transient_state.budget > 0 => {
157 credit -= 1;
159 was_changed = true;
160
161 if transient_state.budget != u64::MAX {
163 transient_state.budget -= 1;
164 if transient_state.budget == 0 {
165 transient_state.mode = CreditMode::Preserve;
167 emit_mode_updated(backend, owner, CreditMode::Preserve)?;
168 }
169 store_credit_state(backend, account_slot, transient_state)?;
170 }
171 }
172 CreditMode::Direct => {
173 if transient_state.budget == 0 {
175 transient_state.mode = CreditMode::Preserve;
177 store_credit_state(backend, account_slot, transient_state)?;
178 emit_mode_updated(backend, owner, CreditMode::Preserve)?;
179 }
180 backend.charge_gas(STORAGE_CREDIT_VALUE)?;
181 }
182 CreditMode::Preserve => {
183 backend.charge_gas(STORAGE_CREDIT_VALUE)?;
185 }
186 CreditMode::Refund => {
187 backend.charge_gas(STORAGE_CREDIT_VALUE)?;
190 transient_state.pending_refunds = transient_state.pending_refunds.saturating_add(1);
191 store_credit_state(backend, account_slot, transient_state)?;
192 }
193 }
194 }
195
196 if was_changed {
197 let result = backend
199 .sstore(
200 STORAGE_CREDITS_ADDRESS,
201 account_slot,
202 U256::from(credit),
203 false,
204 )?
205 .data;
206
207 if result.new_values_changes_present() && result.is_original_eq_present() {
209 backend.charge_gas(backend.gas_params().sstore_reset_without_cold_load_cost())?;
210 };
211 }
212
213 Ok(())
214}