12
12
from .spec import Spec , Spec7883
13
13
14
14
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
+
15
27
@pytest .fixture
16
28
def call_opcode () -> Op :
17
29
"""Return default call used to call the precompile."""
@@ -27,6 +39,15 @@ def call_contract_post_storage() -> Storage:
27
39
return Storage ()
28
40
29
41
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
+
30
51
@pytest .fixture
31
52
def gas_measure_contract (
32
53
pre : Alloc ,
@@ -36,6 +57,7 @@ def gas_measure_contract(
36
57
precompile_gas : int ,
37
58
precompile_gas_modifier : int ,
38
59
call_contract_post_storage : Storage ,
60
+ call_succeeds : bool ,
39
61
) -> Address :
40
62
"""Deploys a contract that measures ModExp gas consumption."""
41
63
assert call_opcode in [Op .CALL , Op .CALLCODE , Op .DELEGATECALL , Op .STATICCALL ]
@@ -78,46 +100,56 @@ def gas_measure_contract(
78
100
79
101
code = (
80
102
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 )
83
104
+ Op .SSTORE (
84
105
call_contract_post_storage .store_next (len (modexp_expected )),
85
106
Op .RETURNDATASIZE (),
86
107
)
108
+ )
109
+
110
+ if call_succeeds :
111
+ code += Op .SSTORE (call_contract_post_storage .store_next (precompile_gas ), gas_calculation )
87
112
# + Op.RETURNDATACOPY(dest_offset=0, offset=0, size=Op.RETURNDATASIZE())
88
113
# + Op.SSTORE(call_contract_post_storage.store_next(
89
114
# 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
+ )
97
122
98
123
return pre .deploy_contract (code )
99
124
100
125
101
126
@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 :
103
130
"""Calculate gas cost for the ModExp precompile and verify it matches expected gas."""
104
131
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 } \n "
114
- f"Lengths: base: { hex (len (vector .input .base ))} ({ len (vector .input .base )} ), "
115
- f"exponent: { hex (len (vector .input .exponent ))} ({ len (vector .input .exponent )} ), "
116
- f"modulus: { hex (len (vector .input .modulus ))} ({ len (vector .input .modulus )} )\n "
117
- f"Exponent: { vector .input .exponent } "
118
- f"({ int .from_bytes (vector .input .exponent , byteorder = 'big' )} )"
119
- )
120
- 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 } \n "
143
+ f"Lengths: base: { hex (len (vector .input .base ))} ({ len (vector .input .base )} ), "
144
+ f"exponent: { hex (len (vector .input .exponent ))} ({ len (vector .input .exponent )} ), "
145
+ f"modulus: { hex (len (vector .input .modulus ))} ({ len (vector .input .modulus )} )\n "
146
+ f"Exponent: { vector .input .exponent } "
147
+ f"({ int .from_bytes (vector .input .exponent , byteorder = 'big' )} )"
148
+ )
149
+ return calculated_gas
150
+ except Exception as e :
151
+ print (f"Error calculating gas: { e } " )
152
+ return 0
121
153
122
154
123
155
@pytest .fixture
0 commit comments