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