@@ -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 return vec ! [ ] ;
576593 }
577594 vec ! [ FuzzOperationData :: ReplaceClass ( * CAIRO1_REPLACEMENT_CLASS_HASH ) ]
@@ -669,8 +686,8 @@ impl FuzzTestManager {
669686 } else {
670687 BTreeSet :: new( )
671688 } ,
672- class_replaced_and_original_class_hash : if root_call. class_replaced_here {
673- Some ( ( root_call. address , root_call . class_hash) )
689+ class_replaced : if root_call. class_replaced_here {
690+ Some ( root_call. class_hash)
674691 } else {
675692 None
676693 } ,
@@ -682,14 +699,17 @@ impl FuzzTestManager {
682699 pub fn apply_revert_info ( & mut self , revert_info : RevertInfo ) {
683700 // Revert class replacement. Do this before "undeploying" deployed contracts so we don't
684701 // "redeploy" anything when we only intend to revert the class hash change.
685- if let Some ( ( address, class_hash) ) = revert_info. class_replaced_and_original_class_hash {
686- self . context . deployed_contracts . insert ( address, class_hash) ;
702+ if let Some ( original_class_hash) = revert_info. class_replaced {
703+ let replaced_address = self . context . replaced_address . take ( ) . unwrap ( ) ;
704+ if self . context . newly_deployed_contracts . contains_key ( & replaced_address) {
705+ self . context . newly_deployed_contracts . insert ( replaced_address, original_class_hash) ;
706+ }
687707 }
688708 // "Undeploy" all deployed contracts.
689709 for address in revert_info. deployed_addresses . iter ( ) {
690710 // Remove without asserting that the address was actually deployed - the
691711 // constructor may have reverted before being finalized.
692- self . context . deployed_contracts . remove ( address) ;
712+ self . context . newly_deployed_contracts . remove ( address) ;
693713 }
694714 }
695715
@@ -714,7 +734,7 @@ impl FuzzTestManager {
714734 }
715735 FuzzOperationData :: Call ( call_operation_data) => {
716736 let address = * call_operation_data. address ( ) ;
717- let class_hash = * self . context . deployed_contracts . get ( & address) . unwrap ( ) ;
737+ let class_hash = self . class_hash_of ( & address) ;
718738 self . enter_call ( address, class_hash, call_operation_data. parent_failure_behavior ( ) ) ;
719739 }
720740 FuzzOperationData :: LibraryCall ( library_call_operation_data) => {
@@ -729,12 +749,10 @@ impl FuzzTestManager {
729749 self . context . next_storage_write_value . 0 += Felt :: ONE ;
730750 }
731751 FuzzOperationData :: ReplaceClass ( class_hash) => {
732- assert ! ( ! self . context. class_replaced ) ;
752+ assert ! ( self . context. replaced_address . is_none ( ) ) ;
733753 assert_eq ! ( class_hash, * CAIRO1_REPLACEMENT_CLASS_HASH ) ;
734- self . context . class_replaced = true ;
735- // Update the mapping from address to class hash, so subsequent calls to this
736- // address will correctly use the new class hash.
737- self . context . deployed_contracts . insert ( self . current_address ( ) , class_hash) ;
754+ let current_address = self . current_address ( ) ;
755+ self . context . replaced_address = Some ( current_address) ;
738756 // Update the current call to mark that it was replaced at this point, to make it
739757 // easy to track if the change must be reverted mid-test.
740758 self . current_fuzz_call_info_mut ( ) . class_replaced_here = true ;
@@ -747,7 +765,7 @@ impl FuzzTestManager {
747765 // Increment the salt for the next deploy operation.
748766 self . context . next_salt . 0 += Felt :: ONE ;
749767 // Update the mapping from address to class hash.
750- self . context . deployed_contracts . insert ( deployed_address, class_hash) ;
768+ self . context . newly_deployed_contracts . insert ( deployed_address, class_hash) ;
751769 // Enter constructor context.
752770 self . enter_deploy ( deployed_address, class_hash) ;
753771 }
@@ -854,10 +872,10 @@ impl FuzzTestManager {
854872 FuzzOperationData :: Call ( call_operation_data) => {
855873 // It's possible that the address is no longer deployed (post-revert).
856874 let class_info_string =
857- match self . context . deployed_contracts . get ( call_operation_data. address ( ) ) {
875+ match self . try_class_hash_of ( call_operation_data. address ( ) ) {
858876 Some ( class_hash) => format ! (
859877 "Cairo{} address, class hash: {}" ,
860- if self . is_cairo1_class( class_hash) { "1" } else { "0" } ,
878+ if self . is_cairo1_class( & class_hash) { "1" } else { "0" } ,
861879 class_hash. 0 . to_hex_string( )
862880 ) ,
863881 None => "unknown class hash, deployment reverted" . to_string ( ) ,
0 commit comments