From 9c6691b09ae3b1201d2a24cc77b842e2af67d5f9 Mon Sep 17 00:00:00 2001 From: jangko Date: Wed, 11 Dec 2024 17:30:56 +0700 Subject: [PATCH] Move EVM code initialization outside of newComputation --- nimbus/evm/computation.nim | 48 ++----------- nimbus/evm/evmc_helpers.nim | 12 ++-- .../evm/interpreter/op_handlers/oph_call.nim | 6 +- .../interpreter/op_handlers/oph_create.nim | 53 ++++++++------ nimbus/evm/interpreter_dispatch.nim | 7 ++ nimbus/evm/message.nim | 32 ++++++++- nimbus/evm/state_transactions.nim | 34 --------- nimbus/evm/types.nim | 1 - nimbus/transaction/call_common.nim | 69 +++++-------------- nimbus/transaction/evmc_vm_glue.nim | 9 ++- nimbus/transaction/host_call_nested.nim | 35 ++++++---- nimbus/transaction/host_types.nim | 1 + nimbus/utils/utils.nim | 9 +-- 13 files changed, 132 insertions(+), 184 deletions(-) delete mode 100644 nimbus/evm/state_transactions.nim diff --git a/nimbus/evm/computation.nim b/nimbus/evm/computation.nim index 880b52555f..346ff69ebb 100644 --- a/nimbus/evm/computation.nim +++ b/nimbus/evm/computation.nim @@ -13,7 +13,7 @@ import std/sequtils, ".."/[db/ledger, constants], - "."/[code_stream, memory, message, stack, state], + "."/[code_stream, memory, stack, state], "."/[types], ./interpreter/[gas_meter, gas_costs, op_codes], ./evm_errors, @@ -46,17 +46,6 @@ when defined(evmc_enabled): const evmc_enabled* = defined(evmc_enabled) -# ------------------------------------------------------------------------------ -# Helpers -# ------------------------------------------------------------------------------ - -proc generateContractAddress(c: Computation, salt: ContractSalt): Address = - if c.msg.kind == EVMC_CREATE: - let creationNonce = c.vmState.readOnlyStateDB().getNonce(c.msg.sender) - result = generateAddress(c.msg.sender, creationNonce) - else: - result = generateSafeAddress(c.msg.sender, salt, c.msg.data) - # ------------------------------------------------------------------------------ # Public functions # ------------------------------------------------------------------------------ @@ -259,42 +248,17 @@ template resolveCode*(c: Computation, address: Address): CodeBytesRef = else: c.vmState.readOnlyStateDB.resolveCode(address) -proc newComputation*(vmState: BaseVMState, sysCall: bool, message: Message, - isPrecompile, keepStack: bool, salt: ContractSalt = ZERO_CONTRACTSALT): Computation = - new result - result.vmState = vmState - result.msg = message - result.gasMeter.init(message.gas) - result.sysCall = sysCall - result.keepStack = keepStack - - if not isPrecompile: - result.memory = EvmMemory.init() - result.stack = EvmStack.init() - - if result.msg.isCreate(): - result.msg.contractAddress = result.generateContractAddress(salt) - result.code = CodeStream.init(message.data) - message.data = @[] - else: - if vmState.fork >= FkPrague: - result.code = CodeStream.init( - vmState.readOnlyStateDB.resolveCode(message.codeAddress)) - else: - result.code = CodeStream.init( - vmState.readOnlyStateDB.getCode(message.codeAddress)) - - -func newComputation*(vmState: BaseVMState, sysCall: bool, - message: Message, code: CodeBytesRef, isPrecompile, keepStack: bool, ): Computation = +func newComputation*(vmState: BaseVMState, + keepStack: bool, + message: Message, + code = CodeBytesRef(nil)): Computation = new result result.vmState = vmState result.msg = message result.gasMeter.init(message.gas) - result.sysCall = sysCall result.keepStack = keepStack - if not isPrecompile: + if not code.isNil: result.code = CodeStream.init(code) result.memory = EvmMemory.init() result.stack = EvmStack.init() diff --git a/nimbus/evm/evmc_helpers.nim b/nimbus/evm/evmc_helpers.nim index b1ad443afe..07569f11ce 100644 --- a/nimbus/evm/evmc_helpers.nim +++ b/nimbus/evm/evmc_helpers.nim @@ -24,9 +24,9 @@ func toEvmc*(h: Hash32): evmc_bytes32 {.inline.} = doAssert sizeof(h) == sizeof(evmc_bytes32) evmc_bytes32(bytes: h.data) -func toEvmc*(h: ContractSalt): evmc_bytes32 {.inline.} = +func toEvmc*(h: Bytes32): evmc_bytes32 {.inline.} = doAssert sizeof(h) == sizeof(evmc_bytes32) - cast[evmc_bytes32](h) + evmc_bytes32(bytes: h.data) func toEvmc*(n: UInt256): evmc_uint256be {.inline.} = when evmc_native: @@ -35,9 +35,9 @@ func toEvmc*(n: UInt256): evmc_uint256be {.inline.} = cast[evmc_uint256be](n.toBytesBE) func fromEvmc*(T: type, n: evmc_bytes32): T {.inline.} = - when T is ContractSalt: + when T is Bytes32: doAssert sizeof(n) == sizeof(T) - cast[T](n) + T(n.bytes) elif T is Hash32: Hash32(n.bytes) elif T is UInt256: @@ -63,6 +63,6 @@ when isMainModule: var h = EMPTY_SHA3 var eh = toEvmc(h) assert(h == fromEvmc(Hash32, eh)) - var s = cast[ContractSalt](EMPTY_ROOT_HASH) + var s = Bytes32(EMPTY_ROOT_HASH.data) var es = toEvmc(s) - assert(s == fromEvmc(ContractSalt, es)) + assert(s == fromEvmc(Bytes32, es)) diff --git a/nimbus/evm/interpreter/op_handlers/oph_call.nim b/nimbus/evm/interpreter/op_handlers/oph_call.nim index b269fff3b9..9558f2eb60 100644 --- a/nimbus/evm/interpreter/op_handlers/oph_call.nim +++ b/nimbus/evm/interpreter/op_handlers/oph_call.nim @@ -21,7 +21,6 @@ import ../../../core/eip7702, ../../computation, ../../memory, - ../../precompiles, ../../stack, ../../types, ../gas_costs, @@ -39,6 +38,7 @@ import when not defined(evmc_enabled): import ../../state, + ../../message, ../../../db/ledger else: import @@ -203,9 +203,9 @@ else: # need to provide explicit and for capturing in chainTo proc() # and are provided by value and need not be captured var - precompile = getPrecompile(c.fork, childMsg.codeAddress) + code = getCallCode(c.vmState, childMsg.codeAddress) child = newComputation( - c.vmState, false, childMsg, isPrecompile = precompile.isSome(), keepStack = false) + c.vmState, keepStack = false, childMsg, code) c.chainTo(child): if not child.shouldBurnGas: diff --git a/nimbus/evm/interpreter/op_handlers/oph_create.nim b/nimbus/evm/interpreter/op_handlers/oph_create.nim index 8c1e17b623..ead9dd2771 100644 --- a/nimbus/evm/interpreter/op_handlers/oph_create.nim +++ b/nimbus/evm/interpreter/op_handlers/oph_create.nim @@ -31,15 +31,16 @@ import chronicles, eth/common, eth/common/eth_types, - stew/assign2, stint when not defined(evmc_enabled): import ../../state, + ../../message, ../../../db/ledger else: import + stew/assign2, stew/saturation_arith # ------------------------------------------------------------------------------ @@ -62,12 +63,12 @@ when evmc_enabled: else: proc execSubCreate(c: Computation; childMsg: Message; - salt: ContractSalt = ZERO_CONTRACTSALT) {.raises: [].} = + code: CodeBytesRef) {.raises: [].} = ## Create new VM -- helper for `Create`-like operations # need to provide explicit and for capturing in chainTo proc() var - child = newComputation(c.vmState, false, childMsg, false, false, salt) + child = newComputation(c.vmState, keepStack = false, childMsg, code) c.chainTo(child): if not child.shouldBurnGas: @@ -154,14 +155,19 @@ proc createOp(cpt: VmCpt): EvmResultVoid = ) c.execSubCreate(msg) else: - var childMsg = Message( - kind: EVMC_CREATE, - depth: cpt.msg.depth + 1, - gas: createMsgGas, - sender: cpt.msg.contractAddress, - value: endowment) - assign(childMsg.data, cpt.memory.read(memPos, memLen)) - cpt.execSubCreate(childMsg) + var + childMsg = Message( + kind: EVMC_CREATE, + depth: cpt.msg.depth + 1, + gas: createMsgGas, + sender: cpt.msg.contractAddress, + contractAddress: generateContractAddress( + cpt.vmState, + EVMC_CREATE, + cpt.msg.contractAddress), + value: endowment) + code = CodeBytesRef.init(cpt.memory.read(memPos, memLen)) + cpt.execSubCreate(childMsg, code) ok() # --------------------- @@ -176,7 +182,7 @@ proc create2Op(cpt: VmCpt): EvmResultVoid = memPos = cpt.stack.lsPeekSafeInt(^2) memLen = cpt.stack.lsPeekSafeInt(^3) salt256 = cpt.stack.lsPeekInt(^4) - salt = ContractSalt(bytes: salt256.toBytesBE) + salt = Bytes32(salt256.toBytesBE) cpt.stack.lsShrink(3) cpt.stack.lsTop(0) @@ -237,14 +243,21 @@ proc create2Op(cpt: VmCpt): EvmResultVoid = ) c.execSubCreate(msg) else: - var childMsg = Message( - kind: EVMC_CREATE2, - depth: cpt.msg.depth + 1, - gas: createMsgGas, - sender: cpt.msg.contractAddress, - value: endowment) - assign(childMsg.data, cpt.memory.read(memPos, memLen)) - cpt.execSubCreate(salt = salt, childMsg = childMsg) + var + code = CodeBytesRef.init(cpt.memory.read(memPos, memLen)) + childMsg = Message( + kind: EVMC_CREATE2, + depth: cpt.msg.depth + 1, + gas: createMsgGas, + sender: cpt.msg.contractAddress, + contractAddress: generateContractAddress( + cpt.vmState, + EVMC_CREATE2, + cpt.msg.contractAddress, + salt, + code), + value: endowment) + cpt.execSubCreate(childMsg, code) ok() # ------------------------------------------------------------------------------ diff --git a/nimbus/evm/interpreter_dispatch.nim b/nimbus/evm/interpreter_dispatch.nim index b0ccd06a36..8bd6f6fb39 100644 --- a/nimbus/evm/interpreter_dispatch.nim +++ b/nimbus/evm/interpreter_dispatch.nim @@ -264,6 +264,13 @@ else: c.dispose() c = c.parent +proc postExecComputation*(c: Computation) = + if c.isSuccess: + if c.fork < FkLondon: + # EIP-3529: Reduction in refunds + c.refundSelfDestruct() + c.vmState.status = c.isSuccess + # ------------------------------------------------------------------------------ # End # ------------------------------------------------------------------------------ diff --git a/nimbus/evm/message.nim b/nimbus/evm/message.nim index 7d9cc0a2bd..35877b249a 100644 --- a/nimbus/evm/message.nim +++ b/nimbus/evm/message.nim @@ -1,5 +1,5 @@ # Nimbus -# Copyright (c) 2018 Status Research & Development GmbH +# Copyright (c) 2018-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) @@ -8,7 +8,35 @@ # at your option. This file may not be copied, modified, or distributed except # according to those terms. -import ./types +import + ./types, + ./state, + ./code_bytes, + ./precompiles, + ../common/evmforks, + ../utils/utils, + ../db/ledger proc isCreate*(message: Message): bool = message.kind in {EVMC_CREATE, EVMC_CREATE2} + +proc generateContractAddress*(vmState: BaseVMState, + kind: CallKind, + sender: Address, + salt = ZERO_CONTRACTSALT, + code = CodeBytesRef(nil)): Address = + if kind == EVMC_CREATE: + let creationNonce = vmState.readOnlyStateDB().getNonce(sender) + generateAddress(sender, creationNonce) + else: + generateSafeAddress(sender, salt, code.bytes) + +proc getCallCode*(vmState: BaseVMState, codeAddress: Address): CodeBytesRef = + let isPrecompile = getPrecompile(vmState.fork, codeAddress).isSome() + if isPrecompile: + return CodeBytesRef(nil) + + if vmState.fork >= FkPrague: + vmState.readOnlyStateDB.resolveCode(codeAddress) + else: + vmState.readOnlyStateDB.getCode(codeAddress) diff --git a/nimbus/evm/state_transactions.nim b/nimbus/evm/state_transactions.nim deleted file mode 100644 index 135d3e25ab..0000000000 --- a/nimbus/evm/state_transactions.nim +++ /dev/null @@ -1,34 +0,0 @@ -# Nimbus -# Copyright (c) 2018-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. - -import - ../constants, - ./computation, - ./interpreter_dispatch, - ./state, - ./types - -{.push raises: [].} - -proc postExecComputation(c: Computation) = - if c.isSuccess: - if c.fork < FkLondon: - # EIP-3529: Reduction in refunds - c.refundSelfDestruct() - c.vmState.status = c.isSuccess - -proc execComputation*(c: Computation) = - c.execCallOrCreate() - c.postExecComputation() - -template execSysCall*(c: Computation) = - # A syscall to EVM doesn't require - # a pre or post ceremony - c.execCallOrCreate() diff --git a/nimbus/evm/types.nim b/nimbus/evm/types.nim index 665b470356..1f35fcdbff 100644 --- a/nimbus/evm/types.nim +++ b/nimbus/evm/types.nim @@ -94,7 +94,6 @@ type else: parent*, child*: Computation continuation*: proc(): EvmResultVoid {.gcsafe, raises: [].} - sysCall*: bool keepStack*: bool finalStack*: seq[UInt256] diff --git a/nimbus/transaction/call_common.nim b/nimbus/transaction/call_common.nim index db96c0a48b..c84b3f8580 100644 --- a/nimbus/transaction/call_common.nim +++ b/nimbus/transaction/call_common.nim @@ -14,7 +14,7 @@ import results, stew/saturation_arith, ../evm/[types, state], - ../evm/[precompiles, internals], + ../evm/[message, precompiles, internals, interpreter_dispatch], ../db/ledger, ../common/evmforks, ../core/eip4844, @@ -28,9 +28,6 @@ when defined(evmc_enabled): import ../utils/utils, ./host_services -else: - import - ../evm/state_transactions export call_types @@ -146,7 +143,8 @@ proc setupHost(call: CallParams, keepStack: bool): TransactionHost = intrinsicGas = intrinsicGas(call, vmState.fork) let host = TransactionHost( - vmState: vmState, + vmState: vmState, + sysCall: call.sysCall, msg: EvmcMessage( kind: if call.isCreate: EVMC_CREATE else: EVMC_CALL, # Default: flags: {}, @@ -165,45 +163,16 @@ proc setupHost(call: CallParams, keepStack: bool): TransactionHost = vmState.gasRefunded = 0 let gasRefund = if call.sysCall: 0 else: preExecComputation(vmState, call) - let isPrecompile = - not call.isCreate and vmState.fork.getPrecompile(host.msg.code_address.fromEvmc).isSome() - - # Generate new contract address, prepare code, and update message `recipient` - # with the contract address. This differs from the previous Nimbus EVM API. - # Guarded under `evmc_enabled` for now so it doesn't break vm2. - when defined(evmc_enabled): - var code: CodeBytesRef - if call.isCreate: - let sender = call.sender - let contractAddress = - generateAddress(sender, call.vmState.readOnlyStateDB.getNonce(sender)) - host.msg.recipient = contractAddress.toEvmc - host.msg.input_size = 0 - host.msg.input_data = nil - code = CodeBytesRef.init(call.input) - else: - # TODO: Share the underlying data, but only after checking this does not - # cause problems with the database. - if isPrecompile: - code = nil - elif 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 - # remains valid after the end of `call` lifetime. - host.input = call.input - host.msg.input_data = host.input[0].addr - - let - cMsg = hostToComputationMessage(host.msg) - host.computation = newComputation( - vmState, call.sysCall, cMsg, code, isPrecompile = isPrecompile, keepStack = keepStack) - host.code = code + var code: CodeBytesRef + if call.isCreate: + let contractAddress = generateContractAddress(call.vmState, EVMC_CREATE, call.sender) + host.msg.recipient = contractAddress.toEvmc + host.msg.input_size = 0 + host.msg.input_data = nil + code = CodeBytesRef.init(call.input) else: + code = getCallCode(host.vmState, 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 @@ -211,10 +180,11 @@ proc setupHost(call: CallParams, keepStack: bool): TransactionHost = host.input = call.input host.msg.input_data = host.input[0].addr - let - cMsg = hostToComputationMessage(host.msg) - host.computation = newComputation( - vmState, call.sysCall, cMsg, isPrecompile = isPrecompile, keepStack = keepStack) + let + cMsg = hostToComputationMessage(host.msg) + host.computation = newComputation( + vmState, keepStack = keepStack, cMsg, code) + host.code = code host.computation.addRefund(gasRefund) vmState.captureStart(host.computation, call.sender, call.to, @@ -333,9 +303,8 @@ proc runComputation*(call: CallParams, T: type): T = when defined(evmc_enabled): doExecEvmc(host, call) else: - if host.computation.sysCall: - execSysCall(host.computation) - else: - execComputation(host.computation) + host.computation.execCallOrCreate() + if not call.sysCall: + host.computation.postExecComputation() finishRunningComputation(host, call, T) diff --git a/nimbus/transaction/evmc_vm_glue.nim b/nimbus/transaction/evmc_vm_glue.nim index 69b27f0132..9730611321 100644 --- a/nimbus/transaction/evmc_vm_glue.nim +++ b/nimbus/transaction/evmc_vm_glue.nim @@ -11,7 +11,7 @@ import stew/saturation_arith, ./host_types, evmc/evmc, - ".."/[evm/types, evm/computation, evm/state_transactions] + ".."/[evm/types, evm/computation, evm/interpreter_dispatch] proc evmcReleaseResult(result: var evmc_result) {.cdecl.} = dealloc(result.output_data) @@ -39,10 +39,9 @@ proc evmcExecute(vm: ptr evmc_vm, hostInterface: ptr evmc_host_interface, # host.computation = c c.host.init(cast[ptr nimbus_host_interface](hostInterface), hostContext) - if c.sysCall: - execSysCall(c) - else: - execComputation(c) + c.execCallOrCreate() + if not host.sysCall: + c.postExecComputation() # When output size is zero, output data pointer may be null. var output_data: ptr byte diff --git a/nimbus/transaction/host_call_nested.nim b/nimbus/transaction/host_call_nested.nim index dddb29450c..75eda6fdb8 100644 --- a/nimbus/transaction/host_call_nested.nim +++ b/nimbus/transaction/host_call_nested.nim @@ -13,7 +13,7 @@ import stew/ptrops, stew/saturation_arith, stint, - ../evm/[types, precompiles], + ../evm/[types, code_bytes, message], ../evm/interpreter_dispatch, ../utils/utils, "."/[host_types, host_trace] @@ -26,17 +26,22 @@ proc evmcResultRelease(res: var EvmcResult) {.cdecl, gcsafe.} = proc beforeExecCreateEvmcNested(host: TransactionHost, m: EvmcMessage): Computation = # TODO: use evmc_message to avoid copy - let childMsg = Message( - kind: CallKind(m.kind.ord), - depth: m.depth, - gas: GasInt m.gas, - sender: m.sender.fromEvmc, - value: m.value.fromEvmc, - data: @(makeOpenArray(m.input_data, m.input_size.int)) - ) - return newComputation(host.vmState, false, childMsg, isPrecompile = false, - keepStack = false, - cast[ContractSalt](m.create2_salt)) + let + code = CodeBytesRef.init(makeOpenArray(m.input_data, m.input_size.int)) + childMsg = Message( + kind: m.kind, + depth: m.depth, + gas: GasInt m.gas, + sender: m.sender.fromEvmc, + value: m.value.fromEvmc, + contractAddress: generateContractAddress( + host.vmState, + m.kind, + m.sender.fromEvmc, + Bytes32(m.create2_salt.bytes), + code) + ) + newComputation(host.vmState, keepStack = false, childMsg, code) proc afterExecCreateEvmcNested(host: TransactionHost, child: Computation, res: var EvmcResult) {.inline.} = @@ -59,7 +64,7 @@ proc afterExecCreateEvmcNested(host: TransactionHost, child: Computation, proc beforeExecCallEvmcNested(host: TransactionHost, m: EvmcMessage): Computation {.inline.} = let childMsg = Message( - kind: CallKind(m.kind.ord), + kind: m.kind, depth: m.depth, gas: GasInt m.gas, sender: m.sender.fromEvmc, @@ -72,8 +77,8 @@ proc beforeExecCallEvmcNested(host: TransactionHost, data: @(makeOpenArray(m.input_data, m.input_size.int)), flags: m.flags, ) - let isPrecompile = getPrecompile(host.vmState.fork, childMsg.codeAddress).isSome() - newComputation(host.vmState, false, childMsg, isPrecompile = isPrecompile, keepStack = false) + let code = getCallCode(host.vmState, childMsg.codeAddress) + newComputation(host.vmState, keepStack = false, childMsg, code) proc afterExecCallEvmcNested(host: TransactionHost, child: Computation, res: var EvmcResult) {.inline.} = diff --git a/nimbus/transaction/host_types.nim b/nimbus/transaction/host_types.nim index d06e7bf13d..91c661a467 100644 --- a/nimbus/transaction/host_types.nim +++ b/nimbus/transaction/host_types.nim @@ -65,6 +65,7 @@ type depth*: int saveComputation*: seq[Computation] hostInterface*: ptr evmc_host_interface + sysCall*: bool # These versions of `toEvmc` and `fromEvmc` don't flip big/little-endian like # the older functions in `evmc_helpers`. New code only flips with _explicit_ diff --git a/nimbus/utils/utils.nim b/nimbus/utils/utils.nim index 456ba7bed7..2ff8cc311e 100644 --- a/nimbus/utils/utils.nim +++ b/nimbus/utils/utils.nim @@ -80,12 +80,9 @@ func hasBody*(h: Header): bool = func generateAddress*(address: Address, nonce: AccountNonce): Address = result.data[0..19] = keccak256(rlp.encodeList(address, nonce)).data.toOpenArray(12, 31) -type ContractSalt* = object - bytes*: array[32, byte] +const ZERO_CONTRACTSALT* = default(Bytes32) -const ZERO_CONTRACTSALT* = default(ContractSalt) - -func generateSafeAddress*(address: Address, salt: ContractSalt, +func generateSafeAddress*(address: Address, salt: Bytes32, data: openArray[byte]): Address = const prefix = [0xff.byte] let @@ -93,7 +90,7 @@ func generateSafeAddress*(address: Address, salt: ContractSalt, hashResult = withKeccak256: h.update(prefix) h.update(address.data) - h.update(salt.bytes) + h.update(salt.data) h.update(dataHash.data) hashResult.to(Address)