sui_cluster_test/test_case/
coin_merge_split_test.rs1use crate::{TestCaseImpl, TestContext, helper::ObjectChecker};
5use async_trait::async_trait;
6use sui_rpc_api::client::ExecutedTransaction;
7use sui_types::base_types::{ObjectID, SuiAddress};
8use sui_types::effects::TransactionEffectsAPI;
9use sui_types::object::Owner;
10use tracing::{debug, info};
11
12pub struct CoinMergeSplitTest;
13
14#[async_trait]
15impl TestCaseImpl for CoinMergeSplitTest {
16 fn name(&self) -> &'static str {
17 "CoinMergeSplit"
18 }
19
20 fn description(&self) -> &'static str {
21 "Test merge and split SUI coins"
22 }
23
24 async fn run(&self, ctx: &mut TestContext) -> Result<(), anyhow::Error> {
25 let mut sui_objs = ctx.get_sui_from_faucet(Some(1)).await;
26 let gas_obj = sui_objs.swap_remove(0);
27 let gas_obj_id = *gas_obj.id();
28
29 let signer = ctx.get_wallet_address();
30 let mut sui_objs_2 = ctx.get_sui_from_faucet(Some(1)).await;
31
32 let primary_coin = sui_objs_2.swap_remove(0);
33 let primary_coin_id = *primary_coin.id();
34 let original_value = primary_coin.value();
35
36 info!("Testing coin split.");
38 let amounts = vec![1, (original_value - 2) / 2];
39
40 let response = Self::split_coin(ctx, signer, primary_coin_id, amounts, gas_obj_id).await;
41 let new_coins: Vec<ObjectID> = response
44 .effects
45 .created()
46 .iter()
47 .map(|(obj_ref, _owner)| obj_ref.0)
48 .collect();
49 assert!(
50 !new_coins.is_empty(),
51 "Expected the split to create new coins"
52 );
53
54 for coin_id in &new_coins {
55 let _ = ObjectChecker::new(*coin_id)
56 .owner(Owner::AddressOwner(signer))
57 .check_into_gas_coin(&ctx.get_grpc_client())
58 .await;
59 }
60
61 info!("Testing coin merge.");
63 let mut coins_merged = Vec::new();
64 for coin_to_merge in &new_coins {
68 debug!(
69 "Merging coin {} back to {}.",
70 coin_to_merge, primary_coin_id
71 );
72 let response =
73 Self::merge_coin(ctx, signer, primary_coin_id, *coin_to_merge, gas_obj_id).await;
74 let deleted: Vec<ObjectID> = response.effects.deleted().iter().map(|r| r.0).collect();
76 assert!(
77 deleted.contains(coin_to_merge),
78 "Merged coin {coin_to_merge} should be deleted, deleted set: {deleted:?}",
79 );
80 coins_merged.push(*coin_to_merge);
81 }
82
83 debug!("Verifying owner still owns the primary coin {primary_coin_id}");
85 let primary_after_merge = ObjectChecker::new(primary_coin_id)
86 .owner(Owner::AddressOwner(signer))
87 .check_into_gas_coin(&ctx.get_grpc_client())
88 .await;
89 assert_eq!(
90 primary_after_merge.value(),
91 original_value,
92 "Split-then-merge yields unexpected coin value, expect {}, got {}",
93 original_value,
94 primary_after_merge.value(),
95 );
96 Ok(())
97 }
98}
99
100impl CoinMergeSplitTest {
101 async fn merge_coin(
102 ctx: &TestContext,
103 signer: SuiAddress,
104 primary_coin: ObjectID,
105 coin_to_merge: ObjectID,
106 gas_obj_id: ObjectID,
107 ) -> ExecutedTransaction {
108 let gas_ref = ctx.current_object_ref(gas_obj_id).await;
109 let builder = ctx.get_grpc_client().transaction_builder();
110 let data = builder
111 .merge_coins(
112 signer,
113 primary_coin,
114 coin_to_merge,
115 Some(gas_ref.0),
116 20_000_000,
117 )
118 .await
119 .unwrap();
120 ctx.sign_and_execute(data, "coin merge").await
121 }
122
123 async fn split_coin(
124 ctx: &TestContext,
125 signer: SuiAddress,
126 primary_coin: ObjectID,
127 amounts: Vec<u64>,
128 gas_obj_id: ObjectID,
129 ) -> ExecutedTransaction {
130 let gas_ref = ctx.current_object_ref(gas_obj_id).await;
131 let builder = ctx.get_grpc_client().transaction_builder();
132 let data = builder
133 .split_coin(signer, primary_coin, amounts, Some(gas_ref.0), 20_000_000)
134 .await
135 .unwrap();
136 ctx.sign_and_execute(data, "coin split").await
137 }
138}