From 9e46f46d17e1460e60a05d8c393f9ab4a8514870 Mon Sep 17 00:00:00 2001 From: Christoph Otter Date: Mon, 11 Sep 2023 10:53:01 +0200 Subject: [PATCH 01/11] Add missing TryFrom impls for Uint64 --- packages/std/src/math/uint128.rs | 12 +++++++++++ packages/std/src/math/uint256.rs | 28 ++++++++++++++++++++++++++ packages/std/src/math/uint512.rs | 34 ++++++++++++++++++++++++++++++++ 3 files changed, 74 insertions(+) diff --git a/packages/std/src/math/uint128.rs b/packages/std/src/math/uint128.rs index 0aebf6b992..06e9549929 100644 --- a/packages/std/src/math/uint128.rs +++ b/packages/std/src/math/uint128.rs @@ -677,6 +677,18 @@ mod tests { assert!(result.is_err()); } + #[test] + fn uint128_try_into() { + assert!(Uint64::try_from(Uint128::MAX).is_err()); + + assert_eq!(Uint64::try_from(Uint128::zero()), Ok(Uint64::zero())); + + assert_eq!( + Uint64::try_from(Uint128::from(42u64)), + Ok(Uint64::from(42u64)) + ); + } + #[test] fn uint128_implements_display() { let a = Uint128(12345); diff --git a/packages/std/src/math/uint256.rs b/packages/std/src/math/uint256.rs index 6422dee764..b9de472435 100644 --- a/packages/std/src/math/uint256.rs +++ b/packages/std/src/math/uint256.rs @@ -389,6 +389,16 @@ impl TryFrom for Uint128 { } } +impl TryFrom for Uint64 { + type Error = ConversionOverflowError; + + fn try_from(value: Uint256) -> Result { + Ok(Uint64::new(value.0.try_into().map_err(|_| { + ConversionOverflowError::new("Uint256", "Uint64", value.to_string()) + })?)) + } +} + impl TryFrom<&str> for Uint256 { type Error = StdError; @@ -1062,6 +1072,24 @@ mod tests { assert!(result.is_err()); } + #[test] + fn uint256_try_into() { + assert!(Uint64::try_from(Uint256::MAX).is_err()); + assert!(Uint128::try_from(Uint256::MAX).is_err()); + + assert_eq!(Uint64::try_from(Uint256::zero()), Ok(Uint64::zero())); + assert_eq!(Uint128::try_from(Uint256::zero()), Ok(Uint128::zero())); + + assert_eq!( + Uint64::try_from(Uint256::from(42u64)), + Ok(Uint64::from(42u64)) + ); + assert_eq!( + Uint128::try_from(Uint256::from(42u128)), + Ok(Uint128::from(42u128)) + ); + } + #[test] fn uint256_convert_to_uint128() { let source = Uint256::from(42u128); diff --git a/packages/std/src/math/uint512.rs b/packages/std/src/math/uint512.rs index c4dd9e3f52..dd5a1e9aa0 100644 --- a/packages/std/src/math/uint512.rs +++ b/packages/std/src/math/uint512.rs @@ -380,6 +380,16 @@ impl TryFrom for Uint128 { } } +impl TryFrom for Uint64 { + type Error = ConversionOverflowError; + + fn try_from(value: Uint512) -> Result { + Ok(Uint64::new(value.0.try_into().map_err(|_| { + ConversionOverflowError::new("Uint512", "Uint64", value.to_string()) + })?)) + } +} + impl TryFrom<&str> for Uint512 { type Error = StdError; @@ -748,6 +758,30 @@ mod tests { assert!(result.is_err()); } + #[test] + fn uint512_try_into() { + assert!(Uint64::try_from(Uint512::MAX).is_err()); + assert!(Uint128::try_from(Uint512::MAX).is_err()); + assert!(Uint256::try_from(Uint512::MAX).is_err()); + + assert_eq!(Uint64::try_from(Uint512::zero()), Ok(Uint64::zero())); + assert_eq!(Uint128::try_from(Uint512::zero()), Ok(Uint128::zero())); + assert_eq!(Uint256::try_from(Uint512::zero()), Ok(Uint256::zero())); + + assert_eq!( + Uint64::try_from(Uint512::from(42u64)), + Ok(Uint64::from(42u64)) + ); + assert_eq!( + Uint128::try_from(Uint512::from(42u128)), + Ok(Uint128::from(42u128)) + ); + assert_eq!( + Uint256::try_from(Uint512::from(42u128)), + Ok(Uint256::from(42u128)) + ); + } + #[test] fn uint512_convert_to_uint128() { let source = Uint512::from(42u128); From 624f437ab8203a4c0c114c0d09dd2828b49bffcc Mon Sep 17 00:00:00 2001 From: Christoph Otter Date: Tue, 12 Sep 2023 16:49:37 +0200 Subject: [PATCH 02/11] Add missing unsigned int to signed int conversions --- packages/std/src/math/int128.rs | 82 ++++++++++++++++++++++++--------- packages/std/src/math/int256.rs | 55 +++++++++++++++++----- packages/std/src/math/int512.rs | 24 +++++++++- packages/std/src/math/int64.rs | 54 +++++++++++++++++++++- 4 files changed, 181 insertions(+), 34 deletions(-) diff --git a/packages/std/src/math/int128.rs b/packages/std/src/math/int128.rs index 6454672e25..82425376f0 100644 --- a/packages/std/src/math/int128.rs +++ b/packages/std/src/math/int128.rs @@ -11,7 +11,7 @@ use serde::{de, ser, Deserialize, Deserializer, Serialize}; use crate::errors::{DivideByZeroError, DivisionError, OverflowError, OverflowOperation, StdError}; use crate::{ forward_ref_partial_eq, CheckedMultiplyRatioError, ConversionOverflowError, Int256, Int512, - Int64, Uint128, Uint64, + Int64, Uint128, Uint256, Uint512, Uint64, }; use super::conversion::shrink_be_int; @@ -270,12 +270,50 @@ impl Int128 { } } +// Uint to Int impl From for Int128 { fn from(val: Uint64) -> Self { val.u64().into() } } +impl TryFrom for Int128 { + type Error = ConversionOverflowError; + + fn try_from(value: Uint128) -> Result { + value + .u128() + .try_into() // convert to i128 + .map(Self::new) + .map_err(|_| ConversionOverflowError::new("Uint128", "Int128", value)) + } +} + +impl TryFrom for Int128 { + type Error = ConversionOverflowError; + + fn try_from(value: Uint256) -> Result { + value + .0 + .try_into() // convert to i128 + .map(Self::new) + .map_err(|_| ConversionOverflowError::new("Uint256", "Int128", value)) + } +} + +impl TryFrom for Int128 { + type Error = ConversionOverflowError; + + fn try_from(value: Uint512) -> Result { + value + .0 + .try_into() // convert to i128 + .map(Self::new) + .map_err(|_| ConversionOverflowError::new("Uint512", "Int128", value)) + } +} + +// uint to Int impl From for Int128 { fn from(val: u64) -> Self { Int128(val.into()) @@ -300,12 +338,34 @@ impl From for Int128 { } } +// Int to Int impl From for Int128 { fn from(val: Int64) -> Self { val.i64().into() } } +impl TryFrom for Int128 { + type Error = ConversionOverflowError; + + fn try_from(value: Int256) -> Result { + shrink_be_int(value.to_be_bytes()) + .ok_or_else(|| ConversionOverflowError::new("Int256", "Int128", value)) + .map(Self::from_be_bytes) + } +} + +impl TryFrom for Int128 { + type Error = ConversionOverflowError; + + fn try_from(value: Int512) -> Result { + shrink_be_int(value.to_be_bytes()) + .ok_or_else(|| ConversionOverflowError::new("Int512", "Int128", value)) + .map(Self::from_be_bytes) + } +} + +// int to Int impl From for Int128 { fn from(val: i128) -> Self { Int128(val) @@ -336,26 +396,6 @@ impl From for Int128 { } } -impl TryFrom for Int128 { - type Error = ConversionOverflowError; - - fn try_from(value: Int256) -> Result { - shrink_be_int(value.to_be_bytes()) - .ok_or_else(|| ConversionOverflowError::new("Int256", "Int128", value)) - .map(Self::from_be_bytes) - } -} - -impl TryFrom for Int128 { - type Error = ConversionOverflowError; - - fn try_from(value: Int512) -> Result { - shrink_be_int(value.to_be_bytes()) - .ok_or_else(|| ConversionOverflowError::new("Int512", "Int128", value)) - .map(Self::from_be_bytes) - } -} - impl TryFrom<&str> for Int128 { type Error = StdError; diff --git a/packages/std/src/math/int256.rs b/packages/std/src/math/int256.rs index 30fccbb6e6..1dfb89ca0c 100644 --- a/packages/std/src/math/int256.rs +++ b/packages/std/src/math/int256.rs @@ -1,3 +1,4 @@ +use bnum::prelude::As; use core::fmt; use core::ops::{ Add, AddAssign, Div, DivAssign, Mul, MulAssign, Neg, Not, Rem, RemAssign, Shl, ShlAssign, Shr, @@ -11,7 +12,7 @@ use serde::{de, ser, Deserialize, Deserializer, Serialize}; use crate::errors::{DivideByZeroError, DivisionError, OverflowError, OverflowOperation, StdError}; use crate::{ forward_ref_partial_eq, CheckedMultiplyRatioError, ConversionOverflowError, Int128, Int512, - Int64, Uint128, Uint256, Uint64, + Int64, Uint128, Uint256, Uint512, Uint64, }; /// Used internally - we don't want to leak this type since we might change @@ -328,6 +329,35 @@ impl Int256 { } } +// Uint to Int +impl TryFrom for Int256 { + type Error = ConversionOverflowError; + + fn try_from(value: Uint512) -> Result { + // Self::MAX fits into Uint512, so we can just cast it + if value.0 > Self::MAX.0.as_() { + return Err(ConversionOverflowError::new("Uint512", "Int256", value)); + } + + // at this point we know it fits + Ok(Self(value.0.as_())) + } +} + +impl TryFrom for Int256 { + type Error = ConversionOverflowError; + + fn try_from(value: Uint256) -> Result { + // Self::MAX fits into Uint256, so we can just cast it + if value.0 > Self::MAX.0.as_() { + return Err(ConversionOverflowError::new("Uint256", "Int256", value)); + } + + // at this point we know it fits + Ok(Self(value.0.as_())) + } +} + impl From for Int256 { fn from(val: Uint128) -> Self { val.u128().into() @@ -340,6 +370,7 @@ impl From for Int256 { } } +// uint to Int impl From for Int256 { fn from(val: u128) -> Self { Int256(val.into()) @@ -370,6 +401,17 @@ impl From for Int256 { } } +// Int to Int +impl TryFrom for Int256 { + type Error = ConversionOverflowError; + + fn try_from(value: Int512) -> Result { + shrink_be_int(value.to_be_bytes()) + .ok_or_else(|| ConversionOverflowError::new("Int512", "Int256", value)) + .map(Self::from_be_bytes) + } +} + impl From for Int256 { fn from(val: Int128) -> Self { val.i128().into() @@ -382,6 +424,7 @@ impl From for Int256 { } } +// int to Int impl From for Int256 { fn from(val: i128) -> Self { Int256(val.into()) @@ -412,16 +455,6 @@ impl From for Int256 { } } -impl TryFrom for Int256 { - type Error = ConversionOverflowError; - - fn try_from(value: Int512) -> Result { - shrink_be_int(value.to_be_bytes()) - .ok_or_else(|| ConversionOverflowError::new("Int512", "Int256", value)) - .map(Self::from_be_bytes) - } -} - impl TryFrom<&str> for Int256 { type Error = StdError; diff --git a/packages/std/src/math/int512.rs b/packages/std/src/math/int512.rs index bc6919d5d6..2308b8d518 100644 --- a/packages/std/src/math/int512.rs +++ b/packages/std/src/math/int512.rs @@ -1,3 +1,4 @@ +use bnum::prelude::As; use core::fmt; use core::ops::{ Add, AddAssign, Div, DivAssign, Mul, MulAssign, Neg, Not, Rem, RemAssign, Shl, ShlAssign, Shr, @@ -9,7 +10,10 @@ use schemars::JsonSchema; use serde::{de, ser, Deserialize, Deserializer, Serialize}; use crate::errors::{DivideByZeroError, DivisionError, OverflowError, OverflowOperation, StdError}; -use crate::{forward_ref_partial_eq, Int128, Int256, Int64, Uint128, Uint256, Uint512, Uint64}; +use crate::{ + forward_ref_partial_eq, ConversionOverflowError, Int128, Int256, Int64, Uint128, Uint256, + Uint512, Uint64, +}; /// Used internally - we don't want to leak this type since we might change /// the implementation in the future. @@ -313,6 +317,21 @@ impl Int512 { } } +// Uint to Int +impl TryFrom for Int512 { + type Error = ConversionOverflowError; + + fn try_from(value: Uint512) -> Result { + // Self::MAX fits into Uint512, so we can just cast it + if value.0 > Self::MAX.0.as_() { + return Err(ConversionOverflowError::new("Uint512", "Int512", value)); + } + + // at this point we know it fits + Ok(Self(value.0.as_())) + } +} + impl From for Int512 { fn from(val: Uint256) -> Self { let mut bytes = [0u8; 64]; @@ -334,6 +353,7 @@ impl From for Int512 { } } +// uint to Int impl From for Int512 { fn from(val: u128) -> Self { Int512(val.into()) @@ -364,6 +384,7 @@ impl From for Int512 { } } +// int to Int impl From for Int512 { fn from(val: i128) -> Self { Int512(val.into()) @@ -394,6 +415,7 @@ impl From for Int512 { } } +// Int to Int impl From for Int512 { fn from(val: Int64) -> Self { Int512(val.i64().into()) diff --git a/packages/std/src/math/int64.rs b/packages/std/src/math/int64.rs index dabb8be22a..e53c0d36bb 100644 --- a/packages/std/src/math/int64.rs +++ b/packages/std/src/math/int64.rs @@ -11,7 +11,7 @@ use serde::{de, ser, Deserialize, Deserializer, Serialize}; use crate::errors::{DivideByZeroError, DivisionError, OverflowError, OverflowOperation, StdError}; use crate::{ forward_ref_partial_eq, CheckedMultiplyRatioError, ConversionOverflowError, Int128, Int256, - Int512, Uint64, + Int512, Uint128, Uint256, Uint512, Uint64, }; use super::conversion::shrink_be_int; @@ -270,6 +270,7 @@ impl Int64 { } } +// uint to Int impl From for Int64 { fn from(val: u32) -> Self { Int64(val.into()) @@ -288,6 +289,7 @@ impl From for Int64 { } } +// int to Int impl From for Int64 { fn from(val: i64) -> Self { Int64(val) @@ -312,6 +314,7 @@ impl From for Int64 { } } +// Int to Int impl TryFrom for Int64 { type Error = ConversionOverflowError; @@ -342,6 +345,55 @@ impl TryFrom for Int64 { } } +// Uint to Int +impl TryFrom for Int64 { + type Error = ConversionOverflowError; + + fn try_from(value: Uint64) -> Result { + value + .u64() + .try_into() // convert to i64 + .map(Self::new) + .map_err(|_| ConversionOverflowError::new("Uint64", "Int64", value)) + } +} + +impl TryFrom for Int64 { + type Error = ConversionOverflowError; + + fn try_from(value: Uint128) -> Result { + value + .u128() + .try_into() // convert to i64 + .map(Self::new) + .map_err(|_| ConversionOverflowError::new("Uint64", "Int64", value)) + } +} + +impl TryFrom for Int64 { + type Error = ConversionOverflowError; + + fn try_from(value: Uint256) -> Result { + value + .0 + .try_into() + .map(Int64) + .map_err(|_| ConversionOverflowError::new("Uint256", "Int64", value)) + } +} + +impl TryFrom for Int64 { + type Error = ConversionOverflowError; + + fn try_from(value: Uint512) -> Result { + value + .0 + .try_into() + .map(Int64) + .map_err(|_| ConversionOverflowError::new("Uint512", "Int64", value)) + } +} + impl TryFrom<&str> for Int64 { type Error = StdError; From d170bb643dbcbcd4b8562f487b8f1049f123758a Mon Sep 17 00:00:00 2001 From: Christoph Otter Date: Wed, 13 Sep 2023 14:15:12 +0200 Subject: [PATCH 03/11] Simplify TryFrom impls --- packages/std/src/math/conversion.rs | 65 +++++++++++++++++++++ packages/std/src/math/int128.rs | 64 +++------------------ packages/std/src/math/int256.rs | 44 ++------------- packages/std/src/math/int512.rs | 19 +------ packages/std/src/math/int64.rs | 87 +++-------------------------- packages/std/src/math/uint128.rs | 19 ++++--- packages/std/src/math/uint256.rs | 12 +--- packages/std/src/math/uint512.rs | 12 +--- packages/std/src/math/uint64.rs | 14 ++++- 9 files changed, 118 insertions(+), 218 deletions(-) diff --git a/packages/std/src/math/conversion.rs b/packages/std/src/math/conversion.rs index 7d3c9dfdb3..1872a3e2df 100644 --- a/packages/std/src/math/conversion.rs +++ b/packages/std/src/math/conversion.rs @@ -60,6 +60,71 @@ pub fn shrink_be_int( Some(output) } +/// Helper macro to implement `TryFrom` for a type that is just a wrapper around another type. +/// This can be used for all our integer conversions where `bint` implements `TryFrom`. +macro_rules! forward_try_from { + ($input: ty, $output: ty) => { + impl TryFrom<$input> for $output { + type Error = ConversionOverflowError; + + fn try_from(value: $input) -> Result { + value.0.try_into().map(Self).map_err(|_| { + ConversionOverflowError::new(stringify!($input), stringify!($output), value) + }) + } + } + }; +} +pub(crate) use forward_try_from; + +// TODO: assert statically that input is bigger than output and that both are ints +/// Helper macro to implement `TryFrom` for a conversion from a bigger signed int to a smaller one. +/// This is needed because `bint` does not implement `TryFrom` for those conversions +/// because of limitations of const generics. +macro_rules! try_from_int_to_int { + ($input: ty, $output: ty) => { + impl TryFrom<$input> for $output { + type Error = ConversionOverflowError; + + fn try_from(value: $input) -> Result { + $crate::math::conversion::shrink_be_int(value.to_be_bytes()) + .ok_or_else(|| { + ConversionOverflowError::new(stringify!($input), stringify!($output), value) + }) + .map(Self::from_be_bytes) + } + } + }; +} +pub(crate) use try_from_int_to_int; + +// TODO: assert statically that input is bigger than output and that both are ints +macro_rules! try_from_uint_to_int { + ($input: ty, $output: ty) => { + impl TryFrom<$input> for $output { + type Error = ConversionOverflowError; + + fn try_from(value: $input) -> Result { + // $input has to be bigger than $output, + // otherwise we would not need a `TryFrom` impl, so we can just cast it + use bnum::prelude::As; + if value.0 > Self::MAX.0.as_() { + return Err(ConversionOverflowError::new( + stringify!($input), + stringify!($output), + value, + )); + } + + // at this point we know it fits + Ok(Self(value.0.as_())) + } + } + }; +} + +pub(crate) use try_from_uint_to_int; + #[cfg(test)] mod tests { use super::*; diff --git a/packages/std/src/math/int128.rs b/packages/std/src/math/int128.rs index 82425376f0..56e2b7cab8 100644 --- a/packages/std/src/math/int128.rs +++ b/packages/std/src/math/int128.rs @@ -14,7 +14,7 @@ use crate::{ Int64, Uint128, Uint256, Uint512, Uint64, }; -use super::conversion::shrink_be_int; +use super::conversion::{forward_try_from, try_from_int_to_int}; /// An implementation of i128 that is using strings for JSON encoding/decoding, /// such that the full i128 range can be used for clients that convert JSON numbers to floats, @@ -30,7 +30,7 @@ use super::conversion::shrink_be_int; /// assert_eq!(a.i128(), 258); /// ``` #[derive(Copy, Clone, Default, Debug, PartialEq, Eq, PartialOrd, Ord, JsonSchema)] -pub struct Int128(#[schemars(with = "String")] i128); +pub struct Int128(#[schemars(with = "String")] pub(crate) i128); forward_ref_partial_eq!(Int128, Int128); @@ -276,42 +276,9 @@ impl From for Int128 { val.u64().into() } } - -impl TryFrom for Int128 { - type Error = ConversionOverflowError; - - fn try_from(value: Uint128) -> Result { - value - .u128() - .try_into() // convert to i128 - .map(Self::new) - .map_err(|_| ConversionOverflowError::new("Uint128", "Int128", value)) - } -} - -impl TryFrom for Int128 { - type Error = ConversionOverflowError; - - fn try_from(value: Uint256) -> Result { - value - .0 - .try_into() // convert to i128 - .map(Self::new) - .map_err(|_| ConversionOverflowError::new("Uint256", "Int128", value)) - } -} - -impl TryFrom for Int128 { - type Error = ConversionOverflowError; - - fn try_from(value: Uint512) -> Result { - value - .0 - .try_into() // convert to i128 - .map(Self::new) - .map_err(|_| ConversionOverflowError::new("Uint512", "Int128", value)) - } -} +forward_try_from!(Uint128, Int128); +forward_try_from!(Uint256, Int128); +forward_try_from!(Uint512, Int128); // uint to Int impl From for Int128 { @@ -345,25 +312,8 @@ impl From for Int128 { } } -impl TryFrom for Int128 { - type Error = ConversionOverflowError; - - fn try_from(value: Int256) -> Result { - shrink_be_int(value.to_be_bytes()) - .ok_or_else(|| ConversionOverflowError::new("Int256", "Int128", value)) - .map(Self::from_be_bytes) - } -} - -impl TryFrom for Int128 { - type Error = ConversionOverflowError; - - fn try_from(value: Int512) -> Result { - shrink_be_int(value.to_be_bytes()) - .ok_or_else(|| ConversionOverflowError::new("Int512", "Int128", value)) - .map(Self::from_be_bytes) - } -} +try_from_int_to_int!(Int256, Int128); +try_from_int_to_int!(Int512, Int128); // int to Int impl From for Int128 { diff --git a/packages/std/src/math/int256.rs b/packages/std/src/math/int256.rs index 1dfb89ca0c..9c5f8707be 100644 --- a/packages/std/src/math/int256.rs +++ b/packages/std/src/math/int256.rs @@ -1,4 +1,3 @@ -use bnum::prelude::As; use core::fmt; use core::ops::{ Add, AddAssign, Div, DivAssign, Mul, MulAssign, Neg, Not, Rem, RemAssign, Shl, ShlAssign, Shr, @@ -19,7 +18,7 @@ use crate::{ /// the implementation in the future. use bnum::types::{I256, U256}; -use super::conversion::{grow_be_int, shrink_be_int}; +use super::conversion::{grow_be_int, try_from_int_to_int, try_from_uint_to_int}; /// An implementation of i256 that is using strings for JSON encoding/decoding, /// such that the full i256 range can be used for clients that convert JSON numbers to floats, @@ -42,7 +41,7 @@ use super::conversion::{grow_be_int, shrink_be_int}; /// assert_eq!(a, b); /// ``` #[derive(Copy, Clone, Default, Debug, PartialEq, Eq, PartialOrd, Ord, JsonSchema)] -pub struct Int256(#[schemars(with = "String")] I256); +pub struct Int256(#[schemars(with = "String")] pub(crate) I256); forward_ref_partial_eq!(Int256, Int256); @@ -330,33 +329,8 @@ impl Int256 { } // Uint to Int -impl TryFrom for Int256 { - type Error = ConversionOverflowError; - - fn try_from(value: Uint512) -> Result { - // Self::MAX fits into Uint512, so we can just cast it - if value.0 > Self::MAX.0.as_() { - return Err(ConversionOverflowError::new("Uint512", "Int256", value)); - } - - // at this point we know it fits - Ok(Self(value.0.as_())) - } -} - -impl TryFrom for Int256 { - type Error = ConversionOverflowError; - - fn try_from(value: Uint256) -> Result { - // Self::MAX fits into Uint256, so we can just cast it - if value.0 > Self::MAX.0.as_() { - return Err(ConversionOverflowError::new("Uint256", "Int256", value)); - } - - // at this point we know it fits - Ok(Self(value.0.as_())) - } -} +try_from_uint_to_int!(Uint512, Int256); +try_from_uint_to_int!(Uint256, Int256); impl From for Int256 { fn from(val: Uint128) -> Self { @@ -402,15 +376,7 @@ impl From for Int256 { } // Int to Int -impl TryFrom for Int256 { - type Error = ConversionOverflowError; - - fn try_from(value: Int512) -> Result { - shrink_be_int(value.to_be_bytes()) - .ok_or_else(|| ConversionOverflowError::new("Int512", "Int256", value)) - .map(Self::from_be_bytes) - } -} +try_from_int_to_int!(Int512, Int256); impl From for Int256 { fn from(val: Int128) -> Self { diff --git a/packages/std/src/math/int512.rs b/packages/std/src/math/int512.rs index 2308b8d518..c58904069f 100644 --- a/packages/std/src/math/int512.rs +++ b/packages/std/src/math/int512.rs @@ -1,4 +1,3 @@ -use bnum::prelude::As; use core::fmt; use core::ops::{ Add, AddAssign, Div, DivAssign, Mul, MulAssign, Neg, Not, Rem, RemAssign, Shl, ShlAssign, Shr, @@ -19,7 +18,7 @@ use crate::{ /// the implementation in the future. use bnum::types::{I512, U512}; -use super::conversion::grow_be_int; +use super::conversion::{grow_be_int, try_from_uint_to_int}; /// An implementation of i512 that is using strings for JSON encoding/decoding, /// such that the full i512 range can be used for clients that convert JSON numbers to floats, @@ -46,7 +45,7 @@ use super::conversion::grow_be_int; /// assert_eq!(a, b); /// ``` #[derive(Copy, Clone, Default, Debug, PartialEq, Eq, PartialOrd, Ord, JsonSchema)] -pub struct Int512(#[schemars(with = "String")] I512); +pub struct Int512(#[schemars(with = "String")] pub(crate) I512); forward_ref_partial_eq!(Int512, Int512); @@ -318,19 +317,7 @@ impl Int512 { } // Uint to Int -impl TryFrom for Int512 { - type Error = ConversionOverflowError; - - fn try_from(value: Uint512) -> Result { - // Self::MAX fits into Uint512, so we can just cast it - if value.0 > Self::MAX.0.as_() { - return Err(ConversionOverflowError::new("Uint512", "Int512", value)); - } - - // at this point we know it fits - Ok(Self(value.0.as_())) - } -} +try_from_uint_to_int!(Uint512, Int512); impl From for Int512 { fn from(val: Uint256) -> Self { diff --git a/packages/std/src/math/int64.rs b/packages/std/src/math/int64.rs index e53c0d36bb..ed5f8f4a70 100644 --- a/packages/std/src/math/int64.rs +++ b/packages/std/src/math/int64.rs @@ -14,7 +14,7 @@ use crate::{ Int512, Uint128, Uint256, Uint512, Uint64, }; -use super::conversion::shrink_be_int; +use super::conversion::{forward_try_from, try_from_int_to_int}; /// An implementation of i64 that is using strings for JSON encoding/decoding, /// such that the full i64 range can be used for clients that convert JSON numbers to floats, @@ -30,7 +30,7 @@ use super::conversion::shrink_be_int; /// assert_eq!(a.i64(), 258); /// ``` #[derive(Copy, Clone, Default, Debug, PartialEq, Eq, PartialOrd, Ord, JsonSchema)] -pub struct Int64(#[schemars(with = "String")] i64); +pub struct Int64(#[schemars(with = "String")] pub(crate) i64); forward_ref_partial_eq!(Int64, Int64); @@ -315,84 +315,15 @@ impl From for Int64 { } // Int to Int -impl TryFrom for Int64 { - type Error = ConversionOverflowError; - - fn try_from(value: Int128) -> Result { - shrink_be_int(value.to_be_bytes()) - .ok_or_else(|| ConversionOverflowError::new("Int128", "Int64", value)) - .map(Self::from_be_bytes) - } -} - -impl TryFrom for Int64 { - type Error = ConversionOverflowError; - - fn try_from(value: Int256) -> Result { - shrink_be_int(value.to_be_bytes()) - .ok_or_else(|| ConversionOverflowError::new("Int256", "Int64", value)) - .map(Self::from_be_bytes) - } -} - -impl TryFrom for Int64 { - type Error = ConversionOverflowError; - - fn try_from(value: Int512) -> Result { - shrink_be_int(value.to_be_bytes()) - .ok_or_else(|| ConversionOverflowError::new("Int512", "Int64", value)) - .map(Self::from_be_bytes) - } -} +try_from_int_to_int!(Int128, Int64); +try_from_int_to_int!(Int256, Int64); +try_from_int_to_int!(Int512, Int64); // Uint to Int -impl TryFrom for Int64 { - type Error = ConversionOverflowError; - - fn try_from(value: Uint64) -> Result { - value - .u64() - .try_into() // convert to i64 - .map(Self::new) - .map_err(|_| ConversionOverflowError::new("Uint64", "Int64", value)) - } -} - -impl TryFrom for Int64 { - type Error = ConversionOverflowError; - - fn try_from(value: Uint128) -> Result { - value - .u128() - .try_into() // convert to i64 - .map(Self::new) - .map_err(|_| ConversionOverflowError::new("Uint64", "Int64", value)) - } -} - -impl TryFrom for Int64 { - type Error = ConversionOverflowError; - - fn try_from(value: Uint256) -> Result { - value - .0 - .try_into() - .map(Int64) - .map_err(|_| ConversionOverflowError::new("Uint256", "Int64", value)) - } -} - -impl TryFrom for Int64 { - type Error = ConversionOverflowError; - - fn try_from(value: Uint512) -> Result { - value - .0 - .try_into() - .map(Int64) - .map_err(|_| ConversionOverflowError::new("Uint512", "Int64", value)) - } -} +forward_try_from!(Uint64, Int64); +forward_try_from!(Uint128, Int64); +forward_try_from!(Uint256, Int64); +forward_try_from!(Uint512, Int64); impl TryFrom<&str> for Int64 { type Error = StdError; diff --git a/packages/std/src/math/uint128.rs b/packages/std/src/math/uint128.rs index 06e9549929..009c059222 100644 --- a/packages/std/src/math/uint128.rs +++ b/packages/std/src/math/uint128.rs @@ -15,9 +15,12 @@ use crate::errors::{ OverflowOperation, StdError, }; use crate::{ - forward_ref_partial_eq, impl_mul_fraction, ConversionOverflowError, Fraction, Uint256, Uint64, + forward_ref_partial_eq, impl_mul_fraction, ConversionOverflowError, Fraction, Int128, Int256, + Int512, Int64, Uint256, Uint64, }; +use super::conversion::forward_try_from; + /// A thin wrapper around u128 that is using strings for JSON encoding/decoding, /// such that the full u128 range can be used for clients that convert JSON numbers to floats, /// like JavaScript and jq. @@ -312,15 +315,13 @@ impl From for Uint128 { } } -impl TryFrom for Uint64 { - type Error = ConversionOverflowError; +forward_try_from!(Uint128, Uint64); - fn try_from(value: Uint128) -> Result { - Ok(Uint64::new(value.0.try_into().map_err(|_| { - ConversionOverflowError::new("Uint128", "Uint64", value.to_string()) - })?)) - } -} +// Int to Uint +forward_try_from!(Int64, Uint128); +forward_try_from!(Int128, Uint128); +forward_try_from!(Int256, Uint128); +forward_try_from!(Int512, Uint128); impl TryFrom<&str> for Uint128 { type Error = StdError; diff --git a/packages/std/src/math/uint256.rs b/packages/std/src/math/uint256.rs index b9de472435..1c6e7f9def 100644 --- a/packages/std/src/math/uint256.rs +++ b/packages/std/src/math/uint256.rs @@ -19,6 +19,8 @@ use crate::{forward_ref_partial_eq, impl_mul_fraction, Fraction, Uint128, Uint51 /// the implementation in the future. use bnum::types::U256; +use super::conversion::forward_try_from; + /// An implementation of u256 that is using strings for JSON encoding/decoding, /// such that the full u256 range can be used for clients that convert JSON numbers to floats, /// like JavaScript and jq. @@ -379,15 +381,7 @@ impl From for Uint256 { } } -impl TryFrom for Uint128 { - type Error = ConversionOverflowError; - - fn try_from(value: Uint256) -> Result { - Ok(Uint128::new(value.0.try_into().map_err(|_| { - ConversionOverflowError::new("Uint256", "Uint128", value.to_string()) - })?)) - } -} +forward_try_from!(Uint256, Uint128); impl TryFrom for Uint64 { type Error = ConversionOverflowError; diff --git a/packages/std/src/math/uint512.rs b/packages/std/src/math/uint512.rs index dd5a1e9aa0..7f418afb11 100644 --- a/packages/std/src/math/uint512.rs +++ b/packages/std/src/math/uint512.rs @@ -17,6 +17,8 @@ use crate::{forward_ref_partial_eq, Uint128, Uint256, Uint64}; /// the implementation in the future. use bnum::types::U512; +use super::conversion::forward_try_from; + /// An implementation of u512 that is using strings for JSON encoding/decoding, /// such that the full u512 range can be used for clients that convert JSON numbers to floats, /// like JavaScript and jq. @@ -370,15 +372,7 @@ impl TryFrom for Uint256 { } } -impl TryFrom for Uint128 { - type Error = ConversionOverflowError; - - fn try_from(value: Uint512) -> Result { - Ok(Uint128::new(value.0.try_into().map_err(|_| { - ConversionOverflowError::new("Uint512", "Uint128", value.to_string()) - })?)) - } -} +forward_try_from!(Uint512, Uint128); impl TryFrom for Uint64 { type Error = ConversionOverflowError; diff --git a/packages/std/src/math/uint64.rs b/packages/std/src/math/uint64.rs index f405b23643..0881952110 100644 --- a/packages/std/src/math/uint64.rs +++ b/packages/std/src/math/uint64.rs @@ -12,7 +12,12 @@ use crate::errors::{ CheckedMultiplyFractionError, CheckedMultiplyRatioError, DivideByZeroError, OverflowError, OverflowOperation, StdError, }; -use crate::{forward_ref_partial_eq, impl_mul_fraction, Fraction, Uint128}; +use crate::{ + forward_ref_partial_eq, impl_mul_fraction, ConversionOverflowError, Fraction, Int128, Int256, + Int512, Int64, Uint128, +}; + +use super::conversion::forward_try_from; /// A thin wrapper around u64 that is using strings for JSON encoding/decoding, /// such that the full u64 range can be used for clients that convert JSON numbers to floats, @@ -269,6 +274,7 @@ impl_mul_fraction!(Uint64); // of the conflict with `TryFrom<&str>` as described here // https://stackoverflow.com/questions/63136970/how-do-i-work-around-the-upstream-crates-may-add-a-new-impl-of-trait-error +// uint to Uint impl From for Uint64 { fn from(val: u64) -> Self { Uint64(val) @@ -293,6 +299,12 @@ impl From for Uint64 { } } +// Int to Uint +forward_try_from!(Int64, Uint64); +forward_try_from!(Int128, Uint64); +forward_try_from!(Int256, Uint64); +forward_try_from!(Int512, Uint64); + impl TryFrom<&str> for Uint64 { type Error = StdError; From e0171af4a85da22e365ab2a035240d0eee03536f Mon Sep 17 00:00:00 2001 From: Christoph Otter Date: Wed, 13 Sep 2023 16:41:36 +0200 Subject: [PATCH 04/11] Add signed to unsigned TryFroms --- packages/std/src/math/conversion.rs | 73 +++++++++++++++++++++++------ packages/std/src/math/int512.rs | 5 +- packages/std/src/math/uint256.rs | 22 ++++----- packages/std/src/math/uint512.rs | 19 ++++---- 4 files changed, 79 insertions(+), 40 deletions(-) diff --git a/packages/std/src/math/conversion.rs b/packages/std/src/math/conversion.rs index 1872a3e2df..15f2602803 100644 --- a/packages/std/src/math/conversion.rs +++ b/packages/std/src/math/conversion.rs @@ -61,16 +61,18 @@ pub fn shrink_be_int( } /// Helper macro to implement `TryFrom` for a type that is just a wrapper around another type. -/// This can be used for all our integer conversions where `bint` implements `TryFrom`. +/// This can be used for all our integer conversions where `bnum` implements `TryFrom`. macro_rules! forward_try_from { ($input: ty, $output: ty) => { impl TryFrom<$input> for $output { - type Error = ConversionOverflowError; + type Error = $crate::ConversionOverflowError; fn try_from(value: $input) -> Result { - value.0.try_into().map(Self).map_err(|_| { - ConversionOverflowError::new(stringify!($input), stringify!($output), value) - }) + value + .0 + .try_into() + .map(Self) + .map_err(|_| Self::Error::new(stringify!($input), stringify!($output), value)) } } }; @@ -79,18 +81,16 @@ pub(crate) use forward_try_from; // TODO: assert statically that input is bigger than output and that both are ints /// Helper macro to implement `TryFrom` for a conversion from a bigger signed int to a smaller one. -/// This is needed because `bint` does not implement `TryFrom` for those conversions +/// This is needed because `bnum` does not implement `TryFrom` for those conversions /// because of limitations of const generics. macro_rules! try_from_int_to_int { ($input: ty, $output: ty) => { impl TryFrom<$input> for $output { - type Error = ConversionOverflowError; + type Error = $crate::ConversionOverflowError; fn try_from(value: $input) -> Result { $crate::math::conversion::shrink_be_int(value.to_be_bytes()) - .ok_or_else(|| { - ConversionOverflowError::new(stringify!($input), stringify!($output), value) - }) + .ok_or_else(|| Self::Error::new(stringify!($input), stringify!($output), value)) .map(Self::from_be_bytes) } } @@ -98,18 +98,20 @@ macro_rules! try_from_int_to_int { } pub(crate) use try_from_int_to_int; -// TODO: assert statically that input is bigger than output and that both are ints +/// Helper macro to implement `TryFrom` for a conversion from a unsigned int to a smaller or +/// equal sized signed int. +/// This is needed because `bnum` does not implement `TryFrom` for all of those conversions. macro_rules! try_from_uint_to_int { ($input: ty, $output: ty) => { impl TryFrom<$input> for $output { - type Error = ConversionOverflowError; + type Error = $crate::ConversionOverflowError; fn try_from(value: $input) -> Result { - // $input has to be bigger than $output, + // $input::MAX has to be bigger than $output::MAX, // otherwise we would not need a `TryFrom` impl, so we can just cast it use bnum::prelude::As; if value.0 > Self::MAX.0.as_() { - return Err(ConversionOverflowError::new( + return Err(Self::Error::new( stringify!($input), stringify!($output), value, @@ -125,6 +127,49 @@ macro_rules! try_from_uint_to_int { pub(crate) use try_from_uint_to_int; +/// Helper macro to implement `TryFrom` for a conversion from a signed int to an unsigned int. +/// This is needed because `bnum` does not implement `TryFrom` for all of those conversions. +macro_rules! try_from_int_to_uint { + ($input: ty, $output: ty) => { + impl TryFrom<$input> for $output { + type Error = ConversionOverflowError; + + fn try_from(value: $input) -> Result { + use bnum::prelude::As; + // if $input::MAX is smaller than $output::MAX, we only need to check the sign + if core::mem::size_of::<$input>() <= core::mem::size_of::<$output>() { + if value.is_negative() { + return Err(ConversionOverflowError::new( + stringify!($input), + stringify!($output), + value, + )); + } + + // otherwise we can just cast it + Ok(Self(value.0.as_())) + } else { + // $output::MAX is smaller than $input::MAX. + // If it is negative or too big, we error. + // We can safely cast $output::MAX to $input size + if value.is_negative() || value.0 > <$output>::MAX.0.as_() { + return Err(ConversionOverflowError::new( + stringify!($input), + stringify!($output), + value, + )); + } + + // at this point we know it fits + Ok(Self(value.0.as_())) + } + } + } + }; +} + +pub(crate) use try_from_int_to_uint; + #[cfg(test)] mod tests { use super::*; diff --git a/packages/std/src/math/int512.rs b/packages/std/src/math/int512.rs index c58904069f..318204408a 100644 --- a/packages/std/src/math/int512.rs +++ b/packages/std/src/math/int512.rs @@ -9,10 +9,7 @@ use schemars::JsonSchema; use serde::{de, ser, Deserialize, Deserializer, Serialize}; use crate::errors::{DivideByZeroError, DivisionError, OverflowError, OverflowOperation, StdError}; -use crate::{ - forward_ref_partial_eq, ConversionOverflowError, Int128, Int256, Int64, Uint128, Uint256, - Uint512, Uint64, -}; +use crate::{forward_ref_partial_eq, Int128, Int256, Int64, Uint128, Uint256, Uint512, Uint64}; /// Used internally - we don't want to leak this type since we might change /// the implementation in the future. diff --git a/packages/std/src/math/uint256.rs b/packages/std/src/math/uint256.rs index 1c6e7f9def..4d08268c63 100644 --- a/packages/std/src/math/uint256.rs +++ b/packages/std/src/math/uint256.rs @@ -13,13 +13,16 @@ use crate::errors::{ CheckedMultiplyFractionError, CheckedMultiplyRatioError, ConversionOverflowError, DivideByZeroError, OverflowError, OverflowOperation, StdError, }; -use crate::{forward_ref_partial_eq, impl_mul_fraction, Fraction, Uint128, Uint512, Uint64}; +use crate::{ + forward_ref_partial_eq, impl_mul_fraction, Fraction, Int128, Int256, Int512, Int64, Uint128, + Uint512, Uint64, +}; /// Used internally - we don't want to leak this type since we might change /// the implementation in the future. use bnum::types::U256; -use super::conversion::forward_try_from; +use super::conversion::{forward_try_from, try_from_int_to_uint}; /// An implementation of u256 that is using strings for JSON encoding/decoding, /// such that the full u256 range can be used for clients that convert JSON numbers to floats, @@ -382,16 +385,13 @@ impl From for Uint256 { } forward_try_from!(Uint256, Uint128); +forward_try_from!(Uint256, Uint64); -impl TryFrom for Uint64 { - type Error = ConversionOverflowError; - - fn try_from(value: Uint256) -> Result { - Ok(Uint64::new(value.0.try_into().map_err(|_| { - ConversionOverflowError::new("Uint256", "Uint64", value.to_string()) - })?)) - } -} +// Int to Uint +try_from_int_to_uint!(Int64, Uint256); +try_from_int_to_uint!(Int128, Uint256); +try_from_int_to_uint!(Int256, Uint256); +try_from_int_to_uint!(Int512, Uint256); impl TryFrom<&str> for Uint256 { type Error = StdError; diff --git a/packages/std/src/math/uint512.rs b/packages/std/src/math/uint512.rs index 7f418afb11..424997dbcf 100644 --- a/packages/std/src/math/uint512.rs +++ b/packages/std/src/math/uint512.rs @@ -11,13 +11,13 @@ use serde::{de, ser, Deserialize, Deserializer, Serialize}; use crate::errors::{ ConversionOverflowError, DivideByZeroError, OverflowError, OverflowOperation, StdError, }; -use crate::{forward_ref_partial_eq, Uint128, Uint256, Uint64}; +use crate::{forward_ref_partial_eq, Int128, Int256, Int512, Int64, Uint128, Uint256, Uint64}; /// Used internally - we don't want to leak this type since we might change /// the implementation in the future. use bnum::types::U512; -use super::conversion::forward_try_from; +use super::conversion::{forward_try_from, try_from_int_to_uint}; /// An implementation of u512 that is using strings for JSON encoding/decoding, /// such that the full u512 range can be used for clients that convert JSON numbers to floats, @@ -373,16 +373,13 @@ impl TryFrom for Uint256 { } forward_try_from!(Uint512, Uint128); +forward_try_from!(Uint512, Uint64); -impl TryFrom for Uint64 { - type Error = ConversionOverflowError; - - fn try_from(value: Uint512) -> Result { - Ok(Uint64::new(value.0.try_into().map_err(|_| { - ConversionOverflowError::new("Uint512", "Uint64", value.to_string()) - })?)) - } -} +// Int to Uint +try_from_int_to_uint!(Int64, Uint512); +try_from_int_to_uint!(Int128, Uint512); +try_from_int_to_uint!(Int256, Uint512); +try_from_int_to_uint!(Int512, Uint512); impl TryFrom<&str> for Uint512 { type Error = StdError; From 0249e687ae1e2ae1ce99fde329ee9c81fa0fbb80 Mon Sep 17 00:00:00 2001 From: Christoph Otter Date: Wed, 13 Sep 2023 16:45:25 +0200 Subject: [PATCH 05/11] Fix unused imports --- packages/std/src/math/int128.rs | 4 ++-- packages/std/src/math/int256.rs | 4 ++-- packages/std/src/math/int64.rs | 4 ++-- packages/std/src/math/uint128.rs | 4 ++-- packages/std/src/math/uint64.rs | 3 +-- 5 files changed, 9 insertions(+), 10 deletions(-) diff --git a/packages/std/src/math/int128.rs b/packages/std/src/math/int128.rs index 56e2b7cab8..ecce5948d3 100644 --- a/packages/std/src/math/int128.rs +++ b/packages/std/src/math/int128.rs @@ -10,8 +10,8 @@ use serde::{de, ser, Deserialize, Deserializer, Serialize}; use crate::errors::{DivideByZeroError, DivisionError, OverflowError, OverflowOperation, StdError}; use crate::{ - forward_ref_partial_eq, CheckedMultiplyRatioError, ConversionOverflowError, Int256, Int512, - Int64, Uint128, Uint256, Uint512, Uint64, + forward_ref_partial_eq, CheckedMultiplyRatioError, Int256, Int512, Int64, Uint128, Uint256, + Uint512, Uint64, }; use super::conversion::{forward_try_from, try_from_int_to_int}; diff --git a/packages/std/src/math/int256.rs b/packages/std/src/math/int256.rs index 9c5f8707be..2e7dcb9206 100644 --- a/packages/std/src/math/int256.rs +++ b/packages/std/src/math/int256.rs @@ -10,8 +10,8 @@ use serde::{de, ser, Deserialize, Deserializer, Serialize}; use crate::errors::{DivideByZeroError, DivisionError, OverflowError, OverflowOperation, StdError}; use crate::{ - forward_ref_partial_eq, CheckedMultiplyRatioError, ConversionOverflowError, Int128, Int512, - Int64, Uint128, Uint256, Uint512, Uint64, + forward_ref_partial_eq, CheckedMultiplyRatioError, Int128, Int512, Int64, Uint128, Uint256, + Uint512, Uint64, }; /// Used internally - we don't want to leak this type since we might change diff --git a/packages/std/src/math/int64.rs b/packages/std/src/math/int64.rs index ed5f8f4a70..006259b305 100644 --- a/packages/std/src/math/int64.rs +++ b/packages/std/src/math/int64.rs @@ -10,8 +10,8 @@ use serde::{de, ser, Deserialize, Deserializer, Serialize}; use crate::errors::{DivideByZeroError, DivisionError, OverflowError, OverflowOperation, StdError}; use crate::{ - forward_ref_partial_eq, CheckedMultiplyRatioError, ConversionOverflowError, Int128, Int256, - Int512, Uint128, Uint256, Uint512, Uint64, + forward_ref_partial_eq, CheckedMultiplyRatioError, Int128, Int256, Int512, Uint128, Uint256, + Uint512, Uint64, }; use super::conversion::{forward_try_from, try_from_int_to_int}; diff --git a/packages/std/src/math/uint128.rs b/packages/std/src/math/uint128.rs index 009c059222..e19decc205 100644 --- a/packages/std/src/math/uint128.rs +++ b/packages/std/src/math/uint128.rs @@ -15,8 +15,8 @@ use crate::errors::{ OverflowOperation, StdError, }; use crate::{ - forward_ref_partial_eq, impl_mul_fraction, ConversionOverflowError, Fraction, Int128, Int256, - Int512, Int64, Uint256, Uint64, + forward_ref_partial_eq, impl_mul_fraction, Fraction, Int128, Int256, Int512, Int64, Uint256, + Uint64, }; use super::conversion::forward_try_from; diff --git a/packages/std/src/math/uint64.rs b/packages/std/src/math/uint64.rs index 0881952110..bcc46b6e37 100644 --- a/packages/std/src/math/uint64.rs +++ b/packages/std/src/math/uint64.rs @@ -13,8 +13,7 @@ use crate::errors::{ OverflowOperation, StdError, }; use crate::{ - forward_ref_partial_eq, impl_mul_fraction, ConversionOverflowError, Fraction, Int128, Int256, - Int512, Int64, Uint128, + forward_ref_partial_eq, impl_mul_fraction, Fraction, Int128, Int256, Int512, Int64, Uint128, }; use super::conversion::forward_try_from; From 56887586a26f75b42e54a95d9206f67215a1779c Mon Sep 17 00:00:00 2001 From: Christoph Otter Date: Wed, 13 Sep 2023 17:12:34 +0200 Subject: [PATCH 06/11] Fix test --- packages/std/src/math/uint128.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/std/src/math/uint128.rs b/packages/std/src/math/uint128.rs index e19decc205..99f811158f 100644 --- a/packages/std/src/math/uint128.rs +++ b/packages/std/src/math/uint128.rs @@ -608,7 +608,7 @@ where #[cfg(test)] mod tests { use crate::errors::CheckedMultiplyFractionError::{ConversionOverflow, DivideByZero}; - use crate::{from_slice, to_vec, Decimal}; + use crate::{from_slice, to_vec, ConversionOverflowError, Decimal}; use super::*; From 4a2cd85cdb2ccdae044ad5dc416118945bfda293 Mon Sep 17 00:00:00 2001 From: Christoph Otter Date: Wed, 13 Sep 2023 17:23:13 +0200 Subject: [PATCH 07/11] Add static assertions --- Cargo.lock | 7 +++++++ packages/std/Cargo.toml | 1 + packages/std/src/math/conversion.rs | 24 +++++++++++++++++++++--- 3 files changed, 29 insertions(+), 3 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index d23defb584..83ac5a0c44 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -502,6 +502,7 @@ dependencies = [ "serde-json-wasm", "serde_json", "sha2 0.10.6", + "static_assertions", "thiserror", ] @@ -2034,6 +2035,12 @@ version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" +[[package]] +name = "static_assertions" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" + [[package]] name = "strsim" version = "0.10.0" diff --git a/packages/std/Cargo.toml b/packages/std/Cargo.toml index 13b12083f3..323b73b70c 100644 --- a/packages/std/Cargo.toml +++ b/packages/std/Cargo.toml @@ -61,6 +61,7 @@ serde = { version = "1.0.103", default-features = false, features = ["derive", " serde-json-wasm = { version = "0.5.0" } thiserror = "1.0.26" bnum = "0.8.0" +static_assertions = "1.1.0" [target.'cfg(not(target_arch = "wasm32"))'.dependencies] cosmwasm-crypto = { path = "../crypto", version = "1.4.0" } diff --git a/packages/std/src/math/conversion.rs b/packages/std/src/math/conversion.rs index 15f2602803..f1292d4fb7 100644 --- a/packages/std/src/math/conversion.rs +++ b/packages/std/src/math/conversion.rs @@ -79,12 +79,15 @@ macro_rules! forward_try_from { } pub(crate) use forward_try_from; -// TODO: assert statically that input is bigger than output and that both are ints /// Helper macro to implement `TryFrom` for a conversion from a bigger signed int to a smaller one. /// This is needed because `bnum` does not implement `TryFrom` for those conversions /// because of limitations of const generics. macro_rules! try_from_int_to_int { ($input: ty, $output: ty) => { + // statically assert that the input is bigger than the output + static_assertions::const_assert!( + core::mem::size_of::<$input>() > core::mem::size_of::<$output>() + ); impl TryFrom<$input> for $output { type Error = $crate::ConversionOverflowError; @@ -103,13 +106,22 @@ pub(crate) use try_from_int_to_int; /// This is needed because `bnum` does not implement `TryFrom` for all of those conversions. macro_rules! try_from_uint_to_int { ($input: ty, $output: ty) => { + // statically assert that... + // input is unsigned + static_assertions::const_assert_eq!(stringify!($input).as_bytes()[0], b'U'); + // output is signed + static_assertions::const_assert_eq!(stringify!($output).as_bytes()[0], b'I'); + // input is bigger than output (otherwise we would not need a `TryFrom` impl) + static_assertions::const_assert!( + core::mem::size_of::<$input>() >= core::mem::size_of::<$output>() + ); + impl TryFrom<$input> for $output { type Error = $crate::ConversionOverflowError; fn try_from(value: $input) -> Result { - // $input::MAX has to be bigger than $output::MAX, - // otherwise we would not need a `TryFrom` impl, so we can just cast it use bnum::prelude::As; + // $input::MAX has to be bigger than $output::MAX, so we can just cast it if value.0 > Self::MAX.0.as_() { return Err(Self::Error::new( stringify!($input), @@ -131,6 +143,12 @@ pub(crate) use try_from_uint_to_int; /// This is needed because `bnum` does not implement `TryFrom` for all of those conversions. macro_rules! try_from_int_to_uint { ($input: ty, $output: ty) => { + // statically assert that... + // input is signed + static_assertions::const_assert_eq!(stringify!($input).as_bytes()[0], b'I'); + // output is unsigned + static_assertions::const_assert_eq!(stringify!($output).as_bytes()[0], b'U'); + impl TryFrom<$input> for $output { type Error = ConversionOverflowError; From 00746b5e21642a521d6bcdd2ddf22edcb8deda09 Mon Sep 17 00:00:00 2001 From: Christoph Otter Date: Wed, 13 Sep 2023 17:53:18 +0200 Subject: [PATCH 08/11] Fix Cargo.lock files --- contracts/burner/Cargo.lock | 7 +++++++ contracts/crypto-verify/Cargo.lock | 7 +++++++ contracts/cyberpunk/Cargo.lock | 7 +++++++ contracts/floaty/Cargo.lock | 7 +++++++ contracts/hackatom/Cargo.lock | 7 +++++++ contracts/ibc-reflect-send/Cargo.lock | 7 +++++++ contracts/ibc-reflect/Cargo.lock | 7 +++++++ contracts/queue/Cargo.lock | 7 +++++++ contracts/reflect/Cargo.lock | 7 +++++++ contracts/staking/Cargo.lock | 7 +++++++ contracts/virus/Cargo.lock | 7 +++++++ 11 files changed, 77 insertions(+) diff --git a/contracts/burner/Cargo.lock b/contracts/burner/Cargo.lock index fd095ac5a3..7fa350f505 100644 --- a/contracts/burner/Cargo.lock +++ b/contracts/burner/Cargo.lock @@ -244,6 +244,7 @@ dependencies = [ "serde", "serde-json-wasm", "sha2 0.10.3", + "static_assertions", "thiserror", ] @@ -1407,6 +1408,12 @@ version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" +[[package]] +name = "static_assertions" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" + [[package]] name = "strsim" version = "0.10.0" diff --git a/contracts/crypto-verify/Cargo.lock b/contracts/crypto-verify/Cargo.lock index 953bf83a64..5636fa7796 100644 --- a/contracts/crypto-verify/Cargo.lock +++ b/contracts/crypto-verify/Cargo.lock @@ -233,6 +233,7 @@ dependencies = [ "serde", "serde-json-wasm", "sha2 0.10.3", + "static_assertions", "thiserror", ] @@ -1450,6 +1451,12 @@ version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" +[[package]] +name = "static_assertions" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" + [[package]] name = "strsim" version = "0.10.0" diff --git a/contracts/cyberpunk/Cargo.lock b/contracts/cyberpunk/Cargo.lock index 4d37fda836..8e57b58a64 100644 --- a/contracts/cyberpunk/Cargo.lock +++ b/contracts/cyberpunk/Cargo.lock @@ -268,6 +268,7 @@ dependencies = [ "serde", "serde-json-wasm", "sha2 0.10.3", + "static_assertions", "thiserror", ] @@ -1531,6 +1532,12 @@ version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" +[[package]] +name = "static_assertions" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" + [[package]] name = "strsim" version = "0.10.0" diff --git a/contracts/floaty/Cargo.lock b/contracts/floaty/Cargo.lock index d3e15072a0..37e0fc6474 100644 --- a/contracts/floaty/Cargo.lock +++ b/contracts/floaty/Cargo.lock @@ -233,6 +233,7 @@ dependencies = [ "serde", "serde-json-wasm", "sha2 0.10.3", + "static_assertions", "thiserror", ] @@ -1408,6 +1409,12 @@ version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" +[[package]] +name = "static_assertions" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" + [[package]] name = "strsim" version = "0.10.0" diff --git a/contracts/hackatom/Cargo.lock b/contracts/hackatom/Cargo.lock index 1a9c1f9dff..f83083cc8f 100644 --- a/contracts/hackatom/Cargo.lock +++ b/contracts/hackatom/Cargo.lock @@ -233,6 +233,7 @@ dependencies = [ "serde", "serde-json-wasm", "sha2 0.10.3", + "static_assertions", "thiserror", ] @@ -1409,6 +1410,12 @@ version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" +[[package]] +name = "static_assertions" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" + [[package]] name = "strsim" version = "0.10.0" diff --git a/contracts/ibc-reflect-send/Cargo.lock b/contracts/ibc-reflect-send/Cargo.lock index 47e48de0e0..81954a09e6 100644 --- a/contracts/ibc-reflect-send/Cargo.lock +++ b/contracts/ibc-reflect-send/Cargo.lock @@ -233,6 +233,7 @@ dependencies = [ "serde", "serde-json-wasm", "sha2 0.10.3", + "static_assertions", "thiserror", ] @@ -1407,6 +1408,12 @@ version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" +[[package]] +name = "static_assertions" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" + [[package]] name = "strsim" version = "0.10.0" diff --git a/contracts/ibc-reflect/Cargo.lock b/contracts/ibc-reflect/Cargo.lock index 1408955925..6a64201229 100644 --- a/contracts/ibc-reflect/Cargo.lock +++ b/contracts/ibc-reflect/Cargo.lock @@ -233,6 +233,7 @@ dependencies = [ "serde", "serde-json-wasm", "sha2 0.10.3", + "static_assertions", "thiserror", ] @@ -1407,6 +1408,12 @@ version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" +[[package]] +name = "static_assertions" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" + [[package]] name = "strsim" version = "0.10.0" diff --git a/contracts/queue/Cargo.lock b/contracts/queue/Cargo.lock index 73f7809bc4..1bd7277f4b 100644 --- a/contracts/queue/Cargo.lock +++ b/contracts/queue/Cargo.lock @@ -233,6 +233,7 @@ dependencies = [ "serde", "serde-json-wasm", "sha2 0.10.3", + "static_assertions", "thiserror", ] @@ -1407,6 +1408,12 @@ version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" +[[package]] +name = "static_assertions" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" + [[package]] name = "strsim" version = "0.10.0" diff --git a/contracts/reflect/Cargo.lock b/contracts/reflect/Cargo.lock index cd1189d134..b144d43571 100644 --- a/contracts/reflect/Cargo.lock +++ b/contracts/reflect/Cargo.lock @@ -233,6 +233,7 @@ dependencies = [ "serde", "serde-json-wasm", "sha2 0.10.3", + "static_assertions", "thiserror", ] @@ -1408,6 +1409,12 @@ version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" +[[package]] +name = "static_assertions" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" + [[package]] name = "strsim" version = "0.10.0" diff --git a/contracts/staking/Cargo.lock b/contracts/staking/Cargo.lock index 884ee8dbae..4197746d30 100644 --- a/contracts/staking/Cargo.lock +++ b/contracts/staking/Cargo.lock @@ -233,6 +233,7 @@ dependencies = [ "serde", "serde-json-wasm", "sha2 0.10.3", + "static_assertions", "thiserror", ] @@ -1435,6 +1436,12 @@ dependencies = [ "snafu", ] +[[package]] +name = "static_assertions" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" + [[package]] name = "strsim" version = "0.10.0" diff --git a/contracts/virus/Cargo.lock b/contracts/virus/Cargo.lock index 767054387a..0727cbeb83 100644 --- a/contracts/virus/Cargo.lock +++ b/contracts/virus/Cargo.lock @@ -233,6 +233,7 @@ dependencies = [ "serde", "serde-json-wasm", "sha2 0.10.3", + "static_assertions", "thiserror", ] @@ -1396,6 +1397,12 @@ version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" +[[package]] +name = "static_assertions" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" + [[package]] name = "strsim" version = "0.10.0" From 47bb0cb5c2de3f37baf47eb95934ffb43e9d83e2 Mon Sep 17 00:00:00 2001 From: Christoph Otter Date: Thu, 14 Sep 2023 11:51:36 +0200 Subject: [PATCH 09/11] Add TryFrom tests --- packages/std/src/math/conversion.rs | 134 +++++++++++++++++++++++++++- packages/std/src/math/int128.rs | 17 +++- packages/std/src/math/int256.rs | 16 +++- packages/std/src/math/int512.rs | 16 +++- packages/std/src/math/int64.rs | 18 +++- packages/std/src/math/mod.rs | 2 + packages/std/src/math/num_consts.rs | 7 ++ packages/std/src/math/uint128.rs | 17 ++++ packages/std/src/math/uint256.rs | 17 ++++ packages/std/src/math/uint512.rs | 18 +++- packages/std/src/math/uint64.rs | 17 ++++ 11 files changed, 272 insertions(+), 7 deletions(-) create mode 100644 packages/std/src/math/num_consts.rs diff --git a/packages/std/src/math/conversion.rs b/packages/std/src/math/conversion.rs index f1292d4fb7..cf889642ae 100644 --- a/packages/std/src/math/conversion.rs +++ b/packages/std/src/math/conversion.rs @@ -136,9 +136,138 @@ macro_rules! try_from_uint_to_int { } }; } - pub(crate) use try_from_uint_to_int; +#[cfg(test)] +pub(crate) fn test_try_from_uint_to_int(input_type: &'static str, output_type: &'static str) +where + I: super::num_consts::NumConsts + + From + + Copy + + TryFrom + + core::fmt::Debug + + core::ops::Add, + O: TryFrom + + From + + NumConsts + + core::cmp::PartialEq + + core::fmt::Debug, + String: From, +{ + let v = I::MAX; + assert_eq!( + O::try_from(v), + Err(crate::ConversionOverflowError::new( + input_type, + output_type, + v + )), + "input::MAX value should not fit" + ); + + let max = I::try_from(O::MAX).unwrap(); + assert_eq!(O::try_from(max), Ok(O::MAX), "output::MAX value should fit"); + + // but $output::MAX + 1 should not fit + let v = max + I::ONE; + assert_eq!( + O::try_from(v), + Err(crate::ConversionOverflowError::new( + input_type, + output_type, + v + )), + "output::MAX + 1 should not fit" + ); + + // zero should work + let v = I::ZERO; + assert_eq!(O::try_from(v), Ok(O::ZERO), "zero should fit"); + + // 42 should work + assert_eq!( + O::try_from(I::from(42u32)), + Ok(O::from(42u32)), + "42 should fit" + ) +} + +#[cfg(test)] +pub(crate) fn test_try_from_int_to_uint(input_type: &'static str, output_type: &'static str) +where + I: NumConsts + From + Copy + TryFrom + core::fmt::Debug + core::ops::Add, + O: TryFrom + + From + + NumConsts + + core::cmp::PartialEq + + core::fmt::Debug, + String: From, + >::Error: std::fmt::Debug, +{ + if core::mem::size_of::() <= core::mem::size_of::() { + // if the input type is smaller than the output type, then `I::MAX` should fit into `O` + let v = I::MAX; + assert_eq!( + O::try_from(v), + Ok(O::try_from(v).unwrap()), + "input::MAX value should fit" + ); + } else { + // if the input is bigger than the output, then `I::MAX` should not fit into `O` + let v = I::MAX; + assert_eq!( + O::try_from(v), + Err(crate::ConversionOverflowError::new( + input_type, + output_type, + v + )), + "input::MAX value should not fit" + ); + // but `O::MAX` should fit + let max = I::try_from(O::MAX).unwrap(); + assert_eq!( + O::try_from(max), + Ok(O::try_from(max).unwrap()), + "output::MAX value should fit" + ); + // while `O::MAX + 1` should not + let v = max + I::ONE; + assert_eq!( + O::try_from(v), + Err(crate::ConversionOverflowError::new( + input_type, + output_type, + v + )), + "output::MAX + 1 should not fit" + ); + } + + // negative numbers should fail + let v = I::from(-42i32); + assert_eq!( + O::try_from(v), + Err(crate::ConversionOverflowError::new( + input_type, + output_type, + v + )), + "negative numbers should not fit" + ); + + // zero should work + let v = I::ZERO; + assert_eq!(O::try_from(v), Ok(O::ZERO), "zero should fit"); + + // 42 should work + assert_eq!( + O::try_from(I::from(42i32)), + Ok(O::from(42u32)), + "42 should fit" + ) +} + /// Helper macro to implement `TryFrom` for a conversion from a signed int to an unsigned int. /// This is needed because `bnum` does not implement `TryFrom` for all of those conversions. macro_rules! try_from_int_to_uint { @@ -185,9 +314,10 @@ macro_rules! try_from_int_to_uint { } }; } - pub(crate) use try_from_int_to_uint; +use super::num_consts::NumConsts; + #[cfg(test)] mod tests { use super::*; diff --git a/packages/std/src/math/int128.rs b/packages/std/src/math/int128.rs index ecce5948d3..e764e0c6d3 100644 --- a/packages/std/src/math/int128.rs +++ b/packages/std/src/math/int128.rs @@ -15,6 +15,7 @@ use crate::{ }; use super::conversion::{forward_try_from, try_from_int_to_int}; +use super::num_consts::NumConsts; /// An implementation of i128 that is using strings for JSON encoding/decoding, /// such that the full i128 range can be used for clients that convert JSON numbers to floats, @@ -270,6 +271,13 @@ impl Int128 { } } +impl NumConsts for Int128 { + const ZERO: Self = Self::zero(); + const ONE: Self = Self::one(); + const MAX: Self = Self::MAX; + const MIN: Self = Self::MIN; +} + // Uint to Int impl From for Int128 { fn from(val: Uint64) -> Self { @@ -562,7 +570,7 @@ where #[cfg(test)] mod tests { use super::*; - use crate::{from_slice, to_vec}; + use crate::{from_slice, math::conversion::test_try_from_uint_to_int, to_vec}; #[test] fn size_of_works() { @@ -677,6 +685,13 @@ mod tests { assert!(result.is_err()); } + #[test] + fn int128_try_from_unsigned_works() { + test_try_from_uint_to_int::("Uint128", "Int128"); + test_try_from_uint_to_int::("Uint256", "Int128"); + test_try_from_uint_to_int::("Uint512", "Int128"); + } + #[test] fn int128_implements_display() { let a = Int128::from(12345u32); diff --git a/packages/std/src/math/int256.rs b/packages/std/src/math/int256.rs index 2e7dcb9206..26ca8dfd5e 100644 --- a/packages/std/src/math/int256.rs +++ b/packages/std/src/math/int256.rs @@ -19,6 +19,7 @@ use crate::{ use bnum::types::{I256, U256}; use super::conversion::{grow_be_int, try_from_int_to_int, try_from_uint_to_int}; +use super::num_consts::NumConsts; /// An implementation of i256 that is using strings for JSON encoding/decoding, /// such that the full i256 range can be used for clients that convert JSON numbers to floats, @@ -328,6 +329,13 @@ impl Int256 { } } +impl NumConsts for Int256 { + const ZERO: Self = Self::zero(); + const ONE: Self = Self::one(); + const MAX: Self = Self::MAX; + const MIN: Self = Self::MIN; +} + // Uint to Int try_from_uint_to_int!(Uint512, Int256); try_from_uint_to_int!(Uint256, Int256); @@ -637,7 +645,7 @@ where #[cfg(test)] mod tests { use super::*; - use crate::{from_slice, to_vec}; + use crate::{from_slice, math::conversion::test_try_from_uint_to_int, to_vec}; #[test] fn size_of_works() { @@ -748,6 +756,12 @@ mod tests { assert!(result.is_err()); } + #[test] + fn int256_try_from_unsigned_works() { + test_try_from_uint_to_int::("Uint256", "Int256"); + test_try_from_uint_to_int::("Uint512", "Int256"); + } + #[test] fn int256_from_i128() { assert_eq!(Int256::from_i128(123i128), Int256::from_str("123").unwrap()); diff --git a/packages/std/src/math/int512.rs b/packages/std/src/math/int512.rs index 318204408a..9824c63eb2 100644 --- a/packages/std/src/math/int512.rs +++ b/packages/std/src/math/int512.rs @@ -16,6 +16,7 @@ use crate::{forward_ref_partial_eq, Int128, Int256, Int64, Uint128, Uint256, Uin use bnum::types::{I512, U512}; use super::conversion::{grow_be_int, try_from_uint_to_int}; +use super::num_consts::NumConsts; /// An implementation of i512 that is using strings for JSON encoding/decoding, /// such that the full i512 range can be used for clients that convert JSON numbers to floats, @@ -313,6 +314,13 @@ impl Int512 { } } +impl NumConsts for Int512 { + const ZERO: Self = Self::zero(); + const ONE: Self = Self::one(); + const MAX: Self = Self::MAX; + const MIN: Self = Self::MIN; +} + // Uint to Int try_from_uint_to_int!(Uint512, Int512); @@ -634,7 +642,7 @@ where #[cfg(test)] mod tests { use super::*; - use crate::{from_slice, to_vec}; + use crate::{from_slice, math::conversion::test_try_from_uint_to_int, to_vec}; #[test] fn size_of_works() { @@ -785,6 +793,12 @@ mod tests { assert!(result.is_err()); } + #[test] + fn int512_try_from_unsigned_works() { + test_try_from_uint_to_int::("Uint256", "Int256"); + test_try_from_uint_to_int::("Uint512", "Int256"); + } + #[test] fn int512_implements_display() { let a = Int512::from(12345u32); diff --git a/packages/std/src/math/int64.rs b/packages/std/src/math/int64.rs index 006259b305..2a0c651500 100644 --- a/packages/std/src/math/int64.rs +++ b/packages/std/src/math/int64.rs @@ -15,6 +15,7 @@ use crate::{ }; use super::conversion::{forward_try_from, try_from_int_to_int}; +use super::num_consts::NumConsts; /// An implementation of i64 that is using strings for JSON encoding/decoding, /// such that the full i64 range can be used for clients that convert JSON numbers to floats, @@ -270,6 +271,13 @@ impl Int64 { } } +impl NumConsts for Int64 { + const ZERO: Self = Self::zero(); + const ONE: Self = Self::one(); + const MAX: Self = Self::MAX; + const MIN: Self = Self::MIN; +} + // uint to Int impl From for Int64 { fn from(val: u32) -> Self { @@ -541,7 +549,7 @@ where #[cfg(test)] mod tests { use super::*; - use crate::{from_slice, to_vec}; + use crate::{from_slice, math::conversion::test_try_from_uint_to_int, to_vec}; #[test] fn size_of_works() { @@ -650,6 +658,14 @@ mod tests { assert!(result.is_err()); } + #[test] + fn int64_try_from_unsigned_works() { + test_try_from_uint_to_int::("Uint64", "Int64"); + test_try_from_uint_to_int::("Uint128", "Int64"); + test_try_from_uint_to_int::("Uint256", "Int64"); + test_try_from_uint_to_int::("Uint512", "Int64"); + } + #[test] fn int64_implements_display() { let a = Int64::from(12345u32); diff --git a/packages/std/src/math/mod.rs b/packages/std/src/math/mod.rs index 9b27fd859a..fdff7647f8 100644 --- a/packages/std/src/math/mod.rs +++ b/packages/std/src/math/mod.rs @@ -7,6 +7,7 @@ mod int256; mod int512; mod int64; mod isqrt; +mod num_consts; mod uint128; mod uint256; mod uint512; @@ -71,6 +72,7 @@ mod tests { + ShrAssign + ShrAssign<&'a u32> + Not + + super::num_consts::NumConsts { } diff --git a/packages/std/src/math/num_consts.rs b/packages/std/src/math/num_consts.rs new file mode 100644 index 0000000000..b539542303 --- /dev/null +++ b/packages/std/src/math/num_consts.rs @@ -0,0 +1,7 @@ +/// Crate internal trait for all our signed and unsigned number types +pub(crate) trait NumConsts { + const MAX: Self; + const MIN: Self; + const ZERO: Self; + const ONE: Self; +} diff --git a/packages/std/src/math/uint128.rs b/packages/std/src/math/uint128.rs index 99f811158f..b51f340001 100644 --- a/packages/std/src/math/uint128.rs +++ b/packages/std/src/math/uint128.rs @@ -20,6 +20,7 @@ use crate::{ }; use super::conversion::forward_try_from; +use super::num_consts::NumConsts; /// A thin wrapper around u128 that is using strings for JSON encoding/decoding, /// such that the full u128 range can be used for clients that convert JSON numbers to floats, @@ -272,6 +273,13 @@ impl Uint128 { } } +impl NumConsts for Uint128 { + const ZERO: Self = Self::zero(); + const ONE: Self = Self::one(); + const MAX: Self = Self::MAX; + const MIN: Self = Self::MIN; +} + impl_mul_fraction!(Uint128); // `From` is implemented manually instead of @@ -608,6 +616,7 @@ where #[cfg(test)] mod tests { use crate::errors::CheckedMultiplyFractionError::{ConversionOverflow, DivideByZero}; + use crate::math::conversion::test_try_from_int_to_uint; use crate::{from_slice, to_vec, ConversionOverflowError, Decimal}; use super::*; @@ -678,6 +687,14 @@ mod tests { assert!(result.is_err()); } + #[test] + fn uint128_try_from_signed_works() { + test_try_from_int_to_uint::("Int64", "Uint128"); + test_try_from_int_to_uint::("Int128", "Uint128"); + test_try_from_int_to_uint::("Int256", "Uint128"); + test_try_from_int_to_uint::("Int512", "Uint128"); + } + #[test] fn uint128_try_into() { assert!(Uint64::try_from(Uint128::MAX).is_err()); diff --git a/packages/std/src/math/uint256.rs b/packages/std/src/math/uint256.rs index 4d08268c63..bd6745eca2 100644 --- a/packages/std/src/math/uint256.rs +++ b/packages/std/src/math/uint256.rs @@ -23,6 +23,7 @@ use crate::{ use bnum::types::U256; use super::conversion::{forward_try_from, try_from_int_to_uint}; +use super::num_consts::NumConsts; /// An implementation of u256 that is using strings for JSON encoding/decoding, /// such that the full u256 range can be used for clients that convert JSON numbers to floats, @@ -340,6 +341,13 @@ impl Uint256 { } } +impl NumConsts for Uint256 { + const ZERO: Self = Self::zero(); + const ONE: Self = Self::one(); + const MAX: Self = Self::MAX; + const MIN: Self = Self::MIN; +} + impl_mul_fraction!(Uint256); impl From for Uint256 { @@ -671,6 +679,7 @@ where mod tests { use super::*; use crate::errors::CheckedMultiplyFractionError::{ConversionOverflow, DivideByZero}; + use crate::math::conversion::test_try_from_int_to_uint; use crate::{from_slice, to_vec, Decimal, Decimal256}; #[test] @@ -1066,6 +1075,14 @@ mod tests { assert!(result.is_err()); } + #[test] + fn uint256_try_from_signed_works() { + test_try_from_int_to_uint::("Int64", "Uint256"); + test_try_from_int_to_uint::("Int128", "Uint256"); + test_try_from_int_to_uint::("Int256", "Uint256"); + test_try_from_int_to_uint::("Int512", "Uint256"); + } + #[test] fn uint256_try_into() { assert!(Uint64::try_from(Uint256::MAX).is_err()); diff --git a/packages/std/src/math/uint512.rs b/packages/std/src/math/uint512.rs index 424997dbcf..3f4db01469 100644 --- a/packages/std/src/math/uint512.rs +++ b/packages/std/src/math/uint512.rs @@ -18,6 +18,7 @@ use crate::{forward_ref_partial_eq, Int128, Int256, Int512, Int64, Uint128, Uint use bnum::types::U512; use super::conversion::{forward_try_from, try_from_int_to_uint}; +use super::num_consts::NumConsts; /// An implementation of u512 that is using strings for JSON encoding/decoding, /// such that the full u512 range can be used for clients that convert JSON numbers to floats, @@ -302,6 +303,13 @@ impl Uint512 { } } +impl NumConsts for Uint512 { + const ZERO: Self = Self::zero(); + const ONE: Self = Self::one(); + const MAX: Self = Self::MAX; + const MIN: Self = Self::MIN; +} + impl From for Uint512 { fn from(val: Uint256) -> Self { let mut bytes = [0u8; 64]; @@ -638,7 +646,7 @@ where #[cfg(test)] mod tests { use super::*; - use crate::{from_slice, to_vec}; + use crate::{from_slice, math::conversion::test_try_from_int_to_uint, to_vec}; #[test] fn size_of_works() { @@ -749,6 +757,14 @@ mod tests { assert!(result.is_err()); } + #[test] + fn uint512_try_from_signed_works() { + test_try_from_int_to_uint::("Int64", "Uint512"); + test_try_from_int_to_uint::("Int128", "Uint512"); + test_try_from_int_to_uint::("Int256", "Uint512"); + test_try_from_int_to_uint::("Int512", "Uint512"); + } + #[test] fn uint512_try_into() { assert!(Uint64::try_from(Uint512::MAX).is_err()); diff --git a/packages/std/src/math/uint64.rs b/packages/std/src/math/uint64.rs index bcc46b6e37..961f46ef17 100644 --- a/packages/std/src/math/uint64.rs +++ b/packages/std/src/math/uint64.rs @@ -17,6 +17,7 @@ use crate::{ }; use super::conversion::forward_try_from; +use super::num_consts::NumConsts; /// A thin wrapper around u64 that is using strings for JSON encoding/decoding, /// such that the full u64 range can be used for clients that convert JSON numbers to floats, @@ -266,6 +267,13 @@ impl Uint64 { } } +impl NumConsts for Uint64 { + const ZERO: Self = Self::zero(); + const ONE: Self = Self::one(); + const MAX: Self = Self::MAX; + const MIN: Self = Self::MIN; +} + impl_mul_fraction!(Uint64); // `From` is implemented manually instead of @@ -570,6 +578,7 @@ where mod tests { use super::*; use crate::errors::CheckedMultiplyFractionError::{ConversionOverflow, DivideByZero}; + use crate::math::conversion::test_try_from_int_to_uint; use crate::{from_slice, to_vec, ConversionOverflowError}; #[test] @@ -629,6 +638,14 @@ mod tests { assert!(result.is_err()); } + #[test] + fn uint64_try_from_signed_works() { + test_try_from_int_to_uint::("Int64", "Uint64"); + test_try_from_int_to_uint::("Int128", "Uint64"); + test_try_from_int_to_uint::("Int256", "Uint64"); + test_try_from_int_to_uint::("Int512", "Uint64"); + } + #[test] fn uint64_implements_display() { let a = Uint64(12345); From 7694f7fd889cccb9f8cbb1f189e3f5ce73ca3613 Mon Sep 17 00:00:00 2001 From: Christoph Otter Date: Thu, 14 Sep 2023 12:15:33 +0200 Subject: [PATCH 10/11] Fix import --- packages/std/src/math/conversion.rs | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/packages/std/src/math/conversion.rs b/packages/std/src/math/conversion.rs index cf889642ae..240cbf8650 100644 --- a/packages/std/src/math/conversion.rs +++ b/packages/std/src/math/conversion.rs @@ -149,7 +149,7 @@ where + core::ops::Add, O: TryFrom + From - + NumConsts + + super::num_consts::NumConsts + core::cmp::PartialEq + core::fmt::Debug, String: From, @@ -195,10 +195,15 @@ where #[cfg(test)] pub(crate) fn test_try_from_int_to_uint(input_type: &'static str, output_type: &'static str) where - I: NumConsts + From + Copy + TryFrom + core::fmt::Debug + core::ops::Add, + I: super::num_consts::NumConsts + + From + + Copy + + TryFrom + + core::fmt::Debug + + core::ops::Add, O: TryFrom + From - + NumConsts + + super::num_consts::NumConsts + core::cmp::PartialEq + core::fmt::Debug, String: From, @@ -316,8 +321,6 @@ macro_rules! try_from_int_to_uint { } pub(crate) use try_from_int_to_uint; -use super::num_consts::NumConsts; - #[cfg(test)] mod tests { use super::*; From c1131cc92d3a354668a9f279df350ff33624c541 Mon Sep 17 00:00:00 2001 From: Christoph Otter Date: Mon, 18 Sep 2023 15:12:02 +0200 Subject: [PATCH 11/11] Add changelog entry --- CHANGELOG.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3572dfa0fd..c51eaa7d0b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -17,11 +17,14 @@ and this project adheres to - cosmwasm-std: Add `Int{64,128,256}::{checked_multiply_ratio, full_mul}` ([#1866]) - cosmwasm-std: Add `is_negative` for `Int{64,128,256,512}` ([#1867]). +- cosmwasm-std: Add `TryFrom for Uint64` and + `TryFrom for Int{B}` where `A >= B` ([#1870]). [#1854]: https://github.com/CosmWasm/cosmwasm/pull/1854 [#1861]: https://github.com/CosmWasm/cosmwasm/pull/1861 [#1866]: https://github.com/CosmWasm/cosmwasm/pull/1866 [#1867]: https://github.com/CosmWasm/cosmwasm/pull/1867 +[#1870]: https://github.com/CosmWasm/cosmwasm/pull/1870 ## [1.4.0] - 2023-09-04