diff --git a/execution_chain/constants.nim b/execution_chain/constants.nim index a6d0d3762..47face364 100644 --- a/execution_chain/constants.nim +++ b/execution_chain/constants.nim @@ -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 diff --git a/execution_chain/core/validate.nim b/execution_chain/core/validate.nim index a30e8b742..8c0d7d482 100644 --- a/execution_chain/core/validate.nim +++ b/execution_chain/core/validate.nim @@ -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: diff --git a/execution_chain/db/access_list.nim b/execution_chain/db/access_list.nim index 0f14e4287..7e5b902c3 100644 --- a/execution_chain/db/access_list.nim +++ b/execution_chain/db/access_list.nim @@ -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) @@ -21,6 +21,7 @@ type AccessList* = object slots: Table[Address, SlotSet] + codeAddrs: HashSet[Address] # ------------------------------------------------------------------------------ # Private helpers @@ -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() @@ -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): @@ -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: @@ -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( @@ -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 diff --git a/execution_chain/db/ledger.nim b/execution_chain/db/ledger.nim index a97c8bd3f..3037795ce 100644 --- a/execution_chain/db/ledger.nim +++ b/execution_chain/db/ledger.nim @@ -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: @@ -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) diff --git a/execution_chain/evm/computation.nim b/execution_chain/evm/computation.nim index c4f60e4ef..f2cffa2b8 100644 --- a/execution_chain/evm/computation.nim +++ b/execution_chain/evm/computation.nim @@ -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) diff --git a/execution_chain/evm/interpreter/op_handlers/oph_call.nim b/execution_chain/evm/interpreter/op_handlers/oph_call.nim index 6f36fbd20..009710d18 100644 --- a/execution_chain/evm/interpreter/op_handlers/oph_call.nim +++ b/execution_chain/evm/interpreter/op_handlers/oph_call.nim @@ -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)) diff --git a/execution_chain/evm/interpreter/op_handlers/oph_create.nim b/execution_chain/evm/interpreter/op_handlers/oph_create.nim index d021cb982..e80f974dc 100644 --- a/execution_chain/evm/interpreter/op_handlers/oph_create.nim +++ b/execution_chain/evm/interpreter/op_handlers/oph_create.nim @@ -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)) @@ -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, @@ -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)) @@ -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 diff --git a/execution_chain/evm/interpreter/op_handlers/oph_defs.nim b/execution_chain/evm/interpreter/op_handlers/oph_defs.nim index 7319542e0..a41f48425 100644 --- a/execution_chain/evm/interpreter/op_handlers/oph_defs.nim +++ b/execution_chain/evm/interpreter/op_handlers/oph_defs.nim @@ -82,6 +82,13 @@ const VmOpCancunAndLater* = VmOpShanghaiAndLater - {FkShanghai} + VmOpPragueAndLater* = + VmOpCancunAndLater - {FkCancun} + + VmOpOsakaAndLater* = + VmOpPragueAndLater - {FkPrague} + + # ------------------------------------------------------------------------------ # End # ------------------------------------------------------------------------------ diff --git a/execution_chain/evm/interpreter/op_handlers/oph_envinfo.nim b/execution_chain/evm/interpreter/op_handlers/oph_envinfo.nim index b519375a1..aa2d540f4 100644 --- a/execution_chain/evm/interpreter/op_handlers/oph_envinfo.nim +++ b/execution_chain/evm/interpreter/op_handlers/oph_envinfo.nim @@ -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 = @@ -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, diff --git a/execution_chain/evm/interpreter/op_handlers/oph_helpers.nim b/execution_chain/evm/interpreter/op_handlers/oph_helpers.nim index 55473a510..f06026b03 100644 --- a/execution_chain/evm/interpreter/op_handlers/oph_helpers.nim +++ b/execution_chain/evm/interpreter/op_handlers/oph_helpers.nim @@ -15,7 +15,9 @@ {.push raises: [].} import + ../../../constants, ../../evm_errors, + ../../interpreter/utils/utils_numeric, ../../types, ../gas_costs, eth/common/[addresses, base], @@ -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 # ------------------------------------------------------------------------------