Skip to main content

ethrex_levm/
gas_cost.rs

1use crate::{
2    call_frame::CallFrame,
3    constants::{WORD_SIZE, WORD_SIZE_IN_BYTES_U64},
4    errors::{ExceptionalHalt, InternalError, PrecompileError, VMError},
5    memory,
6};
7use ExceptionalHalt::OutOfGas;
8/// Contains the gas costs of the EVM instructions
9use ethrex_common::{U256, types::Fork, types::tx_fields::AccessList};
10use malachite::base::num::logic::traits::*;
11use malachite::{Natural, base::num::basic::traits::Zero as _};
12
13// Opcodes cost
14pub const STOP: u64 = 0;
15pub const ADD: u64 = 3;
16pub const MUL: u64 = 5;
17pub const SUB: u64 = 3;
18pub const DIV: u64 = 5;
19pub const SDIV: u64 = 5;
20pub const MOD: u64 = 5;
21pub const SMOD: u64 = 5;
22pub const ADDMOD: u64 = 8;
23pub const MULMOD: u64 = 8;
24pub const EXP_STATIC: u64 = 10;
25pub const EXP_DYNAMIC_BASE: u64 = 50;
26pub const SIGNEXTEND: u64 = 5;
27pub const LT: u64 = 3;
28pub const GT: u64 = 3;
29pub const SLT: u64 = 3;
30pub const SGT: u64 = 3;
31pub const EQ: u64 = 3;
32pub const ISZERO: u64 = 3;
33pub const AND: u64 = 3;
34pub const OR: u64 = 3;
35pub const XOR: u64 = 3;
36pub const NOT: u64 = 3;
37pub const BYTE: u64 = 3;
38pub const SHL: u64 = 3;
39pub const SHR: u64 = 3;
40pub const SAR: u64 = 3;
41pub const KECCAK25_STATIC: u64 = 30;
42pub const KECCAK25_DYNAMIC_BASE: u64 = 6;
43pub const CALLDATALOAD: u64 = 3;
44pub const CALLDATASIZE: u64 = 2;
45pub const CALLDATACOPY_STATIC: u64 = 3;
46pub const CALLDATACOPY_DYNAMIC_BASE: u64 = 3;
47pub const RETURNDATASIZE: u64 = 2;
48pub const RETURNDATACOPY_STATIC: u64 = 3;
49pub const RETURNDATACOPY_DYNAMIC_BASE: u64 = 3;
50pub const ADDRESS: u64 = 2;
51pub const ORIGIN: u64 = 2;
52pub const CALLER: u64 = 2;
53pub const BLOCKHASH: u64 = 20;
54pub const COINBASE: u64 = 2;
55pub const TIMESTAMP: u64 = 2;
56pub const NUMBER: u64 = 2;
57pub const PREVRANDAO: u64 = 2;
58pub const GASLIMIT: u64 = 2;
59pub const CHAINID: u64 = 2;
60pub const SELFBALANCE: u64 = 5;
61pub const BASEFEE: u64 = 2;
62pub const BLOBHASH: u64 = 3;
63pub const BLOBBASEFEE: u64 = 2;
64pub const SLOTNUM: u64 = 2;
65pub const POP: u64 = 2;
66pub const MLOAD_STATIC: u64 = 3;
67pub const MSTORE_STATIC: u64 = 3;
68pub const MSTORE8_STATIC: u64 = 3;
69pub const JUMP: u64 = 8;
70pub const JUMPI: u64 = 10;
71pub const PC: u64 = 2;
72pub const MSIZE: u64 = 2;
73pub const GAS: u64 = 2;
74pub const JUMPDEST: u64 = 1;
75pub const TLOAD: u64 = 100;
76pub const TSTORE: u64 = 100;
77pub const MCOPY_STATIC: u64 = 3;
78pub const MCOPY_DYNAMIC_BASE: u64 = 3;
79pub const PUSH0: u64 = 2;
80pub const PUSHN: u64 = 3;
81pub const DUPN: u64 = 3;
82pub const SWAPN: u64 = 3;
83pub const EXCHANGE: u64 = 3;
84pub const LOGN_STATIC: u64 = 375;
85pub const LOGN_DYNAMIC_BASE: u64 = 375;
86pub const LOGN_DYNAMIC_BYTE_BASE: u64 = 8;
87pub const CALLVALUE: u64 = 2;
88pub const CODESIZE: u64 = 2;
89pub const CODECOPY_STATIC: u64 = 3;
90pub const CODECOPY_DYNAMIC_BASE: u64 = 3;
91pub const GASPRICE: u64 = 2;
92pub const CLZ: u64 = 5;
93
94pub const SELFDESTRUCT_STATIC: u64 = 5000;
95pub const SELFDESTRUCT_DYNAMIC: u64 = 25000;
96pub const SELFDESTRUCT_REFUND: u64 = 24000;
97
98pub const DEFAULT_STATIC: u64 = 0;
99pub const DEFAULT_COLD_DYNAMIC: u64 = 2600;
100pub const DEFAULT_WARM_DYNAMIC: u64 = 100;
101
102pub const SLOAD_STATIC: u64 = 0;
103pub const SLOAD_COLD_DYNAMIC: u64 = 2100;
104pub const SLOAD_WARM_DYNAMIC: u64 = 100;
105
106pub const SSTORE_STATIC: u64 = 0;
107pub const SSTORE_COLD_DYNAMIC: u64 = 2100;
108pub const SSTORE_DEFAULT_DYNAMIC: u64 = 100;
109pub const SSTORE_STORAGE_CREATION: u64 = 20000;
110pub const SSTORE_STORAGE_MODIFICATION: u64 = 2900;
111pub const SSTORE_STIPEND: i64 = 2300;
112
113pub const BALANCE_STATIC: u64 = DEFAULT_STATIC;
114pub const BALANCE_COLD_DYNAMIC: u64 = DEFAULT_COLD_DYNAMIC;
115pub const BALANCE_WARM_DYNAMIC: u64 = DEFAULT_WARM_DYNAMIC;
116
117pub const EXTCODESIZE_STATIC: u64 = DEFAULT_STATIC;
118pub const EXTCODESIZE_COLD_DYNAMIC: u64 = DEFAULT_COLD_DYNAMIC;
119pub const EXTCODESIZE_WARM_DYNAMIC: u64 = DEFAULT_WARM_DYNAMIC;
120
121pub const EXTCODEHASH_STATIC: u64 = DEFAULT_STATIC;
122pub const EXTCODEHASH_COLD_DYNAMIC: u64 = DEFAULT_COLD_DYNAMIC;
123pub const EXTCODEHASH_WARM_DYNAMIC: u64 = DEFAULT_WARM_DYNAMIC;
124
125pub const EXTCODECOPY_STATIC: u64 = 0;
126pub const EXTCODECOPY_DYNAMIC_BASE: u64 = 3;
127pub const EXTCODECOPY_COLD_DYNAMIC: u64 = DEFAULT_COLD_DYNAMIC;
128pub const EXTCODECOPY_WARM_DYNAMIC: u64 = DEFAULT_WARM_DYNAMIC;
129
130pub const CALL_STATIC: u64 = DEFAULT_STATIC;
131pub const CALL_COLD_DYNAMIC: u64 = DEFAULT_COLD_DYNAMIC;
132pub const CALL_WARM_DYNAMIC: u64 = DEFAULT_WARM_DYNAMIC;
133pub const CALL_POSITIVE_VALUE: u64 = 9000;
134pub const CALL_POSITIVE_VALUE_STIPEND: u64 = 2300;
135pub const CALL_TO_EMPTY_ACCOUNT: u64 = 25000;
136
137pub const CALLCODE_STATIC: u64 = DEFAULT_STATIC;
138pub const CALLCODE_COLD_DYNAMIC: u64 = DEFAULT_COLD_DYNAMIC;
139pub const CALLCODE_WARM_DYNAMIC: u64 = DEFAULT_WARM_DYNAMIC;
140pub const CALLCODE_POSITIVE_VALUE: u64 = 9000;
141pub const CALLCODE_POSITIVE_VALUE_STIPEND: u64 = 2300;
142
143pub const DELEGATECALL_STATIC: u64 = DEFAULT_STATIC;
144pub const DELEGATECALL_COLD_DYNAMIC: u64 = DEFAULT_COLD_DYNAMIC;
145pub const DELEGATECALL_WARM_DYNAMIC: u64 = DEFAULT_WARM_DYNAMIC;
146
147pub const STATICCALL_STATIC: u64 = DEFAULT_STATIC;
148pub const STATICCALL_COLD_DYNAMIC: u64 = DEFAULT_COLD_DYNAMIC;
149pub const STATICCALL_WARM_DYNAMIC: u64 = DEFAULT_WARM_DYNAMIC;
150
151// Costs in gas for call opcodes
152pub const WARM_ADDRESS_ACCESS_COST: u64 = 100;
153pub const COLD_ADDRESS_ACCESS_COST: u64 = 2600;
154pub const NON_ZERO_VALUE_COST: u64 = 9000;
155
156pub const VALUE_TO_EMPTY_ACCOUNT_COST: u64 = 25000;
157
158// Costs in gas for create opcodes
159pub const INIT_CODE_WORD_COST: u64 = 2;
160pub const CODE_DEPOSIT_COST: u64 = 200;
161pub const CREATE_BASE_COST: u64 = 32000;
162
163// EIP-8037: Multidimensional gas for state creation (Amsterdam only)
164pub const STATE_BYTES_PER_NEW_ACCOUNT: u64 = 120;
165pub const STATE_BYTES_PER_STORAGE_SET: u64 = 64;
166pub const STATE_BYTES_PER_AUTH_BASE: u64 = 23; // 23-byte delegation indicator slot
167pub const STATE_BYTES_PER_AUTH_TOTAL: u64 = 143; // 120 account + 23 auth-specific
168
169/// EIP-8037 cost_per_state_byte. Pinned to the fixed value 1530.
170/// The dynamic formula derived from the block gas limit
171/// is not active.
172pub fn cost_per_state_byte(_block_gas_limit: u64) -> u64 {
173    1530
174}
175
176pub const REGULAR_GAS_CREATE: u64 = 9000; // replaces CREATE_BASE_COST for Amsterdam
177pub const CODE_DEPOSIT_REGULAR_COST_PER_WORD: u64 = 6; // keccak hash cost per 32-byte word
178
179// Calldata costs
180pub const CALLDATA_COST_ZERO_BYTE: u64 = 4;
181pub const CALLDATA_COST_NON_ZERO_BYTE: u64 = 16;
182pub const STANDARD_TOKEN_COST: u64 = 4;
183
184// Blob gas costs
185pub const BLOB_GAS_PER_BLOB: u64 = 131072;
186
187// Access lists costs
188pub const ACCESS_LIST_STORAGE_KEY_COST: u64 = 1900;
189pub const ACCESS_LIST_ADDRESS_COST: u64 = 2400;
190
191// Precompile costs
192pub const ECRECOVER_COST: u64 = 3000;
193pub const BLS12_381_G1ADD_COST: u64 = 375;
194pub const BLS12_381_G2ADD_COST: u64 = 600;
195pub const BLS12_381_MAP_FP_TO_G1_COST: u64 = 5500;
196pub const BLS12_PAIRING_CHECK_MUL_COST: u64 = 32600;
197pub const BLS12_PAIRING_CHECK_FIXED_COST: u64 = 37700;
198pub const BLS12_381_MAP_FP2_TO_G2_COST: u64 = 23800;
199pub const P256_VERIFY_COST: u64 = 6900;
200
201// Floor cost per token, specified in https://eips.ethereum.org/EIPS/eip-7623
202pub const TOTAL_COST_FLOOR_PER_TOKEN: u64 = 10;
203// EIP-7976 (Amsterdam+): raised floor
204pub const TOTAL_COST_FLOOR_PER_TOKEN_AMSTERDAM: u64 = 16;
205
206/// Returns the floor cost per token for the given fork.
207/// EIP-7976 raises this from 10 (EIP-7623) to 16 starting at Amsterdam.
208pub fn total_cost_floor_per_token(fork: Fork) -> u64 {
209    if fork >= Fork::Amsterdam {
210        TOTAL_COST_FLOOR_PER_TOKEN_AMSTERDAM
211    } else {
212        TOTAL_COST_FLOOR_PER_TOKEN
213    }
214}
215
216pub const SHA2_256_STATIC_COST: u64 = 60;
217pub const SHA2_256_DYNAMIC_BASE: u64 = 12;
218
219pub const RIPEMD_160_STATIC_COST: u64 = 600;
220pub const RIPEMD_160_DYNAMIC_BASE: u64 = 120;
221
222pub const IDENTITY_STATIC_COST: u64 = 15;
223pub const IDENTITY_DYNAMIC_BASE: u64 = 3;
224
225pub const MODEXP_STATIC_COST: u64 = 200;
226pub const MODEXP_DYNAMIC_QUOTIENT: u64 = 3;
227pub const MODEXP_EXPONENT_FACTOR: u64 = 8;
228
229pub const MODEXP_STATIC_COST_OSAKA: u64 = 500;
230pub const MODEXP_DYNAMIC_QUOTIENT_OSAKA: u64 = 1;
231pub const MODEXP_EXPONENT_FACTOR_OSAKA: u64 = 16;
232
233pub const ECADD_COST: u64 = 150;
234pub const ECMUL_COST: u64 = 6000;
235
236pub const ECPAIRING_BASE_COST: u64 = 45000;
237pub const ECPAIRING_GROUP_COST: u64 = 34000;
238
239pub const POINT_EVALUATION_COST: u64 = 50000;
240
241pub const BLAKE2F_ROUND_COST: u64 = 1;
242
243pub const BLS12_381_MSM_MULTIPLIER: u64 = 1000;
244pub const BLS12_381_G1_K_DISCOUNT: [u64; 128] = [
245    1000, 949, 848, 797, 764, 750, 738, 728, 719, 712, 705, 698, 692, 687, 682, 677, 673, 669, 665,
246    661, 658, 654, 651, 648, 645, 642, 640, 637, 635, 632, 630, 627, 625, 623, 621, 619, 617, 615,
247    613, 611, 609, 608, 606, 604, 603, 601, 599, 598, 596, 595, 593, 592, 591, 589, 588, 586, 585,
248    584, 582, 581, 580, 579, 577, 576, 575, 574, 573, 572, 570, 569, 568, 567, 566, 565, 564, 563,
249    562, 561, 560, 559, 558, 557, 556, 555, 554, 553, 552, 551, 550, 549, 548, 547, 547, 546, 545,
250    544, 543, 542, 541, 540, 540, 539, 538, 537, 536, 536, 535, 534, 533, 532, 532, 531, 530, 529,
251    528, 528, 527, 526, 525, 525, 524, 523, 522, 522, 521, 520, 520, 519,
252];
253pub const G1_MUL_COST: u64 = 12000;
254pub const BLS12_381_G2_K_DISCOUNT: [u64; 128] = [
255    1000, 1000, 923, 884, 855, 832, 812, 796, 782, 770, 759, 749, 740, 732, 724, 717, 711, 704,
256    699, 693, 688, 683, 679, 674, 670, 666, 663, 659, 655, 652, 649, 646, 643, 640, 637, 634, 632,
257    629, 627, 624, 622, 620, 618, 615, 613, 611, 609, 607, 606, 604, 602, 600, 598, 597, 595, 593,
258    592, 590, 589, 587, 586, 584, 583, 582, 580, 579, 578, 576, 575, 574, 573, 571, 570, 569, 568,
259    567, 566, 565, 563, 562, 561, 560, 559, 558, 557, 556, 555, 554, 553, 552, 552, 551, 550, 549,
260    548, 547, 546, 545, 545, 544, 543, 542, 541, 541, 540, 539, 538, 537, 537, 536, 535, 535, 534,
261    533, 532, 532, 531, 530, 530, 529, 528, 528, 527, 526, 526, 525, 524, 524,
262];
263pub const G2_MUL_COST: u64 = 22500;
264
265pub fn exp(exponent: U256) -> Result<u64, VMError> {
266    let exponent_byte_size = (exponent.bits().checked_add(7).ok_or(OutOfGas)?) / 8;
267
268    let exponent_byte_size: u64 = exponent_byte_size
269        .try_into()
270        .map_err(|_| ExceptionalHalt::VeryLargeNumber)?;
271
272    let exponent_byte_size_cost = EXP_DYNAMIC_BASE
273        .checked_mul(exponent_byte_size)
274        .ok_or(OutOfGas)?;
275
276    EXP_STATIC
277        .checked_add(exponent_byte_size_cost)
278        .ok_or(OutOfGas.into())
279}
280
281pub fn calldatacopy(
282    new_memory_size: usize,
283    current_memory_size: usize,
284    size: usize,
285) -> Result<u64, VMError> {
286    copy_behavior(
287        new_memory_size,
288        current_memory_size,
289        size,
290        CALLDATACOPY_DYNAMIC_BASE,
291        CALLDATACOPY_STATIC,
292    )
293}
294
295pub fn codecopy(
296    new_memory_size: usize,
297    current_memory_size: usize,
298    size: usize,
299) -> Result<u64, VMError> {
300    copy_behavior(
301        new_memory_size,
302        current_memory_size,
303        size,
304        CODECOPY_DYNAMIC_BASE,
305        CODECOPY_STATIC,
306    )
307}
308
309// Used in return and revert opcodes
310pub fn exit_opcode(new_memory_size: usize, current_memory_size: usize) -> Result<u64, VMError> {
311    memory::expansion_cost(new_memory_size, current_memory_size)
312}
313
314pub fn returndatacopy(
315    new_memory_size: usize,
316    current_memory_size: usize,
317    size: usize,
318) -> Result<u64, VMError> {
319    copy_behavior(
320        new_memory_size,
321        current_memory_size,
322        size,
323        RETURNDATACOPY_DYNAMIC_BASE,
324        RETURNDATACOPY_STATIC,
325    )
326}
327
328fn copy_behavior(
329    new_memory_size: usize,
330    current_memory_size: usize,
331    size: usize,
332    dynamic_base: u64,
333    static_cost: u64,
334) -> Result<u64, VMError> {
335    let minimum_word_size = (size
336        .checked_add(WORD_SIZE)
337        .ok_or(OutOfGas)?
338        .saturating_sub(1))
339        / WORD_SIZE;
340
341    let minimum_word_size: u64 = minimum_word_size
342        .try_into()
343        .map_err(|_| ExceptionalHalt::VeryLargeNumber)?;
344
345    let memory_expansion_cost = memory::expansion_cost(new_memory_size, current_memory_size)?;
346
347    let minimum_word_size_cost = dynamic_base
348        .checked_mul(minimum_word_size)
349        .ok_or(OutOfGas)?;
350    static_cost
351        .checked_add(minimum_word_size_cost)
352        .ok_or(OutOfGas)?
353        .checked_add(memory_expansion_cost)
354        .ok_or(OutOfGas.into())
355}
356
357pub fn keccak256(
358    new_memory_size: usize,
359    current_memory_size: usize,
360    size: usize,
361) -> Result<u64, VMError> {
362    copy_behavior(
363        new_memory_size,
364        current_memory_size,
365        size,
366        KECCAK25_DYNAMIC_BASE,
367        KECCAK25_STATIC,
368    )
369}
370
371pub fn log(
372    new_memory_size: usize,
373    current_memory_size: usize,
374    size: usize,
375    number_of_topics: usize,
376) -> Result<u64, VMError> {
377    let memory_expansion_cost = memory::expansion_cost(new_memory_size, current_memory_size)?;
378
379    // The following conversion can never fail on systems where `usize` is at most 64 bits, which
380    // covers every system in production today.
381    #[expect(clippy::as_conversions)]
382    let topics_cost = LOGN_DYNAMIC_BASE
383        .checked_mul(number_of_topics as u64)
384        .ok_or(OutOfGas)?;
385
386    let size: u64 = size
387        .try_into()
388        .map_err(|_| ExceptionalHalt::VeryLargeNumber)?;
389    let bytes_cost = LOGN_DYNAMIC_BYTE_BASE.checked_mul(size).ok_or(OutOfGas)?;
390
391    topics_cost
392        .checked_add(LOGN_STATIC)
393        .ok_or(OutOfGas)?
394        .checked_add(bytes_cost)
395        .ok_or(OutOfGas)?
396        .checked_add(memory_expansion_cost)
397        .ok_or(OutOfGas.into())
398}
399
400pub fn mload(new_memory_size: usize, current_memory_size: usize) -> Result<u64, VMError> {
401    mem_expansion_behavior(new_memory_size, current_memory_size, MLOAD_STATIC)
402}
403
404pub fn mstore(new_memory_size: usize, current_memory_size: usize) -> Result<u64, VMError> {
405    mem_expansion_behavior(new_memory_size, current_memory_size, MSTORE_STATIC)
406}
407
408pub fn mstore8(new_memory_size: usize, current_memory_size: usize) -> Result<u64, VMError> {
409    mem_expansion_behavior(new_memory_size, current_memory_size, MSTORE8_STATIC)
410}
411
412fn mem_expansion_behavior(
413    new_memory_size: usize,
414    current_memory_size: usize,
415    static_cost: u64,
416) -> Result<u64, VMError> {
417    let memory_expansion_cost = memory::expansion_cost(new_memory_size, current_memory_size)?;
418
419    static_cost
420        .checked_add(memory_expansion_cost)
421        .ok_or(OutOfGas.into())
422}
423
424pub fn sload(storage_slot_was_cold: bool) -> Result<u64, VMError> {
425    let static_gas = SLOAD_STATIC;
426    let dynamic_cost = if storage_slot_was_cold {
427        SLOAD_COLD_DYNAMIC
428    } else {
429        SLOAD_WARM_DYNAMIC
430    };
431    static_gas.checked_add(dynamic_cost).ok_or(OutOfGas.into())
432}
433
434pub fn sstore(
435    original_value: U256,
436    current_value: U256,
437    new_value: U256,
438    storage_slot_was_cold: bool,
439    fork: Fork,
440) -> Result<u64, VMError> {
441    let static_gas = SSTORE_STATIC;
442
443    let mut base_dynamic_gas = if new_value == current_value {
444        SSTORE_DEFAULT_DYNAMIC
445    } else if current_value == original_value {
446        if original_value.is_zero() {
447            // For Amsterdam+, new slot creation uses MODIFICATION cost in regular gas;
448            // the state cost (STATE_BYTES_PER_STORAGE_SET * cost_per_state_byte) is charged separately.
449            if fork >= Fork::Amsterdam {
450                SSTORE_STORAGE_MODIFICATION
451            } else {
452                SSTORE_STORAGE_CREATION
453            }
454        } else {
455            SSTORE_STORAGE_MODIFICATION
456        }
457    } else {
458        SSTORE_DEFAULT_DYNAMIC
459    };
460    // https://eips.ethereum.org/EIPS/eip-2929
461    if storage_slot_was_cold {
462        base_dynamic_gas = base_dynamic_gas
463            .checked_add(SSTORE_COLD_DYNAMIC)
464            .ok_or(OutOfGas)?;
465    }
466    static_gas
467        .checked_add(base_dynamic_gas)
468        .ok_or(OutOfGas.into())
469}
470
471pub fn mcopy(
472    new_memory_size: usize,
473    current_memory_size: usize,
474    size: usize,
475) -> Result<u64, VMError> {
476    let words_copied = (size
477        .checked_add(WORD_SIZE)
478        .ok_or(OutOfGas)?
479        .saturating_sub(1))
480        / WORD_SIZE;
481
482    let memory_expansion_cost = memory::expansion_cost(new_memory_size, current_memory_size)?;
483
484    let words_copied: u64 = words_copied
485        .try_into()
486        .map_err(|_| ExceptionalHalt::VeryLargeNumber)?;
487
488    let copied_words_cost = MCOPY_DYNAMIC_BASE
489        .checked_mul(words_copied)
490        .ok_or(OutOfGas)?;
491
492    MCOPY_STATIC
493        .checked_add(copied_words_cost)
494        .ok_or(OutOfGas)?
495        .checked_add(memory_expansion_cost)
496        .ok_or(OutOfGas.into())
497}
498
499pub fn create(
500    new_memory_size: usize,
501    current_memory_size: usize,
502    code_size_in_memory: usize,
503    fork: Fork,
504) -> Result<u64, VMError> {
505    compute_gas_create(
506        new_memory_size,
507        current_memory_size,
508        code_size_in_memory,
509        false,
510        fork,
511    )
512}
513
514pub fn create_2(
515    new_memory_size: usize,
516    current_memory_size: usize,
517    code_size_in_memory: usize,
518    fork: Fork,
519) -> Result<u64, VMError> {
520    compute_gas_create(
521        new_memory_size,
522        current_memory_size,
523        code_size_in_memory,
524        true,
525        fork,
526    )
527}
528
529fn compute_gas_create(
530    new_memory_size: usize,
531    current_memory_size: usize,
532    code_size_in_memory: usize,
533    is_create_2: bool,
534    fork: Fork,
535) -> Result<u64, VMError> {
536    let minimum_word_size = (code_size_in_memory.checked_add(31).ok_or(OutOfGas)?) / 32;
537
538    let minimum_word_size: u64 = minimum_word_size
539        .try_into()
540        .map_err(|_| ExceptionalHalt::VeryLargeNumber)?;
541
542    // [EIP-3860] - Apply extra gas cost of 2 for every 32-byte chunk of initcode
543    let init_code_cost = if fork >= Fork::Shanghai {
544        minimum_word_size
545            .checked_mul(INIT_CODE_WORD_COST)
546            .ok_or(OutOfGas)? // will not panic since it's 2
547    } else {
548        0
549    };
550
551    let memory_expansion_cost = memory::expansion_cost(new_memory_size, current_memory_size)?;
552
553    let hash_cost = if is_create_2 {
554        minimum_word_size
555            .checked_mul(KECCAK25_DYNAMIC_BASE)
556            .ok_or(OutOfGas)? // will not panic since it's 6
557    } else {
558        0
559    };
560
561    let create_base_cost = if fork >= Fork::Amsterdam {
562        REGULAR_GAS_CREATE
563    } else {
564        CREATE_BASE_COST
565    };
566
567    let gas_create_cost = memory_expansion_cost
568        .checked_add(init_code_cost)
569        .ok_or(OutOfGas)?
570        .checked_add(create_base_cost)
571        .ok_or(OutOfGas)?
572        .checked_add(hash_cost)
573        .ok_or(OutOfGas)?;
574
575    Ok(gas_create_cost)
576}
577
578/// Base cost of SELFDESTRUCT before evaluating NEW_ACCOUNT.
579/// Used for EIP-7928 two-phase gas check: first verify base cost is
580/// available (to allow BAL state access), then charge the full cost.
581pub fn selfdestruct_base(address_was_cold: bool) -> Result<u64, VMError> {
582    let cold_cost = if address_was_cold {
583        COLD_ADDRESS_ACCESS_COST
584    } else {
585        0
586    };
587    SELFDESTRUCT_STATIC
588        .checked_add(cold_cost)
589        .ok_or(OutOfGas.into())
590}
591
592pub fn selfdestruct(
593    address_was_cold: bool,
594    account_is_empty: bool,
595    balance_to_transfer: U256,
596    fork: Fork,
597) -> Result<u64, VMError> {
598    let mut dynamic_cost = if address_was_cold {
599        COLD_ADDRESS_ACCESS_COST
600    } else {
601        0
602    };
603
604    // If a positive balance is sent to an empty account, the dynamic gas is 25000.
605    // For Amsterdam+, this cost is moved to state gas (charged separately).
606    if account_is_empty && balance_to_transfer > U256::zero() && fork < Fork::Amsterdam {
607        dynamic_cost = dynamic_cost
608            .checked_add(SELFDESTRUCT_DYNAMIC)
609            .ok_or(OutOfGas)?;
610    }
611
612    SELFDESTRUCT_STATIC
613        .checked_add(dynamic_cost)
614        .ok_or(OutOfGas.into())
615}
616
617pub fn tx_calldata(calldata: &[u8]) -> Result<u64, VMError> {
618    // This cost applies both for call and create
619    // 4 gas for each zero byte in the transaction data, 16 gas for each non-zero byte.
620    // Counting non-zero bytes in a single pass autovectorizes; the previous per-byte
621    // branch + `checked_add` did not. Byte-identical to the loop (and to the same
622    // overflow → OutOfGas behavior), since:
623    //   non_zero * 16 + zero * 4  ==  sum over bytes of (16 if non-zero else 4).
624    let len = u64::try_from(calldata.len()).map_err(|_| InternalError::TypeConversion)?;
625    let non_zero_bytes = u64::try_from(calldata.iter().filter(|&&byte| byte != 0).count())
626        .map_err(|_| InternalError::TypeConversion)?;
627    // non_zero_bytes <= len, so this never underflows.
628    let zero_bytes = len.saturating_sub(non_zero_bytes);
629
630    let non_zero_cost = non_zero_bytes
631        .checked_mul(CALLDATA_COST_NON_ZERO_BYTE)
632        .ok_or(OutOfGas)?;
633    let zero_cost = zero_bytes
634        .checked_mul(CALLDATA_COST_ZERO_BYTE)
635        .ok_or(OutOfGas)?;
636
637    non_zero_cost.checked_add(zero_cost).ok_or(OutOfGas.into())
638}
639
640/// Returns the total byte-size of an access list:
641/// 20 bytes per address entry + 32 bytes per storage key.
642pub fn access_list_bytes(access_list: &AccessList) -> u64 {
643    let mut bytes: u64 = 0;
644    for (_addr, keys) in access_list {
645        bytes = bytes.saturating_add(20);
646        let keys_len = u64::try_from(keys.len()).unwrap_or(u64::MAX);
647        bytes = bytes.saturating_add(32_u64.saturating_mul(keys_len));
648    }
649    bytes
650}
651
652/// EIP-7981: floor_tokens_in_access_list = access_list_bytes * STANDARD_TOKEN_COST (4).
653/// Used in the floor-gas computation for Amsterdam+.
654pub fn floor_tokens_in_access_list(access_list: &AccessList) -> u64 {
655    access_list_bytes(access_list).saturating_mul(STANDARD_TOKEN_COST)
656}
657
658fn address_access_cost(
659    address_was_cold: bool,
660    static_cost: u64,
661    cold_dynamic_cost: u64,
662    warm_dynamic_cost: u64,
663) -> Result<u64, VMError> {
664    let dynamic_cost: u64 = if address_was_cold {
665        cold_dynamic_cost
666    } else {
667        warm_dynamic_cost
668    };
669
670    static_cost.checked_add(dynamic_cost).ok_or(OutOfGas.into())
671}
672
673pub fn balance(address_was_cold: bool) -> Result<u64, VMError> {
674    address_access_cost(
675        address_was_cold,
676        BALANCE_STATIC,
677        BALANCE_COLD_DYNAMIC,
678        BALANCE_WARM_DYNAMIC,
679    )
680}
681
682pub fn extcodesize(address_was_cold: bool) -> Result<u64, VMError> {
683    address_access_cost(
684        address_was_cold,
685        EXTCODESIZE_STATIC,
686        EXTCODESIZE_COLD_DYNAMIC,
687        EXTCODESIZE_WARM_DYNAMIC,
688    )
689}
690
691pub fn extcodecopy(
692    size: usize,
693    new_memory_size: usize,
694    current_memory_size: usize,
695    address_was_cold: bool,
696) -> Result<u64, VMError> {
697    let base_access_cost = copy_behavior(
698        new_memory_size,
699        current_memory_size,
700        size,
701        EXTCODECOPY_DYNAMIC_BASE,
702        EXTCODECOPY_STATIC,
703    )?;
704    let expansion_access_cost = address_access_cost(
705        address_was_cold,
706        EXTCODECOPY_STATIC,
707        EXTCODECOPY_COLD_DYNAMIC,
708        EXTCODECOPY_WARM_DYNAMIC,
709    )?;
710
711    base_access_cost
712        .checked_add(expansion_access_cost)
713        .ok_or(OutOfGas.into())
714}
715
716pub fn extcodehash(address_was_cold: bool) -> Result<u64, VMError> {
717    address_access_cost(
718        address_was_cold,
719        EXTCODEHASH_STATIC,
720        EXTCODEHASH_COLD_DYNAMIC,
721        EXTCODEHASH_WARM_DYNAMIC,
722    )
723}
724
725#[allow(clippy::too_many_arguments)]
726pub fn call(
727    new_memory_size: usize,
728    current_memory_size: usize,
729    address_was_cold: bool,
730    address_is_empty: bool,
731    value_to_transfer: U256,
732    gas_from_stack: U256,
733    gas_left: u64,
734    fork: Fork,
735) -> Result<(u64, u64), VMError> {
736    let memory_expansion_cost = memory::expansion_cost(new_memory_size, current_memory_size)?;
737
738    let address_access_cost = address_access_cost(
739        address_was_cold,
740        CALL_STATIC,
741        CALL_COLD_DYNAMIC,
742        CALL_WARM_DYNAMIC,
743    )?;
744    let positive_value_cost = if !value_to_transfer.is_zero() {
745        CALL_POSITIVE_VALUE
746    } else {
747        0
748    };
749
750    // For Amsterdam+, the new-account cost is moved to state gas (charged separately).
751    let value_to_empty_account =
752        if address_is_empty && !value_to_transfer.is_zero() && fork < Fork::Amsterdam {
753            CALL_TO_EMPTY_ACCOUNT
754        } else {
755            0
756        };
757
758    let call_gas_costs = memory_expansion_cost
759        .checked_add(address_access_cost)
760        .ok_or(OutOfGas)?
761        .checked_add(positive_value_cost)
762        .ok_or(OutOfGas)?
763        .checked_add(value_to_empty_account)
764        .ok_or(OutOfGas)?;
765
766    calculate_cost_and_gas_limit_call(
767        value_to_transfer.is_zero(),
768        gas_from_stack,
769        gas_left,
770        call_gas_costs,
771        CALL_POSITIVE_VALUE_STIPEND,
772    )
773}
774
775pub fn callcode(
776    new_memory_size: usize,
777    current_memory_size: usize,
778    address_was_cold: bool,
779    value_to_transfer: U256,
780    gas_from_stack: U256,
781    gas_left: u64,
782) -> Result<(u64, u64), VMError> {
783    let memory_expansion_cost = memory::expansion_cost(new_memory_size, current_memory_size)?;
784    let address_access_cost = address_access_cost(
785        address_was_cold,
786        DELEGATECALL_STATIC,
787        DELEGATECALL_COLD_DYNAMIC,
788        DELEGATECALL_WARM_DYNAMIC,
789    )?;
790
791    let positive_value_cost = if !value_to_transfer.is_zero() {
792        CALLCODE_POSITIVE_VALUE
793    } else {
794        0
795    };
796    let call_gas_costs = memory_expansion_cost
797        .checked_add(address_access_cost)
798        .ok_or(OutOfGas)?
799        .checked_add(positive_value_cost)
800        .ok_or(OutOfGas)?;
801
802    calculate_cost_and_gas_limit_call(
803        value_to_transfer.is_zero(),
804        gas_from_stack,
805        gas_left,
806        call_gas_costs,
807        CALLCODE_POSITIVE_VALUE_STIPEND,
808    )
809}
810
811pub fn delegatecall(
812    new_memory_size: usize,
813    current_memory_size: usize,
814    address_was_cold: bool,
815    gas_from_stack: U256,
816    gas_left: u64,
817) -> Result<(u64, u64), VMError> {
818    let memory_expansion_cost = memory::expansion_cost(new_memory_size, current_memory_size)?;
819
820    let address_access_cost = address_access_cost(
821        address_was_cold,
822        DELEGATECALL_STATIC,
823        DELEGATECALL_COLD_DYNAMIC,
824        DELEGATECALL_WARM_DYNAMIC,
825    )?;
826
827    let call_gas_costs = memory_expansion_cost
828        .checked_add(address_access_cost)
829        .ok_or(OutOfGas)?;
830
831    calculate_cost_and_gas_limit_call(true, gas_from_stack, gas_left, call_gas_costs, 0)
832}
833
834pub fn staticcall(
835    new_memory_size: usize,
836    current_memory_size: usize,
837    address_was_cold: bool,
838    gas_from_stack: U256,
839    gas_left: u64,
840) -> Result<(u64, u64), VMError> {
841    let memory_expansion_cost = memory::expansion_cost(new_memory_size, current_memory_size)?;
842
843    let address_access_cost = address_access_cost(
844        address_was_cold,
845        STATICCALL_STATIC,
846        STATICCALL_COLD_DYNAMIC,
847        STATICCALL_WARM_DYNAMIC,
848    )?;
849
850    let call_gas_costs = memory_expansion_cost
851        .checked_add(address_access_cost)
852        .ok_or(OutOfGas)?;
853
854    calculate_cost_and_gas_limit_call(true, gas_from_stack, gas_left, call_gas_costs, 0)
855}
856
857pub fn sha2_256(data_size: usize) -> Result<u64, VMError> {
858    precompile(data_size, SHA2_256_STATIC_COST, SHA2_256_DYNAMIC_BASE)
859}
860
861pub fn ripemd_160(data_size: usize) -> Result<u64, VMError> {
862    precompile(data_size, RIPEMD_160_STATIC_COST, RIPEMD_160_DYNAMIC_BASE)
863}
864
865pub fn identity(data_size: usize) -> Result<u64, VMError> {
866    precompile(data_size, IDENTITY_STATIC_COST, IDENTITY_DYNAMIC_BASE)
867}
868
869pub fn modexp(
870    exponent_first_32_bytes: &Natural,
871    base_size: usize,
872    exponent_size: usize,
873    modulus_size: usize,
874    fork: Fork,
875) -> Result<u64, VMError> {
876    let base_size: u64 = base_size
877        .try_into()
878        .map_err(|_| PrecompileError::ParsingInputError)?;
879    let exponent_size: u64 = exponent_size
880        .try_into()
881        .map_err(|_| PrecompileError::ParsingInputError)?;
882    let modulus_size: u64 = modulus_size
883        .try_into()
884        .map_err(|_| PrecompileError::ParsingInputError)?;
885
886    let max_length = base_size.max(modulus_size);
887
888    //https://eips.ethereum.org/EIPS/eip-2565
889
890    let words = (max_length.checked_add(7).ok_or(OutOfGas)?) / 8;
891
892    let multiplication_complexity = if fork >= Fork::Osaka {
893        if max_length > 32 {
894            2_u64
895                .checked_mul(words.checked_pow(2).ok_or(OutOfGas)?)
896                .ok_or(OutOfGas)?
897        } else {
898            16
899        }
900    } else {
901        words.checked_pow(2).ok_or(OutOfGas)?
902    };
903
904    let modexp_exponent_factor = if fork >= Fork::Osaka {
905        MODEXP_EXPONENT_FACTOR_OSAKA
906    } else {
907        MODEXP_EXPONENT_FACTOR
908    };
909
910    let calculate_iteration_count =
911        if exponent_size <= 32 && *exponent_first_32_bytes != Natural::ZERO {
912            exponent_first_32_bytes
913                .significant_bits()
914                .checked_sub(1)
915                .ok_or(InternalError::Underflow)?
916        } else if exponent_size > 32 {
917            let extra_size = (exponent_size
918                .checked_sub(32)
919                .ok_or(InternalError::Underflow)?)
920            .checked_mul(modexp_exponent_factor)
921            .ok_or(OutOfGas)?;
922            extra_size
923                .checked_add(exponent_first_32_bytes.significant_bits().max(1))
924                .ok_or(OutOfGas)?
925                .checked_sub(1)
926                .ok_or(InternalError::Underflow)?
927        } else {
928            0
929        }
930        .max(1);
931
932    let modexp_static_cost = if fork >= Fork::Osaka {
933        MODEXP_STATIC_COST_OSAKA
934    } else {
935        MODEXP_STATIC_COST
936    };
937
938    let modexp_dynamic_quotient = if fork >= Fork::Osaka {
939        MODEXP_DYNAMIC_QUOTIENT_OSAKA
940    } else {
941        MODEXP_DYNAMIC_QUOTIENT
942    };
943
944    let cost = modexp_static_cost.max(
945        multiplication_complexity
946            .checked_mul(calculate_iteration_count)
947            .ok_or(OutOfGas)?
948            .checked_div(modexp_dynamic_quotient)
949            .ok_or(OutOfGas)?,
950    );
951    Ok(cost)
952}
953
954fn precompile(data_size: usize, static_cost: u64, dynamic_base: u64) -> Result<u64, VMError> {
955    let data_size: u64 = data_size
956        .try_into()
957        .map_err(|_| PrecompileError::ParsingInputError)?;
958
959    let data_word_cost = data_size
960        .checked_add(WORD_SIZE_IN_BYTES_U64 - 1)
961        .ok_or(OutOfGas)?
962        / WORD_SIZE_IN_BYTES_U64;
963
964    let static_gas = static_cost;
965    let dynamic_gas = dynamic_base.checked_mul(data_word_cost).ok_or(OutOfGas)?;
966
967    static_gas.checked_add(dynamic_gas).ok_or(OutOfGas.into())
968}
969
970pub fn ecpairing(groups_number: usize) -> Result<u64, VMError> {
971    let groups_number = u64::try_from(groups_number).map_err(|_| InternalError::TypeConversion)?;
972
973    let groups_cost = groups_number
974        .checked_mul(ECPAIRING_GROUP_COST)
975        .ok_or(OutOfGas)?;
976    groups_cost
977        .checked_add(ECPAIRING_BASE_COST)
978        .ok_or(OutOfGas.into())
979}
980
981/// Max message call gas is all but one 64th of the remaining gas in the current context.
982/// https://eips.ethereum.org/EIPS/eip-150
983#[expect(clippy::arithmetic_side_effects, reason = "can't overflow")]
984#[expect(clippy::as_conversions, reason = "remaining gas conversion")]
985pub fn max_message_call_gas(current_call_frame: &CallFrame) -> Result<u64, VMError> {
986    let mut remaining_gas = current_call_frame.gas_remaining;
987
988    remaining_gas -= remaining_gas / 64;
989
990    Ok(remaining_gas as u64)
991}
992
993fn calculate_cost_and_gas_limit_call(
994    value_is_zero: bool,
995    gas_from_stack: U256,
996    gas_left: u64,
997    call_gas_costs: u64,
998    stipend: u64,
999) -> Result<(u64, u64), VMError> {
1000    let gas_stipend = if value_is_zero { 0 } else { stipend };
1001    let gas_left = gas_left.checked_sub(call_gas_costs).ok_or(OutOfGas)?;
1002
1003    // EIP 150, https://eips.ethereum.org/EIPS/eip-150
1004    let max_gas_for_call = gas_left.checked_sub(gas_left / 64).ok_or(OutOfGas)?;
1005
1006    let gas: u64 = gas_from_stack
1007        .min(max_gas_for_call.into())
1008        .try_into()
1009        .map_err(|_err| ExceptionalHalt::OutOfGas)?;
1010
1011    Ok((
1012        gas.checked_add(call_gas_costs)
1013            .ok_or(ExceptionalHalt::OutOfGas)?,
1014        gas.checked_add(gas_stipend)
1015            .ok_or(ExceptionalHalt::OutOfGas)?,
1016    ))
1017}
1018
1019pub fn bls12_msm(k: usize, discount_table: &[u64; 128], mul_cost: u64) -> Result<u64, VMError> {
1020    if k == 0 {
1021        return Ok(0);
1022    }
1023
1024    let discount = if k < discount_table.len() {
1025        discount_table
1026            .get(k.checked_sub(1).ok_or(InternalError::Underflow)?)
1027            .copied()
1028            .ok_or(InternalError::Slicing)?
1029    } else {
1030        discount_table
1031            .last()
1032            .copied()
1033            .ok_or(InternalError::Slicing)?
1034    };
1035
1036    let gas_cost = u64::try_from(k)
1037        .map_err(|_| ExceptionalHalt::VeryLargeNumber)?
1038        .checked_mul(mul_cost)
1039        .ok_or(ExceptionalHalt::VeryLargeNumber)?
1040        .checked_mul(discount)
1041        .ok_or(ExceptionalHalt::VeryLargeNumber)?
1042        / BLS12_381_MSM_MULTIPLIER;
1043    Ok(gas_cost)
1044}
1045
1046pub fn bls12_pairing_check(k: usize) -> Result<u64, VMError> {
1047    let gas_cost = u64::try_from(k)
1048        .map_err(|_| ExceptionalHalt::VeryLargeNumber)?
1049        .checked_mul(BLS12_PAIRING_CHECK_MUL_COST)
1050        .ok_or(InternalError::Overflow)?
1051        .checked_add(BLS12_PAIRING_CHECK_FIXED_COST)
1052        .ok_or(InternalError::Overflow)?;
1053    Ok(gas_cost)
1054}