Gas & Memory ops refactoring - uses native integer instead of uint256 fixes #35 and #39 (#40)

* Gas refactoring - uses int64

* Use primitive int in test_vm_json
This commit is contained in:
Mamy Ratsimbazafy 2018-05-25 12:25:19 +02:00 committed by GitHub
parent c77b7cd035
commit cb49352b6a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
21 changed files with 263 additions and 244 deletions

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, strutils, sequtils, tables, macros, stint, terminal, strformat, strutils, sequtils, tables, macros, stint, terminal, math,
constants, errors, utils/hexadecimal, utils_numeric, validation, vm_state, logging, opcode_values, types, 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],
@ -15,7 +15,7 @@ import
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: UInt256): UInt256 = proc memoryGasCost*(sizeInBytes: Natural): GasInt =
let let
sizeInWords = ceil32(sizeInBytes) div 32 sizeInWords = ceil32(sizeInBytes) div 32
linearCost = sizeInWords * GAS_MEMORY linearCost = sizeInWords * GAS_MEMORY
@ -86,7 +86,7 @@ method shouldEraseReturnData*(c: BaseComputation): bool =
method prepareChildMessage*( method prepareChildMessage*(
c: var BaseComputation, c: var BaseComputation,
gas: UInt256, gas: GasInt,
to: string, to: string,
value: UInt256, value: UInt256,
data: seq[byte], data: seq[byte],
@ -105,13 +105,10 @@ method prepareChildMessage*(
code, code,
childOptions) childOptions)
method extendMemory*(c: var BaseComputation, startPosition: UInt256, size: UInt256) = method extendMemory*(c: var BaseComputation, startPosition: Natural, size: Natural) =
# Memory Management # Memory Management
#
# validate_uint256(start_position, title="Memory start position")
# validate_uint256(size, title="Memory size")
let beforeSize = ceil32(len(c.memory).u256) let beforeSize = ceil32(len(c.memory))
let afterSize = ceil32(startPosition + size) let afterSize = ceil32(startPosition + size)
let beforeCost = memoryGasCost(beforeSize) let beforeCost = memoryGasCost(beforeSize)
@ -201,21 +198,21 @@ method getLogEntries*(c: BaseComputation): seq[(string, seq[UInt256], string)] =
else: else:
result = @[] result = @[]
method getGasRefund*(c: BaseComputation): UInt256 = method getGasRefund*(c: BaseComputation): GasInt =
if c.isError: if c.isError:
result = 0.u256 result = 0
else: else:
result = c.gasMeter.gasRefunded + c.children.mapIt(it.getGasRefund()).foldl(a + b, 0.u256) result = c.gasMeter.gasRefunded + c.children.mapIt(it.getGasRefund()).foldl(a + b, 0'i64)
method getGasUsed*(c: BaseComputation): UInt256 = method getGasUsed*(c: BaseComputation): GasInt =
if c.shouldBurnGas: if c.shouldBurnGas:
result = c.msg.gas result = c.msg.gas
else: else:
result = max(0.u256, c.msg.gas - c.gasMeter.gasRemaining) result = max(0, c.msg.gas - c.gasMeter.gasRemaining)
method getGasRemaining*(c: BaseComputation): UInt256 = method getGasRemaining*(c: BaseComputation): GasInt =
if c.shouldBurnGas: if c.shouldBurnGas:
result = 0.u256 result = 0
else: else:
result = c.gasMeter.gasRemaining result = c.gasMeter.gasRemaining

View File

@ -46,13 +46,13 @@ proc `==`*(a: UInt256, b: int): bool =
proc `!=`*(a: UInt256, b: int): bool = proc `!=`*(a: UInt256, b: int): bool =
a != b.u256 a != b.u256
proc `^`*(base: int; exp: int): UInt256 = # proc `^`*(base: int; exp: int): UInt256 =
let base = base.u256 # let base = base.u256
var ex = exp # var ex = exp
result = 1.u256 # result = 1.u256
while ex > 0: # while ex > 0:
result = result * base # result = result * base
dec(ex) # dec(ex)
proc `^`*(left: Int256, right: int): Int256 = proc `^`*(left: Int256, right: int): Int256 =
var value = right.i256 var value = right.i256
@ -123,7 +123,7 @@ let
INT_256_MAX_AS_UINT256* = cast[Uint256](high(Int256)) INT_256_MAX_AS_UINT256* = cast[Uint256](high(Int256))
NULLBYTE* = "\x00" NULLBYTE* = "\x00"
EMPTYWORD* = repeat(NULLBYTE, 32) EMPTYWORD* = repeat(NULLBYTE, 32)
UINT160CEILING*: UInt256 = 2 ^ 160 UINT160CEILING*: UInt256 = 2.u256 ^ 160
CREATE_CONTRACT_ADDRESS* = "" CREATE_CONTRACT_ADDRESS* = ""
ZERO_ADDRESS* = repeat("\x00", 20) ZERO_ADDRESS* = repeat("\x00", 20)
ZERO_HASH32* = repeat("\x00", 20) ZERO_HASH32* = repeat("\x00", 20)
@ -147,34 +147,34 @@ let
# GAS_SLOAD_COST* = 20.u256 # GAS_SLOAD_COST* = 20.u256
# GAS_SELF_DESTRUCT_COST* = 0.u256 # GAS_SELF_DESTRUCT_COST* = 0.u256
# GAS_IN_HANDLER* = 0.u256 # to be calculated in handler # GAS_IN_HANDLER* = 0.u256 # to be calculated in handler
REFUND_SCLEAR* = 15_000.u256 REFUND_SCLEAR* = 15_000
# GAS_SELF_DESTRUCT* = 0.u256 # GAS_SELF_DESTRUCT* = 0.u256
# GAS_SELF_DESTRUCT_NEW_ACCOUNT* = 25_000.u256 # GAS_SELF_DESTRUCT_NEW_ACCOUNT* = 25_000.u256
GAS_CREATE* = 32_000.u256 GAS_CREATE* = 32_000.u256
# GAS_CALL* = 40.u256 # GAS_CALL* = 40.u256
GAS_CALL_VALUE* = 9_000.u256 GAS_CALL_VALUE* = 9_000
GAS_CALL_STIPEND* = 2_300.u256 GAS_CALL_STIPEND* = 2_300
GAS_NEW_ACCOUNT* = 25_000.u256 GAS_NEW_ACCOUNT* = 25_000
# GAS_COST_BALANCE* = 400.u256 # TODO: this is post-eip150, see also GAS_BALANCE # GAS_COST_BALANCE* = 400.u256 # TODO: this is post-eip150, see also GAS_BALANCE
# GAS_EXP* = 10.u256 # GAS_EXP* = 10.u256
# GAS_EXP_BYTE* = 10.u256 # GAS_EXP_BYTE* = 10.u256
GAS_MEMORY* = 3.u256 GAS_MEMORY* = 3
GAS_TX_CREATE* = 32_000.u256 GAS_TX_CREATE* = 32_000.u256
GAS_TX_DATA_ZERO* = 4.u256 GAS_TX_DATA_ZERO* = 4.u256
GAS_TX_DATA_NON_ZERO* = 68.u256 GAS_TX_DATA_NON_ZERO* = 68.u256
GAS_TX* = 21_000.u256 GAS_TX* = 21_000.u256
GAS_LOG* = 375.u256 GAS_LOG* = 375.u256
GAS_LOG_DATA* = 8.u256 GAS_LOG_DATA* = 8
GAS_LOG_TOPIC* = 375.u256 GAS_LOG_TOPIC* = 375
# GAS_SHA3* = 30.u256 # GAS_SHA3* = 30.u256
GAS_SHA3_WORD* = 6.u256 GAS_SHA3_WORD* = 6
GAS_COPY* = 3.u256 GAS_COPY* = 3
GAS_BLOCK_HASH* = 20.u256 GAS_BLOCK_HASH* = 20.u256
GAS_CODE_DEPOSIT* = 200.u256 GAS_CODE_DEPOSIT* = 200.u256
GAS_MEMORY_QUADRATIC_DENOMINATOR* = 512.u256 GAS_MEMORY_QUADRATIC_DENOMINATOR* = 512
GAS_SHA256* = 60.u256 GAS_SHA256* = 60.u256
GAS_SHA256WORD* = 12.u256 GAS_SHA256WORD* = 12.u256
GAS_RIP_EMD160* = 600.u256 GAS_RIP_EMD160* = 600.u256
@ -188,7 +188,7 @@ let
GAS_ECPAIRING_PER_POINT* = 80_000.u256 GAS_ECPAIRING_PER_POINT* = 80_000.u256
GAS_LIMIT_EMA_DENOMINATOR* = 1_024.u256 GAS_LIMIT_EMA_DENOMINATOR* = 1_024.u256
GAS_LIMIT_ADJUSTMENT_FACTOR* = 1_024.u256 GAS_LIMIT_ADJUSTMENT_FACTOR* = 1_024.u256
GAS_LIMIT_MAXIMUM*: UInt256 = 2 ^ 63 - 1.u256 GAS_LIMIT_MAXIMUM* = high(int64)
GAS_LIMIT_USAGE_ADJUSTMENT_NUMERATOR* = 3.u256 GAS_LIMIT_USAGE_ADJUSTMENT_NUMERATOR* = 3.u256
GAS_LIMIT_USAGE_ADJUSTMENT_DENOMINATOR* = 2.u256 GAS_LIMIT_USAGE_ADJUSTMENT_DENOMINATOR* = 2.u256
@ -206,7 +206,7 @@ let
MAX_UNCLE_DEPTH* = 6.u256 MAX_UNCLE_DEPTH* = 6.u256
MAX_UNCLES* = 2.u256 MAX_UNCLES* = 2.u256
SECPK1_P*: UInt256 = 2 ^ 256 - 2 ^ 32 - 977.u256 SECPK1_P*: UInt256 = 2.u256 ^ 256 - 2.u256 ^ 32 - 977.u256
SECPK1_N*: UInt256 = "115792089237316195423570985008687907852837564279074904382605163141518161494337".u256 SECPK1_N*: UInt256 = "115792089237316195423570985008687907852837564279074904382605163141518161494337".u256
SECPK1_A* = 0.u256 SECPK1_A* = 0.u256
SECPK1_B* = 7.u256 SECPK1_B* = 7.u256

View File

@ -92,7 +92,7 @@ proc exp*(computation: var BaseComputation) =
var gasCost = computation.gasCosts[GasExp] var gasCost = computation.gasCosts[GasExp]
if not exponent.isZero: if not exponent.isZero:
gasCost += gasCost * (one(Uint256) + log256(exponent)) gasCost += gasCost * (1 + log256(exponent))
computation.gasMeter.consumeGas(gasCost, 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

View File

@ -39,13 +39,13 @@ type
using using
computation: var BaseComputation computation: var BaseComputation
method msgExtraGas*(call: BaseCall, computation; gas: UInt256, to: string, value: UInt256): UInt256 {.base.} = method msgExtraGas*(call: BaseCall, computation; gas: GasInt, to: string, value: UInt256): GasInt {.base.} =
raise newException(NotImplementedError, "Must be implemented by subclasses") raise newException(NotImplementedError, "Must be implemented by subclasses")
method msgGas*(call: BaseCall, computation; gas: UInt256, to: string, value: UInt256): (UInt256, UInt256) {.base.} = method msgGas*(call: BaseCall, computation; gas: GasInt, to: string, value: UInt256): (GasInt, GasInt) {.base.} =
let extraGas = call.msgExtraGas(computation, gas, to, value) let extraGas = call.msgExtraGas(computation, gas, to, value)
let totalFee = gas + extraGas let totalFee = gas + extraGas
let childMsgGas = gas + (if value != 0: GAS_CALL_STIPEND else: 0.u256) let childMsgGas = gas + (if value != 0: GAS_CALL_STIPEND else: 0)
(childMsgGas, totalFee) (childMsgGas, totalFee)
method callParams*(call: BaseCall, computation): (UInt256, UInt256, string, string, string, UInt256, UInt256, UInt256, UInt256, bool, bool) {.base.} = method callParams*(call: BaseCall, computation): (UInt256, UInt256, string, string, string, UInt256, UInt256, UInt256, UInt256, bool, bool) {.base.} =
@ -60,11 +60,13 @@ method runLogic*(call: BaseCall, computation) =
shouldTransferValue, shouldTransferValue,
isStatic) = call.callParams(computation) isStatic) = call.callParams(computation)
computation.extendMemory(memoryInputStartPosition, memoryInputSize) let (memInPos, memInLen, memOutPos, memOutLen) = (memoryInputStartPosition.toInt, memoryInputSize.toInt, memoryOutputStartPosition.toInt, memoryOutputSize.toInt)
computation.extendMemory(memoryOutputStartPosition, memoryOutputSize)
let callData = computation.memory.read(memoryInputStartPosition, memoryInputSize) computation.extendMemory(memInPos, memInLen)
let (childMsgGas, childMsgGasFee) = call.msgGas(computation, gas, to, value) computation.extendMemory(memOutPos, memOutLen)
let callData = computation.memory.read(memInPos, memInLen)
let (childMsgGas, childMsgGasFee) = call.msgGas(computation, gas.toInt, to, value)
computation.gasMeter.consumeGas(childMsgGasFee, reason = $call.kind) computation.gasMeter.consumeGas(childMsgGasFee, reason = $call.kind)
# TODO: Pre-call checks # TODO: Pre-call checks
@ -116,22 +118,22 @@ method runLogic*(call: BaseCall, computation) =
else: else:
computation.stack.push(1.u256) computation.stack.push(1.u256)
if not childComputation.shouldEraseReturnData: if not childComputation.shouldEraseReturnData:
let actualOutputSize = min(memoryOutputSize, childComputation.output.len.u256) let actualOutputSize = min(memOutLen, childComputation.output.len)
computation.memory.write( computation.memory.write(
memoryOutputStartPosition, memOutPos,
actualOutputSize, actualOutputSize,
childComputation.output.toBytes[0 ..< actualOutputSize.toInt]) childComputation.output.toBytes[0 ..< actualOutputSize])
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: UInt256, to: string, value: UInt256): UInt256 = method msgExtraGas(call: Call, computation; gas: GasInt, to: string, value: UInt256): GasInt =
# TODO: db # TODO: db
# with computation.vm_state.state_db(read_only=True) as state_db: # with computation.vm_state.state_db(read_only=True) as state_db:
# let accountExists = db.accountExists(to) # let accountExists = db.accountExists(to)
let accountExists = false let accountExists = false
let transferGasFee = if value != 0: GAS_CALL_VALUE else: 0.u256 let transferGasFee = if value != 0: GAS_CALL_VALUE else: 0
let createGasFee = if not accountExists: GAS_NEW_ACCOUNT else: 0.u256 let createGasFee = if not accountExists: GAS_NEW_ACCOUNT else: 0
transferGasFee + createGasFee transferGasFee + createGasFee
method callParams(call: CallCode, computation): (UInt256, UInt256, string, string, string, UInt256, UInt256, UInt256, UInt256, bool, bool) = method callParams(call: CallCode, computation): (UInt256, UInt256, string, string, string, UInt256, UInt256, UInt256, UInt256, bool, bool) =
@ -154,8 +156,8 @@ method callParams(call: CallCode, computation): (UInt256, UInt256, string, strin
true, # should_transfer_value, true, # should_transfer_value,
computation.msg.isStatic) computation.msg.isStatic)
method msgExtraGas(call: CallCode, computation; gas: UInt256, to: string, value: UInt256): UInt256 = method msgExtraGas(call: CallCode, computation; gas: GasInt, to: string, value: UInt256): GasInt =
if value != 0: GAS_CALL_VALUE else: 0.u256 if value != 0: GAS_CALL_VALUE else: 0
method callParams(call: Call, computation): (UInt256, UInt256, string, string, string, UInt256, UInt256, UInt256, UInt256, bool, bool) = method callParams(call: Call, computation): (UInt256, UInt256, string, string, string, UInt256, UInt256, UInt256, UInt256, bool, bool) =
let gas = computation.stack.popInt() let gas = computation.stack.popInt()
@ -180,11 +182,11 @@ method callParams(call: Call, computation): (UInt256, UInt256, string, string, s
true, # should_transfer_value, true, # should_transfer_value,
computation.msg.isStatic) computation.msg.isStatic)
method msgGas(call: DelegateCall, computation; gas: UInt256, to: string, value: UInt256): (UInt256, UInt256) = method msgGas(call: DelegateCall, computation; gas: GasInt, to: string, value: UInt256): (GasInt, GasInt) =
(gas, gas) (gas, gas)
method msgExtraGas(call: DelegateCall, computation; gas: UInt256, to: string, value: UInt256): UInt256 = method msgExtraGas(call: DelegateCall, computation; gas: GasInt, to: string, value: UInt256): GasInt =
0.u256 0
method callParams(call: DelegateCall, computation): (UInt256, UInt256, string, string, string, UInt256, UInt256, UInt256, UInt256, bool, bool) = method callParams(call: DelegateCall, computation): (UInt256, UInt256, string, string, string, UInt256, UInt256, UInt256, UInt256, bool, bool) =
let gas = computation.stack.popInt() let gas = computation.stack.popInt()
@ -209,30 +211,30 @@ method callParams(call: DelegateCall, computation): (UInt256, UInt256, string, s
false, # should_transfer_value, false, # should_transfer_value,
computation.msg.isStatic) computation.msg.isStatic)
proc maxChildGasEIP150*(gas: UInt256): UInt256 = proc maxChildGasEIP150*(gas: GasInt): GasInt =
gas - gas div 64 gas - gas div 64
proc computeEIP150MsgGas(computation; gas: UInt256, extraGas: UInt256, value: UInt256, name: string, callStipend: UInt256): (UInt256, UInt256) = proc computeEIP150MsgGas(computation; gas, extraGas: GasInt, value: UInt256, name: string, callStipend: GasInt): (GasInt, GasInt) =
if computation.gasMeter.gasRemaining < extraGas: if computation.gasMeter.gasRemaining < extraGas:
raise newException(OutOfGas, &"Out of gas: Needed {extraGas} - Remaining {computation.gasMeter.gasRemaining} - Reason: {name}") raise newException(OutOfGas, &"Out of gas: Needed {extraGas} - Remaining {computation.gasMeter.gasRemaining} - Reason: {name}")
let gas = min(gas, maxChildGasEIP150(computation.gasMeter.gasRemaining - extraGas)) let gas = min(gas, maxChildGasEIP150(computation.gasMeter.gasRemaining - extraGas))
let totalFee = gas + extraGas let totalFee = gas + extraGas
let childMsgGas = gas + (if value != 0: callStipend else: 0.u256) let childMsgGas = gas + (if value != 0: callStipend else: 0)
(childMsgGas, totalFee) (childMsgGas, totalFee)
method msgGas(call: CallEIP150, computation; gas: UInt256, to: string, value: UInt256): (UInt256, UInt256) = method msgGas(call: CallEIP150, computation; gas: GasInt, to: string, value: UInt256): (GasInt, GasInt) =
let extraGas = call.msgExtraGas(computation, gas, to, value) let extraGas = call.msgExtraGas(computation, gas, to, value)
computeEIP150MsgGas(computation, gas, extraGas, value, $call.kind, GAS_CALL_STIPEND) computeEIP150MsgGas(computation, gas, extraGas, value, $call.kind, GAS_CALL_STIPEND)
method msgGas(call: CallCodeEIP150, computation; gas: UInt256, to: string, value: UInt256): (UInt256, UInt256) = method msgGas(call: CallCodeEIP150, computation; gas: GasInt, to: string, value: UInt256): (GasInt, GasInt) =
let extraGas = call.msgExtraGas(computation, gas, to, value) let extraGas = call.msgExtraGas(computation, gas, to, value)
computeEIP150MsgGas(computation, gas, extraGas, value, $call.kind, GAS_CALL_STIPEND) computeEIP150MsgGas(computation, gas, extraGas, value, $call.kind, GAS_CALL_STIPEND)
method msgGas(call: DelegateCallEIP150, computation; gas: UInt256, to: string, value: UInt256): (UInt256, UInt256) = method msgGas(call: DelegateCallEIP150, computation; gas: GasInt, to: string, value: UInt256): (GasInt, GasInt) =
let extraGas = call.msgExtraGas(computation, gas, to, value) let extraGas = call.msgExtraGas(computation, gas, to, value)
computeEIP150MsgGas(computation, gas, extraGas, value, $call.kind, 0.u256) computeEIP150MsgGas(computation, gas, extraGas, value, $call.kind, 0)
proc msgExtraGas*(call: CallEIP161, computation; gas: UInt256, to: string, value: UInt256): UInt256 = proc msgExtraGas*(call: CallEIP161, computation; gas: GasInt, to: string, value: UInt256): GasInt =
# 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:
# account_is_dead = ( # account_is_dead = (
@ -240,8 +242,8 @@ proc msgExtraGas*(call: CallEIP161, computation; gas: UInt256, to: string, value
# state_db.account_is_empty(to)) # state_db.account_is_empty(to))
let accountIsDead = true let accountIsDead = true
let transferGasFee = if value != 0: GAS_CALL_VALUE else: 0.u256 let transferGasFee = if value != 0: GAS_CALL_VALUE else: 0
let createGasFee = if accountIsDead and value != 0: GAS_NEW_ACCOUNT else: 0.u256 let createGasFee = if accountIsDead and value != 0: GAS_NEW_ACCOUNT else: 0
transferGasFee + createGasFee transferGasFee + createGasFee

View File

@ -47,14 +47,15 @@ proc callDataCopy*(computation: var BaseComputation) =
let (memStartPosition, let (memStartPosition,
calldataStartPosition, calldataStartPosition,
size) = computation.stack.popInt(3) size) = computation.stack.popInt(3)
computation.extendMemory(memStartPosition, size) let (memPos, callPos, len) = (memStartPosition.toInt, calldataStartPosition.toInt, size.toInt)
computation.extendMemory(memPos, len)
let wordCount = ceil32(size) div 32 let wordCount = ceil32(len) div 32
let copyGasCost = wordCount * constants.GAS_COPY let copyGasCost = wordCount * constants.GAS_COPY
computation.gasMeter.consumeGas(copyGasCost, reason="CALLDATACOPY fee") computation.gasMeter.consumeGas(copyGasCost, reason="CALLDATACOPY fee")
let value = computation.msg.data[calldataStartPosition.toInt ..< (calldataStartPosition + size).toInt] let value = computation.msg.data[callPos ..< callPos + len]
let paddedValue = padRight(value, size.toInt, 0.byte) let paddedValue = padRight(value, len, 0.byte)
computation.memory.write(memStartPosition, size, paddedValue) computation.memory.write(memPos, len, paddedValue)
proc codesize*(computation: var BaseComputation) = proc codesize*(computation: var BaseComputation) =
@ -66,8 +67,10 @@ proc codecopy*(computation: var BaseComputation) =
let (memStartPosition, let (memStartPosition,
codeStartPosition, codeStartPosition,
size) = computation.stack.popInt(3) size) = computation.stack.popInt(3)
computation.extendMemory(memStartPosition, size) let (memPos, codePos, len) = (memStartPosition.toInt, codeStartPosition.toInt, size.toInt)
let wordCount = ceil32(size) div 32 computation.extendMemory(memPos, len)
let wordCount = ceil32(len) div 32
let copyGasCost = constants.GAS_COPY * wordCount let copyGasCost = constants.GAS_COPY * wordCount
computation.gasMeter.consumeGas(copyGasCost, reason="CODECOPY: word gas cost") computation.gasMeter.consumeGas(copyGasCost, reason="CODECOPY: word gas cost")
@ -79,7 +82,7 @@ proc codecopy*(computation: var BaseComputation) =
proc gasprice*(computation: var BaseComputation) = proc gasprice*(computation: var BaseComputation) =
computation.stack.push(computation.msg.gasPrice) computation.stack.push(computation.msg.gasPrice.u256)
proc extCodeSize*(computation: var BaseComputation) = proc extCodeSize*(computation: var BaseComputation) =
@ -93,8 +96,9 @@ proc extCodeSize*(computation: var BaseComputation) =
proc extCodeCopy*(computation: var BaseComputation) = proc extCodeCopy*(computation: var BaseComputation) =
let account = forceBytesToAddress(computation.stack.popString) let account = forceBytesToAddress(computation.stack.popString)
let (memStartPosition, codeStartPosition, size) = computation.stack.popInt(3) let (memStartPosition, codeStartPosition, size) = computation.stack.popInt(3)
computation.extendMemory(memStartPosition, size) let (memPos, codePos, len) = (memStartPosition.toInt, codeStartPosition.toInt, size.toInt)
let wordCount = ceil32(size) div 32 computation.extendMemory(memPos, len)
let wordCount = ceil32(len) div 32
let copyGasCost = constants.GAS_COPY * wordCount let copyGasCost = constants.GAS_COPY * wordCount
computation.gasMeter.consumeGas(copyGasCost, reason="EXTCODECOPY: word gas cost") computation.gasMeter.consumeGas(copyGasCost, reason="EXTCODECOPY: word gas cost")
@ -112,15 +116,16 @@ 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)
if returnDataStartPosition + size > computation.returnData.len: let (memPos, returnPos, len) = (memStartPosition.toInt, returnDataStartPosition.toInt, size.toInt)
if returnPos + len > computation.returnData.len:
raise newException(OutOfBoundsRead, raise newException(OutOfBoundsRead,
"Return data length is not sufficient to satisfy request. Asked \n" & "Return data length is not sufficient to satisfy request. Asked \n" &
&"for data from index {returnDataStartPosition} to {returnDataStartPosition + size}. Return data is {computation.returnData.len} in \n" & &"for data from index {returnDataStartPosition} to {returnDataStartPosition + size}. Return data is {computation.returnData.len} in \n" &
"length") "length")
computation.extendMemory(memStartPosition, size) computation.extendMemory(memPos, len)
let wordCount = ceil32(size) div 32 let wordCount = ceil32(len) div 32
let copyGasCost = wordCount * constants.GAS_COPY let copyGasCost = wordCount * constants.GAS_COPY
computation.gasMeter.consumeGas(copyGasCost, reason="RETURNDATACOPY fee") computation.gasMeter.consumeGas(copyGasCost, reason="RETURNDATACOPY fee")
let value = ($computation.returnData)[returnDataStartPosition.toInt ..< (returnDataStartPosition + size).toInt] let value = ($computation.returnData)[returnPos ..< returnPos + len]
computation.memory.write(memStartPosition, size, value) computation.memory.write(memPos, len, value)

