QuantAMM

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

Inconsistent Parameter Vector Length Validation in MinimumVarianceUpdateRule

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;
// Issue 1: No validation between _parameters[0].length and numberOfAssets
if (_parameters[0].length == 1) {
// scalar path
} else {
// vector path - no vector length validation
}
}

_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) {
// Issue 2: No consistency validation between:
// - _parameters[0].length
// - _lambdaStore.length
// - _prevWeights.length
locals.lambda = new int128[](_lambdaStore.length);
}

CalculateNewWeights function in UpdateRule.

function _calculateQuantAMMVariance(
int256[] memory _newData,
QuantAMMPoolParameters memory _poolParameters
) internal returns (int256[] memory) {
// Issue 3: State inconsistency due to no validation:
// - _poolParameters.lambda.length vs _poolParameters.numberOfAssets
// - intermediateVarianceState length vs numberOfAssets
if (_poolParameters.lambda.length == 1) {
// scalar path
} else {
// vector path - no state validation
}
}

_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 {
// Setup pool with 2 assets
mockPool.setNumberOfAssets(2);
// Setup parameter vector with incorrect length
int256[][] memory parameters = new int256[][]();
parameters[0] = new int256[](); // Length 2 for 2 assets
parameters[0][0] = 0.5e18;
parameters[0][1] = 0.7e18;
// Setup weights and data
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);
// Setup moving averages
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;
// Setup lambda
int128[] memory lambda = new int128[]();
lambda[0] = 0.7e18;
// Initialize pool state
rule.initialisePoolRuleIntermediateValues(
address(mockPool),
prevMovingAverages,
new int256[](2), // Empty variances
2 // Number of assets
);
// Attempt calculation that should have failed
rule.CalculateUnguardedWeights(
prevWeights,
data,
address(mockPool),
parameters,
lambda,
prevMovingAverages
);
}
function testStateInconsistencyWithVectorParameters() public {
mockPool.setNumberOfAssets(3); // Test with 3 assets
// Setup vector parameters with different lengths
int256[][] memory parameters = new int256[][]();
parameters[0] = new int256[](); // Vector parameters for 3 assets
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;
// Initialize with inconsistent state
rule.initialisePoolRuleIntermediateValues(
address(mockPool),
prevMovingAverages,
new int256[](3),
3
);
// Attempt calculation that will show state inconsistencies
rule.CalculateUnguardedWeights(
prevWeights,
data,
address(mockPool),
parameters,
lambda,
prevMovingAverages
);
}

Trace:

testParameterVectorLengthMismatch

MockMinimumVarianceRule::CalculateUnguardedWeights(
[500000000000000000 [5e17], 500000000000000000 [5e17]], // prevWeights length = 2
[3000000000000000000 [3e18], 4000000000000000000 [4e18]], // data length = 2
MockPool: [0x2e234DAe...],
[[500000000000000000 [5e17], 700000000000000000 [7e17]]], // parameters length = 2
[700000000000000000 [7e17]], // lambda length = 1
[...] // movingAverages
)

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]], // 3 assets
[3000000000000000000 [3e18], 4000000000000000000 [4e18], 5000000000000000000 [5e18]],
MockPool: [0x2e234DAe...],
[[500000000000000000 [5e17], 700000000000000000 [7e17], 600000000000000000 [6e17]]], // 3 parameters
[700000000000000000 [7e17]], // single lambda
[...] // movingAverages
)

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

  • Manual review

  • Foundry

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;
// Add validation
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) {
// scalar path
} else {
// vector path
}
}

Add state validation in QuantammVarianceBasedRule.

require(
_poolParameters.lambda.length == 1 ||
_poolParameters.lambda.length == _poolParameters.numberOfAssets,
"Invalid lambda vector length"
);
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.

Give us feedback!