Skip to content

EIP: Implement EIP-7907 #3380

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 7 commits into
base: master
Choose a base branch
from
Draft
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
7 changes: 6 additions & 1 deletion execution_chain/constants.nim
Original file line number Diff line number Diff line change
Expand Up @@ -59,14 +59,19 @@ const

## Fork specific constants

# See EIP-170 (https://eips.ethereum.org/EIPS/eip-170). Maximum code size
# See EIP-170 (https://eips.ethereum.org/EIPS/eip-170) Maximum code size
# that can be stored for a new contract. Init code when creating a new
# contract is not subject to this limit.
EIP170_MAX_CODE_SIZE* = 0x6000
# See See EIP-7907 (https://eips.ethereum.org/EIPS/eip-7907). Update to limits
CODE_SIZE_THRESHOLD* = 0x6000
EIP7907_MAX_CODE_SIZE* = 0x40000

# See EIP-3860 (https://eips.ethereum.org/EIPS/eip-3860). Maximum initcode
# size when creating a new contract.
EIP3860_MAX_INITCODE_SIZE* = 2 * EIP170_MAX_CODE_SIZE
# See EIP-7907 (https://eips.ethereum.org/EIPS/eip-7907).
EIP7907_MAX_INITCODE_SIZE* = 2 * EIP7907_MAX_CODE_SIZE

