Skip to content

Commit

Permalink
new(tests): EOF - EIP-7620 EOFCREATE gas testing (#785)
Browse files Browse the repository at this point in the history
* new(tests): EOF - EIP-7620 - EOFCREATE gas testing

* fix(tests): EIP-7069/7620 - Use cost_memory_bytes

* fix(tests): EIP-7620 - fix new_account tests for EOFCREATE

* fix(tests): EIP-7620 - remaining review comments

* fix(tests): EIP-7620 - use Container.Init

* fix(tests): EIP-7620 - simplify parametrization in test_gas.py
  • Loading branch information
pdobacz authored Sep 23, 2024
1 parent e01f2d7 commit b035ad3
Show file tree
Hide file tree
Showing 5 changed files with 361 additions and 141 deletions.
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

0 comments on commit b035ad3

Please sign in to comment.