near_api/common/
utils.rs

1// https://github.com/near/near-token-rs/blob/3feafec624e7d1028ed00695f2acf87e1d823fa7/src/utils.rs#L1-L49
2
3use crate::errors::DecimalNumberParsingError;
4
5/// Converts [crate::Data]<[u128]>] to [crate::NearToken].
6pub const fn near_data_to_near_token(data: crate::Data<u128>) -> crate::NearToken {
7    crate::NearToken::from_yoctonear(data.data)
8}
9
10/// Parsing decimal numbers from `&str` type in `u128`.
11/// Function also takes a value of metric prefix in u128 type.
12/// `parse_str` use the `u128` type, and have the same max and min values.
13///
14/// If the fractional part is longer than several zeros in the prefix, it will return the error `DecimalNumberParsingError::LongFractional`.
15///
16/// If the string slice has invalid chars, it will return the error `DecimalNumberParsingError::InvalidNumber`.
17///
18/// If the whole part of the number has a value more than the `u64` maximum value, it will return the error `DecimalNumberParsingError::LongWhole`.
19pub fn parse_decimal_number(s: &str, pref_const: u128) -> Result<u128, DecimalNumberParsingError> {
20    let (int, fraction) = if let Some((whole, fractional)) = s.trim().split_once('.') {
21        let int: u128 = whole
22            .parse()
23            .map_err(|_| DecimalNumberParsingError::InvalidNumber(s.to_owned()))?;
24        let mut fraction: u128 = fractional
25            .parse()
26            .map_err(|_| DecimalNumberParsingError::InvalidNumber(s.to_owned()))?;
27        let len = u32::try_from(fractional.len())
28            .map_err(|_| DecimalNumberParsingError::InvalidNumber(s.to_owned()))?;
29        fraction = fraction
30            .checked_mul(
31                pref_const
32                    .checked_div(10u128.checked_pow(len).ok_or_else(|| {
33                        DecimalNumberParsingError::LongFractional(fractional.to_owned())
34                    })?)
35                    .filter(|n| *n != 0u128)
36                    .ok_or_else(|| {
37                        DecimalNumberParsingError::LongFractional(fractional.to_owned())
38                    })?,
39            )
40            .ok_or_else(|| DecimalNumberParsingError::LongFractional(fractional.to_owned()))?;
41        (int, fraction)
42    } else {
43        let int: u128 = s
44            .parse()
45            .map_err(|_| DecimalNumberParsingError::InvalidNumber(s.to_owned()))?;
46        (int, 0)
47    };
48    let result = fraction
49        .checked_add(
50            int.checked_mul(pref_const)
51                .ok_or_else(|| DecimalNumberParsingError::LongWhole(int.to_string()))?,
52        )
53        .ok_or_else(|| DecimalNumberParsingError::LongWhole(int.to_string()))?;
54    Ok(result)
55}
56
57pub fn is_critical_blocks_error(
58    err: &near_jsonrpc_client::errors::JsonRpcError<
59        near_jsonrpc_primitives::types::blocks::RpcBlockError,
60    >,
61) -> bool {
62    is_critical_json_rpc_error(err, |err| match err {
63        near_jsonrpc_client::methods::block::RpcBlockError::UnknownBlock { .. }
64        | near_jsonrpc_client::methods::block::RpcBlockError::NotSyncedYet
65        | near_jsonrpc_client::methods::block::RpcBlockError::InternalError { .. } => true,
66    })
67}
68
69pub fn is_critical_validator_error(
70    err: &near_jsonrpc_client::errors::JsonRpcError<
71        near_jsonrpc_primitives::types::validator::RpcValidatorError,
72    >,
73) -> bool {
74    is_critical_json_rpc_error(err, |err| match err {
75        near_jsonrpc_primitives::types::validator::RpcValidatorError::UnknownEpoch
76        | near_jsonrpc_primitives::types::validator::RpcValidatorError::ValidatorInfoUnavailable
77        | near_jsonrpc_primitives::types::validator::RpcValidatorError::InternalError { .. } => {
78            true
79        }
80    })
81}
82
83pub fn is_critical_query_error(
84    err: &near_jsonrpc_client::errors::JsonRpcError<
85        near_jsonrpc_primitives::types::query::RpcQueryError,
86    >,
87) -> bool {
88    is_critical_json_rpc_error(err, |err| match err {
89        near_jsonrpc_primitives::types::query::RpcQueryError::NoSyncedBlocks
90        | near_jsonrpc_primitives::types::query::RpcQueryError::UnavailableShard { .. }
91        | near_jsonrpc_primitives::types::query::RpcQueryError::GarbageCollectedBlock { .. }
92        | near_jsonrpc_primitives::types::query::RpcQueryError::UnknownBlock { .. }
93        | near_jsonrpc_primitives::types::query::RpcQueryError::InvalidAccount { .. }
94        | near_jsonrpc_primitives::types::query::RpcQueryError::UnknownAccount { .. }
95        | near_jsonrpc_primitives::types::query::RpcQueryError::NoContractCode { .. }
96        | near_jsonrpc_primitives::types::query::RpcQueryError::TooLargeContractState { .. }
97        | near_jsonrpc_primitives::types::query::RpcQueryError::UnknownAccessKey { .. }
98        | near_jsonrpc_primitives::types::query::RpcQueryError::ContractExecutionError { .. }
99        | near_jsonrpc_primitives::types::query::RpcQueryError::InternalError { .. } => true,
100    })
101}
102
103pub fn is_critical_transaction_error(
104    err: &near_jsonrpc_client::errors::JsonRpcError<
105        near_jsonrpc_client::methods::broadcast_tx_commit::RpcTransactionError,
106    >,
107) -> bool {
108    is_critical_json_rpc_error(err, |err| {
109        match err {
110            near_jsonrpc_client::methods::broadcast_tx_commit::RpcTransactionError::TimeoutError => {
111                false
112            }
113            near_jsonrpc_client::methods::broadcast_tx_commit::RpcTransactionError::InvalidTransaction { .. } |
114                near_jsonrpc_client::methods::broadcast_tx_commit::RpcTransactionError::DoesNotTrackShard |
115                    near_jsonrpc_client::methods::broadcast_tx_commit::RpcTransactionError::RequestRouted{..} |
116                        near_jsonrpc_client::methods::broadcast_tx_commit::RpcTransactionError::UnknownTransaction{..} |
117                            near_jsonrpc_client::methods::broadcast_tx_commit::RpcTransactionError::InternalError{..} => {
118                true
119            }
120        }
121    })
122}
123
124fn is_critical_json_rpc_error<T>(
125    err: &near_jsonrpc_client::errors::JsonRpcError<T>,
126    is_critical_t: impl Fn(&T) -> bool,
127) -> bool {
128    match err {
129        near_jsonrpc_client::errors::JsonRpcError::TransportError(_rpc_transport_error) => {
130            false
131        }
132        near_jsonrpc_client::errors::JsonRpcError::ServerError(rpc_server_error) => match rpc_server_error {
133            near_jsonrpc_client::errors::JsonRpcServerError::HandlerError(rpc_transaction_error) => is_critical_t(rpc_transaction_error),
134            near_jsonrpc_client::errors::JsonRpcServerError::RequestValidationError(..) |
135            near_jsonrpc_client::errors::JsonRpcServerError::NonContextualError(..) => {
136                true
137            }
138            near_jsonrpc_client::errors::JsonRpcServerError::InternalError{ .. } => {
139                false
140            }
141            near_jsonrpc_client::errors::JsonRpcServerError::ResponseStatusError(json_rpc_server_response_status_error) => match json_rpc_server_response_status_error {
142                near_jsonrpc_client::errors::JsonRpcServerResponseStatusError::Unauthorized |
143                near_jsonrpc_client::errors::JsonRpcServerResponseStatusError::Unexpected{..} |
144                near_jsonrpc_client::errors::JsonRpcServerResponseStatusError::BadRequest => {
145                    true
146                }
147                near_jsonrpc_client::errors::JsonRpcServerResponseStatusError::TimeoutError |
148                near_jsonrpc_client::errors::JsonRpcServerResponseStatusError::ServiceUnavailable |
149                near_jsonrpc_client::errors::JsonRpcServerResponseStatusError::TooManyRequests => {
150                    false
151                }
152            }
153        }
154    }
155}
156
157#[cfg(test)]
158mod tests {
159    use super::*;
160
161    const TEST: [(u128, &str, u128); 6] = [
162        (129_380_000_001_u128, "129.380000001", 10u128.pow(9)),
163        (
164            12_938_000_000_100_000_000_u128,
165            "12938000000.1",
166            10u128.pow(9),
167        ),
168        (129_380_000_001_u128, "0.129380000001", 10u128.pow(12)),
169        (129_380_000_001_000_u128, "129.380000001000", 10u128.pow(12)),
170        (
171            9_488_129_380_000_001_u128,
172            "9488.129380000001",
173            10u128.pow(12),
174        ),
175        (129_380_000_001_u128, "00.129380000001", 10u128.pow(12)),
176    ];
177
178    #[test]
179    fn parse_test() {
180        for (expected_value, str_value, precision) in TEST {
181            let parsed_value = parse_decimal_number(str_value, precision).unwrap();
182            assert_eq!(parsed_value, expected_value)
183        }
184    }
185
186    #[test]
187    fn test_long_fraction() {
188        let data = "1.23456";
189        let prefix = 10000u128;
190        assert_eq!(
191            parse_decimal_number(data, prefix),
192            Err(DecimalNumberParsingError::LongFractional(23456.to_string()))
193        );
194    }
195
196    #[test]
197    fn invalid_number_whole() {
198        let num = "1h4.7859";
199        let prefix: u128 = 10000;
200        assert_eq!(
201            parse_decimal_number(num, prefix),
202            Err(DecimalNumberParsingError::InvalidNumber(
203                "1h4.7859".to_owned()
204            ))
205        );
206    }
207    #[test]
208    fn invalid_number_fraction() {
209        let num = "14.785h9";
210        let prefix: u128 = 10000;
211        assert_eq!(
212            parse_decimal_number(num, prefix),
213            Err(DecimalNumberParsingError::InvalidNumber(
214                "14.785h9".to_owned()
215            ))
216        );
217    }
218
219    #[test]
220    fn max_long_fraction() {
221        let max_data = 10u128.pow(17) + 1;
222        let data = "1.".to_string() + max_data.to_string().as_str();
223        let prefix = 10u128.pow(17);
224        assert_eq!(
225            parse_decimal_number(data.as_str(), prefix),
226            Err(DecimalNumberParsingError::LongFractional(
227                max_data.to_string()
228            ))
229        );
230    }
231
232    #[test]
233    fn parse_u128_error_test() {
234        let test_data = u128::MAX.to_string();
235        let gas = parse_decimal_number(&test_data, 10u128.pow(9));
236        assert_eq!(
237            gas,
238            Err(DecimalNumberParsingError::LongWhole(u128::MAX.to_string()))
239        );
240    }
241
242    #[test]
243    fn test() {
244        let data = "1.000000000000000000000000000000000000001";
245        let prefix = 100u128;
246        assert_eq!(
247            parse_decimal_number(data, prefix),
248            Err(DecimalNumberParsingError::LongFractional(
249                "000000000000000000000000000000000000001".to_string()
250            ))
251        );
252    }
253}