Gas refactoring - decouple opcode logic and gas (#49)

* Decoupling op logic and gas - introduce gasometer, rework opcode declaration

* Remove gas constants for gas opcode computation

* Remove gas constants for precompiled contracts

* make vm_types compile

* Make opcode, call and computation compile

* Distinguish between dynamic and complex gas costs, fix arithmetic

* Fix context and sha3

* update memory and storage ops

* Log opcode uses memory expansion code

* update/stub system_ops with gas costs

* Make test compile. Deactivate stub test_vm

* all tests compiles, opcode fails due to https://github.com/nim-lang/Nim/issues/8007 (const object variant in tables reset at runtime)

* Create an enum without holes - workaround: https://github.com/nim-lang/Nim/issues/8007

* Use arrays instead of tables for GasCosts, remove some unused imports - passing all basic tests!

* Make test_vm_json compile

* Fix test_vm_json - workaround https://github.com/nim-lang/Nim/issues/8015

* fix memory expansion cost bug

* Remove leftover special handling from before GckMemExpansion

* cleanup outdated comment, better align =

* Fix sha3 gas cost not taking memory expansion into account

* Improve gas error reporting of test_vm_json

* Fix gas computation regression due to mem expansion

* mass replace for memExpansion->RequestedMemSize was too eager

* fix log gas cost (no tests :/)

* missed a static FeeSchedule

* static as expression is fickle
This commit is contained in:
Mamy Ratsimbazafy 2018-06-12 17:33:47 +02:00 committed by GitHub
parent 8528f1b704
commit 90c3ca4a96
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
24 changed files with 1137 additions and 636 deletions

View File

@ -9,7 +9,7 @@ VMTests
+ add4.json OK + add4.json OK
+ addmod0.json OK + addmod0.json OK
+ addmod1.json OK + addmod1.json OK
+ addmod1_overflow2.json OK - addmod1_overflow2.json Fail
+ addmod1_overflow3.json OK + addmod1_overflow3.json OK
+ addmod1_overflow4.json OK + addmod1_overflow4.json OK
+ addmod1_overflowDiff.json OK + addmod1_overflowDiff.json OK
@ -135,7 +135,7 @@ VMTests
+ mulUnderFlow.json OK + mulUnderFlow.json OK
+ mulmod0.json OK + mulmod0.json OK
+ mulmod1.json OK + mulmod1.json OK
+ mulmod1_overflow.json OK - mulmod1_overflow.json Fail
+ mulmod1_overflow2.json OK + mulmod1_overflow2.json OK
+ mulmod1_overflow3.json OK + mulmod1_overflow3.json OK
+ mulmod1_overflow4.json OK + mulmod1_overflow4.json OK
@ -177,7 +177,7 @@ VMTests
+ signextend_BitIsNotSet.json OK + signextend_BitIsNotSet.json OK
+ signextend_BitIsNotSetInHigherByte.json OK + signextend_BitIsNotSetInHigherByte.json OK
+ signextend_BitIsSetInHigherByte.json OK + signextend_BitIsSetInHigherByte.json OK
- signextend_Overflow_dj42.json Fail + signextend_Overflow_dj42.json OK
+ signextend_bigBytePlus1.json OK + signextend_bigBytePlus1.json OK
+ signextend_bitIsSet.json OK + signextend_bitIsSet.json OK
+ smod0.json OK + smod0.json OK
@ -198,7 +198,7 @@ VMTests
+ sub3.json OK + sub3.json OK
+ sub4.json OK + sub4.json OK
``` ```
OK: 190/195 Fail: 4/195 Skip: 1/195 OK: 189/195 Fail: 5/195 Skip: 1/195
## vmBitwiseLogicOperation ## vmBitwiseLogicOperation
```diff ```diff
+ and0.json OK + and0.json OK
@ -265,20 +265,20 @@ OK: 190/195 Fail: 4/195 Skip: 1/195
OK: 60/60 Fail: 0/60 Skip: 0/60 OK: 60/60 Fail: 0/60 Skip: 0/60
## vmBlockInfoTest ## vmBlockInfoTest
```diff ```diff
- blockhash257Block.json Fail + blockhash257Block.json OK
- blockhash258Block.json Fail + blockhash258Block.json OK
- blockhashInRange.json Fail + blockhashInRange.json OK
- blockhashMyBlock.json Fail + blockhashMyBlock.json OK
- blockhashNotExistingBlock.json Fail + blockhashNotExistingBlock.json OK
- blockhashOutOfRange.json Fail + blockhashOutOfRange.json OK
+ blockhashUnderFlow.json OK + blockhashUnderFlow.json OK
- coinbase.json Fail + coinbase.json OK
+ difficulty.json OK + difficulty.json OK
+ gaslimit.json OK + gaslimit.json OK
+ number.json OK + number.json OK
+ timestamp.json OK + timestamp.json OK
``` ```
OK: 5/12 Fail: 7/12 Skip: 0/12 OK: 12/12 Fail: 0/12 Skip: 0/12
## vmEnvironmentalInfo ## vmEnvironmentalInfo
```diff ```diff
ExtCodeSizeAddressInputTooBigLeftMyAddress.json Skip ExtCodeSizeAddressInputTooBigLeftMyAddress.json Skip

View File

@ -6,25 +6,15 @@
# at your option. This file may not be copied, modified, or distributed except according to those terms. # at your option. This file may not be copied, modified, or distributed except according to those terms.
import import
strformat, strutils, sequtils, tables, macros, stint, terminal, math, eth_common, byteutils, strformat, strutils, sequtils, macros, stint, terminal, math, eth_common, byteutils, tables,
constants, errors, utils/hexadecimal, utils_numeric, validation, vm_state, logging, opcode_values, vm_types, ./constants, ./errors, ./utils/hexadecimal, ./utils_numeric, ./validation, ./vm_state, ./logging, ./opcode_values, ./vm_types,
vm / [code_stream, gas_meter, memory, message, stack], ./vm/[code_stream, gas_meter, memory, message, stack],
# TODO further refactoring of gas cost # TODO further refactoring of gas cost
vm/forks/gas_costs, vm/forks/gas_costs,
vm/forks/f20150730_frontier/frontier_vm_state, vm/forks/f20150730_frontier/frontier_vm_state,
vm/forks/f20161018_tangerine_whistle/tangerine_vm_state vm/forks/f20161018_tangerine_whistle/tangerine_vm_state
proc memoryGasCost*(sizeInBytes: Natural): GasInt =
let
sizeInWords = ceil32(sizeInBytes) div 32
linearCost = sizeInWords * GAS_MEMORY
quadraticCost = sizeInWords ^ 2 div GAS_MEMORY_QUADRATIC_DENOMINATOR
totalCost = linearCost + quadraticCost
result = totalCost
#const VARIABLE_GAS_COST_OPS* = {Op.Exp}
method newBaseComputation*(vmState: BaseVMState, message: Message): BaseComputation {.base.}= method newBaseComputation*(vmState: BaseVMState, message: Message): BaseComputation {.base.}=
raise newException(ValueError, "Must be implemented by subclasses") raise newException(ValueError, "Must be implemented by subclasses")
@ -105,26 +95,6 @@ method prepareChildMessage*(
code, code,
childOptions) childOptions)
method extendMemory*(c: var BaseComputation, startPosition: Natural, size: Natural) =
# Memory Management
let beforeSize = ceil32(len(c.memory))
let afterSize = ceil32(startPosition + size)
let beforeCost = memoryGasCost(beforeSize)
let afterCost = memoryGasCost(afterSize)
c.logger.debug(&"MEMORY: size ({beforeSize} -> {afterSize}) | cost ({beforeCost} -> {afterCost})")
if size > 0:
if beforeCost < afterCost:
var gasFee = afterCost - beforeCost
c.gasMeter.consumeGas(
gasFee,
reason = &"Expanding memory {beforeSize} -> {afterSize}")
c.memory.extend(startPosition, size)
method output*(c: BaseComputation): string = method output*(c: BaseComputation): string =
if c.shouldEraseReturnData: if c.shouldEraseReturnData:
"" ""
@ -261,10 +231,10 @@ template inComputation*(c: untyped, handler: untyped): untyped =
c.gasMeter.gasRemaining, c.gasMeter.gasRemaining,
reason="Zeroing gas due to VM Exception: $1" % getCurrentExceptionMsg()) reason="Zeroing gas due to VM Exception: $1" % getCurrentExceptionMsg())
method getOpcodeFn*(computation: var BaseComputation, op: Op): Opcode = 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): if computation.opcodes.len > 0 and computation.opcodes.hasKey(op):
computation.opcodes[op] OpCode(kind: op, runLogic: computation.opcodes[op])
else: else:
raise newException(InvalidInstruction, raise newException(InvalidInstruction,
&"Invalid opcode {op}") &"Invalid opcode {op}")

View File

@ -114,66 +114,8 @@ let
ZERO_HASH32* = Hash256() ZERO_HASH32* = Hash256()
STACK_DEPTH_LIMIT* = 1024 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 # 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
# GAS_SELF_DESTRUCT* = 0.u256
# GAS_SELF_DESTRUCT_NEW_ACCOUNT* = 25_000.u256
GAS_CREATE* = 32_000.u256
# GAS_CALL* = 40.u256
GAS_CALL_VALUE* = 9_000
GAS_CALL_STIPEND* = 2_300
GAS_NEW_ACCOUNT* = 25_000
# GAS_COST_BALANCE* = 400.u256 # TODO: this is post-eip150, see also GAS_BALANCE
# GAS_EXP* = 10.u256
# GAS_EXP_BYTE* = 10.u256
GAS_MEMORY* = 3
GAS_TX_CREATE* = 32_000.u256
GAS_TX_DATA_ZERO* = 4.u256
GAS_TX_DATA_NON_ZERO* = 68.u256
GAS_TX* = 21_000
GAS_LOG* = 375.u256
GAS_LOG_DATA* = 8
GAS_LOG_TOPIC* = 375
# GAS_SHA3* = 30.u256
GAS_SHA3_WORD* = 6
GAS_COPY* = 3
GAS_BLOCK_HASH* = 20.u256
GAS_CODE_DEPOSIT* = 200.u256
GAS_MEMORY_QUADRATIC_DENOMINATOR* = 512
GAS_SHA256* = 60.u256
GAS_SHA256WORD* = 12.u256
GAS_RIP_EMD160* = 600.u256
GAS_RIP_EMD160WORD* = 120.u256
GAS_IDENTITY* = 15.u256
GAS_IDENTITY_WORD* = 3
GAS_ECRECOVER* = 3_000.u256
GAS_ECADD* = 500.u256
GAS_ECMUL* = 40_000.u256
GAS_ECPAIRING_BASE* = 100_000.u256
GAS_ECPAIRING_PER_POINT* = 80_000.u256
GAS_LIMIT_EMA_DENOMINATOR* = 1_024 GAS_LIMIT_EMA_DENOMINATOR* = 1_024
GAS_LIMIT_ADJUSTMENT_FACTOR* = 1_024 GAS_LIMIT_ADJUSTMENT_FACTOR* = 1_024
GAS_LIMIT_MAXIMUM* = high(GasInt)
GAS_LIMIT_USAGE_ADJUSTMENT_NUMERATOR* = 3 GAS_LIMIT_USAGE_ADJUSTMENT_NUMERATOR* = 3
GAS_LIMIT_USAGE_ADJUSTMENT_DENOMINATOR* = 2 GAS_LIMIT_USAGE_ADJUSTMENT_DENOMINATOR* = 2

View File

