From 1cf7d5ed65953c953571d97a9434a8a4d770c61e Mon Sep 17 00:00:00 2001 From: thedavidmeister Date: Mon, 4 May 2026 20:11:51 +0400 Subject: [PATCH] test InsufficientFlowOutputs revert and boundary MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Pins the lower-bound guard that rejects flowOutputs < MIN_FLOW_SENTINELS returned by the deployer. Two tests: - Negative: any flowOutputs in [0, MIN_FLOW_SENTINELS) reverts with InsufficientFlowOutputs. - Boundary: flowOutputs == MIN_FLOW_SENTINELS is accepted (lowest valid). Mutation verified: inverting the check (`<` → `>=`) makes both tests fail; reverting passes. Closes #311 #321. Co-Authored-By: Claude Opus 4.7 (1M context) --- test/src/concrete/Flow.construction.t.sol | 42 ++++++++++++++++++++++- 1 file changed, 41 insertions(+), 1 deletion(-) diff --git a/test/src/concrete/Flow.construction.t.sol b/test/src/concrete/Flow.construction.t.sol index 03206941..070c4670 100644 --- a/test/src/concrete/Flow.construction.t.sol +++ b/test/src/concrete/Flow.construction.t.sol @@ -6,7 +6,8 @@ import {Vm} from "forge-std/Test.sol"; import {EvaluableConfigV3} from "rain.interpreter.interface/interface/IInterpreterCallerV2.sol"; import {FlowTest} from "test/abstract/FlowTest.sol"; -import {EmptyFlowConfig} from "src/error/ErrFlow.sol"; +import {EmptyFlowConfig, InsufficientFlowOutputs} from "src/error/ErrFlow.sol"; +import {MIN_FLOW_SENTINELS} from "src/interface/IFlowV5.sol"; contract FlowConstructionTest is FlowTest { function testFlowConstructionEmptyConfigReverts() external { @@ -16,6 +17,45 @@ contract FlowConstructionTest is FlowTest { I_CLONE_FACTORY.clone(impl, abi.encode(emptyConfig)); } + /// Reverts with `InsufficientFlowOutputs` when the deployer reports a + /// `flowOutputs` byte below `MIN_FLOW_SENTINELS` (= 3). Pinning this + /// protects the lower-bound guard at `Flow.flowInit` against regression. + /// forge-config: default.fuzz.runs = 100 + function testFlowConstructionRevertsOnInsufficientFlowOutputs( + address expression, + bytes memory bytecode, + uint256[] memory constants, + uint8 flowOutputs + ) external { + vm.assume(flowOutputs < uint8(MIN_FLOW_SENTINELS)); + bytes memory io = abi.encodePacked(uint8(0), flowOutputs); + expressionDeployerDeployExpression2MockCall(expression, io); + + EvaluableConfigV3[] memory flowConfig = new EvaluableConfigV3[](1); + flowConfig[0] = EvaluableConfigV3(DEPLOYER, bytecode, constants); + + address impl = deployFlowImplementation(); + vm.expectRevert(InsufficientFlowOutputs.selector); + I_CLONE_FACTORY.clone(impl, abi.encode(flowConfig)); + } + + /// `flowOutputs == MIN_FLOW_SENTINELS` is the lowest accepted value. + /// Pinning this boundary against regression complements the negative + /// test above. + function testFlowConstructionAcceptsFlowOutputsAtMin( + address expression, + bytes memory bytecode, + uint256[] memory constants + ) external { + bytes memory io = abi.encodePacked(uint8(0), uint8(MIN_FLOW_SENTINELS)); + expressionDeployerDeployExpression2MockCall(expression, io); + + EvaluableConfigV3[] memory flowConfig = new EvaluableConfigV3[](1); + flowConfig[0] = EvaluableConfigV3(DEPLOYER, bytecode, constants); + + I_CLONE_FACTORY.clone(deployFlowImplementation(), abi.encode(flowConfig)); + } + function testFlowConstructionInitialize(address expression, bytes memory bytecode, uint256[] memory constants) external {