View File

@ -55,4 +55,4 @@ proc pc*(computation) =
proc gas*(computation) = proc gas*(computation) =
let gasRemaining = gasMeter.gasRemaining let gasRemaining = gasMeter.gasRemaining
stack.push(gasRemaining) stack.push(gasRemaining.u256)

View File

@ -17,18 +17,19 @@ using
macro logXX(topicCount: static[int]): untyped = macro logXX(topicCount: static[int]): untyped =
if topicCount < 0 or topicCount > 4: if topicCount < 0 or topicCount > 4:
error(&"Invalid log topic size {topicCount} Must be 0, 1, 2, 3, or 4") error(&"Invalid log topic len {topicCount} Must be 0, 1, 2, 3, or 4")
return return
let name = ident(&"log{topicCount}") let name = ident(&"log{topicCount}")
let computation = ident("computation") let computation = ident("computation")
let topics = ident("topics") let topics = ident("topics")
let topicsTuple = ident("topicsTuple") let topicsTuple = ident("topicsTuple")
let size = ident("size") let len = ident("len")
let memStartPosition = ident("memStartPosition") let memPos = ident("memPos")
result = quote: result = quote:
proc `name`*(`computation`: var BaseComputation) = proc `name`*(`computation`: var BaseComputation) =
let (`memStartPosition`, `size`) = `computation`.stack.popInt(2) let (memStartPosition, size) = `computation`.stack.popInt(2)
let (`memPos`, `len`) = (memStartPosition.toInt, size.toInt)
var `topics`: seq[UInt256] var `topics`: seq[UInt256]
var topicCode: NimNode var topicCode: NimNode
@ -50,12 +51,12 @@ macro logXX(topicCount: static[int]): untyped =
result.body.add(topicCode) result.body.add(topicCode)
let logicCode = quote: let logicCode = quote:
let dataGasCost = constants.GAS_LOG_DATA * `size` let dataGasCost = constants.GAS_LOG_DATA * `len`
let topicGasCost = constants.GAS_LOG_TOPIC * `topicCount`.u256 let topicGasCost = constants.GAS_LOG_TOPIC * `topicCount`
let totalGasCost = dataGasCost + topicGasCost let totalGasCost = dataGasCost + topicGasCost
`computation`.gasMeter.consumeGas(totalGasCost, reason="Log topic and data gas cost") `computation`.gasMeter.consumeGas(totalGasCost, reason="Log topic and data gas cost")
`computation`.extendMemory(`memStartPosition`, `size`) `computation`.extendMemory(`memPos`, `len`)
let logData = `computation`.memory.read(`memStartPosition`, `size`).toString let logData = `computation`.memory.read(`memPos`, `len`).toString
`computation`.addLogEntry( `computation`.addLogEntry(
account=`computation`.msg.storageAddress, account=`computation`.msg.storageAddress,
topics=`topics`, topics=`topics`,

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, ../types, .. / vm / [stack, memory], .. / utils / [padding, bytes] ../constants, ../computation, ../types, .. / vm / [stack, memory], .. / utils / [padding, bytes],
stint
{.this: computation.} {.this: computation.}
@ -16,14 +17,14 @@ using
computation: var BaseComputation computation: var BaseComputation
proc mstoreX(computation; x: int) = proc mstoreX(computation; x: int) =
let start = stack.popInt() let start = stack.popInt().toInt
let value = stack.popBinary() let value = stack.popBinary()
let paddedValue = padLeft(value, x, 0.byte) let paddedValue = padLeft(value, x, 0.byte)
let normalizedValue = paddedValue[^x .. ^1] let normalizedValue = paddedValue[^x .. ^1]
extendMemory(start, x.u256) extendMemory(start, x)
memory.write(start, 32.u256, normalizedValue) memory.write(start, 32, normalizedValue)
# TODO template handler # TODO template handler
@ -34,11 +35,11 @@ proc mstore8*(computation) =
mstoreX(1) mstoreX(1)
proc mload*(computation) = proc mload*(computation) =
let start = stack.popInt() let start = stack.popInt().toInt
extendMemory(start, 32.u256) extendMemory(start, 32)
let value = memory.read(start, 32.u256) let value = memory.read(start, 32)
stack.push(value) stack.push(value)
proc msize*(computation) = proc msize*(computation) =

View File

@ -10,9 +10,10 @@ import
proc sha3op*(computation: var BaseComputation) = proc sha3op*(computation: var BaseComputation) =
let (startPosition, size) = computation.stack.popInt(2) let (startPosition, size) = computation.stack.popInt(2)
computation.extendMemory(startPosition, size) let (pos, len) = (startPosition.toInt, size.toInt)
let sha3Bytes = computation.memory.read(startPosition, size) computation.extendMemory(pos, len)
let wordCount = sha3Bytes.len.u256.ceil32 div 32 let sha3Bytes = computation.memory.read(pos, len)
let wordCount = sha3Bytes.len.ceil32 div 32 # TODO, can't we just shr instead of rounding + div
let gasCost = constants.GAS_SHA3_WORD * wordCount let gasCost = constants.GAS_SHA3_WORD * wordCount
computation.gasMeter.consumeGas(gasCost, reason="SHA3: word gas cost") computation.gasMeter.consumeGas(gasCost, reason="SHA3: word gas cost")
var res = keccak("") var res = keccak("")

View File

@ -29,7 +29,7 @@ proc sstore*(computation) =
let isCurrentlyEmpty = not existing # currentValue == 0 let isCurrentlyEmpty = not existing # currentValue == 0
let isGoingToBeEmpty = value == 0 let isGoingToBeEmpty = value == 0
let gasRefund = if isCurrentlyEmpty or not isGoingToBeEmpty: 0.u256 else: REFUND_SCLEAR let gasRefund = if isCurrentlyEmpty or not isGoingToBeEmpty: 0 else: REFUND_SCLEAR
let gasCost = if isCurrentlyEmpty and not isGoingToBeEmpty: GAS_SSET else: GAS_SRESET 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})") computation.gasMeter.consumeGas(computation.gasCosts[gasCost], &"SSTORE: {computation.msg.storageAddress}[slot] -> {value} ({currentValue})")

