1use crate::program::ManifestError;
2use borsh::{BorshDeserialize as Deserialize, BorshSerialize as Serialize};
3use bytemuck::{Pod, Zeroable};
4use hypertree::trace;
5use shank::ShankAccount;
6use solana_program::program_error::ProgramError;
7use static_assertions::const_assert;
8use std::{
9 cmp::Ordering,
10 fmt::Display,
11 ops::{Add, AddAssign, Div, Sub, SubAssign},
12 u128, u32, u64,
13};
14
15pub trait WrapperU64 {
18 fn new(value: u64) -> Self;
19 fn as_u64(&self) -> u64;
20}
21
22macro_rules! checked_math {
23 ($type_name:ident) => {
24 impl $type_name {
25 #[inline(always)]
26 pub fn checked_add(self, other: Self) -> Result<$type_name, ManifestError> {
27 let result_or: Option<u64> = self.inner.checked_add(other.inner);
28 if result_or.is_none() {
29 Err(ManifestError::Overflow)
30 } else {
31 Ok($type_name::new(result_or.unwrap()))
32 }
33 }
34
35 #[inline(always)]
36 pub fn checked_sub(self, other: Self) -> Result<$type_name, ManifestError> {
37 let result_or: Option<u64> = self.inner.checked_sub(other.inner);
38 if result_or.is_none() {
39 Err(ManifestError::Overflow)
40 } else {
41 Ok($type_name::new(result_or.unwrap()))
42 }
43 }
44 }
45 };
46}
47
48macro_rules! overflow_math {
49 ($type_name:ident) => {
50 impl $type_name {
51 #[inline(always)]
52 pub fn overflowing_add(self, other: Self) -> ($type_name, bool) {
53 let (sum, overflow) = self.inner.overflowing_add(other.inner);
54 ($type_name::new(sum), overflow)
55 }
56
57 #[inline(always)]
58 pub fn saturating_add(self, other: Self) -> $type_name {
59 let sum = self.inner.saturating_add(other.inner);
60 $type_name::new(sum)
61 }
62
63 #[inline(always)]
64 pub fn saturating_sub(self, other: Self) -> $type_name {
65 let difference = self.inner.saturating_sub(other.inner);
66 $type_name::new(difference)
67 }
68
69 #[inline(always)]
70 pub fn wrapping_add(self, other: Self) -> $type_name {
71 let sum = self.inner.wrapping_add(other.inner);
72 $type_name::new(sum)
73 }
74
75 #[inline(always)]
76 pub fn wrapping_sub(self, other: Self) -> $type_name {
77 let difference = self.inner.wrapping_sub(other.inner);
78 $type_name::new(difference)
79 }
80 }
81 };
82}
83
84macro_rules! basic_math {
85 ($type_name:ident) => {
86 impl Add for $type_name {
87 type Output = Self;
88
89 #[inline(always)]
90 fn add(self, other: Self) -> Self {
91 $type_name::new(self.inner + other.inner)
92 }
93 }
94
95 impl AddAssign for $type_name {
96 #[inline(always)]
97 fn add_assign(&mut self, other: Self) {
98 *self = *self + other;
99 }
100 }
101
102 impl Sub for $type_name {
103 type Output = Self;
104
105 #[inline(always)]
106 fn sub(self, other: Self) -> Self {
107 $type_name::new(self.inner - other.inner)
108 }
109 }
110
111 impl SubAssign for $type_name {
112 #[inline(always)]
113 fn sub_assign(&mut self, other: Self) {
114 *self = *self - other;
115 }
116 }
117
118 impl Default for $type_name {
119 #[inline(always)]
120 fn default() -> Self {
121 Self::ZERO
122 }
123 }
124
125 impl Display for $type_name {
126 #[inline(always)]
127 fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
128 self.inner.fmt(f)
129 }
130 }
131
132 impl PartialEq for $type_name {
133 #[inline(always)]
134 fn eq(&self, other: &Self) -> bool {
135 self.inner == other.inner
136 }
137 }
138
139 impl Eq for $type_name {}
140 };
141}
142
143macro_rules! basic_u64 {
144 ($type_name:ident) => {
145 impl WrapperU64 for $type_name {
146 #[inline(always)]
147 fn new(value: u64) -> Self {
148 $type_name { inner: value }
149 }
150
151 #[inline(always)]
152 fn as_u64(&self) -> u64 {
153 self.inner
154 }
155 }
156
157 impl $type_name {
158 pub const ZERO: Self = $type_name { inner: 0 };
159 pub const ONE: Self = $type_name { inner: 1 };
160
161 #[inline(always)]
162 pub fn min(self, other: Self) -> Self {
163 if self.inner <= other.inner {
164 self
165 } else {
166 other
167 }
168 }
169 }
170
171 impl From<$type_name> for u64 {
172 #[inline(always)]
173 fn from(x: $type_name) -> u64 {
174 x.inner
175 }
176 }
177
178 impl PartialEq<u64> for $type_name {
180 #[inline(always)]
181 fn eq(&self, other: &u64) -> bool {
182 self.inner == *other
183 }
184 }
185
186 impl PartialEq<$type_name> for u64 {
187 #[inline(always)]
188 fn eq(&self, other: &$type_name) -> bool {
189 *self == other.inner
190 }
191 }
192
193 basic_math!($type_name);
194 checked_math!($type_name);
195 overflow_math!($type_name);
196 };
197}
198
199#[derive(
200 Debug, Clone, Copy, PartialOrd, Ord, Zeroable, Pod, Deserialize, Serialize, ShankAccount,
201)]
202#[repr(transparent)]
203pub struct QuoteAtoms {
204 inner: u64,
205}
206basic_u64!(QuoteAtoms);
207
208#[derive(
209 Debug, Clone, Copy, PartialOrd, Ord, Zeroable, Pod, Deserialize, Serialize, ShankAccount,
210)]
211#[repr(transparent)]
212pub struct BaseAtoms {
213 inner: u64,
214}
215basic_u64!(BaseAtoms);
216
217#[derive(
218 Debug, Clone, Copy, PartialOrd, Ord, Zeroable, Pod, Deserialize, Serialize, ShankAccount,
219)]
220#[repr(transparent)]
221pub struct GlobalAtoms {
222 inner: u64,
223}
224basic_u64!(GlobalAtoms);
225
226#[derive(Clone, Copy, Default, Zeroable, Pod, Deserialize, Serialize, ShankAccount)]
228#[repr(C)]
229pub struct QuoteAtomsPerBaseAtom {
230 pub(crate) inner: [u64; 2],
231}
232
233#[cfg(not(feature = "certora"))]
238const fn u128_to_u64_slice(a: u128) -> [u64; 2] {
239 unsafe {
240 let ptr: *const u128 = &a;
241 *ptr.cast::<[u64; 2]>()
242 }
243}
244pub(crate) fn u64_slice_to_u128(a: [u64; 2]) -> u128 {
245 unsafe {
246 let ptr: *const [u64; 2] = &a;
247 *ptr.cast::<u128>()
248 }
249}
250
251#[cfg(not(feature = "certora"))]
252const ATOM_LIMIT: u128 = u64::MAX as u128;
253const D18: u128 = 10u128.pow(18);
254const D18F: f64 = D18 as f64;
255
256#[cfg(not(feature = "certora"))]
257const DECIMAL_CONSTANTS: [u128; 27] = [
258 10u128.pow(26),
259 10u128.pow(25),
260 10u128.pow(24),
261 10u128.pow(23),
262 10u128.pow(22),
263 10u128.pow(21),
264 10u128.pow(20),
265 10u128.pow(19),
266 10u128.pow(18),
267 10u128.pow(17),
268 10u128.pow(16),
269 10u128.pow(15),
270 10u128.pow(14),
271 10u128.pow(13),
272 10u128.pow(12),
273 10u128.pow(11),
274 10u128.pow(10),
275 10u128.pow(09),
276 10u128.pow(08),
277 10u128.pow(07),
278 10u128.pow(06),
279 10u128.pow(05),
280 10u128.pow(04),
281 10u128.pow(03),
282 10u128.pow(02),
283 10u128.pow(01),
284 10u128.pow(00),
285];
286#[cfg(not(feature = "certora"))]
288static_assertions::const_assert_eq!(
289 DECIMAL_CONSTANTS[QuoteAtomsPerBaseAtom::MAX_EXP as usize],
290 D18
291);
292
293#[cfg(not(feature = "certora"))]
295const_assert!(DECIMAL_CONSTANTS[0] * (u32::MAX as u128) < u128::MAX);
296
297const_assert!(D18 * (u64::MAX as u128) < u128::MAX);
298
299#[cfg(feature = "certora")]
300#[path = "quantities_certora.rs"]
301mod quantities_certora;
302
303#[cfg(not(feature = "certora"))]
304impl QuoteAtomsPerBaseAtom {
305 pub const ZERO: Self = QuoteAtomsPerBaseAtom { inner: [0; 2] };
306 pub const MIN: Self = QuoteAtomsPerBaseAtom::from_mantissa_and_exponent_(1, Self::MIN_EXP);
307 pub const MAX: Self =
308 QuoteAtomsPerBaseAtom::from_mantissa_and_exponent_(u32::MAX, Self::MAX_EXP);
309 pub const MIN_EXP: i8 = -18;
310 pub const MAX_EXP: i8 = 8;
311
312 #[inline(always)]
313 const fn from_mantissa_and_exponent_(mantissa: u32, exponent: i8) -> Self {
314 let offset: usize = (Self::MAX_EXP as i64).wrapping_sub(exponent as i64) as usize;
321 let inner: u128 = DECIMAL_CONSTANTS[offset].wrapping_mul(mantissa as u128);
323 QuoteAtomsPerBaseAtom {
324 inner: u128_to_u64_slice(inner),
325 }
326 }
327
328 pub fn multiply_spread(self, spread_e_5: u32) -> Self {
329 let inner: u128 = u64_slice_to_u128(self.inner);
331 let inner_e_minus_5: u128 = inner.wrapping_mul(spread_e_5 as u128);
332 let new_inner: u128 = inner_e_minus_5.div_ceil(100_000);
333 QuoteAtomsPerBaseAtom {
334 inner: u128_to_u64_slice(new_inner),
335 }
336 }
337
338 pub fn divide_spread(self, spread_e_5: u32) -> Self {
339 QuoteAtomsPerBaseAtom {
341 inner: u128_to_u64_slice(
342 u64_slice_to_u128(self.inner)
343 .wrapping_mul(100_000)
344 .div_ceil(spread_e_5 as u128),
345 ),
346 }
347 }
348
349 pub fn try_from_mantissa_and_exponent(
350 mantissa: u32,
351 exponent: i8,
352 ) -> Result<Self, PriceConversionError> {
353 if exponent > Self::MAX_EXP {
354 trace!("invalid exponent {exponent} > 8 would truncate",);
355 return Err(PriceConversionError(0x1));
356 }
357 if exponent < Self::MIN_EXP {
358 trace!("invalid exponent {exponent} < -18 would truncate",);
359 return Err(PriceConversionError(0x2));
360 }
361 Ok(Self::from_mantissa_and_exponent_(mantissa, exponent))
362 }
363
364 #[inline(always)]
365 pub fn checked_base_for_quote(
366 self,
367 quote_atoms: QuoteAtoms,
368 round_up: bool,
369 ) -> Result<BaseAtoms, ProgramError> {
370 if self == Self::ZERO {
375 return Ok(BaseAtoms::ZERO);
376 }
377 let dividend: u128 = D18.wrapping_mul(quote_atoms.inner as u128);
379 let inner: u128 = u64_slice_to_u128(self.inner);
380 let base_atoms: u128 = if round_up {
381 dividend.div_ceil(inner)
382 } else {
383 dividend.div(inner)
384 };
385 if base_atoms <= ATOM_LIMIT {
386 Ok(BaseAtoms::new(base_atoms as u64))
387 } else {
388 Err(PriceConversionError(0x5).into())
389 }
390 }
391
392 #[inline(always)]
393 fn checked_quote_for_base_(
394 self,
395 base_atoms: BaseAtoms,
396 round_up: bool,
397 ) -> Result<u128, ProgramError> {
398 let inner: u128 = u64_slice_to_u128(self.inner);
399 let product: u128 = inner
400 .checked_mul(base_atoms.inner as u128)
401 .ok_or(PriceConversionError(0x8))?;
402 let quote_atoms: u128 = if round_up {
403 product.div_ceil(D18)
404 } else {
405 product.div(D18)
406 };
407 if quote_atoms <= ATOM_LIMIT {
408 Ok(quote_atoms)
409 } else {
410 Err(PriceConversionError(0x9).into())
411 }
412 }
413
414 #[inline(always)]
415 pub fn checked_quote_for_base(
416 self,
417 other: BaseAtoms,
418 round_up: bool,
419 ) -> Result<QuoteAtoms, ProgramError> {
420 self.checked_quote_for_base_(other, round_up)
421 .map(|r| QuoteAtoms::new(r as u64))
422 }
423}
424
425impl Ord for QuoteAtomsPerBaseAtom {
426 #[inline(always)]
427 fn cmp(&self, other: &Self) -> Ordering {
428 (u64_slice_to_u128(self.inner)).cmp(&u64_slice_to_u128(other.inner))
429 }
430}
431
432impl PartialOrd for QuoteAtomsPerBaseAtom {
433 #[inline(always)]
434 fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
435 Some(self.cmp(other))
436 }
437}
438
439impl PartialEq for QuoteAtomsPerBaseAtom {
440 #[inline(always)]
441 fn eq(&self, other: &Self) -> bool {
442 (self.inner) == (other.inner)
443 }
444}
445
446impl Eq for QuoteAtomsPerBaseAtom {}
447
448impl std::fmt::Display for QuoteAtomsPerBaseAtom {
449 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
450 f.write_fmt(format_args!(
451 "{}",
452 &(u64_slice_to_u128(self.inner) as f64 / D18F)
453 ))
454 }
455}
456
457impl std::fmt::Debug for QuoteAtomsPerBaseAtom {
458 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
459 f.debug_struct("QuoteAtomsPerBaseAtom")
460 .field("value", &(u64_slice_to_u128(self.inner) as f64 / D18F))
461 .finish()
462 }
463}
464
465#[derive(Debug)]
466pub struct PriceConversionError(u32);
467
468const PRICE_CONVERSION_ERROR_BASE: u32 = 100;
469
470impl From<PriceConversionError> for ProgramError {
471 fn from(value: PriceConversionError) -> Self {
472 ProgramError::Custom(value.0 + PRICE_CONVERSION_ERROR_BASE)
473 }
474}
475
476#[inline(always)]
477fn encode_mantissa_and_exponent(value: f64) -> (u32, i8) {
478 let mut exponent: i8 = 0;
479 while exponent < QuoteAtomsPerBaseAtom::MAX_EXP
481 && calculate_mantissa(value, exponent) > u32::MAX as f64
482 {
483 exponent += 1;
484 }
485 while exponent > QuoteAtomsPerBaseAtom::MIN_EXP
487 && calculate_mantissa(value, exponent) < (u32::MAX / 10) as f64
488 {
489 exponent -= 1;
490 }
491 (calculate_mantissa(value, exponent) as u32, exponent)
492}
493
494#[inline(always)]
495fn calculate_mantissa(value: f64, exp: i8) -> f64 {
496 (value * 10f64.powi(-exp as i32)).round()
497}
498
499impl TryFrom<f64> for QuoteAtomsPerBaseAtom {
500 type Error = PriceConversionError;
501
502 fn try_from(value: f64) -> Result<Self, Self::Error> {
503 if value.is_infinite() {
504 trace!("infinite can not be expressed as fixed point decimal");
505 return Err(PriceConversionError(0xC));
506 }
507 if value.is_nan() {
508 trace!("nan can not be expressed as fixed point decimal");
509 return Err(PriceConversionError(0xD));
510 }
511 if value.is_sign_negative() {
512 trace!("price {value} can not be negative");
513 return Err(PriceConversionError(0xE));
514 }
515 if calculate_mantissa(value, Self::MAX_EXP) > u32::MAX as f64 {
516 trace!("price {value} is too large");
517 return Err(PriceConversionError(0xF));
518 }
519
520 let (mantissa, exponent) = encode_mantissa_and_exponent(value);
521
522 Self::try_from_mantissa_and_exponent(mantissa, exponent)
523 }
524}
525
526impl BaseAtoms {
527 #[inline(always)]
528 pub fn checked_mul(
529 self,
530 other: QuoteAtomsPerBaseAtom,
531 round_up: bool,
532 ) -> Result<QuoteAtoms, ProgramError> {
533 other.checked_quote_for_base(self, round_up)
534 }
535}
536
537#[cfg(feature = "certora")]
538mod nondet {
539 use super::*;
540
541 impl ::nondet::Nondet for BaseAtoms {
542 fn nondet() -> Self {
543 Self::new(::nondet::nondet())
544 }
545 }
546
547 impl ::nondet::Nondet for QuoteAtoms {
548 fn nondet() -> Self {
549 Self::new(::nondet::nondet())
550 }
551 }
552
553 impl ::nondet::Nondet for QuoteAtomsPerBaseAtom {
554 fn nondet() -> Self {
555 Self {
556 inner: [::nondet::nondet(), ::nondet::nondet()],
557 }
558 }
559 }
560}
561
562#[test]
563fn test_new_constructor_macro() {
564 let base_atoms_1: BaseAtoms = BaseAtoms::new(5);
565 let base_atoms_2: BaseAtoms = BaseAtoms::new(10);
566
567 assert_eq!(base_atoms_1 + base_atoms_2, BaseAtoms::new(15));
568 assert!((base_atoms_1 + base_atoms_2).eq(&BaseAtoms::new(15)));
569 assert!((base_atoms_1 + base_atoms_2).eq(&15_u64));
570 assert!(15u64.eq(&(base_atoms_1 + base_atoms_2)));
571}
572
573#[test]
574fn test_checked_add() {
575 let base_atoms_1: BaseAtoms = BaseAtoms::new(1);
576 let base_atoms_2: BaseAtoms = BaseAtoms::new(2);
577 assert_eq!(
578 base_atoms_1.checked_add(base_atoms_2).unwrap(),
579 BaseAtoms::new(3)
580 );
581
582 let base_atoms_1: BaseAtoms = BaseAtoms::new(u64::MAX - 1);
583 let base_atoms_2: BaseAtoms = BaseAtoms::new(2);
584 assert!(base_atoms_1.checked_add(base_atoms_2).is_err());
585}
586
587#[test]
588fn test_checked_sub() {
589 let base_atoms_1: BaseAtoms = BaseAtoms::new(1);
590 let base_atoms_2: BaseAtoms = BaseAtoms::new(2);
591 assert_eq!(
592 base_atoms_2.checked_sub(base_atoms_1).unwrap(),
593 BaseAtoms::new(1)
594 );
595
596 assert!(base_atoms_1.checked_sub(base_atoms_2).is_err());
597}
598
599#[test]
600fn test_overflowing_add() {
601 let base_atoms: BaseAtoms = BaseAtoms::new(u64::MAX);
602 let (sum, overflow_detected) = base_atoms.overflowing_add(base_atoms);
603 assert!(overflow_detected);
604
605 let expected = base_atoms - BaseAtoms::ONE;
606 assert_eq!(sum, expected);
607}
608
609#[test]
610fn test_wrapping_add() {
611 let base_atoms: BaseAtoms = BaseAtoms::new(u64::MAX);
612 let sum = base_atoms.wrapping_add(base_atoms);
613 let expected = base_atoms - BaseAtoms::ONE;
614 assert_eq!(sum, expected);
615}
616
617#[test]
618fn test_checked_base_for_quote_edge_cases() {
619 let quote_atoms_per_base_atom: QuoteAtomsPerBaseAtom =
620 QuoteAtomsPerBaseAtom::from_mantissa_and_exponent_(0, 0);
621 assert_eq!(
622 quote_atoms_per_base_atom
623 .checked_base_for_quote(QuoteAtoms::new(1), false)
624 .unwrap(),
625 BaseAtoms::new(0)
626 );
627
628 let quote_atoms_per_base_atom: QuoteAtomsPerBaseAtom =
629 QuoteAtomsPerBaseAtom::from_mantissa_and_exponent_(1, -18);
630 assert!(quote_atoms_per_base_atom
631 .checked_base_for_quote(QuoteAtoms::new(u64::MAX), false)
632 .is_err(),);
633}
634
635#[test]
636fn test_checked_quote_for_base_edge_cases() {
637 let quote_atoms_per_base_atom: QuoteAtomsPerBaseAtom = QuoteAtomsPerBaseAtom::MAX;
639 assert!(quote_atoms_per_base_atom
640 .checked_quote_for_base(BaseAtoms::new(u64::MAX - 1), false)
641 .is_err(),);
642}
643
644#[test]
645fn test_quote_atoms_per_base_atom_edge_case() {
646 assert!(QuoteAtomsPerBaseAtom::try_from(f64::NAN).is_err());
647}
648
649#[test]
650fn test_multiply_macro() {
651 let base_atoms: BaseAtoms = BaseAtoms::new(5);
652 let quote_atoms_per_base_atom: QuoteAtomsPerBaseAtom = QuoteAtomsPerBaseAtom {
653 inner: u128_to_u64_slice(100 * D18 - 1),
654 };
655 assert_eq!(
656 base_atoms
657 .checked_mul(quote_atoms_per_base_atom, true)
658 .unwrap(),
659 QuoteAtoms::new(500)
660 );
661}
662
663#[test]
664fn test_price_limits() {
665 assert!(QuoteAtomsPerBaseAtom::try_from_mantissa_and_exponent(
666 1,
667 QuoteAtomsPerBaseAtom::MAX_EXP
668 )
669 .is_ok());
670 assert!(QuoteAtomsPerBaseAtom::try_from_mantissa_and_exponent(
671 u32::MAX,
672 QuoteAtomsPerBaseAtom::MAX_EXP
673 )
674 .is_ok());
675 assert!(QuoteAtomsPerBaseAtom::try_from_mantissa_and_exponent(
676 1,
677 QuoteAtomsPerBaseAtom::MIN_EXP
678 )
679 .is_ok());
680 assert!(QuoteAtomsPerBaseAtom::try_from_mantissa_and_exponent(
681 u32::MAX,
682 QuoteAtomsPerBaseAtom::MIN_EXP
683 )
684 .is_ok());
685 assert!(QuoteAtomsPerBaseAtom::try_from(0f64).is_ok());
686 assert!(QuoteAtomsPerBaseAtom::try_from_mantissa_and_exponent(0, 0).is_ok());
687 assert!(QuoteAtomsPerBaseAtom::try_from(
688 u32::MAX as f64 * 10f64.powi(QuoteAtomsPerBaseAtom::MAX_EXP as i32)
689 )
690 .is_ok());
691
692 assert!(QuoteAtomsPerBaseAtom::try_from_mantissa_and_exponent(
694 1,
695 QuoteAtomsPerBaseAtom::MAX_EXP + 1
696 )
697 .is_err());
698 assert!(QuoteAtomsPerBaseAtom::try_from_mantissa_and_exponent(
699 1,
700 QuoteAtomsPerBaseAtom::MIN_EXP - 1
701 )
702 .is_err());
703 assert!(QuoteAtomsPerBaseAtom::try_from(-1f64).is_err());
704 assert!(QuoteAtomsPerBaseAtom::try_from(u128::MAX as f64).is_err());
705 assert!(QuoteAtomsPerBaseAtom::try_from(1f64 / 0f64).is_err());
706}
707
708#[derive(Clone, Copy, Default, Debug)]
709#[repr(C)]
710struct AlignmentTest {
711 _alignment_fix: u128,
712 _pad: u64,
713 price: QuoteAtomsPerBaseAtom,
714}
715
716#[test]
717fn test_alignment() {
718 let mut t = AlignmentTest::default();
719 t.price = QuoteAtomsPerBaseAtom::from_mantissa_and_exponent_(u32::MAX, 0);
720 let mut s = t.clone();
721 t.price = s.price.clone();
722 let q = t
723 .price
724 .checked_base_for_quote(QuoteAtoms::new(u32::MAX as u64), true)
725 .unwrap();
726 t._pad = q.as_u64();
727 s._pad = s.price.checked_quote_for_base(q, true).unwrap().as_u64();
728
729 println!("s:{s:?} t:{t:?}");
730}
731
732#[test]
733fn test_print() {
734 println!("{}", BaseAtoms::new(1));
735 println!("{}", QuoteAtoms::new(2));
736 println!(
737 "{}",
738 QuoteAtomsPerBaseAtom {
739 inner: u128_to_u64_slice(123 * D18 / 100),
740 }
741 );
742}
743
744#[test]
745fn test_debug() {
746 println!("{:?}", BaseAtoms::new(1));
747 println!("{:?}", QuoteAtoms::new(2));
748 println!(
749 "{:?}",
750 QuoteAtomsPerBaseAtom {
751 inner: u128_to_u64_slice(123 * D18 / 100),
752 }
753 );
754}