@@ -10,6 +10,7 @@ use pretty_assertions::assert_eq;
1010use rstest:: rstest;
1111use starknet_api:: abi:: abi_utils:: selector_from_name;
1212use starknet_api:: execution_utils:: format_panic_data;
13+ use starknet_api:: state:: StorageKey ;
1314use starknet_api:: transaction:: fields:: Calldata ;
1415use starknet_api:: { calldata as calldata_macro, felt} ;
1516use test_case:: test_case;
@@ -18,6 +19,7 @@ use crate::context::{BlockContext, ChainInfo};
1819use crate :: execution:: contract_class:: TrackedResource ;
1920use crate :: execution:: entry_point:: CallEntryPoint ;
2021use crate :: retdata;
22+ use crate :: state:: state_api:: StateReader ;
2123use crate :: test_utils:: initial_test_state:: test_state;
2224use crate :: test_utils:: syscall:: build_recurse_calldata;
2325use crate :: test_utils:: { trivial_external_entry_point_new, CompilerBasedVersion , BALANCE } ;
@@ -501,3 +503,55 @@ fn test_empty_function_flow(#[case] runnable: RunnableCairo1) {
501503 // Contract should not fail.
502504 assert ! ( !call_info. execution. failed) ;
503505}
506+
507+ /// Test that storage is correctly reverted when nested calls write to the same key.
508+ ///
509+ /// Scenario:
510+ /// - catch_write_revert_panic calls call_write_rewrite_panic and catches/ignores the revert
511+ /// - call_write_rewrite_panic calls write_1, then writes storage = 2, then panics
512+ /// - write_1 writes storage = 1
513+ ///
514+ /// After call_write_rewrite_panic's revert, storage should be 0 (original), not 1 (write_1's
515+ /// write). This tests the fix for a bug where call_write_rewrite_panic would capture write_1's
516+ /// written value (1) as the "original" instead of the true original (0), causing incorrect state
517+ /// after revert.
518+ #[ cfg_attr( feature = "cairo_native" , test_case( RunnableCairo1 :: Native ; "Native" ) ) ]
519+ #[ test_case( RunnableCairo1 :: Casm ; "VM" ) ]
520+ fn test_nested_call_storage_revert ( runnable_version : RunnableCairo1 ) {
521+ let test_contract = FeatureContract :: TestContract ( CairoVersion :: Cairo1 ( runnable_version) ) ;
522+ let chain_info = & ChainInfo :: create_for_testing ( ) ;
523+ let mut state = test_state ( chain_info, BALANCE , & [ ( test_contract, 1 ) ] ) ;
524+
525+ let contract_address = test_contract. get_instance_address ( 0 ) ;
526+ let storage_key = StorageKey :: try_from ( felt ! ( 123_u64 ) ) . unwrap ( ) ;
527+
528+ // Call catch_write_revert_panic which:
529+ // 1. Calls call_write_rewrite_panic
530+ // 2. call_write_rewrite_panic calls write_1
531+ // 3. write_1 writes storage = 1
532+ // 4. call_write_rewrite_panic writes storage = 2
533+ // 5. call_write_rewrite_panic panics (gets reverted)
534+ // 6. catch_write_revert_panic catches the revert and reads storage (should be 0)
535+ let entry_point_call = CallEntryPoint {
536+ entry_point_selector : selector_from_name ( "catch_write_revert_panic" ) ,
537+ calldata : calldata_macro ! [ * contract_address. 0 . key( ) , * storage_key. 0 . key( ) ] ,
538+ ..trivial_external_entry_point_new ( test_contract)
539+ } ;
540+
541+ let call_info = entry_point_call. execute_directly ( & mut state) . unwrap ( ) ;
542+
543+ // catch_write_revert_panic should succeed (it catches call_write_rewrite_panic's revert).
544+ assert ! ( !call_info. execution. failed, "catch_write_revert_panic should not fail" ) ;
545+
546+ // catch_write_revert_panic returns the storage value it read after call_write_rewrite_panic's
547+ // revert. This should be 0 (the original value), not 1 (write_1's write).
548+ assert_eq ! (
549+ call_info. execution. retdata. 0 ,
550+ vec![ felt!( 0_u8 ) ] ,
551+ "Storage should be reverted to 0, not write_1's value (1)"
552+ ) ;
553+
554+ // Double-check by reading storage directly.
555+ let final_value = state. get_storage_at ( contract_address, storage_key) . unwrap ( ) ;
556+ assert_eq ! ( final_value, felt!( 0_u8 ) , "Storage should be 0 after revert" ) ;
557+ }
0 commit comments