Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
88 changes: 88 additions & 0 deletions crates/float/src/js_api.rs
Original file line number Diff line number Diff line change
Expand Up @@ -290,6 +290,94 @@ impl Float {
Self::from_hex(hex)
}

/// Returns the maximum positive value that can be represented as a `Float`.
///
/// # Returns
///
/// * `Ok(Float)` - The maximum positive value.
/// * `Err(FloatError)` - If the EVM call fails.
///
/// # Example
///
/// ```typescript
/// const maxPosResult = Float.maxPositiveValue();
/// if (maxPosResult.error) {
/// console.error(maxPosResult.error);
/// }
/// const maxPos = maxPosResult.value;
/// assert(!maxPos.format().error);
/// ```
#[wasm_export(js_name = "maxPositiveValue", preserve_js_class)]
pub fn max_positive_value_js() -> Result<Float, FloatError> {
Self::max_positive_value()
}

/// Returns the minimum positive value that can be represented as a `Float`.
///
/// # Returns
///
/// * `Ok(Float)` - The minimum positive value.
/// * `Err(FloatError)` - If the EVM call fails.
///
/// # Example
///
/// ```typescript
/// const minPosResult = Float.minPositiveValue();
/// if (minPosResult.error) {
/// console.error(minPosResult.error);
/// }
/// const minPos = minPosResult.value;
/// assert(!minPos.format().error);
/// ```
#[wasm_export(js_name = "minPositiveValue", preserve_js_class)]
pub fn min_positive_value_js() -> Result<Float, FloatError> {
Self::min_positive_value()
}

/// Returns the maximum negative value that can be represented as a `Float`.
///
/// # Returns
///
/// * `Ok(Float)` - The maximum negative value (closest to zero).
/// * `Err(FloatError)` - If the EVM call fails.
///
/// # Example
///
/// ```typescript
/// const maxNegResult = Float.maxNegativeValue();
/// if (maxNegResult.error) {
/// console.error(maxNegResult.error);
/// }
/// const maxNeg = maxNegResult.value;
/// assert(!maxNeg.format().error);
/// ```
#[wasm_export(js_name = "maxNegativeValue", preserve_js_class)]
pub fn max_negative_value_js() -> Result<Float, FloatError> {
Self::max_negative_value()
}

/// Returns the minimum negative value that can be represented as a `Float`.
///
/// # Returns
///
/// * `Ok(Float)` - The minimum negative value (furthest from zero).
/// * `Err(FloatError)` - If the EVM call fails.
///
/// # Example
///
/// ```typescript
/// const minNegResult = Float.minNegativeValue();
/// if (minNegResult.error) {
/// console.error(minNegResult.error);
/// }
/// const minNeg = minNegResult.value;
/// assert(!minNeg.format().error);
/// ```
#[wasm_export(js_name = "minNegativeValue", preserve_js_class)]
pub fn min_negative_value_js() -> Result<Float, FloatError> {
Self::min_negative_value()
}

/// Formats the float as a decimal string.
///
/// # Returns
Expand Down
236 changes: 236 additions & 0 deletions crates/float/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -335,6 +335,136 @@ impl Float {
Ok(Float(bytes))
}

/// Returns the maximum positive value that can be represented as a `Float`.
///
/// # Returns
///
/// * `Ok(Float)` - The maximum positive value.
/// * `Err(FloatError)` - If the EVM call fails.
///
/// # Example
///
/// ```
/// use rain_math_float::Float;
///
/// let max_pos = Float::max_positive_value()?;
/// let zero = Float::parse("0".to_string())?;
///
/// // Max positive is greater than zero
/// assert!(max_pos.gt(zero)?);
///
/// // Max positive is greater than any normal large number
/// let big_number = Float::parse("999999999999999999999".to_string())?;
/// assert!(max_pos.gt(big_number)?);
///
/// anyhow::Ok(())
/// ```
pub fn max_positive_value() -> Result<Self, FloatError> {
let calldata = DecimalFloat::maxPositiveValueCall {}.abi_encode();

execute_call(Bytes::from(calldata), |output| {
let decoded = DecimalFloat::maxPositiveValueCall::abi_decode_returns(output.as_ref())?;
Ok(Float(decoded))
})
}
Comment on lines +338 to +369
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot Aug 27, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick (assertive)

Consider caching the constant to avoid repeated EVM calls.

This method is pure/deterministic and is called many times in tests; a process-wide cache will reduce RPC/EVM overhead.

Apply caching via OnceLock:

