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}