Dual Gas Model
MegaETH dual gas model specification — compute gas, storage gas, SALT bucket multiplier, and per-operation storage gas schedule.
MegaETH's dual gas model separates transaction gas costs into two independent dimensions: compute gas (the gas charged for EVM computation, derived from standard EVM gas semantics) and storage gas (an additional charge for operations that impose persistent storage burden on nodes). A transaction's total gas is the sum of both.
Motivation
Standard EVM gas pricing assumes a base fee high enough that storage-heavy operations (state writes, logs, calldata) are adequately priced by compute gas alone. MegaETH breaks this assumption in two ways:
Extremely low base fees — MegaETH's base fee is 0.001 gwei (10⁶ wei), orders of magnitude lower than Ethereum mainnet. At this fee level, the compute gas cost of an SSTORE (22,100 gas) is negligible relative to the actual cost of persisting the state change.
High transaction gas limits — MegaETH allows up to 10 billion gas per block. A single transaction could write thousands of storage slots, deploy megabytes of bytecode, or emit massive logs for near-zero cost under standard gas pricing.
Without a separate storage gas dimension, a single transaction could bloat on-chain state or history data to unsustainable levels. The dual gas model addresses this by pricing storage burden independently of computation, ensuring that state-heavy operations pay their true cost to node operators regardless of the base fee level.
Specification
The named constants referenced in this section are defined later in Constants.
Total Gas
A node MUST compute total gas for every transaction as:
total_gas_used = compute_gas_used + storage_gas_usedBoth compute gas and storage gas MUST be deducted from the transaction's gas_limit budget. When that budget is exhausted, the transaction MUST halt with OutOfGas; the halt is decided by the EVM gas meter's budget, not by summing the two counters.
Total gas is the sum of the two metered dimensions. It is not the receipt, and a node MUST NOT derive the receipt by summing the two counters.
Gas Charged to the Transaction
The two counters are metering state. The gas charged to a transaction is the EVM gas meter's own accounting, maintained alongside them.
A node MUST charge the transaction from the gas meter, and MUST NOT compute the charge from the two counters. No equation relates them: the counters can total more than the transaction is charged, or less, and both happen in ordinary transactions.
They total less than the charge when the transaction pays for gas neither counter records:
Gas an operation consumed before halting partway through, which is deliberately never recorded as compute gas (see Single-Record Rule).
The difference between the gas limit a caller forwards into a failing precompile and the compute gas recorded for it, which is capped at the remaining compute budget (see Precompiles).
Gas forwarded to a child frame that a compute-gas exceed discarded, and the envelope a transaction has left when the KeylessDeploy dispatch overhead crosses the transaction-level compute limit, are both returned rather than spent, so neither contributes to this gap (see Exceed Behavior).
They total more than the charge when compute gas records gas the transaction never paid. This happens on every value-transferring CALL or CALLCODE: the inherited EVM hands the callee CALL_STIPEND without deducting it from the caller, and the parent's compute gas records it regardless, so the counters run CALL_STIPEND ahead of the charge for each such call (see Forwarded Gas Exclusion).
Receipt Gas
The gas_used field in the transaction receipt MUST be the gas charged to the transaction, less any standard EVM gas refund settled at the end of the transaction, and then raised to the calldata floor cost if that floor is higher.
Refunds and the calldata floor apply to the charge, not to either counter: a node MUST NOT subtract a refund from tracked compute gas, and MUST NOT raise it to a floor.
Compute Gas
Compute gas is based on standard EVM gas semantics inherited from Optimism Isthmus / Ethereum Prague. Unless explicitly overridden elsewhere in this specification, each opcode MUST use the same compute gas cost as in the inherited EVM semantics. The dual gas model itself does not redefine opcode compute gas costs; it adds the storage gas dimension on top of them.
How much of an operation's inherited EVM gas a node records as compute gas — the measurement window, its exclusions, and the recording sites — is specified in Compute Gas Accounting.
Gas Metering Order
Each opcode consumes compute gas and may additionally charge storage gas. Because the compute gas a transaction has consumed is itself a metered resource bounded by the compute gas limit, the order in which an opcode charges storage gas relative to recording compute gas is consensus-visible: it determines, when an opcode halts partway through, how much compute gas has been recorded and which limit is reached first.
A node MUST meter every storage-affecting opcode — SSTORE, LOG0 through LOG4, CALL, CALLCODE, DELEGATECALL, STATICCALL, CREATE, CREATE2, and SELFDESTRUCT — in the following fixed order:
Validate the operands the storage-gas charge itself reads. A validation failure (for example, stack underflow) MUST halt before any gas is charged or recorded. Operands the charge does not read are validated by the opcode body in step 3:
LOG1throughLOG4read only the data length here, so a short stack missing their topic operands reaches step 2 first.Charge the opcode's storage gas against the transaction's gas budget. If the budget is insufficient, the node MUST halt with
OutOfGasbefore executing the opcode body.Execute the opcode body, including every standard EVM dynamic cost such as memory expansion, account access, and child-frame gas forwarding.
CREATE2is the exception to the order of steps 2 and 3: its target address is derived from the initcode, so the memory expansion and hash that derive it necessarily precede the charge for that address, and an insufficient budget surfaces after the expansion has already been paid for.Record the opcode's compute gas as a single amount, measured over a window spanning steps 2 and 3 — or over the equivalent window that opens after step 2 with
storage_gas_chargedtreated as zero (see Storage Gas Exclusion). The recorded amount and its exclusions are defined in Compute Gas Accounting. The node MUST then enforce the compute gas limit, halting if it is exceeded.Surface the opcode's other resource-limit dimensions (data size, key-value updates, state growth). This step fixes when an exceed on those dimensions is acted on, not when their usage is recorded. An opcode MAY record such usage earlier — before its body runs — provided the resulting exceed is not acted on until this step, so that a body that fails still discards the usage it would have added.
SELFDESTRUCT is the exception to the split between steps 4 and 5. It records its beneficiary's data-size, key-value-update, and state-growth usage before its body runs, so it MUST evaluate all four dimensions together once the body completes, rather than deciding compute gas first. When more than one dimension is over its limit on that opcode, the reported dimension follows the fixed priority specified in Multidimensional Resource Limits. SELFDESTRUCT can record all three non-compute dimensions for a newly materialized beneficiary, so that order decides the reported dimension in both directions — data size or key-value updates outrank compute gas, while compute gas outranks state growth.
A node MUST record an opcode's compute gas in exactly one step, after the opcode body has fully executed — with no CREATE2 exception. The no-record rule when the body does not run to completion is specified in Single-Record Rule.
Storage Gas
Storage gas is an additional charge for operations that impose persistent storage burden on nodes. A node MUST charge storage gas according to the following schedule:
Transaction Intrinsic
INTRINSIC_STORAGE_GAS (39,000 flat)
Charged before execution begins, alongside compute intrinsic gas
SSTORE (0 → non-0)
SSTORE_STORAGE_GAS_BASE × (multiplier − 1)
Charged at the time of the SSTORE opcode when writing a non-zero value to a slot that was zero before this transaction
Account Creation
ACCOUNT_CREATION_STORAGE_GAS_BASE × (multiplier − 1)
Charged when a value transfer targets an empty account
Contract Creation
CONTRACT_CREATION_STORAGE_GAS_BASE × (multiplier − 1)
Charged at CREATE/CREATE2 execution or creation transaction, regardless of whether initcode succeeds or fails
Code Deposit
CODE_DEPOSIT_STORAGE_GAS × code_length
Charged per byte when contract creation succeeds and bytecode is stored
LOG Topic
LOG_TOPIC_STORAGE_GAS × topic_count
Charged at the LOG opcode
LOG Data
LOG_DATA_STORAGE_GAS × data_length
Charged at the LOG opcode
Calldata (zero byte)
CALLDATA_ZERO_STORAGE_GAS × zero_byte_count
Charged before execution begins, alongside intrinsic gas
Calldata (non-zero byte)
CALLDATA_NONZERO_STORAGE_GAS × nonzero_byte_count
Charged before execution begins, alongside intrinsic gas
Calldata floor (zero byte)
CALLDATA_FLOOR_ZERO_STORAGE_GAS × zero_byte_count
Post-execution floor check (see below)
Calldata floor (non-zero byte)
CALLDATA_FLOOR_NONZERO_STORAGE_GAS × nonzero_byte_count
Post-execution floor check (see below)
Contract creation MUST charge only CONTRACT_CREATION_STORAGE_GAS_BASE × (multiplier − 1). The account creation storage gas (ACCOUNT_CREATION_STORAGE_GAS_BASE) MUST NOT be charged on top of the contract creation cost.
Top-Level Contract Creation Address
For a top-level contract-creation transaction, the contract-creation storage gas and its SALT bucket multiplier MUST be computed from the address that the transaction will actually create. A node MUST derive that address from the sender account's current state nonce — the same nonce used to create the contract — and MUST NOT derive it from the transaction's nonce field. These two values differ when prior in-block activity or EIP-7702 authorizations have advanced the sender account's nonce ahead of the value carried in the transaction's nonce field.
Calldata Floor Cost
Per EIP-7623, after execution completes, if the total gas consumed is less than the calldata floor cost, the transaction MUST be charged the floor cost instead. The floor cost storage gas component uses the CALLDATA_FLOOR_*_STORAGE_GAS constants, which are STORAGE_GAS_MULTIPLIER (10×) of the standard EVM floor costs defined in EIP-7623.
SSTORE Storage Gas Refund
Setting a storage slot back to its original value within the same transaction MUST NOT refund the storage gas that was charged when the slot was first written to a non-zero value. Storage gas for SSTORE is non-refundable: every zero-to-non-zero SSTORE transition accumulates storage gas even if the slot is later reset.
Standard EVM gas refunds for SSTORE (e.g., the SSTORE_CLEARS_SCHEDULE refund) apply only to the compute gas component and MUST NOT affect storage gas.
Revert Behavior
Storage gas charged within a reverted call frame MUST be consumed and not refunded, consistent with standard EVM gas semantics. The data size tracked for LOG operations within a reverted call frame MUST be rolled back, since the logs themselves are discarded.
Storage Gas Stipend for Value Transfers
The 10× storage gas on LOG opcodes causes a simple LOG1 to cost 4,500 gas (750 compute + 3,750 storage), exceeding the EVM's CALL_STIPEND of 2,300.
A storage gas stipend allowance of 23,000 gas is reserved for internal (depth > 0) value-transferring CALL and CALLCODE opcodes. DELEGATECALL, STATICCALL, top-level transaction calls, and system contract interceptions MUST NOT receive the stipend. The allowance MUST NOT inflate the callee's gas limit: the callee runs with the standard forwarded_gas + CALL_STIPEND (2,300), and the stipend is a separate per-frame budget drawn only at the storage-gas surcharge sites MegaETH adds on top of standard EVM opcode costs (LOG topic/data, new-account materialization, first-time-write SSTORE, contract-creation storage, SELFDESTRUCT beneficiary creation). Because the allowance never enters the callee's gas limit, it MUST NOT be spendable on compute (standard EVM opcode) gas, and any undrawn portion is neither returned to the caller nor rescued for the sender on early termination. See the gas forwarding page for the full mechanism.
Dynamic SALT Multiplier
Storage gas costs for SSTORE, account creation, and contract creation scale dynamically based on SALT bucket capacity.
A node MUST compute the multiplier for each operation as:
Where bucket_capacity is the capacity of the SALT bucket that the target account or storage slot maps to in the parent block's state.
The following rules MUST apply:
When
multiplier = 1(bucket at minimum size): storage gas for the operation MUST be zero, sincebase × (1 − 1) = 0.When
multiplier > 1: storage gas MUST scale linearly asbase × (multiplier − 1).The multiplier MUST be determined from the SALT bucket state of the parent block, not the current transaction's intermediate state.
A system-originated transaction MUST be charged dynamic storage gas as if every target bucket were at minimum capacity (multiplier = 1), regardless of the bucket's actual capacity. Under the base × (multiplier − 1) formula this makes the dynamic storage gas zero, so a protocol-mandated storage write costs only its standard EVM gas and cannot run out of gas as SALT buckets grow. User transactions are unaffected and continue to pay capacity-scaled storage gas.
Bucket ID Calculation
For storage gas calculation, a node MUST determine the SALT bucket from the target key, regardless of whether the account or storage slot already exists.
The bucket ID formula is:
Where ahash is the fixed-seed deterministic hash function defined by SALT. The canonical bucket-mapping implementation is in the SALT repository.
For account creation, the bucket ID MUST be computed from the target account address.
For an
SSTORE-triggered new storage write, the bucket ID MUST be computed from the concatenation of the contract address and storage slot key.
Equivalently:
The multiplier MUST be computed from the capacity of that bucket in the parent-block state, before the new account or storage slot is created.
Bucket Capacity Determination
Bucket capacity is determined by SALT bucket metadata. Each data bucket has a minimum capacity of MIN_BUCKET_SIZE (256 slots).
SALT determines capacity using the following rule:
A bucket starts at
MIN_BUCKET_SIZE.Let
usedbe the number of occupied slots in the bucket.If
used > capacity × 80%, the bucket MUST expand.Expansion doubles the bucket capacity.
Expansion repeats until
used / capacity ≤ 80%.
Equivalently:
The resulting capacity is a power-of-two multiple of MIN_BUCKET_SIZE under normal operation. The maximum bucket capacity is 2^40 slots.
Buckets larger than MIN_BUCKET_SIZE are represented as multiple 256-slot segments under a local SALT bucket subtree. As capacity grows, the bucket subtree root moves upward to cover the larger slot range.
For storage gas calculation, a node MUST use the capacity recorded for the target bucket in the parent-block state. It MUST NOT derive capacity from the transaction's intermediate writes.
The canonical design references are:
salt/src/constant.rsforMIN_BUCKET_SIZE, resize threshold, and resize multiplier
Transaction Intrinsic Costs
All transactions MUST pay both compute gas and storage gas as intrinsic costs before execution begins:
Compute gas
INTRINSIC_COMPUTE_GAS (21,000)
Storage gas
INTRINSIC_STORAGE_GAS (39,000)
Total
60,000
These costs are in addition to standard calldata gas (both compute and storage components). A transaction with gas_limit < 60,000 + calldata_gas MUST be rejected as invalid.
Constants
INTRINSIC_COMPUTE_GAS
21,000
Standard EVM intrinsic gas for all transactions
CALL_STIPEND
2,300
Standard EVM value-transfer call stipend, inherited unchanged
INTRINSIC_STORAGE_GAS
39,000
Storage gas intrinsic for all transactions
SSTORE_STORAGE_GAS_BASE
20,000
Base storage gas for SSTORE (0 → non-0)
ACCOUNT_CREATION_STORAGE_GAS_BASE
25,000
Base storage gas for account creation
CONTRACT_CREATION_STORAGE_GAS_BASE
32,000
Base storage gas for contract creation
CODE_DEPOSIT_STORAGE_GAS
10,000
Storage gas per byte of deployed bytecode
LOG_TOPIC_STORAGE_GAS
3,750
Storage gas per LOG topic
LOG_DATA_STORAGE_GAS
80
Storage gas per byte of LOG data
CALLDATA_ZERO_STORAGE_GAS
40
Storage gas per zero byte of calldata
CALLDATA_NONZERO_STORAGE_GAS
160
Storage gas per non-zero byte of calldata
CALLDATA_FLOOR_ZERO_STORAGE_GAS
100
Storage gas floor per zero byte of calldata
CALLDATA_FLOOR_NONZERO_STORAGE_GAS
400
Storage gas floor per non-zero byte of calldata
STORAGE_GAS_MULTIPLIER
10
Ratio of calldata/LOG storage gas to standard EVM costs
NUM_META_BUCKETS
65,536
Number of SALT buckets reserved for metadata
NUM_KV_BUCKETS
16,711,680
Number of SALT buckets available for key-value state
Rationale
Why base × (multiplier − 1) instead of base × multiplier? The MiniRex spec originally used base × multiplier, which charged storage gas even in uncrowded state regions (multiplier = 1). The Rex spec changed to base × (multiplier − 1) so that operations in minimum-sized SALT buckets incur zero storage gas, removing the penalty for writing to fresh state. See the MiniRex and Rex upgrade pages for the historical evolution.
Why no storage gas refund for SSTORE resets? Allowing refunds would enable a pattern where contracts repeatedly write and clear the same slot to generate refund credits, undermining the storage gas pricing model. The non-refundable design ensures that every state-expanding operation pays its full cost regardless of subsequent reversals within the same transaction.
Why 10× multiplier for calldata and LOG? The STORAGE_GAS_MULTIPLIER of 10 was chosen to reflect the long-term storage and data availability costs that calldata and log operations impose on nodes, relative to their standard EVM gas costs which were designed for Ethereum's higher base fee regime.
Why a flat intrinsic storage gas? Every transaction imposes a baseline storage cost on nodes regardless of its execution: the transaction itself must be stored, the receipt must be persisted, and account state (nonce, balance) must be updated. The 39,000 flat intrinsic storage gas covers this per-transaction overhead.
Security Considerations
If storage gas is omitted or undercharged, MegaETH's 0.001 gwei base fee makes every SSTORE, contract deployment, and LOG call negligible in cost. A single transaction could write thousands of storage slots or emit megabytes of log data for nearly zero fee — the exact attack the dual gas model exists to prevent.
If the STORAGE_CALL_STIPEND allowance is allowed to enter the callee's gas limit or be returned to the caller, system-granted gas leaks to the caller, who can redirect it to non-storage operations — defeating the allowance's purpose of covering only MegaETH's storage-gas surcharges.
Spec History
For the historical evolution of storage gas formulas and constants across specs:
MiniRex — original
base × multiplierformula with 2,000,000 base costRex — revised to
base × (multiplier − 1)with current base costs, added transaction intrinsic storage gasRex4 — storage gas stipend for value transfers
Rex5 — reworked the storage gas stipend into a separated-allowance model, derived the top-level contract-creation storage-gas address from the sender's current state nonce, and made contract-creation code-deposit compute gas atomic with the deployment commit
Rex6 — unified per-opcode gas metering order (compute gas recorded once, after the opcode body, with no
CREATE2exception); system-originated transactions charge dynamic storage gas at minimum bucket capacity; forwarded gas and the KeylessDeploy envelope are returned on a compute-gas exceed rather than spent
Last updated