# EIP
MaxPrecompilesAddr* = 0xFFFF
Expand Down
9 changes: 7 additions & 2 deletions execution_chain/core/validate.nim
Original file line number Diff line number Diff line change
Expand Up @@ -228,8 +228,13 @@ func validateTxBasic*(
if tx.txType == TxEip7702 and fork < FkPrague:
return err("invalid tx: Eip7702 Tx type detected before Prague")

if fork >= FkShanghai and tx.contractCreation and tx.payload.len > EIP3860_MAX_INITCODE_SIZE:
return err("invalid tx: initcode size exceeds maximum")
if tx.contractCreation:
if fork >= FkOsaka and tx.payload.len > EIP7907_MAX_INITCODE_SIZE:
return err("invalid tx: initcode size exceeds maximum")

if fork < FkOsaka and fork >= FkShanghai and tx.payload.len > EIP3860_MAX_INITCODE_SIZE:
return err("invalid tx: initcode size exceeds maximum")


# The total must be the larger of the two
if tx.maxFeePerGasNorm < tx.maxPriorityFeePerGasNorm:
Expand Down
19 changes: 18 additions & 1 deletion execution_chain/db/access_list.nim
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
# Nimbus
# Copyright (c) 2023-2024 Status Research & Development GmbH
# Copyright (c) 2023-2025 Status Research & Development GmbH
# Licensed under either of
# * Apache License, version 2.0, ([LICENSE-APACHE](LICENSE-APACHE) or
# http://www.apache.org/licenses/LICENSE-2.0)
Expand All @@ -21,6 +21,7 @@ type

AccessList* = object
slots: Table[Address, SlotSet]
codeAddrs: HashSet[Address]

# ------------------------------------------------------------------------------
# Private helpers
Expand All @@ -36,6 +37,7 @@ func toStorageKeys(slots: SlotSet): seq[Bytes32] =

proc init*(ac: var AccessList) =
ac.slots = Table[Address, SlotSet]()
ac.codeAddrs = HashSet[Address]()

proc init*(_: type AccessList): AccessList {.inline.} =
result.init()
Expand All @@ -47,6 +49,9 @@ proc init*(_: type AccessList): AccessList {.inline.} =
func contains*(ac: AccessList, address: Address): bool {.inline.} =
address in ac.slots

func containsCode*(ac: AccessList, codeAddr: Address): bool {.inline.} =
codeAddr in ac.codeAddrs

# returnValue: (addressPresent, slotPresent)
func contains*(ac: var AccessList, address: Address, slot: UInt256): bool =
ac.slots.withValue(address, val):
Expand All @@ -55,6 +60,7 @@ func contains*(ac: var AccessList, address: Address, slot: UInt256): bool =
proc mergeAndReset*(ac, other: var AccessList) =
# move values in `other` to `ac`
ac.slots.mergeAndReset(other.slots)
ac.codeAddrs.mergeAndReset(other.codeAddrs)

proc add*(ac: var AccessList, address: Address) =
if address notin ac.slots:
Expand All @@ -66,9 +72,16 @@ proc add*(ac: var AccessList, address: Address, slot: UInt256) =
do:
ac.slots[address] = toHashSet([slot])

proc addCode*(ac: var AccessList, codeAddr: Address) =
ac.codeAddrs.incl codeAddr

proc clear*(ac: var AccessList) {.inline.} =
ac.slots.clear()
ac.codeAddrs.clear()

# TODO: accesses code is still not a part of the transaction access list
# but when it does trickle down into the transaction we will have to add
# it here
func getAccessList*(ac: AccessList): transactions.AccessList =
for address, slots in ac.slots:
result.add transactions.AccessPair(
Expand All @@ -91,4 +104,8 @@ func equal*(ac: AccessList, other: var AccessList): bool =
do:
return false

for codeAddr in ac.codeAddrs:
if codeAddr notin other:
return false

true
11 changes: 11 additions & 0 deletions execution_chain/db/ledger.nim
Original file line number Diff line number Diff line change
Expand Up @@ -829,6 +829,9 @@ proc accessList*(ac: LedgerRef, address: Address) =
proc accessList*(ac: LedgerRef, address: Address, slot: UInt256) =
ac.savePoint.accessList.add(address, slot)

proc codeAccessList*(ac: LedgerRef, codeAddr: Address) =
ac.savePoint.accessList.addCode(codeAddr)

func inAccessList*(ac: LedgerRef, address: Address): bool =
var sp = ac.savePoint
while sp != nil:
Expand All @@ -845,6 +848,14 @@ func inAccessList*(ac: LedgerRef, address: Address, slot: UInt256): bool =
return
sp = sp.parentSavepoint

func inCodeAccessList*(ac: LedgerRef, codeAddr: Address): bool =
var sp = ac.savePoint
while sp != nil:
result = sp.accessList.containsCode(codeAddr)
if result:
return
sp = sp.parentSavepoint

func getAccessList*(ac: LedgerRef): transactions.AccessList =
# make sure all savepoint already committed
doAssert(ac.savePoint.parentSavepoint.isNil)
Expand Down
11 changes: 9 additions & 2 deletions execution_chain/evm/computation.nim
Original file line number Diff line number Diff line change
Expand Up @@ -205,8 +205,15 @@ proc writeContract*(c: Computation) =
c.setError(StatusCode.ContractValidationFailure, true)
return

# EIP-170 constraint (https://eips.ethereum.org/EIPS/eip-3541).
if fork >= FkSpurious and len > EIP170_MAX_CODE_SIZE:
# EIP-7907 constraint (https://eips.ethereum.org/EIPS/eip-7907).
if fork >= FkOsaka and len > EIP7907_MAX_CODE_SIZE:
withExtra trace, "New contract code exceeds EIP-7907 limit",
codeSize=len, maxSize=EIP7907_MAX_CODE_SIZE
c.setError(StatusCode.OutOfGas, true)
return

# EIP-170 constraint (https://eips.ethereum.org/EIPS/eip-170).
if fork < FkOsaka and fork >= FkSpurious and len > EIP170_MAX_CODE_SIZE:
withExtra trace, "New contract code exceeds EIP-170 limit",
codeSize=len, maxSize=EIP170_MAX_CODE_SIZE
c.setError(StatusCode.OutOfGas, true)
Expand Down
6 changes: 5 additions & 1 deletion execution_chain/evm/interpreter/op_handlers/oph_call.nim
Original file line number Diff line number Diff line change
Expand Up @@ -81,11 +81,15 @@ proc updateStackAndParams(q: var LocalParams; c: Computation) =
q.memOffset = q.memOutPos
q.memLength = q.memOutLen

# EIP7907
if FkOsaka <= c.fork:
q.gasCallEIPs = gasCallEIP7907(c, q.codeAddress)

# EIP2929: This came before old gas calculator
# because it will affect `c.gasMeter.gasRemaining`
# and further `childGasLimit`
if FkBerlin <= c.fork:
q.gasCallEIPs = gasCallEIP2929(c, q.codeAddress)
q.gasCallEIPs += gasCallEIP2929(c, q.codeAddress)

if FkPrague <= c.fork:
let delegateTo = parseDelegationAddress(c.getCode(q.codeAddress))
Expand Down
25 changes: 21 additions & 4 deletions execution_chain/evm/interpreter/op_handlers/oph_create.nim
Original file line number Diff line number Diff line change
Expand Up @@ -76,8 +76,12 @@ proc createOp(cpt: VmCpt): EvmResultVoid =
cpt.stack.lsShrink(2)
cpt.stack.lsTop(0)

# EIP-3860
if cpt.fork >= FkShanghai and memLen > EIP3860_MAX_INITCODE_SIZE:
# EIP-7907 and EIP-3860
if cpt.fork >= FkOsaka and memLen > EIP7907_MAX_INITCODE_SIZE:
trace "Initcode size exceeds maximum", initcodeSize = memLen
return err(opErr(InvalidInitCode))

if cpt.fork < FkOsaka and cpt.fork >= FkShanghai and memLen > EIP3860_MAX_INITCODE_SIZE:
trace "Initcode size exceeds maximum", initcodeSize = memLen
return err(opErr(InvalidInitCode))

Expand Down Expand Up @@ -114,6 +118,10 @@ proc createOp(cpt: VmCpt): EvmResultVoid =
createMsgGas -= createMsgGas div 64
? cpt.gasMeter.consumeGas(createMsgGas, reason = "CREATE msg gas")

if cpt.fork >= FkOsaka:
cpt.vmState.mutateLedger:
db.codeAccessList(cpt.msg.contractAddress)

var
childMsg = Message(
kind: CallKind.Create,
Expand Down Expand Up @@ -146,8 +154,12 @@ proc create2Op(cpt: VmCpt): EvmResultVoid =
cpt.stack.lsShrink(3)
cpt.stack.lsTop(0)

# EIP-3860
if cpt.fork >= FkShanghai and memLen > EIP3860_MAX_INITCODE_SIZE:
# EIP-7907 and EIP-3860
if cpt.fork >= FkOsaka and memLen > EIP7907_MAX_INITCODE_SIZE:
trace "Initcode size exceeds maximum", initcodeSize = memLen
return err(opErr(InvalidInitCode))

if cpt.fork < FkOsaka and cpt.fork >= FkShanghai and memLen > EIP3860_MAX_INITCODE_SIZE:
trace "Initcode size exceeds maximum", initcodeSize = memLen
return err(opErr(InvalidInitCode))

Expand Down Expand Up @@ -181,6 +193,11 @@ proc create2Op(cpt: VmCpt): EvmResultVoid =
balance = senderBalance
return ok()

if cpt.fork >= FkOsaka:
cpt.vmState.mutateLedger:
db.codeAccessList(cpt.msg.contractAddress)


var createMsgGas = cpt.gasMeter.gasRemaining
if cpt.fork >= FkTangerine:
createMsgGas -= createMsgGas div 64
Expand Down
7 changes: 7 additions & 0 deletions execution_chain/evm/interpreter/op_handlers/oph_defs.nim
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,13 @@ const
VmOpCancunAndLater* =
VmOpShanghaiAndLater - {FkShanghai}

VmOpPragueAndLater* =
VmOpCancunAndLater - {FkCancun}

VmOpOsakaAndLater* =
VmOpPragueAndLater - {FkPrague}


# ------------------------------------------------------------------------------
# End
# ------------------------------------------------------------------------------
27 changes: 26 additions & 1 deletion execution_chain/evm/interpreter/op_handlers/oph_envinfo.nim
Original file line number Diff line number Diff line change
Expand Up @@ -189,6 +189,24 @@ proc extCodeCopyEIP2929Op(cpt: VmCpt): EvmResultVoid =
cpt.memory.writePadded(code.bytes(), memPos, codePos, len)
ok()

proc extCodeCopyEIP7907Op(cpt: VmCpt): EvmResultVoid =
## 0x3c, Copy an account's code to memory (EIP-2929).
? cpt.stack.lsCheck(4)
let
address = cpt.stack.lsPeekAddress(^1)
memPos = cpt.stack.lsPeekMemRef(^2)
codePos = cpt.stack.lsPeekMemRef(^3)
len = cpt.stack.lsPeekMemRef(^4)
gasCost = cpt.gasCosts[ExtCodeCopy].m_handler(cpt.memory.len, memPos, len) +
cpt.gasEip2929AccountCheck(address) + cpt.gasCallEIP7907(address)

cpt.stack.lsShrink(4)
? cpt.opcodeGasCost(ExtCodeCopy, gasCost, reason = "ExtCodeCopy EIP7907")

let code = cpt.getCode(address)
cpt.memory.writePadded(code.bytes(), memPos, codePos, len)
ok()

# -----------

func returnDataSizeOp(cpt: VmCpt): EvmResultVoid =
Expand Down Expand Up @@ -345,11 +363,18 @@ const


(opCode: ExtCodeCopy, ## 0x3c, Account Code-copy for Berlin through Cancun
forks: VmOpBerlinAndLater,
forks: VmOpBerlinAndLater - VmOpOsakaAndLater,
name: "extCodeCopyEIP2929",
info: "EIP2929: Copy an account's code to memory",
exec: extCodeCopyEIP2929Op),

(opCode: ExtCodeCopy, ## 0x3c, Account Code-copy for Berlin through Cancun
forks: VmOpOsakaAndLater,
name: "extCodeCopyEIP7907",
info: "EIP7907: Copy an account's code to memory",
exec: extCodeCopyEIP7907Op),



(opCode: ReturnDataSize, ## 0x3d, Previous call output data size
forks: VmOpByzantiumAndLater,
Expand Down
14 changes: 14 additions & 0 deletions execution_chain/evm/interpreter/op_handlers/oph_helpers.nim
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,9 @@
{.push raises: [].}

import
../../../constants,
../../evm_errors,
../../interpreter/utils/utils_numeric,
../../types,
../gas_costs,
eth/common/[addresses, base],
Expand Down Expand Up @@ -59,6 +61,18 @@ proc delegateResolutionCost*(c: Computation, address: Address): GasInt =
else:
return WarmStorageReadCost

proc gasCallEIP7907*(c: Computation, codeAddress: Address): GasInt =
c.vmState.mutateLedger:

if not db.inCodeAccessList(codeAddress):
db.codeAccessList(codeAddress)

let
code = db.getCode(codeAddress)
excessContractSize = max(0, code.len - CODE_SIZE_THRESHOLD)
largeContractCost = (ceil32(excessContractSize) * 2) div 32
return GasInt(largeContractCost)

# ------------------------------------------------------------------------------
# End
# ------------------------------------------------------------------------------
Loading