Skip to content

Commit 04778be

Browse files
feat: add eip7702, gas usage and extra edge cases
1 parent a4521cf commit 04778be

File tree

4 files changed

+163
-23
lines changed

4 files changed

+163
-23
lines changed

tests/osaka/eip7883_modexp_gas_increase/conftest.py

Lines changed: 53 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,18 @@
1212
from .spec import Spec, Spec7883
1313

1414

15+
@pytest.fixture
16+
def gas_old() -> int | None:
17+
"""Gas value from the test vector if any."""
18+
return None
19+
20+
21+
@pytest.fixture
22+
def gas_new() -> int | None:
23+
"""Gas value from the test vector if any."""
24+
return None
25+
26+
1527
@pytest.fixture
1628
def call_opcode() -> Op:
1729
"""Return default call used to call the precompile."""
@@ -27,6 +39,15 @@ def call_contract_post_storage() -> Storage:
2739
return Storage()
2840

2941

42+
@pytest.fixture
43+
def call_succeeds() -> bool:
44+
"""
45+
By default, depending on the expected output, we can deduce if the call is expected to succeed
46+
or fail.
47+
"""
48+
return True
49+
50+
3051
@pytest.fixture
3152
def gas_measure_contract(
3253
pre: Alloc,
@@ -36,6 +57,7 @@ def gas_measure_contract(
3657
precompile_gas: int,
3758
precompile_gas_modifier: int,
3859
call_contract_post_storage: Storage,
60+
call_succeeds: bool,
3961
) -> Address:
4062
"""Deploys a contract that measures ModExp gas consumption."""
4163
assert call_opcode in [Op.CALL, Op.CALLCODE, Op.DELEGATECALL, Op.STATICCALL]
@@ -78,41 +100,51 @@ def gas_measure_contract(
78100

79101
code = (
80102
Op.CALLDATACOPY(dest_offset=0, offset=0, size=Op.CALLDATASIZE)
81-
+ Op.SSTORE(call_contract_post_storage.store_next(True), call_result_measurement)
82-
+ Op.SSTORE(call_contract_post_storage.store_next(precompile_gas), gas_calculation)
103+
+ Op.SSTORE(call_contract_post_storage.store_next(call_succeeds), call_result_measurement)
83104
+ Op.SSTORE(
84105
call_contract_post_storage.store_next(len(modexp_expected)),
85106
Op.RETURNDATASIZE(),
86107
)
108+
)
109+
110+
if call_succeeds:
111+
code += Op.SSTORE(call_contract_post_storage.store_next(precompile_gas), gas_calculation)
87112
# + Op.RETURNDATACOPY(dest_offset=0, offset=0, size=Op.RETURNDATASIZE())
88113
# + Op.SSTORE(call_contract_post_storage.store_next(
89114
# keccak256(Bytes(vector.expected))), Op.SHA3(0, Op.RETURNDATASIZE()))
90-
)
91-
for i in range(len(modexp_expected) // 32):
92-
code += Op.RETURNDATACOPY(0, i * 32, 32)
93-
code += Op.SSTORE(
94-
call_contract_post_storage.store_next(modexp_expected[i * 32 : (i + 1) * 32]),
95-
Op.MLOAD(0),
96-
)
115+
116+
for i in range(len(modexp_expected) // 32):
117+
code += Op.RETURNDATACOPY(0, i * 32, 32)
118+
code += Op.SSTORE(
119+
call_contract_post_storage.store_next(modexp_expected[i * 32 : (i + 1) * 32]),
120+
Op.MLOAD(0),
121+
)
97122

98123
return pre.deploy_contract(code)
99124

100125

101126
@pytest.fixture
102-
def precompile_gas(fork: Fork, modexp_input: ModExpInput, gas_old: int, gas_new: int) -> int:
127+
def precompile_gas(
128+
fork: Fork, modexp_input: ModExpInput, gas_old: int | None, gas_new: int | None
129+
) -> int:
103130
"""Calculate gas cost for the ModExp precompile and verify it matches expected gas."""
104131
spec = Spec if fork < Osaka else Spec7883
105-
expected_gas = gas_old if fork < Osaka else gas_new
106-
calculated_gas = spec.calculate_gas_cost(
107-
len(modexp_input.base),
108-
len(modexp_input.modulus),
109-
len(modexp_input.exponent),
110-
modexp_input.exponent,
111-
)
112-
assert calculated_gas == expected_gas, (
113-
f"Calculated gas {calculated_gas} != Vector gas {expected_gas}"
114-
)
115-
return calculated_gas
132+
try:
133+
calculated_gas = spec.calculate_gas_cost(
134+
len(modexp_input.base),
135+
len(modexp_input.modulus),
136+
len(modexp_input.exponent),
137+
modexp_input.exponent,
138+
)
139+
if gas_old is not None and gas_new is not None:
140+
expected_gas = gas_old if fork < Osaka else gas_new
141+
assert calculated_gas == expected_gas, (
142+
f"Calculated gas {calculated_gas} != Vector gas {expected_gas}"
143+
)
144+
return calculated_gas
145+
except Exception as e:
146+
print(f"Error calculating gas: {e}")
147+
return 0
116148

117149

118150
@pytest.fixture

tests/osaka/eip7883_modexp_gas_increase/spec.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22

33
from dataclasses import dataclass
44

5+
from ...byzantium.eip198_modexp_precompile.helpers import ModExpInput
6+
57

68
@dataclass(frozen=True)
79
class ReferenceSpec:
@@ -37,6 +39,17 @@ class Spec:
3739
EXPONENT_THRESHOLD = 32
3840
GAS_DIVISOR = 3
3941

42+
# Test Constants
43+
modexp_input = ModExpInput(
44+
base="e8e77626586f73b955364c7b4bbf0bb7f7685ebd40e852b164633a4acbd3244c0001020304050607",
45+
exponent="01ffffff",
46+
modulus="f01681d2220bfea4bb888a5543db8c0916274ddb1ea93b144c042c01d8164c950001020304050607",
47+
)
48+
modexp_expected = bytes.fromhex(
49+
"1abce71dc2205cce4eb6934397a88136f94641342e283cbcd30e929e85605c6718ed67f475192ffd"
50+
)
51+
modexp_error = bytes()
52+
4053
@classmethod
4154
def calculate_multiplication_complexity(cls, base_length: int, modulus_length: int) -> int:
4255
"""Calculate the multiplication complexity of the ModExp precompile."""

tests/osaka/eip7883_modexp_gas_increase/test_modexp_thresholds.py

Lines changed: 96 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,14 +7,16 @@
77

88
import pytest
99

10+
from ethereum_test_checklists import EIPChecklist
1011
from ethereum_test_tools import (
1112
Alloc,
1213
StateTestFiller,
1314
Transaction,
1415
)
16+
from ethereum_test_tools.vm.opcode import Opcodes as Op
1517

1618
from .helpers import vectors_from_file
17-
from .spec import ref_spec_7883
19+
from .spec import Spec, ref_spec_7883
1820

1921
REFERENCE_SPEC_GIT_PATH = ref_spec_7883.git_path
2022
REFERENCE_SPEC_VERSION = ref_spec_7883.version
@@ -39,3 +41,96 @@ def test_vectors_from_file(
3941
tx=tx,
4042
post=post,
4143
)
44+
45+
46+
@pytest.mark.parametrize(
47+
"modexp_input,modexp_expected,call_succeeds",
48+
[
49+
pytest.param(bytes(), bytes(), False, id="zero-length-calldata"),
50+
],
51+
)
52+
@EIPChecklist.Precompile.Test.Inputs.AllZeros
53+
def test_modexp_invalid(
54+
state_test: StateTestFiller,
55+
pre: Alloc,
56+
tx: Transaction,
57+
post: Dict,
58+
):
59+
"""Test ModExp gas cost using the test vectors from EIP-7883."""
60+
state_test(
61+
pre=pre,
62+
tx=tx,
63+
post=post,
64+
)
65+
66+
67+
@pytest.mark.parametrize(
68+
"call_opcode",
69+
[
70+
Op.CALL,
71+
Op.STATICCALL,
72+
Op.DELEGATECALL,
73+
Op.CALLCODE,
74+
],
75+
)
76+
@pytest.mark.parametrize(
77+
"modexp_input,modexp_expected",
78+
[
79+
pytest.param(Spec.modexp_input, Spec.modexp_expected, id="base-heavy"),
80+
],
81+
)
82+
@EIPChecklist.Precompile.Test.CallContexts.Static
83+
@EIPChecklist.Precompile.Test.CallContexts.Delegate
84+
@EIPChecklist.Precompile.Test.CallContexts.Callcode
85+
@EIPChecklist.Precompile.Test.CallContexts.Normal
86+
def test_modexp_call_operations(
87+
state_test: StateTestFiller,
88+
pre: Alloc,
89+
tx: Transaction,
90+
post: Dict,
91+
):
92+
"""Test ModExp call related operations with EIP-7883."""
93+
state_test(
94+
pre=pre,
95+
tx=tx,
96+
post=post,
97+
)
98+
99+
100+
@pytest.mark.parametrize(
101+
"modexp_input,modexp_expected,precompile_gas_modifier,call_succeeds",
102+
[
103+
pytest.param(
104+
Spec.modexp_input,
105+
Spec.modexp_expected,
106+
1,
107+
True,
108+
id="extra_gas",
109+
),
110+
pytest.param(
111+
Spec.modexp_input,
112+
Spec.modexp_expected,
113+
0,
114+
True,
115+
id="exact_gas",
116+
),
117+
pytest.param(
118+
Spec.modexp_input,
119+
Spec.modexp_error,
120+
-1,
121+
False,
122+
id="insufficient_gas",
123+
),
124+
],
125+
)
126+
@EIPChecklist.Precompile.Test.ValueTransfer.Fee.Over
127+
@EIPChecklist.Precompile.Test.ValueTransfer.Fee.Exact
128+
@EIPChecklist.Precompile.Test.ValueTransfer.Fee.Under
129+
def test_modexp_gas_usage(
130+
state_test: StateTestFiller,
131+
pre: Alloc,
132+
tx: Transaction,
133+
post: Dict,
134+
):
135+
"""Test ModExp gas cost using the test vectors from EIP-7883."""
136+
state_test(pre=pre, tx=tx, post=post)

tests/prague/eip7702_set_code_tx/test_set_code_txs.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2541,7 +2541,7 @@ def test_set_code_to_log(
25412541

25422542
@pytest.mark.with_all_call_opcodes
25432543
@pytest.mark.with_all_precompiles
2544-
@pytest.mark.eip_checklist("precompile/test/call_contexts/set_code", eips=[7951])
2544+
@pytest.mark.eip_checklist("precompile/test/call_contexts/set_code", eips=[7951, 7883])
25452545
def test_set_code_to_precompile(
25462546
state_test: StateTestFiller,
25472547
pre: Alloc,

0 commit comments

Comments
 (0)