tempo_precompiles/storage_credits/
accounting.rs1use super::{CreditMode, StorageCredits, TransientState};
12use crate::storage::FromWord;
13use alloy::primitives::{Address, 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;
20
21pub trait StorageCreditsErr: Sized {
23 fn out_of_gas() -> Self;
24 fn fatal_external() -> Self;
25}
26
27impl StorageCreditsErr 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: StorageCreditsErr;
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 #[inline]
81 fn tip1060_storage_credit_minting_enabled(&self) -> bool {
82 true
83 }
84}
85
86#[inline]
87fn store_credit_state<B: StorageCreditsBackend>(
88 backend: &mut B,
89 key: U256,
90 state: TransientState,
91) -> Result<(), B::Error> {
92 backend.tstore(STORAGE_CREDITS_ADDRESS, key, state.into());
93 Ok(())
94}
95
96pub fn sstore_storage_credits<B: StorageCreditsBackend>(
100 backend: &mut B,
101 owner: Address,
102 caller_state_load: &StateLoad<SStoreResult>,
103) -> Result<(), B::Error> {
104 let values = &caller_state_load.data;
105
106 if values.is_present_zero() == values.is_new_zero() {
109 return Ok(());
110 }
111
112 if owner == STORAGE_CREDITS_ADDRESS {
115 return Ok(());
116 }
117
118 let warm_storage_read_cost = backend.gas_params().warm_storage_read_cost();
120 backend.charge_gas(warm_storage_read_cost)?;
121
122 let account_slot = StorageCredits::slot(owner);
123 let additional_cold_cost = backend.gas_params().cold_storage_additional_cost();
124 let skip_cold = backend.gas_tracker().remaining() < additional_cold_cost;
125 let storage_credit_state_load =
126 backend.sload(STORAGE_CREDITS_ADDRESS, account_slot, skip_cold)?;
127 if storage_credit_state_load.is_cold {
128 backend.charge_gas(additional_cold_cost)?;
129 }
130
131 let mut credit =
132 u64::from_word(storage_credit_state_load.data).map_err(|_| B::Error::fatal_external())?;
133
134 let mut was_changed = false;
135 if values.is_new_zero() {
136 if backend.tip1060_storage_credit_minting_enabled() {
138 credit = credit.saturating_add(1);
139 was_changed = true;
140 }
141 } else {
142 let mut transient_state: TransientState = backend
146 .tload(STORAGE_CREDITS_ADDRESS, account_slot)
147 .try_into()
148 .map_err(|_| B::Error::fatal_external())?;
149
150 match transient_state.mode {
151 CreditMode::Direct if credit > 0 && transient_state.budget > 0 => {
152 credit -= 1;
154 was_changed = true;
155
156 if transient_state.budget != u64::MAX {
158 transient_state.budget -= 1;
159 store_credit_state(backend, account_slot, transient_state)?;
160 }
161 }
162 CreditMode::Direct | CreditMode::Preserve => {
163 backend.charge_gas(STORAGE_CREDIT_VALUE)?;
165 }
166 CreditMode::Refund => {
167 backend.charge_gas(STORAGE_CREDIT_VALUE)?;
170 transient_state.pending_refunds = transient_state.pending_refunds.saturating_add(1);
171 store_credit_state(backend, account_slot, transient_state)?;
172 }
173 }
174 }
175
176 if was_changed {
177 let result = backend
179 .sstore(
180 STORAGE_CREDITS_ADDRESS,
181 account_slot,
182 U256::from(credit),
183 false,
184 )?
185 .data;
186
187 if result.new_values_changes_present() && result.is_original_eq_present() {
189 backend.charge_gas(backend.gas_params().sstore_reset_without_cold_load_cost())?;
190 };
191 }
192
193 Ok(())
194}