@ -5,7 +5,7 @@
# * MIT license ([LICENSE-MIT](LICENSE-MIT) or http://opensource.org/licenses/MIT) # * 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. # at your option. This file may not be copied, modified, or distributed except according to those terms.
import strformat, tables, stint, rlp, ranges, state_db, backends / memory_backend, import stint, tables, rlp, ranges, state_db, backends / memory_backend,
../errors, ../utils/header, ../constants, eth_common, byteutils ../errors, ../utils/header, ../constants, eth_common, byteutils
type type

View File

@ -6,7 +6,7 @@
# at your option. This file may not be copied, modified, or distributed except according to those terms. # at your option. This file may not be copied, modified, or distributed except according to those terms.
import import
strformat, tables, eth_common, eth_common, tables,
../constants, ../errors, ../validation, ../account, ../logging, ../utils_numeric, .. / utils / [padding, bytes, keccak], ../constants, ../errors, ../validation, ../account, ../logging, ../utils_numeric, .. / utils / [padding, bytes, keccak],
stint, rlp stint, rlp

View File

@ -90,10 +90,10 @@ proc exp*(computation: var BaseComputation) =
# Exponentiation # Exponentiation
let (base, exponent) = computation.stack.popInt(2) let (base, exponent) = computation.stack.popInt(2)
var gasCost = computation.gasCosts[GasExp] computation.gasMeter.consumeGas(
if not exponent.isZero: computation.gasCosts[Exp].d_handler(exponent),
gasCost += gasCost * (1 + log256(exponent)) reason="EXP: exponent bytes"
computation.gasMeter.consumeGas(gasCost, reason="EXP: exponent bytes") )
let res = if base.isZero: 0.u256 # 0^0 is 0 in py-evm let res = if base.isZero: 0.u256 # 0^0 is 0 in py-evm
else: base.pow(exponent) else: base.pow(exponent)

View File

@ -39,20 +39,10 @@ type
using using
computation: var BaseComputation computation: var BaseComputation
method msgExtraGas*(call: BaseCall, computation; gas: GasInt, to: EthAddress, value: UInt256): GasInt {.base.} =
raise newException(NotImplementedError, "Must be implemented by subclasses")
method msgGas*(call: BaseCall, computation; gas: GasInt, to: EthAddress, value: UInt256): (GasInt, GasInt) {.base.} =
let extraGas = call.msgExtraGas(computation, gas, to, value)
let totalFee = gas + extraGas
let childMsgGas = gas + (if value != 0: GAS_CALL_STIPEND else: 0)
(childMsgGas, totalFee)
method callParams*(call: BaseCall, computation): (UInt256, UInt256, EthAddress, EthAddress, EthAddress, UInt256, UInt256, UInt256, UInt256, bool, bool) {.base.} = method callParams*(call: BaseCall, computation): (UInt256, UInt256, EthAddress, EthAddress, EthAddress, UInt256, UInt256, UInt256, UInt256, bool, bool) {.base.} =
raise newException(NotImplementedError, "Must be implemented subclasses") raise newException(NotImplementedError, "Must be implemented subclasses")
method runLogic*(call: BaseCall, computation) = method runLogic*(call: BaseCall, computation) =
computation.gasMeter.consumeGas(computation.gasCosts[call.gasCostKind], reason = $call.kind) # TODO: Refactoring call gas costs
let (gas, value, to, sender, let (gas, value, to, sender,
codeAddress, codeAddress,
memoryInputStartPosition, memoryInputSize, memoryInputStartPosition, memoryInputSize,
@ -62,12 +52,15 @@ method runLogic*(call: BaseCall, computation) =
let (memInPos, memInLen, memOutPos, memOutLen) = (memoryInputStartPosition.toInt, memoryInputSize.toInt, memoryOutputStartPosition.toInt, memoryOutputSize.toInt) let (memInPos, memInLen, memOutPos, memOutLen) = (memoryInputStartPosition.toInt, memoryInputSize.toInt, memoryOutputStartPosition.toInt, memoryOutputSize.toInt)
computation.extendMemory(memInPos, memInLen) let (gasCost, childMsgGas) = computation.gasCosts[Op.Call].c_handler(
computation.extendMemory(memOutPos, memOutLen) value,
GasParams() # TODO - stub
)
computation.memory.extend(memInPos, memInLen)
computation.memory.extend(memOutPos, memOutLen)
let callData = computation.memory.read(memInPos, memInLen) let callData = computation.memory.read(memInPos, memInLen)
let (childMsgGas, childMsgGasFee) = call.msgGas(computation, gas.toInt, to, value)
computation.gasMeter.consumeGas(childMsgGasFee, reason = $call.kind)
# TODO: Pre-call checks # TODO: Pre-call checks
# with computation.vm_state.state_db(read_only=True) as state_db: # with computation.vm_state.state_db(read_only=True) as state_db:
@ -125,16 +118,6 @@ method runLogic*(call: BaseCall, computation) =
if not childComputation.shouldBurnGas: if not childComputation.shouldBurnGas:
computation.gasMeter.returnGas(childComputation.gasMeter.gasRemaining) computation.gasMeter.returnGas(childComputation.gasMeter.gasRemaining)
method msgExtraGas(call: Call, computation; gas: GasInt, to: EthAddress, value: UInt256): GasInt =
# TODO: db
# with computation.vm_state.state_db(read_only=True) as state_db:
# let accountExists = db.accountExists(to)
let accountExists = false
let transferGasFee = if value != 0: GAS_CALL_VALUE else: 0
let createGasFee = if not accountExists: GAS_NEW_ACCOUNT else: 0
transferGasFee + createGasFee
method callParams(call: CallCode, computation): (UInt256, UInt256, EthAddress, EthAddress, EthAddress, UInt256, UInt256, UInt256, UInt256, bool, bool) = method callParams(call: CallCode, computation): (UInt256, UInt256, EthAddress, EthAddress, EthAddress, UInt256, UInt256, UInt256, UInt256, bool, bool) =
let gas = computation.stack.popInt() let gas = computation.stack.popInt()
let to = computation.stack.popAddress() let to = computation.stack.popAddress()
@ -155,9 +138,6 @@ method callParams(call: CallCode, computation): (UInt256, UInt256, EthAddress, E
true, # should_transfer_value, true, # should_transfer_value,
computation.msg.isStatic) computation.msg.isStatic)
method msgExtraGas(call: CallCode, computation; gas: GasInt, to: EthAddress, value: UInt256): GasInt =
if value != 0: GAS_CALL_VALUE else: 0
method callParams(call: Call, computation): (UInt256, UInt256, EthAddress, EthAddress, EthAddress, UInt256, UInt256, UInt256, UInt256, bool, bool) = method callParams(call: Call, computation): (UInt256, UInt256, EthAddress, EthAddress, EthAddress, UInt256, UInt256, UInt256, UInt256, bool, bool) =
let gas = computation.stack.popInt() let gas = computation.stack.popInt()
let codeAddress = computation.stack.popAddress() let codeAddress = computation.stack.popAddress()
@ -181,12 +161,6 @@ method callParams(call: Call, computation): (UInt256, UInt256, EthAddress, EthAd
true, # should_transfer_value, true, # should_transfer_value,
computation.msg.isStatic) computation.msg.isStatic)
method msgGas(call: DelegateCall, computation; gas: GasInt, to: EthAddress, value: UInt256): (GasInt, GasInt) =
(gas, gas)
method msgExtraGas(call: DelegateCall, computation; gas: GasInt, to: EthAddress, value: UInt256): GasInt =
0
method callParams(call: DelegateCall, computation): (UInt256, UInt256, EthAddress, EthAddress, EthAddress, UInt256, UInt256, UInt256, UInt256, bool, bool) = method callParams(call: DelegateCall, computation): (UInt256, UInt256, EthAddress, EthAddress, EthAddress, UInt256, UInt256, UInt256, UInt256, bool, bool) =
let gas = computation.stack.popInt() let gas = computation.stack.popInt()
let codeAddress = computation.stack.popAddress() let codeAddress = computation.stack.popAddress()
@ -210,42 +184,6 @@ method callParams(call: DelegateCall, computation): (UInt256, UInt256, EthAddres
false, # should_transfer_value, false, # should_transfer_value,
computation.msg.isStatic) computation.msg.isStatic)
proc maxChildGasEIP150*(gas: GasInt): GasInt =
gas - gas div 64
proc computeEIP150MsgGas(computation; gas, extraGas: GasInt, value: UInt256, name: string, callStipend: GasInt): (GasInt, GasInt) =
if computation.gasMeter.gasRemaining < extraGas:
raise newException(OutOfGas, &"Out of gas: Needed {extraGas} - Remaining {computation.gasMeter.gasRemaining} - Reason: {name}")
let gas = min(gas, maxChildGasEIP150(computation.gasMeter.gasRemaining - extraGas))
let totalFee = gas + extraGas
let childMsgGas = gas + (if value != 0: callStipend else: 0)
(childMsgGas, totalFee)
method msgGas(call: CallEIP150, computation; gas: GasInt, to: EthAddress, value: UInt256): (GasInt, GasInt) =
let extraGas = call.msgExtraGas(computation, gas, to, value)
computeEIP150MsgGas(computation, gas, extraGas, value, $call.kind, GAS_CALL_STIPEND)
method msgGas(call: CallCodeEIP150, computation; gas: GasInt, to: EthAddress, value: UInt256): (GasInt, GasInt) =
let extraGas = call.msgExtraGas(computation, gas, to, value)
computeEIP150MsgGas(computation, gas, extraGas, value, $call.kind, GAS_CALL_STIPEND)
method msgGas(call: DelegateCallEIP150, computation; gas: GasInt, to: EthAddress, value: UInt256): (GasInt, GasInt) =
let extraGas = call.msgExtraGas(computation, gas, to, value)
computeEIP150MsgGas(computation, gas, extraGas, value, $call.kind, 0)
proc msgExtraGas*(call: CallEIP161, computation; gas: GasInt, to: EthAddress, value: UInt256): GasInt =
# TODO: with
# with computation.vm_state.state_db(read_only=True) as state_db:
# account_is_dead = (
# not state_db.account_exists(to) or
# state_db.account_is_empty(to))
let accountIsDead = true
let transferGasFee = if value != 0: GAS_CALL_VALUE else: 0
let createGasFee = if accountIsDead and value != 0: GAS_NEW_ACCOUNT else: 0
transferGasFee + createGasFee
method callParams(call: StaticCall, computation): (UInt256, UInt256, EthAddress, EthAddress, EthAddress, UInt256, UInt256, UInt256, UInt256, bool, bool) = method callParams(call: StaticCall, computation): (UInt256, UInt256, EthAddress, EthAddress, EthAddress, UInt256, UInt256, UInt256, UInt256, bool, bool) =
let gas = computation.stack.popInt() let gas = computation.stack.popInt()
let to = computation.stack.popAddress() let to = computation.stack.popAddress()
@ -265,7 +203,6 @@ method callParams(call: StaticCall, computation): (UInt256, UInt256, EthAddress,
false, # should_transfer_value, false, # should_transfer_value,
true) # is_static true) # is_static
method callParams(call: CallByzantium, computation): (UInt256, UInt256, EthAddress, EthAddress, EthAddress, UInt256, UInt256, UInt256, UInt256, bool, bool) = method callParams(call: CallByzantium, computation): (UInt256, UInt256, EthAddress, EthAddress, EthAddress, UInt256, UInt256, UInt256, UInt256, bool, bool) =
result = procCall callParams(call, computation) result = procCall callParams(call, computation)
if computation.msg.isStatic and result[1] != 0: if computation.msg.isStatic and result[1] != 0:

View File

