Transaction: Unified runner `runComputation` for all EVM call types

New entry point `runComputation`, for all EVM calls.
(Later the intent is `runComputationAsync`.)

As noted in commit 297d789, there are six entry points calling EVM computation,
with different parameters and expecting different behaviours.  Parameters were
dealt with in `setupComputation`.  Behaviours are unified in `runComputation`,
with options passed via `CallParams`.

This code performs the steps used when validating a transaction.  Options for
non-standard behaviour for RPC, GraphQL and tests to be added as required.

This replaces `setupComputation`, `execComputation` and `executeOpcodes`
(other than its own calls).  As a result `Computation` and other EVM types are
no longer referenced in the main program, and many imports can be dropped.

Signed-off-by: Jamie Lokier <jamie@shareable.org>
This commit is contained in:
Jamie Lokier 2021-05-17 13:54:17 +01:00
parent 94f95efd9e
commit c6d50a0ef7
No known key found for this signature in database
GPG Key ID: CBC25C68435C30A2
1 changed files with 54 additions and 3 deletions

View File

@ -27,6 +27,18 @@ type
value*: HostValue # Value sent from sender to recipient. value*: HostValue # Value sent from sender to recipient.
input*: seq[byte] # Input data. input*: seq[byte] # Input data.
# 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).
proc hostToComputationMessage(msg: EvmcMessage): Message = proc hostToComputationMessage(msg: EvmcMessage): Message =
Message( Message(
kind: CallKind(msg.kind), kind: CallKind(msg.kind),
@ -42,7 +54,7 @@ proc hostToComputationMessage(msg: EvmcMessage): Message =
flags: if msg.isStatic: emvcStatic else: emvcNoFlags flags: if msg.isStatic: emvcStatic else: emvcNoFlags
) )
proc setupCall(call: CallParams): TransactionHost = proc setupCall(call: CallParams, useIntrinsic: bool): TransactionHost =
let vmState = call.vmState let vmState = call.vmState
vmState.setupTxContext( vmState.setupTxContext(
origin = call.origin.get(call.sender), origin = call.origin.get(call.sender),
@ -50,13 +62,19 @@ proc setupCall(call: CallParams): TransactionHost =
forkOverride = call.forkOverride forkOverride = call.forkOverride
) )
var intrinsicGas: GasInt = 0
if useIntrinsic:
intrinsicGas = intrinsicGas(call.input, vmState.fork)
if call.isCreate:
intrinsicGas += gasFees[vmState.fork][GasTXCreate]
let host = TransactionHost( let host = TransactionHost(
vmState: vmState, vmState: vmState,
msg: EvmcMessage( msg: EvmcMessage(
kind: if call.isCreate: EVMC_CREATE else: EVMC_CALL, kind: if call.isCreate: EVMC_CREATE else: EVMC_CALL,
# Default: flags: {}, # Default: flags: {},
# Default: depth: 0, # Default: depth: 0,
gas: call.gasLimit, gas: call.gasLimit - intrinsicGas,
destination: call.to.toEvmc, destination: call.to.toEvmc,
sender: call.sender.toEvmc, sender: call.sender.toEvmc,
value: call.value.toEvmc, value: call.value.toEvmc,
@ -76,4 +94,37 @@ proc setupCall(call: CallParams): TransactionHost =
return host return host
proc setupComputation*(call: CallParams): Computation = proc setupComputation*(call: CallParams): Computation =
return setupCall(call).computation return setupCall(call, false).computation
proc runComputation*(call: CallParams): CallResult =
let host = setupCall(call, true)
let c = host.computation
# Charge for gas.
host.vmState.mutateStateDB:
db.subBalance(call.sender, call.gasLimit.u256 * call.gasPrice.u256)
execComputation(c)
# Calculated gas used, taking into account refund rules.
var gasRemaining: GasInt = 0
if not c.shouldBurnGas:
let maxRefund = (call.gasLimit - c.gasMeter.gasRemaining) div 2
let refund = min(c.getGasRefund(), maxRefund)
c.gasMeter.returnGas(refund)
gasRemaining = c.gasMeter.gasRemaining
# Refund for unused gas.
if gasRemaining > 0:
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