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/executor/process_transaction.nim b/execution_chain/core/executor/process_transaction.nim index 2c4cb1704b..a49fd30b21 100644 --- a/execution_chain/core/executor/process_transaction.nim +++ b/execution_chain/core/executor/process_transaction.nim @@ -112,15 +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 diff --git a/execution_chain/core/tx_pool/tx_desc.nim b/execution_chain/core/tx_pool/tx_desc.nim index 5dd0309edb..d980a613a4 100644 --- a/execution_chain/core/tx_pool/tx_desc.nim +++ b/execution_chain/core/tx_pool/tx_desc.nim @@ -23,7 +23,6 @@ import ../../db/ledger, ../../constants, ../../transaction, - ../../core/eip8037, ../../transaction/call_types, ../chain/forked_chain, ../pow/header, @@ -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/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/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 89f4f8335f..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 @@ -101,6 +100,7 @@ type gasCost*: GasInt gasRefund*: int64 stateGas*: GasInt + creditStateGas*: GasInt CallGasResult = tuple[gasCost, childGasLimit: GasInt] @@ -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) @@ -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) diff --git a/execution_chain/evm/interpreter/gas_meter.nim b/execution_chain/evm/interpreter/gas_meter.nim index daef1f5251..547c49d191 100644 --- a/execution_chain/evm/interpreter/gas_meter.nim +++ b/execution_chain/evm/interpreter/gas_meter.nim @@ -90,3 +90,33 @@ 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.stateGasRefund += applied + gasMeter.stateGasRefundPending += amount - applied + +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 + +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/op_handlers/oph_call.nim b/execution_chain/evm/interpreter/op_handlers/oph_call.nim index 417e1edc0a..03de1322de 100644 --- a/execution_chain/evm/interpreter/op_handlers/oph_call.nim +++ b/execution_chain/evm/interpreter/op_handlers/oph_call.nim @@ -197,6 +197,9 @@ 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) + # https://github.com/ethereum/execution-specs/pull/2733/changes + c.gasMeter.creditStateGasRefund(child.gasMeter.stateGasRefundPending) c.merge(child) c.stack.lsTop(1) else: @@ -204,7 +207,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: @@ -244,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 d09b99f0cc..559de31e5a 100644 --- a/execution_chain/evm/interpreter/op_handlers/oph_create.nim +++ b/execution_chain/evm/interpreter/op_handlers/oph_create.nim @@ -56,6 +56,9 @@ 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) + # 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: @@ -63,7 +66,12 @@ 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/2733/changes + if c.fork >= FkAmsterdam: + 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) @@ -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 @@ -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: @@ -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 @@ -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(CREATE_ACCOUNT_STATE_GAS, 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/2733/changes + if cpt.fork >= FkAmsterdam: + cpt.gasMeter.creditStateGasRefund(CREATE_ACCOUNT_STATE_GAS) 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/2733/changes + if cpt.fork >= FkAmsterdam: + 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 c28f3d52ad..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) @@ -69,8 +67,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: - ? c.gasMeter.chargeStateGas(res.stateGas, reason = "SSTORE state gas") + 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/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 7f5837113b..22f504d386 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,9 @@ 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/2733/changes + 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 @@ -195,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: 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 c03fb9c099..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 @@ -102,6 +101,8 @@ type stateGasLeft*: GasInt 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 ce31fbdb11..cc6c1839a8 100644 --- a/execution_chain/transaction/call_common.nim +++ b/execution_chain/transaction/call_common.nim @@ -107,7 +107,7 @@ proc preExecComputation(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 @@ -223,7 +223,21 @@ proc prepareToRunComputation(c: Computation, call: CallParams) = vmState.balTracker.trackSubBalanceChange(call.sender, blobFee) ledger.subBalance(call.sender, blobFee) -proc calculateAndPossiblyRefundGas(c: Computation, call: CallParams, gasRefund: int64): GasUsed = +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(c: Computation, call: CallParams): GasUsed = let vmState = c.vmState fork = c.vmState.fork @@ -236,6 +250,14 @@ proc calculateAndPossiblyRefundGas(c: Computation, call: CallParams, gasRefund: if c.shouldBurnGas: c.gasMeter.burnGas() + if c.fork >= FkAmsterdam: + if c.isSuccess: + # https://github.com/ethereum/execution-specs/pull/2707/changes + c.calcSelfDestructRefundStateGas() + 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 @@ -252,7 +274,7 @@ proc calculateAndPossiblyRefundGas(c: Computation, call: CallParams, gasRefund: txGasUsed = max(txGasUsedAfterRefund, call.intrinsic.floorDataGas) let txRegularGas = call.intrinsic.regular + c.gasMeter.regularGasUsed - intrinsicStateGas = call.intrinsic.state - gasRefund.GasInt + intrinsicStateGas = call.intrinsic.state blockRegularGasUsed = max(txRegularGas, call.intrinsic.floorDataGas) blockStateGasUsed = intrinsicStateGas + c.gasMeter.stateGasUsed debug "EIP-8037 gas accounting", @@ -288,9 +310,9 @@ proc calculateAndPossiblyRefundGas(c: Computation, call: CallParams, gasRefund: ) proc finishRunningComputation( - c: Computation, call: CallParams, gasRefund: int64, T: type): T = + c: Computation, call: CallParams, T: type): T = let - gasUsed = calculateAndPossiblyRefundGas(c, call, gasRefund) + gasUsed = calculateAndPossiblyRefundGas(c, call) # evm gas used without intrinsic gas c.vmState.captureEnd(c, c.output, gasUsed.evmGasUsed, c.errorOpt) @@ -330,4 +352,4 @@ proc runComputation*(call: CallParams, T: type): T = if c.isSuccess: c.execCallOrCreate() c.postExecComputation() - finishRunningComputation(c, call, gasRefund, T) + finishRunningComputation(c, call, T) diff --git a/execution_chain/transaction/call_evm.nim b/execution_chain/transaction/call_evm.nim index 47738009c7..a1c0d72e14 100644 --- a/execution_chain/transaction/call_evm.nim +++ b/execution_chain/transaction/call_evm.nim @@ -57,7 +57,7 @@ proc txCallEvm*(tx: Transaction, proc testCallEvm*(tx: Transaction, sender: Address, vmState: BaseVMState): DebugCallResult = - let + let baseFee = vmState.blockCtx.baseFeePerGas.get(0.u256).truncate(GasInt) call = callParamsForTx(tx, sender, vmState, baseFee, IntrinsicGas()) runComputation(call, DebugCallResult) diff --git a/execution_chain/transaction/call_types.nim b/execution_chain/transaction/call_types.nim index dfa939deab..cad0360ad4 100644 --- a/execution_chain/transaction/call_types.nim +++ b/execution_chain/transaction/call_types.nim @@ -75,55 +75,63 @@ 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 # 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 floorDataGas = regularGas tokens = 0 + accessListBytes = 0 # 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: 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] + 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 += 1 + 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 - stateGas += (STATE_BYTES_PER_NEW_ACCOUNT + STATE_BYTES_PER_AUTH_BASE) * costPerStateByte * GasInt(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) * 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 - floorDataGas += tokens * TOTAL_COST_FLOOR_PER_TOKEN + floorDataGas += tokens * TOTAL_COST_FLOOR_PER_TOKEN_EIP7623 IntrinsicGas( regular: regularGas.GasInt, 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" 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 = diff --git a/scripts/eest_ci_cache.sh b/scripts/eest_ci_cache.sh index 037abc0629..4b9867c9c5 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 --- diff --git a/tests/eest/eest_stateless_execution_test.nim b/tests/eest/eest_stateless_execution_test.nim index cd81173db1..6e22a9b5b2 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 = [