Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions packages/testing/src/execution_testing/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,7 @@
Op,
Opcode,
OpcodeCallArg,
OpcodeGasCalculator,
Opcodes,
UndefinedOpcodes,
call_return_code,
Expand Down Expand Up @@ -174,6 +175,7 @@
"Op",
"Opcode",
"OpcodeCallArg",
"OpcodeGasCalculator",
"Opcodes",
"ParameterSet",
"ReferenceSpec",
Expand Down
198 changes: 140 additions & 58 deletions packages/testing/src/execution_testing/forks/forks/forks.py
Original file line number Diff line number Diff line change
Expand Up @@ -115,46 +115,45 @@ def gas_costs(
"""
del block_number, timestamp
return GasCosts(
G_JUMPDEST=1,
G_BASE=2,
G_VERY_LOW=3,
G_LOW=5,
G_MID=8,
G_HIGH=10,
G_WARM_ACCOUNT_ACCESS=100,
G_COLD_ACCOUNT_ACCESS=2_600,
G_ACCESS_LIST_ADDRESS=2_400,
G_ACCESS_LIST_STORAGE=1_900,
G_WARM_SLOAD=100,
G_COLD_SLOAD=2_100,
G_STORAGE_SET=20_000,
G_STORAGE_RESET=2_900,
R_STORAGE_CLEAR=4_800,
G_SELF_DESTRUCT=5_000,
G_CREATE=32_000,
G_CODE_DEPOSIT_BYTE=200,
G_INITCODE_WORD=2,
G_CALL_VALUE=9_000,
G_CALL_STIPEND=2_300,
G_NEW_ACCOUNT=25_000,
G_EXP=10,
G_EXP_BYTE=50,
G_MEMORY=3,
GAS_JUMPDEST=1,
GAS_BASE=2,
GAS_VERY_LOW=3,
GAS_LOW=5,
GAS_MID=8,
GAS_HIGH=10,
GAS_WARM_ACCESS=100,
GAS_COLD_ACCOUNT_ACCESS=2_600,
TX_ACCESS_LIST_ADDRESS_COST=2_400,
TX_ACCESS_LIST_STORAGE_KEY_COST=1_900,
GAS_COLD_SLOAD=2_100,
GAS_STORAGE_SET=20_000,
GAS_STORAGE_UPDATE=2_900,
GAS_STORAGE_CLEAR_REFUND=4_800,
GAS_SELF_DESTRUCT=5_000,
GAS_CREATE=32_000,
GAS_CODE_DEPOSIT=200,
GAS_INIT_CODE_WORD_COST=2,
GAS_CALL_VALUE=9_000,
GAS_CALL_STIPEND=2_300,
GAS_NEW_ACCOUNT=25_000,
GAS_EXPONENTIATION=10,
GAS_EXPONENTIATION_PER_BYTE=50,
GAS_MEMORY=3,
G_TX_DATA_ZERO=4,
G_TX_DATA_NON_ZERO=68,
G_TX_DATA_STANDARD_TOKEN_COST=0,
G_TX_DATA_FLOOR_TOKEN_COST=0,
G_TRANSACTION=21_000,
G_TRANSACTION_CREATE=32_000,
G_LOG=375,
G_LOG_DATA=8,
G_LOG_TOPIC=375,
G_KECCAK_256=30,
G_KECCAK_256_WORD=6,
G_COPY=3,
G_BLOCKHASH=20,
G_AUTHORIZATION=0,
R_AUTHORIZATION_EXISTING_AUTHORITY=0,
STANDARD_CALLDATA_TOKEN_COST=0,
FLOOR_CALLDATA_COST=0,
TX_BASE_COST=21_000,
TX_CREATE_COST=32_000,
GAS_LOG=375,
GAS_LOG_DATA=8,
GAS_LOG_TOPIC=375,
GAS_KECCAK256=30,
GAS_KECCAK256_WORD=6,
GAS_COPY=3,
GAS_BLOCK_HASH=20,
PER_EMPTY_ACCOUNT_COST=0,
PER_AUTH_BASE_COST=0,
)

@classmethod
Expand All @@ -176,7 +175,7 @@ def fn(*, new_bytes: int, previous_bytes: int = 0) -> int:
previous_words = ceiling_division(previous_bytes, 32)

def c(w: int) -> int:
return (gas_costs.G_MEMORY * w) + ((w * w) // 512)
return (gas_costs.GAS_MEMORY * w) + ((w * w) // 512)

return c(new_words) - c(previous_words)

Expand Down Expand Up @@ -295,11 +294,12 @@ def fn(
f"Authorizations are not supported in {cls.name()}"
)

intrinsic_cost: int = gas_costs.G_TRANSACTION
intrinsic_cost: int = gas_costs.TX_BASE_COST

if contract_creation:
intrinsic_cost += gas_costs.G_INITCODE_WORD * ceiling_division(
len(Bytes(calldata)), 32
intrinsic_cost += (
gas_costs.GAS_INIT_CODE_WORD_COST
* ceiling_division(len(Bytes(calldata)), 32)
)

return intrinsic_cost + calldata_gas_calculator(data=calldata)
Expand Down Expand Up @@ -923,7 +923,7 @@ def fn(
authorization_list_or_count=authorization_list_or_count,
)
if contract_creation:
intrinsic_cost += gas_costs.G_TRANSACTION_CREATE
intrinsic_cost += gas_costs.TX_CREATE_COST
return intrinsic_cost

return fn
Expand Down Expand Up @@ -1155,9 +1155,11 @@ def fn(
)
if access_list is not None:
for access in access_list:
intrinsic_cost += gas_costs.G_ACCESS_LIST_ADDRESS
intrinsic_cost += gas_costs.TX_ACCESS_LIST_ADDRESS_COST
for _ in access.storage_keys:
intrinsic_cost += gas_costs.G_ACCESS_LIST_STORAGE
intrinsic_cost += (
gas_costs.TX_ACCESS_LIST_STORAGE_KEY_COST
)
return intrinsic_cost

return fn
Expand Down Expand Up @@ -1849,15 +1851,15 @@ def gas_costs(
On Prague, the standard token cost and the floor token costs are
introduced due to EIP-7623.
"""
return replace(
super(Prague, cls).gas_costs(
block_number=block_number, timestamp=timestamp
),
G_TX_DATA_STANDARD_TOKEN_COST=4, # https://eips.ethereum.org/EIPS/eip-7623
G_TX_DATA_FLOOR_TOKEN_COST=10,
G_AUTHORIZATION=25_000,
R_AUTHORIZATION_EXISTING_AUTHORITY=12_500,
)
from ethereum.forks.prague.vm import gas as g
from dataclasses import fields

kwargs = {}
for field in fields(GasCosts):
if hasattr(g, field.name):
kwargs[field.name] = int(getattr(g, field.name))

return GasCosts(**kwargs)

@classmethod
def system_contracts(
Expand Down Expand Up @@ -1919,8 +1921,8 @@ def fn(*, data: BytesConvertible, floor: bool = False) -> int:
else:
tokens += 4
if floor:
return tokens * gas_costs.G_TX_DATA_FLOOR_TOKEN_COST
return tokens * gas_costs.G_TX_DATA_STANDARD_TOKEN_COST
return tokens * gas_costs.FLOOR_CALLDATA_COST
return tokens * gas_costs.STANDARD_CALLDATA_TOKEN_COST

return fn

Expand All @@ -1942,7 +1944,7 @@ def transaction_data_floor_cost_calculator(
def fn(*, data: BytesConvertible) -> int:
return (
calldata_gas_calculator(data=data, floor=True)
+ gas_costs.G_TRANSACTION
+ gas_costs.TX_BASE_COST
)

return fn
Expand Down Expand Up @@ -1987,7 +1989,8 @@ def fn(
authorization_list_or_count
)
intrinsic_cost += (
authorization_list_or_count * gas_costs.G_AUTHORIZATION
authorization_list_or_count
* gas_costs.PER_EMPTY_ACCOUNT_COST
)

if return_cost_deducted_prior_execution:
Expand Down Expand Up @@ -2141,6 +2144,32 @@ def engine_forkchoice_updated_version(
del block_number, timestamp
return 3

@classmethod
def op_cost(
cls, opcode: Opcodes, *, block_number: int = 0, timestamp: int = 0
) -> int:
"""
Return the base gas cost for a given opcode at Prague fork.
"""
from ethereum.forks.prague.vm import gas as g

opcode_name = opcode.name

if opcode_name.startswith("PUSH") and opcode_name != "PUSH0":
gas_const_name = "G_PUSHx"
elif opcode_name.startswith("DUP") and len(opcode_name) > 3:
gas_const_name = "G_DUPx"
elif opcode_name.startswith("SWAP") and len(opcode_name) > 4:
gas_const_name = "G_SWAPx"
elif opcode_name.startswith("LOG") and len(opcode_name) > 3:
gas_const_name = "G_LOGx"
else:
gas_const_name = f"G_{opcode_name}"

if hasattr(g, gas_const_name):
return int(getattr(g, gas_const_name))
return 0


class Osaka(Prague, solc_name="cancun"):
"""Osaka fork."""
Expand Down Expand Up @@ -2325,6 +2354,59 @@ def blob_base_cost(
del block_number, timestamp
return 2**13 # EIP-7918 new parameter

@classmethod
def gas_costs(
cls, *, block_number: int = 0, timestamp: int = 0
) -> GasCosts:
"""Return the gas costs for the fork."""
from ethereum.forks.osaka.vm import gas as g
from dataclasses import fields

kwargs = {}
for field in fields(GasCosts):
if hasattr(g, field.name):
kwargs[field.name] = int(getattr(g, field.name))

return GasCosts(**kwargs)

@classmethod
def op_cost(
cls, opcode: Opcodes, *, block_number: int = 0, timestamp: int = 0
) -> int:
Comment on lines +2372 to +2375
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We need to define this op_cost function for every fork to enable fork-dependent gas table feature.

"""
Return the base gas cost for a given opcode at Osaka fork.

Args:
opcode: The opcode enum value
block_number: Block number context
timestamp: Timestamp context

Returns:
Base gas cost for the opcode

Example:
>>> Osaka.op_cost(Opcodes.ADD)
3
"""
from ethereum.forks.osaka.vm import gas as g

opcode_name = opcode.name

if opcode_name.startswith("PUSH") and opcode_name != "PUSH0":
gas_const_name = "G_PUSHx"
elif opcode_name.startswith("DUP") and len(opcode_name) > 3:
gas_const_name = "G_DUPx"
elif opcode_name.startswith("SWAP") and len(opcode_name) > 4:
gas_const_name = "G_SWAPx"
elif opcode_name.startswith("LOG") and len(opcode_name) > 3:
gas_const_name = "G_LOGx"
else:
gas_const_name = f"G_{opcode_name}"

if hasattr(g, gas_const_name):
return int(getattr(g, gas_const_name))
return 0


class BPO1(Osaka, bpo_fork=True):
"""Mainnet BPO1 fork - Blob Parameter Only fork 1."""
Expand Down
87 changes: 43 additions & 44 deletions packages/testing/src/execution_testing/forks/gas_costs.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,56 +7,55 @@
class GasCosts:
"""Class that contains the gas cost constants for any fork."""

G_JUMPDEST: int
G_BASE: int
G_VERY_LOW: int
G_LOW: int
G_MID: int
G_HIGH: int
G_WARM_ACCOUNT_ACCESS: int
G_COLD_ACCOUNT_ACCESS: int
G_ACCESS_LIST_ADDRESS: int
G_ACCESS_LIST_STORAGE: int
G_WARM_SLOAD: int
G_COLD_SLOAD: int
G_STORAGE_SET: int
G_STORAGE_RESET: int

R_STORAGE_CLEAR: int

G_SELF_DESTRUCT: int
G_CREATE: int

G_CODE_DEPOSIT_BYTE: int
G_INITCODE_WORD: int

G_CALL_VALUE: int
G_CALL_STIPEND: int
G_NEW_ACCOUNT: int

G_EXP: int
G_EXP_BYTE: int

G_MEMORY: int
GAS_JUMPDEST: int
GAS_BASE: int
GAS_VERY_LOW: int
GAS_LOW: int
GAS_MID: int
GAS_HIGH: int
GAS_WARM_ACCESS: int
GAS_COLD_ACCOUNT_ACCESS: int
TX_ACCESS_LIST_ADDRESS_COST: int
TX_ACCESS_LIST_STORAGE_KEY_COST: int
GAS_COLD_SLOAD: int
GAS_STORAGE_SET: int
GAS_STORAGE_UPDATE: int

GAS_STORAGE_CLEAR_REFUND: int

GAS_SELF_DESTRUCT: int
GAS_CREATE: int

GAS_CODE_DEPOSIT: int
GAS_INIT_CODE_WORD_COST: int

GAS_CALL_VALUE: int
GAS_CALL_STIPEND: int
GAS_NEW_ACCOUNT: int

GAS_EXPONENTIATION: int
GAS_EXPONENTIATION_PER_BYTE: int

GAS_MEMORY: int

G_TX_DATA_ZERO: int
G_TX_DATA_NON_ZERO: int
G_TX_DATA_STANDARD_TOKEN_COST: int
G_TX_DATA_FLOOR_TOKEN_COST: int
STANDARD_CALLDATA_TOKEN_COST: int
FLOOR_CALLDATA_COST: int

G_TRANSACTION: int
G_TRANSACTION_CREATE: int
TX_BASE_COST: int
TX_CREATE_COST: int

G_LOG: int
G_LOG_DATA: int
G_LOG_TOPIC: int
GAS_LOG: int
GAS_LOG_DATA: int
GAS_LOG_TOPIC: int

G_KECCAK_256: int
G_KECCAK_256_WORD: int
GAS_KECCAK256: int
GAS_KECCAK256_WORD: int

G_COPY: int
G_BLOCKHASH: int
GAS_COPY: int
GAS_BLOCK_HASH: int

G_AUTHORIZATION: int
PER_EMPTY_ACCOUNT_COST: int

R_AUTHORIZATION_EXISTING_AUTHORITY: int
PER_AUTH_BASE_COST: int
Loading
Loading