Pass first fixtures tests

Todo

Pass most VM opcode tests that we can (of those that don't depend
too much on porting the whole py-evm)

Simplify a bit the current py-evm-inspired internal loop arch
This commit is contained in:
Alexander Ivanov 2018-02-14 18:38:01 +02:00
parent 3a0596bac7
commit 9c056b85de
14 changed files with 228 additions and 143 deletions

View File

@ -55,7 +55,8 @@ proc newBaseComputation*(vmState: BaseVMState, message: Message): BaseComputatio
result.children = @[]
result.accountsToDelete = initTable[string, string]()
result.logEntries = @[]
result.code = newCodeStream(message.code)
result.code = newCodeStreamFromUnescaped(message.code) # TODO: what is the best repr
result.rawOutput = "0x"
method logger*(computation: BaseComputation): Logger =
logging.getLogger("vm.computation.BaseComputation")
@ -264,7 +265,7 @@ template inComputation*(c: untyped, handler: untyped): untyped =
method getOpcodeFn*(computation: var BaseComputation, op: Op): Opcode =
if computation.opcodes.hasKey(op):
if computation.opcodes.len > 0 and computation.opcodes.hasKey(op):
computation.opcodes[op]
else:
raise newException(InvalidInstruction,
@ -279,7 +280,7 @@ macro applyComputation*(t: typed, vmState: untyped, message: untyped): untyped =
result = quote:
block:
var res: `typName`
var c = `name`(`vmState`, `message`)
var c = `t` # `name`(`vmState`, `message`)
var handler = proc: `typName` =
# TODO
# if `message`.codeAddress in c.precompiles:
@ -290,7 +291,6 @@ macro applyComputation*(t: typed, vmState: untyped, message: untyped): untyped =
var opcode = c.getOpcodeFn(op)
c.logger.trace(
"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:
@ -298,4 +298,4 @@ macro applyComputation*(t: typed, vmState: untyped, message: untyped): untyped =
return c
inComputation(c):
res = handler()
res
c

View File

@ -6,11 +6,15 @@ type
# TODO db*: JournalDB
proc newBaseChainDB*(db: MemoryDB): BaseChainDB =
new(result)
result.db = db
proc exists*(self: BaseChainDB; key: string): bool =
return self.db.exists(key)
proc `$`*(db: BaseChainDB): string =
result = "BaseChainDB"
# proc getCanonicalHead*(self: BaseChainDB): BlockHeader =
# if notself.exists(CANONICALHEADHASHDBKEY):
# raise newException(CanonicalHeadNotFound,

View File

@ -67,11 +67,27 @@ type
NotImplementedError* = object of VMError
## Not implemented error
# proc makeVMError*(): VMError =
# result.burnsGas = true
# result.erasesReturnData = true
#proc makeVMError*: ref VMError =
# var e: ref VMError
# new(e)
# e.burnsGas = true
# e.erasesReturnData = true
#var a: ref VMError
#new(a)
# proc makeRevert*(): Revert =
# result.burnsGas = false
# result.erasesReturnData = false
#var e = VMError()
#raise makeVMError()
#var e: ref VMError
#new(e)
#echo e[]
#proc x* =
# raise newException(VMError, "")
#var e = makeVMError()
#echo e[]
#x()

View File

@ -1,5 +1,5 @@
import
../constants, ../errors, ../computation, .. / db / state_db, .. / vm / [stack, gas_meter, message]
../constants, ../errors, ../computation, .. / db / state_db, .. / vm / [stack, gas_meter, message], strformat
{.this: computation.}
{.experimental.}
@ -9,8 +9,10 @@ using
proc sstore*(computation) =
let (slot, value) = stack.popInt(2)
# TODO: stateDB
#if value != 0: #and slot == 0:
computation.gasMeter.consumeGas(GAS_SSET, &"SSTORE: {computation.msg.storageAddress}[slot] -> {value} (TODO)")
#else:
#computation.gasMeter.consumeGas(GAS_SRESET, &"SSTORE: {computation.msg.storageAddress}[slot] -> {value} (TODO)")
# with computation.vm_state.state_db(read_only=True) as state_db:
# current_value = state_db.get_storage(
# address=computation.msg.storage_address,

111
src/opcode_table.nim Normal file
View File

@ -0,0 +1,111 @@
import
strformat, strutils, tables, macros,
constants, ttmath, errors, logging, vm_state,
vm / [gas_meter, stack, code_stream, memory, message, value, gas_costs], db / db_chain, computation, opcode, opcode_values, utils / [header, address],
logic / [arithmetic, comparison, sha3, context, block_ops, stack_ops, duplication, swap, memory_ops, storage, flow, logging_ops, invalid, call, system_ops]
var OPCODE_TABLE* = initOpcodes:
# arithmetic
Op.Add: GAS_VERY_LOW add
Op.Mul: GAS_LOW mul
Op.Sub: GAS_VERY_LOW sub
Op.Div: GAS_LOW divide
Op.SDiv: GAS_LOW sdiv
Op.Mod: GAS_LOW modulo
Op.SMod: GAS_LOW smod
Op.AddMod: GAS_MID addmod
Op.MulMod: GAS_MID mulmod
Op.Exp: expGasCost arithmetic.exp
Op.SignExtend: GAS_LOW signextend
# comparison
Op.Lt: GAS_VERY_LOW lt
Op.Gt: GAS_VERY_LOW gt
Op.SLt: GAS_VERY_LOW slt
Op.SGt: GAS_VERY_LOW sgt
Op.Eq: GAS_VERY_LOW eq
Op.IsZero: GAS_VERY_LOW iszero
Op.And: GAS_VERY_LOW andOp
Op.Or: GAS_VERY_LOW orOp
Op.Xor: GAS_VERY_LOW xorOp
Op.Not: GAS_VERY_LOW notOp
Op.Byte: GAS_VERY_LOW byteOp
# sha3
Op.SHA3: GAS_SHA3 sha3op
# context
Op.Address: GAS_BASE context.address
Op.Balance: GAS_COST_BALANCE balance
Op.Origin: GAS_BASE origin
Op.Caller: GAS_BASE caller
Op.CallValue: GAS_BASE callValue
Op.CallDataLoad: GAS_VERY_LOW callDataLoad
Op.CallDataSize: GAS_BASE callDataSize
Op.CallDataCopy: GAS_BASE callDataCopy
Op.CodeSize: GAS_BASE codesize
Op.CodeCopy: GAS_BASE codecopy
Op.ExtCodeSize: GAS_EXT_CODE_COST extCodeSize
Op.ExtCodeCopy: GAS_EXT_CODE_COST extCodeCopy
# block
Op.Blockhash: GAS_BASE block_ops.blockhash
Op.Coinbase: GAS_COINBASE coinbase
Op.Timestamp: GAS_BASE timestamp
Op.Number: GAS_BASE number
Op.Difficulty: GAS_BASE difficulty
Op.GasLimit: GAS_BASE gaslimit
# stack
Op.Pop: GAS_BASE stack_ops.pop
1..32 Op.PushXX: GAS_VERY_LOW pushXX # XX replaced by macro
1..16 Op.DupXX: GAS_VERY_LOW dupXX
1..16 Op.SwapXX: GAS_VERY_LOW swapXX
# memory
Op.MLoad: GAS_VERY_LOW mload
Op.MStore: GAS_VERY_LOW mstore
Op.MStore8: GAS_VERY_LOW mstore8
Op.MSize: GAS_BASE msize
# storage
Op.SLoad: GAS_SLOAD_COST sload
Op.SStore: GAS_ZERO sstore
# flow
Op.Jump: GAS_MID jump
Op.JumpI: GAS_MID jumpi
Op.PC: GAS_HIGH pc
Op.Gas: GAS_BASE flow.gas
Op.JumpDest: GAS_JUMP_DEST jumpdest
Op.Stop: GAS_ZERO stop
# logging
0..4 Op.LogXX: GAS_IN_HANDLER logXX
# invalid
Op.Invalid: GAS_ZERO invalidOp
# system
Op.Return: 0.i256 returnOp
Op.SelfDestruct: GAS_SELF_DESTRUCT_COST selfdestruct
# call
OPCODE_TABLE[Op.Call] = Call(kind: Op.Call)
OPCODE_TABLE[Op.CallCode] = CallCode(kind: Op.CallCode)
OPCODE_TABLE[Op.DelegateCall] = DelegateCall(kind: Op.DelegateCall)
# system
OPCODE_TABLE[Op.Create] = Create(kind: Op.Create)

View File

@ -1,115 +1,9 @@
import
strformat, strutils, tables, macros,
constants, ttmath, errors, logging, vm_state,
constants, ttmath, errors, logging, vm_state, opcode_table,
vm / [gas_meter, stack, code_stream, memory, message, value, gas_costs], db / chain, computation, opcode, opcode_values, utils / [header, address],
logic / [arithmetic, comparison, sha3, context, block_ops, stack_ops, duplication, swap, memory_ops, storage, flow, logging_ops, invalid, call, system_ops]
var opcodes = initOpcodes:
# arithmetic
Op.Add: GAS_VERY_LOW add
Op.Mul: GAS_LOW mul
Op.Sub: GAS_VERY_LOW sub
Op.Div: GAS_LOW divide
Op.SDiv: GAS_LOW sdiv
Op.Mod: GAS_LOW modulo
Op.SMod: GAS_LOW smod
Op.AddMod: GAS_MID addmod
Op.MulMod: GAS_MID mulmod
Op.Exp: expGasCost arithmetic.exp
Op.SignExtend: GAS_LOW signextend
# comparison
Op.Lt: GAS_VERY_LOW lt
Op.Gt: GAS_VERY_LOW gt
Op.SLt: GAS_VERY_LOW slt
Op.SGt: GAS_VERY_LOW sgt
Op.Eq: GAS_VERY_LOW eq
Op.IsZero: GAS_VERY_LOW iszero
Op.And: GAS_VERY_LOW andOp
Op.Or: GAS_VERY_LOW orOp
Op.Xor: GAS_VERY_LOW xorOp
Op.Not: GAS_VERY_LOW notOp
Op.Byte: GAS_VERY_LOW byteOp
# sha3
Op.SHA3: GAS_SHA3 sha3op
# context
Op.Address: GAS_BASE context.address
Op.Balance: GAS_COST_BALANCE balance
Op.Origin: GAS_BASE origin
Op.Caller: GAS_BASE caller
Op.CallValue: GAS_BASE callValue
Op.CallDataLoad: GAS_VERY_LOW callDataLoad
Op.CallDataSize: GAS_BASE callDataSize
Op.CallDataCopy: GAS_BASE callDataCopy
Op.CodeSize: GAS_BASE codesize
Op.CodeCopy: GAS_BASE codecopy
Op.ExtCodeSize: GAS_EXT_CODE_COST extCodeSize
Op.ExtCodeCopy: GAS_EXT_CODE_COST extCodeCopy
# block
Op.Blockhash: GAS_BASE block_ops.blockhash
Op.Coinbase: GAS_COINBASE coinbase
Op.Timestamp: GAS_BASE timestamp
Op.Number: GAS_BASE number
Op.Difficulty: GAS_BASE difficulty
Op.GasLimit: GAS_BASE gaslimit
# stack
Op.Pop: GAS_BASE stack_ops.pop
1..32 Op.PushXX: GAS_VERY_LOW pushXX # XX replaced by macro
1..16 Op.DupXX: GAS_VERY_LOW dupXX
1..16 Op.SwapXX: GAS_VERY_LOW swapXX
# memory
Op.MLoad: GAS_VERY_LOW mload
Op.MStore: GAS_VERY_LOW mstore
Op.MStore8: GAS_VERY_LOW mstore8
Op.MSize: GAS_BASE msize
# storage
Op.SLoad: GAS_SLOAD_COST sload
Op.SStore: GAS_ZERO sstore
# flow
Op.Jump: GAS_MID jump
Op.JumpI: GAS_MID jumpi
Op.PC: GAS_HIGH pc
Op.Gas: GAS_BASE flow.gas
Op.JumpDest: GAS_JUMP_DEST jumpdest
Op.Stop: GAS_ZERO stop
# logging
0..4 Op.LogXX: GAS_IN_HANDLER logXX
# invalid
Op.Invalid: GAS_ZERO invalidOp
# system
Op.Return: 0.i256 returnOp
Op.SelfDestruct: GAS_SELF_DESTRUCT_COST selfdestruct
# call
opcodes[Op.Call] = Call(kind: Op.Call)
opcodes[Op.CallCode] = CallCode(kind: Op.CallCode)
opcodes[Op.DelegateCall] = DelegateCall(kind: Op.DelegateCall)
# system
opcodes[Op.Create] = Create(kind: Op.Create)
var mem = newMemory(pow(1024.int256, 2))
var to = toCanonicalAddress("0x0f572e5295c57f15886f9b263e2f6d2d6c7b5ec6")
@ -146,7 +40,7 @@ var c = BaseComputation(
logEntries: @[],
shouldEraseReturnData: false,
accountsToDelete: initTable[string, string](),
opcodes: opcodes,
opcodes: OPCODE_TABLE,
precompiles: initTable[string, Opcode]())
# var c2 = c.applyComputation(c.vmState, c.msg)

View File

@ -1,4 +1,4 @@
import ../constants, ttmath
import ../constants, ttmath, strformat
type
Header* = ref object
@ -12,6 +12,12 @@ type
# TODO
proc `$`*(header: Header): string =
if header.isNil:
result = "nil"
else:
result = &"Header(timestamp: {header.timestamp} difficulty: {header.difficulty} blockNumber: {header.blockNumber} gasLimit: {header.gasLimit})"
proc generateHeaderFromParentHeader*(
computeDifficultyFn: proc(parentHeader: Header, timestamp: int): int,
parentHeader: Header,

View File

@ -1,5 +1,5 @@
import
strformat, strutils, sequtils, sets, macros,
strformat, strutils, sequtils, parseutils, sets, macros,
../logging, ../constants, ../opcode_values
type
@ -24,6 +24,16 @@ proc newCodeStream*(codeBytes: seq[byte]): CodeStream =
proc newCodeStream*(codeBytes: string): CodeStream =
newCodeStream(codeBytes.mapIt(it.byte))
proc newCodeStreamFromUnescaped*(code: string): CodeStream =
# from 0xunescaped
var codeBytes: seq[byte] = @[]
for z, c in code[2..^1]:
if z mod 2 == 1:
var value: int
discard parseHex(&"0x{code[z+1..z+2]}", value)
codeBytes.add(value.byte)
newCodeStream(codeBytes)
proc read*(c: var CodeStream, size: int): seq[byte] =
if c.pc + size - 1 < c.bytes.len:
result = c.bytes[c.pc .. c.pc + size - 1]

View File

@ -2,11 +2,18 @@ import
logging, constants, errors, vm_state, utils/header, db/db_chain
type
FrontierVMState* = object of BaseVMState
FrontierVMState* = ref object of BaseVMState
# receipts*:
# computationClass*: Any
# accessLogs*: AccessLogs
proc newFrontierVMState*: FrontierVMState =
new(result)
result.prevHeaders = @[]
result.name = "FrontierVM"
result.accessLogs = newAccessLogs()
result.blockHeader = Header(hash: "TODO", coinbase: "TODO", stateRoot: "TODO")
# import
# py2nim_helpers, __future__, rlp, evm, evm.constants, evm.exceptions, evm.rlp.logs,
# evm.rlp.receipts, evm.vm.message, evm.vm_state, evm.utils.address,

View File

@ -23,3 +23,4 @@ proc newFrontierVM*(header: Header, chainDB: BaseChainDB): FrontierVM =
new(result)
result.chainDB = chainDB
result.isStateless = true
result.state = newFrontierVMState()

View File

@ -22,11 +22,18 @@ proc update*[K, V](t: var Table[K, V], elements: Table[K, V]) =
for k, v in elements:
t[k] = v
proc `$`*(vmState: BaseVMState): string =
if vmState.isNil:
result = "nil"
else:
result = &"VMState {vmState.name}:\n header: {vmState.blockHeader}\n chaindb: {vmState.chaindb}"
proc newBaseVMState*: BaseVMState =
new(result)
result.prevHeaders = @[]
result.name = "BaseVM"
result.accessLogs = newAccessLogs()
result.blockHeader = Header(hash: "TODO", coinbase: "TODO", stateRoot: "TODO")
method logger*(vmState: BaseVMState): Logger =
logging.getLogger(&"evm.vmState.{vmState.name}")
@ -65,6 +72,7 @@ macro db*(vmState: untyped, readOnly: untyped, handler: untyped): untyped =
let db = ident("db")
result = quote:
block:
echo `vmState`
var `db` = `vmState`.chaindb.getStateDB(`vmState`.blockHeader.stateRoot, `readOnly`)
`handler`
if `readOnly`:

View File

@ -1,5 +1,5 @@
import
os, macros, json, strformat, strutils, ttmath, utils / [hexadecimal, address], chain, vm_state, constants, db / [db_chain, state_db], vm / forks / frontier / vm
os, macros, json, strformat, strutils, ttmath, utils / [hexadecimal, address], chain, vm_state, constants, db / [db_chain, state_db], vm / forks / frontier / vm, parseutils
# TODO
# This block is a child of the genesis defined in the chain fixture above and contains a single tx
@ -56,14 +56,21 @@ import
#
proc generateTest(filename: string, handler: NimNode): NimNode =
echo filename
let testStatusIMPL = ident("testStatusIMPL")
result = quote:
test `filename`:
`handler`(parseJSON(readFile(`filename`)))
`handler`(parseJSON(readFile(`filename`)), `testStatusIMPL`)
macro jsonTest*(s: static[string], handler: untyped): untyped =
result = nnkStmtList.newTree()
for filename in walkDir(&"tests/{s}", relative=true):
result.add(generateTest(filename.path, handler))
echo &"tests/fixtures/{s}"
var z = 0
for filename in walkDirRec(&"tests/fixtures/{s}"):
if "mul" in filename:
if z >= 4:
break
result.add(generateTest(filename, handler))
z += 1
proc setupStateDB*(desiredState: JsonNode, stateDB: var AccountStateDB) =
for account, accountData in desiredState:
@ -77,3 +84,6 @@ proc setupStateDB*(desiredState: JsonNode, stateDB: var AccountStateDB) =
stateDB.setNonce(account, nonce)
stateDB.setCode(account, code)
stateDB.setBalance(account, balance)
proc getHexadecimalInt*(j: JsonNode): int =
discard parseHex(j.getStr, result)

View File

@ -1,38 +1,55 @@
import
unittest, strformat, strutils, sequtils, tables, ttmath, json,
helpers, constants, errors, logging,
chain, vm_state, computation, opcode, utils / header, vm / [gas_meter, message, code_stream], vm / forks / frontier / vm, db / [db_chain, state_db], db / backends / memory_backend
test_helpers, constants, errors, logging,
chain, vm_state, computation, opcode, opcode_table, utils / header, vm / [gas_meter, message, code_stream], vm / forks / frontier / vm, db / [db_chain, state_db], db / backends / memory_backend
proc testFixture(fixture: JsonNode)
proc testFixture(fixtures: JsonNode, testStatusIMPL: var TestStatus)
suite "vm json tests":
jsonTest("VMTests", testFixture)
proc testFixture(fixture: JsonNode) =
proc testFixture(fixtures: JsonNode, testStatusIMPL: var TestStatus) =
var fixture: JsonNode
for label, child in fixtures:
fixture = child
break
var vm = newFrontierVM(Header(), newBaseChainDB(newMemoryDB()))
let header = Header(
coinbase: fixture{"env"}{"currentCoinbase"}.getStr,
difficulty: fixture{"env"}{"currentDifficulty"}.getInt.i256,
blockNumber: fixture{"env"}{"currentNumber"}.getInt.i256,
gasLimit: fixture{"env"}{"currentGasLimit"}.getInt.i256,
timestamp: fixture{"env"}{"currentTimestamp"}.getInt)
difficulty: fixture{"env"}{"currentDifficulty"}.getHexadecimalInt.i256,
blockNumber: fixture{"env"}{"currentNumber"}.getHexadecimalInt.i256,
gasLimit: fixture{"env"}{"currentGasLimit"}.getHexadecimalInt.i256,
timestamp: fixture{"env"}{"currentTimestamp"}.getHexadecimalInt)
var code = ""
vm.state.db(readOnly=false):
setupStateDB(fixture{"pre"}, db)
code = db.getCode(fixture{"exec"}{"address"}.getStr)
code = fixture{"exec"}{"code"}.getStr
let message = newMessage(
to=fixture{"exec"}{"address"}.getStr,
sender=fixture{"exec"}{"caller"}.getStr,
value=fixture{"exec"}{"value"}.getInt.i256,
value=fixture{"exec"}{"value"}.getHexadecimalInt.i256,
data=fixture{"exec"}{"data"}.getStr.mapIt(it.byte),
code=code,
gas=fixture{"exec"}{"gas"}.getInt.i256,
gasPrice=fixture{"exec"}{"gasPrice"}.getInt.i256,
gas=fixture{"exec"}{"gas"}.getHexadecimalInt.i256,
gasPrice=fixture{"exec"}{"gasPrice"}.getHexadecimalInt.i256,
options=newMessageOptions(origin=fixture{"exec"}{"origin"}.getStr))
let computation = newBaseComputation(vm.state, message).applyComputation(vm.state, message)
echo fixture{"exec"}
var c = newCodeStreamFromUnescaped(code)
var opcodes = c.decompile
for opcode in opcodes:
echo opcode[0], " ", opcode[1], " ", opcode[2]
var computation = newBaseComputation(vm.state, message)
computation.accountsToDelete = initTable[string, string]()
computation.opcodes = OPCODE_TABLE
computation.precompiles = initTable[string, Opcode]()
computation = computation.applyComputation(vm.state, message)
if not fixture{"post"}.isNil:
# Success checks
@ -50,10 +67,9 @@ proc testFixture(fixture: JsonNode) =
let expectedOutput = fixture{"out"}.getStr
check(computation.output == expectedOutput)
let gasMeter = computation.gasMeter
let expectedGasRemaining = fixture{"gas"}.getInt.i256
let expectedGasRemaining = fixture{"gas"}.getHexadecimalInt.i256
let actualGasRemaining = gasMeter.gasRemaining
let gasDelta = actualGasRemaining - expectedGasRemaining
check(gasDelta == 0)
@ -69,8 +85,8 @@ proc testFixture(fixture: JsonNode) =
var (childComputation, createdCall) = child
let toAddress = createdCall{"destination"}.getStr
let data = createdCall{"data"}.getStr.mapIt(it.byte)
let gasLimit = createdCall{"gasLimit"}.getInt.i256
let value = createdCall{"value"}.getInt.i256
let gasLimit = createdCall{"gasLimit"}.getHexadecimalInt.i256
let value = createdCall{"value"}.getHexadecimalInt.i256
check(childComputation.msg.to == toAddress)
check(data == childComputation.msg.data or childComputation.msg.code.len > 0)

View File

@ -1,6 +1,6 @@
import
unittest,
helpers, .. / src / [db / backends / memory, db / chain, constants, utils / hexadecimal]
test_helpers, .. / src / [db / backends / memory, db / chain, constants, utils / hexadecimal]
suite "vm":
test "apply no validation":