diff --git a/test/src/concrete/Flow.construction.t.sol b/test/src/concrete/Flow.construction.t.sol index 85fdaefa..ade81acc 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, UnsupportedFlowInputs} from "src/error/ErrFlow.sol"; +import {EmptyFlowConfig, InsufficientFlowOutputs, UnsupportedFlowInputs} 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)); + } + /// Reverts with `UnsupportedFlowInputs` when the deployer reports any /// non-zero `flowInputs` byte in the IO string. Pinning this protects /// against a future deployer behaviour change leaking non-zero inputs @@ -40,6 +80,7 @@ contract FlowConstructionTest is FlowTest { I_CLONE_FACTORY.clone(impl, abi.encode(flowConfig)); } + function testFlowConstructionInitialize(address expression, bytes memory bytecode, uint256[] memory constants) external {