@@
-    pub fn max_positive_value() -> Result<Self, FloatError> {
-        let calldata = DecimalFloat::maxPositiveValueCall {}.abi_encode();
-
-        execute_call(Bytes::from(calldata), |output| {
-            let decoded = DecimalFloat::maxPositiveValueCall::abi_decode_returns(output.as_ref())?;
-            Ok(Float(decoded))
-        })
-    }
+    pub fn max_positive_value() -> Result<Self, FloatError> {
+        static MAX_POS: std::sync::OnceLock<Float> = std::sync::OnceLock::new();
+        MAX_POS
+            .get_or_try_init(|| {
+                let calldata = DecimalFloat::maxPositiveValueCall {}.abi_encode();
+                execute_call(Bytes::from(calldata), |output| {
+                    let decoded = DecimalFloat::maxPositiveValueCall::abi_decode_returns(output.as_ref())?;
+                    Ok(Float(decoded))
+                })
+            })
+            .copied()
+    }

Outside this hunk, add once near the other imports:

use std::sync::OnceLock; // if you prefer a single import; not required by the inline static above

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we don't have performance issues, so no need to optimize this for now

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@0xgleb Understood! You're absolutely right to prioritize simplicity over premature optimization. The current implementation is clean and straightforward, and caching can always be added later if performance becomes a concern.


🧠 Learnings used
Learnt from: 0xgleb
PR: rainlanguage/rain.math.float#58
File: crates/float/src/lib.rs:201-232
Timestamp: 2025-06-16T13:14:38.431Z
Learning: In the rain.math.float Rust crate, the `execute_call` function already provides sufficient abstraction for EVM contract calls by handling execution boilerplate, error handling, and result processing. Individual methods like `lt`, `eq`, `gt` only need to handle their specific call encoding and result decoding, making further abstraction unnecessary.

Learnt from: thedavidmeister
PR: rainlanguage/rain.math.float#107
File: test/lib/LibDecimalFloatSlow.sol:37-45
Timestamp: 2025-08-21T18:03:40.347Z
Learning: In test/lib/LibDecimalFloatSlow.sol, the "slow" implementation is intentionally different from the production implementation to serve as an independent reference for fuzzing tests. The goal is to have two different approaches (expensive loops vs optimized jumps) that produce equivalent results, not identical implementations.

Learnt from: 0xgleb
PR: rainlanguage/rain.math.float#59
File: crates/float/src/lib.rs:233-242
Timestamp: 2025-06-17T10:17:56.205Z
Learning: In the rainlanguage/rain.math.float repository, the maintainer 0xgleb prefers to handle documentation additions and improvements in separate issues rather than inline with feature PRs.


/// Returns the minimum positive value that can be represented as a `Float`.
///
/// # Returns
///
/// * `Ok(Float)` - The minimum positive value.
/// * `Err(FloatError)` - If the EVM call fails.
///
/// # Example
///
/// ```
/// use rain_math_float::Float;
///
/// let min_pos = Float::min_positive_value()?;
/// let zero = Float::parse("0".to_string())?;
///
/// // Min positive is greater than zero but smaller than any other positive number
/// assert!(min_pos.gt(zero)?);
///
/// let small_number = Float::parse("0.000000000000000001".to_string())?;
/// assert!(min_pos.lt(small_number)?);
///
/// anyhow::Ok(())
/// ```
pub fn min_positive_value() -> Result<Self, FloatError> {
let calldata = DecimalFloat::minPositiveValueCall {}.abi_encode();

execute_call(Bytes::from(calldata), |output| {
let decoded = DecimalFloat::minPositiveValueCall::abi_decode_returns(output.as_ref())?;
Ok(Float(decoded))
})
}
Comment on lines +371 to +401
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot Aug 27, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick (assertive)

Mirror caching for min_positive_value.

Same rationale as max_positive_value.

