QuantAMM

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

Unchecked Bit Shift Counter Underflow Corrupts AMM Pool State Calculations

Summary

The quantAMMUnpack32Array function in ScalarQuantAMMBaseStorage contract contains a dangerous bit shifting vulnerability that undermines the integrity of pool state calculations. At the core of the function's sticky end handling logic lies an unsafe decrementing counter used for unpacking partial data slots.

Each packed 256-bit storage slot holds multiple 32-bit values that need to be extracted sequentially. The function uses an unchecked offset counter that starts at 224 and decrements by 32 with each value extraction. However, the logic fails to account for the fixed size of a storage slot:

https://github.com/Cyfrin/2024-12-quantamm/blob/main/pkg/pool-quantamm/contracts/QuantAMMStorage.sol#L193

if (!divisibleByEight) {
unchecked {
uint offset = 224;
for (uint i = targetIndex; i < targetArray.length; ) {
targetArray[i] = int256(int32(_sourceArray[stickyEndSourceElem] >> offset)) * 1e9;
offset -= 32; // Potential underflow
++i;
}
}
}

The implications for pool operations are severe - after seven iterations (dealing with the maximum values a 256-bit slot can hold), any additional iteration causes the offset to underflow. Since this occurs in an unchecked block, instead of reverting, the offset wraps to a massive number. When this corrupted offset is used for bit shifting, it extracts meaningless data that gets fed into the pool's variance calculations and exponential moving averages, poisoning the mathematical foundation of the AMM's pricing and rebalancing mechanisms.

Recommended mitigation steps

  1. Add bounds checking for the offset:

if (!divisibleByEight) {
int256 offset = 224; // Changed to signed integer
for (uint i = targetIndex; i < targetArray.length; ) {
require(offset >= 0, "Offset underflow");
targetArray[i] = int256(int32(_sourceArray[stickyEndSourceElem] >> uint256(offset))) * 1e9;
offset -= 32;
++i;
}
}
  1. Alternatively, limit the number of iterations explicitly:

if (!divisibleByEight) {
unchecked {
uint offset = 224;
for (uint i = targetIndex; i < targetArray.length && offset >= 32; ) {
targetArray[i] = int256(int32(_sourceArray[stickyEndSourceElem] >> offset)) * 1e9;
offset -= 32;
++i;
}
}
}
  1. Consider adding array length validation:

require(targetArray.length <= targetIndex + 7, "Too many sticky end elements");
Updates

Lead Judging Commences

n0kto Lead Judge 10 months ago
Submission Judgement Published
Invalidated
Reason: Non-acceptable severity
Assigned finding tags:

Informational or Gas / Admin is trusted / Pool creation is trusted / User mistake / Suppositions

Please read the CodeHawks documentation to know which submissions are valid. If you disagree, provide a coded PoC and explain the real likelyhood and the detailed impact on the mainnet without any supposition (if, it could, etc) to prove your point.

Support

FAQs

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