diff --git a/fluffy/tools/eth_data_exporter/parser.nim b/fluffy/tools/eth_data_exporter/parser.nim index 16ff4ce11..49f4ea9e1 100644 --- a/fluffy/tools/eth_data_exporter/parser.nim +++ b/fluffy/tools/eth_data_exporter/parser.nim @@ -118,6 +118,27 @@ proc fromJson*[T: Bytes32 | Hash32](n: JsonNode, name: string, x: var seq[T]) = hexToByteArray(v.getStr(), h.data) x.add h +proc fromJson*(n: JsonNode, name: string, x: var ChainId) = + let node = n[name] + if node.kind == JInt: + x = ChainId(node.getInt) + else: + x = hexToInt(node.getStr(), int).ChainId + +proc parseAuth(n: JsonNode): Authorization = + n.fromJson("chainId", result.chainId) + n.fromJson("address", result.address) + n.fromJson("nonce", result.nonce) + n.fromJson("v", result.v) + n.fromJson("r", result.r) + n.fromJson("s", result.s) + +proc fromJson*(n: JsonNode, name: string, x: var seq[Authorization]) = + let node = n[name] + for v in node: + x = newSeqOfCap[Authorization](node.len) + x.add parseAuth(node) + proc parseBlockHeader*(n: JsonNode): Header = n.fromJson "parentHash", result.parentHash n.fromJson "sha3Uncles", result.ommersHash @@ -184,11 +205,14 @@ proc parseTransaction*(n: JsonNode): Transaction = for acn in accessList: tx.accessList.add parseAccessPair(acn) - if tx.txType >= TxEip4844: + if tx.txType == TxEip4844: n.fromJson "maxFeePerBlobGas", tx.maxFeePerBlobGas - if n.hasKey("versionedHashes") and n["versionedHashes"].kind != JNull: - n.fromJson "versionedHashes", tx.versionedHashes + if n.hasKey("versionedHashes") and n["versionedHashes"].kind != JNull: + n.fromJson "versionedHashes", tx.versionedHashes + + if tx.txType == TxEip7702: + n.fromJson "authorizationList", tx.authorizationList tx diff --git a/nimbus/core/eip7702.nim b/nimbus/core/eip7702.nim new file mode 100644 index 000000000..bf874b418 --- /dev/null +++ b/nimbus/core/eip7702.nim @@ -0,0 +1,64 @@ +# Nimbus +# Copyright (c) 2024 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) +# * MIT license ([LICENSE-MIT](LICENSE-MIT) or +# http://opensource.org/licenses/MIT) +# at your option. This file may not be copied, modified, or distributed except +# according to those terms. + +{.push raises: [].} + +import + ../evm/code_bytes, + results, + stew/assign2, + eth/common/eth_types, + eth/common/eth_types_rlp, + eth/common/keys + +const + DelegationPrefix = [0xef.byte, 0x01, 0x00] + +const + PER_AUTH_BASE_COST* = 2500 + PER_EMPTY_ACCOUNT_COST* = 25000 + +func authority*(auth: Authorization): Opt[Address] = + let sigHash = rlpHashForSigning(auth) + + var bytes: array[65, byte] + assign(bytes.toOpenArray(0, 31), auth.r.toBytesBE()) + assign(bytes.toOpenArray(32, 63), auth.s.toBytesBE()) + bytes[64] = auth.v.byte + + let sig = Signature.fromRaw(bytes).valueOr: + return Opt.none(Address) + + let pubkey = recover(sig, SkMessage(sigHash.data)).valueOr: + return Opt.none(Address) + + ok(pubkey.toCanonicalAddress()) + +func parseDelegation*(code: CodeBytesRef): bool = + if code.len != 23: + return false + + if not code.hasPrefix(DelegationPrefix): + return false + + true + +func addressToDelegation*(auth: Address): array[23, byte] = + assign(result.toOpenArray(0, 2), DelegationPrefix) + assign(result.toOpenArray(3, 22), auth.data) + +func parseDelegationAddress*(code: CodeBytesRef): Opt[Address] = + if code.len != 23: + return Opt.none(Address) + + if not code.hasPrefix(DelegationPrefix): + return Opt.none(Address) + + Opt.some(Address(slice[20](code, 3, 22))) diff --git a/nimbus/core/tx_pool/tx_tasks/tx_classify.nim b/nimbus/core/tx_pool/tx_tasks/tx_classify.nim index d6162a173..377a9ecb1 100644 --- a/nimbus/core/tx_pool/tx_tasks/tx_classify.nim +++ b/nimbus/core/tx_pool/tx_tasks/tx_classify.nim @@ -109,7 +109,7 @@ proc txFeesCovered(xp: TxPoolRef; item: TxItemRef): bool = baseFee = xp.baseFee return false - if item.tx.txType >= TxEip4844: + if item.tx.txType == TxEip4844: let excessBlobGas = xp.excessBlobGas blobGasPrice = getBlobBaseFee(excessBlobGas) diff --git a/nimbus/core/validate.nim b/nimbus/core/validate.nim index 9a60ad0fa..79e9ae948 100644 --- a/nimbus/core/validate.nim +++ b/nimbus/core/validate.nim @@ -222,6 +222,9 @@ proc validateTxBasic*( if tx.txType == TxEip4844 and fork < FkCancun: return err("invalid tx: Eip4844 Tx type detected before Cancun") + 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") @@ -252,7 +255,7 @@ proc validateTxBasic*( if not validateEip2930SignatureForm(tx): return err("invalid tx: invalid post EIP-2930 signature form") - if tx.txType >= TxEip4844: + if tx.txType == TxEip4844: if tx.to.isNone: return err("invalid tx: destination must be not empty") @@ -267,6 +270,19 @@ proc validateTxBasic*( return err("invalid tx: one of blobVersionedHash has invalid version. " & &"get={bv.data[0].int}, expect={VERSIONED_HASH_VERSION_KZG.int}") + if tx.txType == TxEip7702: + if tx.authorizationList.len == 0: + return err("invalid tx: authorization list must not empty") + + const SECP256K1halfN = SECPK1_N div 2 + + for auth in tx.authorizationList: + if auth.v > 1'u64: + return err("invalid tx: auth.v must be 0 or 1") + + if auth.s > SECP256K1halfN: + return err("invalid tx: auth.s must be <= SECP256K1N/2") + ok() proc validateTransaction*( @@ -330,7 +346,7 @@ proc validateTransaction*( if codeHash != EMPTY_CODE_HASH: return err(&"invalid tx: sender is not an EOA. sender={sender.toHex}, codeHash={codeHash.data.toHex}") - if tx.txType >= TxEip4844: + if tx.txType == TxEip4844: # ensure that the user was willing to at least pay the current data gasprice let blobGasPrice = getBlobBaseFee(excessBlobGas) if tx.maxFeePerBlobGas < blobGasPrice: diff --git a/nimbus/db/ledger.nim b/nimbus/db/ledger.nim index 0e1f07b03..e145dc431 100644 --- a/nimbus/db/ledger.nim +++ b/nimbus/db/ledger.nim @@ -19,6 +19,7 @@ import ../utils/mergeutils, ../evm/code_bytes, ../stateless/multi_keys, + ../core/eip7702, "/.."/[constants, utils/utils], ./access_list as ac_access_list, "."/[core_db, storage_types, transient_storage], @@ -449,11 +450,16 @@ proc getNonce*(ac: LedgerRef, address: Address): AccountNonce = if acc.isNil: emptyEthAccount.nonce else: acc.statement.nonce -proc getCode*(ac: LedgerRef, address: Address): CodeBytesRef = +proc getCode*(ac: LedgerRef, + address: Address, + returnHash: static[bool] = false): auto = # Always returns non-nil! let acc = ac.getAccount(address, false) if acc.isNil: - return CodeBytesRef() + when returnHash: + return (EMPTY_CODE_HASH, CodeBytesRef()) + else: + return CodeBytesRef() if acc.code == nil: acc.code = @@ -470,7 +476,10 @@ proc getCode*(ac: LedgerRef, address: Address): CodeBytesRef = else: CodeBytesRef() - acc.code + when returnHash: + (acc.statement.codeHash, acc.code) + else: + acc.code proc getCodeSize*(ac: LedgerRef, address: Address): int = let acc = ac.getAccount(address, false) @@ -494,6 +503,24 @@ proc getCodeSize*(ac: LedgerRef, address: Address): int = acc.code.len() +proc resolveCodeHash*(ac: LedgerRef, address: Address): Hash32 = + let (codeHash, code) = ac.getCode(address, true) + let delegateTo = parseDelegationAddress(code).valueOr: + return codeHash + ac.getCodeHash(delegateTo) + +proc resolveCode*(ac: LedgerRef, address: Address): CodeBytesRef = + let code = ac.getCode(address) + let delegateTo = parseDelegationAddress(code).valueOr: + return code + ac.getCode(delegateTo) + +proc resolveCodeSize*(ac: LedgerRef, address: Address): int = + let code = ac.getCode(address) + let delegateTo = parseDelegationAddress(code).valueOr: + return code.len + ac.getCodeSize(delegateTo) + proc getCommittedStorage*(ac: LedgerRef, address: Address, slot: UInt256): UInt256 = let acc = ac.getAccount(address, false) if acc.isNil: @@ -923,6 +950,9 @@ proc getTransientStorage*(db: ReadOnlyStateDB, address: Address, slot: UInt256): UInt256 = getTransientStorage(distinctBase db, address, slot) proc getAccountProof*(db: ReadOnlyStateDB, address: Address): seq[seq[byte]] = getAccountProof(distinctBase db, address) proc getStorageProof*(db: ReadOnlyStateDB, address: Address, slots: openArray[UInt256]): seq[seq[seq[byte]]] = getStorageProof(distinctBase db, address, slots) +proc resolveCodeHash*(db: ReadOnlyStateDB, address: Address): Hash32 = resolveCodeHash(distinctBase db, address) +proc resolveCode*(db: ReadOnlyStateDB, address: Address): CodeBytesRef = resolveCode(distinctBase db, address) +proc resolveCodeSize*(db: ReadOnlyStateDB, address: Address): int = resolveCodeSize(distinctBase db, address) # ------------------------------------------------------------------------------ # End diff --git a/nimbus/evm/code_bytes.nim b/nimbus/evm/code_bytes.nim index 46ba0e38e..aec300d28 100644 --- a/nimbus/evm/code_bytes.nim +++ b/nimbus/evm/code_bytes.nim @@ -5,7 +5,11 @@ # * MIT license ([LICENSE-MIT](LICENSE-MIT) or http://opensource.org/licenses/MIT) # at your option. This file may not be copied, modified, or distributed except according to those terms. -import stew/byteutils, results, ./interpreter/op_codes +import + stew/byteutils, + stew/assign2, + results, + ./interpreter/op_codes export results @@ -77,3 +81,14 @@ func isValidOpcode*(c: CodeBytesRef, position: int): bool = func `==`*(a: CodeBytesRef, b: openArray[byte]): bool = a.bytes == b + +func hasPrefix*(a: CodeBytesRef, b: openArray[byte]): bool = + if b.len > a.bytes.len: + return false + for i in 0..= FkPrague: + result.code = CodeStream.init( + vmState.readOnlyStateDB.resolveCode(message.codeAddress)) + else: + result.code = CodeStream.init( + vmState.readOnlyStateDB.getCode(message.codeAddress)) + assign(result.authorizationList, authorizationList) func newComputation*(vmState: BaseVMState, sysCall: bool, - message: Message, code: CodeBytesRef): Computation = + message: Message, code: CodeBytesRef, + authorizationList: openArray[Authorization] = []): Computation = new result result.vmState = vmState result.msg = message @@ -254,6 +285,7 @@ func newComputation*(vmState: BaseVMState, sysCall: bool, result.gasMeter.init(message.gas) result.code = CodeStream.init(code) result.sysCall = sysCall + assign(result.authorizationList, authorizationList) template gasCosts*(c: Computation): untyped = c.vmState.gasCosts diff --git a/nimbus/evm/interpreter/gas_costs.nim b/nimbus/evm/interpreter/gas_costs.nim index 8349c6286..b71864b1a 100644 --- a/nimbus/evm/interpreter/gas_costs.nim +++ b/nimbus/evm/interpreter/gas_costs.nim @@ -68,7 +68,7 @@ type kind*: Op isNewAccount*: bool gasLeft*: GasInt - gasCallEIP2929*: GasInt + gasCallEIPs*: GasInt contractGas*: UInt256 currentMemSize*: GasNatural memOffset*: GasNatural @@ -375,9 +375,9 @@ template gasCosts(fork: EVMFork, prefix, ResultGasCostsName: untyped) = # Both gasCost and childGasLimit are always on positive side var gasLeft = params.gasLeft - if gasLeft < params.gasCallEIP2929: + if gasLeft < params.gasCallEIPs: return err(opErr(OutOfGas)) - gasLeft -= params.gasCallEIP2929 + gasLeft -= params.gasCallEIPs var gasCost: GasInt = `prefix gasMemoryExpansion`( params.currentMemSize, @@ -422,11 +422,11 @@ template gasCosts(fork: EVMFork, prefix, ResultGasCostsName: untyped) = return err(gasErr(GasIntOverflow)) childGasLimit = params.contractGas.truncate(GasInt) - if gasCost.u256 + childGasLimit.u256 + params.gasCallEIP2929.u256 > high(GasInt).u256: + if gasCost.u256 + childGasLimit.u256 + params.gasCallEIPs.u256 > high(GasInt).u256: return err(gasErr(GasIntOverflow)) gasCost += childGasLimit - gasCost += params.gasCallEIP2929 + gasCost += params.gasCallEIPs # Ccallgas - Gas sent to the child message if not value.isZero and params.kind in {Call, CallCode}: diff --git a/nimbus/evm/interpreter/op_handlers/oph_call.nim b/nimbus/evm/interpreter/op_handlers/oph_call.nim index 82f4b62f8..cc49d86c5 100644 --- a/nimbus/evm/interpreter/op_handlers/oph_call.nim +++ b/nimbus/evm/interpreter/op_handlers/oph_call.nim @@ -18,6 +18,7 @@ import ../../../constants, ../../evm_errors, ../../../common/evmforks, + ../../../core/eip7702, ../../computation, ../../memory, ../../stack, @@ -59,8 +60,20 @@ type memOffset: int memLength: int contractAddress: Address - gasCallEIP2929: GasInt + gasCallEIPs: GasInt +proc gasCallEIP2929(c: Computation, address: Address): GasInt = + when evmc_enabled: + if c.host.accessAccount(address) == EVMC_ACCESS_COLD: + return ColdAccountAccessCost - WarmStorageReadCost + else: + c.vmState.mutateStateDB: + if not db.inAccessList(address): + db.accessList(address) + + # The WarmStorageReadCostEIP2929 (100) is already deducted in + # the form of a constant `gasCall` + return ColdAccountAccessCost - WarmStorageReadCost proc updateStackAndParams(q: var LocalParams; c: Computation) = c.stack.lsTop(0) @@ -81,17 +94,12 @@ proc updateStackAndParams(q: var LocalParams; c: Computation) = # because it will affect `c.gasMeter.gasRemaining` # and further `childGasLimit` if FkBerlin <= c.fork: - when evmc_enabled: - if c.host.accessAccount(q.codeAddress) == EVMC_ACCESS_COLD: - q.gasCallEIP2929 = ColdAccountAccessCost - WarmStorageReadCost - else: - c.vmState.mutateStateDB: - if not db.inAccessList(q.codeAddress): - db.accessList(q.codeAddress) - - # The WarmStorageReadCostEIP2929 (100) is already deducted in - # the form of a constant `gasCall` - q.gasCallEIP2929 = ColdAccountAccessCost - WarmStorageReadCost + q.gasCallEIPs = gasCallEIP2929(c, q.codeAddress) + + if FkPrague <= c.fork: + let delegateTo = parseDelegationAddress(c.getCode(q.codeAddress)) + if delegateTo.isSome: + q.gasCallEIPs += gasCallEIP2929(c, delegateTo[]) proc callParams(c: Computation): EvmResult[LocalParams] = ## Helper for callOp() @@ -228,7 +236,7 @@ proc callOp(cpt: VmCpt): EvmResultVoid = kind: Call, isNewAccount: not cpt.accountExists(p.contractAddress), gasLeft: cpt.gasMeter.gasRemaining, - gasCallEIP2929: p.gasCallEIP2929, + gasCallEIPs: p.gasCallEIPs, contractGas: p.gas, currentMemSize: cpt.memory.len, memOffset: p.memOffset, @@ -300,7 +308,7 @@ proc callCodeOp(cpt: VmCpt): EvmResultVoid = kind: CallCode, isNewAccount: not cpt.accountExists(p.contractAddress), gasLeft: cpt.gasMeter.gasRemaining, - gasCallEIP2929: p.gasCallEIP2929, + gasCallEIPs: p.gasCallEIPs, contractGas: p.gas, currentMemSize: cpt.memory.len, memOffset: p.memOffset, @@ -373,7 +381,7 @@ proc delegateCallOp(cpt: VmCpt): EvmResultVoid = kind: DelegateCall, isNewAccount: not cpt.accountExists(p.contractAddress), gasLeft: cpt.gasMeter.gasRemaining, - gasCallEIP2929: p.gasCallEIP2929, + gasCallEIPs: p.gasCallEIPs, contractGas: p.gas, currentMemSize: cpt.memory.len, memOffset: p.memOffset, @@ -440,7 +448,7 @@ proc staticCallOp(cpt: VmCpt): EvmResultVoid = kind: StaticCall, isNewAccount: not cpt.accountExists(p.contractAddress), gasLeft: cpt.gasMeter.gasRemaining, - gasCallEIP2929: p.gasCallEIP2929, + gasCallEIPs: p.gasCallEIPs, contractGas: p.gas, currentMemSize: cpt.memory.len, memOffset: p.memOffset, diff --git a/nimbus/evm/interpreter/op_handlers/oph_defs.nim b/nimbus/evm/interpreter/op_handlers/oph_defs.nim index 7dce2a1ac..ff545500a 100644 --- a/nimbus/evm/interpreter/op_handlers/oph_defs.nim +++ b/nimbus/evm/interpreter/op_handlers/oph_defs.nim @@ -82,6 +82,9 @@ const VmOpCancunAndLater* = VmOpShanghaiAndLater - {FkShanghai} + VmOpPragueAndLater* = + VmOpCancunAndLater - {FkCancun} + # ------------------------------------------------------------------------------ # End # ------------------------------------------------------------------------------ diff --git a/nimbus/evm/interpreter/op_handlers/oph_envinfo.nim b/nimbus/evm/interpreter/op_handlers/oph_envinfo.nim index 40e40e62e..63566174c 100644 --- a/nimbus/evm/interpreter/op_handlers/oph_envinfo.nim +++ b/nimbus/evm/interpreter/op_handlers/oph_envinfo.nim @@ -146,7 +146,7 @@ proc extCodeSizeOp(cpt: VmCpt): EvmResultVoid = cpt.stack.unaryAddress(ecs256) proc extCodeSizeEIP2929Op(cpt: VmCpt): EvmResultVoid = - ## 0x3b, Get size of an account's code + ## 0x3b, Get size of an account's code (EIP-2929) template ecsEIP2929(address): auto = let gasCost = cpt.gasEip2929AccountCheck(address) ? cpt.opcodeGasCost(ExtCodeSize, gasCost, reason = "ExtCodeSize EIP2929") @@ -154,6 +154,16 @@ proc extCodeSizeEIP2929Op(cpt: VmCpt): EvmResultVoid = cpt.stack.unaryAddress(ecsEIP2929) +proc extCodeSizeEIP7702Op(cpt: VmCpt): EvmResultVoid = + ## 0x3b, Get size of an account's code (EIP-7702) + template ecsEIP7702(address): auto = + let gasCost = cpt.gasEip2929AccountCheck(address) + + cpt.gasEip7702CodeCheck(address) + ? cpt.opcodeGasCost(ExtCodeSize, gasCost, reason = "ExtCodeSize EIP7702") + cpt.resolveCodeSize(address) + + cpt.stack.unaryAddress(ecsEIP7702) + # ----------- proc extCodeCopyOp(cpt: VmCpt): EvmResultVoid = @@ -176,7 +186,7 @@ proc extCodeCopyOp(cpt: VmCpt): EvmResultVoid = proc extCodeCopyEIP2929Op(cpt: VmCpt): EvmResultVoid = - ## 0x3c, Copy an account's code to memory. + ## 0x3c, Copy an account's code to memory (EIP-2929). ? cpt.stack.lsCheck(4) let address = cpt.stack.lsPeekAddress(^1) @@ -193,6 +203,25 @@ proc extCodeCopyEIP2929Op(cpt: VmCpt): EvmResultVoid = cpt.memory.writePadded(code.bytes(), memPos, codePos, len) ok() +proc extCodeCopyEIP7702Op(cpt: VmCpt): EvmResultVoid = + ## 0x3c, Copy an account's code to memory (EIP-7702). + ? 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.gasEip7702CodeCheck(address) + + cpt.stack.lsShrink(4) + ? cpt.opcodeGasCost(ExtCodeCopy, gasCost, reason = "ExtCodeCopy EIP7702") + + let code = cpt.resolveCode(address) + cpt.memory.writePadded(code.bytes(), memPos, codePos, len) + ok() + # ----------- proc returnDataSizeOp(cpt: VmCpt): EvmResultVoid = @@ -228,13 +257,22 @@ proc extCodeHashOp(cpt: VmCpt): EvmResultVoid = cpt.stack.unaryAddress(ech256) proc extCodeHashEIP2929Op(cpt: VmCpt): EvmResultVoid = - ## 0x3f, EIP2929: Returns the keccak256 hash of a contract’s code + ## 0x3f, Returns the keccak256 hash of a contract’s code (EIP-2929) template echEIP2929(address): auto = let gasCost = cpt.gasEip2929AccountCheck(address) ? cpt.opcodeGasCost(ExtCodeHash, gasCost, reason = "ExtCodeHash EIP2929") cpt.getCodeHash(address) cpt.stack.unaryAddress(echEIP2929) +proc extCodeHashEIP7702Op(cpt: VmCpt): EvmResultVoid = + ## 0x3f, Returns the keccak256 hash of a contract’s code (EIP-7702) + template echEIP7702(address): auto = + let gasCost = cpt.gasEip2929AccountCheck(address) + + cpt.gasEip7702CodeCheck(address) + ? cpt.opcodeGasCost(ExtCodeHash, gasCost, reason = "ExtCodeHash EIP7702") + cpt.resolveCodeHash(address) + cpt.stack.unaryAddress(echEIP7702) + # ------------------------------------------------------------------------------ # Public, op exec table entries # ------------------------------------------------------------------------------ @@ -334,13 +372,20 @@ const exec: extCodeSizeOp), - (opCode: ExtCodeSize, ## 0x3b, Account code size for Berlin and later - forks: VmOpBerlinAndLater, + (opCode: ExtCodeSize, ## 0x3b, Account code size for Berlin through Cancun + forks: VmOpBerlinAndLater - VmOpPragueAndLater, name: "extCodeSizeEIP2929", info: "EIP2929: Get size of an account's code", exec: extCodeSizeEIP2929Op), + (opCode: ExtCodeSize, ## 0x3b, Account code size for Prague and later + forks: VmOpPragueAndLater, + name: "extCodeSizeEIP7702", + info: "EIP7702: Get size of an account's code", + exec: extCodeSizeEIP7702Op), + + (opCode: ExtCodeCopy, ## 0x3c, Account code copy to memory. forks: VmOpAllForks - VmOpBerlinAndLater, name: "extCodeCopy", @@ -348,13 +393,20 @@ const exec: extCodeCopyOp), - (opCode: ExtCodeCopy, ## 0x3c, Account Code-copy for Berlin and later - forks: VmOpBerlinAndLater, + (opCode: ExtCodeCopy, ## 0x3c, Account Code-copy for Berlin through Cancun + forks: VmOpBerlinAndLater - VmOpPragueAndLater, name: "extCodeCopyEIP2929", info: "EIP2929: Copy an account's code to memory", exec: extCodeCopyEIP2929Op), + (opCode: ExtCodeCopy, ## 0x3c, Account code copy for Prague and later + forks: VmOpPragueAndLater, + name: "extCodeCopyEIP7702", + info: "EIP7702: Copy an account's code to memory", + exec: extCodeCopyEIP7702Op), + + (opCode: ReturnDataSize, ## 0x3d, Previous call output data size forks: VmOpByzantiumAndLater, name: "returnDataSize", @@ -377,11 +429,18 @@ const exec: extCodeHashOp), - (opCode: ExtCodeHash, ## 0x3f, Contract hash for berlin and later - forks: VmOpBerlinAndLater, + (opCode: ExtCodeHash, ## 0x3f, Contract hash for Berlin through Cancun + forks: VmOpBerlinAndLater - VmOpPragueAndLater, name: "extCodeHashEIP2929", info: "EIP2929: Returns the keccak256 hash of a contract’s code", - exec: extCodeHashEIP2929Op)] + exec: extCodeHashEIP2929Op), + + + (opCode: ExtCodeHash, ## 0x3f, Contract hash for Prague and later + forks: VmOpPragueAndLater, + name: "extCodeHashEIP7702", + info: "EIP7702: Returns the keccak256 hash of a contract’s code", + exec: extCodeHashEIP7702Op)] # ------------------------------------------------------------------------------ # End diff --git a/nimbus/evm/interpreter/op_handlers/oph_helpers.nim b/nimbus/evm/interpreter/op_handlers/oph_helpers.nim index f1b3a7ddf..a218d0602 100644 --- a/nimbus/evm/interpreter/op_handlers/oph_helpers.nim +++ b/nimbus/evm/interpreter/op_handlers/oph_helpers.nim @@ -15,6 +15,7 @@ {.push raises: [].} import + ../../../core/eip7702, ../../evm_errors, ../../types, ../gas_costs, @@ -25,6 +26,7 @@ import when defined(evmc_enabled): import ../../evmc_api, + ../../code_bytes, evmc/evmc else: import @@ -72,6 +74,15 @@ func checkInStaticContext*(c: Computation): EvmResultVoid = ok() +proc gasEip7702CodeCheck*(c: Computation; address: Address): GasInt = + let code = when defined(evmc_enabled): + CodeBytesRef.init(c.host.copyCode(address)) + else: + c.vmState.readOnlyStateDB.getCode(address) + let delegateTo = parseDelegationAddress(code).valueOr: + return 0 + c.gasEip2929AccountCheck(delegateTo) + # ------------------------------------------------------------------------------ # End # ------------------------------------------------------------------------------ diff --git a/nimbus/evm/state_transactions.nim b/nimbus/evm/state_transactions.nim index 0ae5b7045..def643081 100644 --- a/nimbus/evm/state_transactions.nim +++ b/nimbus/evm/state_transactions.nim @@ -9,13 +9,15 @@ # according to those terms. import + ../core/eip7702, ../constants, ../db/ledger, ./computation, ./interpreter_dispatch, ./message, ./state, - ./types + ./types, + ./interpreter/gas_meter {.push raises: [].} @@ -25,6 +27,40 @@ proc preExecComputation(c: Computation) = c.vmState.mutateStateDB: db.incNonce(c.msg.sender) + # EIP-7702 + for auth in c.authorizationList: + # 1. Verify the chain id is either 0 or the chain's current ID. + if not(auth.chainId == 0.ChainId or auth.chainId == c.vmState.com.chainId): + continue + + # 2. authority = ecrecover(keccak(MAGIC || rlp([chain_id, address, nonce])), y_parity, r, s] + let authority = authority(auth).valueOr: + continue + + # 3. Add authority to accessed_addresses (as defined in EIP-2929.) + let ledger = c.vmState.stateDB + ledger.accessList(authority) + + # 4. Verify the code of authority is either empty or already delegated. + let code = ledger.getCode(authority) + if code.len > 0: + if not parseDelegation(code): + continue + + # 5. Verify the nonce of authority is equal to nonce. + if ledger.getNonce(authority) != auth.nonce: + continue + + # 6. 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): + c.gasMeter.refundGas(PER_EMPTY_ACCOUNT_COST - PER_AUTH_BASE_COST) + + # 7. Set the code of authority to be 0xef0100 || address. This is a delegation designation. + ledger.setCode(authority, @(addressToDelegation(authority))) + + # 8. Increase the nonce of authority by one. + ledger.setNonce(authority, auth.nonce + 1) + proc postExecComputation(c: Computation) = if c.isSuccess: if c.fork < FkLondon: diff --git a/nimbus/evm/types.nim b/nimbus/evm/types.nim index 7201d5975..464840aef 100644 --- a/nimbus/evm/types.nim +++ b/nimbus/evm/types.nim @@ -93,6 +93,7 @@ type parent*, child*: Computation continuation*: proc(): EvmResultVoid {.gcsafe, raises: [].} sysCall*: bool + authorizationList*: seq[Authorization] Error* = ref object evmcStatus*: evmc_status_code diff --git a/nimbus/transaction/call_common.nim b/nimbus/transaction/call_common.nim index b20e058d2..507ecc569 100644 --- a/nimbus/transaction/call_common.nim +++ b/nimbus/transaction/call_common.nim @@ -120,7 +120,10 @@ proc setupHost(call: CallParams): TransactionHost = else: # TODO: Share the underlying data, but only after checking this does not # cause problems with the database. - code = host.vmState.readOnlyStateDB.getCode(host.msg.code_address.fromEvmc) + if host.vmState.fork >= FkPrague: + code = host.vmState.readOnlyStateDB.resolveCode(host.msg.code_address.fromEvmc) + else: + code = host.vmState.readOnlyStateDB.getCode(host.msg.code_address.fromEvmc) if call.input.len > 0: host.msg.input_size = call.input.len.csize_t # Must copy the data so the `host.msg.input_data` pointer @@ -129,7 +132,8 @@ proc setupHost(call: CallParams): TransactionHost = host.msg.input_data = host.input[0].addr let cMsg = hostToComputationMessage(host.msg) - host.computation = newComputation(vmState, call.sysCall, cMsg, code) + host.computation = newComputation(vmState, call.sysCall, cMsg, code, + authorizationList = call.authorizationList) host.code = code @@ -142,7 +146,8 @@ proc setupHost(call: CallParams): TransactionHost = host.msg.input_data = host.input[0].addr let cMsg = hostToComputationMessage(host.msg) - host.computation = newComputation(vmState, call.sysCall, cMsg) + host.computation = newComputation(vmState, call.sysCall, cMsg, + authorizationList = call.authorizationList) vmState.captureStart(host.computation, call.sender, call.to, call.isCreate, call.input, diff --git a/nimbus/transaction/call_evm.nim b/nimbus/transaction/call_evm.nim index 31e2c052a..87da26cb8 100644 --- a/nimbus/transaction/call_evm.nim +++ b/nimbus/transaction/call_evm.nim @@ -11,6 +11,8 @@ import chronicles, chronos, + eth/common/eth_types_rlp, + stew/assign2, ../evm/[types, state, internals], ../db/ledger, ../transaction, @@ -162,10 +164,13 @@ proc callParamsForTx(tx: Transaction, sender: Address, vmState: BaseVMState, bas input: tx.payload ) if tx.txType > TxLegacy: - result.accessList = tx.accessList + assign(result.accessList, tx.accessList) - if tx.txType >= TxEip4844: - result.versionedHashes = tx.versionedHashes + if tx.txType == TxEip4844: + assign(result.versionedHashes, tx.versionedHashes) + + if tx.txType == TxEip7702: + assign(result.authorizationList, tx.authorizationList) proc callParamsForTest(tx: Transaction, sender: Address, vmState: BaseVMState): CallParams = result = CallParams( @@ -182,10 +187,13 @@ proc callParamsForTest(tx: Transaction, sender: Address, vmState: BaseVMState): noRefund: true, # Don't apply gas refund/burn rule. ) if tx.txType > TxLegacy: - result.accessList = tx.accessList + assign(result.accessList, tx.accessList) + + if tx.txType == TxEip4844: + assign(result.versionedHashes, tx.versionedHashes) - if tx.txType >= TxEip4844: - result.versionedHashes = tx.versionedHashes + if tx.txType == TxEip7702: + assign(result.authorizationList, tx.authorizationList) proc txCallEvm*(tx: Transaction, sender: Address, diff --git a/nimbus/transaction/call_types.nim b/nimbus/transaction/call_types.nim index 0b33a6d30..ba9a3791b 100644 --- a/nimbus/transaction/call_types.nim +++ b/nimbus/transaction/call_types.nim @@ -30,6 +30,7 @@ type input*: seq[byte] # Input data. accessList*: AccessList # EIP-2930 (Berlin) tx access list. versionedHashes*: seq[VersionedHash] # EIP-4844 (Cancun) blob versioned hashes + authorizationList*: seq[Authorization] # EIP-7702 (Prague) authorization list noIntrinsic*: bool # Don't charge intrinsic gas. noAccessList*: bool # Don't initialise EIP-2929 access list. noGasCharge*: bool # Don't charge sender account for gas. diff --git a/nimbus/transaction/host_services.nim b/nimbus/transaction/host_services.nim index e30051d1f..7be5a8dc1 100644 --- a/nimbus/transaction/host_services.nim +++ b/nimbus/transaction/host_services.nim @@ -179,7 +179,10 @@ proc getBalance(host: TransactionHost, address: HostAddress): HostBalance {.show proc getCodeSize(host: TransactionHost, address: HostAddress): HostSize {.show.} = # TODO: Check this `HostSize`, it was copied as `uint` from other code. # Note: Old `evmc_host` uses `getCode(address).len` instead. - host.vmState.readOnlyStateDB.getCodeSize(address).HostSize + if host.vmState.fork >= FkPrague: + host.vmState.readOnlyStateDB.resolveCodeSize(address).HostSize + else: + host.vmState.readOnlyStateDB.getCodeSize(address).HostSize proc getCodeHash(host: TransactionHost, address: HostAddress): HostHash {.show.} = let db = host.vmState.readOnlyStateDB @@ -188,7 +191,10 @@ proc getCodeHash(host: TransactionHost, address: HostAddress): HostHash {.show.} if not db.accountExists(address) or db.isEmptyAccount(address): default(HostHash) else: - db.getCodeHash(address) + if host.vmState.fork >= FkPrague: + db.resolveCodeHash(address) + else: + db.getCodeHash(address) proc copyCode(host: TransactionHost, address: HostAddress, code_offset: HostSize, buffer_data: ptr byte, @@ -205,7 +211,11 @@ proc copyCode(host: TransactionHost, address: HostAddress, # # Note, when there is no code, `getCode` result is empty `seq`. It was `nil` # when the DB was first implemented, due to Nim language changes since then. - let code = host.vmState.readOnlyStateDB.getCode(address) + let code = if host.vmState.fork >= FkPrague: + host.vmState.readOnlyStateDB.resolveCode(address) + else: + host.vmState.readOnlyStateDB.getCode(address) + var safe_len: int = code.len # It's safe to assume >= 0. if code_offset >= safe_len.HostSize: