revm_interpreter/gas/
calc.rs1use super::constants::*;
2use crate::{num_words, tri, SStoreResult, SelfDestructResult, StateLoad};
3use context_interface::{
4 journaled_state::AccountLoad, transaction::AccessListItemTr as _, Transaction, TransactionType,
5};
6use primitives::{eip7702, hardfork::SpecId, U256};
7
8#[allow(clippy::collapsible_else_if)]
10#[inline]
11pub fn sstore_refund(spec_id: SpecId, vals: &SStoreResult) -> i64 {
12 if spec_id.is_enabled_in(SpecId::ISTANBUL) {
13 let sstore_clears_schedule = if spec_id.is_enabled_in(SpecId::LONDON) {
15 (SSTORE_RESET - COLD_SLOAD_COST + ACCESS_LIST_STORAGE_KEY) as i64
16 } else {
17 REFUND_SSTORE_CLEARS
18 };
19 if vals.is_new_eq_present() {
20 0
21 } else {
22 if vals.is_original_eq_present() && vals.is_new_zero() {
23 sstore_clears_schedule
24 } else {
25 let mut refund = 0;
26
27 if !vals.is_original_zero() {
28 if vals.is_present_zero() {
29 refund -= sstore_clears_schedule;
30 } else if vals.is_new_zero() {
31 refund += sstore_clears_schedule;
32 }
33 }
34
35 if vals.is_original_eq_new() {
36 let (gas_sstore_reset, gas_sload) = if spec_id.is_enabled_in(SpecId::BERLIN) {
37 (SSTORE_RESET - COLD_SLOAD_COST, WARM_STORAGE_READ_COST)
38 } else {
39 (SSTORE_RESET, sload_cost(spec_id, false))
40 };
41 if vals.is_original_zero() {
42 refund += (SSTORE_SET - gas_sload) as i64;
43 } else {
44 refund += (gas_sstore_reset - gas_sload) as i64;
45 }
46 }
47
48 refund
49 }
50 }
51 } else {
52 if !vals.is_present_zero() && vals.is_new_zero() {
53 REFUND_SSTORE_CLEARS
54 } else {
55 0
56 }
57 }
58}
59
60#[inline]
62pub const fn create2_cost(len: usize) -> Option<u64> {
63 CREATE.checked_add(tri!(cost_per_word(len, KECCAK256WORD)))
64}
65
66#[inline]
67const fn log2floor(value: U256) -> u64 {
68 let mut l: u64 = 256;
69 let mut i = 3;
70 loop {
71 if value.as_limbs()[i] == 0u64 {
72 l -= 64;
73 } else {
74 l -= value.as_limbs()[i].leading_zeros() as u64;
75 if l == 0 {
76 return l;
77 } else {
78 return l - 1;
79 }
80 }
81 if i == 0 {
82 break;
83 }
84 i -= 1;
85 }
86 l
87}
88
89#[inline]
91pub fn exp_cost(spec_id: SpecId, power: U256) -> Option<u64> {
92 if power.is_zero() {
93 Some(EXP)
94 } else {
95 let gas_byte = U256::from(if spec_id.is_enabled_in(SpecId::SPURIOUS_DRAGON) {
97 50
98 } else {
99 10
100 });
101 let gas = U256::from(EXP)
102 .checked_add(gas_byte.checked_mul(U256::from(log2floor(power) / 8 + 1))?)?;
103
104 u64::try_from(gas).ok()
105 }
106}
107
108#[inline]
110pub const fn copy_cost_verylow(len: usize) -> Option<u64> {
111 copy_cost(VERYLOW, len)
112}
113
114#[inline]
116pub const fn extcodecopy_cost(spec_id: SpecId, len: usize, is_cold: bool) -> Option<u64> {
117 let base_gas = if spec_id.is_enabled_in(SpecId::BERLIN) {
118 warm_cold_cost(is_cold)
119 } else if spec_id.is_enabled_in(SpecId::TANGERINE) {
120 700
121 } else {
122 20
123 };
124 copy_cost(base_gas, len)
125}
126
127#[inline]
128pub const fn copy_cost(base_cost: u64, len: usize) -> Option<u64> {
130 base_cost.checked_add(tri!(cost_per_word(len, COPY)))
131}
132
133#[inline]
135pub const fn log_cost(n: u8, len: u64) -> Option<u64> {
136 tri!(LOG.checked_add(tri!(LOGDATA.checked_mul(len)))).checked_add(LOGTOPIC * n as u64)
137}
138
139#[inline]
141pub const fn keccak256_cost(len: usize) -> Option<u64> {
142 KECCAK256.checked_add(tri!(cost_per_word(len, KECCAK256WORD)))
143}
144
145#[inline]
147pub const fn cost_per_word(len: usize, multiple: u64) -> Option<u64> {
148 multiple.checked_mul(num_words(len) as u64)
149}
150
151#[inline]
157pub const fn initcode_cost(len: usize) -> u64 {
158 let Some(cost) = cost_per_word(len, INITCODE_WORD_COST) else {
159 panic!("initcode cost overflow")
160 };
161 cost
162}
163
164#[inline]
166pub const fn sload_cost(spec_id: SpecId, is_cold: bool) -> u64 {
167 if spec_id.is_enabled_in(SpecId::BERLIN) {
168 if is_cold {
169 COLD_SLOAD_COST
170 } else {
171 WARM_STORAGE_READ_COST
172 }
173 } else if spec_id.is_enabled_in(SpecId::ISTANBUL) {
174 ISTANBUL_SLOAD_GAS
176 } else if spec_id.is_enabled_in(SpecId::TANGERINE) {
177 200
179 } else {
180 50
181 }
182}
183
184#[inline]
186pub const fn sstore_cost_static(spec_id: SpecId) -> u64 {
187 if spec_id.is_enabled_in(SpecId::BERLIN) {
188 WARM_STORAGE_READ_COST
189 } else if spec_id.is_enabled_in(SpecId::ISTANBUL) {
190 ISTANBUL_SLOAD_GAS
191 } else {
192 SSTORE_RESET
193 }
194}
195
196#[inline]
198pub const fn sstore_cost_dynamic(spec_id: SpecId, vals: &SStoreResult, is_cold: bool) -> u64 {
199 sstore_cost(spec_id, vals, is_cold) - sstore_cost_static(spec_id)
200}
201
202#[inline]
204pub const fn static_sstore_cost(spec_id: SpecId) -> u64 {
205 if spec_id.is_enabled_in(SpecId::BERLIN) {
206 WARM_STORAGE_READ_COST
207 } else if spec_id.is_enabled_in(SpecId::ISTANBUL) {
208 ISTANBUL_SLOAD_GAS
209 } else {
210 SSTORE_RESET
211 }
212}
213
214#[inline]
216pub const fn dyn_sstore_cost(spec_id: SpecId, vals: &SStoreResult, is_cold: bool) -> u64 {
217 sstore_cost(spec_id, vals, is_cold) - static_sstore_cost(spec_id)
218}
219
220#[inline]
222pub const fn sstore_cost(spec_id: SpecId, vals: &SStoreResult, is_cold: bool) -> u64 {
223 if spec_id.is_enabled_in(SpecId::BERLIN) {
224 let mut gas_cost = istanbul_sstore_cost::<WARM_STORAGE_READ_COST, WARM_SSTORE_RESET>(vals);
226
227 if is_cold {
228 gas_cost += COLD_SLOAD_COST;
229 }
230 gas_cost
231 } else if spec_id.is_enabled_in(SpecId::ISTANBUL) {
232 istanbul_sstore_cost::<ISTANBUL_SLOAD_GAS, SSTORE_RESET>(vals)
234 } else {
235 frontier_sstore_cost(vals)
237 }
238}
239
240#[inline]
242const fn istanbul_sstore_cost<const SLOAD_GAS: u64, const SSTORE_RESET_GAS: u64>(
243 vals: &SStoreResult,
244) -> u64 {
245 if vals.is_new_eq_present() {
246 SLOAD_GAS
247 } else if vals.is_original_eq_present() && vals.is_original_zero() {
248 SSTORE_SET
249 } else if vals.is_original_eq_present() {
250 SSTORE_RESET_GAS
251 } else {
252 SLOAD_GAS
253 }
254}
255
256#[inline]
258const fn frontier_sstore_cost(vals: &SStoreResult) -> u64 {
259 if vals.is_present_zero() && !vals.is_new_zero() {
260 SSTORE_SET
261 } else {
262 SSTORE_RESET
263 }
264}
265
266#[inline]
268pub const fn static_selfdestruct_cost(spec_id: SpecId) -> u64 {
269 if spec_id.is_enabled_in(SpecId::TANGERINE) {
271 5000
272 } else {
273 0
274 }
275}
276
277#[inline]
279pub const fn dyn_selfdestruct_cost(spec_id: SpecId, res: &StateLoad<SelfDestructResult>) -> u64 {
280 let is_tangerine = spec_id.is_enabled_in(SpecId::TANGERINE);
281 let mut gas = 0;
282
283 let should_charge_topup = if spec_id.is_enabled_in(SpecId::SPURIOUS_DRAGON) {
285 res.data.had_value && !res.data.target_exists
286 } else {
287 !res.data.target_exists
288 };
289
290 if is_tangerine && should_charge_topup {
292 gas += NEWACCOUNT
293 }
294
295 if res.is_cold {
296 gas += selfdestruct_cold_beneficiary_cost(spec_id);
297 }
298
299 gas
300}
301
302#[inline]
304pub const fn selfdestruct_cold_beneficiary_cost(spec_id: SpecId) -> u64 {
305 if spec_id.is_enabled_in(SpecId::BERLIN) {
306 COLD_ACCOUNT_ACCESS_COST
307 } else {
308 0
309 }
310}
311
312#[inline]
314pub const fn selfdestruct_cost(spec_id: SpecId, res: StateLoad<SelfDestructResult>) -> u64 {
315 static_selfdestruct_cost(spec_id) + dyn_selfdestruct_cost(spec_id, &res)
316}
317
318#[inline]
324pub fn calc_call_static_gas(spec_id: SpecId, has_transfer: bool) -> u64 {
325 let mut gas = if spec_id.is_enabled_in(SpecId::BERLIN) {
327 WARM_STORAGE_READ_COST
328 } else if spec_id.is_enabled_in(SpecId::TANGERINE) {
329 700
331 } else {
332 40
333 };
334
335 if has_transfer {
337 gas += CALLVALUE;
338 }
339
340 gas
341}
342
343#[inline]
345pub const fn warm_cold_cost(is_cold: bool) -> u64 {
346 if is_cold {
347 COLD_ACCOUNT_ACCESS_COST
348 } else {
349 WARM_STORAGE_READ_COST
350 }
351}
352
353#[inline]
357pub const fn warm_cold_cost_with_delegation(load: StateLoad<AccountLoad>) -> u64 {
358 let mut gas = warm_cold_cost(load.is_cold);
359 if let Some(is_cold) = load.data.is_delegate_account_cold {
360 gas += warm_cold_cost(is_cold);
361 }
362 gas
363}
364
365#[inline]
367pub const fn memory_gas(num_words: usize) -> u64 {
368 let num_words = num_words as u64;
369 MEMORY
370 .saturating_mul(num_words)
371 .saturating_add(num_words.saturating_mul(num_words) / 512)
372}
373
374#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
376#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
377pub struct InitialAndFloorGas {
378 pub initial_gas: u64,
380 pub floor_gas: u64,
383}
384
385impl InitialAndFloorGas {
386 #[inline]
388 pub const fn new(initial_gas: u64, floor_gas: u64) -> Self {
389 Self {
390 initial_gas,
391 floor_gas,
392 }
393 }
394}
395
396pub fn calculate_initial_tx_gas(
404 spec_id: SpecId,
405 input: &[u8],
406 is_create: bool,
407 access_list_accounts: u64,
408 access_list_storages: u64,
409 authorization_list_num: u64,
410) -> InitialAndFloorGas {
411 let mut gas = InitialAndFloorGas::default();
412
413 let tokens_in_calldata = get_tokens_in_calldata(input, spec_id.is_enabled_in(SpecId::ISTANBUL));
415
416 gas.initial_gas += tokens_in_calldata * STANDARD_TOKEN_COST;
417
418 gas.initial_gas += access_list_accounts * ACCESS_LIST_ADDRESS;
420 gas.initial_gas += access_list_storages * ACCESS_LIST_STORAGE_KEY;
421
422 gas.initial_gas += if is_create {
424 if spec_id.is_enabled_in(SpecId::HOMESTEAD) {
425 53000
427 } else {
428 21000
429 }
430 } else {
431 21000
432 };
433
434 if spec_id.is_enabled_in(SpecId::SHANGHAI) && is_create {
437 gas.initial_gas += initcode_cost(input.len())
438 }
439
440 if spec_id.is_enabled_in(SpecId::PRAGUE) {
442 gas.initial_gas += authorization_list_num * eip7702::PER_EMPTY_ACCOUNT_COST;
443
444 gas.floor_gas = calc_tx_floor_cost(tokens_in_calldata);
446 }
447
448 gas
449}
450
451pub fn calculate_initial_tx_gas_for_tx(tx: impl Transaction, spec: SpecId) -> InitialAndFloorGas {
459 let mut accounts = 0;
460 let mut storages = 0;
461 if tx.tx_type() != TransactionType::Legacy {
463 (accounts, storages) = tx
464 .access_list()
465 .map(|al| {
466 al.fold((0, 0), |(mut num_accounts, mut num_storage_slots), item| {
467 num_accounts += 1;
468 num_storage_slots += item.storage_slots().count();
469
470 (num_accounts, num_storage_slots)
471 })
472 })
473 .unwrap_or_default();
474 }
475
476 calculate_initial_tx_gas(
477 spec,
478 tx.input(),
479 tx.kind().is_create(),
480 accounts as u64,
481 storages as u64,
482 tx.authorization_list_len() as u64,
483 )
484}
485
486#[inline]
488pub fn get_tokens_in_calldata(input: &[u8], is_istanbul: bool) -> u64 {
489 let zero_data_len = input.iter().filter(|v| **v == 0).count() as u64;
490 let non_zero_data_len = input.len() as u64 - zero_data_len;
491 let non_zero_data_multiplier = if is_istanbul {
492 NON_ZERO_BYTE_MULTIPLIER_ISTANBUL
494 } else {
495 NON_ZERO_BYTE_MULTIPLIER
496 };
497 zero_data_len + non_zero_data_len * non_zero_data_multiplier
498}
499
500#[inline]
502pub fn calc_tx_floor_cost(tokens_in_calldata: u64) -> u64 {
503 tokens_in_calldata * TOTAL_COST_FLOOR_PER_TOKEN + 21_000
504}