Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -31,8 +31,9 @@
REFERENCE_SPEC_GIT_PATH = ref_spec_7928.git_path
REFERENCE_SPEC_VERSION = ref_spec_7928.version

pytestmark = pytest.mark.valid_from("Amsterdam")


@pytest.mark.valid_from("Amsterdam")
def test_bal_nonce_changes(
pre: Alloc,
blockchain_test: BlockchainTestFiller,
Expand Down Expand Up @@ -68,7 +69,6 @@ def test_bal_nonce_changes(
)


@pytest.mark.valid_from("Amsterdam")
def test_bal_balance_changes(
pre: Alloc,
blockchain_test: BlockchainTestFiller,
Expand Down Expand Up @@ -128,7 +128,6 @@ def test_bal_balance_changes(
)


@pytest.mark.valid_from("Amsterdam")
def test_bal_storage_writes(
pre: Alloc,
blockchain_test: BlockchainTestFiller,
Expand Down Expand Up @@ -174,7 +173,6 @@ def test_bal_storage_writes(
)


@pytest.mark.valid_from("Amsterdam")
def test_bal_storage_reads(
pre: Alloc,
blockchain_test: BlockchainTestFiller,
Expand Down Expand Up @@ -213,7 +211,6 @@ def test_bal_storage_reads(
)


@pytest.mark.valid_from("Amsterdam")
def test_bal_code_changes(
pre: Alloc,
blockchain_test: BlockchainTestFiller,
Expand Down Expand Up @@ -290,7 +287,6 @@ def test_bal_code_changes(
)


@pytest.mark.valid_from("Amsterdam")
@pytest.mark.parametrize("self_destruct_in_same_tx", [True, False], ids=["same_tx", "new_tx"])
@pytest.mark.parametrize("pre_funded", [True, False], ids=["pre_funded", "not_pre_funded"])
def test_bal_self_destruct(
Expand Down Expand Up @@ -424,3 +420,314 @@ def test_bal_self_destruct(
blocks=[block],
post=post,
)


@pytest.mark.parametrize(
"account_access_opcode",
[
pytest.param(lambda target_addr: Op.BALANCE(target_addr), id="balance"),
pytest.param(lambda target_addr: Op.EXTCODESIZE(target_addr), id="extcodesize"),
pytest.param(lambda target_addr: Op.EXTCODECOPY(target_addr, 0, 0, 32), id="extcodecopy"),
pytest.param(lambda target_addr: Op.EXTCODEHASH(target_addr), id="extcodehash"),
pytest.param(lambda target_addr: Op.CALL(0, target_addr, 50, 0, 0, 0, 0), id="call"),
pytest.param(
lambda target_addr: Op.CALLCODE(0, target_addr, 0, 0, 0, 0, 0), id="callcode"
),
pytest.param(
lambda target_addr: Op.DELEGATECALL(0, target_addr, 0, 0, 0, 0), id="delegatecall"
),
pytest.param(
lambda target_addr: Op.STATICCALL(0, target_addr, 0, 0, 0, 0), id="staticcall"
),
],
)
def test_bal_account_access_target(
pre: Alloc,
blockchain_test: BlockchainTestFiller,
account_access_opcode,
):
"""Ensure BAL captures target address of account access opcodes."""
alice = pre.fund_eoa()
target_contract = pre.deploy_contract(code=Op.STOP)

oracle_contract = pre.deploy_contract(
balance=100,
code=account_access_opcode(target_contract),
)

tx = Transaction(sender=alice, to=oracle_contract, gas_limit=5_000_000, gas_price=0xA)

block = Block(
txs=[tx],
expected_block_access_list=BlockAccessListExpectation(
account_expectations={
alice: BalAccountExpectation(
nonce_changes=[BalNonceChange(tx_index=1, post_nonce=1)]
),
target_contract: BalAccountExpectation(),
oracle_contract: BalAccountExpectation(),
}
),
)

blockchain_test(pre=pre, blocks=[block], post={})


def test_bal_call_with_value_transfer(
pre: Alloc,
blockchain_test: BlockchainTestFiller,
):
"""
Ensure BAL captures balance changes from CALL opcode with
value transfer.
"""
alice = pre.fund_eoa()
bob = pre.fund_eoa(amount=0)

# Oracle contract that uses CALL to transfer 100 wei to Bob
oracle_code = Op.CALL(0, bob, 100, 0, 0, 0, 0)
oracle_contract = pre.deploy_contract(code=oracle_code, balance=200)

tx = Transaction(sender=alice, to=oracle_contract, gas_limit=1_000_000, gas_price=0xA)

block = Block(
txs=[tx],
expected_block_access_list=BlockAccessListExpectation(
account_expectations={
alice: BalAccountExpectation(
nonce_changes=[BalNonceChange(tx_index=1, post_nonce=1)],
),
oracle_contract: BalAccountExpectation(
balance_changes=[BalBalanceChange(tx_index=1, post_balance=100)],
),
bob: BalAccountExpectation(
balance_changes=[BalBalanceChange(tx_index=1, post_balance=100)],
),
}
),
)

blockchain_test(pre=pre, blocks=[block], post={})


def test_bal_callcode_with_value_transfer(
pre: Alloc,
blockchain_test: BlockchainTestFiller,
):
"""
Ensure BAL captures balance changes from CALLCODE opcode with
value transfer.
"""
alice = pre.fund_eoa()
bob = pre.fund_eoa(amount=0)

# TargetContract sends 100 wei to bob
target_code = Op.CALL(0, bob, 100, 0, 0, 0, 0)
target_contract = pre.deploy_contract(code=target_code)

# Oracle contract that uses CALLCODE to execute TargetContract's code
oracle_code = Op.CALLCODE(50_000, target_contract, 100, 0, 0, 0, 0)
oracle_contract = pre.deploy_contract(code=oracle_code, balance=200)

tx = Transaction(sender=alice, to=oracle_contract, gas_limit=1_000_000, gas_price=0xA)

block = Block(
txs=[tx],
expected_block_access_list=BlockAccessListExpectation(
account_expectations={
alice: BalAccountExpectation(
nonce_changes=[BalNonceChange(tx_index=1, post_nonce=1)],
),
oracle_contract: BalAccountExpectation(
balance_changes=[BalBalanceChange(tx_index=1, post_balance=100)],
),
bob: BalAccountExpectation(
balance_changes=[BalBalanceChange(tx_index=1, post_balance=100)],
),
target_contract: BalAccountExpectation(),
}
),
)

blockchain_test(pre=pre, blocks=[block], post={})


@pytest.mark.parametrize(
"delegated_opcode",
[
pytest.param(
lambda target_addr: Op.DELEGATECALL(50000, target_addr, 0, 0, 0, 0), id="delegatecall"
),
pytest.param(
lambda target_addr: Op.CALLCODE(50000, target_addr, 0, 0, 0, 0, 0), id="callcode"
),
],
)
def test_bal_delegated_storage_writes(
pre: Alloc,
blockchain_test: BlockchainTestFiller,
delegated_opcode,
):
"""
Ensure BAL captures delegated storage writes via
DELEGATECALL and CALLCODE.
"""
alice = pre.fund_eoa()

# TargetContract that writes 0x42 to slot 0x01
target_code = Op.SSTORE(0x01, 0x42)
target_contract = pre.deploy_contract(code=target_code)

# Oracle contract that uses delegated opcode to execute
# TargetContract's code
oracle_code = delegated_opcode(target_contract)
oracle_contract = pre.deploy_contract(code=oracle_code)

tx = Transaction(
sender=alice,
to=oracle_contract,
gas_limit=1_000_000,
)

block = Block(
txs=[tx],
expected_block_access_list=BlockAccessListExpectation(
account_expectations={
alice: BalAccountExpectation(
nonce_changes=[BalNonceChange(tx_index=1, post_nonce=1)],
),
oracle_contract: BalAccountExpectation(
storage_changes=[
BalStorageSlot(
slot=0x01,
slot_changes=[BalStorageChange(tx_index=1, post_value=0x42)],
)
],
),
target_contract: BalAccountExpectation(),
}
),
)

blockchain_test(pre=pre, blocks=[block], post={})


@pytest.mark.parametrize(
"delegated_opcode",
[
pytest.param(
lambda target_addr: Op.DELEGATECALL(50000, target_addr, 0, 0, 0, 0), id="delegatecall"
),
pytest.param(
lambda target_addr: Op.CALLCODE(50000, target_addr, 0, 0, 0, 0, 0), id="callcode"
),
],
)
def test_bal_delegated_storage_reads(
pre: Alloc,
blockchain_test: BlockchainTestFiller,
delegated_opcode,
):
"""
Ensure BAL captures delegated storage reads via
DELEGATECALL and CALLCODE.
"""
alice = pre.fund_eoa()

# TargetContract that reads from slot 0x01
target_code = Op.SLOAD(0x01) + Op.STOP
target_contract = pre.deploy_contract(code=target_code)

# Oracle contract with storage slot 0x01 = 0x42,
# uses delegated opcode to execute TargetContract's code
oracle_code = delegated_opcode(target_contract)
oracle_contract = pre.deploy_contract(code=oracle_code, storage={0x01: 0x42})

tx = Transaction(
sender=alice,
to=oracle_contract,
gas_limit=1_000_000,
)

block = Block(
txs=[tx],
expected_block_access_list=BlockAccessListExpectation(
account_expectations={
alice: BalAccountExpectation(
nonce_changes=[BalNonceChange(tx_index=1, post_nonce=1)],
),
oracle_contract: BalAccountExpectation(
storage_reads=[0x01],
),
target_contract: BalAccountExpectation(),
}
),
)

blockchain_test(pre=pre, blocks=[block], post={})


def test_bal_block_rewards(
pre: Alloc,
blockchain_test: BlockchainTestFiller,
fork,
):
"""Ensure BAL captures fee recipient balance changes from block rewards."""
alice_initial_balance = 1_000_000
alice = pre.fund_eoa(amount=alice_initial_balance)
bob = pre.fund_eoa(amount=0)
charlie = pre.fund_eoa(amount=0) # fee recipient

intrinsic_gas_calculator = fork.transaction_intrinsic_cost_calculator()
intrinsic_gas_cost = intrinsic_gas_calculator(
calldata=b"",
contract_creation=False,
access_list=[],
)
tx_gas_limit = intrinsic_gas_cost + 1000 # add a small buffer
gas_price = 0xA
base_fee_per_gas = 0x2 # Set base fee for EIP-1559

tx = Transaction(
sender=alice,
to=bob,
value=100,
gas_limit=tx_gas_limit,
gas_price=gas_price,
)

# EIP-1559 fee calculation:
# - Total gas cost
total_gas_cost = intrinsic_gas_cost * gas_price
# - Tip portion
tip_to_charlie = intrinsic_gas_cost * (gas_price - base_fee_per_gas)

alice_final_balance = alice_initial_balance - 100 - total_gas_cost

block = Block(
txs=[tx],
fee_recipient=charlie, # Set Charlie as the fee recipient
base_fee_per_gas=base_fee_per_gas, # Set base fee for EIP-1559
expected_block_access_list=BlockAccessListExpectation(
account_expectations={
alice: BalAccountExpectation(
nonce_changes=[BalNonceChange(tx_index=1, post_nonce=1)],
balance_changes=[
BalBalanceChange(tx_index=1, post_balance=alice_final_balance)
],
),
bob: BalAccountExpectation(
balance_changes=[BalBalanceChange(tx_index=1, post_balance=100)],
),
charlie: BalAccountExpectation(
balance_changes=[BalBalanceChange(tx_index=1, post_balance=tip_to_charlie)],
),
}
),
)

blockchain_test(
pre=pre,
blocks=[block],
post={},
)
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,13 @@
| `test_bal_code_changes` | Ensure BAL captures changes to account code | Alice deploys factory contract that creates new contract | BAL MUST include code changes for newly deployed contract | βœ… Completed |
| `test_bal_self_destruct` | Ensure BAL captures storage access and balance changes caused by `SELFDESTRUCT` | Parameterized test: Alice interacts with a contract (either existing or created same-tx) that reads from storage slot 0x01, writes to storage slot 0x02, then executes `SELFDESTRUCT` with Bob as recipient. Contract may be pre-funded with 10 wei | BAL MUST include Alice's nonce change (increment) and Bob's balance change (100 or 110 depending on pre-funding). For the self-destructing contract: storage_reads=[0x01], empty storage_changes=[], and if pre-funded, balance_changes with post_balance=0; if not pre-funded, no balance change recorded. MUST NOT have code_changes or nonce_changes entries | βœ… Completed |
| `test_bal_2930_slot_listed_but_untouched` | Ensure 2930 access list alone doesn't appear in BAL | Include `(KV, S=0x01)` in tx's EIP-2930 access list; tx executes code that does **no** `SLOAD`/`SSTORE` to `S` (e.g., pure arithmetic/log). | BAL **MUST NOT** contain any entry for `(KV, S)` β€” neither reads nor writes β€” because the slot wasn't touched. | 🟑 Planned |
| `test_bal_account_access_target` | Ensure BAL captures target addresses of account access opcodes | Alice calls `Oracle` contract which uses account access opcodes (`BALANCE`, `EXTCODESIZE`, `EXTCODECOPY`, `EXTCODEHASH`, `CALL`, `CALLCODE`, `DELEGATECALL`, `STATICCALL`) on `TargetContract`. | BAL MUST include Alice, `Oracle`, and `TargetContract` with empty changes for `TargetContract` and nonce changes for Alice. | βœ… Completed |
| `test_bal_call_with_value_transfer` | Ensure BAL captures balance changes from `CALL` opcode with value transfer | Alice calls `Oracle` contract (200 wei balance) which uses `CALL` opcode to transfer 100 wei to Bob (0 wei balance). | BAL MUST include Alice (nonce changes), Oracle (balance change to 100 wei), and Bob (balance change to 100 wei). | βœ… Completed |
| `test_bal_callcode_with_value_transfer` | Ensure BAL captures balance changes from `CALLCODE` opcode with value transfer | Alice calls `Oracle` contract (200 wei balance) which uses `CALLCODE` opcode to execute `TargetContract`'s code with 100 wei value transfer to Bob (0 wei balance). | BAL MUST include Alice (nonce changes), `Oracle` (balance change to 100 wei), Bob (balance change to 100 wei), and `TargetContract` (empty changes). | βœ… Completed |
| `test_bal_delegated_storage_writes` | Ensure BAL captures delegated storage writes via `DELEGATECALL` and `CALLCODE` | Alice calls `Oracle` contract which uses `DELEGATECALL`/`CALLCODE` to `TargetContract` that writes `0x42` to slot `0x01`. | BAL MUST include Alice (nonce changes), `Oracle` (storage changes for slot `0x01` = `0x42`), and `TargetContract` (empty changes). | βœ… Completed |
| `test_bal_delegated_storage_reads` | Ensure BAL captures delegated storage reads via `DELEGATECALL` and `CALLCODE` | Alice calls `Oracle` contract (with slot `0x01` = `0x42`) which uses `DELEGATECALL`/`CALLCODE` to `TargetContract` that reads from slot `0x01`. | BAL MUST include Alice (nonce changes), `Oracle` (storage reads for slot `0x01`), and `TargetContract` (empty changes). | βœ… Completed |
| `test_bal_block_rewards` | BAL tracks fee recipient balance changes from block rewards | Alice sends 100 wei to Bob with Charlie as fee recipient | BAL MUST include fee recipient Charlie with `balance_changes` reflecting transaction fees collected from the block. | βœ… Completed |
| `test_bal_2930_slot_listed_but_untouched` | Ensure 2930 access list alone doesn’t appear in BAL | Include `(KV, S=0x01)` in tx’s EIP-2930 access list; tx executes code that does **no** `SLOAD`/`SSTORE` to `S` (e.g., pure arithmetic/log). | BAL **MUST NOT** contain any entry for `(KV, S)` β€” neither reads nor writes β€” because the slot wasn’t touched. | 🟑 Planned |
| `test_bal_2930_slot_listed_and_modified` | Ensure BAL records writes only because the slot is touched | Same access list as above, but tx executes `SSTORE` to `S`. | BAL **MUST** include `storage_changes` for `(KV, S)` (and no separate read record for that slot if implementation deduplicates). Presence in the access list is irrelevant; inclusion is due to the actual write. | 🟑 Planned |
| `test_bal_7702_delegated_create` | BAL tracks EIP-7702 delegation indicator write and contract creation | Alice sends a type-4 (7702) tx authorizing herself to delegate to `Deployer` code which executes `CREATE` | BAL MUST include for **Alice**: `code_changes` (delegation indicator), `nonce_changes` (increment from 7702 processing), and `balance_changes` (post-gas). For **Child**: `code_changes` (runtime bytecode) and `nonce_changes = 1`. | 🟑 Planned |
| `test_bal_self_transfer` | BAL handles self-transfers correctly | Alice sends `1 ETH` to **Alice** | BAL MUST include **one** entry for Alice with `balance_changes` reflecting **gas only** (value cancels out) and a nonce change; Coinbase balance updated for fees; no separate recipient row. | 🟑 Planned |
Expand Down
Loading