-
Notifications
You must be signed in to change notification settings - Fork 129
Expand file tree
/
Copy pathcommon.rs
More file actions
265 lines (241 loc) · 7.7 KB
/
Copy pathcommon.rs
File metadata and controls
265 lines (241 loc) · 7.7 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
use std::str::FromStr;
use ain_dftx::{Currency, Token};
use bitcoin::{Address, Network, ScriptBuf};
use defichain_rpc::json::token::TokenInfo;
use rust_decimal::Decimal;
use rust_decimal_macros::dec;
use snafu::OptionExt;
use super::query::PaginationQuery;
use crate::{
error::{
InvalidAmountSnafu, InvalidFixedIntervalPriceSnafu, InvalidPoolPairSymbolSnafu,
InvalidTokenCurrencySnafu,
},
hex_encoder::as_sha256,
Result,
};
#[must_use]
pub fn parse_display_symbol(token_info: &TokenInfo) -> String {
if token_info.is_lps {
let tokens: Vec<&str> = token_info.symbol.split('-').collect();
if tokens.len() == 2 {
return format!(
"{}-{}",
parse_dat_symbol(tokens[0]),
parse_dat_symbol(tokens[1])
);
}
} else if token_info.is_dat {
return parse_dat_symbol(&token_info.symbol);
}
token_info.symbol.clone()
}
#[must_use]
pub fn parse_dat_symbol(symbol: &str) -> String {
let special_symbols = ["DUSD", "DFI", "csETH"];
if special_symbols.contains(&symbol) {
symbol.to_string()
} else {
format!("d{symbol}")
}
}
pub fn parse_pool_pair_symbol(item: &str) -> Result<(String, String)> {
let mut parts = item.split('-');
let a = parts
.next()
.context(InvalidPoolPairSymbolSnafu { item })?
.to_string();
let b = parts
.next()
.context(InvalidPoolPairSymbolSnafu { item })?
.to_string();
Ok((a, b))
}
pub fn parse_token_currency(item: &str) -> Result<(Token, Currency)> {
let mut parts = item.split('-');
let token = parts
.next()
.context(InvalidTokenCurrencySnafu { item })?
.to_string();
let currency = parts
.next()
.context(InvalidTokenCurrencySnafu { item })?
.to_string();
Ok((token, currency))
}
pub fn parse_fixed_interval_price(item: &str) -> Result<(Token, Currency)> {
let mut parts = item.split('/');
let token = parts
.next()
.context(InvalidFixedIntervalPriceSnafu { item })?
.to_string();
let currency = parts
.next()
.context(InvalidFixedIntervalPriceSnafu { item })?
.to_string();
Ok((token, currency))
}
pub fn parse_amount(item: &str) -> Result<(String, String)> {
let mut parts = item.split('@');
let amount = parts
.next()
.context(InvalidAmountSnafu { item })?
.to_string();
let symbol = parts
.next()
.context(InvalidAmountSnafu { item })?
.to_string();
Ok((amount, symbol))
}
pub fn parse_query_height_txno(item: &str) -> Result<(u32, usize)> {
let mut parts = item.split('-');
let height = parts.next().context(InvalidAmountSnafu { item })?;
let txno = parts.next().context(InvalidAmountSnafu { item })?;
let height = height.parse::<u32>()?;
let txno = txno.parse::<usize>()?;
Ok((height, txno))
}
#[must_use]
pub fn format_number(v: Decimal) -> String {
if v == dec!(0) {
"0".to_string()
} else {
format!("{v:.8}")
}
}
pub fn from_script(script: ScriptBuf, network: Network) -> Result<String> {
let script = script.as_script();
let address = Address::from_script(script, network)?.to_string();
Ok(address)
}
pub fn to_script(address: &str, network: Network) -> Result<ScriptBuf> {
let addr = Address::from_str(address)?.require_network(network)?;
Ok(ScriptBuf::from(addr))
}
pub fn address_to_hid(address: &str, network: Network) -> Result<[u8; 32]> {
let script = to_script(address, network)?;
let bytes = script.to_bytes();
Ok(as_sha256(bytes))
}
/// Finds the balance of a specified token symbol within a list of token strings.
///
/// This function iterates through a vector of token strings, where each string
/// represents an amount followed by a token symbol in the format "amount@symbol".
/// It searches for the specified token symbol and returns the corresponding balance.
/// If the token symbol is not found or if there are any parsing errors, it returns 0.
///
/// # Arguments
///
/// * `tokens` - A vector of strings representing token amounts with their symbols.
/// * `symbol` - A reference to a string representing the token symbol to find the balance for.
///
/// # Examples
///
/// ```
/// use rust_decimal::Decimal;
/// use rust_decimal_macros::dec;
/// use ain_ocean::api::{common::find_token_balance};
///
/// let tokens = vec![
/// "557.35080849@DFI".to_string(),
/// "9.98000000@BTC".to_string(),
/// "421.46947098@DUSD".to_string()
/// ];
/// let balance = find_token_balance(tokens, "DFI");
/// assert_eq!(balance, dec!(557.35080849));
/// ```
///
/// # Returns
///
/// The balance of the specified token symbol if found; otherwise, returns 0.
#[must_use]
pub fn find_token_balance(tokens: Vec<String>, symbol: &str) -> Decimal {
tokens
.iter()
.find_map(|t| {
t.ends_with(symbol)
.then(|| t.split('@').next().and_then(|v| v.parse::<Decimal>().ok()))
.flatten()
})
.unwrap_or_default()
}
/// Provides a simulated pagination mechanism for iterators where native pagination is not available.
///
/// This trait extends any Rust iterator to include a `paginate` method, allowing for pseudo-pagination
/// based on custom logic. It's should only be used to query defid list* RPC that don't implement native pagination
///
/// # Warning
///
/// This method should be used cautiously, as it involves retrieving all data from the data source
/// before applying pagination. This can lead to significant performance and resource usage issues,
/// especially with large datasets. It is recommended to use this approach only defid does not accept
/// any pagination parameter.
///
/// # Parameters
///
/// - `query`: A reference to `PaginationQuery`
/// - `skip_while`: A closure that determines the starting point of data to consider, mimicking the
/// 'start' parameter in traditional pagination. Once an item fails this condition, pagination starts.
///
/// # Example
///
/// This example is illustrative only and should not be executed directly as it involves API calls.
///
/// ```rust,ignore
/// use ain_ocean::api::common::Paginate;
///
/// let query = PaginationQuery {
/// next: Some(1)
/// limit: 3
/// };
///
/// let skip_while = |el: &LoanSchemeResult| match &query.next {
/// None => false,
/// Some(v) => v != &el.id,
/// };
/// let res: Vec<_> = ctx
/// .client
/// .list_loan_schemes()
/// .await?
/// .into_iter()
/// .fake_paginate(&query, skip_while)
/// .collect();
///
/// assert!(res.len() <= query.size, "The result should not contain more items than the specified limit");
/// assert!(res[0].id > query.next.unwrap(), "The result should start after the requested start id");
/// ```
pub trait Paginate<'a, T>: Iterator<Item = T> + Sized {
fn fake_paginate<F>(
self,
query: &PaginationQuery,
skip_while: F,
) -> Box<dyn Iterator<Item = T> + 'a>
where
F: FnMut(&T) -> bool + 'a;
fn paginate(self, query: &PaginationQuery) -> Box<dyn Iterator<Item = T> + 'a>;
}
impl<'a, T, I> Paginate<'a, T> for I
where
I: Iterator<Item = T> + 'a,
{
fn fake_paginate<F>(
self,
query: &PaginationQuery,
skip_while: F,
) -> Box<dyn Iterator<Item = T> + 'a>
where
F: FnMut(&T) -> bool + 'a,
{
Box::new(
self.skip_while(skip_while)
.skip(usize::from(query.next.is_some()))
.take(query.size),
)
}
fn paginate(self, query: &PaginationQuery) -> Box<dyn Iterator<Item = T> + 'a> {
Box::new(
self.skip(usize::from(query.next.is_some()))
.take(query.size),
)
}
}