1use alloy_primitives::U256;
9use arb_primitives::multigas::MultiGas;
10
11const WARM: u64 = 100; const COLD_SLOAD: u64 = 2100; const COLD_ACCOUNT: u64 = 2600; const SSTORE_SET: u64 = 20_000; const SSTORE_RESET_WRITE: u64 = 2_900; const NEW_ACCOUNT: u64 = 25_000; const SELFDESTRUCT_BASE_WRITE: u64 = 4_900; const COPY_WORD: u64 = 3; const LOG_TOPIC_HISTORY: u64 = 256; const LOG_DATA: u64 = 8; #[derive(Debug, Clone, Copy)]
25pub enum OpKind {
26 StorageRead { cold: bool },
28 StorageWrite {
30 cold: bool,
31 original: U256,
32 present: U256,
33 new: U256,
34 },
35 AccountAccess { cold: bool },
38 ExtCodeCopy { cold: bool, words: u64 },
40 Log { topics: u8, data_len: u64 },
42 Call { cold: bool, new_account: bool },
44 SelfDestruct { cold: bool, new_account: bool },
46 Other,
48}
49
50pub fn classify(kind: OpKind, gas: u64) -> MultiGas {
54 let non_comp = non_computation(kind);
55 if non_comp.single_gas() > gas {
59 return MultiGas::computation_gas(gas);
60 }
61 let computation = gas - non_comp.single_gas();
62 non_comp.saturating_add(MultiGas::computation_gas(computation))
63}
64
65fn non_computation(kind: OpKind) -> MultiGas {
66 use arb_primitives::multigas::ResourceKind::*;
67 match kind {
68 OpKind::StorageRead { cold } => {
69 MultiGas::storage_access_read_gas(if cold { COLD_SLOAD - WARM } else { 0 })
70 }
71 OpKind::StorageWrite {
72 cold,
73 original,
74 present,
75 new,
76 } => {
77 let mut pairs = Vec::with_capacity(2);
78 if cold {
79 pairs.push((StorageAccessRead, COLD_SLOAD));
80 }
81 if present != new && original == present {
82 if original.is_zero() {
83 pairs.push((StorageGrowth, SSTORE_SET));
84 } else {
85 pairs.push((StorageAccessWrite, SSTORE_RESET_WRITE));
86 }
87 }
88 MultiGas::from_pairs(&pairs)
89 }
90 OpKind::AccountAccess { cold } => {
91 MultiGas::storage_access_read_gas(if cold { COLD_ACCOUNT - WARM } else { 0 })
92 }
93 OpKind::ExtCodeCopy { cold, words } => {
94 let cold_part = if cold { COLD_ACCOUNT - WARM } else { 0 };
95 MultiGas::storage_access_read_gas(cold_part + words * COPY_WORD)
96 }
97 OpKind::Log { topics, data_len } => {
98 MultiGas::history_growth_gas(LOG_TOPIC_HISTORY * topics as u64 + LOG_DATA * data_len)
99 }
100 OpKind::Call { cold, new_account } => {
101 let mut pairs = Vec::with_capacity(2);
102 if cold {
103 pairs.push((StorageAccessRead, COLD_ACCOUNT - WARM));
104 }
105 if new_account {
106 pairs.push((StorageGrowth, NEW_ACCOUNT));
107 }
108 MultiGas::from_pairs(&pairs)
109 }
110 OpKind::SelfDestruct { cold, new_account } => {
111 let mut pairs = vec![(StorageAccessWrite, SELFDESTRUCT_BASE_WRITE)];
112 if cold {
113 pairs.push((StorageAccessRead, COLD_ACCOUNT));
114 }
115 if new_account {
116 pairs.push((StorageGrowth, NEW_ACCOUNT));
117 }
118 MultiGas::from_pairs(&pairs)
119 }
120 OpKind::Other => MultiGas::zero(),
121 }
122}
123
124#[cfg(test)]
125mod tests {
126 use arb_primitives::multigas::ResourceKind::*;
127
128 use super::*;
129
130 fn dims(mg: &MultiGas) -> (u64, u64, u64, u64, u64) {
131 (
132 mg.get(Computation),
133 mg.get(StorageAccessRead),
134 mg.get(StorageAccessWrite),
135 mg.get(StorageGrowth),
136 mg.get(HistoryGrowth),
137 )
138 }
139
140 #[test]
141 fn sload_warm() {
142 let mg = classify(OpKind::StorageRead { cold: false }, WARM);
143 assert_eq!(dims(&mg), (100, 0, 0, 0, 0));
144 }
145
146 #[test]
147 fn sload_cold() {
148 let mg = classify(OpKind::StorageRead { cold: true }, COLD_SLOAD);
149 assert_eq!(dims(&mg), (100, 2000, 0, 0, 0));
150 assert_eq!(mg.single_gas(), COLD_SLOAD);
151 }
152
153 #[test]
154 fn sstore_cold_create() {
155 let mg = classify(
156 OpKind::StorageWrite {
157 cold: true,
158 original: U256::ZERO,
159 present: U256::ZERO,
160 new: U256::from(1),
161 },
162 COLD_SLOAD + SSTORE_SET,
163 );
164 assert_eq!(dims(&mg), (0, 2100, 0, 20000, 0));
165 }
166
167 #[test]
168 fn sstore_warm_reset() {
169 let mg = classify(
170 OpKind::StorageWrite {
171 cold: false,
172 original: U256::from(7),
173 present: U256::from(7),
174 new: U256::from(9),
175 },
176 SSTORE_RESET_WRITE,
177 );
178 assert_eq!(dims(&mg), (0, 0, 2900, 0, 0));
179 }
180
181 #[test]
182 fn sstore_cold_reset() {
183 let mg = classify(
184 OpKind::StorageWrite {
185 cold: true,
186 original: U256::from(7),
187 present: U256::from(7),
188 new: U256::from(9),
189 },
190 COLD_SLOAD + SSTORE_RESET_WRITE,
191 );
192 assert_eq!(dims(&mg), (0, 2100, 2900, 0, 0));
193 }
194
195 #[test]
196 fn sstore_warm_dirty_and_noop_are_computation() {
197 let dirty = classify(
198 OpKind::StorageWrite {
199 cold: false,
200 original: U256::from(1),
201 present: U256::from(2),
202 new: U256::from(3),
203 },
204 WARM,
205 );
206 assert_eq!(dims(&dirty), (100, 0, 0, 0, 0));
207 let noop = classify(
208 OpKind::StorageWrite {
209 cold: false,
210 original: U256::from(5),
211 present: U256::from(5),
212 new: U256::from(5),
213 },
214 WARM,
215 );
216 assert_eq!(dims(&noop), (100, 0, 0, 0, 0));
217 }
218
219 #[test]
220 fn account_access_cold_warm() {
221 assert_eq!(
222 dims(&classify(
223 OpKind::AccountAccess { cold: true },
224 COLD_ACCOUNT
225 )),
226 (100, 2500, 0, 0, 0)
227 );
228 assert_eq!(
229 dims(&classify(OpKind::AccountAccess { cold: false }, WARM)),
230 (100, 0, 0, 0, 0)
231 );
232 }
233
234 #[test]
235 fn extcodecopy_cold_with_copy() {
236 let total = WARM + (COLD_ACCOUNT - WARM) + 3 * COPY_WORD + 12;
238 let mg = classify(
239 OpKind::ExtCodeCopy {
240 cold: true,
241 words: 3,
242 },
243 total,
244 );
245 assert_eq!(dims(&mg), (112, 2509, 0, 0, 0));
246 }
247
248 #[test]
249 fn log_two_topics() {
250 let data_len = 10u64;
252 let total = 375 + 2 * 119 + LOG_TOPIC_HISTORY * 2 + LOG_DATA * data_len;
253 let mg = classify(
254 OpKind::Log {
255 topics: 2,
256 data_len,
257 },
258 total,
259 );
260 assert_eq!(dims(&mg), (613, 0, 0, 0, 592));
261 }
262
263 #[test]
264 fn call_cold_new_account_value() {
265 let total = WARM + (COLD_ACCOUNT - WARM) + 9000 + NEW_ACCOUNT;
267 let mg = classify(
268 OpKind::Call {
269 cold: true,
270 new_account: true,
271 },
272 total,
273 );
274 assert_eq!(dims(&mg), (9100, 2500, 0, 25000, 0));
275 }
276
277 #[test]
278 fn selfdestruct_cold_new_account() {
279 let total = 5000 + COLD_ACCOUNT + NEW_ACCOUNT;
281 let mg = classify(
282 OpKind::SelfDestruct {
283 cold: true,
284 new_account: true,
285 },
286 total,
287 );
288 assert_eq!(dims(&mg), (100, 2600, 4900, 25000, 0));
289 }
290
291 #[test]
292 fn other_is_all_computation() {
293 let mg = classify(OpKind::Other, 42);
294 assert_eq!(dims(&mg), (42, 0, 0, 0, 0));
295 }
296
297 #[test]
298 fn out_of_gas_opcode_attributes_only_consumed_gas() {
299 let sload = classify(OpKind::StorageRead { cold: true }, 1_696);
302 assert_eq!(sload.single_gas(), 1_696);
303 assert_eq!(dims(&sload), (1_696, 0, 0, 0, 0));
304 let sstore = classify(
306 OpKind::StorageWrite {
307 cold: false,
308 original: U256::from(7),
309 present: U256::from(7),
310 new: U256::from(9),
311 },
312 2_707,
313 );
314 assert_eq!(sstore.single_gas(), 2_707);
315 let call = classify(
317 OpKind::Call {
318 cold: true,
319 new_account: true,
320 },
321 5_000,
322 );
323 assert_eq!(call.single_gas(), 5_000);
324 }
325
326 #[test]
327 fn single_gas_always_matches_total() {
328 for (kind, gas) in [
329 (OpKind::StorageRead { cold: true }, COLD_SLOAD),
330 (
331 OpKind::Call {
332 cold: true,
333 new_account: true,
334 },
335 36600,
336 ),
337 (
338 OpKind::Log {
339 topics: 4,
340 data_len: 100,
341 },
342 9999,
343 ),
344 (OpKind::Other, 21000),
345 ] {
346 assert_eq!(classify(kind, gas).single_gas(), gas);
347 }
348 }
349}