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
693 changes: 358 additions & 335 deletions .gas-snapshot

Large diffs are not rendered by default.

4 changes: 2 additions & 2 deletions src/generated/Rainterpreter.pointers.sol
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,11 @@
pragma solidity =0.8.25;

/// @dev Hash of the known bytecode.
bytes32 constant BYTECODE_HASH = bytes32(0x7a6f6c2f96777e97914658d74fdcfd399e09f3c52625e9a29356c256f65e7ad1);
bytes32 constant BYTECODE_HASH = bytes32(0x9a2b0c327fc4c41a99ab23f8e3c53a561d1e036de4f3fdfd40101cd261d86cc5);

/// @dev The function pointers known to the interpreter for dynamic dispatch.
/// By setting these as a constant they can be inlined into the interpreter
/// and loaded at eval time for very low gas (~100) due to the compiler
/// optimising it to a single `codecopy` to build the in memory bytes array.
bytes constant OPCODE_FUNCTION_POINTERS =
hex"0851088308a70a330afc0b0e0b200b390b5d0b910ba20bb30c550c740d320de20e660fa810db0d3211d41286132813a013b113c213c213d3143e154915c815e115f51654166d168616bf16ea1703171c1765178c179f1801184f189d18eb19391947199519b81a061a371a451a531a611aaf1ae01b111b5f1b901bc11c0f1c3c1c5f1cad1da3";
hex"085a088c08b00a3c0b050b170b290b420b660b9a0bab0bbc0c5e0c7d0d3b0deb0e6f0fb110e40d3b11dd128f133113a913ba13cb13cb13dc1447155215d115ea15fe165d1676168f16c816f3170c1725176e179517a8180a185818a618f419421950199e19c11a0f1a401a841a921aa01aae1afc1b2d1b5e1bac1bdd1c0e1c5c1c891cac1cfa1df0";
6 changes: 3 additions & 3 deletions src/generated/RainterpreterExpressionDeployer.pointers.sol
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,11 @@
pragma solidity =0.8.25;

/// @dev Hash of the known bytecode.
bytes32 constant BYTECODE_HASH = bytes32(0x3a3a3679560a95636acc278e1222b00d4f9f6d8f5f9198f2be9d8d49597cb14d);
bytes32 constant BYTECODE_HASH = bytes32(0xb13338c000917efed437eb17269fc4ed086e147c98ad73ab3ce7ef90a97bbc70);

/// @dev The hash of the meta that describes the contract.
bytes32 constant DESCRIBED_BY_META_HASH = bytes32(0xb7ce324b6101ee19a2d9653f7b31efe355ed0d8c4663a4e7cb9dc63b59b94bc7);
bytes32 constant DESCRIBED_BY_META_HASH = bytes32(0x1856b55328ed5bd3f5e2390acf6d07222a82c5d0f834c320c0b7ce538c069c1e);

/// @dev The function pointers for the integrity check fns.
bytes constant INTEGRITY_FUNCTION_POINTERS =
hex"0ed00f4e0fb2112c11361136114011491164120a120a126612de12eb1136114012eb113611401136113611361140112c112c112c112c12f5131a13341136113612f51136113612eb11401136113612eb12eb112c133e133e133e133e133e1140133e11361358112c114011401140133e112c112c133e112c112c135811361140135811401334";
hex"0ed80f560fba1134113e113e11481151116c12121212126e12e612f3113e114812f3113e1148113e113e113e1148113411341134113412fd1322133c113e113e12fd113e113e12f31148113e113e12f312f311341346134613461346134611481346113e1360113411481148114811481346113411341346113411341360113e114813601148133c";
8 changes: 4 additions & 4 deletions src/generated/RainterpreterParser.pointers.sol
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
pragma solidity =0.8.25;

