QuantAMM

QuantAMM
49,600 OP
View results
Submission Details
Severity: medium
Valid

The `maxTradeSizeRatio` can be bypassed due to incorrect logic in `onSwap` function

Summary

The code contains a logical vulnerability that arises from the handling of the maxTradeSizeRatio for both directions of the swap. Specifically, in EXACT_IN direction, the maxTradeSizeRatio is used to restrict the amount of the swapped in token and in EXACT_OUT direction, it is used to restrict the amount of the swapped out token. This inconsistency allows an attacker to exploit the system by structuring their trades in a way that allows them to get around the maximum trade size checks, effectively bypassing the maxTradeSizeRatio limits.

Vulnerability Details

In QuantAMMWeightedPool,the maxTradeSizeRatio is used to restrict maximum trade size allowed as a fraction of the pool. It is used in onSwap function:

if (request.kind == SwapKind.EXACT_IN) {
if (request.amountGivenScaled18 > request.balancesScaled18[request.indexIn].mulDown(maxTradeSizeRatio)) {
revert maxTradeSizeRatioExceeded();
}
uint256 amountOutScaled18 = WeightedMath.computeOutGivenExactIn(
request.balancesScaled18[request.indexIn],
tokenInWeight,
request.balancesScaled18[request.indexOut],
tokenOutWeight,
request.amountGivenScaled18
);
return amountOutScaled18;
} else {
// Cannot exceed maximum out ratio
if (request.amountGivenScaled18 > request.balancesScaled18[request.indexOut].mulDown(maxTradeSizeRatio)) {
revert maxTradeSizeRatioExceeded();
}
uint256 amountInScaled18 = WeightedMath.computeInGivenExactOut(
request.balancesScaled18[request.indexIn],
tokenInWeight,
request.balancesScaled18[request.indexOut],
tokenOutWeight,
request.amountGivenScaled18
);
// Fees are added after scaling happens, to reduce the complexity of the rounding direction analysis.
return amountInScaled18;
}

The issue here is that it is used in two direction, making it possible to bypass the maxTradeSizeRatio limits.

Let's say there are 1000 tokenA and 2000 tokenB, the maxTradeSizeRatio is 0.3. Now we can swap 200 tokenA to 610 tokenB.

If we use SwapKind.EXACT_OUT swap, then

request.amountGivenScaled18=610

request.balancesScaled18[request.indexOut].mulDown(maxTradeSizeRatio)=2000 * 0.3 = 600

Since 610>600, the swap will revert.

If we change the swap to SwapKind.EXACT_IN, then

request.amountGivenScaled18=200

request.balancesScaled18[request.indexIn].mulDown(maxTradeSizeRatio)=1000*0.3 = 300

Since 200<300, the swap will succeed.

This inconsistency allows an attacker to exploit the system by structuring their trades in a way that allows them to get around the maximum trade size checks, effectively bypassing the limits imposed on the outputs for EXACT_OUT trades. The EXACT_IN pathway becomes a backdoor for output amounts that breach the intended maximum size, risking liquidity and profitability for the pool.

Impact

The impact is MEDIUM and the likelihood is MEDIUM, as a result, the severity should be MEDIUM.

Tools Used

Manual Review

Recommendations

Consider checking the amountOutScaled18 when request.kind == SwapKind.EXACT_IN

if (request.kind == SwapKind.EXACT_IN) {
uint256 amountOutScaled18 = WeightedMath.computeOutGivenExactIn(
request.balancesScaled18[request.indexIn],
tokenInWeight,
request.balancesScaled18[request.indexOut],
tokenOutWeight,
request.amountGivenScaled18
);
if (amountOutScaled18 > request.balancesScaled18[request.indexOut].mulDown(maxTradeSizeRatio)) {
revert maxTradeSizeRatioExceeded();
}
return amountOutScaled18;
}
Updates

Lead Judging Commences

n0kto Lead Judge 5 months ago
Submission Judgement Published
Validated
Assigned finding tags:

finding_onSwap_exact_in_swap_check_input

Likelihood: Medium, any “exact_in” swap only if there is a price pike of one token. Impact: Medium, bypass the maxTradeSizeRatio check.

Support

FAQs

Can't find an answer? Chat with us on Discord, Twitter or Linkedin.