@ -8,7 +8,8 @@
import import
strformat, strformat,
../constants, ../vm_types, ../errors, ../utils_numeric, ../computation, ../vm_state, ../account, ../db/state_db, ../validation, ../constants, ../vm_types, ../errors, ../utils_numeric, ../computation, ../vm_state, ../account, ../db/state_db, ../validation,
.. / vm / [stack, message, gas_meter, memory, code_stream], .. / utils / [address, padding, bytes], stint .. /vm/[stack, message, gas_meter, memory, code_stream], ../utils/[address, padding, bytes], stint,
../opcode_values
proc balance*(computation: var BaseComputation) = proc balance*(computation: var BaseComputation) =
let address = computation.stack.popAddress() let address = computation.stack.popAddress()
@ -47,12 +48,14 @@ proc callDataCopy*(computation: var BaseComputation) =
let (memStartPosition, let (memStartPosition,
calldataStartPosition, calldataStartPosition,
size) = computation.stack.popInt(3) size) = computation.stack.popInt(3)
let (memPos, callPos, len) = (memStartPosition.toInt, calldataStartPosition.toInt, size.toInt)
computation.extendMemory(memPos, len)
let wordCount = ceil32(len) div 32 computation.gasMeter.consumeGas(
let copyGasCost = wordCount * constants.GAS_COPY computation.gasCosts[CallDataCopy].d_handler(size),
computation.gasMeter.consumeGas(copyGasCost, reason="CALLDATACOPY fee") reason="CALLDATACOPY fee")
let (memPos, callPos, len) = (memStartPosition.toInt, calldataStartPosition.toInt, size.toInt)
computation.memory.extend(memPos, len)
let value = computation.msg.data[callPos ..< callPos + len] let value = computation.msg.data[callPos ..< callPos + len]
let paddedValue = padRight(value, len, 0.byte) let paddedValue = padRight(value, len, 0.byte)
computation.memory.write(memPos, paddedValue) computation.memory.write(memPos, paddedValue)
@ -67,13 +70,14 @@ proc codecopy*(computation: var BaseComputation) =
let (memStartPosition, let (memStartPosition,
codeStartPosition, codeStartPosition,
size) = computation.stack.popInt(3) 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) let (memPos, codePos, len) = (memStartPosition.toInt, codeStartPosition.toInt, size.toInt)
computation.extendMemory(memPos, len) computation.memory.extend(memPos, len)
let wordCount = ceil32(len) div 32
let copyGasCost = constants.GAS_COPY * wordCount
computation.gasMeter.consumeGas(copyGasCost, reason="CODECOPY: word gas cost")
# TODO # TODO
# with computation.code.seek(code_start_position): # with computation.code.seek(code_start_position):
# code_bytes = computation.code.read(size) # code_bytes = computation.code.read(size)
@ -96,12 +100,15 @@ proc extCodeSize*(computation: var BaseComputation) =
proc extCodeCopy*(computation: var BaseComputation) = proc extCodeCopy*(computation: var BaseComputation) =
let account = computation.stack.popAddress() let account = computation.stack.popAddress()
let (memStartPosition, codeStartPosition, size) = computation.stack.popInt(3) let (memStartPosition, codeStartPosition, size) = computation.stack.popInt(3)
let (memPos, codePos, len) = (memStartPosition.toInt, codeStartPosition.toInt, size.toInt)
computation.extendMemory(memPos, len)
let wordCount = ceil32(len) div 32
let copyGasCost = constants.GAS_COPY * wordCount
computation.gasMeter.consumeGas(copyGasCost, reason="EXTCODECOPY: word gas cost") computation.gasMeter.consumeGas(
computation.gasCosts[ExtCodeCopy].d_handler(size),
reason="EXTCODECOPY: word gas cost"
)
let (memPos, codePos, len) = (memStartPosition.toInt, codeStartPosition.toInt, size.toInt)
computation.memory.extend(memPos, len)
# TODO: # TODO:
# with computation.vm_state.state_db(read_only=True) as state_db: # with computation.vm_state.state_db(read_only=True) as state_db:
@ -116,6 +123,12 @@ proc returnDataSize*(computation: var BaseComputation) =
proc returnDataCopy*(computation: var BaseComputation) = proc returnDataCopy*(computation: var BaseComputation) =
let (memStartPosition, returnDataStartPosition, size) = computation.stack.popInt(3) 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) let (memPos, returnPos, len) = (memStartPosition.toInt, returnDataStartPosition.toInt, size.toInt)
if returnPos + len > computation.returnData.len: if returnPos + len > computation.returnData.len:
raise newException(OutOfBoundsRead, raise newException(OutOfBoundsRead,
@ -123,9 +136,7 @@ proc returnDataCopy*(computation: var BaseComputation) =
&"for data from index {returnDataStartPosition} to {returnDataStartPosition + size}. Return data is {computation.returnData.len} in \n" & &"for data from index {returnDataStartPosition} to {returnDataStartPosition + size}. Return data is {computation.returnData.len} in \n" &
"length") "length")
computation.extendMemory(memPos, len) computation.memory.extend(memPos, len)
let wordCount = ceil32(len) div 32
let copyGasCost = wordCount * constants.GAS_COPY
computation.gasMeter.consumeGas(copyGasCost, reason="RETURNDATACOPY fee")
let value = ($computation.returnData)[returnPos ..< returnPos + len] let value = ($computation.returnData)[returnPos ..< returnPos + len]
computation.memory.write(memPos, len, value) computation.memory.write(memPos, len, value)

View File

@ -7,7 +7,9 @@
import import
strformat, macros, strformat, macros,
../constants, ../errors, ../vm_types, ../computation, .. / vm / [stack, memory, gas_meter, message], .. / utils / bytes, stint ../constants, ../errors, ../vm_types, ../computation, ../vm/[stack, memory, gas_meter, message], ../utils/bytes,
../opcode_values,
stint
{.this: computation.} {.this: computation.}
{.experimental.} {.experimental.}
@ -50,12 +52,12 @@ macro logXX(topicCount: static[int]): untyped =
result.body.add(topicCode) result.body.add(topicCode)
let OpName = ident(&"Log{topicCount}")
let logicCode = quote: let logicCode = quote:
let dataGasCost = constants.GAS_LOG_DATA * `len` `computation`.gasMeter.consumeGas(
let topicGasCost = constants.GAS_LOG_TOPIC * `topicCount` `computation`.gasCosts[`OpName`].m_handler(`computation`.memory.len, `memPos` + `len`),
let totalGasCost = dataGasCost + topicGasCost reason="Memory expansion, Log topic and data gas cost")
`computation`.gasMeter.consumeGas(totalGasCost, reason="Log topic and data gas cost") `computation`.memory.extend(`memPos`, `len`)
`computation`.extendMemory(`memPos`, `len`)
let logData = `computation`.memory.read(`memPos`, `len`).toString let logData = `computation`.memory.read(`memPos`, `len`).toString
`computation`.addLogEntry( `computation`.addLogEntry(
account=`computation`.msg.storageAddress, account=`computation`.msg.storageAddress,

View File

@ -6,7 +6,8 @@
# at your option. This file may not be copied, modified, or distributed except according to those terms. # at your option. This file may not be copied, modified, or distributed except according to those terms.
import import
../constants, ../computation, ../vm_types, .. / vm / [stack, memory], .. / utils / [padding, bytes], ../constants, ../computation, ../vm_types, ../vm/[stack, memory, gas_meter], .. /utils/[padding, bytes],
../opcode_values,
stint stint
@ -22,7 +23,12 @@ proc mstore*(computation) =
let start = stack.popInt().toInt let start = stack.popInt().toInt
let normalizedValue = stack.popInt().toByteArrayBE let normalizedValue = stack.popInt().toByteArrayBE
extendMemory(start, 32) 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) memory.write(start, normalizedValue)
proc mstore8*(computation) = proc mstore8*(computation) =
@ -30,14 +36,23 @@ proc mstore8*(computation) =
let value = stack.popInt() let value = stack.popInt()
let normalizedValue = (value and 0xff).toByteArrayBE let normalizedValue = (value and 0xff).toByteArrayBE
extendMemory(start, 1) 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]]) memory.write(start, [normalizedValue[0]])
proc mload*(computation) = proc mload*(computation) =
let start = stack.popInt().toInt let start = stack.popInt().toInt
extendMemory(start, 32) 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) let value = memory.read(start, 32)
stack.push(value) stack.push(value)

View File

@ -6,15 +6,21 @@
# at your option. This file may not be copied, modified, or distributed except according to those terms. # at your option. This file may not be copied, modified, or distributed except according to those terms.
import import
../constants, ../utils_numeric, .. / utils / [keccak, bytes], .. / vm / [stack, memory, gas_meter], ../computation, ../vm_types, helpers, stint ../constants, ../utils_numeric, ../utils/[keccak, bytes], ../vm/[stack, memory, gas_meter],
../computation, ../vm_types, ../opcode_values,
./helpers,
stint
proc sha3op*(computation: var BaseComputation) = proc sha3op*(computation: var BaseComputation) =
let (startPosition, size) = computation.stack.popInt(2) let (startPosition, size) = computation.stack.popInt(2)
let (pos, len) = (startPosition.toInt, size.toInt) let (pos, len) = (startPosition.toInt, size.toInt)
computation.extendMemory(pos, len)
let sha3Bytes = computation.memory.read(pos, len) computation.gasMeter.consumeGas(
let wordCount = sha3Bytes.len.ceil32 div 32 # TODO, can't we just shr instead of rounding + div computation.gasCosts[Sha3].m_handler(computation.memory.len, pos + len),
let gasCost = constants.GAS_SHA3_WORD * wordCount reason="SHA3: word gas cost"
computation.gasMeter.consumeGas(gasCost, reason="SHA3: word gas cost") )
var res = keccak("")
computation.memory.extend(pos, len)
var res = keccak("") # TODO: stub
pushRes() pushRes()

View File

@ -9,6 +9,7 @@ import
../constants, ../vm_types, ../errors, ../computation, ../vm_state, ../constants, ../vm_types, ../errors, ../computation, ../vm_state,
../utils/header, ../utils/header,
../db/[db_chain, state_db], ../vm/[stack, gas_meter, message], ../db/[db_chain, state_db], ../vm/[stack, gas_meter, message],
../opcode_values,
strformat, stint strformat, stint
{.this: computation.} {.this: computation.}
@ -26,15 +27,14 @@ proc sstore*(computation) =
computation.vmState.db(readOnly=false): computation.vmState.db(readOnly=false):
(currentValue, existing) = db.getStorage(computation.msg.storageAddress, slot) (currentValue, existing) = db.getStorage(computation.msg.storageAddress, slot)
let isCurrentlyEmpty = not existing # currentValue == 0 let
let isGoingToBeEmpty = value == 0 gasParam = GasParams(kind: Op.Sstore, s_isStorageEmpty: not existing)
(gasCost, gasRefund) = computation.gasCosts[Sstore].c_handler(currentValue, gasParam)
let gasRefund = if isCurrentlyEmpty or not isGoingToBeEmpty: 0 else: REFUND_SCLEAR computation.gasMeter.consumeGas(gasCost, &"SSTORE: {computation.msg.storageAddress}[slot] -> {value} ({currentValue})")
let gasCost = if isCurrentlyEmpty and not isGoingToBeEmpty: GAS_SSET else: GAS_SRESET
computation.gasMeter.consumeGas(computation.gasCosts[gasCost], &"SSTORE: {computation.msg.storageAddress}[slot] -> {value} ({currentValue})") if gasRefund > 0:
computation.gasMeter.refundGas(gasRefund)
if gasRefund > 0: computation.gasMeter.refundGas(gasRefund)
computation.vmState.db(readOnly=false): computation.vmState.db(readOnly=false):
db.setStorage(computation.msg.storageAddress, slot, value) db.setStorage(computation.msg.storageAddress, slot, value)

View File

