From 232ff73f3a37b666747c3b57d89b0119dc16e8f9 Mon Sep 17 00:00:00 2001 From: 0xgleb Date: Wed, 11 Jun 2025 15:20:11 +0400 Subject: [PATCH 1/6] expose lt, eq, and gt --- crates/float/src/lib.rs | 72 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 72 insertions(+) diff --git a/crates/float/src/lib.rs b/crates/float/src/lib.rs index a8cbcbe0..000993ee 100644 --- a/crates/float/src/lib.rs +++ b/crates/float/src/lib.rs @@ -207,6 +207,39 @@ impl Sub for Float { Ok(Float(decoded)) }) } + + pub fn lt(&mut self, a: Float, b: Float) -> Result { + let Float(a) = a; + let Float(b) = b; + let calldata = DecimalFloat::ltCall { a, b }.abi_encode(); + + self.execute_call(Bytes::from(calldata), |output| { + let decoded = DecimalFloat::ltCall::abi_decode_returns(output.as_ref())?; + Ok(decoded) + }) + } + + pub fn eq(&mut self, a: Float, b: Float) -> Result { + let Float(a) = a; + let Float(b) = b; + let calldata = DecimalFloat::eqCall { a, b }.abi_encode(); + + self.execute_call(Bytes::from(calldata), |output| { + let decoded = DecimalFloat::eqCall::abi_decode_returns(output.as_ref())?; + Ok(decoded) + }) + } + + pub fn gt(&mut self, a: Float, b: Float) -> Result { + let Float(a) = a; + let Float(b) = b; + let calldata = DecimalFloat::gtCall { a, b }.abi_encode(); + + self.execute_call(Bytes::from(calldata), |output| { + let decoded = DecimalFloat::gtCall::abi_decode_returns(output.as_ref())?; + Ok(decoded) + }) + } } #[cfg(test)] @@ -305,4 +338,43 @@ mod tests { ); } } + + proptest! { + #[test] + fn test_lt_eq_gt(a in valid_float()) { + let mut calculator = Calculator::new().unwrap(); + + let b = a; + let eq = calculator.eq(a, a).unwrap(); + prop_assert!(eq); + + let one = calculator.parse("1".to_string()).unwrap(); + + let a = calculator.sub(a, one).unwrap(); + let lt = calculator.lt(a, b).unwrap(); + prop_assert!(lt); + + let a = calculator.add(a, one).unwrap(); + let eq = calculator.eq(a, b).unwrap(); + prop_assert!(eq); + + let a = calculator.add(a, one).unwrap(); + let gt = calculator.gt(a, b).unwrap(); + prop_assert!(gt); + } + + #[test] + fn test_exactly_one_lt_eq_gt(a in valid_float(), b in valid_float()) { + let mut calculator = Calculator::new().unwrap(); + + let eq = calculator.eq(a, a).unwrap(); + let lt = calculator.lt(a, b).unwrap(); + let gt = calculator.gt(a, b).unwrap(); + + prop_assert!(lt || eq || gt); + prop_assert!(!(lt && eq)); + prop_assert!(!(eq && gt)); + prop_assert!(!(lt && gt)); + } + } } From b471637809b142912bd162a63f6ddaa11f4213c1 Mon Sep 17 00:00:00 2001 From: 0xgleb Date: Wed, 11 Jun 2025 18:50:32 +0400 Subject: [PATCH 2/6] add a manual test --- crates/float/src/lib.rs | 27 ++++++++++++++++++++++++++- 1 file changed, 26 insertions(+), 1 deletion(-) diff --git a/crates/float/src/lib.rs b/crates/float/src/lib.rs index 000993ee..d11cd814 100644 --- a/crates/float/src/lib.rs +++ b/crates/float/src/lib.rs @@ -339,9 +339,34 @@ mod tests { } } + #[test] + fn test_lt_eq_gt() { + let mut calculator = Calculator::new().unwrap(); + + let negone = calculator.parse("-1".to_string()).unwrap(); + let zero = calculator.parse("0".to_string()).unwrap(); + let three = calculator.parse("3".to_string()).unwrap(); + + assert!(calculator.lt(negone, zero).unwrap()); + assert!(!calculator.eq(negone, zero).unwrap()); + assert!(!calculator.gt(negone, zero).unwrap()); + + assert!(!calculator.lt(zero, negone).unwrap()); + assert!(!calculator.eq(zero, negone).unwrap()); + assert!(calculator.gt(zero, negone).unwrap()); + + assert!(!calculator.lt(three, zero).unwrap()); + assert!(!calculator.eq(three, zero).unwrap()); + assert!(calculator.gt(three, zero).unwrap()); + + assert!(calculator.lt(zero, three).unwrap()); + assert!(!calculator.eq(zero, three).unwrap()); + assert!(!calculator.gt(zero, three).unwrap()); + } + proptest! { #[test] - fn test_lt_eq_gt(a in valid_float()) { + fn test_lt_eq_gt_with_add(a in valid_float()) { let mut calculator = Calculator::new().unwrap(); let b = a; From 8f9c9f04159e7352173b76ffd1a6316bd1df837c Mon Sep 17 00:00:00 2001 From: 0xgleb Date: Fri, 13 Jun 2025 18:04:42 +0400 Subject: [PATCH 3/6] float methods --- crates/float/src/lib.rs | 139 +++++++++++++++++++--------------------- 1 file changed, 66 insertions(+), 73 deletions(-) diff --git a/crates/float/src/lib.rs b/crates/float/src/lib.rs index d11cd814..e4be9c68 100644 --- a/crates/float/src/lib.rs +++ b/crates/float/src/lib.rs @@ -177,67 +177,67 @@ impl Float { Ok(decoded) }) } -} - -impl Add for Float { - type Output = Result; - fn add(self, b: Self) -> Self::Output { + pub fn lt(self, b: Self) -> Result { let Float(a) = self; let Float(b) = b; - let calldata = DecimalFloat::addCall { a, b }.abi_encode(); + let calldata = DecimalFloat::ltCall { a, b }.abi_encode(); execute_call(Bytes::from(calldata), |output| { - let decoded = DecimalFloat::addCall::abi_decode_returns(output.as_ref())?; - Ok(Float(decoded)) + let decoded = DecimalFloat::ltCall::abi_decode_returns(output.as_ref())?; + Ok(decoded) }) } -} - -impl Sub for Float { - type Output = Result; - fn sub(self, b: Self) -> Self::Output { + pub fn eq(self, b: Self) -> Result { let Float(a) = self; let Float(b) = b; - let calldata = DecimalFloat::subCall { a, b }.abi_encode(); + let calldata = DecimalFloat::eqCall { a, b }.abi_encode(); execute_call(Bytes::from(calldata), |output| { - let decoded = DecimalFloat::subCall::abi_decode_returns(output.as_ref())?; - Ok(Float(decoded)) + let decoded = DecimalFloat::eqCall::abi_decode_returns(output.as_ref())?; + Ok(decoded) }) } - pub fn lt(&mut self, a: Float, b: Float) -> Result { - let Float(a) = a; + pub fn gt(self, b: Self) -> Result { + let Float(a) = self; let Float(b) = b; - let calldata = DecimalFloat::ltCall { a, b }.abi_encode(); + let calldata = DecimalFloat::gtCall { a, b }.abi_encode(); - self.execute_call(Bytes::from(calldata), |output| { - let decoded = DecimalFloat::ltCall::abi_decode_returns(output.as_ref())?; + execute_call(Bytes::from(calldata), |output| { + let decoded = DecimalFloat::gtCall::abi_decode_returns(output.as_ref())?; Ok(decoded) }) } +} - pub fn eq(&mut self, a: Float, b: Float) -> Result { - let Float(a) = a; +impl Add for Float { + type Output = Result; + + fn add(self, b: Self) -> Self::Output { + let Float(a) = self; let Float(b) = b; - let calldata = DecimalFloat::eqCall { a, b }.abi_encode(); + let calldata = DecimalFloat::addCall { a, b }.abi_encode(); - self.execute_call(Bytes::from(calldata), |output| { - let decoded = DecimalFloat::eqCall::abi_decode_returns(output.as_ref())?; - Ok(decoded) + execute_call(Bytes::from(calldata), |output| { + let decoded = DecimalFloat::addCall::abi_decode_returns(output.as_ref())?; + Ok(Float(decoded)) }) } +} + +impl Sub for Float { + type Output = Result; - pub fn gt(&mut self, a: Float, b: Float) -> Result { - let Float(a) = a; + fn sub(self, b: Self) -> Self::Output { + let Float(a) = self; let Float(b) = b; - let calldata = DecimalFloat::gtCall { a, b }.abi_encode(); + let calldata = DecimalFloat::subCall { a, b }.abi_encode(); - self.execute_call(Bytes::from(calldata), |output| { - let decoded = DecimalFloat::gtCall::abi_decode_returns(output.as_ref())?; - Ok(decoded) + execute_call(Bytes::from(calldata), |output| { + let decoded = DecimalFloat::subCall::abi_decode_returns(output.as_ref())?; + Ok(Float(decoded)) }) } } @@ -341,65 +341,58 @@ mod tests { #[test] fn test_lt_eq_gt() { - let mut calculator = Calculator::new().unwrap(); - - let negone = calculator.parse("-1".to_string()).unwrap(); - let zero = calculator.parse("0".to_string()).unwrap(); - let three = calculator.parse("3".to_string()).unwrap(); - - assert!(calculator.lt(negone, zero).unwrap()); - assert!(!calculator.eq(negone, zero).unwrap()); - assert!(!calculator.gt(negone, zero).unwrap()); + let negone = Float::parse("-1".to_string()).unwrap(); + let zero = Float::parse("0".to_string()).unwrap(); + let three = Float::parse("3".to_string()).unwrap(); - assert!(!calculator.lt(zero, negone).unwrap()); - assert!(!calculator.eq(zero, negone).unwrap()); - assert!(calculator.gt(zero, negone).unwrap()); + assert!(negone.lt(zero).unwrap()); + assert!(!negone.eq(zero).unwrap()); + assert!(!negone.gt(zero).unwrap()); - assert!(!calculator.lt(three, zero).unwrap()); - assert!(!calculator.eq(three, zero).unwrap()); - assert!(calculator.gt(three, zero).unwrap()); + assert!(!three.lt(zero).unwrap()); + assert!(!three.eq(zero).unwrap()); + assert!(three.gt(zero).unwrap()); - assert!(calculator.lt(zero, three).unwrap()); - assert!(!calculator.eq(zero, three).unwrap()); - assert!(!calculator.gt(zero, three).unwrap()); + assert!(zero.lt(three).unwrap()); + assert!(!zero.eq(three).unwrap()); + assert!(!zero.gt(three).unwrap()); } proptest! { #[test] - fn test_lt_eq_gt_with_add(a in valid_float()) { - let mut calculator = Calculator::new().unwrap(); - + fn test_lt_eq_gt_with_add(a in reasonable_float()) { let b = a; - let eq = calculator.eq(a, a).unwrap(); + let eq = a.eq(b).unwrap(); prop_assert!(eq); - let one = calculator.parse("1".to_string()).unwrap(); + let one = Float::parse("1".to_string()).unwrap(); - let a = calculator.sub(a, one).unwrap(); - let lt = calculator.lt(a, b).unwrap(); + let a = (a - one).unwrap(); + let lt = a.lt(b).unwrap(); prop_assert!(lt); - let a = calculator.add(a, one).unwrap(); - let eq = calculator.eq(a, b).unwrap(); + let a = (a + one).unwrap(); + let eq = a.eq(b).unwrap(); prop_assert!(eq); - let a = calculator.add(a, one).unwrap(); - let gt = calculator.gt(a, b).unwrap(); + let a = (a + one).unwrap(); + let gt = a.gt(b).unwrap(); prop_assert!(gt); } #[test] - fn test_exactly_one_lt_eq_gt(a in valid_float(), b in valid_float()) { - let mut calculator = Calculator::new().unwrap(); - - let eq = calculator.eq(a, a).unwrap(); - let lt = calculator.lt(a, b).unwrap(); - let gt = calculator.gt(a, b).unwrap(); - - prop_assert!(lt || eq || gt); - prop_assert!(!(lt && eq)); - prop_assert!(!(eq && gt)); - prop_assert!(!(lt && gt)); + fn test_exactly_one_lt_eq_gt(a in reasonable_float(), b in reasonable_float()) { + let eq = a.eq(a).unwrap(); + let lt = a.lt(b).unwrap(); + let gt = a.gt(b).unwrap(); + + let a_str = a.format().unwrap(); + let b_str = b.format().unwrap(); + + prop_assert!(lt || eq || gt, "a: {a_str}, b: {b_str}"); + prop_assert!(!(lt && eq), "a: {a_str}, b: {b_str}"); + prop_assert!(!(eq && gt), "a: {a_str}, b: {b_str}"); + prop_assert!(!(lt && gt), "a: {a_str}, b: {b_str}"); } } } From 3d04cc84cbd5f591f0b71848e6007bc670ffbe7c Mon Sep 17 00:00:00 2001 From: 0xgleb Date: Fri, 13 Jun 2025 18:29:44 +0400 Subject: [PATCH 4/6] use arb_float for test_exactly_one_lt_eq_gt --- crates/float/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/float/src/lib.rs b/crates/float/src/lib.rs index e4be9c68..054cea2b 100644 --- a/crates/float/src/lib.rs +++ b/crates/float/src/lib.rs @@ -381,7 +381,7 @@ mod tests { } #[test] - fn test_exactly_one_lt_eq_gt(a in reasonable_float(), b in reasonable_float()) { + fn test_exactly_one_lt_eq_gt(a in arb_float(), b in arb_float()) { let eq = a.eq(a).unwrap(); let lt = a.lt(b).unwrap(); let gt = a.gt(b).unwrap(); From 0be75c0e05a71cc6859645ac0f2a8777f6fda5d0 Mon Sep 17 00:00:00 2001 From: 0xgleb Date: Mon, 16 Jun 2025 17:04:12 +0400 Subject: [PATCH 5/6] improve error reporting --- crates/float/src/lib.rs | 32 ++++++++++++++++++++++++++------ src/concrete/DecimalFloat.sol | 9 +++++++++ 2 files changed, 35 insertions(+), 6 deletions(-) diff --git a/crates/float/src/lib.rs b/crates/float/src/lib.rs index 054cea2b..521be662 100644 --- a/crates/float/src/lib.rs +++ b/crates/float/src/lib.rs @@ -135,7 +135,6 @@ where pub struct Float(FixedBytes<32>); impl Float { - #[allow(dead_code)] // will be used in future tests #[cfg(test)] fn pack_lossless(coefficient: I224, exponent: i32) -> Result { let calldata = DecimalFloat::packLosslessCall { @@ -150,6 +149,27 @@ impl Float { }) } + #[cfg(test)] + fn unpack(self) -> Result<(I224, i32), FloatError> { + let Float(float) = self; + let calldata = DecimalFloat::unpackCall { float }.abi_encode(); + + execute_call(Bytes::from(calldata), |output| { + let DecimalFloat::unpackReturn { + _0: coefficient, + _1: exponent, + } = DecimalFloat::unpackCall::abi_decode_returns(output.as_ref())?; + + Ok((coefficient, exponent)) + }) + } + + #[cfg(test)] + fn show_unpacked(self) -> Result { + let (coefficient, exponent) = self.unpack()?; + Ok(format!("{coefficient}e{exponent}")) + } + pub fn parse(str: String) -> Result { let calldata = DecimalFloat::parseCall { str }.abi_encode(); @@ -386,13 +406,13 @@ mod tests { let lt = a.lt(b).unwrap(); let gt = a.gt(b).unwrap(); - let a_str = a.format().unwrap(); - let b_str = b.format().unwrap(); + let a_str = a.show_unpacked().unwrap(); + let b_str = b.show_unpacked().unwrap(); prop_assert!(lt || eq || gt, "a: {a_str}, b: {b_str}"); - prop_assert!(!(lt && eq), "a: {a_str}, b: {b_str}"); - prop_assert!(!(eq && gt), "a: {a_str}, b: {b_str}"); - prop_assert!(!(lt && gt), "a: {a_str}, b: {b_str}"); + prop_assert!(!(lt && eq), "both less than and equal: a: {a_str}, b: {b_str}"); + prop_assert!(!(eq && gt), "both equal and greater than: a: {a_str}, b: {b_str}"); + prop_assert!(!(lt && gt), "both less than and greater than: a: {a_str}, b: {b_str}"); } } } diff --git a/src/concrete/DecimalFloat.sol b/src/concrete/DecimalFloat.sol index 9b59a6f8..43f47dac 100644 --- a/src/concrete/DecimalFloat.sol +++ b/src/concrete/DecimalFloat.sol @@ -171,4 +171,13 @@ contract DecimalFloat { function packLossless(int224 coefficient, int32 exponent) external pure returns (Float) { return LibDecimalFloat.packLossless(coefficient, exponent); } + + /// Exposes `LibDecimalFloat.unpack` for offchain use. + /// @param float The float to unpack. + /// @return coefficient The coefficient of the float. + /// @return exponent The exponent of the float. + function unpack(Float float) external pure returns (int224, int32) { + (int256 coefficient, int256 exponent) = LibDecimalFloat.unpack(float); + return (int224(coefficient), int32(exponent)); + } } From ce3be2e9c7595fa156c74a219f83c5a18c6ce9cd Mon Sep 17 00:00:00 2001 From: 0xgleb Date: Mon, 16 Jun 2025 17:12:07 +0400 Subject: [PATCH 6/6] fix typo --- crates/float/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/float/src/lib.rs b/crates/float/src/lib.rs index 521be662..208f0fe2 100644 --- a/crates/float/src/lib.rs +++ b/crates/float/src/lib.rs @@ -402,7 +402,7 @@ mod tests { #[test] fn test_exactly_one_lt_eq_gt(a in arb_float(), b in arb_float()) { - let eq = a.eq(a).unwrap(); + let eq = a.eq(b).unwrap(); let lt = a.lt(b).unwrap(); let gt = a.gt(b).unwrap();