Skip to content
Closed
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
16 changes: 13 additions & 3 deletions src/ethereum_test_forks/forks/forks.py
Original file line number Diff line number Diff line change
Expand Up @@ -116,7 +116,7 @@ def gas_costs(cls, *, block_number: int = 0, timestamp: int = 0) -> GasCosts:
G_CALL_STIPEND=2_300,
G_NEW_ACCOUNT=25_000,
G_EXP=10,
G_EXP_BYTE=50,
G_EXP_BYTE=10,
G_MEMORY=3,
G_TX_DATA_ZERO=4,
G_TX_DATA_NON_ZERO=68,
Expand Down Expand Up @@ -814,12 +814,22 @@ class Tangerine(DAOFork, ignore=True):


class SpuriousDragon(Tangerine, ignore=True):
"""SpuriousDragon fork (EIP-155, EIP-158)."""
"""SpuriousDragon fork (EIP-155, EIP-158, EIP-160)."""

@classmethod
def gas_costs(cls, *, block_number: int = 0, timestamp: int = 0) -> GasCosts:
"""
On SpuriousDragon, EXP byte gas cost is increased to 50.
"""
return replace(
super(SpuriousDragon, cls).gas_costs(block_number=block_number, timestamp=timestamp),
G_EXP_BYTE=50, # https://eips.ethereum.org/EIPS/eip-160
)

pass


class Byzantium(Homestead):
class Byzantium(SpuriousDragon):
"""Byzantium fork."""

@classmethod
Expand Down
173 changes: 173 additions & 0 deletions tests/frontier/opcodes/test_all_opcodes.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@

import pytest

from ethereum_test_base_types.base_types import ZeroPaddedHexNumber
from ethereum_test_forks import Fork
from ethereum_test_tools import (
Account,
Expand All @@ -19,6 +20,8 @@
)
from ethereum_test_vm import Opcode, UndefinedOpcodes
from ethereum_test_vm import Opcodes as Op
from tests.unscheduled.eip7692_eof_v1.eip3540_eof_v1.opcodes import V1_EOF_ONLY_OPCODES
from tests.unscheduled.eip7692_eof_v1.gas_test import gas_test
Copy link
Contributor Author

@pdobacz pdobacz Oct 20, 2025

Choose a reason for hiding this comment

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

my main point I'm not 100% sold on, but gas_test handles a ton of issues out-of-the-box for us. Once EOF tests are maybe removed it could be simplified and moved out of that EOF specific directory.

The bits that gas_tests handles:

  • setting the exact amount of gas needed to oog
  • warm and cold call
  • sanity check, i.e. add one gas and no more oog
  • (not) having to deal with intrinsic gas etc.
  • in general - recognizing when the oog happened and when not - just by manipulating tx gas_limit is quite difficult


REFERENCE_SPEC_GIT_PATH = "N/A"
REFERENCE_SPEC_VERSION = "N/A"
Expand Down Expand Up @@ -122,3 +125,173 @@ def test_cover_revert(state_test: StateTestFiller, pre: Alloc) -> None:
)

state_test(env=Environment(), pre=pre, post={}, tx=tx)


opcode_to_gas = {
Op.ADD: 3,
Op.MUL: 5,
Op.SUB: 3,
Op.DIV: 5,
Op.SDIV: 5,
Op.MOD: 5,
Op.SMOD: 5,
Op.ADDMOD: 8,
Op.MULMOD: 8,
Op.EXP: 10,
Op.SIGNEXTEND: 5,
Op.LT: 3,
Op.GT: 3,
Op.SLT: 3,
Op.SGT: 3,
Op.EQ: 3,
Op.ISZERO: 3,
Op.AND: 3,
Op.OR: 3,
Op.XOR: 3,
Op.NOT: 3,
Op.BYTE: 3,
Op.SHL: 3,
Op.SHR: 3,
Op.SAR: 3,
Op.CLZ: 5,
Op.SHA3: 30,
Op.ADDRESS: 2,
Op.BALANCE: 100,
Op.ORIGIN: 2,
Op.CALLER: 2,
Op.CALLVALUE: 2,
Op.CALLDATALOAD: 3,
Op.CALLDATASIZE: 2,
Op.CALLDATACOPY: 3,
Op.CODESIZE: 2,
Op.CODECOPY: 3,
Op.GASPRICE: 2,
Op.EXTCODESIZE: 100,
Op.EXTCODECOPY: 100,
Op.RETURNDATASIZE: 2,
Op.RETURNDATACOPY: 3,
Op.EXTCODEHASH: 100,
Op.BLOCKHASH: 20,
Op.COINBASE: 2,
Op.TIMESTAMP: 2,
Op.NUMBER: 2,
Op.PREVRANDAO: 2,
Op.GASLIMIT: 2,
Op.CHAINID: 2,
Op.SELFBALANCE: 5,
Op.BASEFEE: 2,
Op.BLOBHASH: 3,
Op.BLOBBASEFEE: 2,
Op.POP: 2,
Op.MLOAD: 3,
Op.MSTORE: 3,
Op.MSTORE8: 3,
Op.SLOAD: 100,
Op.JUMP: 8,
Op.JUMPI: 10,
Op.PC: 2,
Op.MSIZE: 2,
Op.GAS: 2,
Op.JUMPDEST: 1,
Op.TLOAD: 100,
Op.TSTORE: 100,
Op.MCOPY: 3,
Op.PUSH0: 2,
Op.LOG0: 375,
Op.LOG1: 2 * 375,
Op.LOG2: 3 * 375,
Op.LOG3: 4 * 375,
Op.LOG4: 5 * 375,
Op.CREATE: 32000,
Op.CALL: 100,
Op.CALLCODE: 100,
Op.DELEGATECALL: 100,
Op.CREATE2: 32000,
Op.STATICCALL: 100,
Op.SELFDESTRUCT: 5000,
}

