diff --git a/src/generated/Rainterpreter.pointers.sol b/src/generated/Rainterpreter.pointers.sol index 077899368..39f7d0570 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(0x7844dd6794cf33b630c441de28241af557bf1cecb3463e8dd2ba51da7b32fa96); +bytes32 constant BYTECODE_HASH = bytes32(0x6ba365de507490ff7cb262df51a7bb9b131cf720d1268f29e533709a7cd70c61); /// @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"06f8072a074e08da09a309b509c709e00a040a380a490a5a0afc0b1b0bca0c4e0c5f0c700c700c810d0e0d270d3b0d4e0db00dfe0e2f0e7d0f64"; + hex"07010733075708e309ac09be09d009e90a0d0a410a520a630b050b240bd30c570c680c790c790c8a0d170d300d440d570db90e070e380e860ed40fbb"; diff --git a/src/generated/RainterpreterExpressionDeployer.pointers.sol b/src/generated/RainterpreterExpressionDeployer.pointers.sol index 20dd1c595..430c1f4e9 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(0x02455b61a2b9ddfcc272f62e4a9baffdfc9277cb25fdee9de1103210d24ff15e); +bytes32 constant BYTECODE_HASH = bytes32(0x010a570b11206177d53f0f66cd89962bbfca589385bcb0ec0ab4bc6c1dd3b35c); /// @dev The hash of the meta that describes the contract. -bytes32 constant DESCRIBED_BY_META_HASH = bytes32(0x0fe1540732ed38e2456cd4c64af90025053a1def08aa2623d8708f4173c7771b); +bytes32 constant DESCRIBED_BY_META_HASH = bytes32(0x55ffa49be99c9effc2f611777c0d7d88251c9d4ca0c66dbfb3f520cd675940bb); /// @dev The function pointers for the integrity check fns. bytes constant INTEGRITY_FUNCTION_POINTERS = - hex"0da00e1e0e820ffc1006100610101019103410da10da113611ae100610100ffc0ffc0ffc0ffc11bb100610060ffc11c511ed0ffc11ed101011bb"; + hex"0da80e260e8a1004100e100e10181021103c10e210e2113e11b6100e1018100410041004100411c3100e100e100411cd11f5100411f511f5101811c3"; diff --git a/src/generated/RainterpreterParser.pointers.sol b/src/generated/RainterpreterParser.pointers.sol index bb8e214fa..4923f6230 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(0x5ae79f118873b18e79dd113453e372bf50fea0bab70b88a50cdf6e33a57c1653); +bytes32 constant BYTECODE_HASH = bytes32(0xb416945b30bdad60766ef143f600b093a45edb74bf6b644c2d72a654960b6a85); /// @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(0x5ae79f118873b18e79dd113453e372bf50fea /// 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"01092404900810001008201088000400e0010080000800001028010100080800090002cd70471b14fbe600f861d118d0e20c0e8be82d1c7c7f8004e5ab37174d06d00d67df8415fedd510602c61f08bcae760bcd088610f1c17805ac4b9f0a09bc09160ce432091ef7b011dfb72601a91367121faf890c2b9f4819bd68ce14811fdb034a067c0fffb07907743c4a1a15024513edf10f"; + hex"01092404900810009008201088000400e0010080000800001028010100080800090002cd70471c14fbe600f861d118d0e20c0e8be82d1d7c7f8004e5ab37174d06d00d67df8415fedd510602c61f08bcae760bcd088610f1c17805ac4b9f0a09bc09160ce432091ef7b011dfb72601a91367121faf890c2b9f481ae804c119bd68ce14811fdb034a067c0fffb07907743c4a1b15024513edf10f"; /// @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"1982198219821a571b6e1b6e1b6e1a571a571982198219821b6e1b6e1b6e1b6e1b6e1b6e1b6e1b6e1b6e1b6e1b6e1b6e1b6e1b6e19821b6e1b6e"; + hex"198a198a198a1a5f1b761b761b761a5f1a5f198a198a198a1b761b761b761b761b761b761b761b761b761b761b761b761b761b761b76198a1b761b76"; /// @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"14ca16fc173f17dd"; +bytes constant LITERAL_PARSER_FUNCTION_POINTERS = hex"14d21704174717e5"; diff --git a/src/lib/op/LibAllStandardOps.sol b/src/lib/op/LibAllStandardOps.sol index 555baddc5..b38ab5f55 100644 --- a/src/lib/op/LibAllStandardOps.sol +++ b/src/lib/op/LibAllStandardOps.sol @@ -69,7 +69,7 @@ import {LibOpUint256Pow} from "./math/uint256/LibOpUint256Pow.sol"; import {LibOpAdd} from "./math/LibOpAdd.sol"; // import {LibOpAvg} from "./math/LibOpAvg.sol"; // import {LibOpCeil} from "./math/LibOpCeil.sol"; -// import {LibOpMul} from "./math/LibOpMul.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"; @@ -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 = 29; +uint256 constant ALL_STANDARD_OPS_LENGTH = 30; /// @title LibAllStandardOps /// @notice Every opcode available from the core repository laid out as a single @@ -271,7 +271,7 @@ library LibAllStandardOps { ), // AuthoringMetaV2("min", "Finds the minimum number from all inputs."), // AuthoringMetaV2("mod", "Modulos the first number by all other numbers. Errors if any divisor is zero."), - // AuthoringMetaV2("mul", "Multiplies all numbers together. Errors if the multiplication exceeds `max-value()`."), + AuthoringMetaV2("mul", "Multiplies all numbers together."), // AuthoringMetaV2( // "power", // "Raises the first number to the power of the second number. Errors if the exponentiation exceeds `max-value()`." @@ -485,8 +485,8 @@ library LibAllStandardOps { // LibParseOperand.handleOperandDisallowed, // // mod // LibParseOperand.handleOperandDisallowed, - // // mul - // LibParseOperand.handleOperandDisallowed, + // mul + LibParseOperand.handleOperandDisallowed, // // power // LibParseOperand.handleOperandDisallowed, // // scale-18 @@ -605,7 +605,7 @@ library LibAllStandardOps { LibOpMaxValue.integrity, // LibOpMin.integrity, // LibOpMod.integrity, - // LibOpMul.integrity, + LibOpMul.integrity, // LibOpPow.integrity, // LibOpScale18.integrity, // // uint256-to-decimal18 is a repeat of scale18. @@ -717,7 +717,7 @@ library LibAllStandardOps { LibOpMaxValue.run, // LibOpMin.run, // LibOpMod.run, - // LibOpMul.run, + LibOpMul.run, // LibOpPow.run, // LibOpScale18.run, // // uint256-to-decimal18 is a repeat of scale18. diff --git a/src/lib/op/math/LibOpMul.sol b/src/lib/op/math/LibOpMul.sol index 665e0294b..cdee71323 100644 --- a/src/lib/op/math/LibOpMul.sol +++ b/src/lib/op/math/LibOpMul.sol @@ -1,80 +1,99 @@ // SPDX-License-Identifier: CAL pragma solidity ^0.8.18; -// /// Used for reference implementation so that we have two independent -// /// upstreams to compare against. -// import {Math as OZMath} from "openzeppelin-contracts/contracts/utils/math/Math.sol"; -// import {UD60x18, mul} 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 {LibWillOverflow} from "rain.math.fixedpoint/lib/LibWillOverflow.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"; +import {LibDecimalFloatImplementation} from "rain.math.float/lib/implementation/LibDecimalFloatImplementation.sol"; -// /// @title LibOpMul -// /// @notice Opcode to mul N 18 decimal fixed point values. Errors on overflow. -// library LibOpMul { -// function integrity(IntegrityCheckState memory, Operand operand) internal pure returns (uint256, uint256) { -// // There must be at least two inputs. -// uint256 inputs = (Operand.unwrap(operand) >> 0x10) & 0x0F; -// inputs = inputs > 1 ? inputs : 2; -// return (inputs, 1); -// } +/// @title LibOpMul +/// @notice Opcode to mul N 18 floating point values. +library LibOpMul { + function integrity(IntegrityCheckState memory, OperandV2 operand) internal pure returns (uint256, uint256) { + // There must be at least two inputs. + uint256 inputs = uint256(OperandV2.unwrap(operand) >> 0x10) & 0x0F; + inputs = inputs > 1 ? inputs : 2; + return (inputs, 1); + } -// /// mul -// /// 18 decimal fixed point multiplication with implied overflow checks from -// /// PRB Math. -// function run(InterpreterState memory, Operand operand, Pointer stackTop) internal pure returns (Pointer) { -// uint256 a; -// uint256 b; -// assembly ("memory-safe") { -// a := mload(stackTop) -// b := mload(add(stackTop, 0x20)) -// stackTop := add(stackTop, 0x40) -// } -// a = UD60x18.unwrap(mul(UD60x18.wrap(a), UD60x18.wrap(b))); + /// mul + function run(InterpreterState memory, OperandV2 operand, Pointer stackTop) internal pure returns (Pointer) { + Float a; + Float b; + assembly ("memory-safe") { + a := mload(stackTop) + b := mload(add(stackTop, 0x20)) + stackTop := add(stackTop, 0x40) + } + a = LibDecimalFloat.mul(a, b); -// { -// uint256 inputs = (Operand.unwrap(operand) >> 0x10) & 0x0F; -// uint256 i = 2; -// while (i < inputs) { -// assembly ("memory-safe") { -// b := mload(stackTop) -// stackTop := add(stackTop, 0x20) -// } -// a = UD60x18.unwrap(mul(UD60x18.wrap(a), UD60x18.wrap(b))); -// unchecked { -// i++; -// } -// } -// } -// assembly ("memory-safe") { -// stackTop := sub(stackTop, 0x20) -// mstore(stackTop, a) -// } -// return stackTop; -// } + { + uint256 inputs = uint256(OperandV2.unwrap(operand) >> 0x10) & 0x0F; + uint256 i = 2; + while (i < inputs) { + assembly ("memory-safe") { + b := mload(stackTop) + stackTop := add(stackTop, 0x20) + } + a = LibDecimalFloat.mul(a, b); + unchecked { + i++; + } + } + } + assembly ("memory-safe") { + stackTop := sub(stackTop, 0x20) + mstore(stackTop, a) + } + return stackTop; + } -// /// Gas intensive reference implementation of multiplication for testing. -// function referenceFn(InterpreterState memory, Operand, uint256[] memory inputs) -// internal -// pure -// returns (uint256[] 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 a = inputs[0]; -// for (uint256 i = 1; i < inputs.length; i++) { -// uint256 b = inputs[i]; -// if (LibWillOverflow.mulDivWillOverflow(a, b, 1e18)) { -// a = uint256(keccak256(abi.encodePacked("overflow sentinel"))); -// break; -// } -// a = OZMath.mulDiv(a, b, 1e18); -// } -// outputs = new uint256[](1); -// outputs[0] = a; -// } -// } -// } + /// Gas intensive reference implementation of multiplication for testing. + function referenceFn(InterpreterState memory, OperandV2, StackItem[] memory inputs) + internal + pure + 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 { + Float a; + uint256 overflows = 0; + (int256 signedCoefficientA, int256 exponentA) = + LibDecimalFloat.unpack(Float.wrap(StackItem.unwrap(inputs[0]))); + if (int32(exponentA) != exponentA) { + overflows++; + } + 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); + } + + outputs[0] = StackItem.wrap(Float.unwrap(a)); + + return outputs; + } + } +} diff --git a/test/src/lib/op/math/LibOpMul.t.sol b/test/src/lib/op/math/LibOpMul.t.sol index 98dbb22c5..56e3f633f 100644 --- a/test/src/lib/op/math/LibOpMul.t.sol +++ b/test/src/lib/op/math/LibOpMul.t.sol @@ -1,160 +1,163 @@ // SPDX-License-Identifier: CAL pragma solidity =0.8.25; -// import {LibPointer} from "rain.solmem/lib/LibPointer.sol"; -// import {LibOpMul} from "src/lib/op/math/LibOpMul.sol"; -// import {Math as OZMath} from "openzeppelin-contracts/contracts/utils/math/Math.sol"; -// import {OpTest, IntegrityCheckState, Operand, InterpreterState} from "test/abstract/OpTest.sol"; -// import {PRBMath_MulDiv18_Overflow} from "prb-math/Common.sol"; -// import {LibWillOverflow} from "rain.math.fixedpoint/lib/LibWillOverflow.sol"; -// import {LibOperand} from "test/lib/operand/LibOperand.sol"; - -// contract LibOpMulTest is OpTest { -// /// Directly test the integrity logic of LibOpMul. This tests the -// /// happy path where the inputs input and calc match. -// function testOpMulIntegrityHappy(IntegrityCheckState memory state, uint8 inputs, uint16 operandData) -// external -// pure -// { -// inputs = uint8(bound(inputs, 2, 0x0F)); -// (uint256 calcInputs, uint256 calcOutputs) = LibOpMul.integrity(state, LibOperand.build(inputs, 1, operandData)); - -// assertEq(calcInputs, inputs); -// assertEq(calcOutputs, 1); -// } - -// /// Directly test the integrity logic of LibOpMul. This tests the -// /// unhappy path where the operand is invalid due to 0 inputs. -// function testOpMulIntegrityUnhappyZeroInputs(IntegrityCheckState memory state) external pure { -// (uint256 calcInputs, uint256 calcOutputs) = LibOpMul.integrity(state, Operand.wrap(0)); -// // Calc inputs will be minimum 2. -// assertEq(calcInputs, 2); -// assertEq(calcOutputs, 1); -// } - -// /// Directly test the integrity logic of LibOpMul. This tests the -// /// unhappy path where the operand is invalid due to 1 inputs. -// function testOpDecimal18MulNPIntegrityUnhappyOneInput(IntegrityCheckState memory state) external pure { -// (uint256 calcInputs, uint256 calcOutputs) = LibOpMul.integrity(state, Operand.wrap(0x010000)); -// // Calc inputs will be minimum 2. -// assertEq(calcInputs, 2); -// assertEq(calcOutputs, 1); -// } - -// /// Directly test the runtime logic of LibOpMul. -// function testOpMulRun(uint256[] memory inputs) public { -// InterpreterState memory state = opTestDefaultInterpreterState(); -// vm.assume(inputs.length >= 2); -// vm.assume(inputs.length <= 0x0F); -// Operand operand = LibOperand.build(uint8(inputs.length), 1, 0); -// // This is kinda shitty because it just duplicates what the reference -// // fn is doing, but because neither PRB nor Open Zeppelin expose a -// // try/catch for overflow, we have to do this. -// uint256 a = inputs[0]; -// for (uint256 i = 1; i < inputs.length; i++) { -// uint256 b = inputs[i]; -// if (LibWillOverflow.mulDivWillOverflow(a, b, 1e18)) { -// vm.expectRevert(abi.encodeWithSelector(PRBMath_MulDiv18_Overflow.selector, a, b)); -// break; -// } -// a = OZMath.mulDiv(a, b, 1e18); -// } -// opReferenceCheck(state, operand, LibOpMul.referenceFn, LibOpMul.integrity, LibOpMul.run, inputs); -// } - -// /// Test the eval of `mul` opcode parsed from a string. -// /// Tests zero inputs. -// function testOpMulEvalZeroInputs() external { -// checkBadInputs("_: mul();", 0, 2, 0); -// } - -// /// Test the eval of `mul` opcode parsed from a string. -// /// Tests one input. -// function testOpMulEvalOneInput() external { -// checkBadInputs("_: mul(5);", 1, 2, 1); -// checkBadInputs("_: mul(0);", 1, 2, 1); -// checkBadInputs("_: mul(1);", 1, 2, 1); -// checkBadInputs("_: mul(max-value());", 1, 2, 1); -// } - -// function testOpMulZeroOutputs() external { -// checkBadOutputs(": mul(0 0);", 2, 1, 0); -// } - -// function testOpMulTwoOutputs() external { -// checkBadOutputs("_ _: mul(0 0);", 2, 1, 2); -// } - -// /// Test the eval of `mul` opcode parsed from a string. -// /// Tests two inputs. -// /// Tests the happy path where we do not overflow. -// function testOpMulEvalTwoInputsHappy() external view { -// checkHappy("_: mul(0 1);", 0, "0 1"); -// checkHappy("_: mul(1 1);", 1e18, "1 1"); -// checkHappy("_: mul(1 2);", 2e18, "1 2"); -// checkHappy("_: mul(2 1);", 2e18, "2 1"); -// checkHappy("_: mul(2 2);", 4e18, "2 2"); -// checkHappy("_: mul(2 0.1);", 2e17, "2 0.1"); -// checkHappy("_: mul(1 0.1);", 1e17, "1 0.1"); -// checkHappy("_: mul(1 0.01);", 1e16, "1 0.01"); -// checkHappy("_: mul(0.001 0.001);", 1e12, "0.001 0.001"); -// checkHappy("_: mul(10 10);", 1e20, "10 10"); -// // Test an intermediate overflow. -// checkHappy("_: mul(1 max-value());", type(uint256).max, "1 max-value()"); -// } - -// /// Test the eval of `mul` opcode parsed from a string. -// /// Tests two inputs. -// /// Tests the unhappy path where the final result overflows. -// function testOpMulEvalTwoInputsUnhappyOverflow() external { -// checkUnhappy( -// "_: mul(max-value() 10);", -// abi.encodeWithSelector(PRBMath_MulDiv18_Overflow.selector, type(uint256).max, 1e19) -// ); -// checkUnhappy("_: mul(1e52 1e12);", abi.encodeWithSelector(PRBMath_MulDiv18_Overflow.selector, 1e70, 1e30)); -// } - -// /// Test the eval of `mul` opcode parsed from a string. -// /// Tests three inputs. -// /// Tests the happy path where we do not divide by zero or overflow. -// function testOpMulEvalThreeInputsHappy() external view { -// checkHappy("_: mul(0 0 0);", 0, "0 0 0"); -// checkHappy("_: mul(1 0 0);", 0, "1 0 0"); -// checkHappy("_: mul(1 1 0);", 0, "1 1 0"); -// checkHappy("_: mul(1 1 1);", 1e18, "1 1 1"); -// checkHappy("_: mul(1 1 2);", 2e18, "1 1 2"); -// checkHappy("_: mul(1 2 1);", 2e18, "1 2 1"); -// checkHappy("_: mul(2 1 1);", 2e18, "2 1 1"); -// checkHappy("_: mul(2 2 2);", 8e18, "2 2 2"); -// checkHappy("_: mul(2 0.1 1);", 2e17, "2 0.1 1"); -// checkHappy("_: mul(1 0.1 1);", 1e17, "1 0.1 1"); -// checkHappy("_: mul(1 0.01 1);", 1e16, "1 0.01 1"); -// checkHappy("_: mul(0.001 0.001 0.001);", 1e9, "0.001 0.001 0.001"); -// checkHappy("_: mul(10 10 10);", 1e21, "10 10 10"); -// // Test an intermediate overflow. -// checkHappy("_: mul(1 max-value() 1);", type(uint256).max, "1 max-value() 1"); -// } - -// /// Test the eval of `mul` opcode parsed from a string. -// /// Tests three inputs. -// /// Tests the unhappy path where the final result overflows. -// function testOpMulEvalThreeInputsUnhappyOverflow() external { -// checkUnhappy( -// "_: mul(max-value() 1 10);", -// abi.encodeWithSelector(PRBMath_MulDiv18_Overflow.selector, type(uint256).max, 1e19) -// ); -// checkUnhappy("_: mul(1e52 1 1e8);", abi.encodeWithSelector(PRBMath_MulDiv18_Overflow.selector, 1e70, 1e26)); -// checkUnhappy("_: mul(1e52 1e8 1);", abi.encodeWithSelector(PRBMath_MulDiv18_Overflow.selector, 1e70, 1e26)); -// } - -// /// Test the eval of `mul` opcode parsed from a string. -// /// Tests that operands are disallowed. -// function testOpMulEvalOperandsDisallowed() external { -// checkDisallowedOperand("_: mul<0>(1 1 1);"); -// checkDisallowedOperand("_: mul<1>(1 1 1);"); -// checkDisallowedOperand("_: mul<2>(1 1 1);"); -// checkDisallowedOperand("_: mul<0 0>(1 1 1);"); -// checkDisallowedOperand("_: mul<0 1>(1 1 1);"); -// checkDisallowedOperand("_: mul<1 0>(1 1 1);"); -// } -// } +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"; +import {StackItem} from "rain.interpreter.interface/interface/unstable/IInterpreterV4.sol"; +import {Float, LibDecimalFloat} from "rain.math.float/lib/LibDecimalFloat.sol"; +import {LibDecimalFloatImplementation} from "rain.math.float/lib/implementation/LibDecimalFloatImplementation.sol"; +import {ExponentOverflow, CoefficientOverflow} from "rain.math.float/error/ErrDecimalFloat.sol"; + +contract LibOpMulTest is OpTest { + /// Directly test the integrity logic of LibOpMul. This tests the + /// happy path where the inputs input and calc match. + function testOpMulIntegrityHappy(IntegrityCheckState memory state, uint8 inputs, uint16 operandData) + external + pure + { + inputs = uint8(bound(inputs, 2, 0x0F)); + (uint256 calcInputs, uint256 calcOutputs) = LibOpMul.integrity(state, LibOperand.build(inputs, 1, operandData)); + + assertEq(calcInputs, inputs); + assertEq(calcOutputs, 1); + } + + /// Directly test the integrity logic of LibOpMul. This tests the + /// unhappy path where the operand is invalid due to 0 inputs. + function testOpMulIntegrityUnhappyZeroInputs(IntegrityCheckState memory state) external pure { + (uint256 calcInputs, uint256 calcOutputs) = LibOpMul.integrity(state, OperandV2.wrap(0)); + // Calc inputs will be minimum 2. + assertEq(calcInputs, 2); + assertEq(calcOutputs, 1); + } + + /// Directly test the integrity logic of LibOpMul. This tests the + /// unhappy path where the operand is invalid due to 1 inputs. + function testOpDecimal18MulNPIntegrityUnhappyOneInput(IntegrityCheckState memory state) external pure { + (uint256 calcInputs, uint256 calcOutputs) = + LibOpMul.integrity(state, OperandV2.wrap(bytes32(uint256(0x010000)))); + // Calc inputs will be minimum 2. + assertEq(calcInputs, 2); + assertEq(calcOutputs, 1); + } + + function _testOpMulRun(OperandV2 operand, StackItem[] memory inputs) external view { + opReferenceCheck( + opTestDefaultInterpreterState(), operand, LibOpMul.referenceFn, LibOpMul.integrity, LibOpMul.run, inputs + ); + } + + /// Directly test the runtime logic of LibOpMul. + function testOpMulRun(StackItem[] memory inputs) public view { + vm.assume(inputs.length >= 2); + vm.assume(inputs.length <= 0x0F); + OperandV2 operand = LibOperand.build(uint8(inputs.length), 1, 0); + + try this._testOpMulRun(operand, inputs) {} + catch (bytes memory err) { + assert(bytes4(err) == CoefficientOverflow.selector || bytes4(err) == ExponentOverflow.selector); + } + } + + /// Test the eval of `mul` opcode parsed from a string. + /// Tests zero inputs. + function testOpMulEvalZeroInputs() external { + checkBadInputs("_: mul();", 0, 2, 0); + } + + /// Test the eval of `mul` opcode parsed from a string. + /// Tests one input. + function testOpMulEvalOneInput() external { + checkBadInputs("_: mul(5);", 1, 2, 1); + checkBadInputs("_: mul(0);", 1, 2, 1); + checkBadInputs("_: mul(1);", 1, 2, 1); + checkBadInputs("_: mul(max-value());", 1, 2, 1); + } + + function testOpMulZeroOutputs() external { + checkBadOutputs(": mul(0 0);", 2, 1, 0); + } + + function testOpMulTwoOutputs() external { + checkBadOutputs("_ _: mul(0 0);", 2, 1, 2); + } + + /// Test the eval of `mul` opcode parsed from a string. + /// Tests two inputs. + /// Tests the happy path where we do not overflow. + function testOpMulEvalTwoInputsHappy() external view { + checkHappy("_: mul(0 1);", Float.unwrap(LibDecimalFloat.packLossless(0, 0)), "0 1"); + checkHappy("_: mul(1 1);", Float.unwrap(LibDecimalFloat.packLossless(1, 0)), "1 1"); + checkHappy("_: mul(1 2);", Float.unwrap(LibDecimalFloat.packLossless(2, 0)), "1 2"); + checkHappy("_: mul(2 1);", Float.unwrap(LibDecimalFloat.packLossless(2, 0)), "2 1"); + checkHappy("_: mul(2 2);", Float.unwrap(LibDecimalFloat.packLossless(4, 0)), "2 2"); + checkHappy("_: mul(2 0.1);", Float.unwrap(LibDecimalFloat.packLossless(2, -1)), "2 0.1"); + checkHappy("_: mul(1 0.1);", Float.unwrap(LibDecimalFloat.packLossless(1, -1)), "1 0.1"); + checkHappy("_: mul(1 0.01);", Float.unwrap(LibDecimalFloat.packLossless(1, -2)), "1 0.01"); + checkHappy("_: mul(0.001 0.001);", Float.unwrap(LibDecimalFloat.packLossless(1, -6)), "0.001 0.001"); + checkHappy("_: mul(10 10);", Float.unwrap(LibDecimalFloat.packLossless(100, 0)), "10 10"); + // Test an intermediate overflow. + checkHappy( + "_: mul(1 max-value());", + Float.unwrap(LibDecimalFloat.packLossless(type(int224).max, type(int32).max)), + "1 max-value()" + ); + } + + /// Test the eval of `mul` opcode parsed from a string. + /// Tests two inputs. + /// Tests the unhappy path where the final result overflows. + function testOpMulEvalTwoInputsUnhappyOverflow() external { + checkUnhappyOverflow( + "_: mul(max-value() 10);", 13479973333575319897333507543509815336818572211270286240551805124607, 2147483648 + ); + } + + /// Test the eval of `mul` opcode parsed from a string. + /// Tests three inputs. + /// Tests the happy path where we do not divide by zero or overflow. + function testOpMulEvalThreeInputsHappy() external view { + checkHappy("_: mul(0 0 0);", Float.unwrap(LibDecimalFloat.packLossless(0, 0)), "0 0 0"); + checkHappy("_: mul(1 0 0);", Float.unwrap(LibDecimalFloat.packLossless(0, 0)), "1 0 0"); + checkHappy("_: mul(1 1 0);", Float.unwrap(LibDecimalFloat.packLossless(0, 0)), "1 1 0"); + checkHappy("_: mul(1 1 1);", Float.unwrap(LibDecimalFloat.packLossless(1, 0)), "1 1 1"); + checkHappy("_: mul(1 1 2);", Float.unwrap(LibDecimalFloat.packLossless(2, 0)), "1 1 2"); + checkHappy("_: mul(1 2 1);", Float.unwrap(LibDecimalFloat.packLossless(2, 0)), "1 2 1"); + checkHappy("_: mul(2 1 1);", Float.unwrap(LibDecimalFloat.packLossless(2, 0)), "2 1 1"); + checkHappy("_: mul(2 2 2);", Float.unwrap(LibDecimalFloat.packLossless(8, 0)), "2 2 2"); + checkHappy("_: mul(2 0.1 1);", Float.unwrap(LibDecimalFloat.packLossless(2, -1)), "2 0.1 1"); + checkHappy("_: mul(1 0.1 1);", Float.unwrap(LibDecimalFloat.packLossless(1, -1)), "1 0.1 1"); + checkHappy("_: mul(1 0.01 1);", Float.unwrap(LibDecimalFloat.packLossless(1, -2)), "1 0.01 1"); + checkHappy("_: mul(0.001 0.001 0.001);", Float.unwrap(LibDecimalFloat.packLossless(1, -9)), "0.001 0.001 0.001"); + checkHappy("_: mul(10 10 10);", Float.unwrap(LibDecimalFloat.packLossless(1000, 0)), "10 10 10"); + // Test an intermediate overflow. + checkHappy( + "_: mul(1 max-value() 1);", + Float.unwrap(LibDecimalFloat.packLossless(type(int224).max, type(int32).max)), + "1 max-value() 1" + ); + } + + /// Test the eval of `mul` opcode parsed from a string. + /// Tests three inputs. + /// Tests the unhappy path where the final result overflows. + function testOpMulEvalThreeInputsUnhappyOverflow() external { + checkUnhappyOverflow( + "_: mul(max-value() 1 10);", + 13479973333575319897333507543509815336818572211270286240551805124607, + 2147483648 + ); + } + + /// Test the eval of `mul` opcode parsed from a string. + /// Tests that operands are disallowed. + function testOpMulEvalOperandsDisallowed() external { + checkDisallowedOperand("_: mul<0>(1 1 1);"); + checkDisallowedOperand("_: mul<1>(1 1 1);"); + checkDisallowedOperand("_: mul<2>(1 1 1);"); + checkDisallowedOperand("_: mul<0 0>(1 1 1);"); + checkDisallowedOperand("_: mul<0 1>(1 1 1);"); + checkDisallowedOperand("_: mul<1 0>(1 1 1);"); + } +}