Summary
In the contract UpdateWeightRunner, there's a function called setWeightsManually which is only admin and pool manager protected. The function is a breakglass function to allow the admin or the pool manager to set the quantammAdmins weights manually. Everything is okay but an admin or a pool manager can't set weight to 1e18. This oversight may emerge some potential issues i.e., impermanent loss amplification, pool imbalance, inconsistent LP returns, economic exploits, etc.
Vulnerability Details
UpdateWeightRunner::setWeightsManually:
function setWeightsManually(
int256[] calldata _weights,
address _poolAddress,
uint40 _lastInterpolationTimePossible,
uint256 _numberOfAssets
) external {
uint256 poolRegistryEntry = QuantAMMWeightedPool(_poolAddress).poolRegistry();
if (poolRegistryEntry & MASK_POOL_OWNER_UPDATES > 0) {
require(msg.sender == poolRuleSettings[_poolAddress].poolManager, "ONLYMANAGER");
} else if (poolRegistryEntry & MASK_POOL_QUANTAMM_ADMIN_UPDATES > 0) {
require(msg.sender == quantammAdmin, "ONLYADMIN");
} else {
revert("No permission to set weight values");
}
for (uint256 i; i < _weights.length; i++) {
if (i < _numberOfAssets) {
require(_weights[i] > 0, "Negative weight not allowed");
@>
@> require(_weights[i] < 1e18, "greater than 1 weight not allowed");
------------------------------------^
}
}
IQuantAMMWeightedPool(_poolAddress).setWeights(_weights, _poolAddress, _lastInterpolationTimePossible);
emit SetWeightManual(msg.sender, _poolAddress, _weights, _lastInterpolationTimePossible);
}
It's clear that admin or pool manager can't set weights to 100% or 1e18. Issue is clear and bare to see so it doesn't need any PoC therefore code itself is a PoC.
Impact
-
Pool Imbalance
Effect: The pool's total weight would not sum to 1 (100%), leading to undefined or erroneous behavior in the pricing and trading functions.
Impact: This can distort the relative valuation of assets in the pool, causing incorrect pricing during swaps.
-
Erroneous Token Distribution
Effect: The misaligned weights will directly affect how liquidity is distributed among tokens in the pool.
Impact:
Overweighted tokens may dominate liquidity.
Underweighted tokens may have insufficient liquidity, increasing slippage during trades.
-
Impermanent Loss Amplification
Effect: Incorrect weights can exacerbate impermanent loss by deviating from the pool's intended strategy.
Impact: LPs may face higher losses compared to properly weighted pools, discouraging participation.
-
Failed Operations
-
Economic Exploits
-
Inconsistent LP Returns
Effect: The pool's reward mechanisms (e.g., fees or incentives) may no longer function as intended if weights are incorrect.
Impact: LPs could earn disproportionate rewards or face penalties unrelated to their contributions.
-
Protocol Instability
Tools Used
-
Manual Review
-
Chat-gpt (asked its gradual impacts)
Recommendations
Mitigation to this oversight is simple. Update the code as we updated below...
UpdateWeightRunner::setWeightsManually:
function setWeightsManually(
int256[] calldata _weights,
address _poolAddress,
uint40 _lastInterpolationTimePossible,
uint256 _numberOfAssets
) external {
uint256 poolRegistryEntry = QuantAMMWeightedPool(_poolAddress).poolRegistry();
if (poolRegistryEntry & MASK_POOL_OWNER_UPDATES > 0) {
require(msg.sender == poolRuleSettings[_poolAddress].poolManager, "ONLYMANAGER");
} else if (poolRegistryEntry & MASK_POOL_QUANTAMM_ADMIN_UPDATES > 0) {
require(msg.sender == quantammAdmin, "ONLYADMIN");
} else {
revert("No permission to set weight values");
}
//though we try to keep manual overrides as open as possible for unknown unknows
//given how the math library works weights it is easiest to define weights as 18dp
//even though technically G3M works of the ratio between them so it is not strictly necessary
//CYFRIN L-02
for (uint256 i; i < _weights.length; i++) {
if (i < _numberOfAssets) {
require(_weights[i] > 0, "Negative weight not allowed");
- require(_weights[i] < 1e18, "greater than 1 weight not allowed");
+ require(_weights[i] < 1e18, "greater than 1 weight not allowed");
}
}
IQuantAMMWeightedPool(_poolAddress).setWeights(_weights, _poolAddress, _lastInterpolationTimePossible);
emit SetWeightManual(msg.sender, _poolAddress, _weights, _lastInterpolationTimePossible);
}