QuantAMM

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

Precision and Underflow Vulnerabilities in Weight Calculation in the use of local.sign in PowerChannelUpdate.sol

Summary

The current implementation of the calculation:

locals.newWeights[locals.i] = locals.sign.mul(_pow(locals.intermediateRes.abs(), locals.q));

uses locals.sign to determine the sign of the result by multiplying with ONE (1e18) for positive values and -ONE (-1e18) for negative values. However, this approach unintentionally scales the result by 1e18 (or -1e18), leading to a value with 36 decimals, which is unintended.

Vulnerability Details

PowerChannelUpdate.sol

function _getWeights(
int256[] calldata _prevWeights,
int256[] memory _data,
int256[][] calldata _parameters,
QuantAMMPoolParameters memory _poolParameters
) internal override returns (int256[] memory newWeightsConverted) {
...
locals.intermediateRes = ONE.div(locals.denominator).mul(locals.newWeights[locals.i]);
unchecked {
locals.sign = locals.intermediateRes >= 0 ? ONE : -ONE;
}
//sign(1/p(t)*∂p(t)/∂t) * |1/p(t)*∂p(t)/∂t|^q
//stored as it is used in multiple places, saves on recalculation gas. _pow is quite expensive
@> locals.newWeights[locals.i] = locals.sign.mul(_pow(locals.intermediateRes.abs(), locals.q));
if (locals.kappa.length == 1) {
locals.normalizationFactor += locals.newWeights[locals.i];
} else {
locals.normalizationFactor += locals.kappa[locals.i].mul(locals.newWeights[locals.i]);
}
unchecked {
++locals.i;
}
}
// To avoid intermediate overflows (because of normalization), we only downcast in the end to an uint64
newWeightsConverted = new int256[](locals.prevWeightsLength);
if (locals.kappa.length == 1) {
locals.normalizationFactor /= int256(locals.prevWeightsLength);
for (locals.i = 0; locals.i < locals.prevWeightsLength; ) {
//κ · ( sign(1/p(t)*∂p(t)/∂t) * |1/p(t)*∂p(t)/∂t|^q − ℓp(t)
locals.res =
int256(_prevWeights[locals.i]) +
locals.kappa[0].mul(locals.newWeights[locals.i] - locals.normalizationFactor);
newWeightsConverted[locals.i] = locals.res;
unchecked {
++locals.i;
}
}
} else {
//vector parameter calculation, same as scalar but using the per constituent param inside the loops
int256 sumKappa;
for (locals.i = 0; locals.i < locals.kappa.length; ) {
sumKappa += locals.kappa[locals.i];
unchecked {
++locals.i;
}
}
locals.normalizationFactor = locals.normalizationFactor.div(sumKappa);
for (locals.i = 0; locals.i < _prevWeights.length; ) {
//κ · ( sign(1/p(t)*∂p(t)/∂t) * |1/p(t)*∂p(t)/∂t|^q − ℓp(t)
locals.res =
int256(_prevWeights[locals.i]) +
locals.kappa[locals.i].mul(locals.newWeights[locals.i] - locals.normalizationFactor);
require(locals.res >= 0, "Invalid weight");
newWeightsConverted[locals.i] = locals.res;
unchecked {
++locals.i;
}
}
}
return newWeightsConverted;
}

Impact

Results in unintended scaling of the output to 36 decimals, which may cause precision errors in subsequent computations. Could lead to overflow/underflow in locals.newWeights[locals.i] = locals.sign.mul(_pow(locals.intermediateRes.abs(), locals.q));

Intermediate Overflow or Underflow in _pow

  • Underflow: The _pow function calls exp2() on potentially large negative values of log2(x) * y, resulting in a return value of 0.

Tools Used

Manual review

Recommendations

To ensure that the calculation respects the intended precision without introducing scaling issues:

  • Directly apply the sign by negating _pow(locals.intermediateRes.abs(), locals.q) if the sign is negative, rather than multiplying by ONE.

The revised calculation should be:

locals.newWeights[locals.i] = (locals.intermediateRes >= 0)
? _pow(locals.intermediateRes.abs(), locals.q)
: -_pow(locals.intermediateRes.abs(), locals.q);
Updates

Lead Judging Commences

n0kto Lead Judge 7 months ago
Submission Judgement Published
Invalidated
Reason: Incorrect statement

Support

FAQs

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