View File

@ -24,13 +24,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: UInt256): UInt256 {.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)
computation.extendMemory(startPosition, size) let (pos, len) = (startPosition.toInt, size.toInt)
computation.extendMemory(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:
@ -42,7 +43,7 @@ method runLogic*(create: Create, computation) =
# computation.stack.push(0) # computation.stack.push(0)
# return # return
let callData = computation.memory.read(startPosition, size) let callData = computation.memory.read(pos, len)
let createMsgGas = create.maxChildGasModifier(computation.gasMeter.gasRemaining) let createMsgGas = create.maxChildGasModifier(computation.gasMeter.gasRemaining)
computation.gasMeter.consumeGas(createMsgGas, reason="CREATE") computation.gasMeter.consumeGas(createMsgGas, reason="CREATE")
@ -82,7 +83,7 @@ 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: UInt256): UInt256 = method maxChildGasModifier(create: CreateEIP150, gas: GasInt): GasInt =
maxChildGasEIP150(gas) maxChildGasEIP150(gas)
method runLogic*(create: CreateByzantium, computation) = method runLogic*(create: CreateByzantium, computation) =
@ -142,15 +143,17 @@ proc selfdestruct(computation; beneficiary: string) =
proc returnOp*(computation) = proc returnOp*(computation) =
let (startPosition, size) = stack.popInt(2) let (startPosition, size) = stack.popInt(2)
computation.extendMemory(startPosition, size) let (pos, len) = (startPosition.toInt, size.toInt)
let output = memory.read(startPosition, size) computation.extendMemory(pos, len)
let output = memory.read(pos, len)
computation.output = output.toString computation.output = output.toString
raise newException(Halt, "RETURN") raise newException(Halt, "RETURN")
proc revert*(computation) = proc revert*(computation) =
let (startPosition, size) = stack.popInt(2) let (startPosition, size) = stack.popInt(2)
computation.extendMemory(startPosition, size) let (pos, len) = (startPosition.toInt, size.toInt)
let output = memory.read(startPosition, size).toString computation.extendMemory(pos, len)
let output = memory.read(pos, len).toString
computation.output = output computation.output = output
raise newException(Revert, $output) raise newException(Revert, $output)

