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
112 changes: 111 additions & 1 deletion crates/float/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -135,7 +135,6 @@ where
pub struct Float(FixedBytes<32>);

impl Float {
#[allow(dead_code)] // will be used in future tests
#[cfg(test)]
fn pack_lossless(coefficient: I224, exponent: i32) -> Result<Self, FloatError> {
let calldata = DecimalFloat::packLosslessCall {
Expand All @@ -150,6 +149,27 @@ impl Float {
})
}

#[cfg(test)]
fn unpack(self) -> Result<(I224, i32), FloatError> {
let Float(float) = self;
let calldata = DecimalFloat::unpackCall { float }.abi_encode();

execute_call(Bytes::from(calldata), |output| {
let DecimalFloat::unpackReturn {
_0: coefficient,
_1: exponent,
} = DecimalFloat::unpackCall::abi_decode_returns(output.as_ref())?;

Ok((coefficient, exponent))
})
}

#[cfg(test)]
fn show_unpacked(self) -> Result<String, FloatError> {
let (coefficient, exponent) = self.unpack()?;
Ok(format!("{coefficient}e{exponent}"))
}

pub fn parse(str: String) -> Result<Self, FloatError> {
let calldata = DecimalFloat::parseCall { str }.abi_encode();

Expand Down Expand Up @@ -177,6 +197,39 @@ impl Float {
Ok(decoded)
})
}

pub fn lt(self, b: Self) -> Result<bool, FloatError> {
let Float(a) = self;
let Float(b) = b;
let calldata = DecimalFloat::ltCall { a, b }.abi_encode();

execute_call(Bytes::from(calldata), |output| {
let decoded = DecimalFloat::ltCall::abi_decode_returns(output.as_ref())?;
Ok(decoded)
})
}

pub fn eq(self, b: Self) -> Result<bool, FloatError> {
let Float(a) = self;
let Float(b) = b;
let calldata = DecimalFloat::eqCall { a, b }.abi_encode();

execute_call(Bytes::from(calldata), |output| {
let decoded = DecimalFloat::eqCall::abi_decode_returns(output.as_ref())?;
Ok(decoded)
})
}

pub fn gt(self, b: Self) -> Result<bool, FloatError> {
let Float(a) = self;
let Float(b) = b;
let calldata = DecimalFloat::gtCall { a, b }.abi_encode();

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

@coderabbitai coderabbitai Bot Jun 16, 2025

Choose a reason for hiding this comment

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

🧹 Nitpick (assertive)

Three comparison helpers duplicate boilerplate – consider a small macro

lt, eq, and gt share identical call/decoding scaffolding. A tiny macro (or a private fn cmp(self, b, Selector)) would remove six near-identical bodies and centralise potential future changes.

Not blocking, just a DRY suggestion.

🤖 Prompt for AI Agents
In crates/float/src/lib.rs around lines 201 to 232, the functions lt, eq, and gt
contain duplicated code for call encoding and decoding. Refactor by creating a
small private helper function or macro that takes the comparison selector as a
parameter and performs the shared call and decode logic. Then rewrite lt, eq,
and gt to call this helper, eliminating the repeated boilerplate and
centralizing future changes.

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.

That's what execute_call is for

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 You're absolutely right! Looking at the code, execute_call already provides the appropriate abstraction layer by handling all the EVM execution boilerplate, error handling, and result processing. The remaining differences between lt, eq, and gt are minimal and necessary - each method needs to make a different contract call (ltCall, eqCall, gtCall) and decode the corresponding result type.

The pattern is consistent throughout the codebase where execute_call handles the heavy lifting while each public method focuses on its specific call encoding and result decoding. Further abstraction would likely over-engineer the solution without meaningful benefit.


✏️ Learnings added
Learnt from: 0xgleb
PR: rainlanguage/rain.math.float#58
File: crates/float/src/lib.rs:201-232
Timestamp: 2025-06-16T13:14:38.395Z
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.

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 Add for Float {
Expand Down Expand Up @@ -305,4 +358,61 @@ mod tests {
);
}
}

#[test]
fn test_lt_eq_gt() {
let negone = Float::parse("-1".to_string()).unwrap();
let zero = Float::parse("0".to_string()).unwrap();
let three = Float::parse("3".to_string()).unwrap();

assert!(negone.lt(zero).unwrap());
assert!(!negone.eq(zero).unwrap());
assert!(!negone.gt(zero).unwrap());

assert!(!three.lt(zero).unwrap());
assert!(!three.eq(zero).unwrap());
assert!(three.gt(zero).unwrap());

assert!(zero.lt(three).unwrap());
assert!(!zero.eq(three).unwrap());
assert!(!zero.gt(three).unwrap());
}

