sui_rpc/client/v2/
staking_rewards.rs1use prost_types::FieldMask;
2use sui_sdk_types::Address;
3
4use crate::client::DelegatedStake;
5use crate::field::FieldMaskUtil;
6use crate::proto::sui::rpc::v2::simulate_transaction_request::TransactionChecks;
7use crate::proto::sui::rpc::v2::Argument;
8use crate::proto::sui::rpc::v2::GetObjectRequest;
9use crate::proto::sui::rpc::v2::Input;
10use crate::proto::sui::rpc::v2::ListOwnedObjectsRequest;
11use crate::proto::sui::rpc::v2::MoveCall;
12use crate::proto::sui::rpc::v2::Object;
13use crate::proto::sui::rpc::v2::ProgrammableTransaction;
14use crate::proto::sui::rpc::v2::SimulateTransactionRequest;
15use crate::proto::sui::rpc::v2::Transaction;
16
17use super::Client;
18use super::Result;
19
20#[derive(serde::Deserialize, Debug)]
21struct StakedSui {
22 id: Address,
23 pool_id: Address,
25 stake_activation_epoch: u64,
27 principal: u64,
29}
30
31impl Client {
32 pub async fn get_delegated_stake(&mut self, staked_sui_id: &Address) -> Result<DelegatedStake> {
33 let maybe_staked_sui = self
34 .ledger_client()
35 .get_object(
36 GetObjectRequest::new(staked_sui_id)
37 .with_read_mask(FieldMask::from_str("contents")),
38 )
39 .await?
40 .into_inner()
41 .object
42 .unwrap_or_default();
43
44 let mut stakes = self
45 .try_create_delegated_stake_info(&[maybe_staked_sui])
46 .await?;
47 Ok(stakes.remove(0))
48 }
49
50 pub async fn list_delegated_stake(&mut self, address: &Address) -> Result<Vec<DelegatedStake>> {
51 const STAKED_SUI_TYPE: &str = "0x3::staking_pool::StakedSui";
52
53 let mut delegated_stakes = Vec::new();
54
55 let mut list_request = ListOwnedObjectsRequest::default()
56 .with_owner(address)
57 .with_page_size(500u32)
58 .with_read_mask(FieldMask::from_str("contents"))
59 .with_object_type(STAKED_SUI_TYPE);
60
61 loop {
62 let response = self
63 .state_client()
64 .list_owned_objects(list_request.clone())
65 .await?
66 .into_inner();
67
68 delegated_stakes.extend(
71 self.try_create_delegated_stake_info(&response.objects)
72 .await?,
73 );
74
75 if response.next_page_token.is_none() {
78 break;
79 } else {
80 list_request.page_token = response.next_page_token;
81 }
82 }
83
84 Ok(delegated_stakes)
85 }
86
87 async fn try_create_delegated_stake_info(
88 &mut self,
89 maybe_staked_sui: &[Object],
90 ) -> Result<Vec<DelegatedStake>> {
91 let staked_suis = maybe_staked_sui
92 .iter()
93 .map(|o| {
94 o.contents()
95 .deserialize::<StakedSui>()
96 .map_err(Into::into)
97 .map_err(tonic::Status::from_error)
98 })
99 .collect::<Result<Vec<StakedSui>>>()?;
100
101 let ids = staked_suis.iter().map(|s| s.id).collect::<Vec<_>>();
102 let pool_ids = staked_suis.iter().map(|s| s.pool_id).collect::<Vec<_>>();
103
104 let rewards = self.calculate_rewards(&ids).await?;
105 let validator_addresses = self.get_validator_address_by_pool_id(&pool_ids).await?;
106
107 Ok(staked_suis
108 .into_iter()
109 .zip(rewards)
110 .zip(validator_addresses)
111 .map(
112 |((staked_sui, (_id, rewards)), (_pool_id, validator_address))| DelegatedStake {
113 staked_sui_id: staked_sui.id,
114 validator_address,
115 staking_pool: staked_sui.pool_id,
116 activation_epoch: staked_sui.stake_activation_epoch,
117 principal: staked_sui.principal,
118 rewards,
119 },
120 )
121 .collect())
122 }
123
124 async fn calculate_rewards(
125 &mut self,
126 staked_sui_ids: &[Address],
127 ) -> Result<Vec<(Address, u64)>> {
128 let mut ptb = ProgrammableTransaction::default()
129 .with_inputs(vec![Input::default().with_object_id("0x5")]);
130 let system_object = Argument::new_input(0);
131
132 for id in staked_sui_ids {
133 let staked_sui = Argument::new_input(ptb.inputs.len() as u16);
134
135 ptb.inputs.push(Input::default().with_object_id(id));
136
137 ptb.commands.push(
138 MoveCall::default()
139 .with_package("0x3")
140 .with_module("sui_system")
141 .with_function("calculate_rewards")
142 .with_arguments(vec![system_object, staked_sui])
143 .into(),
144 );
145 }
146
147 let transaction = Transaction::default().with_kind(ptb).with_sender("0x0");
148
149 let resp = self
150 .execution_client()
151 .simulate_transaction(
152 SimulateTransactionRequest::new(transaction)
153 .with_read_mask(FieldMask::from_paths([
154 "command_outputs.return_values.value",
155 "transaction.effects.status",
156 ]))
157 .with_checks(TransactionChecks::Disabled),
158 )
159 .await?
160 .into_inner();
161
162 if !resp.transaction().effects().status().success() {
163 return Err(tonic::Status::from_error(
164 "transaction execution failed".into(),
165 ));
166 }
167
168 if staked_sui_ids.len() != resp.command_outputs.len() {
169 return Err(tonic::Status::from_error(
170 "missing transaction command_outputs".into(),
171 ));
172 }
173
174 let mut rewards = Vec::with_capacity(staked_sui_ids.len());
175
176 for (id, output) in staked_sui_ids.iter().zip(resp.command_outputs) {
177 let bcs_rewards = output
178 .return_values
179 .first()
180 .and_then(|o| o.value_opt())
181 .ok_or_else(|| tonic::Status::from_error("missing bcs".into()))?;
182
183 let reward =
184 if bcs_rewards.name() == "u64" && bcs_rewards.value().len() == size_of::<u64>() {
185 u64::from_le_bytes(bcs_rewards.value().try_into().unwrap())
186 } else {
187 return Err(tonic::Status::from_error("missing rewards".into()));
188 };
189 rewards.push((*id, reward));
190 }
191
192 Ok(rewards)
193 }
194
195 async fn get_validator_address_by_pool_id(
196 &mut self,
197 pool_ids: &[Address],
198 ) -> Result<Vec<(Address, Address)>> {
199 let mut ptb = ProgrammableTransaction::default()
200 .with_inputs(vec![Input::default().with_object_id("0x5")]);
201 let system_object = Argument::new_input(0);
202
203 for id in pool_ids {
204 let pool_id = Argument::new_input(ptb.inputs.len() as u16);
205
206 ptb.inputs
207 .push(Input::default().with_pure(id.into_inner().to_vec()));
208
209 ptb.commands.push(
210 MoveCall::default()
211 .with_package("0x3")
212 .with_module("sui_system")
213 .with_function("validator_address_by_pool_id")
214 .with_arguments(vec![system_object, pool_id])
215 .into(),
216 );
217 }
218
219 let transaction = Transaction::default().with_kind(ptb).with_sender("0x0");
220
221 let resp = self
222 .execution_client()
223 .simulate_transaction(
224 SimulateTransactionRequest::new(transaction)
225 .with_read_mask(FieldMask::from_paths([
226 "command_outputs.return_values.value",
227 "transaction.effects.status",
228 ]))
229 .with_checks(TransactionChecks::Disabled),
230 )
231 .await?
232 .into_inner();
233
234 if !resp.transaction().effects().status().success() {
235 return Err(tonic::Status::from_error(
236 "transaction execution failed".into(),
237 ));
238 }
239
240 if pool_ids.len() != resp.command_outputs.len() {
241 return Err(tonic::Status::from_error(
242 "missing transaction command_outputs".into(),
243 ));
244 }
245
246 let mut addresses = Vec::with_capacity(pool_ids.len());
247
248 for (id, output) in pool_ids.iter().zip(resp.command_outputs) {
249 let validator_address = output
250 .return_values
251 .first()
252 .and_then(|o| o.value_opt())
253 .ok_or_else(|| tonic::Status::from_error("missing bcs".into()))?;
254
255 let address = if validator_address.name() == "address"
256 && validator_address.value().len() == Address::LENGTH
257 {
258 Address::from_bytes(validator_address.value())
259 .map_err(|e| tonic::Status::from_error(e.into()))?
260 } else {
261 return Err(tonic::Status::from_error("missing address".into()));
262 };
263 addresses.push((*id, address));
264 }
265
266 Ok(addresses)
267 }
268}