diff --git a/VMTests.md b/VMTests.md index a111e729b..ab8c09107 100644 --- a/VMTests.md +++ b/VMTests.md @@ -310,7 +310,7 @@ OK: 11/12 Fail: 1/12 Skip: 0/12 + calldataload0.json OK + calldataload1.json OK + calldataload2.json OK -+ calldataloadSizeTooHigh.json OK +- calldataloadSizeTooHigh.json Fail + calldataloadSizeTooHighPartial.json OK + calldataload_BigOffset.json OK + calldatasize0.json OK @@ -318,7 +318,7 @@ OK: 11/12 Fail: 1/12 Skip: 0/12 + calldatasize2.json OK + caller.json OK + callvalue.json OK -- codecopy0.json Fail ++ codecopy0.json OK + codecopyZeroMemExpansion.json OK - codecopy_DataIndexTooHigh.json Fail + codesize.json OK @@ -445,7 +445,7 @@ OK: 26/52 Fail: 11/52 Skip: 15/52 + jumpi_at_the_end.json OK + jumpifInsidePushWithJumpDest.json OK + jumpifInsidePushWithoutJumpDest.json OK -- kv1.json Fail ++ kv1.json OK + log1MemExp.json OK + loop_stacklimit_1020.json OK + loop_stacklimit_1021.json OK @@ -461,8 +461,8 @@ OK: 26/52 Fail: 11/52 Skip: 15/52 + mstore0.json OK + mstore1.json OK + mstore8MemExp.json OK -- mstore8WordToBigError.json Fail -- mstore8_0.json Fail ++ mstore8WordToBigError.json OK ++ mstore8_0.json OK - mstore8_1.json Fail + mstoreMemExp.json OK + mstoreWordToBigError.json OK @@ -483,7 +483,7 @@ OK: 26/52 Fail: 11/52 Skip: 15/52 + swapAt52becameMstore.json OK + when.json OK ``` -OK: 110/145 Fail: 34/145 Skip: 1/145 +OK: 113/145 Fail: 31/145 Skip: 1/145 ## vmLogTest ```diff + log0_emptyMem.json OK @@ -673,10 +673,10 @@ OK: 0/17 Fail: 0/17 Skip: 17/17 - sha3_memSizeQuadraticCost33.json Fail - sha3_memSizeQuadraticCost63.json Fail - sha3_memSizeQuadraticCost64.json Fail -- sha3_memSizeQuadraticCost64_2.json Fail ++ sha3_memSizeQuadraticCost64_2.json OK - sha3_memSizeQuadraticCost65.json Fail ``` -OK: 4/18 Fail: 14/18 Skip: 0/18 +OK: 5/18 Fail: 13/18 Skip: 0/18 ## vmSystemOperations ```diff ABAcalls0.json Skip @@ -719,9 +719,9 @@ OK: 4/18 Fail: 14/18 Skip: 0/18 OK: 0/36 Fail: 0/36 Skip: 36/36 ## vmTests ```diff -+ arith.json OK -+ boolean.json OK -+ mktx.json OK +- arith.json Fail +- boolean.json Fail +- mktx.json Fail + suicide.json OK ``` -OK: 4/4 Fail: 0/4 Skip: 0/4 +OK: 1/4 Fail: 3/4 Skip: 0/4 diff --git a/nimbus/constants.nim b/nimbus/constants.nim index 3ad8d7a85..6a40fc293 100644 --- a/nimbus/constants.nim +++ b/nimbus/constants.nim @@ -5,85 +5,52 @@ import proc default(t: typedesc): t = discard # constants +const + UINT_256_MAX*: UInt256 = high(UInt256) + INT_256_MAX_AS_UINT256* = high(Uint256) shr 1 + NULLBYTE* = "\x00" + EMPTYWORD* = repeat(NULLBYTE, 32) + UINT160CEILING*: UInt256 = 2.u256.pow(160) + ZERO_ADDRESS* = default(EthAddress) + CREATE_CONTRACT_ADDRESS* = ZERO_ADDRESS + ZERO_HASH32* = Hash256() + STACK_DEPTH_LIMIT* = 1024 -let - UINT_256_MAX*: UInt256 = high(UInt256) - INT_256_MAX_AS_UINT256* = cast[Uint256](high(Int256)) - NULLBYTE* = "\x00" - EMPTYWORD* = repeat(NULLBYTE, 32) - UINT160CEILING*: UInt256 = 2.u256.pow(160) - ZERO_ADDRESS* = default(EthAddress) - CREATE_CONTRACT_ADDRESS* = ZERO_ADDRESS - ZERO_HASH32* = Hash256() - STACK_DEPTH_LIMIT* = 1024 - - GAS_LIMIT_EMA_DENOMINATOR* = 1_024 - GAS_LIMIT_ADJUSTMENT_FACTOR* = 1_024 - GAS_LIMIT_USAGE_ADJUSTMENT_NUMERATOR* = 3 + GAS_LIMIT_EMA_DENOMINATOR* = 1_024 + GAS_LIMIT_ADJUSTMENT_FACTOR* = 1_024 + GAS_LIMIT_USAGE_ADJUSTMENT_NUMERATOR* = 3 GAS_LIMIT_USAGE_ADJUSTMENT_DENOMINATOR* = 2 - DIFFICULTY_ADJUSTMENT_DENOMINATOR* = 2_048.u256 - DIFFICULTY_MINIMUM* = 131_072.u256 + DIFFICULTY_ADJUSTMENT_DENOMINATOR* = 2_048.u256 + DIFFICULTY_MINIMUM* = 131_072.u256 BYZANTIUM_DIFFICULTY_ADJUSTMENT_CUTOFF* = 9 - BOMB_EXPONENTIAL_PERIOD* = 100_000.u256 - BOMB_EXPONENTIAL_FREE_PERIODS* = 2.u256 + BOMB_EXPONENTIAL_PERIOD* = 100_000.u256 + BOMB_EXPONENTIAL_FREE_PERIODS* = 2.u256 - BLOCK_REWARD* = 5.u256 * 2.u256 # denoms.ether + BLOCK_REWARD* = 5.u256 * 2.u256 # denoms.ether - UNCLE_DEPTH_PENALTY_FACTOR* = 8.u256 + UNCLE_DEPTH_PENALTY_FACTOR* = 8.u256 - MAX_UNCLE_DEPTH* = 6.u256 - MAX_UNCLES* = 2.u256 + MAX_UNCLE_DEPTH* = 6.u256 + MAX_UNCLES* = 2.u256 - EMPTY_UNCLE_HASH* = "1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347".toDigest + EMPTY_UNCLE_HASH* = "1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347".toDigest - GENESIS_BLOCK_NUMBER* = 0.u256 - GENESIS_DIFFICULTY* = 131_072.u256 - GENESIS_GAS_LIMIT* = 3_141_592 - GENESIS_PARENT_HASH* = ZERO_HASH32 - GENESIS_COINBASE* = ZERO_ADDRESS - GENESIS_NONCE* = "\x00\x00\x00\x00\x00\x00\x00B" - GENESIS_MIX_HASH* = ZERO_HASH32 - GENESIS_EXTRA_DATA* = "" - GAS_LIMIT_MINIMUM* = 5000 + GENESIS_BLOCK_NUMBER* = 0.u256 + GENESIS_DIFFICULTY* = 131_072.u256 + GENESIS_GAS_LIMIT* = 3_141_592 + GENESIS_PARENT_HASH* = ZERO_HASH32 + GENESIS_COINBASE* = ZERO_ADDRESS + GENESIS_NONCE* = "\x00\x00\x00\x00\x00\x00\x00B" + GENESIS_MIX_HASH* = ZERO_HASH32 + GENESIS_EXTRA_DATA* = "" + GAS_LIMIT_MINIMUM* = 5000 - BLANK_ROOT_HASH* = "56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421".toDigest - EMPTY_SHA3* = "883f7328a6c30727a655daff17eba3a86049871bc7839a5b71e2bc26a99c4d4c".toDigest + BLANK_ROOT_HASH* = "56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421".toDigest + EMPTY_SHA3* = "883f7328a6c30727a655daff17eba3a86049871bc7839a5b71e2bc26a99c4d4c".toDigest - GAS_MOD_EXP_QUADRATIC_DENOMINATOR* = 20.u256 - - MAX_PREV_HEADER_DEPTH* = 256.toBlockNumber - - FORK_ICEAGE_BLKNUM* = 200_000.u256 - FORK_HOMESTED_BLKNUM* = 1_150_000.u256 - FORK_DAO_BLKNUM* = 1_920_000.u256 - FORK_TANGERINE_WHISTLE_BLKNUM* = 2_463_000.u256 - FORK_SPURIOUS_DRAGON_BLKNUM* = 2_675_000.u256 - FORK_BYZANTIUM_BLKNUM* = 4_370_000.u256 - -# TODO: Move the below to a new utils unit? - -type - Fork = enum fkUnknown, fkFrontier, fkIceAge, fkHomested, fkDao, fkTangerineWhistle, fkSpuriousDragon, fkByzantium - UInt256Pair = tuple[a: Uint256, b: Uint256] - -proc `..`*(a, b: Uint256): UInt256Pair = (a, b) - -proc contains*(ab: UInt256Pair, v: UInt256): bool = - return v >= ab[0] and v <= ab[1] - -proc toFork*(blockNumber: UInt256): Fork = - # TODO - Refactoring: superseded by newNimbusVM for the time being #https://github.com/status-im/nimbus/pull/37 - # TODO - Refactoring: redundant with `chain.nim` getVM - result = fkUnknown - let one = u256(1) - if blockNumber in u256(0)..FORK_ICEAGE_BLKNUM - one: result = fkFrontier - elif blockNumber in FORK_ICEAGE_BLKNUM..FORK_HOMESTED_BLKNUM - one: result = fkIceAge - elif blockNumber in FORK_HOMESTED_BLKNUM..FORK_DAO_BLKNUM - one: result = fkHomested - elif blockNumber in FORK_DAO_BLKNUM..FORK_TANGERINE_WHISTLE_BLKNUM - one: result = fkDao - elif blockNumber in FORK_TANGERINE_WHISTLE_BLKNUM..FORK_SPURIOUS_DRAGON_BLKNUM - one: result = fkTangerineWhistle - elif blockNumber in FORK_SPURIOUS_DRAGON_BLKNUM..FORK_BYZANTIUM_BLKNUM - one: result = fkSpuriousDragon - else: - if blockNumber >= FORK_BYZANTIUM_BLKNUM: result = fkByzantium # Update for constantinople when announced + GAS_MOD_EXP_QUADRATIC_DENOMINATOR* = 20.u256 + MAX_PREV_HEADER_DEPTH* = 256.toBlockNumber + MaxCallDepth* = 1024 diff --git a/nimbus/db/state_db.nim b/nimbus/db/state_db.nim index 2db99ea87..53290064d 100644 --- a/nimbus/db/state_db.nim +++ b/nimbus/db/state_db.nim @@ -119,10 +119,12 @@ proc setNonce*(db: var AccountStateDB, address: EthAddress, nonce: UInt256) = db.setAccount(address, account) proc getNonce*(db: AccountStateDB, address: EthAddress): UInt256 = + # TODO it is very strange that we require a var param here + validateCanonicalAddress(address, title="Storage Address") let account = db.getAccount(address) - return account.nonce + account.nonce proc toByteRange_Unnecessary*(h: KeccakHash): ByteRange = ## XXX: Another proc used to mark unnecessary conversions it the code diff --git a/nimbus/errors.nim b/nimbus/errors.nim index 08216d347..fcbff6fc6 100644 --- a/nimbus/errors.nim +++ b/nimbus/errors.nim @@ -9,9 +9,6 @@ type EVMError* = object of Exception ## Base error class for all evm errors. - VMNotFound* = object of EVMError - ## No VM available for the provided block number. - BlockNotFound* = object of EVMError ## The block with the given number/hash does not exist. @@ -24,9 +21,6 @@ type ValidationError* = object of EVMError ## Error to signal something does not pass a validation check. - Halt* = object of EVMError - ## Raised by opcode function to halt vm execution. - VMError* = object of EVMError ## Class of errors which can be raised during VM execution. erasesReturnData*: bool @@ -57,9 +51,6 @@ type ContractCreationCollision* = object of VMError ## Error signaling that there was an address collision during contract creation. - Revert* = object of VMError - ## Error used by the REVERT opcode - WriteProtection* = object of VMError ## Error raised if an attempt to modify the state database is made while ## operating inside of a STATICCALL context. @@ -78,8 +69,3 @@ proc makeVMError*: ref VMError = new(result) result.burnsGas = true result.erasesReturnData = true - -proc makeRevert*(): ref Revert = - new(result) - result.burnsGas = false - result.erasesReturnData = false diff --git a/nimbus/logging.nim b/nimbus/logging.nim index 46d205be4..737eaf89d 100644 --- a/nimbus/logging.nim +++ b/nimbus/logging.nim @@ -32,7 +32,7 @@ template trace*(l: Logger, msg: string) = when DEBUG: l.log(msg, fgBlue) -proc getLogger*(name: string): Logger = +proc getLogger*(name: string): Logger {.inline.}= result = Logger(name: name) # proc disableLogging* = diff --git a/nimbus/validation.nim b/nimbus/validation.nim index b6b71e159..b8465e38f 100644 --- a/nimbus/validation.nim +++ b/nimbus/validation.nim @@ -43,7 +43,7 @@ proc validateStackItem*(value: string) = raise newException(ValidationError, &"Invalid stack item: expected 32 bytes, got {value.len}: value is {value}") -proc validateStackItem*(value: string | seq[byte]) = +proc validateStackItem*(value: openarray[byte]) = if value.len > 32: raise newException(ValidationError, &"Invalid stack item: expected 32 bytes, got {value.len}: value is {value}") diff --git a/nimbus/vm/base.nim b/nimbus/vm/base.nim deleted file mode 100644 index 1ad6f3865..000000000 --- a/nimbus/vm/base.nim +++ /dev/null @@ -1,64 +0,0 @@ -# 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. - -import - ../logging, ../constants, ../errors, ../transaction, ../vm_types, - ../block_types, ../vm_state, ../vm_state_transactions, ../db/db_chain, ../utils/header, - ./computation - -type - VMkind* = enum - ## List of VMs forks (py-evm vm_class) of the Ethereum network - # TODO: used in Chain.vmsByRange: can we store the runtimetime in a seq/tuple instead? - vmkFrontier, vmkHomestead, vmkTangerineWhistle, vmkSpuriousDragon, vmkByzantium - - VM* = ref object of RootObj - # The VM class represents the Chain rules for a specific protocol definition - # such as the Frontier or Homestead network. Defining an Chain defining - # individual VM classes for each fork of the protocol rules within that - # network - - chainDB*: BaseChainDB - isStateless*: bool - state*: BaseVMState - `block`*: Block - -# TODO - Refactoring: superseded by newNimbusVM for the time being #https://github.com/status-im/nimbus/pull/37 - -# proc newVM*(header: BlockHeader, chainDB: BaseChainDB): VM = -# new(result) -# result.chainDB = chainDB - -method name*(vm: VM): string = - "VM" - -method addTransaction*(vm: var VM, transaction: BaseTransaction, computation: BaseComputation): Block = - # Add a transaction to the given block and save the block data into chaindb - # var receipt = vm.state.makeReceipt(transaction, computation) - # var transactionIdx = len(vm.`block`.transactions) - # TODO - return Block() - -method applyTransaction*(vm: var VM, transaction: BaseTransaction): (BaseComputation, Block) = - # Apply the transaction to the vm in the current block - if vm.isStateless: - var (computation, b, trieData) = vm.state.applyTransaction( - transaction, - vm.`block`, - isStateless=true) - vm.`block` = b - result = (computation, b) - # Persist changed transaction and receipt key-values to self.chaindb. - else: - var (computation, _, _) = vm.state.applyTransaction( - transaction, - vm.`block`, - isStateless=false) - discard vm.addTransaction(transaction, computation) - - result = (computation, vm.`block`) - diff --git a/nimbus/vm/code_stream.nim b/nimbus/vm/code_stream.nim index f7cfed64c..5c6de4268 100644 --- a/nimbus/vm/code_stream.nim +++ b/nimbus/vm/code_stream.nim @@ -45,6 +45,7 @@ proc newCodeStreamFromUnescaped*(code: string): CodeStream = newCodeStream(codeBytes) proc read*(c: var CodeStream, size: int): seq[byte] = + # TODO: use openarray[bytes] if c.pc + size - 1 < c.bytes.len: result = c.bytes[c.pc .. c.pc + size - 1] c.pc += size @@ -60,18 +61,17 @@ proc readVmWord*(c: var CodeStream, n: int): UInt256 = let last = min(c.pc + n, c.bytes.len) let toWrite = last - c.pc for i in 0 ..< toWrite : result_bytes[i] = c.bytes[last - i - 1] - for j in toWrite ..< 32: result_bytes[j] = 0 c.pc = last proc len*(c: CodeStream): int = len(c.bytes) proc next*(c: var CodeStream): Op = - var nextOpcode = c.read(1) - if nextOpcode.len != 0: - return Op(nextOpcode[0]) + if c.pc != c.bytes.len: + result = Op(c.bytes[c.pc]) + inc c.pc else: - return Op.STOP + result = Stop iterator items*(c: var CodeStream): Op = var nextOpcode = c.next() @@ -83,9 +83,10 @@ proc `[]`*(c: CodeStream, offset: int): Op = Op(c.bytes[offset]) proc peek*(c: var CodeStream): Op = - var currentPc = c.pc - result = c.next() - c.pc = currentPc + if c.pc <= c.bytes.len: + result = Op(c.bytes[c.pc]) + else: + result = Stop proc updatePc*(c: var CodeStream, value: int) = c.pc = min(value, len(c)) @@ -100,7 +101,7 @@ when false: finally: cs.pc = anchorPc -proc isValidOpcode*(c: var CodeStream, position: int): bool = +proc isValidOpcode*(c: CodeStream, position: int): bool = if position >= len(c): return false if position in c.invalidPositions: diff --git a/nimbus/vm/computation.nim b/nimbus/vm/computation.nim index a41f29b1a..58a0e4ff6 100644 --- a/nimbus/vm/computation.nim +++ b/nimbus/vm/computation.nim @@ -9,19 +9,11 @@ import strformat, strutils, sequtils, macros, terminal, math, tables, eth_common, byteutils, ../constants, ../errors, ../validation, ../vm_state, ../logging, ../vm_types, - ./interpreter/[opcode_values,gas_meter, gas_costs], - ./code_stream, ./memory, ./message, ./stack, + ./interpreter/[opcode_values, gas_meter, gas_costs, vm_forks], + ./code_stream, ./memory, ./message, ./stack - # TODO further refactoring of gas cost - ./forks/f20150730_frontier/frontier_vm_state, - ./forks/f20161018_tangerine_whistle/tangerine_vm_state - -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) +proc newBaseComputation*(vmState: BaseVMState, blockNumber: UInt256, message: Message): BaseComputation = + new result result.vmState = vmState result.msg = message result.memory = Memory() @@ -32,34 +24,12 @@ method newBaseComputation*(vmState: FrontierVMState, message: Message): BaseComp result.logEntries = @[] result.code = newCodeStreamFromUnescaped(message.code) # TODO: what is the best repr result.rawOutput = "0x" - result.gasCosts = BaseGasCosts + result.gasCosts = blockNumber.toFork.forkToSchedule -method newBaseComputation*(vmState: TangerineVMState, 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[EthAddress, EthAddress]() - result.logEntries = @[] - result.code = newCodeStreamFromUnescaped(message.code) # TODO: what is the best repr - result.rawOutput = "0x" - result.gasCosts = TangerineGasCosts - -method logger*(computation: BaseComputation): Logger = +proc logger*(computation: BaseComputation): Logger = logging.getLogger("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 = +proc isOriginComputation*(c: BaseComputation): bool = # Is this computation the computation initiated by a transaction c.msg.isOrigin @@ -69,13 +39,13 @@ template isSuccess*(c: BaseComputation): bool = template isError*(c: BaseComputation): bool = not c.isSuccess -method shouldBurnGas*(c: BaseComputation): bool = +proc shouldBurnGas*(c: BaseComputation): bool = c.isError and c.error.burnsGas -method shouldEraseReturnData*(c: BaseComputation): bool = +proc shouldEraseReturnData*(c: BaseComputation): bool = c.isError and c.error.erasesReturnData -method prepareChildMessage*( +proc prepareChildMessage*( c: var BaseComputation, gas: GasInt, to: EthAddress, @@ -96,51 +66,16 @@ method prepareChildMessage*( code, childOptions) -method output*(c: BaseComputation): string = +proc output*(c: BaseComputation): string = if c.shouldEraseReturnData: "" else: c.rawOutput -method `output=`*(c: var BaseComputation, value: string) = +proc `output=`*(c: var BaseComputation, value: string) = 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 = "" - else: - c.returnData = childBaseComputation.output - else: - if childBaseComputation.msg.isCreate: - c.returnData = "" - 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: EthAddress) = +proc registerAccountForDeletion*(c: var BaseComputation, beneficiary: EthAddress) = validateCanonicalAddress(beneficiary, title="self destruct beneficiary address") if c.msg.storageAddress in c.accountsToDelete: @@ -149,144 +84,40 @@ method registerAccountForDeletion*(c: var BaseComputation, beneficiary: EthAddre "registered for deletion multiple times") c.accountsToDelete[c.msg.storageAddress] = beneficiary -method addLogEntry*(c: var BaseComputation, account: EthAddress, topics: seq[UInt256], data: string) = +proc addLogEntry*(c: var BaseComputation, account: EthAddress, topics: seq[UInt256], data: string) = validateCanonicalAddress(account, title="log entry address") c.logEntries.add((account, topics, data)) # many methods are basically TODO, but they still return valid values # in order to test some existing code -method getAccountsForDeletion*(c: BaseComputation): seq[(string, string)] = +proc getAccountsForDeletion*(c: BaseComputation): seq[(string, string)] = # TODO if c.isError: result = @[] else: result = @[] -method getLogEntries*(c: BaseComputation): seq[(string, seq[UInt256], string)] = +proc getLogEntries*(c: BaseComputation): seq[(string, seq[UInt256], string)] = # TODO if c.isError: result = @[] else: result = @[] -method getGasRefund*(c: BaseComputation): GasInt = +proc getGasRefund*(c: BaseComputation): GasInt = if c.isError: result = 0 else: result = c.gasMeter.gasRefunded + c.children.mapIt(it.getGasRefund()).foldl(a + b, 0'i64) -method getGasUsed*(c: BaseComputation): GasInt = +proc getGasUsed*(c: BaseComputation): GasInt = if c.shouldBurnGas: result = c.msg.gas else: result = max(0, c.msg.gas - c.gasMeter.gasRemaining) -method getGasRemaining*(c: BaseComputation): GasInt = +proc getGasRemaining*(c: BaseComputation): GasInt = if c.shouldBurnGas: result = 0 else: result = c.gasMeter.gasRemaining - -# -# Context Manager API -# - -template inComputation*(c: untyped, handler: untyped): untyped = - # use similarly to the python manager - # - # inComputation(computation): - # stuff - - `c`.logger.debug( - "COMPUTATION STARTING: gas: $1 | from: $2 | to: $3 | value: $4 | depth: $5 | static: $6" % [ - $`c`.msg.gas, - toHex(`c`.msg.sender), - toHex(`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" % [ - toHex(c.msg.sender), - toHex(c.msg.to), - $c.msg.value, - $c.msg.depth, - if c.msg.isStatic: "y" else: "n", - $(c.msg.gas - c.gasMeter.gasRemaining), - $c.gasMeter.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, - toHex(`c`.msg.sender), - toHex(`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()) - -method getOpcodeFn*(computation: var BaseComputation, op: Op): Opcode = - # TODO use isValidOpcode and remove the Op --> Opcode indirection - if computation.opcodes.len > 0 and computation.opcodes.hasKey(op): - OpCode(kind: op, runLogic: computation.opcodes[op]) - else: - raise newException(InvalidInstruction, - &"Invalid opcode {op}") - -# Super dirty fix for https://github.com/status-im/nimbus/issues/46 -# Pending https://github.com/status-im/nimbus/issues/36 -# Disentangle opcode logic -from ./interpreter/opcodes_impl/call import runLogic, BaseCall - -template run*(opcode: Opcode, computation: var BaseComputation) = - # Hook for performing the actual VM execution - # opcode.consumeGas(computation) - - if opcode.kind == Op.Call: # Super dirty fix for https://github.com/status-im/nimbus/issues/46 - # TODO remove this branch - runLogic(BaseCall(opcode), computation) - elif computation.gasCosts[opcode.kind].kind != GckFixed: - opcode.runLogic(computation) - else: - computation.gasMeter.consumeGas(computation.gasCosts[opcode.kind].cost, reason = $opcode.kind) - opcode.runLogic(computation) - -method logger*(opcode: Opcode): Logger = - logging.getLogger(&"vm.opcode.{opcode.kind}") - -macro applyComputation*(t: typed, vmState: untyped, message: untyped): untyped = - # Perform the computation that would be triggered by the VM message - # c.applyComputation(vmState, 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 = `t` # `name`(`vmState`, `message`) - var handler = proc: `typName` = - # TODO - # if `message`.codeAddress in c.precompiles: - # c.precompiles[`message`.codeAddress].run(c) - # return c - - for op in c.code: - 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: - break - c.logger.log($c.stack & "\n\n", fgGreen) - return c - inComputation(c): - res = handler() - c diff --git a/nimbus/vm/forks/README.md b/nimbus/vm/forks/README.md deleted file mode 100644 index bfdfe7cda..000000000 --- a/nimbus/vm/forks/README.md +++ /dev/null @@ -1 +0,0 @@ -Folder to be deleted diff --git a/nimbus/vm/forks/f20150730_frontier/frontier_block.nim b/nimbus/vm/forks/f20150730_frontier/frontier_block.nim deleted file mode 100644 index fb4084196..000000000 --- a/nimbus/vm/forks/f20150730_frontier/frontier_block.nim +++ /dev/null @@ -1,63 +0,0 @@ -# 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. - -import - ../../../logging, ../../../constants, ../../../errors, ../../../transaction, - ../../../block_types, - ../../../utils/header - -type - FrontierBlock* = ref object of Block - # bloomFilter*: BloomFilter - # header*: BlockHeader - transactions*: seq[BaseTransaction] - # transactionClass*: Any - stateRoot*: cstring - # fields*: seq[(string, Function)] - # cachedRlp: cstring - # uncles*: void - -# import -# rlp, rlp.sedes, eth_bloom, evm.constants, evm.rlp.receipts, evm.rlp.blocks, -# evm.rlp.headers, evm.utils.keccak, transactions - -proc makeFrontierBlock*(header: BlockHeader; transactions: seq[BaseTransaction]; uncles: void): FrontierBlock = - new result - if transactions.len == 0: - result.transactions = @[] - # if uncles is None: - # uncles = @[] - # result.bloomFilter = BloomFilter(header.bloom) - -# method number*(self: FrontierBlock): int = -# return self.header.blockNumber - -# method hash*(self: FrontierBlock): cstring = -# return self.header.hash - -# method getTransactionClass*(cls: typedesc): typedesc = -# return cls.transactionClass - -# method getReceipts*(self: FrontierBlock; chaindb: BaseChainDB): seq[Receipt] = -# return chaindb.getReceipts(self.header, Receipt) - -# method fromHeader*(cls: typedesc; header: BlockHeader; chaindb: BaseChainDB): FrontierBlock = -# ## Returns the block denoted by the given block header. -# if header.unclesHash == EMPTYUNCLEHASH: -# var uncles = @[] -# else: -# uncles = chaindb.getBlockUncles(header.unclesHash) -# var transactions = chaindb.getBlockTransactions(header, cls.getTransactionClass()) -# return cls() - -# proc makeFrontierBlock*(): FrontierBlock = -# result.transactionClass = FrontierTransaction -# result.fields = @[("header", BlockHeader), -# ("transactions", CountableList(transactionClass)), -# ("uncles", CountableList(BlockHeader))] -# result.bloomFilter = nil - diff --git a/nimbus/vm/forks/f20150730_frontier/frontier_validation.nim b/nimbus/vm/forks/f20150730_frontier/frontier_validation.nim deleted file mode 100644 index e541f3ea8..000000000 --- a/nimbus/vm/forks/f20150730_frontier/frontier_validation.nim +++ /dev/null @@ -1,32 +0,0 @@ -# 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. - -import - strformat, - eth_common/eth_types, - ../../../constants, ../../../errors, ../../../vm_state, ../../../transaction, ../../../utils/header - -proc validateFrontierTransaction*(vmState: BaseVmState, transaction: BaseTransaction) = - let gasCost = u256(transaction.gas * transaction.gasPrice) - var senderBalance: UInt256 - # inDB(vmState.stateDB(readOnly=true): - # senderBalance = db.getBalance(transaction.sender) - senderBalance = gasCost # TODO - if senderBalance < gasCost: - raise newException(ValidationError, &"Sender account balance cannot afford txn gas: {transaction.sender}") - - let totalCost = transaction.value + gasCost - - if senderBalance < totalCost: - raise newException(ValidationError, "Sender account balance cannot afford txn") - - if vmState.blockHeader.gasUsed + transaction.gas > vmState.blockHeader.gasLimit: - raise newException(ValidationError, "Transaction exceeds gas limit") - - # inDB(vmState.stateDb(readOnly=true): - # if db.getNonce(transaction.sender) != transaction.nonce: - # raise newException(ValidationError, "Invalid transaction nonce") diff --git a/nimbus/vm/forks/f20150730_frontier/frontier_vm.nim b/nimbus/vm/forks/f20150730_frontier/frontier_vm.nim deleted file mode 100644 index d34ec5439..000000000 --- a/nimbus/vm/forks/f20150730_frontier/frontier_vm.nim +++ /dev/null @@ -1,38 +0,0 @@ -# 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. - -import - eth_common/eth_types, - ../../../logging, ../../../constants, ../../../errors, - ../../../block_types, - ../../../vm/[base, stack], ../../../db/db_chain, ../../../utils/header, - ./frontier_block, ./frontier_vm_state, ./frontier_validation - -type - FrontierVM* = ref object of VM - -method name*(vm: FrontierVM): string = - "FrontierVM" - -method getBlockReward(vm: FrontierVM): UInt256 = - BLOCK_REWARD - -method getUncleReward(vm: FrontierVM, blockNumber: BlockNumber, uncle: Block): UInt256 = - BLOCK_REWARD * (UNCLE_DEPTH_PENALTY_FACTOR + uncle.blockNumber - blockNumber) div UNCLE_DEPTH_PENALTY_FACTOR - - -method getNephewReward(vm: FrontierVM): UInt256 = - vm.getBlockReward() div 32 - -proc newFrontierVM*(header: BlockHeader, chainDB: BaseChainDB): FrontierVM = - new(result) - result.chainDB = chainDB - result.isStateless = true - result.state = newFrontierVMState() - result.state.chaindb = result.chainDB - result.state.blockHeader = header - result.`block` = makeFrontierBlock(header, @[]) diff --git a/nimbus/vm/forks/f20150730_frontier/frontier_vm_state.nim b/nimbus/vm/forks/f20150730_frontier/frontier_vm_state.nim deleted file mode 100644 index a1837bc23..000000000 --- a/nimbus/vm/forks/f20150730_frontier/frontier_vm_state.nim +++ /dev/null @@ -1,170 +0,0 @@ -# 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. - -import - ../../../logging, ../../../constants, ../../../errors, ../../../vm_state, - ../../../utils/header, ../../../db/db_chain - -type - 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 = # 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, -# evm.utils.hexadecimal, evm.utils.keccak, evm.validation, computation, constants, -# validation - -# type -# FrontierVMState* = object of Function -# prevHeaders*: seq[BlockHeader] -# receipts*: void -# computationClass*: Any -# _chaindb*: BaseChainDB -# accessLogs*: AccessLogs -# blockHeader*: BlockHeader - -# proc _executeFrontierTransaction*(vmState: FrontierVMState; -# transaction: FrontierTransaction): FrontierComputation = -# transaction.validate() -# validateFrontierTransaction(vmState, transaction) -# var gasFee = transaction.gas * transaction.gasPrice -# with vmState.stateDb(), -# stateDb.deltaBalance(transaction.sender, -1 * gasFee) -# stateDb.incrementNonce(transaction.sender) -# var messageGas = transaction.gas - transaction.intrinsicGas -# if transaction.to == constants.CREATECONTRACTADDRESS: -# var -# contractAddress = generateContractAddress(transaction.sender, -# stateDb.getNonce(transaction.sender) - 1) -# data = cstring"" -# code = transaction.data -# else: -# contractAddress = None -# data = transaction.data -# code = stateDb.getCode(transaction.to) -# vmState.logger.info("TRANSACTION: sender: %s | to: %s | value: %s | gas: %s | gas-price: %s | s: %s | r: %s | v: %s | data-hash: %s", -# encodeHex(transaction.sender), encodeHex(transaction.to), -# transaction.value, transaction.gas, transaction.gasPrice, -# transaction.s, transaction.r, transaction.v, -# encodeHex(keccak(transaction.data))) -# var message = Message() -# if message.isCreate: -# with vmState.stateDb(), -# var isCollision = stateDb.accountHasCodeOrNonce(contractAddress) -# if isCollision: -# var computation = vmState.getComputation(message) -# computation._error = ContractCreationCollision("Address collision while creating contract: {0}".format( -# encodeHex(contractAddress))) -# vmState.logger.debug("Address collision while creating contract: %s", -# encodeHex(contractAddress)) -# else: -# computation = vmState.getComputation(message).applyCreateMessage() -# else: -# computation = vmState.getComputation(message).applyMessage() -# var numDeletions = len(computation.getAccountsForDeletion()) -# if numDeletions: -# computation.gasMeter.refundGas(REFUNDSELFDESTRUCT * numDeletions) -# var -# gasRemaining = computation.getGasRemaining() -# gasRefunded = computation.getGasRefund() -# gasUsed = transaction.gas - gasRemaining -# gasRefund = min(gasRefunded, gasUsed div 2) -# gasRefundAmount = gasRefund + gasRemaining * transaction.gasPrice -# if gasRefundAmount: -# vmState.logger.debug("TRANSACTION REFUND: %s -> %s", gasRefundAmount, -# encodeHex(message.sender)) -# with vmState.stateDb(), -# stateDb.deltaBalance(message.sender, gasRefundAmount) -# var transactionFee = transaction.gas - gasRemaining - gasRefund * -# transaction.gasPrice -# vmState.logger.debug("TRANSACTION FEE: %s -> %s", transactionFee, -# encodeHex(vmState.coinbase)) -# with vmState.stateDb(), -# stateDb.deltaBalance(vmState.coinbase, transactionFee) -# with vmState.stateDb(), -# for account, beneficiary in computation.getAccountsForDeletion(): -# vmState.logger.debug("DELETING ACCOUNT: %s", encodeHex(account)) -# stateDb.setBalance(account, 0) -# stateDb.deleteAccount(account) -# return computation - -# proc _makeFrontierReceipt*(vmState: FrontierVMState; -# transaction: FrontierTransaction; -# computation: FrontierComputation): Receipt = -# var -# logs = ## py2nim can't generate code for -# ## Log(address, topics, data) -# gasRemaining = computation.getGasRemaining() -# gasRefund = computation.getGasRefund() -# txGasUsed = transaction.gas - gasRemaining - -# min(gasRefund, transaction.gas - gasRemaining div 2) -# gasUsed = vmState.blockHeader.gasUsed + txGasUsed -# receipt = Receipt() -# return receipt - -# method executeTransaction*(self: FrontierVMState; transaction: FrontierTransaction): ( -# , ) = -# var computation = _executeFrontierTransaction(self, transaction) -# return (computation, self.blockHeader) - -# method makeReceipt*(self: FrontierVMState; transaction: FrontierTransaction; -# computation: FrontierComputation): Receipt = -# var receipt = _makeFrontierReceipt(self, transaction, computation) -# return receipt - -# method validateBlock*(self: FrontierVMState; block: FrontierBlock): void = -# if notblock.isGenesis: -# var parentHeader = self.parentHeader -# self._validateGasLimit(block) -# validateLengthLte(block.header.extraData, 32) -# if block.header.timestamp < parentHeader.timestamp: -# raise newException(ValidationError, "`timestamp` is before the parent block\'s timestamp.\\n- block : {0}\\n- parent : {1}. ".format( -# block.header.timestamp, parentHeader.timestamp)) -# elif block.header.timestamp == parentHeader.timestamp: -# raise ValidationError("`timestamp` is equal to the parent block\'s timestamp\\n- block : {0}\\n- parent: {1}. ".format( -# block.header.timestamp, parentHeader.timestamp)) -# if len(block.uncles) > MAXUNCLES: -# raise newException(ValidationError, "Blocks may have a maximum of {0} uncles. Found {1}.".format( -# MAXUNCLES, len(block.uncles))) -# for uncle in block.uncles: -# self.validateUncle(block, uncle) -# if notself.isKeyExists(block.header.stateRoot): -# raise newException(ValidationError, "`state_root` was not found in the db.\\n- state_root: {0}".format( -# block.header.stateRoot)) -# var localUncleHash = keccak(rlp.encode(block.uncles)) -# if localUncleHash != block.header.unclesHash: -# raise newException(ValidationError, "`uncles_hash` and block `uncles` do not match.\\n - num_uncles : {0}\\n - block uncle_hash : {1}\\n - header uncle_hash: {2}".format( -# len(block.uncles), localUncleHash, block.header.uncleHash)) - -# method _validateGasLimit*(self: FrontierVMState; block: FrontierBlock): void = -# var gasLimit = block.header.gasLimit -# if gasLimit < GASLIMITMINIMUM: -# raise newException(ValidationError, "Gas limit {0} is below minimum {1}".format( -# gasLimit, GASLIMITMINIMUM)) -# if gasLimit > GASLIMITMAXIMUM: -# raise newException(ValidationError, "Gas limit {0} is above maximum {1}".format( -# gasLimit, GASLIMITMAXIMUM)) -# var -# parentGasLimit = self.parentHeader.gasLimit -# diff = gasLimit - parentGasLimit -# if diff > parentGasLimit // GASLIMITADJUSTMENTFACTOR: -# raise newException(ValidationError, "Gas limit {0} difference to parent {1} is too big {2}".format( -# gasLimit, parentGasLimit, diff)) - -# proc makeFrontierVMState*(): FrontierVMState = -# result.computationClass = FrontierComputation - diff --git a/nimbus/vm/forks/f20161018_tangerine_whistle/tangerine_block.nim b/nimbus/vm/forks/f20161018_tangerine_whistle/tangerine_block.nim deleted file mode 100644 index c103b36e3..000000000 --- a/nimbus/vm/forks/f20161018_tangerine_whistle/tangerine_block.nim +++ /dev/null @@ -1,21 +0,0 @@ -# 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. - -import - ../../../logging, ../../../constants, ../../../errors, ../../../transaction, - ../../../block_types, - ../../../utils/header - -type - TangerineBlock* = ref object of Block - transactions*: seq[BaseTransaction] - stateRoot*: cstring - -proc makeTangerineBlock*(header: BlockHeader; transactions: seq[BaseTransaction]; uncles: void): TangerineBlock = - new result - if transactions.len == 0: - result.transactions = @[] diff --git a/nimbus/vm/forks/f20161018_tangerine_whistle/tangerine_validation.nim b/nimbus/vm/forks/f20161018_tangerine_whistle/tangerine_validation.nim deleted file mode 100644 index d784236f1..000000000 --- a/nimbus/vm/forks/f20161018_tangerine_whistle/tangerine_validation.nim +++ /dev/null @@ -1,32 +0,0 @@ -# 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. - -import - strformat, - eth_common/eth_types, - ../../../constants, ../../../errors, ../../../vm_state, ../../../transaction, ../../../utils/header - -proc validateTangerineTransaction*(vmState: BaseVmState, transaction: BaseTransaction) = - let gasCost = u256(transaction.gas * transaction.gasPrice) - var senderBalance: UInt256 - # inDB(vmState.stateDB(readOnly=true): - # senderBalance = db.getBalance(transaction.sender) - senderBalance = gasCost # TODO - if senderBalance < gasCost: - raise newException(ValidationError, &"Sender account balance cannot afford txn gas: {transaction.sender}") - - let totalCost = transaction.value + gasCost - - if senderBalance < totalCost: - raise newException(ValidationError, "Sender account balance cannot afford txn") - - if vmState.blockHeader.gasUsed + transaction.gas > vmState.blockHeader.gasLimit: - raise newException(ValidationError, "Transaction exceeds gas limit") - - # inDB(vmState.stateDb(readOnly=true): - # if db.getNonce(transaction.sender) != transaction.nonce: - # raise newException(ValidationError, "Invalid transaction nonce") diff --git a/nimbus/vm/forks/f20161018_tangerine_whistle/tangerine_vm.nim b/nimbus/vm/forks/f20161018_tangerine_whistle/tangerine_vm.nim deleted file mode 100644 index 63462c975..000000000 --- a/nimbus/vm/forks/f20161018_tangerine_whistle/tangerine_vm.nim +++ /dev/null @@ -1,39 +0,0 @@ -# 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. - -import - eth_common/eth_types, - ../../../logging, ../../../constants, ../../../errors, - ../../../block_types, - ../../../vm/[base, stack], ../../../db/db_chain, ../../../utils/header, - ./tangerine_block, ./tangerine_vm_state, ./tangerine_validation - - -type - TangerineVM* = ref object of VM - -method name*(vm: TangerineVM): string = - "TangerineVM" - -method getBlockReward(vm: TangerineVM): UInt256 = - BLOCK_REWARD - -method getUncleReward(vm: TangerineVM, blockNumber: UInt256, uncle: Block): UInt256 = - BLOCK_REWARD * (UNCLE_DEPTH_PENALTY_FACTOR + uncle.blockNumber - blockNumber) div UNCLE_DEPTH_PENALTY_FACTOR - - -method getNephewReward(vm: TangerineVM): UInt256 = - vm.getBlockReward() div 32 - -proc newTangerineVM*(header: BlockHeader, chainDB: BaseChainDB): TangerineVM = - new(result) - result.chainDB = chainDB - result.isStateless = true - result.state = newTangerineVMState() - result.state.chaindb = result.chainDB - result.state.blockHeader = header - result.`block` = makeTangerineBlock(header, @[]) diff --git a/nimbus/vm/forks/f20161018_tangerine_whistle/tangerine_vm_state.nim b/nimbus/vm/forks/f20161018_tangerine_whistle/tangerine_vm_state.nim deleted file mode 100644 index 652a4225d..000000000 --- a/nimbus/vm/forks/f20161018_tangerine_whistle/tangerine_vm_state.nim +++ /dev/null @@ -1,23 +0,0 @@ -# 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. - -import - ../../../logging, ../../../constants, ../../../errors, ../../../vm_state, - ../../../utils/header, ../../../db/db_chain - -type - TangerineVMState* = ref object of BaseVMState - # receipts*: - # computationClass*: Any - # accessLogs*: AccessLogs - -proc newTangerineVMState*: TangerineVMState = - new(result) - result.prevHeaders = @[] - result.name = "TangerineVM" - result.accessLogs = newAccessLogs() - # result.blockHeader = # TODO: ... diff --git a/nimbus/vm/interpreter.nim b/nimbus/vm/interpreter.nim index 10e75331c..1a31787dc 100644 --- a/nimbus/vm/interpreter.nim +++ b/nimbus/vm/interpreter.nim @@ -6,19 +6,19 @@ # at your option. This file may not be copied, modified, or distributed except according to those terms. import - ./interpreter/[opcode_values, gas_meter, opcode_table], + ./interpreter/[opcode_values, gas_meter], ./interpreter/vm_forks -from utils/utils_numeric import bigEndianToInt +from ./interpreter/utils/utils_numeric import bigEndianToInt import # Used in vm_types. Beware of recursive dependencies - ./code_stream, ./computation, ./stack, ./message + ./code_stream, ./computation, ./stack, ./message, interpreter_dispatch export - opcode_values, gas_meter, opcode_table, + opcode_values, gas_meter, vm_forks export utils_numeric.bigEndianToInt export - code_stream, computation, stack, message + code_stream, computation, stack, message, interpreter_dispatch diff --git a/nimbus/vm/interpreter/forks_list.md b/nimbus/vm/interpreter/forks_list.md index 418d091bd..fa0c6e4e7 100644 --- a/nimbus/vm/interpreter/forks_list.md +++ b/nimbus/vm/interpreter/forks_list.md @@ -15,3 +15,10 @@ From https://www.etherchain.org/hardForks From https://ethereum.stackexchange.com/a/28409 ![](forks_list.png) + +Tangerine Whistles introduced new gas costs + +Byzantium introduced opcodes: + - REVERT (EIP 140) + - RETURNDATASIZE and RETURNDATACOPY (EIP 211) + - STATICCALL (EIP 214) diff --git a/nimbus/vm/interpreter/gas_costs.nim b/nimbus/vm/interpreter/gas_costs.nim index a4d25ac6b..0d23011e0 100644 --- a/nimbus/vm/interpreter/gas_costs.nim +++ b/nimbus/vm/interpreter/gas_costs.nim @@ -7,8 +7,8 @@ import math, eth_common/eth_types, - ../utils/[macros_gen_opcodes, utils_numeric], - ./opcode_values + ./utils/[macros_gen_opcodes, utils_numeric], + ./opcode_values, ./vm_forks # Gas Fee Schedule # Yellow Paper Appendix G - https://ethereum.github.io/yellowpaper/paper.pdf @@ -158,11 +158,10 @@ template gasCosts(FeeSchedule: GasFeeSchedule, prefix, ResultGasCostsName: untyp result += static(FeeSchedule[GasSha3]) + static(FeeSchedule[GasSha3Word]) * (memLength).wordCount - func `prefix gasCopy`(value: Uint256): GasInt {.nimcall.} = - ## Value is the size of the input to the CallDataCopy/CodeCopy/ReturnDataCopy function - + func `prefix gasCopy`(currentMemSize, memOffset, memLength: Natural): GasInt {.nimcall.} = result = static(FeeSchedule[GasVeryLow]) + - static(FeeSchedule[GasCopy]) * value.toInt.wordCount + static(FeeSchedule[GasCopy]) * memLength.wordCount + result += `prefix gasMemoryExpansion`(currentMemSize, memOffset, memLength) func `prefix gasExtCodeCopy`(value: Uint256): GasInt {.nimcall.} = ## Value is the size of the input to the CallDataCopy/CodeCopy/ReturnDataCopy function @@ -371,14 +370,14 @@ template gasCosts(FeeSchedule: GasFeeSchedule, prefix, ResultGasCostsName: untyp CallValue: fixed GasBase, CallDataLoad: fixed GasVeryLow, CallDataSize: fixed GasBase, - CallDataCopy: dynamic `prefix gasCopy`, + CallDataCopy: memExpansion `prefix gasCopy`, CodeSize: fixed GasBase, - CodeCopy: dynamic `prefix gasCopy`, + CodeCopy: memExpansion `prefix gasCopy`, GasPrice: fixed GasBase, ExtCodeSize: fixed GasExtcode, ExtCodeCopy: dynamic `prefix gasExtCodeCopy`, ReturnDataSize: fixed GasBase, - ReturnDataCopy: dynamic `prefix gasCopy`, + ReturnDataCopy: memExpansion `prefix gasCopy`, # 40s: Block Information Blockhash: fixed GasBlockhash, @@ -480,7 +479,7 @@ template gasCosts(FeeSchedule: GasFeeSchedule, prefix, ResultGasCostsName: untyp Log4: memExpansion `prefix gasLog4`, # f0s: System operations - Create: fixed GasCreate, + Create: fixed GasCreate, # TODO, dynamic cost Call: complex `prefix gasCall`, CallCode: complex `prefix gasCall`, Return: memExpansion `prefix gasHalt`, @@ -553,3 +552,9 @@ const gasCosts(BaseGasFees, base, BaseGasCosts) gasCosts(TangerineGasFees, tangerine, TangerineGasCosts) + +proc forkToSchedule*(fork: Fork): GasCosts = + if fork < FkTangerine: + BaseGasCosts + else: + TangerineGasCosts diff --git a/nimbus/vm/interpreter/opcode_table.nim b/nimbus/vm/interpreter/opcode_table.nim deleted file mode 100644 index 393238192..000000000 --- a/nimbus/vm/interpreter/opcode_table.nim +++ /dev/null @@ -1,171 +0,0 @@ -# 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. - -import - tables, eth_common/eth_types, - ../../vm_types, ./opcode_values, - opcodes_impl/[arithmetic, comparison, sha3, context, block_ops, stack_ops, duplication, swap, memory_ops, storage, flow, logging_ops, invalid, call, system_ops] - -const - OpLogic*: Table[Op, proc(computation: var BaseComputation){.nimcall.}] = { - # 0s: Stop and Arithmetic Operations - Stop: stop, - Add: arithmetic.add, - Mul: mul, - Sub: sub, - Div: divide, - Sdiv: sdiv, - Mod: modulo, - Smod: smod, - Addmod: arithmetic.addmod, - Mulmod: arithmetic.mulmod, - Exp: arithmetic.exp, - SignExtend: signextend, - - # 10s: Comparison & Bitwise Logic Operations - Lt: lt, - Gt: gt, - Slt: slt, - Sgt: sgt, - Eq: eq, - IsZero: comparison.isZero, - And: andOp, - Or: orOp, - Xor: xorOp, - Not: notOp, - Byte: byteOp, - - # 20s: SHA3 - Sha3: sha3op, - - # 30s: Environmental Information - Address: context.address, - Balance: balance, - Origin: context.origin, - Caller: caller, - CallValue: callValue, - CallDataLoad: callDataLoad, - CallDataSize: callDataSize, - CallDataCopy: callDataCopy, - CodeSize: codeSize, - CodeCopy: codeCopy, - GasPrice: gasPrice, # TODO this wasn't used previously - ExtCodeSize: extCodeSize, - ExtCodeCopy: extCodeCopy, - ReturnDataSize: returnDataSize, # TODO this wasn't used previously - ReturnDataCopy: returnDataCopy, - - # 40s: Block Information - Blockhash: block_ops.blockhash, - Coinbase: block_ops.coinbase, - Timestamp: block_ops.timestamp, - Number: block_ops.number, - Difficulty: block_ops.difficulty, - GasLimit: block_ops.gaslimit, - - # 50s: Stack, Memory, Storage and Flow Operations - Pop: stack_ops.pop, - Mload: mload, - Mstore: mstore, - Mstore8: mstore8, - Sload: sload, - Sstore: sstore, - Jump: jump, - JumpI: jumpi, - Pc: pc, - Msize: msize, - Gas: flow.gas, - JumpDest: jumpDest, - - # 60s & 70s: Push Operations - Push1: push1, - Push2: push2, - Push3: push3, - Push4: push4, - Push5: push5, - Push6: push6, - Push7: push7, - Push8: push8, - Push9: push9, - Push10: push10, - Push11: push11, - Push12: push12, - Push13: push13, - Push14: push14, - Push15: push15, - Push16: push16, - Push17: push17, - Push18: push18, - Push19: push19, - Push20: push20, - Push21: push21, - Push22: push22, - Push23: push23, - Push24: push24, - Push25: push25, - Push26: push26, - Push27: push27, - Push28: push28, - Push29: push29, - Push30: push30, - Push31: push31, - Push32: push32, - - # 80s: Duplication Operations - Dup1: dup1, - Dup2: dup2, - Dup3: dup3, - Dup4: dup4, - Dup5: dup5, - Dup6: dup6, - Dup7: dup7, - Dup8: dup8, - Dup9: dup9, - Dup10: dup10, - Dup11: dup11, - Dup12: dup12, - Dup13: dup13, - Dup14: dup14, - Dup15: dup15, - Dup16: dup16, - - # 90s: Exchange Operations - Swap1: swap1, - Swap2: swap2, - Swap3: swap3, - Swap4: swap4, - Swap5: swap5, - Swap6: swap6, - Swap7: swap7, - Swap8: swap8, - Swap9: swap9, - Swap10: swap10, - Swap11: swap11, - Swap12: swap12, - Swap13: swap13, - Swap14: swap14, - Swap15: swap15, - Swap16: swap16, - - # a0s: Logging Operations - Log0: log0, - Log1: log1, - Log2: log2, - Log3: log3, - Log4: log4, - - # f0s: System operations - # Create: create, - # Call: call, - # CallCode: callCode, - Return: returnOp, - # DelegateCall: delegateCall, - # StaticCall: staticCall, - Op.Revert: revert, - Invalid: invalidOp, - SelfDestruct: selfDestruct - }.toTable diff --git a/nimbus/vm/interpreter/opcode_values.nim b/nimbus/vm/interpreter/opcode_values.nim index 4554e26fc..d214fafa2 100644 --- a/nimbus/vm/interpreter/opcode_values.nim +++ b/nimbus/vm/interpreter/opcode_values.nim @@ -5,7 +5,7 @@ # * 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. -import ../utils/macros_gen_opcodes +import ./utils/macros_gen_opcodes fill_enum_holes: type @@ -19,7 +19,7 @@ fill_enum_holes: # because the intermediate sum (or multiplication) might roll over if # intermediate result is greater or equal 2^256 - Op* {.pure.} = enum + Op* = enum # 0s: Stop and Arithmetic Operations Stop = 0x00, # Halts execution. Add = 0x01, # Addition operation. @@ -31,7 +31,7 @@ fill_enum_holes: Smod = 0x07, # Signed modulo remainder operation. Addmod = 0x08, # Modulo addition operation. Mulmod = 0x09, # Modulo multiplication operation. - Exp = 0x0A, # Exponential operation + Exp = 0x0A, # Exponentiation operation SignExtend = 0x0B, # Extend length of two’s complement signed integer. # 10s: Comparison & Bitwise Logic Operations diff --git a/nimbus/vm/interpreter/opcodes_impl.nim b/nimbus/vm/interpreter/opcodes_impl.nim new file mode 100644 index 000000000..3b8b95049 --- /dev/null +++ b/nimbus/vm/interpreter/opcodes_impl.nim @@ -0,0 +1,784 @@ +# 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. + +import + strformat, times, ranges, + stint, nimcrypto, ranges/typedranges, eth_common, + ./utils/[macros_procs_opcodes, utils_numeric], + ./gas_meter, ./gas_costs, ./opcode_values, ./vm_forks, + ../memory, ../message, ../stack, ../code_stream, ../computation, + ../../vm_state, ../../errors, ../../constants, ../../vm_types, ../../logging, + ../../db/[db_chain, state_db], + ../../utils/[bytes, padding, address] # TODO remove those dependencies + +# ################################## +# Syntactic sugar + +template push(x: typed) {.dirty.} = + ## Push an expression on the computation stack + computation.stack.push x + +# ################################## +# 0s: Stop and Arithmetic Operations + +op add, inline = true, lhs, rhs: + ## 0x01, Addition + push: lhs + rhs + +op mul, inline = true, lhs, rhs: + ## 0x02, Multiplication + push: lhs * rhs + +op sub, inline = true, lhs, rhs: + ## 0x03, Substraction + push: lhs - rhs + +op divide, inline = true, lhs, rhs: + ## 0x04, Division + push: + if rhs == 0: zero(Uint256) # EVM special casing of div by 0 + else: lhs div rhs + +op sdiv, inline = true, lhs, rhs: + ## 0x05, Signed division + push: + if rhs == 0: zero(Uint256) + else: + pseudoSignedToUnsigned( + lhs.unsignedToPseudoSigned div rhs.unsignedToPseudoSigned + ) + +op modulo, inline = true, lhs, rhs: + ## 0x06, Modulo + push: + if rhs == 0: zero(Uint256) + else: lhs mod rhs + +op smod, inline = true, lhs, rhs: + ## 0x07, Signed modulo + push: + if rhs == 0: zero(UInt256) + else: + pseudoSignedToUnsigned( + lhs.unsignedToPseudoSigned mod rhs.unsignedToPseudoSigned + ) + +op addmod, inline = true, lhs, rhs, modulus: + ## 0x08, Modulo addition + ## Intermediate computations do not roll over at 2^256 + push: + if modulus == 0: zero(UInt256) # EVM special casing of div by 0 + else: addmod(lhs, rhs, modulus) + +op mulmod, inline = true, lhs, rhs, modulus: + ## 0x09, Modulo multiplication + ## Intermediate computations do not roll over at 2^256 + push: + if modulus == 0: zero(UInt256) # EVM special casing of div by 0 + else: mulmod(lhs, rhs, modulus) + +op exp, inline = true, base, exponent: + ## 0x0A, Exponentiation + computation.gasMeter.consumeGas( + computation.gasCosts[Exp].d_handler(exponent), + reason="EXP: exponent bytes" + ) + push: + if base == 0: zero(UInt256) + else: base.pow(exponent) + +op signExtend, inline = false, bits, value: + ## 0x0B, Sign extend + ## Extend length of two’s complement signed integer. + + var res: UInt256 + if bits <= 31.u256: + let + testBit = bits.toInt * 8 + 7 + bitPos = (1 shl testBit) + mask = u256(bitPos - 1) + if not isZero(value and bitPos.u256): + res = value or (not mask) + else: + res = value and mask + else: + res = value + + push: res + +# ########################################## +# 10s: Comparison & Bitwise Logic Operations + +op lt, inline = true, lhs, rhs: + ## 0x10, Less-than comparison + push: (lhs < rhs).uint.u256 + +op gt, inline = true, lhs, rhs: + ## 0x11, Greater-than comparison + push: (lhs > rhs).uint.u256 + +op slt, inline = true, lhs, rhs: + ## 0x12, Signed less-than comparison + push: (cast[Int256](lhs) < cast[Int256](rhs)).uint.u256 + +op sgt, inline = true, lhs, rhs: + ## 0x13, Signed greater-than comparison + push: (cast[Int256](lhs) > cast[Int256](rhs)).uint.u256 + +op eq, inline = true, lhs, rhs: + ## 0x14, Signed greater-than comparison + push: (lhs == rhs).uint.u256 + +op isZero, inline = true, value: + ## 0x15, Check if zero + push: value.isZero.uint.u256 + +op andOp, inline = true, lhs, rhs: + ## 0x16, Bitwise AND + push: lhs and rhs + +op orOp, inline = true, lhs, rhs: + ## 0x17, Bitwise AND + push: lhs or rhs + +op xorOp, inline = true, lhs, rhs: + ## 0x18, Bitwise AND + push: lhs xor rhs + +op notOp, inline = true, value: + ## 0x19, Check if zero + push: value.not + +op byteOp, inline = true, position, value: + ## 0x20, Retrieve single byte from word. + + let pos = position.toInt + + push: + if pos >= 32 or pos < 0: zero(Uint256) + else: + when system.cpuEndian == bigEndian: + cast[array[32, byte]](value)[pos].u256 + else: + cast[array[32, byte]](value)[31 - pos].u256 + +# ########################################## +# 20s: SHA3 + +op sha3, inline = true, startPos, length: + ## 0x20, Compute Keccak-256 hash. + let (pos, len) = (startPos.toInt, length.toInt) + + computation.gasMeter.consumeGas( + computation.gasCosts[Op.Sha3].m_handler(computation.memory.len, pos, len), + reason="SHA3: word gas cost" + ) + + computation.memory.extend(pos, len) + let endRange = min(pos + len, computation.memory.len - 1) + push: + keccak256.digest computation.memory.bytes.toOpenArray(pos, endRange) + +# ########################################## +# 30s: Environmental Information + +# TODO - simplify: https://github.com/status-im/nimbus/issues/67 +proc writePaddedResult(mem: var Memory, + data: openarray[byte], + memPos, dataPos, len: Natural, + paddingValue = 0.byte) = + mem.extend(memPos, len) + + let dataEndPosition = dataPos + len - 1 + if dataEndPosition < data.len: + mem.write(memPos, data[dataPos .. dataEndPosition]) + else: + var presentElements = data.len - dataPos + if presentElements > 0: + mem.write(memPos, data.toOpenArray(dataPos, data.len - 1)) + else: + presentElements = 0 + + # Note, we don't need to write padding bytes + # mem.extend already pads with zero properly + +op address, inline = true: + ## 0x30, Get address of currently executing account. + push: computation.msg.storageAddress + +op balance, inline = true: + ## 0x31, Get balance of the given account. + let address = computation.stack.popAddress() + push: computation.vmState.readOnlyStateDB.getBalance(address) + +op origin, inline = true: + ## 0x32, Get execution origination address. + push: computation.msg.origin + +op caller, inline = true: + ## 0x33, Get caller address. + push: computation.msg.sender + +op callValue, inline = true: + ## 0x34, Get deposited value by the instruction/transaction + ## responsible for this execution + push: computation.msg.value + +op callDataLoad, inline = false, startPos: + ## 0x35, Get input data of current environment + # TODO simplification: https://github.com/status-im/nimbus/issues/67 + let dataPos = startPos.toInt + if dataPos >= computation.msg.data.len: + push: 0 + return + + let dataEndPosition = dataPos + 31 + + if dataEndPosition < computation.msg.data.len: + computation.stack.push(computation.msg.data[dataPos .. dataEndPosition]) + else: + var bytes: array[32, byte] + var presentBytes = min(computation.msg.data.len - dataPos, 32) + + if presentBytes > 0: + copyMem(addr bytes[0], addr computation.msg.data[dataPos], presentBytes) + else: + presentBytes = 0 + + for i in presentBytes ..< 32: bytes[i] = 0 + computation.stack.push(bytes) + +op callDataSize, inline = true: + ## 0x36, Get size of input data in current environment. + push: computation.msg.data.len.u256 + +op callDataCopy, inline = false, memStartPos, copyStartPos, size: + ## 0x37, Copy input data in current environment to memory. + # TODO tests: https://github.com/status-im/nimbus/issues/67 + + let (memPos, copyPos, len) = (memStartPos.toInt, copyStartPos.toInt, size.toInt) + + computation.gasMeter.consumeGas( + computation.gasCosts[CallDataCopy].m_handler(memPos, copyPos, len), + reason="CallDataCopy fee") + + computation.memory.extend(memPos, len) + + # If the data does not take 32 bytes, pad with zeros + let lim = min(computation.msg.data.len, copyPos + len) + let padding = copyPos + len - lim + # Note: when extending, extended memory is zero-ed, we only need to offset with padding value + # Also memory.write handles the case where copyPos+padding is out of bounds + computation.memory.write(memPos): + computation.msg.data.toOpenArray(copyPos+padding, copyPos+lim) + +op codesize, inline = true: + ## 0x38, Get size of code running in current environment. + push: computation.code.len + +op codecopy, inline = false, memStartPos, copyStartPos, size: + ## 0x39, Copy code running in current environment to memory. + # TODO tests: https://github.com/status-im/nimbus/issues/67 + + let (memPos, copyPos, len) = (memStartPos.toInt, copyStartPos.toInt, size.toInt) + + computation.gasMeter.consumeGas( + computation.gasCosts[CodeCopy].m_handler(memPos, copyPos, len), + reason="CodeCopy fee") + + computation.memory.writePaddedResult(computation.code.bytes, memPos, copyPos, len) + +op gasprice, inline = true: + ## 0x3A, Get price of gas in current environment. + push: computation.msg.gasPrice + +op extCodeSize, inline = true: + ## 0x3b, Get size of an account's code + let account = computation.stack.popAddress() + let codeSize = computation.vmState.readOnlyStateDB.getCode(account).len + push uint(codeSize) + +op extCodeCopy, inline = true: + ## 0x3c, Copy an account's code to memory. + let account = computation.stack.popAddress() + let (memStartPos, codeStartPos, size) = computation.stack.popInt(3) + let (memPos, codePos, len) = (memStartPos.toInt, codeStartPos.toInt, size.toInt) + + computation.gasMeter.consumeGas( + computation.gasCosts[CodeCopy].m_handler(memPos, codePos, len), + reason="ExtCodeCopy fee") + + let codeBytes = computation.vmState.readOnlyStateDB.getCode(account) + computation.memory.writePaddedResult(codeBytes.toOpenArray, memPos, codePos, len) + +op returnDataSize, inline = true: + ## 0x3d, Get size of output data from the previous call from the current environment. + push: computation.returnData.len + +op returnDataCopy, inline = false, memStartPos, copyStartPos, size: + ## 0x3e, Copy output data from the previous call to memory. + let (memPos, copyPos, len) = (memStartPos.toInt, copyStartPos.toInt, size.toInt) + + computation.gasMeter.consumeGas( + computation.gasCosts[CodeCopy].m_handler(memPos, copyPos, len), + reason="ExtCodeCopy fee") + + if copyPos + len > computation.returnData.len: + # TODO Geth additionally checks copyPos + len < 64 + # Parity uses a saturating addition + # Yellow paper mentions μs[1] + i are not subject to the 2^256 modulo. + raise newException(OutOfBoundsRead, + "Return data length is not sufficient to satisfy request. Asked \n" & + &"for data from index {copyStartPos} to {copyStartPos + size}. Return data is {computation.returnData.len} in \n" & + "length") + + computation.memory.extend(memPos, len) + + computation.memory.write(memPos): + computation.returnData.toOpenArray(copyPos, copyPos+len) + +# ########################################## +# 40s: Block Information + +op blockhash, inline = true, blockNumber: + ## 0x40, Get the hash of one of the 256 most recent complete blocks. + push: computation.vmState.getAncestorHash(blockNumber) + +op coinbase, inline = true: + ## 0x41, Get the block's beneficiary address. + push: computation.vmState.coinbase + +op timestamp, inline = true: + ## 0x42, Get the block's timestamp. + push: computation.vmState.timestamp.toUnix + +op blocknumber, inline = true: + ## 0x43, Get the block's number. + push: computation.vmState.blockNumber + +op difficulty, inline = true: + ## 0x44, Get the block's difficulty + push: computation.vmState.difficulty + +op gasLimit, inline = true: + ## 0x45, Get the block's gas limit + push: computation.vmState.gasLimit + +# ########################################## +# 50s: Stack, Memory, Storage and Flow Operations + +op pop, inline = true: + ## 0x50, Remove item from stack. + discard computation.stack.popInt() + +op mload, inline = true, memStartPos: + ## 0x51, Load word from memory + let memPos = memStartPos.toInt + + computation.gasMeter.consumeGas( + computation.gasCosts[MLoad].m_handler(computation.memory.len, memPos, 32), + reason="MLOAD: GasVeryLow + memory expansion" + ) + computation.memory.extend(memPos, 32) + + push: computation.memory.read(memPos, 32) # TODO, should we convert to native endianness? + +op mstore, inline = true, memStartPos, value: + ## 0x52, Save word to memory + let memPos = memStartPos.toInt + + computation.gasMeter.consumeGas( + computation.gasCosts[MStore].m_handler(computation.memory.len, memPos, 32), + reason="MSTORE: GasVeryLow + memory expansion" + ) + + computation.memory.extend(memPos, 32) + computation.memory.write(memPos, value.toByteArrayBE) # is big-endian correct? Parity/Geth do convert + +op mstore8, inline = true, memStartPos, value: + ## 0x53, Save byte to memory + let memPos = memStartPos.toInt + + computation.gasMeter.consumeGas( + computation.gasCosts[MStore].m_handler(computation.memory.len, memPos, 1), + reason="MSTORE8: GasVeryLow + memory expansion" + ) + + computation.memory.extend(memPos, 1) + computation.memory.write(memPos, [value.toByteArrayBE[0]]) + +op sload, inline = true, slot: + ## 0x54, Load word from storage. + + let (value, found) = computation.vmState.readOnlyStateDB.getStorage(computation.msg.storageAddress, slot) + if found: + push: value + else: + # TODO: raise exception? + discard + +op sstore, inline = false, slot, value: + ## 0x55, Save word to storage. + + let (currentValue, existing) = computation.vmState.readOnlyStateDB.getStorage(computation.msg.storageAddress, slot) + + let + gasParam = GasParams(kind: Op.Sstore, s_isStorageEmpty: not existing) + (gasCost, gasRefund) = computation.gasCosts[Sstore].c_handler(currentValue, gasParam) + + computation.gasMeter.consumeGas(gasCost, &"SSTORE: {computation.msg.storageAddress}[{slot}] -> {value} ({currentValue})") + + if gasRefund > 0: + computation.gasMeter.refundGas(gasRefund) + + computation.vmState.mutateStateDB: + db.setStorage(computation.msg.storageAddress, slot, value) + +op jump, inline = true, jumpTarget: + ## 0x56, Alter the program counter + + let jt = jumpTarget.toInt + computation.code.pc = jt + + let nextOpcode = computation.code.peek + if nextOpcode != JUMPDEST: + raise newException(InvalidJumpDestination, "Invalid Jump Destination") + # TODO: next check seems redundant + if not computation.code.isValidOpcode(jt): + raise newException(InvalidInstruction, "Jump resulted in invalid instruction") + + # TODO: what happens if there is an error, rollback? + +op jumpI, inline = true, jumpTarget, testedValue: + ## 0x57, Conditionally alter the program counter. + + if testedValue != 0: + let jt = jumpTarget.toInt + computation.code.pc = jt + + let nextOpcode = computation.code.peek + if nextOpcode != JUMPDEST: + raise newException(InvalidJumpDestination, "Invalid Jump Destination") + # TODO: next check seems redundant + if not computation.code.isValidOpcode(jt): + raise newException(InvalidInstruction, "Jump resulted in invalid instruction") + +op pc, inline = true: + ## 0x58, Get the value of the program counter prior to the increment corresponding to this instruction. + push: max(computation.code.pc - 1, 0) + +op msize, inline = true: + ## 0x59, Get the size of active memory in bytes. + push: computation.memory.len + +op gas, inline = true: + ## 0x5a, Get the amount of available gas, including the corresponding reduction for the cost of this instruction. + push: computation.gasMeter.gasRemaining + +op jumpDest, inline = true: + ## 0x5b, Mark a valid destination for jumps. This operation has no effect on machine state during execution. + discard + +# ########################################## +# 60s & 70s: Push Operations. +# 80s: Duplication Operations +# 90s: Exchange Operations +# a0s: Logging Operations + +genPush() +genDup() +genSwap() +genLog() + +# ########################################## +# f0s: System operations. + +op create, inline = false, value, startPosition, size: + ## 0xf0, Create a new account with associated code. + # TODO: Forked create for Homestead + + let (memPos, len) = (startPosition.toInt, size.toInt) + + computation.gasMeter.consumeGas( + computation.gasCosts[CodeCopy].m_handler(computation.memory.len, memPos, len), + reason="Create fee") + + computation.memory.extend(memPos, len) + + ##### getBalance type error: expression 'db' is of type: proc (vmState: untyped, readOnly: untyped, handler: untyped): untyped{.noSideEffect, gcsafe, locks: .} + # computation.vmState.db(readOnly=true): + # when ForkName >= FkHomestead: # TODO this is done in Geth but not Parity and Py-EVM + # let insufficientFunds = db.getBalance(computation.msg.storageAddress) < value # TODO check gas balance rollover + # let stackTooDeep = computation.msg.depth >= MaxCallDepth + + # # TODO: error message + # if insufficientFunds or stackTooDeep: + # push: 0 + # return + # else: + # let stackTooDeep = computation.msg.depth >= MaxCallDepth + # if stackTooDeep: + # push: 0 + # return + + let callData = computation.memory.read(memPos, len) + + ## TODO dynamic gas that depends on remaining gas + + ##### getNonce type error: expression 'db' is of type: proc (vmState: untyped, readOnly: untyped, handler: untyped): untyped{.noSideEffect, gcsafe, locks: .} + # computation.vmState.db(readOnly=true): + # let creationNonce = db.getNonce(computation.msg.storageAddress) + # db.incrementNonce(computation.msg.storageAddress) + let contractAddress = ZERO_ADDRESS # generateContractAddress(computation.msg.storageAddress, creationNonce) + + let isCollision = false # TODO: db.accountHasCodeOrNonce ... + + if isCollision: + computation.vmState.logger.debug("Address collision while creating contract: " & contractAddress.toHex) + push: 0 + return + + let childMsg = prepareChildMessage( + computation, + gas = 0, # TODO refactor gas + to = CREATE_CONTRACT_ADDRESS, + value = value, + data = @[], + code = callData.toString, + options = MessageOptions(createAddress: contractAddress) + ) + + # let childComputation = applyChildBaseComputation(computation, childMsg) + var childComputation: BaseComputation # TODO - stub + new childComputation + childComputation.gasMeter = newGasMeter(0) # TODO GasMeter should be a normal object. + + if childComputation.isError: + push: 0 + else: + push: contractAddress + computation.gasMeter.returnGas(childComputation.gasMeter.gasRemaining) + +proc callParams(computation: var BaseComputation): (UInt256, UInt256, EthAddress, EthAddress, EthAddress, UInt256, UInt256, UInt256, UInt256, bool, bool) = + let gas = computation.stack.popInt() + let codeAddress = computation.stack.popAddress() + + let (value, + memoryInputStartPosition, memoryInputSize, + memoryOutputStartPosition, memoryOutputSize) = computation.stack.popInt(5) + + let to = computation.msg.storageAddress + let sender = computation.msg.storageAddress + + result = (gas, + value, + to, + sender, + codeAddress, + memoryInputStartPosition, + memoryInputSize, + memoryOutputStartPosition, + memoryOutputSize, + true, # should_transfer_value, + computation.msg.isStatic) + +proc callCodeParams(computation: var BaseComputation): (UInt256, UInt256, EthAddress, EthAddress, EthAddress, UInt256, UInt256, UInt256, UInt256, bool, bool) = + let gas = computation.stack.popInt() + let to = computation.stack.popAddress() + + let (value, + memoryInputStartPosition, memoryInputSize, + memoryOutputStartPosition, memoryOutputSize) = computation.stack.popInt(5) + + result = (gas, + value, + to, + ZERO_ADDRESS, # sender + ZERO_ADDRESS, # code_address + memoryInputStartPosition, + memoryInputSize, + memoryOutputStartPosition, + memoryOutputSize, + true, # should_transfer_value, + computation.msg.isStatic) + +proc delegateCallParams(computation: var BaseComputation): (UInt256, UInt256, EthAddress, EthAddress, EthAddress, UInt256, UInt256, UInt256, UInt256, bool, bool) = + let gas = computation.stack.popInt() + let codeAddress = computation.stack.popAddress() + + let (memoryInputStartPosition, memoryInputSize, + memoryOutputStartPosition, memoryOutputSize) = computation.stack.popInt(4) + + let to = computation.msg.storageAddress + let sender = computation.msg.storageAddress + let value = computation.msg.value + + result = (gas, + value, + to, + sender, + codeAddress, + memoryInputStartPosition, + memoryInputSize, + memoryOutputStartPosition, + memoryOutputSize, + false, # should_transfer_value, + computation.msg.isStatic) + +proc staticCallParams(computation: var BaseComputation): (UInt256, UInt256, EthAddress, EthAddress, EthAddress, UInt256, UInt256, UInt256, UInt256, bool, bool) = + let gas = computation.stack.popInt() + let to = computation.stack.popAddress() + + let (memoryInputStartPosition, memoryInputSize, + memoryOutputStartPosition, memoryOutputSize) = computation.stack.popInt(4) + + result = (gas, + 0.u256, # value + to, + ZERO_ADDRESS, # sender + ZERO_ADDRESS, # codeAddress + memoryInputStartPosition, + memoryInputSize, + memoryOutputStartPosition, + memoryOutputSize, + false, # should_transfer_value, + true) # is_static + +template genCall(callName: untyped): untyped = + op callName, inline = false: + ## CALL, 0xf1, Message-Call into an account + ## CALLCODE, 0xf2, Message-call into this account with an alternative account's code. + ## DELEGATECALL, 0xf4, Message-call into this account with an alternative account's code, but persisting the current values for sender and value. + ## STATICCALL, 0xfa, Static message-call into an account. + # TODO: forked calls for Homestead + + let (gas, value, to, sender, + codeAddress, + memoryInputStartPosition, memoryInputSize, + memoryOutputStartPosition, memoryOutputSize, + shouldTransferValue, + isStatic) = `callName Params`(computation) + + let (memInPos, memInLen, memOutPos, memOutLen) = (memoryInputStartPosition.toInt, memoryInputSize.toInt, memoryOutputStartPosition.toInt, memoryOutputSize.toInt) + + let (gasCost, childMsgGas) = computation.gasCosts[Op.Call].c_handler( + value, + GasParams(kind: Call, + c_isNewAccount: true, # TODO stub + c_gasBalance: 0, + c_contractGas: 0, + c_currentMemSize: computation.memory.len, + c_memOffset: 0, # TODO make sure if we pass the largest mem requested + c_memLength: 0 # or an addition of mem requested + )) + + computation.memory.extend(memInPos, memInLen) + computation.memory.extend(memOutPos, memOutLen) + + let callData = computation.memory.read(memInPos, memInLen) + + ##### getBalance type error: expression 'db' is of type: proc (vmState: untyped, readOnly: untyped, handler: untyped): untyped{.noSideEffect, gcsafe, locks: .} + # computation.vmState.db(readOnly = true): + # let senderBalance = db.getBalance(computation.msg.storageAddress) # TODO check gas balance rollover + + let insufficientFunds = false # shouldTransferValue and senderBalance < value + let stackTooDeep = computation.msg.depth >= MaxCallDepth + + if insufficientFunds or stackTooDeep: + computation.returnData = @[] + var errMessage: string + if insufficientFunds: + let senderBalance = -1 # TODO workaround + # Note: for some reason we can't use strformat here, we get undeclared identifiers + errMessage = &"Insufficient Funds: have: " & $senderBalance & "need: " & $value + elif stackTooDeep: + errMessage = "Stack Limit Reached" + else: + raise newException(VMError, "Invariant: Unreachable code path") + + # computation.logger.debug(&"failure: {errMessage}") # TODO: Error: expression 'logger' has no type (or is ambiguous) + computation.gasMeter.returnGas(childMsgGas) + push: 0 + return + + ##### getCode type error: expression 'db' is of type: proc (vmState: untyped, readOnly: untyped, handler: untyped): untyped{.noSideEffect, gcsafe, locks: .} + # computation.vmState.db(readOnly = true): + # let code = if codeAddress != ZERO_ADDRESS: db.getCode(codeAddress) + # else: db.getCode(to) + let code = "0x" # This is a stub hack, newCodeStreamFromUnescaped expects length 2 at least + + var childMsg = prepareChildMessage( + computation, + childMsgGas, + to, + value, + callData, + code, + MessageOptions( + shouldTransferValue: shouldTransferValue, + isStatic: isStatic) + ) + + if sender != ZERO_ADDRESS: + childMsg.sender = sender + + # let childComputation = applyChildBaseComputation(computation, childMsg) + var childComputation: BaseComputation # TODO - stub + new childComputation + childComputation.gasMeter = newGasMeter(0) # TODO GasMeter should be a normal object. + + if childComputation.isError: + push: 0 + else: + push: 1 + + if not childComputation.shouldEraseReturnData: + let actualOutputSize = min(memOutLen, childComputation.output.len) + computation.memory.write( + memOutPos, + childComputation.output.toBytes[0 ..< actualOutputSize]) + if not childComputation.shouldBurnGas: + computation.gasMeter.returnGas(childComputation.gasMeter.gasRemaining) + +genCall(call) +genCall(callCode) +genCall(delegateCall) +genCall(staticCall) + +op returnOp, inline = false, startPos, size: + ## 0xf3, Halt execution returning output data. + let (pos, len) = (startPos.toInt, size.toInt) + + computation.gasMeter.consumeGas( + computation.gasCosts[Return].m_handler(computation.memory.len, pos, len), + reason = "RETURN" + ) + + computation.memory.extend(pos, len) + let output = computation.memory.read(pos, len) + computation.output = output.toString + +op revert, inline = false, startPos, size: + ## 0xf0, Halt execution reverting state changes but returning data and remaining gas. + let (pos, len) = (startPos.toInt, size.toInt) + + computation.gasMeter.consumeGas( + computation.gasCosts[Revert].m_handler(computation.memory.len, pos, len), + reason = "REVERT" + ) + + computation.memory.extend(pos, len) + let output = computation.memory.read(pos, len).toString + computation.output = output + +op selfDestruct, inline = false: + ## 0xff Halt execution and register account for later deletion. + let beneficiary = computation.stack.popAddress() + + ## TODO + + computation.registerAccountForDeletion(beneficiary) diff --git a/nimbus/vm/interpreter/opcodes_impl/arithmetic.nim b/nimbus/vm/interpreter/opcodes_impl/arithmetic.nim deleted file mode 100644 index 54cc207dd..000000000 --- a/nimbus/vm/interpreter/opcodes_impl/arithmetic.nim +++ /dev/null @@ -1,117 +0,0 @@ -# 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. - -import - strutils, - eth_common/eth_types, - helpers, ./impl_std_import - -proc add*(computation: var BaseComputation) = - # Addition - let (left, right) = computation.stack.popInt(2) - - let res = left + right - pushRes() - -proc addmod*(computation: var BaseComputation) = - # Modulo Addition - let (left, right, modulus) = computation.stack.popInt(3) - - let res = if modulus.isZero: zero(Uint256) # EVM special casing of div by 0 - else: addmod(left, right, modulus) - pushRes() - -proc sub*(computation: var BaseComputation) = - # Subtraction - let (left, right) = computation.stack.popInt(2) - - let res = left - right - pushRes() - - -proc modulo*(computation: var BaseComputation) = - # Modulo - let (value, modulus) = computation.stack.popInt(2) - - let res = if modulus.isZero: zero(Uint256) # EVM special casing of div by 0 - else: value mod modulus - pushRes() - -proc smod*(computation: var BaseComputation) = - # Signed Modulo - let (value, modulus) = computation.stack.popInt(2) - - let res = if modulus.isZero: zero(Uint256) - else: pseudoSignedToUnsigned( - unsignedToPseudoSigned(value) mod unsignedToPseudoSigned(modulus) - ) - pushRes() - -proc mul*(computation: var BaseComputation) = - # Multiplication - let (left, right) = computation.stack.popInt(2) - - let res = left * right - pushRes() - -proc mulmod*(computation: var BaseComputation) = - # Modulo Multiplication - let (left, right, modulus) = computation.stack.popInt(3) - - let res = if modulus.isZero: zero(Uint256) - else: mulmod(left, right, modulus) - pushRes() - -proc divide*(computation: var BaseComputation) = - # Division - let (numerator, denominator) = computation.stack.popInt(2) - - let res = if denominator.isZero: zero(Uint256) - else: numerator div denominator - pushRes() - -proc sdiv*(computation: var BaseComputation) = - # Signed Division - let (value, divisor) = computation.stack.popInt(2) - - let res = if divisor.isZero: zero(Uint256) - else: pseudoSignedToUnsigned( - unsignedToPseudoSigned(value) div unsignedToPseudoSigned(divisor) - ) - pushRes() - -# no curry -proc exp*(computation: var BaseComputation) = - - # Exponentiation - let (base, exponent) = computation.stack.popInt(2) - - computation.gasMeter.consumeGas( - computation.gasCosts[Exp].d_handler(exponent), - reason="EXP: exponent bytes" - ) - - let res = if base.isZero: 0.u256 # 0^0 is 0 in py-evm - else: base.pow(exponent) - pushRes() - -proc signextend*(computation: var BaseComputation) = - # Signed Extend - let (bits, value) = computation.stack.popInt(2) - - var res: UInt256 - if bits <= 31.u256: - let testBit = bits.toInt * 8 + 7 - let bitPos = (1 shl testBit) - let mask = u256(bitPos - 1) - if not (value and bitPos).isZero: - res = value or (not mask) - else: - res = value and mask - else: - res = value - pushRes() diff --git a/nimbus/vm/interpreter/opcodes_impl/block.nim b/nimbus/vm/interpreter/opcodes_impl/block.nim deleted file mode 100644 index f9267846b..000000000 --- a/nimbus/vm/interpreter/opcodes_impl/block.nim +++ /dev/null @@ -1,27 +0,0 @@ -# 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. - -import - eth_common/eth_types, - ../constants, ../computation, ../vm/stack, ../vm_state - -proc blockhash*(computation: var BaseComputation) = - var blockNumber = computation.stack.popInt() - var blockHash = computation.vmState.getAncestorHash(blockNumber) - computation.stack.push(blockHash) - -proc coinbase*(computation: var BaseComputation) = - computation.stack.push(computation.vmState.coinbase) - -proc timestamp*(computation: var BaseComputation) = - computation.stack.push(computation.vmState.timestamp.int256) - -proc difficulty*(computation: var BaseComputation) = - computation.stack.push(computation.vmState.difficulty) - -proc gaslimit*(computation: var BaseComputation) = - computation.stack.push(computation.vmState.gasLimit) diff --git a/nimbus/vm/interpreter/opcodes_impl/block_ops.nim b/nimbus/vm/interpreter/opcodes_impl/block_ops.nim deleted file mode 100644 index 1cab191bd..000000000 --- a/nimbus/vm/interpreter/opcodes_impl/block_ops.nim +++ /dev/null @@ -1,37 +0,0 @@ -# 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. - -import - times, eth_common/eth_types, ./impl_std_import - -{.this: computation.} -{.experimental.} - -using - computation: var BaseComputation - -proc blockhash*(computation) = - let blockNumber = vmWordToBlockNumber stack.popInt() - let blockHash = vmState.getAncestorHash(blockNumber) - stack.push(blockHash) - -proc coinbase*(computation) = - stack.push(vmState.coinbase) - -proc timestamp*(computation) = - # TODO: EthTime is an alias of Time, which is a distinct int64 so can't use u256(int64) - # This may have implications for different platforms. - stack.push(vmState.timestamp.toUnix.uint64.u256) - -proc number*(computation) = - stack.push(blockNumberToVmWord vmState.blockNumber) - -proc difficulty*(computation) = - stack.push(vmState.difficulty) - -proc gaslimit*(computation) = - stack.push(vmState.gasLimit.u256) diff --git a/nimbus/vm/interpreter/opcodes_impl/call.nim b/nimbus/vm/interpreter/opcodes_impl/call.nim deleted file mode 100644 index 04dc74674..000000000 --- a/nimbus/vm/interpreter/opcodes_impl/call.nim +++ /dev/null @@ -1,213 +0,0 @@ -# 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. - -import - strformat, eth_common, - # ./impl_std_import # Cannot do that due to recursive dependencies - # .../vm/interpreter/opcodes_impl/impl_std_import.nim imports .../vm/computation.nim - # .../vm/computation.nim imports .../vm/interpreter/opcodes_impl/call.nim - # .../vm/interpreter/opcodes_impl/call.nim imports .../vm/interpreter/opcodes_impl/impl_std_import.nim - ../../../constants, ../../../vm_types, ../../../errors, ../../../logging, - ../../../utils/bytes, - ../../computation, ../../stack, ../../memory, ../../message, - ../opcode_values, ../gas_meter, ../gas_costs - -type - # TODO most of these are for gas handling - - BaseCall* = ref object of Opcode - - Call* = ref object of BaseCall - - CallCode* = ref object of BaseCall - - DelegateCall* = ref object of BaseCall - - CallEIP150* = ref object of Call - - CallCodeEIP150* = ref object of CallCode - - DelegateCallEIP150* = ref object of DelegateCall - - CallEIP161* = ref object of CallEIP150 # TODO: Refactoring - put that in VM forks - - # Byzantium - StaticCall* = ref object of CallEIP161 # TODO: Refactoring - put that in VM forks - - CallByzantium* = ref object of CallEIP161 # TODO: Refactoring - put that in VM forks - -using - computation: var BaseComputation - -method callParams*(call: BaseCall, computation): (UInt256, UInt256, EthAddress, EthAddress, EthAddress, UInt256, UInt256, UInt256, UInt256, bool, bool) {.base.} = - raise newException(NotImplementedError, "Must be implemented subclasses") - -method runLogic*(call: BaseCall, computation) = - let (gas, value, to, sender, - codeAddress, - memoryInputStartPosition, memoryInputSize, - memoryOutputStartPosition, memoryOutputSize, - shouldTransferValue, - isStatic) = call.callParams(computation) - - let (memInPos, memInLen, memOutPos, memOutLen) = (memoryInputStartPosition.toInt, memoryInputSize.toInt, memoryOutputStartPosition.toInt, memoryOutputSize.toInt) - - let (gasCost, childMsgGas) = computation.gasCosts[Op.Call].c_handler( - value, - GasParams() # TODO - stub - ) - - computation.memory.extend(memInPos, memInLen) - computation.memory.extend(memOutPos, memOutLen) - - let callData = computation.memory.read(memInPos, memInLen) - - # TODO: Pre-call checks - # with computation.vm_state.state_db(read_only=True) as state_db: - # sender_balance = state_db.get_balance(computation.msg.storage_address) - let senderBalance = 0.u256 - - let insufficientFunds = shouldTransferValue and senderBalance < value - let stackTooDeep = computation.msg.depth + 1 > STACK_DEPTH_LIMIT - - if insufficientFunds or stackTooDeep: - computation.returnData = "" - var errMessage: string - if insufficientFunds: - errMessage = &"Insufficient Funds: have: {senderBalance} | need: {value}" - elif stackTooDeep: - errMessage = "Stack Limit Reached" - else: - raise newException(VMError, "Invariant: Unreachable code path") - - computation.logger.debug(&"{call.kind} failure: {errMessage}") - computation.gasMeter.returnGas(childMsgGas) - computation.stack.push(0.u256) - else: - # TODO: with - # with computation.vm_state.state_db(read_only=True) as state_db: - # if code_address: - # code = state_db.get_code(code_address) - # else: - # code = state_db.get_code(to) - let code = "" - - let childMsg = computation.prepareChildMessage( - childMsgGas, - to, - value, - callData, - code, - MessageOptions( - shouldTransferValue: shouldTransferValue, - isStatic: isStatic)) - if sender != ZERO_ADDRESS: - childMsg.sender = sender - # let childComputation = computation.applyChildComputation(childMsg) - # TODO - var childComputation: BaseComputation - if childComputation.isError: - computation.stack.push(0.u256) - else: - computation.stack.push(1.u256) - if not childComputation.shouldEraseReturnData: - let actualOutputSize = min(memOutLen, childComputation.output.len) - computation.memory.write( - memOutPos, - childComputation.output.toBytes[0 ..< actualOutputSize]) - if not childComputation.shouldBurnGas: - computation.gasMeter.returnGas(childComputation.gasMeter.gasRemaining) - -method callParams(call: CallCode, computation): (UInt256, UInt256, EthAddress, EthAddress, EthAddress, UInt256, UInt256, UInt256, UInt256, bool, bool) = - let gas = computation.stack.popInt() - let to = computation.stack.popAddress() - - let (value, - memoryInputStartPosition, memoryInputSize, - memoryOutputStartPosition, memoryOutputSize) = computation.stack.popInt(5) - - result = (gas, - value, - to, - ZERO_ADDRESS, # sender - ZERO_ADDRESS, # code_address - memoryInputStartPosition, - memoryInputSize, - memoryOutputStartPosition, - memoryOutputSize, - true, # should_transfer_value, - computation.msg.isStatic) - -method callParams(call: Call, computation): (UInt256, UInt256, EthAddress, EthAddress, EthAddress, UInt256, UInt256, UInt256, UInt256, bool, bool) = - let gas = computation.stack.popInt() - let codeAddress = computation.stack.popAddress() - - let (value, - memoryInputStartPosition, memoryInputSize, - memoryOutputStartPosition, memoryOutputSize) = computation.stack.popInt(5) - - let to = computation.msg.storageAddress - let sender = computation.msg.storageAddress - - result = (gas, - value, - to, - sender, - codeAddress, - memoryInputStartPosition, - memoryInputSize, - memoryOutputStartPosition, - memoryOutputSize, - true, # should_transfer_value, - computation.msg.isStatic) - -method callParams(call: DelegateCall, computation): (UInt256, UInt256, EthAddress, EthAddress, EthAddress, UInt256, UInt256, UInt256, UInt256, bool, bool) = - let gas = computation.stack.popInt() - let codeAddress = computation.stack.popAddress() - - let (memoryInputStartPosition, memoryInputSize, - memoryOutputStartPosition, memoryOutputSize) = computation.stack.popInt(4) - - let to = computation.msg.storageAddress - let sender = computation.msg.storageAddress - let value = computation.msg.value - - result = (gas, - value, - to, - sender, - codeAddress, - memoryInputStartPosition, - memoryInputSize, - memoryOutputStartPosition, - memoryOutputSize, - false, # should_transfer_value, - computation.msg.isStatic) - -method callParams(call: StaticCall, computation): (UInt256, UInt256, EthAddress, EthAddress, EthAddress, UInt256, UInt256, UInt256, UInt256, bool, bool) = - let gas = computation.stack.popInt() - let to = computation.stack.popAddress() - - let (memoryInputStartPosition, memoryInputSize, - memoryOutputStartPosition, memoryOutputSize) = computation.stack.popInt(4) - - result = (gas, - 0.u256, # value - to, - ZERO_ADDRESS, # sender - ZERO_ADDRESS, # codeAddress - memoryInputStartPosition, - memoryInputSize, - memoryOutputStartPosition, - memoryOutputSize, - false, # should_transfer_value, - true) # is_static - -method callParams(call: CallByzantium, computation): (UInt256, UInt256, EthAddress, EthAddress, EthAddress, UInt256, UInt256, UInt256, UInt256, 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") diff --git a/nimbus/vm/interpreter/opcodes_impl/comparison.nim b/nimbus/vm/interpreter/opcodes_impl/comparison.nim deleted file mode 100644 index 569c92b95..000000000 --- a/nimbus/vm/interpreter/opcodes_impl/comparison.nim +++ /dev/null @@ -1,54 +0,0 @@ -# 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. - -import - helpers, ./impl_std_import - -quasiBoolean(lt, `<`) # Lesser Comparison - -quasiBoolean(gt, `>`) # Greater Comparison - -quasiBoolean(slt, `<`, signed=true) # Signed Lesser Comparison - -quasiBoolean(sgt, `>`, signed=true) # Signed Greater Comparison - -quasiBoolean(eq, `==`) # Equality - -proc andOp*(computation: var BaseComputation) = - let (lhs, rhs) = computation.stack.popInt(2) - computation.stack.push(lhs and rhs) - -proc orOp*(computation: var BaseComputation) = - let (lhs, rhs) = computation.stack.popInt(2) - computation.stack.push(lhs or rhs) - -proc xorOp*(computation: var BaseComputation) = - let (lhs, rhs) = computation.stack.popInt(2) - computation.stack.push(lhs xor rhs) - -# TODO use isZero from Stint -proc iszero*(computation: var BaseComputation) = - var value = computation.stack.popInt() - - var res = if value == 0: 1.u256 else: 0.u256 - pushRes() - -proc notOp*(computation: var BaseComputation) = - var value = computation.stack.popInt() - - var res = UINT_256_MAX - value - pushRes() - -# TODO: seems like there is an implementation or a comment issue -# this is not a bitwise "and" or the "byte" instruction -proc byteOp*(computation: var BaseComputation) = - # Bitwise And - - var (position, value) = computation.stack.popInt(2) - - var res = if position >= 32.u256: 0.u256 else: (value div (256.u256.pow(31'u64 - position.toInt.uint64))) mod 256 - pushRes() diff --git a/nimbus/vm/interpreter/opcodes_impl/context.nim b/nimbus/vm/interpreter/opcodes_impl/context.nim deleted file mode 100644 index 61c77615b..000000000 --- a/nimbus/vm/interpreter/opcodes_impl/context.nim +++ /dev/null @@ -1,155 +0,0 @@ -# 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. - -import - strformat, - ranges/typedranges, - ./impl_std_import, - ../../../db/state_db - -proc balance*(computation: var BaseComputation) = - let address = computation.stack.popAddress() - let balance = computation.vmState.readOnlyStateDB.getBalance(address) - computation.stack.push balance - -proc origin*(computation: var BaseComputation) = - computation.stack.push(computation.msg.origin) - -proc address*(computation: var BaseComputation) = - computation.stack.push(computation.msg.storageAddress) - -proc caller*(computation: var BaseComputation) = - computation.stack.push(computation.msg.sender) - -proc callValue*(computation: var BaseComputation) = - computation.stack.push(computation.msg.value) - -proc writePaddedResult(mem: var Memory, - data: openarray[byte], - memPos, dataPos, len: Natural, - paddingValue = 0.byte) = - mem.extend(memPos, len) - - let dataEndPosition = dataPos + len - 1 - if dataEndPosition < data.len: - mem.write(memPos, data[dataPos .. dataEndPosition]) - else: - var presentElements = data.len - dataPos - if presentElements > 0: - mem.write(memPos, data.toOpenArray(dataPos, data.len - 1)) - else: - presentElements = 0 - - mem.writePaddingBytes(memPos + presentElements, - len - presentElements, - paddingValue) - -proc callDataLoad*(computation: var BaseComputation) = - # Load call data into memory - let origDataPos = computation.stack.popInt - if origDataPos >= computation.msg.data.len: - computation.stack.push(0) - return - - let - dataPos = origDataPos.toInt - dataEndPosition = dataPos + 31 - - if dataEndPosition < computation.msg.data.len: - computation.stack.push(computation.msg.data[dataPos .. dataEndPosition]) - else: - var bytes: array[32, byte] - var presentBytes = min(computation.msg.data.len - dataPos, 32) - - if presentBytes > 0: - copyMem(addr bytes[0], addr computation.msg.data[dataPos], presentBytes) - else: - presentBytes = 0 - - for i in presentBytes ..< 32: bytes[i] = 0 - computation.stack.push(bytes) - -proc callDataSize*(computation: var BaseComputation) = - let size = computation.msg.data.len.u256 - computation.stack.push(size) - -proc callDataCopy*(computation: var BaseComputation) = - let (memStartPosition, - calldataStartPosition, - size) = computation.stack.popInt(3) - - computation.gasMeter.consumeGas( - computation.gasCosts[CallDataCopy].d_handler(size), - reason="CALLDATACOPY fee") - - let (memPos, callPos, len) = (memStartPosition.toInt, calldataStartPosition.toInt, size.toInt) - - computation.memory.writePaddedResult(computation.msg.data, - memPos, callPos, len) - -proc codeSize*(computation: var BaseComputation) = - let size = computation.code.len.u256 - computation.stack.push(size) - -proc codeCopy*(computation: var BaseComputation) = - let (memStartPosition, - codeStartPosition, - size) = computation.stack.popInt(3) - - computation.gasMeter.consumeGas( - computation.gasCosts[CodeCopy].d_handler(size), - reason="CODECOPY: word gas cost") - - let (memPos, codePos, len) = (memStartPosition.toInt, codeStartPosition.toInt, size.toInt) - - computation.memory.writePaddedResult(computation.code.bytes, memPos, codePos, len) - -proc gasPrice*(computation: var BaseComputation) = - computation.stack.push(computation.msg.gasPrice.u256) - -proc extCodeSize*(computation: var BaseComputation) = - let account = computation.stack.popAddress() - let codeSize = computation.vmState.readOnlyStateDB.getCode(account).len - computation.stack.push uint(codeSize) - -proc extCodeCopy*(computation: var BaseComputation) = - let account = computation.stack.popAddress() - let (memStartPosition, codeStartPosition, size) = computation.stack.popInt(3) - - computation.gasMeter.consumeGas( - computation.gasCosts[ExtCodeCopy].d_handler(size), - reason="EXTCODECOPY: word gas cost" - ) - - let (memPos, codePos, len) = (memStartPosition.toInt, codeStartPosition.toInt, size.toInt) - let codeBytes = computation.vmState.readOnlyStateDB.getCode(account) - - computation.memory.writePaddedResult(codeBytes.toOpenArray, memPos, codePos, len) - -proc returnDataSize*(computation: var BaseComputation) = - let size = computation.returnData.len.u256 - computation.stack.push(size) - -proc returnDataCopy*(computation: var BaseComputation) = - let (memStartPosition, returnDataStartPosition, size) = computation.stack.popInt(3) - - computation.gasMeter.consumeGas( - computation.gasCosts[ReturnDataCopy].d_handler(size), - reason="RETURNDATACOPY fee" - ) - - let (memPos, returnPos, len) = (memStartPosition.toInt, returnDataStartPosition.toInt, size.toInt) - if returnPos + len > computation.returnData.len: - raise newException(OutOfBoundsRead, - "Return data length is not sufficient to satisfy request. Asked \n" & - &"for data from index {returnDataStartPosition} to {returnDataStartPosition + size}. Return data is {computation.returnData.len} in \n" & - "length") - - computation.memory.extend(memPos, len) - - let value = ($computation.returnData)[returnPos ..< returnPos + len] - computation.memory.write(memPos, len, value) diff --git a/nimbus/vm/interpreter/opcodes_impl/duplication.nim b/nimbus/vm/interpreter/opcodes_impl/duplication.nim deleted file mode 100644 index 8c172b3a6..000000000 --- a/nimbus/vm/interpreter/opcodes_impl/duplication.nim +++ /dev/null @@ -1,33 +0,0 @@ -# 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. - -import - macros, strformat, ./impl_std_import - -macro dupXX(position: static[int]): untyped = - let name = ident(&"dup{position}") - result = quote: - proc `name`*(computation: var BaseComputation) = - computation.stack.dup(`position`) - -dupXX(1) -dupXX(2) -dupXX(3) -dupXX(4) -dupXX(5) -dupXX(6) -dupXX(7) -dupXX(8) -dupXX(9) -dupXX(10) -dupXX(11) -dupXX(12) -dupXX(13) -dupXX(14) -dupXX(15) -dupXX(16) - diff --git a/nimbus/vm/interpreter/opcodes_impl/flow.nim b/nimbus/vm/interpreter/opcodes_impl/flow.nim deleted file mode 100644 index ec5875b03..000000000 --- a/nimbus/vm/interpreter/opcodes_impl/flow.nim +++ /dev/null @@ -1,56 +0,0 @@ -# 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. - -import - strformat, ./impl_std_import - -{.this: computation.} -{.experimental.} - -using - computation: var BaseComputation - -proc stop*(computation) = - raise newException(Halt, "STOP") - - -proc jump*(computation) = - let jumpDest = stack.popInt.toInt - code.pc = jumpDest - - let nextOpcode = code.peek() - - if nextOpcode != JUMPDEST: - raise newException(InvalidJumpDestination, "Invalid Jump Destination") - - if not code.isValidOpcode(jumpDest): - raise newException(InvalidInstruction, "Jump resulted in invalid instruction") - -proc jumpi*(computation) = - let (jumpDest, checkValue) = stack.popInt(2) - - if checkValue > 0: - code.pc = jumpDest.toInt - - let nextOpcode = code.peek() - - if nextOpcode != JUMPDEST: - raise newException(InvalidJumpDestination, "Invalid Jump Destination") - - if not code.isValidOpcode(jumpDest.toInt): - raise newException(InvalidInstruction, "Jump resulted in invalid instruction") - -proc jumpdest*(computation) = - discard - -proc pc*(computation) = - let pc = max(code.pc - 1, 0).u256 - stack.push(pc) - -proc gas*(computation) = - let gasRemaining = gasMeter.gasRemaining - stack.push(gasRemaining.u256) diff --git a/nimbus/vm/interpreter/opcodes_impl/helpers.nim b/nimbus/vm/interpreter/opcodes_impl/helpers.nim deleted file mode 100644 index efcd1f19a..000000000 --- a/nimbus/vm/interpreter/opcodes_impl/helpers.nim +++ /dev/null @@ -1,41 +0,0 @@ -# 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. - -import - macros, - eth_common/eth_types - -template pushRes*: untyped = - computation.stack.push(res) - -macro quasiBoolean*(name: untyped, op: untyped, signed: untyped = nil, nonzero: untyped = nil): untyped = - var signedNode = newEmptyNode() - var finishSignedNode = newEmptyNode() - let resNode = ident("res") - var leftNode = ident("left") - var rightNode = ident("right") - var actualLeftNode = leftNode - var actualRightNode = rightNode - if not signed.isNil: - actualLeftNode = ident("leftSigned") - actualRightNode = ident("rightSigned") - signedNode = quote: - let `actualLeftNode` = cast[Int256](`leftNode`) - let `actualRightNode` = cast[Int256](`rightNode`) - var test = if nonzero.isNil: - quote: - `op`(`actualLeftNode`, `actualRightNode`) - else: - quote: - `op`(`actualLeftNode`, `actualRightNode`) != 0 - result = quote: - proc `name`*(computation: var BaseComputation) = - var (`leftNode`, `rightNode`) = computation.stack.popInt(2) - `signedNode` - - var `resNode` = if `test`: 1.u256 else: 0.u256 - computation.stack.push(`resNode`) diff --git a/nimbus/vm/interpreter/opcodes_impl/impl_std_import.nim b/nimbus/vm/interpreter/opcodes_impl/impl_std_import.nim deleted file mode 100644 index b5d12854c..000000000 --- a/nimbus/vm/interpreter/opcodes_impl/impl_std_import.nim +++ /dev/null @@ -1,22 +0,0 @@ -# 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. - -import - eth_common/eth_types, stint/lenient_stint, - ../../../constants, ../../../vm_state, ../../../vm_types, ../../../vm_types, - ../../../errors, ../../../logging, ../../../utils/padding, ../../../utils/bytes, - ../../stack, ../../computation, ../../stack, ../../memory, ../../message, - ../../code_stream, ../../utils/utils_numeric, - ../opcode_values, ../gas_meter, ../gas_costs - -export - eth_types, lenient_stint, - constants, vm_state, vm_types, vm_types, - errors, logging, padding, bytes, - stack, computation, stack, memory, message, - code_stream, utils_numeric, - opcode_values, gas_meter, gas_costs diff --git a/nimbus/vm/interpreter/opcodes_impl/invalid.nim b/nimbus/vm/interpreter/opcodes_impl/invalid.nim deleted file mode 100644 index 05ec36aff..000000000 --- a/nimbus/vm/interpreter/opcodes_impl/invalid.nim +++ /dev/null @@ -1,12 +0,0 @@ -# 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. - -import - ./impl_std_import - -proc invalidOp*(computation: var BaseComputation) = - raise newException(InvalidInstruction, "Invalid opcode") diff --git a/nimbus/vm/interpreter/opcodes_impl/logging_ops.nim b/nimbus/vm/interpreter/opcodes_impl/logging_ops.nim deleted file mode 100644 index c7049543c..000000000 --- a/nimbus/vm/interpreter/opcodes_impl/logging_ops.nim +++ /dev/null @@ -1,71 +0,0 @@ -# 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. - -import - strformat, macros, - ./impl_std_import - -{.this: computation.} -{.experimental.} - -using - computation: var BaseComputation - -macro logXX(topicCount: static[int]): untyped = - if topicCount < 0 or topicCount > 4: - error(&"Invalid log topic len {topicCount} Must be 0, 1, 2, 3, or 4") - return - - let name = ident(&"log{topicCount}") - let computation = ident("computation") - let topics = ident("topics") - let topicsTuple = ident("topicsTuple") - let len = ident("len") - let memPos = ident("memPos") - result = quote: - proc `name`*(`computation`: var BaseComputation) = - let (memStartPosition, size) = `computation`.stack.popInt(2) - let (`memPos`, `len`) = (memStartPosition.toInt, size.toInt) - var `topics`: seq[UInt256] - - var topicCode: NimNode - if topicCount == 0: - topicCode = quote: - `topics` = @[] - elif topicCount > 1: - topicCode = quote: - let `topicsTuple` = `computation`.stack.popInt(`topicCount`) - topicCode = nnkStmtList.newTree(topicCode) - for z in 0 ..< topicCount: - let topicPush = quote: - `topics`.add(`topicsTuple`[`z`]) - topicCode.add(topicPush) - else: - topicCode = quote: - `topics` = @[`computation`.stack.popInt()] - - result.body.add(topicCode) - - let OpName = ident(&"Log{topicCount}") - let logicCode = quote do: - `computation`.gasMeter.consumeGas( - `computation`.gasCosts[`OpName`].m_handler(`computation`.memory.len, `memPos`, `len`), - reason="Memory expansion, Log topic and data gas cost") - `computation`.memory.extend(`memPos`, `len`) - let logData = `computation`.memory.read(`memPos`, `len`).toString - `computation`.addLogEntry( - account = `computation`.msg.storageAddress, - topics = `topics`, - data = log_data) - - result.body.add(logicCode) - -logXX(0) -logXX(1) -logXX(2) -logXX(3) -logXX(4) diff --git a/nimbus/vm/interpreter/opcodes_impl/memory_ops.nim b/nimbus/vm/interpreter/opcodes_impl/memory_ops.nim deleted file mode 100644 index cc6e53efd..000000000 --- a/nimbus/vm/interpreter/opcodes_impl/memory_ops.nim +++ /dev/null @@ -1,57 +0,0 @@ -# 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. - -import - ./impl_std_import - -{.this: computation.} -{.experimental.} - -using - computation: var BaseComputation - -# TODO template handler - -proc mstore*(computation) = - let start = stack.popInt().toInt - let normalizedValue = stack.popInt().toByteArrayBE - - computation.gasMeter.consumeGas( - computation.gasCosts[MStore].m_handler(computation.memory.len, start, 32), - reason="MSTORE: GasVeryLow + memory expansion" - ) - - memory.extend(start, 32) - memory.write(start, normalizedValue) - -proc mstore8*(computation) = - let start = stack.popInt().toInt - let value = stack.popInt() - let normalizedValue = (value and 0xff).toByteArrayBE - - computation.gasMeter.consumeGas( - computation.gasCosts[MStore8].m_handler(computation.memory.len, start, 1), - reason="MSTORE8: GasVeryLow + memory expansion" - ) - - memory.extend(start, 1) - memory.write(start, [normalizedValue[0]]) - -proc mload*(computation) = - let start = stack.popInt().toInt - - computation.gasMeter.consumeGas( - computation.gasCosts[MLoad].m_handler(computation.memory.len, start, 32), - reason="MLOAD: GasVeryLow + memory expansion" - ) - - memory.extend(start, 32) - let value = memory.read(start, 32) - stack.push(value) - -proc msize*(computation) = - stack.push(memory.len.u256) diff --git a/nimbus/vm/interpreter/opcodes_impl/sha3.nim b/nimbus/vm/interpreter/opcodes_impl/sha3.nim deleted file mode 100644 index fd05e0887..000000000 --- a/nimbus/vm/interpreter/opcodes_impl/sha3.nim +++ /dev/null @@ -1,24 +0,0 @@ -# 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. - -import - nimcrypto, - ./impl_std_import, ./helpers - -proc sha3op*(computation: var BaseComputation) = - let (startPosition, size) = computation.stack.popInt(2) - let (pos, len) = (startPosition.toInt, size.toInt) - - computation.gasMeter.consumeGas( - computation.gasCosts[Op.Sha3].m_handler(computation.memory.len, pos, len), - reason="SHA3: word gas cost" - ) - - computation.memory.extend(pos, len) - - var res = keccak256.digest("") # TODO: stub - pushRes() diff --git a/nimbus/vm/interpreter/opcodes_impl/stack_ops.nim b/nimbus/vm/interpreter/opcodes_impl/stack_ops.nim deleted file mode 100644 index 2400e8193..000000000 --- a/nimbus/vm/interpreter/opcodes_impl/stack_ops.nim +++ /dev/null @@ -1,59 +0,0 @@ -# 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. - -import - strformat, macros, ./impl_std_import - -{.this: computation.} -{.experimental.} - -using - computation: var BaseComputation - -proc pop*(computation) = - discard stack.popInt() - -macro pushXX(size: static[int]): untyped = - let computation = ident("computation") - let value = ident("value") - let name = ident(&"push{size}") - result = quote: - proc `name`*(`computation`: var BaseComputation) = - `computation`.stack.push `computation`.code.readVmWord(`size`) - -pushXX(1) -pushXX(2) -pushXX(3) -pushXX(4) -pushXX(5) -pushXX(6) -pushXX(7) -pushXX(8) -pushXX(9) -pushXX(10) -pushXX(11) -pushXX(12) -pushXX(13) -pushXX(14) -pushXX(15) -pushXX(16) -pushXX(17) -pushXX(18) -pushXX(19) -pushXX(20) -pushXX(21) -pushXX(22) -pushXX(23) -pushXX(24) -pushXX(25) -pushXX(26) -pushXX(27) -pushXX(28) -pushXX(29) -pushXX(30) -pushXX(31) -pushXX(32) diff --git a/nimbus/vm/interpreter/opcodes_impl/storage.nim b/nimbus/vm/interpreter/opcodes_impl/storage.nim deleted file mode 100644 index 0da67cabe..000000000 --- a/nimbus/vm/interpreter/opcodes_impl/storage.nim +++ /dev/null @@ -1,44 +0,0 @@ -# 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. - -import - ./impl_std_import, strformat, - ../../../utils/header, - ../../../db/[db_chain, state_db] - -{.this: computation.} -{.experimental.} - -using - computation: var BaseComputation - -proc sstore*(computation) = - let (slot, value) = stack.popInt(2) - - var (currentValue, existing) = computation.vmState.readOnlyStateDB.getStorage(computation.msg.storageAddress, slot) - - let - gasParam = GasParams(kind: Op.Sstore, s_isStorageEmpty: not existing) - (gasCost, gasRefund) = computation.gasCosts[Sstore].c_handler(currentValue, gasParam) - - computation.gasMeter.consumeGas(gasCost, &"SSTORE: {computation.msg.storageAddress}[{slot}] -> {value} ({currentValue})") - - if gasRefund > 0: - computation.gasMeter.refundGas(gasRefund) - - computation.vmState.mutateStateDB: - db.setStorage(computation.msg.storageAddress, slot, value) - -proc sload*(computation) = - let slot = stack.popInt() - let (value, found) = computation.vmState.readOnlyStateDB.getStorage(computation.msg.storageAddress, slot) - if found: - computation.stack.push value - else: - # XXX: raise exception? - discard - diff --git a/nimbus/vm/interpreter/opcodes_impl/swap.nim b/nimbus/vm/interpreter/opcodes_impl/swap.nim deleted file mode 100644 index ad4d52d55..000000000 --- a/nimbus/vm/interpreter/opcodes_impl/swap.nim +++ /dev/null @@ -1,33 +0,0 @@ -# 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. - -import - macros, strformat, ./impl_std_import - -macro swapXX(position: static[int]): untyped = - let name = ident(&"swap{position}") - result = quote: - proc `name`*(computation: var BaseComputation) = - computation.stack.swap(`position`) - -swapXX(0) -swapXX(1) -swapXX(2) -swapXX(3) -swapXX(4) -swapXX(5) -swapXX(6) -swapXX(7) -swapXX(8) -swapXX(9) -swapXX(10) -swapXX(11) -swapXX(12) -swapXX(13) -swapXX(14) -swapXX(15) -swapXX(16) diff --git a/nimbus/vm/interpreter/opcodes_impl/system_ops.nim b/nimbus/vm/interpreter/opcodes_impl/system_ops.nim deleted file mode 100644 index d8f226029..000000000 --- a/nimbus/vm/interpreter/opcodes_impl/system_ops.nim +++ /dev/null @@ -1,177 +0,0 @@ -# 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. - -import - strformat, - ./call, ./impl_std_import, - byteutils, eth_common - -{.this: computation.} -{.experimental.} - -using - computation: var BaseComputation - -type - Create* = ref object of Opcode - - CreateEIP150* = ref object of Create # TODO: Refactoring - put that in VM forks - - CreateByzantium* = ref object of CreateEIP150 # TODO: Refactoring - put that in VM forks - -# method maxChildGasModifier(create: Create, gas: GasInt): GasInt {.base.} = -# gas - -method runLogic*(create: Create, computation) = - # computation.gasMeter.consumeGas(computation.gasCosts[create.gasCost(computation)], reason = $create.kind) # TODO: Refactoring create gas costs - let (value, startPosition, size) = computation.stack.popInt(3) - let (pos, len) = (startPosition.toInt, size.toInt) - computation.memory.extend(pos, len) - - # TODO: with ZZZZ - # with computation.vm_state.state_db(read_only=True) as state_db: - # insufficient_funds = state_db.get_balance( - # computation.msg.storage_address) < value - # stack_too_deep = computation.msg.depth + 1 > constants.STACK_DEPTH_LIMIT - - # if insufficient_funds or stack_too_deep: - # computation.stack.push(0) - # return - - let callData = computation.memory.read(pos, len) - # TODO refactor gas - # let createMsgGas = create.maxChildGasModifier(computation.gasMeter.gasRemaining) - # computation.gasMeter.consumeGas(createMsgGas, reason="CREATE") - - # TODO: with - # with computation.vm_state.state_db() as state_db: - # creation_nonce = state_db.get_nonce(computation.msg.storage_address) - # state_db.increment_nonce(computation.msg.storage_address) - - # contract_address = generate_contract_address( - # computation.msg.storage_address, - # creation_nonce, - # ) - - # is_collision = state_db.account_has_code_or_nonce(contract_address) - let contractAddress = ZERO_ADDRESS - let isCollision = false - - if isCollision: - computation.vmState.logger.debug(&"Address collision while creating contract: {contractAddress.toHex}") - computation.stack.push(0.u256) - return - - let childMsg = computation.prepareChildMessage( - gas=0, # TODO refactor gas - to=CREATE_CONTRACT_ADDRESS, - value=value, - data=cast[seq[byte]](@[]), - code=callData.toString, - options=MessageOptions(createAddress: contractAddress)) - - # let childComputation = computation.applyChildComputation(childMsg) - var childComputation: BaseComputation - - if childComputation.isError: - computation.stack.push(0.u256) - else: - computation.stack.push(contractAddress) - computation.gasMeter.returnGas(childComputation.gasMeter.gasRemaining) - -# TODO refactor gas -# method maxChildGasModifier(create: CreateEIP150, gas: GasInt): GasInt = -# maxChildGasEIP150(gas) - -method runLogic*(create: CreateByzantium, computation) = - if computation.msg.isStatic: - raise newException(WriteProtection, "Cannot modify state while inside of a STATICCALL context") - procCall runLogic(create, computation) - -proc selfdestructEIP150(computation) = - let beneficiary = stack.popAddress() - # TODO: with ZZZZ - # with computation.vm_state.state_db(read_only=True) as state_db: - # if not state_db.account_exists(beneficiary): - # computation.gas_meter.consume_gas( - # constants.GAS_SELFDESTRUCT_NEWACCOUNT, - # reason=mnemonics.SELFDESTRUCT, - # ) - # _selfdestruct(computation, beneficiary) - -proc selfdestructEIP161(computation) = - let beneficiary = stack.popAddress() - # TODO: with ZZZZ - # with computation.vm_state.state_db(read_only=True) as state_db: - # is_dead = ( - # not state_db.account_exists(beneficiary) or - # state_db.account_is_empty(beneficiary) - # ) - # if is_dead and state_db.get_balance(computation.msg.storage_address): - # computation.gas_meter.consume_gas( - # constants.GAS_SELFDESTRUCT_NEWACCOUNT, - # reason=mnemonics.SELFDESTRUCT, - # ) - # _selfdestruct(computation, beneficiary) - -proc selfdestruct(computation; beneficiary: EthAddress) = - discard # TODO: with ZZZZ - # with computation.vm_state.state_db() as state_db: - # local_balance = state_db.get_balance(computation.msg.storage_address) - # beneficiary_balance = state_db.get_balance(beneficiary) - - # # 1st: Transfer to beneficiary - # state_db.set_balance(beneficiary, local_balance + beneficiary_balance) - # # 2nd: Zero the balance of the address being deleted (must come after - # # sending to beneficiary in case the contract named itself as the - # # beneficiary. - # state_db.set_balance(computation.msg.storage_address, 0) - - # computation.vm_state.logger.debug( - # "SELFDESTRUCT: %s (%s) -> %s", - # encode_hex(computation.msg.storage_address), - # local_balance, - # encode_hex(beneficiary)) - - # 3rd: Register the account to be deleted - computation.registerAccountForDeletion(beneficiary) - raise newException(Halt, "SELFDESTRUCT") - - -proc returnOp*(computation) = - let (startPosition, size) = stack.popInt(2) - let (pos, len) = (startPosition.toInt, size.toInt) - - computation.gasMeter.consumeGas( - computation.gasCosts[Return].m_handler(computation.memory.len, pos, len), - reason = "RETURN" - ) - - computation.memory.extend(pos, len) - let output = memory.read(pos, len) - computation.output = output.toString - raise newException(Halt, "RETURN") - -proc revert*(computation) = - let (startPosition, size) = stack.popInt(2) - let (pos, len) = (startPosition.toInt, size.toInt) - - computation.gasMeter.consumeGas( - computation.gasCosts[Op.Revert].m_handler(computation.memory.len, pos, len), - reason = "REVERT" - ) - - computation.memory.extend(pos, len) - let output = memory.read(pos, len).toString - computation.output = output - raise newException(Revert, $output) - -proc selfdestruct*(computation) = - let beneficiary = stack.popAddress() - selfdestruct(computation, beneficiary) - raise newException(Halt, "SELFDESTRUCT") - diff --git a/nimbus/vm/utils/macros_gen_opcodes.nim b/nimbus/vm/interpreter/utils/macros_gen_opcodes.nim similarity index 96% rename from nimbus/vm/utils/macros_gen_opcodes.nim rename to nimbus/vm/interpreter/utils/macros_gen_opcodes.nim index f99a98a2f..1a48d2cd4 100644 --- a/nimbus/vm/utils/macros_gen_opcodes.nim +++ b/nimbus/vm/interpreter/utils/macros_gen_opcodes.nim @@ -5,6 +5,9 @@ # * 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. +# ################################################################## +# Macros to facilitate opcode enum and table creation + import macros, strformat, strutils # Due to https://github.com/nim-lang/Nim/issues/8007, we can't diff --git a/nimbus/vm/interpreter/utils/macros_procs_opcodes.nim b/nimbus/vm/interpreter/utils/macros_procs_opcodes.nim new file mode 100644 index 000000000..02cadcec4 --- /dev/null +++ b/nimbus/vm/interpreter/utils/macros_procs_opcodes.nim @@ -0,0 +1,157 @@ +# 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. + +# ################################################################## +# Macros to facilitate opcode procs creation + +import + macros, strformat, stint, + ../../computation, ../../stack, ../../code_stream, + ../../../constants, ../../../vm_types + +proc pop(tree: var NimNode): NimNode = + ## Returns the last value of a NimNode and remove it + result = tree[tree.len-1] + tree.del(tree.len-1) + +macro op*(procname: untyped, inline: static[bool], stackParams_body: varargs[untyped]): untyped = + ## Usage: + ## .. code-block:: nim + ## op add, inline = true, lhs, rhs: + ## push: + ## lhs + rhs + + # TODO: Unfortunately due to varargs[untyped] consuming all following parameters, + # we can't have a nicer macro signature `stackParams: varargs[untyped], body: untyped` + # see https://github.com/nim-lang/Nim/issues/5855 and are forced to "pop" + + let computation = newIdentNode("computation") + var stackParams = stackParams_body + + # 1. Separate stackParams and body with pop + let body = newStmtList().add stackParams.pop + + # 3. let (x, y, z) = computation.stack.popInt(3) + let len = stackParams.len + var popStackStmt = nnkVarTuple.newTree() + + if len != 0: + for params in stackParams: + popStackStmt.add newIdentNode(params.ident) + + popStackStmt.add newEmptyNode() + popStackStmt.add quote do: + `computation`.stack.popInt(`len`) + + popStackStmt = nnkStmtList.newTree( + nnkLetSection.newTree(popStackStmt) + ) + else: + popStackStmt = nnkDiscardStmt.newTree(newEmptyNode()) + + # 4. Generate the proc + # TODO: replace by func to ensure no side effects + if inline: + result = quote do: + proc `procname`*(`computation`: var BaseComputation) {.inline.} = + `popStackStmt` + `body` + else: + result = quote do: + proc `procname`*(`computation`: var BaseComputation) = + `popStackStmt` + `body` + +macro genPush*(): untyped = + # TODO: avoid allocating a seq[byte], transforming to a string, stripping char + func genName(size: int): NimNode = ident(&"push{size}") + result = newStmtList() + + for size in 1 .. 32: + let name = genName(size) + result.add quote do: + func `name`*(computation: var BaseComputation) {.inline.}= + ## Push `size`-byte(s) on the stack + computation.stack.push computation.code.readVmWord(`size`) + +macro genDup*(): untyped = + func genName(position: int): NimNode = ident(&"dup{position}") + result = newStmtList() + + for pos in 1 .. 16: + let name = genName(pos) + result.add quote do: + func `name`*(computation: var BaseComputation) {.inline.}= + computation.stack.dup(`pos`) + +macro genSwap*(): untyped = + func genName(position: int): NimNode = ident(&"swap{position}") + result = newStmtList() + + for pos in 1 .. 16: + let name = genName(pos) + result.add quote do: + func `name`*(computation: var BaseComputation) {.inline.}= + computation.stack.swap(`pos`) + +proc logImpl(topicCount: int): NimNode = + + # TODO: use toopenArray to avoid some string allocations + + if topicCount < 0 or topicCount > 4: + error(&"Invalid log topic len {topicCount} Must be 0, 1, 2, 3, or 4") + return + + let name = ident(&"log{topicCount}") + let computation = ident("computation") + let topics = ident("topics") + let topicsTuple = ident("topicsTuple") + let len = ident("len") + let memPos = ident("memPos") + result = quote: + proc `name`*(`computation`: var BaseComputation) = + let (memStartPosition, size) = `computation`.stack.popInt(2) + let (`memPos`, `len`) = (memStartPosition.toInt, size.toInt) + var `topics`: seq[UInt256] + + var topicCode: NimNode + if topicCount == 0: + topicCode = quote: + `topics` = @[] + elif topicCount > 1: + topicCode = quote: + let `topicsTuple` = `computation`.stack.popInt(`topicCount`) + topicCode = nnkStmtList.newTree(topicCode) + for z in 0 ..< topicCount: + let topicPush = quote: + `topics`.add(`topicsTuple`[`z`]) + topicCode.add(topicPush) + else: + topicCode = quote: + `topics` = @[`computation`.stack.popInt()] + + result.body.add(topicCode) + + let OpName = ident(&"Log{topicCount}") + let logicCode = quote do: + `computation`.gasMeter.consumeGas( + `computation`.gasCosts[`OpName`].m_handler(`computation`.memory.len, `memPos`, `len`), + reason="Memory expansion, Log topic and data gas cost") + `computation`.memory.extend(`memPos`, `len`) + let logData = `computation`.memory.read(`memPos`, `len`).toString + addLogEntry( + `computation`, + account = `computation`.msg.storageAddress, + topics = `topics`, + data = log_data) + + result.body.add(logicCode) + +macro genLog*(): untyped = + result = newStmtList() + for i in 0..4: + result.add logImpl(i) diff --git a/nimbus/vm/utils/utils_numeric.nim b/nimbus/vm/interpreter/utils/utils_numeric.nim similarity index 82% rename from nimbus/vm/utils/utils_numeric.nim rename to nimbus/vm/interpreter/utils/utils_numeric.nim index e01b31f76..a2e615eee 100644 --- a/nimbus/vm/utils/utils_numeric.nim +++ b/nimbus/vm/interpreter/utils/utils_numeric.nim @@ -8,7 +8,7 @@ import strformat, strutils, sequtils, endians, macros, eth_common/eth_types, rlp, - ../../constants, ../../utils/padding + ../../../constants, ../../../utils/padding # some methods based on py-evm utils/numeric @@ -18,15 +18,15 @@ proc bigEndianToInt*(value: openarray[byte]): UInt256 = else: readUintBE[256](padLeft(@value, 32, 0.byte)) -proc log256*(value: UInt256): Natural = - (255 - value.countLeadingZeroBits) div 8 # Compilers optimize to `shr 3` +proc log256*(value: UInt256): Natural {.inline.}= + (255 - value.countLeadingZeroBits) shr 3 # div 8 -proc unsignedToPseudoSigned*(value: UInt256): UInt256 = +proc unsignedToPseudoSigned*(value: UInt256): UInt256 {.inline.}= result = value if value > INT_256_MAX_AS_UINT256: result -= INT_256_MAX_AS_UINT256 -proc pseudoSignedToUnsigned*(value: UInt256): UInt256 = +proc pseudoSignedToUnsigned*(value: UInt256): UInt256 {.inline.}= result = value if value > INT_256_MAX_AS_UINT256: result += INT_256_MAX_AS_UINT256 diff --git a/nimbus/vm/interpreter/vm_forks.nim b/nimbus/vm/interpreter/vm_forks.nim index dd98715dd..72e9cc7d4 100644 --- a/nimbus/vm/interpreter/vm_forks.nim +++ b/nimbus/vm/interpreter/vm_forks.nim @@ -5,22 +5,46 @@ # * 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. -import - eth_common/eth_types, - ../../db/db_chain, ../../constants, - ../../utils/header, - ../base, - ../forks/f20150730_frontier/frontier_vm, - ../forks/f20161018_tangerine_whistle/tangerine_vm +import stint -# Note (mamy): refactoring is in progress (2018-05-23), this is redundant with -# - `Chain` in src/chain.nim, to be honest I don't understand the need of this abstraction at the moment -# - `toFork` in src/constant. This is temporary until more VMs are implemented +type + Fork* = enum + # FkGenesis + FkFrontier, + FkThawing, + FkHomestead, + FkDao, + FkTangerine, + FkSpurious, + FkByzantium -proc newNimbusVM*(header: BlockHeader, chainDB: BaseChainDB): VM = + UInt256Pair = tuple[a: Uint256, b: Uint256] - # TODO: deal with empty BlockHeader - if header.blockNumber < FORK_TANGERINE_WHISTLE_BLKNUM: - result = newFrontierVM(header, chainDB) +let forkBlocks: array[Fork, Uint256] = [ + FkFrontier: 1.u256, # 30/07/2015 19:26:28 + FkThawing: 200_000.u256, # 08/09/2015 01:33:09 + FkHomestead: 1_150_000.u256, # 14/03/2016 20:49:53 + FkDao: 1_920_000.u256, # 20/07/2016 17:20:40 + FkTangerine: 2_463_000.u256, # 18/10/2016 17:19:31 + FkSpurious: 2_675_000.u256, # 22/11/2016 18:15:44 + FkByzantium: 4_370_000.u256 # 16/10/2017 09:22:11 +] + +proc toFork*(blockNumber: UInt256): Fork = + + # TODO: uint256 comparison is probably quite expensive + # hence binary search is probably worth it earlier than + # linear search + + # TODO: all toFork usage currently incurs comparison to get the fork and then another comparison to + # go to the ultimate needed result. + + # Genesis block 0 also uses the Frontier code path + if blockNumber < forkBlocks[FkThawing]: FkFrontier + elif blockNumber < forkBlocks[FkHomestead]: FkThawing + elif blockNumber < forkBlocks[FkDao]: FkHomestead + elif blockNumber < forkBlocks[FkTangerine]: FkDao + elif blockNumber < forkBlocks[FkSpurious]: FkTangerine + elif blockNumber < forkBlocks[FkByzantium]: FkSpurious else: - result = newTangerineVM(header, chainDB) + FkByzantium # Update for constantinople when announced diff --git a/nimbus/vm/interpreter_dispatch.nim b/nimbus/vm/interpreter_dispatch.nim new file mode 100644 index 000000000..03e41d43f --- /dev/null +++ b/nimbus/vm/interpreter_dispatch.nim @@ -0,0 +1,231 @@ +# 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. + +import + tables, macros, + ./interpreter/[opcode_values, opcodes_impl, vm_forks, gas_costs, gas_meter, utils/macros_gen_opcodes], + ./code_stream, + ../vm_types, ../errors, + ../logging, ./stack, ./computation, terminal # Those are only needed for logging + +func invalidInstruction*(computation: var BaseComputation) {.inline.} = + raise newException(ValueError, "Invalid instruction, received an opcode not implemented in the current fork.") + +let FrontierOpDispatch {.compileTime.}: array[Op, NimNode] = block: + fill_enum_table_holes(Op, newIdentNode"invalidInstruction"): + [ + Stop: newIdentNode "toBeReplacedByBreak", + Add: newIdentNode "add", + Mul: newIdentNode "mul", + Sub: newIdentNode "sub", + Div: newIdentNode "divide", + Sdiv: newIdentNode "sdiv", + Mod: newIdentNode "modulo", + Smod: newIdentNode "smod", + Addmod: newIdentNode "addmod", + Mulmod: newIdentNode "mulmod", + Exp: newIdentNode "exp", + SignExtend: newIdentNode "signExtend", + + # 10s: Comparison & Bitwise Logic Operations + Lt: newIdentNode "lt", + Gt: newIdentNode "gt", + Slt: newIdentNode "slt", + Sgt: newIdentNode "sgt", + Eq: newIdentNode "eq", + IsZero: newIdentNode "isZero", + And: newIdentNode "andOp", + Or: newIdentNode "orOp", + Xor: newIdentNode "xorOp", + Not: newIdentNode "notOp", + Byte: newIdentNode "byteOp", + + # 20s: SHA3 + Sha3: newIdentNode "sha3", + + # 30s: Environmental Information + Address: newIdentNode "address", + Balance: newIdentNode "balance", + Origin: newIdentNode "origin", + Caller: newIdentNode "caller", + CallValue: newIdentNode "callValue", + CallDataLoad: newIdentNode "callDataLoad", + CallDataSize: newIdentNode "callDataSize", + CallDataCopy: newIdentNode "callDataCopy", + CodeSize: newIdentNode "codeSize", + CodeCopy: newIdentNode "codeCopy", + GasPrice: newIdentNode "gasPrice", + ExtCodeSize: newIdentNode "extCodeSize", + ExtCodeCopy: newIdentNode "extCodeCopy", + # ReturnDataSize: introduced in Byzantium + # ReturnDataCopy: introduced in Byzantium + + # 40s: Block Information + Blockhash: newIdentNode "blockhash", + Coinbase: newIdentNode "coinbase", + Timestamp: newIdentNode "timestamp", + Number: newIdentNode "blockNumber", + Difficulty: newIdentNode "difficulty", + GasLimit: newIdentNode "gasLimit", + + # 50s: Stack, Memory, Storage and Flow Operations + Pop: newIdentNode "pop", + Mload: newIdentNode "mload", + Mstore: newIdentNode "mstore", + Mstore8: newIdentNode "mstore8", + Sload: newIdentNode "sload", + Sstore: newIdentNode "sstore", + Jump: newIdentNode "jump", + JumpI: newIdentNode "jumpI", + Pc: newIdentNode "pc", + Msize: newIdentNode "msize", + Gas: newIdentNode "gas", + JumpDest: newIdentNode "jumpDest", + + # 60s & 70s: Push Operations. + Push1: newIdentNode "push1", + Push2: newIdentNode "push2", + Push3: newIdentNode "push3", + Push4: newIdentNode "push4", + Push5: newIdentNode "push5", + Push6: newIdentNode "push6", + Push7: newIdentNode "push7", + Push8: newIdentNode "push8", + Push9: newIdentNode "push9", + Push10: newIdentNode "push10", + Push11: newIdentNode "push11", + Push12: newIdentNode "push12", + Push13: newIdentNode "push13", + Push14: newIdentNode "push14", + Push15: newIdentNode "push15", + Push16: newIdentNode "push16", + Push17: newIdentNode "push17", + Push18: newIdentNode "push18", + Push19: newIdentNode "push19", + Push20: newIdentNode "push20", + Push21: newIdentNode "push21", + Push22: newIdentNode "push22", + Push23: newIdentNode "push23", + Push24: newIdentNode "push24", + Push25: newIdentNode "push25", + Push26: newIdentNode "push26", + Push27: newIdentNode "push27", + Push28: newIdentNode "push28", + Push29: newIdentNode "push29", + Push30: newIdentNode "push30", + Push31: newIdentNode "push31", + Push32: newIdentNode "push32", + + # 80s: Duplication Operations + Dup1: newIdentNode "dup1", + Dup2: newIdentNode "dup2", + Dup3: newIdentNode "dup3", + Dup4: newIdentNode "dup4", + Dup5: newIdentNode "dup5", + Dup6: newIdentNode "dup6", + Dup7: newIdentNode "dup7", + Dup8: newIdentNode "dup8", + Dup9: newIdentNode "dup9", + Dup10: newIdentNode "dup10", + Dup11: newIdentNode "dup11", + Dup12: newIdentNode "dup12", + Dup13: newIdentNode "dup13", + Dup14: newIdentNode "dup14", + Dup15: newIdentNode "dup15", + Dup16: newIdentNode "dup16", + + # 90s: Exchange Operations + Swap1: newIdentNode "swap1", + Swap2: newIdentNode "swap2", + Swap3: newIdentNode "swap3", + Swap4: newIdentNode "swap4", + Swap5: newIdentNode "swap5", + Swap6: newIdentNode "swap6", + Swap7: newIdentNode "swap7", + Swap8: newIdentNode "swap8", + Swap9: newIdentNode "swap9", + Swap10: newIdentNode "swap10", + Swap11: newIdentNode "swap11", + Swap12: newIdentNode "swap12", + Swap13: newIdentNode "swap13", + Swap14: newIdentNode "swap14", + Swap15: newIdentNode "swap15", + Swap16: newIdentNode "swap16", + + # a0s: Logging Operations + Log0: newIdentNode "log0", + Log1: newIdentNode "log1", + Log2: newIdentNode "log2", + Log3: newIdentNode "log3", + Log4: newIdentNode "log4", + + # f0s: System operations + Create: newIdentNode "create", + Call: newIdentNode "call", + CallCode: newIdentNode "callCode", + Return: newIdentNode "returnOp", + DelegateCall: newIdentNode "delegateCall", + # StaticCall: introduced in Byzantium + # Revert: introduced in Byzantium + # Invalid: newIdentNode "invalid", + SelfDestruct: newIdentNode "selfDestruct" + ] + +proc opTableToCaseStmt(opTable: array[Op, NimNode], computation: NimNode): NimNode = + + let instr = genSym(nskVar) + result = nnkCaseStmt.newTree(instr) + + # Add a branch for each (opcode, proc) pair + # We dispatch to the next instruction at the end of each branch + for op, opImpl in opTable.pairs: + let branchStmt = block: + if op == Stop: + quote do: break + else: + let asOp = quote do: Op(`op`) # TODO: unfortunately when passing to runtime, ops are transformed into int + if BaseGasCosts[op].kind == GckFixed: + quote do: + `computation`.gasMeter.consumeGas(`computation`.gasCosts[`asOp`].cost, reason = $`asOp`) + `opImpl`(`computation`) + `instr` = `computation`.code.next() + else: + quote do: + `opImpl`(`computation`) + when `asOp` in {Return, Revert, SelfDestruct}: + break + else: + `instr` = `computation`.code.next() + + result.add nnkOfBranch.newTree( + newIdentNode($op), + branchStmt + ) + + # Wrap the case statement in while true + computed goto + result = quote do: + var `instr` = `computation`.code.next() + while true: + {.computedGoto.} + `computation`.logger.log($`computation`.stack & "\n\n", fgGreen) + `result` + +macro genFrontierDispatch(computation: BaseComputation): untyped = + result = opTableToCaseStmt(FrontierOpDispatch, computation) + +proc frontierVM(computation: var BaseComputation) = + genFrontierDispatch(computation) + +proc executeOpcodes*(computation: var BaseComputation) = + let fork = computation.vmState.blockHeader.blockNumber.toFork + try: + case fork + of FkFrontier: computation.frontierVM() + else: + raise newException(ValueError, "not implemented fork: " & $fork) + except VMError: + computation.error = Error(info: getCurrentExceptionMsg()) diff --git a/nimbus/vm/memory.nim b/nimbus/vm/memory.nim index f9e66186e..8f25912f2 100644 --- a/nimbus/vm/memory.nim +++ b/nimbus/vm/memory.nim @@ -9,7 +9,7 @@ import sequtils, eth_common/eth_types, ../constants, ../errors, ../logging, ../validation, ../utils/bytes, - ./utils/utils_numeric + ./interpreter/utils/utils_numeric type Memory* = ref object @@ -39,6 +39,7 @@ proc newMemory*(size: Natural): Memory = result.extend(0, size) proc read*(memory: var Memory, startPos: Natural, size: Natural): seq[byte] = + # TODO: use an openarray[byte] result = memory.bytes[startPos ..< (startPos + size)] proc write*(memory: var Memory, startPos: Natural, value: openarray[byte]) = @@ -57,14 +58,6 @@ proc write*(memory: var Memory, startPos: Natural, value: openarray[byte]) = for z, b in value: memory.bytes[z + startPos] = b -proc writePaddingBytes*(memory: var Memory, - startPos, numberOfBytes: Natural, - paddingValue = 0.byte) = - let endPos = startPos + numberOfBytes - assert endPos < memory.len - for i in startPos ..< endPos: - memory.bytes[i] = paddingValue - template write*(memory: var Memory, startPos: Natural, size: Natural, value: cstring) = memory.write(startPos, value.toBytes) # TODO ~ O(n^3): diff --git a/nimbus/vm/stack.nim b/nimbus/vm/stack.nim index b8342d9da..fd7e6be44 100644 --- a/nimbus/vm/stack.nim +++ b/nimbus/vm/stack.nim @@ -7,7 +7,7 @@ import strformat, strutils, sequtils, macros, rlp, eth_common, nimcrypto, - ../errors, ../validation, ./utils/utils_numeric, ../constants, ../logging, .. / utils / bytes + ../errors, ../validation, ./interpreter/utils/utils_numeric, ../constants, ../logging, .. / utils / bytes type Stack* = ref object of RootObj @@ -24,7 +24,7 @@ proc len*(stack: Stack): int {.inline.} = len(stack.values) proc toStackElement(v: UInt256, elem: var StackElement) {.inline.} = elem = v -proc toStackElement(v: uint | int, elem: var StackElement) {.inline.} = elem = v.u256 +proc toStackElement(v: uint | int | GasInt, elem: var StackElement) {.inline.} = elem = v.u256 proc toStackElement(v: EthAddress, elem: var StackElement) {.inline.} = elem = bigEndianToInt(v) proc toStackElement(v: MDigest, elem: var StackElement) {.inline.} = elem = readUintBE[256](v.data) @@ -34,7 +34,7 @@ proc fromStackElement(elem: StackElement, v: var Hash256) {.inline.} = v.data = proc toStackElement(v: openarray[byte], elem: var StackElement) {.inline.} = # TODO: This needs to go - # validateStackItem(v) + validateStackItem(v) # This is necessary to pass stack tests elem = bigEndianToInt(v) proc pushAux[T](stack: var Stack, value: T) = @@ -42,7 +42,7 @@ proc pushAux[T](stack: var Stack, value: T) = stack.values.setLen(stack.values.len + 1) toStackElement(value, stack.values[^1]) -proc push*(stack: var Stack, value: uint | UInt256 | EthAddress | Hash256) {.inline.} = +proc push*(stack: var Stack, value: uint | int | GasInt | UInt256 | EthAddress | Hash256) {.inline.} = pushAux(stack, value) proc push*(stack: var Stack, value: openarray[byte]) {.inline.} = diff --git a/nimbus/vm_state.nim b/nimbus/vm_state.nim index 8cdc59bb7..d29734578 100644 --- a/nimbus/vm_state.nim +++ b/nimbus/vm_state.nim @@ -37,12 +37,13 @@ proc `$`*(vmState: BaseVMState): string = else: result = &"VMState {vmState.name}:\n header: {vmState.blockHeader}\n chaindb: {vmState.chaindb}" -proc newBaseVMState*: BaseVMState = - new(result) +proc newBaseVMState*(header: BlockHeader, chainDB: BaseChainDB): BaseVMState = + new result result.prevHeaders = @[] result.name = "BaseVM" result.accessLogs = newAccessLogs() - # result.blockHeader = # TODO... + result.blockHeader = header + result.chaindb = chainDB method logger*(vmState: BaseVMState): Logger = logging.getLogger(&"evm.vmState.{vmState.name}") diff --git a/nimbus/vm_types.nim b/nimbus/vm_types.nim index 315eeebc9..c322f4805 100644 --- a/nimbus/vm_types.nim +++ b/nimbus/vm_types.nim @@ -24,7 +24,7 @@ type code*: CodeStream children*: seq[BaseComputation] rawOutput*: string - returnData*: string + returnData*: seq[byte] error*: Error logEntries*: seq[(EthAddress, seq[UInt256], string)] shouldEraseReturnData*: bool @@ -45,7 +45,7 @@ type kind*: Op runLogic*: proc(computation: var BaseComputation) - GasMeter* = ref object + GasMeter* = ref object # TODO: use a normal object logger*: Logger gasRefunded*: GasInt startGas*: GasInt diff --git a/tests/all_tests.nim b/tests/all_tests.nim index 80e2ced06..49c7e66bb 100644 --- a/tests/all_tests.nim +++ b/tests/all_tests.nim @@ -5,7 +5,7 @@ # * 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. -when false: +when true: import ./test_code_stream, ./test_gas_meter, ./test_memory, @@ -13,5 +13,5 @@ when false: ./test_opcode, ./test_storage_backends -when true: +when false: import ./test_vm_json diff --git a/tests/test_code_stream.nim b/tests/test_code_stream.nim index 5270de9e0..a9d30d06c 100644 --- a/tests/test_code_stream.nim +++ b/tests/test_code_stream.nim @@ -44,14 +44,15 @@ suite "parse bytecode": 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) + # Seek has been dommented out for future deletion + # 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") diff --git a/tests/test_helpers.nim b/tests/test_helpers.nim index b21818c29..694ea16c1 100644 --- a/tests/test_helpers.nim +++ b/tests/test_helpers.nim @@ -11,7 +11,7 @@ import ../nimbus/utils/[address, padding], ../nimbus/[vm_state, constants], ../nimbus/db/[db_chain, state_db], - ../nimbus/vm/base, ../nimbus/transaction + ../nimbus/transaction type Status* {.pure.} = enum OK, Fail, Skip @@ -125,21 +125,3 @@ proc verifyStateDB*(wantedState: JsonNode, stateDB: AccountStateDB) = proc getHexadecimalInt*(j: JsonNode): int = discard parseHex(j.getStr, result) - -method newTransaction*( - vm: VM, addr_from, addr_to: EthAddress, - amount: UInt256, - private_key: PrivateKey, - gas_price = 10.u256, - gas = 100000.u256, - data: seq[byte] = @[] -): BaseTransaction = - # TODO: amount should be an Int to deal with negatives - new result - - # Todo getStateDB is incomplete - let nonce = vm.state.readOnlyStateDB.getNonce(addr_from) - - # TODO - # if !private key: create_unsigned_transaction - # else: create_signed_transaction diff --git a/tests/test_memory.nim b/tests/test_memory.nim index 374be9c01..e25637305 100644 --- a/tests/test_memory.nim +++ b/tests/test_memory.nim @@ -22,7 +22,7 @@ suite "memory": test "write": var mem = memory32() # Test that write creates 32byte string == value padded with zeros - mem.write(startPosition = 0, value = @[1.byte, 0.byte, 1.byte, 0.byte]) + mem.write(startPos = 0, value = @[1.byte, 0.byte, 1.byte, 0.byte]) check(mem.bytes == @[1.byte, 0.byte, 1.byte, 0.byte].concat(repeat(0.byte, 28))) # test "write rejects invalid position": @@ -48,23 +48,23 @@ suite "memory": test "write rejects valyes beyond memory size": expect(ValidationError): var mem = memory128() - mem.write(startPosition = 128, value = @[1.byte, 0.byte, 1.byte, 0.byte]) + mem.write(startPos = 128, value = @[1.byte, 0.byte, 1.byte, 0.byte]) test "extends appropriately extends memory": var mem = newMemory() # Test extends to 32 byte array: 0 < (start_position + size) <= 32 - mem.extend(startPosition = 0, size = 10) + mem.extend(startPos = 0, size = 10) check(mem.bytes == repeat(0.byte, 32)) # Test will extend past length if params require: 32 < (start_position + size) <= 64 - mem.extend(startPosition = 28, size = 32) + mem.extend(startPos = 28, size = 32) check(mem.bytes == repeat(0.byte, 64)) # Test won't extend past length unless params require: 32 < (start_position + size) <= 64 - mem.extend(startPosition = 48, size = 10) + mem.extend(startPos = 48, size = 10) check(mem.bytes == repeat(0.byte, 64)) test "read returns correct bytes": var mem = memory32() - mem.write(startPosition = 5, value = @[1.byte, 0.byte, 1.byte, 0.byte]) - check(mem.read(startPosition = 5, size = 4) == @[1.byte, 0.byte, 1.byte, 0.byte]) - check(mem.read(startPosition = 6, size = 4) == @[0.byte, 1.byte, 0.byte, 0.byte]) - check(mem.read(startPosition = 1, size = 3) == @[0.byte, 0.byte, 0.byte]) + mem.write(startPos = 5, value = @[1.byte, 0.byte, 1.byte, 0.byte]) + check(mem.read(startPos = 5, size = 4) == @[1.byte, 0.byte, 1.byte, 0.byte]) + check(mem.read(startPos = 6, size = 4) == @[0.byte, 1.byte, 0.byte, 0.byte]) + check(mem.read(startPos = 1, size = 3) == @[0.byte, 0.byte, 0.byte]) diff --git a/tests/test_opcode.nim b/tests/test_opcode.nim index a54a101de..c1b5f3dd7 100644 --- a/tests/test_opcode.nim +++ b/tests/test_opcode.nim @@ -8,7 +8,7 @@ import unittest, tables, parseutils, eth_trie/[types, memdb], eth_common/eth_types, - ../nimbus/[constants, vm_types, logging], + ../nimbus/[constants, vm_types, logging, vm_state], ../nimbus/vm/interpreter, ../nimbus/utils/header, ../nimbus/db/[db_chain, state_db, backends/memory_backend], @@ -19,12 +19,13 @@ from eth_common import GasInt proc testCode(code: string, initialGas: GasInt, blockNum: UInt256): BaseComputation = let header = BlockHeader(blockNumber: blockNum) var memDb = newMemDB() - var vm = newNimbusVM(header, newBaseChainDB(trieDB memDb)) - # coinbase: "", - # difficulty: fixture{"env"}{"currentDifficulty"}.getHexadecimalInt.u256, - # blockNumber: fixture{"env"}{"currentNumber"}.getHexadecimalInt.u256, - # gasLimit: fixture{"env"}{"currentGasLimit"}.getHexadecimalInt.u256, - # timestamp: fixture{"env"}{"currentTimestamp"}.getHexadecimalInt) + var vmState = newBaseVMState(header, newBaseChainDB(trieDB memDb)) + + # coinbase: "", + # difficulty: fixture{"env"}{"currentDifficulty"}.getHexadecimalInt.u256, + # blockNumber: fixture{"env"}{"currentNumber"}.getHexadecimalInt.u256, + # gasLimit: fixture{"env"}{"currentGasLimit"}.getHexadecimalInt.u256, + # timestamp: fixture{"env"}{"currentTimestamp"}.getHexadecimalInt) let message = newMessage( to=ZERO_ADDRESS, #fixture{"exec"}{"address"}.getStr, @@ -42,12 +43,10 @@ proc testCode(code: string, initialGas: GasInt, blockNum: UInt256): BaseComputat if DEBUG: c.displayDecompiled() - var computation = newBaseComputation(vm.state, message) - computation.opcodes = OpLogic # TODO remove this need - computation.precompiles = initTable[string, Opcode]() + result = newBaseComputation(vmState, blockNum, message) + result.precompiles = initTable[string, Opcode]() - computation = computation.applyComputation(vm.state, message) - result = computation + result.executeOpcodes() suite "opcodes": test "add": @@ -76,37 +75,39 @@ suite "opcodes": # assert_store(&ext, 0, "fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe"); # } - test "Frontier VM computation - pre-EIP150 gas cost properly applied": - block: # Using Balance (0x31) - var c = testCode( - "0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff31", - 100_000, - 0.u256 - ) - check: c.gasMeter.gasRemaining == 100000 - 3 - 20 # Starting gas - push32 (verylow) - balance - block: # Using SLOAD (0x54) - var c = testCode( - "0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff54", - 100_000, - 0.u256 - ) - check: c.gasMeter.gasRemaining == 100000 - 3 - 50 # Starting gas - push32 (verylow) - SLOAD +# TODO balance and sload were previously stubbed. Test must be rewritten to initialize the DB properly + # test "Frontier VM computation - pre-EIP150 gas cost properly applied": + # block: # Using Balance (0x31) + # var c = testCode( + # "0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff31", + # 100_000, + # 0.u256 + # ) + # check: c.gasMeter.gasRemaining == 100000 - 3 - 20 # Starting gas - push32 (verylow) - balance + + # block: # Using SLOAD (0x54) + # var c = testCode( + # "0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff54", + # 100_000, + # 0.u256 + # ) + # check: c.gasMeter.gasRemaining == 100000 - 3 - 50 # Starting gas - push32 (verylow) - SLOAD - test "Tangerine VM computation - post-EIP150 gas cost properly applied": - block: # Using Balance (0x31) - var c = testCode( - "0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff31", - 100_000, - 2_463_000.u256 # Tangerine block - ) - check: c.gasMeter.gasRemaining == 100000 - 3 - 400 # Starting gas - push32 (verylow) - balance + # test "Tangerine VM computation - post-EIP150 gas cost properly applied": + # block: # Using Balance (0x31) + # var c = testCode( + # "0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff31", + # 100_000, + # 2_463_000.u256 # Tangerine block + # ) + # check: c.gasMeter.gasRemaining == 100000 - 3 - 400 # Starting gas - push32 (verylow) - balance - block: # Using SLOAD (0x54) - var c = testCode( - "0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff54", - 100_000, - 2_463_000.u256 - ) - check: c.gasMeter.gasRemaining == 100000 - 3 - 200 # Starting gas - push32 (verylow) - SLOAD + # block: # Using SLOAD (0x54) + # var c = testCode( + # "0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff54", + # 100_000, + # 2_463_000.u256 + # ) + # check: c.gasMeter.gasRemaining == 100000 - 3 - 200 # Starting gas - push32 (verylow) - SLOAD diff --git a/tests/test_vm_json.nim b/tests/test_vm_json.nim index e0676f61b..f30483bd1 100644 --- a/tests/test_vm_json.nim +++ b/tests/test_vm_json.nim @@ -32,6 +32,7 @@ proc testFixture(fixtures: JsonNode, testStatusIMPL: var TestStatus) = for label, child in fixtures: fixture = child break + let fenv = fixture["env"] var emptyRlpHash = keccak256.digest(rlp.encode("").toOpenArray) let header = BlockHeader( @@ -42,12 +43,12 @@ proc testFixture(fixtures: JsonNode, testStatusIMPL: var TestStatus) = timestamp: fenv{"currentTimestamp"}.getHexadecimalInt.int64.fromUnix, stateRoot: emptyRlpHash ) - var memDb = newMemDB() - var vm = newNimbusVM(header, newBaseChainDB(trieDB memDb)) + var memDb = newMemDB() + var vmState = newBaseVMState(header, newBaseChainDB(trieDB memDb)) let fexec = fixture["exec"] var code = "" - vm.state.mutateStateDB: + vmState.mutateStateDB: setupStateDB(fixture{"pre"}, db) let address = fexec{"address"}.getStr.parseAddress code = stringFromBytes db.getCode(address) @@ -70,11 +71,11 @@ proc testFixture(fixtures: JsonNode, testStatusIMPL: var TestStatus) = if DEBUG: c.displayDecompiled() - var computation = newBaseComputation(vm.state, message) - computation.opcodes = OpLogic + var computation = newBaseComputation(vmState, header.blockNumber, message) + computation.vmState = vmState computation.precompiles = initTable[string, Opcode]() - computation = computation.applyComputation(vm.state, message) + computation.executeOpcodes() if not fixture{"post"}.isNil: # Success checks @@ -130,4 +131,4 @@ proc testFixture(fixtures: JsonNode, testStatusIMPL: var TestStatus) = # Error checks check(computation.isError) # TODO postState = fixture{"pre"} - +