Summary
In the QuantAMMStorage.sol::ScalarRuleQuantAMMStorage contract, there's a function called _quantAMMPack128Array to pack 128 bits integers array. However, function fails to pack array of length 1.
Vulnerability Details
QuantAMMStorage.sol::ScalarRuleQuantAMMStorage::_quantAMMPack128Array:
function _quantAMMPack128Array(int256[] memory _sourceArray) internal pure returns (int256[] memory targetArray) {
uint256 sourceArrayLength = _sourceArray.length;
uint256 targetArrayLength = sourceArrayLength;
uint256 storageIndex;
require(_sourceArray.length != 0, "LEN0");
if (_sourceArray.length % 2 == 0) {
unchecked {
targetArrayLength = (targetArrayLength) / 2;
}
targetArray = new int256[](targetArrayLength);
for (uint256 i; i < sourceArrayLength - 1;) {
targetArray[storageIndex] = _quantAMMPackTwo128(_sourceArray[i], _sourceArray[i + 1]);
unchecked {
i += 2;
++storageIndex;
}
}
} else {
int256 lastArrayItem = _sourceArray[_sourceArray.length - 1];
require(
(lastArrayItem >= int256(type(int128).min)) && (lastArrayItem <= int256(type(int128).max)),
"Last array element overflow"
);
unchecked {
targetArrayLength = ((targetArrayLength - 1) / 2) + 1;
}
targetArray = new int256[](targetArrayLength);
@>
@> uint256 sourceArrayLengthMinusTwo = sourceArrayLength - 2;
for (uint256 i; i < sourceArrayLengthMinusTwo;) {
targetArray[storageIndex] = _quantAMMPackTwo128(_sourceArray[i], _sourceArray[i + 1]);
unchecked {
i += 2;
++storageIndex;
}
}
targetArray[storageIndex] = int256(int128(_sourceArray[sourceArrayLength - 1]));
}
}
When the source array length is 1 then, in that case the local variable sourceArrayLengthMinusTwo can't be calculated because 1-2= -1 and sourceArrayLength - 2 = -1 and solidity version >=0.8.x has unchecked protection. So in that case there would be a Denial of Service (DoS) issue due to over/underflow error.
PoC
-
Go to the test/foundry/QuantAMMStorage.t.sol
-
Paste the following test snippet inside the testing contract.
function testDoSOn128ArrayPackFails() public view {
int256[] memory targetValues = new int256[]();
targetValues[0] = 4e18;
int256[] memory redecoded = mockQuantAMMStorage.ExternalEncodeDecode128Array(targetValues, 1);
checkResult(redecoded, targetValues);
}
Open bash terminal (make sure you're on the right directory 2024-12-quantamm/pkg/pool-quantamm)
forge test --mt testDoSOn128ArrayPackFails -vvvv
See the result logs
[⠢] Compiling...
No files changed, compilation skipped
Ran 1 test for test/foundry/QuantAMMStorage.t.sol:QuantAMMStorageTest
[FAIL. Reason: panic: arithmetic underflow or overflow (0x11)] testDoSOn128ArrayPackFails() (gas: 6952)
Traces:
[1861769] QuantAMMStorageTest::setUp()
├─ [1804896] → new MockQuantAMMStorage@0x5615dEB798BB3E4dFa0139dFa1b3D433Cc23b72f
│ └─ ← [Return] 9015 bytes of code
└─ ← [Stop]
[6952] QuantAMMStorageTest::testDoSOn128ArrayPackFails()
├─ MockQuantAMMStorage::ExternalEncodeDecode128Array(, 1)
│ └─ ← [Revert] panic: arithmetic underflow or overflow (0x11)
└─ ← [Revert] panic: arithmetic underflow or overflow (0x11)
Suite result: FAILED. 0 passed; 1 failed; 0 skipped; finished in 1.44ms (142.10µs CPU time)
Ran 1 test suite in 1.02s (1.44ms CPU time): 0 tests passed, 1 failed, 0 skipped (1 total tests)
Failing tests:
Encountered 1 failing test in test/foundry/QuantAMMStorage.t.sol:QuantAMMStorageTest
[FAIL. Reason: panic: arithmetic underflow or overflow (0x11)] testDoSOn128ArrayPackFails() (gas: 6952)
Encountered a total of 1 failing tests, 0 tests succeeded
The MockQuantAMMStorage::ExternalEncodeDecode128Array calls the _quantAMMPack128Array function internally, this utility is brought to us by You. So we can see it's failing and a DoS occurs due to an over/underflow. This is an edge case.
Impact
DoS due to over/underflow mishandling
Severe disruption in protocol's functionality, almost all functionalities get affected due to this issue i.e., Pool creation, Weight updates, Swaps and liquidity operations etc.
Tools used
Manual review
chisel
console
Recommendations
We can implement a check to allow to do such arithmetic operation when source array length is greater than 1. One similar solution is given below:
QuantAMMStorage.sol::ScalarRuleQuantAMMStorage::_quantAMMPack128Array:
function _quantAMMPack128Array(int256[] memory _sourceArray) internal pure returns (int256[] memory targetArray) {
uint256 sourceArrayLength = _sourceArray.length;
uint256 targetArrayLength = sourceArrayLength;
uint256 storageIndex;
require(_sourceArray.length != 0, "LEN0");
if (_sourceArray.length % 2 == 0) {
unchecked {
targetArrayLength = (targetArrayLength) / 2;
}
targetArray = new int256[](targetArrayLength);
for (uint256 i; i < sourceArrayLength - 1;) {
targetArray[storageIndex] = _quantAMMPackTwo128(_sourceArray[i], _sourceArray[i + 1]);
unchecked {
i += 2;
++storageIndex;
}
}
} else {
int256 lastArrayItem = _sourceArray[_sourceArray.length - 1];
require(
(lastArrayItem >= int256(type(int128).min)) && (lastArrayItem <= int256(type(int128).max)),
"Last array element overflow"
);
unchecked {
targetArrayLength = ((targetArrayLength - 1) / 2) + 1;
}
targetArray = new int256[](targetArrayLength);
+ if (sourceArrayLength > 1) {
uint256 sourceArrayLengthMinusTwo = sourceArrayLength - 2;
for (uint256 i; i < sourceArrayLengthMinusTwo;) {
targetArray[storageIndex] = _quantAMMPackTwo128(_sourceArray[i], _sourceArray[i + 1]);
unchecked {
i += 2;
++storageIndex;
}
}
+ }
targetArray[storageIndex] = int256(int128(_sourceArray[sourceArrayLength - 1]));
}
}