From f43a5a1a2d45631068f3ff88cf8a1cb8fbd1ac13 Mon Sep 17 00:00:00 2001 From: thedavidmeister Date: Tue, 5 May 2026 14:08:54 +0400 Subject: [PATCH] test duplicate evaluable configs emit twice idempotently MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `flowInit` does not de-duplicate identical evaluable configs. Two configs that produce the same (interpreter, store, expression) triple emit two FlowInitialized events and write the same registeredFlows slot twice. The registration is idempotent — the resulting clone is still functional with that evaluable. Pinning this prevents a future change from silently introducing dedup, suppressing the second emit, or rejecting duplicates outright without an explicit ABI bump. Closes #327. Co-Authored-By: Claude Opus 4.7 (1M context) --- test/src/concrete/Flow.construction.t.sol | 32 +++++++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/test/src/concrete/Flow.construction.t.sol b/test/src/concrete/Flow.construction.t.sol index 03206941..e4d6a227 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,33 @@ 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` does not de-duplicate identical evaluable configs: two + /// configs that produce the same `(interpreter, store, expression)` + /// triple emit two `FlowInitialized` events and write the same + /// `registeredFlows` slot twice. The registration is idempotent so + /// the resulting clone is still functional with that evaluable. + /// forge-config: default.fuzz.runs = 100 + function testFlowConstructionDuplicateEvaluablesEmitTwiceIdempotently( + address expression, + bytes memory bytecode, + uint256[] memory constants + ) external { + expressionDeployerDeployExpression2MockCall(bytecode, constants, expression, bytes(hex"0007")); + + EvaluableConfigV3[] memory flowConfig = new EvaluableConfigV3[](2); + flowConfig[0] = EvaluableConfigV3(DEPLOYER, bytecode, constants); + flowConfig[1] = EvaluableConfigV3(DEPLOYER, bytecode, constants); + + vm.recordLogs(); + I_CLONE_FACTORY.clone(deployFlowImplementation(), abi.encode(flowConfig)); + + Vm.Log[] memory init = + vm.getRecordedLogs().findEvents(keccak256("FlowInitialized(address,(address,address,address))")); + assertEq(init.length, 2, "duplicate configs MUST emit two FlowInitialized events"); + + (, EvaluableV2 memory ev0) = abi.decode(init[0].data, (address, EvaluableV2)); + (, EvaluableV2 memory ev1) = abi.decode(init[1].data, (address, EvaluableV2)); + assertEq(keccak256(abi.encode(ev0)), keccak256(abi.encode(ev1)), "duplicate events MUST carry identical evaluable"); + } }