Skip to main content

fix/
fix_value.rs

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