QuantAMM

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

State Inconsistency in DifferenceMomentumUpdateRule Initialization

Summary

The DifferenceMomentumUpdateRule contract allows multiple initializations and fails to maintain consistent state between initializations, leading to corrupted pool states and incorrect weight calculations.

Vulnerability Details

function _setInitialIntermediateValues(
address _poolAddress,
int256[] memory _initialValues,
uint _numberOfAssets
) internal override {
require(_initialValues.length == _numberOfAssets, "Invalid initial values");
uint movingAverageLength = shortMovingAverages[_poolAddress].length;
if (movingAverageLength == 0 || _initialValues.length == _numberOfAssets) {
//should be during create pool
shortMovingAverages[_poolAddress] = _quantAMMPack128Array(_initialValues);
} else {
revert("Invalid set moving avg");
}
}

In the _setInitialIntermediateValues function in DifferenceMomentumUpdateRule there is no state tracking for initialization, the condition movingAverageLength == 0 || _initialValues.length == _numberOfAssets allows multiple initializations, and there is no validation of values ​​after packing.

function initialisePoolRuleIntermediateValues(
address _poolAddress,
int256[] memory _newMovingAverages,
int256[] memory _newInitialValues,
uint _numberOfAssets
) external {
require(msg.sender == _poolAddress || msg.sender == updateWeightRunner, "UNAUTH");
_setInitialMovingAverages(_poolAddress, _newMovingAverages, _numberOfAssets);
_setInitialIntermediateValues(_poolAddress, _newInitialValues, _numberOfAssets);
}

In the initialisePoolRuleIntermediateValues function in UpdateRule there is no locking mechanism after the first initialization that allows multiple calls from authorized addresses and there is no state validation after initialization.

POC

Add this to QuantAMMDifferenceMomentum.t.sol and run it forge test --match-test "testInconsistentStateAfterInitialization|testStateCorruptionViaMultipleInitializations" -vvvv.

function testInconsistentStateAfterInitialization() public {
// Setup
address poolAddress = address(mockPool);
int256[] memory initialValues = new int256[]();
initialValues[0] = 1e18;
initialValues[1] = 1e18;
// First initialization
vm.startPrank(owner);
rule.initialisePoolRuleIntermediateValues(poolAddress, initialValues, initialValues, 2);
// Get state after initialization
int256[] memory storedValues = rule.GetResultWeights();
// Verify state inconsistency
assertEq(storedValues.length, 0); // Will pass, showing the bug
assertNotEq(storedValues.length, initialValues.length); // Will pass, showing the bug
// Try to use these inconsistent values
int256[][] memory parameters = new int256[][]();
parameters[0] = new int256[]();
parameters[0][0] = PRBMathSD59x18.fromInt(1);
parameters[1] = new int256[]();
parameters[1][0] = 0.5e18;
// This should fail due to inconsistent state
rule.CalculateUnguardedWeights(
initialValues,
initialValues,
poolAddress,
parameters,
new int128[](1),
initialValues
);
vm.stopPrank();
}
function testStateCorruptionViaMultipleInitializations() public {
address poolAddress = address(mockPool);
int256[] memory initialValues = new int256[]();
initialValues[0] = 1e18;
initialValues[1] = 1e18;
vm.startPrank(owner);
// First initialization
rule.initialisePoolRuleIntermediateValues(poolAddress, initialValues, initialValues, 2);
// Second initialization with same values
rule.initialisePoolRuleIntermediateValues(poolAddress, initialValues, initialValues, 2);
// Verify state corruption
int256[] memory storedValues = rule.GetResultWeights();
assertEq(storedValues.length, 0); // Will pass, showing corrupted state
// Try to use corrupted state
int256[][] memory parameters = new int256[][]();
parameters[0] = new int256[]();
parameters[0][0] = PRBMathSD59x18.fromInt(1);
parameters[1] = new int256[]();
parameters[1][0] = 0.5e18;
rule.CalculateUnguardedWeights(
initialValues,
initialValues,
poolAddress,
parameters,
new int128[](1),
initialValues
);
vm.stopPrank();
}

Trace:

├─ [2773] MockDifferenceMomentumRule::GetResultWeights() [staticcall]
│ └─ ← [Return] []

An empty array is returned ([]) when it should contain weights, this proves the state was not saved correctly after initialization.

├─ [93103] MockDifferenceMomentumRule::initialisePoolRuleIntermediateValues(...)
│ └─ ← [Return]
├─ [5357] MockDifferenceMomentumRule::initialisePoolRuleIntermediateValues(...)
│ └─ ← [Return]

Two calls to initialisePoolRuleIntermediateValues were executed successfully, no revert on the second initialization, different gas used (93103 vs 5357) indicating state changed.

├─ [81276] MockDifferenceMomentumRule::CalculateUnguardedWeights(...)
│ └─ ← [Return]

The contract still accepts the calculation even though the state is invalid and there is no state validation before the calculation.

This allows manipulation of the state pool which can lead to invalid weight calculations.

Impact

Pool state corruption leading to incorrect weight calculations

Tools Used

  • Manual review

  • Foundry

Recommendations

Implement strict initialization controls.

contract DifferenceMomentumUpdateRule {
mapping(address => bool) private initialized;
function _setInitialIntermediateValues(
address _poolAddress,
int256[] memory _initialValues,
uint _numberOfAssets
) internal override {
require(!initialized[_poolAddress], "Already initialized");
require(_initialValues.length == _numberOfAssets, "Invalid initial values");
shortMovingAverages[_poolAddress] = _quantAMMPack128Array(_initialValues);
// Validate packed values
int256[] memory unpacked = _quantAMMUnpack128Array(
shortMovingAverages[_poolAddress],
_numberOfAssets
);
require(unpacked.length == _numberOfAssets, "Invalid packed state");
initialized[_poolAddress] = true;
emit PoolInitialized(_poolAddress, _initialValues);
}
modifier onlyUninitialized(address pool) {
require(!initialized[pool], "Already initialized");
_;
}
}
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!