mirror of
https://github.com/status-im/nimbus-eth1.git
synced 2025-01-10 04:15:54 +00:00
fa74dc909e
`c.executeOpcodes` is called by some JSON fixture tests. These tests bypass some of the setup and return, and because of this call, continuations aren't processed either. Opcodes that use continuations will behave incorrectly. The opcodes used in these particular tests don't use continuations currently, so just add some assertions to verify this remains the case. This is only used by local tests, and the call to `c.executeOpcodes` will be replaced by the common entry point (that handles things like this correctly in all cases) so we don't need to spend more time on this. Signed-off-by: Jamie Lokier <jamie@shareable.org>
336 lines
12 KiB
Nim
336 lines
12 KiB
Nim
# Nimbus - Various ways of calling the EVM
|
|
#
|
|
# 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/byteutils,
|
|
".."/[vm_types, vm_types2, vm_state, vm_computation, utils],
|
|
".."/[db/db_chain, config, vm_state_transactions, rpc/hexstrings],
|
|
".."/[db/accounts_cache, transaction, vm_precompiles, vm_gas_costs], eth/trie/db,
|
|
".."/vm_internals
|
|
|
|
type
|
|
RpcCallData* = object
|
|
source*: EthAddress
|
|
to*: EthAddress
|
|
gas*: GasInt
|
|
gasPrice*: GasInt
|
|
value*: UInt256
|
|
data*: seq[byte]
|
|
contractCreation*: bool
|
|
|
|
proc rpcSetupComputation(vmState: BaseVMState, call: RpcCallData,
|
|
gasLimit: GasInt, forkOverride = none(Fork)): Computation =
|
|
vmState.setupTxContext(
|
|
origin = call.source,
|
|
gasPrice = call.gasPrice,
|
|
forkOverride = forkOverride,
|
|
)
|
|
|
|
var msg = Message(
|
|
kind: if call.contractCreation: evmcCreate else: evmcCall,
|
|
depth: 0,
|
|
gas: gasLimit,
|
|
sender: call.source,
|
|
contractAddress:
|
|
if not call.contractCreation:
|
|
call.to
|
|
else:
|
|
generateAddress(call.source, vmState.readOnlyStateDB.getNonce(call.source)),
|
|
codeAddress: call.to,
|
|
value: call.value,
|
|
data: call.data
|
|
)
|
|
|
|
return newComputation(vmState, msg)
|
|
|
|
proc rpcDoCall*(call: RpcCallData, header: BlockHeader, chain: BaseChainDB): HexDataStr =
|
|
# TODO: handle revert and error
|
|
# TODO: handle contract ABI
|
|
var
|
|
# we use current header stateRoot, unlike block validation
|
|
# which use previous block stateRoot
|
|
vmState = newBaseVMState(header.stateRoot, header, chain)
|
|
comp = rpcSetupComputation(vmState, call, call.gas)
|
|
|
|
comp.execComputation()
|
|
result = hexDataStr(comp.output)
|
|
|
|
proc rpcMakeCall*(call: RpcCallData, header: BlockHeader, chain: BaseChainDB): (string, GasInt, bool) =
|
|
# TODO: handle revert
|
|
var
|
|
parent = chain.getBlockHeader(header.parentHash)
|
|
vmState = newBaseVMState(parent.stateRoot, header, chain)
|
|
fork = toFork(chain.config, header.blockNumber)
|
|
comp = rpcSetupComputation(vmState, call, call.gas, some(fork))
|
|
|
|
let gas = comp.gasMeter.gasRemaining
|
|
comp.execComputation()
|
|
return (comp.output.toHex, gas - comp.gasMeter.gasRemaining, comp.isError)
|
|
|
|
func rpcIntrinsicGas(call: RpcCallData, fork: Fork): GasInt =
|
|
var intrinsicGas = call.data.intrinsicGas(fork)
|
|
if call.contractCreation:
|
|
intrinsicGas = intrinsicGas + gasFees[fork][GasTXCreate]
|
|
return intrinsicGas
|
|
|
|
func rpcValidateCall(call: RpcCallData, vmState: BaseVMState, gasLimit: GasInt,
|
|
fork: Fork, intrinsicGas: var GasInt, gasCost: var UInt256): bool =
|
|
# This behaviour matches `validateTransaction`, used by `processTransaction`.
|
|
if vmState.cumulativeGasUsed + gasLimit > vmState.blockHeader.gasLimit:
|
|
return false
|
|
let balance = vmState.readOnlyStateDB.getBalance(call.source)
|
|
gasCost = gasLimit.u256 * call.gasPrice.u256
|
|
if gasCost > balance or call.value > balance - gasCost:
|
|
return false
|
|
intrinsicGas = rpcIntrinsicGas(call, fork)
|
|
if intrinsicGas > gasLimit:
|
|
return false
|
|
return true
|
|
|
|
proc rpcInitialAccessListEIP2929(call: RpcCallData, vmState: BaseVMState, fork: Fork) =
|
|
# EIP2929 initial access list.
|
|
if fork >= FkBerlin:
|
|
vmState.mutateStateDB:
|
|
db.accessList(call.source)
|
|
# For contract creations the EVM will add the contract address to the
|
|
# access list itself, after calculating the new contract address.
|
|
if not call.contractCreation:
|
|
db.accessList(call.to)
|
|
for c in activePrecompiles():
|
|
db.accessList(c)
|
|
|
|
proc rpcEstimateGas*(call: RpcCallData, header: BlockHeader, chain: BaseChainDB, haveGasLimit: bool): GasInt =
|
|
# TODO: handle revert and error
|
|
var
|
|
# we use current header stateRoot, unlike block validation
|
|
# which use previous block stateRoot
|
|
vmState = newBaseVMState(header.stateRoot, header, chain)
|
|
fork = toFork(chain.config, header.blockNumber)
|
|
gasLimit = if haveGasLimit: call.gas else: header.gasLimit - vmState.cumulativeGasUsed
|
|
intrinsicGas: GasInt
|
|
gasCost: UInt256
|
|
|
|
# Nimbus `estimateGas` has historically checked against remaining gas in the
|
|
# current block, balance in the sender account (even if the sender is default
|
|
# account 0x00), and other limits, and returned 0 as the gas estimate if any
|
|
# checks failed. This behaviour came from how it used `processTransaction`
|
|
# which calls `validateTransaction`. For now, keep this behaviour the same.
|
|
# Compare this code with `validateTransaction`.
|
|
#
|
|
# TODO: This historically differs from `rpcDoCall` and `rpcMakeCall`. There
|
|
# are other differences in rpc_utils.nim `callData` too. Are the different
|
|
# behaviours intended, and is 0 the correct return value to mean "not enough
|
|
# gas to start"? Probably not.
|
|
if not rpcValidateCall(call, vmState, gasLimit, fork, intrinsicGas, gasCost):
|
|
return 0
|
|
|
|
var dbTx = chain.db.beginTransaction()
|
|
defer: dbTx.dispose()
|
|
|
|
# TODO: EIP2929 setup also historically differs from `rpcDoCall` and `rpcMakeCall`.
|
|
rpcInitialAccessListEIP2929(call, vmState, fork)
|
|
|
|
# TODO: Deduction of `intrinsicGas` also differs from `rpcDoCall` and `rpcMakeCall`.
|
|
var c = rpcSetupComputation(vmState, call, gasLimit - intrinsicGas, some(fork))
|
|
vmState.mutateStateDB:
|
|
db.subBalance(call.source, gasCost)
|
|
|
|
execComputation(c)
|
|
|
|
if c.shouldBurnGas:
|
|
return gasLimit
|
|
let maxRefund = (gasLimit - c.gasMeter.gasRemaining) div 2
|
|
let refund = min(c.getGasRefund(), maxRefund)
|
|
return gasLimit - c.gasMeter.gasRemaining - refund
|
|
|
|
proc txSetupComputation(tx: Transaction, sender: EthAddress, vmState: BaseVMState, fork: Fork): Computation =
|
|
var gas = tx.gasLimit - tx.intrinsicGas(fork)
|
|
assert gas >= 0
|
|
|
|
vmState.setupTxContext(
|
|
origin = sender,
|
|
gasPrice = tx.gasPrice,
|
|
forkOverride = some(fork)
|
|
)
|
|
|
|
let msg = Message(
|
|
kind: if tx.isContractCreation: evmcCreate else: evmcCall,
|
|
depth: 0,
|
|
gas: gas,
|
|
sender: sender,
|
|
contractAddress: tx.getRecipient(sender),
|
|
codeAddress: tx.to,
|
|
value: tx.value,
|
|
data: tx.payload
|
|
)
|
|
|
|
result = newComputation(vmState, msg)
|
|
doAssert result.isOriginComputation
|
|
|
|
proc txRefundgas(tx: Transaction, sender: EthAddress, c: Computation) =
|
|
let maxRefund = (tx.gasLimit - c.gasMeter.gasRemaining) div 2
|
|
let refund = min(c.getGasRefund(), maxRefund)
|
|
c.gasMeter.returnGas(refund)
|
|
c.vmState.mutateStateDB:
|
|
db.addBalance(sender, c.gasMeter.gasRemaining.u256 * tx.gasPrice.u256)
|
|
|
|
proc txInitialAccessListEIP2929(tx: Transaction, sender: EthAddress, vmState: BaseVMState, fork: Fork) =
|
|
# EIP2929 initial access list.
|
|
if fork >= FkBerlin:
|
|
vmState.mutateStateDB:
|
|
db.accessList(sender)
|
|
# For contract creations the EVM will add the contract address to the
|
|
# access list itself, after calculating the new contract address.
|
|
if not tx.isContractCreation:
|
|
db.accessList(tx.getRecipient(sender))
|
|
for c in activePrecompiles():
|
|
db.accessList(c)
|
|
|
|
# EIP2930 optional access list
|
|
if tx.txType == AccessListTxType:
|
|
for n in tx.accessListTx.accessList:
|
|
db.accessList(n.address)
|
|
for x in n.storageKeys:
|
|
db.accessList(n.address, UInt256.fromBytesBE(x))
|
|
|
|
proc txCallEvm*(tx: Transaction, sender: EthAddress, vmState: BaseVMState, fork: Fork): GasInt =
|
|
txInitialAccessListEIP2929(tx, sender, vmState, fork)
|
|
|
|
var c = txSetupComputation(tx, sender, vmState, fork)
|
|
vmState.mutateStateDB:
|
|
db.subBalance(sender, tx.gasLimit.u256 * tx.gasPrice.u256)
|
|
|
|
execComputation(c)
|
|
|
|
if c.shouldBurnGas:
|
|
return tx.gasLimit
|
|
txRefundGas(tx, sender, c)
|
|
return tx.gasLimit - c.gasMeter.gasRemaining
|
|
|
|
proc asmSetupComputation(tx: Transaction, sender: EthAddress, vmState: BaseVMState,
|
|
data: seq[byte], forkOverride = none(Fork)): Computation =
|
|
doAssert tx.isContractCreation
|
|
|
|
let gasUsed = 0 #tx.payload.intrinsicGas.GasInt + gasFees[fork][GasTXCreate]
|
|
|
|
vmState.setupTxContext(
|
|
origin = sender,
|
|
gasPrice = tx.gasPrice,
|
|
forkOverride = forkOverride,
|
|
)
|
|
|
|
let contractAddress = generateAddress(sender, tx.nonce)
|
|
let msg = Message(
|
|
kind: evmcCall,
|
|
depth: 0,
|
|
gas: tx.gasLimit - gasUsed,
|
|
sender: sender,
|
|
contractAddress: contractAddress,
|
|
codeAddress: contractAddress,
|
|
value: tx.value,
|
|
data: data
|
|
)
|
|
|
|
vmState.mutateStateDb:
|
|
db.setCode(contractAddress, tx.payload)
|
|
|
|
return newComputation(vmState, msg)
|
|
|
|
proc asmSetupComputation(blockNumber: Uint256, chainDB: BaseChainDB, code,
|
|
data: seq[byte], forkOverride = none(Fork)): Computation =
|
|
let
|
|
parentNumber = blockNumber - 1
|
|
parent = chainDB.getBlockHeader(parentNumber)
|
|
header = chainDB.getBlockHeader(blockNumber)
|
|
headerHash = header.blockHash
|
|
body = chainDB.getBlockBody(headerHash)
|
|
vmState = newBaseVMState(parent.stateRoot, header, chainDB)
|
|
|
|
var
|
|
tx = body.transactions[0]
|
|
sender = transaction.getSender(tx)
|
|
|
|
tx.payload = code
|
|
tx.gasLimit = 500000000
|
|
return asmSetupComputation(tx, sender, vmState, data, forkOverride)
|
|
|
|
type
|
|
AsmResult* = object
|
|
isSuccess*: bool
|
|
gasUsed*: GasInt
|
|
output*: seq[byte]
|
|
stack*: Stack
|
|
memory*: Memory
|
|
vmState*: BaseVMState
|
|
contractAddress*: EthAddress
|
|
|
|
proc asmCallEvm*(blockNumber: Uint256, chainDB: BaseChainDB, code, data: seq[byte], fork: Fork): AsmResult =
|
|
var c = asmSetupComputation(blockNumber, chainDB, code, data, some(fork))
|
|
let gas = c.gasMeter.gasRemaining
|
|
c.execComputation()
|
|
|
|
# Some of these are extra returned state, for testing, that a normal EVMC API
|
|
# computation doesn't return. We'll have to obtain them outside EVMC.
|
|
result.isSuccess = c.isSuccess
|
|
result.gasUsed = gas - c.gasMeter.gasRemaining
|
|
result.output = c.output
|
|
result.stack = c.stack
|
|
result.memory = c.memory
|
|
result.vmState = c.vmState
|
|
result.contractAddress = c.msg.contractAddress
|
|
|
|
proc fixtureSetupComputation(vmState: BaseVMState, call: RpcCallData,
|
|
origin: EthAddress, forkOverride = none(Fork)): Computation =
|
|
vmState.setupTxContext(
|
|
origin = origin, # Differs from `rpcSetupComputation`
|
|
gasPrice = call.gasPrice,
|
|
forkOverride = forkOverride,
|
|
)
|
|
|
|
var msg = Message(
|
|
kind: if call.contractCreation: evmcCreate else: evmcCall,
|
|
depth: 0,
|
|
gas: call.gas, # Differs from `rpcSetupComputation`
|
|
sender: call.source,
|
|
contractAddress: call.to, # Differs from `rpcSetupComputation`
|
|
codeAddress: call.to,
|
|
value: call.value,
|
|
data: call.data
|
|
)
|
|
|
|
return newComputation(vmState, msg)
|
|
|
|
type
|
|
FixtureResult* = object
|
|
isError*: bool
|
|
error*: Error
|
|
gasUsed*: GasInt
|
|
output*: seq[byte]
|
|
vmState*: BaseVMState
|
|
logEntries*: seq[Log]
|
|
|
|
proc fixtureCallEvm*(vmState: BaseVMState, call: RpcCallData,
|
|
origin: EthAddress, forkOverride = none(Fork)): FixtureResult =
|
|
var c = fixtureSetupComputation(vmState, call, origin, forkOverride)
|
|
let gas = c.gasMeter.gasRemaining
|
|
|
|
# Next line differs from all the other EVM calls. With `execComputation`,
|
|
# most "vm json tests" fail with either `balanceDiff` or `nonceDiff` errors.
|
|
c.executeOpcodes()
|
|
doAssert c.continuation.isNil
|
|
doAssert c.child.isNil
|
|
|
|
# Some of these are extra returned state, for testing, that a normal EVMC API
|
|
# computation doesn't return. We'll have to obtain them outside EVMC.
|
|
result.isError = c.isError
|
|
result.error = c.error
|
|
result.gasUsed = gas - c.gasMeter.gasRemaining
|
|
result.output = c.output
|
|
result.vmState = c.vmState
|
|
shallowCopy(result.logEntries, c.logEntries)
|