diff --git a/Cargo.toml b/Cargo.toml index 4f183cb..cdd5d4e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "hylo-fix" -version = "0.1.8" +version = "0.2.0" edition = "2021" description = "Fixed-point number types with Solana Anchor support" authors = ["0xPlish ", "Curtis McEnroe "] @@ -14,10 +14,11 @@ categories = ["data-structures"] name = "fix" [features] -anchor = ["dep:anchor-lang"] +idl-build = ["anchor-lang/idl-build"] [dependencies] -anchor-lang = { version = "0.30.0", optional = true } +anchor-lang = "0.30" +anyhow = "1.0.82" muldiv = "1.0.1" num-traits = "0.2.17" paste = "1.0.14" diff --git a/src/anchor.rs b/src/anchor.rs deleted file mode 100644 index d1df001..0000000 --- a/src/anchor.rs +++ /dev/null @@ -1,23 +0,0 @@ -use crate::Fix; -use anchor_lang::prelude::Space; - -macro_rules! fix_init_space { - ($ty:ident) => { - impl Space for Fix<$ty, Base, Exp> { - const INIT_SPACE: usize = core::mem::size_of::<$ty>(); - } - }; -} - -fix_init_space!(u8); -fix_init_space!(u16); -fix_init_space!(u32); -fix_init_space!(u64); -fix_init_space!(u128); -fix_init_space!(usize); -fix_init_space!(i8); -fix_init_space!(i16); -fix_init_space!(i32); -fix_init_space!(i64); -fix_init_space!(i128); -fix_init_space!(isize); diff --git a/src/fix_value.rs b/src/fix_value.rs new file mode 100644 index 0000000..e7969a2 --- /dev/null +++ b/src/fix_value.rs @@ -0,0 +1,100 @@ +use crate::typenum::{Integer, U10}; +use crate::Fix; +use anchor_lang::prelude::{borsh, AnchorDeserialize, AnchorSerialize, InitSpace}; +use paste::paste; + +macro_rules! impl_fix_value { + ($sign:ident, $bits:expr) => { + paste! { + /// A value-space `Fix` where base is always 10 and bits are a concrete type. + /// Intended for serialized storage in Solana accounts where generics won't work. + #[derive(PartialEq, Eq, Copy, Clone, Debug, AnchorSerialize, AnchorDeserialize, InitSpace)] + pub struct [<$sign FixValue $bits>] { + pub bits: [<$sign:lower $bits>], + pub exp: i8, + } + + impl [<$sign FixValue $bits>] { + pub fn new(bits: [<$sign:lower $bits>], exp: i8) -> Self { + Self { bits, exp } + } + } + + impl From> for [<$sign FixValue $bits>] + where + Bits: Into<[<$sign:lower $bits>]>, + Exp: Integer, + { + fn from(fix: Fix) -> Self { + Self { + bits: fix.bits.into(), + exp: Exp::to_i8(), + } + } + } + + impl From<[<$sign FixValue $bits>]> for Fix + where + Bits: From<[<$sign:lower $bits>]>, + Exp: Integer, + { + fn from(value: [<$sign FixValue $bits>]) -> Fix { + Fix::new(value.bits.into()) + } + } + } + }; +} + +impl_fix_value!(U, 8); +impl_fix_value!(U, 16); +impl_fix_value!(U, 32); +impl_fix_value!(U, 64); +impl_fix_value!(U, 128); +impl_fix_value!(I, 8); +impl_fix_value!(I, 16); +impl_fix_value!(I, 32); +impl_fix_value!(I, 64); +impl_fix_value!(I, 128); + +#[cfg(test)] +mod tests { + use super::*; + use crate::aliases::si::Kilo; + use anyhow::Result; + use borsh::to_vec; + + macro_rules! fix_value_tests { + ($sign:ident, $bits:expr) => { + paste! { + #[test] + fn []() -> Result<()> { + let start = Kilo::new([<69 $sign:lower $bits>]); + let there: [<$sign FixValue $bits>] = start.into(); + let back: Kilo<[<$sign:lower $bits>]> = there.into(); + assert_eq!(there, [<$sign FixValue $bits>]::new(69, 3)); + Ok(assert_eq!(start, back)) + } + + #[test] + fn []() -> Result<()> { + let start = [<$sign FixValue $bits>]::new(20, -2); + let bytes = to_vec(&start)?; + let back = AnchorDeserialize::deserialize(&mut bytes.as_slice())?; + Ok(assert_eq!(start, back)) + } + } + }; + } + + fix_value_tests!(U, 8); + fix_value_tests!(U, 16); + fix_value_tests!(U, 32); + fix_value_tests!(U, 64); + fix_value_tests!(U, 128); + fix_value_tests!(I, 8); + fix_value_tests!(I, 16); + fix_value_tests!(I, 32); + fix_value_tests!(I, 64); + fix_value_tests!(I, 128); +} diff --git a/src/lib.rs b/src/lib.rs index f4383cd..b4f546b 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -62,16 +62,16 @@ //! //! This crate is `no_std`. -#![no_std] +//#![no_std] pub extern crate muldiv; pub extern crate num_traits; pub extern crate typenum; pub mod aliases; -#[cfg(feature = "anchor")] -pub mod anchor; +pub mod fix_value; pub mod prelude; +pub mod util; use core::cmp::{Eq, Ord, Ordering, PartialEq, PartialOrd}; use core::fmt::{Debug, Error, Formatter}; @@ -80,8 +80,6 @@ use core::marker::PhantomData; use core::ops::{Add, Div, Mul, Neg, Rem, Sub}; use core::ops::{AddAssign, DivAssign, MulAssign, RemAssign, SubAssign}; -#[cfg(feature = "anchor")] -use anchor_lang::prelude::{borsh, AnchorDeserialize, AnchorSerialize}; use muldiv::MulDiv; use num_traits::{CheckedAdd, CheckedDiv, CheckedMul, CheckedSub}; use paste::paste; @@ -117,7 +115,6 @@ use typenum::type_operators::{Abs, IsLess}; /// - _(x BE) × y = (x × y) BE_ /// - _(x BE) ÷ y = (x ÷ y) BE_ /// - _(x BE) % y = (x % y) BE_ -#[cfg_attr(feature = "anchor", derive(AnchorSerialize, AnchorDeserialize))] pub struct Fix { /// The underlying integer. pub bits: Bits, diff --git a/src/prelude.rs b/src/prelude.rs index a1b0404..a17d13c 100644 --- a/src/prelude.rs +++ b/src/prelude.rs @@ -1,6 +1,7 @@ -#[allow(unused)] -#[cfg(feature = "anchor")] -pub use crate::anchor::*; +pub use crate::aliases::decimal::*; +pub use crate::fix_value::*; pub use crate::muldiv::MulDiv; pub use crate::num_traits::{CheckedAdd, CheckedDiv, CheckedMul, CheckedSub}; +pub use crate::typenum::{N1, N10, N11, N12, N2, N3, N4, N5, N6, N7, N8, N9}; +pub use crate::util::*; pub use crate::*; diff --git a/src/util.rs b/src/util.rs new file mode 100644 index 0000000..87d747f --- /dev/null +++ b/src/util.rs @@ -0,0 +1,25 @@ +use typenum::NInt; + +use crate::typenum::{IsLess, NonZero, Unsigned, U10, U20}; +use crate::Fix; + +/// Domain specific extensions to the `Fix` type as it's used in this project. +pub trait FixExt: Sized { + /// This precision's equivalent of 1. + fn one() -> Self; + fn zero() -> Self; +} + +impl FixExt for Fix> +where + Exp: Unsigned + NonZero + IsLess, + Bits: From, +{ + fn one() -> Fix> { + Fix::new(U10::to_u64().pow(Exp::to_u32()).into()) + } + + fn zero() -> Self { + Fix::new(0.into()) + } +}