Skip to content

Commit 61d5423

Browse files
raxhvlfselmo
andauthored
✨ feat(tests): EIP-7928 net zero balance transactions (#2280)
* ✨ feat(tests): test_bal_net_zero_balance_transfer * refactor: net-zero balance transfer tests w/ differing starting balances * chore: add changelog entry for #2280 * ✨ feat(tests): test_bal_net_zero_balance_transfer --------- Co-authored-by: raxhvl <[email protected]> Co-authored-by: fselmo <[email protected]>
1 parent b0cf099 commit 61d5423

File tree

3 files changed

+105
-0
lines changed

3 files changed

+105
-0
lines changed

docs/CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ Test fixtures for use by clients are available for each release on the [Github r
4343
- ✨ Add EIP-7928 successful and OOG single-opcode tests ([#2118](https://github.com/ethereum/execution-spec-tests/pull/2118)).
4444
- ✨ Add EIP-7928 tests for EIP-2930 interactions ([#2167](https://github.com/ethereum/execution-spec-tests/pull/2167)).
4545
- ✨ Add EIP-7928 tests for NOOP operations ([#2178](https://github.com/ethereum/execution-spec-tests/pull/2178)).
46+
- ✨ Add EIP-7928 tests for net-zero balance transfers ([#2280](https://github.com/ethereum/execution-spec-tests/pull/2280)).
4647

4748
## [v5.0.0](https://github.com/ethereum/execution-spec-tests/releases/tag/v5.0.0) - 2025-09-05
4849

tests/amsterdam/eip7928_block_level_access_lists/test_block_access_lists.py

Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -947,6 +947,109 @@ def test_bal_zero_value_transfer(
947947
blockchain_test(pre=pre, blocks=[block], post={})
948948

949949

950+
@pytest.mark.parametrize(
951+
"initial_balance,transfer_amount,transfer_mechanism",
952+
[
953+
pytest.param(0, 0, "call", id="zero_balance_zero_transfer_call"),
954+
pytest.param(0, 0, "selfdestruct", id="zero_balance_zero_transfer_selfdestruct"),
955+
pytest.param(1, 1, "call", id="nonzero_balance_net_zero"),
956+
pytest.param(100, 50, "call", id="larger_balance_net_zero"),
957+
],
958+
)
959+
def test_bal_net_zero_balance_transfer(
960+
pre: Alloc,
961+
blockchain_test: BlockchainTestFiller,
962+
initial_balance: int,
963+
transfer_amount: int,
964+
transfer_mechanism: str,
965+
):
966+
"""
967+
Test that BAL does not record balance changes when net change is zero.
968+
969+
A contract starts with `initial_balance`, receives `transfer_amount`
970+
(increasing its balance), then sends `transfer_amount` to a recipient
971+
(decreasing its balance back to `initial_balance`). The net change is zero,
972+
so BAL should not record any balance changes for this contract.
973+
974+
The contract verifies this by reading its own balance with SELFBALANCE,
975+
storing it in slot 0, then sending that amount to the recipient.
976+
"""
977+
alice = pre.fund_eoa()
978+
recipient = pre.fund_eoa(amount=0)
979+
980+
net_zero_bal_contract_code = (
981+
Op.SSTORE(0, Op.SELFBALANCE) + Op.SELFDESTRUCT(recipient)
982+
if transfer_mechanism == "selfdestruct"
983+
# store current balance in slot 0
984+
else (
985+
Op.SSTORE(0, Op.SELFBALANCE)
986+
# send only the `transfer_amount` received to recipient (net zero)
987+
+ Op.CALL(0, recipient, Op.CALLVALUE, 0, 0, 0, 0)
988+
+ Op.STOP
989+
)
990+
)
991+
net_zero_bal_contract = pre.deploy_contract(
992+
code=net_zero_bal_contract_code, balance=initial_balance
993+
)
994+
995+
tx = Transaction(
996+
sender=alice,
997+
to=net_zero_bal_contract,
998+
value=transfer_amount,
999+
gas_limit=1_000_000,
1000+
gas_price=0xA,
1001+
)
1002+
1003+
expected_balance_in_slot = initial_balance + transfer_amount
1004+
1005+
block = Block(
1006+
txs=[tx],
1007+
expected_block_access_list=BlockAccessListExpectation(
1008+
account_expectations={
1009+
alice: BalAccountExpectation(
1010+
nonce_changes=[BalNonceChange(tx_index=1, post_nonce=1)],
1011+
),
1012+
net_zero_bal_contract: BalAccountExpectation(
1013+
# receives transfer_amount and sends transfer_amount away
1014+
# (net-zero change)
1015+
balance_changes=[],
1016+
storage_reads=[0x00] if expected_balance_in_slot == 0 else [],
1017+
storage_changes=[
1018+
BalStorageSlot(
1019+
slot=0x00,
1020+
slot_changes=[
1021+
BalStorageChange(tx_index=1, post_value=expected_balance_in_slot)
1022+
],
1023+
)
1024+
]
1025+
if expected_balance_in_slot > 0
1026+
else [],
1027+
),
1028+
# recipient receives transfer_amount
1029+
recipient: BalAccountExpectation(
1030+
balance_changes=[BalBalanceChange(tx_index=1, post_balance=transfer_amount)]
1031+
if transfer_amount > 0
1032+
else [],
1033+
),
1034+
}
1035+
),
1036+
)
1037+
1038+
blockchain_test(
1039+
pre=pre,
1040+
blocks=[block],
1041+
post={
1042+
net_zero_bal_contract: Account(
1043+
balance=initial_balance,
1044+
storage={0x00: expected_balance_in_slot} if expected_balance_in_slot > 0 else {},
1045+
),
1046+
recipient: Account(balance=transfer_amount)
1047+
if transfer_amount > 0
1048+
else Account.NONEXISTENT,
1049+
},
1050+
)
1051+
1052+
9501053
def test_bal_pure_contract_call(
9511054
pre: Alloc,
9521055
blockchain_test: BlockchainTestFiller,

tests/amsterdam/eip7928_block_level_access_lists/test_cases.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
| `test_bal_2930_slot_listed_and_unlisted_reads` | Ensure BAL includes storage reads regardless of access list presence | Alice sends tx with EIP-2930 access list including `(StorageReader, slot=0x01)`; StorageReader executes `SLOAD` from slots `0x01` and `0x02` | BAL MUST include `storage_reads` for StorageReader's slots `0x01` and `0x02` | ✅ Completed |
2121
| `test_bal_self_transfer` | BAL handles self-transfers correctly | Alice sends `100 wei` to Alice | BAL **MUST** include one entry for Alice with `balance_changes` reflecting gas cost only (value cancels out) and nonce change. | ✅ Completed |
2222
| `test_bal_zero_value_transfer` | BAL handles zero-value transfers correctly | Alice sends `0 wei` to Bob | BAL **MUST** include Alice with `balance_changes` (gas cost only) and nonce change, and Bob in `account_changes` with empty `balance_changes`. | ✅ Completed |
23+
| `test_bal_net_zero_balance_transfer` | BAL includes accounts with net-zero balance change but excludes them from balance changes | Contract receives and sends same amount to recipient using CALL or SELFDESTRUCT | BAL **MUST** include contract in `account_changes` without `balance_changes` (net zero). BAL **MUST** record non-zero `balance_changes` for recipient. | ✅ Completed |
2324
| `test_bal_system_contracts_2935_4788` | BAL includes pre-exec system writes for parent hash & beacon root | Build a block with `N` normal txs; 2935 & 4788 active | BAL MUST include `HISTORY_STORAGE_ADDRESS` (EIP-2935) and `BEACON_ROOTS_ADDRESS` (EIP-4788) with `storage_changes` to ring-buffer slots; each write uses `tx_index = N` (system op). | 🟡 Planned |
2425
| `test_bal_system_dequeue_withdrawals_eip7002` | BAL tracks post-exec system dequeues for withdrawals | Pre-populate EIP-7002 withdrawal requests; produce a block where dequeues occur | BAL MUST include the 7002 system contract with `storage_changes` (queue head/tail slots 0–3) using `tx_index = len(txs)` and balance changes for withdrawal recipients. | 🟡 Planned |
2526
| `test_bal_system_dequeue_consolidations_eip7251` | BAL tracks post-exec system dequeues for consolidations | Pre-populate EIP-7251 consolidation requests; produce a block where dequeues occur | BAL MUST include the 7251 system contract with `storage_changes` (queue slots 0–3) using `tx_index = len(txs)`. | 🟡 Planned |

0 commit comments

Comments
 (0)