diff --git a/src/generated/Rainterpreter.pointers.sol b/src/generated/Rainterpreter.pointers.sol index 03f1a8f5a..05bc5a3bd 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(0xca873fdc0fafdd8622956664b1ff707a348e5086104c9df4041da19e4ed40eb2); +bytes32 constant BYTECODE_HASH = bytes32(0x7770f5e2359b2a92d0b5cb50af2f0c9dd90e78a0e0121c6ae2e6ac1da0cde34e); /// @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"07d8080a082e09ba0a830a950aa70ac00ae40b180b290b3a0bdc0bfb0cb90d690ded0f2f10620cb9115b120d12af1327133813491349135a13a11423143c14501469148214ad14c614df14f2155415a215f0163e168c16da17281759176717b517e61834186518b3190119e8"; + hex"07e10813083709c30a8c0a9e0ab00ac90aed0b210b320b430be50c040cc20d720df60f38106b0cc21164121612b81330134113521352136313aa142c144514591472148b14b614cf14e8150f1522158415d21620166e16bc170a17581789179717e518161864189518e319311a18"; diff --git a/src/generated/RainterpreterExpressionDeployer.pointers.sol b/src/generated/RainterpreterExpressionDeployer.pointers.sol index 57ee96bd2..7e5341f9c 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(0xbde8830f32466859d248ddbd93c3160f528590905b62e571cdf67a18f501b881); +bytes32 constant BYTECODE_HASH = bytes32(0x7c554993aec9e07bd302f72f51995655bb2775660f136ed05634b376f427a342); /// @dev The hash of the meta that describes the contract. -bytes32 constant DESCRIBED_BY_META_HASH = bytes32(0xb592485a2a86d9c1b4189fb79e5ee78349710462b6cb3d9836d4c211e64dff5b); +bytes32 constant DESCRIBED_BY_META_HASH = bytes32(0xdb2f059f887ac765ade623ce752b384a6dd9fcc8b47085524a8866735f0b24a9); /// @dev The function pointers for the integrity check fns. bytes constant INTEGRITY_FUNCTION_POINTERS = - hex"0e680ee60f4a10c410ce10ce10d810e110fc11a211a211fe1276128310ce10d8128310ce10d810ce10ce10ce10d810c410c410c410c4128d12b210ce10ce10ce10ce10d810ce10ce10c412bc12bc12bc12bc12bc12bc12d610c410d812bc10c412bc10c412d612d610d812b2"; + hex"0e700eee0f5210cc10d610d610e010e9110411aa11aa1206127e128b10d610e0128b10d610e010d610d610d610e010cc10cc10cc10cc129512ba10d610d610d610d610e010d610d6128b10cc12c412c412c412c412c412c412de10cc10e012c410cc12c410cc12de12de10e012ba"; diff --git a/src/generated/RainterpreterParser.pointers.sol b/src/generated/RainterpreterParser.pointers.sol index 692901607..4c6f0ab62 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(0x2dd95b73bafe92197b507ea6cded7f00121a8dedfc9779057387ad0cdd34cb46); +bytes32 constant BYTECODE_HASH = bytes32(0xb0a907d5ee74e592c9a84e74d49fd8dc040d637a7ec422088ef4b740d3557435); /// @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(0x2dd95b73bafe92197b507ea6cded7f00121a8 /// 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"0178ac80600029b010910908a00a7448c001280030060500180000200cc2200408a00c95d8383448145c3534b3f32fd587e204afc8f01e95a26007b9a51f0d5e6f2803ce312215fae0a1271233de118cfd53051c784d25a55ade3112233f1681ac9622ac6dfc30a4e5e91f5fc32a0e92b2401dbbd837100ef76b00cd8814205902e62a9714ec13313592290d473b21f880501427634f2ce048d30f23b4970ac0e53d0b895f991ac908be0983724f12c080bc329d7b7d28757aea2d3a7b86266f62e02b5c0ab5086d2a7b2390a6b319bcb24402af7d4506414b8117b04fb31cb2182f18ef532924c301e31b9314ad33fa5d8001336a592eab2461"; + hex"0178ac80600029b010910908a00a7448c001280030060500180000200cc2200408a424fe9a970c95d8383548145c3634b3f330d587e204afc8f01e95a26007b9a51f0d5e6f2803ce312215fae0a1281233de118cfd53051c784d26a55ade3212233f1681ac9622ac6dfc31a4e5e91f5fc32a0e92b2401dbbd837100ef76b00cd8814205902e62b9714ec133135922a0d473b21f880501427634f2de048d30f23b4970ac0e53d0b895f991ac908be0983724f12c080bc339d7b7d29757aea2e3a7b86276f62e02c5c0ab5086d2a7b2390a6b319bcb24402af7d4506414b8117b04fb31cb2182f18ef532925c301e31b9314ad34fa5d8001336a592fab2461"; /// @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"1a4b1a4b1a4b1b201c371c371c371b201b201a4b1a4b1a4b1c371c371c371c371c371c371c371c371c371c371c371c371c371c371c371c371c371c371c371c371c371c371c371c371c371c371c371c371c371c371c371c371c371c371c371c371c371c371c371a4b1c371c37"; + hex"1a531a531a531b281c3f1c3f1c3f1b281b281a531a531a531c3f1c3f1c3f1c3f1c3f1c3f1c3f1c3f1c3f1c3f1c3f1c3f1c3f1c3f1c3f1c3f1c3f1c3f1c3f1c3f1c3f1c3f1c3f1c3f1c3f1c3f1c3f1c3f1c3f1c3f1c3f1c3f1c3f1c3f1c3f1c3f1c3f1c3f1c3f1c3f1a531c3f1c3f"; /// @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"159317c5180818a6"; +bytes constant LITERAL_PARSER_FUNCTION_POINTERS = hex"159b17cd181018ae"; diff --git a/src/lib/op/LibAllStandardOps.sol b/src/lib/op/LibAllStandardOps.sol index e2d1cfb3c..ef353132c 100644 --- a/src/lib/op/LibAllStandardOps.sol +++ b/src/lib/op/LibAllStandardOps.sol @@ -60,7 +60,7 @@ import {LibOpLessThan} from "./logic/LibOpLessThan.sol"; import {LibOpLessThanOrEqualTo} from "./logic/LibOpLessThanOrEqualTo.sol"; // import {LibOpExponentialGrowth} from "./math/growth/LibOpExponentialGrowth.sol"; -// import {LibOpLinearGrowth} from "./math/growth/LibOpLinearGrowth.sol"; +import {LibOpLinearGrowth} from "./math/growth/LibOpLinearGrowth.sol"; import {LibOpMaxUint256} from "./math/uint256/LibOpMaxUint256.sol"; import {LibOpUint256Add} from "./math/uint256/LibOpUint256Add.sol"; @@ -109,7 +109,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 = 54; +uint256 constant ALL_STANDARD_OPS_LENGTH = 55; /// @title LibAllStandardOps /// @notice Every opcode available from the core repository laid out as a single @@ -240,10 +240,10 @@ library LibAllStandardOps { // "exponential-growth", // "Calculates an exponential growth curve as `base(1 + rate)^t` where `base` is the initial value, `rate` is the rate of growth and `t` is units of time. Inputs in order are `base`, `rate`, and `t` respectively." // ), - // AuthoringMetaV2( - // "linear-growth", - // "Calculates a linear growth curve as `base + (rate * t)` where `base` is the initial value, `rate` is the rate of growth and `t` is units of time. Inputs in order are `base`, `rate`, and `t` respectively." - // ), + AuthoringMetaV2( + "linear-growth", + "Calculates a linear growth curve as `base + (rate * t)` where `base` is the initial value, `rate` is the rate of growth and `t` is units of time. Inputs in order are `base`, `rate`, and `t` respectively." + ), AuthoringMetaV2("uint256-max-value", "The maximum possible unsigned integer value (all binary bits are 1)."), AuthoringMetaV2( "uint256-add", @@ -437,8 +437,8 @@ library LibAllStandardOps { LibParseOperand.handleOperandDisallowed, // // exponential-growth // LibParseOperand.handleOperandDisallowed, - // // linear-growth - // LibParseOperand.handleOperandDisallowed, + // linear-growth + LibParseOperand.handleOperandDisallowed, // uint256-max-value LibParseOperand.handleOperandDisallowed, // uint256-add @@ -576,7 +576,7 @@ library LibAllStandardOps { LibOpLessThan.integrity, LibOpLessThanOrEqualTo.integrity, // LibOpExponentialGrowth.integrity, - // LibOpLinearGrowth.integrity, + LibOpLinearGrowth.integrity, LibOpMaxUint256.integrity, LibOpUint256Add.integrity, LibOpUint256Div.integrity, @@ -684,7 +684,7 @@ library LibAllStandardOps { LibOpLessThan.run, LibOpLessThanOrEqualTo.run, // LibOpExponentialGrowth.run, - // LibOpLinearGrowth.run, + LibOpLinearGrowth.run, LibOpMaxUint256.run, LibOpUint256Add.run, LibOpUint256Div.run, diff --git a/src/lib/op/math/growth/LibOpLinearGrowth.sol b/src/lib/op/math/growth/LibOpLinearGrowth.sol index 0ef441524..3c9f782d8 100644 --- a/src/lib/op/math/growth/LibOpLinearGrowth.sol +++ b/src/lib/op/math/growth/LibOpLinearGrowth.sol @@ -1,48 +1,53 @@ // SPDX-License-Identifier: CAL pragma solidity ^0.8.18; -// import {UD60x18, mul, add} 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 LibOpLinearGrowth -// /// @notice Linear growth is base + rate * t where a is the initial value, r is -// /// the growth rate, and t is time. -// library LibOpLinearGrowth { -// function integrity(IntegrityCheckState memory, Operand) internal pure returns (uint256, uint256) { -// // There must be three inputs and one output. -// return (3, 1); -// } +/// @title LibOpLinearGrowth +/// @notice Linear growth is base + rate * t where a is the initial value, r is +/// the growth rate, and t is time. +library LibOpLinearGrowth { + function integrity(IntegrityCheckState memory, OperandV2) internal pure returns (uint256, uint256) { + // There must be three inputs and one output. + return (3, 1); + } -// /// linear-growth -// function run(InterpreterState memory, Operand, Pointer stackTop) internal pure returns (Pointer) { -// uint256 base; -// uint256 rate; -// uint256 t; -// assembly ("memory-safe") { -// base := mload(stackTop) -// rate := mload(add(stackTop, 0x20)) -// stackTop := add(stackTop, 0x40) -// t := mload(stackTop) -// } -// base = UD60x18.unwrap(add(UD60x18.wrap(base), mul(UD60x18.wrap(rate), UD60x18.wrap(t)))); + /// linear-growth + function run(InterpreterState memory, OperandV2, Pointer stackTop) internal pure returns (Pointer) { + Float base; + Float rate; + Float t; + assembly ("memory-safe") { + base := mload(stackTop) + rate := mload(add(stackTop, 0x20)) + stackTop := add(stackTop, 0x40) + t := mload(stackTop) + } -// assembly ("memory-safe") { -// mstore(stackTop, base) -// } -// return stackTop; -// } + base = LibDecimalFloat.add(base, LibDecimalFloat.mul(rate, t)); -// /// Gas intensive reference implementation 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(add(UD60x18.wrap(inputs[0]), mul(UD60x18.wrap(inputs[1]), UD60x18.wrap(inputs[2])))); -// return outputs; -// } -// } + assembly ("memory-safe") { + mstore(stackTop, base) + } + return stackTop; + } + + /// Gas intensive reference implementation for testing. + function referenceFn(InterpreterState memory, OperandV2, StackItem[] memory inputs) + internal + pure + returns (StackItem[] memory) + { + Float base = Float.wrap(StackItem.unwrap(inputs[0])); + Float rate = Float.wrap(StackItem.unwrap(inputs[1])); + Float t = Float.wrap(StackItem.unwrap(inputs[2])); + StackItem[] memory outputs = new StackItem[](1); + outputs[0] = StackItem.wrap(Float.unwrap(LibDecimalFloat.add(base, LibDecimalFloat.mul(rate, t)))); + return outputs; + } +} diff --git a/test/src/lib/op/math/growth/LibOpLinearGrowth.t.sol b/test/src/lib/op/math/growth/LibOpLinearGrowth.t.sol index 91ef925f7..262c10759 100644 --- a/test/src/lib/op/math/growth/LibOpLinearGrowth.t.sol +++ b/test/src/lib/op/math/growth/LibOpLinearGrowth.t.sol @@ -1,83 +1,104 @@ // SPDX-License-Identifier: CAL pragma solidity =0.8.25; -// import {OpTest, IntegrityCheckState, Operand, InterpreterState, UnexpectedOperand} from "test/abstract/OpTest.sol"; -// import {LibOpLinearGrowth} from "src/lib/op/math/growth/LibOpLinearGrowth.sol"; -// import {LibOperand} from "test/lib/operand/LibOperand.sol"; - -// contract LibOpLinearGrowthTest is OpTest { -// /// Directly test the integrity logic of LibOpLinearGrowth. -// /// Inputs are always 3, outputs are always 1. -// function testOpLinearGrowthIntegrity(IntegrityCheckState memory state, Operand operand) external pure { -// (uint256 calcInputs, uint256 calcOutputs) = LibOpLinearGrowth.integrity(state, operand); -// assertEq(calcInputs, 3); -// assertEq(calcOutputs, 1); -// } - -// /// Directly test the runtime logic of LibOpLinearGrowth. -// function testOpLinearGrowthRun(uint256 a, uint256 r, uint256 t, uint16 operandData) public view { -// // @TODO This is a hack to cover some range that we can definitely -// // handle but it doesn't cover the full range of the function. -// a = bound(a, 0, type(uint128).max); -// r = bound(r, 0, type(uint128).max); -// // PRB math can't reliably handle t beyond 44e18 with a and r both up to -// // ~18e18 (uint64 max). -// t = bound(t, 0, type(uint64).max); -// InterpreterState memory state = opTestDefaultInterpreterState(); - -// Operand operand = LibOperand.build(3, 1, operandData); -// uint256[] memory inputs = new uint256[](3); -// inputs[0] = a; -// inputs[1] = r; -// inputs[2] = t; - -// opReferenceCheck( -// state, operand, LibOpLinearGrowth.referenceFn, LibOpLinearGrowth.integrity, LibOpLinearGrowth.run, inputs -// ); -// } - -// /// Test the eval of `linear-growth`. -// function testOpLinearGrowthEval() external view { -// checkHappy("_: linear-growth(0 0 0);", 0, "0 0 0"); -// checkHappy("_: linear-growth(0 0.1 0);", 0, "0 0.1 0"); -// checkHappy("_: linear-growth(0 0.1 1);", 1e17, "0 0.1 1"); -// checkHappy("_: linear-growth(1 0.1 0);", 1e18, "1 0.1 0"); -// checkHappy("_: linear-growth(1 0.1 1);", 1.1e18, "1 0.1 1"); -// checkHappy("_: linear-growth(1 0.1 2);", 1.2e18, "1 0.1 2"); -// checkHappy("_: linear-growth(1 0.1 2.5);", 1.25e18, "1 0.1 2.5"); -// checkHappy("_: linear-growth(1 0 2);", 1e18, "1 0 2"); -// checkHappy("_: linear-growth(1 0.1 0.5);", 1.05e18, "1 0.1 0.5"); -// checkHappy("_: linear-growth(2 0.1 0);", 2e18, "2 0.1 0"); -// checkHappy("_: linear-growth(2 0.1 1);", 2.1e18, "2 0.1 1"); -// checkHappy("_: linear-growth(2 0.1 2);", 2.2e18, "2 0.1 2"); -// } - -// function testOpLinearGrowthEvalZeroInputs() external { -// checkBadInputs(": linear-growth();", 0, 3, 0); -// } - -// function testOpLinearGrowthEvalOneInput() external { -// checkBadInputs("_: linear-growth(1);", 1, 3, 1); -// } - -// function testOpLinearGrowthEvalTwoInputs() external { -// checkBadInputs("_: linear-growth(1 0);", 2, 3, 2); -// } - -// function testOpLinearGrowthEvalFourInputs() external { -// checkBadInputs("_: linear-growth(1 0 0 1);", 4, 3, 4); -// } - -// function testOpLinearGrowthEvalZeroOutputs() external { -// checkBadOutputs(": linear-growth(1 0 0);", 3, 1, 0); -// } - -// function testOpLinearGrowthEvalTwoOutputs() external { -// checkBadOutputs("_ _: linear-growth(1 0 0);", 3, 1, 2); -// } - -// /// Test that operand is disallowed. -// function testOpLinearGrowthEvalOperandDisallowed() external { -// checkUnhappyParse("_: linear-growth<0>(1 0 0);", abi.encodeWithSelector(UnexpectedOperand.selector)); -// } -// } +import {OpTest, IntegrityCheckState, OperandV2, InterpreterState, UnexpectedOperand} from "test/abstract/OpTest.sol"; +import {LibOpLinearGrowth} from "src/lib/op/math/growth/LibOpLinearGrowth.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 LibOpLinearGrowthTest is OpTest { + /// Directly test the integrity logic of LibOpLinearGrowth. + /// Inputs are always 3, outputs are always 1. + function testOpLinearGrowthIntegrity(IntegrityCheckState memory state, OperandV2 operand) external pure { + (uint256 calcInputs, uint256 calcOutputs) = LibOpLinearGrowth.integrity(state, operand); + assertEq(calcInputs, 3); + assertEq(calcOutputs, 1); + } + + /// Directly test the runtime logic of LibOpLinearGrowth. + function testOpLinearGrowthRun( + int224 signedCoefficientA, + int32 exponentA, + int224 signedCoefficientR, + int32 exponentR, + int224 signedCoefficientT, + int32 exponentT, + uint16 operandData + ) public view { + exponentA = int32(bound(exponentA, type(int24).min, type(int24).max)); + Float a = LibDecimalFloat.packLossless(signedCoefficientA, exponentA); + + exponentR = int32(bound(exponentR, type(int24).min, type(int24).max)); + Float r = LibDecimalFloat.packLossless(signedCoefficientR, exponentR); + + exponentT = int32(bound(exponentT, type(int24).min, type(int24).max)); + Float t = LibDecimalFloat.packLossless(signedCoefficientT, exponentT); + + InterpreterState memory state = opTestDefaultInterpreterState(); + + OperandV2 operand = LibOperand.build(3, 1, operandData); + StackItem[] memory inputs = new StackItem[](3); + inputs[0] = StackItem.wrap(Float.unwrap(a)); + inputs[1] = StackItem.wrap(Float.unwrap(r)); + inputs[2] = StackItem.wrap(Float.unwrap(t)); + + opReferenceCheck( + state, operand, LibOpLinearGrowth.referenceFn, LibOpLinearGrowth.integrity, LibOpLinearGrowth.run, inputs + ); + } + + /// Test the eval of `linear-growth`. + function testOpLinearGrowthEval() external view { + checkHappy("_: linear-growth(0 0 0);", 0, "0 0 0"); + checkHappy("_: linear-growth(0 0.1 0);", 0, "0 0.1 0"); + checkHappy("_: linear-growth(0 0.1 1);", Float.unwrap(LibDecimalFloat.packLossless(1, -1)), "0 0.1 1"); + checkHappy("_: linear-growth(1 0.1 0);", Float.unwrap(LibDecimalFloat.packLossless(1, 0)), "1 0.1 0"); + checkHappy("_: linear-growth(1 0.1 1);", Float.unwrap(LibDecimalFloat.packLossless(1.1e38, -38)), "1 0.1 1"); + checkHappy("_: linear-growth(1 0.1 2);", Float.unwrap(LibDecimalFloat.packLossless(1.2e38, -38)), "1 0.1 2"); + checkHappy( + "_: linear-growth(1 0.1 2.5);", Float.unwrap(LibDecimalFloat.packLossless(1.25e38, -38)), "1 0.1 2.5" + ); + checkHappy("_: linear-growth(1 0 2);", Float.unwrap(LibDecimalFloat.packLossless(1, 0)), "1 0 2"); + checkHappy( + "_: linear-growth(1 0.1 0.5);", Float.unwrap(LibDecimalFloat.packLossless(1.05e39, -39)), "1 0.1 0.5" + ); + checkHappy("_: linear-growth(2 0.1 0);", Float.unwrap(LibDecimalFloat.packLossless(2, 0)), "2 0.1 0"); + checkHappy("_: linear-growth(2 0.1 1);", Float.unwrap(LibDecimalFloat.packLossless(2.1e38, -38)), "2 0.1 1"); + checkHappy("_: linear-growth(2 0.1 2);", Float.unwrap(LibDecimalFloat.packLossless(2.2e38, -38)), "2 0.1 2"); + + checkHappy("_: linear-growth(1 -0.1 1);", Float.unwrap(LibDecimalFloat.packLossless(0.9e38, -38)), "1 -0.1 1"); + checkHappy( + "_: linear-growth(-1 -0.1 2);", Float.unwrap(LibDecimalFloat.packLossless(-1.2e38, -38)), "-1 -0.1 2" + ); + } + + function testOpLinearGrowthEvalZeroInputs() external { + checkBadInputs(": linear-growth();", 0, 3, 0); + } + + function testOpLinearGrowthEvalOneInput() external { + checkBadInputs("_: linear-growth(1);", 1, 3, 1); + } + + function testOpLinearGrowthEvalTwoInputs() external { + checkBadInputs("_: linear-growth(1 0);", 2, 3, 2); + } + + function testOpLinearGrowthEvalFourInputs() external { + checkBadInputs("_: linear-growth(1 0 0 1);", 4, 3, 4); + } + + function testOpLinearGrowthEvalZeroOutputs() external { + checkBadOutputs(": linear-growth(1 0 0);", 3, 1, 0); + } + + function testOpLinearGrowthEvalTwoOutputs() external { + checkBadOutputs("_ _: linear-growth(1 0 0);", 3, 1, 2); + } + + /// Test that operand is disallowed. + function testOpLinearGrowthEvalOperandDisallowed() external { + checkUnhappyParse("_: linear-growth<0>(1 0 0);", abi.encodeWithSelector(UnexpectedOperand.selector)); + } +}