@@ -73,6 +73,15 @@ static FUZZ_ADDRESS_CAIRO0_A: LazyLock<ContractAddress> = LazyLock::new(|| {
7373static FUZZ_ADDRESS_CAIRO0_B : LazyLock < ContractAddress > = LazyLock :: new ( || {
7474 ContractAddress :: try_from ( felt ! ( FUZZ_ADDRESS_CAIRO0_B_EXPECT . data( ) ) ) . unwrap ( )
7575} ) ;
76+ static FUZZ_ADDRESS_TO_CLASS_HASH : LazyLock < BTreeMap < ContractAddress , ClassHash > > =
77+ LazyLock :: new ( || {
78+ BTreeMap :: from ( [
79+ ( * FUZZ_ADDRESS_CAIRO1_A , * CAIRO1_CONTRACT_CLASS_HASH ) ,
80+ ( * FUZZ_ADDRESS_CAIRO1_B , * CAIRO1_CONTRACT_CLASS_HASH ) ,
81+ ( * FUZZ_ADDRESS_CAIRO0_A , * CAIRO0_CONTRACT_CLASS_HASH ) ,
82+ ( * FUZZ_ADDRESS_CAIRO0_B , * CAIRO0_CONTRACT_CLASS_HASH ) ,
83+ ] )
84+ } ) ;
7685
7786/// Storage key that can be written to.
7887static VALID_STORAGE_KEYS : LazyLock < Vec < Felt > > =
@@ -300,26 +309,27 @@ struct RevertInfo {
300309 /// Addresses of deployed contracts that were deployed in the reverted call tree.
301310 pub deployed_addresses : BTreeSet < ContractAddress > ,
302311
303- /// If a class was replaced in the reverted call tree, the address and original class hash of
304- /// the replaced class.
305- pub class_replaced_and_original_class_hash : Option < ( ContractAddress , ClassHash ) > ,
312+ /// If a class was replaced in the reverted call tree, the original class hash of the replaced
313+ /// class.
314+ pub class_replaced : Option < ClassHash > ,
306315}
307316
308317impl RevertInfo {
309318 pub fn combine ( others : Vec < Self > ) -> Self {
310319 // If there is a replace-class, there should be only one.
311- let mut class_replaced_and_original_class_hash = None ;
320+ let mut class_replaced = None ;
312321 for other in others. iter ( ) {
313- if let Some ( inner_replacement) = other. class_replaced_and_original_class_hash {
314- class_replaced_and_original_class_hash = Some ( inner_replacement) ;
322+ if other. class_replaced . is_some ( ) {
323+ assert ! ( class_replaced. is_none( ) ) ;
324+ class_replaced = other. class_replaced ;
315325 }
316326 }
317327 Self {
318328 deployed_addresses : others
319329 . into_iter ( )
320330 . flat_map ( |other| other. deployed_addresses )
321331 . collect ( ) ,
322- class_replaced_and_original_class_hash ,
332+ class_replaced ,
323333 }
324334 }
325335}
@@ -341,11 +351,11 @@ struct FuzzTestContext {
341351 /// List of operations applied to the fuzz test so far.
342352 pub operations : Vec < FuzzOperationData > ,
343353
344- /// Map from contract address to class hash.
345- pub deployed_contracts : BTreeMap < ContractAddress , ClassHash > ,
354+ /// Map from newly deployed contract address to class hash.
355+ pub newly_deployed_contracts : BTreeMap < ContractAddress , ClassHash > ,
346356
347- /// We only allow one replace class per test.
348- pub class_replaced : bool ,
357+ /// We only allow one replace class per test. Track which contract was replaced.
358+ pub replaced_address : Option < ContractAddress > ,
349359
350360 /// Next value to write in a storage-write operation.
351361 pub next_storage_write_value : StarknetStorageValue ,
@@ -357,18 +367,14 @@ struct FuzzTestContext {
357367}
358368
359369impl FuzzTestContext {
360- pub fn init (
361- seed : u64 ,
362- first_call : FuzzCallInfo ,
363- deployed_fuzz_contracts : BTreeMap < ContractAddress , ClassHash > ,
364- ) -> Self {
370+ pub fn init ( seed : u64 , first_call : FuzzCallInfo ) -> Self {
365371 Self {
366372 calls : vec ! [ first_call] ,
367373 current_call : vec ! [ 0 ] ,
368374 final_state : FinalizedState :: Ongoing ,
369375 operations : vec ! [ ] ,
370- deployed_contracts : deployed_fuzz_contracts ,
371- class_replaced : false ,
376+ newly_deployed_contracts : BTreeMap :: new ( ) ,
377+ replaced_address : None ,
372378 next_storage_write_value : StarknetStorageValue ( Felt :: from ( 1u16 << 12 ) ) ,
373379 next_salt : ContractAddressSalt ( Felt :: from ( 1u32 << 16 ) ) ,
374380 rng : ChaCha8Rng :: seed_from_u64 ( seed) ,
@@ -391,15 +397,8 @@ impl FuzzTestManager {
391397 // - two cairo0 fuzz test contracts.
392398 let mut test_manager = Self :: init_deployment ( false ) . await ;
393399
394- let deployed_fuzz_contracts = BTreeMap :: from ( [
395- ( * FUZZ_ADDRESS_CAIRO1_A , * CAIRO1_CONTRACT_CLASS_HASH ) ,
396- ( * FUZZ_ADDRESS_CAIRO1_B , * CAIRO1_CONTRACT_CLASS_HASH ) ,
397- ( * FUZZ_ADDRESS_CAIRO0_A , * CAIRO0_CONTRACT_CLASS_HASH ) ,
398- ( * FUZZ_ADDRESS_CAIRO0_B , * CAIRO0_CONTRACT_CLASS_HASH ) ,
399- ] ) ;
400-
401400 // Initialize the fuzz testing contracts with the orchestrator address.
402- for address in deployed_fuzz_contracts . keys ( ) {
401+ for address in FUZZ_ADDRESS_TO_CLASS_HASH . keys ( ) {
403402 let calldata = create_calldata ( * address, "initialize" , & [ * * * FUZZ_ADDRESS_ORCHESTRATOR ] ) ;
404403 test_manager. add_funded_account_invoke ( invoke_tx_args ! { calldata } ) ;
405404 }
@@ -413,7 +412,7 @@ impl FuzzTestManager {
413412 ParentFailureBehavior :: Cairo1Catching ,
414413 ) ;
415414 Self {
416- context : FuzzTestContext :: init ( seed, first_call, deployed_fuzz_contracts ) ,
415+ context : FuzzTestContext :: init ( seed, first_call) ,
417416 test_manager,
418417 first_called_address,
419418 }
@@ -494,6 +493,26 @@ impl FuzzTestManager {
494493 self . is_cairo1_class ( & self . current_class_hash ( ) )
495494 }
496495
496+ pub fn deployed_contracts ( & self ) -> impl Iterator < Item = & ContractAddress > {
497+ self . context . newly_deployed_contracts . keys ( ) . chain ( FUZZ_ADDRESS_TO_CLASS_HASH . keys ( ) )
498+ }
499+
500+ pub fn try_class_hash_of ( & self , address : & ContractAddress ) -> Option < ClassHash > {
501+ match self . context . replaced_address {
502+ Some ( replaced_address) if & replaced_address == address => {
503+ Some ( * CAIRO1_REPLACEMENT_CLASS_HASH )
504+ }
505+ _ => FUZZ_ADDRESS_TO_CLASS_HASH
506+ . get ( address)
507+ . copied ( )
508+ . or_else ( || self . context . newly_deployed_contracts . get ( address) . copied ( ) ) ,
509+ }
510+ }
511+
512+ pub fn class_hash_of ( & self , address : & ContractAddress ) -> ClassHash {
513+ self . try_class_hash_of ( address) . unwrap ( )
514+ }
515+
497516 /// Returns a vector of operations of the given type that can be applied on the current context.
498517 pub fn valid_operations_of_type (
499518 & self ,
@@ -510,9 +529,7 @@ impl FuzzTestManager {
510529 // There are two Cairo0 contracts and two Cairo1 contracts that can be called.
511530 // When calling from a Cairo1 context, the caller can unwrap the call result or not.
512531 let current_context_is_cairo1 = self . is_current_context_cairo1 ( ) ;
513- self . context
514- . deployed_contracts
515- . keys ( )
532+ self . deployed_contracts ( )
516533 . flat_map ( |address| {
517534 if current_context_is_cairo1 {
518535 [ true , false ]
@@ -571,7 +588,7 @@ impl FuzzTestManager {
571588 . collect ( ) ,
572589 FuzzOperation :: ReplaceClass => {
573590 // If class was already replaced, no more replacements are allowed.
574- if self . context . class_replaced {
591+ if self . context . replaced_address . is_some ( ) {
575592 // TODO(Dori): In this case, replace back to original class.
576593 return vec ! [ ] ;
577594 }
@@ -670,8 +687,8 @@ impl FuzzTestManager {
670687 } else {
671688 BTreeSet :: new( )
672689 } ,
673- class_replaced_and_original_class_hash : if root_call. class_replaced_here {
674- Some ( ( root_call. address , root_call . class_hash) )
690+ class_replaced : if root_call. class_replaced_here {
691+ Some ( root_call. class_hash)
675692 } else {
676693 None
677694 } ,
@@ -683,14 +700,17 @@ impl FuzzTestManager {
683700 pub fn apply_revert_info ( & mut self , revert_info : RevertInfo ) {
684701 // Revert class replacement. Do this before "undeploying" deployed contracts so we don't
685702 // "redeploy" anything when we only intend to revert the class hash change.
686- if let Some ( ( address, class_hash) ) = revert_info. class_replaced_and_original_class_hash {
687- self . context . deployed_contracts . insert ( address, class_hash) ;
703+ if let Some ( original_class_hash) = revert_info. class_replaced {
704+ let replaced_address = self . context . replaced_address . take ( ) . unwrap ( ) ;
705+ if self . context . newly_deployed_contracts . contains_key ( & replaced_address) {
706+ self . context . newly_deployed_contracts . insert ( replaced_address, original_class_hash) ;
707+ }
688708 }
689709 // "Undeploy" all deployed contracts.
690710 for address in revert_info. deployed_addresses . iter ( ) {
691711 // Remove without asserting that the address was actually deployed - the
692712 // constructor may have reverted before being finalized.
693- self . context . deployed_contracts . remove ( address) ;
713+ self . context . newly_deployed_contracts . remove ( address) ;
694714 }
695715 }
696716
@@ -715,7 +735,7 @@ impl FuzzTestManager {
715735 }
716736 FuzzOperationData :: Call ( call_operation_data) => {
717737 let address = * call_operation_data. address ( ) ;
718- let class_hash = * self . context . deployed_contracts . get ( & address) . unwrap ( ) ;
738+ let class_hash = self . class_hash_of ( & address) ;
719739 self . enter_call ( address, class_hash, call_operation_data. parent_failure_behavior ( ) ) ;
720740 }
721741 FuzzOperationData :: LibraryCall ( library_call_operation_data) => {
@@ -730,12 +750,10 @@ impl FuzzTestManager {
730750 self . context . next_storage_write_value . 0 += Felt :: ONE ;
731751 }
732752 FuzzOperationData :: ReplaceClass ( class_hash) => {
733- assert ! ( ! self . context. class_replaced ) ;
753+ assert ! ( self . context. replaced_address . is_none ( ) ) ;
734754 assert_eq ! ( class_hash, * CAIRO1_REPLACEMENT_CLASS_HASH ) ;
735- self . context . class_replaced = true ;
736- // Update the mapping from address to class hash, so subsequent calls to this
737- // address will correctly use the new class hash.
738- self . context . deployed_contracts . insert ( self . current_address ( ) , class_hash) ;
755+ let current_address = self . current_address ( ) ;
756+ self . context . replaced_address = Some ( current_address) ;
739757 // Update the current call to mark that it was replaced at this point, to make it
740758 // easy to track if the change must be reverted mid-test.
741759 self . current_fuzz_call_info_mut ( ) . class_replaced_here = true ;
@@ -748,7 +766,7 @@ impl FuzzTestManager {
748766 // Increment the salt for the next deploy operation.
749767 self . context . next_salt . 0 += Felt :: ONE ;
750768 // Update the mapping from address to class hash.
751- self . context . deployed_contracts . insert ( deployed_address, class_hash) ;
769+ self . context . newly_deployed_contracts . insert ( deployed_address, class_hash) ;
752770 // Enter constructor context.
753771 self . enter_deploy ( deployed_address, class_hash) ;
754772 }
@@ -855,10 +873,10 @@ impl FuzzTestManager {
855873 FuzzOperationData :: Call ( call_operation_data) => {
856874 // It's possible that the address is no longer deployed (post-revert).
857875 let class_info_string =
858- match self . context . deployed_contracts . get ( call_operation_data. address ( ) ) {
876+ match self . try_class_hash_of ( call_operation_data. address ( ) ) {
859877 Some ( class_hash) => format ! (
860878 "Cairo{} address, class hash: {}" ,
861- if self . is_cairo1_class( class_hash) { "1" } else { "0" } ,
879+ if self . is_cairo1_class( & class_hash) { "1" } else { "0" } ,
862880 class_hash. 0 . to_hex_string( )
863881 ) ,
864882 None => "unknown class hash, deployment reverted" . to_string ( ) ,
0 commit comments