mirror of
https://github.com/status-im/nimbus-eth1.git
synced 2025-01-23 02:29:26 +00:00
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:
parent
8528f1b704
commit
90c3ca4a96
24
VMTests.md
24
VMTests.md
@ -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
|
||||
|
@ -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}")
|
||||
|
@ -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
|
||||
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
||||
|
@ -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)
|
||||
|
@ -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:
|
||||
|
@ -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)
|
||||
|
@ -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,
|
||||
|
@ -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)
|
||||
|
||||
|
@ -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()
|
||||
|
@ -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)
|
||||
|
@ -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)
|
||||
|
@ -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)
|
||||
|
||||
|
@ -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
|
||||
|
@ -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 two’s 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 two’s 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.
|
||||
|
126
nimbus/utils/macros_gen_opcodes.nim
Normal file
126
nimbus/utils/macros_gen_opcodes.nim
Normal 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
|
@ -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)
|
||||
|
@ -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 value’s 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 operation’s 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)
|
||||
|
@ -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
|
||||
|
||||
|
@ -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
|
||||
|
@ -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)
|
||||
|
@ -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`
|
||||
|
||||
|
@ -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
|
||||
|
Loading…
x
Reference in New Issue
Block a user