1mod error;
5pub mod unresolved;
6
7use error::Error;
8use sui_types::Address;
9use sui_types::Argument;
10use sui_types::Command;
11use sui_types::GasPayment;
12use sui_types::Identifier;
13use sui_types::Input;
14use sui_types::MakeMoveVector;
15use sui_types::MergeCoins;
16use sui_types::MoveCall;
17use sui_types::ObjectReference;
18use sui_types::Publish;
19use sui_types::SplitCoins;
20use sui_types::Transaction;
21use sui_types::TransactionExpiration;
22use sui_types::TransferObjects;
23use sui_types::TypeTag;
24use sui_types::Upgrade;
25
26use base64ct::Encoding;
27use serde::Serialize;
28
29#[derive(Clone, Default, Debug)]
31pub struct TransactionBuilder {
32 inputs: Vec<unresolved::Input>,
34 commands: Vec<Command>,
37 gas: Vec<unresolved::Input>,
41 gas_budget: Option<u64>,
43 gas_price: Option<u64>,
45 sender: Option<Address>,
47 sponsor: Option<Address>,
49 expiration: TransactionExpiration,
51}
52
53struct RawBytes(Vec<u8>);
56
57pub struct Serialized<'a, T: Serialize>(pub &'a T);
59
60pub struct Function {
62 package: Address,
64 module: Identifier,
66 function: Identifier,
68 type_args: Vec<TypeTag>,
70}
71
72impl TransactionBuilder {
74 pub fn new() -> Self {
76 Self::default()
77 }
78
79 pub fn input(&mut self, i: impl Into<unresolved::Input>) -> Argument {
83 let input = i.into();
84 self.inputs.push(input);
85 Argument::Input((self.inputs.len() - 1) as u16)
86 }
87
88 pub fn gas(&self) -> Argument {
90 Argument::Gas
91 }
92
93 pub fn add_gas_objects<O, I>(&mut self, gas: I)
101 where
102 O: Into<unresolved::Input>,
103 I: IntoIterator<Item = O>,
104 {
105 self.gas.extend(gas.into_iter().map(|x| x.into()));
106 }
107
108 pub fn set_gas_budget(&mut self, budget: u64) {
110 self.gas_budget = Some(budget);
111 }
112
113 pub fn set_gas_price(&mut self, price: u64) {
115 self.gas_price = Some(price);
116 }
117
118 pub fn set_sender(&mut self, sender: Address) {
120 self.sender = Some(sender);
121 }
122
123 pub fn set_sponsor(&mut self, sponsor: Address) {
125 self.sponsor = Some(sponsor);
126 }
127
128 pub fn set_expiration(&mut self, epoch: u64) {
130 self.expiration = TransactionExpiration::Epoch(epoch);
131 }
132
133 pub fn move_call(&mut self, function: Function, arguments: Vec<Argument>) -> Argument {
144 let cmd = Command::MoveCall(MoveCall {
145 package: function.package,
146 module: function.module,
147 function: function.function,
148 type_arguments: function.type_args,
149 arguments,
150 });
151 self.commands.push(cmd);
152 Argument::Result(self.commands.len() as u16 - 1)
153 }
154
155 pub fn transfer_objects(&mut self, objects: Vec<Argument>, address: Argument) {
157 let cmd = Command::TransferObjects(TransferObjects { objects, address });
158 self.commands.push(cmd);
159 }
160
161 pub fn split_coins(&mut self, coin: Argument, amounts: Vec<Argument>) -> Argument {
165 let cmd = Command::SplitCoins(SplitCoins { coin, amounts });
166 self.commands.push(cmd);
167 Argument::Result(self.commands.len() as u16 - 1)
168 }
169
170 pub fn merge_coins(&mut self, coin: Argument, coins_to_merge: Vec<Argument>) {
172 let cmd = Command::MergeCoins(MergeCoins {
173 coin,
174 coins_to_merge,
175 });
176 self.commands.push(cmd);
177 }
178
179 pub fn make_move_vec(&mut self, type_: Option<TypeTag>, elements: Vec<Argument>) -> Argument {
183 let cmd = Command::MakeMoveVector(MakeMoveVector { type_, elements });
184 self.commands.push(cmd);
185 Argument::Result(self.commands.len() as u16 - 1)
186 }
187
188 pub fn publish(&mut self, modules: Vec<Vec<u8>>, dependencies: Vec<Address>) -> Argument {
200 let cmd = Command::Publish(Publish {
201 modules,
202 dependencies,
203 });
204 self.commands.push(cmd);
205 Argument::Result(self.commands.len() as u16 - 1)
206 }
207
208 pub fn upgrade(
271 &mut self,
272 modules: Vec<Vec<u8>>,
273 dependencies: Vec<Address>,
274 package: Address,
275 ticket: Argument,
276 ) -> Argument {
277 let cmd = Command::Upgrade(Upgrade {
278 modules,
279 dependencies,
280 package,
281 ticket,
282 });
283 self.commands.push(cmd);
284 Argument::Result(self.commands.len() as u16 - 1)
285 }
286
287 pub fn finish(self) -> Result<Transaction, Error> {
290 let Some(sender) = self.sender else {
291 return Err(Error::MissingSender);
292 };
293 if self.gas.is_empty() {
294 return Err(Error::MissingGasObjects);
295 }
296 let Some(budget) = self.gas_budget else {
297 return Err(Error::MissingGasBudget);
298 };
299 let Some(price) = self.gas_price else {
300 return Err(Error::MissingGasPrice);
301 };
302
303 Ok(Transaction {
304 kind: sui_types::TransactionKind::ProgrammableTransaction(
305 sui_types::ProgrammableTransaction {
306 inputs: self
307 .inputs
308 .into_iter()
309 .map(try_from_unresolved_input_arg)
310 .collect::<Result<Vec<_>, _>>()?,
311 commands: self.commands,
312 },
313 ),
314 sender,
315 gas_payment: {
316 GasPayment {
317 objects: self
318 .gas
319 .into_iter()
320 .map(try_from_gas_unresolved_input_to_unresolved_obj_ref)
321 .collect::<Result<Vec<_>, _>>()?
322 .into_iter()
323 .map(try_from_unresolved_obj_ref)
324 .collect::<Result<Vec<_>, _>>()?,
325 owner: self.sponsor.unwrap_or(sender),
326 price,
327 budget,
328 }
329 },
330 expiration: self.expiration,
331 })
332 }
333}
334
335impl Function {
336 pub fn new(
338 package: Address,
339 module: Identifier,
340 function: Identifier,
341 type_args: Vec<TypeTag>,
342 ) -> Self {
343 Self {
344 package,
345 module,
346 function,
347 type_args,
348 }
349 }
350}
351
352impl From<RawBytes> for unresolved::Input {
353 fn from(raw: RawBytes) -> Self {
354 Self {
355 kind: Some(unresolved::InputKind::Pure),
356 value: Some(unresolved::Value::String(base64ct::Base64::encode_string(
357 &raw.0,
358 ))),
359 object_id: None,
360 version: None,
361 digest: None,
362 mutable: None,
363 }
364 }
365}
366
367impl<'a, T: Serialize> From<Serialized<'a, T>> for unresolved::Input {
368 fn from(value: Serialized<'a, T>) -> Self {
369 Self::from(RawBytes(bcs::to_bytes(value.0).unwrap()))
370 }
371}
372
373fn try_from_gas_unresolved_input_to_unresolved_obj_ref(
376 input: unresolved::Input,
377) -> Result<unresolved::ObjectReference, Error> {
378 match input.kind {
379 Some(unresolved::InputKind::ImmutableOrOwned) => {
380 let object_id = input.object_id.ok_or(Error::MissingObjectId)?;
381 let version = input.version;
382 let digest = input.digest;
383 Ok(unresolved::ObjectReference {
384 object_id,
385 version,
386 digest,
387 })
388 }
389 _ => Err(Error::WrongGasObject),
390 }
391}
392
393fn try_from_unresolved_obj_ref(obj: unresolved::ObjectReference) -> Result<ObjectReference, Error> {
395 let obj_id = obj.object_id;
396 let version = obj.version.ok_or(Error::MissingVersion(obj_id))?;
397 let digest = obj.digest.ok_or(Error::MissingDigest(obj_id))?;
398 Ok(ObjectReference::new(obj_id, version, digest))
399}
400
401fn try_from_unresolved_input_arg(value: unresolved::Input) -> Result<Input, Error> {
404 if let Some(kind) = value.kind {
405 match kind {
406 unresolved::InputKind::Pure => {
407 let Some(value) = value.value else {
408 return Err(Error::MissingPureValue);
409 };
410
411 match value {
412 unresolved::Value::String(v) => {
413 let bytes = base64ct::Base64::decode_vec(&v).map_err(Error::Decoding)?;
414 Ok(Input::Pure { value: bytes })
415 }
416 _ => Err(Error::Input(
417 "expected a base64 string value for the Pure input argument".to_string(),
418 )),
419 }
420 }
421 unresolved::InputKind::ImmutableOrOwned => {
422 let Some(object_id) = value.object_id else {
423 return Err(Error::MissingObjectId);
424 };
425 let Some(version) = value.version else {
426 return Err(Error::MissingVersion(object_id));
427 };
428 let Some(digest) = value.digest else {
429 return Err(Error::MissingDigest(object_id));
430 };
431 Ok(Input::ImmutableOrOwned(ObjectReference::new(
432 object_id, version, digest,
433 )))
434 }
435 unresolved::InputKind::Shared => {
436 let Some(object_id) = value.object_id else {
437 return Err(Error::MissingObjectId);
438 };
439 let Some(initial_shared_version) = value.version else {
440 return Err(Error::MissingInitialSharedVersion(object_id));
441 };
442 let Some(mutable) = value.mutable else {
443 return Err(Error::SharedObjectMutability(object_id));
444 };
445
446 Ok(Input::Shared {
447 object_id,
448 initial_shared_version,
449 mutable,
450 })
451 }
452 unresolved::InputKind::Receiving => {
453 let Some(object_id) = value.object_id else {
454 return Err(Error::MissingObjectId);
455 };
456 let Some(version) = value.version else {
457 return Err(Error::MissingVersion(object_id));
458 };
459 let Some(digest) = value.digest else {
460 return Err(Error::MissingDigest(object_id));
461 };
462 Ok(Input::Receiving(ObjectReference::new(
463 object_id, version, digest,
464 )))
465 }
466 unresolved::InputKind::Literal => Err(Error::UnsupportedLiteral),
467 }
468 } else {
469 Err(Error::Input(
470 "unresolved::Input must have a kind that is not None".to_string(),
471 ))
472 }
473}
474
475#[cfg(test)]
476mod tests {
477 use std::str::FromStr;
478
479 use anyhow::Context;
480 use base64ct::Encoding;
481 use serde::de;
482 use serde::Deserialize;
483 use serde::Deserializer;
484
485 use sui_crypto::ed25519::Ed25519PrivateKey;
486 use sui_crypto::SuiSigner;
487 use sui_graphql_client::faucet::FaucetClient;
488 use sui_graphql_client::Client;
489 use sui_graphql_client::PaginationFilter;
490
491 use sui_types::framework::Coin;
492 use sui_types::Address;
493 use sui_types::Digest;
494 use sui_types::ExecutionStatus;
495 use sui_types::IdOperation;
496 use sui_types::ObjectType;
497 use sui_types::TransactionEffects;
498 use sui_types::TypeTag;
499
500 use crate::unresolved::Input;
501 use crate::Function;
502 use crate::Serialized;
503 use crate::TransactionBuilder;
504
505 #[derive(serde::Deserialize, Debug)]
507 struct MovePackageData {
508 #[serde(deserialize_with = "bcs_from_str")]
509 modules: Vec<Vec<u8>>,
510 #[serde(deserialize_with = "deps_from_str")]
511 dependencies: Vec<Address>,
512 digest: Vec<u8>,
513 }
514
515 fn bcs_from_str<'de, D>(deserializer: D) -> Result<Vec<Vec<u8>>, D::Error>
516 where
517 D: Deserializer<'de>,
518 {
519 let bcs = Vec::<String>::deserialize(deserializer)?;
520 bcs.into_iter()
521 .map(|s| base64ct::Base64::decode_vec(&s).map_err(de::Error::custom))
522 .collect()
523 }
524
525 fn deps_from_str<'de, D>(deserializer: D) -> Result<Vec<Address>, D::Error>
526 where
527 D: Deserializer<'de>,
528 {
529 let deps = Vec::<String>::deserialize(deserializer)?;
530 deps.into_iter()
531 .map(|s| Address::from_str(&s).map_err(de::Error::custom))
532 .collect()
533 }
534
535 fn move_package_data(file: &str) -> MovePackageData {
541 let data = std::fs::read_to_string(file)
542 .with_context(|| {
543 format!(
544 "Failed to read {file}. \
545 Run `make test-with-localnet` from the root of the repository that will \
546 generate the right json files with the package data and then run the tests."
547 )
548 })
549 .unwrap();
550 serde_json::from_str(&data).unwrap()
551 }
552
553 fn helper_address_pk() -> (Address, Ed25519PrivateKey) {
555 let pk = Ed25519PrivateKey::generate(rand::thread_rng());
556 let address = pk.public_key().derive_address();
557 (address, pk)
558 }
559
560 async fn helper_setup(
569 tx: &mut TransactionBuilder,
570 client: &Client,
571 ) -> (Address, Ed25519PrivateKey, Vec<Coin<'static>>) {
572 let (address, pk) = helper_address_pk();
573 let faucet = FaucetClient::local();
574 let faucet_resp = faucet.request(address).await.unwrap();
575 wait_for_tx(
576 client,
577 faucet_resp
578 .coins_sent
579 .unwrap()
580 .first()
581 .unwrap()
582 .transfer_tx_digest,
583 )
584 .await;
585
586 let coins = client
587 .coins(address, None, PaginationFilter::default())
588 .await
589 .unwrap();
590 let coins = coins.data();
591
592 let gas = coins.last().unwrap().id();
593 let gas_obj: Input = (&client.object(*gas, None).await.unwrap().unwrap()).into();
595 tx.add_gas_objects(vec![gas_obj.with_owned_kind()]);
596 tx.set_gas_budget(500000000);
597 tx.set_gas_price(1000);
598 tx.set_sender(address);
599
600 (address, pk, coins.to_vec())
601 }
602
603 async fn wait_for_tx(client: &Client, digest: Digest) {
606 while client.transaction(digest).await.unwrap().is_none() {
607 tokio::time::sleep(tokio::time::Duration::from_millis(100)).await;
608 }
609 }
610
611 async fn wait_for_tx_and_check_effects_status_success(
614 client: &Client,
615 digest: Digest,
616 effects: Result<Option<TransactionEffects>, sui_graphql_client::error::Error>,
617 ) {
618 assert!(effects.is_ok(), "Execution failed. Effects: {effects:?}");
619 wait_for_tx(client, digest).await;
621 let status = effects.unwrap();
623 let expected_status = ExecutionStatus::Success;
624 assert_eq!(&expected_status, status.as_ref().unwrap().status());
625 }
626
627 #[tokio::test]
628 async fn test_finish() {
629 let mut tx = TransactionBuilder::new();
630 let coin_obj_id = "0x19406ea4d9609cd9422b85e6bf2486908f790b778c757aff805241f3f609f9b4";
631 let coin_digest = "7opR9rFUYivSTqoJHvFb9p6p54THyHTatMG6id4JKZR9";
632 let coin_version = 2;
633 let coin = tx.input(Input::owned(
634 coin_obj_id.parse().unwrap(),
635 coin_version,
636 coin_digest.parse().unwrap(),
637 ));
638
639 let addr = Address::generate(rand::thread_rng());
640 let recipient = tx.input(Serialized(&addr));
641
642 let result = tx.clone().finish();
643 assert!(result.is_err());
644
645 tx.transfer_objects(vec![coin], recipient);
646 tx.set_gas_budget(500000000);
647 tx.set_gas_price(1000);
648 tx.add_gas_objects(vec![Input::immutable(
649 "0xd8792bce2743e002673752902c0e7348dfffd78638cb5367b0b85857bceb9821"
650 .parse()
651 .unwrap(),
652 2,
653 "2ZigdvsZn5BMeszscPQZq9z8ebnS2FpmAuRbAi9ednCk"
654 .parse()
655 .unwrap(),
656 )]);
657 tx.set_sender(
658 "0xc574ea804d9c1a27c886312e96c0e2c9cfd71923ebaeb3000d04b5e65fca2793"
659 .parse()
660 .unwrap(),
661 );
662
663 let tx = tx.finish();
664 assert!(tx.is_ok());
665 }
666
667 #[tokio::test]
668 async fn test_transfer_obj_execution() {
669 let mut tx = TransactionBuilder::new();
670 let client = Client::new_localhost();
671 let (_, pk, coins) = helper_setup(&mut tx, &client).await;
672
673 let first = coins.first().unwrap().id();
675 let coin: Input = (&client.object(*first, None).await.unwrap().unwrap()).into();
676 let coin_input = tx.input(coin.with_owned_kind());
677 let recipient = Address::generate(rand::thread_rng());
678 let recipient_input = tx.input(Serialized(&recipient));
679 tx.transfer_objects(vec![coin_input], recipient_input);
680
681 let tx = tx.finish().unwrap();
682 let sig = pk.sign_transaction(&tx).unwrap();
683
684 let effects = client.execute_tx(vec![sig], &tx).await;
685 wait_for_tx_and_check_effects_status_success(&client, tx.digest(), effects).await;
686
687 let recipient_coins = client
689 .coins(recipient, None, PaginationFilter::default())
690 .await
691 .unwrap();
692 assert_eq!(recipient_coins.data().len(), 1);
693 }
694
695 #[tokio::test]
696 async fn test_move_call() {
697 let client = Client::new_localhost();
699 let mut tx = TransactionBuilder::new();
700 let (_, pk, _) = helper_setup(&mut tx, &client).await;
702 let function = Function::new(
703 "0x1".parse().unwrap(),
704 "option".parse().unwrap(),
705 "is_none".parse().unwrap(),
706 vec![TypeTag::U64],
707 );
708 let input = tx.input(Serialized(&vec![1u64]));
709 tx.move_call(function, vec![input]);
710
711 let tx = tx.finish().unwrap();
712 let sig = pk.sign_transaction(&tx).unwrap();
713 let effects = client.execute_tx(vec![sig], &tx).await;
714 wait_for_tx_and_check_effects_status_success(&client, tx.digest(), effects).await;
715 }
716
717 #[tokio::test]
718 async fn test_split_transfer() {
719 let client = Client::new_localhost();
720 let mut tx = TransactionBuilder::new();
721 let (_, pk, _) = helper_setup(&mut tx, &client).await;
722
723 let amount = tx.input(Serialized(&1_000_000_000u64));
725 let result = tx.split_coins(tx.gas(), vec![amount]);
726 let recipient_address = Address::generate(rand::thread_rng());
727 let recipient = tx.input(Serialized(&recipient_address));
728 tx.transfer_objects(vec![result], recipient);
729
730 let tx = tx.finish().unwrap();
731 let sig = pk.sign_transaction(&tx).unwrap();
732
733 let effects = client.execute_tx(vec![sig], &tx).await;
734 wait_for_tx_and_check_effects_status_success(&client, tx.digest(), effects).await;
735
736 let recipient_coins = client
738 .coins(recipient_address, None, PaginationFilter::default())
739 .await
740 .unwrap();
741 assert_eq!(recipient_coins.data().len(), 1);
742 }
743
744 #[tokio::test]
745 async fn test_split_without_transfer_should_fail() {
746 let client = Client::new_localhost();
747 let mut tx = TransactionBuilder::new();
748 let (_, pk, coins) = helper_setup(&mut tx, &client).await;
749
750 let coin = coins.first().unwrap().id();
751 let coin_obj: Input = (&client.object(*coin, None).await.unwrap().unwrap()).into();
752 let coin_input = tx.input(coin_obj.with_owned_kind());
753
754 let amount = tx.input(Serialized(&1_000_000_000u64));
756 tx.split_coins(coin_input, vec![amount]);
757
758 let tx = tx.finish().unwrap();
759 let sig = pk.sign_transaction(&tx).unwrap();
760
761 let effects = client.execute_tx(vec![sig], &tx).await;
762 assert!(effects.is_ok());
763
764 loop {
766 let tx_digest = client.transaction(tx.digest()).await.unwrap();
767 if tx_digest.is_some() {
768 break;
769 }
770 }
771 assert!(effects.is_ok());
772 let status = effects.unwrap();
773 let expected_status = ExecutionStatus::Success;
774 assert_ne!(&expected_status, status.as_ref().unwrap().status());
776 }
777
778 #[tokio::test]
779 async fn test_merge_coins() {
780 let client = Client::new_localhost();
781 let mut tx = TransactionBuilder::new();
782 let (address, pk, coins) = helper_setup(&mut tx, &client).await;
783
784 let coin1 = coins.first().unwrap().id();
785 let coin1_obj: Input = (&client.object(*coin1, None).await.unwrap().unwrap()).into();
786 let coin_to_merge = tx.input(coin1_obj.with_owned_kind());
787
788 let mut coins_to_merge = vec![];
789 for c in coins[1..&coins.len() - 1].iter() {
791 let coin: Input = (&client.object(*c.id(), None).await.unwrap().unwrap()).into();
792 coins_to_merge.push(tx.input(coin.with_owned_kind()));
793 }
794
795 tx.merge_coins(coin_to_merge, coins_to_merge);
796 let tx = tx.finish().unwrap();
797 let sig = pk.sign_transaction(&tx).unwrap();
798
799 let effects = client.execute_tx(vec![sig], &tx).await;
800 wait_for_tx_and_check_effects_status_success(&client, tx.digest(), effects).await;
801
802 let coins_after = client
804 .coins(address, None, PaginationFilter::default())
805 .await
806 .unwrap();
807 assert_eq!(coins_after.data().len(), 2);
808 }
809
810 #[tokio::test]
811 async fn test_make_move_vec() {
812 let client = Client::new_localhost();
813 let mut tx = TransactionBuilder::new();
814 let (_, pk, _) = helper_setup(&mut tx, &client).await;
815
816 let input = tx.input(Serialized(&1u64));
817 tx.make_move_vec(Some(TypeTag::U64), vec![input]);
818
819 let tx = tx.finish().unwrap();
820 let sig = pk.sign_transaction(&tx).unwrap();
821
822 let effects = client.execute_tx(vec![sig], &tx).await;
823 wait_for_tx_and_check_effects_status_success(&client, tx.digest(), effects).await;
824 }
825
826 #[tokio::test]
827 async fn test_publish() {
828 let client = Client::new_localhost();
829 let mut tx = TransactionBuilder::new();
830 let (address, pk, _) = helper_setup(&mut tx, &client).await;
831
832 let package = move_package_data("package_test_example_v1.json");
833 let sender = tx.input(Serialized(&address));
834 let upgrade_cap = tx.publish(package.modules, package.dependencies);
835 tx.transfer_objects(vec![upgrade_cap], sender);
836 let tx = tx.finish().unwrap();
837 let sig = pk.sign_transaction(&tx).unwrap();
838 let effects = client.execute_tx(vec![sig], &tx).await;
839 wait_for_tx_and_check_effects_status_success(&client, tx.digest(), effects).await;
840 }
841
842 #[tokio::test]
843 async fn test_upgrade() {
844 let client = Client::new_localhost();
845 let mut tx = TransactionBuilder::new();
846 let (address, pk, coins) = helper_setup(&mut tx, &client).await;
847
848 let package = move_package_data("package_test_example_v2.json");
849 let sender = tx.input(Serialized(&address));
850 let upgrade_cap = tx.publish(package.modules, package.dependencies);
851 tx.transfer_objects(vec![upgrade_cap], sender);
852 let tx = tx.finish().unwrap();
853 let sig = pk.sign_transaction(&tx).unwrap();
854 let effects = client.execute_tx(vec![sig], &tx).await;
855 let mut package_id: Option<Address> = None;
856 let mut created_objs = vec![];
857 if let Ok(Some(ref effects)) = effects {
858 match effects {
859 TransactionEffects::V2(e) => {
860 for obj in e.changed_objects.clone() {
861 if obj.id_operation == IdOperation::Created {
862 let change = obj.output_state;
863 match change {
864 sui_types::ObjectOut::PackageWrite { .. } => {
865 package_id = Some(obj.object_id);
866 }
867 sui_types::ObjectOut::ObjectWrite { .. } => {
868 created_objs.push(obj.object_id);
869 }
870 _ => {}
871 }
872 }
873 }
874 }
875 _ => panic!("Expected V2 effects"),
876 }
877 }
878 wait_for_tx_and_check_effects_status_success(&client, tx.digest(), effects).await;
879
880 let mut tx = TransactionBuilder::new();
881 let mut upgrade_cap = None;
882 for o in created_objs {
883 let obj = client.object(o, None).await.unwrap().unwrap();
884 match obj.object_type() {
885 ObjectType::Struct(x) if x.name.to_string() == "UpgradeCap" => {
886 match obj.owner() {
887 sui_types::Owner::Address(_) => {
888 let obj: Input = (&obj).into();
889 upgrade_cap = Some(tx.input(obj.with_owned_kind()))
890 }
891 sui_types::Owner::Shared(_) => {
892 upgrade_cap = Some(tx.input(&obj))
893 }
894 sui_types::Owner::Object(_) => {
897 panic!("Upgrade capability controlled by object")
898 }
899 sui_types::Owner::Immutable => panic!("Upgrade capability is stored immutably and cannot be used for upgrades"),
900 sui_types::Owner::ConsensusAddress { .. } => {
901 upgrade_cap = Some(tx.input(&obj))
902 }
903 _ => panic!("unknwon owner"),
904 };
905 break;
906 }
907 _ => {}
908 };
909 }
910
911 let upgrade_policy = tx.input(Serialized(&0u8));
912 let updated_package = move_package_data("package_test_example_v2.json");
913 let digest_arg = tx.input(Serialized(&updated_package.digest));
914
915 let upgrade_ticket = tx.move_call(
917 Function::new(
918 "0x2".parse().unwrap(),
919 "package".parse().unwrap(),
920 "authorize_upgrade".parse().unwrap(),
921 vec![],
922 ),
923 vec![upgrade_cap.unwrap(), upgrade_policy, digest_arg],
924 );
925 let upgrade_receipt = tx.upgrade(
927 updated_package.modules,
928 updated_package.dependencies,
929 package_id.unwrap(),
930 upgrade_ticket,
931 );
932
933 tx.move_call(
935 Function::new(
936 "0x2".parse().unwrap(),
937 "package".parse().unwrap(),
938 "commit_upgrade".parse().unwrap(),
939 vec![],
940 ),
941 vec![upgrade_cap.unwrap(), upgrade_receipt],
942 );
943
944 let gas = coins.last().unwrap().id();
945 let gas_obj: Input = (&client.object(*gas, None).await.unwrap().unwrap()).into();
946 tx.add_gas_objects(vec![gas_obj.with_owned_kind()]);
947 tx.set_gas_budget(500000000);
948 tx.set_gas_price(1000);
949 tx.set_sender(address);
950 let tx = tx.finish().unwrap();
951 let sig = pk.sign_transaction(&tx).unwrap();
952 let effects = client.execute_tx(vec![sig], &tx).await;
953 wait_for_tx_and_check_effects_status_success(&client, tx.digest(), effects).await;
954 }
955}