From 94184afb4b411ec4dba6357b7abdd50248445768 Mon Sep 17 00:00:00 2001 From: 0xgleb Date: Tue, 17 Jun 2025 17:02:12 +0400 Subject: [PATCH 1/7] update the contract --- src/concrete/DecimalFloat.sol | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/src/concrete/DecimalFloat.sol b/src/concrete/DecimalFloat.sol index 43f47dac..58a039c4 100644 --- a/src/concrete/DecimalFloat.sol +++ b/src/concrete/DecimalFloat.sol @@ -180,4 +180,15 @@ contract DecimalFloat { (int256 coefficient, int256 exponent) = LibDecimalFloat.unpack(float); return (int224(coefficient), int32(exponent)); } + + /// Exposes `LibDecimalFloat.fromFixedDecimalLosslessPacked` for offchain + /// use. + /// @param value The fixed point decimal value to convert. + /// @param decimals The number of decimals in the fixed point + /// representation. e.g. If 1e18 represents 1 this would be 18 decimals. + /// @return float The Float struct containing the signed coefficient and + /// exponent. + function fromFixedDecimalLosslessPacked(uint256 value, uint8 decimals) external pure returns (Float) { + return LibDecimalFloat.fromFixedDecimalLosslessPacked(value, decimals); + } } From 25b8d156139707c9b743da161d30706041015c9a Mon Sep 17 00:00:00 2001 From: 0xgleb Date: Tue, 17 Jun 2025 17:39:28 +0400 Subject: [PATCH 2/7] add from_fixed_decimal --- crates/float/src/lib.rs | 55 ++++++++++++++++++++++++++++++++++++++++- 1 file changed, 54 insertions(+), 1 deletion(-) diff --git a/crates/float/src/lib.rs b/crates/float/src/lib.rs index 1d1dc972..1a55209b 100644 --- a/crates/float/src/lib.rs +++ b/crates/float/src/lib.rs @@ -9,7 +9,7 @@ use revm::database::InMemoryDB; use revm::handler::EthPrecompiles; use revm::handler::instructions::EthInstructions; use revm::interpreter::interpreter::EthInterpreter; -use revm::primitives::{address, fixed_bytes}; +use revm::primitives::{U256, address, fixed_bytes}; use revm::{Context, MainBuilder, MainContext, SystemCallEvm}; use std::cell::RefCell; use std::ops::{Add, Div, Mul, Sub}; @@ -135,6 +135,18 @@ where pub struct Float(FixedBytes<32>); impl Float { + pub fn from_fixed_decimal(value: U256, decimals: u8) -> Result { + let calldata = + DecimalFloat::fromFixedDecimalLosslessPackedCall { value, decimals }.abi_encode(); + + execute_call(Bytes::from(calldata), |output| { + let decoded = DecimalFloat::fromFixedDecimalLosslessPackedCall::abi_decode_returns( + output.as_ref(), + )?; + Ok(Float(decoded)) + }) + } + #[cfg(test)] fn pack_lossless(coefficient: I224, exponent: i32) -> Result { let calldata = DecimalFloat::packLosslessCall { @@ -592,4 +604,45 @@ mod tests { FloatError::DecimalFloat(DecimalFloatErrors::ExponentOverflow(_)) )); } + + #[test] + fn test_from_fixed_decimal() { + let cases = vec![ + (U256::from(0u128), 0u8, "0"), + (U256::from(0u128), 18u8, "0"), + (U256::from(1u128), 18u8, "1e-18"), + (U256::from(123456789u128), 0u8, "123456789"), + (U256::from(123456789u128), 2u8, "123456789e-2"), + (U256::from(1000000000000000000u128), 18u8, "1"), + ]; + + for (amount, decimals, expected) in cases { + let float = Float::from_fixed_decimal(amount, decimals).expect("should convert"); + let expected = Float::parse(expected.to_string()).unwrap(); + assert!(float.eq(expected).unwrap()); + } + } + + #[test] + fn test_from_fixed_decimal_err() { + let err = Float::from_fixed_decimal(U256::MAX, 1).unwrap_err(); + assert!(matches!( + err, + FloatError::DecimalFloat(DecimalFloatErrors::LossyConversionToFloat(_)) + )); + } + + proptest! { + #[test] + fn test_from_fixed_decimal_valid_range(coeff in any::(), decimals in 0u8..=66u8) { + prop_assume!(coeff >= I224::ZERO); + + let exponent = -(decimals as i32); + let value = U256::from(coeff); + + let float = Float::from_fixed_decimal(value, decimals).unwrap(); + let expected = Float::pack_lossless(coeff, exponent).unwrap(); + prop_assert!(float.eq(expected).unwrap()); + } + } } From 04a7ede8f2aff0312747203e951ebcbbf4562632 Mon Sep 17 00:00:00 2001 From: 0xgleb Date: Tue, 17 Jun 2025 18:22:19 +0400 Subject: [PATCH 3/7] From for B256 --- crates/float/src/lib.rs | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/crates/float/src/lib.rs b/crates/float/src/lib.rs index 1a55209b..183e01da 100644 --- a/crates/float/src/lib.rs +++ b/crates/float/src/lib.rs @@ -1,6 +1,6 @@ #[cfg(test)] use alloy::primitives::aliases::I224; -use alloy::primitives::{Address, Bytes, FixedBytes}; +use alloy::primitives::{Address, B256, Bytes, FixedBytes}; use alloy::sol_types::{SolError, SolInterface}; use alloy::{sol, sol_types::SolCall}; use revm::context::result::{EVMError, ExecutionResult, HaltReason, Output, SuccessReason}; @@ -132,7 +132,13 @@ where } #[derive(Debug, Copy, Clone, PartialEq)] -pub struct Float(FixedBytes<32>); +pub struct Float(B256); + +impl From for B256 { + fn from(float: Float) -> Self { + float.0 + } +} impl Float { pub fn from_fixed_decimal(value: U256, decimals: u8) -> Result { From 23e7fe0f72e9208fa68f99b27c262baa5b5332c9 Mon Sep 17 00:00:00 2001 From: 0xgleb Date: Tue, 17 Jun 2025 19:43:02 +0400 Subject: [PATCH 4/7] add serde --- Cargo.lock | 2 ++ Cargo.toml | 1 + crates/float/Cargo.toml | 2 ++ crates/float/src/lib.rs | 27 ++++++++++++++++++++++++++- 4 files changed, 31 insertions(+), 1 deletion(-) diff --git a/Cargo.lock b/Cargo.lock index 00365697..0fb56079 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3168,6 +3168,8 @@ dependencies = [ "getrandom 0.2.16", "proptest", "revm", + "serde", + "serde_json", "thiserror", ] diff --git a/Cargo.toml b/Cargo.toml index 06950bff..f2b38aa2 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -17,3 +17,4 @@ revm = { version = "25.0.0", default-features = false, features = [ ] } thiserror = "2.0.12" proptest = "1.7.0" +serde = "1.0.219" diff --git a/crates/float/Cargo.toml b/crates/float/Cargo.toml index e1e3cf1b..5a4d1da0 100644 --- a/crates/float/Cargo.toml +++ b/crates/float/Cargo.toml @@ -8,6 +8,7 @@ homepage.workspace = true [dependencies] alloy.workspace = true thiserror.workspace = true +serde.workspace = true [target.'cfg(not(target_family = "wasm"))'.dependencies] revm = { workspace = true, default-features = false, features = [ @@ -29,3 +30,4 @@ getrandom = { version = "0.2.11", features = ["js", "js-sys"] } [dev-dependencies] alloy = { workspace = true, features = ["sol-types", "json-rpc", "arbitrary"] } proptest.workspace = true +serde_json = "1.0.140" diff --git a/crates/float/src/lib.rs b/crates/float/src/lib.rs index 183e01da..8247669b 100644 --- a/crates/float/src/lib.rs +++ b/crates/float/src/lib.rs @@ -11,6 +11,7 @@ use revm::handler::instructions::EthInstructions; use revm::interpreter::interpreter::EthInterpreter; use revm::primitives::{U256, address, fixed_bytes}; use revm::{Context, MainBuilder, MainContext, SystemCallEvm}; +use serde::{Deserialize, Serialize}; use std::cell::RefCell; use std::ops::{Add, Div, Mul, Sub}; use std::thread::AccessError; @@ -131,7 +132,7 @@ where } } -#[derive(Debug, Copy, Clone, PartialEq)] +#[derive(Debug, Copy, Clone, PartialEq, Serialize, Deserialize)] pub struct Float(B256); impl From for B256 { @@ -316,6 +317,7 @@ mod tests { use super::*; use core::str::FromStr; use proptest::prelude::*; + use serde_json::json; prop_compose! { fn arb_float()( @@ -341,6 +343,29 @@ mod tests { } } + #[test] + fn test_serde() { + let float = Float::parse("1.1341234234625468391".to_string()).unwrap(); + let serialized = serde_json::to_string(&float).unwrap(); + assert_eq!( + serialized, + json!("0xffffffed00000000000000000000000000000000000000009d642872ad59a7e7").to_string() + ); + let deserialized: Float = serde_json::from_str(&serialized).unwrap(); + assert_eq!(float, deserialized); + } + + proptest! { + #[test] + fn proptest_serde(float in arb_float()) { + let serialized = serde_json::to_string(&float).unwrap(); + let deserialized: Float = serde_json::from_str(&serialized).unwrap(); + prop_assert!(float.eq(deserialized).unwrap()); + let re_serialized = serde_json::to_string(&deserialized).unwrap(); + prop_assert_eq!(serialized, re_serialized); + } + } + #[test] fn test_parse_and_format() { let float = Float::parse("1.1341234234625468391".to_string()).unwrap(); From 2cb3589c9fd13302bd4efd9e6cd871da09c46179 Mon Sep 17 00:00:00 2001 From: 0xgleb Date: Wed, 18 Jun 2025 14:00:26 +0400 Subject: [PATCH 5/7] make inner B256 pub --- crates/float/src/lib.rs | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/crates/float/src/lib.rs b/crates/float/src/lib.rs index 8247669b..09922caa 100644 --- a/crates/float/src/lib.rs +++ b/crates/float/src/lib.rs @@ -132,14 +132,8 @@ where } } -#[derive(Debug, Copy, Clone, PartialEq, Serialize, Deserialize)] -pub struct Float(B256); - -impl From for B256 { - fn from(float: Float) -> Self { - float.0 - } -} +#[derive(Debug, Copy, Clone, Serialize, Deserialize)] +pub struct Float(pub B256); impl Float { pub fn from_fixed_decimal(value: U256, decimals: u8) -> Result { From 0b056f827e41de3ae4a3cc0f153c7b2552dab21e Mon Sep 17 00:00:00 2001 From: 0xgleb Date: Fri, 20 Jun 2025 15:52:50 +0400 Subject: [PATCH 6/7] expose pack_lossless --- crates/float/src/lib.rs | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/crates/float/src/lib.rs b/crates/float/src/lib.rs index 09922caa..b14d511f 100644 --- a/crates/float/src/lib.rs +++ b/crates/float/src/lib.rs @@ -1,4 +1,3 @@ -#[cfg(test)] use alloy::primitives::aliases::I224; use alloy::primitives::{Address, B256, Bytes, FixedBytes}; use alloy::sol_types::{SolError, SolInterface}; @@ -148,8 +147,7 @@ impl Float { }) } - #[cfg(test)] - fn pack_lossless(coefficient: I224, exponent: i32) -> Result { + pub fn pack_lossless(coefficient: I224, exponent: i32) -> Result { let calldata = DecimalFloat::packLosslessCall { coefficient, exponent, @@ -346,7 +344,7 @@ mod tests { json!("0xffffffed00000000000000000000000000000000000000009d642872ad59a7e7").to_string() ); let deserialized: Float = serde_json::from_str(&serialized).unwrap(); - assert_eq!(float, deserialized); + assert!(float.eq(deserialized).unwrap()); } proptest! { From edb8d225f8d667ae946b2aea887adb9c06c5a117 Mon Sep 17 00:00:00 2001 From: 0xgleb Date: Sat, 21 Jun 2025 15:03:16 +0400 Subject: [PATCH 7/7] Default --- crates/float/src/lib.rs | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/crates/float/src/lib.rs b/crates/float/src/lib.rs index 46c42709..7f5116c6 100644 --- a/crates/float/src/lib.rs +++ b/crates/float/src/lib.rs @@ -131,7 +131,7 @@ where } } -#[derive(Debug, Copy, Clone, Serialize, Deserialize)] +#[derive(Debug, Copy, Clone, Default, Serialize, Deserialize)] pub struct Float(pub B256); impl Float { @@ -345,6 +345,12 @@ mod tests { use proptest::prelude::*; use serde_json::json; + #[test] + fn test_default() { + let zero = Float::parse("0".to_string()).unwrap(); + assert!(zero.eq(Float::default()).unwrap()); + } + prop_compose! { fn arb_float()( coefficient in any::(),