#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?