mirror of
https://github.com/status-im/nimbus-eth1.git
synced 2025-01-12 21:34:33 +00:00
537cac1bf5
This fixes a bug spotted by @mjfh that was introduced by commit 2a7ccceb: try: if not c.continuation.isNil: (c.continuation)() c.continuation = nil c.selectVM(fork) except CatchableError as e: ... The call to `(c.continuation)()` was moved by 2a7ccceb inside the `try` so that, like all the Op functions do already, if the continuation raises, the interpreter's general catch turns the exception into a an error status result. But if the continuation raises an exception, `continuation` is not cleared in the next line, and at the next resumption the continuation is called again. It may loop doing this. This doesn't currently happen because the continuations don't really raise, but it's still a correctness issue. This fix also allows a continuation to spawn a second continuation, if it encounters a second suspension point. This also doesn't happen currently, but the pattern will become useful with async EVM. Signed-off-by: Jamie Lokier <jamie@shareable.org>
410 lines
12 KiB
Nim
410 lines
12 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.suicides = 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)
|
|
)
|
|
|
|
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 isSuicided*(c: Computation, address: EthAddress): bool =
|
|
result = address in c.suicides
|
|
|
|
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.isSuicided(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.suicides.incl child.suicides
|
|
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.suicides.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.suicides.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
|