Summary
A potential arithmetic underflow bug exists in QuantAMM's pool initialization process when attempting to initialize a pool with a single asset. The bug occurs in _quantAMMPack128Array
when calculating sourceArrayLengthMinusTwo = sourceArrayLength - 2
for single-element arrays.
function _quantAMMPack128Array(int256[] memory _sourceArray) internal pure returns (int256[] memory targetArray) {
uint sourceArrayLength = _sourceArray.length;
uint targetArrayLength = sourceArrayLength;
uint storageIndex;
require(_sourceArray.length != 0, "LEN0");
if (_sourceArray.length % 2 == 0) {
} else {
targetArray = new int256[](targetArrayLength);
uint sourceArrayLengthMinusTwo = sourceArrayLength - 2;
}
}
Impact
Pool creators will experience failed transactions and waste gas when attempting to initialize single-asset pools, leading to actual financial costs every time the revert happens due to the underflow.
Proof of concept
Add testSingleElementArrayBug
test in pkg/pool-quantamm/test/foundry/rules/UpdateRule.t.sol
and run the test :)
function testSingleElementArrayBug() public {
vm.startPrank(owner);
int256[] memory singleMovingAverage = new int256[]();
singleMovingAverage[0] = PRBMathSD59x18.fromInt(1);
int256[] memory singleAlpha = new int256[]();
singleAlpha[0] = PRBMathSD59x18.fromInt(1);
updateRule.initialisePoolRuleIntermediateValues(
address(mockPool),
singleMovingAverage,
singleAlpha,
1
);
int256[][] memory parameters = new int256[][]();
parameters[0] = new int256[]();
parameters[0][0] = PRBMathSD59x18.fromInt(1);
uint64[] memory lambdas = new uint64[]();
lambdas[0] = uint64(0.7e18);
int256[] memory prevWeights = new int256[]();
prevWeights[0] = PRBMathSD59x18.fromInt(1);
int256[] memory data = new int256[]();
data[0] = PRBMathSD59x18.fromInt(1);
updateRule.CalculateNewWeights(
prevWeights,
data,
address(mockPool),
parameters,
lambdas,
uint64(0.1e18),
uint64(0.1e18)
);
vm.stopPrank();
}
The result logs of underflow:
[17536] UpdateRuleTest::testSingleElementArrayBug()
├─ [0] VM::startPrank(0x7E5F4552091A69125d5DfCb7b8C2659029395Bdf)
│ └─ ← [Return]
├─ [4174] MockUpdateRule::initialisePoolRuleIntermediateValues(0x0000000000000000000000000000000000000000, [1000000000000000000 [1e18]], [1000000000000000000 [1e18]], 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 14.86ms (2.54ms CPU time)
Ran 1 test suite in 1.77s (14.86ms CPU time): 0 tests passed, 1 failed, 0 skipped (1 total tests)
Failing tests:
Encountered 1 failing test in test/foundry/rules/UpdateRule.t.sol:UpdateRuleTest
[FAIL: panic: arithmetic underflow or overflow (0x11)] testSingleElementArrayBug() (gas: 17536)
Encountered a total of 1 failing tests, 0 tests succeeded
Recommendation
Add validation in UpdateRule.sol:
function initialisePoolRuleIntermediateValues(
address _poolAddress,
int256[] memory _newMovingAverages,
int256[] memory _newInitialValues,
uint _numberOfAssets
) external {
require(_numberOfAssets >= 2, "MIN_ASSETS");
require(msg.sender == _poolAddress || msg.sender == updateWeightRunner, "UNAUTH");
_setInitialMovingAverages(_poolAddress, _newMovingAverages, _numberOfAssets);
_setInitialIntermediateValues(_poolAddress, _newInitialValues, _numberOfAssets);
}
If single-asset pools are intended to be supported, modify the array packing logic in _quantAMMPack128Array
to handle single-element arrays safely.