1use celestia_proto::cosmos::base::query::v1beta1::{
2 PageRequest as RawPageRequest, PageResponse as RawPageResponse,
3};
4use celestia_proto::cosmos::staking::v1beta1::{
5 Delegation as RawDelegation, DelegationResponse as RawDelegationResponse,
6 QueryDelegationResponse as RawQueryDelegationResponse,
7 QueryRedelegationsResponse as RawQueryRedelegationsResponse,
8 QueryUnbondingDelegationResponse as RawQueryUnbondingDelegationResponse,
9 Redelegation as RawRedelegation, RedelegationEntry as RawRedelegationEntry,
10 RedelegationEntryResponse as RawRedelegationEntryResponse,
11 RedelegationResponse as RawRedelegationResponse, UnbondingDelegation as RawUnbondingDelegation,
12 UnbondingDelegationEntry as RawUnbondingDelegationEntry,
13};
14use rust_decimal::Decimal;
15use serde::{Deserialize, Serialize};
16use tendermint::Time;
17
18use crate::Height;
19use crate::error::{Error, Result};
20use crate::state::{AccAddress, Coin, ValAddress};
21
22pub type PageRequest = RawPageRequest;
24pub type PageResponse = RawPageResponse;
26
27#[derive(Debug, Clone, Serialize, Deserialize)]
29#[serde(
30 try_from = "RawQueryDelegationResponse",
31 into = "RawQueryDelegationResponse"
32)]
33pub struct QueryDelegationResponse {
34 pub response: DelegationResponse,
36}
37
38#[derive(Debug, Clone, Serialize, Deserialize)]
43pub struct DelegationResponse {
44 pub delegation: Delegation,
46 pub balance: u64,
48}
49
50#[derive(Debug, Clone, Serialize, Deserialize)]
56pub struct Delegation {
57 pub delegator_address: AccAddress,
59 pub validator_address: ValAddress,
61 pub shares: Decimal,
63}
64
65impl TryFrom<RawQueryDelegationResponse> for QueryDelegationResponse {
66 type Error = Error;
67
68 fn try_from(value: RawQueryDelegationResponse) -> Result<QueryDelegationResponse> {
69 let resp = value
70 .delegation_response
71 .ok_or(Error::MissingDelegationResponse)?;
72
73 let delegation = resp
74 .delegation
75 .ok_or(Error::MissingDelegation)
76 .and_then(|val| {
77 Ok(Delegation {
78 delegator_address: val.delegator_address.parse()?,
79 validator_address: val.validator_address.parse()?,
80 shares: parse_cosmos_dec(&val.shares)?,
81 })
82 })?;
83
84 let balance: Coin = resp.balance.ok_or(Error::MissingBalance)?.try_into()?;
85
86 Ok(QueryDelegationResponse {
87 response: DelegationResponse {
88 delegation,
89 balance: balance.amount(),
90 },
91 })
92 }
93}
94
95impl From<QueryDelegationResponse> for RawQueryDelegationResponse {
96 fn from(value: QueryDelegationResponse) -> Self {
97 let balance = Coin::utia(value.response.balance);
98 let delegation = value.response.delegation;
99 RawQueryDelegationResponse {
100 delegation_response: Some(RawDelegationResponse {
101 delegation: Some(RawDelegation {
102 delegator_address: delegation.delegator_address.to_string(),
103 validator_address: delegation.validator_address.to_string(),
104 shares: cosmos_dec_to_string(&delegation.shares),
105 }),
106 balance: Some(balance.into()),
107 }),
108 }
109 }
110}
111
112#[derive(Debug, Clone, Serialize, Deserialize)]
114#[serde(
115 try_from = "RawQueryUnbondingDelegationResponse",
116 into = "RawQueryUnbondingDelegationResponse"
117)]
118pub struct QueryUnbondingDelegationResponse {
119 pub unbond: UnbondingDelegation,
121}
122
123#[derive(Debug, Clone, Serialize, Deserialize)]
125pub struct UnbondingDelegation {
126 pub delegator_address: AccAddress,
128 pub validator_address: ValAddress,
130 pub entries: Vec<UnbondingDelegationEntry>,
132}
133
134#[derive(Debug, Clone, Serialize, Deserialize)]
136pub struct UnbondingDelegationEntry {
137 pub creation_height: Height,
139 pub completion_time: Time,
141 pub initial_balance: u64,
143 pub balance: u64,
145 pub unbonding_id: u64,
147 pub unbonding_on_hold_ref_count: i64,
149}
150
151impl From<QueryUnbondingDelegationResponse> for RawQueryUnbondingDelegationResponse {
152 fn from(value: QueryUnbondingDelegationResponse) -> Self {
153 RawQueryUnbondingDelegationResponse {
154 unbond: Some(RawUnbondingDelegation {
155 delegator_address: value.unbond.delegator_address.to_string(),
156 validator_address: value.unbond.validator_address.to_string(),
157 entries: value.unbond.entries.into_iter().map(Into::into).collect(),
158 }),
159 }
160 }
161}
162
163impl TryFrom<RawQueryUnbondingDelegationResponse> for QueryUnbondingDelegationResponse {
164 type Error = Error;
165
166 fn try_from(value: RawQueryUnbondingDelegationResponse) -> Result<Self, Self::Error> {
167 let unbond = value.unbond.ok_or(Error::MissingUnbond)?;
168
169 let delegator_address = unbond.delegator_address.parse()?;
170 let validator_address = unbond.validator_address.parse()?;
171
172 let entries = unbond
173 .entries
174 .into_iter()
175 .map(|entry| {
176 let creation_height = entry.creation_height.try_into()?;
177
178 let completion_time = entry
179 .completion_time
180 .ok_or(Error::MissingCompletionTime)?
181 .try_into()?;
182
183 let initial_balance = entry
184 .initial_balance
185 .parse()
186 .map_err(|_| Error::InvalidBalance(entry.initial_balance))?;
187
188 let balance = entry
189 .balance
190 .parse()
191 .map_err(|_| Error::InvalidBalance(entry.balance))?;
192
193 Ok(UnbondingDelegationEntry {
194 creation_height,
195 completion_time,
196 initial_balance,
197 balance,
198 unbonding_id: entry.unbonding_id,
199 unbonding_on_hold_ref_count: entry.unbonding_on_hold_ref_count,
200 })
201 })
202 .collect::<Result<Vec<_>, Error>>()?;
203
204 Ok(QueryUnbondingDelegationResponse {
205 unbond: UnbondingDelegation {
206 delegator_address,
207 validator_address,
208 entries,
209 },
210 })
211 }
212}
213
214impl From<UnbondingDelegationEntry> for RawUnbondingDelegationEntry {
215 fn from(value: UnbondingDelegationEntry) -> Self {
216 RawUnbondingDelegationEntry {
217 creation_height: value.creation_height.into(),
218 completion_time: Some(value.completion_time.into()),
219 initial_balance: value.initial_balance.to_string(),
220 balance: value.balance.to_string(),
221 unbonding_id: value.unbonding_id,
222 unbonding_on_hold_ref_count: value.unbonding_on_hold_ref_count,
223 }
224 }
225}
226
227#[derive(Debug, Clone, Serialize, Deserialize)]
229#[serde(
230 try_from = "RawQueryRedelegationsResponse",
231 into = "RawQueryRedelegationsResponse"
232)]
233pub struct QueryRedelegationsResponse {
234 pub responses: Vec<RedelegationResponse>,
236 pub pagination: Option<PageResponse>,
238}
239
240#[derive(Debug, Clone, Serialize, Deserialize)]
245pub struct RedelegationResponse {
246 pub redelegation: Redelegation,
248
249 pub entries: Vec<RedelegationEntryResponse>,
254}
255
256#[derive(Debug, Clone, Serialize, Deserialize)]
260pub struct Redelegation {
261 pub delegator_address: AccAddress,
263 pub src_validator_address: ValAddress,
265 pub dest_validator_address: ValAddress,
267 pub entries: Vec<RedelegationEntry>,
269}
270
271#[derive(Debug, Clone, Serialize, Deserialize)]
276pub struct RedelegationEntryResponse {
277 pub redelegation_entry: RedelegationEntry,
279 pub balance: u64,
281}
282
283#[derive(Debug, Clone, Serialize, Deserialize)]
285pub struct RedelegationEntry {
286 pub creation_height: Height,
288 pub completion_time: Time,
290 pub initial_balance: u64,
292 pub dest_shares: Decimal,
294 pub unbonding_id: u64,
296 pub unbonding_on_hold_ref_count: i64,
298}
299
300impl TryFrom<RawRedelegation> for Redelegation {
301 type Error = Error;
302
303 fn try_from(value: RawRedelegation) -> Result<Self, Self::Error> {
304 let entries = value
305 .entries
306 .into_iter()
307 .map(|entry| entry.try_into())
308 .collect::<Result<Vec<_>>>()?;
309
310 Ok(Redelegation {
311 delegator_address: value.delegator_address.parse()?,
312 src_validator_address: value.validator_src_address.parse()?,
313 dest_validator_address: value.validator_dst_address.parse()?,
314 entries,
315 })
316 }
317}
318
319impl From<RedelegationEntry> for RawRedelegationEntry {
320 fn from(value: RedelegationEntry) -> Self {
321 RawRedelegationEntry {
322 creation_height: value.creation_height.into(),
323 completion_time: Some(value.completion_time.into()),
324 initial_balance: value.initial_balance.to_string(),
325 shares_dst: cosmos_dec_to_string(&value.dest_shares),
326 unbonding_id: value.unbonding_id,
327 unbonding_on_hold_ref_count: value.unbonding_on_hold_ref_count,
328 }
329 }
330}
331
332impl TryFrom<RawRedelegationEntry> for RedelegationEntry {
333 type Error = Error;
334
335 fn try_from(value: RawRedelegationEntry) -> Result<Self, Self::Error> {
336 let initial_balance = value
337 .initial_balance
338 .as_str()
339 .parse::<u64>()
340 .map_err(|_| Error::InvalidBalance(value.initial_balance))?;
341
342 Ok(RedelegationEntry {
343 creation_height: value.creation_height.try_into()?,
344 completion_time: value
345 .completion_time
346 .ok_or(Error::MissingCompletionTime)?
347 .try_into()?,
348 initial_balance,
349 dest_shares: parse_cosmos_dec(&value.shares_dst)?,
350 unbonding_id: value.unbonding_id,
351 unbonding_on_hold_ref_count: value.unbonding_on_hold_ref_count,
352 })
353 }
354}
355
356impl From<QueryRedelegationsResponse> for RawQueryRedelegationsResponse {
357 fn from(value: QueryRedelegationsResponse) -> Self {
358 RawQueryRedelegationsResponse {
359 redelegation_responses: value
360 .responses
361 .into_iter()
362 .map(|response| RawRedelegationResponse {
363 redelegation: Some(RawRedelegation {
364 delegator_address: response.redelegation.delegator_address.to_string(),
365 validator_src_address: response
366 .redelegation
367 .src_validator_address
368 .to_string(),
369 validator_dst_address: response
370 .redelegation
371 .dest_validator_address
372 .to_string(),
373 entries: response
374 .redelegation
375 .entries
376 .into_iter()
377 .map(Into::into)
378 .collect(),
379 }),
380 entries: response
381 .entries
382 .into_iter()
383 .map(|entry| RawRedelegationEntryResponse {
384 redelegation_entry: Some(entry.redelegation_entry.into()),
385 balance: entry.balance.to_string(),
386 })
387 .collect(),
388 })
389 .collect(),
390 pagination: value.pagination,
391 }
392 }
393}
394
395impl TryFrom<RawQueryRedelegationsResponse> for QueryRedelegationsResponse {
396 type Error = Error;
397
398 fn try_from(value: RawQueryRedelegationsResponse) -> Result<Self, Self::Error> {
399 let redelegation_responses = value
400 .redelegation_responses
401 .into_iter()
402 .map(|resp| {
403 let redelegation = resp
404 .redelegation
405 .ok_or(Error::MissingRedelegation)?
406 .try_into()?;
407
408 let entries = resp
409 .entries
410 .into_iter()
411 .map(|resp| {
412 let redelegation_entry = resp
413 .redelegation_entry
414 .ok_or(Error::MissingRedelegationEntry)?
415 .try_into()?;
416
417 let balance = resp
418 .balance
419 .as_str()
420 .parse::<u64>()
421 .map_err(|_| Error::InvalidBalance(resp.balance))?;
422
423 Ok(RedelegationEntryResponse {
424 redelegation_entry,
425 balance,
426 })
427 })
428 .collect::<Result<Vec<_>>>()?;
429
430 Ok(RedelegationResponse {
431 redelegation,
432 entries,
433 })
434 })
435 .collect::<Result<Vec<_>>>()?;
436
437 Ok(QueryRedelegationsResponse {
438 responses: redelegation_responses,
439 pagination: value.pagination,
440 })
441 }
442}
443
444const FIXED_POINT_EXPONENT: u32 = 18;
445
446fn parse_cosmos_dec(s: &str) -> Result<Decimal> {
452 let val = s
453 .parse::<i128>()
454 .map_err(|_| Error::InvalidCosmosDecimal(s.to_owned()))?;
455 let val = Decimal::try_from_i128_with_scale(val, FIXED_POINT_EXPONENT)
456 .map_err(|_| Error::InvalidCosmosDecimal(s.to_owned()))?;
457 Ok(val)
458}
459
460fn cosmos_dec_to_string(d: &Decimal) -> String {
462 let mantissa = d.mantissa();
463 debug_assert_eq!(d.scale(), FIXED_POINT_EXPONENT);
464 format!("{mantissa}")
465}
466
467#[cfg(test)]
468mod tests {
469 use super::*;
470
471 #[test]
472 fn string_decimal_round_trip() {
473 let epsilon = "1";
474 let v = parse_cosmos_dec(epsilon).unwrap();
475 assert_eq!(cosmos_dec_to_string(&v), epsilon);
476 assert_eq!(v, Decimal::new(1, FIXED_POINT_EXPONENT));
477
478 let unit = "1000000000000000000";
479 let v = parse_cosmos_dec(unit).unwrap();
480 assert_eq!(cosmos_dec_to_string(&v), unit);
481 assert_eq!(u32::try_from(v).unwrap(), 1);
482 }
483}