From c3ff252660f778f1dfdfd5d96d20196a65e27c63 Mon Sep 17 00:00:00 2001 From: jangko Date: Tue, 21 Apr 2026 16:00:14 +0700 Subject: [PATCH 01/17] EIP-7976: Increase Calldata Floor Cost --- execution_chain/transaction/call_types.nim | 30 ++++++++++++++-------- 1 file changed, 20 insertions(+), 10 deletions(-) diff --git a/execution_chain/transaction/call_types.nim b/execution_chain/transaction/call_types.nim index fed107bc4e..a38eeee678 100644 --- a/execution_chain/transaction/call_types.nim +++ b/execution_chain/transaction/call_types.nim @@ -75,7 +75,8 @@ func isError*(cr: CallResult): bool = cr.error.len > 0 const - TOTAL_COST_FLOOR_PER_TOKEN = 10 + TOTAL_COST_FLOOR_PER_TOKEN_EIP7623 = 10 + TOTAL_COST_FLOOR_PER_TOKEN_EIP7976 = 16 func intrinsicGas*(call: CallParams | Transaction, fork: EVMFork, gasLimit: GasInt): IntrinsicGas = # Compute the baseline gas cost for this transaction. This is the amount @@ -102,14 +103,22 @@ func intrinsicGas*(call: CallParams | Transaction, fork: EVMFork, gasLimit: GasI # Input data cost, reduced in EIP-2028 (Istanbul). let gasZero = gasFees[fork][GasTXDataZero] let gasNonZero = gasFees[fork][GasTXDataNonZero] - for b in call.input: - if b == 0: - regularGas += gasZero - tokens += 1 - else: - regularGas += gasNonZero - tokens += 4 - + if fork >= FkAmsterdam: + for b in call.input: + if b == 0: + regularGas += gasZero + tokens += 4 + else: + regularGas += gasNonZero + tokens += 4 + else: + for b in call.input: + if b == 0: + regularGas += gasZero + tokens += 1 + else: + regularGas += gasNonZero + tokens += 4 # EIP-2930 (Berlin) intrinsic gas for transaction access list. if fork >= FkBerlin: @@ -121,9 +130,10 @@ func intrinsicGas*(call: CallParams | Transaction, fork: EVMFork, gasLimit: GasI if fork >= FkAmsterdam: regularGas += REGULAR_PER_AUTH_BASE_COST * call.authorizationList.len stateGas += (STATE_BYTES_PER_NEW_ACCOUNT + STATE_BYTES_PER_AUTH_BASE) * costPerStateByte * GasInt(call.authorizationList.len) + floorDataGas += tokens * TOTAL_COST_FLOOR_PER_TOKEN_EIP7976 else: regularGas += call.authorizationList.len * PER_EMPTY_ACCOUNT_COST - floorDataGas += tokens * TOTAL_COST_FLOOR_PER_TOKEN + floorDataGas += tokens * TOTAL_COST_FLOOR_PER_TOKEN_EIP7623 IntrinsicGas( regular: regularGas.GasInt, From a65d88655b43caee642d6e09efe7927539d4c3f3 Mon Sep 17 00:00:00 2001 From: jangko Date: Tue, 21 Apr 2026 16:11:05 +0700 Subject: [PATCH 02/17] EIP-7981: Increase Access List Cost --- execution_chain/transaction/call_types.nim | 37 +++++++++++----------- 1 file changed, 19 insertions(+), 18 deletions(-) diff --git a/execution_chain/transaction/call_types.nim b/execution_chain/transaction/call_types.nim index a38eeee678..9a2d1b691c 100644 --- a/execution_chain/transaction/call_types.nim +++ b/execution_chain/transaction/call_types.nim @@ -90,6 +90,7 @@ func intrinsicGas*(call: CallParams | Transaction, fork: EVMFork, gasLimit: GasI stateGas = 0.GasInt floorDataGas = regularGas tokens = 0 + accessListBytes = 0 # EIP-2 (Homestead) extra intrinsic gas for contract creations. if call.isCreate: @@ -101,34 +102,34 @@ func intrinsicGas*(call: CallParams | Transaction, fork: EVMFork, gasLimit: GasI regularGas += (gasFees[fork][GasInitcodeWord] * call.input.len.wordCount) # Input data cost, reduced in EIP-2028 (Istanbul). - let gasZero = gasFees[fork][GasTXDataZero] - let gasNonZero = gasFees[fork][GasTXDataNonZero] - if fork >= FkAmsterdam: - for b in call.input: - if b == 0: - regularGas += gasZero - tokens += 4 - else: - regularGas += gasNonZero - tokens += 4 - else: - for b in call.input: - if b == 0: - regularGas += gasZero - tokens += 1 - else: - regularGas += gasNonZero - tokens += 4 + let + gasZero = gasFees[fork][GasTXDataZero] + gasNonZero = gasFees[fork][GasTXDataNonZero] + byteZeroToken = if fork >= FkAmsterdam: 4 else: 1 + + for b in call.input: + if b == 0: + regularGas += gasZero + tokens += byteZeroToken + else: + regularGas += gasNonZero + tokens += 4 # EIP-2930 (Berlin) intrinsic gas for transaction access list. if fork >= FkBerlin: for account in call.accessList: regularGas += ACCESS_LIST_ADDRESS_COST regularGas += account.storageKeys.len * ACCESS_LIST_STORAGE_KEY_COST + # Total byte count of addresses(20 bytes each) and storage keys (32 bytes each) in the access list. + accessListBytes += 20 + account.storageKeys.len * 32 if fork >= FkPrague: if fork >= FkAmsterdam: regularGas += REGULAR_PER_AUTH_BASE_COST * call.authorizationList.len + # EIP-7981: Increase Access List Cost + let floorTokensInAccessList = accessListBytes * 4 + tokens += floorTokensInAccessList + regularGas += TOTAL_COST_FLOOR_PER_TOKEN_EIP7976 * floorTokensInAccessList stateGas += (STATE_BYTES_PER_NEW_ACCOUNT + STATE_BYTES_PER_AUTH_BASE) * costPerStateByte * GasInt(call.authorizationList.len) floorDataGas += tokens * TOTAL_COST_FLOOR_PER_TOKEN_EIP7976 else: From 43c433ce2544869edc87fd959518090fc190d444 Mon Sep 17 00:00:00 2001 From: jangko Date: Wed, 22 Apr 2026 09:52:11 +0700 Subject: [PATCH 03/17] zero execution state gas on top-level failure --- execution_chain/evm/interpreter/gas_meter.nim | 4 ++++ execution_chain/evm/interpreter_dispatch.nim | 17 +++++++---------- 2 files changed, 11 insertions(+), 10 deletions(-) diff --git a/execution_chain/evm/interpreter/gas_meter.nim b/execution_chain/evm/interpreter/gas_meter.nim index daef1f5251..315cbd20a2 100644 --- a/execution_chain/evm/interpreter/gas_meter.nim +++ b/execution_chain/evm/interpreter/gas_meter.nim @@ -90,3 +90,7 @@ 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 diff --git a/execution_chain/evm/interpreter_dispatch.nim b/execution_chain/evm/interpreter_dispatch.nim index d55257be01..066e726b58 100644 --- a/execution_chain/evm/interpreter_dispatch.nim +++ b/execution_chain/evm/interpreter_dispatch.nim @@ -93,11 +93,6 @@ proc afterExecCall(c: Computation) = # Special case to account for geth+parity bug c.vmState.ledger.ripemdSpecial() - if c.isSuccess: - c.commit() - else: - c.rollback() - proc beforeExecCreate(c: Computation): bool = c.vmState.mutateLedger: let nonce = ledger.getNonce(c.msg.sender) @@ -168,11 +163,6 @@ proc afterExecCreate(c: Computation) = # right cases, particularly important with EVMC where it must be cleared. c.output.reset() - if c.isSuccess: - c.commit() - else: - c.rollback() - const MsgKindToOp: array[CallKind, Op] = [Call, DelegateCall, CallCode, Create, Create2] @@ -205,6 +195,13 @@ proc afterExec(c: Computation) = else: c.afterExecCreate() + if c.isSuccess: + c.commit() + else: + # https://github.com/ethereum/execution-specs/pull/2689/changes + c.gasMeter.returnAllStateGas() + c.rollback() + if c.msg.depth > 0: let gasUsed = c.msg.gas - c.gasMeter.gasRemaining c.vmState.captureExit(c, c.output, gasUsed, c.errorOpt) From 9a0a58d20255c7526f36337845cf5b5aa585a04c Mon Sep 17 00:00:00 2001 From: jangko Date: Wed, 22 Apr 2026 10:21:18 +0700 Subject: [PATCH 04/17] CREATE failure refunds state gas to reservoir --- execution_chain/evm/interpreter/gas_meter.nim | 4 ++++ .../interpreter/op_handlers/oph_create.nim | 24 +++++++++++++++++-- execution_chain/evm/interpreter_dispatch.nim | 5 ++++ 3 files changed, 31 insertions(+), 2 deletions(-) diff --git a/execution_chain/evm/interpreter/gas_meter.nim b/execution_chain/evm/interpreter/gas_meter.nim index 315cbd20a2..b01397ad05 100644 --- a/execution_chain/evm/interpreter/gas_meter.nim +++ b/execution_chain/evm/interpreter/gas_meter.nim @@ -94,3 +94,7 @@ func checkGas*(gasMeter: GasMeter, cost, amount: GasInt): EvmResultVoid = func returnAllStateGas*(gasMeter: var GasMeter) = gasMeter.stateGasLeft += gasMeter.stateGasUsed gasMeter.stateGasUsed = 0 + +func refundStateGas*(gasMeter: var GasMeter; amount: GasInt) = + gasMeter.stateGasLeft += amount + gasMeter.stateGasUsed -= amount diff --git a/execution_chain/evm/interpreter/op_handlers/oph_create.nim b/execution_chain/evm/interpreter/op_handlers/oph_create.nim index d09b99f0cc..04ff726527 100644 --- a/execution_chain/evm/interpreter/op_handlers/oph_create.nim +++ b/execution_chain/evm/interpreter/op_handlers/oph_create.nim @@ -64,6 +64,12 @@ proc execSubCreate(c: Computation; childMsg: Message; # 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/2704/changes + if c.fork >= FkAmsterdam: + let createAccountStateGas = STATE_BYTES_PER_NEW_ACCOUNT * c.getCostPerStateByte + c.gasMeter.refundStateGas(createAccountStateGas) + if not child.error.burnsGas: # Means return was `REVERT`. # From create, only use `outputData` if child returned with `REVERT`. c.returnData = move(child.output) @@ -98,6 +104,7 @@ proc createOp(cpt: VmCpt): EvmResultVoid = memOffset: memPos, memLength: memLen) gasCost = cpt.gasCosts[Create].cr_handler(false, gasParams) + createAccountStateGas = STATE_BYTES_PER_NEW_ACCOUNT * cpt.getCostPerStateByte ? cpt.opcodeGasCost(Create, gasCost, reason = "CREATE: GasCreate + memLen * memory expansion") @@ -114,7 +121,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(createAccountStateGas, reason = "CREATE: State gas new account") elif memLen > EIP3860_MAX_INITCODE_SIZE: # EIP-3860 @@ -129,6 +136,9 @@ proc createOp(cpt: VmCpt): EvmResultVoid = reason = "Stack too deep", maxDepth = MaxCallDepth, depth = cpt.msg.depth + # https://github.com/ethereum/execution-specs/pull/2704/changes + if cpt.fork >= FkAmsterdam: + cpt.gasMeter.refundStateGas(createAccountStateGas) return ok() if endowment != 0: @@ -138,6 +148,9 @@ proc createOp(cpt: VmCpt): EvmResultVoid = reason = "Insufficient funds available to transfer", required = endowment, balance = senderBalance + # https://github.com/ethereum/execution-specs/pull/2704/changes + if cpt.fork >= FkAmsterdam: + cpt.gasMeter.refundStateGas(createAccountStateGas) return ok() var createMsgGas = cpt.gasMeter.gasRemaining @@ -190,6 +203,7 @@ proc create2Op(cpt: VmCpt): EvmResultVoid = currentMemSize: cpt.memory.len, memOffset: memPos, memLength: memLen) + createAccountStateGas = STATE_BYTES_PER_NEW_ACCOUNT * cpt.getCostPerStateByte var gasCost = cpt.gasCosts[Create].cr_handler(false, gasParams) gasCost = gasCost + cpt.gasCosts[Create2].m_handler(0, 0, memLen) @@ -209,7 +223,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(createAccountStateGas, reason = "CREATE2: State gas new account") elif memLen > EIP3860_MAX_INITCODE_SIZE: # EIP-3860 @@ -224,6 +238,9 @@ proc create2Op(cpt: VmCpt): EvmResultVoid = reason = "Stack too deep", maxDepth = MaxCallDepth, depth = cpt.msg.depth + # https://github.com/ethereum/execution-specs/pull/2704/changes + if cpt.fork >= FkAmsterdam: + cpt.gasMeter.refundStateGas(createAccountStateGas) return ok() if endowment != 0: @@ -233,6 +250,9 @@ proc create2Op(cpt: VmCpt): EvmResultVoid = reason = "Insufficient funds available to transfer", required = endowment, balance = senderBalance + # https://github.com/ethereum/execution-specs/pull/2704/changes + if cpt.fork >= FkAmsterdam: + cpt.gasMeter.refundStateGas(createAccountStateGas) return ok() var createMsgGas = cpt.gasMeter.gasRemaining diff --git a/execution_chain/evm/interpreter_dispatch.nim b/execution_chain/evm/interpreter_dispatch.nim index 066e726b58..9680c22f83 100644 --- a/execution_chain/evm/interpreter_dispatch.nim +++ b/execution_chain/evm/interpreter_dispatch.nim @@ -14,6 +14,7 @@ import std/[macros, strformat], chronicles, stew/byteutils, + ../core/eip8037, ../constants, ../db/ledger, ./interpreter/op_dispatcher, @@ -123,6 +124,10 @@ proc beforeExecCreate(c: Computation): bool = # regularGasUsed. if c.msg.depth == 0: c.gasMeter.gasRemaining = 0 + elif c.fork >= FkAmsterdam: + # https://github.com/ethereum/execution-specs/pull/2704/changes + let createAccountStateGas = STATE_BYTES_PER_NEW_ACCOUNT * c.getCostPerStateByte + c.gasMeter.refundStateGas(createAccountStateGas) let blurb = c.msg.contractAddress.toHex c.setError("Address collision when creating contract address=" & blurb, true) return true From bcec2e3110a22d9602ddffe1ddd49fde51d4079b Mon Sep 17 00:00:00 2001 From: jangko Date: Wed, 22 Apr 2026 10:31:04 +0700 Subject: [PATCH 05/17] immutable intrinsic state gas for EIP-7702 --- execution_chain/transaction/call_common.nim | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/execution_chain/transaction/call_common.nim b/execution_chain/transaction/call_common.nim index ca43dee8fa..5a75837e1b 100644 --- a/execution_chain/transaction/call_common.nim +++ b/execution_chain/transaction/call_common.nim @@ -170,7 +170,7 @@ proc setupHost(call: CallParams, keepStack: bool): TransactionHost = if isAmsterdamOrLater: gasLeft = min(regularGasBudget, executionGas) - intrinsicStateGas = intrinsic.state - gasRefund.GasInt + intrinsicStateGas = intrinsic.state stateGas = executionGas - gasLeft + gasRefund.GasInt let From 8f214114e8a1d87b8ca5849194e9f50891e01423 Mon Sep 17 00:00:00 2001 From: jangko Date: Wed, 22 Apr 2026 11:21:53 +0700 Subject: [PATCH 06/17] per-dimension block gas limit check at tx inclusion --- .../core/executor/process_transaction.nim | 33 +++++++++++++++---- execution_chain/core/tx_pool/tx_desc.nim | 6 +++- execution_chain/core/validate.nim | 6 +--- execution_chain/transaction/call_common.nim | 3 +- execution_chain/transaction/call_evm.nim | 13 +++++--- execution_chain/transaction/call_types.nim | 1 + tests/test_transaction_json.nim | 5 ++- tools/txparse/txparse.nim | 5 ++- 8 files changed, 51 insertions(+), 21 deletions(-) diff --git a/execution_chain/core/executor/process_transaction.nim b/execution_chain/core/executor/process_transaction.nim index 6ce2b22db1..a1b2e6c0d9 100644 --- a/execution_chain/core/executor/process_transaction.nim +++ b/execution_chain/core/executor/process_transaction.nim @@ -17,6 +17,7 @@ import ../../db/ledger, ../../transaction/call_evm, ../../transaction/call_common, + ../../transaction/call_types, ../../transaction, ../../evm/state, ../../evm/types, @@ -111,13 +112,30 @@ proc processTransaction*( priorityFee = min(tx.maxPriorityFeePerGasNorm(), tx.maxFeePerGasNorm() - baseFee) excessBlobGas = vmState.blockCtx.excessBlobGas regularGasAvailable = vmState.blockCtx.gasLimit - vmState.blockRegularGasUsed + stateGasAvailable = vmState.blockCtx.gasLimit - vmState.blockStateGasUsed + intrinsic = tx.intrinsicGas(fork, vmState.blockCtx.gasLimit) + com = vmState.com - # 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 < FkAmsterdam: 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 @@ -127,6 +145,8 @@ proc processTransaction*( return err("blobGasUsed " & $blobGasUsed & " exceeds maximum allowance " & $maxBlobGasPerBlock) + ? validateTxBasic(com, tx, intrinsic, fork) + # Actually, the EIP-1559 reference does not mention an early exit. # # Even though database was not changed yet but, a `persist()` directive @@ -134,7 +154,6 @@ proc processTransaction*( # of the `processTransaction()` function. So there is no `return err()` # statement, here. let - com = vmState.com txRes = roDB.validateTransaction(tx, sender, vmState.blockCtx.gasLimit, baseFee256, excessBlobGas, com, fork) res = if txRes.isOk: # Execute the transaction. @@ -144,7 +163,7 @@ proc processTransaction*( vmState.balTracker.beginCallFrame() let savePoint = vmState.ledger.beginSavePoint() - var callResult = tx.txCallEvm(sender, vmState, baseFee) + var callResult = tx.txCallEvm(sender, vmState, baseFee, intrinsic) vmState.captureTxEnd(tx.gasLimit - callResult.gasUsed) let tmp = commitOrRollbackDependingOnGasUsed( diff --git a/execution_chain/core/tx_pool/tx_desc.nim b/execution_chain/core/tx_pool/tx_desc.nim index da0d899204..7b9e765ac9 100644 --- a/execution_chain/core/tx_pool/tx_desc.nim +++ b/execution_chain/core/tx_pool/tx_desc.nim @@ -23,6 +23,7 @@ import ../../db/ledger, ../../constants, ../../transaction, + ../../transaction/call_types, ../../core/eip8037, ../chain/forked_chain, ../pow/header, @@ -365,10 +366,13 @@ proc addTx*(xp: TxPoolRef, ptx: PooledTransaction): Result[void, TxError] = debug "Transaction already known", txHash = id return err(txErrorAlreadyKnown) + let + intrinsic = ptx.tx.intrinsicGas(xp.nextFork, xp.gasLimit) + validateTxBasic( xp.com, ptx.tx, - xp.gasLimit, + intrinsic, xp.nextFork, validateFork = true).isOkOr: debug "Invalid transaction: Basic validation failed", diff --git a/execution_chain/core/validate.nim b/execution_chain/core/validate.nim index d34b34e8b2..5d4c15f537 100644 --- a/execution_chain/core/validate.nim +++ b/execution_chain/core/validate.nim @@ -239,7 +239,7 @@ func gasCost*(tx: Transaction): UInt256 = func validateTxBasic*( com: CommonRef, tx: Transaction; ## tx to validate - gasLimit: GasInt; + intrinsic:IntrinsicGas; fork: EVMFork, validateFork: bool = true): Result[void, string] = @@ -269,7 +269,6 @@ func validateTxBasic*( if fork >= FkAmsterdam: let - intrinsic = tx.intrinsicGas(fork, gasLimit) intrinsicGas = intrinsic.regular + intrinsic.state minGasLimit = max(intrinsicGas, intrinsic.floorDataGas) minRegularGasLimit = max(intrinsic.regular, intrinsic.floorDataGas) @@ -285,7 +284,6 @@ func validateTxBasic*( return err("tx.gasLimit " & $tx.gasLimit & " exceeds maximum " & $TX_GAS_LIMIT) let - intrinsic = tx.intrinsicGas(fork, gasLimit) minGasLimit = max(intrinsic.regular, intrinsic.floorDataGas) if tx.gasLimit < minGasLimit: @@ -355,8 +353,6 @@ proc validateTransaction*( com: CommonRef, fork: EVMFork): Result[void, string] = - ? validateTxBasic(com, tx, gasLimit, fork) - let balance = ledger.getBalance(sender) nonce = ledger.getNonce(sender) diff --git a/execution_chain/transaction/call_common.nim b/execution_chain/transaction/call_common.nim index 5a75837e1b..d9839df87e 100644 --- a/execution_chain/transaction/call_common.nim +++ b/execution_chain/transaction/call_common.nim @@ -150,8 +150,7 @@ proc setupHost(call: CallParams, keepStack: bool): TransactionHost = let isAmsterdamOrLater = fork >= FkAmsterdam - intrinsic = if call.sysCall: IntrinsicGas() - else: intrinsicGas(call, fork, vmState.blockCtx.gasLimit) + intrinsic = call.intrinsic gasRefund = if call.sysCall: 0 else: preExecComputation(vmState, call) intrinsicGas = intrinsic.regular + intrinsic.state diff --git a/execution_chain/transaction/call_evm.nim b/execution_chain/transaction/call_evm.nim index 7f4827fd4b..95ca9ae063 100644 --- a/execution_chain/transaction/call_evm.nim +++ b/execution_chain/transaction/call_evm.nim @@ -20,7 +20,9 @@ import export call_common -proc callParamsForTx(tx: Transaction, sender: Address, vmState: BaseVMState, baseFee: GasInt): CallParams = +proc callParamsForTx(tx: Transaction, sender: Address, + vmState: BaseVMState, baseFee: GasInt, + intrinsic: IntrinsicGas): CallParams = # Is there a nice idiom for this kind of thing? Should I # just be writing this as a bunch of assignment statements? result = CallParams( @@ -31,7 +33,8 @@ proc callParamsForTx(tx: Transaction, sender: Address, vmState: BaseVMState, bas to: tx.destination, isCreate: tx.contractCreation, value: tx.value, - input: tx.payload + input: tx.payload, + intrinsic: intrinsic ) if tx.txType > TxLegacy: assign(result.accessList, tx.accessList) @@ -65,9 +68,11 @@ proc callParamsForTest(tx: Transaction, sender: Address, vmState: BaseVMState): proc txCallEvm*(tx: Transaction, sender: Address, - vmState: BaseVMState, baseFee: GasInt): LogResult = + vmState: BaseVMState, + baseFee: GasInt, + intrinsic: IntrinsicGas): LogResult = let - call = callParamsForTx(tx, sender, vmState, baseFee) + call = callParamsForTx(tx, sender, vmState, baseFee, intrinsic) runComputation(call, LogResult) proc testCallEvm*(tx: Transaction, diff --git a/execution_chain/transaction/call_types.nim b/execution_chain/transaction/call_types.nim index 9a2d1b691c..d4c19644d2 100644 --- a/execution_chain/transaction/call_types.nim +++ b/execution_chain/transaction/call_types.nim @@ -37,6 +37,7 @@ type versionedHashes*: seq[VersionedHash] # EIP-4844 (Cancun) blob versioned hashes authorizationList*: seq[Authorization] # EIP-7702 (Prague) authorization list sysCall*: bool # System call or ordinary call + intrinsic*: IntrinsicGas # Standard call result. CallResult* = object of RootObj diff --git a/tests/test_transaction_json.nim b/tests/test_transaction_json.nim index 7c30951e90..9b1a7e768b 100644 --- a/tests/test_transaction_json.nim +++ b/tests/test_transaction_json.nim @@ -19,6 +19,7 @@ import ../execution_chain/db/core_db, ../execution_chain/common/common, ../execution_chain/transaction, + ../execution_chain/transaction/call_types, ../execution_chain/core/validate, ../execution_chain/utils/utils @@ -41,8 +42,10 @@ proc testTxByFork(tx: Transaction, forkData: JsonNode, forkName: string, testSta config = getChainConfig(forkName) memDB = newCoreDbRef DefaultDbMemory com = CommonRef.new(memDB, config) + fork = nameToFork[forkName] + intrinsic = tx.intrinsicGas(fork, 10_000_000) - validateTxBasic(com, tx, 10_000_000, nameToFork[forkName]).isOkOr: + validateTxBasic(com, tx, intrinsic, fork).isOkOr: return if forkData.len > 0 and "sender" in forkData: diff --git a/tools/txparse/txparse.nim b/tools/txparse/txparse.nim index 042fd8dcf9..f0a15d66dc 100644 --- a/tools/txparse/txparse.nim +++ b/tools/txparse/txparse.nim @@ -15,6 +15,7 @@ import ../common/helpers, ../../execution_chain/db/core_db/memory_only, ../../execution_chain/transaction, + ../../execution_chain/transaction/call_types, ../../execution_chain/core/validate, ../../execution_chain/common/evmforks, ../../execution_chain/common/common @@ -25,8 +26,10 @@ proc parseTx(com: CommonRef, hexLine: string) = bytes = hexToSeqByte(hexLine) tx = decodeTx(bytes) address = tx.recoverSender().expect("valid signature") + fork = FkPrague + intrinsic = tx.intrinsicGas(fork, 10_000_000) - validateTxBasic(com, tx, 10_000_000, FkPrague).isOkOr: + validateTxBasic(com, tx, intrinsic, fork).isOkOr: echo "err: ", error # everything ok From 9098936f4d1303b883c6426f8b790430bf06bac2 Mon Sep 17 00:00:00 2001 From: jangko Date: Wed, 22 Apr 2026 11:55:33 +0700 Subject: [PATCH 07/17] EIP-8037 - 0 to x to 0 SSTORE refunds to state gas --- execution_chain/evm/interpreter/gas_costs.nim | 5 +++-- execution_chain/evm/interpreter/gas_meter.nim | 4 ++++ execution_chain/evm/interpreter/op_handlers/oph_call.nim | 3 ++- execution_chain/evm/interpreter/op_handlers/oph_create.nim | 3 ++- execution_chain/evm/interpreter/op_handlers/oph_memory.nim | 3 ++- execution_chain/evm/types.nim | 1 + 6 files changed, 14 insertions(+), 5 deletions(-) diff --git a/execution_chain/evm/interpreter/gas_costs.nim b/execution_chain/evm/interpreter/gas_costs.nim index 85f2bb86d9..e07d737951 100644 --- a/execution_chain/evm/interpreter/gas_costs.nim +++ b/execution_chain/evm/interpreter/gas_costs.nim @@ -71,7 +71,7 @@ type nonZeroVal*: bool gasCost1*: GasInt isNewAccount*: proc(): bool {.gcsafe, raises: [].} - gasLeft*: GasInt + gasLeft*: GasInt gasCallDelegate*: GasProc contractGas*: UInt256 @@ -329,7 +329,8 @@ 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.gasRefund += CleanRefund else: res.gasRefund += InitRefund else: # reset to original existing slot (2.2.2.2) diff --git a/execution_chain/evm/interpreter/gas_meter.nim b/execution_chain/evm/interpreter/gas_meter.nim index b01397ad05..82b5de956b 100644 --- a/execution_chain/evm/interpreter/gas_meter.nim +++ b/execution_chain/evm/interpreter/gas_meter.nim @@ -98,3 +98,7 @@ func returnAllStateGas*(gasMeter: var GasMeter) = func refundStateGas*(gasMeter: var GasMeter; amount: GasInt) = gasMeter.stateGasLeft += amount gasMeter.stateGasUsed -= amount + gasMeter.stateGasRefund += amount + +func appendStateGasRefund*(gasMeter: var GasMeter; amount: GasInt) = + gasMeter.stateGasRefund += amount diff --git a/execution_chain/evm/interpreter/op_handlers/oph_call.nim b/execution_chain/evm/interpreter/op_handlers/oph_call.nim index 417e1edc0a..4416c04a24 100644 --- a/execution_chain/evm/interpreter/op_handlers/oph_call.nim +++ b/execution_chain/evm/interpreter/op_handlers/oph_call.nim @@ -197,6 +197,7 @@ proc execSubCall(c: Computation; childMsg: Message; memPos, memLen: int) = if child.isSuccess: c.gasMeter.returnStateGas(child.gasMeter.stateGasLeft) c.gasMeter.appendStateGasUsed(child.gasMeter.stateGasUsed) + c.gasMeter.appendStateGasRefund(child.gasMeter.stateGasRefund) c.merge(child) c.stack.lsTop(1) else: @@ -204,7 +205,7 @@ proc execSubCall(c: Computation; childMsg: Message; memPos, memLen: int) = # 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) + c.gasMeter.returnStateGas(child.gasMeter.stateGasUsed + child.gasMeter.stateGasLeft - child.gasMeter.stateGasRefund) let actualOutputSize = min(memLen, child.output.len) if actualOutputSize > 0: diff --git a/execution_chain/evm/interpreter/op_handlers/oph_create.nim b/execution_chain/evm/interpreter/op_handlers/oph_create.nim index 04ff726527..29c9636417 100644 --- a/execution_chain/evm/interpreter/op_handlers/oph_create.nim +++ b/execution_chain/evm/interpreter/op_handlers/oph_create.nim @@ -56,6 +56,7 @@ proc execSubCreate(c: Computation; childMsg: Message; if child.isSuccess: c.gasMeter.returnStateGas(child.gasMeter.stateGasLeft) c.gasMeter.appendStateGasUsed(child.gasMeter.stateGasUsed) + c.gasMeter.appendStateGasRefund(child.gasMeter.stateGasRefund) c.merge(child) c.stack.lsTop child.msg.contractAddress else: @@ -63,7 +64,7 @@ proc execSubCreate(c: Computation; childMsg: Message; # 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) + c.gasMeter.returnStateGas(child.gasMeter.stateGasUsed + child.gasMeter.stateGasLeft - child.gasMeter.stateGasRefund) # https://github.com/ethereum/execution-specs/pull/2704/changes if c.fork >= FkAmsterdam: diff --git a/execution_chain/evm/interpreter/op_handlers/oph_memory.nim b/execution_chain/evm/interpreter/op_handlers/oph_memory.nim index c28f3d52ad..e18f015742 100644 --- a/execution_chain/evm/interpreter/op_handlers/oph_memory.nim +++ b/execution_chain/evm/interpreter/op_handlers/oph_memory.nim @@ -70,7 +70,8 @@ proc sstoreNetGasMeteringImpl(c: Computation; slot, newValue: UInt256, coldAcces ? c.opcodeGasCost(Sstore, res.gasCost + coldAccess, "SSTORE") if stateGas and res.stateGas > 0: - ? c.gasMeter.chargeStateGas(res.stateGas, reason = "SSTORE state gas") + # https://github.com/ethereum/execution-specs/pull/2698/changes + c.gasMeter.refundStateGas(res.stateGas) c.gasMeter.refundGas(res.gasRefund) diff --git a/execution_chain/evm/types.nim b/execution_chain/evm/types.nim index c03fb9c099..2919f9a8ab 100644 --- a/execution_chain/evm/types.nim +++ b/execution_chain/evm/types.nim @@ -102,6 +102,7 @@ type stateGasLeft*: GasInt stateGasUsed*: GasInt regularGasUsed*: GasInt + stateGasRefund*: GasInt CallKind* {.pure.} = enum Call # Request CALL. From c68dae95e5c3498c9c00c496e687c56fe114aeaf Mon Sep 17 00:00:00 2001 From: jangko Date: Wed, 22 Apr 2026 12:10:33 +0700 Subject: [PATCH 08/17] EIP-8037 nested child frame refunds --- execution_chain/evm/interpreter/gas_costs.nim | 2 ++ execution_chain/evm/interpreter/gas_meter.nim | 11 ++++++---- .../evm/interpreter/op_handlers/oph_call.nim | 2 ++ .../interpreter/op_handlers/oph_create.nim | 22 ++++++++++--------- .../interpreter/op_handlers/oph_memory.nim | 9 +++++--- execution_chain/evm/interpreter_dispatch.nim | 8 +++---- execution_chain/evm/types.nim | 1 + execution_chain/transaction/call_common.nim | 7 ++++++ 8 files changed, 40 insertions(+), 22 deletions(-) diff --git a/execution_chain/evm/interpreter/gas_costs.nim b/execution_chain/evm/interpreter/gas_costs.nim index e07d737951..b1d6218420 100644 --- a/execution_chain/evm/interpreter/gas_costs.nim +++ b/execution_chain/evm/interpreter/gas_costs.nim @@ -101,6 +101,7 @@ type gasCost*: GasInt gasRefund*: int64 stateGas*: GasInt + creditStateGas*: GasInt CallGasResult = tuple[gasCost, childGasLimit: GasInt] @@ -330,6 +331,7 @@ template gasCosts(fork: EVMFork, prefix, ResultGasCostsName: untyped) = if params.originalValue.isZero: # reset to original inexistent slot (2.2.2.1) when fork >= FkAmsterdam: # https://github.com/ethereum/execution-specs/pull/2698/changes + res.creditStateGas = params.stateGasStorageSet res.gasRefund += CleanRefund else: res.gasRefund += InitRefund diff --git a/execution_chain/evm/interpreter/gas_meter.nim b/execution_chain/evm/interpreter/gas_meter.nim index 82b5de956b..62746c592e 100644 --- a/execution_chain/evm/interpreter/gas_meter.nim +++ b/execution_chain/evm/interpreter/gas_meter.nim @@ -95,10 +95,13 @@ func returnAllStateGas*(gasMeter: var GasMeter) = gasMeter.stateGasLeft += gasMeter.stateGasUsed gasMeter.stateGasUsed = 0 -func refundStateGas*(gasMeter: var GasMeter; amount: GasInt) = - gasMeter.stateGasLeft += amount - gasMeter.stateGasUsed -= amount - gasMeter.stateGasRefund += amount +# 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.stateGasRefund += applied + gasMeter.stateGasRefundPending += amount - applied func appendStateGasRefund*(gasMeter: var GasMeter; amount: GasInt) = gasMeter.stateGasRefund += amount diff --git a/execution_chain/evm/interpreter/op_handlers/oph_call.nim b/execution_chain/evm/interpreter/op_handlers/oph_call.nim index 4416c04a24..35c7982271 100644 --- a/execution_chain/evm/interpreter/op_handlers/oph_call.nim +++ b/execution_chain/evm/interpreter/op_handlers/oph_call.nim @@ -198,6 +198,8 @@ proc execSubCall(c: Computation; childMsg: Message; memPos, memLen: int) = c.gasMeter.returnStateGas(child.gasMeter.stateGasLeft) c.gasMeter.appendStateGasUsed(child.gasMeter.stateGasUsed) c.gasMeter.appendStateGasRefund(child.gasMeter.stateGasRefund) + # https://github.com/ethereum/execution-specs/pull/2733/changes + c.gasMeter.creditStateGasRefund(child.gasMeter.stateGasRefundPending) c.merge(child) c.stack.lsTop(1) else: diff --git a/execution_chain/evm/interpreter/op_handlers/oph_create.nim b/execution_chain/evm/interpreter/op_handlers/oph_create.nim index 29c9636417..810d7e80ff 100644 --- a/execution_chain/evm/interpreter/op_handlers/oph_create.nim +++ b/execution_chain/evm/interpreter/op_handlers/oph_create.nim @@ -57,6 +57,8 @@ proc execSubCreate(c: Computation; childMsg: Message; c.gasMeter.returnStateGas(child.gasMeter.stateGasLeft) c.gasMeter.appendStateGasUsed(child.gasMeter.stateGasUsed) c.gasMeter.appendStateGasRefund(child.gasMeter.stateGasRefund) + # 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: @@ -66,10 +68,10 @@ proc execSubCreate(c: Computation; childMsg: Message; # the child's `state_gas_used` is not accumulated. c.gasMeter.returnStateGas(child.gasMeter.stateGasUsed + child.gasMeter.stateGasLeft - child.gasMeter.stateGasRefund) - # https://github.com/ethereum/execution-specs/pull/2704/changes + # https://github.com/ethereum/execution-specs/pull/2733/changes if c.fork >= FkAmsterdam: let createAccountStateGas = STATE_BYTES_PER_NEW_ACCOUNT * c.getCostPerStateByte - c.gasMeter.refundStateGas(createAccountStateGas) + c.gasMeter.creditStateGasRefund(createAccountStateGas) if not child.error.burnsGas: # Means return was `REVERT`. # From create, only use `outputData` if child returned with `REVERT`. @@ -137,9 +139,9 @@ proc createOp(cpt: VmCpt): EvmResultVoid = reason = "Stack too deep", maxDepth = MaxCallDepth, depth = cpt.msg.depth - # https://github.com/ethereum/execution-specs/pull/2704/changes + # https://github.com/ethereum/execution-specs/pull/2733/changes if cpt.fork >= FkAmsterdam: - cpt.gasMeter.refundStateGas(createAccountStateGas) + cpt.gasMeter.creditStateGasRefund(createAccountStateGas) return ok() if endowment != 0: @@ -149,9 +151,9 @@ proc createOp(cpt: VmCpt): EvmResultVoid = reason = "Insufficient funds available to transfer", required = endowment, balance = senderBalance - # https://github.com/ethereum/execution-specs/pull/2704/changes + # https://github.com/ethereum/execution-specs/pull/2733/changes if cpt.fork >= FkAmsterdam: - cpt.gasMeter.refundStateGas(createAccountStateGas) + cpt.gasMeter.creditStateGasRefund(createAccountStateGas) return ok() var createMsgGas = cpt.gasMeter.gasRemaining @@ -239,9 +241,9 @@ proc create2Op(cpt: VmCpt): EvmResultVoid = reason = "Stack too deep", maxDepth = MaxCallDepth, depth = cpt.msg.depth - # https://github.com/ethereum/execution-specs/pull/2704/changes + # https://github.com/ethereum/execution-specs/pull/2733/changes if cpt.fork >= FkAmsterdam: - cpt.gasMeter.refundStateGas(createAccountStateGas) + cpt.gasMeter.creditStateGasRefund(createAccountStateGas) return ok() if endowment != 0: @@ -251,9 +253,9 @@ proc create2Op(cpt: VmCpt): EvmResultVoid = reason = "Insufficient funds available to transfer", required = endowment, balance = senderBalance - # https://github.com/ethereum/execution-specs/pull/2704/changes + # https://github.com/ethereum/execution-specs/pull/2733/changes if cpt.fork >= FkAmsterdam: - cpt.gasMeter.refundStateGas(createAccountStateGas) + cpt.gasMeter.creditStateGasRefund(createAccountStateGas) return ok() var createMsgGas = cpt.gasMeter.gasRemaining diff --git a/execution_chain/evm/interpreter/op_handlers/oph_memory.nim b/execution_chain/evm/interpreter/op_handlers/oph_memory.nim index e18f015742..cfac76d769 100644 --- a/execution_chain/evm/interpreter/op_handlers/oph_memory.nim +++ b/execution_chain/evm/interpreter/op_handlers/oph_memory.nim @@ -69,9 +69,12 @@ proc sstoreNetGasMeteringImpl(c: Computation; slot, newValue: UInt256, coldAcces # reservoir on frame failure. ? c.opcodeGasCost(Sstore, res.gasCost + coldAccess, "SSTORE") - if stateGas and res.stateGas > 0: - # https://github.com/ethereum/execution-specs/pull/2698/changes - c.gasMeter.refundStateGas(res.stateGas) + if stateGas: + # https://github.com/ethereum/execution-specs/pull/2733/changes + if res.creditStateGas > 0: + c.gasMeter.creditStateGasRefund(res.creditStateGas) + if res.stateGas > 0: + ? c.gasMeter.chargeStateGas(res.stateGas, reason = "SSTORE state gas") c.gasMeter.refundGas(res.gasRefund) diff --git a/execution_chain/evm/interpreter_dispatch.nim b/execution_chain/evm/interpreter_dispatch.nim index 9680c22f83..ae9346a66f 100644 --- a/execution_chain/evm/interpreter_dispatch.nim +++ b/execution_chain/evm/interpreter_dispatch.nim @@ -125,9 +125,9 @@ proc beforeExecCreate(c: Computation): bool = if c.msg.depth == 0: c.gasMeter.gasRemaining = 0 elif c.fork >= FkAmsterdam: - # https://github.com/ethereum/execution-specs/pull/2704/changes + # https://github.com/ethereum/execution-specs/pull/2733/changes let createAccountStateGas = STATE_BYTES_PER_NEW_ACCOUNT * c.getCostPerStateByte - c.gasMeter.refundStateGas(createAccountStateGas) + c.gasMeter.creditStateGasRefund(createAccountStateGas) let blurb = c.msg.contractAddress.toHex c.setError("Address collision when creating contract address=" & blurb, true) return true @@ -202,9 +202,7 @@ proc afterExec(c: Computation) = if c.isSuccess: c.commit() - else: - # https://github.com/ethereum/execution-specs/pull/2689/changes - c.gasMeter.returnAllStateGas() + else: c.rollback() if c.msg.depth > 0: diff --git a/execution_chain/evm/types.nim b/execution_chain/evm/types.nim index 2919f9a8ab..6e590048c9 100644 --- a/execution_chain/evm/types.nim +++ b/execution_chain/evm/types.nim @@ -103,6 +103,7 @@ type stateGasUsed*: GasInt regularGasUsed*: GasInt stateGasRefund*: GasInt + stateGasRefundPending*: GasInt CallKind* {.pure.} = enum Call # Request CALL. diff --git a/execution_chain/transaction/call_common.nim b/execution_chain/transaction/call_common.nim index d9839df87e..40c99914a9 100644 --- a/execution_chain/transaction/call_common.nim +++ b/execution_chain/transaction/call_common.nim @@ -252,6 +252,13 @@ proc calculateAndPossiblyRefundGas(host: TransactionHost, call: CallParams): Gas if c.shouldBurnGas: c.gasMeter.burnGas() + if c.fork >= FkAmsterdam: + if c.isSuccess: + discard + else: + # https://github.com/ethereum/execution-specs/pull/2689/changes + c.gasMeter.returnAllStateGas() + # Calculated gas used, taking into account refund rules. let txGasUsedBeforeRefund = call.gasLimit - c.gasMeter.gasRemaining - c.gasMeter.stateGasLeft From e013d5e98d71b69c63e650e84893474e5ca9774c Mon Sep 17 00:00:00 2001 From: jangko Date: Wed, 22 Apr 2026 17:04:08 +0700 Subject: [PATCH 09/17] EIP-8037 - SELFDESTRUCT same-tx refunds state gas at end of tx --- execution_chain/db/ledger.nim | 58 +++++++++++++------ execution_chain/evm/interpreter/gas_meter.nim | 5 ++ execution_chain/transaction/call_common.nim | 17 +++++- 3 files changed, 62 insertions(+), 18 deletions(-) diff --git a/execution_chain/db/ledger.nim b/execution_chain/db/ledger.nim index 3d7695b2d5..7a0ef95279 100644 --- a/execution_chain/db/ledger.nim +++ b/execution_chain/db/ledger.nim @@ -111,6 +111,10 @@ type selfDestruct: HashSet[Address] accessList: ac_access_list.AccessList + SelfDestructRefund* = object + createdSlots*: int + codeLen*: int + const resetFlags = { Dirty, @@ -328,6 +332,31 @@ 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() + +proc getCodeSize(ledger: LedgerRef, acc: AccountRef): int = + getCodeSizeImpl(ledger, acc) + +proc calcCreatedSlots(ledger: LedgerRef, acc: AccountRef): int = + for _, val in acc.overlayStorage: + inc(result, val.isZero.not.int) + # ------------------------------------------------------------------------------ # Public methods # ------------------------------------------------------------------------------ @@ -455,23 +484,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 resolveCode*(ledger: LedgerRef, address: Address): CodeBytesRef = let code = ledger.getCode(address) @@ -639,6 +652,17 @@ iterator nonZeroSelfDestructAccounts*(ledger: LedgerRef): (Address, UInt256) = continue yield (address, value) +iterator newlyCreatedSelfDestructRefund*(ledger: LedgerRef): SelfDestructRefund = + for address in ledger.savePoint.selfDestruct: + let acc = ledger.getAccount(address, false) + doAssert(acc.isNil.not) + if NewlyCreated notin acc.flags: + continue + yield SelfDestructRefund( + createdSlots: calcCreatedSlots(ledger, acc), + codeLen: getCodeSize(ledger, acc), + ) + proc ripemdSpecial*(ledger: LedgerRef) = ledger.ripemdSpecial = true diff --git a/execution_chain/evm/interpreter/gas_meter.nim b/execution_chain/evm/interpreter/gas_meter.nim index 62746c592e..6a45c0fda5 100644 --- a/execution_chain/evm/interpreter/gas_meter.nim +++ b/execution_chain/evm/interpreter/gas_meter.nim @@ -105,3 +105,8 @@ func creditStateGasRefund*(gasMeter: var GasMeter; amount: GasInt) = func appendStateGasRefund*(gasMeter: var GasMeter; amount: GasInt) = gasMeter.stateGasRefund += amount + +func selfDestructRefundStateGas*(gasMeter: var GasMeter; amount: GasInt) = + let applied = min(amount, gasMeter.stateGasUsed) + gasMeter.stateGasLeft += applied + gasMeter.stateGasUsed -= applied diff --git a/execution_chain/transaction/call_common.nim b/execution_chain/transaction/call_common.nim index 40c99914a9..c209252af9 100644 --- a/execution_chain/transaction/call_common.nim +++ b/execution_chain/transaction/call_common.nim @@ -238,6 +238,20 @@ proc prepareToRunComputation(host: TransactionHost, call: CallParams) = vmState.balTracker.trackSubBalanceChange(call.sender, blobFee) ledger.subBalance(call.sender, blobFee) +proc calcSelfDestructRefundStateGas(c: Computation) = + let + ledger = c.vmState.ledger + + var + refundSum = 0 + + for refund in newlyCreatedSelfDestructRefund(ledger): + refundSum += CREATE_ACCOUNT_STATE_GAS + refundSum += STATE_GAS_STORAGE_SET * refund.createdSlots + refundSum += COST_PER_STATE_BYTE * refund.codeLen + + c.gasMeter.selfDestructRefundStateGas(refundSum.GasInt) + proc calculateAndPossiblyRefundGas(host: TransactionHost, call: CallParams): GasUsed = let c = host.computation @@ -254,7 +268,8 @@ proc calculateAndPossiblyRefundGas(host: TransactionHost, call: CallParams): Gas if c.fork >= FkAmsterdam: if c.isSuccess: - discard + # https://github.com/ethereum/execution-specs/pull/2707/changes + c.calcSelfDestructRefundStateGas() else: # https://github.com/ethereum/execution-specs/pull/2689/changes c.gasMeter.returnAllStateGas() From 62abe6b099ec49c2387c0c75eb276dbe64b59120 Mon Sep 17 00:00:00 2001 From: jangko Date: Thu, 23 Apr 2026 20:04:23 +0700 Subject: [PATCH 10/17] Upgrade debug_bal.nim --- execution_chain/utils/debug_bal.nim | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/execution_chain/utils/debug_bal.nim b/execution_chain/utils/debug_bal.nim index f20cc35005..bc1947bee8 100644 --- a/execution_chain/utils/debug_bal.nim +++ b/execution_chain/utils/debug_bal.nim @@ -8,6 +8,8 @@ # at your option. This file may not be copied, modified, or distributed except # according to those terms. +{.push raises: [], gcsafe.} + import std/[json, strutils], stint, @@ -25,7 +27,7 @@ proc `@@`(x: Address): JsonNode = proc `@@`(x: Bytes): JsonNode = %("0x" & x.toHex) -proc `@@`(x: uint16 | uint64): JsonNode = +proc `@@`(x: uint16 | uint32 | uint64): JsonNode = %("0x" & x.toHex) proc `@@`(x: UInt256): JsonNode = From e0a5b9fcaadf4b74f3b1e8be2d8c8ae20dbd2a79 Mon Sep 17 00:00:00 2001 From: jangko Date: Thu, 23 Apr 2026 21:57:29 +0700 Subject: [PATCH 11/17] Update nvp access list test --- nimbus_verified_proxy/tests/data/access_list.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nimbus_verified_proxy/tests/data/access_list.json b/nimbus_verified_proxy/tests/data/access_list.json index 2498bf0778..214b27e76d 100644 --- a/nimbus_verified_proxy/tests/data/access_list.json +++ b/nimbus_verified_proxy/tests/data/access_list.json @@ -7,5 +7,5 @@ ] } ], - "gasUsed": "0x669a" + "gasUsed": "0x216" } From eaa663428f04862197bee7bec9c6fdc91f26bbb0 Mon Sep 17 00:00:00 2001 From: jangko Date: Fri, 24 Apr 2026 09:17:44 +0700 Subject: [PATCH 12/17] Disable zkevm test due to incompatibility --- tests/eest/eest_stateless_execution_test.nim | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/tests/eest/eest_stateless_execution_test.nim b/tests/eest/eest_stateless_execution_test.nim index 1e927f1671..689d8909ed 100644 --- a/tests/eest/eest_stateless_execution_test.nim +++ b/tests/eest/eest_stateless_execution_test.nim @@ -18,7 +18,9 @@ const eestType = "blockchain_tests" eestReleases = [ "eest_develop", - "eest_zkevm" + # TODO: zkevm@v0.3.3 is not compatible with bal@v5.7.0 + # enable this when they become compatible again + # "eest_zkevm" ] const skipFiles = [ From 15df0291d782f001fb87d936ff086515fc904a5e Mon Sep 17 00:00:00 2001 From: jangko Date: Wed, 29 Apr 2026 11:23:41 +0700 Subject: [PATCH 13/17] Make CPSB fixed --- execution_chain/core/eip8037.nim | 8 +++----- execution_chain/core/tx_pool/tx_desc.nim | 2 -- execution_chain/evm/computation.nim | 6 ++---- execution_chain/evm/interpreter/gas_costs.nim | 7 +++---- .../evm/interpreter/op_handlers/oph_call.nim | 5 ++--- .../evm/interpreter/op_handlers/oph_create.nim | 17 +++++++---------- .../evm/interpreter/op_handlers/oph_memory.nim | 4 +--- .../evm/interpreter/op_handlers/oph_sysops.nim | 2 +- execution_chain/evm/interpreter_dispatch.nim | 5 ++--- execution_chain/evm/state.nim | 2 -- execution_chain/evm/types.nim | 1 - execution_chain/transaction/call_common.nim | 2 +- execution_chain/transaction/call_types.nim | 7 ++----- execution_chain/utils/debug.nim | 1 - 14 files changed, 24 insertions(+), 45 deletions(-) diff --git a/execution_chain/core/eip8037.nim b/execution_chain/core/eip8037.nim index 99621e212e..9802df2b52 100644 --- a/execution_chain/core/eip8037.nim +++ b/execution_chain/core/eip8037.nim @@ -8,14 +8,12 @@ # those terms {.push raises: [].} -import - eth/common/base const STATE_BYTES_PER_NEW_ACCOUNT* = 112 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 + COST_PER_STATE_BYTE* = 1174 + 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 diff --git a/execution_chain/core/tx_pool/tx_desc.nim b/execution_chain/core/tx_pool/tx_desc.nim index 7b9e765ac9..d980a613a4 100644 --- a/execution_chain/core/tx_pool/tx_desc.nim +++ b/execution_chain/core/tx_pool/tx_desc.nim @@ -24,7 +24,6 @@ import ../../constants, ../../transaction, ../../transaction/call_types, - ../../core/eip8037, ../chain/forked_chain, ../pow/header, ../eip4844, @@ -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, diff --git a/execution_chain/evm/computation.nim b/execution_chain/evm/computation.nim index 9d20d55fb0..0efbb4b095 100644 --- a/execution_chain/evm/computation.nim +++ b/execution_chain/evm/computation.nim @@ -19,6 +19,7 @@ import ./evm_errors, ./code_bytes, ./eip7708, + ../core/eip8037, ../common/[evmforks], ../utils/[utils, mergeutils], ../common/common, @@ -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) @@ -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: diff --git a/execution_chain/evm/interpreter/gas_costs.nim b/execution_chain/evm/interpreter/gas_costs.nim index b1d6218420..16073785fc 100644 --- a/execution_chain/evm/interpreter/gas_costs.nim +++ b/execution_chain/evm/interpreter/gas_costs.nim @@ -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 @@ -78,7 +78,6 @@ type GasParamsSs* = object currentValue*: UInt256 originalValue*: UInt256 - stateGasStorageSet*: GasInt GasParamsCr* = object currentMemSize*: GasNatural @@ -302,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) @@ -331,7 +330,7 @@ template gasCosts(fork: EVMFork, prefix, ResultGasCostsName: untyped) = if params.originalValue.isZero: # reset to original inexistent slot (2.2.2.1) when fork >= FkAmsterdam: # https://github.com/ethereum/execution-specs/pull/2698/changes - res.creditStateGas = params.stateGasStorageSet + res.creditStateGas = STATE_GAS_STORAGE_SET res.gasRefund += CleanRefund else: res.gasRefund += InitRefund diff --git a/execution_chain/evm/interpreter/op_handlers/oph_call.nim b/execution_chain/evm/interpreter/op_handlers/oph_call.nim index 35c7982271..03de1322de 100644 --- a/execution_chain/evm/interpreter/op_handlers/oph_call.nim +++ b/execution_chain/evm/interpreter/op_handlers/oph_call.nim @@ -247,13 +247,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 diff --git a/execution_chain/evm/interpreter/op_handlers/oph_create.nim b/execution_chain/evm/interpreter/op_handlers/oph_create.nim index 810d7e80ff..559de31e5a 100644 --- a/execution_chain/evm/interpreter/op_handlers/oph_create.nim +++ b/execution_chain/evm/interpreter/op_handlers/oph_create.nim @@ -70,8 +70,7 @@ proc execSubCreate(c: Computation; childMsg: Message; # https://github.com/ethereum/execution-specs/pull/2733/changes if c.fork >= FkAmsterdam: - let createAccountStateGas = STATE_BYTES_PER_NEW_ACCOUNT * c.getCostPerStateByte - c.gasMeter.creditStateGasRefund(createAccountStateGas) + 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`. @@ -107,7 +106,6 @@ proc createOp(cpt: VmCpt): EvmResultVoid = memOffset: memPos, memLength: memLen) gasCost = cpt.gasCosts[Create].cr_handler(false, gasParams) - createAccountStateGas = STATE_BYTES_PER_NEW_ACCOUNT * cpt.getCostPerStateByte ? cpt.opcodeGasCost(Create, gasCost, reason = "CREATE: GasCreate + memLen * memory expansion") @@ -124,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(createAccountStateGas, + ? cpt.gasMeter.chargeStateGas(CREATE_ACCOUNT_STATE_GAS, reason = "CREATE: State gas new account") elif memLen > EIP3860_MAX_INITCODE_SIZE: # EIP-3860 @@ -141,7 +139,7 @@ proc createOp(cpt: VmCpt): EvmResultVoid = depth = cpt.msg.depth # https://github.com/ethereum/execution-specs/pull/2733/changes if cpt.fork >= FkAmsterdam: - cpt.gasMeter.creditStateGasRefund(createAccountStateGas) + cpt.gasMeter.creditStateGasRefund(CREATE_ACCOUNT_STATE_GAS) return ok() if endowment != 0: @@ -153,7 +151,7 @@ proc createOp(cpt: VmCpt): EvmResultVoid = balance = senderBalance # https://github.com/ethereum/execution-specs/pull/2733/changes if cpt.fork >= FkAmsterdam: - cpt.gasMeter.creditStateGasRefund(createAccountStateGas) + cpt.gasMeter.creditStateGasRefund(CREATE_ACCOUNT_STATE_GAS) return ok() var createMsgGas = cpt.gasMeter.gasRemaining @@ -206,7 +204,6 @@ proc create2Op(cpt: VmCpt): EvmResultVoid = currentMemSize: cpt.memory.len, memOffset: memPos, memLength: memLen) - createAccountStateGas = STATE_BYTES_PER_NEW_ACCOUNT * cpt.getCostPerStateByte var gasCost = cpt.gasCosts[Create].cr_handler(false, gasParams) gasCost = gasCost + cpt.gasCosts[Create2].m_handler(0, 0, memLen) @@ -226,7 +223,7 @@ proc create2Op(cpt: VmCpt): EvmResultVoid = # Charge state gas after initcode size validation # https://github.com/ethereum/execution-specs/commit/b9f0afa931a773cdb764310035d0ff383ebecf9e - ? cpt.gasMeter.chargeStateGas(createAccountStateGas, + ? cpt.gasMeter.chargeStateGas(CREATE_ACCOUNT_STATE_GAS, reason = "CREATE2: State gas new account") elif memLen > EIP3860_MAX_INITCODE_SIZE: # EIP-3860 @@ -243,7 +240,7 @@ proc create2Op(cpt: VmCpt): EvmResultVoid = depth = cpt.msg.depth # https://github.com/ethereum/execution-specs/pull/2733/changes if cpt.fork >= FkAmsterdam: - cpt.gasMeter.creditStateGasRefund(createAccountStateGas) + cpt.gasMeter.creditStateGasRefund(CREATE_ACCOUNT_STATE_GAS) return ok() if endowment != 0: @@ -255,7 +252,7 @@ proc create2Op(cpt: VmCpt): EvmResultVoid = balance = senderBalance # https://github.com/ethereum/execution-specs/pull/2733/changes if cpt.fork >= FkAmsterdam: - cpt.gasMeter.creditStateGasRefund(createAccountStateGas) + cpt.gasMeter.creditStateGasRefund(CREATE_ACCOUNT_STATE_GAS) return ok() var createMsgGas = cpt.gasMeter.gasRemaining diff --git a/execution_chain/evm/interpreter/op_handlers/oph_memory.nim b/execution_chain/evm/interpreter/op_handlers/oph_memory.nim index cfac76d769..e9381dc805 100644 --- a/execution_chain/evm/interpreter/op_handlers/oph_memory.nim +++ b/execution_chain/evm/interpreter/op_handlers/oph_memory.nim @@ -27,8 +27,7 @@ import ./oph_defs, ./oph_helpers, ../../state, - ../../../db/ledger, - ../../../core/eip8037 + ../../../db/ledger # ------------------------------------------------------------------------------ # Private helpers @@ -59,7 +58,6 @@ proc sstoreNetGasMeteringImpl(c: Computation; slot, newValue: UInt256, coldAcces gasParam = GasParamsSs( currentValue: currentValue, originalValue: ledger.getCommittedStorage(c.msg.contractAddress, slot), - stateGasStorageSet: STATE_BYTES_PER_STORAGE_SET * c.getCostPerStateByte, ) res = c.gasCosts[Sstore].ss_handler(newValue, gasParam) diff --git a/execution_chain/evm/interpreter/op_handlers/oph_sysops.nim b/execution_chain/evm/interpreter/op_handlers/oph_sysops.nim index 4a5412113b..3de1c7a6cf 100644 --- a/execution_chain/evm/interpreter/op_handlers/oph_sysops.nim +++ b/execution_chain/evm/interpreter/op_handlers/oph_sysops.nim @@ -190,7 +190,7 @@ proc selfDestructEIP8037Op(cpt: VmCpt): EvmResultVoid = gasCost, reason = "SELFDESTRUCT EIP-8037") if condition: - ? cpt.gasMeter.chargeStateGas(STATE_BYTES_PER_NEW_ACCOUNT * cpt.getCostPerStateByte, + ? cpt.gasMeter.chargeStateGas(CREATE_ACCOUNT_STATE_GAS, reason = "SELFDESTRUCT EIP-8037: State gas new account") cpt.selfDestruct(beneficiary) diff --git a/execution_chain/evm/interpreter_dispatch.nim b/execution_chain/evm/interpreter_dispatch.nim index ae9346a66f..be44b0a490 100644 --- a/execution_chain/evm/interpreter_dispatch.nim +++ b/execution_chain/evm/interpreter_dispatch.nim @@ -126,8 +126,7 @@ proc beforeExecCreate(c: Computation): bool = c.gasMeter.gasRemaining = 0 elif c.fork >= FkAmsterdam: # https://github.com/ethereum/execution-specs/pull/2733/changes - let createAccountStateGas = STATE_BYTES_PER_NEW_ACCOUNT * c.getCostPerStateByte - c.gasMeter.creditStateGasRefund(createAccountStateGas) + c.gasMeter.creditStateGasRefund(CREATE_ACCOUNT_STATE_GAS) let blurb = c.msg.contractAddress.toHex c.setError("Address collision when creating contract address=" & blurb, true) return true @@ -202,7 +201,7 @@ proc afterExec(c: Computation) = if c.isSuccess: c.commit() - else: + else: c.rollback() if c.msg.depth > 0: diff --git a/execution_chain/evm/state.nim b/execution_chain/evm/state.nim index f0f4a5887e..56002160af 100644 --- a/execution_chain/evm/state.nim +++ b/execution_chain/evm/state.nim @@ -16,7 +16,6 @@ import ../db/ledger, ../common/[common, evmforks], ../block_access_list/block_access_list_tracker, - ../core/eip8037, ./interpreter/[op_codes, gas_costs], ./types, ./evm_errors @@ -68,7 +67,6 @@ func blockCtx(header: Header): BlockContext = excessBlobGas: header.excessBlobGas.get(0'u64), parentHash : header.parentHash, slotNumber : header.slotNumber.get(0'u64), - costPerStateByte: stateGasPerByte(header.gasLimit), ) # -------------- diff --git a/execution_chain/evm/types.nim b/execution_chain/evm/types.nim index 6e590048c9..4788eceeb1 100644 --- a/execution_chain/evm/types.nim +++ b/execution_chain/evm/types.nim @@ -34,7 +34,6 @@ type excessBlobGas* : uint64 parentHash* : Hash32 slotNumber* : uint64 - costPerStateByte* : GasInt TxContext* = object origin* : Address diff --git a/execution_chain/transaction/call_common.nim b/execution_chain/transaction/call_common.nim index c209252af9..02d813468d 100644 --- a/execution_chain/transaction/call_common.nim +++ b/execution_chain/transaction/call_common.nim @@ -112,7 +112,7 @@ proc preExecComputation(vmState: BaseVMState, call: CallParams): int64 = # 7. Add PER_EMPTY_ACCOUNT_COST - PER_AUTH_BASE_COST gas to the global refund counter if authority exists in the trie. if ledger.accountExists(authority): if vmState.fork >= FkAmsterdam: - gasRefund += int64(STATE_BYTES_PER_NEW_ACCOUNT * vmState.blockCtx.costPerStateByte) + gasRefund += int64(CREATE_ACCOUNT_STATE_GAS) else: gasRefund += PER_EMPTY_ACCOUNT_COST - PER_AUTH_BASE_COST diff --git a/execution_chain/transaction/call_types.nim b/execution_chain/transaction/call_types.nim index d4c19644d2..e88b742cdf 100644 --- a/execution_chain/transaction/call_types.nim +++ b/execution_chain/transaction/call_types.nim @@ -83,9 +83,6 @@ func intrinsicGas*(call: CallParams | Transaction, fork: EVMFork, gasLimit: GasI # Compute the baseline gas cost for this transaction. This is the amount # of gas needed to send this transaction (but that is not actually used # for computation). - let - costPerStateByte = stateGasPerByte(gasLimit) - var regularGas = TX_BASE_COST stateGas = 0.GasInt @@ -96,7 +93,7 @@ func intrinsicGas*(call: CallParams | Transaction, fork: EVMFork, gasLimit: GasI # EIP-2 (Homestead) extra intrinsic gas for contract creations. if call.isCreate: if fork >= FkAmsterdam: - stateGas += STATE_BYTES_PER_NEW_ACCOUNT * costPerStateByte + stateGas += CREATE_ACCOUNT_STATE_GAS regularGas += gasFees[fork][GasTXCreate] if fork >= FkShanghai: @@ -131,7 +128,7 @@ func intrinsicGas*(call: CallParams | Transaction, fork: EVMFork, gasLimit: GasI let floorTokensInAccessList = accessListBytes * 4 tokens += floorTokensInAccessList regularGas += TOTAL_COST_FLOOR_PER_TOKEN_EIP7976 * floorTokensInAccessList - stateGas += (STATE_BYTES_PER_NEW_ACCOUNT + STATE_BYTES_PER_AUTH_BASE) * costPerStateByte * GasInt(call.authorizationList.len) + stateGas += (STATE_BYTES_PER_NEW_ACCOUNT + STATE_BYTES_PER_AUTH_BASE) * COST_PER_STATE_BYTE * GasInt(call.authorizationList.len) floorDataGas += tokens * TOTAL_COST_FLOOR_PER_TOKEN_EIP7976 else: regularGas += call.authorizationList.len * PER_EMPTY_ACCOUNT_COST diff --git a/execution_chain/utils/debug.nim b/execution_chain/utils/debug.nim index ed92d278ad..111b6a538c 100644 --- a/execution_chain/utils/debug.nim +++ b/execution_chain/utils/debug.nim @@ -95,7 +95,6 @@ proc debug*(vms: BaseVMState): string = result.add "excessBlobGas : " & $vms.blockCtx.excessBlobGas & "\n" result.add "parentHash : " & $vms.blockCtx.parentHash & "\n" result.add "slotNumber : " & $vms.blockCtx.slotNumber & "\n" - result.add "costPerStateByte : " & $vms.blockCtx.costPerStateByte & "\n" result.add "flags : " & $vms.flags & "\n" result.add "receipts.len : " & $vms.receipts.len & "\n" result.add "ledger.root : " & $vms.ledger.getStateRoot() & "\n" From 929617b8cd5909a9d1d6519119325b96d551a3dc Mon Sep 17 00:00:00 2001 From: jangko Date: Thu, 30 Apr 2026 09:20:06 +0700 Subject: [PATCH 14/17] Upgrade test fixtures to snobal-devnet-6@v1.1.0 --- scripts/eest_ci_cache.sh | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/scripts/eest_ci_cache.sh b/scripts/eest_ci_cache.sh index e6031beaef..8c9a65dd87 100755 --- a/scripts/eest_ci_cache.sh +++ b/scripts/eest_ci_cache.sh @@ -21,10 +21,10 @@ EEST_DEVELOP_ARCHIVE="fixtures_develop.tar.gz" EEST_DEVELOP_URL="https://github.com/ethereum/execution-spec-tests/releases/download/${EEST_DEVELOP_VERSION}/${EEST_DEVELOP_ARCHIVE}" # --- BAL Release --- -EEST_BAL_NAME="bal" -EEST_BAL_VERSION="v5.6.1" +EEST_BAL_NAME="snobal-devnet-6" +EEST_BAL_VERSION="v1.1.0" EEST_BAL_DIR="${FIXTURES_DIR}/eest_bal" -EEST_BAL_ARCHIVE="fixtures_bal.tar.gz" +EEST_BAL_ARCHIVE="fixtures_${EEST_BAL_NAME}.tar.gz" EEST_BAL_URL="https://github.com/ethereum/execution-spec-tests/releases/download/${EEST_BAL_NAME}%40${EEST_BAL_VERSION}/${EEST_BAL_ARCHIVE}" # --- zkevm Release --- From 6009e746ec7eb718c76d28feb97891628478a6fa Mon Sep 17 00:00:00 2001 From: jangko Date: Fri, 1 May 2026 07:48:23 +0700 Subject: [PATCH 15/17] EIP-8037: Handle state gas restoration --- execution_chain/evm/interpreter/gas_meter.nim | 10 ++++++++++ execution_chain/evm/interpreter_dispatch.nim | 3 +++ 2 files changed, 13 insertions(+) diff --git a/execution_chain/evm/interpreter/gas_meter.nim b/execution_chain/evm/interpreter/gas_meter.nim index 6a45c0fda5..547c49d191 100644 --- a/execution_chain/evm/interpreter/gas_meter.nim +++ b/execution_chain/evm/interpreter/gas_meter.nim @@ -110,3 +110,13 @@ func selfDestructRefundStateGas*(gasMeter: var GasMeter; amount: GasInt) = let applied = min(amount, gasMeter.stateGasUsed) gasMeter.stateGasLeft += applied gasMeter.stateGasUsed -= applied + +func restoreStateGasReservoir*(gasMeter: var GasMeter, reservoir: GasInt) = + let totalState = gasMeter.stateGasUsed + gasMeter.stateGasLeft + if totalState > reservoir: + gasMeter.regularGasUsed += totalState - reservoir + gasMeter.stateGasLeft = reservoir + gasMeter.stateGasUsed = 0 + gasMeter.stateGasRefund = 0 + gasMeter.stateGasRefundPending = 0 + \ No newline at end of file diff --git a/execution_chain/evm/interpreter_dispatch.nim b/execution_chain/evm/interpreter_dispatch.nim index be44b0a490..31037d4005 100644 --- a/execution_chain/evm/interpreter_dispatch.nim +++ b/execution_chain/evm/interpreter_dispatch.nim @@ -199,6 +199,9 @@ proc afterExec(c: Computation) = else: c.afterExecCreate() + if c.fork >= FkAmsterdam and c.shouldBurnGas: + c.gasMeter.restoreStateGasReservoir(c.msg.stateGas) + if c.isSuccess: c.commit() else: From 9b6283b5137eb97efb53bb655de15c3b3e904195 Mon Sep 17 00:00:00 2001 From: jangko Date: Wed, 6 May 2026 11:23:02 +0700 Subject: [PATCH 16/17] Add missing 'i64 --- execution_chain/transaction/call_common.nim | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/execution_chain/transaction/call_common.nim b/execution_chain/transaction/call_common.nim index 60e184f30e..b9255167cd 100644 --- a/execution_chain/transaction/call_common.nim +++ b/execution_chain/transaction/call_common.nim @@ -151,7 +151,7 @@ proc setupHost(call: CallParams, keepStack: bool): TransactionHost = let isAmsterdamOrLater = fork >= FkAmsterdam intrinsic = call.intrinsic - gasRefund = if call.sysCall: 0 + gasRefund = if call.sysCall: 0'i64 else: preExecComputation(vmState, call) intrinsicGas = intrinsic.regular + intrinsic.state From 882364408f42ab66115263a5c35dd9ebd3573a32 Mon Sep 17 00:00:00 2001 From: jangko Date: Thu, 7 May 2026 17:56:03 +0700 Subject: [PATCH 17/17] restore nvp access list test --- nimbus_verified_proxy/tests/data/access_list.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nimbus_verified_proxy/tests/data/access_list.json b/nimbus_verified_proxy/tests/data/access_list.json index 214b27e76d..2498bf0778 100644 --- a/nimbus_verified_proxy/tests/data/access_list.json +++ b/nimbus_verified_proxy/tests/data/access_list.json @@ -7,5 +7,5 @@ ] } ], - "gasUsed": "0x216" + "gasUsed": "0x669a" }