From 46144bfb5e5819774a87232760bc63aac9fa69bd Mon Sep 17 00:00:00 2001 From: thedavidmeister Date: Sun, 13 Jul 2025 22:46:11 +0400 Subject: [PATCH 1/7] max --- src/generated/Rainterpreter.pointers.sol | 4 +-- .../RainterpreterParser.pointers.sol | 8 ++--- src/lib/op/LibAllStandardOps.sol | 12 ++++---- src/lib/op/math/LibOpMax.sol | 30 +++++++++---------- src/lib/op/math/LibOpMin.sol | 4 +-- 5 files changed, 28 insertions(+), 30 deletions(-) diff --git a/src/generated/Rainterpreter.pointers.sol b/src/generated/Rainterpreter.pointers.sol index a24cd16b8..82b6a8275 100644 --- a/src/generated/Rainterpreter.pointers.sol +++ b/src/generated/Rainterpreter.pointers.sol @@ -10,11 +10,11 @@ pragma solidity =0.8.25; /// @dev Hash of the known bytecode. -bytes32 constant BYTECODE_HASH = bytes32(0x201dec199626b955935bb8907110beb3f69aeb86c149be19adf8adb5a01ec361); +bytes32 constant BYTECODE_HASH = bytes32(0x644ba235e66d6bca0d77666cc1d1bbea303261fb97ee8051f12c3edc168579cb); /// @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"071c074e077208fe09c709d909eb0a040a280a5c0a6d0a7e0b200b3f0bee0c720c830c940c940ca50d320d4b0d5f0d780d910da40e060e540e850ed30f210f6f1056"; + hex"07250757077b090709d009e209f40a0d0a310a650a760a870b290b480bf70c7b0c8c0c9d0c9d0cae0d3b0d540d680d810d9a0dad0e0f0e5d0eab0edc0f2a0f780fc610ad"; diff --git a/src/generated/RainterpreterParser.pointers.sol b/src/generated/RainterpreterParser.pointers.sol index 920e92d41..e6b07354e 100644 --- a/src/generated/RainterpreterParser.pointers.sol +++ b/src/generated/RainterpreterParser.pointers.sol @@ -10,7 +10,7 @@ pragma solidity =0.8.25; /// @dev Hash of the known bytecode. -bytes32 constant BYTECODE_HASH = bytes32(0xe37800b673d2b3028fae1ac8509130acb9670683fe9888cd58d2ccebab7d0a02); +bytes32 constant BYTECODE_HASH = bytes32(0xb6ec256d9a7f6de902c35416a668a5fc6cbe39626d02046d0dbacfd6f5dcf605); /// @dev The parse meta that is used to lookup word definitions. /// The structure of the parse meta is: @@ -29,7 +29,7 @@ bytes32 constant BYTECODE_HASH = bytes32(0xe37800b673d2b3028fae1ac8509130acb9670 /// 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"01092404940810009008201088000400e0010080000800001028010100080902090002cd70471f14fbe61c85bda21685cf8700f861d11ad0e20c0e8be82d207c7f8004e5ab37194d06d00d67df8415fedd510602c61f08bcae760bcd088610f1c17805ac4b9f0a09bc09180ce432091ef7b011dfb72601a91367121faf890c2b9f481de804c11bbd68ce14811fdb1726c459034a067c0fffb07907743c4a1e15024513edf10f"; + hex"01092404940810009008201088000400e001008000080000102801010008090209081bcc663402cd70472014fbe61d85bda21685cf8700f861d11ad0e20c0e8be82d217c7f8004e5ab37194d06d00d67df8415fedd510602c61f08bcae760bcd088610f1c17805ac4b9f0a09bc09180ce432091ef7b011dfb72601a91367121faf890c2b9f481ee804c11cbd68ce14811fdb1726c459034a067c0fffb07907743c4a1f15024513edf10f"; /// @dev The build depth of the parser meta. @@ -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"19a219a219a21a771b8e1b8e1b8e1a771a7719a219a219a21b8e1b8e1b8e1b8e1b8e1b8e1b8e1b8e1b8e1b8e1b8e1b8e1b8e1b8e1b8e1b8e1b8e1b8e19a21b8e1b8e"; + hex"19aa19aa19aa1a7f1b961b961b961a7f1a7f19aa19aa19aa1b961b961b961b961b961b961b961b961b961b961b961b961b961b961b961b961b961b961b9619aa1b961b96"; /// @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"14ea171c175f17fd"; +bytes constant LITERAL_PARSER_FUNCTION_POINTERS = hex"14f2172417671805"; diff --git a/src/lib/op/LibAllStandardOps.sol b/src/lib/op/LibAllStandardOps.sol index 92edacf3f..205621cc3 100644 --- a/src/lib/op/LibAllStandardOps.sol +++ b/src/lib/op/LibAllStandardOps.sol @@ -105,7 +105,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 = 33; +uint256 constant ALL_STANDARD_OPS_LENGTH = 34; /// @title LibAllStandardOps /// @notice Every opcode available from the core repository laid out as a single @@ -264,7 +264,7 @@ library LibAllStandardOps { // AuthoringMetaV2("ln", "Natural logarithm ln(x). Errors if the number is zero."), // AuthoringMetaV2("log2", "Base 2 logarithm log2(x). Errors if the number is zero."), // AuthoringMetaV2("log10", "Base 10 logarithm log10(x). Errors if the number is zero."), - // AuthoringMetaV2("max", "Finds the maximum number from all inputs."), + AuthoringMetaV2("max", "Finds the maximum number from all inputs."), AuthoringMetaV2( "max-value", "The maximum representable float value. This is so large that it is effectively infinity (it has ~2.1 billion zero digits). Almost all numbers that you could possibly subtract from it will be ignored as a rounding error." @@ -477,8 +477,8 @@ library LibAllStandardOps { // LibParseOperand.handleOperandDisallowed, // // log10 // LibParseOperand.handleOperandDisallowed, - // // max - // LibParseOperand.handleOperandDisallowed, + // max + LibParseOperand.handleOperandDisallowed, // max-value LibParseOperand.handleOperandDisallowed, // min @@ -601,7 +601,7 @@ library LibAllStandardOps { // LibOpLn.integrity, // LibOpLog2.integrity, // LibOpLog10.integrity, - // LibOpMax.integrity, + LibOpMax.integrity, LibOpMaxValue.integrity, LibOpMin.integrity, // LibOpMod.integrity, @@ -713,7 +713,7 @@ library LibAllStandardOps { // LibOpLn.run, // LibOpLog2.run, // LibOpLog10.run, - // LibOpMax.run, + LibOpMax.run, LibOpMaxValue.run, LibOpMin.run, // LibOpMod.run, diff --git a/src/lib/op/math/LibOpMax.sol b/src/lib/op/math/LibOpMax.sol index 1c279d095..d4ac9f089 100644 --- a/src/lib/op/math/LibOpMax.sol +++ b/src/lib/op/math/LibOpMax.sol @@ -5,9 +5,11 @@ import {OperandV2} from "rain.interpreter.interface/interface/unstable/IInterpre import {Pointer} from "rain.solmem/lib/LibPointer.sol"; import {InterpreterState} from "../../state/LibInterpreterState.sol"; import {IntegrityCheckState} from "../../integrity/LibIntegrityCheck.sol"; +import {Float, LibDecimalFloat} from "rain.math.float/lib/LibDecimalFloat.sol"; +import {StackItem} from "rain.interpreter.interface/interface/unstable/IInterpreterV4.sol"; /// @title LibOpMax -/// @notice Opcode to find the max from N integers. +/// @notice Opcode to find the max from N floats. library LibOpMax { function integrity(IntegrityCheckState memory, OperandV2 operand) internal pure returns (uint256, uint256) { // There must be at least two inputs. @@ -17,18 +19,16 @@ library LibOpMax { } /// max - /// Finds the maximum value from N integers. + /// Finds the maximum value from N floats. function run(InterpreterState memory, OperandV2 operand, Pointer stackTop) internal pure returns (Pointer) { - uint256 a; - uint256 b; + Float a; + Float b; assembly ("memory-safe") { a := mload(stackTop) b := mload(add(stackTop, 0x20)) stackTop := add(stackTop, 0x40) } - if (a < b) { - a = b; - } + a = LibDecimalFloat.max(a, b); { uint256 inputs = uint256((OperandV2.unwrap(operand) >> 0x10) & bytes32(uint256(0x0F))); @@ -38,9 +38,7 @@ library LibOpMax { b := mload(stackTop) stackTop := add(stackTop, 0x20) } - if (a < b) { - a = b; - } + a = LibDecimalFloat.max(a, b); unchecked { i++; } @@ -55,20 +53,20 @@ library LibOpMax { } /// Gas intensive reference implementation of maximum for testing. - function referenceFn(InterpreterState memory, OperandV2, uint256[] memory inputs) + function referenceFn(InterpreterState memory, OperandV2, StackItem[] memory inputs) internal pure - returns (uint256[] memory outputs) + returns (StackItem[] memory outputs) { // Unchecked so that when we assert that an overflow error is thrown, we // see the revert from the real function and not the reference function. unchecked { - uint256 acc = inputs[0]; + Float acc = Float.wrap(StackItem.unwrap(inputs[0])); for (uint256 i = 1; i < inputs.length; i++) { - acc = acc < inputs[i] ? inputs[i] : acc; + acc = LibDecimalFloat.max(acc, Float.wrap(StackItem.unwrap(inputs[i]))); } - outputs = new uint256[](1); - outputs[0] = acc; + outputs = new StackItem[](1); + outputs[0] = StackItem.wrap(Float.unwrap(acc)); } } } diff --git a/src/lib/op/math/LibOpMin.sol b/src/lib/op/math/LibOpMin.sol index 1d2479475..da8915695 100644 --- a/src/lib/op/math/LibOpMin.sol +++ b/src/lib/op/math/LibOpMin.sol @@ -9,7 +9,7 @@ import {Float, LibDecimalFloat} from "rain.math.float/lib/LibDecimalFloat.sol"; import {StackItem} from "rain.interpreter.interface/interface/unstable/IInterpreterV4.sol"; /// @title LibOpMin -/// @notice Opcode to find the min from N integers. +/// @notice Opcode to find the min from N floats. library LibOpMin { function integrity(IntegrityCheckState memory, OperandV2 operand) internal pure returns (uint256, uint256) { // There must be at least two inputs. @@ -19,7 +19,7 @@ library LibOpMin { } /// min - /// Finds the minimum value from N integers. + /// Finds the minimum value from N floats. function run(InterpreterState memory, OperandV2 operand, Pointer stackTop) internal pure returns (Pointer) { Float a; Float b; From 5a03c97eafcd75f66752c985691e30dea52c8621 Mon Sep 17 00:00:00 2001 From: thedavidmeister Date: Sun, 13 Jul 2025 22:46:44 +0400 Subject: [PATCH 2/7] pointers --- src/generated/RainterpreterExpressionDeployer.pointers.sol | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/generated/RainterpreterExpressionDeployer.pointers.sol b/src/generated/RainterpreterExpressionDeployer.pointers.sol index 0cfe02b29..34c2ee666 100644 --- a/src/generated/RainterpreterExpressionDeployer.pointers.sol +++ b/src/generated/RainterpreterExpressionDeployer.pointers.sol @@ -10,11 +10,11 @@ pragma solidity =0.8.25; /// @dev Hash of the known bytecode. -bytes32 constant BYTECODE_HASH = bytes32(0x32ce28951182ae67a8783f6c482e3433eda8756bab71349543b7c621816c6c92); +bytes32 constant BYTECODE_HASH = bytes32(0x7ed4b28e0bdc7587fcf5c0fd4b45cf110f04c2332088675958848363235f14a9); /// @dev The hash of the meta that describes the contract. -bytes32 constant DESCRIBED_BY_META_HASH = bytes32(0x5aaf62fc2ee9a61ce7f262226c5142750dd5f7fc3eaed156ae0ef88e03a44d38); +bytes32 constant DESCRIBED_BY_META_HASH = bytes32(0x544c6f225ebf1a4605bec17742511680cb8f305809b42fb6600b66086e241427); /// @dev The function pointers for the integrity check fns. bytes constant INTEGRITY_FUNCTION_POINTERS = - hex"0dc00e3e0ea2101c1026102610301039105410fa10fa115611ce10261030101c101c101c101c11db1026102610261026101c11e5120d101c11e5120d120d103011db"; + hex"0dc80e460eaa1024102e102e10381041105c11021102115e11d6102e1038102410241024102411e3102e102e102e102e102411ed121511ed102411ed12151215103811e3"; From 18e91675141c9e01e95bd3aecf248be9f97202de Mon Sep 17 00:00:00 2001 From: thedavidmeister Date: Sun, 13 Jul 2025 22:47:02 +0400 Subject: [PATCH 3/7] pointers --- src/generated/RainterpreterExpressionDeployer.pointers.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/generated/RainterpreterExpressionDeployer.pointers.sol b/src/generated/RainterpreterExpressionDeployer.pointers.sol index 34c2ee666..cfc5cccdd 100644 --- a/src/generated/RainterpreterExpressionDeployer.pointers.sol +++ b/src/generated/RainterpreterExpressionDeployer.pointers.sol @@ -10,7 +10,7 @@ pragma solidity =0.8.25; /// @dev Hash of the known bytecode. -bytes32 constant BYTECODE_HASH = bytes32(0x7ed4b28e0bdc7587fcf5c0fd4b45cf110f04c2332088675958848363235f14a9); +bytes32 constant BYTECODE_HASH = bytes32(0x3dfba5f094aad903d939aeb443521b162f8a902e75fe1ddfcd9214d215b2f4da); /// @dev The hash of the meta that describes the contract. bytes32 constant DESCRIBED_BY_META_HASH = bytes32(0x544c6f225ebf1a4605bec17742511680cb8f305809b42fb6600b66086e241427); From bf14f8b1576cffb91a0c22b922340d58afdd216a Mon Sep 17 00:00:00 2001 From: thedavidmeister Date: Sun, 13 Jul 2025 23:04:53 +0400 Subject: [PATCH 4/7] max tests --- test/src/lib/op/math/LibOpMax.t.sol | 303 ++++++++++++++++------------ 1 file changed, 178 insertions(+), 125 deletions(-) diff --git a/test/src/lib/op/math/LibOpMax.t.sol b/test/src/lib/op/math/LibOpMax.t.sol index 2f0d398f8..33ea9951a 100644 --- a/test/src/lib/op/math/LibOpMax.t.sol +++ b/test/src/lib/op/math/LibOpMax.t.sol @@ -3,141 +3,194 @@ pragma solidity =0.8.25; // import {LibUint256Array} from "rain.solmem/lib/LibUint256Array.sol"; -// import {OpTest} from "test/abstract/OpTest.sol"; +import {OpTest} from "test/abstract/OpTest.sol"; // import {LibContext} from "rain.interpreter.interface/lib/caller/LibContext.sol"; // import {UnexpectedOperand} from "src/error/ErrParse.sol"; -// import {LibOpMax} from "src/lib/op/math/LibOpMax.sol"; -// import {InterpreterState} from "src/lib/state/LibInterpreterState.sol"; -// import {IntegrityCheckState} from "src/lib/integrity/LibIntegrityCheck.sol"; -// import {OperandV2} from "rain.interpreter.interface/interface/unstable/IInterpreterV4.sol"; -// import {LibOperand} from "test/lib/operand/LibOperand.sol"; +import {LibOpMax} from "src/lib/op/math/LibOpMax.sol"; +import {InterpreterState} from "src/lib/state/LibInterpreterState.sol"; +import {IntegrityCheckState} from "src/lib/integrity/LibIntegrityCheck.sol"; +import {OperandV2} from "rain.interpreter.interface/interface/unstable/IInterpreterV4.sol"; +import {LibOperand} from "test/lib/operand/LibOperand.sol"; +import {Float, LibDecimalFloat} from "rain.math.float/lib/LibDecimalFloat.sol"; +import {StackItem} from "rain.interpreter.interface/interface/unstable/IInterpreterV4.sol"; -// contract LibOpMaxTest is OpTest { -// using LibUint256Array for uint256[]; +contract LibOpMaxTest is OpTest { + /// Directly test the integrity logic of LibOpMax. This tests the happy + /// path where the inputs input and calc match. + function testOpMaxIntegrityHappy(IntegrityCheckState memory state, uint8 inputs, uint16 operandData) + external + pure + { + inputs = uint8(bound(inputs, 2, 0x0F)); + (uint256 calcInputs, uint256 calcOutputs) = LibOpMax.integrity(state, LibOperand.build(inputs, 1, operandData)); -// /// Directly test the integrity logic of LibOpMax. This tests the happy -// /// path where the inputs input and calc match. -// function testOpMaxIntegrityHappy(IntegrityCheckState memory state, uint8 inputs, uint16 operandData) -// external -// pure -// { -// inputs = uint8(bound(inputs, 2, 0x0F)); -// (uint256 calcInputs, uint256 calcOutputs) = LibOpMax.integrity(state, LibOperand.build(inputs, 1, operandData)); + assertEq(calcInputs, inputs); + assertEq(calcOutputs, 1); + } -// assertEq(calcInputs, inputs); -// assertEq(calcOutputs, 1); -// } + /// Directly test the integrity logic of LibOpMax. This tests the unhappy + /// path where the operand is invalid due to 0 inputs. + function testOpMaxIntegrityUnhappyZeroInputs(IntegrityCheckState memory state) external pure { + (uint256 calcInputs, uint256 calcOutputs) = LibOpMax.integrity(state, OperandV2.wrap(0)); + // Calc inputs will be minimum 2. + assertEq(calcInputs, 2); + assertEq(calcOutputs, 1); + } -// /// Directly test the integrity logic of LibOpMax. This tests the unhappy -// /// path where the operand is invalid due to 0 inputs. -// function testOpMaxIntegrityUnhappyZeroInputs(IntegrityCheckState memory state) external pure { -// (uint256 calcInputs, uint256 calcOutputs) = LibOpMax.integrity(state, Operand.wrap(0)); -// // Calc inputs will be minimum 2. -// assertEq(calcInputs, 2); -// assertEq(calcOutputs, 1); -// } + /// Directly test the integrity logic of LibOpMax. This tests the unhappy + /// path where the operand is invalid due to 1 inputs. + function testOpMaxIntegrityUnhappyOneInput(IntegrityCheckState memory state) external pure { + (uint256 calcInputs, uint256 calcOutputs) = + LibOpMax.integrity(state, OperandV2.wrap(bytes32(uint256(0x010000)))); + // Calc inputs will be minimum 2. + assertEq(calcInputs, 2); + assertEq(calcOutputs, 1); + } -// /// Directly test the integrity logic of LibOpMax. This tests the unhappy -// /// path where the operand is invalid due to 1 inputs. -// function testOpMaxIntegrityUnhappyOneInput(IntegrityCheckState memory state) external pure { -// (uint256 calcInputs, uint256 calcOutputs) = LibOpMax.integrity(state, Operand.wrap(0x010000)); -// // Calc inputs will be minimum 2. -// assertEq(calcInputs, 2); -// assertEq(calcOutputs, 1); -// } + /// Directly test the runtime logic of LibOpMax. + function testOpMaxRun(StackItem[] memory inputs) external view { + InterpreterState memory state = opTestDefaultInterpreterState(); + vm.assume(inputs.length >= 2); + vm.assume(inputs.length <= 0x0F); + OperandV2 operand = LibOperand.build(uint8(inputs.length), 1, 0); + opReferenceCheck(state, operand, LibOpMax.referenceFn, LibOpMax.integrity, LibOpMax.run, inputs); + } -// /// Directly test the runtime logic of LibOpMax. -// function testOpMaxRun(uint256[] memory inputs) external view { -// InterpreterState memory state = opTestDefaultInterpreterState(); -// vm.assume(inputs.length >= 2); -// vm.assume(inputs.length <= 0x0F); -// Operand operand = LibOperand.build(uint8(inputs.length), 1, 0); -// opReferenceCheck(state, operand, LibOpMax.referenceFn, LibOpMax.integrity, LibOpMax.run, inputs); -// } + /// Test the eval of `max` opcode parsed from a string. Tests zero inputs. + function testOpMaxEvalZeroInputs() external { + checkBadInputs("_: max();", 0, 2, 0); + } -// /// Test the eval of `max` opcode parsed from a string. Tests zero inputs. -// function testOpMaxEvalZeroInputs() external { -// checkBadInputs("_: max();", 0, 2, 0); -// } + /// Test the eval of `max` opcode parsed from a string. Tests one input. + function testOpMaxEvalOneInput() external { + checkBadInputs("_: max(5e-18);", 1, 2, 1); + checkBadInputs("_: max(0);", 1, 2, 1); + checkBadInputs("_: max(1e-18);", 1, 2, 1); + checkBadInputs("_: max(max-value());", 1, 2, 1); + } -// /// Test the eval of `max` opcode parsed from a string. Tests one input. -// function testOpMaxEvalOneInput() external { -// checkBadInputs("_: max(5e-18);", 1, 2, 1); -// checkBadInputs("_: max(0);", 1, 2, 1); -// checkBadInputs("_: max(1e-18);", 1, 2, 1); -// checkBadInputs("_: max(max-value());", 1, 2, 1); -// } + function testOpMaxEvalTwoOutputs() external { + checkBadOutputs("_ _: max(0 0);", 2, 1, 2); + } -// function testOpMaxEvalTwoOutputs() external { -// checkBadOutputs("_ _: max(0 0);", 2, 1, 2); -// } + /// Test the eval of `max` opcode parsed from a string. Tests two inputs. + function testOpMaxEval2InputsHappy() external view { + checkHappy("_: max(0 0);", 0, "0 > 0 ? 0 : 1"); + checkHappy("_: max(1 0);", Float.unwrap(LibDecimalFloat.packLossless(1, 0)), "1 > 0 ? 1 : 0"); + checkHappy( + "_: max(max-value() 0);", + Float.unwrap(LibDecimalFloat.packLossless(type(int224).max, type(int32).max)), + "max-value() > 0 ? max-value() : 0" + ); + checkHappy("_: max(0 1);", Float.unwrap(LibDecimalFloat.packLossless(1, 0)), "0 > 1 ? 0 : 1"); + checkHappy("_: max(1 1);", Float.unwrap(LibDecimalFloat.packLossless(1, 0)), "1 > 1 ? 1 : 1"); + checkHappy( + "_: max(0 max-value());", + Float.unwrap(LibDecimalFloat.packLossless(type(int224).max, type(int32).max)), + "0 > max-value() ? 0 : max-value()" + ); + checkHappy( + "_: max(1 max-value());", + Float.unwrap(LibDecimalFloat.packLossless(type(int224).max, type(int32).max)), + "1 > max-value() ? 1 : max-value()" + ); + checkHappy( + "_: max(max-value() 1);", + Float.unwrap(LibDecimalFloat.packLossless(type(int224).max, type(int32).max)), + "1 > max-value() ? 1 : max-value()" + ); + checkHappy( + "_: max(max-value() max-value());", + Float.unwrap(LibDecimalFloat.packLossless(type(int224).max, type(int32).max)), + "max-value() > max-value() ? max-value() : max-value()" + ); + checkHappy("_: max(0 2);", Float.unwrap(LibDecimalFloat.packLossless(2, 0)), "0 > 2 ? 0 : 2"); + checkHappy("_: max(1 2);", Float.unwrap(LibDecimalFloat.packLossless(2, 0)), "1 > 2 ? 1 : 2"); + checkHappy("_: max(2 2);", Float.unwrap(LibDecimalFloat.packLossless(2, 0)), "2 > 2 ? 2 : 2"); + } -// /// Test the eval of `max` opcode parsed from a string. Tests two inputs. -// function testOpMaxEval2InputsHappy() external view { -// checkHappy("_: max(0 0);", 0, "0 > 0 ? 0 : 1"); -// checkHappy("_: max(1e-18 0);", 1, "1 > 0 ? 1 : 0"); -// checkHappy("_: max(max-value() 0);", type(uint256).max, "max-value() > 0 ? max-value() : 0"); -// checkHappy("_: max(0 1e-18);", 1, "0 > 1 ? 0 : 1"); -// checkHappy("_: max(1e-18 1e-18);", 1, "1 > 1 ? 1 : 1"); -// checkHappy("_: max(0 max-value());", type(uint256).max, "0 > max-value() ? 0 : max-value()"); -// checkHappy("_: max(1e-18 max-value());", type(uint256).max, "1 > max-value() ? 1 : max-value()"); -// checkHappy("_: max(max-value() 1e-18);", type(uint256).max, "1 > max-value() ? 1 : max-value()"); -// checkHappy( -// "_: max(max-value() max-value());", -// type(uint256).max, -// "max-value() > max-value() ? max-value() : max-value()" -// ); -// checkHappy("_: max(0 2e-18);", 2, "0 > 2 ? 0 : 2"); -// checkHappy("_: max(1e-18 2e-18);", 2, "1 > 2 ? 1 : 2"); -// checkHappy("_: max(2e-18 2e-18);", 2, "2 > 2 ? 2 : 2"); -// } + /// Test the eval of `max` opcode parsed from a string. Tests three inputs. + function testOpMaxEval3InputsHappy() external view { + checkHappy("_: max(0 0 0);", 0, "0 0 0"); + checkHappy("_: max(1 0 0);", Float.unwrap(LibDecimalFloat.packLossless(1, 0)), "1 0 0"); + checkHappy("_: max(2 0 0);", Float.unwrap(LibDecimalFloat.packLossless(2, 0)), "2 0 0"); + checkHappy("_: max(0 1 0);", Float.unwrap(LibDecimalFloat.packLossless(1, 0)), "0 1 0"); + checkHappy("_: max(1 1 0);", Float.unwrap(LibDecimalFloat.packLossless(1, 0)), "1 1 0"); + checkHappy("_: max(2 1 0);", Float.unwrap(LibDecimalFloat.packLossless(2, 0)), "2 1 0"); + checkHappy("_: max(0 2 0);", Float.unwrap(LibDecimalFloat.packLossless(2, 0)), "0 2 0"); + checkHappy("_: max(1 2 0);", Float.unwrap(LibDecimalFloat.packLossless(2, 0)), "1 2 0"); + checkHappy("_: max(2 2 0);", Float.unwrap(LibDecimalFloat.packLossless(2, 0)), "2 2 0"); + checkHappy("_: max(0 0 1);", Float.unwrap(LibDecimalFloat.packLossless(1, 0)), "0 0 1"); + checkHappy("_: max(1 0 1);", Float.unwrap(LibDecimalFloat.packLossless(1, 0)), "1 0 1"); + checkHappy("_: max(2 0 1);", Float.unwrap(LibDecimalFloat.packLossless(2, 0)), "2 0 1"); + checkHappy("_: max(0 1 1);", Float.unwrap(LibDecimalFloat.packLossless(1, 0)), "0 1 1"); + checkHappy("_: max(1 1 1);", Float.unwrap(LibDecimalFloat.packLossless(1, 0)), "1 1 1"); + checkHappy("_: max(2 1 1);", Float.unwrap(LibDecimalFloat.packLossless(2, 0)), "2 1 1"); + checkHappy("_: max(0 2 1);", Float.unwrap(LibDecimalFloat.packLossless(2, 0)), "0 2 1"); + checkHappy("_: max(1 2 1);", Float.unwrap(LibDecimalFloat.packLossless(2, 0)), "1 2 1"); + checkHappy("_: max(2 2 1);", Float.unwrap(LibDecimalFloat.packLossless(2, 0)), "2 2 1"); + checkHappy("_: max(0 0 2);", Float.unwrap(LibDecimalFloat.packLossless(2, 0)), "0 0 2"); + checkHappy("_: max(1 0 2);", Float.unwrap(LibDecimalFloat.packLossless(2, 0)), "1 0 2"); + checkHappy("_: max(2 0 2);", Float.unwrap(LibDecimalFloat.packLossless(2, 0)), "2 0 2"); + checkHappy("_: max(0 1 2);", Float.unwrap(LibDecimalFloat.packLossless(2, 0)), "0 1 2"); + checkHappy("_: max(1 1 2);", Float.unwrap(LibDecimalFloat.packLossless(2, 0)), "1 1 2"); + checkHappy("_: max(2 1 2);", Float.unwrap(LibDecimalFloat.packLossless(2, 0)), "2 1 2"); + checkHappy("_: max(0 2 2);", Float.unwrap(LibDecimalFloat.packLossless(2, 0)), "0 2 2"); + checkHappy("_: max(1 2 2);", Float.unwrap(LibDecimalFloat.packLossless(2, 0)), "1 2 2"); + checkHappy("_: max(2 2 2);", Float.unwrap(LibDecimalFloat.packLossless(2, 0)), "2 2 2"); + checkHappy( + "_: max(0 0 max-value());", + Float.unwrap(LibDecimalFloat.packLossless(type(int224).max, type(int32).max)), + "0 0 max-value()" + ); + checkHappy( + "_: max(1 0 max-value());", + Float.unwrap(LibDecimalFloat.packLossless(type(int224).max, type(int32).max)), + "1 0 max-value()" + ); + checkHappy( + "_: max(2 0 max-value());", + Float.unwrap(LibDecimalFloat.packLossless(type(int224).max, type(int32).max)), + "2 0 max-value()" + ); + checkHappy( + "_: max(0 1 max-value());", + Float.unwrap(LibDecimalFloat.packLossless(type(int224).max, type(int32).max)), + "0 1 max-value()" + ); + checkHappy( + "_: max(1 1 max-value());", + Float.unwrap(LibDecimalFloat.packLossless(type(int224).max, type(int32).max)), + "1 1 max-value()" + ); + checkHappy( + "_: max(2 1 max-value());", + Float.unwrap(LibDecimalFloat.packLossless(type(int224).max, type(int32).max)), + "2 1 max-value()" + ); + checkHappy( + "_: max(0 2 max-value());", + Float.unwrap(LibDecimalFloat.packLossless(type(int224).max, type(int32).max)), + "0 2 max-value()" + ); + checkHappy( + "_: max(1 2 max-value());", + Float.unwrap(LibDecimalFloat.packLossless(type(int224).max, type(int32).max)), + "1 2 max-value()" + ); + checkHappy( + "_: max(2 2 max-value());", + Float.unwrap(LibDecimalFloat.packLossless(type(int224).max, type(int32).max)), + "2 2 max-value()" + ); + } -// /// Test the eval of `max` opcode parsed from a string. Tests three inputs. -// function testOpMaxEval3InputsHappy() external view { -// checkHappy("_: max(0 0 0);", 0, "0 0 0"); -// checkHappy("_: max(1e-18 0 0);", 1, "1 0 0"); -// checkHappy("_: max(2e-18 0 0);", 2, "2 0 0"); -// checkHappy("_: max(0 1e-18 0);", 1, "0 1 0"); -// checkHappy("_: max(1e-18 1e-18 0);", 1, "1 1 0"); -// checkHappy("_: max(2e-18 1e-18 0);", 2, "2 1 0"); -// checkHappy("_: max(0 2e-18 0);", 2, "0 2 0"); -// checkHappy("_: max(1e-18 2e-18 0);", 2, "1 2 0"); -// checkHappy("_: max(2e-18 2e-18 0);", 2, "2 2 0"); -// checkHappy("_: max(0 0 1e-18);", 1, "0 0 1"); -// checkHappy("_: max(1e-18 0 1e-18);", 1, "1 0 1"); -// checkHappy("_: max(2e-18 0 1e-18);", 2, "2 0 1"); -// checkHappy("_: max(0 1e-18 1e-18);", 1, "0 1 1"); -// checkHappy("_: max(1e-18 1e-18 1e-18);", 1, "1 1 1"); -// checkHappy("_: max(2e-18 1e-18 1e-18);", 2, "2 1 1"); -// checkHappy("_: max(0 2e-18 1e-18);", 2, "0 2 1"); -// checkHappy("_: max(1e-18 2e-18 1e-18);", 2, "1 2 1"); -// checkHappy("_: max(2e-18 2e-18 1e-18);", 2, "2 2 1"); -// checkHappy("_: max(0 0 2e-18);", 2, "0 0 2"); -// checkHappy("_: max(1e-18 0 2e-18);", 2, "1 0 2"); -// checkHappy("_: max(2e-18 0 2e-18);", 2, "2 0 2"); -// checkHappy("_: max(0 1e-18 2e-18);", 2, "0 1 2"); -// checkHappy("_: max(1e-18 1e-18 2e-18);", 2, "1 1 2"); -// checkHappy("_: max(2e-18 1e-18 2e-18);", 2, "2 1 2"); -// checkHappy("_: max(0 2e-18 2e-18);", 2, "0 2 2"); -// checkHappy("_: max(1e-18 2e-18 2e-18);", 2, "1 2 2"); -// checkHappy("_: max(2e-18 2e-18 2e-18);", 2, "2 2 2"); -// checkHappy("_: max(0 0 max-value());", type(uint256).max, "0 0 max-value()"); -// checkHappy("_: max(1e-18 0 max-value());", type(uint256).max, "1 0 max-value()"); -// checkHappy("_: max(2e-18 0 max-value());", type(uint256).max, "2 0 max-value()"); -// checkHappy("_: max(0 1e-18 max-value());", type(uint256).max, "0 1 max-value()"); -// checkHappy("_: max(1e-18 1e-18 max-value());", type(uint256).max, "1 1 max-value()"); -// checkHappy("_: max(2e-18 1e-18 max-value());", type(uint256).max, "2 1 max-value()"); -// checkHappy("_: max(0 2e-18 max-value());", type(uint256).max, "0 2 max-value()"); -// checkHappy("_: max(1e-18 2e-18 max-value());", type(uint256).max, "1 2 max-value()"); -// checkHappy("_: max(2e-18 2e-18 max-value());", type(uint256).max, "2 2 max-value()"); -// } - -// /// Test the eval of `max` opcode parsed from a string. -// /// Tests that operands are disallowed. -// function testOpMaxEvalOperandDisallowed() external { -// checkDisallowedOperand("_: max<0>(0 0 0);"); -// checkDisallowedOperand("_: max<1>(0 0 0);"); -// checkDisallowedOperand("_: max<2>(0 0 0);"); -// checkDisallowedOperand("_: max<3 1>(0 0 0);"); -// } -// } + /// Test the eval of `max` opcode parsed from a string. + /// Tests that operands are disallowed. + function testOpMaxEvalOperandDisallowed() external { + checkDisallowedOperand("_: max<0>(0 0 0);"); + checkDisallowedOperand("_: max<1>(0 0 0);"); + checkDisallowedOperand("_: max<2>(0 0 0);"); + checkDisallowedOperand("_: max<3 1>(0 0 0);"); + } +} From 5d0a082a22d6b018dc24989763dd75def78a4de0 Mon Sep 17 00:00:00 2001 From: thedavidmeister Date: Mon, 14 Jul 2025 21:49:11 +0400 Subject: [PATCH 5/7] fix mul test --- src/lib/op/math/LibOpMul.sol | 33 ++++------------------------- test/src/lib/op/math/LibOpMul.t.sol | 2 ++ 2 files changed, 6 insertions(+), 29 deletions(-) diff --git a/src/lib/op/math/LibOpMul.sol b/src/lib/op/math/LibOpMul.sol index cdee71323..612f83d50 100644 --- a/src/lib/op/math/LibOpMul.sol +++ b/src/lib/op/math/LibOpMul.sol @@ -60,38 +60,13 @@ library LibOpMul { // Unchecked so that when we assert that an overflow error is thrown, we // see the revert from the real function and not the reference function. unchecked { - Float a; - uint256 overflows = 0; - (int256 signedCoefficientA, int256 exponentA) = - LibDecimalFloat.unpack(Float.wrap(StackItem.unwrap(inputs[0]))); - if (int32(exponentA) != exponentA) { - overflows++; - } + Float acc = Float.wrap(StackItem.unwrap(inputs[0])); for (uint256 i = 1; i < inputs.length; i++) { - (int256 signedCoefficientB, int256 exponentB) = - LibDecimalFloat.unpack(Float.wrap(StackItem.unwrap(inputs[i]))); - if (int32(exponentB) != exponentB) { - overflows++; - break; - } - - (signedCoefficientA, exponentA) = - LibDecimalFloatImplementation.mul(signedCoefficientA, exponentA, signedCoefficientB, exponentB); - - if (int32(exponentA) != exponentA) { - overflows++; - break; - } - } - outputs = new StackItem[](1); - - if (overflows > 0) { - a = Float.wrap(keccak256(abi.encodePacked("overflow sentinel"))); - } else { - a = LibDecimalFloat.packLossless(signedCoefficientA, exponentA); + acc = LibDecimalFloat.mul(acc, Float.wrap(StackItem.unwrap(inputs[i]))); } - outputs[0] = StackItem.wrap(Float.unwrap(a)); + outputs = new StackItem[](1); + outputs[0] = StackItem.wrap(Float.unwrap(acc)); return outputs; } diff --git a/test/src/lib/op/math/LibOpMul.t.sol b/test/src/lib/op/math/LibOpMul.t.sol index a415e9711..3bb22fbe4 100644 --- a/test/src/lib/op/math/LibOpMul.t.sol +++ b/test/src/lib/op/math/LibOpMul.t.sol @@ -1,6 +1,7 @@ // SPDX-License-Identifier: CAL pragma solidity =0.8.25; +import {console2} from "forge-std/Test.sol"; import {LibOpMul} from "src/lib/op/math/LibOpMul.sol"; import {OpTest, IntegrityCheckState, OperandV2, InterpreterState} from "test/abstract/OpTest.sol"; import {LibOperand} from "test/lib/operand/LibOperand.sol"; @@ -56,6 +57,7 @@ contract LibOpMulTest is OpTest { try this._testOpMulRun(operand, inputs) {} catch (bytes memory err) { + console2.logBytes(err); assertTrue(bytes4(err) == CoefficientOverflow.selector || bytes4(err) == ExponentOverflow.selector); } } From 24bda16fd9bcee21457496d585346403b3be0aba Mon Sep 17 00:00:00 2001 From: thedavidmeister Date: Mon, 14 Jul 2025 21:59:55 +0400 Subject: [PATCH 6/7] tests --- test/src/lib/op/math/LibOpMax.t.sol | 10 ++++++++++ test/src/lib/op/math/LibOpMin.t.sol | 14 ++++++++++++++ 2 files changed, 24 insertions(+) diff --git a/test/src/lib/op/math/LibOpMax.t.sol b/test/src/lib/op/math/LibOpMax.t.sol index 33ea9951a..379bb23f2 100644 --- a/test/src/lib/op/math/LibOpMax.t.sol +++ b/test/src/lib/op/math/LibOpMax.t.sol @@ -107,6 +107,13 @@ contract LibOpMaxTest is OpTest { checkHappy("_: max(0 2);", Float.unwrap(LibDecimalFloat.packLossless(2, 0)), "0 > 2 ? 0 : 2"); checkHappy("_: max(1 2);", Float.unwrap(LibDecimalFloat.packLossless(2, 0)), "1 > 2 ? 1 : 2"); checkHappy("_: max(2 2);", Float.unwrap(LibDecimalFloat.packLossless(2, 0)), "2 > 2 ? 2 : 2"); + checkHappy("_: max(-1 1);", Float.unwrap(LibDecimalFloat.packLossless(1, 0)), "-1 > 1 ? -1 : 1"); + checkHappy("_: max(-1 0);", Float.unwrap(LibDecimalFloat.packLossless(0, 0)), "-1 > 0 ? -1 : 0"); + checkHappy("_: max(-1 -1);", Float.unwrap(LibDecimalFloat.packLossless(-1, 0)), "-1 > -1 ? -1 : -1"); + checkHappy("_: max(-1 -2);", Float.unwrap(LibDecimalFloat.packLossless(-1, 0)), "-1 > -2 ? -1 : -2"); + checkHappy("_: max(-2 -1);", Float.unwrap(LibDecimalFloat.packLossless(-1, 0)), "-2 > -1 ? -2 : -1"); + checkHappy("_: max(-1.1 -1.0);", Float.unwrap(LibDecimalFloat.packLossless(-1, 0)), "-1.1 > -1.2 ? -1.1 : -1.2"); + checkHappy("_: max(-1.0 -1);", Float.unwrap(LibDecimalFloat.packLossless(-1, 0)), "-1.0 > -1.0 ? -1.0 : -1.0"); } /// Test the eval of `max` opcode parsed from a string. Tests three inputs. @@ -183,6 +190,9 @@ contract LibOpMaxTest is OpTest { Float.unwrap(LibDecimalFloat.packLossless(type(int224).max, type(int32).max)), "2 2 max-value()" ); + checkHappy("_: max(-1.1 -1.0 0);", Float.unwrap(LibDecimalFloat.packLossless(0, 0)), "-1.1 -1.0 0"); + checkHappy("_: max(-1.0 -1 0);", Float.unwrap(LibDecimalFloat.packLossless(0, 0)), "-1.0 -1 0"); + checkHappy("_: max(-1.0 1.0 0);", Float.unwrap(LibDecimalFloat.packLossless(1, 0)), "-1.0 -1.0 0"); } /// Test the eval of `max` opcode parsed from a string. diff --git a/test/src/lib/op/math/LibOpMin.t.sol b/test/src/lib/op/math/LibOpMin.t.sol index 33213bb74..2cb468b5a 100644 --- a/test/src/lib/op/math/LibOpMin.t.sol +++ b/test/src/lib/op/math/LibOpMin.t.sol @@ -88,6 +88,17 @@ contract LibOpMinTest is OpTest { checkHappy("_: min(0 2);", 0, "0 > 2 ? 0 : 2"); checkHappy("_: min(1 2);", Float.unwrap(LibDecimalFloat.packLossless(1, 0)), "1 > 2 ? 1 : 2"); checkHappy("_: min(2 2);", Float.unwrap(LibDecimalFloat.packLossless(2, 0)), "2 > 2 ? 2 : 2"); + checkHappy("_: min(-1 1);", Float.unwrap(LibDecimalFloat.packLossless(-1, 0)), "-1 > 1 ? -1 : 1"); + checkHappy("_: min(-1 0);", Float.unwrap(LibDecimalFloat.packLossless(-1, 0)), "-1 > 0 ? -1 : 0"); + checkHappy("_: min(-1 -1);", Float.unwrap(LibDecimalFloat.packLossless(-1, 0)), "-1 > -1 ? -1 : -1"); + checkHappy("_: min(-1 -2);", Float.unwrap(LibDecimalFloat.packLossless(-2, 0)), "-1 > -2 ? -1 : -2"); + checkHappy("_: min(-2 -1);", Float.unwrap(LibDecimalFloat.packLossless(-2, 0)), "-2 > -1 ? -2 : -1"); + checkHappy( + "_: min(-1.1 -1.0);", Float.unwrap(LibDecimalFloat.packLossless(-11, -1)), "-1.1 > -1.0 ? -1.1 : -1.0" + ); + checkHappy("_: min(-1.0 -1);", Float.unwrap(LibDecimalFloat.packLossless(-1, 0)), "-1.0 > -1 ? -1.0 : -1"); + checkHappy("_: min(-1.0 1.0);", Float.unwrap(LibDecimalFloat.packLossless(-1, 0)), "-1.0 > 1.0 ? -1.0 : 1.0"); + checkHappy("_: min(-1.0 0);", Float.unwrap(LibDecimalFloat.packLossless(-1, 0)), "-1.0 > 0 ? -1.0 : 0"); } /// Test the eval of `min` opcode parsed from a string. Tests three inputs. @@ -184,6 +195,9 @@ contract LibOpMinTest is OpTest { Float.unwrap(LibDecimalFloat.packLossless(type(int224).max, type(int32).max)), "max-value() max-value() max-value()" ); + checkHappy("_: min(0 0 -2);", Float.unwrap(LibDecimalFloat.packLossless(-2, 0)), "0 0 -2"); + checkHappy("_: min(1 0 -2);", Float.unwrap(LibDecimalFloat.packLossless(-2, 0)), "1 0 -2"); + checkHappy("_: min(-1.1 -1.0 0);", Float.unwrap(LibDecimalFloat.packLossless(-11, -1)), "-1.1 -1.0 0"); } /// Test the eval of `min` opcode parsed from a string. From 5ef39f402a3565bd8f63756992149a2f092a9e70 Mon Sep 17 00:00:00 2001 From: thedavidmeister Date: Mon, 14 Jul 2025 22:01:37 +0400 Subject: [PATCH 7/7] lint --- test/src/lib/op/math/LibOpMax.t.sol | 4 ---- 1 file changed, 4 deletions(-) diff --git a/test/src/lib/op/math/LibOpMax.t.sol b/test/src/lib/op/math/LibOpMax.t.sol index 379bb23f2..af9882a76 100644 --- a/test/src/lib/op/math/LibOpMax.t.sol +++ b/test/src/lib/op/math/LibOpMax.t.sol @@ -1,11 +1,7 @@ // SPDX-License-Identifier: CAL pragma solidity =0.8.25; -// import {LibUint256Array} from "rain.solmem/lib/LibUint256Array.sol"; - import {OpTest} from "test/abstract/OpTest.sol"; -// import {LibContext} from "rain.interpreter.interface/lib/caller/LibContext.sol"; -// import {UnexpectedOperand} from "src/error/ErrParse.sol"; import {LibOpMax} from "src/lib/op/math/LibOpMax.sol"; import {InterpreterState} from "src/lib/state/LibInterpreterState.sol"; import {IntegrityCheckState} from "src/lib/integrity/LibIntegrityCheck.sol";