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