In BalancerV3 WeightedPools, weights represent each asset’s share of total liquidity. However, in the single-κ\kappaκ branch of ChannelFollowingUpdateRule no check enforces newWeight >= 0. Under certain parameters and market conditions, the calculated weight can go negative—and affect how pool allocate assets which can lead to loss of funds to LPers.
function testPoCNegativeWeightSingleKappa_Force() public {
* --------------------------------------------------------------
* 1) Single-\kappa\ parameters => triggers the single-\kappa\ path
* We'll use an extremely high \kappa\ plus conditions that
* strongly push a negative gradient on one asset.
* --------------------------------------------------------------
*/
int256[][] memory parameters = new int256[][]();
parameters[0] = new int256[]();
parameters[0][0] = 2000e18;
parameters[1] = new int256[]();
parameters[1][0] = 0.1e18;
parameters[2] = new int256[]();
parameters[2][0] = 1e18;
parameters[3] = new int256[]();
parameters[3][0] = 1e18;
parameters[4] = new int256[]();
parameters[4][0] = 1e18;
parameters[5] = new int256[]();
parameters[5][0] = 1e18;
parameters[6] = new int256[]();
parameters[6][0] = 1e18;
* --------------------------------------------------------------
* 2) Two assets => single-kappa branch is still used,
* but we only have one \kappa\ for both assets.
* --------------------------------------------------------------
*/
mockPool.setNumberOfAssets(2);
int256[] memory prevWeights = new int256[]();
prevWeights[0] = 0.1e18;
prevWeights[1] = 0.9e18;
int256[] memory prevAlphas = new int256[]();
prevAlphas[0] = 1e18;
prevAlphas[1] = 1e18;
int256[] memory prevMovingAverages = new int256[]();
prevMovingAverages[0] = 5e18;
prevMovingAverages[1] = 5e18;
int256[] memory movingAverages = new int256[]();
movingAverages[0] = 5e18;
movingAverages[1] = 5e18;
* --------------------------------------------------------------
* 5) Create new raw prices that strongly drop for the first asset
* => a negative gradient. The second asset moves slightly
* => might be negative or positive, but less dramatic.
* --------------------------------------------------------------
*/
int256[] memory data = new int256[]();
data[0] = 0.1e18;
data[1] = 4.9e18;
int128[] memory lambdas = new int128[]();
lambdas[0] = int128(0.9e18);
rule.initialisePoolRuleIntermediateValues(
address(mockPool),
prevMovingAverages,
prevAlphas,
mockPool.numAssets()
);
* --------------------------------------------------------------
* 7) Calculate new weights in single-kappa mode
* --------------------------------------------------------------
*/
rule.CalculateUnguardedWeights(
prevWeights,
data,
address(mockPool),
parameters,
lambdas,
movingAverages
);
int256[] memory resultWeights = rule.GetResultWeights();
* --------------------------------------------------------------
* 8) Check if Asset0 or Asset1 ended up negative
* --------------------------------------------------------------
*/
console.log("Asset0 final weight:");
console.logInt(resultWeights[0]);
console.log("");
console.log("Asset1 final weight:");
console.logInt(resultWeights[1]);
console.log("");
if (resultWeights[0] < 0) {
console.log("BUG: negative weight for Asset0 in single-kappa scenario");
}
if (resultWeights[1] < 0) {
console.log("BUG: negative weight for Asset1 in single-kappa scenario");
}
A negative weight can undermine Balancer’s WeightedPool integrity, enabling asset mispricing, liquidity drain, and direct financial losses for users.