8
8
from ethereum_test_tools import Account , Address , Alloc , Storage , Transaction
9
9
from ethereum_test_tools .vm .opcode import Opcodes as Op
10
10
11
- from .helpers import Vector
11
+ from ... byzantium . eip198_modexp_precompile . helpers import ModExpInput
12
12
from .spec import Spec , Spec7883
13
13
14
14
@@ -19,97 +19,148 @@ def call_opcode() -> Op:
19
19
20
20
21
21
@pytest .fixture
22
- def gas_measure_contract (pre : Alloc , call_opcode : Op , fork : Fork , vector : Vector ) -> Address :
22
+ def call_contract_post_storage () -> Storage :
23
+ """
24
+ Storage of the test contract after the transaction is executed.
25
+ Note: Fixture `call_contract_code` fills the actual expected storage values.
26
+ """
27
+ return Storage ()
28
+
29
+
30
+ @pytest .fixture
31
+ def gas_measure_contract (
32
+ pre : Alloc ,
33
+ call_opcode : Op ,
34
+ fork : Fork ,
35
+ modexp_expected : bytes ,
36
+ precompile_gas : int ,
37
+ precompile_gas_modifier : int ,
38
+ call_contract_post_storage : Storage ,
39
+ ) -> Address :
23
40
"""Deploys a contract that measures ModExp gas consumption."""
41
+ assert call_opcode in [Op .CALL , Op .CALLCODE , Op .DELEGATECALL , Op .STATICCALL ]
42
+ value = [0 ] if call_opcode in [Op .CALL , Op .CALLCODE ] else []
43
+
24
44
call_code = call_opcode (
25
- address = Spec .MODEXP_ADDRESS ,
26
- value = 0 ,
27
- args_offset = 0 ,
28
- args_size = Op .CALLDATASIZE ,
45
+ precompile_gas + precompile_gas_modifier ,
46
+ Spec .MODEXP_ADDRESS ,
47
+ * value ,
48
+ 0 ,
49
+ Op .CALLDATASIZE (),
50
+ 0 ,
51
+ 0 ,
29
52
)
53
+
30
54
gas_costs = fork .gas_costs ()
31
55
extra_gas = (
32
56
gas_costs .G_WARM_ACCOUNT_ACCESS
33
- + (gas_costs .G_VERY_LOW * (len (call_opcode .kwargs ) - 2 )) # type: ignore
34
- + (gas_costs .G_BASE * 3 )
57
+ + (gas_costs .G_VERY_LOW * (len (call_opcode .kwargs ) - 1 )) # type: ignore
58
+ + gas_costs .G_BASE # CALLDATASIZE
59
+ + gas_costs .G_BASE # GAS
35
60
)
36
- measure_code = (
61
+
62
+ # Build the gas measurement contract code
63
+ # Stack operations:
64
+ # [gas_start]
65
+ # [gas_start, call_result]
66
+ # [gas_start, call_result, gas_end]
67
+ # [gas_start, gas_end, call_result]
68
+ call_result_measurement = Op .GAS + call_code + Op .GAS + Op .SWAP1
69
+
70
+ # Calculate gas consumed: gas_start - (gas_end + extra_gas)
71
+ # Stack Operation:
72
+ # [gas_start, gas_end]
73
+ # [gas_start, gas_end, extra_gas]
74
+ # [gas_start, gas_end + extra_gas]
75
+ # [gas_end + extra_gas, gas_start]
76
+ # [gas_consumed]
77
+ gas_calculation = Op .PUSH2 [extra_gas ] + Op .ADD + Op .SWAP1 + Op .SUB
78
+
79
+ code = (
37
80
Op .CALLDATACOPY (dest_offset = 0 , offset = 0 , size = Op .CALLDATASIZE )
38
- + Op .GAS # [gas_start]
39
- + call_code # [gas_start, call_result]
40
- + Op .GAS # [gas_start, call_result, gas_end]
41
- + Op .SWAP1 # [gas_start, gas_end, call_result]
42
- + Op .PUSH1 [0 ] # [gas_start, gas_end, call_result, 0]
43
- + Op .SSTORE # [gas_start, gas_end]
44
- + Op .PUSH2 [extra_gas ] # [gas_start, gas_end, extra_gas]
45
- + Op .ADD # [gas_start, gas_end + extra_gas]
46
- + Op .SWAP1 # [gas_end + extra_gas, gas_start]
47
- + Op .SUB # [gas_start - (gas_end + extra_gas)]
48
- + Op .PUSH1 [1 ] # [gas_start - (gas_end + extra_gas), 1]
49
- + Op .SSTORE # []
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 )
83
+ + Op .SSTORE (
84
+ call_contract_post_storage .store_next (len (modexp_expected )),
85
+ Op .RETURNDATASIZE (),
86
+ )
87
+ # + Op.RETURNDATACOPY(dest_offset=0, offset=0, size=Op.RETURNDATASIZE())
88
+ # + Op.SSTORE(call_contract_post_storage.store_next(
89
+ # keccak256(Bytes(vector.expected))), Op.SHA3(0, Op.RETURNDATASIZE()))
50
90
)
51
- measure_code += Op .SSTORE (2 , Op .RETURNDATASIZE ())
52
- for i in range (len (vector .expected ) // 32 ):
53
- measure_code += Op .RETURNDATACOPY (0 , i * 32 , 32 )
54
- measure_code += Op .SSTORE (i + 3 , Op .MLOAD (0 ))
55
- measure_code += Op .STOP ()
56
- return pre .deploy_contract (measure_code )
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
+ )
97
+
98
+ return pre .deploy_contract (code )
57
99
58
100
59
101
@pytest .fixture
60
- def precompile_gas (fork : Fork , vector : Vector ) -> int :
102
+ def precompile_gas (fork : Fork , modexp_input : ModExpInput , gas_old : int , gas_new : int ) -> int :
61
103
"""Calculate gas cost for the ModExp precompile and verify it matches expected gas."""
62
104
spec = Spec if fork < Osaka else Spec7883
63
- expected_gas = vector . gas_old if fork < Osaka else vector . gas_new
105
+ expected_gas = gas_old if fork < Osaka else gas_new
64
106
calculated_gas = spec .calculate_gas_cost (
65
- len (vector . input .base ),
66
- len (vector . input .modulus ),
67
- len (vector . input .exponent ),
68
- vector . input .exponent ,
107
+ len (modexp_input .base ),
108
+ len (modexp_input .modulus ),
109
+ len (modexp_input .exponent ),
110
+ modexp_input .exponent ,
69
111
)
70
112
assert calculated_gas == expected_gas , (
71
113
f"Calculated gas { calculated_gas } != Vector gas { expected_gas } "
72
114
)
73
115
return calculated_gas
74
116
75
117
118
+ @pytest .fixture
119
+ def precompile_gas_modifier () -> int :
120
+ """Return the gas modifier for the ModExp precompile."""
121
+ return 0
122
+
123
+
76
124
@pytest .fixture
77
125
def tx (
78
- fork : Fork ,
79
126
pre : Alloc ,
80
127
gas_measure_contract : Address ,
81
- vector : Vector ,
82
- precompile_gas : int ,
128
+ modexp_input : ModExpInput ,
129
+ tx_gas_limit : int ,
83
130
) -> Transaction :
84
131
"""Transaction to measure gas consumption of the ModExp precompile."""
85
- intrinsic_gas_cost_calc = fork .transaction_intrinsic_cost_calculator ()
86
- intrinsic_gas_cost = intrinsic_gas_cost_calc (calldata = vector .input )
87
- memory_expansion_gas_calc = fork .memory_expansion_gas_calculator ()
88
- memory_expansion_gas = memory_expansion_gas_calc (new_bytes = len (bytes (vector .input )))
89
- sstore_gas = fork .gas_costs ().G_STORAGE_SET * (len (vector .expected ) // 32 )
90
132
return Transaction (
91
133
sender = pre .fund_eoa (),
92
134
to = gas_measure_contract ,
93
- data = vector .input ,
94
- gas_limit = intrinsic_gas_cost
135
+ data = bytes (modexp_input ),
136
+ gas_limit = tx_gas_limit ,
137
+ )
138
+
139
+
140
+ @pytest .fixture
141
+ def tx_gas_limit (
142
+ fork : Fork , modexp_expected : bytes , modexp_input : ModExpInput , precompile_gas : int
143
+ ) -> int :
144
+ """Transaction gas limit used for the test (Can be overridden in the test)."""
145
+ intrinsic_gas_cost_calculator = fork .transaction_intrinsic_cost_calculator ()
146
+ memory_expansion_gas_calculator = fork .memory_expansion_gas_calculator ()
147
+ sstore_gas = fork .gas_costs ().G_STORAGE_SET * (len (modexp_expected ) // 32 )
148
+ extra_gas = 100_000
149
+ return (
150
+ extra_gas
151
+ + intrinsic_gas_cost_calculator (calldata = bytes (modexp_input ))
152
+ + memory_expansion_gas_calculator (new_bytes = len (bytes (modexp_input )))
95
153
+ precompile_gas
96
- + memory_expansion_gas
97
154
+ sstore_gas
98
- + 100_000 ,
99
155
)
100
156
101
157
102
158
@pytest .fixture
103
159
def post (
104
160
gas_measure_contract : Address ,
105
- precompile_gas : int ,
106
- vector : Vector ,
161
+ call_contract_post_storage : Storage ,
107
162
) -> Dict [Address , Account ]:
108
163
"""Return expected post state with gas consumption check."""
109
- storage = Storage ()
110
- storage [0 ] = 1
111
- storage [1 ] = precompile_gas
112
- storage [2 ] = len (vector .expected )
113
- for i in range (len (vector .expected ) // 32 ):
114
- storage [i + 3 ] = vector .expected [i * 32 : (i + 1 ) * 32 ]
115
- return {gas_measure_contract : Account (storage = storage )}
164
+ return {
165
+ gas_measure_contract : Account (storage = call_contract_post_storage ),
166
+ }
0 commit comments