Skip to content

test_staticcall_call_to_precompile()

Documentation for tests/byzantium/eip214_staticcall/test_staticcall.py::test_staticcall_call_to_precompile@ffe48572.

Generate fixtures for these test cases for Bogota with:

fill -v tests/byzantium/eip214_staticcall/test_staticcall.py::test_staticcall_call_to_precompile --fork Bogota

Test CALL to precompile inside STATICCALL with zero and non-zero value.

Contract A STATICCALLs contract B. Contract B attempts to CALL precompile. With value = 0, this succeeds. With value > 0, this fails (static context).

Source code in tests/byzantium/eip214_staticcall/test_staticcall.py
@pytest.mark.with_all_precompiles
@pytest.mark.parametrize(
    "call_value", [0, 2], ids=["zero_value", "nonzero_value"]
)
@pytest.mark.ported_from(
    [
        "https://github.com/ethereum/tests/blob/v13.3/src/GeneralStateTestsFiller/stStaticFlagEnabled/CallWithZeroValueToPrecompileFromTransactionFiller.yml",
        "https://github.com/ethereum/tests/blob/v13.3/src/GeneralStateTestsFiller/stStaticFlagEnabled/CallWithNOTZeroValueToPrecompileFromTransactionFiller.yml",
    ],
)
@pytest.mark.valid_from("Byzantium")
def test_staticcall_call_to_precompile(
    pre: Alloc,
    state_test: StateTestFiller,
    precompile: Address,
    call_value: int,
    fork: Fork,
) -> None:
    """
    Test CALL to precompile inside STATICCALL with zero and non-zero value.

    Contract A STATICCALLs contract B. Contract B attempts to CALL precompile.
    With value = 0, this succeeds. With value > 0, this fails (static context).
    """
    alice = pre.fund_eoa()

    initial_contract_balance = 1000
    marker = 0xFEEDFEEDFEEDFEEDFEEDFEEDFEEDFEEDFEEDFEEDFEEDFEEDFEEDFEEDFEEDFEED

    # Contract B: attempts CALL to precompile with the parametrized value
    contract_b = pre.deploy_contract(
        code=Op.CALL(gas=100_000, address=precompile, value=call_value),
        balance=initial_contract_balance,
    )

    # Contract A: STATICCALLs contract B and stores the result
    contract_a = pre.deploy_contract(
        code=(
            Op.SSTORE(0, marker)
            + Op.SSTORE(1, Op.STATICCALL(gas=200_000, address=contract_b))
            + Op.SSTORE(2, marker)
        ),
        balance=initial_contract_balance,
    )

    tx_value = 100
    staticcall_result = 1 if call_value == 0 else 0

    bal_expectation = None
    if fork.is_eip_enabled(7928):
        contract_a_balance_changes = [
            BalBalanceChange(
                block_access_index=1,
                post_balance=initial_contract_balance + tx_value,
            )
        ]

        # slot 1 read when call_value > 0
        account_expectations: dict[Address, BalAccountExpectation | None] = {
            contract_a: (
                bal_expectation_for_contract_with_markers(
                    marker=marker,
                    staticcall_result=staticcall_result,
                    balance_change=tx_value,
                    initial_balance=initial_contract_balance,
                )
                if call_value == 0
                else BalAccountExpectation(
                    storage_changes=[
                        BalStorageSlot(
                            slot=0,
                            slot_changes=[
                                BalStorageChange(
                                    block_access_index=1, post_value=marker
                                )
                            ],
                        ),
                        BalStorageSlot(
                            slot=2,
                            slot_changes=[
                                BalStorageChange(
                                    block_access_index=1, post_value=marker
                                )
                            ],
                        ),
                    ],
                    storage_reads=[1],
                    balance_changes=contract_a_balance_changes,
                )
            ),
            contract_b: BalAccountExpectation.empty(),  # STATICCALLed
        }

        if call_value == 0:
            account_expectations[precompile] = BalAccountExpectation.empty()
        else:
            account_expectations[precompile] = None  # reverted before accessed

        bal_expectation = BlockAccessListExpectation(
            account_expectations=account_expectations
        )

    state_test(
        pre=pre,
        tx=Transaction(
            sender=alice,
            to=contract_a,
            value=tx_value,
            protected=True,
        ),
        expected_block_access_list=bal_expectation,
        post={
            contract_a: Account(
                balance=initial_contract_balance + tx_value,
                storage={
                    0: marker,
                    1: staticcall_result,
                    2: marker,
                },
            ),
            contract_b: Account(balance=initial_contract_balance),
        },
    )

Parametrized Test Cases

This test generates 36 parametrized test cases across 13 forks.