Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 16 additions & 5 deletions crates/vm/backends/levm/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -31,8 +31,8 @@ use ethrex_levm::EVMConfig;
use ethrex_levm::account::{AccountStatus, LevmAccount};
use ethrex_levm::call_frame::Stack;
use ethrex_levm::constants::{
POST_OSAKA_GAS_LIMIT_CAP, STACK_LIMIT, SYS_CALL_GAS_LIMIT, TX_BASE_COST,
TX_MAX_GAS_LIMIT_AMSTERDAM,
POST_OSAKA_GAS_LIMIT_CAP, STACK_LIMIT, SYS_CALL_GAS_LIMIT, SYS_CALL_GAS_LIMIT_AMSTERDAM,
TX_BASE_COST, TX_MAX_GAS_LIMIT_AMSTERDAM,
};
use ethrex_levm::db::Database;
use ethrex_levm::db::gen_db::{CacheDB, GeneralizedDatabase};
Expand Down Expand Up @@ -2526,11 +2526,22 @@ pub fn generic_system_contract_levm(
.current_accounts_state
.get(&block_header.coinbase)
.cloned();
// EIPs 2935, 4788, 7002 and 7251 give system calls a fixed 30M execution budget
// and they do not pay intrinsic gas. From Amsterdam, EIP-8037 (ethereum/EIPs#11573)
// additionally reserves `STATE_BYTES_PER_STORAGE_SET × CPSB × SYSTEM_MAX_SSTORES_PER_CALL`
// for state-creation in `state_gas_reservoir`, lifting the total budget to
// `SYS_CALL_GAS_LIMIT_AMSTERDAM`. The reservoir split is performed in
// `prepare_execution` keyed on `is_system_call`.
let sys_call_gas_limit = if config.fork >= Fork::Amsterdam {
SYS_CALL_GAS_LIMIT_AMSTERDAM
} else {
SYS_CALL_GAS_LIMIT
};
let env = Environment {
origin: system_address,
// EIPs 2935, 4788, 7002 and 7251 dictate that the system calls have a gas limit of 30 million and they do not use intrinsic gas.
// So we add the base cost that will be taken in the execution.
gas_limit: SYS_CALL_GAS_LIMIT + TX_BASE_COST,
// We add TX_BASE_COST because intrinsic gas (which includes it) is consumed
// during execution.
gas_limit: sys_call_gas_limit + TX_BASE_COST,
block_number: block_header.number,
coinbase: block_header.coinbase,
timestamp: block_header.timestamp,
Expand Down
21 changes: 21 additions & 0 deletions crates/vm/levm/src/constants.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,27 @@ pub const MEMORY_EXPANSION_QUOTIENT: u64 = 512;
// Dedicated gas limit for system calls according to EIPs 2935, 4788, 7002 and 7251
pub const SYS_CALL_GAS_LIMIT: u64 = 30000000;

// EIP-8037 (ethereum/EIPs#11573): per-system-call upper bound on new storage slots
// written. Matches MAX_WITHDRAWAL_REQUESTS_PER_BLOCK (EIP-7002), the largest per-block
// bound across the existing system contracts.
pub const SYSTEM_MAX_SSTORES_PER_CALL: u64 = 16;

// EIP-8037: extra state-gas budget reserved for system contracts that perform up to
// SYSTEM_MAX_SSTORES_PER_CALL new SSTOREs per invocation. Derived symbolically from
// the EIP-8037 byte constant so the bound stays in lockstep with
// SYSTEM_MAX_SSTORES_PER_CALL. The CPSB factor (1174) is inlined here because
// `cost_per_state_byte` is a runtime fn; if CPSB ever changes, this constant and
// `cost_per_state_byte` must move together.
pub const SYS_CALL_STATE_GAS_RESERVOIR: u64 =
crate::gas_cost::STATE_BYTES_PER_STORAGE_SET * 1174 * SYSTEM_MAX_SSTORES_PER_CALL;
Comment on lines +34 to +35
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Hardcoded CPSB value creates maintenance coupling

SYS_CALL_STATE_GAS_RESERVOIR inlines 1174 directly rather than deriving it through cost_per_state_byte. This means a future change to the CPSB value in gas_cost::cost_per_state_byte will silently leave SYS_CALL_STATE_GAS_RESERVOIR (and therefore SYS_CALL_GAS_LIMIT_AMSTERDAM) stale. The comment already flags this risk, but it may be worth at minimum a debug_assert_eq!(SYS_CALL_STATE_GAS_RESERVOIR, gas_cost::STATE_BYTES_PER_STORAGE_SET * cost_per_state_byte(0) * SYSTEM_MAX_SSTORES_PER_CALL) in a test to make the divergence loud.

Prompt To Fix With AI
This is a comment left during a code review.
Path: crates/vm/levm/src/constants.rs
Line: 34-35

Comment:
**Hardcoded CPSB value creates maintenance coupling**

`SYS_CALL_STATE_GAS_RESERVOIR` inlines `1174` directly rather than deriving it through `cost_per_state_byte`. This means a future change to the CPSB value in `gas_cost::cost_per_state_byte` will silently leave `SYS_CALL_STATE_GAS_RESERVOIR` (and therefore `SYS_CALL_GAS_LIMIT_AMSTERDAM`) stale. The comment already flags this risk, but it may be worth at minimum a `debug_assert_eq!(SYS_CALL_STATE_GAS_RESERVOIR, gas_cost::STATE_BYTES_PER_STORAGE_SET * cost_per_state_byte(0) * SYSTEM_MAX_SSTORES_PER_CALL)` in a test to make the divergence loud.

How can I resolve this? If you propose a fix, please make it concise.


// EIP-8037: From Amsterdam, system calls receive the legacy 30M execution budget plus
// SYS_CALL_STATE_GAS_RESERVOIR placed in `state_gas_reservoir`. This preserves the
// existing 30M execution margin under the higher state-creation costs.
//
// 30_000_000 + 32 * 1174 * 16 = 30_601_088
pub const SYS_CALL_GAS_LIMIT_AMSTERDAM: u64 = SYS_CALL_GAS_LIMIT + SYS_CALL_STATE_GAS_RESERVOIR;
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

SYS_CALL_GAS_LIMIT_AMSTERDAM = 30_000_000 + 32 * 1174 * 16 = 30_601_088 checks out against the spec. SYSTEM_MAX_SSTORES_PER_CALL = 16 is correctly anchored to MAX_WITHDRAWAL_REQUESTS_PER_BLOCK (EIP-7002, the loosest bound). The hardcoded 1174 here (rather than calling cost_per_state_byte()) is justified by the doc — cost_per_state_byte isn't const. Worth a const_assert! (or similar) somewhere that pins cost_per_state_byte(0) == 1174 so the coupling can't drift silently if a future PR parameterizes CPSB.


// Transaction costs in gas
pub const TX_BASE_COST: u64 = 21000;

Expand Down
21 changes: 7 additions & 14 deletions crates/vm/levm/src/gas_cost.rs
Original file line number Diff line number Diff line change
Expand Up @@ -164,22 +164,15 @@ pub const CREATE_BASE_COST: u64 = 32000;
// EIP-8037: Multidimensional gas for state creation (Amsterdam only)
pub const STATE_BYTES_PER_NEW_ACCOUNT: u64 = 112;
pub const STATE_BYTES_PER_STORAGE_SET: u64 = 32;
pub const STATE_BYTES_PER_AUTH_TOTAL: u64 = 135; // 112 account + 23 auth-specific
pub const STATE_BYTES_PER_AUTH_ONLY: u64 = 23; // auth-specific delta when authority pre-existed (downgrade)
pub const STATE_BYTES_PER_AUTH_BASE: u64 = 23;

// EIP-8037: Dynamic cost_per_state_byte formula constants (execution-specs#2687)
pub const BLOCKS_PER_YEAR: u64 = 2_628_000;
pub const TARGET_STATE_GROWTH_PER_YEAR: u64 = 100 * (1u64 << 30); // 100 GiB
pub const CPSB_SIGNIFICANT_BITS: u32 = 5;
pub const CPSB_OFFSET: u64 = 9578;

/// Compute cost_per_state_byte from the block gas limit (EIP-8037, execution-specs#2687).
/// Cost per state byte for EIP-8037 (ethereum/EIPs#11573).
///
/// TEMPORARY for bal-devnet-4: returns the fixed value 1174 used by bal-devnet-3
/// regardless of `block_gas_limit`. The dynamic formula (BLOCKS_PER_YEAR /
/// TARGET_STATE_GROWTH_PER_YEAR / CPSB_SIGNIFICANT_BITS / CPSB_OFFSET) is preserved
/// in the consts above so this commit can be reverted with a single `git revert` to
/// restore the formula body. See execution-specs#2687.
/// Pinned at 1174, the value derived from a 100 GiB/year state-growth target at a
/// 96M-gas reference block limit. The original draft computed this dynamically from
/// the block gas limit; PR 11573 collapses that to a single fixed constant, so
/// `block_gas_limit` is no longer an input. The argument is retained to keep call
/// sites stable; if a future EIP re-derives CPSB, change the body.
pub fn cost_per_state_byte(_block_gas_limit: u64) -> u64 {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we still need this function if the value will be left fixed?

1174
}
Comment on lines 176 to 178
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Silently-ignored parameter may mislead callers

The _block_gas_limit argument is now permanently unused but still accepted. Any caller that constructs its own block_gas_limit value and passes it expecting it to influence the cost will silently get 1174 regardless. Consider making the contract explicit by changing the signature to cost_per_state_byte() -> u64 (a breaking change at call sites, but honest) or at least adding an inline note to make it clear the call sites don't need to supply a meaningful value.

Prompt To Fix With AI
This is a comment left during a code review.
Path: crates/vm/levm/src/gas_cost.rs
Line: 176-178

Comment:
**Silently-ignored parameter may mislead callers**

The `_block_gas_limit` argument is now permanently unused but still accepted. Any caller that constructs its own `block_gas_limit` value and passes it expecting it to influence the cost will silently get `1174` regardless. Consider making the contract explicit by changing the signature to `cost_per_state_byte() -> u64` (a breaking change at call sites, but honest) or at least adding an inline note to make it clear the call sites don't need to supply a meaningful value.

How can I resolve this? If you propose a fix, please make it concise.

Expand Down
21 changes: 13 additions & 8 deletions crates/vm/levm/src/state_diff.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,12 @@ pub struct StateDiff {
pub new_storage_slots: FxHashSet<(Address, H256)>,
/// Per-address deployed-code byte counts (charged at code-deposit step in CREATE).
pub code_deposits: FxHashMap<Address, u64>,
/// EIP-7702 auth-total entries: authority address → 135 bytes (full new-account+auth charge).
/// EIP-7702 auth-total entries: authority address →
/// `STATE_BYTES_PER_NEW_ACCOUNT + STATE_BYTES_PER_AUTH_BASE` bytes
/// (full new-account + auth-base charge, authority did not pre-exist).
pub auth_total: FxHashSet<Address>,
/// EIP-7702 auth-only entries: authority address → 23 bytes (downgraded — authority pre-existed).
/// EIP-7702 auth-only entries: authority address → `STATE_BYTES_PER_AUTH_BASE` bytes
/// (downgraded — authority pre-existed).
pub auth_only: FxHashSet<Address>,

/// Cross-frame cancellations: storage slots cleared (N→0) but created in an ancestor.
Expand All @@ -37,9 +40,13 @@ impl StateDiff {
)]
pub fn bytes(&self) -> u64 {
use crate::gas_cost::{
STATE_BYTES_PER_AUTH_ONLY, STATE_BYTES_PER_AUTH_TOTAL, STATE_BYTES_PER_NEW_ACCOUNT,
STATE_BYTES_PER_STORAGE_SET,
STATE_BYTES_PER_AUTH_BASE, STATE_BYTES_PER_NEW_ACCOUNT, STATE_BYTES_PER_STORAGE_SET,
};
// EIP-8037 ethereum/EIPs#11573: a fresh 7702 authorization charges
// STATE_BYTES_PER_NEW_ACCOUNT + STATE_BYTES_PER_AUTH_BASE; an authority that
// pre-existed downgrades to STATE_BYTES_PER_AUTH_BASE alone.
let auth_total_bytes =
STATE_BYTES_PER_NEW_ACCOUNT.saturating_add(STATE_BYTES_PER_AUTH_BASE);

(self.new_accounts.len() as u64)
.saturating_mul(STATE_BYTES_PER_NEW_ACCOUNT)
Expand All @@ -52,10 +59,8 @@ impl StateDiff {
.copied()
.fold(0u64, u64::saturating_add),
)
.saturating_add(
(self.auth_total.len() as u64).saturating_mul(STATE_BYTES_PER_AUTH_TOTAL),
)
.saturating_add((self.auth_only.len() as u64).saturating_mul(STATE_BYTES_PER_AUTH_ONLY))
.saturating_add((self.auth_total.len() as u64).saturating_mul(auth_total_bytes))
.saturating_add((self.auth_only.len() as u64).saturating_mul(STATE_BYTES_PER_AUTH_BASE))
}

// -------------------------------------------------------------------------
Expand Down
69 changes: 50 additions & 19 deletions crates/vm/levm/src/utils.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ use crate::{
gas_cost::{
self, ACCESS_LIST_ADDRESS_COST, ACCESS_LIST_STORAGE_KEY_COST, BLOB_GAS_PER_BLOB,
COLD_ADDRESS_ACCESS_COST, CREATE_BASE_COST, REGULAR_GAS_CREATE, STANDARD_TOKEN_COST,
STATE_BYTES_PER_AUTH_TOTAL, STATE_BYTES_PER_NEW_ACCOUNT, WARM_ADDRESS_ACCESS_COST,
STATE_BYTES_PER_AUTH_BASE, STATE_BYTES_PER_NEW_ACCOUNT, WARM_ADDRESS_ACCESS_COST,
cost_per_state_byte, floor_tokens_in_access_list, total_cost_floor_per_token,
},
vm::{Substate, VM},
Expand Down Expand Up @@ -284,8 +284,9 @@ impl<'a> VM<'a> {
// or any other validation in this function never reach the
// `record_auth_downgrade_to_only` call below. They remain in `auth_total` in
// state_diff_intrinsic_seed (seeded by add_intrinsic_gas), matching legacy behavior
// where invalid tuples still cost the full STATE_BYTES_PER_AUTH_TOTAL (135 bytes)
// intrinsic state gas.
// where invalid tuples still cost the full new-account + auth-base charge
// (STATE_BYTES_PER_NEW_ACCOUNT + STATE_BYTES_PER_AUTH_BASE = 135 bytes) of intrinsic
// state gas.
let mut refunded_gas: u64 = 0;
// IMPORTANT:
// If any of the below steps fail, immediately stop processing that tuple and continue to the next tuple in the list. It will in the case of multiple tuples for the same authority, set the code using the address in the last valid occurrence.
Expand Down Expand Up @@ -451,14 +452,36 @@ impl<'a> VM<'a> {
// execution_gas = what remains after all intrinsic gas; regular_gas_budget = how much
// regular execution gas is allowed (capped at TX_MAX_GAS_LIMIT_AMSTERDAM); the difference becomes
// the reservoir for drawing state gas without consuming regular gas_remaining.
//
// System calls (EIPs 2935, 4788, 7002, 7251) bypass EIP-7825's TX_MAX_GAS_LIMIT cap and
// instead receive a fixed split per ethereum/EIPs#11573:
// gas_left = SYS_CALL_GAS_LIMIT (the legacy 30M execution budget)
// state_gas_reservoir = STATE_BYTES_PER_STORAGE_SET * CPSB * SYSTEM_MAX_SSTORES_PER_CALL
// The caller (`generic_system_contract_levm`) sets `env.gas_limit` to the sum of both
// plus TX_BASE_COST, so we just split `execution_gas` here.
if self.env.config.fork >= Fork::Amsterdam {
let gas_limit = self.tx.gas_limit();
// System calls have `tx.gas_limit() == 0` (the tx is built from
// `EIP1559Transaction::default()` in `generic_system_contract_levm`); their
// budget lives in `env.gas_limit` instead. For ordinary user txs the two
// agree (set together at the env-builder), but for `eth_call`/simulation
// paths `env.gas_limit` may default to the block max while the wrapping
// `tx` has `gas_limit = 0`. So we read whichever source carries the budget
// for the path we're on.
let gas_limit = if self.env.is_system_call {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Doc/impl mismatch: the surrounding comment says "For ordinary user txs the two agree (set together at the env-builder), but for eth_call/simulation paths env.gas_limit may default to the block max while the wrapping tx has gas_limit = 0. So we read whichever source carries the budget for the path we're on" — but the branch below only switches on is_system_call, not on is_simulation/eth_call. So for an Amsterdam+ eth_call (non-system-call) we hit the tx.gas_limit() arm, which is 0, making execution_gas = 0 and the reservoir/gas_left logic effectively no-op. In practice that's probably fine because eth_call doesn't depend on the GAS opcode return value the way real txs do, but the source comment is misleading as written.

Two possible fixes — pick whichever matches your intent:

  • Tighten the comment to system-call vs user-tx only and drop the eth_call digression, since the code doesn't actually treat eth_call specially.
  • Add an eth_call/simulation-aware branch (e.g., key on self.env.is_simulation if that flag exists) that also reads env.gas_limit instead of tx.gas_limit(), if you do want simulations to get the same reservoir handling as system calls.

self.env.gas_limit
} else {
self.tx.gas_limit()
};
let execution_gas = gas_limit.saturating_sub(total_gas);
let regular_gas_budget = TX_MAX_GAS_LIMIT_AMSTERDAM.saturating_sub(regular_gas);
let gas_left = regular_gas_budget.min(execution_gas);
let reservoir = execution_gas.saturating_sub(gas_left);
let reservoir = if self.env.is_system_call {
execution_gas.saturating_sub(SYS_CALL_GAS_LIMIT)
} else {
let regular_gas_budget = TX_MAX_GAS_LIMIT_AMSTERDAM.saturating_sub(regular_gas);
let gas_left = regular_gas_budget.min(execution_gas);
execution_gas.saturating_sub(gas_left)
};
if reservoir > 0 {
// Pre-consume reservoir from gas_remaining so GAS opcode returns <= TX_MAX_GAS_LIMIT_AMSTERDAM
// Pre-consume reservoir from gas_remaining so GAS opcode returns gas_left only.
let reservoir_i64 =
i64::try_from(reservoir).map_err(|_| InternalError::Overflow)?;
self.current_call_frame.gas_remaining = self
Expand Down Expand Up @@ -568,14 +591,17 @@ impl<'a> VM<'a> {
};

if fork >= Fork::Amsterdam {
// EIP-8037: per-auth regular cost is PER_AUTH_BASE_COST, state is STATE_BYTES_PER_AUTH_TOTAL * cost_per_state_byte
// EIP-8037 (ethereum/EIPs#11573): per-auth regular cost is PER_AUTH_BASE_COST;
// state cost assumes account creation, i.e.
// (STATE_BYTES_PER_NEW_ACCOUNT + STATE_BYTES_PER_AUTH_BASE) * cost_per_state_byte.
let regular_auth_cost = PER_AUTH_BASE_COST
.checked_mul(amount_of_auth_tuples)
.ok_or(InternalError::Overflow)?;
regular_gas = regular_gas.checked_add(regular_auth_cost).ok_or(OutOfGas)?;
#[expect(clippy::arithmetic_side_effects, reason = "bounded constants")]
let auth_total_state_gas =
gas_cost::STATE_BYTES_PER_AUTH_TOTAL * self.cost_per_state_byte;
let auth_total_state_gas = (gas_cost::STATE_BYTES_PER_NEW_ACCOUNT
+ gas_cost::STATE_BYTES_PER_AUTH_BASE)
* self.cost_per_state_byte;
let state_auth_cost = auth_total_state_gas
.checked_mul(amount_of_auth_tuples)
.ok_or(InternalError::Overflow)?;
Expand Down Expand Up @@ -689,14 +715,19 @@ pub fn intrinsic_gas_dimensions(

let (state_gas_new_account, state_gas_auth_total) = if fork >= Fork::Amsterdam {
let cpsb = cost_per_state_byte(block_gas_limit);
(
STATE_BYTES_PER_NEW_ACCOUNT
.checked_mul(cpsb)
.ok_or(InternalError::Overflow)?,
STATE_BYTES_PER_AUTH_TOTAL
.checked_mul(cpsb)
.ok_or(InternalError::Overflow)?,
)
let state_gas_new_account = STATE_BYTES_PER_NEW_ACCOUNT
.checked_mul(cpsb)
.ok_or(InternalError::Overflow)?;
// EIP-8037 (ethereum/EIPs#11573): a fresh authorization charges
// STATE_BYTES_PER_NEW_ACCOUNT + STATE_BYTES_PER_AUTH_BASE bytes; the
// per-byte cost is the same constant CPSB.
let auth_total_bytes = STATE_BYTES_PER_NEW_ACCOUNT
.checked_add(STATE_BYTES_PER_AUTH_BASE)
.ok_or(InternalError::Overflow)?;
let state_gas_auth_total = auth_total_bytes
.checked_mul(cpsb)
.ok_or(InternalError::Overflow)?;
(state_gas_new_account, state_gas_auth_total)
} else {
(0, 0)
};
Expand Down
78 changes: 26 additions & 52 deletions test/tests/levm/eip8037_tests.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,9 @@
//! EIP-8037: Dynamic cost_per_state_byte Tests
//! EIP-8037: cost_per_state_byte (CPSB) tests.
//!
//! Per ethereum/EIPs#11573, CPSB is a fixed constant (1174). The original draft
//! defined a dynamic, block-gas-limit-dependent formula with quantization; that
//! mechanism has been removed from the spec, so we only assert the fixed value
//! here.
//!
//! Also covers parity between the standalone `intrinsic_gas_dimensions`
//! helper (used by mempool / payload builder) and `VM::get_intrinsic_gas`
Expand Down Expand Up @@ -26,58 +31,27 @@ use ethrex_levm::{
use rustc_hash::FxHashMap;
use std::sync::Arc;

/// Sanity check: cost_per_state_byte(120_000_000) == 1174
/// (matches the legacy hardcoded COST_PER_STATE_BYTE constant)
#[test]
fn test_cpsb_120m() {
assert_eq!(cost_per_state_byte(120_000_000), 1174);
}

/// gas_limit = 30_000_000
/// num = 30_000_000 * 2_628_000 = 78_840_000_000_000
/// denom = 2 * 100 * 2^30 = 214_748_364_800
/// raw = ceil(78_840_000_000_000 / 214_748_364_800) = 368
/// shifted = 368 + 9578 = 9946
/// bit_length = 14, shift = 9
/// quantized = (9946 >> 9) << 9 = 19 * 512 = 9728
/// result = 9728 - 9578 = 150
#[test]
#[ignore = "bal-devnet-4: cost_per_state_byte temporarily fixed to 1174; re-enable when dynamic formula is restored"]
fn test_cpsb_30m() {
assert_eq!(cost_per_state_byte(30_000_000), 150);
}

/// gas_limit = 500_000_000
/// raw = ceil(500_000_000 * 2_628_000 / 214_748_364_800) = 6119
/// shifted = 6119 + 9578 = 15697
/// bit_length = 14, shift = 9
/// quantized = (15697 >> 9) << 9 = 30 * 512 = 15360
/// result = 15360 - 9578 = 5782
/// CPSB is a fixed constant of 1174 (ethereum/EIPs#11573). The block-gas-limit
/// argument is retained in the function signature for forward-compatibility but
/// must not influence the result.
#[test]
#[ignore = "bal-devnet-4: cost_per_state_byte temporarily fixed to 1174; re-enable when dynamic formula is restored"]
fn test_cpsb_500m() {
assert_eq!(cost_per_state_byte(500_000_000), 5782);
}

/// Low-end clamp: formula produces `quantized <= CPSB_OFFSET`, so the function
/// returns 1 (the minimum viable cost). Guard against an off-by-one in the
/// `if quantized > CPSB_OFFSET` branch.
#[test]
#[ignore = "bal-devnet-4: cost_per_state_byte temporarily fixed to 1174; re-enable when dynamic formula is restored"]
fn test_cpsb_clamp_to_one_for_tiny_gas_limit() {
assert_eq!(cost_per_state_byte(1), 1);
assert_eq!(cost_per_state_byte(5_000_000), 1);
}

/// Upper boundary of the 30M quantization bin — `cpsb(14_999_999)` must not
/// jump across the next bin's value just because `raw` changes by 1. All
/// gas_limits in the 5M–30M range quantize to 150.
#[test]
#[ignore = "bal-devnet-4: cost_per_state_byte temporarily fixed to 1174; re-enable when dynamic formula is restored"]
fn test_cpsb_30m_bin_boundary() {
assert_eq!(cost_per_state_byte(14_999_999), 150);
assert_eq!(cost_per_state_byte(15_000_000), 150);
assert_eq!(cost_per_state_byte(29_999_999), 150);
fn test_cpsb_is_fixed_at_1174() {
for gas_limit in [
1u64,
5_000_000,
29_999_999,
30_000_000,
96_000_000,
120_000_000,
500_000_000,
u64::MAX,
] {
assert_eq!(
cost_per_state_byte(gas_limit),
1174,
"CPSB must be fixed at 1174 regardless of block gas limit (input: {gas_limit})",
);
}
}

// ==================== intrinsic_gas_dimensions parity ====================
Expand Down
Loading