nimbus-eth1/nimbus/vm/computation.nim
Jamie Lokier b3a788c7ce
Transaction: Move contract address generation outside the EVM
The current EVM generates its own new contract addresses, and this is why there
are separate `msg.contractAddress` and `msg.codeAddress` fields in the
computation start message.

In EVMC, account updates are only allowed on the host side, including contract
generation, and the start message has one destination field, `msg.destination`.
The EVM cannot select addresses, only use them.  It's a sensible design.

The difference makes the current EVM incompatible with EVMC and its message
format, so this patch corrects the difference.  It moves contract address
generation to the host side.  This simplifies the EVM and its API a little.

(As an API change, this is incompatible with vm2, so it's guarded under
`evmc_enabled` to allow vm2 to continue to build and run at this time.  This is
also why there are fewer deletions than would otherwise be expected.)

Signed-off-by: Jamie Lokier <jamie@shareable.org>
2021-06-08 15:36:30 +01:00

428 lines
13 KiB
Nim

# Nimbus
# Copyright (c) 2018 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
chronicles, strformat, macros, options, times,
sets, eth/[common, keys],
../constants, ../errors,
./interpreter/[opcode_values, gas_meter, gas_costs, vm_forks],
./code_stream, ./memory, ./message, ./stack, ./types, ./state,
../db/[accounts_cache, db_chain],
../utils/header, ./precompiles,
./transaction_tracer, ../utils
when defined(chronicles_log_level):
import stew/byteutils
when defined(evmc_enabled):
import evmc/evmc, evmc_helpers, evmc_api, stew/ranges/ptr_arith
logScope:
topics = "vm computation"
const
evmc_enabled* = defined(evmc_enabled)
template getCoinbase*(c: Computation): EthAddress =
when evmc_enabled:
c.host.getTxContext().block_coinbase
else:
c.vmState.coinbase
template getTimestamp*(c: Computation): int64 =
when evmc_enabled:
c.host.getTxContext().block_timestamp
else:
c.vmState.timestamp.toUnix
template getBlockNumber*(c: Computation): Uint256 =
when evmc_enabled:
c.host.getTxContext().block_number.u256
else:
c.vmState.blockNumber.blockNumberToVmWord
template getDifficulty*(c: Computation): DifficultyInt =
when evmc_enabled:
Uint256.fromEvmc c.host.getTxContext().block_difficulty
else:
c.vmState.difficulty
template getGasLimit*(c: Computation): GasInt =
when evmc_enabled:
c.host.getTxContext().block_gas_limit.GasInt
else:
c.vmState.gasLimit
template getChainId*(c: Computation): uint =
when evmc_enabled:
Uint256.fromEvmc(c.host.getTxContext().chain_id).truncate(uint)
else:
c.vmState.chaindb.config.chainId.uint
template getOrigin*(c: Computation): EthAddress =
when evmc_enabled:
c.host.getTxContext().tx_origin
else:
c.vmState.txOrigin
template getGasPrice*(c: Computation): GasInt =
when evmc_enabled:
Uint256.fromEvmc(c.host.getTxContext().tx_gas_price).truncate(GasInt)
else:
c.vmState.txGasPrice
template getBlockHash*(c: Computation, blockNumber: Uint256): Hash256 =
when evmc_enabled:
c.host.getBlockHash(blockNumber)
else:
c.vmState.getAncestorHash(blockNumber.vmWordToBlockNumber)
template accountExists*(c: Computation, address: EthAddress): bool =
when evmc_enabled:
c.host.accountExists(address)
else:
if c.fork >= FkSpurious:
not c.vmState.readOnlyStateDB.isDeadAccount(address)
else:
c.vmState.readOnlyStateDB.accountExists(address)
template getStorage*(c: Computation, slot: Uint256): Uint256 =
when evmc_enabled:
c.host.getStorage(c.msg.contractAddress, slot)
else:
c.vmState.readOnlyStateDB.getStorage(c.msg.contractAddress, slot)
template getBalance*(c: Computation, address: EthAddress): Uint256 =
when evmc_enabled:
c.host.getBalance(address)
else:
c.vmState.readOnlyStateDB.getBalance(address)
template getCodeSize*(c: Computation, address: EthAddress): uint =
when evmc_enabled:
c.host.getCodeSize(address)
else:
uint(c.vmState.readOnlyStateDB.getCodeSize(address))
template getCodeHash*(c: Computation, address: EthAddress): Hash256 =
when evmc_enabled:
c.host.getCodeHash(address)
else:
let db = c.vmState.readOnlyStateDB
if not db.accountExists(address) or db.isEmptyAccount(address):
default(Hash256)
else:
db.getCodeHash(address)
template selfDestruct*(c: Computation, address: EthAddress) =
when evmc_enabled:
c.host.selfDestruct(c.msg.contractAddress, address)
else:
c.execSelfDestruct(address)
template getCode*(c: Computation, address: EthAddress): seq[byte] =
when evmc_enabled:
c.host.copyCode(address)
else:
c.vmState.readOnlyStateDB.getCode(address)
proc generateContractAddress(c: Computation, salt: Uint256): EthAddress =
if c.msg.kind == evmcCreate:
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)
import stew/byteutils
proc newComputation*(vmState: BaseVMState, message: Message, salt= 0.u256): Computation =
new result
result.vmState = vmState
result.msg = message
result.memory = Memory()
result.stack = newStack()
result.returnStack = @[]
result.gasMeter.init(message.gas)
result.touchedAccounts = initHashSet[EthAddress]()
result.selfDestructs = initHashSet[EthAddress]()
if result.msg.isCreate():
result.msg.contractAddress = result.generateContractAddress(salt)
result.code = newCodeStream(message.data)
message.data = @[]
else:
result.code = newCodeStream(vmState.readOnlyStateDb.getCode(message.codeAddress))
when evmc_enabled:
result.host.init(
nim_host_get_interface(),
cast[evmc_host_context](result)
)
proc newComputation*(vmState: BaseVMState, message: Message, code: seq[byte]): Computation =
new result
result.vmState = vmState
result.msg = message
result.memory = Memory()
result.stack = newStack()
result.returnStack = @[]
result.gasMeter.init(message.gas)
result.touchedAccounts = initHashSet[EthAddress]()
result.selfDestructs = initHashSet[EthAddress]()
result.code = newCodeStream(code)
when evmc_enabled:
result.host.init(
nim_host_get_interface(),
cast[evmc_host_context](result)
)
template gasCosts*(c: Computation): untyped =
c.vmState.gasCosts
template fork*(c: Computation): untyped =
c.vmState.fork
proc isOriginComputation*(c: Computation): bool =
# Is this computation the computation initiated by a transaction
c.msg.sender == c.vmState.txOrigin
template isSuccess*(c: Computation): bool =
c.error.isNil
template isError*(c: Computation): bool =
not c.isSuccess
func shouldBurnGas*(c: Computation): bool =
c.isError and c.error.burnsGas
proc isSelfDestructed*(c: Computation, address: EthAddress): bool =
result = address in c.selfDestructs
proc snapshot*(c: Computation) =
c.savePoint = c.vmState.accountDb.beginSavePoint()
proc commit*(c: Computation) =
c.vmState.accountDb.commit(c.savePoint)
proc dispose*(c: Computation) {.inline.} =
c.vmState.accountDb.safeDispose(c.savePoint)
c.savePoint = nil
proc rollback*(c: Computation) =
c.vmState.accountDb.rollback(c.savePoint)
proc setError*(c: Computation, msg: string, burnsGas = false) {.inline.} =
c.error = Error(info: msg, burnsGas: burnsGas)
proc writeContract*(c: Computation, fork: Fork): bool {.gcsafe.} =
result = true
let contractCode = c.output
if contractCode.len == 0: return
if fork >= FkSpurious and contractCode.len >= EIP170_CODE_SIZE_LIMIT:
debug "Contract code size exceeds EIP170", limit=EIP170_CODE_SIZE_LIMIT, actual=contractCode.len
return false
let storageAddr = c.msg.contractAddress
if c.isSelfDestructed(storageAddr): return
let gasParams = GasParams(kind: Create, cr_memLength: contractCode.len)
let codeCost = c.gasCosts[Create].c_handler(0.u256, gasParams).gasCost
if c.gasMeter.gasRemaining >= codeCost:
c.gasMeter.consumeGas(codeCost, reason = "Write contract code for CREATE")
c.vmState.mutateStateDb:
db.setCode(storageAddr, contractCode)
result = true
else:
if fork < FkHomestead or fork >= FkByzantium: c.output = @[]
result = false
proc initAddress(x: int): EthAddress {.compileTime.} = result[19] = x.byte
const ripemdAddr = initAddress(3)
proc executeOpcodes*(c: Computation) {.gcsafe.}
proc beforeExecCall(c: Computation) =
c.snapshot()
if c.msg.kind == evmcCall:
c.vmState.mutateStateDb:
db.subBalance(c.msg.sender, c.msg.value)
db.addBalance(c.msg.contractAddress, c.msg.value)
proc afterExecCall(c: Computation) =
## Collect all of the accounts that *may* need to be deleted based on EIP161
## https://github.com/ethereum/EIPs/blob/master/EIPS/eip-161.md
## also see: https://github.com/ethereum/EIPs/issues/716
if c.isError or c.fork >= FKByzantium:
if c.msg.contractAddress == ripemdAddr:
# Special case to account for geth+parity bug
c.vmState.touchedAccounts.incl c.msg.contractAddress
if c.isSuccess:
c.commit()
c.touchedAccounts.incl c.msg.contractAddress
else:
c.rollback()
proc beforeExecCreate(c: Computation): bool =
c.vmState.mutateStateDB:
db.incNonce(c.msg.sender)
# We add this to the access list _before_ taking a snapshot.
# Even if the creation fails, the access-list change should not be rolled back
# EIP2929
if c.fork >= FkBerlin:
db.accessList(c.msg.contractAddress)
c.snapshot()
if c.vmState.readOnlyStateDb().hasCodeOrNonce(c.msg.contractAddress):
c.setError("Address collision when creating contract address={c.msg.contractAddress.toHex}", true)
c.rollback()
return true
c.vmState.mutateStateDb:
db.subBalance(c.msg.sender, c.msg.value)
db.addBalance(c.msg.contractAddress, c.msg.value)
db.clearStorage(c.msg.contractAddress)
if c.fork >= FkSpurious:
# EIP161 nonce incrementation
db.incNonce(c.msg.contractAddress)
return false
proc afterExecCreate(c: Computation) =
if c.isSuccess:
let fork = c.fork
let contractFailed = not c.writeContract(fork)
if contractFailed and fork >= FkHomestead:
c.setError(&"writeContract failed, depth={c.msg.depth}", true)
if c.isSuccess:
c.commit()
else:
c.rollback()
proc beforeExec(c: Computation): bool {.noinline.} =
if not c.msg.isCreate:
c.beforeExecCall()
false
else:
c.beforeExecCreate()
proc afterExec(c: Computation) {.noinline.} =
if not c.msg.isCreate:
c.afterExecCall()
else:
c.afterExecCreate()
template chainTo*(c: Computation, toChild: typeof(c.child), after: untyped) =
c.child = toChild
c.continuation = proc() =
c.continuation = nil
after
when vm_use_recursion:
# Recursion with tiny stack frame per level.
proc execCallOrCreate*(c: Computation) =
defer: c.dispose()
if c.beforeExec():
return
c.executeOpcodes()
while not c.continuation.isNil:
when evmc_enabled:
c.res = c.host.call(c.child[])
else:
execCallOrCreate(c.child)
c.child = nil
c.executeOpcodes()
c.afterExec()
else:
# No actual recursion, but simulate recursion including before/after/dispose.
proc execCallOrCreate*(cParam: Computation) =
var (c, before) = (cParam, true)
defer:
while not c.isNil:
c.dispose()
c = c.parent
while true:
while true:
if before and c.beforeExec():
break
c.executeOpcodes()
if c.continuation.isNil:
c.afterExec()
break
(before, c.child, c, c.parent) = (true, nil.Computation, c.child, c)
if c.parent.isNil:
break
c.dispose()
(before, c.parent, c) = (false, nil.Computation, c.parent)
proc merge*(c, child: Computation) =
c.logEntries.add child.logEntries
c.gasMeter.refundGas(child.gasMeter.gasRefunded)
c.selfDestructs.incl child.selfDestructs
c.touchedAccounts.incl child.touchedAccounts
proc execSelfDestruct*(c: Computation, beneficiary: EthAddress) =
c.vmState.mutateStateDB:
let
localBalance = c.getBalance(c.msg.contractAddress)
beneficiaryBalance = c.getBalance(beneficiary)
# Transfer to beneficiary
db.setBalance(beneficiary, localBalance + beneficiaryBalance)
# Zero the balance of the address being deleted.
# This must come after sending to beneficiary in case the
# contract named itself as the beneficiary.
db.setBalance(c.msg.contractAddress, 0.u256)
trace "SELFDESTRUCT",
contractAddress = c.msg.contractAddress.toHex,
localBalance = localBalance.toString,
beneficiary = beneficiary.toHex
c.touchedAccounts.incl beneficiary
# Register the account to be deleted
c.selfDestructs.incl(c.msg.contractAddress)
proc addLogEntry*(c: Computation, log: Log) {.inline.} =
c.logEntries.add(log)
proc getGasRefund*(c: Computation): GasInt =
if c.isSuccess:
result = c.gasMeter.gasRefunded
proc refundSelfDestruct*(c: Computation) =
let cost = gasFees[c.fork][RefundSelfDestruct]
c.gasMeter.refundGas(cost * c.selfDestructs.len)
proc tracingEnabled*(c: Computation): bool {.inline.} =
EnableTracing in c.vmState.tracer.flags
proc traceOpCodeStarted*(c: Computation, op: Op): int {.inline.} =
c.vmState.tracer.traceOpCodeStarted(c, op)
proc traceOpCodeEnded*(c: Computation, op: Op, lastIndex: int) {.inline.} =
c.vmState.tracer.traceOpCodeEnded(c, op, lastIndex)
proc traceError*(c: Computation) {.inline.} =
c.vmState.tracer.traceError(c)
proc prepareTracer*(c: Computation) {.inline.} =
c.vmState.tracer.prepare(c.msg.depth)
include interpreter_dispatch
when defined(evmc_enabled):
include evmc_host