1use std::{
5 collections::BTreeMap,
6 path::{Path, PathBuf},
7};
8
9use crate::{TestCluster, TestClusterBuilder};
10use move_core_types::identifier::Identifier;
11use sui_keys::keystore::AccountKeystore;
12use sui_protocol_config::{OverrideGuard, ProtocolConfig, ProtocolVersion};
13use sui_test_transaction_builder::{FundSource, TestTransactionBuilder};
14use sui_types::{
15 SUI_FRAMEWORK_PACKAGE_ID, TypeTag,
16 accumulator_metadata::get_accumulator_object_count,
17 accumulator_root::{AccumulatorValue, U128},
18 balance::Balance,
19 base_types::{FullObjectRef, ObjectID, ObjectRef, SequenceNumber, SuiAddress},
20 coin_reservation::ParsedObjectRefWithdrawal,
21 digests::{ChainIdentifier, TransactionDigest},
22 effects::{TransactionEffects, TransactionEffectsAPI},
23 error::SuiResult,
24 gas_coin::GAS,
25 object::Owner,
26 programmable_transaction_builder::ProgrammableTransactionBuilder,
27 storage::ChildObjectResolver,
28 transaction::{
29 CallArg, FundsWithdrawalArg, GasData, ObjectArg, TransactionData, TransactionDataV1,
30 TransactionExpiration, TransactionKind,
31 },
32};
33
34pub struct TestEnvBuilder {
38 num_validators: usize,
39 test_cluster_builder_cb: Option<Box<dyn Fn(TestClusterBuilder) -> TestClusterBuilder + Send>>,
40 proto_override_cb:
41 Option<Box<dyn Fn(ProtocolVersion, ProtocolConfig) -> ProtocolConfig + Send>>,
42}
43
44impl Default for TestEnvBuilder {
45 fn default() -> Self {
46 Self::new()
47 }
48}
49
50impl TestEnvBuilder {
51 pub fn new() -> Self {
52 Self {
53 test_cluster_builder_cb: None,
54 proto_override_cb: None,
55 num_validators: 1,
56 }
57 }
58
59 #[allow(dead_code)]
60 pub fn with_num_validators(mut self, num_validators: usize) -> Self {
61 self.num_validators = num_validators;
62 self
63 }
64
65 pub fn with_proto_override_cb(
66 mut self,
67 cb: Box<dyn Fn(ProtocolVersion, ProtocolConfig) -> ProtocolConfig + Send>,
68 ) -> Self {
69 self.proto_override_cb = Some(cb);
70 self
71 }
72
73 pub fn with_test_cluster_builder_cb(
74 mut self,
75 cb: Box<dyn Fn(TestClusterBuilder) -> TestClusterBuilder + Send>,
76 ) -> Self {
77 self.test_cluster_builder_cb = Some(cb);
78 self
79 }
80
81 pub async fn build(self) -> TestEnv {
82 let _guard = self
83 .proto_override_cb
84 .map(ProtocolConfig::apply_overrides_for_testing);
85
86 let mut test_cluster_builder =
87 TestClusterBuilder::new().with_num_validators(self.num_validators);
88
89 if let Some(cb) = self.test_cluster_builder_cb {
90 test_cluster_builder = cb(test_cluster_builder);
91 }
92
93 let test_cluster = test_cluster_builder.build().await;
94
95 let chain_id = test_cluster.get_chain_identifier();
96 let rgp = test_cluster.get_reference_gas_price().await;
97
98 let mut test_env = TestEnv {
99 cluster: test_cluster,
100 _guard,
101 rgp,
102 chain_id,
103 gas_objects: BTreeMap::new(),
104 };
105
106 test_env.update_all_gas().await;
107 test_env
108 }
109}
110
111pub struct TestEnv {
112 pub cluster: TestCluster,
113 _guard: Option<OverrideGuard>,
114 pub rgp: u64,
115 pub chain_id: ChainIdentifier,
116 pub gas_objects: BTreeMap<SuiAddress, Vec<ObjectRef>>,
117}
118
119impl TestEnv {
120 pub async fn update_all_gas(&mut self) {
121 let addresses = self.cluster.wallet.config.keystore.addresses();
123 self.gas_objects.clear();
124
125 for address in addresses {
126 let gas: Vec<_> = self
127 .cluster
128 .wallet
129 .gas_objects(address)
130 .await
131 .unwrap()
132 .into_iter()
133 .map(|(_, obj)| obj.compute_object_reference())
134 .collect();
135
136 self.gas_objects.insert(address, gas);
137 }
138 }
139
140 pub async fn fund_one_address_balance(&mut self, address: SuiAddress, amount: u64) {
141 let gas = self.gas_objects[&address][0];
142 let tx = TestTransactionBuilder::new(address, gas, self.rgp)
143 .transfer_sui_to_address_balance(FundSource::coin(gas), vec![(amount, address)])
144 .build();
145 let (digest, effects) = self
146 .cluster
147 .sign_and_execute_transaction_directly(&tx)
148 .await
149 .unwrap();
150 self.gas_objects.get_mut(&address).unwrap()[0] = effects.gas_object().unwrap().0;
152 self.cluster.wait_for_tx_settlement(&[digest]).await;
153 }
154
155 #[allow(dead_code)]
156 pub async fn fund_all_address_balances(&mut self, amount: u64) {
157 let senders = self.gas_objects.keys().copied().collect::<Vec<_>>();
158 for sender in senders {
159 self.fund_one_address_balance(sender, amount).await;
160 }
161 }
162
163 pub fn get_sender(&self, index: usize) -> SuiAddress {
164 self.gas_objects.keys().copied().nth(index).unwrap()
165 }
166
167 pub fn get_sender_and_gas(&self, index: usize) -> (SuiAddress, ObjectRef) {
168 let sender = self.get_sender(index);
169 let gas = self.gas_objects[&sender][0];
170 (sender, gas)
171 }
172
173 pub fn get_sender_and_all_gas(&self, index: usize) -> (SuiAddress, Vec<ObjectRef>) {
174 let sender = self.get_sender(index);
175 let gas = self.gas_objects[&sender].clone();
176 (sender, gas)
177 }
178
179 pub fn get_all_senders(&self) -> Vec<SuiAddress> {
180 self.cluster.wallet.get_addresses()
181 }
182
183 pub fn get_gas_for_sender(&self, sender: SuiAddress) -> Vec<ObjectRef> {
184 self.gas_objects.get(&sender).unwrap().clone()
185 }
186
187 pub fn tx_builder(&self, sender: SuiAddress) -> TestTransactionBuilder {
188 let gas = self.gas_objects.get(&sender).unwrap()[0];
189 TestTransactionBuilder::new(sender, gas, self.rgp)
190 }
191
192 pub fn tx_builder_with_gas(
193 &self,
194 sender: SuiAddress,
195 gas: ObjectRef,
196 ) -> TestTransactionBuilder {
197 TestTransactionBuilder::new(sender, gas, self.rgp)
198 }
199
200 pub fn tx_builder_with_gas_objects(
201 &self,
202 sender: SuiAddress,
203 gas_objects: Vec<ObjectRef>,
204 ) -> TestTransactionBuilder {
205 TestTransactionBuilder::new_with_gas_objects(sender, gas_objects, self.rgp)
206 }
207
208 pub async fn exec_tx_directly(
209 &mut self,
210 tx: TransactionData,
211 ) -> SuiResult<(TransactionDigest, TransactionEffects)> {
212 let res = self
213 .cluster
214 .sign_and_execute_transaction_directly(&tx)
215 .await;
216 self.update_all_gas().await;
217 res
218 }
219
220 pub async fn setup_test_package(&mut self, path: impl AsRef<Path>) -> ObjectID {
221 let context = &mut self.cluster.wallet;
222 let (sender, gas_object) = context.get_one_gas_object().await.unwrap().unwrap();
223 let gas_price = context.get_reference_gas_price().await.unwrap();
224 let txn = context
225 .sign_transaction(
226 &TestTransactionBuilder::new(sender, gas_object, gas_price)
227 .publish_async(path.as_ref().to_path_buf())
228 .await
229 .build(),
230 )
231 .await;
232 let resp = context.execute_transaction_must_succeed(txn).await;
233 let package_ref = resp.get_new_package_obj().unwrap();
234 self.update_all_gas().await;
235 package_ref.0
236 }
237
238 pub async fn setup_custom_coin(&mut self) -> (SuiAddress, TypeTag) {
239 let (publisher, package_id, _) = self.publish_coins_package().await;
240 let coin_a_type: TypeTag = format!("{}::coin_a::COIN_A", package_id).parse().unwrap();
241 (publisher, coin_a_type)
242 }
243
244 pub async fn setup_mintable_coin(&mut self) -> (SuiAddress, ObjectID, TypeTag, ObjectRef) {
247 let (publisher, package_id, effects) = self.publish_coins_package().await;
248 let coin_type: TypeTag = format!("{}::mintable_coin::MINTABLE_COIN", package_id)
249 .parse()
250 .unwrap();
251 let treasury_cap_ref = self.find_created_treasury_cap(&effects).await;
252 (publisher, package_id, coin_type, treasury_cap_ref)
253 }
254
255 async fn publish_coins_package(&mut self) -> (SuiAddress, ObjectID, TransactionEffects) {
257 let mut path = std::path::PathBuf::from(env!("CARGO_MANIFEST_DIR"));
258 path.extend(["..", "sui-e2e-tests", "tests", "data", "coins"]);
259 let (publisher, gas) = self
260 .cluster
261 .wallet
262 .get_one_gas_object()
263 .await
264 .unwrap()
265 .unwrap();
266 let tx = TestTransactionBuilder::new(publisher, gas, self.rgp)
267 .publish_async(path)
268 .await
269 .build();
270 let (_, effects) = self.exec_tx_directly(tx).await.unwrap();
271 assert!(
272 effects.status().is_ok(),
273 "Publish failed: {:?}",
274 effects.status()
275 );
276 let package_id = self.find_created_package(&effects).await;
277 (publisher, package_id, effects)
278 }
279
280 async fn find_created_package(&self, effects: &TransactionEffects) -> ObjectID {
281 for (obj_ref, owner) in effects.created() {
282 if owner.is_immutable()
283 && let Some(obj) = self
284 .cluster
285 .get_object_from_fullnode_store(&obj_ref.0)
286 .await
287 && obj.is_package()
288 {
289 return obj_ref.0;
290 }
291 }
292 panic!("Package should exist among created objects");
293 }
294
295 async fn find_created_treasury_cap(&self, effects: &TransactionEffects) -> ObjectRef {
296 for (obj_ref, owner) in effects.created() {
297 if matches!(owner, Owner::AddressOwner(_))
298 && let Some(obj) = self
299 .cluster
300 .get_object_from_fullnode_store(&obj_ref.0)
301 .await
302 && obj
303 .data
304 .try_as_move()
305 .is_some_and(|m| m.type_().is_treasury_cap())
306 {
307 return obj_ref;
308 }
309 }
310 panic!("TreasuryCap should exist among created objects");
311 }
312
313 pub async fn mint_coin(
316 &mut self,
317 publisher: SuiAddress,
318 package_id: ObjectID,
319 treasury_cap_ref: ObjectRef,
320 amount: u64,
321 recipient: SuiAddress,
322 ) -> (ObjectRef, ObjectRef) {
323 let tx = self
324 .tx_builder(publisher)
325 .move_call(
326 package_id,
327 "mintable_coin",
328 "mint_and_transfer",
329 vec![
330 CallArg::Object(ObjectArg::ImmOrOwnedObject(treasury_cap_ref)),
331 CallArg::Pure(bcs::to_bytes(&amount).unwrap()),
332 CallArg::Pure(bcs::to_bytes(&recipient).unwrap()),
333 ],
334 )
335 .build();
336 let (_, effects) = self.exec_tx_directly(tx).await.unwrap();
337 assert!(
338 effects.status().is_ok(),
339 "Mint failed: {:?}",
340 effects.status()
341 );
342 let new_treasury_cap_ref = effects
343 .mutated()
344 .into_iter()
345 .find(|(obj_ref, _)| obj_ref.0 == treasury_cap_ref.0)
346 .unwrap()
347 .0;
348 let coin_ref = effects
349 .created()
350 .into_iter()
351 .find(|(_, owner)| matches!(owner, Owner::AddressOwner(addr) if *addr == recipient))
352 .unwrap()
353 .0;
354 (new_treasury_cap_ref, coin_ref)
355 }
356
357 pub fn encode_coin_reservation(
358 &self,
359 sender: SuiAddress,
360 epoch: u64,
361 amount: u64,
362 ) -> ObjectRef {
363 let accumulator_obj_id = get_sui_accumulator_object_id(sender);
364 ParsedObjectRefWithdrawal::new(accumulator_obj_id, epoch, amount)
365 .encode(SequenceNumber::new(), self.chain_id)
366 }
367
368 pub fn encode_coin_reservation_for_type(
369 &self,
370 sender: SuiAddress,
371 epoch: u64,
372 amount: u64,
373 coin_type: TypeTag,
374 ) -> ObjectRef {
375 let accumulator_obj_id = get_accumulator_object_id(sender, coin_type);
376 ParsedObjectRefWithdrawal::new(accumulator_obj_id, epoch, amount)
377 .encode(SequenceNumber::new(), self.chain_id)
378 }
379
380 pub async fn transfer_from_coin_to_address_balance(
382 &mut self,
383 sender: SuiAddress,
384 coin: ObjectRef,
385 amounts_and_recipients: Vec<(u64, SuiAddress)>,
386 ) -> SuiResult<(TransactionDigest, TransactionEffects)> {
387 let tx = self
388 .tx_builder(sender)
389 .transfer_sui_to_address_balance(FundSource::coin(coin), amounts_and_recipients)
390 .build();
391 let res = self.exec_tx_directly(tx).await;
392 self.update_all_gas().await;
393 res
394 }
395
396 pub async fn transfer_coin_to_address_balance(
398 &mut self,
399 sender: SuiAddress,
400 coin: ObjectRef,
401 recipient: SuiAddress,
402 ) -> SuiResult<(TransactionDigest, TransactionEffects)> {
403 let tx = self
404 .tx_builder(sender)
405 .transfer(FullObjectRef::from_fastpath_ref(coin), recipient)
406 .build();
407 let res = self.exec_tx_directly(tx).await;
408 self.update_all_gas().await;
409 res
410 }
411
412 pub fn verify_accumulator_exists(&self, owner: SuiAddress, expected_balance: u64) {
413 self.cluster.fullnode_handle.sui_node.with(|node| {
414 let state = node.state();
415 let child_object_resolver = state.get_child_object_resolver().as_ref();
416 verify_accumulator_exists(child_object_resolver, owner, expected_balance);
417 });
418 }
419
420 pub fn verify_accumulator_object_count(&self, expected_object_count: u64) {
422 self.cluster.fullnode_handle.sui_node.with(|node| {
423 let state = node.state();
424
425 let object_count = get_accumulator_object_count(state.get_object_store().as_ref())
426 .expect("read cannot fail")
427 .expect("accumulator object count should exist after settlement");
428 assert_eq!(object_count, expected_object_count);
429 });
430 }
431
432 pub fn get_sui_balance_ab(&self, owner: SuiAddress) -> u64 {
434 self.get_balance_ab(owner, GAS::type_tag())
435 }
436
437 pub async fn get_coin_balance(&self, object_id: ObjectID) -> u64 {
438 self.cluster
439 .get_object_from_fullnode_store(&object_id)
440 .await
441 .expect("coin object should exist")
442 .data
443 .try_as_move()
444 .expect("should be a Move object")
445 .get_coin_value_unsafe()
446 }
447
448 pub fn get_balance_ab(&self, owner: SuiAddress, coin_type: TypeTag) -> u64 {
450 let db_balance = self.cluster.fullnode_handle.sui_node.with({
451 let coin_type = coin_type.clone();
452 move |node| {
453 let state = node.state();
454 let child_object_resolver = state.get_child_object_resolver().as_ref();
455 get_balance(child_object_resolver, owner, coin_type)
456 }
457 });
458
459 let client = self.cluster.grpc_client();
460 tokio::task::spawn(async move {
462 match client
463 .get_balance(owner, &coin_type.to_canonical_string(true).parse().unwrap())
464 .await
465 {
466 Ok(rpc_balance) => {
467 assert_eq!(db_balance, rpc_balance.address_balance());
468 }
469 Err(e) => {
470 tracing::info!("Failed to verify balance via gRPC: {e}");
473 }
474 }
475 });
476
477 db_balance
478 }
479
480 pub async fn get_sui_balance(&self, owner: SuiAddress) -> u64 {
482 self.get_balance_for_coin_type(owner, GAS::type_tag()).await
483 }
484
485 pub async fn get_balance_for_coin_type(&self, owner: SuiAddress, coin_type: TypeTag) -> u64 {
487 let client = self.cluster.grpc_client();
488 let rpc_balance = client
489 .get_balance(owner, &coin_type.to_canonical_string(true).parse().unwrap())
490 .await
491 .unwrap();
492 rpc_balance.balance()
493 }
494
495 pub fn verify_accumulator_removed(&self, owner: SuiAddress) {
496 self.cluster.fullnode_handle.sui_node.with(|node| {
497 let state = node.state();
498 let child_object_resolver = state.get_child_object_resolver().as_ref();
499 let sui_coin_type = Balance::type_tag(GAS::type_tag());
500 assert!(
501 !AccumulatorValue::exists(child_object_resolver, None, owner, &sui_coin_type)
502 .unwrap(),
503 "Accumulator value should have been removed"
504 );
505 });
506 }
507
508 pub async fn trigger_reconfiguration(&self) {
509 self.cluster.trigger_reconfiguration().await;
510 }
511
512 pub fn create_gasless_transaction(
513 &self,
514 amount: u64,
515 token_type: TypeTag,
516 sender: SuiAddress,
517 recipient: SuiAddress,
518 nonce: u32,
519 epoch: u64,
520 ) -> TransactionData {
521 let mut builder = ProgrammableTransactionBuilder::new();
522 let withdraw_arg = FundsWithdrawalArg::balance_from_sender(amount, token_type.clone());
523 let withdraw_arg = builder.funds_withdrawal(withdraw_arg).unwrap();
524 let balance = builder.programmable_move_call(
525 SUI_FRAMEWORK_PACKAGE_ID,
526 Identifier::new("balance").unwrap(),
527 Identifier::new("redeem_funds").unwrap(),
528 vec![token_type.clone()],
529 vec![withdraw_arg],
530 );
531 let recipient_arg = builder.pure(recipient).unwrap();
532 builder.programmable_move_call(
533 SUI_FRAMEWORK_PACKAGE_ID,
534 Identifier::new("balance").unwrap(),
535 Identifier::new("send_funds").unwrap(),
536 vec![token_type],
537 vec![balance, recipient_arg],
538 );
539 let tx_kind = TransactionKind::ProgrammableTransaction(builder.finish());
540 self.gasless_transaction_data(tx_kind, sender, nonce, epoch)
541 }
542
543 pub fn gasless_transaction_data(
544 &self,
545 tx_kind: TransactionKind,
546 sender: SuiAddress,
547 nonce: u32,
548 epoch: u64,
549 ) -> TransactionData {
550 TransactionData::V1(TransactionDataV1 {
551 kind: tx_kind,
552 sender,
553 gas_data: GasData {
554 payment: vec![],
555 owner: sender,
556 price: 0,
557 budget: 0,
558 },
559 expiration: TransactionExpiration::ValidDuring {
560 min_epoch: Some(epoch),
561 max_epoch: Some(epoch),
562 min_timestamp: None,
563 max_timestamp: None,
564 chain: self.chain_id,
565 nonce,
566 },
567 })
568 }
569
570 pub async fn setup_funded_object_balance_vault(&mut self, amount: u64) -> (ObjectID, ObjectID) {
573 let sender = self.get_sender(0);
574
575 let tx = self
576 .tx_builder(sender)
577 .publish_examples("object_balance")
578 .await
579 .build();
580 let (_, effects) = self.exec_tx_directly(tx).await.unwrap();
581 let package_id = effects
582 .created()
583 .into_iter()
584 .find(|(_, owner)| owner.is_immutable())
585 .unwrap()
586 .0
587 .0;
588
589 let tx = self
590 .tx_builder(sender)
591 .move_call(package_id, "object_balance", "new_owned", vec![])
592 .build();
593 let (_, effects) = self.exec_tx_directly(tx).await.unwrap();
594 let vault_id = effects.created().into_iter().next().unwrap().0.0;
595
596 let tx = self
597 .tx_builder(sender)
598 .transfer_sui_to_address_balance(
599 FundSource::coin(self.get_sender_and_gas(0).1),
600 vec![(amount, vault_id.into())],
601 )
602 .build();
603 self.exec_tx_directly(tx).await.unwrap();
604 self.trigger_reconfiguration().await;
605
606 (package_id, vault_id)
607 }
608
609 pub async fn publish_trusted_coin(
611 &mut self,
612 sender: SuiAddress,
613 ) -> (ObjectID, TypeTag, ObjectRef) {
614 let mut path = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
615 path.pop();
616 path.extend(["sui-e2e-tests", "tests", "rpc", "data", "trusted_coin"]);
617
618 let tx = self.tx_builder(sender).publish_async(path).await.build();
619 let (_, effects) = self.exec_tx_directly(tx).await.unwrap();
620
621 let package_id = effects.published_packages().into_iter().next().unwrap();
622 let coin_type: TypeTag = format!("{}::trusted_coin::TRUSTED_COIN", package_id)
623 .parse()
624 .unwrap();
625
626 let mut treasury_cap = None;
628 for (obj_ref, owner) in effects.created() {
629 if owner.is_address_owned() {
630 let object = self
631 .cluster
632 .fullnode_handle
633 .sui_node
634 .with_async(
635 |node| async move { node.state().get_object(&obj_ref.0).await.unwrap() },
636 )
637 .await;
638 if object.type_().unwrap().name().as_str() == "TreasuryCap" {
639 treasury_cap = Some(obj_ref);
640 break;
641 }
642 }
643 }
644
645 (
646 package_id,
647 coin_type,
648 treasury_cap.expect("Treasury cap not found"),
649 )
650 }
651
652 pub async fn mint_trusted_coin(
654 &mut self,
655 sender: SuiAddress,
656 package_id: ObjectID,
657 treasury_cap: ObjectRef,
658 amount: u64,
659 ) -> (ObjectRef, ObjectRef) {
660 let tx = self
661 .tx_builder(sender)
662 .move_call(
663 package_id,
664 "trusted_coin",
665 "mint",
666 vec![
667 CallArg::Object(ObjectArg::ImmOrOwnedObject(treasury_cap)),
668 CallArg::Pure(bcs::to_bytes(&amount).unwrap()),
669 ],
670 )
671 .build();
672 let (_, effects) = self.exec_tx_directly(tx).await.unwrap();
673
674 let coin_ref = effects
675 .created()
676 .iter()
677 .find(|(_, owner)| owner.is_address_owned())
678 .unwrap()
679 .0;
680
681 let new_treasury_cap = effects
682 .mutated()
683 .iter()
684 .find(|(obj_ref, _)| obj_ref.0 == treasury_cap.0)
685 .map(|(obj_ref, _)| *obj_ref)
686 .unwrap();
687
688 (coin_ref, new_treasury_cap)
689 }
690
691 pub async fn transfer_coin(
693 &mut self,
694 sender: SuiAddress,
695 coin: ObjectRef,
696 recipient: SuiAddress,
697 ) {
698 let tx = self
699 .tx_builder(sender)
700 .transfer(FullObjectRef::from_fastpath_ref(coin), recipient)
701 .build();
702 self.exec_tx_directly(tx).await.unwrap();
703 }
704
705 pub async fn transfer_sui(&mut self, sender: SuiAddress, recipient: SuiAddress, amount: u64) {
707 let tx = self
708 .tx_builder(sender)
709 .transfer_sui(Some(amount), recipient)
710 .build();
711 self.exec_tx_directly(tx).await.unwrap();
712 }
713
714 pub async fn transfer_sui_to_address_balance(
716 &mut self,
717 sender: SuiAddress,
718 recipient: SuiAddress,
719 amount: u64,
720 ) {
721 let gas = self.gas_objects[&sender][0];
722 let tx = TestTransactionBuilder::new(sender, gas, self.rgp)
723 .transfer_sui_to_address_balance(FundSource::coin(gas), vec![(amount, recipient)])
724 .build();
725 self.exec_tx_directly(tx).await.unwrap();
726 }
727
728 pub async fn publish_trusted_coin_and_setup(
730 &mut self,
731 funder: SuiAddress,
732 recipient: SuiAddress,
733 config: &CoinTypeConfig,
734 coin_amount: u64,
735 ) -> (ObjectID, TypeTag) {
736 let (package_id, coin_type, mut treasury_cap) = self.publish_trusted_coin(funder).await;
737
738 for _ in 0..config.real_coins {
739 let (coin, new_cap) = self
740 .mint_trusted_coin(funder, package_id, treasury_cap, coin_amount)
741 .await;
742 treasury_cap = new_cap;
743 self.transfer_coin(funder, coin, recipient).await;
744 }
745
746 if config.has_address_balance {
747 let (coin, _) = self
748 .mint_trusted_coin(funder, package_id, treasury_cap, coin_amount)
749 .await;
750 let tx = self
751 .tx_builder(funder)
752 .transfer_funds_to_address_balance(
753 FundSource::Coin(coin),
754 vec![(coin_amount, recipient)],
755 coin_type.clone(),
756 )
757 .build();
758 self.exec_tx_directly(tx).await.unwrap();
759 }
760
761 (package_id, coin_type)
762 }
763
764 pub async fn publish_and_mint_trusted_coin(
766 &mut self,
767 sender: SuiAddress,
768 amount: u64,
769 ) -> (ObjectID, TypeTag) {
770 let config = CoinTypeConfig {
771 real_coins: 1,
772 has_address_balance: true,
773 };
774 self.publish_trusted_coin_and_setup(sender, sender, &config, amount)
775 .await
776 }
777}
778
779#[derive(Clone, Debug)]
781pub struct CoinTypeConfig {
782 pub real_coins: usize,
784 pub has_address_balance: bool,
786}
787
788pub fn get_sui_accumulator_object_id(sender: SuiAddress) -> ObjectID {
789 get_accumulator_object_id(sender, GAS::type_tag())
790}
791
792pub fn get_accumulator_object_id(sender: SuiAddress, coin_type: TypeTag) -> ObjectID {
793 *AccumulatorValue::get_field_id(sender, &Balance::type_tag(coin_type))
794 .unwrap()
795 .inner()
796}
797
798pub fn get_balance(
799 child_object_resolver: &dyn ChildObjectResolver,
800 owner: SuiAddress,
801 coin_type: TypeTag,
802) -> u64 {
803 sui_core::accumulators::balances::get_balance(owner, child_object_resolver, coin_type).unwrap()
804}
805
806pub fn get_sui_balance(child_object_resolver: &dyn ChildObjectResolver, owner: SuiAddress) -> u64 {
807 get_balance(child_object_resolver, owner, GAS::type_tag())
808}
809
810pub fn verify_accumulator_exists(
811 child_object_resolver: &dyn ChildObjectResolver,
812 owner: SuiAddress,
813 expected_balance: u64,
814) {
815 let sui_coin_type = Balance::type_tag(GAS::type_tag());
816
817 assert!(
818 AccumulatorValue::exists(child_object_resolver, None, owner, &sui_coin_type).unwrap(),
819 "Accumulator value should have been created"
820 );
821
822 let accumulator_object =
823 AccumulatorValue::load_object(child_object_resolver, None, owner, &sui_coin_type)
824 .expect("read cannot fail")
825 .expect("accumulator should exist");
826
827 assert!(
828 accumulator_object
829 .data
830 .try_as_move()
831 .unwrap()
832 .type_()
833 .is_efficient_representation()
834 );
835
836 let accumulator_value =
837 AccumulatorValue::load(child_object_resolver, None, owner, &sui_coin_type)
838 .expect("read cannot fail")
839 .expect("accumulator should exist");
840
841 assert_eq!(
842 accumulator_value,
843 AccumulatorValue::U128(U128 {
844 value: expected_balance as u128
845 }),
846 "Accumulator value should be {expected_balance}"
847 );
848}