fix/
fix_value.rs

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