Skip to content

test_call_clears_return_data_on_insufficient_balance()

Documentation for tests/byzantium/eip211_return_data/test_call.py::test_call_clears_return_data_on_insufficient_balance@26332146.

Generate fixtures for these test cases for Amsterdam with:

fill -v tests/byzantium/eip211_return_data/test_call.py::test_call_clears_return_data_on_insufficient_balance --fork Amsterdam

Test that a CALL clears the return-data buffer even when the call fails the insufficient-balance pre-check and the callee is never entered.

A CALL whose value exceeds the caller's balance is a "light" failure: it pushes 0 and never executes the callee, but per EIP-211 it must still reset the return-data buffer. A client that skips the reset on this early-return path would leave stale return data from a preceding CALL observable via RETURNDATASIZE/RETURNDATACOPY.

(The other CALL pre-check -- the 1024 call-stack depth limit -- is not exercised here: since EIP-150's 63/64 gas-forwarding rule, a call chain runs out of gas long before reaching depth 1024, so that branch is effectively unreachable.)

Storage layout

slot N = RETURNDATASIZE after the funded CALL (expected 32) slot N+1 = RETURNDATASIZE after the failing CALL (expected 0) slot N+2 = the failing CALL result (expected 0, failure)

Source code in tests/byzantium/eip211_return_data/test_call.py
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
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
@pytest.mark.valid_from("Byzantium")
def test_call_clears_return_data_on_insufficient_balance(
    pre: Alloc,
    state_test: StateTestFiller,
) -> None:
    """
    Test that a CALL clears the return-data buffer even when the call fails the
    insufficient-balance pre-check and the callee is never entered.

    A CALL whose value exceeds the caller's balance is a "light" failure: it
    pushes 0 and never executes the callee, but per EIP-211 it must still reset
    the return-data buffer. A client that skips the reset on this early-return
    path would leave stale return data from a preceding CALL observable via
    RETURNDATASIZE/RETURNDATACOPY.

    (The other CALL pre-check -- the 1024 call-stack depth limit -- is not
    exercised here: since EIP-150's 63/64 gas-forwarding rule, a call chain
    runs out of gas long before reaching depth 1024, so that branch is
    effectively unreachable.)

    Storage layout:
      slot N = RETURNDATASIZE after the funded CALL    (expected 32)
      slot N+1 = RETURNDATASIZE after the failing CALL   (expected 0)
      slot N+2 = the failing CALL result                 (expected 0, failure)
    """
    storage = Storage()

    # Callee returns 32 bytes, so the caller's return-data buffer is 32 bytes.
    callee = pre.deploy_contract(
        code=Op.MSTORE(0, 0x11223344) + Op.RETURN(0, 32),
    )

    init_balance = 1

    # Caller has balance 1, so a CALL with value 2 fails the balance pre-check
    # before entering the callee.
    caller = pre.deploy_contract(
        balance=init_balance,
        code=(
            Op.CALL(gas=Op.GAS, address=callee, ret_size=32)
            + Op.SSTORE(
                storage.store_next(32, "rds_after_call"), Op.RETURNDATASIZE
            )
            + Op.SSTORE(
                storage.store_next(0, "failed_call_result"),
                Op.CALL(gas=Op.GAS, address=callee, value=init_balance + 1),
            )
            + Op.SSTORE(
                storage.store_next(0, "rds_after_failed_call"),
                Op.RETURNDATASIZE,
            )
            + Op.STOP
        ),
        storage=dict.fromkeys(storage, 0xFF),
    )

    tx = Transaction(
        sender=pre.fund_eoa(),
        to=caller,
    )

    state_test(
        pre=pre,
        post={caller: Account(storage=storage)},
        tx=tx,
    )

Parametrized Test Cases

This test generates 1 parametrized test case across 12 forks.