mirror of
https://github.com/status-im/nimbus-eth1.git
synced 2025-01-17 15:50:57 +00:00
8857fccb44
In the current VM opcode dispatcher, a two-level case statement is generated that first matches the opcode and then uses another nested case statement to select the actual implementation based on which fork it is, causing the dispatcher to grow by `O(opcodes) * O(forks)`. The fork does not change between instructions causing significant inefficiency for this approach - not only because it repeats the fork lookup but also because of code size bloat and missed optimizations. A second source of inefficiency in dispatching is the tracer code which in the vast majority of cases is disabled but nevertheless sees multiple conditionals being evaluated for each instruction only to remain disabled throughout exeuction. This PR rewrites the opcode dispatcher macro to generate a separate dispatcher for each fork and tracer setting and goes on to pick the right one at the start of the computation. This has many advantages: * much smaller dispatcher * easier to compile * better inlining * fewer pointlessly repeated instruction * simplified macro (!) * slow "low-compiler-memory" dispatcher code can be removed Net block import improvement at about 4-6% depending on the contract - synthetic EVM benchmnarks would show an even better result most likely.
855 lines
35 KiB
Nim
855 lines
35 KiB
Nim
# Nimbus
|
||
# Copyright (c) 2018-2024 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
|
||
math, eth/common/eth_types,
|
||
./utils/[macros_gen_opcodes, utils_numeric],
|
||
./op_codes, ../../common/evmforks,
|
||
../evm_errors
|
||
|
||
# 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.
|
||
GasExtCodeHash, # Payment for contract's code hashing
|
||
GasInitcodeWord # Payment for each word (rounded up) for initcode
|
||
GasWarmStorageRead # Transient storage read and write cost.
|
||
|
||
GasFeeSchedule = array[GasFeeKind, Natural]
|
||
|
||
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.
|
||
|
||
kind*: Op
|
||
isNewAccount*: bool
|
||
gasLeft*: GasInt
|
||
gasCallEIP2929*: GasInt
|
||
contractGas*: UInt256
|
||
currentMemSize*: GasNatural
|
||
memOffset*: GasNatural
|
||
memLength*: GasNatural
|
||
|
||
GasParamsSs* = object
|
||
currentValue*: UInt256
|
||
originalValue*: UInt256
|
||
|
||
GasParamsCr* = object
|
||
currentMemSize*: GasNatural
|
||
memOffset*: GasNatural
|
||
memLength*: GasNatural
|
||
|
||
GasCostKind* = enum
|
||
GckInvalidOp,
|
||
GckFixed,
|
||
GckDynamic,
|
||
GckMemExpansion,
|
||
GckCreate,
|
||
GckCall,
|
||
GckLater,
|
||
GckSuicide,
|
||
GckSstore
|
||
|
||
# gasRefund of sstore can be a negative number
|
||
SStoreGasResult* = object
|
||
gasCost*: GasInt
|
||
gasRefund*: int64
|
||
|
||
CallGasResult = tuple[gasCost, childGasLimit: GasInt]
|
||
|
||
GasCost = object
|
||
case kind*: GasCostKind
|
||
of GckInvalidOp:
|
||
discard
|
||
of GckFixed, GckLater:
|
||
cost*: GasInt
|
||
of GckDynamic:
|
||
d_handler*: proc(value: UInt256): GasInt
|
||
{.nimcall, gcsafe, raises: [].}
|
||
of GckMemExpansion:
|
||
m_handler*: proc(currentMemSize, memOffset, memLength: GasNatural): GasInt
|
||
{.nimcall, gcsafe, raises: [].}
|
||
of GckCreate:
|
||
cr_handler*: proc(value: UInt256, params: GasParamsCr): GasInt
|
||
{.nimcall, gcsafe, raises: [].}
|
||
of GckCall:
|
||
c_handler*: proc(value: UInt256, params: GasParams): EvmResult[CallGasResult]
|
||
{.nimcall, gcsafe, raises: [].}
|
||
# 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)
|
||
of GckSuicide:
|
||
sc_handler*: proc(condition: bool): GasInt
|
||
{.nimcall, gcsafe, raises: [].}
|
||
of GckSstore:
|
||
ss_handler*: proc(value: UInt256, params: GasParamsSs): SStoreGasResult
|
||
{.nimcall, gcsafe, raises: [].}
|
||
|
||
GasCosts* = array[Op, GasCost]
|
||
|
||
const
|
||
# From EIP-2929
|
||
ColdSloadCost* = 2100
|
||
ColdAccountAccessCost* = 2600
|
||
WarmStorageReadCost* = 100
|
||
|
||
# From EIP-2930 (Berlin).
|
||
ACCESS_LIST_STORAGE_KEY_COST* = 1900.GasInt
|
||
ACCESS_LIST_ADDRESS_COST* = 2400.GasInt
|
||
|
||
template gasCosts(fork: EVMFork, prefix, ResultGasCostsName: untyped) =
|
||
|
||
## Generate the gas cost for each forks and store them in a const
|
||
## named `ResultGasCostsName`
|
||
|
||
const FeeSchedule = gasFees[fork]
|
||
|
||
# ############### Helper functions ##############################
|
||
|
||
func `prefix gasMemoryExpansion`(currentMemSize, memOffset, memLength: GasNatural): 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
|
||
#
|
||
# Except when memLength = 0, where per eq (297),
|
||
# M(currentMemSize, f, l) = currentMemSize
|
||
|
||
let
|
||
prevWords = currentMemSize.wordCount
|
||
newWords = (memOffset + memLength).wordCount
|
||
|
||
if memLength == 0 or newWords <= prevWords:
|
||
# Special subcase of memory-expansion cost
|
||
# currentMemSize - currentMemSize = 0
|
||
# "Referencing a zero length range ... does not require memory to be extended
|
||
# to the beginning of the range."
|
||
#
|
||
# Also, don't credit EVM code for allocating memory
|
||
# then accessing lots of low memory. memoryGasCost,
|
||
# via go-ethereum, checks for this as special case.
|
||
return 0
|
||
|
||
let
|
||
prevCost = prevWords * static(FeeSchedule[GasMemory]) +
|
||
(prevWords ^ 2) shr 9 # div 512
|
||
newCost = newWords * static(FeeSchedule[GasMemory]) +
|
||
(newWords ^ 2) shr 9 # div 512
|
||
|
||
# TODO: add logging
|
||
result = GasInt(max(newCost - prevCost, 0))
|
||
|
||
when fork >= FkTangerine:
|
||
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
|
||
|
||
# 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 = GasInt(static FeeSchedule[GasExp])
|
||
if not value.isZero:
|
||
result += GasInt(static(FeeSchedule[GasExpByte]) * (1 + log256(value)))
|
||
|
||
func `prefix gasCreate`(value: UInt256, params: GasParamsCr): GasInt {.nimcall.} =
|
||
if value.isZero:
|
||
result = GasInt(static(FeeSchedule[GasCodeDeposit]) * params.memLength)
|
||
else:
|
||
result = GasInt(static(FeeSchedule[GasCreate]) +
|
||
(static(FeeSchedule[GasInitcodeWord]) * params.memLength.wordCount)) +
|
||
`prefix gasMemoryExpansion`(params.currentMemSize, params.memOffset, params.memLength)
|
||
|
||
func `prefix gasSha3`(currentMemSize, memOffset, memLength: GasNatural): GasInt {.nimcall.} =
|
||
|
||
result = `prefix gasMemoryExpansion`(currentMemSize, memOffset, memLength)
|
||
result += GasInt(static(FeeSchedule[GasSha3]) +
|
||
static(FeeSchedule[GasSha3Word]) * (memLength).wordCount)
|
||
|
||
func `prefix gasCopy`(currentMemSize, memOffset, memLength: GasNatural): GasInt {.nimcall.} =
|
||
result = GasInt(static(FeeSchedule[GasVeryLow]) +
|
||
static(FeeSchedule[GasCopy]) * memLength.wordCount)
|
||
result += GasInt(`prefix gasMemoryExpansion`(currentMemSize, memOffset, memLength))
|
||
|
||
func `prefix gasExtCodeCopy`(currentMemSize, memOffset, memLength: GasNatural): GasInt {.nimcall.} =
|
||
result = GasInt(static(FeeSchedule[GasExtCode]) +
|
||
static(FeeSchedule[GasCopy]) * memLength.wordCount)
|
||
result += `prefix gasMemoryExpansion`(currentMemSize, memOffset, memLength)
|
||
|
||
func `prefix gasLoadStore`(currentMemSize, memOffset, memLength: GasNatural): GasInt {.nimcall.} =
|
||
result = GasInt(static(FeeSchedule[GasVeryLow]))
|
||
result += `prefix gasMemoryExpansion`(currentMemSize, memOffset, memLength)
|
||
|
||
func `prefix gasSstore`(value: UInt256, params: GasParamsSs): SStoreGasResult {.nimcall.} =
|
||
## Value is word to save
|
||
var res: SStoreGasResult
|
||
when fork >= FkBerlin:
|
||
# EIP2929
|
||
const
|
||
SLOAD_GAS = WarmStorageReadCost
|
||
SSTORE_RESET_GAS = 5000 - ColdSloadCost
|
||
else:
|
||
const
|
||
SLOAD_GAS = FeeSchedule[GasSload]
|
||
SSTORE_RESET_GAS = FeeSchedule[GasSreset]
|
||
|
||
const
|
||
NoopGas {.used.} = GasInt SLOAD_GAS # if the value doesn't change.
|
||
DirtyGas {.used.} = GasInt SLOAD_GAS # if a dirty value is changed.
|
||
InitGas {.used.} = GasInt FeeSchedule[GasSset] # from clean zero to non-zero
|
||
InitRefund {.used.} = FeeSchedule[GasSset] - SLOAD_GAS # resetting to the original zero value
|
||
CleanGas {.used.} = GasInt SSTORE_RESET_GAS # from clean non-zero to something else
|
||
CleanRefund {.used.} = SSTORE_RESET_GAS - SLOAD_GAS # resetting to the original non-zero value
|
||
ClearRefund {.used.} = FeeSchedule[RefundsClear]# clearing an originally existing storage slot
|
||
|
||
when fork < FkConstantinople or fork == FkPetersburg:
|
||
let isStorageEmpty = params.currentValue.isZero
|
||
|
||
# Gas cost - literal translation of Yellow Paper
|
||
res.gasCost = if value.isZero.not and isStorageEmpty:
|
||
InitGas
|
||
else:
|
||
CleanGas
|
||
|
||
# Refund
|
||
if value.isZero and not isStorageEmpty:
|
||
res.gasRefund = ClearRefund
|
||
else:
|
||
# 0. If *gasleft* is less than or equal to 2300, fail the current call.
|
||
# 1. If current value equals new value (this is a no-op), SSTORE_NOOP_GAS gas is deducted.
|
||
# 2. If current value does not equal new value:
|
||
# 2.1. If original value equals current value (this storage slot has not been changed by the current execution context):
|
||
# 2.1.1. If original value is 0, SSTORE_INIT_GAS gas is deducted.
|
||
# 2.1.2. Otherwise, SSTORE_CLEAN_GAS gas is deducted. If new value is 0, add SSTORE_CLEAR_REFUND to refund counter.
|
||
# 2.2. If original value does not equal current value (this storage slot is dirty), SSTORE_DIRTY_GAS gas is deducted. Apply both of the following clauses:
|
||
# 2.2.1. If original value is not 0:
|
||
# 2.2.1.1. If current value is 0 (also means that new value is not 0), subtract SSTORE_CLEAR_REFUND gas from refund counter. We can prove that refund counter will never go below 0.
|
||
# 2.2.1.2. If new value is 0 (also means that current value is not 0), add SSTORE_CLEAR_REFUND gas to refund counter.
|
||
# 2.2.2. If original value equals new value (this storage slot is reset):
|
||
# 2.2.2.1. If original value is 0, add SSTORE_INIT_REFUND to refund counter.
|
||
# 2.2.2.2. Otherwise, add SSTORE_CLEAN_REFUND gas to refund counter.
|
||
|
||
# Gas sentry honoured, do the actual gas calculation based on the stored value
|
||
if params.currentValue == value: # noop (1)
|
||
res.gasCost = NoopGas
|
||
return res
|
||
|
||
if params.originalValue == params.currentValue:
|
||
if params.originalValue.isZero: # create slot (2.1.1)
|
||
res.gasCost = InitGas
|
||
return res
|
||
|
||
if value.isZero: # delete slot (2.1.2b)
|
||
res.gasRefund = ClearRefund
|
||
|
||
res.gasCost = CleanGas # write existing slot (2.1.2)
|
||
return res
|
||
|
||
if not params.originalValue.isZero:
|
||
if params.currentValue.isZero: # recreate slot (2.2.1.1)
|
||
res.gasRefund -= ClearRefund
|
||
if value.isZero: # delete slot (2.2.1.2)
|
||
res.gasRefund += ClearRefund
|
||
|
||
if params.originalValue == value:
|
||
if params.originalValue.isZero: # reset to original inexistent slot (2.2.2.1)
|
||
res.gasRefund += InitRefund
|
||
else: # reset to original existing slot (2.2.2.2)
|
||
res.gasRefund += CleanRefund
|
||
|
||
res.gasCost = DirtyGas # dirty update (2.2)
|
||
res
|
||
|
||
func `prefix gasLog0`(currentMemSize, memOffset, memLength: GasNatural): GasInt {.nimcall.} =
|
||
result = `prefix gasMemoryExpansion`(currentMemSize, memOffset, memLength)
|
||
|
||
result += GasInt(static(FeeSchedule[GasLog]) +
|
||
static(FeeSchedule[GasLogData]) * memLength)
|
||
|
||
func `prefix gasLog1`(currentMemSize, memOffset, memLength: GasNatural): GasInt {.nimcall.} =
|
||
result = `prefix gasMemoryExpansion`(currentMemSize, memOffset, memLength)
|
||
|
||
result += GasInt(static(FeeSchedule[GasLog]) +
|
||
static(FeeSchedule[GasLogData]) * memLength +
|
||
static(FeeSchedule[GasLogTopic]))
|
||
|
||
func `prefix gasLog2`(currentMemSize, memOffset, memLength: GasNatural): GasInt {.nimcall.} =
|
||
result = `prefix gasMemoryExpansion`(currentMemSize, memOffset, memLength)
|
||
|
||
result += GasInt(static(FeeSchedule[GasLog]) +
|
||
static(FeeSchedule[GasLogData]) * memLength +
|
||
static(2 * FeeSchedule[GasLogTopic]))
|
||
|
||
func `prefix gasLog3`(currentMemSize, memOffset, memLength: GasNatural): GasInt {.nimcall.} =
|
||
result = `prefix gasMemoryExpansion`(currentMemSize, memOffset, memLength)
|
||
|
||
result += GasInt(static(FeeSchedule[GasLog]) +
|
||
static(FeeSchedule[GasLogData]) * memLength +
|
||
static(3 * FeeSchedule[GasLogTopic]))
|
||
|
||
func `prefix gasLog4`(currentMemSize, memOffset, memLength: GasNatural): GasInt {.nimcall.} =
|
||
result = `prefix gasMemoryExpansion`(currentMemSize, memOffset, memLength)
|
||
|
||
result += GasInt(static(FeeSchedule[GasLog]) +
|
||
static(FeeSchedule[GasLogData]) * memLength +
|
||
static(4 * FeeSchedule[GasLogTopic]))
|
||
|
||
func `prefix gasCall`(value: UInt256, params: GasParams): EvmResult[CallGasResult] {.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
|
||
|
||
# Both gasCost and childGasLimit are always on positive side
|
||
|
||
var gasLeft = params.gasLeft
|
||
if gasLeft < params.gasCallEIP2929:
|
||
return err(opErr(OutOfGas))
|
||
gasLeft -= params.gasCallEIP2929
|
||
|
||
var gasCost: GasInt = `prefix gasMemoryExpansion`(
|
||
params.currentMemSize,
|
||
params.memOffset,
|
||
params.memLength)
|
||
var childGasLimit: GasInt
|
||
|
||
# Cnew_account
|
||
if params.isNewAccount and params.kind == Call:
|
||
when fork < FkSpurious:
|
||
# Pre-EIP161 all account creation calls consumed 25000 gas.
|
||
gasCost += static(GasInt(FeeSchedule[GasNewAccount]))
|
||
else:
|
||
# Afterwards, only those transfering value:
|
||
# https://github.com/ethereum/EIPs/blob/master/EIPS/eip-158.md
|
||
# https://github.com/ethereum/EIPs/blob/master/EIPS/eip-161.md
|
||
if not value.isZero:
|
||
gasCost += static(GasInt(FeeSchedule[GasNewAccount]))
|
||
|
||
# Cxfer
|
||
if not value.isZero and params.kind in {Call, CallCode}:
|
||
gasCost += static(GasInt(FeeSchedule[GasCallValue]))
|
||
|
||
# Cextra
|
||
gasCost += static(GasInt(FeeSchedule[GasCall]))
|
||
|
||
if gasLeft < gasCost:
|
||
return err(opErr(OutOfGas))
|
||
gasLeft -= gasCost
|
||
|
||
# Cgascap
|
||
when fork >= FkTangerine:
|
||
# https://github.com/ethereum/EIPs/blob/master/EIPS/eip-150.md
|
||
let gas = `prefix all_but_one_64th`(gasLeft)
|
||
if params.contractGas > high(GasInt).u256 or
|
||
gas < params.contractGas.truncate(GasInt):
|
||
childGasLimit = gas
|
||
else:
|
||
childGasLimit = params.contractGas.truncate(GasInt)
|
||
else:
|
||
if params.contractGas > high(GasInt).u256:
|
||
return err(gasErr(GasIntOverflow))
|
||
childGasLimit = params.contractGas.truncate(GasInt)
|
||
|
||
if gasCost.u256 + childGasLimit.u256 + params.gasCallEIP2929.u256 > high(GasInt).u256:
|
||
return err(gasErr(GasIntOverflow))
|
||
|
||
gasCost += childGasLimit
|
||
gasCost += params.gasCallEIP2929
|
||
|
||
# Ccallgas - Gas sent to the child message
|
||
if not value.isZero and params.kind in {Call, CallCode}:
|
||
childGasLimit += static(GasInt(FeeSchedule[GasCallStipend]))
|
||
|
||
# at this point gasCost and childGasLimit is always > 0
|
||
ok( (gasCost, childGasLimit) )
|
||
|
||
func `prefix gasHalt`(currentMemSize, memOffset, memLength: GasNatural): GasInt {.nimcall.} =
|
||
`prefix gasMemoryExpansion`(currentMemSize, memOffset, memLength)
|
||
|
||
func `prefix gasSelfDestruct`(condition: bool): GasInt {.nimcall.} =
|
||
result += static(GasInt(FeeSchedule[GasSelfDestruct]))
|
||
when fork >= FkTangerine:
|
||
if condition:
|
||
result += static(GasInt(FeeSchedule[GasNewAccount]))
|
||
|
||
func `prefix gasCreate2`(currentMemSize, memOffset, memLength: GasNatural): GasInt {.nimcall.} =
|
||
result = GasInt(static(FeeSchedule[GasSha3Word]) * (memLength).wordCount)
|
||
|
||
# ###################################################################################################
|
||
|
||
let `ResultGasCostsName`*{.inject, compileTime.}: 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(GasInt(FeeSchedule[gasFeeKind])))
|
||
|
||
func fixedOrLater(gasFeeKind: static[GasFeeKind]): GasCost =
|
||
when fork < FkBerlin:
|
||
GasCost(kind: GckFixed, cost: static(GasInt(FeeSchedule[gasFeeKind])))
|
||
else:
|
||
# GckLater is processed by the opcode
|
||
GasCost(kind: GckLater, cost: static(GasInt(FeeSchedule[gasFeeKind])))
|
||
|
||
func dynamic(handler: proc(value: UInt256): GasInt
|
||
{.nimcall, gcsafe, raises: [].}): GasCost =
|
||
GasCost(kind: GckDynamic, d_handler: handler)
|
||
|
||
func memExpansion(handler: proc(currentMemSize, memOffset, memLength: GasNatural): GasInt
|
||
{.nimcall, gcsafe, raises: [].}): GasCost =
|
||
GasCost(kind: GckMemExpansion, m_handler: handler)
|
||
|
||
func handleCall(handler: proc(value: UInt256, gasParams: GasParams): EvmResult[CallGasResult]
|
||
{.nimcall, gcsafe, raises: [].}): GasCost =
|
||
GasCost(kind: GckCall, c_handler: handler)
|
||
|
||
func handleCreate(handler: proc(value: UInt256, gasParams: GasParamsCr): GasInt
|
||
{.nimcall, gcsafe, raises: [].}): GasCost =
|
||
GasCost(kind: GckCreate, cr_handler: handler)
|
||
|
||
func handleSuicide(handler: proc(condition: bool): GasInt
|
||
{.nimcall, gcsafe, raises: [].}): GasCost =
|
||
GasCost(kind: GckSuicide, sc_handler: handler)
|
||
|
||
func handleSstore(handler: proc(value: UInt256, gasParams: GasParamsSs): SStoreGasResult
|
||
{.nimcall, gcsafe, raises: [].}): GasCost =
|
||
GasCost(kind: GckSstore, ss_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,
|
||
Shl: fixed GasVeryLow,
|
||
Shr: fixed GasVeryLow,
|
||
Sar: fixed GasVeryLow,
|
||
|
||
# 20s: SHA3
|
||
Sha3: memExpansion `prefix gasSha3`,
|
||
|
||
# 30s: Environmental Information
|
||
Address: fixed GasBase,
|
||
Balance: fixedOrLater GasBalance,
|
||
Origin: fixed GasBase,
|
||
Caller: fixed GasBase,
|
||
CallValue: fixed GasBase,
|
||
CallDataLoad: fixed GasVeryLow,
|
||
CallDataSize: fixed GasBase,
|
||
CallDataCopy: memExpansion `prefix gasCopy`,
|
||
CodeSize: fixed GasBase,
|
||
CodeCopy: memExpansion `prefix gasCopy`,
|
||
GasPrice: fixed GasBase,
|
||
ExtCodeSize: fixedOrLater GasExtCode,
|
||
ExtCodeCopy: memExpansion `prefix gasExtCodeCopy`,
|
||
ReturnDataSize: fixed GasBase,
|
||
ReturnDataCopy: memExpansion `prefix gasCopy`,
|
||
ExtCodeHash: fixedOrLater GasExtCodeHash,
|
||
|
||
# 40s: Block Information
|
||
Blockhash: fixed GasBlockhash,
|
||
Coinbase: fixed GasBase,
|
||
Timestamp: fixed GasBase,
|
||
Number: fixed GasBase,
|
||
Difficulty: fixed GasBase,
|
||
GasLimit: fixed GasBase,
|
||
ChainIdOp: fixed GasBase,
|
||
SelfBalance: fixed GasLow,
|
||
BaseFee: fixed GasBase,
|
||
BlobHash: fixed GasVeryLow,
|
||
BlobBaseFee: 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: fixedOrLater GasSload,
|
||
Sstore: handleSstore `prefix gasSstore`,
|
||
Jump: fixed GasMid,
|
||
JumpI: fixed GasHigh,
|
||
Pc: fixed GasBase,
|
||
Msize: fixed GasBase,
|
||
Gas: fixed GasBase,
|
||
JumpDest: fixed GasJumpDest,
|
||
|
||
# 5c & 5d: Transient storage operations
|
||
Tload: fixed GasWarmStorageRead,
|
||
Tstore: fixed GasWarmStorageRead,
|
||
|
||
# 5e: Memory copy
|
||
Mcopy: memExpansion `prefix gasCopy`,
|
||
|
||
# 5f, 60s & 70s: Push Operations
|
||
Push0: fixed GasBase,
|
||
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: handleCreate `prefix gasCreate`,
|
||
Call: handleCall `prefix gasCall`,
|
||
CallCode: handleCall `prefix gasCall`,
|
||
Return: memExpansion `prefix gasHalt`,
|
||
DelegateCall: handleCall `prefix gasCall`,
|
||
Create2: memExpansion `prefix gasCreate2`,
|
||
StaticCall: handleCall `prefix gasCall`,
|
||
Revert: memExpansion `prefix gasHalt`,
|
||
Invalid: fixed GasZero,
|
||
SelfDestruct: handleSuicide `prefix gasSelfDestruct`
|
||
]
|
||
|
||
# Generate the fork-specific gas costs tables
|
||
const
|
||
BaseGasFees: GasFeeSchedule = [
|
||
# Fee Schedule for the initial Ethereum forks
|
||
GasZero: 0,
|
||
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: 0, # Changed to 32000 in Homestead (EIP2)
|
||
GasTXDataZero: 4,
|
||
GasTXDataNonZero: 68,
|
||
GasTransaction: 21000,
|
||
GasLog: 375,
|
||
GasLogData: 8,
|
||
GasLogTopic: 375,
|
||
GasSha3: 30,
|
||
GasSha3Word: 6,
|
||
GasCopy: 3,
|
||
GasBlockhash: 20,
|
||
GasExtCodeHash: 400,
|
||
GasInitcodeWord: 0, # Changed to 2 in EIP-3860
|
||
GasWarmStorageRead: WarmStorageReadCost
|
||
]
|
||
|
||
# Create the schedule for each forks
|
||
func homesteadGasFees(previousFees: GasFeeSchedule): GasFeeSchedule =
|
||
# https://github.com/ethereum/EIPs/blob/master/EIPS/eip-2.md
|
||
result = previousFees
|
||
result[GasTXCreate] = 32000
|
||
|
||
func tangerineGasFees(previousFees: GasFeeSchedule): GasFeeSchedule =
|
||
# https://github.com/ethereum/EIPs/blob/master/EIPS/eip-150.md
|
||
result = previousFees
|
||
result[GasExtCode] = 700
|
||
result[GasSload] = 200
|
||
result[GasSelfDestruct] = 5000
|
||
result[GasBalance] = 400
|
||
result[GasCall] = 700
|
||
|
||
func spuriousGasFees(previousFees: GasFeeSchedule): GasFeeSchedule =
|
||
# https://github.com/ethereum/EIPs/blob/master/EIPS/eip-160.md
|
||
result = previousFees
|
||
result[GasExpByte] = 50
|
||
|
||
func istanbulGasFees(previousFees: GasFeeSchedule): GasFeeSchedule =
|
||
# https://eips.ethereum.org/EIPS/eip-1884
|
||
result = previousFees
|
||
result[GasSload] = 800
|
||
result[GasExtCodeHash] = 700
|
||
result[GasBalance] = 700
|
||
result[GasTXDataNonZero]= 16
|
||
|
||
func berlinGasFees(previousFees: GasFeeSchedule): GasFeeSchedule =
|
||
# https://eips.ethereum.org/EIPS/eip-2929
|
||
result = previousFees
|
||
|
||
# these opcodes gas are calculated inside opcode
|
||
result[GasBalance] = 0
|
||
result[GasExtCodeHash] = 0
|
||
result[GasExtCode] = 0
|
||
|
||
# SLOAD gasCost become fully dynamic, see sloadEIP2929
|
||
result[GasSload] = 0
|
||
result[GasCall] = WarmStorageReadCost
|
||
|
||
func londonGasFees(previousFees: GasFeeSchedule): GasFeeSchedule =
|
||
result = previousFees
|
||
# EIP-3529 RefundsClear(4800) =
|
||
# EIP-2929(5000 - ColdSloadCost) +
|
||
# EIP-2930(ACCESS_LIST_STORAGE_KEY_COST)
|
||
result[RefundsClear] =
|
||
5000 - ColdSloadCost +
|
||
ACCESS_LIST_STORAGE_KEY_COST
|
||
|
||
func shanghaiGasFees(previousFees: GasFeeSchedule): GasFeeSchedule =
|
||
result = previousFees
|
||
result[GasInitcodeWord] = 2.GasInt # INITCODE_WORD_COST from EIP-3860
|
||
|
||
const
|
||
HomesteadGasFees = BaseGasFees.homesteadGasFees
|
||
TangerineGasFees = HomesteadGasFees.tangerineGasFees
|
||
SpuriousGasFees = TangerineGasFees.spuriousGasFees
|
||
IstanbulGasFees = SpuriousGasFees.istanbulGasFees
|
||
BerlinGasFees = IstanbulGasFees.berlinGasFees
|
||
LondonGasFees = BerlinGasFees.londonGasFees
|
||
ShanghaiGasFees = LondonGasFees.shanghaiGasFees
|
||
|
||
gasFees*: array[EVMFork, GasFeeSchedule] = [
|
||
FkFrontier: BaseGasFees,
|
||
FkHomestead: HomesteadGasFees,
|
||
FkTangerine: TangerineGasFees,
|
||
FkSpurious: SpuriousGasFees,
|
||
FkByzantium: SpuriousGasFees,
|
||
FkConstantinople: SpuriousGasFees,
|
||
FkPetersburg: SpuriousGasFees,
|
||
FkIstanbul: IstanbulGasFees,
|
||
FkBerlin: BerlinGasFees,
|
||
FkLondon: LondonGasFees,
|
||
FkParis: LondonGasFees,
|
||
FkShanghai: ShanghaiGasFees,
|
||
FkCancun: ShanghaiGasFees,
|
||
FkPrague: ShanghaiGasFees,
|
||
]
|
||
|
||
gasCosts(FkFrontier, base, BaseGasCosts)
|
||
gasCosts(FkHomestead, homestead, HomesteadGasCosts)
|
||
gasCosts(FkTangerine, tangerine, TangerineGasCosts)
|
||
gasCosts(FkSpurious, spurious, SpuriousGasCosts)
|
||
gasCosts(FkConstantinople, constantinople, ConstantinopleGasCosts)
|
||
gasCosts(FkIstanbul, istanbul, IstanbulGasCosts)
|
||
gasCosts(FkBerlin, berlin, BerlinGasCosts)
|
||
gasCosts(FkLondon, london, LondonGasCosts)
|
||
gasCosts(FkShanghai, shanghai, ShanghaiGasCosts)
|
||
|
||
proc forkToSchedule*(fork: EVMFork): GasCosts =
|
||
if fork < FkHomestead:
|
||
BaseGasCosts
|
||
elif fork < FkTangerine:
|
||
HomesteadGasCosts
|
||
elif fork < FkSpurious:
|
||
TangerineGasCosts
|
||
elif fork == FkConstantinople:
|
||
ConstantinopleGasCosts # with EIP-1283
|
||
elif fork < FkIstanbul:
|
||
SpuriousGasCosts
|
||
elif fork < FkBerlin:
|
||
IstanbulGasCosts
|
||
elif fork < FkLondon:
|
||
BerlinGasCosts
|
||
elif fork < FkShanghai:
|
||
LondonGasCosts
|
||
else:
|
||
ShanghaiGasCosts
|
||
|
||
const
|
||
## Precompile costs
|
||
GasSHA256* = GasInt 60
|
||
GasSHA256Word* = GasInt 12
|
||
GasRIPEMD160* = GasInt 600
|
||
GasRIPEMD160Word* = GasInt 120
|
||
GasIdentity* = GasInt 15
|
||
GasIdentityWord* = GasInt 3
|
||
GasECRecover* = GasInt 3000
|
||
GasECAdd* = GasInt 500
|
||
GasECAddIstanbul* = GasInt 150
|
||
GasECMul* = GasInt 40000
|
||
GasECMulIstanbul* = GasInt 6000
|
||
GasECPairingBase* = GasInt 100000
|
||
GasECPairingBaseIstanbul* = GasInt 45000
|
||
GasECPairingPerPoint* = GasInt 80000
|
||
GasECPairingPerPointIstanbul* = GasInt 34000
|
||
# The Yellow Paper is special casing the GasQuadDivisor.
|
||
# It is defined in Appendix G with the other GasFeeKind constants
|
||
# instead of Appendix E for precompiled contracts
|
||
GasQuadDivisor* = 20
|
||
GasQuadDivisorEIP2565* = 3
|
||
# https://eips.ethereum.org/EIPS/eip-2537
|
||
Bls12381G1AddGas* = GasInt 500
|
||
Bls12381G1MulGas* = GasInt 12000
|
||
Bls12381G2AddGas* = GasInt 800
|
||
Bls12381G2MulGas* = GasInt 45000
|
||
Bls12381PairingBaseGas* = GasInt 115000
|
||
Bls12381PairingPerPairGas* = GasInt 23000
|
||
Bls12381MapG1Gas* = GasInt 5500
|
||
Bls12381MapG2Gas* = GasInt 110000
|