@ -9,6 +9,7 @@ import
strformat, strformat,
../constants, ../vm_types, ../errors, ../computation, ../opcode, ../opcode_values, ../logging, ../vm_state, call, ../constants, ../vm_types, ../errors, ../computation, ../opcode, ../opcode_values, ../logging, ../vm_state, call,
.. / vm / [stack, gas_meter, memory, message], .. / utils / [address, hexadecimal, bytes], .. / vm / [stack, gas_meter, memory, message], .. / utils / [address, hexadecimal, bytes],
../opcode_values,
stint, byteutils, eth_common stint, byteutils, eth_common
{.this: computation.} {.this: computation.}
@ -24,14 +25,14 @@ type
CreateByzantium* = ref object of CreateEIP150 # 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.} = # method maxChildGasModifier(create: Create, gas: GasInt): GasInt {.base.} =
gas # gas
method runLogic*(create: Create, computation) = method runLogic*(create: Create, computation) =
computation.gasMeter.consumeGas(computation.gasCosts[create.gasCost(computation)], reason = $create.kind) # TODO: Refactoring create gas costs # 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 (value, startPosition, size) = computation.stack.popInt(3)
let (pos, len) = (startPosition.toInt, size.toInt) let (pos, len) = (startPosition.toInt, size.toInt)
computation.extendMemory(pos, len) computation.memory.extend(pos, len)
# TODO: with # TODO: with
# with computation.vm_state.state_db(read_only=True) as state_db: # with computation.vm_state.state_db(read_only=True) as state_db:
@ -44,8 +45,9 @@ method runLogic*(create: Create, computation) =
# return # return
let callData = computation.memory.read(pos, len) let callData = computation.memory.read(pos, len)
let createMsgGas = create.maxChildGasModifier(computation.gasMeter.gasRemaining) # TODO refactor gas
computation.gasMeter.consumeGas(createMsgGas, reason="CREATE") # let createMsgGas = create.maxChildGasModifier(computation.gasMeter.gasRemaining)
# computation.gasMeter.consumeGas(createMsgGas, reason="CREATE")
# TODO: with # TODO: with
# with computation.vm_state.state_db() as state_db: # with computation.vm_state.state_db() as state_db:
@ -67,7 +69,7 @@ method runLogic*(create: Create, computation) =
return return
let childMsg = computation.prepareChildMessage( let childMsg = computation.prepareChildMessage(
gas=createMsgGas, gas=0, # TODO refactor gas
to=constants.CREATE_CONTRACT_ADDRESS, to=constants.CREATE_CONTRACT_ADDRESS,
value=value, value=value,
data=cast[seq[byte]](@[]), data=cast[seq[byte]](@[]),
@ -83,8 +85,9 @@ method runLogic*(create: Create, computation) =
computation.stack.push(contractAddress) computation.stack.push(contractAddress)
computation.gasMeter.returnGas(childComputation.gasMeter.gasRemaining) computation.gasMeter.returnGas(childComputation.gasMeter.gasRemaining)
method maxChildGasModifier(create: CreateEIP150, gas: GasInt): GasInt = # TODO refactor gas
maxChildGasEIP150(gas) # method maxChildGasModifier(create: CreateEIP150, gas: GasInt): GasInt =
# maxChildGasEIP150(gas)
method runLogic*(create: CreateByzantium, computation) = method runLogic*(create: CreateByzantium, computation) =
if computation.msg.isStatic: if computation.msg.isStatic:
@ -144,7 +147,13 @@ proc selfdestruct(computation; beneficiary: EthAddress) =
proc returnOp*(computation) = proc returnOp*(computation) =
let (startPosition, size) = stack.popInt(2) let (startPosition, size) = stack.popInt(2)
let (pos, len) = (startPosition.toInt, size.toInt) let (pos, len) = (startPosition.toInt, size.toInt)
computation.extendMemory(pos, len)
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) let output = memory.read(pos, len)
computation.output = output.toString computation.output = output.toString
raise newException(Halt, "RETURN") raise newException(Halt, "RETURN")
@ -152,7 +161,13 @@ proc returnOp*(computation) =
proc revert*(computation) = proc revert*(computation) =
let (startPosition, size) = stack.popInt(2) let (startPosition, size) = stack.popInt(2)
let (pos, len) = (startPosition.toInt, size.toInt) let (pos, len) = (startPosition.toInt, size.toInt)
computation.extendMemory(pos, len)
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 let output = memory.read(pos, len).toString
computation.output = output computation.output = output
raise newException(Revert, $output) raise newException(Revert, $output)

View File

@ -20,68 +20,15 @@ from logic.call import runLogic, BaseCall
template run*(opcode: Opcode, computation: var BaseComputation) = template run*(opcode: Opcode, computation: var BaseComputation) =
# Hook for performing the actual VM execution # Hook for performing the actual VM execution
# opcode.consumeGas(computation) # opcode.consumeGas(computation)
computation.gasMeter.consumeGas(computation.gasCosts[opcode.gasCost(computation)], reason = $opcode.kind) # TODO: further refactoring of gas costs
if opcode.kind == Op.Call: # Super dirty fix for https://github.com/status-im/nimbus/issues/46 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) runLogic(BaseCall(opcode), computation)
elif computation.gasCosts[opcode.kind].kind != GckFixed:
opcode.runLogic(computation)
else: else:
computation.gasMeter.consumeGas(computation.gasCosts[opcode.kind].cost, reason = $opcode.kind)
opcode.runLogic(computation) opcode.runLogic(computation)
method logger*(opcode: Opcode): Logger = method logger*(opcode: Opcode): Logger =
logging.getLogger(&"vm.opcode.{opcode.kind}") logging.getLogger(&"vm.opcode.{opcode.kind}")
method gasCost*(opcode: Opcode, computation: var BaseComputation): GasCostKind =
#if opcode.kind in VARIABLE_GAS_COST_OPS:
# opcode.gasCostHandler(computation)
#else:
opcode.gasCostKind
template newOpcode*(kind: Op, gasCost: UInt256, logic: proc(computation: var BaseComputation)): Opcode =
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.gasCostKind
# if opcode.kind in VARIABLE_GAS_COST_OPS:
# "variable"
# else:
# $opcode.gasCostKind
&"{opcode.kind}(0x{opcode.kind.int.toHex(2)}: {gasCost})"
macro initOpcodes*(spec: untyped): untyped =
var value = ident("value")
result = quote:
block:
var `value` = initTable[Op, Opcode]()
for child in spec:
var ops, gasCosts, handlers: seq[NimNode]
if child.kind == nnkInfix and child[0].repr == "..":
ops = @[]
gasCosts = @[]
handlers = @[]
let first = child[1].repr.parseInt
let last = child[2][0].repr.parseInt
let op = child[2][1][1].repr
for z in first .. last:
ops.add(nnkDotExpr.newTree(ident("Op"), ident(op.replace("XX", $z))))
gasCosts.add(child[3][0][0])
handlers.add(ident(child[3][0][1].repr.replace("XX", $z)))
else:
ops = @[child[0]]
gasCosts = @[child[1][0][0]]
handlers = @[child[1][0][1]]
for z in 0 ..< ops.len:
let (op, gasCost, handler) = (ops[z], gasCosts[z], handlers[z])
let opcode = if gasCost.repr[0].isLowerAscii():
quote:
`value`[`op`] = Opcode(kind: `op`, gasCostHandler: `gasCost`, runLogic: `handler`)
else:
quote:
`value`[`op`] = Opcode(kind: `op`, gasCostKind: `gasCost`, runLogic: `handler`)
result[1].add(opcode)
result[1].add(value)

View File

@ -6,114 +6,168 @@
# at your option. This file may not be copied, modified, or distributed except according to those terms. # at your option. This file may not be copied, modified, or distributed except according to those terms.
import import
strformat, strutils, tables, macros, tables,
constants, stint, errors, logging, vm_state, stint, logging,
vm / [gas_meter, stack, code_stream, memory, message], db / db_chain, computation, opcode, opcode_values, utils / [header, address], vm / [gas_meter, stack, code_stream, memory, message], 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],
./vm_types ./vm_types
var OPCODE_TABLE* = initOpcodes: const
# arithmetic OpLogic*: Table[Op, proc(computation: var BaseComputation){.nimcall.}] = {
Op.Add: GasVeryLow add # 0s: Stop and Arithmetic Operations
Op.Mul: GasLow mul Stop: stop,
Op.Sub: GasVeryLow sub Add: arithmetic.add,
Op.Div: GasLow divide Mul: mul,
Op.SDiv: GasLow sdiv Sub: sub,
Op.Mod: GasLow modulo Div: divide,
Op.SMod: GasLow smod Sdiv: sdiv,
Op.AddMod: GasMid addmod Mod: modulo,
Op.MulMod: GasMid mulmod Smod: smod,
Op.Exp: GasInHandler arithmetic.exp Addmod: arithmetic.addmod,
Op.SignExtend: GasLow signextend 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,
# comparison # 20s: SHA3
Op.Lt: GasVeryLow lt Sha3: sha3op,
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
# 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,
# sha3 # 40s: Block Information
Op.SHA3: GasSHA3 sha3op 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,
# context # 60s & 70s: Push Operations
Op.Address: GasBase context.address Push1: push1,
Op.Balance: GasBalance balance Push2: push2,
Op.Origin: GasBase origin Push3: push3,
Op.Caller: GasBase caller Push4: push4,
Op.CallValue: GasBase callValue Push5: push5,
Op.CallDataLoad: GasVeryLow callDataLoad Push6: push6,
Op.CallDataSize: GasBase callDataSize Push7: push7,
Op.CallDataCopy: GasBase callDataCopy Push8: push8,
Op.CodeSize: GasBase codesize Push9: push9,
Op.CodeCopy: GasBase codecopy Push10: push10,
Op.ExtCodeSize: GasExtCode extCodeSize Push11: push11,
Op.ExtCodeCopy: GasExtCode extCodeCopy 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,
# block # 90s: Exchange Operations
Op.Blockhash: GasBase block_ops.blockhash Swap1: swap1,
Op.Coinbase: GasCoinbase coinbase Swap2: swap2,
Op.Timestamp: GasBase timestamp Swap3: swap3,
Op.Number: GasBase number Swap4: swap4,
Op.Difficulty: GasBase difficulty Swap5: swap5,
Op.GasLimit: GasBase gaslimit 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,
# stack # f0s: System operations
Op.Pop: GasBase stack_ops.pop # Create: create,
1..32 Op.PushXX: GasVeryLow pushXX # XX replaced by macro # Call: call,
1..16 Op.DupXX: GasVeryLow dupXX # CallCode: callCode,
1..16 Op.SwapXX: GasVeryLow swapXX Return: returnOp,
# DelegateCall: delegateCall,
# StaticCall: staticCall,
# memory Op.Revert: revert,
Op.MLoad: GasVeryLow mload Invalid: invalidOp,
Op.MStore: GasVeryLow mstore SelfDestruct: selfDestruct
Op.MStore8: GasVeryLow mstore8 }.toTable
Op.MSize: GasBase msize
# storage
Op.SLoad: GasSload sload
Op.SStore: GasInHandler sstore
# flow
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: GasInHandler logXX
# invalid
Op.Invalid: GasZero invalidOp
# system
Op.Return: GasZero returnOp
Op.SelfDestruct: GasSelfDestruct selfdestruct
# call
OPCODE_TABLE[Op.Call] = Call(kind: Op.Call)
OPCODE_TABLE[Op.CallCode] = CallCode(kind: Op.CallCode)
OPCODE_TABLE[Op.DelegateCall] = DelegateCall(kind: Op.DelegateCall)
# system
OPCODE_TABLE[Op.Create] = Create(kind: Op.Create)

View File

