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
+ addmod0.json OK
+ addmod1.json OK
+ addmod1_overflow2.json OK
- addmod1_overflow2.json Fail
+ addmod1_overflow3.json OK
+ addmod1_overflow4.json OK
+ addmod1_overflowDiff.json OK
@ -135,7 +135,7 @@ VMTests
+ mulUnderFlow.json OK
+ mulmod0.json OK
+ mulmod1.json OK
+ mulmod1_overflow.json OK
- mulmod1_overflow.json Fail
+ mulmod1_overflow2.json OK
+ mulmod1_overflow3.json OK
+ mulmod1_overflow4.json OK
@ -177,7 +177,7 @@ VMTests
+ signextend_BitIsNotSet.json OK
+ signextend_BitIsNotSetInHigherByte.json OK
+ signextend_BitIsSetInHigherByte.json OK
- signextend_Overflow_dj42.json Fail
+ signextend_Overflow_dj42.json OK
+ signextend_bigBytePlus1.json OK
+ signextend_bitIsSet.json OK
+ smod0.json OK
@ -198,7 +198,7 @@ VMTests
+ sub3.json OK
+ sub4.json OK
```
OK: 190/195 Fail: 4/195 Skip: 1/195
OK: 189/195 Fail: 5/195 Skip: 1/195
## vmBitwiseLogicOperation
```diff
+ 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
## vmBlockInfoTest
```diff
- blockhash257Block.json Fail
- blockhash258Block.json Fail
- blockhashInRange.json Fail
- blockhashMyBlock.json Fail
- blockhashNotExistingBlock.json Fail
- blockhashOutOfRange.json Fail
+ blockhash257Block.json OK
+ blockhash258Block.json OK
+ blockhashInRange.json OK
+ blockhashMyBlock.json OK
+ blockhashNotExistingBlock.json OK
+ blockhashOutOfRange.json OK
+ blockhashUnderFlow.json OK
- coinbase.json Fail
+ coinbase.json OK
+ difficulty.json OK
+ gaslimit.json OK
+ number.json OK
+ timestamp.json OK
```
OK: 5/12 Fail: 7/12 Skip: 0/12
OK: 12/12 Fail: 0/12 Skip: 0/12
## vmEnvironmentalInfo
```diff
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.
import
strformat, strutils, sequtils, tables, macros, stint, terminal, math, eth_common, byteutils,
constants, errors, utils/hexadecimal, utils_numeric, validation, vm_state, logging, opcode_values, vm_types,
vm / [code_stream, gas_meter, memory, message, stack],
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,
./vm/[code_stream, gas_meter, memory, message, stack],
# TODO further refactoring of gas cost
vm/forks/gas_costs,
vm/forks/f20150730_frontier/frontier_vm_state,
vm/forks/f20161018_tangerine_whistle/tangerine_vm_state
proc memoryGasCost*(sizeInBytes: 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.}=
raise newException(ValueError, "Must be implemented by subclasses")
@ -105,26 +95,6 @@ method prepareChildMessage*(
code,
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 =
if c.shouldEraseReturnData:
""
@ -261,10 +231,10 @@ template inComputation*(c: untyped, handler: untyped): untyped =
c.gasMeter.gasRemaining,
reason="Zeroing gas due to VM Exception: $1" % getCurrentExceptionMsg())
method getOpcodeFn*(computation: var BaseComputation, op: Op): Opcode =
# TODO use isValidOpcode and remove the Op --> Opcode indirection
if computation.opcodes.len > 0 and computation.opcodes.hasKey(op):
computation.opcodes[op]
OpCode(kind: op, runLogic: computation.opcodes[op])
else:
raise newException(InvalidInstruction,
&"Invalid opcode {op}")

View File

@ -114,66 +114,8 @@ let
ZERO_HASH32* = Hash256()
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_ADJUSTMENT_FACTOR* = 1_024
GAS_LIMIT_MAXIMUM* = high(GasInt)
GAS_LIMIT_USAGE_ADJUSTMENT_NUMERATOR* = 3
GAS_LIMIT_USAGE_ADJUSTMENT_DENOMINATOR* = 2

View File

@ -5,7 +5,7 @@
# * MIT license ([LICENSE-MIT](LICENSE-MIT) or http://opensource.org/licenses/MIT)
# at your option. This file may not be copied, modified, or distributed except according to those terms.
import 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
type

View File

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

View File

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

View File

@ -39,20 +39,10 @@ type
using
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.} =
raise newException(NotImplementedError, "Must be implemented subclasses")
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,
codeAddress,
memoryInputStartPosition, memoryInputSize,
@ -62,12 +52,15 @@ method runLogic*(call: BaseCall, computation) =
let (memInPos, memInLen, memOutPos, memOutLen) = (memoryInputStartPosition.toInt, memoryInputSize.toInt, memoryOutputStartPosition.toInt, memoryOutputSize.toInt)
computation.extendMemory(memInPos, memInLen)
computation.extendMemory(memOutPos, memOutLen)
let (gasCost, childMsgGas) = computation.gasCosts[Op.Call].c_handler(
value,
GasParams() # TODO - stub
)
computation.memory.extend(memInPos, memInLen)
computation.memory.extend(memOutPos, memOutLen)
let callData = computation.memory.read(memInPos, memInLen)
let (childMsgGas, childMsgGasFee) = call.msgGas(computation, gas.toInt, to, value)
computation.gasMeter.consumeGas(childMsgGasFee, reason = $call.kind)
# TODO: Pre-call checks
# 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:
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) =
let gas = computation.stack.popInt()
let to = computation.stack.popAddress()
@ -155,9 +138,6 @@ method callParams(call: CallCode, computation): (UInt256, UInt256, EthAddress, E
true, # should_transfer_value,
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) =
let gas = computation.stack.popInt()
let codeAddress = computation.stack.popAddress()
@ -181,12 +161,6 @@ method callParams(call: Call, computation): (UInt256, UInt256, EthAddress, EthAd
true, # should_transfer_value,
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) =
let gas = computation.stack.popInt()
let codeAddress = computation.stack.popAddress()
@ -210,42 +184,6 @@ method callParams(call: DelegateCall, computation): (UInt256, UInt256, EthAddres
false, # should_transfer_value,
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) =
let gas = computation.stack.popInt()
let to = computation.stack.popAddress()
@ -265,7 +203,6 @@ method callParams(call: StaticCall, computation): (UInt256, UInt256, EthAddress,
false, # should_transfer_value,
true) # is_static
method callParams(call: CallByzantium, computation): (UInt256, UInt256, EthAddress, EthAddress, EthAddress, UInt256, UInt256, UInt256, UInt256, bool, bool) =
result = procCall callParams(call, computation)
if computation.msg.isStatic and result[1] != 0:

View File

@ -8,7 +8,8 @@
import
strformat,
../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) =
let address = computation.stack.popAddress()
@ -47,12 +48,14 @@ proc callDataCopy*(computation: var BaseComputation) =
let (memStartPosition,
calldataStartPosition,
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
let copyGasCost = wordCount * constants.GAS_COPY
computation.gasMeter.consumeGas(copyGasCost, reason="CALLDATACOPY fee")
computation.gasMeter.consumeGas(
computation.gasCosts[CallDataCopy].d_handler(size),
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 paddedValue = padRight(value, len, 0.byte)
computation.memory.write(memPos, paddedValue)
@ -67,13 +70,14 @@ proc codecopy*(computation: var BaseComputation) =
let (memStartPosition,
codeStartPosition,
size) = computation.stack.popInt(3)
computation.gasMeter.consumeGas(
computation.gasCosts[CodeCopy].d_handler(size),
reason="CODECOPY: word gas cost")
let (memPos, codePos, len) = (memStartPosition.toInt, codeStartPosition.toInt, size.toInt)
computation.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
# with computation.code.seek(code_start_position):
# code_bytes = computation.code.read(size)
@ -96,12 +100,15 @@ proc extCodeSize*(computation: var BaseComputation) =
proc extCodeCopy*(computation: var BaseComputation) =
let account = computation.stack.popAddress()
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:
# 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) =
let (memStartPosition, returnDataStartPosition, size) = computation.stack.popInt(3)
computation.gasMeter.consumeGas(
computation.gasCosts[ReturnDataCopy].d_handler(size),
reason="RETURNDATACOPY fee"
)
let (memPos, returnPos, len) = (memStartPosition.toInt, returnDataStartPosition.toInt, size.toInt)
if returnPos + len > computation.returnData.len:
raise newException(OutOfBoundsRead,
@ -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" &
"length")
computation.extendMemory(memPos, len)
let wordCount = ceil32(len) div 32
let copyGasCost = wordCount * constants.GAS_COPY
computation.gasMeter.consumeGas(copyGasCost, reason="RETURNDATACOPY fee")
computation.memory.extend(memPos, len)
let value = ($computation.returnData)[returnPos ..< returnPos + len]
computation.memory.write(memPos, len, value)

View File

@ -7,7 +7,9 @@
import
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.}
{.experimental.}
@ -50,12 +52,12 @@ macro logXX(topicCount: static[int]): untyped =
result.body.add(topicCode)
let OpName = ident(&"Log{topicCount}")
let logicCode = quote:
let dataGasCost = constants.GAS_LOG_DATA * `len`
let topicGasCost = constants.GAS_LOG_TOPIC * `topicCount`
let totalGasCost = dataGasCost + topicGasCost
`computation`.gasMeter.consumeGas(totalGasCost, reason="Log topic and data gas cost")
`computation`.extendMemory(`memPos`, `len`)
`computation`.gasMeter.consumeGas(
`computation`.gasCosts[`OpName`].m_handler(`computation`.memory.len, `memPos` + `len`),
reason="Memory expansion, Log topic and data gas cost")
`computation`.memory.extend(`memPos`, `len`)
let logData = `computation`.memory.read(`memPos`, `len`).toString
`computation`.addLogEntry(
account=`computation`.msg.storageAddress,

View File

@ -6,7 +6,8 @@
# at your option. This file may not be copied, modified, or distributed except according to those terms.
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
@ -22,7 +23,12 @@ proc mstore*(computation) =
let start = stack.popInt().toInt
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)
proc mstore8*(computation) =
@ -30,14 +36,23 @@ proc mstore8*(computation) =
let value = stack.popInt()
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]])
proc mload*(computation) =
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)
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.
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) =
let (startPosition, size) = computation.stack.popInt(2)
let (pos, len) = (startPosition.toInt, size.toInt)
computation.extendMemory(pos, len)
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
computation.gasMeter.consumeGas(gasCost, reason="SHA3: word gas cost")
var res = keccak("")
computation.gasMeter.consumeGas(
computation.gasCosts[Sha3].m_handler(computation.memory.len, pos + len),
reason="SHA3: word gas cost"
)
computation.memory.extend(pos, len)
var res = keccak("") # TODO: stub
pushRes()

View File

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

View File

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

View File

@ -20,68 +20,15 @@ from logic.call import runLogic, BaseCall
template run*(opcode: Opcode, computation: var BaseComputation) =
# Hook for performing the actual VM execution
# 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
# TODO remove this branch
runLogic(BaseCall(opcode), computation)
elif computation.gasCosts[opcode.kind].kind != GckFixed:
opcode.runLogic(computation)
else:
computation.gasMeter.consumeGas(computation.gasCosts[opcode.kind].cost, reason = $opcode.kind)
opcode.runLogic(computation)
method logger*(opcode: Opcode): Logger =
logging.getLogger(&"vm.opcode.{opcode.kind}")
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.
import
strformat, strutils, tables, macros,
constants, stint, errors, logging, vm_state,
tables,
stint, logging,
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],
./vm_types
var OPCODE_TABLE* = initOpcodes:
# arithmetic
Op.Add: GasVeryLow add
Op.Mul: GasLow mul
Op.Sub: GasVeryLow sub
Op.Div: GasLow divide
Op.SDiv: GasLow sdiv
Op.Mod: GasLow modulo
Op.SMod: GasLow smod
Op.AddMod: GasMid addmod
Op.MulMod: GasMid mulmod
Op.Exp: GasInHandler arithmetic.exp
Op.SignExtend: GasLow signextend
const
OpLogic*: Table[Op, proc(computation: var BaseComputation){.nimcall.}] = {
# 0s: Stop and Arithmetic Operations
Stop: stop,
Add: arithmetic.add,
Mul: mul,
Sub: sub,
Div: divide,
Sdiv: sdiv,
Mod: modulo,
Smod: smod,
Addmod: arithmetic.addmod,
Mulmod: arithmetic.mulmod,
Exp: arithmetic.exp,
SignExtend: signextend,
# 10s: Comparison & Bitwise Logic Operations
Lt: lt,
Gt: gt,
Slt: slt,
Sgt: sgt,
Eq: eq,
IsZero: comparison.isZero,
And: andOp,
Or: orOp,
Xor: xorOp,
Not: notOp,
Byte: byteOp,
# comparison
Op.Lt: GasVeryLow lt
Op.Gt: GasVeryLow gt
Op.SLt: GasVeryLow slt
Op.SGt: GasVeryLow sgt
Op.Eq: GasVeryLow eq
Op.IsZero: GasVeryLow iszero
Op.And: GasVeryLow andOp
Op.Or: GasVeryLow orOp
Op.Xor: GasVeryLow xorOp
Op.Not: GasVeryLow notOp
Op.Byte: GasVeryLow byteOp
# 20s: SHA3
Sha3: sha3op,
# 30s: Environmental Information
Address: context.address,
Balance: balance,
Origin: context.origin,
Caller: caller,
CallValue: callValue,
CallDataLoad: callDataLoad,
CallDataSize: callDataSize,
CallDataCopy: callDataCopy,
CodeSize: codesize,
CodeCopy: codecopy,
GasPrice: gasPrice, # TODO this wasn't used previously
ExtCodeSize: extCodeSize,
ExtCodeCopy: extCodeCopy,
ReturnDataSize: returnDataSize, # TODO this wasn't used previously
ReturnDataCopy: returnDataCopy,
# sha3
Op.SHA3: GasSHA3 sha3op
# 40s: Block Information
Blockhash: block_ops.blockhash,
Coinbase: block_ops.coinbase,
Timestamp: block_ops.timestamp,
Number: block_ops.number,
Difficulty: block_ops.difficulty,
GasLimit: block_ops.gaslimit,
# 50s: Stack, Memory, Storage and Flow Operations
Pop: stack_ops.pop,
Mload: mload,
Mstore: mstore,
Mstore8: mstore8,
Sload: sload,
Sstore: sstore,
Jump: jump,
JumpI: jumpi,
Pc: pc,
Msize: msize,
Gas: flow.gas,
JumpDest: jumpDest,
# context
Op.Address: GasBase context.address
Op.Balance: GasBalance balance
Op.Origin: GasBase origin
Op.Caller: GasBase caller
Op.CallValue: GasBase callValue
Op.CallDataLoad: GasVeryLow callDataLoad
Op.CallDataSize: GasBase callDataSize
Op.CallDataCopy: GasBase callDataCopy
Op.CodeSize: GasBase codesize
Op.CodeCopy: GasBase codecopy
Op.ExtCodeSize: GasExtCode extCodeSize
Op.ExtCodeCopy: GasExtCode extCodeCopy
# 60s & 70s: Push Operations
Push1: push1,
Push2: push2,
Push3: push3,
Push4: push4,
Push5: push5,
Push6: push6,
Push7: push7,
Push8: push8,
Push9: push9,
Push10: push10,
Push11: push11,
Push12: push12,
Push13: push13,
Push14: push14,
Push15: push15,
Push16: push16,
Push17: push17,
Push18: push18,
Push19: push19,
Push20: push20,
Push21: push21,
Push22: push22,
Push23: push23,
Push24: push24,
Push25: push25,
Push26: push26,
Push27: push27,
Push28: push28,
Push29: push29,
Push30: push30,
Push31: push31,
Push32: push32,
# 80s: Duplication Operations
Dup1: dup1,
Dup2: dup2,
Dup3: dup3,
Dup4: dup4,
Dup5: dup5,
Dup6: dup6,
Dup7: dup7,
Dup8: dup8,
Dup9: dup9,
Dup10: dup10,
Dup11: dup11,
Dup12: dup12,
Dup13: dup13,
Dup14: dup14,
Dup15: dup15,
Dup16: dup16,
# block
Op.Blockhash: GasBase block_ops.blockhash
Op.Coinbase: GasCoinbase coinbase
Op.Timestamp: GasBase timestamp
Op.Number: GasBase number
Op.Difficulty: GasBase difficulty
Op.GasLimit: GasBase gaslimit
# 90s: Exchange Operations
Swap1: swap1,
Swap2: swap2,
Swap3: swap3,
Swap4: swap4,
Swap5: swap5,
Swap6: swap6,
Swap7: swap7,
Swap8: swap8,
Swap9: swap9,
Swap10: swap10,
Swap11: swap11,
Swap12: swap12,
Swap13: swap13,
Swap14: swap14,
Swap15: swap15,
Swap16: swap16,
# a0s: Logging Operations
Log0: log0,
Log1: log1,
Log2: log2,
Log3: log3,
Log4: log4,
# stack
Op.Pop: GasBase stack_ops.pop
1..32 Op.PushXX: GasVeryLow pushXX # XX replaced by macro
1..16 Op.DupXX: GasVeryLow dupXX
1..16 Op.SwapXX: GasVeryLow swapXX
# memory
Op.MLoad: GasVeryLow mload
Op.MStore: GasVeryLow mstore
Op.MStore8: GasVeryLow mstore8
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)
# f0s: System operations
# Create: create,
# Call: call,
# CallCode: callCode,
Return: returnOp,
# DelegateCall: delegateCall,
# StaticCall: staticCall,
Op.Revert: revert,
Invalid: invalidOp,
SelfDestruct: selfDestruct
}.toTable

View File

@ -5,164 +5,174 @@
# * 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.
type
Op* {.pure.} = enum
STOP = 0x0, # 0
ADD, # 1
MUL, # 2
SUB, # 3
DIV, # 4
SDIV, # 5
MOD, # 6
SMOD, # 7
ADDMOD, # 8
MULMOD, # 9
EXP, # 10
SIGNEXTEND, # 11
import ./utils/macros_gen_opcodes
LT = 0x10, # 16
GT, # 17
SLT, # 18
SGT, # 19
EQ, # 20
ISZERO, # 21
AND, # 22
OR, # 23
XOR, # 24
NOT, # 25
BYTE, # 26
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
SHA3 = 0x20, # 32
Op* {.pure.} = enum
# 0s: Stop and Arithmetic Operations
Stop = 0x00, # Halts execution.
Add = 0x01, # Addition operation.
Mul = 0x02, # Multiplication operation.
Sub = 0x03, # Subtraction operation.
Div = 0x04, # Integer division operation.
Sdiv = 0x05, # Signed integer division operation (truncated).
Mod = 0x06, # Modulo remainder operation.
Smod = 0x07, # Signed modulo remainder operation.
Addmod = 0x08, # Modulo addition operation.
Mulmod = 0x09, # Modulo multiplication operation.
Exp = 0x0A, # Exponential operation
SignExtend = 0x0B, # Extend length of twos complement signed integer.
ADDRESS = 0x30,# 48
BALANCE, # 49
ORIGIN, # 50
# 10s: Comparison & Bitwise Logic Operations
Lt = 0x10, # Less-than comparison.
Gt = 0x11, # Greater-than comparison.
Slt = 0x12, # Signed less-than comparison.
Sgt = 0x13, # Signed greater-than comparison.
Eq = 0x14, # Equality comparison.
IsZero = 0x15, # Simple not operator. (Note: real Yellow Paper description)
And = 0x16, # Bitwise AND operation.
Or = 0x17, # Bitwise OR operation.
Xor = 0x18, # Bitwise XOR operation.
Not = 0x19, # Bitwise NOT operation.
Byte = 0x1A, # Retrieve single byte from word.
CALLER, # 51
CALLVALUE, # 52
CALLDATALOAD, # 53
CALLDATASIZE, # 54
CALLDATACOPY, # 55
# 20s: SHA3
Sha3 = 0x20, # Compute Keccak-256 hash.
CODESIZE, # 56
CODECOPY, # 57
# 30s: Environmental Information
Address = 0x30, # Get address of currently executing account.
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.
GASPRICE, # 58
# 40s: Block Information
Blockhash = 0x40, # Get the hash of one of the 256 most recent complete blocks.
Coinbase = 0x41, # Get the block's beneficiary address.
Timestamp = 0x42, # Get the block's timestamp.
Number = 0x43, # Get the block's number.
Difficulty = 0x44, # Get the block's difficulty.
GasLimit = 0x45, # Get the block's gas limit.
EXTCODESIZE, # 59
EXTCODECOPY, # 60
# 50s: Stack, Memory, Storage and Flow Operations
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.
RETURNDATASIZE, # 61
RETURNDATACOPY, # 62
# 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.
BLOCKHASH = 0x40,# 64
# 80s: Duplication Operations
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.
COINBASE, # 65
# 90s: Exchange Operations
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.
TIMESTAMP, # 66
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
# a0s: Logging Operations
Log0 = 0xa0, # Append log record with no topics.
Log1 = 0xa1, # Append log record with one topics.
Log2,
Log3,
Log4 = 0xa4, # Append log record with four topics.
# 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
# TODO improve
proc intToBigEndian*(value: UInt256): Bytes {.deprecated.} =
result = newSeq[byte](32)
result[0 .. ^1] = value.toByteArrayBE()
@ -21,21 +19,9 @@ proc bigEndianToInt*(value: openarray[byte]): UInt256 =
else:
readUintBE[256](padLeft(@value, 32, 0.byte))
#echo intToBigEndian("32482610168005790164680892356840817100452003984372336767666156211029086934369".u256)
# proc bitLength*(value: UInt256): int =
# 255 - value.countLeadingZeroBits
proc log256*(value: UInt256): Natural =
(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 =
0.i256
# 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:
result += INT_256_MAX_AS_UINT256
# it's deasible to map nameXX methods like that (originally decorator)
macro ceilXX(ceiling: static[int]): untyped =
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
func ceil32*(value: Natural): Natural {.inline.}=
# Round input to the nearest bigger multiple of 32
result = value
ceilXX(32)
ceilXX(8)
let remainder = result and 31 # equivalent to modulo 32
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.
import
stint, ../../vm_types
stint, eth_common, math,
../../utils/macros_gen_opcodes,
../../opcode_values,
../../utils_numeric
# TODO: Make that computation at compile-time.
# Go-Ethereum uses pure uint64 for gas computation
const BaseGasCosts*: GasCosts = [
GasZero: 0'i64,
GasBase: 2,
GasVeryLow: 3,
GasLow: 5,
GasMid: 8,
GasHigh: 10,
GasSload: 50, # Changed to 200 in Tangerine (EIP150)
GasJumpDest: 1,
GasSset: 20_000,
GasSreset: 5_000,
GasExtCode: 20,
GasCoinbase: 20,
GasSelfDestruct: 0, # Changed to 5000 in Tangerine (EIP150)
GasInHandler: 0, # to be calculated in handler
GasRefundSclear: 15000,
# Gas Fee Schedule
# Yellow Paper Appendix G - https://ethereum.github.io/yellowpaper/paper.pdf
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.
GasBalance: 20, # Changed to 400 in Tangerine (EIP150)
GasCall: 40, # Changed to 700 in Tangerine (EIP150)
GasExp: 10,
GasSHA3: 30
]
GasFeeSchedule = array[GasFeeKind, GasInt]
proc tangerineGasCosts(baseCosts: GasCosts): GasCosts =
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,
GasBase: 2,
GasVeryLow: 3,
GasLow: 5,
GasMid: 8,
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)
GasJumpDest: 1,
GasSset: 20_000,
GasSreset: 5_000,
RefundSclear: 15_000,
RefundSelfDestruct: 24_000,
GasSelfDestruct: 0, # Changed to 5000 in Tangerine (EIP150)
GasCreate: 32000,
GasCodeDeposit: 200,
GasCall: 40, # Changed to 700 in Tangerine (EIP150)
GasCallValue: 9000,
GasCallStipend: 2300,
GasNewAccount: 25_000,
GasExp: 10,
GasExpByte: 10, # Changed to 50 in Spurious Dragon (EIP160)
GasMemory: 3,
GasTXCreate: 32000,
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
result = baseCosts
result = previous_fees
result[GasSload] = 200
result[GasSelfDestruct] = 5000
result[GasBalance] = 400
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,
constants, vm_state,
opcode_values, stint, eth_common,
vm / [code_stream, memory, stack],
vm / [code_stream, memory, stack, forks/gas_costs],
./logging
export GasInt
export GasInt, gas_costs
type
BaseComputation* = ref object of RootObj
@ -30,9 +30,9 @@ type
logEntries*: seq[(EthAddress, seq[UInt256], string)]
shouldEraseReturnData*: bool
accountsToDelete*: Table[EthAddress, EthAddress]
opcodes*: Table[Op, Opcode] # TODO array[Op, Opcode]
opcodes*: Table[Op, proc(computation: var BaseComputation){.nimcall.}]
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
info*: string
@ -40,16 +40,10 @@ type
erasesReturnData*: bool
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
#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)
GasMeter* = ref object
@ -58,30 +52,6 @@ type
startGas*: 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
# A message for VM computation

View File

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

View File

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

View File

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

View File

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