1
- """Shared pytest definitions local to EIP-7883 tests."""
1
+ """Shared pytest definitions for EIP-7883 tests."""
2
2
3
- from typing import Dict
3
+ from typing import Dict , Tuple
4
4
5
5
import pytest
6
6
9
9
Account ,
10
10
Address ,
11
11
Alloc ,
12
+ Bytecode ,
13
+ CodeGasMeasure ,
12
14
Environment ,
13
15
Storage ,
14
16
Transaction ,
15
17
)
16
18
from ethereum_test_tools .vm .opcode import Opcodes as Op
17
19
20
+ from .helpers import parse_modexp_input
18
21
from .spec import Spec
19
22
20
23
@@ -25,74 +28,102 @@ def env() -> Environment:
25
28
26
29
27
30
@pytest .fixture
28
- def sender (pre : Alloc ):
31
+ def parsed_input (input_data : bytes ) -> Tuple [bytes , bytes , bytes , int ]:
32
+ """Parse the ModExp input data."""
33
+ return parse_modexp_input (input_data )
34
+
35
+
36
+ @pytest .fixture
37
+ def base (parsed_input : Tuple [bytes , bytes , bytes , int ]) -> bytes :
38
+ """Get the base value from the parsed input."""
39
+ return parsed_input [0 ]
40
+
41
+
42
+ @pytest .fixture
43
+ def exponent_bytes (parsed_input : Tuple [bytes , bytes , bytes , int ]) -> bytes :
44
+ """Get the exponent bytes from the parsed input."""
45
+ return parsed_input [1 ]
46
+
47
+
48
+ @pytest .fixture
49
+ def modulus (parsed_input : Tuple [bytes , bytes , bytes , int ]) -> bytes :
50
+ """Get the modulus value from the parsed input."""
51
+ return parsed_input [2 ]
52
+
53
+
54
+ @pytest .fixture
55
+ def exponent (parsed_input : Tuple [bytes , bytes , bytes , int ]) -> int :
56
+ """Get the exponent value from the parsed input."""
57
+ return parsed_input [3 ]
58
+
59
+
60
+ @pytest .fixture
61
+ def sender (pre : Alloc ) -> EOA :
29
62
"""Create and fund an EOA to be used as the transaction sender."""
30
63
return pre .fund_eoa ()
31
64
32
65
33
66
@pytest .fixture
34
- def gas_measure_contract (pre : Alloc ):
35
- """Deploys a simple contract to call ModExp and store its success."""
36
- # TODO: fix the gas accounting
37
- measured_code = Op .CALLDATACOPY (0 , 0 , Op .CALLDATASIZE ) + Op .SSTORE (
38
- 0 ,
39
- Op .CALL (
40
- address = Spec .MODEXP_ADDRESS ,
41
- value = 0 ,
42
- args_offset = 0 ,
43
- args_size = Op .CALLDATASIZE ,
44
- ret_offset = 0 ,
45
- ret_size = 0x80 ,
46
- ),
47
- )
48
- return pre .deploy_contract (measured_code )
67
+ def call_opcode () -> Op :
68
+ """Return default call used to call the precompile."""
69
+ return Op .CALL
49
70
50
71
51
72
@pytest .fixture
52
- def base (base_length , request ):
53
- """Generate base bytes with the specified length."""
54
- return b"\xff " * base_length # arbitrary bytes
73
+ def modexp_call_code (call_opcode : Op , input_data : bytes ) -> Bytecode :
74
+ """Create bytecode to call the ModExp precompile."""
75
+ call_code = call_opcode (
76
+ address = Spec .MODEXP_ADDRESS ,
77
+ value = 0 ,
78
+ args_offset = 0 ,
79
+ args_size = Op .CALLDATASIZE ,
80
+ ret_offset = 0 ,
81
+ ret_size = 0x80 ,
82
+ )
83
+ call_code += Op .SSTORE (0 , Op .ISZERO (Op .ISZERO ))
84
+ return call_code
55
85
56
86
57
87
@pytest .fixture
58
- def modulus (modulus_length , request ):
59
- """Generate modulus bytes with the specified length."""
60
- return b"\xff " * modulus_length # arbitrary bytes
88
+ def gas_measure_contract (pre : Alloc , modexp_call_code : Bytecode ) -> Address :
89
+ """Deploys a contract that measures ModExp gas consumption."""
90
+ calldata_copy = Op .CALLDATACOPY (0 , 0 , Op .CALLDATASIZE )
91
+ measured_code = CodeGasMeasure (
92
+ code = calldata_copy + modexp_call_code ,
93
+ overhead_cost = 12 , # TODO: Calculate overhead cost
94
+ extra_stack_items = 0 ,
95
+ sstore_key = 1 ,
96
+ stop = True ,
97
+ )
98
+ return pre .deploy_contract (measured_code )
61
99
62
100
63
101
@pytest .fixture
64
- def exponent_bytes (exponent_length , exponent , request ):
65
- """Convert the integer exponent to bytes with the specified length."""
66
- # Truncate to the lowest exponent length bytes to avoid overflow
67
- mask = (1 << (8 * exponent_length )) - 1
68
- truncated = exponent & mask
69
- return truncated .to_bytes (exponent_length , byteorder = "big" )
102
+ def precompile_gas_modifier () -> int :
103
+ """Modify the gas passed to the precompile, for negative testing purposes."""
104
+ return 0
70
105
71
106
72
107
@pytest .fixture
73
- def expected_gas (
74
- base : bytes ,
75
- exponent_bytes : bytes ,
76
- modulus : bytes ,
77
- ) -> int :
78
- """Return expected gas consumption of the ModExp precompile."""
108
+ def precompile_gas (base : bytes , exponent_bytes : bytes , modulus : bytes , expected_gas : int ) -> int :
109
+ """Calculate gas cost for the ModExp precompile and verify it matches expected gas."""
79
110
base_length = len (base )
80
111
exponent_length = len (exponent_bytes )
81
112
modulus_length = len (modulus )
82
113
exponent_value = int .from_bytes (exponent_bytes , byteorder = "big" )
83
- # return Spec.calculate_new_gas_cost(
84
- # base_length, modulus_length, exponent_length, exponent_value
85
- # )
86
- # Temporarily use the old (EIP-2565) gas schedule
87
- return Spec .calculate_old_gas_cost (
114
+ calculated_gas = Spec .calculate_new_gas_cost (
88
115
base_length , modulus_length , exponent_length , exponent_value
89
116
)
117
+ assert (
118
+ calculated_gas == expected_gas
119
+ ), f"Calculated gas { calculated_gas } != Vector gas { expected_gas } "
120
+ return calculated_gas
90
121
91
122
92
123
@pytest .fixture
93
- def modexp_input_data (base : bytes , exponent_bytes : bytes , modulus : bytes ) -> bytes :
94
- """ModExp input data."""
95
- return Spec . create_modexp_input ( base , exponent_bytes , modulus )
124
+ def modexp_input_data (input_data : bytes ) -> bytes :
125
+ """ModExp input data, directly use the input from the test vector ."""
126
+ return input_data
96
127
97
128
98
129
@pytest .fixture
@@ -106,20 +137,21 @@ def tx(
106
137
sender = sender ,
107
138
to = gas_measure_contract ,
108
139
data = modexp_input_data ,
109
- gas_limit = 100_000 ,
140
+ gas_limit = 1_000_000 ,
110
141
)
111
142
112
143
113
144
@pytest .fixture
114
145
def post (
115
146
gas_measure_contract : Address ,
116
- expected_gas : int ,
147
+ precompile_gas : int ,
117
148
) -> Dict [Address , Dict [str , Storage ]]:
118
- """Return expected post state."""
149
+ """Return expected post state with gas consumption check ."""
119
150
return {
120
151
gas_measure_contract : Account (
121
152
storage = {
122
- 0 : 1 ,
153
+ 0 : 1 , # Call should succeed
154
+ 1 : precompile_gas ,
123
155
}
124
156
)
125
157
}
0 commit comments