diff --git a/Cargo.lock b/Cargo.lock index e06525d7d..7c6f1f6ab 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3479,9 +3479,11 @@ name = "hp-fixed" version = "0.1.0" dependencies = [ "anyhow", + "bincode", "num-bigint", "num-traits", "serde", + "serde_json", ] [[package]] @@ -6745,9 +6747,9 @@ dependencies = [ [[package]] name = "serde" -version = "1.0.173" +version = "1.0.177" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e91f70896d6720bc714a4a57d22fc91f1db634680e65c8efe13323f1fa38d53f" +checksum = "63ba2516aa6bf82e0b19ca8b50019d52df58455d3cf9bdaf6315225fdd0c560a" dependencies = [ "serde_derive", ] @@ -6793,9 +6795,9 @@ dependencies = [ [[package]] name = "serde_derive" -version = "1.0.173" +version = "1.0.177" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a6250dde8342e0232232be9ca3db7aa40aceb5a3e5dd9bddbc00d99a007cde49" +checksum = "401797fe7833d72109fedec6bfcbe67c0eed9b99772f26eb8afd261f0abc6fd3" dependencies = [ "proc-macro2 1.0.66", "quote 1.0.31", diff --git a/core/rpc/src/tests.rs b/core/rpc/src/tests.rs index 2641ed6dd..003702c32 100644 --- a/core/rpc/src/tests.rs +++ b/core/rpc/src/tests.rs @@ -188,10 +188,7 @@ async fn test_rpc_get_flk_balance() -> Result<()> { if value.get("result").is_some() { // Parse the response as a successful response let success_response: RpcSuccessResponse> = serde_json::from_value(value)?; - assert_eq!( - HpUfixed::<18>::new(1_000_u32.into()), - success_response.result - ); + assert_eq!(HpUfixed::<18>::from(1_000_u32), success_response.result); } else { panic!("Rpc Error: {value}") } @@ -315,12 +312,10 @@ async fn test_rpc_get_staked() -> Result<()> { ) .await?; rpc.config.port = port; - task::spawn(async move { rpc.start().await; }); wait_for_server_start(port).await?; - let req = json!({ "jsonrpc": "2.0", "method":"flk_get_staked", @@ -333,12 +328,9 @@ async fn test_rpc_get_staked() -> Result<()> { if response.status().is_success() { let value: Value = response.json().await?; if value.get("result").is_some() { - // Parse the response as a successful response + //Parse the response as a successful response let success_response: RpcSuccessResponse> = serde_json::from_value(value)?; - assert_eq!( - HpUfixed::<18>::new(1_000_u32.into()), - success_response.result - ); + assert_eq!(HpUfixed::<18>::from(1_000_u32), success_response.result); } else { panic!("Rpc Error: {value}") } @@ -402,7 +394,7 @@ async fn test_rpc_get_stables_balance() -> Result<()> { if value.get("result").is_some() { // Parse the response as a successful response let success_response: RpcSuccessResponse> = serde_json::from_value(value)?; - assert_eq!(HpUfixed::<6>::new(2_00_u32.into()), success_response.result); + assert_eq!(HpUfixed::<6>::from(2_00_u32), success_response.result); } else { panic!("Rpc Error: {value}") } @@ -660,7 +652,7 @@ async fn test_rpc_get_locked() -> Result<()> { if value.get("result").is_some() { // Parse the response as a successful response let success_response: RpcSuccessResponse> = serde_json::from_value(value)?; - assert_eq!(HpUfixed::<18>::new(500_u32.into()), success_response.result); + assert_eq!(HpUfixed::<18>::from(500_u32), success_response.result); } else { panic!("Rpc Error: {value}") } diff --git a/lib/hp-fixed/Cargo.toml b/lib/hp-fixed/Cargo.toml index 3d2f6995c..0d2c6f74a 100644 --- a/lib/hp-fixed/Cargo.toml +++ b/lib/hp-fixed/Cargo.toml @@ -12,3 +12,6 @@ num-bigint = {version = "0.4.3", features=["serde"]} num-traits ="0.2.15" serde = { version = "1.0", features = ["derive"] } +[dev-dependencies] +serde_json.workspace = true +bincode.workspace = true \ No newline at end of file diff --git a/lib/hp-fixed/README.md b/lib/hp-fixed/README.md index e9929657f..725812794 100644 --- a/lib/hp-fixed/README.md +++ b/lib/hp-fixed/README.md @@ -26,7 +26,7 @@ async fn main() { assert_eq!(z, HpFixed::<5>::from(30.24690)); - let value = HpFixed::<19>::new(BigInt::from(std::i32::MAX as i64 + 1)); + let value = HpFixed::<19>::from(std::i32::MAX as i64 + 1); assert_eq!( TryInto::::try_into(value.clone()).unwrap(), std::i32::MAX as isize + 1 @@ -44,13 +44,13 @@ async fn main() { use hp_fixed::unsigned::HpUfixed; use num_bigint::BigUint; - let a = HpUfixed::<5>::new(BigUint::from(10u32)); - let b = HpUfixed::<5>::new(BigUint::from(20u32)); + let a = HpUfixed::<5>::from(10u32); + let b = HpUfixed::<5>::from(20u32); let c = a + b; - assert_eq!(c, HpUfixed::<5>::new(BigUint::from(30u32))); + assert_eq!(c, HpUfixed::<5>::from(30u32)); - let value = HpUfixed::<20>::new(BigUint::from(std::u64::MAX as u128 + 1_u128)); + let value = HpUfixed::<20>::from(std::u64::MAX as u128 + 1_u128); assert_eq!( std::u64::MAX as u128 + 1_u128, value.clone().try_into().unwrap() diff --git a/lib/hp-fixed/src/lib.rs b/lib/hp-fixed/src/lib.rs index 01a8fec31..9a399bc73 100644 --- a/lib/hp-fixed/src/lib.rs +++ b/lib/hp-fixed/src/lib.rs @@ -13,6 +13,32 @@ pub enum HpFixedConversionError { Underflow, DivisionError, FloatParseError, + ParseError, +} + +impl fmt::Display for HpFixedConversionError { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match *self { + HpFixedConversionError::PrecisionLevelNotSupported => { + write!(f, "Precision level not supported") + }, + HpFixedConversionError::Overflow => { + write!(f, "Overflow") + }, + HpFixedConversionError::Underflow => { + write!(f, "Underflow") + }, + HpFixedConversionError::DivisionError => { + write!(f, "Division error") + }, + HpFixedConversionError::FloatParseError => { + write!(f, "Float parse error") + }, + HpFixedConversionError::ParseError => { + write!(f, "Parse error") + }, + } + } } fn format_hp_fixed(value: &T, f: &mut fmt::Formatter<'_>) -> fmt::Result @@ -37,7 +63,7 @@ where } } formatted = formatted.chars().rev().collect(); - write!(f, "HpUfixed<{P}>({formatted})") + write!(f, "{formatted}<{P}>") } } diff --git a/lib/hp-fixed/src/signed.rs b/lib/hp-fixed/src/signed.rs index 5b731b864..0a4566aeb 100644 --- a/lib/hp-fixed/src/signed.rs +++ b/lib/hp-fixed/src/signed.rs @@ -2,6 +2,7 @@ use std::{ convert::TryInto, fmt, ops::{Add, AddAssign, Div, Mul, Sub, SubAssign}, + str::FromStr, }; use num_bigint::{ @@ -9,11 +10,11 @@ use num_bigint::{ Sign::{Minus, Plus}, }; use num_traits::{FromPrimitive, Signed, ToPrimitive, Zero}; -use serde::{Deserialize, Serialize}; +use serde::{Deserialize, Deserializer, Serialize}; use crate::{format_hp_fixed, get_float_parts, HpFixedConversionError}; -#[derive(Clone, Debug, Hash, PartialEq, PartialOrd, Ord, Eq, Serialize, Deserialize, Default)] +#[derive(Clone, Debug, Hash, PartialEq, PartialOrd, Ord, Eq, Default)] /// A high-precision fixed-point number backed by a `BigInt`. /// @@ -50,8 +51,7 @@ pub struct HpFixed(BigInt); impl HpFixed

{ pub fn new(value: BigInt) -> Self { - let ten: BigInt = BigUint::from(10u32).into(); - HpFixed::

(value * ten.pow(P.try_into().unwrap())) + HpFixed::

(value) } pub fn zero() -> HpFixed

{ @@ -92,6 +92,40 @@ impl fmt::Display for HpFixed

{ } } +impl FromStr for HpFixed

{ + type Err = HpFixedConversionError; + fn from_str(s: &str) -> Result { + let value = BigInt::from_str(s).map_err(|_| HpFixedConversionError::ParseError)?; + + Ok(HpFixed::new(value)) + } +} + +impl Serialize for HpFixed

{ + fn serialize(&self, serializer: S) -> Result + where + S: serde::ser::Serializer, + { + let s = &self.to_string(); + let cleaned_s = s.replace('_', ""); + let parts: Vec<&str> = cleaned_s.split('<').collect(); + let final_string = parts[0].to_string(); + + serializer.serialize_str(&final_string) + } +} + +impl<'de, const P: usize> Deserialize<'de> for HpFixed

{ + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + let s = String::deserialize(deserializer)?; + s.parse::>() + .map_err(|_| serde::de::Error::custom("Failed to deserialize HpFixed")) + } +} + impl Add> for HpFixed

{ type Output = HpFixed

; @@ -438,9 +472,9 @@ mod tests { #[test] fn test_try_into() { - let large = HpFixed::<20>::new(BigInt::from(std::i64::MIN as i128 - 1)); - let medium = HpFixed::<19>::new(BigInt::from(std::i32::MAX as i64 + 1)); - let small = HpFixed::<18>::new(BigInt::from(std::i16::MAX as i32 + 1)); + let large = HpFixed::<20>::from(BigInt::from(std::i64::MIN as i128 - 1)); + let medium = HpFixed::<19>::from(BigInt::from(std::i32::MAX as i64 + 1)); + let small = HpFixed::<18>::from(BigInt::from(std::i16::MAX as i32 + 1)); assert_eq!(std::i64::MIN as i128 - 1, large.clone().try_into().unwrap()); assert!(matches!( diff --git a/lib/hp-fixed/src/unsigned.rs b/lib/hp-fixed/src/unsigned.rs index b7158e157..3e624e066 100644 --- a/lib/hp-fixed/src/unsigned.rs +++ b/lib/hp-fixed/src/unsigned.rs @@ -1,11 +1,12 @@ use std::{ fmt, ops::{Add, AddAssign, Div, Mul, Sub, SubAssign}, + str::FromStr, }; use num_bigint::BigUint; -use num_traits::{zero, CheckedDiv, FromPrimitive, ToPrimitive}; -use serde::{Deserialize, Serialize}; +use num_traits::{CheckedDiv, FromPrimitive, ToPrimitive, Zero}; +use serde::{Deserialize, Deserializer, Serialize}; use crate::{format_hp_fixed, get_float_parts, HpFixedConversionError}; @@ -42,16 +43,16 @@ use crate::{format_hp_fixed, get_float_parts, HpFixedConversionError}; /// /// * `BigUint`: The underlying large unsigned integer value that the `HpUfixed` wraps around. -#[derive(Clone, Debug, Hash, PartialEq, PartialOrd, Ord, Eq, Serialize, Deserialize, Default)] +#[derive(Clone, Debug, Hash, PartialEq, PartialOrd, Ord, Eq, Default)] pub struct HpUfixed(BigUint); impl HpUfixed

{ pub fn new(value: BigUint) -> Self { - HpUfixed::

(value * BigUint::from(10u32).pow(P.try_into().unwrap())) + HpUfixed::

(value) } pub fn zero() -> HpUfixed

{ - HpUfixed::new(zero()) + HpUfixed::new(BigUint::zero()) } pub fn convert_precision(&self) -> HpUfixed { let current_value: &BigUint = &self.0; @@ -86,6 +87,39 @@ impl fmt::Display for HpUfixed

{ } } +impl FromStr for HpUfixed

{ + type Err = HpFixedConversionError; + fn from_str(s: &str) -> Result { + let value = BigUint::from_str(s).map_err(|_| HpFixedConversionError::ParseError)?; + + Ok(HpUfixed::new(value)) + } +} + +impl Serialize for HpUfixed

{ + fn serialize(&self, serializer: S) -> Result + where + S: serde::ser::Serializer, + { + let s = &self.to_string(); + let cleaned_s = s.replace('_', ""); + let parts: Vec<&str> = cleaned_s.split('<').collect(); + let final_string = parts[0].to_string(); + serializer.serialize_str(&final_string) + } +} + +impl<'de, const P: usize> Deserialize<'de> for HpUfixed

{ + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + let s = String::deserialize(deserializer)?; + s.parse::>() + .map_err(|_| serde::de::Error::custom("Failed to deserialize HpUfixed")) + } +} + impl Add> for HpUfixed

{ type Output = HpUfixed

; @@ -379,9 +413,9 @@ mod tests { #[test] fn test_try_into() { - let large = HpUfixed::<20>::new(BigUint::from(std::u64::MAX as u128 + 1_u128)); - let medium = HpUfixed::<19>::new(BigUint::from(std::u32::MAX as u64 + 1_u64)); - let small = HpUfixed::<18>::new(BigUint::from(std::u16::MAX as u32 + 1_u32)); + let large = HpUfixed::<20>::from(BigUint::from(std::u64::MAX as u128 + 1_u128)); + let medium = HpUfixed::<19>::from(BigUint::from(std::u32::MAX as u64 + 1_u64)); + let small = HpUfixed::<18>::from(BigUint::from(std::u16::MAX as u32 + 1_u32)); assert_eq!( std::u64::MAX as u128 + 1_u128, @@ -534,4 +568,21 @@ mod tests { let result = decimal1.convert_precision::<2>(); assert_eq!(result.0, BigUint::from(123_412_u128)); } + + #[test] + fn test_serde() { + let decimal: HpUfixed<18> = HpUfixed::from(10_f64); + let ser = serde_json::to_string(&decimal).unwrap(); + let decimal2: HpUfixed<18> = serde_json::from_str(&ser).unwrap(); + assert_eq!(decimal, decimal2); + } + + #[test] + fn bincode_serde_test() { + let decimal: HpUfixed<18> = HpUfixed::from(10_f64); + let serialized = bincode::serialize(&decimal).expect("Failed to serialize using bincode"); + let deserialized: HpUfixed<18> = + bincode::deserialize(&serialized).expect("Failed to deserialize using bincode"); + assert_eq!(decimal, deserialized); + } }