View File

@ -9,7 +9,8 @@ import
tables, tables,
constants, vm_state, constants, vm_state,
opcode_values, stint, opcode_values, stint,
vm / [code_stream, gas_meter, memory, message, stack] vm / [code_stream, memory, stack],
./logging
type type
BaseComputation* = ref object of RootObj BaseComputation* = ref object of RootObj
@ -49,6 +50,16 @@ type
gasCostKind*: GasCostKind gasCostKind*: GasCostKind
runLogic*: proc(computation: var BaseComputation) runLogic*: proc(computation: var BaseComputation)
GasInt* = int64
## Type alias used for gas computation
# For reference - https://github.com/status-im/nimbus/issues/35#issuecomment-391726518
GasMeter* = ref object
logger*: Logger
gasRefunded*: GasInt
startGas*: GasInt
gasRemaining*: GasInt
GasCostKind* = enum GasCostKind* = enum
GasZero GasZero
GasBase GasBase
@ -71,4 +82,42 @@ type
GasExp GasExp
GasSHA3 GasSHA3
GasCosts* = array[GasCostKind, UInt256] GasCosts* = array[GasCostKind, GasInt]
Message* = ref object
# A message for VM computation
# depth = None
# code = None
# codeAddress = None
# createAddress = None
# shouldTransferValue = None
# isStatic = None
# logger = logging.getLogger("evm.vm.message.Message")
gas*: GasInt
gasPrice*: GasInt
to*: string
sender*: string
value*: UInt256
data*: seq[byte]
code*: string
internalOrigin*: string
internalCodeAddress*: string
depth*: int
internalStorageAddress*: string
shouldTransferValue*: bool
isStatic*: bool
isCreate*: bool
MessageOptions* = ref object
origin*: string
depth*: int
createAddress*: string
codeAddress*: string
shouldTransferValue*: bool
isStatic*: bool