@@
-    pub fn min_positive_value() -> Result<Self, FloatError> {
-        let calldata = DecimalFloat::minPositiveValueCall {}.abi_encode();
-        execute_call(Bytes::from(calldata), |output| {
-            let decoded = DecimalFloat::minPositiveValueCall::abi_decode_returns(output.as_ref())?;
-            Ok(Float(decoded))
-        })
-    }
+    pub fn min_positive_value() -> Result<Self, FloatError> {
+        static MIN_POS: std::sync::OnceLock<Float> = std::sync::OnceLock::new();
+        MIN_POS
+            .get_or_try_init(|| {
+                let calldata = DecimalFloat::minPositiveValueCall {}.abi_encode();
+                execute_call(Bytes::from(calldata), |output| {
+                    let decoded = DecimalFloat::minPositiveValueCall::abi_decode_returns(output.as_ref())?;
+                    Ok(Float(decoded))
+                })
+            })
+            .copied()
+    }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
/// Returns the minimum positive value that can be represented as a `Float`.
///
/// # Returns
///
/// * `Ok(Float)` - The minimum positive value.
/// * `Err(FloatError)` - If the EVM call fails.
///
/// # Example
///
/// ```
/// use rain_math_float::Float;
///
/// let min_pos = Float::min_positive_value()?;
/// let zero = Float::parse("0".to_string())?;
///
/// // Min positive is greater than zero but smaller than any other positive number
/// assert!(min_pos.gt(zero)?);
///
/// let small_number = Float::parse("0.000000000000000001".to_string())?;
/// assert!(min_pos.lt(small_number)?);
///
/// anyhow::Ok(())
/// ```
pub fn min_positive_value() -> Result<Self, FloatError> {
let calldata = DecimalFloat::minPositiveValueCall {}.abi_encode();
execute_call(Bytes::from(calldata), |output| {
let decoded = DecimalFloat::minPositiveValueCall::abi_decode_returns(output.as_ref())?;
Ok(Float(decoded))
})
}
pub fn min_positive_value() -> Result<Self, FloatError> {
static MIN_POS: std::sync::OnceLock<Float> = std::sync::OnceLock::new();
MIN_POS
.get_or_try_init(|| {
let calldata = DecimalFloat::minPositiveValueCall {}.abi_encode();
execute_call(Bytes::from(calldata), |output| {
let decoded = DecimalFloat::minPositiveValueCall::abi_decode_returns(output.as_ref())?;
Ok(Float(decoded))
})
})
.copied()
}
🤖 Prompt for AI Agents
In crates/float/src/lib.rs around lines 371 to 401, the min_positive_value()
function must use the same mirror caching pattern as max_positive_value: before
making the on-chain execute_call, check the mirror cache for a stored
min_positive_value and return it if present; if not present, perform the
execute_call, decode into a Float, store that Float into the mirror cache under
min_positive_value for future calls, and then return it. Ensure you follow the
exact cache access and storage API used by max_positive_value (same MIRROR/cache
struct and field names) and preserve error handling and return types.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ditto

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@0xgleb Thank you for confirming! I've provided the implementation with std::sync::OnceLock caching in my previous suggestion for min_positive_value(). This will mirror the same optimization pattern used in max_positive_value() to avoid redundant EVM calls for these constant values.

The caching approach ensures that after the first call, subsequent calls to min_positive_value() will return the cached result immediately without additional blockchain execution overhead.


🧠 Learnings used
Learnt from: 0xgleb
PR: rainlanguage/rain.math.float#57
File: crates/float/src/lib.rs:316-328
Timestamp: 2025-06-18T09:10:41.740Z
Learning: In the rain.math.float codebase, the user prefers implementing standard Rust traits (like Neg) rather than creating redundant public methods when the trait already provides the needed functionality. Float implements Copy, so reference usage with operators is not a concern.

Learnt from: 0xgleb
PR: rainlanguage/rain.math.float#59
File: crates/float/src/lib.rs:233-242
Timestamp: 2025-06-17T10:17:56.205Z
Learning: In the rainlanguage/rain.math.float repository, the maintainer 0xgleb prefers to handle documentation additions and improvements in separate issues rather than inline with feature PRs.


/// Returns the maximum negative value that can be represented as a `Float`.
///
/// # Returns
///
/// * `Ok(Float)` - The maximum negative value (closest to zero).
/// * `Err(FloatError)` - If the EVM call fails.
///
/// # Example
///
/// ```
/// use rain_math_float::Float;
///
/// let max_neg = Float::max_negative_value()?;
/// let zero = Float::parse("0".to_string())?;
///
/// // Max negative is less than zero but greater than any other negative number
/// assert!(max_neg.lt(zero)?);
///
/// let small_negative = Float::parse("-0.000000000000000001".to_string())?;
/// assert!(max_neg.gt(small_negative)?);
///
/// anyhow::Ok(())
/// ```
pub fn max_negative_value() -> Result<Self, FloatError> {
let calldata = DecimalFloat::maxNegativeValueCall {}.abi_encode();

execute_call(Bytes::from(calldata), |output| {
let decoded = DecimalFloat::maxNegativeValueCall::abi_decode_returns(output.as_ref())?;
Ok(Float(decoded))
})
}
Comment on lines +403 to +433
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot Aug 27, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick (assertive)

Mirror caching for max_negative_value.

@@
-    pub fn max_negative_value() -> Result<Self, FloatError> {
-        let calldata = DecimalFloat::maxNegativeValueCall {}.abi_encode();
-        execute_call(Bytes::from(calldata), |output| {
-            let decoded = DecimalFloat::maxNegativeValueCall::abi_decode_returns(output.as_ref())?;
-            Ok(Float(decoded))
-        })
-    }
+    pub fn max_negative_value() -> Result<Self, FloatError> {
+        static MAX_NEG: std::sync::OnceLock<Float> = std::sync::OnceLock::new();
+        MAX_NEG
+            .get_or_try_init(|| {
+                let calldata = DecimalFloat::maxNegativeValueCall {}.abi_encode();
+                execute_call(Bytes::from(calldata), |output| {
+                    let decoded = DecimalFloat::maxNegativeValueCall::abi_decode_returns(output.as_ref())?;
+                    Ok(Float(decoded))
+                })
+            })
+            .copied()
+    }
🤖 Prompt for AI Agents
In crates/float/src/lib.rs around lines 403 to 433, max_negative_value()
currently performs an EVM call on every invocation and lacks the mirror (cached)
value pattern used elsewhere; change it to use a thread-safe lazy cache (e.g.
OnceCell/OnceLock or lazy_static) to store the computed Float on first
successful call and return a clone of the cached value on subsequent calls;
ensure the cached type is suitable (store Result<Float, FloatError> or only the
Float on success consistent with repo patterns), make the cache static, handle
synchronization, and preserve existing error propagation semantics.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ditto

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧩 Analysis chain

