1use move_core_types::ident_str;
5use move_core_types::u256::U256;
6use shared_crypto::intent::{Intent, IntentMessage};
7use std::path::PathBuf;
8use sui_genesis_builder::validator_info::GenesisValidatorMetadata;
9use sui_move_build::{BuildConfig, CompiledPackage};
10use sui_rpc_api::client::ExecutedTransaction;
11use sui_sdk::wallet_context::WalletContext;
12use sui_types::balance::Balance;
13use sui_types::base_types::{FullObjectRef, ObjectID, ObjectRef, SequenceNumber, SuiAddress};
14use sui_types::committee::EpochId;
15use sui_types::crypto::{AccountKeyPair, Signature, Signer, get_key_pair};
16use sui_types::digests::ChainIdentifier;
17use sui_types::digests::TransactionDigest;
18use sui_types::effects::TransactionEffectsAPI;
19use sui_types::gas_coin::GAS;
20use sui_types::multisig::{BitmapUnit, MultiSig, MultiSigPublicKey};
21use sui_types::multisig_legacy::{MultiSigLegacy, MultiSigPublicKeyLegacy};
22use sui_types::object::Owner;
23use sui_types::programmable_transaction_builder::ProgrammableTransactionBuilder;
24use sui_types::signature::GenericSignature;
25use sui_types::sui_system_state::SUI_SYSTEM_MODULE_NAME;
26use sui_types::transaction::{
27 Argument, CallArg, DEFAULT_VALIDATOR_GAS_PRICE, FundsWithdrawalArg, ObjectArg,
28 SharedObjectMutability, TEST_ONLY_GAS_UNIT_FOR_HEAVY_COMPUTATION_STORAGE,
29 TEST_ONLY_GAS_UNIT_FOR_TRANSFER, Transaction, TransactionData,
30};
31use sui_types::{Identifier, SUI_FRAMEWORK_PACKAGE_ID, SUI_RANDOMNESS_STATE_OBJECT_ID};
32use sui_types::{SUI_SYSTEM_PACKAGE_ID, TypeTag};
33
34#[derive(Clone)]
35pub enum FundSource {
36 Coin(ObjectRef),
37 AddressFund {
38 reservation: Option<u64>,
40 },
41 ObjectFund {
42 package_id: ObjectID,
45 withdraw_object: ObjectFundObject,
47 },
48}
49
50#[derive(Clone)]
51pub enum ObjectFundObject {
52 Owned(ObjectRef),
53 Shared(ObjectID, SequenceNumber ),
54}
55
56#[derive(Clone)]
58pub struct AddressBalanceGasConfig {
59 pub chain_identifier: ChainIdentifier,
60 pub current_epoch: EpochId,
61 pub nonce: u32,
62}
63
64impl FundSource {
65 pub fn coin(coin: ObjectRef) -> Self {
66 Self::Coin(coin)
67 }
68
69 pub fn address_fund() -> Self {
70 Self::AddressFund { reservation: None }
71 }
72
73 pub fn address_fund_with_reservation(reservation: u64) -> Self {
74 Self::AddressFund {
75 reservation: Some(reservation),
76 }
77 }
78
79 pub fn object_fund_owned(package_id: ObjectID, withdraw_object: ObjectRef) -> Self {
80 Self::ObjectFund {
81 package_id,
82 withdraw_object: ObjectFundObject::Owned(withdraw_object),
83 }
84 }
85
86 pub fn object_fund_shared(
87 package_id: ObjectID,
88 withdraw_object_id: ObjectID,
89 initial_shared_version: SequenceNumber,
90 ) -> Self {
91 Self::ObjectFund {
92 package_id,
93 withdraw_object: ObjectFundObject::Shared(withdraw_object_id, initial_shared_version),
94 }
95 }
96}
97
98pub struct TestTransactionBuilder {
99 ptb_builder: ProgrammableTransactionBuilder,
100 sender: SuiAddress,
101 gas_object: Option<ObjectRef>,
102 gas_price: u64,
103 gas_budget: Option<u64>,
104 address_balance_gas: Option<AddressBalanceGasConfig>,
105}
106
107impl TestTransactionBuilder {
108 pub fn new(sender: SuiAddress, gas_object: ObjectRef, gas_price: u64) -> Self {
109 Self::new_impl(sender, Some(gas_object), gas_price)
110 }
111
112 fn new_impl(sender: SuiAddress, gas_object: Option<ObjectRef>, gas_price: u64) -> Self {
113 Self {
114 ptb_builder: ProgrammableTransactionBuilder::new(),
115 sender,
116 gas_object,
117 gas_price,
118 gas_budget: None,
119 address_balance_gas: None,
120 }
121 }
122
123 pub fn new_with_address_balance_gas(
124 sender: SuiAddress,
125 gas_price: u64,
126 chain_identifier: ChainIdentifier,
127 current_epoch: EpochId,
128 nonce: u32,
129 ) -> Self {
130 Self::new_impl(sender, None, gas_price).with_address_balance_gas(
131 chain_identifier,
132 current_epoch,
133 nonce,
134 )
135 }
136
137 pub fn sender(&self) -> SuiAddress {
138 self.sender
139 }
140
141 pub fn gas_object(&self) -> Option<ObjectRef> {
142 self.gas_object
143 }
144
145 pub fn ptb_builder_mut(&mut self) -> &mut ProgrammableTransactionBuilder {
146 &mut self.ptb_builder
147 }
148
149 pub fn move_call(
150 self,
151 package_id: ObjectID,
152 module: &'static str,
153 function: &'static str,
154 args: Vec<CallArg>,
155 ) -> Self {
156 self.move_call_with_type_args(package_id, module, function, vec![], args)
157 }
158
159 pub fn move_call_with_type_args(
160 mut self,
161 package_id: ObjectID,
162 module: &'static str,
163 function: &'static str,
164 type_args: Vec<TypeTag>,
165 args: Vec<CallArg>,
166 ) -> Self {
167 self.ptb_builder
168 .move_call(
169 package_id,
170 ident_str!(module).to_owned(),
171 ident_str!(function).to_owned(),
172 type_args,
173 args,
174 )
175 .unwrap();
176
177 self
178 }
179
180 pub fn with_gas_budget(mut self, gas_budget: u64) -> Self {
181 self.gas_budget = Some(gas_budget);
182 self
183 }
184
185 pub fn with_address_balance_gas(
186 mut self,
187 chain_identifier: ChainIdentifier,
188 current_epoch: EpochId,
189 nonce: u32,
190 ) -> Self {
191 self.address_balance_gas = Some(AddressBalanceGasConfig {
192 chain_identifier,
193 current_epoch,
194 nonce,
195 });
196 self
197 }
198
199 pub fn call_counter_create(self, package_id: ObjectID) -> Self {
200 self.move_call(package_id, "counter", "create", vec![])
201 }
202
203 pub fn call_counter_increment(
204 self,
205 package_id: ObjectID,
206 counter_id: ObjectID,
207 counter_initial_shared_version: SequenceNumber,
208 ) -> Self {
209 self.move_call(
210 package_id,
211 "counter",
212 "increment",
213 vec![CallArg::Object(ObjectArg::SharedObject {
214 id: counter_id,
215 initial_shared_version: counter_initial_shared_version,
216 mutability: SharedObjectMutability::Mutable,
217 })],
218 )
219 }
220
221 pub fn call_counter_read(
222 self,
223 package_id: ObjectID,
224 counter_id: ObjectID,
225 counter_initial_shared_version: SequenceNumber,
226 ) -> Self {
227 self.move_call(
228 package_id,
229 "counter",
230 "value",
231 vec![CallArg::Object(ObjectArg::SharedObject {
232 id: counter_id,
233 initial_shared_version: counter_initial_shared_version,
234 mutability: SharedObjectMutability::Immutable,
235 })],
236 )
237 }
238
239 pub fn call_counter_delete(
240 self,
241 package_id: ObjectID,
242 counter_id: ObjectID,
243 counter_initial_shared_version: SequenceNumber,
244 ) -> Self {
245 self.move_call(
246 package_id,
247 "counter",
248 "delete",
249 vec![CallArg::Object(ObjectArg::SharedObject {
250 id: counter_id,
251 initial_shared_version: counter_initial_shared_version,
252 mutability: SharedObjectMutability::Mutable,
253 })],
254 )
255 }
256
257 pub fn call_nft_create(self, package_id: ObjectID) -> Self {
258 self.move_call(
259 package_id,
260 "testnet_nft",
261 "mint_to_sender",
262 vec![
263 CallArg::Pure(bcs::to_bytes("example_nft_name").unwrap()),
264 CallArg::Pure(bcs::to_bytes("example_nft_description").unwrap()),
265 CallArg::Pure(
266 bcs::to_bytes("https://sui.io/_nuxt/img/sui-logo.8d3c44e.svg").unwrap(),
267 ),
268 ],
269 )
270 }
271
272 pub fn call_nft_delete(self, package_id: ObjectID, nft_to_delete: ObjectRef) -> Self {
273 self.move_call(
274 package_id,
275 "testnet_nft",
276 "burn",
277 vec![CallArg::Object(ObjectArg::ImmOrOwnedObject(nft_to_delete))],
278 )
279 }
280
281 pub fn call_staking(self, stake_coin: ObjectRef, validator: SuiAddress) -> Self {
282 self.move_call(
283 SUI_SYSTEM_PACKAGE_ID,
284 SUI_SYSTEM_MODULE_NAME.as_str(),
285 "request_add_stake",
286 vec![
287 CallArg::SUI_SYSTEM_MUT,
288 CallArg::Object(ObjectArg::ImmOrOwnedObject(stake_coin)),
289 CallArg::Pure(bcs::to_bytes(&validator).unwrap()),
290 ],
291 )
292 }
293
294 pub fn call_emit_random(
295 self,
296 package_id: ObjectID,
297 randomness_initial_shared_version: SequenceNumber,
298 ) -> Self {
299 self.move_call(
300 package_id,
301 "random",
302 "new",
303 vec![CallArg::Object(ObjectArg::SharedObject {
304 id: SUI_RANDOMNESS_STATE_OBJECT_ID,
305 initial_shared_version: randomness_initial_shared_version,
306 mutability: SharedObjectMutability::Immutable,
307 })],
308 )
309 }
310
311 pub fn call_request_add_validator(self) -> Self {
312 self.move_call(
313 SUI_SYSTEM_PACKAGE_ID,
314 SUI_SYSTEM_MODULE_NAME.as_str(),
315 "request_add_validator",
316 vec![CallArg::SUI_SYSTEM_MUT],
317 )
318 }
319
320 pub fn call_request_add_validator_candidate(
321 self,
322 validator: &GenesisValidatorMetadata,
323 ) -> Self {
324 self.move_call(
325 SUI_SYSTEM_PACKAGE_ID,
326 SUI_SYSTEM_MODULE_NAME.as_str(),
327 "request_add_validator_candidate",
328 vec![
329 CallArg::SUI_SYSTEM_MUT,
330 CallArg::Pure(bcs::to_bytes(&validator.protocol_public_key).unwrap()),
331 CallArg::Pure(bcs::to_bytes(&validator.network_public_key).unwrap()),
332 CallArg::Pure(bcs::to_bytes(&validator.worker_public_key).unwrap()),
333 CallArg::Pure(bcs::to_bytes(&validator.proof_of_possession).unwrap()),
334 CallArg::Pure(bcs::to_bytes(validator.name.as_bytes()).unwrap()),
335 CallArg::Pure(bcs::to_bytes(validator.description.as_bytes()).unwrap()),
336 CallArg::Pure(bcs::to_bytes(validator.image_url.as_bytes()).unwrap()),
337 CallArg::Pure(bcs::to_bytes(validator.project_url.as_bytes()).unwrap()),
338 CallArg::Pure(bcs::to_bytes(&validator.network_address).unwrap()),
339 CallArg::Pure(bcs::to_bytes(&validator.p2p_address).unwrap()),
340 CallArg::Pure(bcs::to_bytes(&validator.primary_address).unwrap()),
341 CallArg::Pure(bcs::to_bytes(&validator.worker_address).unwrap()),
342 CallArg::Pure(bcs::to_bytes(&DEFAULT_VALIDATOR_GAS_PRICE).unwrap()), CallArg::Pure(bcs::to_bytes(&0u64).unwrap()), ],
345 )
346 }
347
348 pub fn call_request_remove_validator(self) -> Self {
349 self.move_call(
350 SUI_SYSTEM_PACKAGE_ID,
351 SUI_SYSTEM_MODULE_NAME.as_str(),
352 "request_remove_validator",
353 vec![CallArg::SUI_SYSTEM_MUT],
354 )
355 }
356
357 pub fn call_object_create_party(self, package_id: ObjectID) -> Self {
358 let sender = self.sender;
359 self.move_call(
360 package_id,
361 "object_basics",
362 "create_party",
363 vec![
364 CallArg::Pure(bcs::to_bytes(&1000u64).unwrap()),
365 CallArg::Pure(bcs::to_bytes(&sender).unwrap()),
366 ],
367 )
368 }
369
370 pub fn call_object_party_transfer_single_owner(
371 self,
372 package_id: ObjectID,
373 object_arg: ObjectArg,
374 recipient: SuiAddress,
375 ) -> Self {
376 self.move_call(
377 package_id,
378 "object_basics",
379 "party_transfer_single_owner",
380 vec![
381 CallArg::Object(object_arg),
382 CallArg::Pure(bcs::to_bytes(&recipient).unwrap()),
383 ],
384 )
385 }
386
387 pub fn call_object_delete(self, package_id: ObjectID, object_arg: ObjectArg) -> Self {
388 self.move_call(
389 package_id,
390 "object_basics",
391 "delete",
392 vec![CallArg::Object(object_arg)],
393 )
394 }
395
396 pub fn transfer(mut self, object: FullObjectRef, recipient: SuiAddress) -> Self {
397 self.ptb_builder.transfer_object(recipient, object).unwrap();
398 self
399 }
400
401 pub fn transfer_sui(mut self, amount: Option<u64>, recipient: SuiAddress) -> Self {
402 self.ptb_builder.transfer_sui(recipient, amount);
403 self
404 }
405
406 pub fn transfer_sui_to_address_balance(
407 self,
408 source: FundSource,
409 amounts_and_recipients: Vec<(u64, SuiAddress)>,
410 ) -> Self {
411 self.transfer_funds_to_address_balance(source, amounts_and_recipients, GAS::type_tag())
412 }
413
414 pub fn transfer_funds_to_address_balance(
415 mut self,
416 fund_source: FundSource,
417 amounts_and_recipients: Vec<(u64, SuiAddress)>,
418 type_arg: TypeTag,
419 ) -> Self {
420 fn send_funds(
421 builder: &mut ProgrammableTransactionBuilder,
422 balance: Argument,
423 recipient: SuiAddress,
424 type_arg: TypeTag,
425 ) {
426 let recipient_arg = builder.pure(recipient).unwrap();
427 builder.programmable_move_call(
428 SUI_FRAMEWORK_PACKAGE_ID,
429 Identifier::new("balance").unwrap(),
430 Identifier::new("send_funds").unwrap(),
431 vec![type_arg],
432 vec![balance, recipient_arg],
433 );
434 }
435
436 let total_amount = amounts_and_recipients
437 .iter()
438 .map(|(amount, _)| *amount)
439 .sum::<u64>();
440 match fund_source {
441 FundSource::Coin(coin) => {
442 let source = if Some(coin) == self.gas_object {
443 Argument::GasCoin
444 } else {
445 self.ptb_builder
446 .obj(ObjectArg::ImmOrOwnedObject(coin))
447 .unwrap()
448 };
449 for (amount, recipient) in amounts_and_recipients {
450 let amount_arg = self.ptb_builder.pure(amount).unwrap();
451 let coin = self.ptb_builder.programmable_move_call(
452 SUI_FRAMEWORK_PACKAGE_ID,
453 Identifier::new("coin").unwrap(),
454 Identifier::new("split").unwrap(),
455 vec![type_arg.clone()],
456 vec![source, amount_arg],
457 );
458 let balance = self.ptb_builder.programmable_move_call(
459 SUI_FRAMEWORK_PACKAGE_ID,
460 Identifier::new("coin").unwrap(),
461 Identifier::new("into_balance").unwrap(),
462 vec![type_arg.clone()],
463 vec![coin],
464 );
465 send_funds(&mut self.ptb_builder, balance, recipient, type_arg.clone());
466 }
467 }
468 FundSource::AddressFund { reservation } => {
469 let reservation = reservation.unwrap_or(total_amount);
470 let source = self
471 .ptb_builder
472 .funds_withdrawal(FundsWithdrawalArg::balance_from_sender(
473 reservation,
474 type_arg.clone(),
475 ))
476 .unwrap();
477 for (amount, recipient) in amounts_and_recipients {
478 let amount_arg = self.ptb_builder.pure(U256::from(amount)).unwrap();
479 let split = self.ptb_builder.programmable_move_call(
480 SUI_FRAMEWORK_PACKAGE_ID,
481 Identifier::new("funds_accumulator").unwrap(),
482 Identifier::new("withdrawal_split").unwrap(),
483 vec![Balance::type_tag(type_arg.clone())],
484 vec![source, amount_arg],
485 );
486 let balance = self.ptb_builder.programmable_move_call(
487 SUI_FRAMEWORK_PACKAGE_ID,
488 Identifier::new("balance").unwrap(),
489 Identifier::new("redeem_funds").unwrap(),
490 vec![type_arg.clone()],
491 vec![split],
492 );
493 send_funds(&mut self.ptb_builder, balance, recipient, type_arg.clone());
494 }
495 }
496 FundSource::ObjectFund {
497 package_id,
498 withdraw_object,
499 } => {
500 let source = match withdraw_object {
501 ObjectFundObject::Owned(object) => self
502 .ptb_builder
503 .obj(ObjectArg::ImmOrOwnedObject(object))
504 .unwrap(),
505 ObjectFundObject::Shared(object_id, initial_shared_version) => self
506 .ptb_builder
507 .obj(ObjectArg::SharedObject {
508 id: object_id,
509 initial_shared_version,
510 mutability: SharedObjectMutability::Mutable,
511 })
512 .unwrap(),
513 };
514 for (amount, recipient) in amounts_and_recipients {
515 let amount_arg = self.ptb_builder.pure(amount).unwrap();
516 let balance = self.ptb_builder.programmable_move_call(
517 package_id,
518 Identifier::new("object_balance").unwrap(),
519 Identifier::new("withdraw_funds").unwrap(),
520 vec![type_arg.clone()],
521 vec![source, amount_arg],
522 );
523 send_funds(&mut self.ptb_builder, balance, recipient, type_arg.clone());
524 }
525 }
526 }
527
528 self
529 }
530
531 pub fn split_coin(mut self, coin: ObjectRef, amounts: Vec<u64>) -> Self {
532 self.ptb_builder.split_coin(self.sender, coin, amounts);
533 self
534 }
535
536 pub fn publish(self, path: PathBuf) -> Self {
537 if cfg!(msim) {
538 panic!("In simtests, you must use publish_async() instead");
539 }
540 self.publish_with_data(PublishData::Source(path, false))
541 }
542
543 pub async fn publish_async(self, path: PathBuf) -> Self {
544 self.publish_with_data_async(PublishData::Source(path, false))
545 .await
546 }
547
548 pub fn publish_with_deps(self, path: PathBuf) -> Self {
549 self.publish_with_data(PublishData::Source(path, true))
550 }
551
552 pub fn publish_with_data(mut self, data: PublishData) -> Self {
553 let (all_module_bytes, dependencies) = match data {
554 PublishData::Source(path, with_unpublished_deps) => {
555 let mut config = BuildConfig::new_for_testing();
556 config.config.set_unpublished_deps_to_zero = with_unpublished_deps;
557 let compiled_package = config.build(&path).unwrap();
558 let all_module_bytes = compiled_package.get_package_bytes(with_unpublished_deps);
559 let dependencies = compiled_package.get_dependency_storage_package_ids();
560 (all_module_bytes, dependencies)
561 }
562 PublishData::ModuleBytes(bytecode) => (bytecode, vec![]),
563 PublishData::CompiledPackage(compiled_package) => {
564 let all_module_bytes = compiled_package.get_package_bytes(false);
565 let dependencies = compiled_package.get_dependency_storage_package_ids();
566 (all_module_bytes, dependencies)
567 }
568 };
569
570 let upgrade_cap = self
571 .ptb_builder
572 .publish_upgradeable(all_module_bytes, dependencies);
573 self.ptb_builder.transfer_arg(self.sender, upgrade_cap);
574
575 self
576 }
577
578 pub async fn publish_with_data_async(mut self, data: PublishData) -> Self {
579 let (all_module_bytes, dependencies) = match data {
580 PublishData::Source(path, with_unpublished_deps) => {
581 let compiled_package = BuildConfig::new_for_testing()
582 .build_async(&path)
583 .await
584 .unwrap();
585 let all_module_bytes = compiled_package.get_package_bytes(with_unpublished_deps);
586 let dependencies = compiled_package.get_dependency_storage_package_ids();
587 (all_module_bytes, dependencies)
588 }
589 PublishData::ModuleBytes(bytecode) => (bytecode, vec![]),
590 PublishData::CompiledPackage(compiled_package) => {
591 let all_module_bytes = compiled_package.get_package_bytes(false);
592 let dependencies = compiled_package.get_dependency_storage_package_ids();
593 (all_module_bytes, dependencies)
594 }
595 };
596
597 let upgrade_cap = self
598 .ptb_builder
599 .publish_upgradeable(all_module_bytes, dependencies);
600 self.ptb_builder.transfer_arg(self.sender, upgrade_cap);
601
602 self
603 }
604
605 pub async fn publish_examples(self, subpath: &'static str) -> Self {
606 let path: PathBuf = if let Ok(p) = std::env::var("MOVE_EXAMPLES_DIR") {
607 let mut path = PathBuf::from(p);
608 path.extend([subpath]);
609 path
610 } else {
611 let mut path = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
612 path.extend(["..", "..", "examples", "move", subpath]);
613 path
614 };
615 self.publish_async(path).await
616 }
617
618 pub fn build(self) -> TransactionData {
619 let pt = self.ptb_builder.finish();
620 let gas_budget = self
621 .gas_budget
622 .unwrap_or(self.gas_price * TEST_ONLY_GAS_UNIT_FOR_HEAVY_COMPUTATION_STORAGE);
623
624 if let Some(ab_gas) = self.address_balance_gas {
625 TransactionData::new_programmable_with_address_balance_gas(
626 self.sender,
627 pt,
628 gas_budget,
629 self.gas_price,
630 ab_gas.chain_identifier,
631 ab_gas.current_epoch,
632 ab_gas.nonce,
633 )
634 } else {
635 TransactionData::new_programmable(
636 self.sender,
637 vec![
638 self.gas_object
639 .expect("gas_object required when not using address_balance_gas"),
640 ],
641 pt,
642 gas_budget,
643 self.gas_price,
644 )
645 }
646 }
647
648 pub fn build_and_sign(self, signer: &dyn Signer<Signature>) -> Transaction {
649 Transaction::from_data_and_signer(self.build(), vec![signer])
650 }
651
652 pub fn build_and_sign_multisig(
653 self,
654 multisig_pk: MultiSigPublicKey,
655 signers: &[&dyn Signer<Signature>],
656 bitmap: BitmapUnit,
657 ) -> Transaction {
658 let data = self.build();
659 let intent_msg = IntentMessage::new(Intent::sui_transaction(), data.clone());
660
661 let mut signatures = Vec::with_capacity(signers.len());
662 for signer in signers {
663 signatures.push(
664 GenericSignature::from(Signature::new_secure(&intent_msg, *signer))
665 .to_compressed()
666 .unwrap(),
667 );
668 }
669
670 let multisig =
671 GenericSignature::MultiSig(MultiSig::insecure_new(signatures, bitmap, multisig_pk));
672
673 Transaction::from_generic_sig_data(data, vec![multisig])
674 }
675
676 pub fn build_and_sign_multisig_legacy(
677 self,
678 multisig_pk: MultiSigPublicKeyLegacy,
679 signers: &[&dyn Signer<Signature>],
680 ) -> Transaction {
681 let data = self.build();
682 let intent = Intent::sui_transaction();
683 let intent_msg = IntentMessage::new(intent.clone(), data.clone());
684
685 let mut signatures = Vec::with_capacity(signers.len());
686 for signer in signers {
687 signatures.push(Signature::new_secure(&intent_msg, *signer).into());
688 }
689
690 let multisig = GenericSignature::MultiSigLegacy(
691 MultiSigLegacy::combine(signatures, multisig_pk).unwrap(),
692 );
693
694 Transaction::from_generic_sig_data(data, vec![multisig])
695 }
696}
697
698#[allow(clippy::large_enum_variant)]
699pub enum PublishData {
700 Source(PathBuf, bool),
703 ModuleBytes(Vec<Vec<u8>>),
704 CompiledPackage(CompiledPackage),
705}
706
707pub async fn batch_make_transfer_transactions(
719 context: &WalletContext,
720 max_txn_num: usize,
721) -> Vec<Transaction> {
722 let recipient = get_key_pair::<AccountKeyPair>().0;
723 let result = context.get_all_accounts_and_gas_objects().await;
724 let accounts_and_objs = result.unwrap();
725 let mut res = Vec::with_capacity(max_txn_num);
726
727 let gas_price = context.get_reference_gas_price().await.unwrap();
728 for (address, objs) in accounts_and_objs {
729 for obj in objs {
730 if res.len() >= max_txn_num {
731 return res;
732 }
733 let data = TransactionData::new_transfer_sui(
734 recipient,
735 address,
736 Some(2),
737 obj,
738 gas_price * TEST_ONLY_GAS_UNIT_FOR_TRANSFER,
739 gas_price,
740 );
741 let tx = context.sign_transaction(&data).await;
742 res.push(tx);
743 }
744 }
745 res
746}
747
748pub async fn make_transfer_sui_transaction(
749 context: &WalletContext,
750 recipient: Option<SuiAddress>,
751 amount: Option<u64>,
752) -> Transaction {
753 let (sender, gas_object) = context.get_one_gas_object().await.unwrap().unwrap();
754 let gas_price = context.get_reference_gas_price().await.unwrap();
755 context
756 .sign_transaction(
757 &TestTransactionBuilder::new(sender, gas_object, gas_price)
758 .transfer_sui(amount, recipient.unwrap_or(sender))
759 .build(),
760 )
761 .await
762}
763
764pub async fn make_transfer_sui_address_balance_transaction(
765 context: &WalletContext,
766 recipient: Option<SuiAddress>,
767 amount: u64,
768) -> Transaction {
769 let (sender, gas_object) = context.get_one_gas_object().await.unwrap().unwrap();
770 let gas_price = context.get_reference_gas_price().await.unwrap();
771 context
772 .sign_transaction(
773 &TestTransactionBuilder::new(sender, gas_object, gas_price)
774 .transfer_sui_to_address_balance(
775 FundSource::Coin(gas_object),
776 vec![(amount, recipient.unwrap_or(sender))],
777 )
778 .build(),
779 )
780 .await
781}
782
783pub async fn make_staking_transaction(
784 context: &WalletContext,
785 validator_address: SuiAddress,
786) -> Transaction {
787 let accounts_and_objs = context.get_all_accounts_and_gas_objects().await.unwrap();
788 let sender = accounts_and_objs[0].0;
789 let gas_object = accounts_and_objs[0].1[0];
790 let stake_object = accounts_and_objs[0].1[1];
791 let gas_price = context.get_reference_gas_price().await.unwrap();
792 context
793 .sign_transaction(
794 &TestTransactionBuilder::new(sender, gas_object, gas_price)
795 .call_staking(stake_object, validator_address)
796 .build(),
797 )
798 .await
799}
800
801pub async fn make_publish_transaction(context: &WalletContext, path: PathBuf) -> Transaction {
802 let (sender, gas_object) = context.get_one_gas_object().await.unwrap().unwrap();
803 let gas_price = context.get_reference_gas_price().await.unwrap();
804 context
805 .sign_transaction(
806 &TestTransactionBuilder::new(sender, gas_object, gas_price)
807 .publish_async(path)
808 .await
809 .build(),
810 )
811 .await
812}
813
814pub async fn make_publish_transaction_with_deps(
815 context: &WalletContext,
816 path: PathBuf,
817) -> Transaction {
818 let (sender, gas_object) = context.get_one_gas_object().await.unwrap().unwrap();
819 let gas_price = context.get_reference_gas_price().await.unwrap();
820 context
821 .sign_transaction(
822 &TestTransactionBuilder::new(sender, gas_object, gas_price)
823 .publish_with_deps(path)
824 .build(),
825 )
826 .await
827}
828
829pub async fn publish_package(context: &WalletContext, path: PathBuf) -> ObjectRef {
830 let (sender, gas_object) = context.get_one_gas_object().await.unwrap().unwrap();
831 let gas_price = context.get_reference_gas_price().await.unwrap();
832 let txn = context
833 .sign_transaction(
834 &TestTransactionBuilder::new(sender, gas_object, gas_price)
835 .publish_async(path)
836 .await
837 .build(),
838 )
839 .await;
840 let resp = context.execute_transaction_must_succeed(txn).await;
841 resp.get_new_package_obj().unwrap()
842}
843
844pub async fn publish_basics_package(context: &WalletContext) -> ObjectRef {
846 let (sender, gas_object) = context.get_one_gas_object().await.unwrap().unwrap();
847 let gas_price = context.get_reference_gas_price().await.unwrap();
848 let txn = context
849 .sign_transaction(
850 &TestTransactionBuilder::new(sender, gas_object, gas_price)
851 .publish_examples("basics")
852 .await
853 .build(),
854 )
855 .await;
856 let resp = context.execute_transaction_must_succeed(txn).await;
857 resp.get_new_package_obj().unwrap()
858}
859
860pub async fn publish_basics_package_and_make_counter(
863 context: &WalletContext,
864) -> (ObjectRef, ObjectRef) {
865 let package_ref = publish_basics_package(context).await;
866 let (sender, gas_object) = context.get_one_gas_object().await.unwrap().unwrap();
867 let gas_price = context.get_reference_gas_price().await.unwrap();
868 let counter_creation_txn = context
869 .sign_transaction(
870 &TestTransactionBuilder::new(sender, gas_object, gas_price)
871 .call_counter_create(package_ref.0)
872 .build(),
873 )
874 .await;
875 let resp = context
876 .execute_transaction_must_succeed(counter_creation_txn)
877 .await;
878 let counter_ref = resp
879 .effects
880 .created()
881 .iter()
882 .find(|obj_ref| matches!(obj_ref.1, Owner::Shared { .. }))
883 .unwrap()
884 .0;
885 (package_ref, counter_ref)
886}
887
888pub async fn publish_basics_package_and_make_party_object(
891 context: &WalletContext,
892) -> (ObjectRef, ObjectRef) {
893 let package_ref = publish_basics_package(context).await;
894 let (sender, gas_object) = context.get_one_gas_object().await.unwrap().unwrap();
895 let gas_price = context.get_reference_gas_price().await.unwrap();
896 let object_creation_txn = context
897 .sign_transaction(
898 &TestTransactionBuilder::new(sender, gas_object, gas_price)
899 .call_object_create_party(package_ref.0)
900 .build(),
901 )
902 .await;
903 let resp = context
904 .execute_transaction_must_succeed(object_creation_txn)
905 .await;
906 let object_ref = resp
907 .effects
908 .created()
909 .iter()
910 .find(|obj_ref| matches!(obj_ref.1, Owner::ConsensusAddressOwner { .. }))
911 .unwrap()
912 .0;
913 (package_ref, object_ref)
914}
915
916pub async fn increment_counter(
919 context: &WalletContext,
920 sender: SuiAddress,
921 gas_object_id: Option<ObjectID>,
922 package_id: ObjectID,
923 counter_id: ObjectID,
924 initial_shared_version: SequenceNumber,
925) -> ExecutedTransaction {
926 let gas_object = if let Some(gas_object_id) = gas_object_id {
927 context.get_object_ref(gas_object_id).await.unwrap()
928 } else {
929 context
930 .get_one_gas_object_owned_by_address(sender)
931 .await
932 .unwrap()
933 .unwrap()
934 };
935 let rgp = context.get_reference_gas_price().await.unwrap();
936 let txn = context
937 .sign_transaction(
938 &TestTransactionBuilder::new(sender, gas_object, rgp)
939 .call_counter_increment(package_id, counter_id, initial_shared_version)
940 .build(),
941 )
942 .await;
943 context.execute_transaction_must_succeed(txn).await
944}
945
946pub async fn emit_new_random_u128(
948 context: &WalletContext,
949 package_id: ObjectID,
950) -> ExecutedTransaction {
951 let (sender, gas_object) = context.get_one_gas_object().await.unwrap().unwrap();
952 let rgp = context.get_reference_gas_price().await.unwrap();
953
954 let mut client = context.grpc_client().unwrap();
955 let random_obj = client
956 .get_object(SUI_RANDOMNESS_STATE_OBJECT_ID)
957 .await
958 .unwrap();
959 let random_obj_owner = random_obj.owner().to_owned();
960
961 let Owner::Shared {
962 initial_shared_version,
963 } = random_obj_owner
964 else {
965 panic!("Expect Randomness to be shared object")
966 };
967 let random_call_arg = CallArg::Object(ObjectArg::SharedObject {
968 id: SUI_RANDOMNESS_STATE_OBJECT_ID,
969 initial_shared_version,
970 mutability: SharedObjectMutability::Immutable,
971 });
972
973 let txn = context
974 .sign_transaction(
975 &TestTransactionBuilder::new(sender, gas_object, rgp)
976 .move_call(package_id, "random", "new", vec![random_call_arg])
977 .build(),
978 )
979 .await;
980 context.execute_transaction_must_succeed(txn).await
981}
982
983pub async fn publish_nfts_package(
986 context: &WalletContext,
987) -> (ObjectID, ObjectID, TransactionDigest) {
988 let (sender, gas_object) = context.get_one_gas_object().await.unwrap().unwrap();
989 let gas_id = gas_object.0;
990 let gas_price = context.get_reference_gas_price().await.unwrap();
991 let txn = context
992 .sign_transaction(
993 &TestTransactionBuilder::new(sender, gas_object, gas_price)
994 .publish_examples("nft")
995 .await
996 .build(),
997 )
998 .await;
999 let resp = context.execute_transaction_must_succeed(txn).await;
1000 let package_id = resp.get_new_package_obj().unwrap().0;
1001 (package_id, gas_id, resp.transaction.digest())
1002}
1003
1004pub async fn create_nft(
1008 context: &WalletContext,
1009 package_id: ObjectID,
1010) -> (SuiAddress, ObjectID, TransactionDigest) {
1011 let (sender, gas_object) = context.get_one_gas_object().await.unwrap().unwrap();
1012 let rgp = context.get_reference_gas_price().await.unwrap();
1013
1014 let txn = context
1015 .sign_transaction(
1016 &TestTransactionBuilder::new(sender, gas_object, rgp)
1017 .call_nft_create(package_id)
1018 .build(),
1019 )
1020 .await;
1021 let resp = context.execute_transaction_must_succeed(txn).await;
1022
1023 let object_id = resp.effects.created().first().unwrap().0.0;
1024
1025 (sender, object_id, resp.transaction.digest())
1026}
1027
1028pub async fn delete_nft(
1030 context: &WalletContext,
1031 sender: SuiAddress,
1032 package_id: ObjectID,
1033 nft_to_delete: ObjectRef,
1034) -> ExecutedTransaction {
1035 let gas = context
1036 .get_one_gas_object_owned_by_address(sender)
1037 .await
1038 .unwrap()
1039 .unwrap_or_else(|| panic!("Expect {sender} to have at least one gas object"));
1040 let rgp = context.get_reference_gas_price().await.unwrap();
1041 let txn = context
1042 .sign_transaction(
1043 &TestTransactionBuilder::new(sender, gas, rgp)
1044 .call_nft_delete(package_id, nft_to_delete)
1045 .build(),
1046 )
1047 .await;
1048 context.execute_transaction_must_succeed(txn).await
1049}