1st gas refactoring - handle different gas costs in Ethereum forks (#37)

* Start by renaming the fork by chronological order

* initial commit - groundwork: support different gasCosts depending on VMs

* Running VMTests with new gas config - only push32AndSuicide changed

* Introduce newNimbusVM that (temporarily) abstract over Frontier and Tangerine VM creation

* Fix Gas - push32AndSuicide repassing and expXY_success passing - fixes #34

* case sensitive imports (thank you auto-completion) fix Travis on Linux

* Add pre and post EIP-150 tests

* Nimify gas identifiers that supports multiple forks
This commit is contained in:
Mamy Ratsimbazafy 2018-05-24 12:01:59 +02:00 committed by GitHub
parent 762b0b24d2
commit 0b6fbbad67
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
32 changed files with 432 additions and 176 deletions

View File

@ -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

View File

@ -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

View File

@ -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")

View File

@ -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

View File

@ -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

View File

@ -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,

View File

@ -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)

View File

@ -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)

View File

@ -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)

View File

@ -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

View File

@ -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]

View File

@ -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"

View File

@ -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 = @[]

View File

@ -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")

View File

@ -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, @[])

View File

@ -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")

View File

@ -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)

BIN
src/vm/forks/forks_list.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 104 KiB

View File

@ -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()

View File

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

View File

@ -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

26
src/vm/forks/vm_forks.nim Normal file
View File

@ -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)

View File

@ -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))

View File

@ -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)

View File

@ -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

View File

@ -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

View File

@ -41,4 +41,3 @@ suite "VM":
# state_db.getBalance(recipient) == amount
# b.transactions[txIdx] == tx
# b.header.gasUsed == constants.GAS_TX

View File

@ -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):