pallet_evm/runner/
meter.rs1use alloc::collections::btree_set::BTreeSet;
19use evm::{
20 gasometer::{GasCost, StorageTarget},
21 Opcode,
22};
23use fp_evm::ACCOUNT_STORAGE_PROOF_SIZE;
24use sp_core::{H160, H256};
25
26#[derive(Debug, PartialEq)]
28pub enum MeterError {
29 LimitExceeded,
30}
31
32#[derive(Clone)]
34pub struct StorageMeter {
35 usage: u64,
36 limit: u64,
37 recorded_new_entries: BTreeSet<(H160, H256)>,
38}
39
40impl StorageMeter {
41 pub fn new(limit: u64) -> Self {
43 Self {
44 usage: 0,
45 limit,
46 recorded_new_entries: BTreeSet::new(),
47 }
48 }
49
50 pub fn record(&mut self, amount: u64) -> Result<(), MeterError> {
53 let usage = self.usage.checked_add(amount).ok_or_else(|| {
54 fp_evm::set_storage_oog();
55 MeterError::LimitExceeded
56 })?;
57
58 if usage > self.limit {
59 fp_evm::set_storage_oog();
60 return Err(MeterError::LimitExceeded);
61 }
62 self.usage = usage;
63 Ok(())
64 }
65
66 pub fn record_dynamic_opcode_cost(
68 &mut self,
69 _opcode: Opcode,
70 gas_cost: GasCost,
71 target: StorageTarget,
72 ) -> Result<(), MeterError> {
73 if let GasCost::SStore { original, new, .. } = gas_cost {
74 let (address, index) = match target {
81 StorageTarget::Slot(address, index) => (address, index),
82 _ => return Ok(()),
83 };
84 let recorded = self.recorded_new_entries.contains(&(address, index));
85 if !recorded && original == H256::default() && !new.is_zero() {
86 self.record(ACCOUNT_STORAGE_PROOF_SIZE)?;
87 self.recorded_new_entries.insert((address, index));
88 }
89 }
90 Ok(())
91 }
92
93 pub fn usage(&self) -> u64 {
95 self.usage
96 }
97
98 pub fn limit(&self) -> u64 {
100 self.limit
101 }
102
103 pub fn available(&self) -> u64 {
105 self.limit.saturating_sub(self.usage)
106 }
107
108 pub fn storage_to_gas(&self, ratio: u64) -> u64 {
110 self.usage.saturating_mul(ratio)
111 }
112}
113#[cfg(test)]
114mod test {
115 use super::*;
116
117 #[test]
119 fn test_basic_functionality() {
120 let limit = 100;
121 let mut meter = StorageMeter::new(limit);
122
123 assert_eq!(meter.usage(), 0);
124 assert_eq!(meter.limit(), limit);
125
126 let amount = 10;
127 meter.record(amount).unwrap();
128 assert_eq!(meter.usage(), amount);
129 }
130
131 #[test]
133 fn test_reaching_limit() {
134 let limit = 100;
135 let mut meter = StorageMeter::new(limit);
136
137 meter.record(limit - 1).unwrap();
139 assert_eq!(meter.usage(), limit - 1);
140
141 meter.record(1).unwrap();
143 assert_eq!(meter.usage(), limit);
144
145 let res = meter.record(1);
147 assert_eq!(meter.usage(), limit);
148 assert!(res.is_err());
149 assert_eq!(res, Err(MeterError::LimitExceeded));
150 }
151
152 #[test]
154 fn test_record_dynamic_opcode_cost() {
155 let limit = 200;
156 let mut meter = StorageMeter::new(limit);
157
158 let gas_cost = GasCost::SStore {
160 original: H256::from_low_u64_be(1),
161 current: Default::default(),
162 new: H256::from_low_u64_be(2),
163 target_is_cold: false,
164 };
165 let target = StorageTarget::Slot(H160::default(), H256::from_low_u64_be(1));
166
167 meter
168 .record_dynamic_opcode_cost(Opcode::SSTORE, gas_cost, target)
169 .unwrap();
170 assert_eq!(meter.usage(), 0);
171
172 let gas_cost = GasCost::SStore {
174 original: H256::default(),
175 current: Default::default(),
176 new: H256::from_low_u64_be(1),
177 target_is_cold: false,
178 };
179 meter
180 .record_dynamic_opcode_cost(Opcode::SSTORE, gas_cost, target)
181 .unwrap();
182 assert_eq!(meter.usage(), ACCOUNT_STORAGE_PROOF_SIZE);
183
184 let gas_cost = GasCost::SStore {
186 original: H256::default(),
187 current: Default::default(),
188 new: H256::from_low_u64_be(1),
189 target_is_cold: false,
190 };
191 meter
192 .record_dynamic_opcode_cost(Opcode::SSTORE, gas_cost, target)
193 .unwrap();
194 assert_eq!(meter.usage(), ACCOUNT_STORAGE_PROOF_SIZE);
195
196 let gas_cost = GasCost::SStore {
198 original: H256::default(),
199 current: Default::default(),
200 new: H256::from_low_u64_be(2),
201 target_is_cold: false,
202 };
203 let target = StorageTarget::Slot(H160::default(), H256::from_low_u64_be(2));
204
205 let res = meter.record_dynamic_opcode_cost(Opcode::SSTORE, gas_cost, target);
206 assert!(res.is_err());
207 assert_eq!(res, Err(MeterError::LimitExceeded));
208 assert_eq!(meter.usage(), 116);
209 }
210}