/// @dev Hash of the known bytecode.
bytes32 constant BYTECODE_HASH = bytes32(0x00c53169f4e29f4b3b3db1358da530c2ca03a751b674b0ddc045437ac968cf1a);
bytes32 constant BYTECODE_HASH = bytes32(0x61765f108a4eafb7e39d931d12e91c4239dc9b5fd31d5dcd388366c10ed0d7bc);

/// @dev The parse meta that is used to lookup word definitions.
/// The structure of the parse meta is:
Expand All @@ -29,7 +29,7 @@ bytes32 constant BYTECODE_HASH = bytes32(0x00c53169f4e29f4b3b3db1358da530c2ca03a
/// bit count of the previous bloom filter. If we reach the end of the bloom
/// filters then we have a miss.
bytes constant PARSE_META =
hex"027360018812c0058102300804a1a02000120192e89864d1586010788a0800310122000002000000000000000000000000000000000005000000000000000000000000253bfdf302d3ac012ac8268505b44edf34182f2a14690c630d83a7eb32c5853a08ccb48c3358350a1c21b6883f98a6e5262057e32f7e09af17b43369161b9cec2b5ab5e8243c910831738e091e8a3fa7150113a32e22720213c659b9225ac7c1285da1bd41cc18770aa63c19048eb6fa018179d300dfb6a91fde8a882cb772ea03596d3111fd7ab9427b42922d4396833e58726b1d2330703694005b21f438852081752c3cff19581ac1a49e30ffeff037e1eec73824bdfe2760978f232d6a9a09d520ad061480a0294c65fb0ecb8d4d0b8b587210e7c1b518e4b09907152b041261f729198ab90539f8942b1b45c1270f22a49a3ba6838c4060cc7c0c668e383a10da5035d6d3e33de7d2ff";
hex"027360018812c0058112300804a1a02000120192e89864d1586010788a0800310122000002000000000000000000000000000000000005000000000000000000000000253bfdf302d3ac012ac8268505b44edf35182f2a14690c630d83a7eb32c5853a08ccb48c3358350a1c21b6884098a6e5262057e32f7e09af17b43369161b9cec2b5ab5e8243c910831738e091e8a3fa7150113a32e22720213c659b9225ac7c1285da1bd42cc18770aa63c19048eb6fa018179d300dfb6a91fde8a882cb772ea03596d3111fd7ab9437b42922d4396833f58726b1d2330703794005b21f438852081752c3dff19581ac1a49e30ffeff038e1eec73924bdfe2760978f232d6a9a09d520ad061480a0294c65fb344763440ecb8d4d0b8b587210e7c1b518e4b09907152b041261f729198ab9053af8942b1b45c1270f22a49a3ca6838c4160cc7c0c668e383b10da5036d6d3e33ee7d2ff";

/// @dev The build depth of the parser meta.

Expand All @@ -39,11 +39,11 @@ uint8 constant PARSE_META_BUILD_DEPTH = 2;
/// These positional indexes all map to the same indexes looked up in the parse
/// meta.
bytes constant OPERAND_HANDLER_FUNCTION_POINTERS =
hex"1ab51ab51ab51b8a1ca11ca11ca11b8a1b8a1ab51ab51ab51ca11ca11ca11ca11ca11ca11ca11ca11ca11ca11ca11ca11ca11ca11ca11ca11ca11ca11ca11ca11ca11ca11ca11ca11ca11ca11ca11ca11ca11ca11ca11ca11ca11ca11ca11ca11ca11ca11ca11ca11ca11ca11ca11ca11ca11ca11ca11ca11ca11ca11ca11ca11ab51ca11ca1";
hex"1abd1abd1abd1b921ca91ca91ca91b921b921abd1abd1abd1ca91ca91ca91ca91ca91ca91ca91ca91ca91ca91ca91ca91ca91ca91ca91ca91ca91ca91ca91ca91ca91ca91ca91ca91ca91ca91ca91ca91ca91ca91ca91ca91ca91ca91ca91ca91ca91ca91ca91ca91ca91ca91ca91ca91ca91ca91ca91ca91ca91ca91ca91ca91ca91abd1ca91ca9";

/// @dev Every two bytes is a function pointer for a literal parser.
/// Literal dispatches are determined by the first byte(s) of the literal
/// rather than a full word lookup, and are done with simple conditional
/// jumps as the possibilities are limited compared to the number of words we
/// have.
bytes constant LITERAL_PARSER_FUNCTION_POINTERS = hex"15fd182f18721910";
bytes constant LITERAL_PARSER_FUNCTION_POINTERS = hex"16051837187a1918";
14 changes: 7 additions & 7 deletions src/lib/op/LibAllStandardOps.sol
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ import {LibOpAvg} from "./math/LibOpAvg.sol";
import {LibOpMul} from "./math/LibOpMul.sol";
import {LibOpDiv} from "./math/LibOpDiv.sol";
import {LibOpE} from "./math/LibOpE.sol";
// import {LibOpExp} from "./math/LibOpExp.sol";
import {LibOpExp} from "./math/LibOpExp.sol";
// import {LibOpExp2} from "./math/LibOpExp2.sol";
import {LibOpFloor} from "./math/LibOpFloor.sol";
import {LibOpFrac} from "./math/LibOpFrac.sol";
Expand Down Expand Up @@ -112,7 +112,7 @@ import {LibParseLiteralHex} from "../parse/literal/LibParseLiteralHex.sol";
import {LibParseLiteralSubParseable} from "../parse/literal/LibParseLiteralSubParseable.sol";

/// @dev Number of ops currently provided by `AllStandardOps`.
uint256 constant ALL_STANDARD_OPS_LENGTH = 67;
uint256 constant ALL_STANDARD_OPS_LENGTH = 68;

/// @title LibAllStandardOps
/// @notice Every opcode available from the core repository laid out as a single
Expand Down Expand Up @@ -273,7 +273,7 @@ library LibAllStandardOps {
// AuthoringMetaV2("ceil", "Ceiling of a number."),
AuthoringMetaV2("div", "Divides the first number by all other numbers. Errors if any divisor is zero."),
AuthoringMetaV2("e", "The mathematical constant e."),
// AuthoringMetaV2("exp", "Natural exponential e^x. Errors if the exponentiation exceeds `max-value()`."),
AuthoringMetaV2("exp", "Natural exponential e^x."),
// AuthoringMetaV2("exp2", "Binary exponential 2^x where x. Errors if the exponentiation exceeds `max-value()`."),
AuthoringMetaV2("floor", "Floor of a number."),
AuthoringMetaV2("frac", "Fractional part of a number."),
Expand Down Expand Up @@ -472,8 +472,8 @@ library LibAllStandardOps {
LibParseOperand.handleOperandDisallowed,
// e
LibParseOperand.handleOperandDisallowed,
// // exp
// LibParseOperand.handleOperandDisallowed,
// exp
LibParseOperand.handleOperandDisallowed,
// // exp2
// LibParseOperand.handleOperandDisallowed,
// floor
Expand Down Expand Up @@ -604,7 +604,7 @@ library LibAllStandardOps {
// LibOpCeil.integrity,
LibOpDiv.integrity,
LibOpE.integrity,
// LibOpExp.integrity,
LibOpExp.integrity,
// LibOpExp2.integrity,
LibOpFloor.integrity,
LibOpFrac.integrity,
Expand Down Expand Up @@ -715,7 +715,7 @@ library LibAllStandardOps {
// LibOpCeil.run,
LibOpDiv.run,
LibOpE.run,
// LibOpExp.run,
LibOpExp.run,
// LibOpExp2.run,
LibOpFloor.run,
LibOpFrac.run,
Expand Down
78 changes: 42 additions & 36 deletions src/lib/op/math/LibOpExp.sol
Original file line number Diff line number Diff line change
@@ -1,43 +1,49 @@
// SPDX-License-Identifier: CAL
pragma solidity ^0.8.18;

// import {UD60x18, exp} from "prb-math/UD60x18.sol";
// import {OperandV2} from "rain.interpreter.interface/interface/unstable/IInterpreterV4.sol";
// import {Pointer} from "rain.solmem/lib/LibPointer.sol";
// import {InterpreterState} from "../../state/LibInterpreterState.sol";
// import {IntegrityCheckState} from "../../integrity/LibIntegrityCheck.sol";
import {OperandV2} from "rain.interpreter.interface/interface/unstable/IInterpreterV4.sol";
import {Pointer} from "rain.solmem/lib/LibPointer.sol";
import {InterpreterState} from "../../state/LibInterpreterState.sol";
import {IntegrityCheckState} from "../../integrity/LibIntegrityCheck.sol";
import {LibDecimalFloat, Float} from "rain.math.float/lib/LibDecimalFloat.sol";
import {StackItem} from "rain.interpreter.interface/interface/unstable/IInterpreterV4.sol";

// /// @title LibOpExp
// /// @notice Opcode for the natural exponential e^x as decimal 18 fixed point.
// library LibOpExp {
// function integrity(IntegrityCheckState memory, Operand) internal pure returns (uint256, uint256) {
// // There must be one inputs and one output.
// return (1, 1);
// }
/// @title LibOpExp
/// @notice Opcode for the natural exponential e^x as decimal floating point.
library LibOpExp {
using LibDecimalFloat for Float;

// /// exp
// /// 18 decimal fixed point natural exponent of a number.
// function run(InterpreterState memory, Operand, Pointer stackTop) internal pure returns (Pointer) {
// uint256 a;
// assembly ("memory-safe") {
// a := mload(stackTop)
// }
// a = UD60x18.unwrap(exp(UD60x18.wrap(a)));
function integrity(IntegrityCheckState memory, OperandV2) internal pure returns (uint256, uint256) {
// There must be one inputs and one output.
return (1, 1);
}

// assembly ("memory-safe") {
// mstore(stackTop, a)
// }
// return stackTop;
// }
/// exp
/// decimal floating point natural exponent of a number.
function run(InterpreterState memory, OperandV2, Pointer stackTop) internal view returns (Pointer) {
Float a;
assembly ("memory-safe") {
a := mload(stackTop)
}
a = LibDecimalFloat.FLOAT_E.pow(a, LibDecimalFloat.LOG_TABLES_ADDRESS);

// /// Gas intensive reference implementation of exp for testing.
// function referenceFn(InterpreterState memory, Operand, uint256[] memory inputs)
// internal
// pure
// returns (uint256[] memory)
// {
// uint256[] memory outputs = new uint256[](1);
// outputs[0] = UD60x18.unwrap(exp(UD60x18.wrap(inputs[0])));
// return outputs;
// }
// }
assembly ("memory-safe") {
mstore(stackTop, a)
}
return stackTop;
}

/// Gas intensive reference implementation of exp for testing.
function referenceFn(InterpreterState memory, OperandV2, StackItem[] memory inputs)
internal
view
returns (StackItem[] memory)
{
Float a = Float.wrap(StackItem.unwrap(inputs[0]));
a = LibDecimalFloat.FLOAT_E.pow(a, LibDecimalFloat.LOG_TABLES_ADDRESS);

StackItem[] memory outputs = new StackItem[](1);
outputs[0] = StackItem.wrap(Float.unwrap(a));
return outputs;
}
}
138 changes: 82 additions & 56 deletions test/src/lib/op/math/LibOpExp.t.sol
Original file line number Diff line number Diff line change
@@ -1,59 +1,85 @@
// SPDX-License-Identifier: CAL
pragma solidity =0.8.25;

// import {OpTest, IntegrityCheckState, Operand, InterpreterState, UnexpectedOperand} from "test/abstract/OpTest.sol";
// import {LibOpExp} from "src/lib/op/math/LibOpExp.sol";
// import {LibOperand} from "test/lib/operand/LibOperand.sol";

// contract LibOpExpTest is OpTest {
// /// Directly test the integrity logic of LibOpExp.
// /// Inputs are always 1, outputs are always 1.
// function testOpExpIntegrity(IntegrityCheckState memory state, Operand operand) external pure {
// (uint256 calcInputs, uint256 calcOutputs) = LibOpExp.integrity(state, operand);
// assertEq(calcInputs, 1);
// assertEq(calcOutputs, 1);
// }

// /// Directly test the runtime logic of LibOpExp.
// function testOpExpRun(uint256 a, uint16 operandData) public view {
// a = bound(a, 0, type(uint64).max - 1e18);
// InterpreterState memory state = opTestDefaultInterpreterState();

// Operand operand = LibOperand.build(1, 1, operandData);
// uint256[] memory inputs = new uint256[](1);
// inputs[0] = a;

// opReferenceCheck(state, operand, LibOpExp.referenceFn, LibOpExp.integrity, LibOpExp.run, inputs);
// }

// /// Test the eval of `exp`.
// function testOpExpEval() external view {
// checkHappy("_: exp(0);", 1e18, "e^0");
// checkHappy("_: exp(1);", 2718281828459045234, "e^1");
// checkHappy("_: exp(0.5);", 1648721270700128145, "e^0.5");
// checkHappy("_: exp(2);", 7389056098930650223, "e^2");
// checkHappy("_: exp(3);", 20085536923187667724, "e^3");
// }

// /// Test the eval of `exp` for bad inputs.
// function testOpExpEvalZeroInputs() external {
// checkBadInputs("_: exp();", 0, 1, 0);
// }

// function testOpExpEvalTwoInputs() external {
// checkBadInputs("_: exp(1 1);", 2, 1, 2);
// }

// function testOpExpZeroOutputs() external {
// checkBadOutputs(": exp(1);", 1, 1, 0);
// }

// function testOpExpTwoOutputs() external {
// checkBadOutputs("_ _: exp(1);", 1, 1, 2);
// }

// /// Test that operand is disallowed.
// function testOpExpEvalOperandDisallowed() external {
// checkUnhappyParse("_: exp<0>(1);", abi.encodeWithSelector(UnexpectedOperand.selector));
// }
// }
import {OpTest, IntegrityCheckState, OperandV2, InterpreterState, UnexpectedOperand} from "test/abstract/OpTest.sol";
import {LibOpExp} from "src/lib/op/math/LibOpExp.sol";
import {LibOperand} from "test/lib/operand/LibOperand.sol";
import {LibDecimalFloat, Float} from "rain.math.float/lib/LibDecimalFloat.sol";
import {StackItem} from "rain.interpreter.interface/interface/unstable/IInterpreterV4.sol";

contract LibOpExpTest is OpTest {
function beforeOpTestConstructor() internal virtual override {
vm.createSelectFork(vm.envString("ETH_RPC_URL"));
}

/// Directly test the integrity logic of LibOpExp.
/// Inputs are always 1, outputs are always 1.
function testOpExpIntegrity(IntegrityCheckState memory state, OperandV2 operand) external pure {
(uint256 calcInputs, uint256 calcOutputs) = LibOpExp.integrity(state, operand);
assertEq(calcInputs, 1);
assertEq(calcOutputs, 1);
}

/// Directly test the runtime logic of LibOpExp.
function testOpExpRun(int224 signedCoefficientA, int32 exponentA, uint16 operandData) public view {
signedCoefficientA = int224(bound(signedCoefficientA, 0, 10000));
exponentA = int32(bound(exponentA, -10, 5));
Comment on lines +24 to +26
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.

🧹 Nitpick (assertive)

Consider expanding fuzz test bounds and adding negative input tests

The fuzz test bounds are quite restrictive:

  • Coefficient is bounded to [0, 10000] - consider testing negative values as e^x is defined for all real x
  • Exponent range [-10, 5] might miss edge cases with larger magnitudes

Also consider adding explicit test cases for:

  • Negative inputs (e.g., exp(-1), exp(-2))
  • Edge cases near overflow/underflow boundaries
-        signedCoefficientA = int224(bound(signedCoefficientA, 0, 10000));
-        exponentA = int32(bound(exponentA, -10, 5));
+        signedCoefficientA = int224(bound(signedCoefficientA, -100000, 100000));
+        exponentA = int32(bound(exponentA, -20, 10));
📝 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
function testOpExpRun(int224 signedCoefficientA, int32 exponentA, uint16 operandData) public view {
signedCoefficientA = int224(bound(signedCoefficientA, 0, 10000));
exponentA = int32(bound(exponentA, -10, 5));
function testOpExpRun(int224 signedCoefficientA, int32 exponentA, uint16 operandData) public view {
- signedCoefficientA = int224(bound(signedCoefficientA, 0, 10000));
- exponentA = int32(bound(exponentA, -10, 5));
+ signedCoefficientA = int224(bound(signedCoefficientA, -100000, 100000));
+ exponentA = int32(bound(exponentA, -20, 10));
🤖 Prompt for AI Agents
In test/src/lib/op/math/LibOpExp.t.sol around lines 24 to 26, the fuzz test
bounds are too narrow and exclude negative coefficients, limiting test coverage.
Expand the bounds for signedCoefficientA to include negative values, for example
from a negative lower bound to a positive upper bound, and increase the
exponentA range to cover larger magnitudes both positive and negative.
Additionally, add explicit test cases for negative inputs such as exp(-1) and
exp(-2), and include tests targeting edge cases near overflow and underflow
boundaries to ensure robustness.


InterpreterState memory state = opTestDefaultInterpreterState();
OperandV2 operand = LibOperand.build(1, 1, operandData);

Float a = LibDecimalFloat.packLossless(signedCoefficientA, exponentA);

StackItem[] memory inputs = new StackItem[](1);
inputs[0] = StackItem.wrap(Float.unwrap(a));

opReferenceCheck(state, operand, LibOpExp.referenceFn, LibOpExp.integrity, LibOpExp.run, inputs);
}

/// Test the eval of `exp`.
function testOpExpEval() external view {
checkHappy("_: exp(0);", Float.unwrap(LibDecimalFloat.packLossless(1, 0)), "e^0");
checkHappy(
"_: exp(1);",
Float.unwrap(LibDecimalFloat.packLossless(271828182845904523536028747135266249770000, -41)),
"e^1"
);
checkHappy(
"_: exp(0.5);",
Float.unwrap(LibDecimalFloat.packLossless(164864091422952261768014373567633124880000, -41)),
"e^0.5"
);
checkHappy(
"_: exp(2);",
Float.unwrap(LibDecimalFloat.packLossless(73901273138361809414411498854106499908000, -40)),
"e^2"
);
checkHappy(
"_: exp(3);",
Float.unwrap(LibDecimalFloat.packLossless(200884548537713570608086241405798749310000, -40)),
"e^3"
);
}
Comment on lines +40 to +62
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.

🧹 Nitpick (assertive)

Add test cases for negative inputs

The eval tests only cover positive inputs. Consider adding test cases for negative values to ensure the exponential function handles them correctly:

         checkHappy(
             "_: exp(3);",
             Float.unwrap(LibDecimalFloat.packLossless(200884548537713570608086241405798749310000, -40)),
             "e^3"
         );
+        checkHappy(
+            "_: exp(-1);",
+            Float.unwrap(LibDecimalFloat.packLossless(367897834377610754067267193921935356200000, -42)),
+            "e^-1"
+        );
+        checkHappy(
+            "_: exp(-2);",
+            Float.unwrap(LibDecimalFloat.packLossless(135263130223700958314976198650936531970000, -42)),
+            "e^-2"
+        );
📝 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
function testOpExpEval() external view {
checkHappy("_: exp(0);", Float.unwrap(LibDecimalFloat.packLossless(1, 0)), "e^0");
checkHappy(
"_: exp(1);",
Float.unwrap(LibDecimalFloat.packLossless(271828182845904523536028747135266249770000, -41)),
"e^1"
);
checkHappy(
"_: exp(0.5);",
Float.unwrap(LibDecimalFloat.packLossless(164864091422952261768014373567633124880000, -41)),
"e^0.5"
);
checkHappy(
"_: exp(2);",
Float.unwrap(LibDecimalFloat.packLossless(73901273138361809414411498854106499908000, -40)),
"e^2"
);
checkHappy(
"_: exp(3);",
Float.unwrap(LibDecimalFloat.packLossless(200884548537713570608086241405798749310000, -40)),
"e^3"
);
}
function testOpExpEval() external view {
checkHappy("_: exp(0);", Float.unwrap(LibDecimalFloat.packLossless(1, 0)), "e^0");
checkHappy(
"_: exp(1);",
Float.unwrap(LibDecimalFloat.packLossless(271828182845904523536028747135266249770000, -41)),
"e^1"
);
checkHappy(
"_: exp(0.5);",
Float.unwrap(LibDecimalFloat.packLossless(164864091422952261768014373567633124880000, -41)),
"e^0.5"
);
checkHappy(
"_: exp(2);",
Float.unwrap(LibDecimalFloat.packLossless(73901273138361809414411498854106499908000, -40)),
"e^2"
);
checkHappy(
"_: exp(3);",
Float.unwrap(LibDecimalFloat.packLossless(200884548537713570608086241405798749310000, -40)),
"e^3"
);
+ checkHappy(
+ "_: exp(-1);",
+ Float.unwrap(LibDecimalFloat.packLossless(367897834377610754067267193921935356200000, -42)),
+ "e^-1"
+ );
+ checkHappy(
+ "_: exp(-2);",
+ Float.unwrap(LibDecimalFloat.packLossless(135263130223700958314976198650936531970000, -42)),
+ "e^-2"
+ );
}
🤖 Prompt for AI Agents
In test/src/lib/op/math/LibOpExp.t.sol between lines 40 and 62, the
testOpExpEval function only tests positive inputs for the exponential function.
Add additional test cases with negative input values such as -1, -0.5, and -2 to
verify that the exponential function correctly handles negative exponents. Use
the same checkHappy pattern with appropriate expected results for these negative
inputs.


/// Test the eval of `exp` for bad inputs.
function testOpExpEvalZeroInputs() external {
checkBadInputs("_: exp();", 0, 1, 0);
}

function testOpExpEvalTwoInputs() external {
checkBadInputs("_: exp(1 1);", 2, 1, 2);
}

function testOpExpZeroOutputs() external {
checkBadOutputs(": exp(1);", 1, 1, 0);
}

function testOpExpTwoOutputs() external {
checkBadOutputs("_ _: exp(1);", 1, 1, 2);
}

/// Test that operand is disallowed.
function testOpExpEvalOperandDisallowed() external {
checkUnhappyParse("_: exp<0>(1);", abi.encodeWithSelector(UnexpectedOperand.selector));
}
}
1 change: 1 addition & 0 deletions test/src/lib/op/math/LibOpSqrt.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ contract LibOpSqrtTest is OpTest {

/// Directly test the runtime logic of LibOpSqrt.
function testOpSqrtRun(Float a) public view {
vm.assume(!a.eq(LibDecimalFloat.FLOAT_MIN_NEGATIVE_VALUE));
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.

💡 Verification agent

🧩 Analysis chain

Document why FLOAT_MIN_NEGATIVE_VALUE is excluded from testing

The test excludes LibDecimalFloat.FLOAT_MIN_NEGATIVE_VALUE from the fuzz inputs. This edge case exclusion could mask potential bugs in the sqrt implementation. Please add a comment explaining why this specific value needs to be excluded, or consider handling it properly in the implementation.


🏁 Script executed:

#!/bin/bash
# Check if FLOAT_MIN_NEGATIVE_VALUE is handled consistently across other math operations
ast-grep --pattern 'FLOAT_MIN_NEGATIVE_VALUE' 

Length of output: 45


🏁 Script executed:

#!/bin/bash
# Search for all references to FLOAT_MIN_NEGATIVE_VALUE in the repo
rg -n "FLOAT_MIN_NEGATIVE_VALUE" .

# Locate its definition in the LibDecimalFloat contract
rg -n "constant FLOAT_MIN_NEGATIVE_VALUE" -t sol

Length of output: 496


🏁 Script executed:

#!/bin/bash
# Locate the Solidity file defining LibDecimalFloat
rg -l "library LibDecimalFloat" --glob '*.sol'

# Search for FLOAT_MIN_NEGATIVE_VALUE definition in all .sol files
rg -n "FLOAT_MIN_NEGATIVE_VALUE" --glob '*.sol'

Length of output: 473


🏁 Script executed:

#!/bin/bash
# Display the sqrt test with context around the assume
sed -n '1,80p' test/src/lib/op/math/LibOpSqrt.t.sol

# Locate and display the implementation of LibOpSqrt
file=$(fd LibOpSqrt.sol --search-path src)
if [ -n "$file" ]; then
  echo "Implementation file: $file"
  sed -n '1,80p' "$file"
else
  echo "LibOpSqrt.sol not found"
fi

Length of output: 4913


Add a comment explaining why the smallest negative float is excluded

The line in testOpSqrtRun excludes LibDecimalFloat.FLOAT_MIN_NEGATIVE_VALUE, likely because calling .abs() on the smallest negative value overflows or is undefined. Please document this edge-case in the test (or, if you intend to support it, handle it in the implementation and add a test). For example:

     /// Directly test the runtime logic of LibOpSqrt.
     function testOpSqrtRun(Float a) public view {
-        vm.assume(!a.eq(LibDecimalFloat.FLOAT_MIN_NEGATIVE_VALUE));
+        // Exclude the minimum negative float because abs(min) cannot be represented
+        vm.assume(!a.eq(LibDecimalFloat.FLOAT_MIN_NEGATIVE_VALUE));
         a = a.abs();
         InterpreterState memory state = opTestDefaultInterpreterState();
         …

• File: test/src/lib/op/math/LibOpSqrt.t.sol
Line: 27

📝 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
vm.assume(!a.eq(LibDecimalFloat.FLOAT_MIN_NEGATIVE_VALUE));
/// Directly test the runtime logic of LibOpSqrt.
function testOpSqrtRun(Float a) public view {
- vm.assume(!a.eq(LibDecimalFloat.FLOAT_MIN_NEGATIVE_VALUE));
+ // Exclude the minimum negative float because abs(min) cannot be represented
+ vm.assume(!a.eq(LibDecimalFloat.FLOAT_MIN_NEGATIVE_VALUE));
a = a.abs();
InterpreterState memory state = opTestDefaultInterpreterState();
🤖 Prompt for AI Agents
In test/src/lib/op/math/LibOpSqrt.t.sol at line 27, add a comment explaining
that the smallest negative float is excluded because taking its absolute value
causes overflow or undefined behavior. This clarifies the reason for the
vm.assume condition and documents the edge case for future reference.

a = a.abs();
InterpreterState memory state = opTestDefaultInterpreterState();

Expand Down
Loading