proptest! {
#[test]
fn test_lt_eq_gt_with_add(a in reasonable_float()) {
let b = a;
let eq = a.eq(b).unwrap();
prop_assert!(eq);

let one = Float::parse("1".to_string()).unwrap();

let a = (a - one).unwrap();
let lt = a.lt(b).unwrap();
prop_assert!(lt);

let a = (a + one).unwrap();
let eq = a.eq(b).unwrap();
prop_assert!(eq);

let a = (a + one).unwrap();
let gt = a.gt(b).unwrap();
prop_assert!(gt);
}
Comment on lines +382 to +401
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot Jun 16, 2025

Choose a reason for hiding this comment

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

🧹 Nitpick (assertive)

Minor readability nit – heavy variable shadowing in property test

Within test_lt_eq_gt_with_add the name a is re-bound three times, which forces the reader to track its evolving semantics:

let a = (a - one).unwrap(); // shadows
...
let a = (a + one).unwrap(); // shadows again

Using distinct identifiers (a_minus_one, a_plus_one, …) or an in-place let mut a mutation would make the intent clearer without altering behaviour.

🤖 Prompt for AI Agents
In crates/float/src/lib.rs between lines 382 and 401, the test function
test_lt_eq_gt_with_add uses the variable name 'a' repeatedly with shadowing,
which reduces readability. Refactor the code to use distinct variable names like
'a_minus_one' and 'a_plus_one' for the modified values or convert 'a' to a
mutable variable and update it in place. This will clarify the intent without
changing the test behavior.

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.

the current version is fine for the purpose of this test

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! I'll respect your decision on the variable shadowing pattern in this property test.

  🐰
 (  )
(    )

| 😔 |

\    /
 \  /
  \/

✏️ Learnings added
Learnt from: 0xgleb
PR: rainlanguage/rain.math.float#58
File: crates/float/src/lib.rs:382-401
Timestamp: 2025-06-16T13:19:31.990Z
Learning: In the rainlanguage/rain.math.float codebase, variable shadowing in property tests like test_lt_eq_gt_with_add is acceptable when it represents a logical progression of the same conceptual value being tested through different states.

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.

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.

what is happening with ai in here lol

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.

Seems like the humans are having a chat. I'll hop back into my burrow for now. If you need me again, just tag @coderabbitai in a new comment, and I'll come hopping out!


#[test]
fn test_exactly_one_lt_eq_gt(a in arb_float(), b in arb_float()) {
let eq = a.eq(b).unwrap();
let lt = a.lt(b).unwrap();
let gt = a.gt(b).unwrap();

let a_str = a.show_unpacked().unwrap();
let b_str = b.show_unpacked().unwrap();

prop_assert!(lt || eq || gt, "a: {a_str}, b: {b_str}");
prop_assert!(!(lt && eq), "both less than and equal: a: {a_str}, b: {b_str}");
prop_assert!(!(eq && gt), "both equal and greater than: a: {a_str}, b: {b_str}");
prop_assert!(!(lt && gt), "both less than and greater than: a: {a_str}, b: {b_str}");
}
}
}
9 changes: 9 additions & 0 deletions src/concrete/DecimalFloat.sol
Original file line number Diff line number Diff line change
Expand Up @@ -171,4 +171,13 @@ contract DecimalFloat {
function packLossless(int224 coefficient, int32 exponent) external pure returns (Float) {
return LibDecimalFloat.packLossless(coefficient, exponent);
}

/// Exposes `LibDecimalFloat.unpack` for offchain use.
/// @param float The float to unpack.
/// @return coefficient The coefficient of the float.
/// @return exponent The exponent of the float.
function unpack(Float float) external pure returns (int224, int32) {
(int256 coefficient, int256 exponent) = LibDecimalFloat.unpack(float);
return (int224(coefficient), int32(exponent));
}
Comment on lines +175 to +182
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot Jun 16, 2025

Choose a reason for hiding this comment

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

🧹 Nitpick (assertive)

Rename the parameter to avoid shadowing the Float type

Using float as the parameter name (function unpack(Float float)) shadows the Float type identifier and mildly hurts readability. A one–letter change (e.g. value or f) prevents any ambiguity without altering the ABI.

-function unpack(Float float) external pure returns (int224, int32) {
-    (int256 coefficient, int256 exponent) = LibDecimalFloat.unpack(float);
+function unpack(Float value) external pure returns (int224, int32) {
+    (int256 coefficient, int256 exponent) = LibDecimalFloat.unpack(value);
     return (int224(coefficient), int32(exponent));
 }
📝 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
/// Exposes `LibDecimalFloat.unpack` for offchain use.
/// @param float The float to unpack.
/// @return coefficient The coefficient of the float.
/// @return exponent The exponent of the float.
function unpack(Float float) external pure returns (int224, int32) {
(int256 coefficient, int256 exponent) = LibDecimalFloat.unpack(float);
return (int224(coefficient), int32(exponent));
}
/// Exposes `LibDecimalFloat.unpack` for offchain use.
/// @param float The float to unpack.
/// @return coefficient The coefficient of the float.
/// @return exponent The exponent of the float.
function unpack(Float value) external pure returns (int224, int32) {
(int256 coefficient, int256 exponent) = LibDecimalFloat.unpack(value);
return (int224(coefficient), int32(exponent));
}
🤖 Prompt for AI Agents
In src/concrete/DecimalFloat.sol around lines 175 to 182, rename the parameter
of the unpack function from 'float' to a different name like 'value' or 'f' to
avoid shadowing the 'Float' type identifier. This change improves readability
without affecting the ABI. Update all references to this parameter within the
function accordingly.

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.

This is consistent with unpack in LibDecimalFloat.sol

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
# Find the LibDecimalFloat.sol file and examine the unpack function
fd "LibDecimalFloat.sol" --exec cat {} \;

Length of output: 31333


@0xgleb You're absolutely right! I can see that LibDecimalFloat.sol consistently uses float as the parameter name for Float type parameters throughout the codebase - including unpack(Float float), toFixedDecimalLossy(Float float, uint8 decimals), minus(Float float), abs(Float float), and many others.

This establishes a clear naming convention that prioritizes consistency across the codebase. Thank you for the clarification!


✏️ Learnings added
Learnt from: 0xgleb
PR: rainlanguage/rain.math.float#58
File: src/concrete/DecimalFloat.sol:175-182
Timestamp: 2025-06-16T13:17:28.495Z
Learning: In the rainlanguage/rain.math.float codebase, there's an established naming convention where functions accepting a `Float` type parameter consistently use `float` as the parameter name, even though it shadows the type name. This pattern is used throughout `LibDecimalFloat.sol` and should be maintained for consistency in related contracts like `DecimalFloat.sol`.

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.

}