|
1 | 1 | """Account structure of ethereum/tests fillers.""" |
2 | 2 |
|
3 | | -from typing import Any, Dict, List, Mapping, Set, Tuple |
| 3 | +from typing import TYPE_CHECKING, Any, Dict, List, Mapping, Set, Tuple |
4 | 4 |
|
5 | 5 | from pydantic import BaseModel |
6 | 6 |
|
7 | | -from ethereum_test_base_types import Bytes, EthereumTestRootModel, HexNumber, Storage |
| 7 | +if TYPE_CHECKING: |
| 8 | + from .environment import EnvironmentInStateTestFiller |
| 9 | + from .expect_section import ResultInFiller |
| 10 | + |
| 11 | +from ethereum_test_base_types import ( |
| 12 | + Account, |
| 13 | + Address, |
| 14 | + Bytes, |
| 15 | + EthereumTestRootModel, |
| 16 | + HexNumber, |
| 17 | + Storage, |
| 18 | +) |
8 | 19 | from ethereum_test_types import Alloc |
| 20 | +from ethereum_test_types.block_types import EnvironmentGeneric |
9 | 21 |
|
10 | 22 | from .common import ( |
11 | 23 | AddressOrTagInFiller, |
@@ -84,6 +96,17 @@ def resolve(self, tags: TagDict) -> Dict[str, Any]: |
84 | 96 | account_properties["storage"] = resolved_storage |
85 | 97 | return account_properties |
86 | 98 |
|
| 99 | + def __hash__(self) -> int: |
| 100 | + """Generate deterministic hash for the account.""" |
| 101 | + # Create a tuple of hashable representations |
| 102 | + hash_tuple = ( |
| 103 | + self.balance if self.balance is not None else None, |
| 104 | + str(self.code) if self.code is not None else None, |
| 105 | + self.nonce if self.nonce is not None else None, |
| 106 | + str(self.storage) if self.storage is not None else None, |
| 107 | + ) |
| 108 | + return hash(hash_tuple) |
| 109 | + |
87 | 110 |
|
88 | 111 | class PreInFiller(EthereumTestRootModel): |
89 | 112 | """Class that represents a pre-state in filler.""" |
@@ -142,7 +165,14 @@ def _topological_sort(self, dep_graph: Dict[str, Set[str]]) -> List[str]: |
142 | 165 |
|
143 | 166 | return sorted_nodes |
144 | 167 |
|
145 | | - def setup(self, pre: Alloc, all_dependencies: Dict[str, Tag]) -> TagDict: |
| 168 | + def setup( |
| 169 | + self, |
| 170 | + pre: Alloc, |
| 171 | + all_dependencies: Dict[str, Tag], |
| 172 | + env: "EnvironmentInStateTestFiller | None" = None, |
| 173 | + expect_result: "ResultInFiller | None" = None, |
| 174 | + request: Any = None, |
| 175 | + ) -> TagDict: |
146 | 176 | """Resolve the pre-state with improved tag resolution.""" |
147 | 177 | resolved_accounts: TagDict = {} |
148 | 178 |
|
@@ -174,12 +204,78 @@ def setup(self, pre: Alloc, all_dependencies: Dict[str, Tag]) -> TagDict: |
174 | 204 | if tag_name in tag_to_address: |
175 | 205 | tag = tag_to_address[tag_name] |
176 | 206 | if isinstance(tag, ContractTag): |
177 | | - # Deploy with placeholder to get address |
178 | | - deployed_address = pre.deploy_contract( |
179 | | - code=b"", # Temporary placeholder |
180 | | - label=tag_name, |
| 207 | + # Check if this is a coinbase tag during pre-alloc generation |
| 208 | + is_coinbase = ( |
| 209 | + env is not None |
| 210 | + and isinstance(env.current_coinbase, Tag) |
| 211 | + and env.current_coinbase.name == tag_name |
181 | 212 | ) |
182 | | - resolved_accounts[tag_name] = deployed_address |
| 213 | + |
| 214 | + if is_coinbase and expect_result is not None and request is not None: |
| 215 | + # Check if we're in pre-alloc generation or usage mode |
| 216 | + generate_groups = request.config.getoption( |
| 217 | + "generate_pre_alloc_groups", False |
| 218 | + ) |
| 219 | + use_groups = request.config.getoption("use_pre_alloc_groups", False) |
| 220 | + if generate_groups or use_groups: |
| 221 | + # Get pre-state account |
| 222 | + coinbase_account = tagged_accounts.get(tag) |
| 223 | + |
| 224 | + # Check if account has any pre-state |
| 225 | + has_pre_state = coinbase_account is not None and ( |
| 226 | + coinbase_account.code is not None |
| 227 | + or coinbase_account.storage is not None |
| 228 | + or coinbase_account.balance is not None |
| 229 | + or coinbase_account.nonce is not None |
| 230 | + ) |
| 231 | + |
| 232 | + # Find post-state expectations for this coinbase |
| 233 | + coinbase_post_account = None |
| 234 | + has_post_state = False |
| 235 | + for addr_or_tag, result_account in expect_result.root.items(): |
| 236 | + if isinstance(addr_or_tag, Tag) and addr_or_tag.name == tag_name: |
| 237 | + if result_account is not None: |
| 238 | + has_post_state = True |
| 239 | + coinbase_post_account = result_account |
| 240 | + break |
| 241 | + |
| 242 | + # If no state changes, use the default fee_recipient |
| 243 | + if not has_pre_state and not has_post_state: |
| 244 | + deployed_address = ( |
| 245 | + EnvironmentGeneric.model_fields["fee_recipient"].default |
| 246 | + ) |
| 247 | + resolved_accounts[tag_name] = deployed_address |
| 248 | + else: |
| 249 | + # Use hash to create deterministic address |
| 250 | + # Combine pre and post hashes |
| 251 | + pre_hash = hash(coinbase_account) if coinbase_account else 0 |
| 252 | + post_hash = ( |
| 253 | + hash(coinbase_post_account) if coinbase_post_account else 0 |
| 254 | + ) |
| 255 | + combined_hash = hash((pre_hash, post_hash)) |
| 256 | + |
| 257 | + hash_bytes = abs(combined_hash).to_bytes(32, byteorder="big") |
| 258 | + # Start with 0xc01b (coinbase) prefix |
| 259 | + address_bytes = b"\xc0\x1b" + hash_bytes[:18] |
| 260 | + deployed_address = Address(address_bytes) |
| 261 | + resolved_accounts[tag_name] = deployed_address |
| 262 | + # Manually add to pre-alloc if it doesn't exist |
| 263 | + if deployed_address not in pre: |
| 264 | + pre[deployed_address] = Account() |
| 265 | + else: |
| 266 | + # Not in pre-alloc generation, normal deployment |
| 267 | + deployed_address = pre.deploy_contract( |
| 268 | + code=b"", # Temporary placeholder |
| 269 | + label=tag_name, |
| 270 | + ) |
| 271 | + resolved_accounts[tag_name] = deployed_address |
| 272 | + else: |
| 273 | + # Not a coinbase tag, normal deployment |
| 274 | + deployed_address = pre.deploy_contract( |
| 275 | + code=b"", # Temporary placeholder |
| 276 | + label=tag_name, |
| 277 | + ) |
| 278 | + resolved_accounts[tag_name] = deployed_address |
183 | 279 | elif isinstance(tag, SenderTag): |
184 | 280 | # Create EOA to get address - use amount=1 to ensure account is created |
185 | 281 | eoa = pre.fund_eoa(amount=1, label=tag_name) |
|
0 commit comments