From 05afb1541f7293d02f66c204d0ae5e9b6eaabbad Mon Sep 17 00:00:00 2001 From: thedavidmeister Date: Mon, 21 Jul 2025 15:19:02 +0400 Subject: [PATCH 1/2] inv op --- src/generated/Rainterpreter.pointers.sol | 4 +- ...interpreterExpressionDeployer.pointers.sol | 6 +- .../RainterpreterParser.pointers.sol | 8 +- src/lib/op/LibAllStandardOps.sol | 14 +-- src/lib/op/math/LibOpInv.sol | 73 +++++------ test/src/lib/op/math/LibOpInv.t.sol | 118 +++++++++--------- 6 files changed, 115 insertions(+), 108 deletions(-) diff --git a/src/generated/Rainterpreter.pointers.sol b/src/generated/Rainterpreter.pointers.sol index 4aa034c03..64b424b15 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(0xef5d5c21b4a25cc7b3dfdd4402f2d8a35099c9d415c4b6a4f0f76ebfed67e0b0); +bytes32 constant BYTECODE_HASH = bytes32(0x1f82584ad80b630eb25769669c965fc8efff9d8f9cd3dcc6f9fc0ac842e81474); /// @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"07bd07ef0813099f0a680a7a0a8c0aa50ac90afd0b0e0b1f0bc10be00c9e0d4e0dd20f1410470c9e114011f21294130c131d132e132e133f1386140814211435144e1467149214ab14c414d71539158715d51623167116bf170d175b178c17da18281876195d"; + hex"07c607f8081c09a80a710a830a950aae0ad20b060b170b280bca0be90ca70d570ddb0f1d10500ca7114911fb129d13151326133713371348138f1411142a143e14571470149b14b414cd14e01542159015de162c167a16c817161724177217a317f1183f188d1974"; diff --git a/src/generated/RainterpreterExpressionDeployer.pointers.sol b/src/generated/RainterpreterExpressionDeployer.pointers.sol index 61dca72e7..adefd5f6f 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(0xce3a44f2ed702547f2da7cc4e7adfc9d9dd5a2bfbfd806b228db3cfcdd1da125); +bytes32 constant BYTECODE_HASH = bytes32(0xb76a360580b20009cee2999206d01cce8a7be9c16321becaa7a8897a4e8fc30c); /// @dev The hash of the meta that describes the contract. -bytes32 constant DESCRIBED_BY_META_HASH = bytes32(0xf8bdd3a04e26f925f45376a4f3cfe9455904c1abbbfbf60f84d982803beea817); +bytes32 constant DESCRIBED_BY_META_HASH = bytes32(0xde93b39376f1e94f38fde797b17027f86a9a59b2540f89ec15cc09759f171eb8); /// @dev The function pointers for the integrity check fns. bytes constant INTEGRITY_FUNCTION_POINTERS = - hex"0e500ece0f3210ac10b610b610c010c910e4118a118a11e6125e126b10b610c0126b10b610c010b610b610b610c010ac10ac10ac10ac1275129a10b610b610b610b610c010b610b610ac12a412a412a412a412a412a412be12a410ac12a412be12be10c0129a"; + hex"0e580ed60f3a10b410be10be10c810d110ec1192119211ee1266127310be10c8127310be10c810be10be10be10c810b410b410b410b4127d12a210be10be10be10be10c810be10be10b412ac12ac12ac12ac12ac12ac12c610c812ac10b412ac12c612c610c812a2"; diff --git a/src/generated/RainterpreterParser.pointers.sol b/src/generated/RainterpreterParser.pointers.sol index a0c3704c4..cb9b6f699 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(0x802ffd0c76897b508453402052a3a1840b73d060ed67e227d1c874d3cff34ec4); +bytes32 constant BYTECODE_HASH = bytes32(0x680382e10b1fa1d3a16b62c994f80ced2eff3fbd3cb51ed90817bf25bb904f08); /// @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(0x802ffd0c76897b508453402052a3a1840b73d /// 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"0178ac80600029b010810908a00a3448c001280030060100180000200cc2200408a00c95d8383148145c3234b3f32dd587e204afc8f01e95a26007b9a51f0d5e6f2803ce312215fae0a1271233de118cfd53051c784d25a55ade1681ac9622ac6dfc2ea4e5e91f5fc32a0e92b2401dbbd837100ef76b00cd8814205902e62a9714ec13313592290d473b21f880501427634f0f23b4970ac0e53d0b895f991ac908be0983724f12c080bc2f9d7b7d28757aea266f62e02b5c0ab5086d2a7b2390a6b319bcb24402af7d4506414b8117b04fb31cb2182f18ef532924c301e31b9314ad30fa5d8001336a592cab2461"; + hex"0178ac80600029b010910908a00a3448c001280030060100180000200cc2200408a00c95d8383248145c3334b3f32ed587e204afc8f01e95a26007b9a51f0d5e6f2803ce312215fae0a1271233de118cfd53051c784d25a55ade1681ac9622ac6dfc2fa4e5e91f5fc32a0e92b2401dbbd837100ef76b00cd8814205902e62a9714ec13313592290d473b21f880501427634f0f23b4970ac0e53d0b895f991ac908be0983724f12c080bc309d7b7d28757aea2c3a7b86266f62e02b5c0ab5086d2a7b2390a6b319bcb24402af7d4506414b8117b04fb31cb2182f18ef532924c301e31b9314ad31fa5d8001336a592dab2461"; /// @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"1a331a331a331b081c1f1c1f1c1f1b081b081a331a331a331c1f1c1f1c1f1c1f1c1f1c1f1c1f1c1f1c1f1c1f1c1f1c1f1c1f1c1f1c1f1c1f1c1f1c1f1c1f1c1f1c1f1c1f1c1f1c1f1c1f1c1f1c1f1c1f1c1f1c1f1c1f1c1f1c1f1c1f1c1f1c1f1a331c1f1c1f"; + hex"1a3b1a3b1a3b1b101c271c271c271b101b101a3b1a3b1a3b1c271c271c271c271c271c271c271c271c271c271c271c271c271c271c271c271c271c271c271c271c271c271c271c271c271c271c271c271c271c271c271c271c271c271c271c271c271a3b1c271c27"; /// @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"157b17ad17f0188e"; +bytes constant LITERAL_PARSER_FUNCTION_POINTERS = hex"158317b517f81896"; diff --git a/src/lib/op/LibAllStandardOps.sol b/src/lib/op/LibAllStandardOps.sol index 60a592616..f31d63565 100644 --- a/src/lib/op/LibAllStandardOps.sol +++ b/src/lib/op/LibAllStandardOps.sol @@ -81,7 +81,7 @@ import {LibOpDiv} from "./math/LibOpDiv.sol"; // import {LibOpFrac} from "./math/LibOpFrac.sol"; // import {LibOpGm} from "./math/LibOpGm.sol"; // import {LibOpHeadroom} from "./math/LibOpHeadroom.sol"; -// import {LibOpInv} from "./math/LibOpInv.sol"; +import {LibOpInv} from "./math/LibOpInv.sol"; // import {LibOpLn} from "./math/LibOpLn.sol"; // import {LibOpLog10} from "./math/LibOpLog10.sol"; import {LibOpMax} from "./math/LibOpMax.sol"; @@ -108,7 +108,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 = 51; +uint256 constant ALL_STANDARD_OPS_LENGTH = 52; /// @title LibAllStandardOps /// @notice Every opcode available from the core repository laid out as a single @@ -277,7 +277,7 @@ library LibAllStandardOps { // "headroom", // "Headroom of a number. I.e. the distance to the next whole number (1 - frac(x)). The headroom at any whole number is 1 (not 0)." // ), - // AuthoringMetaV2("inv", "The inverse (1 / x) of a number. Errors if the number is zero."), + AuthoringMetaV2("inv", "The inverse (1 / x) of a number. Errors if the number is zero."), // 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."), @@ -492,8 +492,8 @@ library LibAllStandardOps { // LibParseOperand.handleOperandDisallowed, // // headroom // LibParseOperand.handleOperandDisallowed, - // // inv - // LibParseOperand.handleOperandDisallowed, + // inv + LibParseOperand.handleOperandDisallowed, // // ln // LibParseOperand.handleOperandDisallowed, // // log2 @@ -623,7 +623,7 @@ library LibAllStandardOps { // LibOpFrac.integrity, // LibOpGm.integrity, // LibOpHeadroom.integrity, - // LibOpInv.integrity, + LibOpInv.integrity, // LibOpLn.integrity, // LibOpLog2.integrity, // LibOpLog10.integrity, @@ -738,7 +738,7 @@ library LibAllStandardOps { // LibOpFrac.run, // LibOpGm.run, // LibOpHeadroom.run, - // LibOpInv.run, + LibOpInv.run, // LibOpLn.run, // LibOpLog2.run, // LibOpLog10.run, diff --git a/src/lib/op/math/LibOpInv.sol b/src/lib/op/math/LibOpInv.sol index 50702a71c..ff21b799d 100644 --- a/src/lib/op/math/LibOpInv.sol +++ b/src/lib/op/math/LibOpInv.sol @@ -1,43 +1,44 @@ // SPDX-License-Identifier: CAL pragma solidity ^0.8.18; -// import {UD60x18, inv} 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 {Float, LibDecimalFloat} from "rain.math.float/lib/LibDecimalFloat.sol"; +import {StackItem} from "rain.interpreter.interface/interface/unstable/IInterpreterV4.sol"; -// /// @title LibOpInv -// /// @notice Opcode for the inverse 1 / x of an decimal 18 fixed point number. -// library LibOpInv { -// function integrity(IntegrityCheckState memory, Operand) internal pure returns (uint256, uint256) { -// // There must be one inputs and one output. -// return (1, 1); -// } +/// @title LibOpInv +/// @notice Opcode for the inverse 1 / x of a floating point number. +library LibOpInv { + function integrity(IntegrityCheckState memory, OperandV2) internal pure returns (uint256, uint256) { + // There must be one inputs and one output. + return (1, 1); + } -// /// inv -// /// 18 decimal fixed point inverse 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(inv(UD60x18.wrap(a))); + /// inv + /// floating point inverse of a number. + function run(InterpreterState memory, OperandV2, Pointer stackTop) internal pure returns (Pointer) { + Float a; + assembly ("memory-safe") { + a := mload(stackTop) + } + a = LibDecimalFloat.inv(a); -// assembly ("memory-safe") { -// mstore(stackTop, a) -// } -// return stackTop; -// } + assembly ("memory-safe") { + mstore(stackTop, a) + } + return stackTop; + } -// /// Gas intensive reference implementation of inv 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(inv(UD60x18.wrap(inputs[0]))); -// return outputs; -// } -// } + /// Gas intensive reference implementation of inv for testing. + function referenceFn(InterpreterState memory, OperandV2, StackItem[] memory inputs) + internal + pure + returns (StackItem[] memory) + { + StackItem[] memory outputs = new StackItem[](1); + outputs[0] = StackItem.wrap(Float.unwrap((LibDecimalFloat.inv(Float.wrap(StackItem.unwrap(inputs[0])))))); + return outputs; + } +} diff --git a/test/src/lib/op/math/LibOpInv.t.sol b/test/src/lib/op/math/LibOpInv.t.sol index 997e7da2f..64ef7f76d 100644 --- a/test/src/lib/op/math/LibOpInv.t.sol +++ b/test/src/lib/op/math/LibOpInv.t.sol @@ -1,59 +1,65 @@ // SPDX-License-Identifier: CAL pragma solidity =0.8.25; -// import {OpTest, IntegrityCheckState, Operand, InterpreterState, UnexpectedOperand} from "test/abstract/OpTest.sol"; -// import {LibOpInv} from "src/lib/op/math/LibOpInv.sol"; -// import {LibOperand} from "test/lib/operand/LibOperand.sol"; - -// contract LibOpInvTest is OpTest { -// /// Directly test the integrity logic of LibOpInv. -// /// Inputs are always 1, outputs are always 1. -// function testOpInvIntegrity(IntegrityCheckState memory state, Operand operand) external pure { -// (uint256 calcInputs, uint256 calcOutputs) = LibOpInv.integrity(state, operand); -// assertEq(calcInputs, 1); -// assertEq(calcOutputs, 1); -// } - -// /// Directly test the runtime logic of LibOpInv. -// function testOpInvRun(uint256 a, uint16 operandData) public view { -// // 0 is division by 0. -// a = bound(a, 1, 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, LibOpInv.referenceFn, LibOpInv.integrity, LibOpInv.run, inputs); -// } - -// /// Test the eval of `inv`. -// function testOpInvEval() external view { -// checkHappy("_: inv(1);", 1e18, "1"); -// checkHappy("_: inv(0.5);", 2e18, "0.5"); -// checkHappy("_: inv(2);", 0.5e18, "2"); -// checkHappy("_: inv(3);", 333333333333333333, "3"); -// } - -// /// Test the eval of `inv` for bad inputs. -// function testOpInvZeroInputs() external { -// checkBadInputs("_: inv();", 0, 1, 0); -// } - -// function testOpInvTwoInputs() external { -// checkBadInputs("_: inv(1 1);", 2, 1, 2); -// } - -// function testOpInvZeroOutputs() external { -// checkBadOutputs(": inv(1);", 1, 1, 0); -// } - -// function testOpInvTwoOutputs() external { -// checkBadOutputs("_ _: inv(1);", 1, 1, 2); -// } - -// /// Test that operand is disallowed. -// function testOpExpEvalOperandDisallowed() external { -// checkUnhappyParse("_: inv<0>(1);", abi.encodeWithSelector(UnexpectedOperand.selector)); -// } -// } +import {OpTest, IntegrityCheckState, OperandV2, InterpreterState, UnexpectedOperand} from "test/abstract/OpTest.sol"; +import {LibDecimalFloat, Float} from "rain.math.float/lib/LibDecimalFloat.sol"; +import {LibOpInv} from "src/lib/op/math/LibOpInv.sol"; +import {LibOperand} from "test/lib/operand/LibOperand.sol"; +import {StackItem} from "rain.interpreter.interface/interface/unstable/IInterpreterV4.sol"; + +contract LibOpInvTest is OpTest { + /// Directly test the integrity logic of LibOpInv. + /// Inputs are always 1, outputs are always 1. + function testOpInvIntegrity(IntegrityCheckState memory state, OperandV2 operand) external pure { + (uint256 calcInputs, uint256 calcOutputs) = LibOpInv.integrity(state, operand); + assertEq(calcInputs, 1); + assertEq(calcOutputs, 1); + } + + /// Directly test the runtime logic of LibOpInv. + function testOpInvRun(Float a, uint16 operandData) public view { + // 0 is division by 0. + vm.assume(!LibDecimalFloat.isZero(a)); + InterpreterState memory state = opTestDefaultInterpreterState(); + + OperandV2 operand = LibOperand.build(1, 1, operandData); + StackItem[] memory inputs = new StackItem[](1); + inputs[0] = StackItem.wrap(Float.unwrap(a)); + + opReferenceCheck(state, operand, LibOpInv.referenceFn, LibOpInv.integrity, LibOpInv.run, inputs); + } + + /// Test the eval of `inv`. + function testOpInvEval() external view { + checkHappy("_: inv(1);", Float.unwrap(LibDecimalFloat.packLossless(1e38, -38)), "1"); + checkHappy("_: inv(0.5);", Float.unwrap(LibDecimalFloat.packLossless(2e37, -37)), "0.5"); + checkHappy("_: inv(2);", Float.unwrap(LibDecimalFloat.packLossless(0.5e38, -38)), "2"); + checkHappy( + "_: inv(3);", + Float.unwrap(LibDecimalFloat.packLossless(0.33333333333333333333333333333333333333e38, -38)), + "3" + ); + } + + /// Test the eval of `inv` for bad inputs. + function testOpInvZeroInputs() external { + checkBadInputs("_: inv();", 0, 1, 0); + } + + function testOpInvTwoInputs() external { + checkBadInputs("_: inv(1 1);", 2, 1, 2); + } + + function testOpInvZeroOutputs() external { + checkBadOutputs(": inv(1);", 1, 1, 0); + } + + function testOpInvTwoOutputs() external { + checkBadOutputs("_ _: inv(1);", 1, 1, 2); + } + + /// Test that operand is disallowed. + function testOpExpEvalOperandDisallowed() external { + checkUnhappyParse("_: inv<0>(1);", abi.encodeWithSelector(UnexpectedOperand.selector)); + } +} From 541ebbf3a05eef035de9b3920916004131d172f5 Mon Sep 17 00:00:00 2001 From: thedavidmeister Date: Mon, 21 Jul 2025 16:18:10 +0400 Subject: [PATCH 2/2] fix tests --- test/src/lib/op/math/LibOpInv.t.sol | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/test/src/lib/op/math/LibOpInv.t.sol b/test/src/lib/op/math/LibOpInv.t.sol index 64ef7f76d..21fdc4eff 100644 --- a/test/src/lib/op/math/LibOpInv.t.sol +++ b/test/src/lib/op/math/LibOpInv.t.sol @@ -17,7 +17,9 @@ contract LibOpInvTest is OpTest { } /// Directly test the runtime logic of LibOpInv. - function testOpInvRun(Float a, uint16 operandData) public view { + function testOpInvRun(int224 signedCoefficient, int32 exponent, uint16 operandData) public view { + exponent = int32(bound(exponent, type(int16).min, type(int16).max)); + Float a = LibDecimalFloat.packLossless(signedCoefficient, exponent); // 0 is division by 0. vm.assume(!LibDecimalFloat.isZero(a)); InterpreterState memory state = opTestDefaultInterpreterState();