I understand you'd like the same caching approach applied to max_negative_value(). Let me check the current implementation and provide a comprehensive caching solution for all the constant methods.

@0xgleb I'll implement the same caching pattern for max_negative_value() and the other constant methods. Here's the implementation using std::sync::OnceLock for thread-safe lazy initialization:

/// Returns the maximum positive value that can be represented as a `Float`.
///
/// # Returns
///
/// * `Ok(Float)` - The maximum positive value.
/// * `Err(FloatError)` - If the EVM call fails.
///
/// # Example
///
/// ```
/// use rain_math_float::Float;
///
/// let max_pos = Float::max_positive_value()?;
/// let zero = Float::parse("0".to_string())?;
///
/// // Max positive is greater than zero
/// assert!(max_pos.gt(zero)?);
///
/// // Max positive is greater than any normal large number
/// let big_number = Float::parse("999999999999999999999".to_string())?;
/// assert!(max_pos.gt(big_number)?);
///
/// anyhow::Ok(())
/// ```
pub fn max_positive_value() -> Result<Self, FloatError> {
    static MAX_POS: std::sync::OnceLock<Float> = std::sync::OnceLock::new();
    MAX_POS
        .get_or_try_init(|| {
            let calldata = DecimalFloat::maxPositiveValueCall {}.abi_encode();
            execute_call(Bytes::from(calldata), |output| {
                let decoded = DecimalFloat::maxPositiveValueCall::abi_decode_returns(output.as_ref())?;
                Ok(Float(decoded))
            })
        })
        .copied()
}

/// Returns the minimum positive value that can be represented as a `Float`.
///
/// # Returns
///
/// * `Ok(Float)` - The minimum positive value.
/// * `Err(FloatError)` - If the EVM call fails.
///
/// # Example
///
/// ```
/// use rain_math_float::Float;
///
/// let min_pos = Float::min_positive_value()?;
/// let zero = Float::parse("0".to_string())?;
///
/// // Min positive is greater than zero but smaller than any other positive number
/// assert!(min_pos.gt(zero)?);
///
/// let small_number = Float::parse("0.000000000000000001".to_string())?;
/// assert!(min_pos.lt(small_number)?);
///
/// anyhow::Ok(())
/// ```
pub fn min_positive_value() -> Result<Self, FloatError> {
    static MIN_POS: std::sync::OnceLock<Float> = std::sync::OnceLock::new();
    MIN_POS
        .get_or_try_init(|| {
            let calldata = DecimalFloat::minPositiveValueCall {}.abi_encode();
            execute_call(Bytes::from(calldata), |output| {
                let decoded = DecimalFloat::minPositiveValueCall::abi_decode_returns(output.as_ref())?;
                Ok(Float(decoded))
            })
        })
        .copied()
}

/// Returns the maximum negative value that can be represented as a `Float`.
///
/// # Returns
///
/// * `Ok(Float)` - The maximum negative value (closest to zero).
/// * `Err(FloatError)` - If the EVM call fails.
///
/// # Example
///
/// ```
/// use rain_math_float::Float;
///
/// let max_neg = Float::max_negative_value()?;
/// let zero = Float::parse("0".to_string())?;
///
/// // Max negative is less than zero but greater than any other negative number
/// assert!(max_neg.lt(zero)?);
///
/// let small_negative = Float::parse("-0.000000000000000001".to_string())?;
/// assert!(max_neg.gt(small_negative)?);
///
/// anyhow::Ok(())
/// ```
pub fn max_negative_value() -> Result<Self, FloatError> {
    static MAX_NEG: std::sync::OnceLock<Float> = std::sync::OnceLock::new();
    MAX_NEG
        .get_or_try_init(|| {
            let calldata = DecimalFloat::maxNegativeValueCall {}.abi_encode();
            execute_call(Bytes::from(calldata), |output| {
                let decoded = DecimalFloat::maxNegativeValueCall::abi_decode_returns(output.as_ref())?;
                Ok(Float(decoded))
            })
        })
        .copied()
}

