Skip to content

[A01-8] [INFO] Duplicate evaluable registration in flowInit not pinned #327

@thedavidmeister

Description

@thedavidmeister

Gap

flowInit writes registeredFlows[evaluable.hash()] = FLOW_IS_REGISTERED in a loop with no dedup check (src/concrete/Flow.sol:220). If two EvaluableConfigV3 entries deploy to identical (interpreter, store, expression) tuples — i.e. identical hashes — the second iteration silently overwrites the same slot and emits FlowInitialized a second time for the same evaluable.

This may be intentional (deployers are trusted), but no test pins the behaviour either way.

Source

  • src/concrete/Flow.sol:220 (mapping write)
  • src/concrete/Flow.sol:225 (event emit, unconditional)

Existing related tests

None — no test passes duplicate evaluable configs.

Proposed test

Add to test/src/concrete/Flow.construction.t.sol:

import {LibEvaluable, EvaluableV2} from "rain.interpreter.interface/lib/caller/LibEvaluable.sol";
import {Vm} from "forge-std/Test.sol";
import {LibLogHelper} from "test/lib/LibLogHelper.sol";
import {IFlowV5} from "src/interface/IFlowV5.sol";
import {SignedContextV1} from "rain.interpreter.interface/interface/IInterpreterCallerV2.sol";

using LibEvaluable for EvaluableV2;
using LibLogHelper for Vm.Log[];

function testFlowConstructionDuplicateEvaluables(
    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();
    address clone = I_CLONE_FACTORY.clone(deployFlowImplementation(), abi.encode(flowConfig));

    Vm.Log[] memory logs = vm.getRecordedLogs();
    Vm.Log[] memory initLogs =
        logs.findEvents(keccak256("FlowInitialized(address,(address,address,address))"));
    // Pin current behaviour: one event per config, even when duplicated.
    assertEq(initLogs.length, 2, "expected one FlowInitialized per config");

    // The (single) registered evaluable should still be callable.
    EvaluableV2 memory evaluable = EvaluableV2(INTERPRETER, STORE, expression);
    interpreterEval2MockCall(generateFlowStack(transferEmpty()), new uint256[](0));
    IFlowV5(clone).flow(evaluable, new uint256[](0), new SignedContextV1[](0));
}

Metadata

Metadata

Assignees

No one assigned

    Labels

    auditAudit findinginfoSeverity: infopass2Audit Pass 2: Test Coverage

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions