@@ -2301,3 +2301,143 @@ async fn test_soft_bundle_different_gas_payers() {
23012301
23022302 test_cluster. trigger_reconfiguration ( ) . await ;
23032303}
2304+
2305+ #[ sim_test]
2306+ async fn test_multiple_deposits_merged_in_effects ( ) {
2307+ let _guard = ProtocolConfig :: apply_overrides_for_testing ( |_, mut cfg| {
2308+ cfg. create_root_accumulator_object_for_testing ( ) ;
2309+ cfg. enable_accumulators_for_testing ( ) ;
2310+ cfg
2311+ } ) ;
2312+
2313+ let mut test_cluster = TestClusterBuilder :: new ( ) . build ( ) . await ;
2314+ let rgp = test_cluster. get_reference_gas_price ( ) . await ;
2315+ let context = & mut test_cluster. wallet ;
2316+
2317+ let ( sender, gas) = get_sender_and_gas ( context) . await ;
2318+ let recipient = sender;
2319+
2320+ let initial_deposit = make_send_to_account_tx ( 10000 , recipient, sender, gas, rgp) ;
2321+ let res = test_cluster
2322+ . sign_and_execute_transaction ( & initial_deposit)
2323+ . await ;
2324+ let gas = res. effects . unwrap ( ) . gas_object ( ) . reference . to_object_ref ( ) ;
2325+
2326+ test_cluster. fullnode_handle . sui_node . with ( |node| {
2327+ let state = node. state ( ) ;
2328+ let child_object_resolver = state. get_child_object_resolver ( ) . as_ref ( ) ;
2329+ verify_accumulator_exists ( child_object_resolver, recipient, 10000 ) ;
2330+ } ) ;
2331+
2332+ let mut builder = ProgrammableTransactionBuilder :: new ( ) ;
2333+
2334+ let deposit_amounts = vec ! [ 1000u64 , 2000u64 , 3000u64 ] ;
2335+ for amount in & deposit_amounts {
2336+ let amount_arg = builder. pure ( * amount) . unwrap ( ) ;
2337+ let recipient_arg = builder. pure ( recipient) . unwrap ( ) ;
2338+ let coin = builder. command ( Command :: SplitCoins ( Argument :: GasCoin , vec ! [ amount_arg] ) ) ;
2339+
2340+ let Argument :: Result ( coin_idx) = coin else {
2341+ panic ! ( "coin is not a result" ) ;
2342+ } ;
2343+
2344+ let coin = Argument :: NestedResult ( coin_idx, 0 ) ;
2345+
2346+ builder. programmable_move_call (
2347+ SUI_FRAMEWORK_PACKAGE_ID ,
2348+ Identifier :: new ( "coin" ) . unwrap ( ) ,
2349+ Identifier :: new ( "send_funds" ) . unwrap ( ) ,
2350+ vec ! [ "0x2::sui::SUI" . parse( ) . unwrap( ) ] ,
2351+ vec ! [ coin, recipient_arg] ,
2352+ ) ;
2353+ }
2354+
2355+ let withdraw_amounts = vec ! [ 500u64 , 1500u64 ] ;
2356+ for amount in & withdraw_amounts {
2357+ let withdraw_arg = sui_types:: transaction:: FundsWithdrawalArg :: balance_from_sender (
2358+ * amount,
2359+ sui_types:: type_input:: TypeInput :: from ( sui_types:: gas_coin:: GAS :: type_tag ( ) ) ,
2360+ ) ;
2361+ let withdraw_arg = builder. funds_withdrawal ( withdraw_arg) . unwrap ( ) ;
2362+
2363+ let amount_arg = builder
2364+ . pure ( move_core_types:: u256:: U256 :: from ( * amount) )
2365+ . unwrap ( ) ;
2366+
2367+ let split_withdraw_arg = builder. programmable_move_call (
2368+ SUI_FRAMEWORK_PACKAGE_ID ,
2369+ Identifier :: new ( "funds_accumulator" ) . unwrap ( ) ,
2370+ Identifier :: new ( "withdrawal_split" ) . unwrap ( ) ,
2371+ vec ! [ "0x2::balance::Balance<0x2::sui::SUI>" . parse( ) . unwrap( ) ] ,
2372+ vec ! [ withdraw_arg, amount_arg] ,
2373+ ) ;
2374+
2375+ let coin = builder. programmable_move_call (
2376+ SUI_FRAMEWORK_PACKAGE_ID ,
2377+ Identifier :: new ( "coin" ) . unwrap ( ) ,
2378+ Identifier :: new ( "redeem_funds" ) . unwrap ( ) ,
2379+ vec ! [ "0x2::sui::SUI" . parse( ) . unwrap( ) ] ,
2380+ vec ! [ split_withdraw_arg] ,
2381+ ) ;
2382+
2383+ builder. transfer_arg ( sender, coin) ;
2384+ }
2385+
2386+ let tx = TransactionKind :: ProgrammableTransaction ( builder. finish ( ) ) ;
2387+ let tx_data = TransactionData :: new ( tx, sender, gas, 10000000 , rgp) ;
2388+
2389+ let signed_tx = test_cluster. wallet . sign_transaction ( & tx_data) . await ;
2390+ let ( effects, _) = test_cluster
2391+ . execute_transaction_return_raw_effects ( signed_tx)
2392+ . await
2393+ . unwrap ( ) ;
2394+
2395+ assert ! (
2396+ effects. status( ) . is_ok( ) ,
2397+ "Transaction should succeed, got: {:?}" ,
2398+ effects. status( )
2399+ ) ;
2400+
2401+ let acc_events = effects. accumulator_events ( ) ;
2402+ assert_eq ! (
2403+ acc_events. len( ) ,
2404+ 1 ,
2405+ "Should have exactly 1 accumulator event (merged), got: {}" ,
2406+ acc_events. len( )
2407+ ) ;
2408+
2409+ let event = & acc_events[ 0 ] ;
2410+ assert_eq ! (
2411+ event. write. address. address, recipient,
2412+ "Accumulator event should be for the recipient"
2413+ ) ;
2414+
2415+ let deposit_total: u64 = deposit_amounts. iter ( ) . sum ( ) ;
2416+ let withdraw_total: u64 = withdraw_amounts. iter ( ) . sum ( ) ;
2417+ let expected_net = deposit_total - withdraw_total;
2418+
2419+ match & event. write . value {
2420+ sui_types:: effects:: AccumulatorValue :: Integer ( value) => {
2421+ assert_eq ! (
2422+ * value, expected_net,
2423+ "Merged accumulator value should be {} (deposits {} - withdrawals {}), got {}" ,
2424+ expected_net, deposit_total, withdraw_total, value
2425+ ) ;
2426+ }
2427+ _ => panic ! ( "Expected Integer accumulator value" ) ,
2428+ }
2429+
2430+ match & event. write . operation {
2431+ sui_types:: effects:: AccumulatorOperation :: Merge => { }
2432+ _ => panic ! ( "Expected Merge operation since deposits > withdrawals" ) ,
2433+ }
2434+
2435+ let expected_final_balance = 10000 + expected_net;
2436+ test_cluster. fullnode_handle . sui_node . with ( |node| {
2437+ let state = node. state ( ) ;
2438+ let child_object_resolver = state. get_child_object_resolver ( ) . as_ref ( ) ;
2439+ verify_accumulator_exists ( child_object_resolver, recipient, expected_final_balance) ;
2440+ } ) ;
2441+
2442+ test_cluster. trigger_reconfiguration ( ) . await ;
2443+ }
0 commit comments