From 706f3982a7ce08fedd4223b375dcdabcc604e98d Mon Sep 17 00:00:00 2001 From: Alexander Ivanov Date: Tue, 16 Jan 2018 19:05:20 +0200 Subject: [PATCH] Translate several more modules, start shaping computation module --- src/computation.nim | 314 ++++++++++++++++++++++++++++++++++++++ src/constants.nim | 117 ++++++++++---- src/logging.nim | 8 +- src/logic/arithmetic.nim | 104 +++++++++++++ src/utils/hexadecimal.nim | 11 ++ src/utils/state.nim | 13 ++ src/utils_numeric.nim | 34 ++++- src/vm/memory.nim | 28 ++++ src/vm/message.nim | 58 ++++--- src/vm/stack.nim | 92 +++++++---- src/vm/value.nim | 4 +- src/vm_state.nim | 25 +++ 12 files changed, 726 insertions(+), 82 deletions(-) create mode 100644 src/computation.nim create mode 100644 src/logic/arithmetic.nim create mode 100644 src/utils/hexadecimal.nim create mode 100644 src/utils/state.nim create mode 100644 src/vm/memory.nim create mode 100644 src/vm_state.nim diff --git a/src/computation.nim b/src/computation.nim new file mode 100644 index 000000000..9ffe327b2 --- /dev/null +++ b/src/computation.nim @@ -0,0 +1,314 @@ +import + strformat, strutils, sequtils, tables, macros, + constants, errors, utils/hexadecimal, utils_numeric, validation, vm_state, logging, + vm / [code_stream, gas_meter, memory, message, stack] + +proc memoryGasCost*(sizeInBytes: Int256): Int256 = + var + sizeInWords = ceil32(sizeInBytes) div 32 + linearCost = sizeInWords * GAS_MEMORY + quadraticCost = sizeInWords ^ 2 div GAS_MEMORY_QUADRATIC_DENOMINATOR + totalCost = linearCost + quadraticCost + result = totalCost + +type + BaseComputation* = ref object of RootObj + # The execution computation + vmState*: BaseVMState + msg*: Message + memory*: Memory + stack*: Stack + gasMeter*: GasMeter + code*: CodeStream + children*: seq[BaseComputation] + rawOutput*: cstring + returnData*: cstring + error*: Error + logEntries*: seq[(cstring, seq[Int256], cstring)] + shouldEraseReturnData*: bool + accountsToDelete*: Table[cstring, cstring] + opcodes*: cstring + precompiles: cstring + logs*: bool + logger*: Logger + + Error* = ref object + info*: string + burnsGas*: bool + erasesReturnData*: bool + +proc newBaseComputation*(vmState: BaseVMState, message: Message): BaseComputation = + new(result) + result.vmState = vmState + result.msg = message + result.memory = Memory() + result.stack = newStack() + result.gasMeter = newGasMeter(message.gas) + result.children = @[] + result.accountsToDelete = initTable[cstring, cstring]() + result.logEntries = @[] + result.code = newCodeStream(message.code) + result.logger = logging.getLogger("evm.vm.computation.BaseComputation") + +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, + gas: Int256, + to: cstring, + value: Int256, + data: cstring, + code: cstring, + options: MessageOptions = newMessageOptions()): Message = + + # ? kwargs.setdefault('sender', self.msg.storage_address) + + var childOptions = options + childOptions.depth = c.msg.depth + 1.Int256 + result = newMessage( + gas, + c.msg.gasPrice, + c.msg.origin, + to, + value, + data, + code, + childOptions) + + # +method extendMemory*(c: var BaseComputation, startPosition: Int256, size: Int256) = + # Memory Management + # + # validate_uint256(start_position, title="Memory start position") + # validate_uint256(size, title="Memory size") + + let beforeSize = ceil32(len(c.memory).Int256) + let afterSize = ceil32(startPosition + size) + + let beforeCost = memoryGasCost(beforeSize) + let afterCost = memoryGasCost(afterSize) + + c.logger.debug(%"MEMORY: size ({beforeSize} -> {afterSize}) | cost ({beforeCost} -> {afterCost})") + + if size > 0: + if beforeCost < afterCost: + var gasFee = afterCost - beforeCost + c.gasMeter.consumeGas( + gasFee, + reason = %"Expanding memory {beforeSize} -> {afterSize}") + + c.memory.extend(startPosition, size) + +method output*(c: BaseComputation): cstring = + if c.shouldEraseReturnData: + cstring"" + else: + c.rawOutput + +method `output=`*(c: var BaseComputation, value: cstring) = + c.rawOutput = value + +macro generateChildBaseComputation*(t: typed, vmState: typed, childMsg: typed): untyped = + var typ = repr(getType(t)[1]).split(":", 1)[0] + var name = ident(%"new{typ}") + var typName = ident(typ) + result = quote: + block: + var c: `typName` + if childMsg.isCreate: + var child = `name`(`vmState`, `childMsg`) + c = child.applyCreateMessage() + else: + 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: + c.returnData = cstring"" + else: + c.returnData = childBaseComputation.output + else: + if childBaseComputation.msg.isCreate: + c.returnData = cstring"" + 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 + +method registerAccountForDeletion*(c: var BaseComputation, beneficiary: cstring) = + 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 + +method addLogEntry*(c: var BaseComputation, account: cstring, topics: seq[Int256], data: cstring) = + validateCanonicalAddress(account, title="log entry address") + c.logEntries.add((account, topics, data)) + +method getAccountsForDeletion*(c: BaseComputation): seq[(cstring, cstring)] = + # TODO + if c.isError: + result = @[] + else: + result = @[] + +method getLogEntries*(c: BaseComputation): seq[(cstring, seq[Int256], cstring)] = + # TODO + if c.isError: + result = @[] + else: + result = @[] + +method getGasRefund*(c: BaseComputation): Int256 = + if c.isError: + result = 0.Int256 + else: + result = c.gasMeter.gasRefunded + c.children.mapIt(it.getGasRefund()).foldl(a + b, 0.Int256) + +method getGasUsed*(c: BaseComputation): Int256 = + if c.shouldBurnGas: + result = c.msg.gas + else: + result = max(0.Int256, c.msg.gas - c.gasMeter.gasRemaining) + +method getGasRemaining*(c: BaseComputation): Int256 = + if c.shouldBurnGas: + result = 0.Int256 + else: + result = c.gasMeter.gasRemaining + +# +# Context Manager API +# + +template inBaseComputation*(c: untyped, handler: untyped): untyped = + `c`.logger.debug( + "COMPUTATION STARTING: gas: $1 | from: $2 | to: $3 | value: $4 | depth: $5 | static: $6" % [ + $`c`.msg.gas, + $encodeHex(`c`.msg.sender), + $encodeHex(`c`.msg.to), + $`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" % [ + $encodeHex(c.msg.sender), + $encodeHex(c.msg.to), + $c.msg.value, + $c.msg.depth, + if c.msg.isStatic: "y" else: "n", + $(c.msg.gas - c.gasMeter.gasRemaining), + $c.msg.gasRemaining]) + except VMError: + `c`.logger.debug( + "COMPUTATION ERROR: gas: $1 | from: $2 | to: $3 | value: $4 | depth: $5 | static: $6 | error: $7" % [ + $`c`.msg.gas, + $encodeHex(`c`.msg.sender), + $encodeHex(`c`.msg.to), + $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()) + +macro applyComputation(t: typed, vmState: untyped, message: untyped): untyped = + # Perform the computation that would be triggered by the VM message + var typ = repr(getType(t)[1]).split(":", 1)[0] + var name = ident(%"new{typ}") + var typName = ident(typ) + result = quote: + block: + var res: `typName` + var c = `name`(`vmState`, `message`) + var handler = proc: `typName` = + if `message`.codeAddress in c.precompiles: + c.precompiles[`message`.codeAddress](c) + return c + + for opcode in c.code: + var opcodeFn = c.getOpcodeFn(c.opcodes, opcode) + c.logger.trace( + "OPCODE: 0x$1 ($2) | pc: $3" % [$opcode, $opcodeFn, $max(0, c.code.pc - 1)]) + + #try: + # somehow call opcodeFn(c) + #except halt: + # break + return c + inComputation(c): + res = handler() + res + + # # + # # Opcode API + # # + # @property + # def precompiles(self): + # if self._precompiles is None: + # return dict() + # else: + # return self._precompiles + + # def get_opcode_fn(self, opcodes, opcode): + # try: + # return opcodes[opcode] + # except KeyError: + # return InvalidOpcode(opcode) + + # # + # # classmethod + # # + # @classmethod + # def configure(cls, + # name, + # **overrides): + # """ + # Class factory method for simple inline subclassing. + # """ + # for key in overrides: + # if not hasattr(cls, key): + # raise TypeError( + # "The BaseComputation.configure cannot set attributes that are not " + # "already present on the base class. The attribute `{0}` was " + # "not found on the base class `{1}`".format(key, cls) + # ) + # return type(name, (cls,), overrides) diff --git a/src/constants.nim b/src/constants.nim index c3efd1601..abf56bbb7 100644 --- a/src/constants.nim +++ b/src/constants.nim @@ -7,23 +7,45 @@ type Int256* = distinct int # TODO +proc `==`*(a: Int256, b: Int256): bool = + a.int == b.int + +proc `==`*(a: Int256, b: int): bool = + a.int == b + +proc `!=`*(a: Int256, b: Int256): bool = + a.int != b.int + +proc `!=`*(a: Int256, b: int): bool = + a.int != b + +proc `^`*(a: Int256, b: Int256): Int256 = + (a.int ^ b.int).Int256 + +proc `^`*(a: Int256, b: int): Int256 = + (a.int ^ b).Int256 + proc `>`*(a: Int256, b: Int256): bool = a.int > b.int +proc `>`*(a: Int256, b: int): bool = + a.int > b + +proc `>=`*(a: Int256, b: Int256): bool = + a.int >= b.int + proc `<`*(a: Int256, b: Int256): bool = a.int < b.int +proc `<`*(a: Int256, b: int): bool = + a.int < b + +proc `<=`*(a: Int256, b: Int256): bool = + a.int <= b.int + proc `$`*(a: Int256): string = $(a.int) -# proc `-`*(a: Int256, b: Int256): Int256 {.borrow.} - -# proc `+`*(a: Int256, b: Int256): Int256 {.borrow.} - -# proc `-=`*(a: var Int256, b: Int256) {.borrow.} - -# proc `+=`*(a: var Int256, b: Int256) {.borrow.} - proc `-`*(a: Int256, b: Int256): Int256 = (a.int - b.int).Int256 @@ -36,36 +58,67 @@ proc `-=`*(a: var Int256, b: Int256) = proc `+=`*(a: var Int256, b: Int256) = a = (a + b).Int256 +proc `*`*(a: Int256, b: Int256): Int256 = + (a.int * b.int).Int256 + +proc `mod`*(a: Int256, b: Int256): Int256 = + (a.int mod b.int).Int256 + +proc `mod`*(a: Int256, b: int): Int256 = + (a.int mod b).Int256 + +proc `div`*(a: Int256, b: Int256): Int256 = + (a.int div b.int).Int256 + +proc `div`*(a: Int256, b: int): Int256 = + (a.int div b).Int256 + +proc `abs`*(a: Int256): Int256 = + a.int.abs.Int256 + +proc `and`*(a: Int256, b: Int256): Int256 = + (a.int and b.int).Int256 + +proc `or`*(a: Int256, b: Int256): Int256 = + (a.int or b.int).Int256 + +proc max*(a: Int256, b: Int256): Int256 = + max(a.int, b.int).Int256 + +proc min*(a: Int256, b: Int256): Int256 = + min(a.int, b.int).Int256 + proc repeat(b: cstring, count: int): cstring = # TODO: faster var s = $b result = cstring(repeat(s, count)) const - # UINT256MAX = 2 ^ 256 - 1 - # UINT256CEILING = 2 ^ 256 - # UINT255MAX = 2 ^ 255 - 1 - # UINT255CEILING = 2 ^ 255 + X = 62 # 256 + UINT_256_MAX* = (2 ^ X - 1).Int256 + UINT_256_CEILING* = (2 ^ X).Int256 + UINT_255_MAX* = (2 ^ (X - 1) - 1).Int256 + UINT_255_CEILING* = (2 ^ (X - 1)).Int256 NULLBYTE = cstring"\\x00" EMPTYWORD = repeat(NULLBYTE, 32) # UINT160CEILING = 2 ^ 160 CREATE_CONTRACT_ADDRESS* = cstring"" - ZEROADDRESS = repeat(cstring"\\x00", 20) - ZEROHASH32 = repeat(cstring"\\x00", 20) - STACKDEPTHLIMIT = 1024 - GASNULL = 0 - GASZERO = 0 - GASBASE = 2 - GASVERYLOW = 3 - GASLOW = 5 - GASMID = 8 - GASHIGH = 10 - GASEXTCODE = 20 - GASBALANCE = 20 - GASSLOAD = 50 - GASJUMPDEST = 1 - GASSSET = 20000 - GASSRESET = 5000 + ZERO_ADDRESS* = repeat(cstring"\x00", 20) + ZERO_HASH32* = repeat(cstring"\x00", 20) + STACKDEPTHLIMIT* = 1024 + GAS_NULL* = 0.Int256 + GAS_ZERO* = 0.Int256 + GAS_BASE* = 2.Int256 + GAS_VERY_LOW* = 3.Int256 + GAS_LOW* = 5.Int256 + GAS_MID* = 8.Int256 + GAS_HIGH* = 10.Int256 + GAS_EXT_CODE* = 20.Int256 + GAS_BALANCE* = 20.Int256 + GAS_SLOAD* = 50.Int256 + GAS_JUMP_DEST* = 1.Int256 + GAS_SSET* = 20000.Int256 + GAS_SRESET* = 5000.Int256 REFUNDSCLEAR = 15000 GASSELFDESTRUCT = 0 GASSELFDESTRUCTNEWACCOUNT = 25000 @@ -76,8 +129,8 @@ const GASNEWACCOUNT = 25000 GASEXP = 10 GASEXPBYTE = 10 - GASMEMORY = 3 - GASTXCREATE = 32000 + GAS_MEMORY* = 3.Int256 + GAS_TX_CREATE* = 32000.Int256 GASTXDATAZERO = 4 GASTXDATANONZERO = 68 GASTX = 21000 @@ -89,8 +142,8 @@ const GASCOPY = 3 GASBLOCKHASH = 20 GASCODEDEPOSIT = 200 - GASMEMORYQUADRATICDENOMINATOR = 512 - GASSHA256 = 60 + GAS_MEMORY_QUADRATIC_DENOMINATOR* = 512.Int256 + GAS_SHA256 = 60.Int256 GASSHA256WORD = 12 GASRIPEMD160 = 600 GASRIPEMD160WORD = 120 diff --git a/src/logging.nim b/src/logging.nim index 3b7419e92..1c31ecb70 100644 --- a/src/logging.nim +++ b/src/logging.nim @@ -4,11 +4,17 @@ type Logger* = object name*: string +const DEBUG = true + proc log*(l: Logger, msg: string) = echo fmt"#{l.name}: {msg}" +proc debug*(l: Logger, msg: string) = + if DEBUG: + l.log(msg) + proc trace*(l: Logger, msg: string) = - echo fmt"#{l.name}: {msg}" + l.log(msg) proc getLogger*(name: string): Logger = result = Logger(name: name) diff --git a/src/logic/arithmetic.nim b/src/logic/arithmetic.nim new file mode 100644 index 000000000..55ba24566 --- /dev/null +++ b/src/logic/arithmetic.nim @@ -0,0 +1,104 @@ +import + ../constants, ../utils_numeric, ../stack + +template pushRes = + computation.stack.push(res) + +proc add*(computation: var Computation) = + # Addition + var (left, right = computation.stack.popInt(2) + + var res = (left + right) and constants.UINT_256_MAX + pushRes() + +proc addmod*(computation: var Computation) = + # Modulo Addition + var (left, right, arg) = computation.stack.popInt(3) + + var res = if arg == 0: 0.Int256 else: (left + right) mod arg + pushRes() + +proc sub*(computation: var Computation) = + # Subtraction + var (left, right) = computation.stack.popInt(2) + + var res = (left - right) and constants.UINT_256_MAX + pushRes() + + +proc modulo*(computation: var Computation) = + # Modulo + var (value, arg) = computation.stack.popInt(2) + + var res = if arg == 0: 0.Int256 else: value mod arg + pushRes() + +proc smod*(computation: var Computation) = + # Signed Modulo + var (value, arg) = computation.stack.popInt(2) + value = unsignedToSigned(value) + arg = unsignedToSigned(value) + + var posOrNeg = if value < 0: -1.Int256 else 1.Int256 + var res = if mod == 0: 0.Int256 else: ((value.abs mod arg.abs) * posOrNeg) and constants.UINT_256_MAX + res = signedToUnsigned(res) + pushRes() + +proc mul*(computation: var Computation) = + # Multiplication + var (left, right) = computation.stack.popInt(2) + + var res = (left * right) and constants.UINT_256_MAX + pushRes() + +proc mulmod*(computation: var Computation) = + # Modulo Multiplication + var (left, right, arg) = computation.stack.popInt(3) + + var res = if mod == 0: 0.Int256 else: (left * right) mod arg + pushRes() + +proc divide*(computation: var Computation) = + # Division + var (numerator, denominator) = computation.stack.popInt(2) + + var res = if denominator == 0: 0.Int256 else: (numerator div denominator) and constants.UINT_256_MAX + pushRes() + +proc sdiv*(computation: var Computation) = + # Signed Division + var (numerator, denominator) = computation.stack.popInt(2) + numerator = unsignedToSigned(numerator) + denominator = unsignedToSigned(denominator) + + var posOrNeg = if numerator * denominator < 0: -1.Int256 else 1.Int256 + var res = if denominator == 0: 0.Int256 else: (posOrNeg * (numerator.abs div denominator.abs)) + res = unsignedToSigned(res) + pushRes() + +# no curry +proc exp*(computation: var Computation, gasPerByte: Int256) = + # Exponentiation + var (base, exponent) = computation.stack.popInt(2) + + var bitSize = exponent.bitLength() + var byteSize = ceil8(bitSize) div 8 + var res = if base == 0: 0.Int256 else: (base ^ exponent) mod constants.UINT_256_CEILING + computation.gasMeter.consumeGas( + gasPerByte * byteSize, + reason="EXP: exponent bytes" + ) + pushRes() + +proc signextend(computation: var Computation) = + # Signed Extend + var (bits, value) = computation.stack.popInt(2) + + var res: Int256 + if bits <= 31.Int256: + var testBit = bits * 8.Int256 + 7.Int256 + var signBit = (1.Int256 shl testBit) + res = if value and signBit != 0: value or (constants.UINT_256_CEILING - signBit) else: value and (signBit - 1) + else: + res = value + pushRes() diff --git a/src/utils/hexadecimal.nim b/src/utils/hexadecimal.nim new file mode 100644 index 000000000..0f603da1e --- /dev/null +++ b/src/utils/hexadecimal.nim @@ -0,0 +1,11 @@ +import strutils + +proc encodeHex*(value: cstring): string = + # return "0x" & codecs.decode(codecs.encode(value, "hex"), "utf8") + return $value + +proc decodeHex*(value: string): cstring = + # var hexPart = value.rsplit("x", 1)[1] + return cstring(value) + # return codecs.decode(hexPart, "hex") + diff --git a/src/utils/state.nim b/src/utils/state.nim new file mode 100644 index 000000000..bf8d4a8f3 --- /dev/null +++ b/src/utils/state.nim @@ -0,0 +1,13 @@ +# import +# eth_utils, rlp, trie, evm.db.backends.memory, evm.db.chain + +# proc makeTrieRootAndNodes*(transactions: auto; trieClass: auto): auto = +# var +# chaindb = BaseChainDB(MemoryDB()) +# db = chaindb.db +# transactionDb = trieClass(db) +# for index, transaction in transactions: +# var indexKey = rlp.encode(index) +# transactionDb[indexKey] = rlp.encode(transaction) +# return (transactionDb.rootHash, transactionDb.db.wrappedDb.kvStore) + diff --git a/src/utils_numeric.nim b/src/utils_numeric.nim index a5c1231b4..95aa97067 100644 --- a/src/utils_numeric.nim +++ b/src/utils_numeric.nim @@ -1,5 +1,33 @@ -proc intToBigEndian*(value: int): cstring = +import constants, strformat, macros + +proc intToBigEndian*(value: Int256): cstring = result = cstring"" -proc bigEndianToInt*(value: cstring): int = - result = 0 +proc bigEndianToInt*(value: cstring): Int256 = + result = 0.Int256 + +proc unsignedToSigned(value: Int256): Int256 = + if value <= UINT_255_MAX: + return value + else: + return value - UINT_256_CEILING + +proc signedToUnsigned(value: Int256): Int256 = + if value < 0: + return value + UINT_256_CEILING + else: + return value + +macro ceilXX(ceiling: static[int]): untyped = + var name = ident(%"ceil{ceiling}") + result = quote: + proc `name`*(value: Int256): Int256 = + var remainder = value mod `ceiling`.Int256 + if remainder == 0: + return value + else: + return value + `ceiling`.Int256 - remainder + echo result.repr + +ceilXX(32) +ceilXX(8) diff --git a/src/vm/memory.nim b/src/vm/memory.nim new file mode 100644 index 000000000..9ad43be23 --- /dev/null +++ b/src/vm/memory.nim @@ -0,0 +1,28 @@ +import + sequtils, + ../constants, ../errors, ../logging, ../validation, ../utils_numeric + +type + Memory* = ref object + logger*: Logger + bytes*: seq[byte] + +proc newMemory*: Memory = + new(result) + result.bytes = @[] + result.logger = logging.getLogger("evm.vm.memory.Memory") + +proc len*(memory: Memory): int = + result = len(memory.bytes) + +proc extend*(memory: var Memory; startPosition: Int256; size: Int256) = + if size == 0: + return + var newSize = ceil32(startPosition + size) + if newSize <= len(memory).Int256: + return + var sizeToExtend = newSize.int - len(memory) + memory.bytes = memory.bytes.concat(repeat(0.byte, sizeToExtend)) + +proc read*(self: var Memory; startPosition: Int256; size: Int256): cstring = + return cstring"" diff --git a/src/vm/message.nim b/src/vm/message.nim index 8820e155f..60d2ba4cf 100644 --- a/src/vm/message.nim +++ b/src/vm/message.nim @@ -30,6 +30,15 @@ type internalStorageAddress: cstring shouldTransferValue*: bool isStatic*: bool + isCreate*: bool + + MessageOptions* = ref object + origin*: cstring + depth*: Int256 + createAddress*: cstring + codeAddress*: cstring + shouldTransferValue*: bool + isStatic*: bool proc `origin=`*(message: var Message, value: cstring) = message.internalOrigin = value @@ -40,6 +49,22 @@ proc `codeAddress=`*(message: var Message, value: cstring) = proc `storageAddress=`*(message: var Message, value: cstring) = message.internalStorageAddress = value +proc newMessageOptions*( + origin: cstring = nil, + depth: Int256 = 0.Int256, + createAddress: cstring = nil, + codeAddress: cstring = nil, + shouldTransferValue: bool = true, + isStatic: bool = false): MessageOptions = + + result = MessageOptions( + origin: origin, + depth: depth, + createAddress: createAddress, + codeAddress: codeAddress, + shouldTransferValue: shouldTransferValue, + isStatic: isStatic) + proc newMessage*( gas: Int256, gasPrice: Int256, @@ -48,12 +73,7 @@ proc newMessage*( value: Int256, data: cstring, code: cstring, - origin: cstring = nil, - depth: Int256 = 0.Int256, - createAddress: cstring = nil, - codeAddress: cstring = nil, - shouldTransferValue: bool = true, - isStatic: bool = false): Message = + options: MessageOptions = newMessageOptions()): Message = new(result) result.gas = gas @@ -70,26 +90,26 @@ proc newMessage*( result.data = data - if not origin.isNil: - validateCanonicalAddress(origin, title="Message.origin") - result.internalOrigin = origin + if not options.origin.isNil: + validateCanonicalAddress(options.origin, title="Message.origin") + result.internalOrigin = options.origin - validateGte(depth, minimum=0, title="Message.depth") - result.depth = depth + validateGte(options.depth, minimum=0, title="Message.depth") + result.depth = options.depth result.code = code - if not createAddress.isNil: - validateCanonicalAddress(createAddress, title="Message.storage_address") - result.storageAddress = createAddress + if not options.createAddress.isNil: + validateCanonicalAddress(options.createAddress, title="Message.storage_address") + result.storageAddress = options.createAddress - if not codeAddress.isNil: - validateCanonicalAddress(codeAddress, title="Message.code_address") - result.codeAddress = codeAddress + if not options.codeAddress.isNil: + validateCanonicalAddress(options.codeAddress, title="Message.code_address") + result.codeAddress = options.codeAddress - result.shouldTransferValue = shouldTransferValue + result.shouldTransferValue = options.shouldTransferValue - result.isStatic = isStatic + result.isStatic = options.isStatic proc origin*(message: Message): cstring = if not message.internalOrigin.isNil: diff --git a/src/vm/stack.nim b/src/vm/stack.nim index 982fff10d..85def4788 100644 --- a/src/vm/stack.nim +++ b/src/vm/stack.nim @@ -1,6 +1,5 @@ - import - strformat, + strformat, macros, value, ../errors, ../validation, ../utils_numeric, ../constants, ../logging type @@ -14,47 +13,53 @@ template ensureStackLimit: untyped = if len(stack.values) > 1023: raise newException(FullStack, "Stack limit reached") -method len*(stack: Stack): int = +proc len*(stack: Stack): int = len(stack.values) -method push*(stack: var Stack; value: Value) = +proc push*(stack: var Stack; value: Value) = ## Push an item onto the stack ensureStackLimit() stack.values.add(value) -method push*(stack: var Stack; value: int) = +proc push*(stack: var Stack; value: int) = + ## Push an integer onto the stack + ensureStackLimit() + + stack.values.add(Value(kind: VInt, i: value.Int256)) + +proc push*(stack: var Stack; value: Int256) = ## Push an integer onto the stack ensureStackLimit() stack.values.add(Value(kind: VInt, i: value)) -method push*(stack: var Stack; value: cstring) = +proc push*(stack: var Stack; value: cstring) = ## Push a binary onto the stack ensureStackLimit() stack.values.add(Value(kind: VBinary, b: value)) -method internalPop(stack: var Stack; numItems: int): seq[Value] = +proc internalPop(stack: var Stack; numItems: int): seq[Value] = if len(stack) < numItems: result = @[] else: result = stack.values[^numItems .. ^1] stack.values = stack.values[0 ..< ^numItems] -template toType(i: int, _: typedesc[int]): int = +template toType(i: Int256, _: typedesc[Int256]): Int256 = i -template toType(i: int, _: typedesc[cstring]): cstring = +template toType(i: Int256, _: typedesc[cstring]): cstring = intToBigEndian(i) -template toType(b: cstring, _: typedesc[int]): int = +template toType(b: cstring, _: typedesc[Int256]): Int256 = bigEndianToInt(b) template toType(b: cstring, _: typedesc[cstring]): cstring = b -method internalPop(stack: var Stack; numItems: int, T: typedesc): seq[T] = +proc internalPop(stack: var Stack; numItems: int, T: typedesc): seq[T] = result = @[] if len(stack) < numItems: return @@ -71,40 +76,75 @@ template ensurePop(elements: untyped, a: untyped): untyped = if len(`elements`) < `a`: raise newException(InsufficientStack, "No stack items") -method pop*(stack: var Stack): Value = +proc pop*(stack: var Stack): Value = ## Pop an item off the stack var elements = stack.internalPop(1) ensurePop(elements, 1) result = elements[0] -method pop*(stack: var Stack; numItems: int): seq[Value] = +proc pop*(stack: var Stack; numItems: int): seq[Value] = ## Pop many items off the stack result = stack.internalPop(numItems) ensurePop(result, numItems) -method popInt*(stack: var Stack): int = - var elements = stack.internalPop(1, int) - ensurePop(elements, 1) - result = elements[0] +# proc popInt*(stack: var Stack): Int256 = +# var elements = stack.internalPop(1, Int256) +# ensurePop(elements, 1) +# result = elements[0] -method popInt*(stack: var Stack; numItems: int): seq[int] = - result = stack.internalPop(numItems, int) - ensurePop(result, numItems) +macro internalPopTuple(numItems: static[int]): untyped = + var name = ident(%"internalPopTuple{numItems}") + var typ = nnkPar.newTree() + var t = ident("T") + for z in 0 ..< numItems: + typ.add(t) + result = quote: + proc `name`(stack: var Stack, `t`: typedesc): `typ` = + for z in 0 ..< `numItems`: + var value = stack.values.pop() + case value.kind: + of VInt: + result[z] = toType(value.i, `t`) + of VBinary: + result[z] = toType(value.b, `t`) -method popBinary*(stack: var Stack): cstring = +# define pop for tuples +internalPopTuple(2) +internalPopTuple(3) +internalPopTuple(4) +internalPopTuple(5) +internalPopTuple(6) +internalPopTuple(7) + +macro popInt*(stack: typed; numItems: static[int]): untyped = + var resultNode = ident("result") + if numItems >= 8: + result = quote: + `resultNode` = `stack`.internalPop(`numItems`, Int256) + else: + var name = ident(%"internalPopTuple{numItems}") + result = quote: + `resultNode` = `name`(`stack`, Int256) + +# proc popInt*(stack: var Stack, numItems: int): seq[Int256] = +# result = stack.internalPop(numItems, Int256) +# ensurePop(result, numItems) + +proc popBinary*(stack: var Stack): cstring = var elements = stack.internalPop(1, cstring) ensurePop(elements, 1) result = elements[0] -method popBinary*(stack: var Stack; numItems: int): seq[cstring] = +proc popBinary*(stack: var Stack; numItems: int): seq[cstring] = result = stack.internalPop(numItems, cstring) ensurePop(result, numItems) -proc makeStack*(): Stack = - # result.logger = logging.getLogger("evm.vm.stack.Stack") +proc newStack*(): Stack = + new(result) + result.logger = logging.getLogger("evm.vm.stack.Stack") result.values = @[] -method swap*(stack: var Stack; position: int) = +proc swap*(stack: var Stack; position: int) = ## Perform a SWAP operation on the stack var idx = position + 1 if idx < len(stack) + 1: @@ -113,7 +153,7 @@ method swap*(stack: var Stack; position: int) = raise newException(InsufficientStack, %"Insufficient stack items for SWAP{position}") -method dup*(stack: var Stack; position: int) = +proc dup*(stack: var Stack; position: int) = ## Perform a DUP operation on the stack if position < len(stack) + 1: stack.push(stack.values[^position]) diff --git a/src/vm/value.nim b/src/vm/value.nim index deec94d02..e59d9f772 100644 --- a/src/vm/value.nim +++ b/src/vm/value.nim @@ -1,10 +1,12 @@ +import ../constants + type ValueKind* = enum VInt, VBinary Value* = ref object case kind*: ValueKind: of VInt: - i*: int + i*: Int256 of VBinary: b*: cstring diff --git a/src/vm_state.nim b/src/vm_state.nim new file mode 100644 index 000000000..567b9dd1d --- /dev/null +++ b/src/vm_state.nim @@ -0,0 +1,25 @@ +import + strformat, + logging, constants, errors, utils/state + +type + BaseVMState* = ref object of RootObj + prevHeaders*: bool + receipts*: bool + computationClass*: bool + chaindb*: bool + accessLogs*: seq[bool] + blockHeader*: bool + name*: string + +proc newBaseVMState*: BaseVMState = + new(result) + # result.chaindb = nil + # result.blockHeader = nil + # result.prevHeaders = nil + # result.computationClass = nil + # result.accessLogs = nil + # result.receipts = nil + +method logger*(vmState: BaseVMState): Logger = + logging.getLogger(%"evm.vmState.{vmState.name}")