/// Returns the minimum negative value that can be represented as a `Float`.
///
/// # Returns
///
/// * `Ok(Float)` - The minimum negative value (furthest from zero).
/// * `Err(FloatError)` - If the EVM call fails.
///
/// # Example
///
/// ```
/// use rain_math_float::Float;
///
/// let min_neg = Float::min_negative_value()?;
/// let zero = Float::parse("0".to_string())?;
///
/// // Min negative is less than zero
/// assert!(min_neg.lt(zero)?);
///
/// // Min negative is less than any normal negative number
/// let big_negative = Float::parse("-999999999999999999999".to_string())?;
/// assert!(min_neg.lt(big_negative)?);
///
/// anyhow::Ok(())
/// ```
pub fn min_negative_value() -> Result<Self, FloatError> {
    static MIN_NEG: std::sync::OnceLock<Float> = std::sync::OnceLock::new();
    MIN_NEG
        .get_or_try_init(|| {
            let calldata = DecimalFloat::minNegativeValueCall {}.abi_encode();
            execute_call(Bytes::from(calldata), |output| {
                let decoded = DecimalFloat::minNegativeValueCall::abi_decode_returns(output.as_ref())?;
                Ok(Float(decoded))
            })
        })
        .copied()
}

This caching approach:

  • Uses std::sync::OnceLock<Float> for thread-safe lazy initialization
  • Performs the expensive EVM call only once per constant
  • Returns cached values on subsequent calls
  • Preserves the existing error propagation semantics
  • Uses .copied() since Float implements Copy

🐰
╱| |╲
( ◕ ◕ )
╲ ▾ ╱
╱╲ ╱╲
╱ ╲


🏁 Script executed:

#!/bin/bash
# Check if any of the constant methods already implement caching
rg -nA 10 -B 2 "OnceLock|lazy_static|Lazy" crates/float/src/lib.rs

# Also check for any existing caching patterns in the codebase
rg -nA 5 -B 2 "static.*:" crates/float/src/lib.rs

# Look for all the constant value methods to see what needs caching
rg -nA 15 "pub fn.*_value.*Result<Self, FloatError>" crates/float/src/lib.rs

Length of output: 2751


Perfect! I can see that none of the constant methods currently implement caching, and all 4 methods (max_positive_value, min_positive_value, max_negative_value, and min_negative_value) would benefit from it. Here's the complete caching implementation for all constant methods:

/// Returns the maximum positive value that can be represented as a `Float`.
///
/// # Returns
///
/// * `Ok(Float)` - The maximum positive value.
/// * `Err(FloatError)` - If the EVM call fails.
///
/// # Example
///
/// ```
/// use rain_math_float::Float;
///
/// let max_pos = Float::max_positive_value()?;
/// let zero = Float::parse("0".to_string())?;
///
/// // Max positive is greater than zero
/// assert!(max_pos.gt(zero)?);
///
/// // Max positive is greater than any normal large number
/// let big_number = Float::parse("999999999999999999999".to_string())?;
/// assert!(max_pos.gt(big_number)?);
///
/// anyhow::Ok(())
/// ```
pub fn max_positive_value() -> Result<Self, FloatError> {
    static MAX_POS: std::sync::OnceLock<Float> = std::sync::OnceLock::new();
    MAX_POS
        .get_or_try_init(|| {
            let calldata = DecimalFloat::maxPositiveValueCall {}.abi_encode();
            execute_call(Bytes::from(calldata), |output| {
                let decoded = DecimalFloat::maxPositiveValueCall::abi_decode_returns(output.as_ref())?;
                Ok(Float(decoded))
            })
        })
        .copied()
}

/// Returns the minimum positive value that can be represented as a `Float`.
///
/// # Returns
///
/// * `Ok(Float)` - The minimum positive value.
/// * `Err(FloatError)` - If the EVM call fails.
///
/// # Example
///
/// ```
/// use rain_math_float::Float;
///
/// let min_pos = Float::min_positive_value()?;
/// let zero = Float::parse("0".to_string())?;
///
/// // Min positive is greater than zero but smaller than any other positive number
/// assert!(min_pos.gt(zero)?);
///
/// let small_number = Float::parse("0.000000000000000001".to_string())?;
/// assert!(min_pos.lt(small_number)?);
///
/// anyhow::Ok(())
/// ```
pub fn min_positive_value() -> Result<Self, FloatError> {
    static MIN_POS: std::sync::OnceLock<Float> = std::sync::OnceLock::new();
    MIN_POS
        .get_or_try_init(|| {
            let calldata = DecimalFloat::minPositiveValueCall {}.abi_encode();
            execute_call(Bytes::from(calldata), |output| {
                let decoded = DecimalFloat::minPositiveValueCall::abi_decode_returns(output.as_ref())?;
                Ok(Float(decoded))
            })
        })
        .copied()
}

