|
5 | 5 | Tests running worst-case block scenarios for EVMs.
|
6 | 6 | """
|
7 | 7 |
|
| 8 | +import random |
| 9 | + |
8 | 10 | import pytest
|
9 | 11 |
|
10 | 12 | from ethereum_test_forks import Fork
|
11 | 13 | from ethereum_test_tools import (
|
| 14 | + AccessList, |
12 | 15 | Account,
|
13 | 16 | Address,
|
14 | 17 | Alloc,
|
15 | 18 | Block,
|
16 | 19 | BlockchainTestFiller,
|
| 20 | + Hash, |
17 | 21 | StateTestFiller,
|
18 | 22 | Transaction,
|
19 | 23 | )
|
@@ -164,6 +168,12 @@ def total_cost_floor_per_token():
|
164 | 168 | return 10
|
165 | 169 |
|
166 | 170 |
|
| 171 | +@pytest.fixture |
| 172 | +def total_cost_standard_per_token(): |
| 173 | + """Total cost floor per token.""" |
| 174 | + return 4 |
| 175 | + |
| 176 | + |
167 | 177 | @pytest.mark.valid_from("Prague")
|
168 | 178 | @pytest.mark.parametrize("zero_byte", [True, False])
|
169 | 179 | def test_block_full_data(
|
@@ -219,3 +229,96 @@ def test_block_full_data(
|
219 | 229 | post={},
|
220 | 230 | tx=tx,
|
221 | 231 | )
|
| 232 | + |
| 233 | + |
| 234 | +@pytest.mark.valid_from("Prague") |
| 235 | +def test_block_full_access_list_and_data( |
| 236 | + state_test: StateTestFiller, |
| 237 | + pre: Alloc, |
| 238 | + intrinsic_cost: int, |
| 239 | + total_cost_standard_per_token: int, |
| 240 | + fork: Fork, |
| 241 | + gas_benchmark_value: int, |
| 242 | +): |
| 243 | + """Test a block with access lists (60% gas) and calldata (40% gas) using random mixed bytes.""" |
| 244 | + attack_gas_limit = gas_benchmark_value |
| 245 | + gas_available = attack_gas_limit - intrinsic_cost |
| 246 | + |
| 247 | + # Split available gas: 60% for access lists, 40% for calldata |
| 248 | + gas_for_access_list = int(gas_available * 0.6) |
| 249 | + gas_for_calldata = int(gas_available * 0.4) |
| 250 | + |
| 251 | + # Access list gas costs from fork's gas_costs |
| 252 | + gas_costs = fork.gas_costs() |
| 253 | + gas_per_address = gas_costs.G_ACCESS_LIST_ADDRESS |
| 254 | + gas_per_storage_key = gas_costs.G_ACCESS_LIST_STORAGE |
| 255 | + |
| 256 | + # Calculate number of storage keys we can fit |
| 257 | + gas_after_address = gas_for_access_list - gas_per_address |
| 258 | + num_storage_keys = gas_after_address // gas_per_storage_key |
| 259 | + |
| 260 | + # Create access list with 1 address and many storage keys |
| 261 | + access_address = Address("0x1234567890123456789012345678901234567890") |
| 262 | + storage_keys = [] |
| 263 | + for i in range(num_storage_keys): |
| 264 | + # Generate random-looking storage keys |
| 265 | + storage_keys.append(Hash(i)) |
| 266 | + |
| 267 | + access_list = [ |
| 268 | + AccessList( |
| 269 | + address=access_address, |
| 270 | + storage_keys=storage_keys, |
| 271 | + ) |
| 272 | + ] |
| 273 | + |
| 274 | + # Calculate calldata with 29% of gas for zero bytes and 71% for non-zero bytes |
| 275 | + # Token accounting: tokens_in_calldata = zero_bytes + 4 * non_zero_bytes |
| 276 | + # We want to split the gas budget: |
| 277 | + # - 29% of gas_for_calldata for zero bytes |
| 278 | + # - 71% of gas_for_calldata for non-zero bytes |
| 279 | + |
| 280 | + max_tokens_in_calldata = gas_for_calldata // total_cost_standard_per_token |
| 281 | + |
| 282 | + # Calculate how many tokens to allocate to each type |
| 283 | + tokens_for_zero_bytes = int(max_tokens_in_calldata * 0.29) |
| 284 | + tokens_for_non_zero_bytes = max_tokens_in_calldata - tokens_for_zero_bytes |
| 285 | + |
| 286 | + # Convert tokens to actual byte counts |
| 287 | + # Zero bytes: 1 token per byte |
| 288 | + # Non-zero bytes: 4 tokens per byte |
| 289 | + num_zero_bytes = tokens_for_zero_bytes # 1 token = 1 zero byte |
| 290 | + num_non_zero_bytes = tokens_for_non_zero_bytes // 4 # 4 tokens = 1 non-zero byte |
| 291 | + |
| 292 | + # Create calldata with mixed bytes |
| 293 | + calldata = bytearray() |
| 294 | + |
| 295 | + # Add zero bytes |
| 296 | + calldata.extend(b"\x00" * num_zero_bytes) |
| 297 | + |
| 298 | + # Add non-zero bytes (random values from 0x01 to 0xff) |
| 299 | + rng = random.Random(42) # For reproducibility |
| 300 | + for _ in range(num_non_zero_bytes): |
| 301 | + calldata.append(rng.randint(1, 255)) |
| 302 | + |
| 303 | + # Shuffle the bytes to mix zero and non-zero bytes |
| 304 | + calldata_list = list(calldata) |
| 305 | + rng.shuffle(calldata_list) |
| 306 | + shuffled_calldata = bytes(calldata_list) |
| 307 | + |
| 308 | + tx = Transaction( |
| 309 | + to=pre.fund_eoa(amount=0), |
| 310 | + data=shuffled_calldata, |
| 311 | + gas_limit=attack_gas_limit, |
| 312 | + sender=pre.fund_eoa(), |
| 313 | + access_list=access_list, |
| 314 | + ) |
| 315 | + |
| 316 | + state_test( |
| 317 | + pre=pre, |
| 318 | + post={}, |
| 319 | + tx=tx, |
| 320 | + expected_benchmark_gas_used=fork.transaction_intrinsic_cost_calculator()( |
| 321 | + calldata=shuffled_calldata, |
| 322 | + access_list=access_list, |
| 323 | + ), |
| 324 | + ) |
0 commit comments