|
| 1 | +""" |
| 2 | +abstract: Tests [EIP-2929: Gas cost increases for state access opcodes](https://eips.ethereum.org/EIPS/eip-2929) |
| 3 | + Test cases for [EIP-2929: Gas cost increases for state access opcodes](https://eips.ethereum.org/EIPS/eip-2929). |
| 4 | +""" # noqa: E501 |
| 5 | + |
| 6 | +from typing import Iterator, Tuple |
| 7 | + |
| 8 | +import pytest |
| 9 | + |
| 10 | +from ethereum_test_forks import ( |
| 11 | + Fork, |
| 12 | + get_transition_fork_predecessor, |
| 13 | + get_transition_fork_successor, |
| 14 | +) |
| 15 | +from ethereum_test_tools import ( |
| 16 | + Account, |
| 17 | + Address, |
| 18 | + Alloc, |
| 19 | + Block, |
| 20 | + BlockchainTestFiller, |
| 21 | + Transaction, |
| 22 | +) |
| 23 | +from ethereum_test_tools.vm.opcode import Opcodes as Op |
| 24 | + |
| 25 | +REFERENCE_SPEC_GIT_PATH = "EIPS/eip-2929.md" |
| 26 | +REFERENCE_SPEC_VERSION = "0e11417265a623adb680c527b15d0cb6701b870b" |
| 27 | + |
| 28 | + |
| 29 | +def precompile_addresses_in_predecessor_successor( |
| 30 | + fork: Fork, |
| 31 | +) -> Iterator[Tuple[Address, bool, bool]]: |
| 32 | + """ |
| 33 | + Yield the addresses of precompiled contracts and whether they existed in the parent fork. |
| 34 | +
|
| 35 | + Args: |
| 36 | + fork (Fork): The transition fork instance containing precompiled contract information. |
| 37 | +
|
| 38 | + Yields: |
| 39 | + Iterator[Tuple[str, bool]]: A tuple containing the address in hexadecimal format and a |
| 40 | + boolean indicating whether the address has existed in the predecessor. |
| 41 | +
|
| 42 | + """ |
| 43 | + predecessor_precompiles = set(get_transition_fork_predecessor(fork).precompiles()) |
| 44 | + successor_precompiles = set(get_transition_fork_successor(fork).precompiles()) |
| 45 | + all_precompiles = successor_precompiles | predecessor_precompiles |
| 46 | + highest_precompile = int.from_bytes(max(all_precompiles)) |
| 47 | + extra_range = 32 |
| 48 | + extra_precompiles = { |
| 49 | + Address(i) for i in range(highest_precompile + 1, highest_precompile + extra_range) |
| 50 | + } |
| 51 | + all_precompiles = all_precompiles | extra_precompiles |
| 52 | + for address in sorted(all_precompiles): |
| 53 | + yield address, address in successor_precompiles, address in predecessor_precompiles |
| 54 | + |
| 55 | + |
| 56 | +@pytest.mark.valid_at_transition_to("Paris", subsequent_forks=True) |
| 57 | +@pytest.mark.parametrize_by_fork( |
| 58 | + "address,precompile_in_successor,precompile_in_predecessor", |
| 59 | + precompile_addresses_in_predecessor_successor, |
| 60 | +) |
| 61 | +def test_precompile_warming( |
| 62 | + blockchain_test: BlockchainTestFiller, |
| 63 | + fork: Fork, |
| 64 | + address: Address, |
| 65 | + precompile_in_successor: bool, |
| 66 | + precompile_in_predecessor: bool, |
| 67 | + pre: Alloc, |
| 68 | +): |
| 69 | + """ |
| 70 | + Call BALANCE of a precompile addresses before and after a fork. |
| 71 | +
|
| 72 | + According to EIP-2929, when a transaction begins, accessed_addresses is initialized to include: |
| 73 | + - tx.sender, tx.to |
| 74 | + - and the set of all precompiles |
| 75 | +
|
| 76 | + This test verifies that: |
| 77 | + 1. Precompiles that exist in the predecessor fork are always "warm" (lower gas cost) |
| 78 | + 2. New precompiles added in a fork are "cold" before the fork and become "warm" after |
| 79 | +
|
| 80 | + """ |
| 81 | + sender = pre.fund_eoa() |
| 82 | + call_cost_slot = 0 |
| 83 | + |
| 84 | + code = ( |
| 85 | + Op.GAS |
| 86 | + + Op.BALANCE(address) |
| 87 | + + Op.POP |
| 88 | + + Op.SSTORE(call_cost_slot, Op.SUB(Op.SWAP1, Op.GAS)) |
| 89 | + + Op.STOP |
| 90 | + ) |
| 91 | + before = pre.deploy_contract(code, storage={call_cost_slot: 0xDEADBEEF}) |
| 92 | + after = pre.deploy_contract(code, storage={call_cost_slot: 0xDEADBEEF}) |
| 93 | + |
| 94 | + # Block before fork |
| 95 | + blocks = [ |
| 96 | + Block( |
| 97 | + timestamp=10_000, |
| 98 | + txs=[ |
| 99 | + Transaction( |
| 100 | + sender=sender, |
| 101 | + to=before, |
| 102 | + gas_limit=1_000_000, |
| 103 | + ) |
| 104 | + ], |
| 105 | + ) |
| 106 | + ] |
| 107 | + |
| 108 | + # Block after fork |
| 109 | + blocks += [ |
| 110 | + Block( |
| 111 | + timestamp=20_000, |
| 112 | + txs=[ |
| 113 | + Transaction( |
| 114 | + sender=sender, |
| 115 | + to=after, |
| 116 | + gas_limit=1_000_000, |
| 117 | + ) |
| 118 | + ], |
| 119 | + ) |
| 120 | + ] |
| 121 | + |
| 122 | + predecessor = get_transition_fork_predecessor(fork) |
| 123 | + successor = get_transition_fork_successor(fork) |
| 124 | + |
| 125 | + def get_expected_gas(precompile_present: bool, fork: Fork) -> int: |
| 126 | + gas_costs = fork.gas_costs() |
| 127 | + warm_access_cost = gas_costs.G_WARM_ACCOUNT_ACCESS |
| 128 | + cold_access_cost = gas_costs.G_COLD_ACCOUNT_ACCESS |
| 129 | + extra_cost = gas_costs.G_BASE * 2 + gas_costs.G_VERY_LOW |
| 130 | + if precompile_present: |
| 131 | + return warm_access_cost + extra_cost |
| 132 | + else: |
| 133 | + return cold_access_cost + extra_cost |
| 134 | + |
| 135 | + expected_gas_before = get_expected_gas(precompile_in_predecessor, predecessor) |
| 136 | + expected_gas_after = get_expected_gas(precompile_in_successor, successor) |
| 137 | + |
| 138 | + post = { |
| 139 | + before: Account(storage={call_cost_slot: expected_gas_before}), |
| 140 | + after: Account(storage={call_cost_slot: expected_gas_after}), |
| 141 | + } |
| 142 | + |
| 143 | + blockchain_test( |
| 144 | + pre=pre, |
| 145 | + post=post, |
| 146 | + blocks=blocks, |
| 147 | + ) |
0 commit comments