Skip to main content

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