Summary
The QuantAMMWeightedPoolFactory contract initializes pools with possible mismatched token counts and weights, leading to operational issues. The contract uses the length of normalizedWeights array to determine the number of tokens the pool will manage, instead of validating against the actual number of tokens provided. And both normalizedWeightsand tokens are not validated to be equal.
Vulnerability Details
Here's the NewPoolParams struct that declares both tokens and normalizedWeights:
struct NewPoolParams {
TokenConfig[] tokens;
uint256[] normalizedWeights;
}
https://github.com/Cyfrin/2024-12-quantamm/blob/a775db4273eb36e7b4536c5b60207c9f17541b92/pkg/pool-quantamm/contracts/QuantAMMWeightedPoolFactory.sol#L43C8-L44C37
However, in pool creation, normalizedWeights is wrongly passed as the number of tokens:
pool = _create(abi.encode(
QuantAMMWeightedPool.NewPoolParams({
name: params.name,
symbol: params.symbol,
numTokens: params.normalizedWeights.length,
version: "version",
updateWeightRunner: _updateWeightRunner,
poolRegistry: params.poolRegistry,
poolDetails: params.poolDetails
}),
getVault()
), params.salt);
https://github.com/Cyfrin/2024-12-quantamm/blob/a775db4273eb36e7b4536c5b60207c9f17541b92/pkg/pool-quantamm/contracts/QuantAMMWeightedPoolFactory.sol#L99
https://github.com/Cyfrin/2024-12-quantamm/blob/a775db4273eb36e7b4536c5b60207c9f17541b92/pkg/pool-quantamm/contracts/QuantAMMWeightedPoolFactory.sol#L144
There could be a scenario where there are more weights than token or more tokens than weights:
Scenario 1 - More Weights Than Tokens:
tokens = [WETH, USDC]
weights = [0.3, 0.3, 0.4]
Result: Pool expects 3 tokens but only has 2
Scenario 2 - More Tokens Than Weights:
tokens = [WETH, USDC, DAI]
weights = [0.5, 0.5]
Result: Pool missing weight for third token
This is because normalizedWeightsand token arrays length are not checked to be equal.
Impact
Token array length can differ from weight array length causing use of incorrect token count.
Tools Used
Manual review
Recommendations
Validate tokens and pass tokens to the numTokens parameter:function validateTokenConfiguration(
TokenConfig[] memory tokens,
uint256[] memory weights
) internal pure {
require(tokens.length == weights.length, "Token and weight counts must match");
require(tokens.length >= MIN_TOKENS, "Insufficient tokens");
require(tokens.length <= MAX_TOKENS, "Too many tokens");
for (uint256 i = 0; i < tokens.length - 1; i++) {
for (uint256 j = i + 1; j < tokens.length; j++) {
require(tokens[i].token ! = tokens[j].token, "Duplicate tokens");
}
}
}
function create(NewPoolParams memory params) external returns (address pool, bytes memory poolArgs) {
validateTokenConfiguration(params.tokens, params.normalizedWeights);
pool = _create(abi.encode(
QuantAMMWeightedPool.NewPoolParams({
numTokens: params.tokens.length,
}),
getVault()
), params.salt);
}