Summary
The MinimumVarianceUpdateRule contract fails to properly validate parameter vector lengths against the number of assets and lambda values, leading to state inconsistencies and incorrect weight calculations.
Vulnerability Details
function _getWeights(
int256[] calldata _prevWeights,
int256[] memory _data,
int256[][] calldata _parameters,
QuantAMMPoolParameters memory _poolParameters
) internal override returns (int256[] memory newWeightsConverted) {
_poolParameters.numberOfAssets = _prevWeights.length;
if (_parameters[0].length == 1) {
} else {
}
}
_getWeights function on MinimumVarianceUpdateRule.
function CalculateNewWeights(
int256[] calldata _prevWeights,
int256[] calldata _data,
address _pool,
int256[][] calldata _parameters,
uint64[] calldata _lambdaStore,
uint64 _epsilonMax,
uint64 _absoluteWeightGuardRail
) external returns (int256[] memory updatedWeights) {
locals.lambda = new int128[](_lambdaStore.length);
}
CalculateNewWeights function in UpdateRule.
function _calculateQuantAMMVariance(
int256[] memory _newData,
QuantAMMPoolParameters memory _poolParameters
) internal returns (int256[] memory) {
if (_poolParameters.lambda.length == 1) {
} else {
}
}
_calculateQuantAMMVariance function in QuantammVarianceBasedRule.
The root of the problem is that there is no length validation between the parameter vector and the number of assets, no state consistency validation between the lambda and the parameter vector, and no intermediate state consistency check which can cause parameter vectors with inappropriate lengths to be accepted and weight calculations can be inaccurate.
POC
Add this to QuantAMMMinVariance.t.sol and run it forge test --match-test "testParameterVectorLengthMismatch|testStateInconsistencyWithVectorParameters" -vvvv.
function testParameterVectorLengthMismatch() public {
mockPool.setNumberOfAssets(2);
int256[][] memory parameters = new int256[][]();
parameters[0] = new int256[]();
parameters[0][0] = 0.5e18;
parameters[0][1] = 0.7e18;
int256[] memory prevWeights = new int256[]();
prevWeights[0] = 0.5e18;
prevWeights[1] = 0.5e18;
int256[] memory data = new int256[]();
data[0] = PRBMathSD59x18.fromInt(3);
data[1] = PRBMathSD59x18.fromInt(4);
int256[] memory prevMovingAverages = new int256[]();
prevMovingAverages[0] = PRBMathSD59x18.fromInt(1);
prevMovingAverages[1] = PRBMathSD59x18.fromInt(1) + 0.5e18;
prevMovingAverages[2] = PRBMathSD59x18.fromInt(1);
prevMovingAverages[3] = PRBMathSD59x18.fromInt(1) + 0.5e18;
int128[] memory lambda = new int128[]();
lambda[0] = 0.7e18;
rule.initialisePoolRuleIntermediateValues(
address(mockPool),
prevMovingAverages,
new int256[](2),
2
);
rule.CalculateUnguardedWeights(
prevWeights,
data,
address(mockPool),
parameters,
lambda,
prevMovingAverages
);
}
function testStateInconsistencyWithVectorParameters() public {
mockPool.setNumberOfAssets(3);
int256[][] memory parameters = new int256[][]();
parameters[0] = new int256[]();
parameters[0][0] = 0.5e18;
parameters[0][1] = 0.7e18;
parameters[0][2] = 0.6e18;
int256[] memory prevWeights = new int256[]();
prevWeights[0] = 0.33e18;
prevWeights[1] = 0.33e18;
prevWeights[2] = 0.34e18;
int256[] memory data = new int256[]();
data[0] = PRBMathSD59x18.fromInt(3);
data[1] = PRBMathSD59x18.fromInt(4);
data[2] = PRBMathSD59x18.fromInt(5);
int256[] memory prevMovingAverages = new int256[]();
for(uint i = 0; i < 6; i++) {
prevMovingAverages[i] = PRBMathSD59x18.fromInt(1);
}
int128[] memory lambda = new int128[]();
lambda[0] = 0.7e18;
rule.initialisePoolRuleIntermediateValues(
address(mockPool),
prevMovingAverages,
new int256[](3),
3
);
rule.CalculateUnguardedWeights(
prevWeights,
data,
address(mockPool),
parameters,
lambda,
prevMovingAverages
);
}
Trace:
testParameterVectorLengthMismatch
MockMinimumVarianceRule::CalculateUnguardedWeights(
[500000000000000000 [5e17], 500000000000000000 [5e17]],
[3000000000000000000 [3e18], 4000000000000000000 [4e18]],
MockPool: [0x2e234DAe...],
[[500000000000000000 [5e17], 700000000000000000 [7e17]]],
[700000000000000000 [7e17]],
[...]
)
This test passes (PASS) even though there is a mismatch between the vector length = 2 and lambda length = 1 parameters. This indicates the contract does not validate the consistency between the vector and lambda parameters.
testStateInconsistencyWithVectorParameters
MockMinimumVarianceRule::CalculateUnguardedWeights(
[330000000000000000 [3.3e17], 330000000000000000 [3.3e17], 340000000000000000 [3.4e17]],
[3000000000000000000 [3e18], 4000000000000000000 [4e18], 5000000000000000000 [5e18]],
MockPool: [0x2e234DAe...],
[[500000000000000000 [5e17], 700000000000000000 [7e17], 600000000000000000 [6e17]]],
[700000000000000000 [7e17]],
[...]
)
This test also PASS even though it uses 3 assets with parameter vector length = 3 but only uses a single lambda and there is no state consistency validation.
Impact
Incorrect weight calculations due to mismatched parameter lengths
Tools Used
Recommendations
Add proper validation in MinimumVarianceUpdateRule.
function _getWeights(
int256[] calldata _prevWeights,
int256[] memory _data,
int256[][] calldata _parameters,
QuantAMMPoolParameters memory _poolParameters
) internal override returns (int256[] memory newWeightsConverted) {
_poolParameters.numberOfAssets = _prevWeights.length;
require(_parameters.length == 1, "Invalid parameters length");
require(
_parameters[0].length == 1 ||
_parameters[0].length == _poolParameters.numberOfAssets,
"Invalid parameter vector length"
);
require(
_parameters[0].length == 1 ||
_parameters[0].length == _poolParameters.lambda.length,
"Parameter and lambda length mismatch"
);
if (_parameters[0].length == 1) {
} else {
}
}
Add state validation in QuantammVarianceBasedRule.
require(
_poolParameters.lambda.length == 1 ||
_poolParameters.lambda.length == _poolParameters.numberOfAssets,
"Invalid lambda vector length"
);