|
11 | 11 |
|
12 | 12 | from ethereum_test_forks import Fork
|
13 | 13 | from ethereum_test_tools import (
|
| 14 | + Address, |
14 | 15 | Alloc,
|
15 | 16 | Block,
|
16 | 17 | BlockchainTestFiller,
|
@@ -113,6 +114,94 @@ def test_worst_keccak(
|
113 | 114 | )
|
114 | 115 |
|
115 | 116 |
|
| 117 | +@pytest.mark.zkevm |
| 118 | +@pytest.mark.valid_from("Cancun") |
| 119 | +@pytest.mark.parametrize( |
| 120 | + "gas_limit", |
| 121 | + [ |
| 122 | + Environment().gas_limit, |
| 123 | + ], |
| 124 | +) |
| 125 | +@pytest.mark.parametrize( |
| 126 | + "address,static_cost,per_word_dynamic_cost,bytes_per_unit_of_work", |
| 127 | + [ |
| 128 | + pytest.param(0x02, 60, 12, 64, id="SHA2-256"), |
| 129 | + pytest.param(0x03, 600, 120, 64, id="RIPEMD-160"), |
| 130 | + pytest.param(0x04, 15, 3, 1, id="IDENTITY"), |
| 131 | + ], |
| 132 | +) |
| 133 | +def test_worst_precompile_only_data_input( |
| 134 | + blockchain_test: BlockchainTestFiller, |
| 135 | + pre: Alloc, |
| 136 | + fork: Fork, |
| 137 | + gas_limit: int, |
| 138 | + address: Address, |
| 139 | + static_cost: int, |
| 140 | + per_word_dynamic_cost: int, |
| 141 | + bytes_per_unit_of_work: int, |
| 142 | +): |
| 143 | + """Test running a block with as many precompile calls which have a single `data` input.""" |
| 144 | + env = Environment(gas_limit=gas_limit) |
| 145 | + |
| 146 | + # Intrinsic gas cost is paid once. |
| 147 | + intrinsic_gas_calculator = fork.transaction_intrinsic_cost_calculator() |
| 148 | + available_gas = gas_limit - intrinsic_gas_calculator() |
| 149 | + |
| 150 | + gsc = fork.gas_costs() |
| 151 | + mem_exp_gas_calculator = fork.memory_expansion_gas_calculator() |
| 152 | + |
| 153 | + # Discover the optimal input size to maximize precompile calls, not precompile permutations. |
| 154 | + max_work = 0 |
| 155 | + optimal_input_length = 0 |
| 156 | + for input_length in range(1, 1_000_000, 32): |
| 157 | + staticcall_parameters_gas = ( |
| 158 | + gsc.G_BASE # PUSH0 = arg offset |
| 159 | + + gsc.G_BASE # PUSH0 = arg size |
| 160 | + + gsc.G_BASE # PUSH0 = arg size |
| 161 | + + gsc.G_VERY_LOW # PUSH0 = arg offset |
| 162 | + + gsc.G_VERY_LOW # PUSHN = address |
| 163 | + + gsc.G_BASE # GAS |
| 164 | + ) |
| 165 | + iteration_gas_cost = ( |
| 166 | + staticcall_parameters_gas |
| 167 | + + +static_cost # Precompile static cost |
| 168 | + + math.ceil(input_length / 32) * per_word_dynamic_cost # Precompile dynamic cost |
| 169 | + + gsc.G_BASE # POP |
| 170 | + ) |
| 171 | + # From the available gas, we substract the mem expansion costs considering we know the |
| 172 | + # current input size length. |
| 173 | + available_gas_after_expansion = max( |
| 174 | + 0, available_gas - mem_exp_gas_calculator(new_bytes=input_length) |
| 175 | + ) |
| 176 | + # Calculate how many calls we can do. |
| 177 | + num_calls = available_gas_after_expansion // iteration_gas_cost |
| 178 | + total_work = num_calls * math.ceil(input_length / bytes_per_unit_of_work) |
| 179 | + |
| 180 | + # If we found an input size that is better (reg permutations/gas), then save it. |
| 181 | + if total_work > max_work: |
| 182 | + max_work = total_work |
| 183 | + optimal_input_length = input_length |
| 184 | + |
| 185 | + calldata = Op.CODECOPY(0, 0, optimal_input_length) |
| 186 | + attack_block = Op.POP(Op.STATICCALL(Op.GAS, address, 0, optimal_input_length, 0, 0)) |
| 187 | + code = code_loop_precompile_call(calldata, attack_block) |
| 188 | + |
| 189 | + code_address = pre.deploy_contract(code=code) |
| 190 | + |
| 191 | + tx = Transaction( |
| 192 | + to=code_address, |
| 193 | + gas_limit=gas_limit, |
| 194 | + sender=pre.fund_eoa(), |
| 195 | + ) |
| 196 | + |
| 197 | + blockchain_test( |
| 198 | + env=env, |
| 199 | + pre=pre, |
| 200 | + post={}, |
| 201 | + blocks=[Block(txs=[tx])], |
| 202 | + ) |
| 203 | + |
| 204 | + |
116 | 205 | @pytest.mark.zkevm
|
117 | 206 | @pytest.mark.valid_from("Cancun")
|
118 | 207 | @pytest.mark.parametrize(
|
|
0 commit comments