manifest/
quantities.rs

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
15/// New and as_u64 for creating and switching to u64 when needing to use base or
16/// quote
17pub 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        // Below should only be used in tests.
179        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// Manifest pricing
227#[derive(Clone, Copy, Default, Zeroable, Pod, Deserialize, Serialize, ShankAccount)]
228#[repr(C)]
229pub struct QuoteAtomsPerBaseAtom {
230    pub(crate) inner: [u64; 2],
231}
232
233// These conversions are necessary, bc. the compiler forces 16 byte alignment
234// on the u128 type, which is not necessary given that the target architecture
235// has no native support for u128 math and requires us only to be 8 byte
236// aligned.
237#[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// ensures that the index lookup is correct when converting from floating point
287#[cfg(not(feature = "certora"))]
288static_assertions::const_assert_eq!(
289    DECIMAL_CONSTANTS[QuoteAtomsPerBaseAtom::MAX_EXP as usize],
290    D18
291);
292
293// ensures that we can remove bounds checks on certain multiplications
294#[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        /* map exponent to array range
315          8 ->  [0] -> D26
316          0 ->  [8] -> D18
317        -10 -> [18] -> D08
318        -18 -> [26] ->  D0
319        */
320        let offset: usize = (Self::MAX_EXP as i64).wrapping_sub(exponent as i64) as usize;
321        // can not overflow 10^26 * u32::MAX < u128::MAX
322        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        // Stored as u128 * 10^-26
330        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        // multiply then divide
340        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        // prevents division by zero further down the line. zero is not an
371        // ideal answer, but this is only used in impact_base_atoms, which
372        // is used to calculate error free order sizes and for that purpose
373        // it works well.
374        if self == Self::ZERO {
375            return Ok(BaseAtoms::ZERO);
376        }
377        // this doesn't need a check, will never overflow: u64::MAX * D18 < u128::MAX
378        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    // prevent overflow when casting to u32
480    while exponent < QuoteAtomsPerBaseAtom::MAX_EXP
481        && calculate_mantissa(value, exponent) > u32::MAX as f64
482    {
483        exponent += 1;
484    }
485    // prevent underflow and maximize precision available
486    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    // edge case is where u64MAX * 10**18  < product < u128MAX
638    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    // failures
693    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}