sui_test_transaction_builder/
lib.rs

1// Copyright (c) Mysten Labs, Inc.
2// SPDX-License-Identifier: Apache-2.0
3
4use move_core_types::ident_str;
5use shared_crypto::intent::{Intent, IntentMessage};
6use std::path::PathBuf;
7use sui_genesis_builder::validator_info::GenesisValidatorMetadata;
8use sui_move_build::{BuildConfig, CompiledPackage};
9use sui_sdk::rpc_types::{
10    SuiObjectDataOptions, SuiTransactionBlockEffectsAPI, SuiTransactionBlockResponse,
11};
12use sui_sdk::wallet_context::WalletContext;
13use sui_types::SUI_RANDOMNESS_STATE_OBJECT_ID;
14use sui_types::base_types::{FullObjectRef, ObjectID, ObjectRef, SequenceNumber, SuiAddress};
15use sui_types::crypto::{AccountKeyPair, Signature, Signer, get_key_pair};
16use sui_types::digests::TransactionDigest;
17use sui_types::multisig::{BitmapUnit, MultiSig, MultiSigPublicKey};
18use sui_types::multisig_legacy::{MultiSigLegacy, MultiSigPublicKeyLegacy};
19use sui_types::object::Owner;
20use sui_types::signature::GenericSignature;
21use sui_types::sui_system_state::SUI_SYSTEM_MODULE_NAME;
22use sui_types::transaction::{
23    CallArg, DEFAULT_VALIDATOR_GAS_PRICE, ObjectArg, ProgrammableTransaction,
24    SharedObjectMutability, TEST_ONLY_GAS_UNIT_FOR_HEAVY_COMPUTATION_STORAGE,
25    TEST_ONLY_GAS_UNIT_FOR_TRANSFER, Transaction, TransactionData,
26};
27use sui_types::{SUI_SYSTEM_PACKAGE_ID, TypeTag};
28
29pub struct TestTransactionBuilder {
30    test_data: TestTransactionData,
31    sender: SuiAddress,
32    gas_object: ObjectRef,
33    gas_price: u64,
34    gas_budget: Option<u64>,
35}
36
37impl TestTransactionBuilder {
38    pub fn new(sender: SuiAddress, gas_object: ObjectRef, gas_price: u64) -> Self {
39        Self {
40            test_data: TestTransactionData::Empty,
41            sender,
42            gas_object,
43            gas_price,
44            gas_budget: None,
45        }
46    }
47
48    pub fn sender(&self) -> SuiAddress {
49        self.sender
50    }
51
52    pub fn gas_object(&self) -> ObjectRef {
53        self.gas_object
54    }
55
56    // Use `with_type_args` below to provide type args if any
57    pub fn move_call(
58        mut self,
59        package_id: ObjectID,
60        module: &'static str,
61        function: &'static str,
62        args: Vec<CallArg>,
63    ) -> Self {
64        assert!(matches!(self.test_data, TestTransactionData::Empty));
65        self.test_data = TestTransactionData::Move(MoveData {
66            package_id,
67            module,
68            function,
69            args,
70            type_args: vec![],
71        });
72        self
73    }
74
75    pub fn with_type_args(mut self, type_args: Vec<TypeTag>) -> Self {
76        if let TestTransactionData::Move(data) = &mut self.test_data {
77            assert!(data.type_args.is_empty());
78            data.type_args = type_args;
79        } else {
80            panic!("Cannot set type args for non-move call");
81        }
82        self
83    }
84
85    pub fn with_gas_budget(mut self, gas_budget: u64) -> Self {
86        self.gas_budget = Some(gas_budget);
87        self
88    }
89
90    pub fn call_counter_create(self, package_id: ObjectID) -> Self {
91        self.move_call(package_id, "counter", "create", vec![])
92    }
93
94    pub fn call_counter_increment(
95        self,
96        package_id: ObjectID,
97        counter_id: ObjectID,
98        counter_initial_shared_version: SequenceNumber,
99    ) -> Self {
100        self.move_call(
101            package_id,
102            "counter",
103            "increment",
104            vec![CallArg::Object(ObjectArg::SharedObject {
105                id: counter_id,
106                initial_shared_version: counter_initial_shared_version,
107                mutability: SharedObjectMutability::Mutable,
108            })],
109        )
110    }
111
112    pub fn call_counter_read(
113        self,
114        package_id: ObjectID,
115        counter_id: ObjectID,
116        counter_initial_shared_version: SequenceNumber,
117    ) -> Self {
118        self.move_call(
119            package_id,
120            "counter",
121            "value",
122            vec![CallArg::Object(ObjectArg::SharedObject {
123                id: counter_id,
124                initial_shared_version: counter_initial_shared_version,
125                mutability: SharedObjectMutability::Immutable,
126            })],
127        )
128    }
129
130    pub fn call_counter_delete(
131        self,
132        package_id: ObjectID,
133        counter_id: ObjectID,
134        counter_initial_shared_version: SequenceNumber,
135    ) -> Self {
136        self.move_call(
137            package_id,
138            "counter",
139            "delete",
140            vec![CallArg::Object(ObjectArg::SharedObject {
141                id: counter_id,
142                initial_shared_version: counter_initial_shared_version,
143                mutability: SharedObjectMutability::Mutable,
144            })],
145        )
146    }
147
148    pub fn call_nft_create(self, package_id: ObjectID) -> Self {
149        self.move_call(
150            package_id,
151            "testnet_nft",
152            "mint_to_sender",
153            vec![
154                CallArg::Pure(bcs::to_bytes("example_nft_name").unwrap()),
155                CallArg::Pure(bcs::to_bytes("example_nft_description").unwrap()),
156                CallArg::Pure(
157                    bcs::to_bytes("https://sui.io/_nuxt/img/sui-logo.8d3c44e.svg").unwrap(),
158                ),
159            ],
160        )
161    }
162
163    pub fn call_nft_delete(self, package_id: ObjectID, nft_to_delete: ObjectRef) -> Self {
164        self.move_call(
165            package_id,
166            "testnet_nft",
167            "burn",
168            vec![CallArg::Object(ObjectArg::ImmOrOwnedObject(nft_to_delete))],
169        )
170    }
171
172    pub fn call_staking(self, stake_coin: ObjectRef, validator: SuiAddress) -> Self {
173        self.move_call(
174            SUI_SYSTEM_PACKAGE_ID,
175            SUI_SYSTEM_MODULE_NAME.as_str(),
176            "request_add_stake",
177            vec![
178                CallArg::SUI_SYSTEM_MUT,
179                CallArg::Object(ObjectArg::ImmOrOwnedObject(stake_coin)),
180                CallArg::Pure(bcs::to_bytes(&validator).unwrap()),
181            ],
182        )
183    }
184
185    pub fn call_emit_random(
186        self,
187        package_id: ObjectID,
188        randomness_initial_shared_version: SequenceNumber,
189    ) -> Self {
190        self.move_call(
191            package_id,
192            "random",
193            "new",
194            vec![CallArg::Object(ObjectArg::SharedObject {
195                id: SUI_RANDOMNESS_STATE_OBJECT_ID,
196                initial_shared_version: randomness_initial_shared_version,
197                mutability: SharedObjectMutability::Immutable,
198            })],
199        )
200    }
201
202    pub fn call_request_add_validator(self) -> Self {
203        self.move_call(
204            SUI_SYSTEM_PACKAGE_ID,
205            SUI_SYSTEM_MODULE_NAME.as_str(),
206            "request_add_validator",
207            vec![CallArg::SUI_SYSTEM_MUT],
208        )
209    }
210
211    pub fn call_request_add_validator_candidate(
212        self,
213        validator: &GenesisValidatorMetadata,
214    ) -> Self {
215        self.move_call(
216            SUI_SYSTEM_PACKAGE_ID,
217            SUI_SYSTEM_MODULE_NAME.as_str(),
218            "request_add_validator_candidate",
219            vec![
220                CallArg::SUI_SYSTEM_MUT,
221                CallArg::Pure(bcs::to_bytes(&validator.protocol_public_key).unwrap()),
222                CallArg::Pure(bcs::to_bytes(&validator.network_public_key).unwrap()),
223                CallArg::Pure(bcs::to_bytes(&validator.worker_public_key).unwrap()),
224                CallArg::Pure(bcs::to_bytes(&validator.proof_of_possession).unwrap()),
225                CallArg::Pure(bcs::to_bytes(validator.name.as_bytes()).unwrap()),
226                CallArg::Pure(bcs::to_bytes(validator.description.as_bytes()).unwrap()),
227                CallArg::Pure(bcs::to_bytes(validator.image_url.as_bytes()).unwrap()),
228                CallArg::Pure(bcs::to_bytes(validator.project_url.as_bytes()).unwrap()),
229                CallArg::Pure(bcs::to_bytes(&validator.network_address).unwrap()),
230                CallArg::Pure(bcs::to_bytes(&validator.p2p_address).unwrap()),
231                CallArg::Pure(bcs::to_bytes(&validator.primary_address).unwrap()),
232                CallArg::Pure(bcs::to_bytes(&validator.worker_address).unwrap()),
233                CallArg::Pure(bcs::to_bytes(&DEFAULT_VALIDATOR_GAS_PRICE).unwrap()), // gas_price
234                CallArg::Pure(bcs::to_bytes(&0u64).unwrap()), // commission_rate
235            ],
236        )
237    }
238
239    pub fn call_request_remove_validator(self) -> Self {
240        self.move_call(
241            SUI_SYSTEM_PACKAGE_ID,
242            SUI_SYSTEM_MODULE_NAME.as_str(),
243            "request_remove_validator",
244            vec![CallArg::SUI_SYSTEM_MUT],
245        )
246    }
247
248    pub fn call_object_create_party(self, package_id: ObjectID) -> Self {
249        let sender = self.sender;
250        self.move_call(
251            package_id,
252            "object_basics",
253            "create_party",
254            vec![
255                CallArg::Pure(bcs::to_bytes(&1000u64).unwrap()),
256                CallArg::Pure(bcs::to_bytes(&sender).unwrap()),
257            ],
258        )
259    }
260
261    pub fn call_object_party_transfer_single_owner(
262        self,
263        package_id: ObjectID,
264        object_arg: ObjectArg,
265        recipient: SuiAddress,
266    ) -> Self {
267        self.move_call(
268            package_id,
269            "object_basics",
270            "party_transfer_single_owner",
271            vec![
272                CallArg::Object(object_arg),
273                CallArg::Pure(bcs::to_bytes(&recipient).unwrap()),
274            ],
275        )
276    }
277
278    pub fn call_object_delete(self, package_id: ObjectID, object_arg: ObjectArg) -> Self {
279        self.move_call(
280            package_id,
281            "object_basics",
282            "delete",
283            vec![CallArg::Object(object_arg)],
284        )
285    }
286
287    pub fn transfer(mut self, object: FullObjectRef, recipient: SuiAddress) -> Self {
288        self.test_data = TestTransactionData::Transfer(TransferData { object, recipient });
289        self
290    }
291
292    pub fn transfer_sui(mut self, amount: Option<u64>, recipient: SuiAddress) -> Self {
293        self.test_data = TestTransactionData::TransferSui(TransferSuiData { amount, recipient });
294        self
295    }
296
297    pub fn split_coin(mut self, coin: ObjectRef, amounts: Vec<u64>) -> Self {
298        self.test_data = TestTransactionData::SplitCoin(SplitCoinData { coin, amounts });
299        self
300    }
301
302    pub fn publish(mut self, path: PathBuf) -> Self {
303        assert!(matches!(self.test_data, TestTransactionData::Empty));
304        self.test_data = TestTransactionData::Publish(PublishData::Source(path, false));
305        self
306    }
307
308    pub fn publish_with_deps(mut self, path: PathBuf) -> Self {
309        assert!(matches!(self.test_data, TestTransactionData::Empty));
310        self.test_data = TestTransactionData::Publish(PublishData::Source(path, true));
311        self
312    }
313
314    pub fn publish_with_data(mut self, data: PublishData) -> Self {
315        assert!(matches!(self.test_data, TestTransactionData::Empty));
316        self.test_data = TestTransactionData::Publish(data);
317        self
318    }
319
320    pub fn publish_examples(self, subpath: &'static str) -> Self {
321        let path = if let Ok(p) = std::env::var("MOVE_EXAMPLES_DIR") {
322            let mut path = PathBuf::from(p);
323            path.extend([subpath]);
324            path
325        } else {
326            let mut path = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
327            path.extend(["..", "..", "examples", "move", subpath]);
328            path
329        };
330        self.publish(path)
331    }
332
333    pub fn programmable(mut self, programmable: ProgrammableTransaction) -> Self {
334        self.test_data = TestTransactionData::Programmable(programmable);
335        self
336    }
337
338    pub fn build(self) -> TransactionData {
339        match self.test_data {
340            TestTransactionData::Move(data) => TransactionData::new_move_call(
341                self.sender,
342                data.package_id,
343                ident_str!(data.module).to_owned(),
344                ident_str!(data.function).to_owned(),
345                data.type_args,
346                self.gas_object,
347                data.args,
348                self.gas_budget
349                    .unwrap_or(self.gas_price * TEST_ONLY_GAS_UNIT_FOR_HEAVY_COMPUTATION_STORAGE),
350                self.gas_price,
351            )
352            .unwrap(),
353            TestTransactionData::Transfer(data) => TransactionData::new_transfer(
354                data.recipient,
355                data.object,
356                self.sender,
357                self.gas_object,
358                self.gas_budget
359                    .unwrap_or(self.gas_price * TEST_ONLY_GAS_UNIT_FOR_TRANSFER),
360                self.gas_price,
361            ),
362            TestTransactionData::TransferSui(data) => TransactionData::new_transfer_sui(
363                data.recipient,
364                self.sender,
365                data.amount,
366                self.gas_object,
367                self.gas_budget
368                    .unwrap_or(self.gas_price * TEST_ONLY_GAS_UNIT_FOR_TRANSFER),
369                self.gas_price,
370            ),
371            TestTransactionData::SplitCoin(data) => TransactionData::new_split_coin(
372                self.sender,
373                data.coin,
374                data.amounts,
375                self.gas_object,
376                self.gas_budget
377                    .unwrap_or(self.gas_price * TEST_ONLY_GAS_UNIT_FOR_TRANSFER),
378                self.gas_price,
379            ),
380            TestTransactionData::Publish(data) => {
381                let (all_module_bytes, dependencies) = match data {
382                    PublishData::Source(path, with_unpublished_deps) => {
383                        let compiled_package = BuildConfig::new_for_testing().build(&path).unwrap();
384                        let all_module_bytes =
385                            compiled_package.get_package_bytes(with_unpublished_deps);
386                        let dependencies = compiled_package.get_dependency_storage_package_ids();
387                        (all_module_bytes, dependencies)
388                    }
389                    PublishData::ModuleBytes(bytecode) => (bytecode, vec![]),
390                    PublishData::CompiledPackage(compiled_package) => {
391                        let all_module_bytes = compiled_package.get_package_bytes(false);
392                        let dependencies = compiled_package.get_dependency_storage_package_ids();
393                        (all_module_bytes, dependencies)
394                    }
395                };
396
397                TransactionData::new_module(
398                    self.sender,
399                    self.gas_object,
400                    all_module_bytes,
401                    dependencies,
402                    self.gas_budget.unwrap_or(
403                        self.gas_price * TEST_ONLY_GAS_UNIT_FOR_HEAVY_COMPUTATION_STORAGE,
404                    ),
405                    self.gas_price,
406                )
407            }
408            TestTransactionData::Programmable(pt) => TransactionData::new_programmable(
409                self.sender,
410                vec![self.gas_object],
411                pt,
412                self.gas_budget
413                    .unwrap_or(self.gas_price * TEST_ONLY_GAS_UNIT_FOR_HEAVY_COMPUTATION_STORAGE),
414                self.gas_price,
415            ),
416            TestTransactionData::Empty => {
417                panic!("Cannot build empty transaction");
418            }
419        }
420    }
421
422    pub fn build_and_sign(self, signer: &dyn Signer<Signature>) -> Transaction {
423        Transaction::from_data_and_signer(self.build(), vec![signer])
424    }
425
426    pub fn build_and_sign_multisig(
427        self,
428        multisig_pk: MultiSigPublicKey,
429        signers: &[&dyn Signer<Signature>],
430        bitmap: BitmapUnit,
431    ) -> Transaction {
432        let data = self.build();
433        let intent_msg = IntentMessage::new(Intent::sui_transaction(), data.clone());
434
435        let mut signatures = Vec::with_capacity(signers.len());
436        for signer in signers {
437            signatures.push(
438                GenericSignature::from(Signature::new_secure(&intent_msg, *signer))
439                    .to_compressed()
440                    .unwrap(),
441            );
442        }
443
444        let multisig =
445            GenericSignature::MultiSig(MultiSig::insecure_new(signatures, bitmap, multisig_pk));
446
447        Transaction::from_generic_sig_data(data, vec![multisig])
448    }
449
450    pub fn build_and_sign_multisig_legacy(
451        self,
452        multisig_pk: MultiSigPublicKeyLegacy,
453        signers: &[&dyn Signer<Signature>],
454    ) -> Transaction {
455        let data = self.build();
456        let intent = Intent::sui_transaction();
457        let intent_msg = IntentMessage::new(intent.clone(), data.clone());
458
459        let mut signatures = Vec::with_capacity(signers.len());
460        for signer in signers {
461            signatures.push(Signature::new_secure(&intent_msg, *signer).into());
462        }
463
464        let multisig = GenericSignature::MultiSigLegacy(
465            MultiSigLegacy::combine(signatures, multisig_pk).unwrap(),
466        );
467
468        Transaction::from_generic_sig_data(data, vec![multisig])
469    }
470}
471
472#[allow(clippy::large_enum_variant)]
473enum TestTransactionData {
474    Move(MoveData),
475    Transfer(TransferData),
476    TransferSui(TransferSuiData),
477    SplitCoin(SplitCoinData),
478    Publish(PublishData),
479    Programmable(ProgrammableTransaction),
480    Empty,
481}
482
483struct MoveData {
484    package_id: ObjectID,
485    module: &'static str,
486    function: &'static str,
487    args: Vec<CallArg>,
488    type_args: Vec<TypeTag>,
489}
490
491#[allow(clippy::large_enum_variant)]
492pub enum PublishData {
493    /// Path to source code directory and with_unpublished_deps.
494    /// with_unpublished_deps indicates whether to publish unpublished dependencies in the same transaction or not.
495    Source(PathBuf, bool),
496    ModuleBytes(Vec<Vec<u8>>),
497    CompiledPackage(CompiledPackage),
498}
499
500struct TransferData {
501    object: FullObjectRef,
502    recipient: SuiAddress,
503}
504
505struct TransferSuiData {
506    amount: Option<u64>,
507    recipient: SuiAddress,
508}
509
510struct SplitCoinData {
511    coin: ObjectRef,
512    amounts: Vec<u64>,
513}
514
515/// A helper function to make Transactions with controlled accounts in WalletContext.
516/// Particularly, the wallet needs to own gas objects for transactions.
517/// However, if this function is called multiple times without any "sync" actions
518/// on gas object management, txns may fail and objects may be locked.
519///
520/// The param is called `max_txn_num` because it does not always return the exact
521/// same amount of Transactions, for example when there are not enough gas objects
522/// controlled by the WalletContext. Caller should rely on the return value to
523/// check the count.
524pub async fn batch_make_transfer_transactions(
525    context: &WalletContext,
526    max_txn_num: usize,
527) -> Vec<Transaction> {
528    let recipient = get_key_pair::<AccountKeyPair>().0;
529    let result = context.get_all_accounts_and_gas_objects().await;
530    let accounts_and_objs = result.unwrap();
531    let mut res = Vec::with_capacity(max_txn_num);
532
533    let gas_price = context.get_reference_gas_price().await.unwrap();
534    for (address, objs) in accounts_and_objs {
535        for obj in objs {
536            if res.len() >= max_txn_num {
537                return res;
538            }
539            let data = TransactionData::new_transfer_sui(
540                recipient,
541                address,
542                Some(2),
543                obj,
544                gas_price * TEST_ONLY_GAS_UNIT_FOR_TRANSFER,
545                gas_price,
546            );
547            let tx = context.sign_transaction(&data).await;
548            res.push(tx);
549        }
550    }
551    res
552}
553
554pub async fn make_transfer_sui_transaction(
555    context: &WalletContext,
556    recipient: Option<SuiAddress>,
557    amount: Option<u64>,
558) -> Transaction {
559    let (sender, gas_object) = context.get_one_gas_object().await.unwrap().unwrap();
560    let gas_price = context.get_reference_gas_price().await.unwrap();
561    context
562        .sign_transaction(
563            &TestTransactionBuilder::new(sender, gas_object, gas_price)
564                .transfer_sui(amount, recipient.unwrap_or(sender))
565                .build(),
566        )
567        .await
568}
569
570pub async fn make_staking_transaction(
571    context: &WalletContext,
572    validator_address: SuiAddress,
573) -> Transaction {
574    let accounts_and_objs = context.get_all_accounts_and_gas_objects().await.unwrap();
575    let sender = accounts_and_objs[0].0;
576    let gas_object = accounts_and_objs[0].1[0];
577    let stake_object = accounts_and_objs[0].1[1];
578    let gas_price = context.get_reference_gas_price().await.unwrap();
579    context
580        .sign_transaction(
581            &TestTransactionBuilder::new(sender, gas_object, gas_price)
582                .call_staking(stake_object, validator_address)
583                .build(),
584        )
585        .await
586}
587
588pub async fn make_publish_transaction(context: &WalletContext, path: PathBuf) -> Transaction {
589    let (sender, gas_object) = context.get_one_gas_object().await.unwrap().unwrap();
590    let gas_price = context.get_reference_gas_price().await.unwrap();
591    context
592        .sign_transaction(
593            &TestTransactionBuilder::new(sender, gas_object, gas_price)
594                .publish(path)
595                .build(),
596        )
597        .await
598}
599
600pub async fn make_publish_transaction_with_deps(
601    context: &WalletContext,
602    path: PathBuf,
603) -> Transaction {
604    let (sender, gas_object) = context.get_one_gas_object().await.unwrap().unwrap();
605    let gas_price = context.get_reference_gas_price().await.unwrap();
606    context
607        .sign_transaction(
608            &TestTransactionBuilder::new(sender, gas_object, gas_price)
609                .publish_with_deps(path)
610                .build(),
611        )
612        .await
613}
614
615pub async fn publish_package(context: &WalletContext, path: PathBuf) -> ObjectRef {
616    let (sender, gas_object) = context.get_one_gas_object().await.unwrap().unwrap();
617    let gas_price = context.get_reference_gas_price().await.unwrap();
618    let txn = context
619        .sign_transaction(
620            &TestTransactionBuilder::new(sender, gas_object, gas_price)
621                .publish(path)
622                .build(),
623        )
624        .await;
625    let resp = context.execute_transaction_must_succeed(txn).await;
626    resp.get_new_package_obj().unwrap()
627}
628
629/// Executes a transaction to publish the `basics` package and returns the package object ref.
630pub async fn publish_basics_package(context: &WalletContext) -> ObjectRef {
631    let (sender, gas_object) = context.get_one_gas_object().await.unwrap().unwrap();
632    let gas_price = context.get_reference_gas_price().await.unwrap();
633    let txn = context
634        .sign_transaction(
635            &TestTransactionBuilder::new(sender, gas_object, gas_price)
636                .publish_examples("basics")
637                .build(),
638        )
639        .await;
640    let resp = context.execute_transaction_must_succeed(txn).await;
641    resp.get_new_package_obj().unwrap()
642}
643
644/// Executes a transaction to publish the `basics` package and another one to create a counter.
645/// Returns the package object ref and the counter object ref.
646pub async fn publish_basics_package_and_make_counter(
647    context: &WalletContext,
648) -> (ObjectRef, ObjectRef) {
649    let package_ref = publish_basics_package(context).await;
650    let (sender, gas_object) = context.get_one_gas_object().await.unwrap().unwrap();
651    let gas_price = context.get_reference_gas_price().await.unwrap();
652    let counter_creation_txn = context
653        .sign_transaction(
654            &TestTransactionBuilder::new(sender, gas_object, gas_price)
655                .call_counter_create(package_ref.0)
656                .build(),
657        )
658        .await;
659    let resp = context
660        .execute_transaction_must_succeed(counter_creation_txn)
661        .await;
662    let counter_ref = resp
663        .effects
664        .unwrap()
665        .created()
666        .iter()
667        .find(|obj_ref| matches!(obj_ref.owner, Owner::Shared { .. }))
668        .unwrap()
669        .reference
670        .to_object_ref();
671    (package_ref, counter_ref)
672}
673
674/// Executes a transaction to publish the `basics` package and another one to create a party
675/// object. Returns the package object ref and the counter object ref.
676pub async fn publish_basics_package_and_make_party_object(
677    context: &WalletContext,
678) -> (ObjectRef, ObjectRef) {
679    let package_ref = publish_basics_package(context).await;
680    let (sender, gas_object) = context.get_one_gas_object().await.unwrap().unwrap();
681    let gas_price = context.get_reference_gas_price().await.unwrap();
682    let object_creation_txn = context
683        .sign_transaction(
684            &TestTransactionBuilder::new(sender, gas_object, gas_price)
685                .call_object_create_party(package_ref.0)
686                .build(),
687        )
688        .await;
689    let resp = context
690        .execute_transaction_must_succeed(object_creation_txn)
691        .await;
692    let object_ref = resp
693        .effects
694        .unwrap()
695        .created()
696        .iter()
697        .find(|obj_ref| matches!(obj_ref.owner, Owner::ConsensusAddressOwner { .. }))
698        .unwrap()
699        .reference
700        .to_object_ref();
701    (package_ref, object_ref)
702}
703
704/// Executes a transaction to increment a counter object.
705/// Must be called after calling `publish_basics_package_and_make_counter`.
706pub async fn increment_counter(
707    context: &WalletContext,
708    sender: SuiAddress,
709    gas_object_id: Option<ObjectID>,
710    package_id: ObjectID,
711    counter_id: ObjectID,
712    initial_shared_version: SequenceNumber,
713) -> SuiTransactionBlockResponse {
714    let gas_object = if let Some(gas_object_id) = gas_object_id {
715        context.get_object_ref(gas_object_id).await.unwrap()
716    } else {
717        context
718            .get_one_gas_object_owned_by_address(sender)
719            .await
720            .unwrap()
721            .unwrap()
722    };
723    let rgp = context.get_reference_gas_price().await.unwrap();
724    let txn = context
725        .sign_transaction(
726            &TestTransactionBuilder::new(sender, gas_object, rgp)
727                .call_counter_increment(package_id, counter_id, initial_shared_version)
728                .build(),
729        )
730        .await;
731    context.execute_transaction_must_succeed(txn).await
732}
733
734/// Executes a transaction that generates a new random u128 using Random and emits it as an event.
735pub async fn emit_new_random_u128(
736    context: &WalletContext,
737    package_id: ObjectID,
738) -> SuiTransactionBlockResponse {
739    let (sender, gas_object) = context.get_one_gas_object().await.unwrap().unwrap();
740    let rgp = context.get_reference_gas_price().await.unwrap();
741
742    let client = context.get_client().await.unwrap();
743    let random_obj = client
744        .read_api()
745        .get_object_with_options(
746            SUI_RANDOMNESS_STATE_OBJECT_ID,
747            SuiObjectDataOptions::new().with_owner(),
748        )
749        .await
750        .unwrap()
751        .into_object()
752        .unwrap();
753    let random_obj_owner = random_obj
754        .owner
755        .expect("Expect Randomness object to have an owner");
756
757    let Owner::Shared {
758        initial_shared_version,
759    } = random_obj_owner
760    else {
761        panic!("Expect Randomness to be shared object")
762    };
763    let random_call_arg = CallArg::Object(ObjectArg::SharedObject {
764        id: SUI_RANDOMNESS_STATE_OBJECT_ID,
765        initial_shared_version,
766        mutability: SharedObjectMutability::Immutable,
767    });
768
769    let txn = context
770        .sign_transaction(
771            &TestTransactionBuilder::new(sender, gas_object, rgp)
772                .move_call(package_id, "random", "new", vec![random_call_arg])
773                .build(),
774        )
775        .await;
776    context.execute_transaction_must_succeed(txn).await
777}
778
779/// Executes a transaction to publish the `nfts` package and returns the package id, id of the gas
780/// object used, and the digest of the transaction.
781pub async fn publish_nfts_package(
782    context: &WalletContext,
783) -> (ObjectID, ObjectID, TransactionDigest) {
784    let (sender, gas_object) = context.get_one_gas_object().await.unwrap().unwrap();
785    let gas_id = gas_object.0;
786    let gas_price = context.get_reference_gas_price().await.unwrap();
787    let txn = context
788        .sign_transaction(
789            &TestTransactionBuilder::new(sender, gas_object, gas_price)
790                .publish_examples("nft")
791                .build(),
792        )
793        .await;
794    let resp = context.execute_transaction_must_succeed(txn).await;
795    let package_id = resp.get_new_package_obj().unwrap().0;
796    (package_id, gas_id, resp.digest)
797}
798
799/// Pre-requisite: `publish_nfts_package` must be called before this function.  Executes a
800/// transaction to create an NFT and returns the sender address, the object id of the NFT, and the
801/// digest of the transaction.
802pub async fn create_nft(
803    context: &WalletContext,
804    package_id: ObjectID,
805) -> (SuiAddress, ObjectID, TransactionDigest) {
806    let (sender, gas_object) = context.get_one_gas_object().await.unwrap().unwrap();
807    let rgp = context.get_reference_gas_price().await.unwrap();
808
809    let txn = context
810        .sign_transaction(
811            &TestTransactionBuilder::new(sender, gas_object, rgp)
812                .call_nft_create(package_id)
813                .build(),
814        )
815        .await;
816    let resp = context.execute_transaction_must_succeed(txn).await;
817
818    let object_id = resp
819        .effects
820        .as_ref()
821        .unwrap()
822        .created()
823        .first()
824        .unwrap()
825        .reference
826        .object_id;
827
828    (sender, object_id, resp.digest)
829}
830
831/// Executes a transaction to delete the given NFT.
832pub async fn delete_nft(
833    context: &WalletContext,
834    sender: SuiAddress,
835    package_id: ObjectID,
836    nft_to_delete: ObjectRef,
837) -> SuiTransactionBlockResponse {
838    let gas = context
839        .get_one_gas_object_owned_by_address(sender)
840        .await
841        .unwrap()
842        .unwrap_or_else(|| panic!("Expect {sender} to have at least one gas object"));
843    let rgp = context.get_reference_gas_price().await.unwrap();
844    let txn = context
845        .sign_transaction(
846            &TestTransactionBuilder::new(sender, gas, rgp)
847                .call_nft_delete(package_id, nft_to_delete)
848                .build(),
849        )
850        .await;
851    context.execute_transaction_must_succeed(txn).await
852}