nautilus_model/defi/types/
money.rs1use alloy_primitives::U256;
19use nautilus_core::correctness::{CorrectnessError, CorrectnessResult};
20
21use crate::types::{Currency, Money, Quantity};
22
23impl Money {
24 pub fn from_u256(amount: U256, currency: Currency) -> CorrectnessResult<Self> {
31 let quantity = Quantity::from_u256(amount, currency.precision)?;
32 let raw =
33 i128::try_from(quantity.raw()).map_err(|_| CorrectnessError::PredicateViolation {
34 message: format!("Amount for {currency} exceeds Money raw range"),
35 })?;
36 Self::from_raw_checked(raw, currency)
37 }
38
39 pub fn from_wei<U>(raw_wei: U, currency: Currency) -> Self
49 where
50 U: Into<U256>,
51 {
52 assert!(
53 currency.precision == 18,
54 "`from_wei` requires a currency with precision 18, was {} for {}",
55 currency.precision,
56 currency.code,
57 );
58
59 let raw_u256: U256 = raw_wei.into();
60 let raw_u128: u128 = raw_u256
61 .try_into()
62 .expect("raw wei value exceeds 128-bit range");
63
64 assert!(
65 raw_u128 <= i128::MAX as u128,
66 "raw wei value exceeds signed 128-bit range"
67 );
68
69 let raw_i128: i128 = raw_u128 as i128;
70 Self::from_raw(raw_i128, currency)
71 }
72
73 #[must_use]
80 pub fn to_wei(&self) -> U256 {
81 assert!(
82 self.currency.precision == 18,
83 "Failed to convert money with precision {} to wei (requires precision 18)",
84 self.currency.precision,
85 );
86 assert!(
87 !self.is_negative(),
88 "Failed to convert negative money to wei"
89 );
90 U256::from(self.raw() as u128)
91 }
92}
93
94#[cfg(test)]
95mod tests {
96 use alloy_primitives::U256;
97 use rstest::rstest;
98 use rust_decimal::Decimal;
99 use rust_decimal_macros::dec;
100
101 use super::*;
102 use crate::enums::CurrencyType;
103
104 #[rstest]
105 fn test_from_wei_one_eth() {
106 let eth = Currency::new("ETH", 18, 0, "Ethereum", CurrencyType::Crypto);
107 let one_eth_wei = U256::from(1_000_000_000_000_000_000_u64);
108 let money = Money::from_wei(one_eth_wei, eth);
109
110 assert_eq!(money.as_decimal(), dec!(1));
112 assert_eq!(money.currency.precision, 18);
113 }
114
115 #[rstest]
116 fn test_from_u256_scales_six_decimal_currency_exactly() {
117 let usdc = Currency::new("USDC", 6, 0, "USD Coin", CurrencyType::Crypto);
118
119 let money = Money::from_u256(U256::from(987_654_321_u64), usdc).unwrap();
120
121 assert_eq!(money.raw(), 9_876_543_210_000_000_000);
122 assert_eq!(money.as_decimal(), dec!(987.654321));
123 assert_eq!(money.currency, usdc);
124 }
125
126 #[rstest]
127 fn test_from_wei_small_amount() {
128 let eth = Currency::new("ETH", 18, 0, "Ethereum", CurrencyType::Crypto);
129 let small_wei = U256::from(1_000_000_000_000_u64); let money = Money::from_wei(small_wei, eth);
131
132 assert_eq!(money.as_decimal(), dec!(0.000001)); }
135
136 #[rstest]
137 fn test_to_wei_one_eth() {
138 let eth = Currency::new("ETH", 18, 0, "Ethereum", CurrencyType::Crypto);
139 let money = Money::from_wei(U256::from(1_000_000_000_000_000_000_u64), eth);
140 let wei_value = money.to_wei();
141
142 assert_eq!(wei_value, U256::from(1_000_000_000_000_000_000_u64));
143 }
144
145 #[rstest]
146 fn test_to_wei_small_amount() {
147 let eth = Currency::new("ETH", 18, 0, "Ethereum", CurrencyType::Crypto);
148 let money = Money::from_wei(U256::from(1_000_000_000_000_u64), eth);
149 let wei_value = money.to_wei();
150
151 assert_eq!(wei_value, U256::from(1_000_000_000_000_u64));
152 }
153
154 #[rstest]
155 fn test_wei_roundtrip() {
156 let eth = Currency::new("ETH", 18, 0, "Ethereum", CurrencyType::Crypto);
157 let original_wei = U256::from(1_234_567_890_123_456_789_u64);
158 let money = Money::from_wei(original_wei, eth);
159 let roundtrip_wei = money.to_wei();
160
161 assert_eq!(original_wei, roundtrip_wei);
162 }
163
164 #[rstest]
165 fn test_checked_arith_rejects_mixed_scale_same_code() {
166 let eth_standard = Currency::ETH(); let eth_wei = Currency::new("ETH", 18, 0, "Ethereum", CurrencyType::Crypto);
171
172 let standard = Money::new(1.0, eth_standard);
173 let wei = Money::from_wei(U256::from(1_000_000_000_000_000_000_u128), eth_wei);
174
175 assert_eq!(wei.checked_add(standard), None);
176 assert_eq!(standard.checked_add(wei), None);
177 assert_eq!(wei.checked_sub(standard), None);
178 assert_eq!(standard.checked_sub(wei), None);
179 }
180
181 #[rstest]
182 #[should_panic(expected = "`from_wei` requires a currency with precision 18")]
183 fn test_from_wei_rejects_non_18_precision() {
184 let eth_8 = Currency::ETH(); let _ = Money::from_wei(U256::from(1_000_000_000_000_000_000_u128), eth_8);
186 }
187
188 #[rstest]
189 #[should_panic(expected = "requires precision 18")]
190 fn test_to_wei_rejects_non_18_precision() {
191 let usd = Currency::new("USD", 2, 840, "United States dollar", CurrencyType::Fiat);
192 let m = Money::new(1.0, usd);
193 let _ = m.to_wei();
194 }
195
196 #[rstest]
197 fn test_from_wei_zero() {
198 let eth = Currency::new("ETH", 18, 0, "Ethereum", CurrencyType::Crypto);
199 let money = Money::from_wei(U256::ZERO, eth);
200
201 assert!(money.is_zero());
202 assert_eq!(money.as_decimal(), Decimal::ZERO);
203 assert_eq!(money.to_wei(), U256::ZERO);
204 }
205
206 #[rstest]
209 #[should_panic(expected = "raw wei value exceeds signed 128-bit range")]
210 fn test_from_wei_maximum_u128() {
211 let eth = Currency::new("ETH", 18, 0, "Ethereum", CurrencyType::Crypto);
212 let max_wei = U256::from(u128::MAX);
213 let _ = Money::from_wei(max_wei, eth);
214 }
215
216 #[rstest]
217 #[should_panic(expected = "raw wei value exceeds 128-bit range")]
218 fn test_from_wei_overflow() {
219 let eth = Currency::new("ETH", 18, 0, "Ethereum", CurrencyType::Crypto);
220 let overflow_wei = U256::from(u128::MAX) + U256::from(1u64);
221 Money::from_wei(overflow_wei, eth);
222 }
223
224 #[rstest]
225 fn test_from_wei_different_tokens() {
226 let usdc = Currency::new("USDC", 18, 0, "USD Coin", CurrencyType::Crypto);
227 let dai = Currency::new("DAI", 18, 0, "Dai Stablecoin", CurrencyType::Crypto);
228
229 let wei_amount = U256::from(500_000_000_000_000_000_u64); let usdc_money = Money::from_wei(wei_amount, usdc);
231 let dai_money = Money::from_wei(wei_amount, dai);
232
233 assert_eq!(usdc_money.as_decimal(), dai_money.as_decimal());
234 assert_eq!(usdc_money.to_wei(), dai_money.to_wei());
235 assert_ne!(usdc_money.currency, dai_money.currency);
236 }
237
238 #[rstest]
239 fn test_arithmetic_with_wei_values() {
240 let eth = Currency::new("ETH", 18, 0, "Ethereum", CurrencyType::Crypto);
241 let money1 = Money::from_wei(U256::from(1_000_000_000_000_000_000_u64), eth); let money2 = Money::from_wei(U256::from(500_000_000_000_000_000_u64), eth); let sum = money1 + money2;
245 assert_eq!(sum.as_decimal(), dec!(1.5)); assert_eq!(sum.to_wei(), U256::from(1_500_000_000_000_000_000_u64));
247
248 let diff = money1 - money2;
249 assert_eq!(diff.as_decimal(), dec!(0.5)); assert_eq!(diff.to_wei(), U256::from(500_000_000_000_000_000_u64));
251 }
252
253 #[rstest]
254 fn test_comparison_with_wei_values() {
255 let eth = Currency::new("ETH", 18, 0, "Ethereum", CurrencyType::Crypto);
256 let money1 = Money::from_wei(U256::from(1_000_000_000_000_000_000_u64), eth); let money2 = Money::from_wei(U256::from(2_000_000_000_000_000_000_u64), eth); let money3 = Money::from_wei(U256::from(1_000_000_000_000_000_000_u64), eth); assert!(money1 < money2);
261 assert!(money2 > money1);
262 assert_eq!(money1, money3);
263 assert!(money1 <= money3);
264 assert!(money1 >= money3);
265 }
266}