11"""Account-related types for Ethereum tests."""
22
3+ import json
34from dataclasses import dataclass , field
45from enum import Enum , auto
5- from typing import Dict , List , Literal , Optional , Tuple
6+ from typing import Any , Dict , List , Literal , Optional , Self , Tuple
67
78from coincurve .keys import PrivateKey
89from ethereum_types .bytes import Bytes20
910from ethereum_types .numeric import U256 , Bytes32 , Uint
1011from pydantic import PrivateAttr
11- from typing_extensions import Self
1212
1313from ethereum_test_base_types import (
1414 Account ,
@@ -133,25 +133,6 @@ def copy(self) -> Self:
133133 return self .__class__ (Address (self ), key = self .key , nonce = self .nonce )
134134
135135
136- class CollisionError (Exception ):
137- """Exception raised when two tests describe different accounts at the same address."""
138-
139- def __init__ (self , address : Address , account_1 : Account , account_2 : Account ):
140- """Initialize the exception."""
141- self .address = address
142- self .account_1 = account_1
143- self .account_2 = account_2
144-
145- def __str__ (self ) -> str :
146- """Exception message."""
147- return (
148- "Overlapping key defining different accounts detected:\n "
149- f"address={ self .address } :\n "
150- f"account_1={ self .account_1 .model_dump_json (indent = 2 )} \n "
151- f"account_2={ self .account_2 .model_dump_json (indent = 2 )} "
152- )
153-
154-
155136class Alloc (BaseAlloc ):
156137 """Allocation of accounts in the state, pre and post test execution."""
157138
@@ -164,12 +145,6 @@ class UnexpectedAccountError(Exception):
164145 address : Address
165146 account : Account | None
166147
167- def __init__ (self , address : Address , account : Account | None , * args ):
168- """Initialize the exception."""
169- super ().__init__ (args )
170- self .address = address
171- self .account = account
172-
173148 def __str__ (self ):
174149 """Print exception string."""
175150 return f"unexpected account in allocation { self .address } : { self .account } "
@@ -180,15 +155,50 @@ class MissingAccountError(Exception):
180155
181156 address : Address
182157
183- def __init__ (self , address : Address , * args ):
184- """Initialize the exception."""
185- super ().__init__ (args )
186- self .address = address
187-
188158 def __str__ (self ):
189159 """Print exception string."""
190160 return f"Account missing from allocation { self .address } "
191161
162+ @dataclass (kw_only = True )
163+ class CollisionError (Exception ):
164+ """Different accounts at the same address."""
165+
166+ address : Address
167+ account_1 : Account | None
168+ account_2 : Account | None
169+
170+ def to_json (self ) -> Dict [str , Any ]:
171+ """Dump to json object."""
172+ return {
173+ "address" : self .address .hex (),
174+ "account_1" : self .account_1 .model_dump (mode = "json" )
175+ if self .account_1 is not None
176+ else None ,
177+ "account_2" : self .account_2 .model_dump (mode = "json" )
178+ if self .account_2 is not None
179+ else None ,
180+ }
181+
182+ @classmethod
183+ def from_json (cls , obj : Dict [str , Any ]) -> Self :
184+ """Parse from a json dict."""
185+ return cls (
186+ address = Address (obj ["address" ]),
187+ account_1 = Account .model_validate (obj ["account_1" ])
188+ if obj ["account_1" ] is not None
189+ else None ,
190+ account_2 = Account .model_validate (obj ["account_2" ])
191+ if obj ["account_2" ] is not None
192+ else None ,
193+ )
194+
195+ def __str__ (self ) -> str :
196+ """Print exception string."""
197+ return (
198+ "Overlapping key defining different accounts detected:\n "
199+ f"{ json .dumps (self .to_json (), indent = 2 )} "
200+ )
201+
192202 class KeyCollisionMode (Enum ):
193203 """Mode for handling key collisions when merging allocations."""
194204
@@ -216,7 +226,7 @@ def merge(
216226 account_1 = alloc_1 [key ]
217227 account_2 = alloc_2 [key ]
218228 if account_1 != account_2 :
219- raise CollisionError (
229+ raise Alloc . CollisionError (
220230 address = key ,
221231 account_1 = account_1 ,
222232 account_2 = account_2 ,
@@ -309,15 +319,17 @@ def verify_post_alloc(self, got_alloc: "Alloc"):
309319 if account is None :
310320 # Account must not exist
311321 if address in got_alloc .root and got_alloc .root [address ] is not None :
312- raise Alloc .UnexpectedAccountError (address , got_alloc .root [address ])
322+ raise Alloc .UnexpectedAccountError (
323+ address = address , account = got_alloc .root [address ]
324+ )
313325 else :
314326 if address in got_alloc .root :
315327 got_account = got_alloc .root [address ]
316328 assert isinstance (got_account , Account )
317329 assert isinstance (account , Account )
318330 account .check_alloc (address , got_account )
319331 else :
320- raise Alloc .MissingAccountError (address )
332+ raise Alloc .MissingAccountError (address = address )
321333
322334 def deploy_contract (
323335 self ,
0 commit comments