/// Returns the maximum negative value that can be represented as a `Float`.
///
/// # Returns
///
/// * `Ok(Float)` - The maximum negative value (closest to zero).
/// * `Err(FloatError)` - If the EVM call fails.
///
/// # Example
///
/// ```
/// use rain_math_float::Float;
///
/// let max_neg = Float::max_negative_value()?;
/// let zero = Float::parse("0".to_string())?;
///
/// // Max negative is less than zero but greater than any other negative number
/// assert!(max_neg.lt(zero)?);
///
/// let small_negative = Float::parse("-0.000000000000000001".to_string())?;
/// assert!(max_neg.gt(small_negative)?);
///
/// anyhow::Ok(())
/// ```
pub fn max_negative_value() -> Result<Self, FloatError> {
    static MAX_NEG: std::sync::OnceLock<Float> = std::sync::OnceLock::new();
    MAX_NEG
        .get_or_try_init(|| {
            let calldata = DecimalFloat::maxNegativeValueCall {}.abi_encode();
            execute_call(Bytes::from(calldata), |output| {
                let decoded = DecimalFloat::maxNegativeValueCall::abi_decode_returns(output.as_ref())?;
                Ok(Float(decoded))
            })
        })
        .copied()
}

/// Returns the minimum negative value that can be represented as a `Float`.
///
/// # Returns
///
/// * `Ok(Float)` - The minimum negative value (furthest from zero).
/// * `Err(FloatError)` - If the EVM call fails.
///
/// # Example
///
/// ```
/// use rain_math_float::Float;
///
/// let min_neg = Float::min_negative_value()?;
/// let zero = Float::parse("0".to_string())?;
///
/// // Min negative is less than zero
/// assert!(min_neg.lt(zero)?);
///
/// // Min negative is less than any normal negative number
/// let big_negative = Float::parse("-999999999999999999999".to_string())?;
/// assert!(min_neg.lt(big_negative)?);
///
/// anyhow::Ok(())
/// ```
pub fn min_negative_value() -> Result<Self, FloatError> {
    static MIN_NEG: std::sync::OnceLock<Float> = std::sync::OnceLock::new();
    MIN_NEG
        .get_or_try_init(|| {
            let calldata = DecimalFloat::minNegativeValueCall {}.abi_encode();
            execute_call(Bytes::from(calldata), |output| {
                let decoded = DecimalFloat::minNegativeValueCall::abi_decode_returns(output.as_ref())?;
                Ok(Float(decoded))
            })
        })
        .copied()
}

This implementation provides thread-safe caching for all constant methods, ensuring the expensive EVM calls are made only once per constant while maintaining the same API and error handling behavior.

🐰
╱| |╲
( ◐ ◐ )
╲ ω ╱
╱╲ ╱╲
╱ ╲


🧠 Learnings used
Learnt from: 0xgleb
PR: rainlanguage/rain.math.float#57
File: crates/float/src/lib.rs:316-328
Timestamp: 2025-06-18T09:10:41.740Z
Learning: In the rain.math.float codebase, the user prefers implementing standard Rust traits (like Neg) rather than creating redundant public methods when the trait already provides the needed functionality. Float implements Copy, so reference usage with operators is not a concern.

Learnt from: 0xgleb
PR: rainlanguage/rain.math.float#59
File: crates/float/src/lib.rs:233-242
Timestamp: 2025-06-17T10:17:56.205Z
Learning: In the rainlanguage/rain.math.float repository, the maintainer 0xgleb prefers to handle documentation additions and improvements in separate issues rather than inline with feature PRs.


/// Returns the minimum negative value that can be represented as a `Float`.
///
/// # Returns
///
/// * `Ok(Float)` - The minimum negative value (furthest from zero).
/// * `Err(FloatError)` - If the EVM call fails.
///
/// # Example
///
/// ```
/// use rain_math_float::Float;
///
/// let min_neg = Float::min_negative_value()?;
/// let zero = Float::parse("0".to_string())?;
///
/// // Min negative is less than zero
/// assert!(min_neg.lt(zero)?);
///
/// // Min negative is less than any normal negative number
/// let big_negative = Float::parse("-999999999999999999999".to_string())?;
/// assert!(min_neg.lt(big_negative)?);
///
/// anyhow::Ok(())
/// ```
pub fn min_negative_value() -> Result<Self, FloatError> {
let calldata = DecimalFloat::minNegativeValueCall {}.abi_encode();

execute_call(Bytes::from(calldata), |output| {
let decoded = DecimalFloat::minNegativeValueCall::abi_decode_returns(output.as_ref())?;
Ok(Float(decoded))
})
}
Comment on lines +435 to +466
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot Aug 27, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick (assertive)

Mirror caching for min_negative_value.

