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
2 changes: 2 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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"
2 changes: 2 additions & 0 deletions crates/float/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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 = [
Expand All @@ -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"
96 changes: 89 additions & 7 deletions crates/float/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
#[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};
Expand All @@ -9,8 +8,9 @@ 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 serde::{Deserialize, Serialize};
use std::cell::RefCell;
use std::ops::{Add, Div, Mul, Neg, Sub};
use std::thread::AccessError;
Expand Down Expand Up @@ -131,12 +131,23 @@ where
}
}

#[derive(Debug, Copy, Clone, PartialEq)]
pub struct Float(FixedBytes<32>);
#[derive(Debug, Copy, Clone, Default, Serialize, Deserialize)]
pub struct Float(pub B256);
Comment on lines +134 to +135
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot Jun 21, 2025

Choose a reason for hiding this comment

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

🧹 Nitpick (assertive)

Note: Changing the Float struct's inner type is a breaking change.

The change from FixedBytes<32> to B256 modifies the public API. Any external code that pattern matches on Float(FixedBytes<32>) or otherwise depends on the exact type will break. Consider documenting this in the changelog or release notes.

The addition of Serialize and Deserialize traits is excellent for the goal of enabling Rust usage.

🤖 Prompt for AI Agents
In crates/float/src/lib.rs at lines 134-135, the Float struct's inner type was
changed from FixedBytes<32> to B256, which is a breaking change affecting
external code relying on the original type. To address this, update the
changelog or release notes to clearly document this API change so users are
aware of the impact. No code modification is needed here beyond ensuring proper
documentation of the breaking change.

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.

B256 is an alias for FixedBytes<32>

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

🏁 Script executed:

#!/bin/bash
# Search for B256 type definition or alias in the codebase to verify the user's claim
rg -A 5 -B 5 "type B256|pub type B256" 

# Also search for any documentation about B256 being an alias
rg -A 3 -B 3 "B256.*FixedBytes|FixedBytes.*B256"

Length of output: 485


🌐 Web query:

alloy primitives B256 type definition FixedBytes alias

💡 Result:

Definition of B256 in Alloy Primitives

B256 is a type alias provided by the Alloy primitives Rust library, widely used in Ethereum-related Rust projects such as Foundry, reth, and revm.

Type Alias Definition

  • B256 is defined as a type alias for a 32-byte fixed-size byte array:

    pub type B256 = FixedBytes<32>;
    

    This means B256 and FixedBytes<32> are interchangeable in type usage[4].

  • The underlying type for B256 is a struct holding an array of 32 bytes:

    struct B256(pub [u8; 32]);
    

    This struct encapsulates the bytes and provides implementations and traits relevant to Ethereum-style fixed-length data[4].

Related Type Aliases

Alloy provides similar type aliases for various sizes of fixed-length byte arrays, such as:

  • B8: 1 byte
  • B16: 2 bytes
  • B32: 4 bytes
  • B64: 8 bytes
  • B128: 16 bytes
  • B192: 24 bytes
  • B224: 28 bytes
  • B256: 32 bytes
  • B512: 64 bytes
  • B1024: 128 bytes[2]

These aliases make it easy to work with byte arrays matching the sizes commonly used in Ethereum and Solidity smart contracts.

Usage Context

  • B256 is commonly used to represent 32-byte data such as hashes (e.g., block hash, transaction hash) in Ethereum-like blockchains[2][4].
  • Its fixed size and named alias help clarify intent and ensure type safety in contracts, protocol code, and storage layers.
  • The FixedBytes generic struct and its type aliases (like B256) are part of the alloy_primitives crate, which standardizes these primitives across many EVM-based Rust projects[1][2][4].

Example Usage

use alloy_primitives::{B256, FixedBytes};

// Create a B256 instance from a 32-byte array
let my_hash: B256 = FixedBytes::<32>::from([0u8; 32]);

This example illustrates that B256 is simply a more readable alias for FixedBytes<32>[1][4].

Summary Table

Alias Definition Byte Length Typical Use
B256 FixedBytes<32> 32 Hashes, 32-byte EVM data

References to Source

  • B256 is defined as pub type B256 = FixedBytes<32>;, a 32-byte fixed byte-array type[2][4].
  • The FixedBytes generic allows for arbitrary fixed-size byte arrays, with aliases like B256 for convenience[1][2].

This approach standardizes handling of fixed-size byte arrays in Rust for Ethereum and related applications.

Citations:


@0xgleb You're absolutely correct! Thank you for the clarification.

The web search confirms that B256 is indeed a type alias for FixedBytes<32> in the Alloy primitives library:

pub type B256 = FixedBytes<32>;

Since they are completely interchangeable types, the change from FixedBytes<32> to B256 is not a breaking change. I apologize for the incorrect analysis in my previous comment. The change is purely cosmetic and uses the more conventional alias that's commonly used in Ethereum-related Rust projects.

The addition of Serialize and Deserialize traits remains a great improvement for enabling Rust usage of the Float code.


✏️ Learnings added
Learnt from: 0xgleb
PR: rainlanguage/rain.math.float#68
File: crates/float/src/lib.rs:134-135
Timestamp: 2025-06-21T11:07:19.152Z
Learning: In Alloy primitives library, B256 is a type alias for FixedBytes<32> (pub type B256 = FixedBytes<32>), making them completely interchangeable in type usage. Changing between these types is not a breaking change.

Note: Learnings are effective only in the context of similar code segments. To apply general coding guidelines, please configure review instructions. You can manage existing learnings in the app.


impl Float {
#[cfg(test)]
fn pack_lossless(coefficient: I224, exponent: i32) -> Result<Self, FloatError> {
pub fn from_fixed_decimal(value: U256, decimals: u8) -> Result<Self, FloatError> {
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))
})
}

pub fn pack_lossless(coefficient: I224, exponent: i32) -> Result<Self, FloatError> {
let calldata = DecimalFloat::packLosslessCall {
coefficient,
exponent,
Expand Down Expand Up @@ -386,6 +397,13 @@ mod tests {
use super::*;
use core::str::FromStr;
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()(
Expand All @@ -411,6 +429,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!(float.eq(deserialized).unwrap());
}

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();
Expand Down Expand Up @@ -773,6 +814,33 @@ mod tests {
));
}

#[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(_))
));
}

#[test]
fn test_frac_and_floor_integers() {
let int_float = Float::parse("12345".to_string()).unwrap();
Expand Down Expand Up @@ -808,6 +876,20 @@ mod tests {
assert!(frac.eq(expected_frac).unwrap());
}

proptest! {
#[test]
fn test_from_fixed_decimal_valid_range(coeff in any::<I224>(), 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());
}
}

proptest! {
#[test]
fn test_frac_floor_properties(float in arb_float()) {
Expand Down
11 changes: 11 additions & 0 deletions src/concrete/DecimalFloat.sol
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
}