Skip to main content

fix/
fix_value.rs

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