Skip to main content

ethrex_levm/
gas_cost.rs

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