2021-05-17 11:39:52 +00:00
|
|
|
# Nimbus - Common entry point to the EVM from all different callers
|
|
|
|
#
|
|
|
|
# Copyright (c) 2018-2021 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
|
|
|
|
eth/common/eth_types, stint, options, stew/ranges/ptr_arith,
|
2021-06-01 10:54:13 +00:00
|
|
|
".."/[vm_types, vm_state, vm_computation, vm_state_transactions],
|
|
|
|
".."/[vm_internals, vm_precompiles, vm_gas_costs],
|
|
|
|
".."/[db/accounts_cache, utils, forks],
|
Transaction: Add "host services", accessors to host state from EVM
This provides "host services", functions provided by the application to an EVM.
They are a key part of EVMC compatibility, but we will switch to using these
with "native" EVM as well.
These are functions like `getStorage`, `setStorage` and `emitLog` for accessing
the account state, because the EVM is not allowed direct access to the database.
This code is adapted from `nimbus/vm/evmc_host.nim` and other places, but there
is more emphasis on being host-side only, no dependency on the EVM or
`Computation` type. It uses `TransactionHost` and types in `host_types`.
These host services have two goals: To be compatible with EVMC, and to be a
good way for the Nimbus EVM to access the data it needs. In our new Nimbus
internal architecture, the EVM will only access the databases and other
application state via these host service functions.
The reason for containing the EVM like this, even "native" EVM, is that having
one good interface to the data makes it a lot easier to change how the database
works, which is on the roadmap.
These functions almost have EVMC signatures, but they are not binary compatible
with EVMC. (Binary compatibility is provided by another module). It would be
fine for Nimbus EVM to call these functions directly when linked directly.
Signed-off-by: Jamie Lokier <jamie@shareable.org>
2021-05-20 19:08:36 +00:00
|
|
|
./host_types, ./host_services
|
2021-05-17 11:39:52 +00:00
|
|
|
|
|
|
|
type
|
|
|
|
# Standard call parameters.
|
|
|
|
CallParams* = object
|
|
|
|
vmState*: BaseVMState # Chain, database, state, block, fork.
|
|
|
|
forkOverride*: Option[Fork] # Default fork is usually correct.
|
|
|
|
origin*: Option[HostAddress] # Default origin is `sender`.
|
|
|
|
gasPrice*: GasInt # Gas price for this call.
|
|
|
|
gasLimit*: GasInt # Maximum gas available for this call.
|
|
|
|
sender*: HostAddress # Sender account.
|
|
|
|
to*: HostAddress # Recipient (ignored when `isCreate`).
|
|
|
|
isCreate*: bool # True if this is a contract creation.
|
|
|
|
value*: HostValue # Value sent from sender to recipient.
|
|
|
|
input*: seq[byte] # Input data.
|
2021-05-26 15:05:18 +00:00
|
|
|
accessList*: AccessList # EIP-2930 (Berlin) tx access list.
|
2021-05-17 14:16:44 +00:00
|
|
|
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.
|
|
|
|
noRefund*: bool # Don't apply gas refund/burn rule.
|
2021-05-17 16:27:20 +00:00
|
|
|
noTransfer*: bool # Don't update balances, nonces, code.
|
2021-05-17 11:39:52 +00:00
|
|
|
|
2021-05-17 12:54:17 +00:00
|
|
|
# Standard call result. (Some fields are beyond what EVMC can return,
|
|
|
|
# and must only be used from tests because they will not always be set).
|
|
|
|
CallResult* = object
|
|
|
|
isError*: bool # True if the call failed.
|
|
|
|
gasUsed*: GasInt # Gas used by the call.
|
|
|
|
contractAddress*: EthAddress # Created account (when `isCreate`).
|
|
|
|
output*: seq[byte] # Output data.
|
|
|
|
logEntries*: seq[Log] # Output logs.
|
|
|
|
stack*: Stack # EVM stack on return (for test only).
|
|
|
|
memory*: Memory # EVM memory on return (for test only).
|
|
|
|
error*: Error # Error if `isError` (for test only).
|
|
|
|
|
2021-05-18 22:53:14 +00:00
|
|
|
proc hostToComputationMessage*(msg: EvmcMessage): Message =
|
2021-05-17 11:39:52 +00:00
|
|
|
Message(
|
|
|
|
kind: CallKind(msg.kind),
|
|
|
|
depth: msg.depth,
|
|
|
|
gas: msg.gas,
|
|
|
|
sender: msg.sender.fromEvmc,
|
|
|
|
contractAddress: msg.destination.fromEvmc,
|
|
|
|
codeAddress: msg.destination.fromEvmc,
|
|
|
|
value: msg.value.fromEvmc,
|
|
|
|
# When input size is zero, input data pointer may be null.
|
|
|
|
data: if msg.input_size <= 0: @[]
|
|
|
|
else: @(makeOpenArray(msg.input_data, msg.input_size.int)),
|
|
|
|
flags: if msg.isStatic: emvcStatic else: emvcNoFlags
|
|
|
|
)
|
|
|
|
|
2021-05-26 15:05:51 +00:00
|
|
|
func intrinsicGas(call: CallParams, fork: Fork): GasInt {.inline.} =
|
|
|
|
# Compute the baseline gas cost for this transaction. This is the amount
|
|
|
|
# of gas needed to send this transaction (but that is not actually used
|
|
|
|
# for computation).
|
|
|
|
var gas = gasFees[fork][GasTransaction]
|
|
|
|
|
|
|
|
# EIP-2 (Homestead) extra intrinsic gas for contract creations.
|
|
|
|
if call.isCreate:
|
|
|
|
gas += gasFees[fork][GasTXCreate]
|
|
|
|
|
|
|
|
# Input data cost, reduced in EIP-2028 (Istanbul).
|
|
|
|
let gasZero = gasFees[fork][GasTXDataZero]
|
|
|
|
let gasNonZero = gasFees[fork][GasTXDataNonZero]
|
|
|
|
for b in call.input:
|
|
|
|
gas += (if b == 0: gasZero else: gasNonZero)
|
|
|
|
|
|
|
|
# EIP-2930 (Berlin) intrinsic gas for transaction access list.
|
|
|
|
if fork >= FkBerlin:
|
|
|
|
for account in call.accessList:
|
|
|
|
gas += ACCESS_LIST_ADDRESS_COST
|
|
|
|
gas += account.storageKeys.len * ACCESS_LIST_STORAGE_KEY_COST
|
|
|
|
return gas
|
|
|
|
|
2021-05-27 07:45:55 +00:00
|
|
|
proc initialAccessListEIP2929(call: CallParams) =
|
|
|
|
# EIP2929 initial access list.
|
|
|
|
let vmState = call.vmState
|
|
|
|
if vmState.fork < FkBerlin:
|
|
|
|
return
|
|
|
|
|
|
|
|
vmState.mutateStateDB:
|
|
|
|
db.accessList(call.sender)
|
|
|
|
# For contract creations the EVM will add the contract address to the
|
|
|
|
# access list itself, after calculating the new contract address.
|
|
|
|
if not call.isCreate:
|
|
|
|
db.accessList(call.to)
|
|
|
|
# TODO: Check this only adds the correct subset of precompiles.
|
|
|
|
for c in activePrecompiles():
|
|
|
|
db.accessList(c)
|
|
|
|
|
2021-05-26 15:05:18 +00:00
|
|
|
# EIP2930 optional access list.
|
|
|
|
for account in call.accessList:
|
|
|
|
db.accessList(account.address)
|
|
|
|
for key in account.storageKeys:
|
|
|
|
db.accessList(account.address, UInt256.fromBytesBE(key))
|
|
|
|
|
2021-05-17 17:01:32 +00:00
|
|
|
proc setupHost(call: CallParams): TransactionHost =
|
2021-05-17 11:39:52 +00:00
|
|
|
let vmState = call.vmState
|
|
|
|
vmState.setupTxContext(
|
|
|
|
origin = call.origin.get(call.sender),
|
|
|
|
gasPrice = call.gasPrice,
|
|
|
|
forkOverride = call.forkOverride
|
|
|
|
)
|
|
|
|
|
2021-05-17 12:54:17 +00:00
|
|
|
var intrinsicGas: GasInt = 0
|
2021-05-17 17:01:32 +00:00
|
|
|
if not call.noIntrinsic:
|
2021-05-26 15:05:51 +00:00
|
|
|
intrinsicGas = intrinsicGas(call, vmState.fork)
|
2021-05-17 12:54:17 +00:00
|
|
|
|
2021-05-17 11:39:52 +00:00
|
|
|
let host = TransactionHost(
|
|
|
|
vmState: vmState,
|
|
|
|
msg: EvmcMessage(
|
|
|
|
kind: if call.isCreate: EVMC_CREATE else: EVMC_CALL,
|
|
|
|
# Default: flags: {},
|
|
|
|
# Default: depth: 0,
|
2021-05-17 12:54:17 +00:00
|
|
|
gas: call.gasLimit - intrinsicGas,
|
2021-05-17 11:39:52 +00:00
|
|
|
destination: call.to.toEvmc,
|
|
|
|
sender: call.sender.toEvmc,
|
|
|
|
value: call.value.toEvmc,
|
|
|
|
)
|
|
|
|
# All other defaults in `TransactionHost` are fine.
|
|
|
|
)
|
|
|
|
|
2021-05-18 13:43:38 +00:00
|
|
|
# Generate new contract address, prepare code, and update message `destination`
|
|
|
|
# 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: seq[byte]
|
|
|
|
if call.isCreate:
|
|
|
|
let sender = call.sender
|
|
|
|
let contractAddress =
|
|
|
|
generateAddress(sender, call.vmState.readOnlyStateDB.getNonce(sender))
|
|
|
|
host.msg.destination = contractAddress.toEvmc
|
|
|
|
host.msg.input_size = 0
|
|
|
|
host.msg.input_data = nil
|
|
|
|
code = call.input
|
|
|
|
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.destination.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, cMsg, code)
|
2021-05-18 22:53:14 +00:00
|
|
|
shallowCopy(host.code, code)
|
2021-05-18 13:43:38 +00:00
|
|
|
|
|
|
|
else:
|
|
|
|
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, cMsg)
|
2021-05-17 11:39:52 +00:00
|
|
|
|
|
|
|
return host
|
|
|
|
|
2021-05-18 22:53:14 +00:00
|
|
|
proc doExec(host: TransactionHost, call: CallParams) =
|
|
|
|
let c = host.computation
|
|
|
|
if call.noTransfer:
|
|
|
|
# TODO: This isn't doing `noTransfer` properly yet, just enough for
|
|
|
|
# fixtures tests.
|
|
|
|
executeOpcodes(c)
|
|
|
|
doAssert c.continuation.isNil
|
|
|
|
doAssert c.child.isNil
|
|
|
|
else:
|
|
|
|
execComputation(c)
|
|
|
|
|
|
|
|
when defined(evmc_enabled):
|
|
|
|
import ./host_services
|
|
|
|
proc doExecEvmc(host: TransactionHost, call: CallParams) =
|
|
|
|
if call.noTransfer:
|
|
|
|
let c = host.computation
|
|
|
|
c.setError("Unable to perform noTransfer computations in EVMC mode", true)
|
|
|
|
else:
|
|
|
|
let callResult = evmcExecComputation(host)
|
|
|
|
|
2021-05-17 12:54:17 +00:00
|
|
|
proc runComputation*(call: CallParams): CallResult =
|
2021-05-17 17:01:32 +00:00
|
|
|
let host = setupHost(call)
|
2021-05-17 12:54:17 +00:00
|
|
|
let c = host.computation
|
|
|
|
|
2021-05-17 17:01:32 +00:00
|
|
|
# Must come after `setupHost` for correct fork.
|
2021-05-17 14:16:44 +00:00
|
|
|
if not call.noAccessList:
|
|
|
|
initialAccessListEIP2929(call)
|
2021-05-27 07:45:55 +00:00
|
|
|
|
2021-05-17 12:54:17 +00:00
|
|
|
# Charge for gas.
|
2021-05-17 14:16:44 +00:00
|
|
|
if not call.noGasCharge:
|
|
|
|
host.vmState.mutateStateDB:
|
|
|
|
db.subBalance(call.sender, call.gasLimit.u256 * call.gasPrice.u256)
|
2021-05-17 12:54:17 +00:00
|
|
|
|
2021-05-18 22:53:14 +00:00
|
|
|
when defined(evmc_enabled):
|
|
|
|
doExecEvmc(host, call)
|
2021-05-17 16:27:20 +00:00
|
|
|
else:
|
2021-05-18 22:53:14 +00:00
|
|
|
doExec(host, call)
|
2021-05-17 12:54:17 +00:00
|
|
|
|
2021-06-28 13:12:14 +00:00
|
|
|
# EIP-3529: Reduction in refunds
|
|
|
|
let MaxRefundQuotient = if host.vmState.fork >= FkLondon:
|
|
|
|
5.GasInt
|
|
|
|
else:
|
|
|
|
2.GasInt
|
|
|
|
|
2021-05-17 12:54:17 +00:00
|
|
|
# Calculated gas used, taking into account refund rules.
|
|
|
|
var gasRemaining: GasInt = 0
|
2021-05-17 14:16:44 +00:00
|
|
|
if call.noRefund:
|
|
|
|
gasRemaining = c.gasMeter.gasRemaining
|
|
|
|
elif not c.shouldBurnGas:
|
2021-06-28 13:12:14 +00:00
|
|
|
let maxRefund = (call.gasLimit - c.gasMeter.gasRemaining) div MaxRefundQuotient
|
2021-05-17 12:54:17 +00:00
|
|
|
let refund = min(c.getGasRefund(), maxRefund)
|
|
|
|
c.gasMeter.returnGas(refund)
|
|
|
|
gasRemaining = c.gasMeter.gasRemaining
|
|
|
|
|
|
|
|
# Refund for unused gas.
|
2021-05-17 14:16:44 +00:00
|
|
|
if gasRemaining > 0 and not call.noGasCharge:
|
2021-05-17 12:54:17 +00:00
|
|
|
host.vmState.mutateStateDB:
|
|
|
|
db.addBalance(call.sender, gasRemaining.u256 * call.gasPrice.u256)
|
|
|
|
|
|
|
|
result.isError = c.isError
|
|
|
|
result.gasUsed = call.gasLimit - gasRemaining
|
|
|
|
shallowCopy(result.output, c.output)
|
|
|
|
result.contractAddress = if call.isCreate: c.msg.contractAddress
|
|
|
|
else: default(HostAddress)
|
|
|
|
shallowCopy(result.logEntries, c.logEntries)
|
|
|
|
result.stack = c.stack
|
|
|
|
result.memory = c.memory
|
|
|
|
result.error = c.error
|