View File

@ -36,8 +36,8 @@ proc bigEndianToInt*(value: Bytes): UInt256 =
# proc bitLength*(value: UInt256): int = # proc bitLength*(value: UInt256): int =
# 255 - value.countLeadingZeroBits # 255 - value.countLeadingZeroBits
proc log256*(value: UInt256): UInt256 = proc log256*(value: UInt256): Natural =
((255 - value.countLeadingZeroBits) div 8).u256 (255 - value.countLeadingZeroBits) div 8 # Compilers optimize to `shr 3`
# proc ceil8*(value: int): int = # proc ceil8*(value: int): int =
# let remainder = value mod 8 # let remainder = value mod 8
@ -68,12 +68,12 @@ proc pseudoSignedToUnsigned*(value: UInt256): UInt256 =
macro ceilXX(ceiling: static[int]): untyped = macro ceilXX(ceiling: static[int]): untyped =
var name = ident(&"ceil{ceiling}") var name = ident(&"ceil{ceiling}")
result = quote: result = quote:
proc `name`*(value: UInt256): UInt256 = proc `name`*(value: Natural): Natural =
var remainder = value mod `ceiling`.u256 var remainder = value mod `ceiling`
if remainder == 0: if remainder == 0:
return value return value
else: else:
return value + `ceiling`.u256 - remainder return value + `ceiling` - remainder
ceilXX(32) ceilXX(32)

View File