@ -5,164 +5,174 @@
# * MIT license ([LICENSE-MIT](LICENSE-MIT) or http://opensource.org/licenses/MIT) # * 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. # at your option. This file may not be copied, modified, or distributed except according to those terms.
type import ./utils/macros_gen_opcodes
fill_enum_holes:
type
# Yellow Paper Appendix H - https://ethereum.github.io/yellowpaper/paper.pdf
# Special notes from Yellow Paper:
# - Signed values are treated as twos complement signed 256-bit integers.
# - When 2^255 is negated, there is an overflow
# - For addmod and mulmod, intermediate computations are not subject to the 2^256 modulo.
# Nimbus authors note:
# - This means that we can't naively do (Uint256 + Uint256) mod uint256,
# because the intermediate sum (or multiplication) might roll over if
# intermediate result is greater or equal 2^256
Op* {.pure.} = enum Op* {.pure.} = enum
STOP = 0x0, # 0 # 0s: Stop and Arithmetic Operations
ADD, # 1 Stop = 0x00, # Halts execution.
MUL, # 2 Add = 0x01, # Addition operation.
SUB, # 3 Mul = 0x02, # Multiplication operation.
DIV, # 4 Sub = 0x03, # Subtraction operation.
SDIV, # 5 Div = 0x04, # Integer division operation.
MOD, # 6 Sdiv = 0x05, # Signed integer division operation (truncated).
SMOD, # 7 Mod = 0x06, # Modulo remainder operation.
ADDMOD, # 8 Smod = 0x07, # Signed modulo remainder operation.
MULMOD, # 9 Addmod = 0x08, # Modulo addition operation.
EXP, # 10 Mulmod = 0x09, # Modulo multiplication operation.
SIGNEXTEND, # 11 Exp = 0x0A, # Exponential operation
SignExtend = 0x0B, # Extend length of twos complement signed integer.
LT = 0x10, # 16 # 10s: Comparison & Bitwise Logic Operations
GT, # 17 Lt = 0x10, # Less-than comparison.
SLT, # 18 Gt = 0x11, # Greater-than comparison.
SGT, # 19 Slt = 0x12, # Signed less-than comparison.
EQ, # 20 Sgt = 0x13, # Signed greater-than comparison.
ISZERO, # 21 Eq = 0x14, # Equality comparison.
AND, # 22 IsZero = 0x15, # Simple not operator. (Note: real Yellow Paper description)
OR, # 23 And = 0x16, # Bitwise AND operation.
XOR, # 24 Or = 0x17, # Bitwise OR operation.
NOT, # 25 Xor = 0x18, # Bitwise XOR operation.
BYTE, # 26 Not = 0x19, # Bitwise NOT operation.
Byte = 0x1A, # Retrieve single byte from word.
SHA3 = 0x20, # 32 # 20s: SHA3
Sha3 = 0x20, # Compute Keccak-256 hash.
ADDRESS = 0x30,# 48 # 30s: Environmental Information
BALANCE, # 49 Address = 0x30, # Get address of currently executing account.
ORIGIN, # 50 Balance = 0x31, # Get balance of the given account.
Origin = 0x32, # Get execution origination address.
Caller = 0x33, # Get caller address.
CallValue = 0x34, # Get deposited value by the instruction/transaction responsible for this execution.
CallDataLoad = 0x35, # Get input data of current environment.
CallDataSize = 0x36, # Get size of input data in current environment.
CallDataCopy = 0x37, # Copy input data in current environment to memory.
CodeSize = 0x38, # Get size of code running in current environment.
CodeCopy = 0x39, # Copy code running in current environment to memory.
GasPrice = 0x3a, # Get price of gas in current environment.
ExtCodeSize = 0x3b, # Get size of an account's code
ExtCodeCopy = 0x3c, # Copy an account's code to memory.
ReturnDataSize = 0x3d, # Get size of output data from the previous call from the current environment.
ReturnDataCopy = 0x3e, # Copy output data from the previous call to memory.
CALLER, # 51 # 40s: Block Information
CALLVALUE, # 52 Blockhash = 0x40, # Get the hash of one of the 256 most recent complete blocks.
CALLDATALOAD, # 53 Coinbase = 0x41, # Get the block's beneficiary address.
CALLDATASIZE, # 54 Timestamp = 0x42, # Get the block's timestamp.
CALLDATACOPY, # 55 Number = 0x43, # Get the block's number.
Difficulty = 0x44, # Get the block's difficulty.
GasLimit = 0x45, # Get the block's gas limit.
CODESIZE, # 56 # 50s: Stack, Memory, Storage and Flow Operations
CODECOPY, # 57 Pop = 0x50, # Remove item from stack.
Mload = 0x51, # Load word from memory.
Mstore = 0x52, # Save word to memory.
Mstore8 = 0x53, # Save byte to memory.
Sload = 0x54, # Load word from storage.
Sstore = 0x55, # Save word to storage.
Jump = 0x56, # Alter the program counter.
JumpI = 0x57, # Conditionally alter the program counter.
Pc = 0x58, # Get the value of the program counter prior to the increment corresponding to this instruction.
Msize = 0x59, # Get the size of active memory in bytes.
Gas = 0x5a, # Get the amount of available gas, including the corresponding reduction for the cost of this instruction.
JumpDest = 0x5b, # Mark a valid destination for jumps. This operation has no effect on machine state during execution.
GASPRICE, # 58 # 60s & 70s: Push Operations.
Push1 = 0x60, # Place 1-byte item on stack.
Push2 = 0x61, # Place 2-byte item on stack.
Push3,
Push4,
Push5,
Push6,
Push7,
Push8,
Push9,
Push10,
Push11,
Push12,
Push13,
Push14,
Push15,
Push16,
Push17,
Push18,
Push19,
Push20,
Push21,
Push22,
Push23,
Push24,
Push25,
Push26,
Push27,
Push28,
Push29,
Push30,
Push31,
Push32 = 0x7f, # Place 32-byte (full word) item on stack.
EXTCODESIZE, # 59 # 80s: Duplication Operations
EXTCODECOPY, # 60 Dup1 = 0x80, # Duplicate 1st stack item.
Dup2 = 0x81, # Duplicate 2nd stack item.
Dup3,
Dup4,
Dup5,
Dup6,
Dup7,
Dup8,
Dup9,
Dup10,
Dup11,
Dup12,
Dup13,
Dup14,
Dup15,
Dup16 = 0x8f, # Duplicate 16th stack item.
RETURNDATASIZE, # 61 # 90s: Exchange Operations
RETURNDATACOPY, # 62 Swap1 = 0x90, # Exchange 1st and 2nd stack items.
Swap2 = 0x91, # Exchange 1st and 3rd stack items.
Swap3,
Swap4,
Swap5,
Swap6,
Swap7,
Swap8,
Swap9,
Swap10,
Swap11,
Swap12,
Swap13,
Swap14,
Swap15,
Swap16 = 0x9f, # Exchange 1st and 17th stack items.
BLOCKHASH = 0x40,# 64 # a0s: Logging Operations
Log0 = 0xa0, # Append log record with no topics.
COINBASE, # 65 Log1 = 0xa1, # Append log record with one topics.
Log2,
TIMESTAMP, # 66 Log3,
Log4 = 0xa4, # Append log record with four topics.
NUMBER, # 67
DIFFICULTY, # 68
GASLIMIT, # 69
POP = 0x50, # 80
MLOAD, # 81
MSTORE, # 82
MSTORE8 # 83
SLOAD, # 84
SSTORE, # 85
JUMP, # 86
JUMPI, # 87
PC, # 88
MSIZE, # 89
GAS, # 90
JUMPDEST, # 91
PUSH1 = 0x60, # 96
PUSH2, # 97
PUSH3, # 98
PUSH4, # 99
PUSH5, # 100
PUSH6, # 101
PUSH7, # 102
PUSH8, # 103
PUSH9, # 104
PUSH10, # 105
PUSH11, # 106
PUSH12, # 107
PUSH13, # 108
PUSH14, # 109
PUSH15, # 110
PUSH16, # 111
PUSH17, # 112
PUSH18, # 113
PUSH19, # 114
PUSH20, # 115
PUSH21, # 116
PUSH22, # 117
PUSH23, # 118
PUSH24, # 119
PUSH25, # 120
PUSH26, # 121
PUSH27, # 122
PUSH28, # 123
PUSH29, # 124
PUSH30, # 125
PUSH31, # 126
PUSH32, # 127
DUP1, # 128
DUP2, # 129
DUP3, # 130
DUP4, # 131
DUP5, # 132
DUP6, # 133
DUP7, # 134
DUP8, # 135
DUP9, # 136
DUP10, # 137
DUP11, # 138
DUP12, # 139
DUP13, # 140
DUP14, # 141
DUP15, # 142
DUP16, # 143
SWAP1, # 144
SWAP2, # 145
SWAP3, # 146
SWAP4, # 147
SWAP5, # 148
SWAP6, # 149
SWAP7, # 150
SWAP8, # 151
SWAP9, # 152
SWAP10, # 153
SWAP11, # 154
SWAP12, # 155
SWAP13, # 156
SWAP14, # 157
SWAP15, # 158
SWAP16, # 159
LOG0, # 160
LOG1, # 161
LOG2, # 162
LOG3, # 163
LOG4, # 164
CREATE = 0xf0, # 240
CALL, # 241
CALLCODE, # 242
RETURN, # 243
DELEGATECALL, # 244
STATICCALL = 0xfa,# 250
REVERT = 0xfd, # 253
SELFDESTRUCT = 0xff,# 255
INVALID # invalid
# f0s: System operations
Create = 0xf0, # Create a new account with associated code.
Call = 0xf1, # Message-call into an account.
CallCode = 0xf2, # Message-call into this account with an alternative account's code.
Return = 0xf3, # Halt execution returning output data.
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.
Revert = 0xfd, # Halt execution reverting state changes but returning data and remaining gas.
Invalid = 0xfe, # Designated invalid instruction.
SelfDestruct = 0xff # Halt execution and register account for later deletion.

View File

@ -0,0 +1,126 @@
# 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, strutils
# Due to https://github.com/nim-lang/Nim/issues/8007, we can't
# use compile-time Tables of object variants, so instead, we use
# const arrays to map Op --> gas prices.
# Arrays require an enum without hole. This macro will fill the holes
# in the enum.
# This has an added benefits that we can use computed gotos (direct threaded interpreter)
# instead of call or subroutine threading to dispatch opcode.
# see: https://github.com/nim-lang/Nim/issues/7699 (computed gotos, bad codegen with enum with holes)
# see: https://github.com/status-im/nimbus/wiki/Interpreter-optimization-resources
# for interpreter dispatch strategies
macro fill_enum_holes*(body: untyped): untyped =
## Fill the holes of an enum
## For example
## type Foo = enum
## A = 0x00,
## B = 0x10
# Sanity checks
#
# StmtList
# TypeSection
# TypeDef
# PragmaExpr
# Postfix
# Ident "*"
# Ident "Op"
# Pragma
# Ident "pure"
# Empty
# EnumTy
# Empty
# EnumFieldDef
# Ident "Stop"
# IntLit 0
# EnumFieldDef
# Ident "Add"
# IntLit 1
body[0].expectKind(nnkTypeSection)
body[0][0][2].expectKind(nnkEnumTy)
let opcodes = body[0][0][2]
# We will iterate over all the opcodes
# check if the i-th value is declared, if not add a no-op
# and accumulate that in a "dense opcodes" declaration
var
opcode = 0
holes_idx = 1
dense_opcs = nnkEnumTy.newTree()
dense_opcs.add newEmptyNode()
# Iterate on the enum with holes
while holes_idx < opcodes.len:
let curr_ident = opcodes[holes_idx]
if curr_ident.kind in {nnkIdent, nnkEmpty} or
(curr_ident.kind == nnkEnumFieldDef and
curr_ident[1].intVal == opcode):
dense_opcs.add curr_ident
inc holes_idx
else:
dense_opcs.add newIdentNode(&"Nop0x{opcode.toHex(2)}")
inc opcode
result = body
result[0][0][2] = dense_opcs
macro fill_enum_table_holes*(enumTy: typedesc[enum], nop_filler, body: untyped): untyped =
## Fill the holes of table mapping for enum with a default value
##
## For example for enum
## type Foo = enum
## A = 0x00,
## B = 0x01,
## C = 0x02
## let foo = fill_enum_table_holes(Foo, 999):
## [A: 10, C: 20]
##
## will result into `[A: 10, B: 999, C: 20]`
# Sanity checks - body
# StmtList
# Bracket
# ExprColonExpr
# Ident "Stop"
# Command
# Ident "fixed"
# Ident "GasZero"
# ExprColonExpr
# Ident "Add"
# Command
# Ident "fixed"
# Ident "GasVeryLow"
body[0].expectKind(nnkBracket)
let
enumImpl = enumTy.getType[1]
opctable = body[0]
result = nnkBracket.newTree()
var
opcode = 1 # enumImpl[0] is an empty node
body_idx = 0
while opcode < enumImpl.len:
opctable[body_idx].expectKind(nnkExprColonExpr)
if eqIdent(enumImpl[opcode], opctable[body_idx][0]):
result.add opctable[body_idx]
inc body_idx
else:
result.add nop_filler
inc opcode

