From ff22fea7ef6539627bcdd75a5d41a3c7734fdd89 Mon Sep 17 00:00:00 2001 From: baku-ccron Date: Mon, 7 Sep 2026 14:33:27 +0000 Subject: [PATCH] Cross-reference FLOAT_E against e derived in Rust, read through e() Float::e() calls the concrete's e(), which returns LibDecimalFloat.FLOAT_E, so the check reaches the library's Solidity rather than a copied literal. e is derived by the 1/k! series in 512-bit integer arithmetic with guard digits, rounded to nearest at 66 places, and asserted equal both by pack_lossless and through the contract's parser. FLOAT_PI follows the same path once the library release carrying it is pinned here and the concrete exposes pi(). Co-Authored-By: Claude Fable 5.1 Claude-Session: https://claude.ai/code/session_01QyCCzi9WZPhuXcU1hwr2bq --- crates/float/src/constants.rs | 73 +++++++++++++++++++++++++++++++++++ crates/float/src/lib.rs | 17 ++++++++ 2 files changed, 90 insertions(+) create mode 100644 crates/float/src/constants.rs diff --git a/crates/float/src/constants.rs b/crates/float/src/constants.rs new file mode 100644 index 0000000..c344741 --- /dev/null +++ b/crates/float/src/constants.rs @@ -0,0 +1,73 @@ +//! Cross-references the constants `LibDecimalFloat` packs against values +//! derived here in integer arithmetic. The constants are read back through the +//! compiled concrete's getters, so what is checked is the library's Solidity, +//! not a copy of its digits. + +#[cfg(test)] +mod tests { + use crate::Float; + use alloy::primitives::{aliases::I224, U512}; + + /// Decimal places the library packs the constants at. + const PLACES: u32 = 66; + + /// Extra digits carried through the series so rounding at `PLACES` is + /// exact. + const GUARD: u32 = 12; + + fn ten_pow(n: u32) -> U512 { + U512::from(10u64).pow(U512::from(n)) + } + + /// `e * 10^scale` by the series sum of 1/k!. Every intermediate is an + /// integer; the series stops once a term underflows the scale. + fn e_scaled(scale: u32) -> U512 { + let mut term = ten_pow(scale); + let mut sum = U512::ZERO; + let mut k = 1u64; + while !term.is_zero() { + sum += term; + term /= U512::from(k); + k += 1; + } + sum + } + + /// Drops `GUARD` digits, rounding to nearest. + fn round_to_places(scaled: U512) -> U512 { + let divisor = ten_pow(GUARD); + let (q, r) = (scaled / divisor, scaled % divisor); + if r * U512::from(2u64) >= divisor { + q + U512::from(1u64) + } else { + q + } + } + + fn coefficient(scaled: U512) -> I224 { + I224::from_dec_str(&round_to_places(scaled).to_string()).unwrap() + } + + fn decimal_string(scaled: U512) -> String { + let digits = round_to_places(scaled).to_string(); + let (int, frac) = digits.split_at(digits.len() - PLACES as usize); + format!("{int}.{frac}") + } + + #[test] + fn e_is_e_rounded_to_nearest() { + let scaled = e_scaled(PLACES + GUARD); + let expected = Float::pack_lossless(coefficient(scaled), -(PLACES as i32)).unwrap(); + let e = Float::e().unwrap(); + assert_eq!(e.as_hex(), expected.as_hex()); + } + + #[test] + fn e_parses_from_its_digits() { + let s = decimal_string(e_scaled(PLACES + GUARD)); + assert!(s.starts_with("2.71828182845904523536028747135266249")); + let parsed = Float::parse(s).unwrap(); + let e = Float::e().unwrap(); + assert!(e.eq(parsed).unwrap()); + } +} diff --git a/crates/float/src/lib.rs b/crates/float/src/lib.rs index 994b8fd..7426b12 100644 --- a/crates/float/src/lib.rs +++ b/crates/float/src/lib.rs @@ -9,6 +9,7 @@ use wasm_bindgen_utils::prelude::*; #[cfg(test)] use alloy::primitives::aliases::I224; +mod constants; pub mod error; mod evm; mod fuzz_ops; @@ -500,6 +501,22 @@ impl Float { }) } + /// Returns Euler's number as the library packs it, `LibDecimalFloat.FLOAT_E`, + /// read through the contract's `e()`. + /// + /// # Returns + /// + /// * `Ok(Float)` - The constant e. + /// * `Err(FloatError)` - If the EVM call fails. + pub fn e() -> Result { + let calldata = DecimalFloat::eCall {}.abi_encode(); + + execute_call(Bytes::from(calldata), |output| { + let decoded = DecimalFloat::eCall::abi_decode_returns(output.as_ref())?; + Ok(Float(decoded)) + }) + } + /// Returns the default minimum value for scientific notation formatting (1e-4). /// /// Values smaller than this (in absolute value) will be formatted in scientific notation.