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    IOTA_FRAMEWORK_PACKAGE_ID,
11    base_types::{IotaAddress, ObjectID},
12    move_package::MovePackage,
13    object::Owner,
14    programmable_transaction_builder::ProgrammableTransactionBuilder,
15    transaction::{Argument, ObjectArg, TransactionData, TransactionKind},
16};
17use move_core_types::ident_str;
18
19use crate::TransactionBuilder;
20
21impl TransactionBuilder {
22    /// Build a [`TransactionKind::ProgrammableTransaction`] 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::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::ProgrammableTransaction`] 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::AddressOwner(_) => {
88                    ObjectArg::ImmOrOwnedObject(upgrade_capability.object_ref())
89                }
90                Owner::Shared {
91                    initial_shared_version,
92                } => ObjectArg::SharedObject {
93                    id: upgrade_capability.object_ref().0,
94                    initial_shared_version,
95                    mutable: true,
96                },
97                Owner::Immutable => {
98                    bail!("Upgrade capability is stored immutably and cannot be used for upgrades")
99                }
100                // If the capability is owned by an object, then the module defining the owning
101                // object gets to decide how the upgrade capability should be used.
102                Owner::ObjectOwner(_) => {
103                    return Err(anyhow::anyhow!("Upgrade capability controlled by object"));
104                }
105            };
106            builder.obj(capability_arg).unwrap();
107            let upgrade_arg = builder.pure(upgrade_policy).unwrap();
108            let digest_arg = builder.pure(digest).unwrap();
109            let upgrade_ticket = builder.programmable_move_call(
110                IOTA_FRAMEWORK_PACKAGE_ID,
111                ident_str!("package").to_owned(),
112                ident_str!("authorize_upgrade").to_owned(),
113                vec![],
114                vec![Argument::Input(0), upgrade_arg, digest_arg],
115            );
116            let upgrade_receipt = builder.upgrade(package_id, upgrade_ticket, dep_ids, modules);
117
118            builder.programmable_move_call(
119                IOTA_FRAMEWORK_PACKAGE_ID,
120                ident_str!("package").to_owned(),
121                ident_str!("commit_upgrade").to_owned(),
122                vec![],
123                vec![Argument::Input(0), upgrade_receipt],
124            );
125
126            builder.finish()
127        };
128
129        Ok(TransactionKind::programmable(pt))
130    }
131
132    /// Upgrade an existing move package.
133    pub async fn upgrade(
134        &self,
135        sender: IotaAddress,
136        package_id: ObjectID,
137        compiled_modules: Vec<Vec<u8>>,
138        dep_ids: Vec<ObjectID>,
139        upgrade_capability: ObjectID,
140        upgrade_policy: u8,
141        gas: impl Into<Option<ObjectID>>,
142        gas_budget: u64,
143    ) -> anyhow::Result<TransactionData> {
144        let gas_price = self.0.get_reference_gas_price().await?;
145        let gas = self
146            .select_gas(sender, gas, gas_budget, vec![], gas_price)
147            .await?;
148        let upgrade_cap = self
149            .0
150            .get_object_with_options(
151                upgrade_capability,
152                IotaObjectDataOptions::new().with_owner(),
153            )
154            .await?
155            .into_object()?;
156        let cap_owner = upgrade_cap
157            .owner
158            .ok_or_else(|| anyhow!("Unable to determine ownership of upgrade capability"))?;
159        let digest =
160            MovePackage::compute_digest_for_modules_and_deps(&compiled_modules, &dep_ids).to_vec();
161        TransactionData::new_upgrade(
162            sender,
163            gas,
164            package_id,
165            compiled_modules,
166            dep_ids,
167            (upgrade_cap.object_ref(), cap_owner),
168            upgrade_policy,
169            digest,
170            gas_budget,
171            gas_price,
172        )
173    }
174}