View File

@ -9,8 +9,6 @@ import stint, constants, strformat, strutils, sequtils, endians, macros, utils /
# some methods based on py-evm utils/numeric # some methods based on py-evm utils/numeric
# TODO improve
proc intToBigEndian*(value: UInt256): Bytes {.deprecated.} = proc intToBigEndian*(value: UInt256): Bytes {.deprecated.} =
result = newSeq[byte](32) result = newSeq[byte](32)
result[0 .. ^1] = value.toByteArrayBE() result[0 .. ^1] = value.toByteArrayBE()
@ -21,21 +19,9 @@ proc bigEndianToInt*(value: openarray[byte]): UInt256 =
else: else:
readUintBE[256](padLeft(@value, 32, 0.byte)) readUintBE[256](padLeft(@value, 32, 0.byte))
#echo intToBigEndian("32482610168005790164680892356840817100452003984372336767666156211029086934369".u256)
# proc bitLength*(value: UInt256): int =
# 255 - value.countLeadingZeroBits
proc log256*(value: UInt256): Natural = proc log256*(value: UInt256): Natural =
(255 - value.countLeadingZeroBits) div 8 # Compilers optimize to `shr 3` (255 - value.countLeadingZeroBits) div 8 # Compilers optimize to `shr 3`
# proc ceil8*(value: int): int =
# let remainder = value mod 8
# if remainder == 0:
# value
# else:
# value + 8 - remainder
proc unsignedToSigned*(value: UInt256): Int256 = proc unsignedToSigned*(value: UInt256): Int256 =
0.i256 0.i256
# TODO Remove stub (used in quasiBoolean for signed comparison) # TODO Remove stub (used in quasiBoolean for signed comparison)
@ -54,17 +40,16 @@ proc pseudoSignedToUnsigned*(value: UInt256): UInt256 =
if value > INT_256_MAX_AS_UINT256: if value > INT_256_MAX_AS_UINT256:
result += INT_256_MAX_AS_UINT256 result += INT_256_MAX_AS_UINT256
# it's deasible to map nameXX methods like that (originally decorator) func ceil32*(value: Natural): Natural {.inline.}=
macro ceilXX(ceiling: static[int]): untyped = # Round input to the nearest bigger multiple of 32
var name = ident(&"ceil{ceiling}")
result = quote:
proc `name`*(value: Natural): Natural =
var remainder = value mod `ceiling`
if remainder == 0:
return value
else:
return value + `ceiling` - remainder
result = value
ceilXX(32) let remainder = result and 31 # equivalent to modulo 32
ceilXX(8) if remainder != 0:
return value + 32 - remainder
func wordCount*(length: Natural): Natural {.inline.}=
# Returns the number of EVM words corresponding to a specific size.
# EVM words is rounded up
length.ceil32 shr 5 # equivalent to `div 32` (32 = 2^5)

View File

