Summary
The _setInitialWeights
function in the QuantAMMWeightedPool
contract checks if _normalizedFirstFourWeights
and _normalizedSecondFourWeights
are zero to ensure they are not re-initialized. However, this check might not be sufficient if the weights are set to zero initially, leading to potential re-initialization and unintended behavior.
Vulnerability Details
The _setInitialWeights
function is designed to set the initial weights for the pool. It includes checks to ensure that the weights are not re-initialized by verifying if _normalizedFirstFourWeights
and _normalizedSecondFourWeights
are zero. However, if the weights are set to zero initially, these checks will pass, allowing re-initialization.
https://github.com/Cyfrin/2024-12-quantamm/blob/a775db4273eb36e7b4536c5b60207c9f17541b92/pkg/pool-quantamm/contracts/QuantAMMWeightedPool.sol#L644-L664
function _setInitialWeights(int256[] memory _weights) internal {
require(_normalizedFirstFourWeights == 0, "init");
require(_normalizedSecondFourWeights == 0, "init");
InputHelpers.ensureInputLengthMatch(_totalTokens, _weights.length);
int256 normalizedSum;
int256[] memory _weightsAndBlockMultiplier = new int256[]();
for (uint i; i < _weights.length; ) {
if (_weights[i] < int256(uint256(absoluteWeightGuardRail))) {
revert MinWeight();
}
_weightsAndBlockMultiplier[i] = _weights[i];
normalizedSum += _weights[i];
_weightsAndBlockMultiplier[i + _weights.length] = int256(0);
unchecked {
++i;
}
}
if (uint256(normalizedSum) != FixedPoint.ONE) {
revert NormalizedWeightInvariant();
}
if (_weightsAndBlockMultiplier.length > 8) {
int256[][] memory splitWeights = _splitWeightAndMultipliers(_weightsAndBlockMultiplier);
_normalizedFirstFourWeights = quantAMMPack32Array(splitWeights[0])[0];
_normalizedSecondFourWeights = quantAMMPack32Array(splitWeights[1])[0];
} else {
_normalizedFirstFourWeights = quantAMMPack32Array(_weightsAndBlockMultiplier)[0];
}
poolSettings.quantAMMBaseInterpolationDetails = QuantAMMBaseInterpolationVariables({
lastPossibleInterpolationTime: uint40(block.timestamp),
lastUpdateIntervalTime: uint40(block.timestamp)
});
emit WeightsUpdated(address(this), _weights);
}
PoC:
pragma solidity ^0.8.24;
import "forge-std/Test.sol";\
import "../src/QuantAMMWeightedPool.sol";
contract QuantAMMWeightedPoolTest is Test {\
QuantAMMWeightedPool pool;
function setUp() public {
int256[] memory initialWeights = new int256[]();
pool = new QuantAMMWeightedPool(
QuantAMMWeightedPool.NewPoolParams({
name: "Test Pool",
symbol: "TP",
numTokens: 8,
version: "1.0",
updateWeightRunner: address(this),
poolRegistry: 1,
poolDetails: new string[][]()
}),
IVault(address(this))
);
pool.initialize(initialWeights, poolSettings, new int256[](0), new int256[](), 3600);
}
function testReinitializeWeights() public {
int256[] memory newWeights = new int256[]();
newWeights[0] = 1e18;
newWeights[1] = 1e18;
newWeights[2] = 1e18;
newWeights[3] = 1e18;
newWeights[4] = 1e18;
newWeights[5] = 1e18;
newWeights[6] = 1e18;
newWeights[7] = 1e18;
pool._setInitialWeights(newWeights);
}
The test will fail, indicating that the weights were re-initialized despite the initial check.
Impact
The vulnerability allows re-initialization of weights if they are initially set to zero. This can lead to unintended behavior and potential manipulation of the pool's state.
Tools Used
Manual review.
Recommendations
To mitigate this issue, add a separate flag to track whether the weights have been initialized. This flag should be set to true after the initial weights are set and checked before allowing any re-initialization.
bool private weightsInitialized;
function _setInitialWeights(int256[] memory _weights) internal {
require(!weightsInitialized, "Weights already initialized");
InputHelpers.ensureInputLengthMatch(_totalTokens, _weights.length);
int256 normalizedSum;
int256[] memory _weightsAndBlockMultiplier = new int256[]();
for (uint i; i < _weights.length; ) {
if (_weights[i] < int256(uint256(absoluteWeightGuardRail))) {
revert MinWeight();
}
_weightsAndBlockMultiplier[i] = _weights[i];
normalizedSum += _weights[i];
_weightsAndBlockMultiplier[i + _weights.length] = int256(0);
unchecked {
++i;
}
}
if (uint256(normalizedSum) != FixedPoint.ONE) {
revert NormalizedWeightInvariant();
}
if (_weightsAndBlockMultiplier.length > 8) {
int256[][] memory splitWeights = _splitWeightAndMultipliers(_weightsAndBlockMultiplier);
_normalizedFirstFourWeights = quantAMMPack32Array(splitWeights[0])[0];
_normalizedSecondFourWeights = quantAMMPack32Array(splitWeights[1])[0];
} else {
_normalizedFirstFourWeights = quantAMMPack32Array(_weightsAndBlockMultiplier)[0];
}
poolSettings.quantAMMBaseInterpolationDetails = QuantAMMBaseInterpolationVariables({
lastPossibleInterpolationTime: uint40(block.timestamp),
lastUpdateIntervalTime: uint40(block.timestamp)
});
weightsInitialized = true;
emit WeightsUpdated(address(this), _weights);
}