From 76753c390fa13b5724c2d23161ac64bad0a646e2 Mon Sep 17 00:00:00 2001 From: baku-ccron Date: Mon, 7 Sep 2026 14:01:43 +0000 Subject: [PATCH 1/3] Add the pi constant word Mirrors e (LibOpE): LibOpPi with integrity (0 in, 1 out), run pushing the constant, and referenceFn; registered in LibAllStandardOps between mul and power (72 -> 73 ops). The coefficient is pi rounded to nearest at 66 places, the same packing as FLOAT_E. #535 rebased onto the split library; the interpreter bytecode consequence is rainlang.deploy's next bump and release. Closes #225. Co-Authored-By: Claude Fable 5.1 Claude-Session: https://claude.ai/code/session_01QyCCzi9WZPhuXcU1hwr2bq --- src/lib/op/LibAllStandardOps.sol | 8 ++- src/lib/op/math/LibOpPi.sol | 49 +++++++++++++++ test/src/lib/op/LibAllStandardOps.t.sol | 28 +++++---- test/src/lib/op/math/LibOpPi.t.sol | 82 +++++++++++++++++++++++++ 4 files changed, 153 insertions(+), 14 deletions(-) create mode 100644 src/lib/op/math/LibOpPi.sol create mode 100644 test/src/lib/op/math/LibOpPi.t.sol diff --git a/src/lib/op/LibAllStandardOps.sol b/src/lib/op/LibAllStandardOps.sol index 49cbeee54..6e11f8b27 100644 --- a/src/lib/op/LibAllStandardOps.sol +++ b/src/lib/op/LibAllStandardOps.sol @@ -78,6 +78,7 @@ import {LibOpMin} from "./math/LibOpMin.sol"; import {LibOpMinNegativeValue} from "./math/LibOpMinNegativeValue.sol"; import {LibOpMinPositiveValue} from "./math/LibOpMinPositiveValue.sol"; import {LibOpMul} from "./math/LibOpMul.sol"; +import {LibOpPi} from "./math/LibOpPi.sol"; import {LibOpPower} from "./math/LibOpPower.sol"; import {LibOpSqrt} from "./math/LibOpSqrt.sol"; import {LibOpSub} from "./math/LibOpSub.sol"; @@ -102,7 +103,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 = 72; +uint256 constant ALL_STANDARD_OPS_LENGTH = 73; /// @title LibAllStandardOps /// @notice Every opcode available from the core repository laid out as a single @@ -287,6 +288,7 @@ library LibAllStandardOps { "The minimum positive representable float value. This is the smallest number that can be represented that is still greater than zero." ), AuthoringMetaV2("mul", "Multiplies all numbers together."), + AuthoringMetaV2("pi", "The mathematical constant pi."), AuthoringMetaV2("power", "Raises the first number to the power of the second number."), AuthoringMetaV2("sqrt", "Calculates the square root of the input. Errors if the input is negative."), AuthoringMetaV2("sub", "Subtracts all numbers from the first number."), @@ -502,6 +504,8 @@ library LibAllStandardOps { LibParseOperand.handleOperandDisallowed, // mul LibParseOperand.handleOperandDisallowed, + // pi + LibParseOperand.handleOperandDisallowed, // power LibParseOperand.handleOperandDisallowed, // sqrt @@ -620,6 +624,7 @@ library LibAllStandardOps { LibOpMinNegativeValue.integrity, LibOpMinPositiveValue.integrity, LibOpMul.integrity, + LibOpPi.integrity, LibOpPower.integrity, LibOpSqrt.integrity, LibOpSub.integrity, @@ -724,6 +729,7 @@ library LibAllStandardOps { LibOpMinNegativeValue.run, LibOpMinPositiveValue.run, LibOpMul.run, + LibOpPi.run, LibOpPower.run, LibOpSqrt.run, LibOpSub.run, diff --git a/src/lib/op/math/LibOpPi.sol b/src/lib/op/math/LibOpPi.sol new file mode 100644 index 000000000..235b840fb --- /dev/null +++ b/src/lib/op/math/LibOpPi.sol @@ -0,0 +1,49 @@ +// SPDX-License-Identifier: LicenseRef-DCL-1.0 +// SPDX-FileCopyrightText: Copyright (c) 2020 Rain Open Source Software Ltd +pragma solidity ^0.8.25; + +import {Pointer} from "rain-solmem-0.1.28/src/lib/LibPointer.sol"; +import {OperandV2, StackItem} from "rainlang-interface-0.2.8/src/interface/IInterpreterV4.sol"; +import {InterpreterState} from "../../state/LibInterpreterState.sol"; +import {IntegrityCheckState} from "../../integrity/LibIntegrityCheck.sol"; +import {Float} from "rain-math-float-0.1.1/src/lib/LibDecimalFloat.sol"; + +/// @dev The mathematical constant pi as a `Float`. +/// 3.141592653589793238462643383279502884197169399375105820974944592308e66, -66 +Float constant FLOAT_PI = + Float.wrap(bytes32(uint256(0xffffffbe1dd4c9e873614f593bba9c6007d9a7ac8d03a4b6c700a65cb537a1b4))); + +/// @title LibOpPi +/// @notice Stacks the mathematical constant pi. +library LibOpPi { + /// @notice `pi` integrity check. Requires 0 inputs and produces 1 output. + /// @return The number of inputs. + /// @return The number of outputs. + function integrity(IntegrityCheckState memory, OperandV2) internal pure returns (uint256, uint256) { + return (0, 1); + } + + /// @notice `pi` opcode. Pushes the mathematical constant pi onto the stack. + /// @param stackTop Pointer to the top of the stack. + /// @return The new stack top pointer after execution. + function run(InterpreterState memory, OperandV2, Pointer stackTop) internal pure returns (Pointer) { + Float pi = FLOAT_PI; + assembly ("memory-safe") { + stackTop := sub(stackTop, 0x20) + mstore(stackTop, pi) + } + return stackTop; + } + + /// @notice Reference implementation of `pi` for testing. + /// @return The output values to push onto the stack. + function referenceFn(InterpreterState memory, OperandV2, StackItem[] memory) + internal + pure + returns (StackItem[] memory) + { + StackItem[] memory outputs = new StackItem[](1); + outputs[0] = StackItem.wrap(Float.unwrap(FLOAT_PI)); + return outputs; + } +} diff --git a/test/src/lib/op/LibAllStandardOps.t.sol b/test/src/lib/op/LibAllStandardOps.t.sol index 7eb9782bc..c9cdd0da5 100644 --- a/test/src/lib/op/LibAllStandardOps.t.sol +++ b/test/src/lib/op/LibAllStandardOps.t.sol @@ -201,33 +201,35 @@ contract LibAllStandardOpsTest is Test { //forge-lint: disable-next-line(unsafe-typecast) assertEq(words[58].word, bytes32("mul")); //forge-lint: disable-next-line(unsafe-typecast) - assertEq(words[59].word, bytes32("power")); + assertEq(words[59].word, bytes32("pi")); //forge-lint: disable-next-line(unsafe-typecast) - assertEq(words[60].word, bytes32("sqrt")); + assertEq(words[60].word, bytes32("power")); //forge-lint: disable-next-line(unsafe-typecast) - assertEq(words[61].word, bytes32("sub")); + assertEq(words[61].word, bytes32("sqrt")); + //forge-lint: disable-next-line(unsafe-typecast) + assertEq(words[62].word, bytes32("sub")); // math/growth/ //forge-lint: disable-next-line(unsafe-typecast) - assertEq(words[62].word, bytes32("exponential-growth")); + assertEq(words[63].word, bytes32("exponential-growth")); //forge-lint: disable-next-line(unsafe-typecast) - assertEq(words[63].word, bytes32("linear-growth")); + assertEq(words[64].word, bytes32("linear-growth")); // math/uint256/ //forge-lint: disable-next-line(unsafe-typecast) - assertEq(words[64].word, bytes32("uint256-add")); + assertEq(words[65].word, bytes32("uint256-add")); //forge-lint: disable-next-line(unsafe-typecast) - assertEq(words[65].word, bytes32("uint256-div")); + assertEq(words[66].word, bytes32("uint256-div")); //forge-lint: disable-next-line(unsafe-typecast) - assertEq(words[66].word, bytes32("uint256-max-value")); + assertEq(words[67].word, bytes32("uint256-max-value")); //forge-lint: disable-next-line(unsafe-typecast) - assertEq(words[67].word, bytes32("uint256-mul")); + assertEq(words[68].word, bytes32("uint256-mul")); //forge-lint: disable-next-line(unsafe-typecast) - assertEq(words[68].word, bytes32("uint256-power")); + assertEq(words[69].word, bytes32("uint256-power")); //forge-lint: disable-next-line(unsafe-typecast) - assertEq(words[69].word, bytes32("uint256-sub")); + assertEq(words[70].word, bytes32("uint256-sub")); // store/ //forge-lint: disable-next-line(unsafe-typecast) - assertEq(words[70].word, bytes32("get")); + assertEq(words[71].word, bytes32("get")); //forge-lint: disable-next-line(unsafe-typecast) - assertEq(words[71].word, bytes32("set")); + assertEq(words[72].word, bytes32("set")); } } diff --git a/test/src/lib/op/math/LibOpPi.t.sol b/test/src/lib/op/math/LibOpPi.t.sol new file mode 100644 index 000000000..58f1cdc69 --- /dev/null +++ b/test/src/lib/op/math/LibOpPi.t.sol @@ -0,0 +1,82 @@ +// SPDX-License-Identifier: LicenseRef-DCL-1.0 +// SPDX-FileCopyrightText: Copyright (c) 2020 Rain Open Source Software Ltd +pragma solidity =0.8.25; + +import {OpTest, UnexpectedOperand} from "test/abstract/OpTest.sol"; +import {InterpreterState} from "../../../../../src/lib/state/LibInterpreterState.sol"; +import {LibOpPi, FLOAT_PI} from "../../../../../src/lib/op/math/LibOpPi.sol"; +import {LibOperand, OperandV2} from "test/lib/operand/LibOperand.sol"; +import {IntegrityCheckState} from "../../../../../src/lib/integrity/LibIntegrityCheck.sol"; +import { + EvalV4, + StackItem, + FullyQualifiedNamespace, + SourceIndexV2 +} from "rainlang-interface-0.2.8/src/interface/IInterpreterV4.sol"; +import {SignedContextV1} from "rainlang-interface-0.2.8/src/interface/IInterpreterCallerV4.sol"; +import {LibContext} from "rainlang-interface-0.2.8/src/lib/caller/LibContext.sol"; +import {Float} from "rain-math-float-0.1.1/src/lib/LibDecimalFloat.sol"; + +/// @title LibOpPiTest +/// @notice Tests for the mathematical constant pi opcode. +contract LibOpPiTest is OpTest { + /// Directly test the integrity logic of LibOpPi. + function testOpPiIntegrity(IntegrityCheckState memory state, uint8 inputs, uint8 outputs, uint16 operandData) + external + pure + { + inputs = uint8(bound(inputs, 0, 0x0F)); + outputs = uint8(bound(outputs, 0, 0x0F)); + (uint256 calcInputs, uint256 calcOutputs) = + LibOpPi.integrity(state, LibOperand.build(inputs, outputs, operandData)); + + assertEq(calcInputs, 0); + assertEq(calcOutputs, 1); + } + + /// Directly test the runtime logic of LibOpPi. This tests that the + /// opcode correctly pushes the mathematical constant pi onto the stack. + function testOpPiRun(uint16 operandData) external view { + InterpreterState memory state = opTestDefaultInterpreterState(); + OperandV2 operand = LibOperand.build(0, 1, operandData); + StackItem[] memory inputs = new StackItem[](0); + opReferenceCheck(state, operand, LibOpPi.referenceFn, LibOpPi.integrity, LibOpPi.run, inputs); + } + + /// Test the eval of a mathematical constant pi opcode parsed from a string. + function testOpPiEval() external view { + bytes memory bytecode = I_DEPLOYER.parse2("_: pi();"); + + (StackItem[] memory stack, bytes32[] memory kvs) = I_INTERPRETER.eval4( + EvalV4({ + store: I_STORE, + namespace: FullyQualifiedNamespace.wrap(0), + bytecode: bytecode, + sourceIndex: SourceIndexV2.wrap(0), + context: LibContext.build(new bytes32[][](0), new SignedContextV1[](0)), + inputs: new StackItem[](0), + stateOverlay: new bytes32[](0) + }) + ); + assertEq(stack.length, 1); + assertEq(StackItem.unwrap(stack[0]), Float.unwrap(FLOAT_PI)); + assertEq(kvs.length, 0); + } + + function testOpPiEvalOneInput() external { + checkBadInputs("_: pi(0x00);", 1, 0, 1); + } + + function testOpPiEvalZeroOutputs() external { + checkBadOutputs(": pi();", 0, 1, 0); + } + + function testOpPiEvalTwoOutputs() external { + checkBadOutputs("_ _: pi();", 0, 1, 2); + } + + /// Test that operand is disallowed. + function testOpPiEvalOperandDisallowed() external { + checkUnhappyParse("_: pi<0>();", abi.encodeWithSelector(UnexpectedOperand.selector)); + } +} From a36f5bd28c457874ed49b636d3fd0aebebe2f3ac Mon Sep 17 00:00:00 2001 From: baku-ccron Date: Mon, 7 Sep 2026 14:16:13 +0000 Subject: [PATCH 2/3] Pin FLOAT_PI to the losslessly packed coefficient The other tests compare the op to the literal; this one compares the literal to pi rounded to nearest at 66 places. Co-Authored-By: Claude Fable 5.1 Claude-Session: https://claude.ai/code/session_01QyCCzi9WZPhuXcU1hwr2bq --- test/src/lib/op/math/LibOpPi.t.sol | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/test/src/lib/op/math/LibOpPi.t.sol b/test/src/lib/op/math/LibOpPi.t.sol index 58f1cdc69..4b915ae87 100644 --- a/test/src/lib/op/math/LibOpPi.t.sol +++ b/test/src/lib/op/math/LibOpPi.t.sol @@ -15,11 +15,20 @@ import { } from "rainlang-interface-0.2.8/src/interface/IInterpreterV4.sol"; import {SignedContextV1} from "rainlang-interface-0.2.8/src/interface/IInterpreterCallerV4.sol"; import {LibContext} from "rainlang-interface-0.2.8/src/lib/caller/LibContext.sol"; -import {Float} from "rain-math-float-0.1.1/src/lib/LibDecimalFloat.sol"; +import {Float, LibDecimalFloat} from "rain-math-float-0.1.1/src/lib/LibDecimalFloat.sol"; /// @title LibOpPiTest /// @notice Tests for the mathematical constant pi opcode. contract LibOpPiTest is OpTest { + /// The packed constant is pi rounded to nearest at 66 decimal places: + /// the 67-digit coefficient at exponent -66, packed losslessly, must be + /// the literal `run` pushes. + function testOpPiConstant() external pure { + Float expected = + LibDecimalFloat.packLossless(3141592653589793238462643383279502884197169399375105820974944592308, -66); + assertEq(Float.unwrap(FLOAT_PI), Float.unwrap(expected)); + } + /// Directly test the integrity logic of LibOpPi. function testOpPiIntegrity(IntegrityCheckState memory state, uint8 inputs, uint8 outputs, uint16 operandData) external From 25fb99e67e42e2192801de619ac60139c4a8f316 Mon Sep 17 00:00:00 2001 From: baku-ccron Date: Wed, 9 Sep 2026 04:51:10 +0000 Subject: [PATCH 3/3] Read pi from LibDecimalFloat.FLOAT_PI The library defines it as of rain-math-float 0.2.1, exactly as FLOAT_E; the local literal and its packing test go, the library carries both. Co-Authored-By: Claude Fable 5.1 Claude-Session: https://claude.ai/code/session_01QyCCzi9WZPhuXcU1hwr2bq --- src/lib/op/math/LibOpPi.sol | 11 +++-------- test/src/lib/op/math/LibOpPi.t.sol | 15 +++------------ 2 files changed, 6 insertions(+), 20 deletions(-) diff --git a/src/lib/op/math/LibOpPi.sol b/src/lib/op/math/LibOpPi.sol index 235b840fb..fc43f7a05 100644 --- a/src/lib/op/math/LibOpPi.sol +++ b/src/lib/op/math/LibOpPi.sol @@ -6,12 +6,7 @@ import {Pointer} from "rain-solmem-0.1.28/src/lib/LibPointer.sol"; import {OperandV2, StackItem} from "rainlang-interface-0.2.8/src/interface/IInterpreterV4.sol"; import {InterpreterState} from "../../state/LibInterpreterState.sol"; import {IntegrityCheckState} from "../../integrity/LibIntegrityCheck.sol"; -import {Float} from "rain-math-float-0.1.1/src/lib/LibDecimalFloat.sol"; - -/// @dev The mathematical constant pi as a `Float`. -/// 3.141592653589793238462643383279502884197169399375105820974944592308e66, -66 -Float constant FLOAT_PI = - Float.wrap(bytes32(uint256(0xffffffbe1dd4c9e873614f593bba9c6007d9a7ac8d03a4b6c700a65cb537a1b4))); +import {Float, LibDecimalFloat} from "rain-math-float-0.2.1/src/lib/LibDecimalFloat.sol"; /// @title LibOpPi /// @notice Stacks the mathematical constant pi. @@ -27,7 +22,7 @@ library LibOpPi { /// @param stackTop Pointer to the top of the stack. /// @return The new stack top pointer after execution. function run(InterpreterState memory, OperandV2, Pointer stackTop) internal pure returns (Pointer) { - Float pi = FLOAT_PI; + Float pi = LibDecimalFloat.FLOAT_PI; assembly ("memory-safe") { stackTop := sub(stackTop, 0x20) mstore(stackTop, pi) @@ -43,7 +38,7 @@ library LibOpPi { returns (StackItem[] memory) { StackItem[] memory outputs = new StackItem[](1); - outputs[0] = StackItem.wrap(Float.unwrap(FLOAT_PI)); + outputs[0] = StackItem.wrap(Float.unwrap(LibDecimalFloat.FLOAT_PI)); return outputs; } } diff --git a/test/src/lib/op/math/LibOpPi.t.sol b/test/src/lib/op/math/LibOpPi.t.sol index 4b915ae87..384c733c7 100644 --- a/test/src/lib/op/math/LibOpPi.t.sol +++ b/test/src/lib/op/math/LibOpPi.t.sol @@ -4,7 +4,7 @@ pragma solidity =0.8.25; import {OpTest, UnexpectedOperand} from "test/abstract/OpTest.sol"; import {InterpreterState} from "../../../../../src/lib/state/LibInterpreterState.sol"; -import {LibOpPi, FLOAT_PI} from "../../../../../src/lib/op/math/LibOpPi.sol"; +import {LibOpPi} from "../../../../../src/lib/op/math/LibOpPi.sol"; import {LibOperand, OperandV2} from "test/lib/operand/LibOperand.sol"; import {IntegrityCheckState} from "../../../../../src/lib/integrity/LibIntegrityCheck.sol"; import { @@ -15,20 +15,11 @@ import { } from "rainlang-interface-0.2.8/src/interface/IInterpreterV4.sol"; import {SignedContextV1} from "rainlang-interface-0.2.8/src/interface/IInterpreterCallerV4.sol"; import {LibContext} from "rainlang-interface-0.2.8/src/lib/caller/LibContext.sol"; -import {Float, LibDecimalFloat} from "rain-math-float-0.1.1/src/lib/LibDecimalFloat.sol"; +import {Float, LibDecimalFloat} from "rain-math-float-0.2.1/src/lib/LibDecimalFloat.sol"; /// @title LibOpPiTest /// @notice Tests for the mathematical constant pi opcode. contract LibOpPiTest is OpTest { - /// The packed constant is pi rounded to nearest at 66 decimal places: - /// the 67-digit coefficient at exponent -66, packed losslessly, must be - /// the literal `run` pushes. - function testOpPiConstant() external pure { - Float expected = - LibDecimalFloat.packLossless(3141592653589793238462643383279502884197169399375105820974944592308, -66); - assertEq(Float.unwrap(FLOAT_PI), Float.unwrap(expected)); - } - /// Directly test the integrity logic of LibOpPi. function testOpPiIntegrity(IntegrityCheckState memory state, uint8 inputs, uint8 outputs, uint16 operandData) external @@ -68,7 +59,7 @@ contract LibOpPiTest is OpTest { }) ); assertEq(stack.length, 1); - assertEq(StackItem.unwrap(stack[0]), Float.unwrap(FLOAT_PI)); + assertEq(StackItem.unwrap(stack[0]), Float.unwrap(LibDecimalFloat.FLOAT_PI)); assertEq(kvs.length, 0); }