@ -6,40 +6,549 @@
# at your option. This file may not be copied, modified, or distributed except according to those terms. # at your option. This file may not be copied, modified, or distributed except according to those terms.
import import
stint, ../../vm_types stint, eth_common, math,
../../utils/macros_gen_opcodes,
../../opcode_values,
../../utils_numeric
# TODO: Make that computation at compile-time. # Gas Fee Schedule
# Go-Ethereum uses pure uint64 for gas computation # Yellow Paper Appendix G - https://ethereum.github.io/yellowpaper/paper.pdf
const BaseGasCosts*: GasCosts = [ type
GasFeeKind = enum
GasZero, # Nothing paid for operations of the set Wzero.
GasBase, # Amount of gas to pay for operations of the set Wbase.
GasVeryLow, # Amount of gas to pay for operations of the set Wverylow.
GasLow, # Amount of gas to pay for operations of the set Wlow.
GasMid, # Amount of gas to pay for operations of the set Wmid.
GasHigh, # Amount of gas to pay for operations of the set Whigh.
GasExtCode, # Amount of gas to pay for operations of the set Wextcode.
GasBalance, # Amount of gas to pay for a BALANCE operation.
GasSload, # Paid for a SLOAD operation.
GasJumpDest, # Paid for a JUMPDEST operation.
GasSset, # Paid for an SSTORE operation when the storage value is set to non-zero from zero.
GasSreset, # Paid for an SSTORE operation when the storage values zeroness remains unchanged or is set to zero.
RefundSclear, # Refund given (added into refund counter) when the storage value is set to zero from non-zero.
RefundSelfDestruct, # Refund given (added into refund counter) for self-destructing an account.
GasSelfDestruct, # Amount of gas to pay for a SELFDESTRUCT operation.
GasCreate, # Paid for a CREATE operation.
GasCodeDeposit, # Paid per byte for a CREATE operation to succeed in placing code into state.
GasCall, # Paid for a CALL operation.
GasCallValue, # Paid for a non-zero value transfer as part of the CALL operation.
GasCallStipend, # A stipend for the called contract subtracted from Gcallvalue for a non-zero value transfer.
GasNewAccount, # Paid for a CALL or SELFDESTRUCT operation which creates an account.
GasExp, # Partial payment for an EXP operation.
GasExpByte, # Partial payment when multiplied by ⌈log256(exponent)⌉ for the EXP operation.
GasMemory, # Paid for every additional word when expanding memory.
GasTXCreate, # Paid by all contract-creating transactions after the Homestead transition.
GasTXDataZero, # Paid for every zero byte of data or code for a transaction.
GasTXDataNonZero, # Paid for every non-zero byte of data or code for a transaction.
GasTransaction, # Paid for every transaction.
GasLog, # Partial payment for a LOG operation.
GasLogData, # Paid for each byte in a LOG operations data.
GasLogTopic, # Paid for each topic of a LOG operation.
GasSha3, # Paid for each SHA3 operation.
GasSha3Word, # Paid for each word (rounded up) for input data to a SHA3 operation.
GasCopy, # Partial payment for COPY operations, multiplied by words copied, rounded up.
GasBlockhash # Payment for BLOCKHASH operation.
# GasQuadDivisor # The quadratic coefficient of the input sizes of the exponentiation-over-modulo precompiled contract.
GasFeeSchedule = array[GasFeeKind, GasInt]
GasParams* = object
# Yellow Paper, Appendix H - https://ethereum.github.io/yellowpaper/paper.pdf
# GasCost is a function of (σ, μ):
# - σ is the full system state
# - μ is the machine state
# In practice, we often require the following from
# - σ: an account address
# - μ: a value popped from the stack or its size.
case kind*: Op
of Sstore:
s_isStorageEmpty*: bool
of Call:
c_isNewAccount*: bool
c_gasBalance*: GasInt
c_contractGas*: Gasint
c_activeMemSize*: Natural
c_memRequested*: Natural
else:
discard
GasCostKind* = enum
GckInvalidOp,
GckFixed,
GckDynamic,
GckMemExpansion,
GckComplex
GasResult = tuple[gasCost, gasRefund: GasInt]
GasCost = object
case kind*: GasCostKind
of GckInvalidOp:
discard
of GckFixed:
cost*: GasInt
of GckDynamic:
d_handler*: proc(value: Uint256): GasInt {.nimcall.}
of GckMemExpansion:
m_handler*: proc(activeMemSize, requestedMemSize: Natural): GasInt {.nimcall.}
of GckComplex:
c_handler*: proc(value: Uint256, gasParams: GasParams): GasResult {.nimcall.}
# We use gasCost/gasRefund for:
# - Properly log and order cost and refund (for Sstore especially)
# - Allow to use unsigned integer in the future
# - CALL instruction requires passing the child message gas (Ccallgas in yellow paper)
GasCosts* = array[Op, GasCost]
template gasCosts(FeeSchedule: GasFeeSchedule, prefix, ResultGasCostsName: untyped) =
## Generate the gas cost for each forks and store them in a const
## named `ResultGasCostsName`
# ############### Helper functions ##############################
func `prefix gasMemoryExpansion`(activeMemSize, requestedMemSize: Natural): GasInt {.inline.} =
# Input: size (in bytes)
# Yellow Paper:
# Memory expansion cost
# = Cmem(μi) Cmem(μi)
# μi is memory size before opcode execution
# μ'i is the memory size after opcode execution
# Cmem(a) ≡ Gmemory · a + a² / 512
let
prev_words = activeMemSize.wordCount
prev_cost = prev_words * static(FeeSchedule[GasMemory]) +
(prev_words ^ 2) shr 9 # div 512
new_words = requestedMemSize.wordCount
new_cost = new_words * static(FeeSchedule[GasMemory]) +
(new_words ^ 2) shr 9 # div 512
# TODO: add logging
result = new_cost - prev_cost
func `prefix all_but_one_64th`(gas: GasInt): GasInt {.inline.} =
## Computes all but 1/64th
## L(n) ≡ n ⌊n/64⌋ - (floored(n/64))
# Introduced in EIP-150 - https://github.com/ethereum/EIPs/blob/master/EIPS/eip-150.md
# TODO: deactivate it pre-EIP150
# Note: The all-but-one-64th calculation should occur after the memory expansion fee is taken
# https://github.com/ethereum/yellowpaper/pull/442
result = gas - (gas shr 6)
# ############### Opcode gas functions ##############################
func `prefix gasExp`(value: Uint256): GasInt {.nimcall.} =
## Value is the exponent
result = static FeeSchedule[GasExp]
if not value.isZero:
result += static(FeeSchedule[GasExpByte]) * (1 + log256(value))
func `prefix gasSha3`(activeMemSize, requestedMemSize: Natural): GasInt {.nimcall.} =
result = `prefix gasMemoryExpansion`(activeMemSize, requestedMemSize)
result += static(FeeSchedule[GasSha3]) +
static(FeeSchedule[GasSha3Word]) * requestedMemSize.wordCount
func `prefix gasCopy`(value: Uint256): GasInt {.nimcall.} =
## Value is the size of the input to the CallDataCopy/CodeCopy/ReturnDataCopy function
result = static(FeeSchedule[GasVeryLow]) +
static(FeeSchedule[GasCopy]) * value.toInt.wordCount
func `prefix gasExtCodeCopy`(value: Uint256): GasInt {.nimcall.} =
## Value is the size of the input to the CallDataCopy/CodeCopy/ReturnDataCopy function
result = static(FeeSchedule[GasVeryLow]) +
static(FeeSchedule[GasCopy]) * value.toInt.wordCount
func `prefix gasLoadStore`(activeMemSize, requestedMemSize: Natural): GasInt {.nimcall.} =
result = static(FeeSchedule[GasVeryLow])
result += `prefix gasMemoryExpansion`(activeMemSize, requestedMemSize)
func `prefix gasSstore`(value: Uint256, gasParams: Gasparams): GasResult {.nimcall.} =
## Value is word to save
# workaround for static evaluation not working for if expression
const
gSet = FeeSchedule[GasSset]
gSreset = FeeSchedule[GasSreset]
# Gas cost - literal translation of Yellow Paper
result.gasCost = if value.isZero.not xor gasParams.s_isStorageEmpty:
gSet
else:
gSreset
# Refund
if value.isZero xor gasParams.s_isStorageEmpty:
result.gasRefund = static(FeeSchedule[RefundSclear])
func `prefix gasLog0`(activeMemSize, requestedMemSize: Natural): GasInt {.nimcall.} =
result = `prefix gasMemoryExpansion`(activeMemSize, requestedMemSize)
result += static(FeeSchedule[GasLog]) +
static(FeeSchedule[GasLogData]) * (requestedMemSize - activeMemSize)
func `prefix gasLog1`(activeMemSize, requestedMemSize: Natural): GasInt {.nimcall.} =
result = `prefix gasMemoryExpansion`(activeMemSize, requestedMemSize)
result += static(FeeSchedule[GasLog]) +
static(FeeSchedule[GasLogData]) * (requestedMemSize - activeMemSize) +
static(FeeSchedule[GasLogTopic])
func `prefix gasLog2`(activeMemSize, requestedMemSize: Natural): GasInt {.nimcall.} =
result = `prefix gasMemoryExpansion`(activeMemSize, requestedMemSize)
result += static(FeeSchedule[GasLog]) +
static(FeeSchedule[GasLogData]) * (requestedMemSize - activeMemSize) +
static(2 * FeeSchedule[GasLogTopic])
func `prefix gasLog3`(activeMemSize, requestedMemSize: Natural): GasInt {.nimcall.} =
result = `prefix gasMemoryExpansion`(activeMemSize, requestedMemSize)
result = static(FeeSchedule[GasLog]) +
static(FeeSchedule[GasLogData]) * (requestedMemSize - activeMemSize) +
static(3 * FeeSchedule[GasLogTopic])
func `prefix gasLog4`(activeMemSize, requestedMemSize: Natural): GasInt {.nimcall.} =
result = `prefix gasMemoryExpansion`(activeMemSize, requestedMemSize)
result = static(FeeSchedule[GasLog]) +
static(FeeSchedule[GasLogData]) * (requestedMemSize - activeMemSize) +
static(4 * FeeSchedule[GasLogTopic])
func `prefix gasCall`(value: Uint256, gasParams: Gasparams): GasResult {.nimcall.} =
# From the Yellow Paper, going through the equation from bottom to top
# https://ethereum.github.io/yellowpaper/paper.pdf#appendix.H
#
# More readable info on the subtleties wiki page: https://github.com/ethereum/wiki/wiki/Subtleties#other-operations
# CALL has a multi-part gas cost:
#
# - 700 base
# - 9000 additional if the value is nonzero
# - 25000 additional if the destination account does not yet exist
#
# The child message of a nonzero-value CALL operation (NOT the top-level message arising from a transaction!)
# gains an additional 2300 gas on top of the gas supplied by the calling account;
# this stipend can be considered to be paid out of the 9000 mandatory additional fee for nonzero-value calls.
# This ensures that a call recipient will always have enough gas to log that it received funds.
#
# EIP150 goes over computation: https://github.com/ethereum/eips/issues/150
#
# The discussion for the draft EIP-5, which proposes to change the CALL opcode also goes over
# the current implementation - https://github.com/ethereum/EIPs/issues/8
# First we have to take into account the costs of memory expansion:
# Note there is a "bug" in the Ethereum Yellow Paper
# - https://github.com/ethereum/yellowpaper/issues/325
# μg already includes memory expansion costs but it is not
# plainly explained n the CALL opcode details
# i.e. Cmem(μi) Cmem(μi)
# Yellow Paper: μi ≡ M(M(μi,μs[3],μs[4]),μs[5],μs[6])
# M is the memory expansion function
# μi is passed through gasParams.memRequested
# TODO:
# - Py-EVM has costs for both input and output memory expansion
# https://github.com/ethereum/py-evm/blob/eed0bfe4499b394ee58113408e487e7d35ab88d6/evm/vm/logic/call.py#L56-L57
# - Parity only for the largest expansion
# https://github.com/paritytech/parity/blob/af1088ef61323f171915555023d8e993aaaed755/ethcore/evm/src/interpreter/gasometer.rs#L192-L195
# - Go-Ethereum only has one cost
# https://github.com/ethereum/go-ethereum/blob/13af27641829f61d1e6b383e37aab6caae22f2c1/core/vm/gas_table.go#L334
# ⚠⚠ Py-EVM seems wrong if memory is needed for both in and out.
result.gasCost = `prefix gasMemoryExpansion`(
gasParams.c_activeMemSize,
gasParams.c_memRequested
)
# Cnew_account - TODO - pre-EIP158 zero-value call consumed 25000 gas
# https://github.com/ethereum/eips/issues/158
if gasParams.c_isNewAccount and not value.isZero:
result.gasCost += static(FeeSchedule[GasNewAccount])
# Cxfer
if not value.isZero:
result.gasCost += static(FeeSchedule[GasCallValue])
# Cextra
result.gasCost += static(FeeSchedule[GasCall])
let cextra = result.gasCost
# Cgascap
result.gasCost = if gasParams.c_gasBalance >= result.gasCost:
min(
`prefix all_but_one_64th`(gasParams.c_gasBalance - result.gasCost),
gasParams.c_contract_gas
)
else:
gasParams.c_contract_gas
# Ccallgas - Gas sent to the child message
result.gasRefund = result.gasCost
if not value.isZero:
result.gasRefund += static(FeeSchedule[GasCallStipend])
# Ccall
result.gasCost += cextra
func `prefix gasHalt`(activeMemSize, requestedMemSize: Natural): GasInt {.nimcall.} =
`prefix gasMemoryExpansion`(activeMemSize, requestedMemSize)
func `prefix gasSelfDestruct`(value: Uint256, gasParams: Gasparams): GasResult {.nimcall.} =
# TODO
discard
# ###################################################################################################
# TODO - change this `let` into `const` - pending: https://github.com/nim-lang/Nim/issues/8015
let `ResultGasCostsName`*{.inject.}: GasCosts = block:
# We use a block expression to avoid name redefinition conflicts
# with "fixed" and "dynamic"
# Syntactic sugar
func fixed(gasFeeKind: static[GasFeeKind]): GasCost =
GasCost(kind: GckFixed, cost: static(FeeSchedule[gasFeeKind]))
func dynamic(handler: proc(value: Uint256): GasInt {.nimcall.}): GasCost =
GasCost(kind: GckDynamic, d_handler: handler)
func memExpansion(handler: proc(activeMemSize, memExpansion: Natural): GasInt {.nimcall.}): GasCost =
GasCost(kind: GckMemExpansion, m_handler: handler)
func complex(handler: proc(value: Uint256, gasParams: GasParams): GasResult {.nimcall.}): GasCost =
GasCost(kind: GckComplex, c_handler: handler)
# Returned value
fill_enum_table_holes(Op, GasCost(kind: GckInvalidOp)):
[
# 0s: Stop and Arithmetic Operations
Stop: fixed GasZero,
Add: fixed GasVeryLow,
Mul: fixed GasLow,
Sub: fixed GasVeryLow,
Div: fixed GasLow,
Sdiv: fixed GasLow,
Mod: fixed GasLow,
Smod: fixed GasLow,
Addmod: fixed GasMid,
Mulmod: fixed GasMid,
Exp: dynamic `prefix gasExp`,
SignExtend: fixed GasLow,
# 10s: Comparison & Bitwise Logic Operations
Lt: fixed GasVeryLow,
Gt: fixed GasVeryLow,
Slt: fixed GasVeryLow,
Sgt: fixed GasVeryLow,
Eq: fixed GasVeryLow,
IsZero: fixed GasVeryLow,
And: fixed GasVeryLow,
Or: fixed GasVeryLow,
Xor: fixed GasVeryLow,
Not: fixed GasVeryLow,
Byte: fixed GasVeryLow,
# 20s: SHA3
Sha3: memExpansion `prefix gasSha3`,
# 30s: Environmental Information
Address: fixed GasBase,
Balance: fixed GasBalance,
Origin: fixed GasBase,
Caller: fixed GasBase,
CallValue: fixed GasBase,
CallDataLoad: fixed GasVeryLow,
CallDataSize: fixed GasBase,
CallDataCopy: dynamic `prefix gasCopy`,
CodeSize: fixed GasBase,
CodeCopy: dynamic `prefix gasCopy`,
GasPrice: fixed GasBase,
ExtCodeSize: fixed GasExtcode,
ExtCodeCopy: dynamic `prefix gasExtCodeCopy`,
ReturnDataSize: fixed GasBase,
ReturnDataCopy: dynamic `prefix gasCopy`,
# 40s: Block Information
Blockhash: fixed GasBlockhash,
Coinbase: fixed GasBase,
Timestamp: fixed GasBase,
Number: fixed GasBase,
Difficulty: fixed GasBase,
GasLimit: fixed GasBase,
# 50s: Stack, Memory, Storage and Flow Operations
Pop: fixed GasBase,
Mload: memExpansion `prefix gasLoadStore`,
Mstore: memExpansion `prefix gasLoadStore`,
Mstore8: memExpansion `prefix gasLoadStore`,
Sload: fixed GasSload,
Sstore: complex `prefix gasSstore`,
Jump: fixed GasMid,
JumpI: fixed GasHigh,
Pc: fixed GasBase,
Msize: fixed GasBase,
Gas: fixed GasBase,
JumpDest: fixed GasJumpDest,
# 60s & 70s: Push Operations
Push1: fixed GasVeryLow,
Push2: fixed GasVeryLow,
Push3: fixed GasVeryLow,
Push4: fixed GasVeryLow,
Push5: fixed GasVeryLow,
Push6: fixed GasVeryLow,
Push7: fixed GasVeryLow,
Push8: fixed GasVeryLow,
Push9: fixed GasVeryLow,
Push10: fixed GasVeryLow,
Push11: fixed GasVeryLow,
Push12: fixed GasVeryLow,
Push13: fixed GasVeryLow,
Push14: fixed GasVeryLow,
Push15: fixed GasVeryLow,
Push16: fixed GasVeryLow,
Push17: fixed GasVeryLow,
Push18: fixed GasVeryLow,
Push19: fixed GasVeryLow,
Push20: fixed GasVeryLow,
Push21: fixed GasVeryLow,
Push22: fixed GasVeryLow,
Push23: fixed GasVeryLow,
Push24: fixed GasVeryLow,
Push25: fixed GasVeryLow,
Push26: fixed GasVeryLow,
Push27: fixed GasVeryLow,
Push28: fixed GasVeryLow,
Push29: fixed GasVeryLow,
Push30: fixed GasVeryLow,
Push31: fixed GasVeryLow,
Push32: fixed GasVeryLow,
# 80s: Duplication Operations
Dup1: fixed GasVeryLow,
Dup2: fixed GasVeryLow,
Dup3: fixed GasVeryLow,
Dup4: fixed GasVeryLow,
Dup5: fixed GasVeryLow,
Dup6: fixed GasVeryLow,
Dup7: fixed GasVeryLow,
Dup8: fixed GasVeryLow,
Dup9: fixed GasVeryLow,
Dup10: fixed GasVeryLow,
Dup11: fixed GasVeryLow,
Dup12: fixed GasVeryLow,
Dup13: fixed GasVeryLow,
Dup14: fixed GasVeryLow,
Dup15: fixed GasVeryLow,
Dup16: fixed GasVeryLow,
# 90s: Exchange Operations
Swap1: fixed GasVeryLow,
Swap2: fixed GasVeryLow,
Swap3: fixed GasVeryLow,
Swap4: fixed GasVeryLow,
Swap5: fixed GasVeryLow,
Swap6: fixed GasVeryLow,
Swap7: fixed GasVeryLow,
Swap8: fixed GasVeryLow,
Swap9: fixed GasVeryLow,
Swap10: fixed GasVeryLow,
Swap11: fixed GasVeryLow,
Swap12: fixed GasVeryLow,
Swap13: fixed GasVeryLow,
Swap14: fixed GasVeryLow,
Swap15: fixed GasVeryLow,
Swap16: fixed GasVeryLow,
# a0s: Logging Operations
Log0: memExpansion `prefix gasLog0`,
Log1: memExpansion `prefix gasLog1`,
Log2: memExpansion `prefix gasLog2`,
Log3: memExpansion `prefix gasLog3`,
Log4: memExpansion `prefix gasLog4`,
# f0s: System operations
Create: fixed GasCreate,
Call: complex `prefix gasCall`,
CallCode: complex `prefix gasCall`,
Return: memExpansion `prefix gasHalt`,
DelegateCall: complex `prefix gasCall`,
StaticCall: complex `prefix gasCall`,
Op.Revert: memExpansion `prefix gasHalt`,
Invalid: fixed GasZero,
SelfDestruct: complex `prefix gasSelfDestruct`
]
# Generate the fork-specific gas costs tables
const
BaseGasFees: GasFeeSchedule = [
# Fee Schedule at for the initial Ethereum forks
GasZero: 0'i64, GasZero: 0'i64,
GasBase: 2, GasBase: 2,
GasVeryLow: 3, GasVeryLow: 3,
GasLow: 5, GasLow: 5,
GasMid: 8, GasMid: 8,
GasHigh: 10, GasHigh: 10,
GasExtCode: 20, # Changed to 700 in Tangerine (EIP150)
GasBalance: 20, # Changed to 400 in Tangerine (EIP150)
GasSload: 50, # Changed to 200 in Tangerine (EIP150) GasSload: 50, # Changed to 200 in Tangerine (EIP150)
GasJumpDest: 1, GasJumpDest: 1,
GasSset: 20_000, GasSset: 20_000,
GasSreset: 5_000, GasSreset: 5_000,
GasExtCode: 20, RefundSclear: 15_000,
GasCoinbase: 20, RefundSelfDestruct: 24_000,
GasSelfDestruct: 0, # Changed to 5000 in Tangerine (EIP150) GasSelfDestruct: 0, # Changed to 5000 in Tangerine (EIP150)
GasInHandler: 0, # to be calculated in handler GasCreate: 32000,
GasRefundSclear: 15000, GasCodeDeposit: 200,
GasBalance: 20, # Changed to 400 in Tangerine (EIP150)
GasCall: 40, # Changed to 700 in Tangerine (EIP150) GasCall: 40, # Changed to 700 in Tangerine (EIP150)
GasCallValue: 9000,
GasCallStipend: 2300,
GasNewAccount: 25_000,
GasExp: 10, GasExp: 10,
GasSHA3: 30 GasExpByte: 10, # Changed to 50 in Spurious Dragon (EIP160)
] GasMemory: 3,
GasTXCreate: 32000,
proc tangerineGasCosts(baseCosts: GasCosts): GasCosts = GasTXDataZero: 4,
GasTXDataNonZero: 68,
GasTransaction: 21000,
GasLog: 375,
GasLogData: 8,
GasLogTopic: 375,
GasSha3: 30,
GasSha3Word: 6,
GasCopy: 3,
GasBlockhash: 20
# GasQuadDivisor: 100 # Unused, do not confuse with the quadratic coefficient 512 for memory expansion
]
# Create the schedule for each forks
func tangerineGasFees(previous_fees: GasFeeSchedule): GasFeeSchedule =
# https://github.com/ethereum/EIPs/blob/master/EIPS/eip-150.md # https://github.com/ethereum/EIPs/blob/master/EIPS/eip-150.md
result = baseCosts result = previous_fees
result[GasSload] = 200 result[GasSload] = 200
result[GasSelfDestruct] = 5000 result[GasSelfDestruct] = 5000
result[GasBalance] = 400 result[GasBalance] = 400
result[GasCall] = 40 result[GasCall] = 40
const TangerineGasCosts* = BaseGasCosts.tangerineGasCosts func spuriousGasFees(previous_fees: GasFeeSchedule): GasFeeSchedule =
# https://github.com/ethereum/EIPs/blob/master/EIPS/eip-160.md
result = previous_fees
result[GasExpByte] = 50
const
TangerineGasFees = BaseGasFees.tangerineGasFees
SpuriousGasFees = TangerineGasFees.spuriousGasFees
gasCosts(BaseGasFees, base, BaseGasCosts)
gasCosts(TangerineGasFees, tangerine, TangerineGasCosts)