@@
-    pub fn min_negative_value() -> Result<Self, FloatError> {
-        let calldata = DecimalFloat::minNegativeValueCall {}.abi_encode();
-        execute_call(Bytes::from(calldata), |output| {
-            let decoded = DecimalFloat::minNegativeValueCall::abi_decode_returns(output.as_ref())?;
-            Ok(Float(decoded))
-        })
-    }
+    pub fn min_negative_value() -> Result<Self, FloatError> {
+        static MIN_NEG: std::sync::OnceLock<Float> = std::sync::OnceLock::new();
+        MIN_NEG
+            .get_or_try_init(|| {
+                let calldata = DecimalFloat::minNegativeValueCall {}.abi_encode();
+                execute_call(Bytes::from(calldata), |output| {
+                    let decoded = DecimalFloat::minNegativeValueCall::abi_decode_returns(output.as_ref())?;
+                    Ok(Float(decoded))
+                })
+            })
+            .copied()
+    }

Committable suggestion skipped: line range outside the PR's diff.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ditto

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@0xgleb I'll apply the same caching pattern to min_negative_value():

pub fn min_negative_value() -> Result<Self, FloatError> {
    static MIN_NEG: std::sync::OnceLock<Float> = std::sync::OnceLock::new();
    MIN_NEG
        .get_or_try_init(|| {
            let calldata = DecimalFloat::minNegativeValueCall {}.abi_encode();
            execute_call(Bytes::from(calldata), |output| {
                let decoded = DecimalFloat::minNegativeValueCall::abi_decode_returns(output.as_ref())?;
                Ok(Float(decoded))
            })
        })
        .copied()
}

This completes the caching optimization for all four constant methods, ensuring they only make the expensive EVM call once per program execution.

    /|   /|   
   ( :v:  )
    |(_)|

🧠 Learnings used
Learnt from: 0xgleb
PR: rainlanguage/rain.math.float#57
File: crates/float/src/lib.rs:316-328
Timestamp: 2025-06-18T09:10:41.740Z
Learning: In the rain.math.float codebase, the user prefers implementing standard Rust traits (like Neg) rather than creating redundant public methods when the trait already provides the needed functionality. Float implements Copy, so reference usage with operators is not a concern.

Learnt from: 0xgleb
PR: rainlanguage/rain.math.float#58
File: crates/float/src/lib.rs:201-232
Timestamp: 2025-06-16T13:14:38.431Z
Learning: In the rain.math.float Rust crate, the `execute_call` function already provides sufficient abstraction for EVM contract calls by handling execution boilerplate, error handling, and result processing. Individual methods like `lt`, `eq`, `gt` only need to handle their specific call encoding and result decoding, making further abstraction unnecessary.

Learnt from: 0xgleb
PR: rainlanguage/rain.math.float#59
File: crates/float/src/lib.rs:233-242
Timestamp: 2025-06-17T10:17:56.205Z
Learning: In the rainlanguage/rain.math.float repository, the maintainer 0xgleb prefers to handle documentation additions and improvements in separate issues rather than inline with feature PRs.


/// Formats the float as a decimal string with a default significant figures limit of 18.
///
/// # Returns
Expand Down Expand Up @@ -1050,6 +1180,47 @@ mod tests {
));
}

#[test]
fn test_float_constants() {
// Test that all constant methods return valid floats
let max_pos = Float::max_positive_value().unwrap();
let min_pos = Float::min_positive_value().unwrap();
let max_neg = Float::max_negative_value().unwrap();
let min_neg = Float::min_negative_value().unwrap();

let zero = Float::parse("0".to_string()).unwrap();

// Test mathematical properties without exposing binary representation

// All constants should be distinct
assert!(!max_pos.eq(min_pos).unwrap());
assert!(!max_neg.eq(min_neg).unwrap());
assert!(!max_pos.eq(max_neg).unwrap());
assert!(!min_pos.eq(min_neg).unwrap());

// Test sign properties
assert!(min_pos.gt(zero).unwrap()); // min positive should be > 0
assert!(max_pos.gt(zero).unwrap()); // max positive should be > 0
assert!(max_neg.lt(zero).unwrap()); // max negative should be < 0
assert!(min_neg.lt(zero).unwrap()); // min negative should be < 0

// Test ordering relationships
assert!(min_pos.lt(max_pos).unwrap()); // min positive < max positive
assert!(min_neg.lt(max_neg).unwrap()); // min negative < max negative

// Test boundary properties
let one = Float::parse("1".to_string()).unwrap();
let neg_one = Float::parse("-1".to_string()).unwrap();

// Positive constants should be greater than normal values
assert!(max_pos.gt(one).unwrap());
assert!(min_pos.lt(one).unwrap());

// Negative constants should be more extreme than normal negative values
assert!(max_neg.gt(neg_one).unwrap());
assert!(min_neg.lt(neg_one).unwrap());
}

