Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
ba4bbd5
EIP-7976: Increase Calldata Floor Cost
jangko Apr 21, 2026
14a4279
EIP-7981: Increase Access List Cost
jangko Apr 21, 2026
40c8e24
zero execution state gas on top-level failure
jangko Apr 22, 2026
84810a2
CREATE failure refunds state gas to reservoir
jangko Apr 22, 2026
8267448
per-dimension block gas limit check at tx inclusion
jangko Apr 22, 2026
b4e7400
EIP-8037 - 0 to x to 0 SSTORE refunds to state gas
jangko Apr 22, 2026
38395f4
EIP-8037 nested child frame refunds
jangko Apr 22, 2026
35393ca
EIP-8037 - SELFDESTRUCT same-tx refunds state gas at end of tx
jangko Apr 22, 2026
c5857cb
Upgrade debug_bal.nim
jangko Apr 23, 2026
d67ff08
Disable zkevm test due to incompatibility
jangko Apr 24, 2026
fe23037
Make CPSB fixed
jangko Apr 29, 2026
5da8e45
7702 refund block-level gas accounting for EIP-8037
jangko May 8, 2026
836fbab
correct gas accounting for blocks under create collision
jangko May 11, 2026
6d9507d
EIP-8037 more refund fixes
jangko May 11, 2026
e7eb772
update bal-devnet-7 EIP-8037 bytes values; CPSB 1174 -> 1530
jangko May 11, 2026
3491f4d
EIP-8037 tx created contracts destroyed in same tx
jangko May 11, 2026
a828a76
EIP-8037 system transaction gas state reservoir
jangko May 12, 2026
ee1d2bd
align with latest EIP-8037 auth refund changes
jangko May 13, 2026
a810de9
Remove SD state gas refunds from EIP-8037
jangko May 13, 2026
a0faf25
EIP-7928 BAL: Fix for EIP-7702 account call
jangko May 14, 2026
f095380
refill auth state gas on delegation clear for EIP-8037
jangko May 14, 2026
87ae1d2
Upgrade test fixtures to tests-bal@v7.1.1
jangko Apr 30, 2026
30deb0b
Less parseDelegation when BALL meet 7702
jangko May 14, 2026
1d08c45
Remove excess BAL tracking
jangko May 15, 2026
d9df5df
Merge branch 'master' into bal-devnet-7
jangko May 16, 2026
8de9e32
Upgrade zkevm test fixtures to v0.4.0 and reenable test
jangko May 16, 2026
89254cd
Optimize getCallCode
jangko May 16, 2026
f99d31b
Less parseDelegation
jangko May 17, 2026
c4b86a7
Remove unused newlyCreatedSelfDestructRefund from ledger
jangko May 18, 2026
df5f48c
Improves state gas access guard
jangko May 18, 2026
3b53a39
Restore call to precompile optimization
jangko May 18, 2026
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
14 changes: 7 additions & 7 deletions execution_chain/core/eip8037.nim
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,14 @@
# those terms

{.push raises: [].}
import
eth/common/base

const
STATE_BYTES_PER_NEW_ACCOUNT* = 112
STATE_BYTES_PER_NEW_ACCOUNT* = 120
STATE_BYTES_PER_AUTH_BASE* = 23
REGULAR_PER_AUTH_BASE_COST* = 7500
STATE_BYTES_PER_STORAGE_SET* = 32

