rtvm_interpreter/gas/
calc.rs

1use rtvm_primitives::Bytes;
2
3use super::constants::*;
4use crate::{
5    primitives::{
6        Address, SpecId,
7        SpecId::{BERLIN, SPURIOUS_DRAGON, TANGERINE},
8        U256,
9    },
10    SelfDestructResult,
11};
12use std::vec::Vec;
13
14/// `const` Option `?`.
15macro_rules! tri {
16    ($e:expr) => {
17        match $e {
18            Some(v) => v,
19            None => return None,
20        }
21    };
22}
23
24/// `SSTORE` opcode refund calculation.
25#[allow(clippy::collapsible_else_if)]
26#[inline]
27pub fn sstore_refund(spec_id: SpecId, original: U256, current: U256, new: U256) -> i64 {
28    if spec_id.is_enabled_in(SpecId::ISTANBUL) {
29        // EIP-3529: Reduction in refunds
30        let sstore_clears_schedule = if spec_id.is_enabled_in(SpecId::LONDON) {
31            (SSTORE_RESET - COLD_SLOAD_COST + ACCESS_LIST_STORAGE_KEY) as i64
32        } else {
33            REFUND_SSTORE_CLEARS
34        };
35        if current == new {
36            0
37        } else {
38            if original == current && new == U256::ZERO {
39                sstore_clears_schedule
40            } else {
41                let mut refund = 0;
42
43                if original != U256::ZERO {
44                    if current == U256::ZERO {
45                        refund -= sstore_clears_schedule;
46                    } else if new == U256::ZERO {
47                        refund += sstore_clears_schedule;
48                    }
49                }
50
51                if original == new {
52                    let (gas_sstore_reset, gas_sload) = if spec_id.is_enabled_in(SpecId::BERLIN) {
53                        (SSTORE_RESET - COLD_SLOAD_COST, WARM_STORAGE_READ_COST)
54                    } else {
55                        (SSTORE_RESET, sload_cost(spec_id, false))
56                    };
57                    if original == U256::ZERO {
58                        refund += (SSTORE_SET - gas_sload) as i64;
59                    } else {
60                        refund += (gas_sstore_reset - gas_sload) as i64;
61                    }
62                }
63
64                refund
65            }
66        }
67    } else {
68        if current != U256::ZERO && new == U256::ZERO {
69            REFUND_SSTORE_CLEARS
70        } else {
71            0
72        }
73    }
74}
75
76/// `CREATE2` opcode cost calculation.
77#[inline]
78pub const fn create2_cost(len: u64) -> Option<u64> {
79    CREATE.checked_add(tri!(cost_per_word(len, KECCAK256WORD)))
80}
81
82#[inline]
83const fn log2floor(value: U256) -> u64 {
84    let mut l: u64 = 256;
85    let mut i = 3;
86    loop {
87        if value.as_limbs()[i] == 0u64 {
88            l -= 64;
89        } else {
90            l -= value.as_limbs()[i].leading_zeros() as u64;
91            if l == 0 {
92                return l;
93            } else {
94                return l - 1;
95            }
96        }
97        if i == 0 {
98            break;
99        }
100        i -= 1;
101    }
102    l
103}
104
105/// `EXP` opcode cost calculation.
106#[inline]
107pub fn exp_cost(spec_id: SpecId, power: U256) -> Option<u64> {
108    if power == U256::ZERO {
109        Some(EXP)
110    } else {
111        // EIP-160: EXP cost increase
112        let gas_byte = U256::from(if spec_id.is_enabled_in(SpecId::SPURIOUS_DRAGON) {
113            50
114        } else {
115            10
116        });
117        let gas = U256::from(EXP)
118            .checked_add(gas_byte.checked_mul(U256::from(log2floor(power) / 8 + 1))?)?;
119
120        u64::try_from(gas).ok()
121    }
122}
123
124/// `*COPY` opcodes cost calculation.
125#[inline]
126pub const fn verylowcopy_cost(len: u64) -> Option<u64> {
127    VERYLOW.checked_add(tri!(cost_per_word(len, COPY)))
128}
129
130/// `EXTCODECOPY` opcode cost calculation.
131#[inline]
132pub const fn extcodecopy_cost(spec_id: SpecId, len: u64, is_cold: bool) -> Option<u64> {
133    let base_gas = if spec_id.is_enabled_in(SpecId::BERLIN) {
134        warm_cold_cost(is_cold)
135    } else if spec_id.is_enabled_in(SpecId::TANGERINE) {
136        700
137    } else {
138        20
139    };
140    base_gas.checked_add(tri!(cost_per_word(len, COPY)))
141}
142
143/// `LOG` opcode cost calculation.
144#[inline]
145pub const fn log_cost(n: u8, len: u64) -> Option<u64> {
146    tri!(LOG.checked_add(tri!(LOGDATA.checked_mul(len)))).checked_add(LOGTOPIC * n as u64)
147}
148
149/// `KECCAK256` opcode cost calculation.
150#[inline]
151pub const fn keccak256_cost(len: u64) -> Option<u64> {
152    KECCAK256.checked_add(tri!(cost_per_word(len, KECCAK256WORD)))
153}
154
155/// Calculate the cost of buffer per word.
156#[inline]
157pub const fn cost_per_word(len: u64, multiple: u64) -> Option<u64> {
158    multiple.checked_mul(len.div_ceil(32))
159}
160
161/// EIP-3860: Limit and meter initcode
162///
163/// Apply extra gas cost of 2 for every 32-byte chunk of initcode.
164///
165/// This cannot overflow as the initcode length is assumed to be checked.
166#[inline]
167pub const fn initcode_cost(len: u64) -> u64 {
168    let Some(cost) = cost_per_word(len, INITCODE_WORD_COST) else {
169        panic!("initcode cost overflow")
170    };
171    cost
172}
173
174/// `SLOAD` opcode cost calculation.
175#[inline]
176pub const fn sload_cost(spec_id: SpecId, is_cold: bool) -> u64 {
177    if spec_id.is_enabled_in(SpecId::BERLIN) {
178        if is_cold {
179            COLD_SLOAD_COST
180        } else {
181            WARM_STORAGE_READ_COST
182        }
183    } else if spec_id.is_enabled_in(SpecId::ISTANBUL) {
184        // EIP-1884: Repricing for trie-size-dependent opcodes
185        INSTANBUL_SLOAD_GAS
186    } else if spec_id.is_enabled_in(SpecId::TANGERINE) {
187        // EIP-150: Gas cost changes for IO-heavy operations
188        200
189    } else {
190        50
191    }
192}
193
194/// `SSTORE` opcode cost calculation.
195#[inline]
196pub fn sstore_cost(
197    spec_id: SpecId,
198    original: U256,
199    current: U256,
200    new: U256,
201    gas: u64,
202    is_cold: bool,
203) -> Option<u64> {
204    // EIP-1706 Disable SSTORE with gasleft lower than call stipend
205    if spec_id.is_enabled_in(SpecId::ISTANBUL) && gas <= CALL_STIPEND {
206        return None;
207    }
208
209    if spec_id.is_enabled_in(SpecId::BERLIN) {
210        // Berlin specification logic
211        let mut gas_cost = istanbul_sstore_cost::<WARM_STORAGE_READ_COST, WARM_SSTORE_RESET>(
212            original, current, new,
213        );
214
215        if is_cold {
216            gas_cost += COLD_SLOAD_COST;
217        }
218        Some(gas_cost)
219    } else if spec_id.is_enabled_in(SpecId::ISTANBUL) {
220        // Istanbul logic
221        Some(istanbul_sstore_cost::<INSTANBUL_SLOAD_GAS, SSTORE_RESET>(
222            original, current, new,
223        ))
224    } else {
225        // Frontier logic
226        Some(frontier_sstore_cost(current, new))
227    }
228}
229
230/// EIP-2200: Structured Definitions for Net Gas Metering
231#[inline]
232fn istanbul_sstore_cost<const SLOAD_GAS: u64, const SSTORE_RESET_GAS: u64>(
233    original: U256,
234    current: U256,
235    new: U256,
236) -> u64 {
237    if new == current {
238        SLOAD_GAS
239    } else if original == current && original == U256::ZERO {
240        SSTORE_SET
241    } else if original == current {
242        SSTORE_RESET_GAS
243    } else {
244        SLOAD_GAS
245    }
246}
247
248/// Frontier sstore cost just had two cases set and reset values.
249#[inline]
250fn frontier_sstore_cost(current: U256, new: U256) -> u64 {
251    if current == U256::ZERO && new != U256::ZERO {
252        SSTORE_SET
253    } else {
254        SSTORE_RESET
255    }
256}
257
258/// `SELFDESTRUCT` opcode cost calculation.
259#[inline]
260pub const fn selfdestruct_cost(spec_id: SpecId, res: SelfDestructResult) -> u64 {
261    // EIP-161: State trie clearing (invariant-preserving alternative)
262    let should_charge_topup = if spec_id.is_enabled_in(SpecId::SPURIOUS_DRAGON) {
263        res.had_value && !res.target_exists
264    } else {
265        !res.target_exists
266    };
267
268    // EIP-150: Gas cost changes for IO-heavy operations
269    let selfdestruct_gas_topup = if spec_id.is_enabled_in(SpecId::TANGERINE) && should_charge_topup
270    {
271        25000
272    } else {
273        0
274    };
275
276    // EIP-150: Gas cost changes for IO-heavy operations
277    let selfdestruct_gas = if spec_id.is_enabled_in(SpecId::TANGERINE) {
278        5000
279    } else {
280        0
281    };
282
283    let mut gas = selfdestruct_gas + selfdestruct_gas_topup;
284    if spec_id.is_enabled_in(SpecId::BERLIN) && res.is_cold {
285        gas += COLD_ACCOUNT_ACCESS_COST
286    }
287    gas
288}
289
290/// Calculate call gas cost for the call instruction.
291///
292/// There is three types of gas.
293/// * Account access gas. after berlin it can be cold or warm.
294/// * Transfer value gas. If value is transferred and balance of target account is updated.
295/// * If account is not existing and needs to be created. After Spurious dragon
296/// this is only accounted if value is transferred.
297#[inline]
298pub const fn call_cost(
299    spec_id: SpecId,
300    transfers_value: bool,
301    is_cold: bool,
302    new_account_accounting: bool,
303) -> u64 {
304    // Account access.
305    let mut gas = if spec_id.is_enabled_in(BERLIN) {
306        warm_cold_cost(is_cold)
307    } else if spec_id.is_enabled_in(TANGERINE) {
308        // EIP-150: Gas cost changes for IO-heavy operations
309        700
310    } else {
311        40
312    };
313
314    // transfer value cost
315    if transfers_value {
316        gas += CALLVALUE;
317    }
318
319    // new account cost
320    if new_account_accounting {
321        // EIP-161: State trie clearing (invariant-preserving alternative)
322        if spec_id.is_enabled_in(SPURIOUS_DRAGON) {
323            // account only if there is value transferred.
324            if transfers_value {
325                gas += NEWACCOUNT;
326            }
327        } else {
328            gas += NEWACCOUNT;
329        }
330    }
331
332    gas
333}
334
335/// Berlin warm and cold storage access cost for account access.
336#[inline]
337pub const fn warm_cold_cost(is_cold: bool) -> u64 {
338    if is_cold {
339        COLD_ACCOUNT_ACCESS_COST
340    } else {
341        WARM_STORAGE_READ_COST
342    }
343}
344
345/// Memory expansion cost calculation.
346#[inline]
347pub const fn memory_gas(a: usize) -> u64 {
348    let a = a as u64;
349    MEMORY
350        .saturating_mul(a)
351        .saturating_add(a.saturating_mul(a) / 512)
352}
353
354/// Initial gas that is deducted for transaction to be included.
355/// Initial gas contains initial stipend gas, gas for access list and input data.
356pub fn validate_initial_tx_gas(
357    spec_id: SpecId,
358    input: &[u8],
359    is_create: bool,
360    access_list: &[(Address, Vec<U256>)],
361    initcodes: &[Bytes],
362) -> u64 {
363    let mut initial_gas = 0;
364    let mut zero_data_len = input.iter().filter(|v| **v == 0).count() as u64;
365    let mut non_zero_data_len = input.len() as u64 - zero_data_len;
366
367    // Enabling of initcode is checked in `validate_env` handler.
368    for initcode in initcodes {
369        let zeros = initcode.iter().filter(|v| **v == 0).count() as u64;
370        zero_data_len += zeros;
371        non_zero_data_len += initcode.len() as u64 - zeros;
372    }
373
374    // initdate stipend
375    initial_gas += zero_data_len * TRANSACTION_ZERO_DATA;
376    // EIP-2028: Transaction data gas cost reduction
377    initial_gas += non_zero_data_len
378        * if spec_id.is_enabled_in(SpecId::ISTANBUL) {
379            16
380        } else {
381            68
382        };
383
384    // get number of access list account and storages.
385    if spec_id.is_enabled_in(SpecId::BERLIN) {
386        let accessed_slots = access_list
387            .iter()
388            .fold(0, |slot_count, (_, slots)| slot_count + slots.len() as u64);
389        initial_gas += access_list.len() as u64 * ACCESS_LIST_ADDRESS;
390        initial_gas += accessed_slots * ACCESS_LIST_STORAGE_KEY;
391    }
392
393    // base stipend
394    initial_gas += if is_create {
395        if spec_id.is_enabled_in(SpecId::HOMESTEAD) {
396            // EIP-2: Homestead Hard-fork Changes
397            53000
398        } else {
399            21000
400        }
401    } else {
402        21000
403    };
404
405    // EIP-3860: Limit and meter initcode
406    // Initcode stipend for bytecode analysis
407    if spec_id.is_enabled_in(SpecId::SHANGHAI) && is_create {
408        initial_gas += initcode_cost(input.len() as u64)
409    }
410
411    initial_gas
412}