From fd1f0706b00bbed203b7356ad3bf08bac4465f69 Mon Sep 17 00:00:00 2001 From: 0xgleb Date: Mon, 16 Jun 2025 18:02:12 +0400 Subject: [PATCH 1/4] mul/div --- crates/float/src/lib.rs | 84 ++++++++++++++++++++++++++++++++++++++++- 1 file changed, 83 insertions(+), 1 deletion(-) diff --git a/crates/float/src/lib.rs b/crates/float/src/lib.rs index 208f0fe2..35d36082 100644 --- a/crates/float/src/lib.rs +++ b/crates/float/src/lib.rs @@ -12,7 +12,7 @@ use revm::interpreter::interpreter::EthInterpreter; use revm::primitives::{address, fixed_bytes}; use revm::{Context, MainBuilder, MainContext, SystemCallEvm}; use std::cell::RefCell; -use std::ops::{Add, Sub}; +use std::ops::{Add, Div, Mul, Sub}; use std::thread::AccessError; use thiserror::Error; @@ -262,6 +262,36 @@ impl Sub for Float { } } +impl Mul for Float { + type Output = Result; + + fn mul(self, b: Self) -> Self::Output { + let Float(a) = self; + let Float(b) = b; + let calldata = DecimalFloat::mulCall { a, b }.abi_encode(); + + execute_call(Bytes::from(calldata), |output| { + let decoded = DecimalFloat::mulCall::abi_decode_returns(output.as_ref())?; + Ok(Float(decoded)) + }) + } +} + +impl Div for Float { + type Output = Result; + + fn div(self, b: Self) -> Self::Output { + let Float(a) = self; + let Float(b) = b; + let calldata = DecimalFloat::divCall { a, b }.abi_encode(); + + execute_call(Bytes::from(calldata), |output| { + let decoded = DecimalFloat::divCall::abi_decode_returns(output.as_ref())?; + Ok(Float(decoded)) + }) + } +} + #[cfg(test)] mod tests { use super::*; @@ -415,4 +445,56 @@ mod tests { prop_assert!(!(lt && gt), "both less than and greater than: a: {a_str}, b: {b_str}"); } } + + proptest! { + #[test] + fn test_mul(a in reasonable_float(), b in reasonable_float()) { + (a * b).unwrap(); + } + } + + proptest! { + #[test] + fn test_div(a in reasonable_float(), b in reasonable_float()) { + let zero = Float::parse("0".to_string()).unwrap(); + prop_assume!(!b.eq(zero).unwrap()); + + (a / b).unwrap(); + } + } + + prop_compose! { + fn small_int_float()(int_part in -1_000_000_000_000i128..1_000_000_000_000i128) -> Float { + Float::parse(int_part.to_string()).unwrap() + } + } + + proptest! { + #[test] + fn test_mul_div_int(a in small_int_float(), b in small_int_float()) { + let zero = Float::parse("0".to_string()).unwrap(); + prop_assume!(!b.eq(zero).unwrap()); + + let product = (a * b).unwrap(); + let quotient = (product / b).unwrap(); + + prop_assert!( + a.eq(quotient).unwrap(), + "a: {}, quotient: {}, b: {}", + a.show_unpacked().unwrap(), + quotient.show_unpacked().unwrap(), + b.show_unpacked().unwrap() + ); + } + } + + #[test] + fn test_mul_div_manual() { + let two = Float::parse("2".to_string()).unwrap(); + let three = Float::parse("3".to_string()).unwrap(); + let six = Float::parse("6".to_string()).unwrap(); + + assert!(two.eq((six / three).unwrap()).unwrap()); + assert!(six.eq((two * three).unwrap()).unwrap()); + } } From 82ba88359a66c3aa0a5cb75856e95d38d7cd3ec8 Mon Sep 17 00:00:00 2001 From: 0xgleb Date: Mon, 16 Jun 2025 18:21:19 +0400 Subject: [PATCH 2/4] cover more error cases --- crates/float/src/lib.rs | 34 ++++++++++++++++++++++++++++++++-- 1 file changed, 32 insertions(+), 2 deletions(-) diff --git a/crates/float/src/lib.rs b/crates/float/src/lib.rs index 35d36082..331e5273 100644 --- a/crates/float/src/lib.rs +++ b/crates/float/src/lib.rs @@ -188,6 +188,7 @@ impl Float { }) } + // NOTE: LibFormatDecimalFloat.toDecimalString currently uses 18 decimal places pub fn format(self) -> Result { let Float(a) = self; let calldata = DecimalFloat::formatCall { a }.abi_encode(); @@ -324,8 +325,6 @@ mod tests { #[test] fn test_parse_and_format() { let float = Float::parse("1.1341234234625468391".to_string()).unwrap(); - // NOTE: LibFormatDecimalFloat.toDecimalString currently uses 18 decimal places - // TODO: make this fail on a separate PR let err = float.format().unwrap_err(); assert!(matches!( @@ -334,6 +333,23 @@ mod tests { )); } + #[test] + fn test_parse_empty_string_error() { + let err = Float::parse("".to_string()).unwrap_err(); + // We don't know the exact selector here, just ensure the error path is hit. + assert!(matches!(err, FloatError::DecimalFloatSelector(_))); + } + + #[test] + fn test_parse_exponent_overflow_error() { + // Extremely large exponent expected to overflow (exponent >> i32::MAX). + let err = Float::parse("1e3000000000".to_string()).unwrap_err(); + assert!(matches!( + err, + FloatError::DecimalFloat(DecimalFloatErrors::ExponentOverflow(_)) + )); + } + #[test] fn test_parse_edge_cases() { let err = Float::parse("1.2.3".to_string()).unwrap_err(); @@ -497,4 +513,18 @@ mod tests { assert!(two.eq((six / three).unwrap()).unwrap()); assert!(six.eq((two * three).unwrap()).unwrap()); } + + #[test] + fn test_divide_by_zero_error() { + let one = Float::parse("1".to_string()).unwrap(); + let zero = Float::parse("0".to_string()).unwrap(); + let err = (one / zero).unwrap_err(); + // Division by zero should revert or return a DecimalFloat error/selector. + match err { + FloatError::DecimalFloat(_) + | FloatError::DecimalFloatSelector(_) + | FloatError::Revert(_) => {} + _ => panic!("Unexpected error type: {err:?}"), + } + } } From ac2cea963add64b407eddf4c91466c44f93f0b30 Mon Sep 17 00:00:00 2001 From: 0xgleb Date: Mon, 16 Jun 2025 18:49:46 +0400 Subject: [PATCH 3/4] cover more error cases --- crates/float/src/lib.rs | 45 ++++++++++++++++++++++++++++++++++------- 1 file changed, 38 insertions(+), 7 deletions(-) diff --git a/crates/float/src/lib.rs b/crates/float/src/lib.rs index 331e5273..81f08e92 100644 --- a/crates/float/src/lib.rs +++ b/crates/float/src/lib.rs @@ -519,12 +519,43 @@ mod tests { let one = Float::parse("1".to_string()).unwrap(); let zero = Float::parse("0".to_string()).unwrap(); let err = (one / zero).unwrap_err(); - // Division by zero should revert or return a DecimalFloat error/selector. - match err { - FloatError::DecimalFloat(_) - | FloatError::DecimalFloatSelector(_) - | FloatError::Revert(_) => {} - _ => panic!("Unexpected error type: {err:?}"), - } + + assert!(matches!(err, FloatError::Revert(_))); + } + + #[test] + fn test_mul_exponent_overflow_error() { + let near_max_exp = Float::parse("1e2147483646".to_string()).unwrap(); + let one_e_two = Float::parse("1e2".to_string()).unwrap(); + + let err = (near_max_exp * one_e_two).unwrap_err(); + assert!(matches!( + err, + FloatError::DecimalFloat(DecimalFloatErrors::ExponentOverflow(_)) + )); + } + + #[test] + fn test_div_exponent_overflow_error() { + let near_max_exp = Float::parse("1e2147483646".to_string()).unwrap(); + let one_e_neg_hundred = Float::parse("1e-100".to_string()).unwrap(); + + let err = (near_max_exp / one_e_neg_hundred).unwrap_err(); + assert!(matches!( + err, + FloatError::DecimalFloat(DecimalFloatErrors::ExponentOverflow(_)) + )); + } + + #[test] + fn test_mul_exponent_underflow_error() { + let near_min_exp = Float::parse("1e-2147483646".to_string()).unwrap(); + let one_e_neg_three = Float::parse("1e-3".to_string()).unwrap(); + + let err = (near_min_exp * one_e_neg_three).unwrap_err(); + assert!(matches!( + err, + FloatError::DecimalFloat(DecimalFloatErrors::ExponentOverflow(_)) + )); } } From 7940a0ca03727e064480cb1599f8c618b7ed82de Mon Sep 17 00:00:00 2001 From: 0xgleb Date: Mon, 16 Jun 2025 19:26:57 +0400 Subject: [PATCH 4/4] add error test cases for add/sub --- crates/float/src/lib.rs | 34 ++++++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/crates/float/src/lib.rs b/crates/float/src/lib.rs index 81f08e92..1d1dc972 100644 --- a/crates/float/src/lib.rs +++ b/crates/float/src/lib.rs @@ -296,6 +296,7 @@ impl Div for Float { #[cfg(test)] mod tests { use super::*; + use core::str::FromStr; use proptest::prelude::*; prop_compose! { @@ -376,6 +377,39 @@ mod tests { } } + #[test] + fn test_add_exponent_overflow_error() { + let max_coeff_str = "13479973333575319897333507543509815336818572211270286240551805124607"; + let large_coeff_i224 = I224::from_str(max_coeff_str).unwrap(); + let exponent_max = i32::MAX; + + let a = Float::pack_lossless(large_coeff_i224, exponent_max).unwrap(); + + let err = (a + a).unwrap_err(); + + assert!(matches!( + err, + FloatError::DecimalFloat(DecimalFloatErrors::ExponentOverflow(_)) + )); + } + + #[test] + fn test_sub_exponent_overflow_error() { + let max_coeff_str = "13479973333575319897333507543509815336818572211270286240551805124607"; + let large_coeff_i224 = I224::from_str(max_coeff_str).unwrap(); + let exponent_max = i32::MAX; + + let a = Float::pack_lossless(large_coeff_i224, exponent_max).unwrap(); + let b = Float::pack_lossless(-large_coeff_i224, exponent_max).unwrap(); + + let err = (b - a).unwrap_err(); + + assert!(matches!( + err, + FloatError::DecimalFloat(DecimalFloatErrors::ExponentOverflow(_)) + )); + } + proptest! { #[test] fn test_add(a in reasonable_float(), b in reasonable_float()) {