Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

new(tests): EOF - EIP-7620 EOFCREATE gas testing #785

Merged
merged 6 commits into from
Sep 23, 2024
Merged
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
6 changes: 0 additions & 6 deletions tests/prague/eip7692_eof_v1/eip7069_extcall/helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,6 @@
slot_call_status = next(_slot)
slot_calldata_1 = next(_slot)
slot_calldata_2 = next(_slot)
slot_cold_gas = next(_slot)
slot_warm_gas = next(_slot)
slot_oog_call_result = next(_slot)
slot_sanity_call_result = next(_slot)

slot_last_slot = next(_slot)

Expand All @@ -28,8 +24,6 @@

"""Storage values for common testing fields"""
value_code_worked = 0x2015
value_call_legacy_abort = 0
value_call_legacy_success = 1

"""Memory and storage value for calldata"""
value_calldata_1 = 0xC1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1C1
Expand Down
140 changes: 12 additions & 128 deletions tests/prague/eip7692_eof_v1/eip7069_extcall/test_gas.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,21 +5,14 @@

import pytest

from ethereum_test_tools import Account, Alloc, Environment, StateTestFiller, Transaction
from ethereum_test_tools import Alloc, Environment, StateTestFiller
from ethereum_test_tools.eof.v1 import Container
from ethereum_test_tools.vm.opcode import Opcodes as Op
from ethereum_test_vm import Bytecode, EVMCodeType
from ethereum_test_types.helpers import cost_memory_bytes

from .. import EOF_FORK_NAME
from ..gas_test import gas_test
from . import REFERENCE_SPEC_GIT_PATH, REFERENCE_SPEC_VERSION
from .helpers import (
slot_cold_gas,
slot_oog_call_result,
slot_sanity_call_result,
slot_warm_gas,
value_call_legacy_abort,
value_call_legacy_success,
)

REFERENCE_SPEC_GIT_PATH = REFERENCE_SPEC_GIT_PATH
REFERENCE_SPEC_VERSION = REFERENCE_SPEC_VERSION
Expand All @@ -45,112 +38,6 @@ def state_env() -> Environment:
return Environment()


def gas_test(
state_test: StateTestFiller,
env: Environment,
pre: Alloc,
setup_code: Bytecode,
subject_code: Bytecode,
tear_down_code: Bytecode,
cold_gas: int,
warm_gas: int | None = None,
):
"""
Creates a State Test to check the gas cost of a sequence of EOF code.

`setup_code` and `tear_down_code` are called multiple times during the test, and MUST NOT have
any side-effects which persist across message calls, and in particular, any effects on the gas
usage of `subject_code`.
"""
if cold_gas <= 0:
raise ValueError(f"Target gas allocations (cold_gas) must be > 0, got {cold_gas}")
if warm_gas is None:
warm_gas = cold_gas

sender = pre.fund_eoa()

address_baseline = pre.deploy_contract(Container.Code(setup_code + tear_down_code))
address_subject = pre.deploy_contract(
Container.Code(setup_code + subject_code + tear_down_code)
)
# 2 times GAS, POP, CALL, 6 times PUSH1 - instructions charged for at every gas run
gas_single_gas_run = 2 * 2 + 2 + WARM_ACCOUNT_ACCESS_GAS + 6 * 3
address_legacy_harness = pre.deploy_contract(
code=(
# warm subject and baseline without executing
(Op.BALANCE(address_subject) + Op.POP + Op.BALANCE(address_baseline) + Op.POP)
# Baseline gas run
+ (
Op.GAS
+ Op.CALL(address=address_baseline, gas=Op.GAS)
+ Op.POP
+ Op.GAS
+ Op.SWAP1
+ Op.SUB
)
# cold gas run
+ (
Op.GAS
+ Op.CALL(address=address_subject, gas=Op.GAS)
+ Op.POP
+ Op.GAS
+ Op.SWAP1
+ Op.SUB
)
# warm gas run
+ (
Op.GAS
+ Op.CALL(address=address_subject, gas=Op.GAS)
+ Op.POP
+ Op.GAS
+ Op.SWAP1
+ Op.SUB
)
# Store warm gas: DUP3 is the gas of the baseline gas run
+ (Op.DUP3 + Op.SWAP1 + Op.SUB + Op.PUSH2(slot_warm_gas) + Op.SSTORE)
# store cold gas: DUP2 is the gas of the baseline gas run
+ (Op.DUP2 + Op.SWAP1 + Op.SUB + Op.PUSH2(slot_cold_gas) + Op.SSTORE)
# oog gas run:
# - DUP7 is the gas of the baseline gas run, after other CALL args were pushed
# - subtract the gas charged by the harness
# - add warm gas charged by the subject
# - subtract 1 to cause OOG exception
+ Op.SSTORE(
slot_oog_call_result,
Op.CALL(
gas=Op.ADD(warm_gas - gas_single_gas_run - 1, Op.DUP7),
address=address_subject,
),
)
# sanity gas run: not subtracting 1 to see if enough gas makes the call succeed
+ Op.SSTORE(
slot_sanity_call_result,
Op.CALL(
gas=Op.ADD(warm_gas - gas_single_gas_run, Op.DUP7),
address=address_subject,
),
)
+ Op.STOP
),
evm_code_type=EVMCodeType.LEGACY, # Needs to be legacy to use GAS opcode
)

