#39528 [SC-Insight] Lack of Validation for Min and Max Values in FlatCFMFactory leads to wrong payou

Submitted on Jan 31st 2025 at 22:06:32 UTC by @NHristov for Audit Comp | Butter

  • Report ID: #39528

  • Report Type: Smart Contract

  • Report severity: Insight

  • Target: https://github.com/immunefi-team/audit-comp-butter-cfm-v1

  • Impacts:

    • Contract fails to deliver promised returns, but doesn't lose value

    • Griefing (e.g. no profit motive for an attacker, but damage to the users or the protocol)

Description

Brief/Intro

Min and max values are not validated when creating a FlatCFM, allowing minValue == maxValue. This results in the scalar market incorrectly resolving all payout to the short tokens if the numeric answer equals that boundary.

Vulnerability Details

Because both min and max can be the same when creating a FlatCFM generic question params for the conditional scalar markets the metric question resolution interprets that numeric answer as valid for short tokens.

function createFlatCFM(
        FlatCFMOracleAdapter oracleAdapter,
        uint256 decisionTemplateId,
        uint256 metricTemplateId,
        FlatCFMQuestionParams calldata flatCFMQParams,
        GenericScalarQuestionParams calldata genericScalarQuestionParams,
        IERC20 collateralToken,
        string calldata metadataUri
    ) external payable returns (FlatCFM cfm) {
        uint256 outcomeCount = flatCFMQParams.outcomeNames.length;
        if (outcomeCount == 0 || outcomeCount > MAX_OUTCOME_COUNT) {
            revert InvalidOutcomeCount();
        }
        for (uint256 i = 0; i < outcomeCount; i++) {
            string memory outcomeName = flatCFMQParams.outcomeNames[i];
            if (bytes(outcomeName).length > MAX_OUTCOME_NAME_LENGTH) revert InvalidOutcomeNameLength(outcomeName);
        }

        cfm = FlatCFM(flatCfmImplementation.clone());

        bytes32 decisionQuestionId =
            oracleAdapter.askDecisionQuestion{value: msg.value}(decisionTemplateId, flatCFMQParams);

        // +1 for 'Invalid' slot.
        bytes32 decisionConditionId =
            conditionalTokens.getConditionId(address(cfm), decisionQuestionId, outcomeCount + 1);
        if (conditionalTokens.getOutcomeSlotCount(decisionConditionId) == 0) {
            conditionalTokens.prepareCondition(address(cfm), decisionQuestionId, outcomeCount + 1);
        }

        paramsToDeploy[cfm] = DeploymentParams({
            collateralToken: collateralToken,
            metricTemplateId: metricTemplateId,
            genericScalarQuestionParams: genericScalarQuestionParams,
            decisionConditionId: decisionConditionId,
            outcomeNames: flatCFMQParams.outcomeNames
        });

        cfm.initialize(oracleAdapter, conditionalTokens, outcomeCount, decisionQuestionId, metadataUri);

        emit FlatCFMCreated(address(cfm), decisionConditionId);
    }

When we call FlatCFMFactory::createConditionalScalarMarket to create the conditional scalar market everything will work as expected. However, once the answer is provided by the oracle and if the numeric answer equals the same value as the min and max (since they are equal), the payouts will be received only from the short tokens.

Impact Details

Funds can be diverted solely to the short outcome, causing mispricing and potentially incorrect compensation for other positions if exploited to the bug.

References

https://github.com/immunefi-team/audit-comp-butter-cfm-v1/blob/045ab0ec86fd9a3f7cd0b0cd4068d75c46d2e316/src/FlatCFMFactory.sol#L103C5-L144C6

https://github.com/immunefi-team/audit-comp-butter-cfm-v1/blob/045ab0ec86fd9a3f7cd0b0cd4068d75c46d2e316/src/ConditionalScalarMarket.sol#L72C5-L91C6

Proof of Concept

From the unit test folder, in the CondtionalScalarMarket.t.sol paste the following code to prove that if the min and max values are the same and only short tokens will receive payouts

and to prove that a cfm can be created with equal min and max generic scalar parameters paste this at the bottom of the file FlatCFMFactory.t.sol

Last updated

Was this helpful?