View File

@ -9,10 +9,10 @@ import
tables, tables,
constants, vm_state, constants, vm_state,
opcode_values, stint, eth_common, opcode_values, stint, eth_common,
vm / [code_stream, memory, stack], vm / [code_stream, memory, stack, forks/gas_costs],
./logging ./logging
export GasInt export GasInt, gas_costs
type type
BaseComputation* = ref object of RootObj BaseComputation* = ref object of RootObj
@ -30,9 +30,9 @@ type
logEntries*: seq[(EthAddress, seq[UInt256], string)] logEntries*: seq[(EthAddress, seq[UInt256], string)]
shouldEraseReturnData*: bool shouldEraseReturnData*: bool
accountsToDelete*: Table[EthAddress, EthAddress] accountsToDelete*: Table[EthAddress, EthAddress]
opcodes*: Table[Op, Opcode] # TODO array[Op, Opcode] opcodes*: Table[Op, proc(computation: var BaseComputation){.nimcall.}]
precompiles*: Table[string, Opcode] precompiles*: Table[string, Opcode]
gasCosts*: GasCosts # TODO separate opcode processing and gas computation gasCosts*: GasCosts # TODO - avoid allocating memory for this const
Error* = ref object Error* = ref object
info*: string info*: string
@ -40,16 +40,10 @@ type
erasesReturnData*: bool erasesReturnData*: bool
Opcode* = ref object of RootObj Opcode* = ref object of RootObj
# TODO can't use a stack-allocated object because
# "BaseComputation is not a concrete type"
# TODO: We can probably remove this.
kind*: Op kind*: Op
#of VARIABLE_GAS_COST_OPS:
# gasCostHandler*: proc(computation: var BaseComputation): UInt256
## so, we could have special logic that separates all gas cost calculations
## from actual opcode execution
## that's what parity does:
## 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
gasCostKind*: GasCostKind
runLogic*: proc(computation: var BaseComputation) runLogic*: proc(computation: var BaseComputation)
GasMeter* = ref object GasMeter* = ref object
@ -58,30 +52,6 @@ type
startGas*: GasInt startGas*: GasInt
gasRemaining*: GasInt gasRemaining*: GasInt
GasCostKind* = enum
GasZero
GasBase
GasVeryLow
GasLow
GasMid
GasHigh
GasSload
GasJumpDest
GasSset
GasSreset
GasExtCode
GasCoinbase
GasSelfDestruct
GasInHandler
GasRefundSclear
GasBalance
GasCall
GasExp
GasSHA3
GasCosts* = array[GasCostKind, GasInt]
Message* = ref object Message* = ref object
# A message for VM computation # A message for VM computation

View File

@ -9,6 +9,6 @@ import ./test_code_stream,
./test_gas_meter, ./test_gas_meter,
./test_memory, ./test_memory,
./test_stack, ./test_stack,
./test_opcode, ./test_opcode
./test_vm # ./test_vm
# ./test_vm_json # ./test_vm_json

View File

@ -42,7 +42,7 @@ proc testCode(code: string, initialGas: GasInt, blockNum: UInt256): BaseComputat
c.displayDecompiled() c.displayDecompiled()
var computation = newBaseComputation(vm.state, message) var computation = newBaseComputation(vm.state, message)
computation.opcodes = OPCODE_TABLE computation.opcodes = OpLogic # TODO remove this need
computation.precompiles = initTable[string, Opcode]() computation.precompiles = initTable[string, Opcode]()
computation = computation.applyComputation(vm.state, message) computation = computation.applyComputation(vm.state, message)

View File

@ -30,7 +30,7 @@ suite "VM":
# check(not computation.isError) # check(not computation.isError)
let let
txGas = tx.gasPrice * constants.GAS_TX # txGas = tx.gasPrice * constants.GAS_TX
state_db = vm.state.readOnlyStateDB state_db = vm.state.readOnlyStateDB
b = vm.`block` b = vm.`block`

View File

@ -9,12 +9,14 @@ import
unittest, strformat, strutils, sequtils, tables, stint, json, ospaths, times, unittest, strformat, strutils, sequtils, tables, stint, json, ospaths, times,
./test_helpers, ./test_helpers,
../nimbus/[constants, errors, logging], ../nimbus/[constants, errors, logging],
../nimbus/[chain, vm_state, computation, opcode, vm_types, opcode_table], ../nimbus/[chain, vm_state, computation, opcode, vm_types],
../nimbus/utils/[header, padding], ../nimbus/utils/[header, padding],
../nimbus/vm/[gas_meter, message, code_stream, stack], ../nimbus/vm/[gas_meter, message, code_stream, stack],
../nimbus/vm/forks/vm_forks, ../nimbus/db/[db_chain, state_db, backends/memory_backend], ../nimbus/vm/forks/vm_forks, ../nimbus/db/[db_chain, state_db, backends/memory_backend],
eth_common eth_common
from ../nimbus/opcode_table import OpLogic
proc testFixture(fixtures: JsonNode, testStatusIMPL: var TestStatus) proc testFixture(fixtures: JsonNode, testStatusIMPL: var TestStatus)
suite "vm json tests": suite "vm json tests":
@ -58,7 +60,7 @@ proc testFixture(fixtures: JsonNode, testStatusIMPL: var TestStatus) =
c.displayDecompiled() c.displayDecompiled()
var computation = newBaseComputation(vm.state, message) var computation = newBaseComputation(vm.state, message)
computation.opcodes = OPCODE_TABLE computation.opcodes = OpLogic
computation.precompiles = initTable[string, Opcode]() computation.precompiles = initTable[string, Opcode]()
computation = computation.applyComputation(vm.state, message) computation = computation.applyComputation(vm.state, message)
@ -85,7 +87,7 @@ proc testFixture(fixtures: JsonNode, testStatusIMPL: var TestStatus) =
let expectedGasRemaining = fixture{"gas"}.getHexadecimalInt let expectedGasRemaining = fixture{"gas"}.getHexadecimalInt
let actualGasRemaining = gasMeter.gasRemaining let actualGasRemaining = gasMeter.gasRemaining
checkpoint(&"{actualGasRemaining} {expectedGasRemaining}") checkpoint(&"Remaining: {actualGasRemaining} - Expected: {expectedGasRemaining}")
check(actualGasRemaining == expectedGasRemaining or check(actualGasRemaining == expectedGasRemaining or
computation.code.hasSStore() and computation.code.hasSStore() and
(actualGasRemaining > expectedGasRemaining and (actualGasRemaining - expectedGasRemaining) mod 15_000 == 0 or (actualGasRemaining > expectedGasRemaining and (actualGasRemaining - expectedGasRemaining) mod 15_000 == 0 or