2018-04-06 14:52:10 +00:00
|
|
|
# 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.
|
|
|
|
|
2018-01-16 17:05:20 +00:00
|
|
|
import
|
2018-06-12 15:33:47 +00:00
|
|
|
strformat, strutils, sequtils, macros, stint, terminal, math, eth_common, byteutils, tables,
|
|
|
|
./constants, ./errors, ./utils/hexadecimal, ./utils_numeric, ./validation, ./vm_state, ./logging, ./opcode_values, ./vm_types,
|
|
|
|
./vm/[code_stream, gas_meter, memory, message, stack],
|
2018-05-24 10:01:59 +00:00
|
|
|
|
|
|
|
# TODO further refactoring of gas cost
|
|
|
|
vm/forks/gas_costs,
|
|
|
|
vm/forks/f20150730_frontier/frontier_vm_state,
|
|
|
|
vm/forks/f20161018_tangerine_whistle/tangerine_vm_state
|
2018-01-16 17:05:20 +00:00
|
|
|
|
2018-05-24 10:01:59 +00:00
|
|
|
method newBaseComputation*(vmState: BaseVMState, message: Message): BaseComputation {.base.}=
|
|
|
|
raise newException(ValueError, "Must be implemented by subclasses")
|
|
|
|
|
|
|
|
# TODO refactor that
|
|
|
|
method newBaseComputation*(vmState: FrontierVMState, message: Message): BaseComputation =
|
|
|
|
new(result)
|
|
|
|
result.vmState = vmState
|
|
|
|
result.msg = message
|
|
|
|
result.memory = Memory()
|
|
|
|
result.stack = newStack()
|
|
|
|
result.gasMeter = newGasMeter(message.gas)
|
|
|
|
result.children = @[]
|
2018-05-30 16:11:15 +00:00
|
|
|
result.accountsToDelete = initTable[EthAddress, EthAddress]()
|
2018-05-24 10:01:59 +00:00
|
|
|
result.logEntries = @[]
|
|
|
|
result.code = newCodeStreamFromUnescaped(message.code) # TODO: what is the best repr
|
|
|
|
result.rawOutput = "0x"
|
|
|
|
result.gasCosts = BaseGasCosts
|
|
|
|
|
|
|
|
method newBaseComputation*(vmState: TangerineVMState, message: Message): BaseComputation =
|
2018-01-16 17:05:20 +00:00
|
|
|
new(result)
|
|
|
|
result.vmState = vmState
|
|
|
|
result.msg = message
|
|
|
|
result.memory = Memory()
|
|
|
|
result.stack = newStack()
|
|
|
|
result.gasMeter = newGasMeter(message.gas)
|
|
|
|
result.children = @[]
|
2018-05-30 16:11:15 +00:00
|
|
|
result.accountsToDelete = initTable[EthAddress, EthAddress]()
|
2018-01-16 17:05:20 +00:00
|
|
|
result.logEntries = @[]
|
2018-02-14 16:38:01 +00:00
|
|
|
result.code = newCodeStreamFromUnescaped(message.code) # TODO: what is the best repr
|
|
|
|
result.rawOutput = "0x"
|
2018-05-24 10:01:59 +00:00
|
|
|
result.gasCosts = TangerineGasCosts
|
2018-01-24 13:31:24 +00:00
|
|
|
|
|
|
|
method logger*(computation: BaseComputation): Logger =
|
|
|
|
logging.getLogger("vm.computation.BaseComputation")
|
2018-01-16 17:05:20 +00:00
|
|
|
|
|
|
|
method applyMessage*(c: var BaseComputation): BaseComputation =
|
|
|
|
# Execution of an VM message
|
|
|
|
raise newException(ValueError, "Must be implemented by subclasses")
|
|
|
|
|
|
|
|
method applyCreateMessage(c: var BaseComputation): BaseComputation =
|
|
|
|
# Execution of an VM message to create a new contract
|
|
|
|
raise newException(ValueError, "Must be implemented by subclasses")
|
|
|
|
|
|
|
|
method isOriginComputation*(c: BaseComputation): bool =
|
|
|
|
# Is this computation the computation initiated by a transaction
|
|
|
|
c.msg.isOrigin
|
|
|
|
|
|
|
|
template isSuccess*(c: BaseComputation): bool =
|
|
|
|
c.error.isNil
|
|
|
|
|
|
|
|
template isError*(c: BaseComputation): bool =
|
|
|
|
not c.isSuccess
|
|
|
|
|
|
|
|
method shouldBurnGas*(c: BaseComputation): bool =
|
|
|
|
c.isError and c.error.burnsGas
|
|
|
|
|
|
|
|
method shouldEraseReturnData*(c: BaseComputation): bool =
|
|
|
|
c.isError and c.error.erasesReturnData
|
|
|
|
|
|
|
|
method prepareChildMessage*(
|
|
|
|
c: var BaseComputation,
|
2018-05-25 10:25:19 +00:00
|
|
|
gas: GasInt,
|
2018-05-30 16:11:15 +00:00
|
|
|
to: EthAddress,
|
2018-02-20 17:27:43 +00:00
|
|
|
value: UInt256,
|
2018-01-29 17:40:22 +00:00
|
|
|
data: seq[byte],
|
2018-01-31 12:57:05 +00:00
|
|
|
code: string,
|
2018-01-16 17:05:20 +00:00
|
|
|
options: MessageOptions = newMessageOptions()): Message =
|
|
|
|
|
|
|
|
var childOptions = options
|
2018-01-30 18:12:05 +00:00
|
|
|
childOptions.depth = c.msg.depth + 1
|
2018-01-16 17:05:20 +00:00
|
|
|
result = newMessage(
|
|
|
|
gas,
|
|
|
|
c.msg.gasPrice,
|
|
|
|
c.msg.origin,
|
|
|
|
to,
|
|
|
|
value,
|
|
|
|
data,
|
|
|
|
code,
|
|
|
|
childOptions)
|
|
|
|
|
2018-01-31 12:57:05 +00:00
|
|
|
method output*(c: BaseComputation): string =
|
2018-01-16 17:05:20 +00:00
|
|
|
if c.shouldEraseReturnData:
|
2018-01-31 12:57:05 +00:00
|
|
|
""
|
2018-01-16 17:05:20 +00:00
|
|
|
else:
|
|
|
|
c.rawOutput
|
|
|
|
|
2018-01-31 12:57:05 +00:00
|
|
|
method `output=`*(c: var BaseComputation, value: string) =
|
2018-01-16 17:05:20 +00:00
|
|
|
c.rawOutput = value
|
|
|
|
|
|
|
|
macro generateChildBaseComputation*(t: typed, vmState: typed, childMsg: typed): untyped =
|
|
|
|
var typ = repr(getType(t)[1]).split(":", 1)[0]
|
2018-01-22 22:23:07 +00:00
|
|
|
var name = ident(&"new{typ}")
|
2018-01-16 17:05:20 +00:00
|
|
|
var typName = ident(typ)
|
|
|
|
result = quote:
|
|
|
|
block:
|
|
|
|
var c: `typName`
|
|
|
|
if childMsg.isCreate:
|
|
|
|
var child = `name`(`vmState`, `childMsg`)
|
|
|
|
c = child.applyCreateMessage()
|
2018-01-29 17:40:22 +00:00
|
|
|
else:
|
2018-01-16 17:05:20 +00:00
|
|
|
var child = `name`(`vmState`, `childMsg`)
|
|
|
|
c = child.applyMessage()
|
|
|
|
c
|
|
|
|
|
|
|
|
method addChildBaseComputation*(c: var BaseComputation, childBaseComputation: BaseComputation) =
|
|
|
|
if childBaseComputation.isError:
|
|
|
|
if childBaseComputation.msg.isCreate:
|
|
|
|
c.returnData = childBaseComputation.output
|
|
|
|
elif childBaseComputation.shouldBurnGas:
|
2018-01-31 12:57:05 +00:00
|
|
|
c.returnData = ""
|
2018-01-16 17:05:20 +00:00
|
|
|
else:
|
|
|
|
c.returnData = childBaseComputation.output
|
|
|
|
else:
|
|
|
|
if childBaseComputation.msg.isCreate:
|
2018-01-31 12:57:05 +00:00
|
|
|
c.returnData = ""
|
2018-01-16 17:05:20 +00:00
|
|
|
else:
|
|
|
|
c.returnData = childBaseComputation.output
|
|
|
|
c.children.add(childBaseComputation)
|
|
|
|
|
|
|
|
method applyChildBaseComputation*(c: var BaseComputation, childMsg: Message): BaseComputation =
|
|
|
|
var childBaseComputation = generateChildBaseComputation(c, c.vmState, childMsg)
|
|
|
|
c.addChildBaseComputation(childBaseComputation)
|
|
|
|
result = childBaseComputation
|
|
|
|
|
2018-05-30 16:11:15 +00:00
|
|
|
method registerAccountForDeletion*(c: var BaseComputation, beneficiary: EthAddress) =
|
2018-01-16 17:05:20 +00:00
|
|
|
validateCanonicalAddress(beneficiary, title="self destruct beneficiary address")
|
|
|
|
|
|
|
|
if c.msg.storageAddress in c.accountsToDelete:
|
|
|
|
raise newException(ValueError,
|
|
|
|
"invariant: should be impossible for an account to be " &
|
|
|
|
"registered for deletion multiple times")
|
|
|
|
c.accountsToDelete[c.msg.storageAddress] = beneficiary
|
|
|
|
|
2018-05-30 16:11:15 +00:00
|
|
|
method addLogEntry*(c: var BaseComputation, account: EthAddress, topics: seq[UInt256], data: string) =
|
2018-01-16 17:05:20 +00:00
|
|
|
validateCanonicalAddress(account, title="log entry address")
|
|
|
|
c.logEntries.add((account, topics, data))
|
|
|
|
|
2018-03-13 14:30:38 +00:00
|
|
|
# many methods are basically TODO, but they still return valid values
|
|
|
|
# in order to test some existing code
|
2018-01-31 12:57:05 +00:00
|
|
|
method getAccountsForDeletion*(c: BaseComputation): seq[(string, string)] =
|
2018-01-16 17:05:20 +00:00
|
|
|
# TODO
|
|
|
|
if c.isError:
|
|
|
|
result = @[]
|
|
|
|
else:
|
|
|
|
result = @[]
|
|
|
|
|
2018-02-20 17:27:43 +00:00
|
|
|
method getLogEntries*(c: BaseComputation): seq[(string, seq[UInt256], string)] =
|
2018-01-16 17:05:20 +00:00
|
|
|
# TODO
|
|
|
|
if c.isError:
|
|
|
|
result = @[]
|
|
|
|
else:
|
|
|
|
result = @[]
|
2018-01-29 17:40:22 +00:00
|
|
|
|
2018-05-25 10:25:19 +00:00
|
|
|
method getGasRefund*(c: BaseComputation): GasInt =
|
2018-01-16 17:05:20 +00:00
|
|
|
if c.isError:
|
2018-05-25 10:25:19 +00:00
|
|
|
result = 0
|
2018-01-16 17:05:20 +00:00
|
|
|
else:
|
2018-05-25 10:25:19 +00:00
|
|
|
result = c.gasMeter.gasRefunded + c.children.mapIt(it.getGasRefund()).foldl(a + b, 0'i64)
|
2018-01-16 17:05:20 +00:00
|
|
|
|
2018-05-25 10:25:19 +00:00
|
|
|
method getGasUsed*(c: BaseComputation): GasInt =
|
2018-01-16 17:05:20 +00:00
|
|
|
if c.shouldBurnGas:
|
|
|
|
result = c.msg.gas
|
|
|
|
else:
|
2018-05-25 10:25:19 +00:00
|
|
|
result = max(0, c.msg.gas - c.gasMeter.gasRemaining)
|
2018-01-16 17:05:20 +00:00
|
|
|
|
2018-05-25 10:25:19 +00:00
|
|
|
method getGasRemaining*(c: BaseComputation): GasInt =
|
2018-01-16 17:05:20 +00:00
|
|
|
if c.shouldBurnGas:
|
2018-05-25 10:25:19 +00:00
|
|
|
result = 0
|
2018-01-16 17:05:20 +00:00
|
|
|
else:
|
|
|
|
result = c.gasMeter.gasRemaining
|
|
|
|
|
|
|
|
#
|
|
|
|
# Context Manager API
|
|
|
|
#
|
|
|
|
|
2018-01-17 11:24:09 +00:00
|
|
|
template inComputation*(c: untyped, handler: untyped): untyped =
|
|
|
|
# use similarly to the python manager
|
|
|
|
#
|
|
|
|
# inComputation(computation):
|
|
|
|
# stuff
|
|
|
|
|
2018-01-16 17:05:20 +00:00
|
|
|
`c`.logger.debug(
|
|
|
|
"COMPUTATION STARTING: gas: $1 | from: $2 | to: $3 | value: $4 | depth: $5 | static: $6" % [
|
|
|
|
$`c`.msg.gas,
|
2018-05-30 16:11:15 +00:00
|
|
|
toHex(`c`.msg.sender),
|
|
|
|
toHex(`c`.msg.to),
|
2018-01-16 17:05:20 +00:00
|
|
|
$`c`.msg.value,
|
|
|
|
$`c`.msg.depth,
|
|
|
|
if c.msg.isStatic: "y" else: "n"])
|
|
|
|
try:
|
|
|
|
`handler`
|
|
|
|
c.logger.debug(
|
|
|
|
"COMPUTATION SUCCESS: from: $1 | to: $2 | value: $3 | depth: $4 | static: $5 | gas-used: $6 | gas-remaining: $7" % [
|
2018-05-30 16:11:15 +00:00
|
|
|
toHex(c.msg.sender),
|
|
|
|
toHex(c.msg.to),
|
2018-01-16 17:05:20 +00:00
|
|
|
$c.msg.value,
|
|
|
|
$c.msg.depth,
|
|
|
|
if c.msg.isStatic: "y" else: "n",
|
|
|
|
$(c.msg.gas - c.gasMeter.gasRemaining),
|
2018-01-24 13:31:24 +00:00
|
|
|
$c.gasMeter.gasRemaining])
|
2018-01-16 17:05:20 +00:00
|
|
|
except VMError:
|
|
|
|
`c`.logger.debug(
|
|
|
|
"COMPUTATION ERROR: gas: $1 | from: $2 | to: $3 | value: $4 | depth: $5 | static: $6 | error: $7" % [
|
|
|
|
$`c`.msg.gas,
|
2018-05-30 16:11:15 +00:00
|
|
|
toHex(`c`.msg.sender),
|
|
|
|
toHex(`c`.msg.to),
|
2018-01-16 17:05:20 +00:00
|
|
|
$c.msg.value,
|
|
|
|
$c.msg.depth,
|
|
|
|
if c.msg.isStatic: "y" else: "n",
|
|
|
|
getCurrentExceptionMsg()])
|
|
|
|
`c`.error = Error(info: getCurrentExceptionMsg())
|
|
|
|
if c.shouldBurnGas:
|
|
|
|
c.gasMeter.consumeGas(
|
|
|
|
c.gasMeter.gasRemaining,
|
|
|
|
reason="Zeroing gas due to VM Exception: $1" % getCurrentExceptionMsg())
|
|
|
|
|
2018-01-24 13:31:24 +00:00
|
|
|
method getOpcodeFn*(computation: var BaseComputation, op: Op): Opcode =
|
2018-06-12 15:33:47 +00:00
|
|
|
# TODO use isValidOpcode and remove the Op --> Opcode indirection
|
2018-02-14 16:38:01 +00:00
|
|
|
if computation.opcodes.len > 0 and computation.opcodes.hasKey(op):
|
2018-06-12 15:33:47 +00:00
|
|
|
OpCode(kind: op, runLogic: computation.opcodes[op])
|
2018-01-24 13:31:24 +00:00
|
|
|
else:
|
|
|
|
raise newException(InvalidInstruction,
|
|
|
|
&"Invalid opcode {op}")
|
|
|
|
|
|
|
|
macro applyComputation*(t: typed, vmState: untyped, message: untyped): untyped =
|
|
|
|
# Perform the computation that would be triggered by the VM message
|
|
|
|
# c.applyComputation(vmState, message)
|
2018-01-16 17:05:20 +00:00
|
|
|
var typ = repr(getType(t)[1]).split(":", 1)[0]
|
2018-01-22 22:23:07 +00:00
|
|
|
var name = ident(&"new{typ}")
|
2018-01-16 17:05:20 +00:00
|
|
|
var typName = ident(typ)
|
|
|
|
result = quote:
|
|
|
|
block:
|
|
|
|
var res: `typName`
|
2018-02-14 16:38:01 +00:00
|
|
|
var c = `t` # `name`(`vmState`, `message`)
|
2018-01-16 17:05:20 +00:00
|
|
|
var handler = proc: `typName` =
|
2018-02-13 17:18:08 +00:00
|
|
|
# TODO
|
|
|
|
# if `message`.codeAddress in c.precompiles:
|
|
|
|
# c.precompiles[`message`.codeAddress].run(c)
|
|
|
|
# return c
|
2018-01-16 17:05:20 +00:00
|
|
|
|
2018-01-24 13:31:24 +00:00
|
|
|
for op in c.code:
|
|
|
|
var opcode = c.getOpcodeFn(op)
|
2018-01-16 17:05:20 +00:00
|
|
|
c.logger.trace(
|
2018-01-24 13:31:24 +00:00
|
|
|
"OPCODE: 0x$1 ($2) | pc: $3" % [opcode.kind.int.toHex(2), $opcode.kind, $max(0, c.code.pc - 1)])
|
|
|
|
try:
|
|
|
|
opcode.run(c)
|
|
|
|
except Halt:
|
|
|
|
break
|
2018-02-20 17:27:43 +00:00
|
|
|
c.logger.log($c.stack & "\n\n", fgGreen)
|
2018-01-16 17:05:20 +00:00
|
|
|
return c
|
|
|
|
inComputation(c):
|
|
|
|
res = handler()
|
2018-02-14 16:38:01 +00:00
|
|
|
c
|