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