iota_transaction_builder/
stake.rs

1// Copyright (c) Mysten Labs, Inc.
2// Modifications Copyright (c) 2024 IOTA Stiftung
3// SPDX-License-Identifier: Apache-2.0
4
5use anyhow::{Ok, anyhow, bail, ensure};
6use iota_types::{
7    base_types::{Identifier, IotaAddress, ObjectID, ObjectType},
8    governance::{ADD_STAKE_MUL_COIN_FUN_NAME, WITHDRAW_STAKE_FUN_NAME},
9    programmable_transaction_builder::ProgrammableTransactionBuilder,
10    timelock::timelocked_staking::{
11        ADD_TIMELOCKED_STAKE_FUN_NAME, WITHDRAW_TIMELOCKED_STAKE_FUN_NAME,
12    },
13    transaction::{CallArg, Command, ObjectArg, TransactionData},
14};
15
16use crate::TransactionBuilder;
17
18impl TransactionBuilder {
19    /// Add stake to a validator's staking pool using multiple IOTA coins.
20    pub async fn request_add_stake(
21        &self,
22        signer: IotaAddress,
23        mut coins: Vec<ObjectID>,
24        amount: impl Into<Option<u64>>,
25        validator: IotaAddress,
26        gas: impl Into<Option<ObjectID>>,
27        gas_budget: u64,
28    ) -> anyhow::Result<TransactionData> {
29        let gas_price = self.0.get_reference_gas_price().await?;
30        let gas = self
31            .select_gas(signer, gas, gas_budget, coins.clone(), gas_price)
32            .await?;
33
34        let mut obj_vec = vec![];
35        let coin = coins
36            .pop()
37            .ok_or_else(|| anyhow!("Coins input should contain at lease one coin object."))?;
38        let (oref, coin_type) = self.get_object_ref_and_type(coin).await?;
39
40        let ObjectType::Struct(type_) = &coin_type else {
41            bail!("Provided object [{coin}] is not a move object.");
42        };
43        ensure!(
44            type_.is_coin(),
45            "Expecting either Coin<T> input coin objects. Received [{type_}]"
46        );
47
48        for coin in coins {
49            let (oref, type_) = self.get_object_ref_and_type(coin).await?;
50            ensure!(
51                type_ == coin_type,
52                "All coins should be the same type, expecting {coin_type}, got {type_}."
53            );
54            obj_vec.push(ObjectArg::ImmOrOwnedObject(oref))
55        }
56        obj_vec.push(ObjectArg::ImmOrOwnedObject(oref));
57
58        let pt = {
59            let mut builder = ProgrammableTransactionBuilder::new();
60            let arguments = vec![
61                builder.input(CallArg::IOTA_SYSTEM_MUT).unwrap(),
62                builder.make_obj_vec(obj_vec)?,
63                builder
64                    .input(CallArg::Pure(bcs::to_bytes(&amount.into())?))
65                    .unwrap(),
66                builder
67                    .input(CallArg::Pure(bcs::to_bytes(&validator)?))
68                    .unwrap(),
69            ];
70            builder.command(Command::move_call(
71                ObjectID::SYSTEM,
72                Identifier::IOTA_SYSTEM_MODULE,
73                ADD_STAKE_MUL_COIN_FUN_NAME,
74                vec![],
75                arguments,
76            ));
77            builder.finish()
78        };
79        Ok(TransactionData::new_programmable(
80            signer,
81            vec![gas],
82            pt,
83            gas_budget,
84            gas_price,
85        ))
86    }
87
88    /// Withdraw stake from a validator's staking pool.
89    pub async fn request_withdraw_stake(
90        &self,
91        signer: IotaAddress,
92        staked_iota: ObjectID,
93        gas: impl Into<Option<ObjectID>>,
94        gas_budget: u64,
95    ) -> anyhow::Result<TransactionData> {
96        let staked_iota = self.get_object_ref(staked_iota).await?;
97        let gas_price = self.0.get_reference_gas_price().await?;
98        let gas = self
99            .select_gas(signer, gas, gas_budget, vec![], gas_price)
100            .await?;
101        TransactionData::new_move_call(
102            signer,
103            ObjectID::SYSTEM,
104            Identifier::IOTA_SYSTEM_MODULE,
105            WITHDRAW_STAKE_FUN_NAME,
106            vec![],
107            gas,
108            vec![
109                CallArg::IOTA_SYSTEM_MUT,
110                CallArg::Object(ObjectArg::ImmOrOwnedObject(staked_iota)),
111            ],
112            gas_budget,
113            gas_price,
114        )
115    }
116
117    /// Add stake to a validator's staking pool using a timelocked IOTA coin.
118    pub async fn request_add_timelocked_stake(
119        &self,
120        signer: IotaAddress,
121        locked_balance: ObjectID,
122        validator: IotaAddress,
123        gas: ObjectID,
124        gas_budget: u64,
125    ) -> anyhow::Result<TransactionData> {
126        let gas_price = self.0.get_reference_gas_price().await?;
127        let gas = self
128            .select_gas(signer, Some(gas), gas_budget, vec![], gas_price)
129            .await?;
130
131        let (oref, locked_balance_type) = self.get_object_ref_and_type(locked_balance).await?;
132
133        let ObjectType::Struct(type_) = &locked_balance_type else {
134            bail!("Provided object [{locked_balance}] is not a move object.");
135        };
136        ensure!(
137            type_.is_timelocked_balance(),
138            "Expecting either TimeLock<Balance<T>> input objects. Received [{type_}]"
139        );
140
141        let pt = {
142            let mut builder = ProgrammableTransactionBuilder::new();
143            let arguments = vec![
144                builder.input(CallArg::IOTA_SYSTEM_MUT)?,
145                builder.input(CallArg::Object(ObjectArg::ImmOrOwnedObject(oref)))?,
146                builder.input(CallArg::Pure(bcs::to_bytes(&validator)?))?,
147            ];
148            builder.command(Command::move_call(
149                ObjectID::SYSTEM,
150                Identifier::TIMELOCKED_STAKING_MODULE,
151                ADD_TIMELOCKED_STAKE_FUN_NAME,
152                vec![],
153                arguments,
154            ));
155            builder.finish()
156        };
157        Ok(TransactionData::new_programmable(
158            signer,
159            vec![gas],
160            pt,
161            gas_budget,
162            gas_price,
163        ))
164    }
165
166    /// Withdraw timelocked stake from a validator's staking pool.
167    pub async fn request_withdraw_timelocked_stake(
168        &self,
169        signer: IotaAddress,
170        timelocked_staked_iota: ObjectID,
171        gas: ObjectID,
172        gas_budget: u64,
173    ) -> anyhow::Result<TransactionData> {
174        let timelocked_staked_iota = self.get_object_ref(timelocked_staked_iota).await?;
175        let gas_price = self.0.get_reference_gas_price().await?;
176        let gas = self
177            .select_gas(signer, Some(gas), gas_budget, vec![], gas_price)
178            .await?;
179        TransactionData::new_move_call(
180            signer,
181            ObjectID::SYSTEM,
182            Identifier::TIMELOCKED_STAKING_MODULE,
183            WITHDRAW_TIMELOCKED_STAKE_FUN_NAME,
184            vec![],
185            gas,
186            vec![
187                CallArg::IOTA_SYSTEM_MUT,
188                CallArg::Object(ObjectArg::ImmOrOwnedObject(timelocked_staked_iota)),
189            ],
190            gas_budget,
191            gas_price,
192        )
193    }
194}