revm_interpreter/gas/
calc.rs1use super::constants::*;
2use crate::{num_words, tri, SStoreResult, SelfDestructResult, StateLoad};
3use context_interface::journaled_state::AccountLoad;
4use primitives::U256;
5use specification::{eip7702, hardfork::SpecId};
6
7#[allow(clippy::collapsible_else_if)]
9#[inline]
10pub fn sstore_refund(spec_id: SpecId, vals: &SStoreResult) -> i64 {
11 if spec_id.is_enabled_in(SpecId::ISTANBUL) {
12 let sstore_clears_schedule = if spec_id.is_enabled_in(SpecId::LONDON) {
14 (SSTORE_RESET - COLD_SLOAD_COST + ACCESS_LIST_STORAGE_KEY) as i64
15 } else {
16 REFUND_SSTORE_CLEARS
17 };
18 if vals.is_new_eq_present() {
19 0
20 } else {
21 if vals.is_original_eq_present() && vals.is_new_zero() {
22 sstore_clears_schedule
23 } else {
24 let mut refund = 0;
25
26 if !vals.is_original_zero() {
27 if vals.is_present_zero() {
28 refund -= sstore_clears_schedule;
29 } else if vals.is_new_zero() {
30 refund += sstore_clears_schedule;
31 }
32 }
33
34 if vals.is_original_eq_new() {
35 let (gas_sstore_reset, gas_sload) = if spec_id.is_enabled_in(SpecId::BERLIN) {
36 (SSTORE_RESET - COLD_SLOAD_COST, WARM_STORAGE_READ_COST)
37 } else {
38 (SSTORE_RESET, sload_cost(spec_id, false))
39 };
40 if vals.is_original_zero() {
41 refund += (SSTORE_SET - gas_sload) as i64;
42 } else {
43 refund += (gas_sstore_reset - gas_sload) as i64;
44 }
45 }
46
47 refund
48 }
49 }
50 } else {
51 if !vals.is_present_zero() && vals.is_new_zero() {
52 REFUND_SSTORE_CLEARS
53 } else {
54 0
55 }
56 }
57}
58
59#[inline]
61pub const fn create2_cost(len: usize) -> Option<u64> {
62 CREATE.checked_add(tri!(cost_per_word(len, KECCAK256WORD)))
63}
64
65#[inline]
66const fn log2floor(value: U256) -> u64 {
67 let mut l: u64 = 256;
68 let mut i = 3;
69 loop {
70 if value.as_limbs()[i] == 0u64 {
71 l -= 64;
72 } else {
73 l -= value.as_limbs()[i].leading_zeros() as u64;
74 if l == 0 {
75 return l;
76 } else {
77 return l - 1;
78 }
79 }
80 if i == 0 {
81 break;
82 }
83 i -= 1;
84 }
85 l
86}
87
88#[inline]
90pub fn exp_cost(spec_id: SpecId, power: U256) -> Option<u64> {
91 if power.is_zero() {
92 Some(EXP)
93 } else {
94 let gas_byte = U256::from(if spec_id.is_enabled_in(SpecId::SPURIOUS_DRAGON) {
96 50
97 } else {
98 10
99 });
100 let gas = U256::from(EXP)
101 .checked_add(gas_byte.checked_mul(U256::from(log2floor(power) / 8 + 1))?)?;
102
103 u64::try_from(gas).ok()
104 }
105}
106
107#[inline]
109pub const fn copy_cost_verylow(len: usize) -> Option<u64> {
110 copy_cost(VERYLOW, len)
111}
112
113#[inline]
115pub const fn extcodecopy_cost(spec_id: SpecId, len: usize, is_cold: bool) -> Option<u64> {
116 let base_gas = if spec_id.is_enabled_in(SpecId::BERLIN) {
117 warm_cold_cost(is_cold)
118 } else if spec_id.is_enabled_in(SpecId::TANGERINE) {
119 700
120 } else {
121 20
122 };
123 copy_cost(base_gas, len)
124}
125
126#[inline]
127pub const fn copy_cost(base_cost: u64, len: usize) -> Option<u64> {
128 base_cost.checked_add(tri!(cost_per_word(len, COPY)))
129}
130
131#[inline]
133pub const fn log_cost(n: u8, len: u64) -> Option<u64> {
134 tri!(LOG.checked_add(tri!(LOGDATA.checked_mul(len)))).checked_add(LOGTOPIC * n as u64)
135}
136
137#[inline]
139pub const fn keccak256_cost(len: usize) -> Option<u64> {
140 KECCAK256.checked_add(tri!(cost_per_word(len, KECCAK256WORD)))
141}
142
143#[inline]
145pub const fn cost_per_word(len: usize, multiple: u64) -> Option<u64> {
146 multiple.checked_mul(num_words(len) as u64)
147}
148
149#[inline]
155pub const fn initcode_cost(len: usize) -> u64 {
156 let Some(cost) = cost_per_word(len, INITCODE_WORD_COST) else {
157 panic!("initcode cost overflow")
158 };
159 cost
160}
161
162#[inline]
164pub const fn sload_cost(spec_id: SpecId, is_cold: bool) -> u64 {
165 if spec_id.is_enabled_in(SpecId::BERLIN) {
166 if is_cold {
167 COLD_SLOAD_COST
168 } else {
169 WARM_STORAGE_READ_COST
170 }
171 } else if spec_id.is_enabled_in(SpecId::ISTANBUL) {
172 ISTANBUL_SLOAD_GAS
174 } else if spec_id.is_enabled_in(SpecId::TANGERINE) {
175 200
177 } else {
178 50
179 }
180}
181
182#[inline]
184pub fn sstore_cost(spec_id: SpecId, vals: &SStoreResult, is_cold: bool) -> u64 {
185 if spec_id.is_enabled_in(SpecId::BERLIN) {
186 let mut gas_cost = istanbul_sstore_cost::<WARM_STORAGE_READ_COST, WARM_SSTORE_RESET>(vals);
188
189 if is_cold {
190 gas_cost += COLD_SLOAD_COST;
191 }
192 gas_cost
193 } else if spec_id.is_enabled_in(SpecId::ISTANBUL) {
194 istanbul_sstore_cost::<ISTANBUL_SLOAD_GAS, SSTORE_RESET>(vals)
196 } else {
197 frontier_sstore_cost(vals)
199 }
200}
201
202#[inline]
204fn istanbul_sstore_cost<const SLOAD_GAS: u64, const SSTORE_RESET_GAS: u64>(
205 vals: &SStoreResult,
206) -> u64 {
207 if vals.is_new_eq_present() {
208 SLOAD_GAS
209 } else if vals.is_original_eq_present() && vals.is_original_zero() {
210 SSTORE_SET
211 } else if vals.is_original_eq_present() {
212 SSTORE_RESET_GAS
213 } else {
214 SLOAD_GAS
215 }
216}
217
218#[inline]
220fn frontier_sstore_cost(vals: &SStoreResult) -> u64 {
221 if vals.is_present_zero() && !vals.is_new_zero() {
222 SSTORE_SET
223 } else {
224 SSTORE_RESET
225 }
226}
227
228#[inline]
230pub const fn selfdestruct_cost(spec_id: SpecId, res: StateLoad<SelfDestructResult>) -> u64 {
231 let should_charge_topup = if spec_id.is_enabled_in(SpecId::SPURIOUS_DRAGON) {
233 res.data.had_value && !res.data.target_exists
234 } else {
235 !res.data.target_exists
236 };
237
238 let selfdestruct_gas_topup = if spec_id.is_enabled_in(SpecId::TANGERINE) && should_charge_topup
240 {
241 25000
242 } else {
243 0
244 };
245
246 let selfdestruct_gas = if spec_id.is_enabled_in(SpecId::TANGERINE) {
248 5000
249 } else {
250 0
251 };
252
253 let mut gas = selfdestruct_gas + selfdestruct_gas_topup;
254 if spec_id.is_enabled_in(SpecId::BERLIN) && res.is_cold {
255 gas += COLD_ACCOUNT_ACCESS_COST
256 }
257 gas
258}
259
260#[inline]
281pub const fn call_cost(
282 spec_id: SpecId,
283 transfers_value: bool,
284 account_load: StateLoad<AccountLoad>,
285) -> u64 {
286 let is_empty = account_load.data.is_empty;
287 let mut gas = if spec_id.is_enabled_in(SpecId::BERLIN) {
289 warm_cold_cost_with_delegation(account_load)
290 } else if spec_id.is_enabled_in(SpecId::TANGERINE) {
291 700
293 } else {
294 40
295 };
296
297 if transfers_value {
299 gas += CALLVALUE;
300 }
301
302 if is_empty {
304 if spec_id.is_enabled_in(SpecId::SPURIOUS_DRAGON) {
306 if transfers_value {
308 gas += NEWACCOUNT;
309 }
310 } else {
311 gas += NEWACCOUNT;
312 }
313 }
314
315 gas
316}
317
318#[inline]
320pub const fn warm_cold_cost(is_cold: bool) -> u64 {
321 if is_cold {
322 COLD_ACCOUNT_ACCESS_COST
323 } else {
324 WARM_STORAGE_READ_COST
325 }
326}
327
328#[inline]
332pub const fn warm_cold_cost_with_delegation(load: StateLoad<AccountLoad>) -> u64 {
333 let mut gas = warm_cold_cost(load.is_cold);
334 if let Some(is_cold) = load.data.is_delegate_account_cold {
335 gas += warm_cold_cost(is_cold);
336 }
337 gas
338}
339
340#[inline]
342pub const fn memory_gas(num_words: usize) -> u64 {
343 let num_words = num_words as u64;
344 MEMORY
345 .saturating_mul(num_words)
346 .saturating_add(num_words.saturating_mul(num_words) / 512)
347}
348
349#[derive(Clone, Copy, Debug, Default)]
351pub struct InitialAndFloorGas {
352 pub initial_gas: u64,
354 pub floor_gas: u64,
357}
358
359pub fn calculate_initial_tx_gas(
367 spec_id: SpecId,
368 input: &[u8],
369 is_create: bool,
370 access_list_accounts: u64,
371 access_list_storages: u64,
372 authorization_list_num: u64,
373) -> InitialAndFloorGas {
374 let mut gas = InitialAndFloorGas::default();
375
376 let tokens_in_calldata = get_tokens_in_calldata(input, spec_id.is_enabled_in(SpecId::ISTANBUL));
378 gas.initial_gas += tokens_in_calldata * STANDARD_TOKEN_COST;
379
380 gas.initial_gas += access_list_accounts * ACCESS_LIST_ADDRESS;
382 gas.initial_gas += access_list_storages * ACCESS_LIST_STORAGE_KEY;
383
384 gas.initial_gas += if is_create {
386 if spec_id.is_enabled_in(SpecId::HOMESTEAD) {
387 53000
389 } else {
390 21000
391 }
392 } else {
393 21000
394 };
395
396 if spec_id.is_enabled_in(SpecId::SHANGHAI) && is_create {
399 gas.initial_gas += initcode_cost(input.len())
400 }
401
402 if spec_id.is_enabled_in(SpecId::PRAGUE) {
404 gas.initial_gas += authorization_list_num * eip7702::PER_EMPTY_ACCOUNT_COST;
405
406 gas.floor_gas = calc_tx_floor_cost(tokens_in_calldata);
408 }
409
410 gas
411}
412
413#[inline]
415pub fn get_tokens_in_calldata(input: &[u8], is_istanbul: bool) -> u64 {
416 let zero_data_len = input.iter().filter(|v| **v == 0).count() as u64;
417 let non_zero_data_len = input.len() as u64 - zero_data_len;
418 let non_zero_data_multiplier = if is_istanbul {
419 NON_ZERO_BYTE_MULTIPLIER_ISTANBUL
421 } else {
422 NON_ZERO_BYTE_MULTIPLIER
423 };
424 zero_data_len + non_zero_data_len * non_zero_data_multiplier
425}
426
427#[inline]
429pub fn calc_tx_floor_cost(tokens_in_calldata: u64) -> u64 {
430 tokens_in_calldata * TOTAL_COST_FLOOR_PER_TOKEN + 21_000
431}