Skip to main content

fix/
fix_value.rs

1use core::fmt::{self, Display, Formatter};
2use std::error::Error;
3
4use anchor_lang::error::Error as AnchorError;
5use anchor_lang::error::ErrorCode::InvalidNumericConversion;
6use anchor_lang::prelude::{borsh, AnchorDeserialize, AnchorSerialize, InitSpace};
7use paste::paste;
8use serde::{Deserialize, Serialize};
9
10use crate::typenum::{Integer, U10};
11use crate::Fix;
12
13/// Exponent mismatch converting a `FixValue` into a typed `Fix`.
14#[derive(Debug, Clone, Copy, PartialEq, Eq)]
15pub struct ExponentMismatch {
16    pub expected: i8,
17    pub actual: i8,
18}
19
20impl Display for ExponentMismatch {
21    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
22        write!(
23            f,
24            "Exponent mismatch converting `FixValue` to `Fix`: expected: {}, got: {}.",
25            self.expected, self.actual
26        )
27    }
28}
29
30impl Error for ExponentMismatch {}
31
32impl From<ExponentMismatch> for AnchorError {
33    fn from(_: ExponentMismatch) -> AnchorError {
34        InvalidNumericConversion.into()
35    }
36}
37
38macro_rules! impl_fix_value {
39    ($sign:ident, $bits:expr) => {
40        paste! {
41           /// A value-space `Fix` where base is always 10 and bits are a concrete type.
42           /// Intended for serialized storage in Solana accounts where generics won't work.
43            #[derive(PartialEq, Eq, Copy, Clone, Debug, Default, Serialize, Deserialize, AnchorSerialize, AnchorDeserialize, InitSpace)]
44            pub struct [<$sign FixValue $bits>] {
45                pub bits: [<$sign:lower $bits>],
46                pub exp: i8,
47            }
48
49            impl [<$sign FixValue $bits>] {
50                #[must_use] pub fn new(bits: [<$sign:lower $bits>], exp: i8) -> Self {
51                    Self { bits, exp }
52                }
53            }
54
55            impl<Bits, Exp> From<Fix<Bits, U10, Exp>> for [<$sign FixValue $bits>]
56            where
57                Bits: Into<[<$sign:lower $bits>]>,
58                Exp: Integer,
59            {
60                fn from(fix: Fix<Bits, U10, Exp>) -> Self {
61                    Self {
62                        bits: fix.bits.into(),
63                        exp: Exp::to_i8(),
64                    }
65                }
66            }
67
68            impl<Bits, Exp> TryFrom<[<$sign FixValue $bits>]> for Fix<Bits, U10, Exp>
69            where
70                Bits: From<[<$sign:lower $bits>]>,
71                Exp: Integer,
72            {
73              type Error = ExponentMismatch;
74              fn try_from(
75                  value: [<$sign FixValue $bits>],
76              ) -> Result<Fix<Bits, U10, Exp>, ExponentMismatch> {
77                if value.exp == Exp::to_i8() {
78                  Ok(Fix::new(value.bits.into()))
79                } else {
80                  Err(ExponentMismatch { expected: Exp::to_i8(), actual: value.exp })
81                }
82              }
83            }
84        }
85    };
86}
87
88impl_fix_value!(U, 8);
89impl_fix_value!(U, 16);
90impl_fix_value!(U, 32);
91impl_fix_value!(U, 64);
92impl_fix_value!(U, 128);
93impl_fix_value!(I, 8);
94impl_fix_value!(I, 16);
95impl_fix_value!(I, 32);
96impl_fix_value!(I, 64);
97impl_fix_value!(I, 128);
98
99#[cfg(test)]
100mod tests {
101    use super::*;
102    use crate::aliases::si::Kilo;
103    use anyhow::Result;
104    use borsh::to_vec;
105
106    macro_rules! fix_value_tests {
107        ($sign:ident, $bits:expr) => {
108            paste! {
109                #[test]
110                fn [<roundtrip_into_ $sign:lower $bits>]() -> Result<()> {
111                    let start = Kilo::new([<69 $sign:lower $bits>]);
112                    let there: [<$sign FixValue $bits>] = start.into();
113                    let back: Kilo<[<$sign:lower $bits>]> = there.try_into()?;
114                    assert_eq!(there, [<$sign FixValue $bits>]::new(69, 3));
115                    Ok(assert_eq!(start, back))
116                }
117
118                #[test]
119                fn [<roundtrip_serialize_ $sign:lower $bits>]() -> Result<()> {
120                    let start = [<$sign FixValue $bits>]::new(20, -2);
121                    let bytes = to_vec(&start)?;
122                    let back = AnchorDeserialize::deserialize(&mut bytes.as_slice())?;
123                    Ok(assert_eq!(start, back))
124                }
125
126                #[test]
127                fn [<wrong_exp_should_fail_ $sign:lower $bits>]() -> Result<()> {
128                    let pow11 = [<$sign FixValue $bits>]::new(42, -11);
129                    let wrong = TryInto::<Kilo<[<$sign:lower $bits>]>>::try_into(pow11);
130                    Ok(assert_eq!(
131                        Err(ExponentMismatch { expected: 3, actual: -11 }),
132                        wrong
133                    ))
134                }
135            }
136        };
137    }
138
139    fix_value_tests!(U, 8);
140    fix_value_tests!(U, 16);
141    fix_value_tests!(U, 32);
142    fix_value_tests!(U, 64);
143    fix_value_tests!(U, 128);
144    fix_value_tests!(I, 8);
145    fix_value_tests!(I, 16);
146    fix_value_tests!(I, 32);
147    fix_value_tests!(I, 64);
148    fix_value_tests!(I, 128);
149}