5
5
these are simple data classes that can be composed together.
6
6
"""
7
7
8
- from typing import Any , ClassVar , Dict , List
8
+ from typing import Any , ClassVar , List
9
9
10
+ import ethereum_rlp as eth_rlp
10
11
from pydantic import Field
11
12
12
13
from ethereum_test_base_types import (
13
14
Address ,
14
15
Bytes ,
15
16
CamelModel ,
17
+ EthereumTestRootModel ,
16
18
HexNumber ,
17
19
Number ,
18
20
RLPSerializable ,
@@ -98,56 +100,61 @@ class BalAccountChange(CamelModel, RLPSerializable):
98
100
]
99
101
100
102
101
- class BlockAccessList (CamelModel , RLPSerializable ):
103
+ class BlockAccessList (EthereumTestRootModel [ List [ BalAccountChange ]] ):
102
104
"""
103
- Expected Block Access List for verification.
105
+ Block Access List for t8n tool communication and fixtures.
106
+
107
+ This model represents the BAL exactly as defined in EIP-7928 - it is itself a list
108
+ of account changes (root model), not a container. Used for:
109
+ - Communication with t8n tools
110
+ - Fixture generation
111
+ - RLP encoding for hash verification
104
112
105
113
Example:
106
- expected_block_access_list = BlockAccessList(
114
+ bal = BlockAccessList([
115
+ BalAccountChange(address=alice, nonce_changes=[...]),
116
+ BalAccountChange(address=bob, balance_changes=[...])
117
+ ])
118
+
119
+ """
120
+
121
+ root : List [BalAccountChange ] = Field (default_factory = list )
122
+
123
+ def to_list (self ) -> List [Any ]:
124
+ """Return the list for RLP encoding per EIP-7928."""
125
+ return to_serializable_element (self .root )
126
+
127
+ def rlp (self ) -> Bytes :
128
+ """Return the RLP encoded block access list for hash verification."""
129
+ return Bytes (eth_rlp .encode (self .to_list ()))
130
+
131
+
132
+ class BlockAccessListExpectation (CamelModel ):
133
+ """
134
+ Block Access List expectation model for test writing.
135
+
136
+ This model is used to define expected BAL values in tests. It supports:
137
+ - Partial validation (only checks explicitly set fields)
138
+ - Convenient test syntax with named parameters
139
+ - Verification against actual BAL from t8n
140
+
141
+ Example:
142
+ # In test definition
143
+ expected_block_access_list = BlockAccessListExpectation(
107
144
account_changes=[
108
145
BalAccountChange(
109
146
address=alice,
110
- nonce_changes=[
111
- BalNonceChange(tx_index=0, post_nonce=1)
112
- ],
113
- balance_changes=[
114
- BalBalanceChange(tx_index=0, post_balance=9000)
115
- ]
116
- ),
117
- BalAccountChange(
118
- address=bob,
119
- balance_changes=[
120
- BalBalanceChange(tx_index=0, post_balance=100)
121
- ],
122
- code_changes=[
123
- BalCodeChange(tx_index=0, new_code=b"0x1234")
124
- ],
125
- ),
147
+ nonce_changes=[BalNonceChange(tx_index=1, post_nonce=1)]
148
+ )
126
149
]
127
150
)
128
151
129
152
"""
130
153
131
154
account_changes : List [BalAccountChange ] = Field (
132
- default_factory = list , description = "List of account changes in the block "
155
+ default_factory = list , description = "Expected account changes to verify "
133
156
)
134
157
135
- rlp_fields : ClassVar [List [str ]] = ["account_changes" ]
136
-
137
- def to_list (self , signing : bool = False ) -> List [Any ]:
138
- """
139
- Override to_list to return the account changes list directly.
140
-
141
- The BlockAccessList IS the list of account changes, not a container
142
- that contains a list, per EIP-7928.
143
- """
144
- # Return the list of accounts directly, not wrapped in another list
145
- return to_serializable_element (self .account_changes )
146
-
147
- def to_dict (self ) -> Dict [str , Any ]:
148
- """Convert to dictionary for serialization."""
149
- return self .model_dump (exclude_none = True )
150
-
151
158
def verify_against (self , actual_bal : "BlockAccessList" ) -> None :
152
159
"""
153
160
Verify that the actual BAL from the client matches this expected BAL.
@@ -159,7 +166,7 @@ def verify_against(self, actual_bal: "BlockAccessList") -> None:
159
166
Exception: If verification fails
160
167
161
168
"""
162
- actual_accounts_by_addr = {acc .address : acc for acc in actual_bal .account_changes }
169
+ actual_accounts_by_addr = {acc .address : acc for acc in actual_bal .root }
163
170
expected_accounts_by_addr = {acc .address : acc for acc in self .account_changes }
164
171
165
172
# Check for missing accounts
0 commit comments