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