Skip to content

test_intrinsic_reduction_across_amsterdam_transition()

Documentation for tests/amsterdam/eip2780_reduce_intrinsic_tx_gas/test_fork_transition.py::test_intrinsic_reduction_across_amsterdam_transition@c74f1a67.

Generate fixtures for these test cases for Amsterdam with:

fill -v tests/amsterdam/eip2780_reduce_intrinsic_tx_gas/test_fork_transition.py::test_intrinsic_reduction_across_amsterdam_transition --fork Amsterdam

Pin the EIP-2780 intrinsic change across the Amsterdam boundary.

The same transaction shape is sent in a pre-fork block (Osaka rules, flat 21_000 intrinsic) and a post-fork block (Amsterdam rules, decomposed intrinsic). Each block uses a distinct sender so its post-tx balance pins the fork-appropriate intrinsic; the recipient is an existing EOA (or the sender itself for self_transfer), so neither block runs EVM bytecode and gas_used equals the intrinsic exactly.

The per-fork intrinsic returned by the calculator is also checked against a hand-derived decomposition built from each fork's gas constants, so a calculator regression fails here with a clear message rather than only as a downstream balance mismatch.

Source code in tests/amsterdam/eip2780_reduce_intrinsic_tx_gas/test_fork_transition.py
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
@pytest.mark.parametrize(
    "self_transfer",
    [
        pytest.param(False, id="plain_call"),
        pytest.param(True, id="self_transfer"),
    ],
)
@pytest.mark.parametrize(
    "value",
    [
        pytest.param(0, id="zero_value"),
        pytest.param(1, id="non-zero_value"),
    ],
)
def test_intrinsic_reduction_across_amsterdam_transition(
    blockchain_test: BlockchainTestFiller,
    pre: Alloc,
    fork: TransitionFork,
    self_transfer: bool,
    value: int,
) -> None:
    """
    Pin the EIP-2780 intrinsic change across the Amsterdam boundary.

    The same transaction shape is sent in a pre-fork block (Osaka
    rules, flat 21_000 intrinsic) and a post-fork block (Amsterdam
    rules, decomposed intrinsic). Each block uses a distinct sender so
    its post-tx balance pins the fork-appropriate intrinsic; the
    recipient is an existing EOA (or the sender itself for
    ``self_transfer``), so neither block runs EVM bytecode and
    ``gas_used`` equals the intrinsic exactly.

    The per-fork intrinsic returned by the calculator is also checked
    against a hand-derived decomposition built from each fork's gas
    constants, so a calculator regression fails here with a clear
    message rather than only as a downstream balance mismatch.
    """
    gas_price = 1_000_000_000
    recipient_type = RecipientType.SELF if self_transfer else RecipientType.EOA

    pre_fork = fork.fork_at(timestamp=PRE_FORK_TIMESTAMP)
    post_fork = fork.fork_at(timestamp=POST_FORK_TIMESTAMP)

    # Pre-fork: flat ``TX_BASE`` regardless of recipient kind or value.
    expected_pre = pre_fork.gas_costs().TX_BASE
    # Post-fork: EIP-2780 decomposition. Self-transfers are fully
    # carved out; other recipients pay the recipient access charge plus
    # the value-transfer charges when value is moved.
    post_gas_costs = post_fork.gas_costs()
    expected_post = post_gas_costs.TX_BASE
    if not self_transfer:
        expected_post += post_gas_costs.COLD_ACCOUNT_ACCESS
        if value:
            expected_post += (
                post_gas_costs.TRANSFER_LOG_COST + post_gas_costs.TX_VALUE_COST
            )

    timestamps = [PRE_FORK_TIMESTAMP, POST_FORK_TIMESTAMP]
    expected_intrinsics = [expected_pre, expected_post]
    blocks = []
    post: dict[Address, Account] = {}

    for timestamp, expected_intrinsic in zip(
        timestamps, expected_intrinsics, strict=True
    ):
        sub_fork = fork.fork_at(timestamp=timestamp)
        intrinsic_gas = sub_fork.transaction_intrinsic_cost_calculator()(
            sends_value=bool(value),
            recipient_type=recipient_type,
            return_cost_deducted_prior_execution=True,
        )
        assert intrinsic_gas == expected_intrinsic, (
            f"intrinsic at timestamp {timestamp} ({sub_fork}) is "
            f"{intrinsic_gas}, expected {expected_intrinsic}"
        )

        sender_initial_balance = 10**18
        sender = pre.fund_eoa(sender_initial_balance)
        if self_transfer:
            target = sender
        else:
            target = pre.fund_eoa(amount=EOA_INITIAL_BALANCE)

        # No EVM bytecode runs (recipient is an EOA or the sender), so
        # gas_used == intrinsic_gas; the gas limit is pinned to exactly
        # the intrinsic, leaving no buffer.
        tx = Transaction(
            sender=sender,
            to=target,
            value=value,
            gas_limit=intrinsic_gas,
            gas_price=gas_price,
        )
        blocks.append(Block(timestamp=timestamp, txs=[tx]))

        # A self-transfer returns the value to the sender (net zero);
        # a plain call moves ``value`` to the distinct recipient.
        sender_value_delta = 0 if self_transfer else value
        sender_final_balance = (
            sender_initial_balance
            - sender_value_delta
            - intrinsic_gas * gas_price
        )
        post[sender] = Account(nonce=1, balance=sender_final_balance)
        if not self_transfer:
            post[target] = Account(balance=EOA_INITIAL_BALANCE + value)

    blockchain_test(pre=pre, blocks=blocks, post=post)

Parametrized Test Cases

This test generates 4 parametrized test cases across 1 fork.