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