func stateGasPerByte*(gasLimit: GasInt): GasInt =
return 1174.GasInt
STATE_BYTES_PER_STORAGE_SET* = 64
COST_PER_STATE_BYTE* = 1530
CREATE_ACCOUNT_STATE_GAS* = COST_PER_STATE_BYTE * STATE_BYTES_PER_NEW_ACCOUNT
STATE_GAS_STORAGE_SET* = COST_PER_STATE_BYTE * STATE_BYTES_PER_STORAGE_SET
SYSTEM_MAX_SSTORES_PER_CALL = 16
SYSTEM_STATE_GAS_RESERVOIR* = STATE_BYTES_PER_STORAGE_SET * COST_PER_STATE_BYTE * SYSTEM_MAX_SSTORES_PER_CALL
25 changes: 20 additions & 5 deletions execution_chain/core/executor/process_transaction.nim
Original file line number Diff line number Diff line change
Expand Up @@ -105,14 +105,29 @@ proc processTransaction*(
com = vmState.com
fork = vmState.hardFork
regularGasAvailable = vmState.blockCtx.gasLimit - vmState.blockRegularGasUsed
stateGasAvailable = vmState.blockCtx.gasLimit - vmState.blockStateGasUsed
intrinsic = tx.intrinsicGas(fork, vmState.blockCtx.gasLimit)

# Regular gas is capped at TX_MAX_GAS_LIMIT per EIP-7825.
# State gas is not checked per-tx; block-end validation enforces
# max(block_regular_gas_used, block_state_gas_used) <= gas_limit.
if min(TX_GAS_LIMIT.GasInt, tx.gasLimit) > regularGasAvailable:
# Per-tx 2D gas inclusion check: for each dimension the worst-case
# contribution must fit in the remaining budget. Block-end
# validation still enforces
if fork < Amsterdam:
let want = min(TX_GAS_LIMIT.GasInt, tx.gasLimit)
return err("regular gas used exceeds limit want: " & $want & ", available: " & $regularGasAvailable)
if want > regularGasAvailable:
return err("regular gas used exceeds limit, want: " & $want & ", available: " & $regularGasAvailable)
else:
# https://github.com/ethereum/execution-specs/pull/2703/changes
# Worst-case regular contribution: tx.gasLimit minus the portion that
# must go to intrinsic state gas, capped at TX_MAX_GAS_LIMIT.
let want = min(TX_GAS_LIMIT.GasInt, tx.gasLimit - intrinsic.state)
if want > regularGasAvailable:
return err("regular gas used exceeds limit, want: " & $want & ", available: " & $regularGasAvailable)

# Worst-case state contribution: tx.gasLimit minus the portion that
# must go to intrinsic regular gas.
let stateGas = tx.gasLimit - intrinsic.regular
if stateGas > stateGasAvailable:
return err("state gas used exceeds limit, want: " & $stateGas & ", available: " & $stateGasAvailable)

# blobGasUsed will be added to vmState.blobGasUsed if the tx is ok.
let
Expand Down
2 changes: 0 additions & 2 deletions execution_chain/core/tx_pool/tx_desc.nim
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,6 @@ import
../../db/ledger,
../../constants,
../../transaction,
../../core/eip8037,
../../transaction/call_types,
../chain/forked_chain,
../pow/header,
Expand Down Expand Up @@ -106,7 +105,6 @@ proc setupVMState(com: CommonRef;
excessBlobGas: com.calcExcessBlobGas(parent, fork),
parentHash : parentHash,
slotNumber : pos.slotNumber,
costPerStateByte: stateGasPerByte(gasLimit),
),
txFrame = parentFrame.txFrameBegin(),
com = com,
Expand Down
36 changes: 19 additions & 17 deletions execution_chain/db/ledger.nim
Original file line number Diff line number Diff line change
Expand Up @@ -327,6 +327,24 @@ proc makeDirty(ledger: LedgerRef, address: Address, cloneStorage = true): Accoun
ledger.savePoint.cache[address] = result
ledger.savePoint.dirty[address] = result

template getCodeSizeImpl(ledger: LedgerRef, acc: AccountRef): int =
if acc.code == nil:
if acc.statement.codeHash == EMPTY_CODE_HASH:
return 0
acc.code = ledger.code.get(acc.statement.codeHash).valueOr:
# On a cache miss, we don't fetch the code - instead, we fetch just the
# length - should the code itself be needed, it will typically remain
# cached and easily accessible in the database layer - this is to prevent
# EXTCODESIZE calls from messing up the code cache and thus causing
# recomputation of the jump destination table
var rc = ledger.txFrame.len(contractHashKey(acc.statement.codeHash).toOpenArray)

return rc.valueOr:
warn logTxt "getCodeSize()", codeHash=acc.statement.codeHash, error=($$rc.error)
0

acc.code.len()

# ------------------------------------------------------------------------------
# Public methods
# ------------------------------------------------------------------------------
Expand Down Expand Up @@ -454,23 +472,7 @@ proc getCodeSize*(ledger: LedgerRef, address: Address): int =
let acc = ledger.getAccount(address, false)
if acc.isNil:
return 0

if acc.code == nil:
if acc.statement.codeHash == EMPTY_CODE_HASH:
return 0
acc.code = ledger.code.get(acc.statement.codeHash).valueOr:
# On a cache miss, we don't fetch the code - instead, we fetch just the
# length - should the code itself be needed, it will typically remain
# cached and easily accessible in the database layer - this is to prevent
# EXTCODESIZE calls from messing up the code cache and thus causing
# recomputation of the jump destination table
var rc = ledger.txFrame.len(contractHashKey(acc.statement.codeHash).toOpenArray)

return rc.valueOr:
warn logTxt "getCodeSize()", codeHash=acc.statement.codeHash, error=($$rc.error)
0

acc.code.len()
getCodeSizeImpl(ledger, acc)

proc getCommittedStorage*(ledger: LedgerRef, address: Address, slot: UInt256): UInt256 =
let acc = ledger.getAccount(address, false)
Expand Down
6 changes: 2 additions & 4 deletions execution_chain/evm/computation.nim
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import
./evm_errors,
./code_bytes,
./eip7708,
../core/eip8037,
../common/[evmforks],
../utils/[utils, mergeutils],
../common/common,
Expand Down Expand Up @@ -120,9 +121,6 @@ template getCodeHash*(c: Computation, address: Address): Hash32 =
else:
db.getCodeHash(address)

template getCostPerStateByte*(c: Computation): GasInt =
c.vmState.blockCtx.costPerStateByte

template selfDestruct*(c: Computation, address: Address) =
c.execSelfDestruct(address)

Expand Down Expand Up @@ -259,7 +257,7 @@ proc writeContract*(c: Computation) =
break writeContractCode

let
codeDepositStateGas = len.GasInt * c.vmState.blockCtx.costPerStateByte
codeDepositStateGas = len.GasInt * COST_PER_STATE_BYTE
codeHashGas = (6 * wordCount(len)).GasInt

c.gasMeter.consumeGas(codeHashGas, reason = "Code hash gas").isOkOr:
Expand Down
10 changes: 6 additions & 4 deletions execution_chain/evm/interpreter/gas_costs.nim
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import
math, eth/common/eth_types,
./utils/[macros_gen_opcodes, utils_numeric],
./op_codes, ../../common/evmforks,
../evm_errors
../evm_errors, ../../core/eip8037

# Gas Fee Schedule
# Yellow Paper Appendix G - https://ethereum.github.io/yellowpaper/paper.pdf
Expand Down Expand Up @@ -78,7 +78,6 @@ type
GasParamsSs* = object
currentValue*: UInt256
originalValue*: UInt256
stateGasStorageSet*: GasInt

GasParamsCr* = object
currentMemSize*: GasNatural
Expand All @@ -101,6 +100,7 @@ type
gasCost*: GasInt
gasRefund*: int64
stateGas*: GasInt
creditStateGas*: GasInt

CallGasResult = tuple[gasCost, childGasLimit: GasInt]

Expand Down Expand Up @@ -301,7 +301,7 @@ template gasCosts(fork: EVMFork, prefix, ResultGasCostsName: untyped) =
res.gasCost = CleanGas # write existing slot (2.1.2)

if params.originalValue.isZero: # create slot (2.1.1)
res.stateGas = params.stateGasStorageSet
res.stateGas = STATE_GAS_STORAGE_SET
return res

if value.isZero: # delete slot (2.1.2b)
Expand Down Expand Up @@ -329,7 +329,9 @@ template gasCosts(fork: EVMFork, prefix, ResultGasCostsName: untyped) =
if params.originalValue == value:
if params.originalValue.isZero: # reset to original inexistent slot (2.2.2.1)
when fork >= FkAmsterdam:
res.gasRefund += params.stateGasStorageSet.int64 + CleanRefund
# https://github.com/ethereum/execution-specs/pull/2698/changes
res.creditStateGas = STATE_GAS_STORAGE_SET
res.gasRefund += CleanRefund
else:
res.gasRefund += InitRefund
else: # reset to original existing slot (2.2.2.2)
Expand Down
11 changes: 11 additions & 0 deletions execution_chain/evm/interpreter/gas_meter.nim
Original file line number Diff line number Diff line change
Expand Up @@ -90,3 +90,14 @@ func checkGas*(gasMeter: GasMeter, cost, amount: GasInt): EvmResultVoid =
if amount > gasMeter.stateGasLeft + gasMeter.gasRemaining - cost:
return err(gasErr(OutOfGas))
ok()

func returnAllStateGas*(gasMeter: var GasMeter) =
gasMeter.stateGasLeft += gasMeter.stateGasUsed
gasMeter.stateGasUsed = 0

# https://github.com/ethereum/execution-specs/pull/2733/changes
func creditStateGasRefund*(gasMeter: var GasMeter; amount: GasInt) =
let applied = min(amount, gasMeter.stateGasUsed)
gasMeter.stateGasLeft += applied
gasMeter.stateGasUsed -= applied
gasMeter.stateGasRefundPending += amount - applied
40 changes: 25 additions & 15 deletions execution_chain/evm/interpreter/op_handlers/oph_call.nim
Original file line number Diff line number Diff line change
Expand Up @@ -15,14 +15,17 @@
{.push raises: [].}

import
../../../db/ledger,
../../../constants,
../../evm_errors,
../../../common/evmforks,
../../../core/[eip7702, eip8037],
../../computation,
../../precompiles,
../../evm_errors,
../../memory,
../../stack,
../../types,
../../state,
../gas_costs,
../gas_meter,
../op_codes,
Expand All @@ -32,9 +35,7 @@ import
chronicles,
eth/common/addresses,
stew/assign2,
stint,
../../state,
../../../db/ledger
stint

# ------------------------------------------------------------------------------
# Private
Expand Down Expand Up @@ -184,13 +185,19 @@ proc staticCallParams(c: Computation, res: var LocalParams): EvmResult[void] =
res.updateStackAndParams(c)
ok()

proc getCallCode(c: Computation): CodeBytesRef =
# Avoid accessing ledger if it's a precompile address
if getPrecompile(c.vmState.fork, c.msg.codeAddress).isSome:
return CodeBytesRef(nil)
c.vmState.readOnlyLedger.getCode(c.delegateTo)

proc execSubCall(c: Computation; childMsg: Message; memPos, memLen: int) =
## Call new VM -- helper for `Call`-like operations

# need to provide explicit <c> and <child> for capturing in chainTo proc()
# <memPos> and <memLen> are provided by value and need not be captured
var
code = c.vmState.readOnlyLedger.getCode(c.delegateTo)
code = c.getCallCode()
child = newComputation(
c.vmState, keepStack = false, childMsg, code)

Expand All @@ -202,16 +209,20 @@ proc execSubCall(c: Computation; childMsg: Message; memPos, memLen: int) =
c.gasMeter.appendRegularGasUsed(child.gasMeter.regularGasUsed)

if child.isSuccess:
c.gasMeter.returnStateGas(child.gasMeter.stateGasLeft)
c.gasMeter.appendStateGasUsed(child.gasMeter.stateGasUsed)
if c.fork >= FkAmsterdam:
c.gasMeter.returnStateGas(child.gasMeter.stateGasLeft)
c.gasMeter.appendStateGasUsed(child.gasMeter.stateGasUsed)
# https://github.com/ethereum/execution-specs/pull/2733/changes
c.gasMeter.creditStateGasRefund(child.gasMeter.stateGasRefundPending)
c.merge(child)
c.stack.lsTop(1)
else:
# On failure (revert or exceptional halt) state changes are rolled back,
# so no state was actually grown. All state gas, both reservoir and any
# that spilled into `gas_left`, is restored to the parent's reservoir and
# the child's `state_gas_used` is not accumulated.
c.gasMeter.returnStateGas(child.gasMeter.stateGasUsed + child.gasMeter.stateGasLeft)
if c.fork >= FkAmsterdam:
# On failure (revert or exceptional halt) state changes are rolled back,
# so no state was actually grown. All state gas, both reservoir and any
# that spilled into `gas_left`, is restored to the parent's reservoir and
# the child's `state_gas_used` is not accumulated.
c.gasMeter.returnStateGas(child.gasMeter.stateGasUsed + child.gasMeter.stateGasLeft)

let actualOutputSize = min(memLen, child.output.len)
if actualOutputSize > 0:
Expand Down Expand Up @@ -251,13 +262,12 @@ proc callOp(cpt: VmCpt): EvmResultVoid =
# into regular gas, it must reduce the gas available for childGasLimit.
if cpt.fork >= FkAmsterdam:
if isNewAccount() and params1.nonZeroVal:
let newAcccountStateGas = STATE_BYTES_PER_NEW_ACCOUNT * cpt.getCostPerStateByte
# eels reviewer think there is an issue with the design to charge regular gas multiple times.
# https://github.com/ethereum/execution-specs/pull/2526/changes#diff-28a1b575fd7c3d82832c0826cf58a881101643543d35c123c78ca65202152c23R456
# And it also make EVM tracer produce two traces of call or weird result.
# So we check it here before actually charging state gas and keep the tracer produce single trace of call.
? cpt.gasMeter.checkGas(gasCost1, newAcccountStateGas)
? cpt.gasMeter.chargeStateGas(newAcccountStateGas,
? cpt.gasMeter.checkGas(gasCost1, CREATE_ACCOUNT_STATE_GAS)
? cpt.gasMeter.chargeStateGas(CREATE_ACCOUNT_STATE_GAS,
reason = "CALL: State gas new account")

let
Expand Down
38 changes: 29 additions & 9 deletions execution_chain/evm/interpreter/op_handlers/oph_create.nim
Original file line number Diff line number Diff line change
Expand Up @@ -54,16 +54,24 @@ proc execSubCreate(c: Computation; childMsg: Message;
c.gasMeter.appendRegularGasUsed(child.gasMeter.regularGasUsed)

if child.isSuccess:
c.gasMeter.returnStateGas(child.gasMeter.stateGasLeft)
c.gasMeter.appendStateGasUsed(child.gasMeter.stateGasUsed)
if c.fork >= FkAmsterdam:
c.gasMeter.returnStateGas(child.gasMeter.stateGasLeft)
c.gasMeter.appendStateGasUsed(child.gasMeter.stateGasUsed)
# https://github.com/ethereum/execution-specs/pull/2733/changes
c.gasMeter.creditStateGasRefund(child.gasMeter.stateGasRefundPending)
c.merge(child)
c.stack.lsTop child.msg.contractAddress
else:
# On failure (revert or exceptional halt) state changes are rolled back,
# so no state was actually grown. All state gas, both reservoir and any
# that spilled into `gas_left`, is restored to the parent's reservoir and
# the child's `state_gas_used` is not accumulated.
c.gasMeter.returnStateGas(child.gasMeter.stateGasUsed + child.gasMeter.stateGasLeft)
if c.fork >= FkAmsterdam:
# On failure (revert or exceptional halt) state changes are rolled back,
# so no state was actually grown. All state gas, both reservoir and any
# that spilled into `gas_left`, is restored to the parent's reservoir and
# the child's `state_gas_used` is not accumulated.
c.gasMeter.returnStateGas(child.gasMeter.stateGasUsed + child.gasMeter.stateGasLeft)

# https://github.com/ethereum/execution-specs/pull/2733/changes
c.gasMeter.creditStateGasRefund(CREATE_ACCOUNT_STATE_GAS)

if not child.error.burnsGas: # Means return was `REVERT`.
# From create, only use `outputData` if child returned with `REVERT`.
c.returnData = move(child.output)
Expand Down Expand Up @@ -114,7 +122,7 @@ proc createOp(cpt: VmCpt): EvmResultVoid =

# Charge state gas after initcode size validation
# https://github.com/ethereum/execution-specs/commit/b9f0afa931a773cdb764310035d0ff383ebecf9e
? cpt.gasMeter.chargeStateGas(STATE_BYTES_PER_NEW_ACCOUNT * cpt.getCostPerStateByte,
? cpt.gasMeter.chargeStateGas(CREATE_ACCOUNT_STATE_GAS,
reason = "CREATE: State gas new account")
elif memLen > EIP3860_MAX_INITCODE_SIZE:
# EIP-3860
Expand All @@ -129,6 +137,9 @@ proc createOp(cpt: VmCpt): EvmResultVoid =
reason = "Stack too deep",
maxDepth = MaxCallDepth,
depth = cpt.msg.depth
# https://github.com/ethereum/execution-specs/pull/2733/changes
if cpt.fork >= FkAmsterdam:
cpt.gasMeter.creditStateGasRefund(CREATE_ACCOUNT_STATE_GAS)
return ok()

if endowment != 0:
Expand All @@ -138,6 +149,9 @@ proc createOp(cpt: VmCpt): EvmResultVoid =
reason = "Insufficient funds available to transfer",
required = endowment,
balance = senderBalance
# https://github.com/ethereum/execution-specs/pull/2733/changes
if cpt.fork >= FkAmsterdam:
cpt.gasMeter.creditStateGasRefund(CREATE_ACCOUNT_STATE_GAS)
return ok()

var createMsgGas = cpt.gasMeter.gasRemaining
Expand Down Expand Up @@ -208,7 +222,7 @@ proc create2Op(cpt: VmCpt): EvmResultVoid =

# Charge state gas after initcode size validation
# https://github.com/ethereum/execution-specs/commit/b9f0afa931a773cdb764310035d0ff383ebecf9e
? cpt.gasMeter.chargeStateGas(STATE_BYTES_PER_NEW_ACCOUNT * cpt.getCostPerStateByte,
? cpt.gasMeter.chargeStateGas(CREATE_ACCOUNT_STATE_GAS,
reason = "CREATE2: State gas new account")
elif memLen > EIP3860_MAX_INITCODE_SIZE:
# EIP-3860
Expand All @@ -223,6 +237,9 @@ proc create2Op(cpt: VmCpt): EvmResultVoid =
reason = "Stack too deep",
maxDepth = MaxCallDepth,
depth = cpt.msg.depth
# https://github.com/ethereum/execution-specs/pull/2733/changes
if cpt.fork >= FkAmsterdam:
cpt.gasMeter.creditStateGasRefund(CREATE_ACCOUNT_STATE_GAS)
return ok()

if endowment != 0:
Expand All @@ -232,6 +249,9 @@ proc create2Op(cpt: VmCpt): EvmResultVoid =
reason = "Insufficient funds available to transfer",
required = endowment,
balance = senderBalance
# https://github.com/ethereum/execution-specs/pull/2733/changes
if cpt.fork >= FkAmsterdam:
cpt.gasMeter.creditStateGasRefund(CREATE_ACCOUNT_STATE_GAS)
return ok()

var createMsgGas = cpt.gasMeter.gasRemaining
Expand Down
Loading
Loading