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