@ -10,36 +10,36 @@ import
# TODO: Make that computation at compile-time. # TODO: Make that computation at compile-time.
# Go-Ethereum uses pure uint64 for gas computation # Go-Ethereum uses pure uint64 for gas computation
let BaseGasCosts*: GasCosts = [ const BaseGasCosts*: GasCosts = [
GasZero: 0.u256, GasZero: 0'i64,
GasBase: 2.u256, GasBase: 2,
GasVeryLow: 3.u256, GasVeryLow: 3,
GasLow: 5.u256, GasLow: 5,
GasMid: 8.u256, GasMid: 8,
GasHigh: 10.u256, GasHigh: 10,
GasSload: 50.u256, # Changed to 200 in Tangerine (EIP150) GasSload: 50, # Changed to 200 in Tangerine (EIP150)
GasJumpDest: 1.u256, GasJumpDest: 1,
GasSset: 20_000.u256, GasSset: 20_000,
GasSreset: 5_000.u256, GasSreset: 5_000,
GasExtCode: 20.u256, GasExtCode: 20,
GasCoinbase: 20.u256, GasCoinbase: 20,
GasSelfDestruct: 0.u256, # Changed to 5000 in Tangerine (EIP150) GasSelfDestruct: 0, # Changed to 5000 in Tangerine (EIP150)
GasInHandler: 0.u256, # to be calculated in handler GasInHandler: 0, # to be calculated in handler
GasRefundSclear: 15000.u256, GasRefundSclear: 15000,
GasBalance: 20.u256, # Changed to 400 in Tangerine (EIP150) GasBalance: 20, # Changed to 400 in Tangerine (EIP150)
GasCall: 40.u256, # Changed to 700 in Tangerine (EIP150) GasCall: 40, # Changed to 700 in Tangerine (EIP150)
GasExp: 10.u256, GasExp: 10,
GasSHA3: 30.u256 GasSHA3: 30
] ]
proc tangerineGasCosts(baseCosts: GasCosts): GasCosts = proc tangerineGasCosts(baseCosts: GasCosts): GasCosts =
# 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 = baseCosts
result[GasSload] = 200.u256 result[GasSload] = 200
result[GasSelfDestruct] = 5000.u256 result[GasSelfDestruct] = 5000
result[GasBalance] = 400.u256 result[GasBalance] = 400
result[GasCall] = 40.u256 result[GasCall] = 40
let TangerineGasCosts* = BaseGasCosts.tangerineGasCosts const TangerineGasCosts* = BaseGasCosts.tangerineGasCosts

View File

@ -7,25 +7,20 @@
import import
strformat, strformat,
../logging, ../errors, ../constants, stint ../logging, ../errors, ../types
type proc newGasMeter*(startGas: GasInt): GasMeter =
GasMeter* = ref object
logger*: Logger
gasRefunded*: UInt256
startGas*: UInt256
gasRemaining*: UInt256
proc newGasMeter*(startGas: UInt256): GasMeter =
new(result) new(result)
result.startGas = startGas result.startGas = startGas
result.gasRemaining = result.startGas result.gasRemaining = result.startGas
result.gasRefunded = 0.u256 result.gasRefunded = 0
result.logger = logging.getLogger("gas") result.logger = logging.getLogger("gas")
proc consumeGas*(gasMeter: var GasMeter; amount: UInt256; reason: string) = proc consumeGas*(gasMeter: var GasMeter; amount: GasInt; reason: string) =
#if amount < 0.u256: #if amount < 0.u256:
# raise newException(ValidationError, "Gas consumption amount must be positive") # raise newException(ValidationError, "Gas consumption amount must be positive")
# Alternatively: use a range type `range[0'i64 .. high(int64)]`
# https://github.com/status-im/nimbus/issues/35#issuecomment-391726518
if amount > gasMeter.gasRemaining: if amount > gasMeter.gasRemaining:
raise newException(OutOfGas, raise newException(OutOfGas,
&"Out of gas: Needed {amount} - Remaining {gasMeter.gasRemaining} - Reason: {reason}") &"Out of gas: Needed {amount} - Remaining {gasMeter.gasRemaining} - Reason: {reason}")
@ -33,16 +28,20 @@ proc consumeGas*(gasMeter: var GasMeter; amount: UInt256; reason: string) =
gasMeter.logger.trace( gasMeter.logger.trace(
&"GAS CONSUMPTION: {gasMeter.gasRemaining + amount} - {amount} -> {gasMeter.gasRemaining} ({reason})") &"GAS CONSUMPTION: {gasMeter.gasRemaining + amount} - {amount} -> {gasMeter.gasRemaining} ({reason})")
proc returnGas*(gasMeter: var GasMeter; amount: UInt256) = proc returnGas*(gasMeter: var GasMeter; amount: GasInt) =
#if amount < 0.int256: #if amount < 0.int256:
# raise newException(ValidationError, "Gas return amount must be positive") # raise newException(ValidationError, "Gas return amount must be positive")
# Alternatively: use a range type `range[0'i64 .. high(int64)]`
# https://github.com/status-im/nimbus/issues/35#issuecomment-391726518
gasMeter.gasRemaining += amount gasMeter.gasRemaining += amount
gasMeter.logger.trace( gasMeter.logger.trace(
&"GAS RETURNED: {gasMeter.gasRemaining - amount} + {amount} -> {gasMeter.gasRemaining}") &"GAS RETURNED: {gasMeter.gasRemaining - amount} + {amount} -> {gasMeter.gasRemaining}")
proc refundGas*(gasMeter: var GasMeter; amount: UInt256) = proc refundGas*(gasMeter: var GasMeter; amount: GasInt) =
#if amount < 0.int256: #if amount < 0.int256:
# raise newException(ValidationError, "Gas refund amount must be positive") # raise newException(ValidationError, "Gas refund amount must be positive")
# Alternatively: use a range type `range[0'i64 .. high(int64)]`
# https://github.com/status-im/nimbus/issues/35#issuecomment-391726518
gasMeter.gasRefunded += amount gasMeter.gasRefunded += amount
gasMeter.logger.trace( gasMeter.logger.trace(
&"GAS REFUND: {gasMeter.gasRemaining - amount} + {amount} -> {gasMeter.gasRefunded}") &"GAS REFUND: {gasMeter.gasRemaining - amount} + {amount} -> {gasMeter.gasRefunded}")

View File

@ -24,39 +24,39 @@ proc len*(memory: Memory): int =
# TODO: why is the size passed as a UInt256? # TODO: why is the size passed as a UInt256?
proc extend*(memory: var Memory; startPosition: UInt256; size: UInt256) = proc extend*(memory: var Memory; startPosition: Natural; size: Natural) =
if size == 0: if size == 0:
return return
var newSize = ceil32(startPosition + size) var newSize = ceil32(startPosition + size)
if newSize <= len(memory).u256: if newSize <= len(memory):
return return
var sizeToExtend = newSize - len(memory).u256 var sizeToExtend = newSize - len(memory)
memory.bytes = memory.bytes.concat(repeat(0.byte, sizeToExtend.toInt)) memory.bytes = memory.bytes.concat(repeat(0.byte, sizeToExtend))
proc newMemory*(size: UInt256): Memory = proc newMemory*(size: Natural): Memory =
result = newMemory() result = newMemory()
result.extend(0.u256, size) result.extend(0, size)
# TODO: why is the size passed as a UInt256? # TODO: why is the size passed as a UInt256?
proc read*(memory: var Memory, startPosition: UInt256, size: UInt256): seq[byte] = proc read*(memory: var Memory, startPosition: Natural, size: Natural): seq[byte] =
result = memory.bytes[startPosition.toInt ..< (startPosition + size).toInt] result = memory.bytes[startPosition ..< (startPosition + size)]
# TODO: why is the size passed as a UInt256? # TODO: why is the size passed as a UInt256?
proc write*(memory: var Memory, startPosition: UInt256, size: UInt256, value: seq[byte]) = proc write*(memory: var Memory, startPosition: Natural, size: Natural, value: seq[byte]) =
if size == 0: if size == 0:
return return
#echo size #echo size
#echo startPosition #echo startPosition
#validateGte(startPosition, 0) #validateGte(startPosition, 0)
#validateGte(size, 0) #validateGte(size, 0)
validateLength(value, size.toInt) validateLength(value, size)
validateLte(startPosition + size, memory.len) validateLte(startPosition + size, memory.len)
let index = memory.len let index = memory.len
if memory.len.u256 < startPosition + size: if memory.len < startPosition + size:
memory.bytes = memory.bytes.concat(repeat(0.byte, memory.len - (startPosition + size).toInt)) # TODO: better logarithmic scaling? memory.bytes = memory.bytes.concat(repeat(0.byte, memory.len - (startPosition + size))) # TODO: better logarithmic scaling?
for z, b in value: for z, b in value:
memory.bytes[z + startPosition.toInt] = b memory.bytes[z + startPosition] = b
template write*(memory: var Memory, startPosition: UInt256, size: UInt256, value: cstring) = template write*(memory: var Memory, startPosition: Natural, size: Natural, value: cstring) =
memory.write(startPosition, size, value.toBytes) memory.write(startPosition, size, value.toBytes)

