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