diff --git a/CHANGELOG.md b/CHANGELOG.md index 863b428b5a..aeec2f6831 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,7 @@ and this project adheres to - cosmwasm-std: Implement `checked_multiply_ratio` for `Uint64`/`Uint128`/`Uint256` +- cosmwasm-std: Implement `checked_from_ratio` for `Decimal`/`Decimal256` ### Changed diff --git a/packages/std/src/math/decimal.rs b/packages/std/src/math/decimal.rs index 92d260ba45..f31708a6d2 100644 --- a/packages/std/src/math/decimal.rs +++ b/packages/std/src/math/decimal.rs @@ -7,7 +7,7 @@ use std::ops::{Add, AddAssign, Div, DivAssign, Mul, MulAssign, Sub, SubAssign}; use std::str::FromStr; use thiserror::Error; -use crate::errors::StdError; +use crate::errors::{CheckedMultiplyRatioError, StdError}; use crate::OverflowError; use super::Fraction; @@ -118,16 +118,30 @@ impl Decimal { /// Returns the ratio (numerator / denominator) as a Decimal pub fn from_ratio(numerator: impl Into, denominator: impl Into) -> Self { + match Decimal::checked_from_ratio(numerator, denominator) { + Ok(value) => value, + Err(CheckedMultiplyRatioError::DivideByZero) => { + panic!("Denominator must not be zero") + } + Err(CheckedMultiplyRatioError::Overflow) => panic!("Multiplication overflow"), + } + } + + /// Returns the ratio (numerator / denominator) as a Decimal + pub fn checked_from_ratio( + numerator: impl Into, + denominator: impl Into, + ) -> Result { let numerator: Uint128 = numerator.into(); let denominator: Uint128 = denominator.into(); if denominator.is_zero() { - panic!("Denominator must not be zero"); + return Err(CheckedMultiplyRatioError::DivideByZero); } - Decimal( + Ok(Decimal( // numerator * DECIMAL_FRACTIONAL / denominator - numerator.multiply_ratio(Self::DECIMAL_FRACTIONAL, denominator), - ) + numerator.checked_multiply_ratio(Self::DECIMAL_FRACTIONAL, denominator)?, + )) } pub const fn is_zero(&self) -> bool { @@ -658,6 +672,19 @@ mod tests { Decimal::from_ratio(1u128, 0u128); } + #[test] + fn decimal_checked_from_ratio_does_not_panic() { + assert_eq!( + Decimal::checked_from_ratio(1u128, 0u128), + Err(CheckedMultiplyRatioError::DivideByZero) + ); + + assert_eq!( + Decimal::checked_from_ratio(u128::MAX, 1u128), + Err(CheckedMultiplyRatioError::Overflow) + ); + } + #[test] fn decimal_implements_fraction() { let fraction = Decimal::from_str("1234.567").unwrap(); diff --git a/packages/std/src/math/decimal256.rs b/packages/std/src/math/decimal256.rs index 4dc1ef01ff..c2be94d875 100644 --- a/packages/std/src/math/decimal256.rs +++ b/packages/std/src/math/decimal256.rs @@ -7,7 +7,7 @@ use std::ops::{Add, AddAssign, Div, DivAssign, Mul, MulAssign, Sub, SubAssign}; use std::str::FromStr; use thiserror::Error; -use crate::errors::StdError; +use crate::errors::{CheckedMultiplyRatioError, StdError}; use crate::{OverflowError, Uint512}; use super::Fraction; @@ -130,16 +130,30 @@ impl Decimal256 { /// Returns the ratio (numerator / denominator) as a Decimal256 pub fn from_ratio(numerator: impl Into, denominator: impl Into) -> Self { + match Decimal256::checked_from_ratio(numerator, denominator) { + Ok(value) => value, + Err(CheckedMultiplyRatioError::DivideByZero) => { + panic!("Denominator must not be zero") + } + Err(CheckedMultiplyRatioError::Overflow) => panic!("Multiplication overflow"), + } + } + + /// Returns the ratio (numerator / denominator) as a Decimal256 + pub fn checked_from_ratio( + numerator: impl Into, + denominator: impl Into, + ) -> Result { let numerator: Uint256 = numerator.into(); let denominator: Uint256 = denominator.into(); if denominator.is_zero() { - panic!("Denominator must not be zero"); + return Err(CheckedMultiplyRatioError::DivideByZero); } - Self( + Ok(Self( // numerator * DECIMAL_FRACTIONAL / denominator - numerator.multiply_ratio(Self::DECIMAL_FRACTIONAL, denominator), - ) + numerator.checked_multiply_ratio(Self::DECIMAL_FRACTIONAL, denominator)?, + )) } pub const fn is_zero(&self) -> bool { @@ -690,6 +704,19 @@ mod tests { Decimal256::from_ratio(1u128, 0u128); } + #[test] + fn decimal256_checked_from_ratio_does_not_panic() { + assert_eq!( + Decimal256::checked_from_ratio(1u128, 0u128), + Err(CheckedMultiplyRatioError::DivideByZero) + ); + + assert_eq!( + Decimal256::checked_from_ratio(u128::MAX, 1u128), + Err(CheckedMultiplyRatioError::Overflow) + ); + } + #[test] fn decimal256_implements_fraction() { let fraction = Decimal256::from_str("1234.567").unwrap();