View File

@ -6,46 +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
../logging, ../constants, ../validation, stint ../logging, ../constants, ../validation, stint, ../types
type
Message* = ref object
# A message for VM computation
# depth = None
# code = None
# codeAddress = None
# createAddress = None
# shouldTransferValue = None
# isStatic = None
# logger = logging.getLogger("evm.vm.message.Message")
gas*: UInt256
gasPrice*: UInt256
to*: string
sender*: string
value*: UInt256
data*: seq[byte]
code*: string
internalOrigin: string
internalCodeAddress: string
depth*: int
internalStorageAddress: string
shouldTransferValue*: bool
isStatic*: bool
isCreate*: bool
MessageOptions* = ref object
origin*: string
depth*: int
createAddress*: string
codeAddress*: string
shouldTransferValue*: bool
isStatic*: bool
proc `origin=`*(message: var Message, value: string) = proc `origin=`*(message: var Message, value: string) =
message.internalOrigin = value message.internalOrigin = value
@ -73,8 +34,8 @@ proc newMessageOptions*(
isStatic: isStatic) isStatic: isStatic)
proc newMessage*( proc newMessage*(
gas: UInt256, gas: GasInt,
gasPrice: UInt256, gasPrice: GasInt,
to: string, to: string,
sender: string, sender: string,
value: UInt256, value: UInt256,

View File

@ -7,7 +7,7 @@
import unittest, macros, strformat, strutils, sequtils, import unittest, macros, strformat, strutils, sequtils,
stint, stint,
../src/[constants, opcode_values, errors, logging, vm/gas_meter] ../src/[constants, opcode_values, errors, logging, types, vm/gas_meter]
# TODO: quicktest # TODO: quicktest
# PS: parametrize can be easily immitated, but still quicktests would be even more useful # PS: parametrize can be easily immitated, but still quicktests would be even more useful
@ -15,7 +15,7 @@ import unittest, macros, strformat, strutils, sequtils,
disableLogging() disableLogging()
proc gasMeters: seq[GasMeter] = proc gasMeters: seq[GasMeter] =
@[newGasMeter(10.u256), newGasMeter(100.u256), newGasMeter(999.u256)] @[newGasMeter(10), newGasMeter(100), newGasMeter(999)]
macro all(element: untyped, handler: untyped): untyped = macro all(element: untyped, handler: untyped): untyped =
let name = ident(&"{element.repr}s") let name = ident(&"{element.repr}s")
@ -84,15 +84,15 @@ suite "gasMeter":
all(gasMeter): all(gasMeter):
check(gasMeter.gasRemaining == gasMeter.startGas) check(gasMeter.gasRemaining == gasMeter.startGas)
expect(OutOfGas): expect(OutOfGas):
gasMeter.consumeGas(gasMeter.startGas + 1.u256, "") gasMeter.consumeGas(gasMeter.startGas + 1, "")
test "return refund works correctly": test "return refund works correctly":
all(gasMeter): all(gasMeter):
check(gasMeter.gasRemaining == gasMeter.startGas) check(gasMeter.gasRemaining == gasMeter.startGas)
check(gasMeter.gasRefunded == 0) check(gasMeter.gasRefunded == 0)
gasMeter.consumeGas(5.u256, "") gasMeter.consumeGas(5, "")
check(gasMeter.gasRemaining == gasMeter.startGas - 5.u256) check(gasMeter.gasRemaining == gasMeter.startGas - 5)
gasMeter.returnGas(5.u256) gasMeter.returnGas(5)
check(gasMeter.gasRemaining == gasMeter.startGas) check(gasMeter.gasRemaining == gasMeter.startGas)
gasMeter.refundGas(5.u256) gasMeter.refundGas(5)
check(gasMeter.gasRefunded == 5.u256) check(gasMeter.gasRefunded == 5)

View File

@ -11,17 +11,17 @@ import unittest, macros, strformat, strutils, sequtils,
proc memory32: Memory = proc memory32: Memory =
result = newMemory() result = newMemory()
result.extend(0.u256, 32.u256) result.extend(0, 32)
proc memory128: Memory = proc memory128: Memory =
result = newMemory() result = newMemory()
result.extend(0.u256, 128.u256) result.extend(0, 128)
suite "memory": suite "memory":
test "write": test "write":
var mem = memory32() var mem = memory32()
# Test that write creates 32byte string == value padded with zeros # Test that write creates 32byte string == value padded with zeros
mem.write(startPosition = 0.u256, size = 4.u256, value = @[1.byte, 0.byte, 1.byte, 0.byte]) mem.write(startPosition = 0, size = 4, value = @[1.byte, 0.byte, 1.byte, 0.byte])
check(mem.bytes == @[1.byte, 0.byte, 1.byte, 0.byte].concat(repeat(0.byte, 28))) check(mem.bytes == @[1.byte, 0.byte, 1.byte, 0.byte].concat(repeat(0.byte, 28)))
# test "write rejects invalid position": # test "write rejects invalid position":
@ -47,28 +47,28 @@ suite "memory":
test "write rejects invalid value": test "write rejects invalid value":
expect(ValidationError): expect(ValidationError):
var mem = memory32() var mem = memory32()
mem.write(startPosition = 0.u256, size = 4.u256, value = @[1.byte, 0.byte]) mem.write(startPosition = 0, size = 4, value = @[1.byte, 0.byte])
test "write rejects valyes beyond memory size": test "write rejects valyes beyond memory size":
expect(ValidationError): expect(ValidationError):
var mem = memory128() var mem = memory128()
mem.write(startPosition = 128.u256, size = 4.u256, value = @[1.byte, 0.byte, 1.byte, 0.byte]) mem.write(startPosition = 128, size = 4, value = @[1.byte, 0.byte, 1.byte, 0.byte])
test "extends appropriately extends memory": test "extends appropriately extends memory":
var mem = newMemory() var mem = newMemory()
# Test extends to 32 byte array: 0 < (start_position + size) <= 32 # Test extends to 32 byte array: 0 < (start_position + size) <= 32
mem.extend(startPosition = 0.u256, size = 10.u256) mem.extend(startPosition = 0, size = 10)
check(mem.bytes == repeat(0.byte, 32)) check(mem.bytes == repeat(0.byte, 32))
# Test will extend past length if params require: 32 < (start_position + size) <= 64 # Test will extend past length if params require: 32 < (start_position + size) <= 64
mem.extend(startPosition = 28.u256, size = 32.u256) mem.extend(startPosition = 28, size = 32)
check(mem.bytes == repeat(0.byte, 64)) check(mem.bytes == repeat(0.byte, 64))
# Test won't extend past length unless params require: 32 < (start_position + size) <= 64 # Test won't extend past length unless params require: 32 < (start_position + size) <= 64
mem.extend(startPosition = 48.u256, size = 10.u256) mem.extend(startPosition = 48, size = 10)
check(mem.bytes == repeat(0.byte, 64)) check(mem.bytes == repeat(0.byte, 64))
test "read returns correct bytes": test "read returns correct bytes":
var mem = memory32() var mem = memory32()
mem.write(startPosition = 5.u256, size = 4.u256, value = @[1.byte, 0.byte, 1.byte, 0.byte]) mem.write(startPosition = 5, size = 4, value = @[1.byte, 0.byte, 1.byte, 0.byte])
check(mem.read(startPosition = 5.u256, size = 4.u256) == @[1.byte, 0.byte, 1.byte, 0.byte]) check(mem.read(startPosition = 5, size = 4) == @[1.byte, 0.byte, 1.byte, 0.byte])
check(mem.read(startPosition = 6.u256, size = 4.u256) == @[0.byte, 1.byte, 0.byte, 0.byte]) check(mem.read(startPosition = 6, size = 4) == @[0.byte, 1.byte, 0.byte, 0.byte])
check(mem.read(startPosition = 1.u256, size = 3.u256) == @[0.byte, 0.byte, 0.byte]) check(mem.read(startPosition = 1, size = 3) == @[0.byte, 0.byte, 0.byte])

View File

@ -16,7 +16,7 @@ import
test_helpers test_helpers
proc testCode(code: string, initialGas: UInt256, blockNum: UInt256): BaseComputation = proc testCode(code: string, initialGas: GasInt, blockNum: UInt256): BaseComputation =
let header = BlockHeader(blockNumber: blockNum) let header = BlockHeader(blockNumber: blockNum)
var vm = newNimbusVM(header, newBaseChainDB(newMemoryDB())) var vm = newNimbusVM(header, newBaseChainDB(newMemoryDB()))
# coinbase: "", # coinbase: "",
@ -32,7 +32,7 @@ proc testCode(code: string, initialGas: UInt256, blockNum: UInt256): BaseComputa
data = @[], data = @[],
code=code, code=code,
gas=initial_gas, gas=initial_gas,
gasPrice=1.u256) # What is this used for? gasPrice=1) # What is this used for?
# gasPrice=fixture{"exec"}{"gasPrice"}.getHexadecimalInt.u256, # gasPrice=fixture{"exec"}{"gasPrice"}.getHexadecimalInt.u256,
#options=newMessageOptions(origin=fixture{"exec"}{"origin"}.getStr)) #options=newMessageOptions(origin=fixture{"exec"}{"origin"}.getStr))
@ -53,10 +53,10 @@ suite "opcodes":
test "add": test "add":
var c = testCode( var c = testCode(
"0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff01", "0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff01",
100_000.u256, 100_000,
0.u256 0.u256
) )
check(c.gasMeter.gasRemaining == 99_991.u256) check(c.gasMeter.gasRemaining == 99_991)
check(c.stack.peek == "115792089237316195423570985008687907853269984665640564039457584007913129639934".u256) check(c.stack.peek == "115792089237316195423570985008687907853269984665640564039457584007913129639934".u256)
# let address = Address::from_str("0f572e5295c57f15886f9b263e2f6d2d6c7b5ec6").unwrap(); # let address = Address::from_str("0f572e5295c57f15886f9b263e2f6d2d6c7b5ec6").unwrap();
# let code = "7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff01600055".from_hex().unwrap(); # let code = "7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff01600055".from_hex().unwrap();
@ -80,33 +80,33 @@ suite "opcodes":
block: # Using Balance (0x31) block: # Using Balance (0x31)
var c = testCode( var c = testCode(
"0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff31", "0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff31",
100_000.u256, 100_000,
0.u256 0.u256
) )
check: c.gasMeter.gasRemaining == 100000.u256 - 3.u256 - 20.u256 # Starting gas - push32 (verylow) - balance check: c.gasMeter.gasRemaining == 100000 - 3 - 20 # Starting gas - push32 (verylow) - balance
block: # Using SLOAD (0x54) block: # Using SLOAD (0x54)
var c = testCode( var c = testCode(
"0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff54", "0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff54",
100_000.u256, 100_000,
0.u256 0.u256
) )
check: c.gasMeter.gasRemaining == 100000.u256 - 3.u256 - 50.u256 # Starting gas - push32 (verylow) - SLOAD check: c.gasMeter.gasRemaining == 100000 - 3 - 50 # Starting gas - push32 (verylow) - SLOAD
test "Tangerine VM computation - post-EIP150 gas cost properly applied": test "Tangerine VM computation - post-EIP150 gas cost properly applied":
block: # Using Balance (0x31) block: # Using Balance (0x31)
var c = testCode( var c = testCode(
"0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff31", "0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff31",
100_000.u256, 100_000,
2_463_000.u256 # Tangerine block 2_463_000.u256 # Tangerine block
) )
check: c.gasMeter.gasRemaining == 100000.u256 - 3.u256 - 400.u256 # Starting gas - push32 (verylow) - balance check: c.gasMeter.gasRemaining == 100000 - 3 - 400 # Starting gas - push32 (verylow) - balance
block: # Using SLOAD (0x54) block: # Using SLOAD (0x54)
var c = testCode( var c = testCode(
"0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff54", "0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff54",
100_000.u256, 100_000,
2_463_000.u256 2_463_000.u256
) )
check: c.gasMeter.gasRemaining == 100000.u256 - 3.u256 - 200.u256 # Starting gas - push32 (verylow) - SLOAD check: c.gasMeter.gasRemaining == 100000 - 3 - 200 # Starting gas - push32 (verylow) - SLOAD

View File

@ -45,8 +45,8 @@ proc testFixture(fixtures: JsonNode, testStatusIMPL: var TestStatus) =
value=fixture{"exec"}{"value"}.getHexadecimalInt.u256, value=fixture{"exec"}{"value"}.getHexadecimalInt.u256,
data=fixture{"exec"}{"data"}.getStr.mapIt(it.byte), data=fixture{"exec"}{"data"}.getStr.mapIt(it.byte),
code=code, code=code,
gas=fixture{"exec"}{"gas"}.getHexadecimalInt.u256, gas=fixture{"exec"}{"gas"}.getHexadecimalInt,
gasPrice=fixture{"exec"}{"gasPrice"}.getHexadecimalInt.u256, gasPrice=fixture{"exec"}{"gasPrice"}.getHexadecimalInt,
options=newMessageOptions(origin=fixture{"exec"}{"origin"}.getStr)) options=newMessageOptions(origin=fixture{"exec"}{"origin"}.getStr))
#echo fixture{"exec"} #echo fixture{"exec"}
@ -81,7 +81,7 @@ proc testFixture(fixtures: JsonNode, testStatusIMPL: var TestStatus) =
check(computation.output == expectedOutput) check(computation.output == expectedOutput)
let gasMeter = computation.gasMeter let gasMeter = computation.gasMeter
let expectedGasRemaining = fixture{"gas"}.getHexadecimalInt.u256 let expectedGasRemaining = fixture{"gas"}.getHexadecimalInt
let actualGasRemaining = gasMeter.gasRemaining let actualGasRemaining = gasMeter.gasRemaining
checkpoint(&"{actualGasRemaining} {expectedGasRemaining}") checkpoint(&"{actualGasRemaining} {expectedGasRemaining}")
check(actualGasRemaining == expectedGasRemaining or check(actualGasRemaining == expectedGasRemaining or
@ -100,7 +100,7 @@ proc testFixture(fixtures: JsonNode, testStatusIMPL: var TestStatus) =
var (childComputation, createdCall) = child var (childComputation, createdCall) = child
let toAddress = createdCall{"destination"}.getStr let toAddress = createdCall{"destination"}.getStr
let data = createdCall{"data"}.getStr.mapIt(it.byte) let data = createdCall{"data"}.getStr.mapIt(it.byte)
let gasLimit = createdCall{"gasLimit"}.getHexadecimalInt.u256 let gasLimit = createdCall{"gasLimit"}.getHexadecimalInt
let value = createdCall{"value"}.getHexadecimalInt.u256 let value = createdCall{"value"}.getHexadecimalInt.u256
check(childComputation.msg.to == toAddress) check(childComputation.msg.to == toAddress)