post = {
address_legacy_harness: Account(
storage={
slot_warm_gas: warm_gas,
slot_cold_gas: cold_gas,
slot_oog_call_result: value_call_legacy_abort,
slot_sanity_call_result: value_call_legacy_success,
},
),
}

tx = Transaction(to=address_legacy_harness, gas_limit=env.gas_limit, sender=sender)

state_test(env=env, pre=pre, tx=tx, post=post)


@pytest.mark.parametrize(
["opcode", "pre_setup", "cold_gas", "warm_gas", "new_account"],
[
Expand Down Expand Up @@ -221,13 +108,8 @@ def gas_test(
],
)
@pytest.mark.parametrize(
["mem_expansion_size", "mem_expansion_extra_gas"],
[
pytest.param(0, 0, id="no_mem_expansion"),
pytest.param(1, 3, id="1byte_mem_expansion"),
pytest.param(32, 3, id="1word_mem_expansion"),
pytest.param(33, 6, id="33bytes_mem_expansion"),
],
"mem_expansion_bytes",
[0, 1, 32, 33],
)
def test_ext_calls_gas(
state_test: StateTestFiller,
Expand All @@ -238,8 +120,7 @@ def test_ext_calls_gas(
cold_gas: int,
warm_gas: int,
new_account: bool,
mem_expansion_size: int,
mem_expansion_extra_gas: int,
mem_expansion_bytes: int,
):
"""Tests variations of EXT*CALL gas, both warm and cold, without and with mem expansions"""
address_target = (
Expand All @@ -250,9 +131,12 @@ def test_ext_calls_gas(
state_test,
state_env,
pre,
setup_code=pre_setup + Op.PUSH1(mem_expansion_size) + Op.PUSH0 + Op.PUSH20(address_target),
setup_code=pre_setup
+ Op.PUSH1(mem_expansion_bytes)
+ Op.PUSH0
+ Op.PUSH20(address_target),
subject_code=opcode,
tear_down_code=Op.STOP,
cold_gas=cold_gas + mem_expansion_extra_gas,
warm_gas=warm_gas + mem_expansion_extra_gas,
cold_gas=cold_gas + cost_memory_bytes(mem_expansion_bytes, 0),
warm_gas=warm_gas + cost_memory_bytes(mem_expansion_bytes, 0),
)
51 changes: 44 additions & 7 deletions tests/prague/eip7692_eof_v1/eip7620_eof_create/helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
slot_returndata_size = next(_slot)
slot_max_depth = next(_slot)
slot_call_or_create = next(_slot)
slot_counter = next(_slot)

slot_last_slot = next(_slot)

Expand All @@ -30,12 +31,7 @@
value_eof_call_result_reverted = 1
value_eof_call_result_failed = 2

smallest_runtime_subcontainer = Container(
name="Runtime Subcontainer",
sections=[
Section.Code(code=Op.STOP),
],
)
smallest_runtime_subcontainer = Container.Code(code=Op.STOP, name="Runtime Subcontainer")

smallest_initcode_subcontainer = Container(
name="Initcode Subcontainer",
Expand All @@ -44,5 +40,46 @@
Section.Container(container=smallest_runtime_subcontainer),
],
)
smallest_initcode_subcontainer_gas = 2 * 3

aborting_container = Container.Code(Op.INVALID, name="Aborting Container")
reverting_container = Container.Code(Op.REVERT(0, 0), name="Reverting Container")
expensively_reverting_container = Container.Code(
Op.SHA3(0, 32) + Op.REVERT(0, 0), name="Expensively Reverting Container"
)
expensively_reverting_container_gas = 2 * 3 + 30 + 3 + 6 + 2 * 3
big_runtime_subcontainer = Container.Code(Op.NOOP * 10000 + Op.STOP, name="Big Subcontainer")

bigger_initcode_subcontainer_gas = 3 + 4 + 2 * 3
bigger_initcode_subcontainer = Container(
name="Bigger Initcode Subcontainer",
sections=[
Section.Code(
code=Op.RJUMPI[len(Op.RETURNCONTRACT[0](0, 0))](1)
+ Op.RETURNCONTRACT[0](0, 0)
+ Op.RETURNCONTRACT[1](0, 0)
),
Section.Container(container=smallest_runtime_subcontainer),
Section.Container(container=smallest_runtime_subcontainer),
],
)

aborting_container = Container.Code(Op.INVALID)
data_runtime_container = smallest_runtime_subcontainer.copy()
data_runtime_container.sections.append(Section.Data("0x00"))

data_initcode_subcontainer = Container(
name="Data Initcode Subcontainer",
sections=[
Section.Code(code=Op.RETURNCONTRACT[0](0, 0)),
Section.Container(container=data_runtime_container),
],
)

data_appending_initcode_subcontainer = Container(
name="Data Appending Initcode Subcontainer",
sections=[
Section.Code(code=Op.RETURNCONTRACT[0](0, 1)),
Section.Container(container=smallest_runtime_subcontainer),
],
)
data_appending_initcode_subcontainer_gas = 2 * 3 + 3
Loading