CodeStream impl and tests, fix byte and string fields

This commit is contained in:
Alexander Ivanov 2018-01-31 14:57:05 +02:00
parent 62f1417439
commit a233cef719
31 changed files with 329 additions and 190 deletions

View File

@ -13,7 +13,7 @@ type
## current block number.
header*: BlockHeader
logger*: Logger
networkId*: cstring
networkId*: string
vmsByRange*: seq[tuple[blockNumber: Int256, vm: VM]] # TODO
importBlock*: bool
validateBlock*: bool
@ -23,18 +23,18 @@ type
blockNumber*: Int256
difficulty*: Int256
gasLimit*: Int256
parentHash*: cstring
coinbase*: cstring
nonce: cstring
mixHash: cstring
extraData: cstring
parentHash*: string
coinbase*: string
nonce: string
mixHash: string
extraData: string
timestamp: int,
stateRoot: cstring
stateRoot: string
FundedAddress* = ref object
balance*: Int256
nonce*: int
code*: cstring
code*: string
proc configureChain*(name: string, blockNumber: Int256, vm: VM, importBlock: bool = true, validateBlock: bool = true): Chain =

View File

@ -23,14 +23,14 @@ type
gasMeter*: GasMeter
code*: CodeStream
children*: seq[BaseComputation]
rawOutput*: cstring
returnData*: cstring
rawOutput*: string
returnData*: string
error*: Error
logEntries*: seq[(cstring, seq[Int256], cstring)]
logEntries*: seq[(string, seq[Int256], string)]
shouldEraseReturnData*: bool
accountsToDelete*: Table[cstring, cstring]
accountsToDelete*: Table[string, string]
opcodes*: Table[Op, Opcode] # TODO array[Op, Opcode]
precompiles*: Table[cstring, Opcode]
precompiles*: Table[string, Opcode]
Error* = ref object
info*: string
@ -53,7 +53,7 @@ proc newBaseComputation*(vmState: BaseVMState, message: Message): BaseComputatio
result.stack = newStack()
result.gasMeter = newGasMeter(message.gas)
result.children = @[]
result.accountsToDelete = initTable[cstring, cstring]()
result.accountsToDelete = initTable[string, string]()
result.logEntries = @[]
result.code = newCodeStream(message.code)
@ -87,10 +87,10 @@ method shouldEraseReturnData*(c: BaseComputation): bool =
method prepareChildMessage*(
c: var BaseComputation,
gas: Int256,
to: cstring,
to: string,
value: Int256,
data: seq[byte],
code: cstring,
code: string,
options: MessageOptions = newMessageOptions()): Message =
var childOptions = options
@ -128,13 +128,13 @@ method extendMemory*(c: var BaseComputation, startPosition: Int256, size: Int256
c.memory.extend(startPosition, size)
method output*(c: BaseComputation): cstring =
method output*(c: BaseComputation): string =
if c.shouldEraseReturnData:
cstring""
""
else:
c.rawOutput
method `output=`*(c: var BaseComputation, value: cstring) =
method `output=`*(c: var BaseComputation, value: string) =
c.rawOutput = value
macro generateChildBaseComputation*(t: typed, vmState: typed, childMsg: typed): untyped =
@ -157,12 +157,12 @@ method addChildBaseComputation*(c: var BaseComputation, childBaseComputation: Ba
if childBaseComputation.msg.isCreate:
c.returnData = childBaseComputation.output
elif childBaseComputation.shouldBurnGas:
c.returnData = cstring""
c.returnData = ""
else:
c.returnData = childBaseComputation.output
else:
if childBaseComputation.msg.isCreate:
c.returnData = cstring""
c.returnData = ""
else:
c.returnData = childBaseComputation.output
c.children.add(childBaseComputation)
@ -172,7 +172,7 @@ method applyChildBaseComputation*(c: var BaseComputation, childMsg: Message): Ba
c.addChildBaseComputation(childBaseComputation)
result = childBaseComputation
method registerAccountForDeletion*(c: var BaseComputation, beneficiary: cstring) =
method registerAccountForDeletion*(c: var BaseComputation, beneficiary: string) =
validateCanonicalAddress(beneficiary, title="self destruct beneficiary address")
if c.msg.storageAddress in c.accountsToDelete:
@ -181,18 +181,18 @@ method registerAccountForDeletion*(c: var BaseComputation, beneficiary: cstring)
"registered for deletion multiple times")
c.accountsToDelete[c.msg.storageAddress] = beneficiary
method addLogEntry*(c: var BaseComputation, account: cstring, topics: seq[Int256], data: cstring) =
method addLogEntry*(c: var BaseComputation, account: string, topics: seq[Int256], data: string) =
validateCanonicalAddress(account, title="log entry address")
c.logEntries.add((account, topics, data))
method getAccountsForDeletion*(c: BaseComputation): seq[(cstring, cstring)] =
method getAccountsForDeletion*(c: BaseComputation): seq[(string, string)] =
# TODO
if c.isError:
result = @[]
else:
result = @[]
method getLogEntries*(c: BaseComputation): seq[(cstring, seq[Int256], cstring)] =
method getLogEntries*(c: BaseComputation): seq[(string, seq[Int256], string)] =
# TODO
if c.isError:
result = @[]

View File

@ -86,12 +86,12 @@ let
UINT_255_MAX*: Int256 = 2 ^ (256 - 1) - 1
UINT_255_CEILING*: Int256 = 2 ^ (256 - 1)
NULLBYTE* = cstring"\x00"
NULLBYTE* = "\x00"
EMPTYWORD* = repeat(NULLBYTE, 32)
UINT160CEILING*: Int256 = 2 ^ 160
CREATE_CONTRACT_ADDRESS* = cstring""
ZERO_ADDRESS* = repeat(cstring"\x00", 20)
ZERO_HASH32* = repeat(cstring"\x00", 20)
CREATE_CONTRACT_ADDRESS* = ""
ZERO_ADDRESS* = repeat("\x00", 20)
ZERO_HASH32* = repeat("\x00", 20)
STACK_DEPTH_LIMIT* = 1024
GAS_NULL* = 0.i256
@ -178,19 +178,19 @@ let
SECPK1_Gy* = 0.i256
SECPK1_G* = (SECPK1Gx, SECPK1Gy)
EMPTY_UNCLE_HASH* = cstring"\x1d\xccM\xe8\xde\xc7]z\xab\x85\xb5g\xb6\xcc\xd4\x1a\xd3\x12E\x1b\x94\x8at\x13\xf0\xa1B\xfd@\xd4\x93G"
EMPTY_UNCLE_HASH* = "\x1d\xccM\xe8\xde\xc7]z\xab\x85\xb5g\xb6\xcc\xd4\x1a\xd3\x12E\x1b\x94\x8at\x13\xf0\xa1B\xfd@\xd4\x93G"
GENESIS_BLOCK_NUMBER* = 0.i256
GENESIS_DIFFICULTY* = 131_072.i256
GENESIS_GAS_LIMIT* = 3_141_592.i256
GENESIS_PARENT_HASH* = ZERO_HASH32
GENESIS_COINBASE* = ZERO_ADDRESS
GENESIS_NONCE* = cstring"\x00\x00\x00\x00\x00\x00\x00B"
GENESIS_NONCE* = "\x00\x00\x00\x00\x00\x00\x00B"
GENESIS_MIX_HASH* = ZERO_HASH32
GENESIS_EXTRA_DATA = cstring""
GENESIS_EXTRA_DATA = ""
EMPTYSHA3 = cstring"\xc5\xd2F\x01\x86\xf7#<\x92~}\xb2\xdc\xc7\x03\xc0\xe5\x00\xb6S\xca\x82';{\xfa\xd8\x04]\x85\xa4p"
BLANK_ROOT_HASH* = cstring"V\xe8\x1f\x17\x1b\xccU\xa6\xff\x83E\xe6\x92\xc0\xf8n[H\xe0\x1b\x99l\xad\xc0\x01b/\xb5\xe3c\xb4!"
EMPTYSHA3 = "\xc5\xd2F\x01\x86\xf7#<\x92~}\xb2\xdc\xc7\x03\xc0\xe5\x00\xb6S\xca\x82';{\xfa\xd8\x04]\x85\xa4p"
BLANK_ROOT_HASH* = "V\xe8\x1f\x17\x1b\xccU\xa6\xff\x83E\xe6\x92\xc0\xf8n[H\xe0\x1b\x99l\xad\xc0\x01b/\xb5\xe3c\xb4!"
GAS_MOD_EXP_QUADRATIC_DENOMINATOR* = 20.i256

View File

@ -4,15 +4,15 @@ import
type
AccountStateDB* = ref object
db*: Table[cstring, Int256]
db*: Table[string, Int256]
proc newAccountStateDB*(db: Table[cstring, Int256], readOnly: bool = false): AccountStateDB =
proc newAccountStateDB*(db: Table[string, Int256], readOnly: bool = false): AccountStateDB =
result = AccountStateDB(db: db)
proc logger*(db: AccountStateDB): Logger =
logging.getLogger("db.State")
proc getAccount(db: AccountStateDB, address: cstring): Account =
proc getAccount(db: AccountStateDB, address: string): Account =
# let rlpAccount = db.trie[address]
# if not rlpAccount.isNil:
# account = rlp.decode[Account](rlpAccount)
@ -21,23 +21,23 @@ proc getAccount(db: AccountStateDB, address: cstring): Account =
# account = newAccount()
result = newAccount() # TODO
proc setAccount(db: AccountStateDB, address: cstring, account: Account) =
proc setAccount(db: AccountStateDB, address: string, account: Account) =
# db.trie[address] = rlp.encode[Account](account)
discard # TODO
# public
proc getBalance*(db: AccountStateDB, address: cstring): Int256 =
proc getBalance*(db: AccountStateDB, address: string): Int256 =
validateCanonicalAddress(address, title="Storage Address")
let account = db.getAccount(address)
account.balance
proc setBalance*(db: var AccountStateDB, address: cstring, balance: Int256) =
proc setBalance*(db: var AccountStateDB, address: string, balance: Int256) =
validateCanonicalAddress(address, title="Storage Address")
let account = db.getAccount(address)
account.balance = balance
db.setAccount(address, account)
proc deltaBalance*(db: var AccountStateDB, address: cstring, delta: Int256) =
proc deltaBalance*(db: var AccountStateDB, address: string, delta: Int256) =
db.setBalance(address, db.getBalance(address) + delta)

View File

@ -30,16 +30,16 @@ type
using
computation: var BaseComputation
method msgExtraGas*(call: BaseCall, computation; gas: Int256, to: cstring, value: Int256): Int256 {.base.} =
method msgExtraGas*(call: BaseCall, computation; gas: Int256, to: string, value: Int256): Int256 {.base.} =
raise newException(NotImplementedError, "Must be implemented by subclasses")
method msgGas*(call: BaseCall, computation; gas: Int256, to: cstring, value: Int256): (Int256, Int256) {.base.} =
method msgGas*(call: BaseCall, computation; gas: Int256, to: string, value: Int256): (Int256, Int256) {.base.} =
let extraGas = call.msgExtraGas(computation, gas, to, value)
let totalFee = gas + extraGas
let childMsgGas = gas + (if value != 0: GAS_CALL_STIPEND else: 0.i256)
(childMsgGas, totalFee)
method callParams*(call: BaseCall, computation): (Int256, Int256, cstring, cstring, cstring, Int256, Int256, Int256, Int256, bool, bool) {.base.} =
method callParams*(call: BaseCall, computation): (Int256, Int256, string, string, string, Int256, Int256, Int256, Int256, bool, bool) {.base.} =
raise newException(NotImplementedError, "Must be implemented subclasses")
method runLogic*(call: BaseCall, computation) =
@ -67,7 +67,7 @@ method runLogic*(call: BaseCall, computation) =
let stackTooDeep = computation.msg.depth + 1 > constants.STACK_DEPTH_LIMIT
if insufficientFunds or stackTooDeep:
computation.returnData = cstring""
computation.returnData = ""
var errMessage: string
if insufficientFunds:
errMessage = &"Insufficient Funds: have: {senderBalance} | need: {value}"
@ -86,7 +86,7 @@ method runLogic*(call: BaseCall, computation) =
# code = state_db.get_code(code_address)
# else:
# code = state_db.get_code(to)
let code = cstring""
let code = ""
let childMsg = computation.prepareChildMessage(
childMsgGas,
@ -115,7 +115,7 @@ method runLogic*(call: BaseCall, computation) =
if not childComputation.shouldBurnGas:
computation.gasMeter.returnGas(childComputation.gasMeter.gasRemaining)
method msgExtraGas(call: Call, computation; gas: Int256, to: cstring, value: Int256): Int256 =
method msgExtraGas(call: Call, computation; gas: Int256, to: string, value: Int256): Int256 =
# TODO: db
# with computation.vm_state.state_db(read_only=True) as state_db:
# let accountExists = db.accountExists(to)
@ -125,9 +125,9 @@ method msgExtraGas(call: Call, computation; gas: Int256, to: cstring, value: Int
let createGasFee = if not accountExists: GAS_NEW_ACCOUNT else: 0.i256
transferGasFee + createGasFee
method callParams(call: CallCode, computation): (Int256, Int256, cstring, cstring, cstring, Int256, Int256, Int256, Int256, bool, bool) =
method callParams(call: CallCode, computation): (Int256, Int256, string, string, string, Int256, Int256, Int256, Int256, bool, bool) =
let gas = computation.stack.popInt()
let to = cstring(forceBytesToAddress(computation.stack.popBinary))
let to = forceBytesToAddress(computation.stack.popBinary)
let (value,
memoryInputStartPosition, memoryInputSize,
@ -145,12 +145,12 @@ method callParams(call: CallCode, computation): (Int256, Int256, cstring, cstrin
true, # should_transfer_value,
computation.msg.isStatic)
method msgExtraGas(call: CallCode, computation; gas: Int256, to: cstring, value: Int256): Int256 =
method msgExtraGas(call: CallCode, computation; gas: Int256, to: string, value: Int256): Int256 =
if value != 0: GAS_CALL_VALUE else: 0.i256
method callParams(call: Call, computation): (Int256, Int256, cstring, cstring, cstring, Int256, Int256, Int256, Int256, bool, bool) =
method callParams(call: Call, computation): (Int256, Int256, string, string, string, Int256, Int256, Int256, Int256, bool, bool) =
let gas = computation.stack.popInt()
let codeAddress = cstring(forceBytesToAddress(computation.stack.popBinary))
let codeAddress = forceBytesToAddress(computation.stack.popBinary)
let (value,
memoryInputStartPosition, memoryInputSize,
@ -171,15 +171,15 @@ method callParams(call: Call, computation): (Int256, Int256, cstring, cstring, c
true, # should_transfer_value,
computation.msg.isStatic)
method msgGas(call: DelegateCall, computation; gas: Int256, to: cstring, value: Int256): (Int256, Int256) =
method msgGas(call: DelegateCall, computation; gas: Int256, to: string, value: Int256): (Int256, Int256) =
(gas, gas)
method msgExtraGas(call: DelegateCall, computation; gas: Int256, to: cstring, value: Int256): Int256 =
method msgExtraGas(call: DelegateCall, computation; gas: Int256, to: string, value: Int256): Int256 =
0.i256
method callParams(call: DelegateCall, computation): (Int256, Int256, cstring, cstring, cstring, Int256, Int256, Int256, Int256, bool, bool) =
method callParams(call: DelegateCall, computation): (Int256, Int256, string, string, string, Int256, Int256, Int256, Int256, bool, bool) =
let gas = computation.stack.popInt()
let codeAddress = cstring(forceBytesToAddress(computation.stack.popBinary))
let codeAddress = forceBytesToAddress(computation.stack.popBinary)
let (memoryInputStartPosition, memoryInputSize,
memoryOutputStartPosition, memoryOutputSize) = computation.stack.popInt(4)
@ -211,19 +211,19 @@ proc computeEIP150MsgGas(computation; gas: Int256, extraGas: Int256, value: Int2
let childMsgGas = gas + (if value != 0: callStipend else: 0.i256)
(childMsgGas, totalFee)
method msgGas(call: CallEIP150, computation; gas: Int256, to: cstring, value: Int256): (Int256, Int256) =
method msgGas(call: CallEIP150, computation; gas: Int256, to: string, value: Int256): (Int256, Int256) =
let extraGas = call.msgExtraGas(computation, gas, to, value)
computeEIP150MsgGas(computation, gas, extraGas, value, $call.kind, GAS_CALL_STIPEND)
method msgGas(call: CallCodeEIP150, computation; gas: Int256, to: cstring, value: Int256): (Int256, Int256) =
method msgGas(call: CallCodeEIP150, computation; gas: Int256, to: string, value: Int256): (Int256, Int256) =
let extraGas = call.msgExtraGas(computation, gas, to, value)
computeEIP150MsgGas(computation, gas, extraGas, value, $call.kind, GAS_CALL_STIPEND)
method msgGas(call: DelegateCallEIP150, computation; gas: Int256, to: cstring, value: Int256): (Int256, Int256) =
method msgGas(call: DelegateCallEIP150, computation; gas: Int256, to: string, value: Int256): (Int256, Int256) =
let extraGas = call.msgExtraGas(computation, gas, to, value)
computeEIP150MsgGas(computation, gas, extraGas, value, $call.kind, 0.i256)
proc msgExtraGas*(call: CallEIP161, computation; gas: Int256, to: cstring, value: Int256): Int256 =
proc msgExtraGas*(call: CallEIP161, computation; gas: Int256, to: string, value: Int256): Int256 =
# TODO: with
# with computation.vm_state.state_db(read_only=True) as state_db:
# account_is_dead = (
@ -236,9 +236,9 @@ proc msgExtraGas*(call: CallEIP161, computation; gas: Int256, to: cstring, value
transferGasFee + createGasFee
method callParams(call: StaticCall, computation): (Int256, Int256, cstring, cstring, cstring, Int256, Int256, Int256, Int256, bool, bool) =
method callParams(call: StaticCall, computation): (Int256, Int256, string, string, string, Int256, Int256, Int256, Int256, bool, bool) =
let gas = computation.stack.popInt()
let to = cstring(forceBytesToAddress(computation.stack.popBinary))
let to = forceBytesToAddress(computation.stack.popBinary)
let (memoryInputStartPosition, memoryInputSize,
memoryOutputStartPosition, memoryOutputSize) = computation.stack.popInt(4)
@ -256,7 +256,7 @@ method callParams(call: StaticCall, computation): (Int256, Int256, cstring, cstr
true) # is_static
method callParams(call: CallByzantium, computation): (Int256, Int256, cstring, cstring, cstring, Int256, Int256, Int256, Int256, bool, bool) =
method callParams(call: CallByzantium, computation): (Int256, Int256, string, string, string, Int256, Int256, Int256, Int256, bool, bool) =
result = procCall callParams(call, computation)
if computation.msg.isStatic and result[1] != 0:
raise newException(WriteProtection, "Cannot modify state while inside of a STATICCALL context")

View File

@ -26,8 +26,8 @@ proc callValue*(computation: var BaseComputation) =
proc callDataLoad*(computation: var BaseComputation) =
# Load call data into memory
let startPosition = computation.stack.popInt.getInt
let value = computation.msg.data[startPosition ..< startPosition + 32].toCString
let paddedValue = padRight(value, 32, cstring"\x00")
let value = computation.msg.data[startPosition ..< startPosition + 32].toString
let paddedValue = padRight(value, 32, "\x00")
let normalizedValue = paddedValue.lStrip(0.char)
computation.stack.push(normalizedValue)
@ -45,8 +45,8 @@ proc callDataCopy*(computation: var BaseComputation) =
let wordCount = ceil32(size) div 32
let copyGasCost = wordCount * constants.GAS_COPY
computation.gasMeter.consumeGas(copyGasCost, reason="CALLDATACOPY fee")
let value = computation.msg.data[calldataStartPosition.getInt ..< (calldataStartPosition + size).getInt].toCString
let paddedValue = padRight(value, size.getInt, cstring"\x00")
let value = computation.msg.data[calldataStartPosition.getInt ..< (calldataStartPosition + size).getInt].toString
let paddedValue = padRight(value, size.getInt, "\x00")
computation.memory.write(memStartPosition, size, paddedValue)
@ -115,5 +115,5 @@ proc returnDataCopy*(computation: var BaseComputation) =
let wordCount = ceil32(size) div 32
let copyGasCost = wordCount * constants.GAS_COPY
computation.gasMeter.consumeGas(copyGasCost, reason="RETURNDATACOPY fee")
let value = cstring(($computation.returnData)[returnDataStartPosition.getInt ..< (returnDataStartPosition + size).getInt])
let value = ($computation.returnData)[returnDataStartPosition.getInt ..< (returnDataStartPosition + size).getInt]
computation.memory.write(memStartPosition, size, value)

View File

@ -48,7 +48,7 @@ macro logXX(topicCount: static[int]): untyped =
let totalGasCost = dataGasCost + topicGasCost
`computation`.gasMeter.consumeGas(totalGasCost, reason="Log topic and data gas cost")
`computation`.extendMemory(`memStartPosition`, `size`)
let logData = `computation`.memory.read(`memStartPosition`, `size`).toCString
let logData = `computation`.memory.read(`memStartPosition`, `size`).toString
`computation`.addLogEntry(
account=`computation`.msg.storageAddress,
topics=`topics`,

View File

@ -12,8 +12,8 @@ proc mstoreX(computation; x: int) =
let start = stack.popInt()
let value = stack.popBinary()
let paddedValue = padLeft(value, x, cstring"\x00")
let normalizedValue = cstring(($paddedValue)[^x .. ^1])
let paddedValue = padLeft(value, x, "\x00")
let normalizedValue = ($paddedValue)[^x .. ^1]
extendMemory(start, x.int256)
memory.write(start, 32.int256, normalizedValue)
@ -31,7 +31,7 @@ proc mload*(computation) =
extendMemory(start, 32.int256)
let value = memory.read(start, 32.int256).toCstring
let value = memory.read(start, 32.int256).toString
stack.push(value)
proc msize*(computation) =

View File

@ -8,5 +8,5 @@ proc sha3op*(computation: var BaseComputation) =
let wordCount = sha3Bytes.len.i256.ceil32 div 32
let gasCost = constants.GAS_SHA3_WORD * wordCount
computation.gasMeter.consumeGas(gasCost, reason="SHA3: word gas cost")
var res = keccak(cstring"")
var res = keccak("")
pushRes()

View File

@ -17,12 +17,12 @@ macro pushXX(size: static[int]): untyped =
let name = ident(&"push{size}")
result = quote:
proc `name`*(`computation`: var BaseComputation) =
let `value` = `computation`.code.read(`size`).toCString
let `value` = `computation`.code.read(`size`).toString
let stripped = `value`.strip(0.char)
if stripped.len == 0:
`computation`.stack.push(0.i256)
else:
let paddedValue = `value`.padRight(`size`, cstring"\x00")
let paddedValue = `value`.padRight(`size`, "\x00")
`computation`.stack.push(paddedValue)

View File

@ -50,7 +50,7 @@ method runLogic*(create: Create, computation) =
# )
# is_collision = state_db.account_has_code_or_nonce(contract_address)
let contractAddress = cstring""
let contractAddress = ""
let isCollision = false
if isCollision:
@ -63,7 +63,7 @@ method runLogic*(create: Create, computation) =
to=constants.CREATE_CONTRACT_ADDRESS,
value=value,
data=cast[seq[byte]](@[]),
code=callData.toCString,
code=callData.toString,
options=MessageOptions(createAddress: contractAddress))
# let childComputation = computation.applyChildComputation(childMsg)
@ -109,7 +109,7 @@ proc selfdestructEIP161(computation) =
# )
# _selfdestruct(computation, beneficiary)
proc selfdestruct(computation; beneficiary: cstring) =
proc selfdestruct(computation; beneficiary: string) =
discard # TODO: with
# with computation.vm_state.state_db() as state_db:
# local_balance = state_db.get_balance(computation.msg.storage_address)
@ -136,14 +136,14 @@ proc selfdestruct(computation; beneficiary: cstring) =
proc returnOp*(computation) =
let (startPosition, size) = stack.popInt(2)
computation.extendMemory(startPosition, size)
let output = memory.read(startPosition, size).toCString
let output = memory.read(startPosition, size).toString
computation.output = output
raise newException(Halt, "RETURN")
proc revert*(computation) =
let (startPosition, size) = stack.popInt(2)
computation.extendMemory(startPosition, size)
let output = memory.read(startPosition, size).toCString
let output = memory.read(startPosition, size).toString
computation.output = output
raise newException(Revert, $output)

View File

@ -1,7 +1,7 @@
# TODO : hex
type
Op* {.pure.} = enum
STOP, # 0
STOP = 0x0, # 0
ADD, # 1
MUL, # 2
SUB, # 3
@ -14,7 +14,7 @@ type
EXP, # 10
SIGNEXTEND, # 11
LT, # 16
LT = 0x10, # 16
GT, # 17
SLT, # 18
SGT, # 19
@ -26,9 +26,9 @@ type
NOT, # 25
BYTE, # 26
SHA3, # 32
SHA3 = 0x20, # 32
ADDRESS, # 48
ADDRESS = 0x30,# 48
BALANCE, # 49
ORIGIN, # 50
@ -49,7 +49,7 @@ type
RETURNDATASIZE, # 61
RETURNDATACOPY, # 62
BLOCKHASH, # 64
BLOCKHASH = 0x40,# 64
COINBASE, # 65
@ -61,7 +61,7 @@ type
GASLIMIT, # 69
POP, # 80
POP = 0x50, # 80
MLOAD, # 81
MSTORE, # 82
@ -81,7 +81,7 @@ type
JUMPDEST, # 91
PUSH1, # 96
PUSH1 = 0x60, # 96
PUSH2, # 97
PUSH3, # 98
PUSH4, # 99
@ -150,13 +150,13 @@ type
LOG2, # 162
LOG3, # 163
LOG4, # 164
CREATE, # 240
CREATE = 0xf0, # 240
CALL, # 241
CALLCODE, # 242
RETURN, # 243
DELEGATECALL, # 244
STATICCALL, # 250
REVERT, # 253
SELFDESTRUCT # 255
STATICCALL = 0xfa,# 250
REVERT = 0xfd, # 253
SELFDESTRUCT = 0xff,# 255
INVALID # invalid

View File

@ -112,10 +112,10 @@ opcodes[Op.Create] = Create(kind: Op.Create)
var mem = newMemory(pow(1024.int256, 2))
var to = toCanonicalAddress(cstring"0x0f572e5295c57f15886f9b263e2f6d2d6c7b5ec6")
var sender = toCanonicalAddress(cstring"0xcd1722f3947def4cf144679da39c4c32bdc35681")
var to = toCanonicalAddress("0x0f572e5295c57f15886f9b263e2f6d2d6c7b5ec6")
var sender = toCanonicalAddress("0xcd1722f3947def4cf144679da39c4c32bdc35681")
var code = cstring""
var code = ""
var data: seq[byte] = @[]
var msg = newMessage(
@ -140,14 +140,14 @@ var c = BaseComputation(
gasMeter: newGasMeter(msg.gas),
code: newCodeStream(code),
children: @[],
rawOutput: cstring"",
returnData: cstring"",
rawOutput: "",
returnData: "",
error: nil,
logEntries: @[],
shouldEraseReturnData: false,
accountsToDelete: initTable[cstring, cstring](),
accountsToDelete: initTable[string, string](),
opcodes: opcodes,
precompiles: initTable[cstring, Opcode]())
precompiles: initTable[string, Opcode]())
# var c2 = c.applyComputation(c.vmState, c.msg)

View File

@ -6,9 +6,9 @@ type
nonce*: Int256
gasPrice*: Int256
gas*: Int256
to*: cstring
to*: string
value*: Int256
data*: cstring
data*: string
v*: Int256
r*: Int256
s*: Int256
@ -31,6 +31,6 @@ proc validate*(t: BaseTransaction) =
raise newException(ValidationError, "Insufficient gas")
# self.check_signature_validity()
proc sender*(t: BaseTransaction): cstring =
proc sender*(t: BaseTransaction): string =
# TODO
cstring""
""

View File

@ -1,11 +1,19 @@
proc toBytes*(value: cstring): seq[byte] =
result = newSeq[byte](value.len)
for z, c in value:
result[z] = c.byte
# result = toSeq(value)
import strutils, sequtils
proc toCString*(value: seq[byte]): cstring =
var res = ""
for c in value:
res.add(c.char)
cstring(res) # TODO: faster
# proc toBytes*(value: cstring): seq[byte] =
# result = newSeq[byte](value.len)
# for z, c in value:
# result[z] = c.byte
# # result = toSeq(value)
# proc toCString*(value: seq[byte]): cstring =
# var res = ""
# for c in value:
# res.add(c.char)
# cstring(res) # TODO: faster
proc toString*(value: seq[byte]): string =
value.mapIt(it.char).join("")
proc toBytes*(value: string): seq[byte] =
result = value.mapIt(it.byte)

View File

@ -5,16 +5,16 @@ type
timestamp*: int
difficulty*: Int256
blockNumber*: Int256
hash*: cstring
coinbase*: cstring
hash*: string
coinbase*: string
# TODO
proc generateHeaderFromParentHeader*(
computeDifficultyFn: proc(parentHeader: Header, timestamp: int): int,
parentHeader: Header,
coinbase: cstring,
coinbase: string,
timestamp: int = -1,
extraData: cstring = cstring""): Header =
extraData: string = string""): Header =
Header()
# Generate BlockHeader from state_root and parent_header
# if timestamp is None:

View File

@ -1,11 +1,11 @@
import strutils
proc encodeHex*(value: cstring): string =
proc encodeHex*(value: string): string =
# return "0x" & codecs.decode(codecs.encode(value, "hex"), "utf8")
return $value
return value
proc decodeHex*(value: string): cstring =
proc decodeHex*(value: string): string =
# var hexPart = value.rsplit("x", 1)[1]
return cstring(value)
return value
# return codecs.decode(hexPart, "hex")

View File

@ -1,10 +1,10 @@
import
keccak_tiny, strutils
template keccak*(value: string): cstring =
cstring($keccak_256(value))
template keccak*(value: string): string =
$keccak_256(value)
template keccak*(value: cstring): cstring =
template keccak*(value: cstring): string =
($value).keccak

View File

@ -88,3 +88,9 @@ proc rStrip*(value: cstring, c: char): cstring =
proc strip*(value: cstring, c: char): cstring =
result = value.lStrip(c).rStrip(c)
proc lStrip*(value: string, c: char): string =
value.strip(chars={c}, trailing=false)
proc rStip*(value: string, c: char): string =
value.strip(chars={c}, leading=false)

View File

@ -1,9 +1,9 @@
import bigints, constants, strformat, macros
proc intToBigEndian*(value: Int256): cstring =
result = cstring""
proc intToBigEndian*(value: Int256): string =
result = ""
proc bigEndianToInt*(value: cstring): Int256 =
proc bigEndianToInt*(value: string): Int256 =
result = 0.int256
proc unsignedToSigned*(value: Int256): Int256 =

View File

@ -4,7 +4,7 @@ import
strformat,
errors, constants, bigints
proc validateCanonicalAddress*(value: cstring, title: string = "Value") =
proc validateCanonicalAddress*(value: string, title: string = "Value") =
# TODO
if false: #len(value) != 20:
raise newException(ValidationError,

View File

@ -1,5 +1,5 @@
import
strformat, strutils, sequtils, sets,
strformat, strutils, sequtils, sets, macros,
../logging, ../constants, ../opcode_values
type
@ -13,37 +13,43 @@ type
proc `$`*(b: byte): string =
$(b.int)
proc newCodeStream*(codeBytes: cstring): CodeStream =
proc newCodeStream*(codeBytes: seq[byte]): CodeStream =
new(result)
result.bytes = codeBytes.mapIt(it.byte)
result.bytes = codeBytes
result.pc = 0
result.invalidPositions = initSet[int]()
result.depthProcessed = 0
result.logger = logging.getLogger("evm.vm.CodeStream")
result.logger = logging.getLogger("vm.code_stream")
proc newCodeStream*(codeBytes: string): CodeStream =
newCodeStream(codeBytes.mapIt(it.byte))
proc read*(c: var CodeStream, size: int): seq[byte] =
result = c.bytes[c.pc .. c.pc + size - 1]
c.pc += size
if c.pc + size - 1 < c.bytes.len:
result = c.bytes[c.pc .. c.pc + size - 1]
c.pc += size
else:
result = @[]
c.pc = c.bytes.len
proc len*(c: CodeStream): int =
len(c.bytes)
proc next*(c: var CodeStream): Op =
var nextOpcode = c.read(1)
if nextOpcode[0] != 0x0.byte:
if nextOpcode.len != 0:
return Op(nextOpcode[0])
else:
return Op.STOP
iterator items*(c: var CodeStream): Op =
var nextOpcode = c.next()
while nextOpcode != Op.STOP:
yield nextOpcode
nextOpcode = c.next()
proc `[]`*(c: CodeStream, offset: int): byte =
c.bytes[offset]
proc `[]`*(c: CodeStream, offset: int): Op =
Op(c.bytes[offset])
proc peek*(c: var CodeStream): Op =
var currentPc = c.pc
@ -53,14 +59,16 @@ proc peek*(c: var CodeStream): Op =
proc updatePc*(c: var CodeStream, value: int) =
c.pc = min(value, len(c))
template seek*(c: var CodeStream, pc: int, handler: untyped): untyped =
var anchorPc = pc
`c`.pc = pc
try:
var c = `c` {.inject.}
`handler`
finally:
`c`.pc = anchorPc
macro seek*(c: var CodeStream, pc: int, handler: untyped): untyped =
let c2 = ident("c")
result = quote:
var anchorPc = `c`.pc
`c`.pc = `pc`
try:
var `c2` = `c`
`handler`
finally:
`c`.pc = anchorPc
proc isValidOpcode*(c: var CodeStream, position: int): bool =
if position >= len(c):

View File

@ -5,4 +5,4 @@ import
proc expGasCost*(computation: var BaseComputation): Int256 =
let arg = computation.stack.getInt(0)
result = if arg == 0: 10.i256 else: (10.i256 + 10.i256 * (1.i256 + arg.log256))

View File

@ -19,41 +19,41 @@ type
gas*: Int256
gasPrice*: Int256
to*: cstring
sender*: cstring
to*: string
sender*: string
value*: Int256
data*: seq[byte]
code*: cstring
internalOrigin: cstring
internalCodeAddress: cstring
code*: string
internalOrigin: string
internalCodeAddress: string
depth*: int
internalStorageAddress: cstring
internalStorageAddress: string
shouldTransferValue*: bool
isStatic*: bool
isCreate*: bool
MessageOptions* = ref object
origin*: cstring
origin*: string
depth*: int
createAddress*: cstring
codeAddress*: cstring
createAddress*: string
codeAddress*: string
shouldTransferValue*: bool
isStatic*: bool
proc `origin=`*(message: var Message, value: cstring) =
proc `origin=`*(message: var Message, value: string) =
message.internalOrigin = value
proc `codeAddress=`*(message: var Message, value: cstring) =
proc `codeAddress=`*(message: var Message, value: string) =
message.internalCodeAddress = value
proc `storageAddress=`*(message: var Message, value: cstring) =
proc `storageAddress=`*(message: var Message, value: string) =
message.internalStorageAddress = value
proc newMessageOptions*(
origin: cstring = nil,
origin: string = "",
depth: int = 0,
createAddress: cstring = nil,
codeAddress: cstring = nil,
createAddress: string = "",
codeAddress: string = "",
shouldTransferValue: bool = true,
isStatic: bool = false): MessageOptions =
@ -68,11 +68,11 @@ proc newMessageOptions*(
proc newMessage*(
gas: Int256,
gasPrice: Int256,
to: cstring,
sender: cstring,
to: string,
sender: string,
value: Int256,
data: seq[byte],
code: cstring,
code: string,
options: MessageOptions = newMessageOptions()): Message =
new(result)
@ -111,8 +111,8 @@ proc newMessage*(
result.isStatic = options.isStatic
proc origin*(message: Message): cstring =
if not message.internalOrigin.isNil:
proc origin*(message: Message): string =
if not message.internalOrigin.len == 0:
message.internalOrigin
else:
message.sender
@ -120,14 +120,14 @@ proc origin*(message: Message): cstring =
proc isOrigin*(message: Message): bool =
message.sender == message.origin
proc codeAddress*(message: Message): cstring =
if not message.internalCodeAddress.isNil:
proc codeAddress*(message: Message): string =
if not message.internalCodeAddress.len == 0:
message.internalCodeAddress
else:
message.to
proc `storageAddress`*(message: Message): cstring =
if not message.internalStorageAddress.isNil:
proc `storageAddress`*(message: Message): string =
if not message.internalStorageAddress.len == 0:
message.internalStorageAddress
else:
message.to

View File

@ -34,7 +34,7 @@ proc push*(stack: var Stack; value: Int256) =
stack.values.add(Value(kind: VInt, i: value))
proc push*(stack: var Stack; value: cstring) =
proc push*(stack: var Stack; value: string) =
## Push a binary onto the stack
ensureStackLimit()
@ -50,13 +50,13 @@ proc internalPop(stack: var Stack; numItems: int): seq[Value] =
template toType(i: Int256, _: typedesc[Int256]): Int256 =
i
template toType(i: Int256, _: typedesc[cstring]): cstring =
template toType(i: Int256, _: typedesc[string]): string =
intToBigEndian(i)
template toType(b: cstring, _: typedesc[Int256]): Int256 =
template toType(b: string, _: typedesc[Int256]): Int256 =
bigEndianToInt(b)
template toType(b: cstring, _: typedesc[cstring]): cstring =
template toType(b: string, _: typedesc[string]): string =
b
proc internalPop(stack: var Stack; numItems: int, T: typedesc): seq[T] =
@ -136,13 +136,13 @@ macro popInt*(stack: typed; numItems: static[int]): untyped =
# result = stack.internalPop(numItems, Int256)
# ensurePop(result, numItems)
proc popBinary*(stack: var Stack): cstring =
var elements = stack.internalPop(1, cstring)
proc popBinary*(stack: var Stack): string =
var elements = stack.internalPop(1, string)
ensurePop(elements, 1)
result = elements[0]
proc popBinary*(stack: var Stack; numItems: int): seq[cstring] =
result = stack.internalPop(numItems, cstring)
proc popBinary*(stack: var Stack; numItems: int): seq[string] =
result = stack.internalPop(numItems, string)
ensurePop(result, numItems)
proc newStack*(): Stack =
@ -176,7 +176,7 @@ proc getInt*(stack: Stack, position: int): Int256 =
else:
raise newException(TypeError, "Expected int")
proc getBinary*(stack: Stack, position: int): cstring =
proc getBinary*(stack: Stack, position: int): string =
let element = stack.values[position]
case element.kind:
of VBinary:

View File

@ -10,7 +10,7 @@ type
of VInt:
i*: Int256
of VBinary:
b*: cstring
b*: string
proc `$`*(value: Value): string =
case value.kind:
@ -25,6 +25,6 @@ proc vint*(i: int): Value =
proc vint*(i: Int256): Value =
Value(kind: VInt, i: i)
proc vbinary*(b: cstring): Value =
proc vbinary*(b: string): Value =
Value(kind: VBinary, b: b)

View File

@ -19,10 +19,10 @@ proc newBaseVMState*: BaseVMState =
method logger*(vmState: BaseVMState): Logger =
logging.getLogger(&"evm.vmState.{vmState.name}")
method blockhash*(vmState: BaseVMState): cstring =
method blockhash*(vmState: BaseVMState): string =
vmState.blockHeader.hash
method coinbase*(vmState: BaseVMState): cstring =
method coinbase*(vmState: BaseVMState): string =
vmState.blockHeader.coinbase
method timestamp*(vmState: BaseVMState): int =
@ -37,11 +37,11 @@ method difficulty*(vmState: BaseVMState): Int256 =
method gasLimit*(vmState: BaseVMState): Int256 =
vmState.blockHeader.gasLimit
method getAncestorHash*(vmState: BaseVMState, blockNumber: Int256): cstring =
method getAncestorHash*(vmState: BaseVMState, blockNumber: Int256): string =
var ancestorDepth = vmState.blockHeader.blockNumber - blockNumber - 1.int256
if ancestorDepth >= constants.MAX_PREV_HEADER_DEPTH or
ancestorDepth < 0 or
ancestorDepth >= vmState.prevHeaders.len.int256:
return cstring""
return ""
var header = vmState.prevHeaders[ancestorDepth.getInt]
result = header.hash

View File

@ -7,7 +7,7 @@ method executeTransaction(vmState: var BaseVMState, transaction: BaseTransaction
raise newException(ValueError, "Must be implemented by subclasses")
method addTransaction*(vmState: var BaseVMState, transaction: BaseTransaction, computation: BaseComputation, b: Block): (Block, Table[cstring, cstring]) =
method addTransaction*(vmState: var BaseVMState, transaction: BaseTransaction, computation: BaseComputation, b: Block): (Block, Table[string, string]) =
# Add a transaction to the given block and
# return `trieData` to store the transaction data in chaindb in VM layer
# Update the bloomFilter, transaction trie and receipt trie roots, bloom_filter,
@ -34,13 +34,13 @@ method addTransaction*(vmState: var BaseVMState, transaction: BaseTransaction, c
# block.header.gas_used = receipt.gas_used
# return block, trie_data
result = (b, initTable[cstring, cstring]())
result = (b, initTable[string, string]())
method applyTransaction*(
vmState: var BaseVMState,
transaction: BaseTransaction,
b: Block,
isStateless: bool): (BaseComputation, Block, Table[cstring, cstring]) =
isStateless: bool): (BaseComputation, Block, Table[string, string]) =
# Apply transaction to the given block
# transaction: the transaction need to be applied
# b: the block which the transaction applies on
@ -52,10 +52,10 @@ method applyTransaction*(
var (computation, blockHeader) = vmState.executeTransaction(transaction)
ourBlock.header = blockHeader
var trieData: Table[cstring, cstring]
var trieData: Table[string, string]
(ourBlock, trieData) = vmState.addTransaction(transaction, computation, ourBlock)
result = (computation, ourBlock, trieData)
else:
var (computation, blockHeader) = vmState.executeTransaction(transaction)
return (computation, nil, initTable[cstring, cstring]())
return (computation, nil, initTable[string, string]())

8
tests/README.md Normal file
View File

@ -0,0 +1,8 @@
# tests
TODO: more vm tests and fixtures!
```bash
nim c tests/code_stream_test.nim
./tests/code_stream_test
```

108
tests/code_stream_test.nim Normal file
View File

@ -0,0 +1,108 @@
import unittest, strutils, sequtils, opcode_values, vm / code_stream
suite "parse bytecode":
test "accepts bytes":
let codeStream = newCodeStream("\x01")
check(codeStream.len == 1)
# quicktest
# @pytest.mark.parametrize("code_bytes", (1010, '1010', True, bytearray(32)))
# def test_codeStream_rejects_invalid_code_byte_values(code_bytes):
# with pytest.raises(ValidationError):
# CodeStream(code_bytes)
test "next returns the correct opcode":
var codeStream = newCodeStream("\x01\x02\x30")
check(codeStream.next == Op.ADD)
check(codeStream.next == Op.MUL)
check(codeStream.next == Op.ADDRESS)
test "peek returns next opcode without changing location":
var codeStream = newCodeStream("\x01\x02\x30")
check(codeStream.pc == 0)
check(codeStream.peek == Op.ADD)
check(codeStream.pc == 0)
check(codeStream.next == Op.ADD)
check(codeStream.pc == 1)
check(codeStream.peek == Op.MUL)
check(codeStream.pc == 1)
test "stop opcode is returned when end reached":
var codeStream = newCodeStream("\x01\x02")
discard codeStream.next
discard codeStream.next
check(codeStream.next == Op.STOP)
test "seek reverts to original position on exit":
var codeStream = newCodeStream("\x01\x02\x30")
check(codeStream.pc == 0)
codeStream.seek(1):
check(codeStream.pc == 1)
check(codeStream.next == Op.MUL)
check(codeStream.pc == 0)
check(codeStream.peek == Op.ADD)
test "[] returns opcode":
let codeStream = newCodeStream("\x01\x02\x30")
check(codeStream[0] == Op.ADD)
check(codeStream[1] == Op.MUL)
check(codeStream[2] == Op.ADDRESS)
test "isValidOpcode invalidates after PUSHXX":
var codeStream = newCodeStream("\x02\x60\x02\x04")
check(codeStream.isValidOpcode(0))
check(codeStream.isValidOpcode(1))
check(not codeStream.isValidOpcode(2))
check(codeStream.isValidOpcode(3))
check(not codeStream.isValidOpcode(4))
test "isValidOpcode 0":
var codeStream = newCodeStream(@[2.byte, 3.byte, 0x72.byte].concat(repeat(4.byte, 32)).concat(@[5.byte]))
# valid: 0 - 2 :: 22 - 35
# invalid: 3-21 (PUSH19) :: 36+ (too long)
check(codeStream.isValidOpcode(0))
check(codeStream.isValidOpcode(1))
check(codeStream.isValidOpcode(2))
check(not codeStream.isValidOpcode(3))
check(not codeStream.isValidOpcode(21))
check(codeStream.isValidOpcode(22))
check(codeStream.isValidOpcode(35))
check(not codeStream.isValidOpcode(36))
test "isValidOpcode 1":
let test = @[2.byte, 3.byte, 0x7d.byte].concat(repeat(4.byte, 32)).concat(@[5.byte, 0x7e.byte]).concat(repeat(4.byte, 35)).concat(@[1.byte, 0x61.byte, 1.byte, 1.byte, 1.byte])
var codeStream = newCodeStream(test)
# valid: 0 - 2 :: 33 - 36 :: 68 - 73 :: 76
# invalid: 3 - 32 (PUSH30) :: 37 - 67 (PUSH31) :: 74, 75 (PUSH2) :: 77+ (too long)
check(codeStream.isValidOpcode(0))
check(codeStream.isValidOpcode(1))
check(codeStream.isValidOpcode(2))
check(not codeStream.isValidOpcode(3))
check(not codeStream.isValidOpcode(32))
check(codeStream.isValidOpcode(33))
check(codeStream.isValidOpcode(36))
check(not codeStream.isValidOpcode(37))
check(not codeStream.isValidOpcode(67))
check(codeStream.isValidOpcode(68))
check(codeStream.isValidOpcode(71))
check(codeStream.isValidOpcode(72))
check(codeStream.isValidOpcode(73))
check(not codeStream.isValidOpcode(74))
check(not codeStream.isValidOpcode(75))
check(codeStream.isValidOpcode(76))
check(not codeStream.isValidOpcode(77))
test "right number of bytes invalidates":
var codeStream = newCodeStream("\x02\x03\x60\x02\x02")
check(codeStream.isValidOpcode(0))
check(codeStream.isValidOpcode(1))
check(codeStream.isValidOpcode(2))
check(not codeStream.isValidOpcode(3))
check(codeStream.isValidOpcode(4))
check(not codeStream.isValidOpcode(5))

1
tests/nim.cfg Normal file
View File

@ -0,0 +1 @@
-p:"../src"