# PUSHx, SWAPx, DUPx have uniform gas costs
for opcode in set(Op):
if 0x60 <= opcode.int() <= 0x9F:
opcode_to_gas[opcode] = 3

constant_gas_opcodes = (
set(Op)
-
# zero constant gas opcodes - untestable
{Op.STOP, Op.RETURN, Op.REVERT, Op.INVALID}
-
# TODO: EOF opcodes. Remove once EOF is removed
set(V1_EOF_ONLY_OPCODES)
-
# SSTORE - untestable due to 2300 gas stipend rule
{Op.SSTORE}
)


def prepare_stack_constant_gas_oog(opcode: Opcode) -> Bytecode:
"""Prepare valid stack for opcode."""
if opcode == Op.JUMPI:
return Op.PUSH1(1) + Op.PUSH1(3) + Op.PC + Op.ADD
if opcode == Op.JUMP:
return Op.PUSH1(3) + Op.PC + Op.ADD
if opcode == Op.BLOCKHASH:
return Op.PUSH1(0x01)
return Op.PUSH1(0x00) * 32


# NOTE: Gas costs varying across forks not being supported yet would make this
# test very complex.
@pytest.mark.valid_at("Osaka")
@pytest.mark.parametrize("opcode", sorted(constant_gas_opcodes))
def test_constant_gas(
state_test: StateTestFiller, pre: Alloc, opcode: Op, fork: Fork, env: Environment
) -> None:
"""Test that constant gas opcodes work as expected."""
warm_gas = opcode_to_gas[opcode]
cold_gas = warm_gas + (
2500
if opcode
in [
Op.BALANCE,
Op.EXTCODESIZE,
Op.EXTCODECOPY,
Op.EXTCODEHASH,
Op.CALL,
Op.CALLCODE,
Op.DELEGATECALL,
Op.STATICCALL,
]
else 2600
if opcode == Op.SELFDESTRUCT
else 2000
if opcode == Op.SLOAD
else 0
)

if cap := fork.transaction_gas_limit_cap():
env.gas_limit = ZeroPaddedHexNumber(cap)

# Using `TLOAD` / `TSTORE` to work around warm/cold gas differences. We
# need a counter to pick a distinct salt on each `CREATE2` and avoid
# running into address conflicts.
code_increment_counter = Op.TLOAD(1234) + Op.DUP1 + Op.TSTORE(1234, Op.PUSH1(1) + Op.ADD)
setup_code = (
Op.MLOAD(0)
+ Op.POP
+ prepare_stack_constant_gas_oog(opcode)
+ (code_increment_counter if opcode == Op.CREATE2 else Bytecode())
)
gas_test(
fork,
state_test,
env,
pre,
setup_code=setup_code,
subject_code=opcode,
tear_down_code=prepare_suffix(opcode),
cold_gas=cold_gas,
warm_gas=warm_gas,
eof=False,
)
61 changes: 61 additions & 0 deletions tests/frontier/opcodes/test_exp.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
"""
Test EXP opcode.
"""

import pytest

from ethereum_test_base_types.base_types import ZeroPaddedHexNumber
from ethereum_test_forks import Fork
from ethereum_test_tools import (
Alloc,
Environment,
StateTestFiller,
)
from ethereum_test_vm import Opcodes as Op
from tests.unscheduled.eip7692_eof_v1.gas_test import gas_test

