Skip to content

Commit

Permalink
feat(tests/eof): EIP-7069 - expand EXT*CALL gas testing (ethereum#771)
Browse files Browse the repository at this point in the history
Co-authored-by: danceratopz <[email protected]>
  • Loading branch information
pdobacz and danceratopz authored Sep 11, 2024
1 parent 4b46f12 commit 061e41c
Show file tree
Hide file tree
Showing 4 changed files with 187 additions and 67 deletions.
4 changes: 4 additions & 0 deletions tests/prague/eip7692_eof_v1/eip7069_extcall/helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@
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 @@ -26,6 +28,8 @@

"""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
175 changes: 145 additions & 30 deletions tests/prague/eip7692_eof_v1/eip7069_extcall/test_gas.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,20 +6,33 @@
import pytest

from ethereum_test_tools import Account, Alloc, Environment, StateTestFiller, Transaction
from ethereum_test_tools.eof.v1 import Container, Section
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 .. import EOF_FORK_NAME
from . import REFERENCE_SPEC_GIT_PATH, REFERENCE_SPEC_VERSION
from .helpers import slot_cold_gas, slot_warm_gas
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

pytestmark = pytest.mark.valid_from(EOF_FORK_NAME)


COLD_ACCOUNT_ACCESS_GAS = 2600
WARM_ACCOUNT_ACCESS_GAS = 100
CALL_WITH_VALUE_GAS = 9000
ACCOUNT_CREATION_GAS = 25000


@pytest.fixture
def state_env() -> Environment:
"""
Expand All @@ -42,37 +55,43 @@ def gas_test(
cold_gas: int,
warm_gas: int | None = None,
):
"""Creates a State Test to check the gas cost of a sequence of EOF code."""
"""
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 (warm_gas) must be > 0, got {cold_gas}")
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(10**18)
sender = pre.fund_eoa()

address_baseline = pre.deploy_contract(
Container(sections=[Section.Code(setup_code + tear_down_code)])
)
address_baseline = pre.deploy_contract(Container.Code(setup_code + tear_down_code))
address_subject = pre.deploy_contract(
Container(sections=[Section.Code(setup_code + subject_code + tear_down_code)])
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)
# cold gas run
# Baseline gas run
+ (
Op.GAS
+ Op.CALL(address=address_subject, gas=500_000)
+ Op.CALL(address=address_baseline, gas=Op.GAS)
+ Op.POP
+ Op.GAS
+ Op.SWAP1
+ Op.SUB
)
# Baseline gas run
# cold gas run
+ (
Op.GAS
+ Op.CALL(address=address_baseline, gas=500_000)
+ Op.CALL(address=address_subject, gas=Op.GAS)
+ Op.POP
+ Op.GAS
+ Op.SWAP1
Expand All @@ -81,16 +100,36 @@ def gas_test(
# warm gas run
+ (
Op.GAS
+ Op.CALL(address=address_subject, gas=500_000)
+ Op.CALL(address=address_subject, gas=Op.GAS)
+ Op.POP
+ Op.GAS
+ Op.SWAP1
+ Op.SUB
)
# Store warm gas
+ (Op.DUP2 + Op.SWAP1 + Op.SUB + Op.PUSH2(slot_warm_gas) + Op.SSTORE)
# store cold gas
+ (Op.SWAP1 + Op.SUB + Op.PUSH2(slot_cold_gas) + Op.SSTORE)
# 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
Expand All @@ -101,22 +140,93 @@ def gas_test(
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=2_000_000, sender=sender)
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"],
["opcode", "pre_setup", "cold_gas", "warm_gas", "new_account"],
[
pytest.param(Op.EXTCALL, Op.PUSH0, 2600, 100, id="EXTCALL"),
pytest.param(Op.EXTCALL, Op.PUSH1(1), 2600 + 9000, 100 + 9000, id="EXTCALL_with_value"),
pytest.param(Op.EXTDELEGATECALL, Op.NOOP, 2600, 100, id="EXTSTATICCALL"),
pytest.param(Op.EXTSTATICCALL, Op.NOOP, 2600, 100, id="EXTDELEGATECALL"),
pytest.param(
Op.EXTCALL,
Op.PUSH0,
COLD_ACCOUNT_ACCESS_GAS,
WARM_ACCOUNT_ACCESS_GAS,
False,
id="EXTCALL",
),
pytest.param(
Op.EXTCALL,
Op.PUSH1(1),
COLD_ACCOUNT_ACCESS_GAS + CALL_WITH_VALUE_GAS,
WARM_ACCOUNT_ACCESS_GAS + CALL_WITH_VALUE_GAS,
False,
id="EXTCALL_with_value",
),
pytest.param(
Op.EXTDELEGATECALL,
Op.NOOP,
COLD_ACCOUNT_ACCESS_GAS,
WARM_ACCOUNT_ACCESS_GAS,
False,
id="EXTDELEGATECALL",
),
pytest.param(
Op.EXTSTATICCALL,
Op.NOOP,
COLD_ACCOUNT_ACCESS_GAS,
WARM_ACCOUNT_ACCESS_GAS,
False,
id="EXTSTATICCALL",
),
pytest.param(
Op.EXTCALL,
Op.PUSH0,
COLD_ACCOUNT_ACCESS_GAS,
WARM_ACCOUNT_ACCESS_GAS,
True,
id="EXTCALL_new_acc",
),
pytest.param(
Op.EXTCALL,
Op.PUSH1(1),
COLD_ACCOUNT_ACCESS_GAS + ACCOUNT_CREATION_GAS + CALL_WITH_VALUE_GAS,
WARM_ACCOUNT_ACCESS_GAS + ACCOUNT_CREATION_GAS + CALL_WITH_VALUE_GAS,
True,
id="EXTCALL_with_value_new_acc",
),
pytest.param(
Op.EXTDELEGATECALL,
Op.NOOP,
COLD_ACCOUNT_ACCESS_GAS,
WARM_ACCOUNT_ACCESS_GAS,
True,
id="EXTDELEGATECALL_new_acc",
),
pytest.param(
Op.EXTSTATICCALL,
Op.NOOP,
COLD_ACCOUNT_ACCESS_GAS,
WARM_ACCOUNT_ACCESS_GAS,
True,
id="EXTSTATICCALL_new_acc",
),
],
)
@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"),
],
)
def test_ext_calls_gas(
Expand All @@ -126,18 +236,23 @@ def test_ext_calls_gas(
opcode: Op,
pre_setup: Op,
cold_gas: int,
warm_gas: int | None,
warm_gas: int,
new_account: bool,
mem_expansion_size: int,
mem_expansion_extra_gas: int,
):
"""Tests 4 variations of EXT*CALL gas, both warm and cold"""
address_target = pre.deploy_contract(Container(sections=[Section.Code(code=Op.STOP)]))
"""Tests variations of EXT*CALL gas, both warm and cold, without and with mem expansions"""
address_target = (
pre.fund_eoa(0) if new_account else pre.deploy_contract(Container.Code(Op.STOP))
)

gas_test(
state_test,
state_env,
pre,
setup_code=pre_setup + Op.PUSH0 + Op.PUSH0 + Op.PUSH20(address_target),
setup_code=pre_setup + Op.PUSH1(mem_expansion_size) + Op.PUSH0 + Op.PUSH20(address_target),
subject_code=opcode,
tear_down_code=Op.STOP,
cold_gas=cold_gas,
warm_gas=warm_gas,
cold_gas=cold_gas + mem_expansion_extra_gas,
warm_gas=warm_gas + mem_expansion_extra_gas,
)
Loading

0 comments on commit 061e41c

Please sign in to comment.