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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 3 additions & 5 deletions execution_chain/core/eip8037.nim
Original file line number Diff line number Diff line change
Expand Up @@ -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
25 changes: 20 additions & 5 deletions execution_chain/core/executor/process_transaction.nim
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 0 additions & 2 deletions execution_chain/core/tx_pool/tx_desc.nim
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,6 @@ import
../../db/ledger,
../../constants,
../../transaction,
../../core/eip8037,
../../transaction/call_types,
../chain/forked_chain,
../pow/header,
Expand Down Expand Up @@ -106,7 +105,6 @@ proc setupVMState(com: CommonRef;
excessBlobGas: com.calcExcessBlobGas(parent, fork),
parentHash : parentHash,
slotNumber : pos.slotNumber,
costPerStateByte: stateGasPerByte(gasLimit),
),
txFrame = parentFrame.txFrameBegin(),
com = com,
Expand Down
58 changes: 41 additions & 17 deletions execution_chain/db/ledger.nim
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,10 @@ type
selfDestruct: HashSet[Address]
accessList: ac_access_list.AccessList

SelfDestructRefund* = object
createdSlots*: int
codeLen*: int

const
resetFlags = {
Dirty,
Expand Down Expand Up @@ -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
# ------------------------------------------------------------------------------
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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

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

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

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

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

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

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

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

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

CallGasResult = tuple[gasCost, childGasLimit: GasInt]

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

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

if value.isZero: # delete slot (2.1.2b)
Expand Down Expand Up @@ -329,7 +329,9 @@ template gasCosts(fork: EVMFork, prefix, ResultGasCostsName: untyped) =
if params.originalValue == value:
if params.originalValue.isZero: # reset to original inexistent slot (2.2.2.1)
when fork >= FkAmsterdam:
res.gasRefund += params.stateGasStorageSet.int64 + CleanRefund
# https://github.com/ethereum/execution-specs/pull/2698/changes
res.creditStateGas = STATE_GAS_STORAGE_SET
res.gasRefund += CleanRefund
else:
res.gasRefund += InitRefund
else: # reset to original existing slot (2.2.2.2)
Expand Down
30 changes: 30 additions & 0 deletions execution_chain/evm/interpreter/gas_meter.nim
Original file line number Diff line number Diff line change
Expand Up @@ -90,3 +90,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

10 changes: 6 additions & 4 deletions execution_chain/evm/interpreter/op_handlers/oph_call.nim
Original file line number Diff line number Diff line change
Expand Up @@ -197,14 +197,17 @@ 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:
# On failure (revert or exceptional halt) state changes are rolled back,
# so no state was actually grown. All state gas, both reservoir and any
# that spilled into `gas_left`, is restored to the parent's reservoir and
# the child's `state_gas_used` is not accumulated.
c.gasMeter.returnStateGas(child.gasMeter.stateGasUsed + child.gasMeter.stateGasLeft)
c.gasMeter.returnStateGas(child.gasMeter.stateGasUsed + child.gasMeter.stateGasLeft - child.gasMeter.stateGasRefund)

let actualOutputSize = min(memLen, child.output.len)
if actualOutputSize > 0:
Expand Down Expand Up @@ -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
Expand Down
26 changes: 23 additions & 3 deletions execution_chain/evm/interpreter/op_handlers/oph_create.nim
Original file line number Diff line number Diff line change
Expand Up @@ -56,14 +56,22 @@ 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:
# On failure (revert or exceptional halt) state changes are rolled back,
# so no state was actually grown. All state gas, both reservoir and any
# that spilled into `gas_left`, is restored to the parent's reservoir and
# the child's `state_gas_used` is not accumulated.
c.gasMeter.returnStateGas(child.gasMeter.stateGasUsed + child.gasMeter.stateGasLeft)
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)
Expand Down Expand Up @@ -114,7 +122,7 @@ proc createOp(cpt: VmCpt): EvmResultVoid =

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

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

var createMsgGas = cpt.gasMeter.gasRemaining
Expand Down Expand Up @@ -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
Expand All @@ -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:
Expand All @@ -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
Expand Down
Loading
Loading