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_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, TransactionData, TransactionDataAPI},
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(CallArg::ImmutableOrOwned(oref))
55        }
56        obj_vec.push(CallArg::ImmutableOrOwned(oref));
57
58        let pt = {
59            let mut builder = ProgrammableTransactionBuilder::new();
60            let arguments = vec![
61                builder.input(CallArg::IOTA_SYSTEM_MUTABLE).unwrap(),
62                builder.make_obj_vec(obj_vec)?,
63                builder.pure(amount.into()).unwrap(),
64                builder.pure(validator).unwrap(),
65            ];
66            builder.command(Command::new_move_call(
67                ObjectID::SYSTEM,
68                Identifier::IOTA_SYSTEM_MODULE,
69                ADD_STAKE_MUL_COIN_FUN_NAME,
70                vec![],
71                arguments,
72            ));
73            builder.finish()
74        };
75        Ok(TransactionData::new_programmable(
76            signer,
77            vec![gas],
78            pt,
79            gas_budget,
80            gas_price,
81        ))
82    }
83
84    /// Withdraw stake from a validator's staking pool.
85    pub async fn request_withdraw_stake(
86        &self,
87        signer: IotaAddress,
88        staked_iota: ObjectID,
89        gas: impl Into<Option<ObjectID>>,
90        gas_budget: u64,
91    ) -> anyhow::Result<TransactionData> {
92        let staked_iota = self.get_object_ref(staked_iota).await?;
93        let gas_price = self.0.get_reference_gas_price().await?;
94        let gas = self
95            .select_gas(signer, gas, gas_budget, vec![], gas_price)
96            .await?;
97        TransactionData::new_move_call(
98            signer,
99            ObjectID::SYSTEM,
100            Identifier::IOTA_SYSTEM_MODULE,
101            WITHDRAW_STAKE_FUN_NAME,
102            vec![],
103            gas,
104            vec![
105                CallArg::IOTA_SYSTEM_MUTABLE,
106                CallArg::ImmutableOrOwned(staked_iota),
107            ],
108            gas_budget,
109            gas_price,
110        )
111    }
112
113    /// Add stake to a validator's staking pool using a timelocked IOTA coin.
114    pub async fn request_add_timelocked_stake(
115        &self,
116        signer: IotaAddress,
117        locked_balance: ObjectID,
118        validator: IotaAddress,
119        gas: ObjectID,
120        gas_budget: u64,
121    ) -> anyhow::Result<TransactionData> {
122        let gas_price = self.0.get_reference_gas_price().await?;
123        let gas = self
124            .select_gas(signer, Some(gas), gas_budget, vec![], gas_price)
125            .await?;
126
127        let (oref, locked_balance_type) = self.get_object_ref_and_type(locked_balance).await?;
128
129        let ObjectType::Struct(type_) = &locked_balance_type else {
130            bail!("Provided object [{locked_balance}] is not a move object.");
131        };
132        ensure!(
133            type_.is_timelocked_balance(),
134            "Expecting either TimeLock<Balance<T>> input objects. Received [{type_}]"
135        );
136
137        let pt = {
138            let mut builder = ProgrammableTransactionBuilder::new();
139            let arguments = vec![
140                builder.input(CallArg::IOTA_SYSTEM_MUTABLE)?,
141                builder.input(CallArg::ImmutableOrOwned(oref))?,
142                builder.pure(validator)?,
143            ];
144            builder.command(Command::new_move_call(
145                ObjectID::SYSTEM,
146                Identifier::TIMELOCKED_STAKING_MODULE,
147                ADD_TIMELOCKED_STAKE_FUN_NAME,
148                vec![],
149                arguments,
150            ));
151            builder.finish()
152        };
153        Ok(TransactionData::new_programmable(
154            signer,
155            vec![gas],
156            pt,
157            gas_budget,
158            gas_price,
159        ))
160    }
161
162    /// Withdraw timelocked stake from a validator's staking pool.
163    pub async fn request_withdraw_timelocked_stake(
164        &self,
165        signer: IotaAddress,
166        timelocked_staked_iota: ObjectID,
167        gas: ObjectID,
168        gas_budget: u64,
169    ) -> anyhow::Result<TransactionData> {
170        let timelocked_staked_iota = self.get_object_ref(timelocked_staked_iota).await?;
171        let gas_price = self.0.get_reference_gas_price().await?;
172        let gas = self
173            .select_gas(signer, Some(gas), gas_budget, vec![], gas_price)
174            .await?;
175        TransactionData::new_move_call(
176            signer,
177            ObjectID::SYSTEM,
178            Identifier::TIMELOCKED_STAKING_MODULE,
179            WITHDRAW_TIMELOCKED_STAKE_FUN_NAME,
180            vec![],
181            gas,
182            vec![
183                CallArg::IOTA_SYSTEM_MUTABLE,
184                CallArg::ImmutableOrOwned(timelocked_staked_iota),
185            ],
186            gas_budget,
187            gas_price,
188        )
189    }
190}