diff --git a/test/src/concrete/Flow.construction.t.sol b/test/src/concrete/Flow.construction.t.sol index 03206941..e6fd6158 100644 --- a/test/src/concrete/Flow.construction.t.sol +++ b/test/src/concrete/Flow.construction.t.sol @@ -7,8 +7,11 @@ 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 {EvaluableV2} from "rain.interpreter.interface/lib/caller/LibEvaluable.sol"; +import {LibLogHelper} from "test/lib/LibLogHelper.sol"; contract FlowConstructionTest is FlowTest { + using LibLogHelper for Vm.Log[]; function testFlowConstructionEmptyConfigReverts() external { EvaluableConfigV3[] memory emptyConfig = new EvaluableConfigV3[](0); address impl = deployFlowImplementation(); @@ -37,4 +40,43 @@ contract FlowConstructionTest is FlowTest { assertEq(sender, address(I_CLONE_FACTORY), "wrong sender in Initialize event"); assertEq(keccak256(abi.encode(flowConfig)), keccak256(abi.encode(config)), "wrong compare Structs"); } + + /// `flowInit` MUST emit one `FlowInitialized(sender, evaluable)` per + /// registered config, with `sender` equal to the clone-factory caller + /// and `evaluable` equal to the deployer-returned `(interpreter, store, + /// expression)` triple. Pinning this prevents a future change that + /// drops the event, mismatches the sender, or skips emissions on + /// duplicate configs. + /// forge-config: default.fuzz.runs = 100 + function testFlowConstructionEmitsFlowInitializedPerConfig(address[] memory expressions) external { + uint256 length = bound(expressions.length, 1, 5); + assembly ("memory-safe") { + mstore(expressions, length) + } + + EvaluableConfigV3[] memory flowConfig = new EvaluableConfigV3[](length); + for (uint256 i = 0; i < length; i++) { + // Distinct (bytecode, constants) per config so each call to the + // deployer mock returns a distinct `expression`. + bytes memory bytecode = abi.encodePacked(uint256(i)); + uint256[] memory constants = new uint256[](0); + expressionDeployerDeployExpression2MockCall(bytecode, constants, expressions[i], bytes(hex"0007")); + flowConfig[i] = EvaluableConfigV3(DEPLOYER, bytecode, constants); + } + + vm.recordLogs(); + I_CLONE_FACTORY.clone(deployFlowImplementation(), abi.encode(flowConfig)); + + Vm.Log[] memory all = vm.getRecordedLogs(); + Vm.Log[] memory init = all.findEvents(keccak256("FlowInitialized(address,(address,address,address))")); + + assertEq(init.length, length, "FlowInitialized count"); + for (uint256 i = 0; i < length; i++) { + (address sender, EvaluableV2 memory ev) = abi.decode(init[i].data, (address, EvaluableV2)); + assertEq(sender, address(I_CLONE_FACTORY), "FlowInitialized sender"); + assertEq(address(ev.interpreter), address(INTERPRETER), "FlowInitialized interpreter"); + assertEq(address(ev.store), address(STORE), "FlowInitialized store"); + assertEq(ev.expression, expressions[i], "FlowInitialized expression"); + } + } }