nimbus-eth1/nimbus/evm/interpreter/gas_costs.nim

881 lines
36 KiB
Nim
Raw Normal View History

# 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 values zeroness remains unchanged or is set to zero.
2022-04-08 04:54:11 +00:00
RefundsClear, # Refund given (added into refund counter) when the storage value is set to zero from non-zero.
RefundSelfDestruct, # Refund given (added into refund counter) for self-destructing an account.
GasSelfDestruct, # Amount of gas to pay for a SELFDESTRUCT operation.
GasCreate, # Paid for a CREATE operation.
GasCodeDeposit, # Paid per byte for a CREATE operation to succeed in placing code into state.
GasCall, # Paid for a CALL operation.
GasCallValue, # Paid for a non-zero value transfer as part of the CALL operation.
GasCallStipend, # A stipend for the called contract subtracted from Gcallvalue for a non-zero value transfer.
GasNewAccount, # Paid for a CALL or SELFDESTRUCT operation which creates an account.
GasExp, # Partial payment for an EXP operation.
GasExpByte, # Partial payment when multiplied by ⌈log256(exponent)⌉ for the EXP operation.
GasMemory, # Paid for every additional word when expanding memory.
GasTXCreate, # Paid by all contract-creating transactions after the Homestead transition.
GasTXDataZero, # Paid for every zero byte of data or code for a transaction.
GasTXDataNonZero, # Paid for every non-zero byte of data or code for a transaction.
GasTransaction, # Paid for every transaction.
GasLog, # Partial payment for a LOG operation.
GasLogData, # Paid for each byte in a LOG operations data.
GasLogTopic, # Paid for each topic of a LOG operation.
GasSha3, # Paid for each SHA3 operation.
GasSha3Word, # Paid for each word (rounded up) for input data to a SHA3 operation.
GasCopy, # Partial payment for COPY operations, multiplied by words copied, rounded up.
GasBlockhash, # Payment for BLOCKHASH operation.
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
2022-12-02 04:35:41 +00:00
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 ##############################
2022-04-08 04:54:11 +00:00
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,
2023-06-26 09:58:59 +00:00
# 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,
2022-04-08 04:54:11 +00:00
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
2022-04-08 04:54:11 +00:00
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
2022-12-02 04:35:41 +00:00
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)
type
OpGck* = array[Op, GasCostKind]
func opGck(gc: GasCosts): OpGck {.compileTime.} =
for op, x in gc:
result[op] = x.kind
# Map fork to GasCostKind
# used in op_dispatcher.nim
const forkToGck*: array[EVMFork, OpGck] = [
opGck BaseGasCosts , # FkFrontier
opGck HomesteadGasCosts , # FkHomestead
opGck TangerineGasCosts , # kTangerine
opGck SpuriousGasCosts , # FkSpurious
opGck SpuriousGasCosts , # FkByzantium
opGck ConstantinopleGasCosts, # FkConstantinople
opGck SpuriousGasCosts , # FkPetersburg
opGck IstanbulGasCosts , # FkIstanbul
opGck BerlinGasCosts , # FkBerlin
opGck LondonGasCosts , # FkLondon
opGck LondonGasCosts , # FkParis
opGck ShanghaiGasCosts , # FkShanghai
opGck ShanghaiGasCosts , # FkCancun
opGck ShanghaiGasCosts , # FkPrague
]
2022-12-02 04:35:41 +00:00
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