nautilus_blockchain/
decode.rs

1// -------------------------------------------------------------------------------------------------
2//  Copyright (C) 2015-2025 Posei Systems Pty Ltd. All rights reserved.
3//  https://poseitrader.io
4//
5//  Licensed under the GNU Lesser General Public License Version 3.0 (the "License");
6//  You may not use this file except in compliance with the License.
7//  You may obtain a copy of the License at https://www.gnu.org/licenses/lgpl-3.0.en.html
8//
9//  Unless required by applicable law or agreed to in writing, software
10//  distributed under the License is distributed on an "AS IS" BASIS,
11//  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12//  See the License for the specific language governing permissions and
13//  limitations under the License.
14// -------------------------------------------------------------------------------------------------
15
16use alloy::primitives::U256;
17use nautilus_model::types::{fixed::FIXED_PRECISION, price::Price, quantity::Quantity};
18
19use crate::math::convert_u256_to_f64;
20
21/// Convert a `U256` amount to [`Quantity`].
22///
23/// - If `decimals == 18` the value represents WEI and we leverage the dedicated
24///   `Price::from_wei` constructor for loss-less conversion.
25/// - For other precisions we fall back to a floating-point conversion identical
26///   to the pre-existing path in `convert_u256_to_f64` and then construct a
27///   `Quantity` with the smaller `decimals` (clamped to `FIXED_PRECISION`).
28///
29/// # Errors
30///
31/// Returns an error when the helper must fall back to the floating-point path
32/// (i.e. `decimals != 18`) and the provided `amount` cannot be converted to an
33/// `f64` (see `convert_u256_to_f64`).
34pub fn u256_to_quantity(amount: U256, decimals: u8) -> anyhow::Result<Quantity> {
35    if decimals == 18 {
36        return Ok(Quantity::from_wei(amount));
37    }
38
39    let value = convert_u256_to_f64(amount, decimals)?;
40    let precision = decimals.min(FIXED_PRECISION);
41    Ok(Quantity::new(value, precision))
42}
43
44/// Convert a `U256` amount to [`Price`].
45///
46/// - If `decimals == 18` the value represents WEI and we leverage the dedicated
47///   `Quantity::from_wei` constructor for loss-less conversion.
48/// - For other precisions we fall back to a floating-point conversion identical
49///   to the pre-existing path in `convert_u256_to_f64` and then construct a
50///   `Quantity` with the smaller `decimals` (clamped to `FIXED_PRECISION`).
51///
52/// # Errors
53///
54/// Returns an error when the helper must fall back to the floating-point path
55/// (i.e. `decimals != 18`) and the provided `amount` cannot be converted to an
56/// `f64` (see `convert_u256_to_f64`).
57pub fn u256_to_price(amount: U256, decimals: u8) -> anyhow::Result<Price> {
58    if decimals == 18 {
59        return Ok(Price::from_wei(amount));
60    }
61
62    let value = convert_u256_to_f64(amount, decimals)?;
63    let precision = decimals.min(FIXED_PRECISION);
64    Ok(Price::new(value, precision))
65}
66
67////////////////////////////////////////////////////////////////////////////////
68// Tests
69////////////////////////////////////////////////////////////////////////////////
70
71#[cfg(test)]
72mod tests {
73    use alloy::primitives::U256;
74    use rstest::rstest;
75
76    use super::*;
77
78    #[rstest]
79    fn test_quantity_from_wei() {
80        let wei = U256::from(1_000_000_000_000_000_000u128); // 1 * 10^18
81        let q = u256_to_quantity(wei, 18).unwrap();
82        assert_eq!(q.precision, 18);
83        assert_eq!(q.as_wei(), wei);
84    }
85
86    #[rstest]
87    fn test_quantity_from_small_decimals() {
88        let raw = U256::from(1_500_000u128); // 1.5 with 6 decimals
89        let q = u256_to_quantity(raw, 6).unwrap();
90        assert_eq!(q.precision, 6.min(FIXED_PRECISION));
91        assert_eq!(q.to_string(), "1.500000");
92    }
93
94    #[rstest]
95    fn test_price_from_wei() {
96        let wei = U256::from(2_000_000_000_000_000_000u128); // 2 ETH
97        let p = u256_to_price(wei, 18).unwrap();
98        assert_eq!(p.precision, 18);
99        assert_eq!(p.as_wei(), wei);
100    }
101
102    #[rstest]
103    fn test_price_precision_clamp() {
104        let value = U256::from(10_000_000_000u128); // 10 with 9 decimals
105        // Request unrealistic 20-dec precision → should clamp to FIXED_PRECISION (16 or 9)
106        let p = u256_to_price(value, 20).unwrap();
107        assert_eq!(p.precision, FIXED_PRECISION);
108    }
109}