diff --git a/VMTests.md b/VMTests.md index 6ad20d4b0..6f268a1bb 100644 --- a/VMTests.md +++ b/VMTests.md @@ -116,7 +116,7 @@ VMTests + expPowerOf2_64.json OK + expPowerOf2_8.json OK - expXY.json Fail -- expXY_success.json Fail ++ expXY_success.json OK + fibbonacci_unrolled.json OK + mod0.json OK + mod1.json OK @@ -198,7 +198,7 @@ VMTests + sub3.json OK + sub4.json OK ``` -OK: 188/195 Fail: 6/195 Skip: 1/195 +OK: 189/195 Fail: 5/195 Skip: 1/195 ## vmBitwiseLogicOperation ```diff + and0.json OK diff --git a/src/chain.nim b/src/chain.nim index b246eb8c5..23a6052a8 100644 --- a/src/chain.nim +++ b/src/chain.nim @@ -8,7 +8,7 @@ import tables, stint, ./logging, ./constants, ./errors, ./validation, ./utils/hexadecimal, ./vm/base, ./db/db_chain, - ./utils/header, ./vm/forks/frontier/vm + ./utils/header, ./vm/forks/f20150730_frontier/frontier_vm type Chain* = ref object @@ -74,8 +74,11 @@ proc fromGenesis*( # TODO # chainDB.persistBlockToDB(result.getBlock) + proc getVMClassForBlockNumber*(chain: Chain, blockNumber: UInt256): VMKind = ## Returns the VM class for the given block number + # TODO - Refactoring: superseded by newNimbusVM for the time being #https://github.com/status-im/nimbus/pull/37 + # TODO - Refactoring: redundant with constants.nim `toFork` # TODO should the return value be a typedesc? # TODO: validate_block_number @@ -86,9 +89,12 @@ proc getVMClassForBlockNumber*(chain: Chain, blockNumber: UInt256): VMKind = raise newException(ValueError, "VM not found for block #" & $blockNumber) # TODO: VMNotFound exception +# TODO - Refactoring: superseded by newNimbusVM for the time being #https://github.com/status-im/nimbus/pull/37 + proc getVM*(chain: Chain, header: BlockHeader = nil): VM = ## Returns the VM instance for the given block number - + # TODO - Refactoring: superseded by newNimbusVM for the time being #https://github.com/status-im/nimbus/pull/37 + # TODO - Refactoring: redundant with constants.nim `toFork` # shadowing input param let header = if header.isNil: chain.header else: header diff --git a/src/computation.nim b/src/computation.nim index d358fc449..03c22dc6e 100644 --- a/src/computation.nim +++ b/src/computation.nim @@ -8,7 +8,12 @@ import strformat, strutils, sequtils, tables, macros, stint, terminal, constants, errors, utils/hexadecimal, utils_numeric, validation, vm_state, logging, opcode_values, types, - vm / [code_stream, gas_meter, memory, message, stack] + vm / [code_stream, gas_meter, memory, message, stack], + + # TODO further refactoring of gas cost + vm/forks/gas_costs, + vm/forks/f20150730_frontier/frontier_vm_state, + vm/forks/f20161018_tangerine_whistle/tangerine_vm_state proc memoryGasCost*(sizeInBytes: UInt256): UInt256 = let @@ -20,7 +25,11 @@ proc memoryGasCost*(sizeInBytes: UInt256): UInt256 = #const VARIABLE_GAS_COST_OPS* = {Op.Exp} -proc newBaseComputation*(vmState: BaseVMState, message: Message): BaseComputation = +method newBaseComputation*(vmState: BaseVMState, message: Message): BaseComputation {.base.}= + raise newException(ValueError, "Must be implemented by subclasses") + +# TODO refactor that +method newBaseComputation*(vmState: FrontierVMState, message: Message): BaseComputation = new(result) result.vmState = vmState result.msg = message @@ -32,6 +41,21 @@ proc newBaseComputation*(vmState: BaseVMState, message: Message): BaseComputatio result.logEntries = @[] result.code = newCodeStreamFromUnescaped(message.code) # TODO: what is the best repr result.rawOutput = "0x" + result.gasCosts = BaseGasCosts + +method newBaseComputation*(vmState: TangerineVMState, message: Message): BaseComputation = + new(result) + result.vmState = vmState + result.msg = message + result.memory = Memory() + result.stack = newStack() + result.gasMeter = newGasMeter(message.gas) + result.children = @[] + result.accountsToDelete = initTable[string, string]() + result.logEntries = @[] + result.code = newCodeStreamFromUnescaped(message.code) # TODO: what is the best repr + result.rawOutput = "0x" + result.gasCosts = TangerineGasCosts method logger*(computation: BaseComputation): Logger = logging.getLogger("vm.computation.BaseComputation") diff --git a/src/constants.nim b/src/constants.nim index b950037bb..ca77c6677 100644 --- a/src/constants.nim +++ b/src/constants.nim @@ -129,38 +129,38 @@ let ZERO_HASH32* = repeat("\x00", 20) STACK_DEPTH_LIMIT* = 1024 - GAS_NULL* = 0.u256 - GAS_ZERO* = 0.u256 - GAS_BASE* = 2.u256 - GAS_VERY_LOW* = 3.u256 - GAS_LOW* = 5.u256 - GAS_MID* = 8.u256 - GAS_HIGH* = 10.u256 - GAS_EXT_CODE* = 20.u256 - GAS_BALANCE* = 20.u256 - GAS_SLOAD* = 200.u256 # TODO: pre eip150 - GAS_JUMP_DEST* = 1.u256 - GAS_SSET* = 20_000.u256 - GAS_SRESET* = 5_000.u256 - GAS_EXT_CODE_COST* = 700.u256 - GAS_COINBASE* = 20.u256 - GAS_SLOAD_COST* = 20.u256 - GAS_SELF_DESTRUCT_COST* = 0.u256 - GAS_IN_HANDLER* = 0.u256 # to be calculated in handler + # GAS_NULL* = 0.u256 + # GAS_ZERO* = 0.u256 + # GAS_BASE* = 2.u256 + # GAS_VERY_LOW* = 3.u256 + # GAS_LOW* = 5.u256 + # GAS_MID* = 8.u256 + # GAS_HIGH* = 10.u256 + # GAS_EXT_CODE* = 20.u256 # TODO: this is pre-eip150, see also GAS_EXT_CODE_COST + # GAS_BALANCE* = 20.u256 # TODO: this is pre-eip150, see also GAS_COST_BALANCE + # GAS_SLOAD* = 200.u256 # TODO: pre eip150 + # GAS_JUMP_DEST* = 1.u256 + # GAS_SSET* = 20_000.u256 + # GAS_SRESET* = 5_000.u256 + # GAS_EXT_CODE_COST* = 700.u256 + # GAS_COINBASE* = 20.u256 + # GAS_SLOAD_COST* = 20.u256 + # GAS_SELF_DESTRUCT_COST* = 0.u256 + # GAS_IN_HANDLER* = 0.u256 # to be calculated in handler REFUND_SCLEAR* = 15_000.u256 - GAS_SELF_DESTRUCT* = 0.u256 - GAS_SELF_DESTRUCT_NEW_ACCOUNT* = 25_000.u256 + # GAS_SELF_DESTRUCT* = 0.u256 + # GAS_SELF_DESTRUCT_NEW_ACCOUNT* = 25_000.u256 GAS_CREATE* = 32_000.u256 - GAS_CALL* = 40.u256 + # GAS_CALL* = 40.u256 GAS_CALL_VALUE* = 9_000.u256 GAS_CALL_STIPEND* = 2_300.u256 GAS_NEW_ACCOUNT* = 25_000.u256 - GAS_COST_BALANCE* = 400.u256 + # GAS_COST_BALANCE* = 400.u256 # TODO: this is post-eip150, see also GAS_BALANCE - GAS_EXP* = 10.u256 - GAS_EXP_BYTE* = 10.u256 + # GAS_EXP* = 10.u256 + # GAS_EXP_BYTE* = 10.u256 GAS_MEMORY* = 3.u256 GAS_TX_CREATE* = 32_000.u256 GAS_TX_DATA_ZERO* = 4.u256 @@ -169,7 +169,7 @@ let GAS_LOG* = 375.u256 GAS_LOG_DATA* = 8.u256 GAS_LOG_TOPIC* = 375.u256 - GAS_SHA3* = 30.u256 + # GAS_SHA3* = 30.u256 GAS_SHA3_WORD* = 6.u256 GAS_COPY* = 3.u256 GAS_BLOCK_HASH* = 20.u256 @@ -252,6 +252,8 @@ 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 diff --git a/src/logic/arithmetic.nim b/src/logic/arithmetic.nim index 0e4fe7dab..50fd42c2d 100644 --- a/src/logic/arithmetic.nim +++ b/src/logic/arithmetic.nim @@ -90,9 +90,9 @@ proc exp*(computation: var BaseComputation) = # Exponentiation let (base, exponent) = computation.stack.popInt(2) - var gasCost = GAS_EXP_BYTE.u256 + var gasCost = computation.gasCosts[GasExp] if not exponent.isZero: - gasCost += GAS_EXP_BYTE * (one(Uint256) + log256(exponent)) + gasCost += gasCost * (one(Uint256) + log256(exponent)) computation.gasMeter.consumeGas(gasCost, reason="EXP: exponent bytes") let res = if base.isZero: 0.u256 # 0^0 is 0 in py-evm diff --git a/src/logic/call.nim b/src/logic/call.nim index 8a8d29c31..e1d22bcee 100644 --- a/src/logic/call.nim +++ b/src/logic/call.nim @@ -13,6 +13,8 @@ import stint type + # TODO most of these are for gas handling + BaseCall* = ref object of Opcode Call* = ref object of BaseCall @@ -27,12 +29,12 @@ type DelegateCallEIP150* = ref object of DelegateCall - CallEIP161* = ref object of CallEIP150 + CallEIP161* = ref object of CallEIP150 # TODO: Refactoring - put that in VM forks # Byzantium - StaticCall* = ref object of CallEIP161 + StaticCall* = ref object of CallEIP161 # TODO: Refactoring - put that in VM forks - CallByzantium* = ref object of CallEIP161 + CallByzantium* = ref object of CallEIP161 # TODO: Refactoring - put that in VM forks using computation: var BaseComputation @@ -50,7 +52,7 @@ method callParams*(call: BaseCall, computation): (UInt256, UInt256, string, stri raise newException(NotImplementedError, "Must be implemented subclasses") method runLogic*(call: BaseCall, computation) = - computation.gasMeter.consumeGas(call.gasCost(computation), reason = $call.kind) + computation.gasMeter.consumeGas(computation.gasCosts[call.gasCost(computation)], reason = $call.kind) # TODO: Refactoring call gas costs let (gas, value, to, sender, codeAddress, memoryInputStartPosition, memoryInputSize, diff --git a/src/logic/storage.nim b/src/logic/storage.nim index c64b489fc..16ec21a7f 100644 --- a/src/logic/storage.nim +++ b/src/logic/storage.nim @@ -32,7 +32,7 @@ proc sstore*(computation) = let gasRefund = if isCurrentlyEmpty or not isGoingToBeEmpty: 0.u256 else: REFUND_SCLEAR let gasCost = if isCurrentlyEmpty and not isGoingToBeEmpty: GAS_SSET else: GAS_SRESET - computation.gasMeter.consumeGas(gasCost, &"SSTORE: {computation.msg.storageAddress}[slot] -> {value} ({currentValue})") + computation.gasMeter.consumeGas(computation.gasCosts[gasCost], &"SSTORE: {computation.msg.storageAddress}[slot] -> {value} ({currentValue})") if gasRefund > 0: computation.gasMeter.refundGas(gasRefund) diff --git a/src/logic/system_ops.nim b/src/logic/system_ops.nim index 4d9f1b1b0..80a8aaf5f 100644 --- a/src/logic/system_ops.nim +++ b/src/logic/system_ops.nim @@ -20,15 +20,15 @@ using type Create* = ref object of Opcode - CreateEIP150* = ref object of Create + CreateEIP150* = ref object of Create # TODO: Refactoring - put that in VM forks - CreateByzantium* = ref object of CreateEIP150 + CreateByzantium* = ref object of CreateEIP150 # TODO: Refactoring - put that in VM forks method maxChildGasModifier(create: Create, gas: UInt256): UInt256 {.base.} = gas method runLogic*(create: Create, computation) = - computation.gasMeter.consumeGas(create.gasCost(computation), reason = $create.kind) + computation.gasMeter.consumeGas(computation.gasCosts[create.gasCost(computation)], reason = $create.kind) # TODO: Refactoring create gas costs let (value, startPosition, size) = computation.stack.popInt(3) computation.extendMemory(startPosition, size) diff --git a/src/opcode.nim b/src/opcode.nim index 0a6702578..1a59f7df1 100644 --- a/src/opcode.nim +++ b/src/opcode.nim @@ -7,35 +7,36 @@ import strformat, strutils, sequtils, macros, - constants, logging, errors, types, opcode_values, computation, vm/stack, stint + constants, logging, errors, types, opcode_values, computation, vm/stack, stint, + ./types template run*(opcode: Opcode, computation: var BaseComputation) = # Hook for performing the actual VM execution # opcode.consumeGas(computation) - computation.gasMeter.consumeGas(opcode.gasCost(computation), reason = $opcode.kind) + computation.gasMeter.consumeGas(computation.gasCosts[opcode.gasCost(computation)], reason = $opcode.kind) # TODO: further refactoring of gas costs opcode.runLogic(computation) method logger*(opcode: Opcode): Logger = logging.getLogger(&"vm.opcode.{opcode.kind}") -method gasCost*(opcode: Opcode, computation: var BaseComputation): UInt256 = +method gasCost*(opcode: Opcode, computation: var BaseComputation): GasCostKind = #if opcode.kind in VARIABLE_GAS_COST_OPS: # opcode.gasCostHandler(computation) #else: - opcode.gasCostConstant + opcode.gasCostKind template newOpcode*(kind: Op, gasCost: UInt256, logic: proc(computation: var BaseComputation)): Opcode = - Opcode(kind: kind, gasCostConstant: gasCost, runLogic: logic) + Opcode(kind: kind, gasCostKind: gasCost, runLogic: logic) template newOpcode*(kind: Op, gasHandler: proc(computation: var BaseComputation): UInt256, logic: proc(computation: var BaseComputation)): Opcode = Opcode(kind: kind, gasCostHandler: gasHandler, runLogic: logic) method `$`*(opcode: Opcode): string = - let gasCost = $opcode.gasCostConstant + let gasCost = $opcode.gasCostKind # if opcode.kind in VARIABLE_GAS_COST_OPS: # "variable" # else: - # $opcode.gasCostConstant + # $opcode.gasCostKind &"{opcode.kind}(0x{opcode.kind.int.toHex(2)}: {gasCost})" macro initOpcodes*(spec: untyped): untyped = @@ -68,7 +69,7 @@ macro initOpcodes*(spec: untyped): untyped = `value`[`op`] = Opcode(kind: `op`, gasCostHandler: `gasCost`, runLogic: `handler`) else: quote: - `value`[`op`] = Opcode(kind: `op`, gasCostConstant: `gasCost`, runLogic: `handler`) + `value`[`op`] = Opcode(kind: `op`, gasCostKind: `gasCost`, runLogic: `handler`) result[1].add(opcode) result[1].add(value) diff --git a/src/opcode_table.nim b/src/opcode_table.nim index f62f76515..e421d4c6c 100644 --- a/src/opcode_table.nim +++ b/src/opcode_table.nim @@ -9,103 +9,104 @@ import strformat, strutils, tables, macros, constants, stint, errors, logging, vm_state, vm / [gas_meter, stack, code_stream, memory, message, value], db / db_chain, computation, opcode, opcode_values, utils / [header, address], - logic / [arithmetic, comparison, sha3, context, block_ops, stack_ops, duplication, swap, memory_ops, storage, flow, logging_ops, invalid, call, system_ops] + logic / [arithmetic, comparison, sha3, context, block_ops, stack_ops, duplication, swap, memory_ops, storage, flow, logging_ops, invalid, call, system_ops], + ./types var OPCODE_TABLE* = initOpcodes: # arithmetic - Op.Add: GAS_VERY_LOW add - Op.Mul: GAS_LOW mul - Op.Sub: GAS_VERY_LOW sub - Op.Div: GAS_LOW divide - Op.SDiv: GAS_LOW sdiv - Op.Mod: GAS_LOW modulo - Op.SMod: GAS_LOW smod - Op.AddMod: GAS_MID addmod - Op.MulMod: GAS_MID mulmod - Op.Exp: GAS_ZERO arithmetic.exp - Op.SignExtend: GAS_LOW signextend + Op.Add: GasVeryLow add + Op.Mul: GasLow mul + Op.Sub: GasVeryLow sub + Op.Div: GasLow divide + Op.SDiv: GasLow sdiv + Op.Mod: GasLow modulo + Op.SMod: GasLow smod + Op.AddMod: GasMid addmod + Op.MulMod: GasMid mulmod + Op.Exp: GasInHandler arithmetic.exp + Op.SignExtend: GasLow signextend # comparison - Op.Lt: GAS_VERY_LOW lt - Op.Gt: GAS_VERY_LOW gt - Op.SLt: GAS_VERY_LOW slt - Op.SGt: GAS_VERY_LOW sgt - Op.Eq: GAS_VERY_LOW eq - Op.IsZero: GAS_VERY_LOW iszero - Op.And: GAS_VERY_LOW andOp - Op.Or: GAS_VERY_LOW orOp - Op.Xor: GAS_VERY_LOW xorOp - Op.Not: GAS_VERY_LOW notOp - Op.Byte: GAS_VERY_LOW byteOp + Op.Lt: GasVeryLow lt + Op.Gt: GasVeryLow gt + Op.SLt: GasVeryLow slt + Op.SGt: GasVeryLow sgt + Op.Eq: GasVeryLow eq + Op.IsZero: GasVeryLow iszero + Op.And: GasVeryLow andOp + Op.Or: GasVeryLow orOp + Op.Xor: GasVeryLow xorOp + Op.Not: GasVeryLow notOp + Op.Byte: GasVeryLow byteOp # sha3 - Op.SHA3: GAS_SHA3 sha3op + Op.SHA3: GasSHA3 sha3op # context - Op.Address: GAS_BASE context.address - Op.Balance: GAS_COST_BALANCE balance - Op.Origin: GAS_BASE origin - Op.Caller: GAS_BASE caller - Op.CallValue: GAS_BASE callValue - Op.CallDataLoad: GAS_VERY_LOW callDataLoad - Op.CallDataSize: GAS_BASE callDataSize - Op.CallDataCopy: GAS_BASE callDataCopy - Op.CodeSize: GAS_BASE codesize - Op.CodeCopy: GAS_BASE codecopy - Op.ExtCodeSize: GAS_EXT_CODE_COST extCodeSize - Op.ExtCodeCopy: GAS_EXT_CODE_COST extCodeCopy + Op.Address: GasBase context.address + Op.Balance: GasBalance balance + Op.Origin: GasBase origin + Op.Caller: GasBase caller + Op.CallValue: GasBase callValue + Op.CallDataLoad: GasVeryLow callDataLoad + Op.CallDataSize: GasBase callDataSize + Op.CallDataCopy: GasBase callDataCopy + Op.CodeSize: GasBase codesize + Op.CodeCopy: GasBase codecopy + Op.ExtCodeSize: GasExtCode extCodeSize + Op.ExtCodeCopy: GasExtCode extCodeCopy # block - Op.Blockhash: GAS_BASE block_ops.blockhash - Op.Coinbase: GAS_COINBASE coinbase - Op.Timestamp: GAS_BASE timestamp - Op.Number: GAS_BASE number - Op.Difficulty: GAS_BASE difficulty - Op.GasLimit: GAS_BASE gaslimit + Op.Blockhash: GasBase block_ops.blockhash + Op.Coinbase: GasCoinbase coinbase + Op.Timestamp: GasBase timestamp + Op.Number: GasBase number + Op.Difficulty: GasBase difficulty + Op.GasLimit: GasBase gaslimit # stack - Op.Pop: GAS_BASE stack_ops.pop - 1..32 Op.PushXX: GAS_VERY_LOW pushXX # XX replaced by macro - 1..16 Op.DupXX: GAS_VERY_LOW dupXX - 1..16 Op.SwapXX: GAS_VERY_LOW swapXX + Op.Pop: GasBase stack_ops.pop + 1..32 Op.PushXX: GasVeryLow pushXX # XX replaced by macro + 1..16 Op.DupXX: GasVeryLow dupXX + 1..16 Op.SwapXX: GasVeryLow swapXX # memory - Op.MLoad: GAS_VERY_LOW mload - Op.MStore: GAS_VERY_LOW mstore - Op.MStore8: GAS_VERY_LOW mstore8 - Op.MSize: GAS_BASE msize + Op.MLoad: GasVeryLow mload + Op.MStore: GasVeryLow mstore + Op.MStore8: GasVeryLow mstore8 + Op.MSize: GasBase msize # storage - Op.SLoad: GAS_SLOAD sload - Op.SStore: GAS_ZERO sstore + Op.SLoad: GasSload sload + Op.SStore: GasInHandler sstore # flow - Op.Jump: GAS_MID jump - Op.JumpI: GAS_MID jumpi - Op.PC: GAS_HIGH pc - Op.Gas: GAS_BASE flow.gas - Op.JumpDest: GAS_JUMP_DEST jumpdest - Op.Stop: GAS_ZERO stop + Op.Jump: GasMid jump + Op.JumpI: GasMid jumpi + Op.PC: GasHigh pc + Op.Gas: GasBase flow.gas + Op.JumpDest: GasJumpDest jumpdest + Op.Stop: GasZero stop # logging - 0..4 Op.LogXX: GAS_IN_HANDLER logXX + 0..4 Op.LogXX: GasInHandler logXX # invalid - Op.Invalid: GAS_ZERO invalidOp + Op.Invalid: GasZero invalidOp # system - Op.Return: 0.u256 returnOp - Op.SelfDestruct: GAS_SELF_DESTRUCT_COST selfdestruct + Op.Return: GasZero returnOp + Op.SelfDestruct: GasSelfDestruct selfdestruct # call diff --git a/src/types.nim b/src/types.nim index 1124dd686..04856d03b 100644 --- a/src/types.nim +++ b/src/types.nim @@ -29,6 +29,7 @@ type accountsToDelete*: Table[string, string] opcodes*: Table[Op, Opcode] # TODO array[Op, Opcode] precompiles*: Table[string, Opcode] + gasCosts*: GasCosts # TODO separate opcode processing and gas computation Error* = ref object info*: string @@ -45,5 +46,29 @@ type ## it uses the peek methods of the stack and calculates the cost ## then it actually pops/pushes stuff in exec ## I followed the py-evm approach which does that in opcode logic - gasCostConstant*: UInt256 + gasCostKind*: GasCostKind runLogic*: proc(computation: var BaseComputation) + + GasCostKind* = enum + GasZero + GasBase + GasVeryLow + GasLow + GasMid + GasHigh + GasSload + GasJumpDest + GasSset + GasSreset + GasExtCode + GasCoinbase + GasSelfDestruct + GasInHandler + GasRefundSclear + + GasBalance + GasCall + GasExp + GasSHA3 + + GasCosts* = array[GasCostKind, UInt256] diff --git a/src/vm/base.nim b/src/vm/base.nim index 8a5fb11a1..8b250ba44 100644 --- a/src/vm/base.nim +++ b/src/vm/base.nim @@ -25,9 +25,11 @@ type state*: BaseVMState `block`*: Block -proc newVM*(header: BlockHeader, chainDB: BaseChainDB): VM = - new(result) - result.chainDB = chainDB +# 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" diff --git a/src/vm/forks/frontier/frontier_block.nim b/src/vm/forks/f20150730_frontier/frontier_block.nim similarity index 100% rename from src/vm/forks/frontier/frontier_block.nim rename to src/vm/forks/f20150730_frontier/frontier_block.nim diff --git a/src/vm/forks/frontier/frontier_validation.nim b/src/vm/forks/f20150730_frontier/frontier_validation.nim similarity index 100% rename from src/vm/forks/frontier/frontier_validation.nim rename to src/vm/forks/f20150730_frontier/frontier_validation.nim diff --git a/src/vm/forks/frontier/vm.nim b/src/vm/forks/f20150730_frontier/frontier_vm.nim similarity index 100% rename from src/vm/forks/frontier/vm.nim rename to src/vm/forks/f20150730_frontier/frontier_vm.nim diff --git a/src/vm/forks/frontier/frontier_vm_state.nim b/src/vm/forks/f20150730_frontier/frontier_vm_state.nim similarity index 100% rename from src/vm/forks/frontier/frontier_vm_state.nim rename to src/vm/forks/f20150730_frontier/frontier_vm_state.nim diff --git a/src/vm/forks/f20161018_tangerine_whistle/tangerine_block.nim b/src/vm/forks/f20161018_tangerine_whistle/tangerine_block.nim new file mode 100644 index 000000000..c103b36e3 --- /dev/null +++ b/src/vm/forks/f20161018_tangerine_whistle/tangerine_block.nim @@ -0,0 +1,21 @@ +# 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/src/vm/forks/f20161018_tangerine_whistle/tangerine_validation.nim b/src/vm/forks/f20161018_tangerine_whistle/tangerine_validation.nim new file mode 100644 index 000000000..88a446b56 --- /dev/null +++ b/src/vm/forks/f20161018_tangerine_whistle/tangerine_validation.nim @@ -0,0 +1,31 @@ +# 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, stint, + ../../../constants, ../../../errors, ../../../vm_state, ../../../transaction, ../../../utils/header + +proc validateTangerineTransaction*(vmState: BaseVmState, transaction: BaseTransaction) = + let gasCost = 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/src/vm/forks/f20161018_tangerine_whistle/tangerine_vm.nim b/src/vm/forks/f20161018_tangerine_whistle/tangerine_vm.nim new file mode 100644 index 000000000..4f1fadfe9 --- /dev/null +++ b/src/vm/forks/f20161018_tangerine_whistle/tangerine_vm.nim @@ -0,0 +1,37 @@ +# 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, + stint, + ../../../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.`block` = makeTangerineBlock(header, @[]) diff --git a/src/vm/forks/f20161018_tangerine_whistle/tangerine_vm_state.nim b/src/vm/forks/f20161018_tangerine_whistle/tangerine_vm_state.nim new file mode 100644 index 000000000..3a9686d03 --- /dev/null +++ b/src/vm/forks/f20161018_tangerine_whistle/tangerine_vm_state.nim @@ -0,0 +1,23 @@ +# 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 = BlockHeader(hash: "TODO", coinbase: "TODO", stateRoot: "TODO") diff --git a/src/vm/forks/forks_list.md b/src/vm/forks/forks_list.md new file mode 100644 index 000000000..418d091bd --- /dev/null +++ b/src/vm/forks/forks_list.md @@ -0,0 +1,17 @@ +# List of Ethereum hard forks with VM impact. + +From https://www.etherchain.org/hardForks + + +| Name | On Roadmap | Date | Block | +| --------------------------- | ------------ | ------------------- | ------- | +| Frontier | Yes | 30/07/2015 19:26:28 | 1 | +| Frontier Thawing | Yes | 08/09/2015 01:33:09 | 200000 | +| Homestead | Yes | 14/03/2016 20:49:53 | 1150000 | +| DAO Fork | No | 20/07/2016 17:20:40 | 1920000 | +| Tangerine Whistle (EIP-150) | No | 18/10/2016 17:19:31 | 2463000 | +| Spurious Dragon | No | 22/11/2016 18:15:44 | 2675000 | +| Byzantium | Yes | 16/10/2017 09:22:11 | 4370000 | + +From https://ethereum.stackexchange.com/a/28409 +![](forks_list.png) diff --git a/src/vm/forks/forks_list.png b/src/vm/forks/forks_list.png new file mode 100644 index 000000000..657f0875a Binary files /dev/null and b/src/vm/forks/forks_list.png differ diff --git a/src/vm/forks/frontier/frontier_headers.nim b/src/vm/forks/frontier/frontier_headers.nim deleted file mode 100644 index 0b4b5d061..000000000 --- a/src/vm/forks/frontier/frontier_headers.nim +++ /dev/null @@ -1,28 +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, validation, utils/header, vm / forks / frontier / vm - -method computeDifficulty*(parentHeader: BlockHeader, timestamp: int): Int256 = - validateGt(timestamp, parentHeader.timestamp, title="BlockHeader timestamp") - let offset = parentHeader.difficulty div DIFFICULTY_ADJUSTMENT_DENOMINATOR - # We set the minimum to the lowest of the protocol minimum and the parent - # minimum to allow for the initial frontier *warming* period during which - # the difficulty begins lower than the protocol minimum - let difficultyMinimum = min(parentHeader.difficulty, DIFFICULTY_MINIMUM) - # let test = (timestamp - parentHeader.timestamp).Int256 < FRONTIER_DIFFICULTY_ADJUSTMENT_CUTOFF - # let baseDifficulty = max(parent.BlockHeader.difficulty + (if test: offset else: -offset), difficultyMinimum) - # # Adjust for difficulty bomb - # let numBombPeriods = ((parentHeader.blockNumber + 1) div BOMB_EXPONENTIAL_PERIOD) - BOMB_EXPONENTIAL_FREE_PERIODS - # result = if numBombPeriods >= 0: max(baseDifficulty + 2.Int256 ^ numBombPeriods, DIFFICULTY_MINIMUM) else: baseDifficulty - result = 0.Int256 - -method createHeaderFromParent*(parentHeader: BlockHeader): BlockHeader = - # TODO - result = BlockHeader() - diff --git a/src/vm/forks/frontier/nim.cfg b/src/vm/forks/frontier/nim.cfg deleted file mode 100644 index ebbc1d958..000000000 --- a/src/vm/forks/frontier/nim.cfg +++ /dev/null @@ -1 +0,0 @@ -p:"../../../" diff --git a/src/vm/forks/gas_costs.nim b/src/vm/forks/gas_costs.nim new file mode 100644 index 000000000..53fe7e883 --- /dev/null +++ b/src/vm/forks/gas_costs.nim @@ -0,0 +1,45 @@ +# 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 + stint, ../../types + +# TODO: Make that computation at compile-time. +# Go-Ethereum uses pure uint64 for gas computation +let BaseGasCosts*: GasCosts = [ + GasZero: 0.u256, + GasBase: 2.u256, + GasVeryLow: 3.u256, + GasLow: 5.u256, + GasMid: 8.u256, + GasHigh: 10.u256, + GasSload: 50.u256, # Changed to 200 in Tangerine (EIP150) + GasJumpDest: 1.u256, + GasSset: 20_000.u256, + GasSreset: 5_000.u256, + GasExtCode: 20.u256, + GasCoinbase: 20.u256, + GasSelfDestruct: 0.u256, # Changed to 5000 in Tangerine (EIP150) + GasInHandler: 0.u256, # to be calculated in handler + GasRefundSclear: 15000.u256, + + GasBalance: 20.u256, # Changed to 400 in Tangerine (EIP150) + GasCall: 40.u256, # Changed to 700 in Tangerine (EIP150) + GasExp: 10.u256, + GasSHA3: 30.u256 +] + +proc tangerineGasCosts(baseCosts: GasCosts): GasCosts = + + # https://github.com/ethereum/EIPs/blob/master/EIPS/eip-150.md + result = baseCosts + result[GasSload] = 200.u256 + result[GasSelfDestruct] = 5000.u256 + result[GasBalance] = 400.u256 + result[GasCall] = 40.u256 + +let TangerineGasCosts* = BaseGasCosts.tangerineGasCosts diff --git a/src/vm/forks/vm_forks.nim b/src/vm/forks/vm_forks.nim new file mode 100644 index 000000000..5cfd7bfed --- /dev/null +++ b/src/vm/forks/vm_forks.nim @@ -0,0 +1,26 @@ +# 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 + ../../db/db_chain, ../../constants, + ../../utils/header, + ../base, + ./f20150730_frontier/frontier_vm, + ./f20161018_tangerine_whistle/tangerine_vm, + 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 + +proc newNimbusVM*(header: BlockHeader, chainDB: BaseChainDB): VM = + + # TODO: deal with empty BlockHeader + if header.blockNumber < FORK_TANGERINE_WHISTLE_BLKNUM: + result = newFrontierVM(header, chainDB) + else: + result = newTangerineVM(header, chainDB) diff --git a/src/vm/gas_costs.nim b/src/vm/gas_costs.nim deleted file mode 100644 index f8d2bc6d2..000000000 --- a/src/vm/gas_costs.nim +++ /dev/null @@ -1,17 +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, stint, - ../types, ../constants, ../opcode, ../computation, stack - -# TODO - currently unused - -proc expGasCost*(computation: var BaseComputation): UInt256 = - let arg = computation.stack.getInt(0) - result = if arg == 0: 10.u256 else: (10.u256 + 10.u256 * (1.u256 + arg.log256)) - diff --git a/tests/fixtures.nim b/tests/fixtures.nim index e78186f22..447d894d1 100644 --- a/tests/fixtures.nim +++ b/tests/fixtures.nim @@ -8,7 +8,7 @@ import unittest, strformat, tables, times, stint, - ../src/[constants, chain, vm/base, vm/forks/frontier/vm, utils/header, utils/address, db/db_chain, db/backends/memory_backend] + ../src/[constants, chain, vm/base, vm/forks/f20150730_frontier/frontier_vm, utils/header, utils/address, db/db_chain, db/backends/memory_backend] proc chainWithoutBlockValidation*: Chain = result = configureChain("TestChain", GENESIS_BLOCK_NUMBER, vmkFrontier, false, false) diff --git a/tests/test_helpers.nim b/tests/test_helpers.nim index 0e33244b3..8eb3f4a31 100644 --- a/tests/test_helpers.nim +++ b/tests/test_helpers.nim @@ -10,7 +10,7 @@ import stint, ../src/utils/[hexadecimal, address, padding], ../src/[chain, vm_state, constants], - ../src/db/[db_chain, state_db], ../src/vm/forks/frontier/vm, + ../src/db/[db_chain, state_db], ../src/vm/forks/f20150730_frontier/frontier_vm, ../src/vm/base, ../src/transaction type diff --git a/tests/test_opcode.nim b/tests/test_opcode.nim index 807fce55a..0d24fb6b0 100644 --- a/tests/test_opcode.nim +++ b/tests/test_opcode.nim @@ -11,14 +11,14 @@ import ../src/[chain, vm_state, computation, opcode, opcode_table], ../src/[utils/header, utils/padding], ../src/vm/[gas_meter, message, code_stream, stack], - ../src/vm/forks/frontier/vm, + ../src/vm/forks/vm_forks, ../src/db/[db_chain, state_db, backends/memory_backend], test_helpers -proc testCode(code: string, gas: UInt256): BaseComputation = - var vm = newFrontierVM(BlockHeader(), newBaseChainDB(newMemoryDB())) - let header = BlockHeader() +proc testCode(code: string, initialGas: UInt256, blockNum: UInt256): BaseComputation = + let header = BlockHeader(blockNumber: blockNum) + var vm = newNimbusVM(header, newBaseChainDB(newMemoryDB())) # coinbase: "", # difficulty: fixture{"env"}{"currentDifficulty"}.getHexadecimalInt.u256, # blockNumber: fixture{"env"}{"currentNumber"}.getHexadecimalInt.u256, @@ -31,15 +31,15 @@ proc testCode(code: string, gas: UInt256): BaseComputation = value=0.u256, data = @[], code=code, - gas=gas, - gasPrice=1.u256) + gas=initial_gas, + gasPrice=1.u256) # What is this used for? # gasPrice=fixture{"exec"}{"gasPrice"}.getHexadecimalInt.u256, #options=newMessageOptions(origin=fixture{"exec"}{"origin"}.getStr)) #echo fixture{"exec"} var c = newCodeStreamFromUnescaped(code) - #if DEBUG: - c.displayDecompiled() + if DEBUG: + c.displayDecompiled() var computation = newBaseComputation(vm.state, message) computation.accountsToDelete = initTable[string, string]() @@ -51,7 +51,11 @@ proc testCode(code: string, gas: UInt256): BaseComputation = suite "opcodes": test "add": - var c = testCode("0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff01", 100_000.u256) + var c = testCode( + "0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff01", + 100_000.u256, + 0.u256 + ) check(c.gasMeter.gasRemaining == 99_991.u256) check(c.stack.peek == "115792089237316195423570985008687907853269984665640564039457584007913129639934".u256) # let address = Address::from_str("0f572e5295c57f15886f9b263e2f6d2d6c7b5ec6").unwrap(); @@ -71,3 +75,38 @@ suite "opcodes": # assert_eq!(gas_left, U256::from(79_988)); # 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.u256, + 0.u256 + ) + check: c.gasMeter.gasRemaining == 100000.u256 - 3.u256 - 20.u256 # Starting gas - push32 (verylow) - balance + + block: # Using SLOAD (0x54) + var c = testCode( + "0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff54", + 100_000.u256, + 0.u256 + ) + check: c.gasMeter.gasRemaining == 100000.u256 - 3.u256 - 50.u256 # 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.u256, + 2_463_000.u256 # Tangerine block + ) + check: c.gasMeter.gasRemaining == 100000.u256 - 3.u256 - 400.u256 # Starting gas - push32 (verylow) - balance + + block: # Using SLOAD (0x54) + var c = testCode( + "0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff54", + 100_000.u256, + 2_463_000.u256 + ) + check: c.gasMeter.gasRemaining == 100000.u256 - 3.u256 - 200.u256 # Starting gas - push32 (verylow) - SLOAD diff --git a/tests/test_vm.nim b/tests/test_vm.nim index 3d19a3262..6468dec66 100644 --- a/tests/test_vm.nim +++ b/tests/test_vm.nim @@ -41,4 +41,3 @@ suite "VM": # state_db.getBalance(recipient) == amount # b.transactions[txIdx] == tx # b.header.gasUsed == constants.GAS_TX - diff --git a/tests/test_vm_json.nim b/tests/test_vm_json.nim index 6b12f3a7f..b655a7498 100644 --- a/tests/test_vm_json.nim +++ b/tests/test_vm_json.nim @@ -12,7 +12,7 @@ import ../src/[chain, vm_state, computation, opcode, types, opcode_table], ../src/utils/[header, padding], ../src/vm/[gas_meter, message, code_stream, stack], - ../src/vm/forks/frontier/vm, ../src/db/[db_chain, state_db, backends/memory_backend] + ../src/vm/forks/vm_forks, ../src/db/[db_chain, state_db, backends/memory_backend] proc testFixture(fixtures: JsonNode, testStatusIMPL: var TestStatus) @@ -24,13 +24,14 @@ proc testFixture(fixtures: JsonNode, testStatusIMPL: var TestStatus) = for label, child in fixtures: fixture = child break - var vm = newFrontierVM(BlockHeader(), newBaseChainDB(newMemoryDB())) let header = BlockHeader( coinbase: fixture{"env"}{"currentCoinbase"}.getStr, difficulty: fixture{"env"}{"currentDifficulty"}.getHexadecimalInt.u256, blockNumber: fixture{"env"}{"currentNumber"}.getHexadecimalInt.u256, # gasLimit: fixture{"env"}{"currentGasLimit"}.getHexadecimalInt.u256, - timestamp: fixture{"env"}{"currentTimestamp"}.getHexadecimalInt.int64.fromUnix) + timestamp: fixture{"env"}{"currentTimestamp"}.getHexadecimalInt.int64.fromUnix + ) + var vm = newNimbusVM(header, newBaseChainDB(newMemoryDB())) var code = "" vm.state.db(readOnly=false):