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_emit_random(
307 self,
308 package_id: ObjectID,
309 randomness_initial_shared_version: SequenceNumber,
310 ) -> Self {
311 self.move_call(
312 package_id,
313 "random",
314 "new",
315 vec![CallArg::Object(ObjectArg::SharedObject {
316 id: SUI_RANDOMNESS_STATE_OBJECT_ID,
317 initial_shared_version: randomness_initial_shared_version,
318 mutability: SharedObjectMutability::Immutable,
319 })],
320 )
321 }
322
323 pub fn call_request_add_validator(self) -> Self {
324 self.move_call(
325 SUI_SYSTEM_PACKAGE_ID,
326 SUI_SYSTEM_MODULE_NAME.as_str(),
327 "request_add_validator",
328 vec![CallArg::SUI_SYSTEM_MUT],
329 )
330 }
331
332 pub fn call_request_add_validator_candidate(
333 self,
334 validator: &GenesisValidatorMetadata,
335 ) -> Self {
336 self.move_call(
337 SUI_SYSTEM_PACKAGE_ID,
338 SUI_SYSTEM_MODULE_NAME.as_str(),
339 "request_add_validator_candidate",
340 vec![
341 CallArg::SUI_SYSTEM_MUT,
342 CallArg::Pure(bcs::to_bytes(&validator.protocol_public_key).unwrap()),
343 CallArg::Pure(bcs::to_bytes(&validator.network_public_key).unwrap()),
344 CallArg::Pure(bcs::to_bytes(&validator.worker_public_key).unwrap()),
345 CallArg::Pure(bcs::to_bytes(&validator.proof_of_possession).unwrap()),
346 CallArg::Pure(bcs::to_bytes(validator.name.as_bytes()).unwrap()),
347 CallArg::Pure(bcs::to_bytes(validator.description.as_bytes()).unwrap()),
348 CallArg::Pure(bcs::to_bytes(validator.image_url.as_bytes()).unwrap()),
349 CallArg::Pure(bcs::to_bytes(validator.project_url.as_bytes()).unwrap()),
350 CallArg::Pure(bcs::to_bytes(&validator.network_address).unwrap()),
351 CallArg::Pure(bcs::to_bytes(&validator.p2p_address).unwrap()),
352 CallArg::Pure(bcs::to_bytes(&validator.primary_address).unwrap()),
353 CallArg::Pure(bcs::to_bytes(&validator.worker_address).unwrap()),
354 CallArg::Pure(bcs::to_bytes(&DEFAULT_VALIDATOR_GAS_PRICE).unwrap()), CallArg::Pure(bcs::to_bytes(&0u64).unwrap()), ],
357 )
358 }
359
360 pub fn call_request_remove_validator(self) -> Self {
361 self.move_call(
362 SUI_SYSTEM_PACKAGE_ID,
363 SUI_SYSTEM_MODULE_NAME.as_str(),
364 "request_remove_validator",
365 vec![CallArg::SUI_SYSTEM_MUT],
366 )
367 }
368
369 pub fn call_object_create_party(self, package_id: ObjectID) -> Self {
370 let sender = self.sender;
371 self.move_call(
372 package_id,
373 "object_basics",
374 "create_party",
375 vec![
376 CallArg::Pure(bcs::to_bytes(&1000u64).unwrap()),
377 CallArg::Pure(bcs::to_bytes(&sender).unwrap()),
378 ],
379 )
380 }
381
382 pub fn call_object_party_transfer_single_owner(
383 self,
384 package_id: ObjectID,
385 object_arg: ObjectArg,
386 recipient: SuiAddress,
387 ) -> Self {
388 self.move_call(
389 package_id,
390 "object_basics",
391 "party_transfer_single_owner",
392 vec![
393 CallArg::Object(object_arg),
394 CallArg::Pure(bcs::to_bytes(&recipient).unwrap()),
395 ],
396 )
397 }
398
399 pub fn call_object_delete(self, package_id: ObjectID, object_arg: ObjectArg) -> Self {
400 self.move_call(
401 package_id,
402 "object_basics",
403 "delete",
404 vec![CallArg::Object(object_arg)],
405 )
406 }
407
408 pub fn transfer(mut self, object: FullObjectRef, recipient: SuiAddress) -> Self {
409 self.ptb_builder.transfer_object(recipient, object).unwrap();
410 self
411 }
412
413 pub fn transfer_sui(mut self, amount: Option<u64>, recipient: SuiAddress) -> Self {
414 self.ptb_builder.transfer_sui(recipient, amount);
415 self
416 }
417
418 pub fn transfer_sui_to_address_balance(
419 self,
420 source: FundSource,
421 amounts_and_recipients: Vec<(u64, SuiAddress)>,
422 ) -> Self {
423 self.transfer_funds_to_address_balance(source, amounts_and_recipients, GAS::type_tag())
424 }
425
426 pub fn transfer_funds_to_address_balance(
427 mut self,
428 fund_source: FundSource,
429 amounts_and_recipients: Vec<(u64, SuiAddress)>,
430 type_arg: TypeTag,
431 ) -> Self {
432 fn send_funds(
433 builder: &mut ProgrammableTransactionBuilder,
434 balance: Argument,
435 recipient: SuiAddress,
436 type_arg: TypeTag,
437 ) {
438 let recipient_arg = builder.pure(recipient).unwrap();
439 builder.programmable_move_call(
440 SUI_FRAMEWORK_PACKAGE_ID,
441 Identifier::new("balance").unwrap(),
442 Identifier::new("send_funds").unwrap(),
443 vec![type_arg],
444 vec![balance, recipient_arg],
445 );
446 }
447
448 let total_amount = amounts_and_recipients
449 .iter()
450 .map(|(amount, _)| *amount)
451 .sum::<u64>();
452 match fund_source {
453 FundSource::Coin(coin) => {
454 let source = if self.gas_objects.first() == Some(&coin) {
455 Argument::GasCoin
456 } else {
457 self.ptb_builder
458 .obj(ObjectArg::ImmOrOwnedObject(coin))
459 .unwrap()
460 };
461 for (amount, recipient) in amounts_and_recipients {
462 let amount_arg = self.ptb_builder.pure(amount).unwrap();
463 let coin = self.ptb_builder.programmable_move_call(
464 SUI_FRAMEWORK_PACKAGE_ID,
465 Identifier::new("coin").unwrap(),
466 Identifier::new("split").unwrap(),
467 vec![type_arg.clone()],
468 vec![source, amount_arg],
469 );
470 let balance = self.ptb_builder.programmable_move_call(
471 SUI_FRAMEWORK_PACKAGE_ID,
472 Identifier::new("coin").unwrap(),
473 Identifier::new("into_balance").unwrap(),
474 vec![type_arg.clone()],
475 vec![coin],
476 );
477 send_funds(&mut self.ptb_builder, balance, recipient, type_arg.clone());
478 }
479 }
480 FundSource::AddressFund { reservation } => {
481 let reservation = reservation.unwrap_or(total_amount);
482 let source = self
483 .ptb_builder
484 .funds_withdrawal(FundsWithdrawalArg::balance_from_sender(
485 reservation,
486 type_arg.clone(),
487 ))
488 .unwrap();
489 for (amount, recipient) in amounts_and_recipients {
490 let amount_arg = self.ptb_builder.pure(U256::from(amount)).unwrap();
491 let split = self.ptb_builder.programmable_move_call(
492 SUI_FRAMEWORK_PACKAGE_ID,
493 Identifier::new("funds_accumulator").unwrap(),
494 Identifier::new("withdrawal_split").unwrap(),
495 vec![Balance::type_tag(type_arg.clone())],
496 vec![source, amount_arg],
497 );
498 let balance = self.ptb_builder.programmable_move_call(
499 SUI_FRAMEWORK_PACKAGE_ID,
500 Identifier::new("balance").unwrap(),
501 Identifier::new("redeem_funds").unwrap(),
502 vec![type_arg.clone()],
503 vec![split],
504 );
505 send_funds(&mut self.ptb_builder, balance, recipient, type_arg.clone());
506 }
507 }
508 FundSource::ObjectFund {
509 package_id,
510 withdraw_object,
511 } => {
512 let source = match withdraw_object {
513 ObjectFundObject::Owned(object) => self
514 .ptb_builder
515 .obj(ObjectArg::ImmOrOwnedObject(object))
516 .unwrap(),
517 ObjectFundObject::Shared(object_id, initial_shared_version) => self
518 .ptb_builder
519 .obj(ObjectArg::SharedObject {
520 id: object_id,
521 initial_shared_version,
522 mutability: SharedObjectMutability::Mutable,
523 })
524 .unwrap(),
525 };
526 for (amount, recipient) in amounts_and_recipients {
527 let amount_arg = self.ptb_builder.pure(amount).unwrap();
528 let balance = self.ptb_builder.programmable_move_call(
529 package_id,
530 Identifier::new("object_balance").unwrap(),
531 Identifier::new("withdraw_funds").unwrap(),
532 vec![type_arg.clone()],
533 vec![source, amount_arg],
534 );
535 send_funds(&mut self.ptb_builder, balance, recipient, type_arg.clone());
536 }
537 }
538 }
539
540 self
541 }
542
543 pub fn split_coin(mut self, coin: ObjectRef, amounts: Vec<u64>) -> Self {
544 self.ptb_builder.split_coin(self.sender, coin, amounts);
545 self
546 }
547
548 pub fn merge_coins(mut self, target: ObjectRef, coins: Vec<ObjectRef>) -> Self {
549 self.ptb_builder.merge_coins(target, coins).unwrap();
550 self
551 }
552
553 pub fn publish(self, path: PathBuf) -> Self {
554 if cfg!(msim) {
555 panic!("In simtests, you must use publish_async() instead");
556 }
557 self.publish_with_data(PublishData::Source(path, false))
558 }
559
560 pub async fn publish_async(self, path: PathBuf) -> Self {
561 self.publish_with_data_async(PublishData::Source(path, false))
562 .await
563 }
564
565 pub fn publish_with_deps(self, path: PathBuf) -> Self {
566 self.publish_with_data(PublishData::Source(path, true))
567 }
568
569 pub fn publish_with_data(mut self, data: PublishData) -> Self {
570 let (all_module_bytes, dependencies) = match data {
571 PublishData::Source(path, with_unpublished_deps) => {
572 let mut config = BuildConfig::new_for_testing();
573 config.config.set_unpublished_deps_to_zero = with_unpublished_deps;
574 let compiled_package = config.build(&path).unwrap();
575 let all_module_bytes = compiled_package.get_package_bytes(with_unpublished_deps);
576 let dependencies = compiled_package.get_dependency_storage_package_ids();
577 (all_module_bytes, dependencies)
578 }
579 PublishData::ModuleBytes(bytecode) => (bytecode, vec![]),
580 PublishData::CompiledPackage(compiled_package) => {
581 let all_module_bytes = compiled_package.get_package_bytes(false);
582 let dependencies = compiled_package.get_dependency_storage_package_ids();
583 (all_module_bytes, dependencies)
584 }
585 };
586
587 let upgrade_cap = self
588 .ptb_builder
589 .publish_upgradeable(all_module_bytes, dependencies);
590 self.ptb_builder.transfer_arg(self.sender, upgrade_cap);
591
592 self
593 }
594
595 pub async fn publish_with_data_async(mut self, data: PublishData) -> Self {
596 let (all_module_bytes, dependencies) = match data {
597 PublishData::Source(path, with_unpublished_deps) => {
598 let compiled_package = BuildConfig::new_for_testing()
599 .build_async(&path)
600 .await
601 .unwrap();
602 let all_module_bytes = compiled_package.get_package_bytes(with_unpublished_deps);
603 let dependencies = compiled_package.get_dependency_storage_package_ids();
604 (all_module_bytes, dependencies)
605 }
606 PublishData::ModuleBytes(bytecode) => (bytecode, vec![]),
607 PublishData::CompiledPackage(compiled_package) => {
608 let all_module_bytes = compiled_package.get_package_bytes(false);
609 let dependencies = compiled_package.get_dependency_storage_package_ids();
610 (all_module_bytes, dependencies)
611 }
612 };
613
614 let upgrade_cap = self
615 .ptb_builder
616 .publish_upgradeable(all_module_bytes, dependencies);
617 self.ptb_builder.transfer_arg(self.sender, upgrade_cap);
618
619 self
620 }
621
622 pub async fn publish_examples(self, subpath: &'static str) -> Self {
623 let path: PathBuf = if let Ok(p) = std::env::var("MOVE_EXAMPLES_DIR") {
624 let mut path = PathBuf::from(p);
625 path.extend([subpath]);
626 path
627 } else {
628 let mut path = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
629 path.extend(["..", "..", "examples", "move", subpath]);
630 path
631 };
632 self.publish_async(path).await
633 }
634
635 pub fn build(self) -> TransactionData {
636 let pt = self.ptb_builder.finish();
637 let gas_budget = self
638 .gas_budget
639 .unwrap_or(self.gas_price * TEST_ONLY_GAS_UNIT_FOR_HEAVY_COMPUTATION_STORAGE);
640
641 if let Some(ab_gas) = self.address_balance_gas {
642 TransactionData::new_programmable_with_address_balance_gas(
643 self.sender,
644 pt,
645 gas_budget,
646 self.gas_price,
647 ab_gas.chain_identifier,
648 ab_gas.current_epoch,
649 ab_gas.nonce,
650 )
651 } else {
652 assert!(
653 !self.gas_objects.is_empty(),
654 "gas_objects required when not using address_balance_gas"
655 );
656 TransactionData::new_programmable(
657 self.sender,
658 self.gas_objects,
659 pt,
660 gas_budget,
661 self.gas_price,
662 )
663 }
664 }
665
666 pub fn build_and_sign(self, signer: &dyn Signer<Signature>) -> Transaction {
667 Transaction::from_data_and_signer(self.build(), vec![signer])
668 }
669
670 pub fn build_and_sign_multisig(
671 self,
672 multisig_pk: MultiSigPublicKey,
673 signers: &[&dyn Signer<Signature>],
674 bitmap: BitmapUnit,
675 ) -> Transaction {
676 let data = self.build();
677 let intent_msg = IntentMessage::new(Intent::sui_transaction(), data.clone());
678
679 let mut signatures = Vec::with_capacity(signers.len());
680 for signer in signers {
681 signatures.push(
682 GenericSignature::from(Signature::new_secure(&intent_msg, *signer))
683 .to_compressed()
684 .unwrap(),
685 );
686 }
687
688 let multisig =
689 GenericSignature::MultiSig(MultiSig::insecure_new(signatures, bitmap, multisig_pk));
690
691 Transaction::from_generic_sig_data(data, vec![multisig])
692 }
693
694 pub fn build_and_sign_multisig_legacy(
695 self,
696 multisig_pk: MultiSigPublicKeyLegacy,
697 signers: &[&dyn Signer<Signature>],
698 ) -> Transaction {
699 let data = self.build();
700 let intent = Intent::sui_transaction();
701 let intent_msg = IntentMessage::new(intent.clone(), data.clone());
702
703 let mut signatures = Vec::with_capacity(signers.len());
704 for signer in signers {
705 signatures.push(Signature::new_secure(&intent_msg, *signer).into());
706 }
707
708 let multisig = GenericSignature::MultiSigLegacy(
709 MultiSigLegacy::combine(signatures, multisig_pk).unwrap(),
710 );
711
712 Transaction::from_generic_sig_data(data, vec![multisig])
713 }
714}
715
716#[allow(clippy::large_enum_variant)]
717pub enum PublishData {
718 Source(PathBuf, bool),
721 ModuleBytes(Vec<Vec<u8>>),
722 CompiledPackage(CompiledPackage),
723}
724
725pub async fn batch_make_transfer_transactions(
737 context: &WalletContext,
738 max_txn_num: usize,
739) -> Vec<Transaction> {
740 let recipient = get_key_pair::<AccountKeyPair>().0;
741 let result = context.get_all_accounts_and_gas_objects().await;
742 let accounts_and_objs = result.unwrap();
743 let mut res = Vec::with_capacity(max_txn_num);
744
745 let gas_price = context.get_reference_gas_price().await.unwrap();
746 for (address, objs) in accounts_and_objs {
747 for obj in objs {
748 if res.len() >= max_txn_num {
749 return res;
750 }
751 let data = TransactionData::new_transfer_sui(
752 recipient,
753 address,
754 Some(2),
755 obj,
756 gas_price * TEST_ONLY_GAS_UNIT_FOR_TRANSFER,
757 gas_price,
758 );
759 let tx = context.sign_transaction(&data).await;
760 res.push(tx);
761 }
762 }
763 res
764}
765
766pub async fn make_transfer_sui_transaction(
767 context: &WalletContext,
768 recipient: Option<SuiAddress>,
769 amount: Option<u64>,
770) -> Transaction {
771 let (sender, gas_object) = context.get_one_gas_object().await.unwrap().unwrap();
772 let gas_price = context.get_reference_gas_price().await.unwrap();
773 context
774 .sign_transaction(
775 &TestTransactionBuilder::new(sender, gas_object, gas_price)
776 .transfer_sui(amount, recipient.unwrap_or(sender))
777 .build(),
778 )
779 .await
780}
781
782pub async fn make_transfer_sui_address_balance_transaction(
783 context: &WalletContext,
784 recipient: Option<SuiAddress>,
785 amount: u64,
786) -> Transaction {
787 let (sender, gas_object) = context.get_one_gas_object().await.unwrap().unwrap();
788 let gas_price = context.get_reference_gas_price().await.unwrap();
789 context
790 .sign_transaction(
791 &TestTransactionBuilder::new(sender, gas_object, gas_price)
792 .transfer_sui_to_address_balance(
793 FundSource::Coin(gas_object),
794 vec![(amount, recipient.unwrap_or(sender))],
795 )
796 .build(),
797 )
798 .await
799}
800
801pub async fn make_staking_transaction(
802 context: &WalletContext,
803 validator_address: SuiAddress,
804) -> Transaction {
805 let accounts_and_objs = context.get_all_accounts_and_gas_objects().await.unwrap();
806 let sender = accounts_and_objs[0].0;
807 let gas_object = accounts_and_objs[0].1[0];
808 let stake_object = accounts_and_objs[0].1[1];
809 let gas_price = context.get_reference_gas_price().await.unwrap();
810 context
811 .sign_transaction(
812 &TestTransactionBuilder::new(sender, gas_object, gas_price)
813 .call_staking(stake_object, validator_address)
814 .build(),
815 )
816 .await
817}
818
819pub async fn make_publish_transaction(context: &WalletContext, path: PathBuf) -> Transaction {
820 let (sender, gas_object) = context.get_one_gas_object().await.unwrap().unwrap();
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 .publish_async(path)
826 .await
827 .build(),
828 )
829 .await
830}
831
832pub async fn make_publish_transaction_with_deps(
833 context: &WalletContext,
834 path: PathBuf,
835) -> Transaction {
836 let (sender, gas_object) = context.get_one_gas_object().await.unwrap().unwrap();
837 let gas_price = context.get_reference_gas_price().await.unwrap();
838 context
839 .sign_transaction(
840 &TestTransactionBuilder::new(sender, gas_object, gas_price)
841 .publish_with_deps(path)
842 .build(),
843 )
844 .await
845}
846
847pub async fn publish_package(context: &WalletContext, path: PathBuf) -> ObjectRef {
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 let txn = context
851 .sign_transaction(
852 &TestTransactionBuilder::new(sender, gas_object, gas_price)
853 .publish_async(path)
854 .await
855 .build(),
856 )
857 .await;
858 let resp = context.execute_transaction_must_succeed(txn).await;
859 resp.get_new_package_obj().unwrap()
860}
861
862pub async fn publish_basics_package(context: &WalletContext) -> ObjectRef {
864 let (sender, gas_object) = context.get_one_gas_object().await.unwrap().unwrap();
865 let gas_price = context.get_reference_gas_price().await.unwrap();
866 let txn = context
867 .sign_transaction(
868 &TestTransactionBuilder::new(sender, gas_object, gas_price)
869 .publish_examples("basics")
870 .await
871 .build(),
872 )
873 .await;
874 let resp = context.execute_transaction_must_succeed(txn).await;
875 resp.get_new_package_obj().unwrap()
876}
877
878pub async fn publish_basics_package_and_make_counter(
881 context: &WalletContext,
882) -> (ObjectRef, ObjectRef) {
883 let package_ref = publish_basics_package(context).await;
884 let (sender, gas_object) = context.get_one_gas_object().await.unwrap().unwrap();
885 let gas_price = context.get_reference_gas_price().await.unwrap();
886 let counter_creation_txn = context
887 .sign_transaction(
888 &TestTransactionBuilder::new(sender, gas_object, gas_price)
889 .call_counter_create(package_ref.0)
890 .build(),
891 )
892 .await;
893 let resp = context
894 .execute_transaction_must_succeed(counter_creation_txn)
895 .await;
896 let counter_ref = resp
897 .effects
898 .created()
899 .iter()
900 .find(|obj_ref| matches!(obj_ref.1, Owner::Shared { .. }))
901 .unwrap()
902 .0;
903 (package_ref, counter_ref)
904}
905
906pub async fn publish_basics_package_and_make_party_object(
909 context: &WalletContext,
910) -> (ObjectRef, ObjectRef) {
911 let package_ref = publish_basics_package(context).await;
912 let (sender, gas_object) = context.get_one_gas_object().await.unwrap().unwrap();
913 let gas_price = context.get_reference_gas_price().await.unwrap();
914 let object_creation_txn = context
915 .sign_transaction(
916 &TestTransactionBuilder::new(sender, gas_object, gas_price)
917 .call_object_create_party(package_ref.0)
918 .build(),
919 )
920 .await;
921 let resp = context
922 .execute_transaction_must_succeed(object_creation_txn)
923 .await;
924 let object_ref = resp
925 .effects
926 .created()
927 .iter()
928 .find(|obj_ref| matches!(obj_ref.1, Owner::ConsensusAddressOwner { .. }))
929 .unwrap()
930 .0;
931 (package_ref, object_ref)
932}
933
934pub async fn increment_counter(
937 context: &WalletContext,
938 sender: SuiAddress,
939 gas_object_id: Option<ObjectID>,
940 package_id: ObjectID,
941 counter_id: ObjectID,
942 initial_shared_version: SequenceNumber,
943) -> ExecutedTransaction {
944 let gas_object = if let Some(gas_object_id) = gas_object_id {
945 context.get_object_ref(gas_object_id).await.unwrap()
946 } else {
947 context
948 .get_one_gas_object_owned_by_address(sender)
949 .await
950 .unwrap()
951 .unwrap()
952 };
953 let rgp = context.get_reference_gas_price().await.unwrap();
954 let txn = context
955 .sign_transaction(
956 &TestTransactionBuilder::new(sender, gas_object, rgp)
957 .call_counter_increment(package_id, counter_id, initial_shared_version)
958 .build(),
959 )
960 .await;
961 context.execute_transaction_must_succeed(txn).await
962}
963
964pub async fn emit_new_random_u128(
966 context: &WalletContext,
967 package_id: ObjectID,
968) -> ExecutedTransaction {
969 let (sender, gas_object) = context.get_one_gas_object().await.unwrap().unwrap();
970 let rgp = context.get_reference_gas_price().await.unwrap();
971
972 let mut client = context.grpc_client().unwrap();
973 let random_obj = client
974 .get_object(SUI_RANDOMNESS_STATE_OBJECT_ID)
975 .await
976 .unwrap();
977 let random_obj_owner = random_obj.owner().to_owned();
978
979 let Owner::Shared {
980 initial_shared_version,
981 } = random_obj_owner
982 else {
983 panic!("Expect Randomness to be shared object")
984 };
985 let random_call_arg = CallArg::Object(ObjectArg::SharedObject {
986 id: SUI_RANDOMNESS_STATE_OBJECT_ID,
987 initial_shared_version,
988 mutability: SharedObjectMutability::Immutable,
989 });
990
991 let txn = context
992 .sign_transaction(
993 &TestTransactionBuilder::new(sender, gas_object, rgp)
994 .move_call(package_id, "random", "new", vec![random_call_arg])
995 .build(),
996 )
997 .await;
998 context.execute_transaction_must_succeed(txn).await
999}
1000
1001pub async fn publish_nfts_package(
1004 context: &WalletContext,
1005) -> (ObjectID, ObjectID, TransactionDigest) {
1006 let (sender, gas_object) = context.get_one_gas_object().await.unwrap().unwrap();
1007 let gas_id = gas_object.0;
1008 let gas_price = context.get_reference_gas_price().await.unwrap();
1009 let txn = context
1010 .sign_transaction(
1011 &TestTransactionBuilder::new(sender, gas_object, gas_price)
1012 .publish_examples("nft")
1013 .await
1014 .build(),
1015 )
1016 .await;
1017 let resp = context.execute_transaction_must_succeed(txn).await;
1018 let package_id = resp.get_new_package_obj().unwrap().0;
1019 (package_id, gas_id, resp.transaction.digest())
1020}
1021
1022pub async fn create_nft(
1026 context: &WalletContext,
1027 package_id: ObjectID,
1028) -> (SuiAddress, ObjectID, TransactionDigest) {
1029 let (sender, gas_object) = context.get_one_gas_object().await.unwrap().unwrap();
1030 let rgp = context.get_reference_gas_price().await.unwrap();
1031
1032 let txn = context
1033 .sign_transaction(
1034 &TestTransactionBuilder::new(sender, gas_object, rgp)
1035 .call_nft_create(package_id)
1036 .build(),
1037 )
1038 .await;
1039 let resp = context.execute_transaction_must_succeed(txn).await;
1040
1041 let object_id = resp.effects.created().first().unwrap().0.0;
1042
1043 (sender, object_id, resp.transaction.digest())
1044}
1045
1046pub async fn delete_nft(
1048 context: &WalletContext,
1049 sender: SuiAddress,
1050 package_id: ObjectID,
1051 nft_to_delete: ObjectRef,
1052) -> ExecutedTransaction {
1053 let gas = context
1054 .get_one_gas_object_owned_by_address(sender)
1055 .await
1056 .unwrap()
1057 .unwrap_or_else(|| panic!("Expect {sender} to have at least one gas object"));
1058 let rgp = context.get_reference_gas_price().await.unwrap();
1059 let txn = context
1060 .sign_transaction(
1061 &TestTransactionBuilder::new(sender, gas, rgp)
1062 .call_nft_delete(package_id, nft_to_delete)
1063 .build(),
1064 )
1065 .await;
1066 context.execute_transaction_must_succeed(txn).await
1067}