Skip to main content

iota_transaction_builder/
package.rs

1// Copyright (c) Mysten Labs, Inc.
2// Modifications Copyright (c) 2024 IOTA Stiftung
3// SPDX-License-Identifier: Apache-2.0
4
5use std::result::Result;
6
7use anyhow::{Ok, anyhow, bail};
8use iota_json_rpc_types::IotaObjectDataOptions;
9use iota_sdk_types::{
10    Argument, Identifier, ObjectId, Owner, TransactionKind, move_package::MovePackage,
11};
12use iota_types::{
13    base_types::IotaAddress,
14    programmable_transaction_builder::ProgrammableTransactionBuilder,
15    transaction::{CallArg, SharedObjectRef, TransactionData, TransactionDataAPI},
16};
17
18use crate::TransactionBuilder;
19
20impl TransactionBuilder {
21    /// Build a [`TransactionKind::Programmable`] that contains
22    /// [`iota_sdk_types::Command::Publish`] for the provided package.
23    pub async fn publish_tx_kind(
24        &self,
25        sender: IotaAddress,
26        modules: Vec<Vec<u8>>,
27        dep_ids: Vec<ObjectId>,
28    ) -> Result<TransactionKind, anyhow::Error> {
29        let pt = {
30            let mut builder = ProgrammableTransactionBuilder::new();
31            let upgrade_cap = builder.publish_upgradeable(modules, dep_ids);
32            builder.transfer_arg(sender, upgrade_cap);
33            builder.finish()
34        };
35        Ok(TransactionKind::new_programmable(pt))
36    }
37
38    /// Publish a new move package.
39    pub async fn publish(
40        &self,
41        sender: IotaAddress,
42        compiled_modules: Vec<Vec<u8>>,
43        dep_ids: Vec<ObjectId>,
44        gas: impl Into<Option<ObjectId>>,
45        gas_budget: u64,
46    ) -> anyhow::Result<TransactionData> {
47        let gas_price = self.0.get_reference_gas_price().await?;
48        let gas = self
49            .select_gas(sender, gas, gas_budget, vec![], gas_price)
50            .await?;
51        Ok(TransactionData::new_module(
52            sender,
53            gas,
54            compiled_modules,
55            dep_ids,
56            gas_budget,
57            gas_price,
58        ))
59    }
60
61    /// Build a [`TransactionKind::Programmable`] that contains
62    /// [`iota_sdk_types::Command::Upgrade`] for the provided package.
63    pub async fn upgrade_tx_kind(
64        &self,
65        package_id: ObjectId,
66        modules: Vec<Vec<u8>>,
67        dep_ids: Vec<ObjectId>,
68        upgrade_capability: ObjectId,
69        upgrade_policy: u8,
70        digest: Vec<u8>,
71    ) -> Result<TransactionKind, anyhow::Error> {
72        let upgrade_capability = self
73            .0
74            .get_object_with_options(
75                upgrade_capability,
76                IotaObjectDataOptions::new().with_owner(),
77            )
78            .await?
79            .into_object()?;
80        let capability_owner = upgrade_capability
81            .owner
82            .ok_or_else(|| anyhow!("Unable to determine ownership of upgrade capability"))?;
83        let pt = {
84            let mut builder = ProgrammableTransactionBuilder::new();
85            let capability_arg = match capability_owner {
86                Owner::Address(_) => CallArg::ImmutableOrOwned(upgrade_capability.object_ref()),
87                Owner::Shared(initial_shared_version) => CallArg::Shared(SharedObjectRef::new(
88                    upgrade_capability.object_ref().object_id,
89                    initial_shared_version,
90                    true,
91                )),
92                Owner::Immutable => {
93                    bail!("Upgrade capability is stored immutably and cannot be used for upgrades")
94                }
95                // If the capability is owned by an object, then the module defining the owning
96                // object gets to decide how the upgrade capability should be used.
97                Owner::Object(_) => {
98                    bail!("Upgrade capability controlled by object");
99                }
100                _ => unimplemented!("a new Owner enum variant was added and needs to be handled"),
101            };
102            builder.obj(capability_arg).unwrap();
103            let upgrade_arg = builder.pure(upgrade_policy).unwrap();
104            let digest_arg = builder.pure(digest).unwrap();
105            let upgrade_ticket = builder.programmable_move_call(
106                ObjectId::FRAMEWORK,
107                Identifier::PACKAGE_MODULE,
108                Identifier::from_static("authorize_upgrade"),
109                vec![],
110                vec![Argument::Input(0), upgrade_arg, digest_arg],
111            );
112            let upgrade_receipt = builder.upgrade(package_id, upgrade_ticket, dep_ids, modules);
113
114            builder.programmable_move_call(
115                ObjectId::FRAMEWORK,
116                Identifier::PACKAGE_MODULE,
117                Identifier::from_static("commit_upgrade"),
118                vec![],
119                vec![Argument::Input(0), upgrade_receipt],
120            );
121
122            builder.finish()
123        };
124
125        Ok(TransactionKind::new_programmable(pt))
126    }
127
128    /// Upgrade an existing move package.
129    pub async fn upgrade(
130        &self,
131        sender: IotaAddress,
132        package_id: ObjectId,
133        compiled_modules: Vec<Vec<u8>>,
134        dep_ids: Vec<ObjectId>,
135        upgrade_capability: ObjectId,
136        upgrade_policy: u8,
137        gas: impl Into<Option<ObjectId>>,
138        gas_budget: u64,
139    ) -> anyhow::Result<TransactionData> {
140        let gas_price = self.0.get_reference_gas_price().await?;
141        let gas = self
142            .select_gas(sender, gas, gas_budget, vec![], gas_price)
143            .await?;
144        let upgrade_cap = self
145            .0
146            .get_object_with_options(
147                upgrade_capability,
148                IotaObjectDataOptions::new().with_owner(),
149            )
150            .await?
151            .into_object()?;
152        let cap_owner = upgrade_cap
153            .owner
154            .ok_or_else(|| anyhow!("Unable to determine ownership of upgrade capability"))?;
155        let digest = MovePackage::compute_digest_for_modules_and_deps(&compiled_modules, &dep_ids)
156            .into_inner()
157            .to_vec();
158        TransactionData::new_upgrade(
159            sender,
160            gas,
161            package_id,
162            compiled_modules,
163            dep_ids,
164            (upgrade_cap.object_ref(), cap_owner),
165            upgrade_policy,
166            digest,
167            gas_budget,
168            gas_price,
169        )
170    }
171}