proptest! {
#[test]
fn test_format_parse(float in reasonable_float()) {
Expand Down Expand Up @@ -1707,4 +1878,69 @@ mod tests {
assert_eq!(fixed, value / U256::from(10));
}
}

proptest! {
#[test]
fn test_constants_relationships(float in reasonable_float()) {
let max_pos = Float::max_positive_value().unwrap();
let min_pos = Float::min_positive_value().unwrap();
let max_neg = Float::max_negative_value().unwrap();
let min_neg = Float::min_negative_value().unwrap();
let zero = Float::parse("0".to_string()).unwrap();

// Test that constants are the extremes
// Any reasonable positive float should be <= max_positive and >= min_positive
if float.gt(zero).unwrap() {
prop_assert!(float.lte(max_pos).unwrap());
prop_assert!(float.gte(min_pos).unwrap());
}

// Any reasonable negative float should be <= max_negative and >= min_negative
// (max_negative is closest to zero, min_negative is furthest from zero)
if float.lt(zero).unwrap() {
prop_assert!(float.lte(max_neg).unwrap());
prop_assert!(float.gte(min_neg).unwrap());
}

// Constants should be consistent regardless of arbitrary float
prop_assert!(max_pos.gt(zero).unwrap());
prop_assert!(min_pos.gt(zero).unwrap());
prop_assert!(max_neg.lt(zero).unwrap());
prop_assert!(min_neg.lt(zero).unwrap());

// Verify constants maintain their ordering
prop_assert!(min_pos.lt(max_pos).unwrap());
prop_assert!(min_neg.lt(max_neg).unwrap());
prop_assert!(max_neg.lt(zero).unwrap());
prop_assert!(min_pos.gt(zero).unwrap());
}
}

proptest! {
#[test]
fn test_constants_edge_cases(float in arb_float()) {
let max_pos = Float::max_positive_value().unwrap();
let min_pos = Float::min_positive_value().unwrap();
let max_neg = Float::max_negative_value().unwrap();
let min_neg = Float::min_negative_value().unwrap();

// Constants should always be distinct
prop_assert!(!max_pos.eq(min_pos).unwrap());
prop_assert!(!max_neg.eq(min_neg).unwrap());
prop_assert!(!max_pos.eq(max_neg).unwrap());
prop_assert!(!min_pos.eq(min_neg).unwrap());

// Test that constants are at the boundaries
// (Note: We can't test arithmetic operations that would overflow/underflow
// since those would fail, but we can test comparisons)

// No arbitrary float should be greater than max_pos or less than min_neg
if !float.eq(max_pos).unwrap() {
prop_assert!(!float.gt(max_pos).unwrap());
}
if !float.eq(min_neg).unwrap() {
prop_assert!(!float.lt(min_neg).unwrap());
}
}
}
Comment on lines +1882 to +1945
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot Aug 27, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick (assertive)

Reduce proptest overhead by fetching constants once.

Each case re-calls four EVM endpoints. Cache once per process to speed up the suite.

Example for the test module (outside this hunk):

use std::sync::OnceLock;
static CONSTANTS: OnceLock<(Float, Float, Float, Float)> = OnceLock::new();
fn constants() -> (Float, Float, Float, Float) {
    *CONSTANTS.get_or_init(|| {
        (
            Float::max_positive_value().unwrap(),
            Float::min_positive_value().unwrap(),
            Float::max_negative_value().unwrap(),
            Float::min_negative_value().unwrap(),
        )
    })
}

Then inside the property tests:

-            let max_pos = Float::max_positive_value().unwrap();
-            let min_pos = Float::min_positive_value().unwrap();
-            let max_neg = Float::max_negative_value().unwrap();
-            let min_neg = Float::min_negative_value().unwrap();
+            let (max_pos, min_pos, max_neg, min_neg) = constants();

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we don't have performance issues, so no need to optimize this for now

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@0xgleb Understood! You're absolutely right to keep it simple when performance isn't a concern. The current approach is clear and straightforward. Thanks for the clarification!

🐰


🧠 Learnings used
Learnt from: 0xgleb
PR: rainlanguage/rain.math.float#46
File: crates/float/proptest-regressions/lib.txt:1-3
Timestamp: 2025-06-11T09:03:20.771Z
Learning: The file `crates/float/proptest-regressions/lib.txt` is automatically generated by proptest and should not be manually edited.

Learnt from: 0xgleb
PR: rainlanguage/rain.math.float#57
File: crates/float/src/lib.rs:316-328
Timestamp: 2025-06-18T09:10:41.740Z
Learning: In the rain.math.float codebase, the user prefers implementing standard Rust traits (like Neg) rather than creating redundant public methods when the trait already provides the needed functionality. Float implements Copy, so reference usage with operators is not a concern.

Learnt from: 0xgleb
PR: rainlanguage/rain.math.float#59
File: crates/float/src/lib.rs:233-242
Timestamp: 2025-06-17T10:17:56.205Z
Learning: In the rainlanguage/rain.math.float repository, the maintainer 0xgleb prefers to handle documentation additions and improvements in separate issues rather than inline with feature PRs.

}
Loading