REFERENCE_SPEC_GIT_PATH = "N/A"
REFERENCE_SPEC_VERSION = "N/A"


def exp_gas(fork: Fork, exponent: int) -> int:
"""Calculate gas cost for EXP opcode given the exponent."""
byte_len = (exponent.bit_length() + 7) // 8
return fork.gas_costs().G_EXP + fork.gas_costs().G_EXP_BYTE * byte_len


@pytest.mark.valid_from("Berlin")
@pytest.mark.parametrize("a", [0, 1, pytest.param(2**256 - 1, id="a2to256minus1")])
@pytest.mark.parametrize(
"exponent",
[
0,
1,
2,
1023,
1024,
pytest.param(2**255, id="exponent2to255"),
pytest.param(2**256 - 1, id="exponent2to256minus1"),
],
)
def test_gas(
state_test: StateTestFiller, pre: Alloc, a: int, exponent: int, fork: Fork, env: Environment
) -> None:
"""Test that EXP gas works as expected."""
warm_gas = exp_gas(fork, exponent)

if cap := fork.transaction_gas_limit_cap():
env.gas_limit = ZeroPaddedHexNumber(cap)

gas_test(
fork,
state_test,
env,
pre,
setup_code=Op.PUSH32(exponent) + Op.PUSH32(a),
subject_code=Op.EXP,
tear_down_code=Op.STOP,
cold_gas=warm_gas,
warm_gas=warm_gas,
eof=False,
)
77 changes: 77 additions & 0 deletions tests/frontier/opcodes/test_log.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
"""
Test LOGx opcodes.
"""

import pytest

from ethereum_test_base_types.base_types import ZeroPaddedHexNumber
from ethereum_test_forks import Fork
from ethereum_test_tools import (
Alloc,
Environment,
StateTestFiller,
)
from ethereum_test_vm import Opcodes as Op
from tests.unscheduled.eip7692_eof_v1.gas_test import gas_test

REFERENCE_SPEC_GIT_PATH = "N/A"
REFERENCE_SPEC_VERSION = "N/A"


def log_gas(fork: Fork, topics: int, data_size: int) -> int:
"""
Calculate gas cost for LOGx opcodes given the number of topics and data
size.
"""
return (
fork.gas_costs().G_LOG
+ fork.gas_costs().G_LOG_TOPIC * topics
+ fork.gas_costs().G_LOG_DATA * data_size
)


@pytest.mark.valid_from("Berlin")
@pytest.mark.parametrize(
"opcode,topics", [(Op.LOG0, 0), (Op.LOG1, 1), (Op.LOG2, 2), (Op.LOG3, 3), (Op.LOG4, 4)]
)
@pytest.mark.parametrize(
"data_size",
[
0,
1,
2,
1023,
1024,
],
)
def test_gas(
state_test: StateTestFiller,
pre: Alloc,
opcode: Op,
topics: int,
data_size: int,
fork: Fork,
env: Environment,
) -> None:
"""Test that LOGx gas works as expected."""
warm_gas = log_gas(fork, topics, data_size)

if cap := fork.transaction_gas_limit_cap():
env.gas_limit = ZeroPaddedHexNumber(cap)
print(hex(data_size))

gas_test(
fork,
state_test,
env,
pre,
setup_code=Op.MSTORE8(data_size, 0)
+ Op.PUSH1(0) * topics
+ Op.PUSH32(data_size)
+ Op.PUSH1(0),
subject_code=opcode,
tear_down_code=Op.STOP,
cold_gas=warm_gas,
warm_gas=warm_gas,
eof=False,
)
3 changes: 3 additions & 0 deletions tests/unscheduled/eip7692_eof_v1/eip3540_eof_v1/opcodes.py
Original file line number Diff line number Diff line change
Expand Up @@ -212,6 +212,7 @@
Op.EXTCALL,
Op.EXTDELEGATECALL,
Op.EXTSTATICCALL,
Op.RETURNDATALOAD,
# EIP-7480 EOF Data Section Access
Op.DATALOAD,
Op.DATALOADN,
Expand All @@ -220,6 +221,8 @@
# EIP-7620 EOF Create and Return Contract operation
Op.EOFCREATE,
Op.RETURNCODE,
# EIP-7873 TXCREATE and InitcodeTransaction
Op.TXCREATE,
]
"""
List of valid EOF V1 opcodes that are disabled in legacy bytecode.
Expand Down
Loading
Loading