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