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