The White Paper states that there should be no difference in the calculation of scaler and vector values across formulas. Additionally, during the unguarded weights stage, the protocol should allow negative weights, as the guard weight ensures final weights validity.
The White Paper mentions that a strategy can utilize either scalar or vector kappa values. The primary difference lies in implementation complexity, as vector kappa values require an additional SLOAD operation and a nested loop for processing.

The same formula is applied for both scaler and vector kappa values, ensuring uniformity in calculations regardless of the type of kappa value used.

The current strategy algorithm supports both short and long positions. However, the additional check in the implementation, as shown in the code below, prevents the weighted pool from functioning with long/short positions if the unguarded weights return negative values after a price change.
The following POC demonstrates how the algorithm behaves differently when using scaler versus vector kappa values.
pragma solidity ^0.8.24;
import "forge-std/Test.sol";
import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import { IVault } from "@balancer-labs/v3-interfaces/contracts/vault/IVault.sol";
import { IVaultErrors } from "@balancer-labs/v3-interfaces/contracts/vault/IVaultErrors.sol";
import { PoolRoleAccounts } from "@balancer-labs/v3-interfaces/contracts/vault/VaultTypes.sol";
import { CastingHelpers } from "@balancer-labs/v3-solidity-utils/contracts/helpers/CastingHelpers.sol";
import { ArrayHelpers } from "@balancer-labs/v3-solidity-utils/contracts/test/ArrayHelpers.sol";
import { BalancerPoolToken } from "@balancer-labs/v3-vault/contracts/BalancerPoolToken.sol";
import { BaseVaultTest } from "@balancer-labs/v3-vault/test/foundry/utils/BaseVaultTest.sol";
import { QuantAMMWeightedPool } from "../../contracts/QuantAMMWeightedPool.sol";
import { QuantAMMWeightedPoolFactory } from "../../contracts/QuantAMMWeightedPoolFactory.sol";
import { QuantAMMWeightedPoolContractsDeployer } from "./utils/QuantAMMWeightedPoolContractsDeployer.sol";
import { PoolSwapParams, SwapKind } from "@balancer-labs/v3-interfaces/contracts/vault/VaultTypes.sol";
import { OracleWrapper } from "@balancer-labs/v3-interfaces/contracts/pool-quantamm/OracleWrapper.sol";
import { MockUpdateWeightRunner } from "../../contracts/mock/MockUpdateWeightRunner.sol";
import { MockMomentumRule } from "../../contracts/mock/mockRules/MockMomentumRule.sol";
import { MockAntiMomentumRule } from "../../contracts/mock/mockRules/MockAntiMomentumRule.sol";
import { MockChainlinkOracle } from "../../contracts/mock/MockChainlinkOracles.sol";
import "@balancer-labs/v3-interfaces/contracts/pool-quantamm/IQuantAMMWeightedPool.sol";
contract QuantAMMWeightedPoolRevertCase is QuantAMMWeightedPoolContractsDeployer, BaseVaultTest {
using CastingHelpers for address[];
using ArrayHelpers for *;
uint256 internal daiIdx;
uint256 internal usdcIdx;
uint64 public constant MAX_SWAP_FEE_PERCENTAGE = 10e16;
QuantAMMWeightedPoolFactory internal quantAMMWeightedPoolFactory;
function setUp() public override {
uint delay = 3600;
super.setUp();
(address ownerLocal, address addr1Local, address addr2Local) = (vm.addr(1), vm.addr(2), vm.addr(3));
owner = ownerLocal;
addr1 = addr1Local;
addr2 = addr2Local;
vm.startPrank(owner);
updateWeightRunner = new MockUpdateWeightRunner(owner, addr2, false);
chainlinkOracle = _deployOracle(1e18, delay);
chainlinkOracle2 = _deployOracle(1e18, delay);
updateWeightRunner.addOracle(OracleWrapper(chainlinkOracle));
updateWeightRunner.addOracle(OracleWrapper(chainlinkOracle2));
vm.stopPrank();
quantAMMWeightedPoolFactory = deployQuantAMMWeightedPoolFactory(
IVault(address(vault)),
365 days,
"Factory v1",
"Pool v1"
);
vm.label(address(quantAMMWeightedPoolFactory), "quantamm weighted pool factory");
(daiIdx, usdcIdx) = getSortedIndexes(address(dai), address(usdc));
}
function testQuantAMMWeightedPoolGetNormalizedWeightsInitial() public {
QuantAMMWeightedPoolFactory.NewPoolParams memory params = _createPoolParams(true,ZERO_BYTES32);
params._initialWeights[0] = 0.80e18;
params._initialWeights[1] = 0.20e18;
(address quantAMMWeightedPool, ) = quantAMMWeightedPoolFactory.create(params);
uint256[] memory weights = QuantAMMWeightedPool(quantAMMWeightedPool).getNormalizedWeights();
vm.prank(owner);
updateWeightRunner.setApprovedActionsForPool(quantAMMWeightedPool,1);
updateWeightRunner.performUpdate(quantAMMWeightedPool);
chainlinkOracle.updateData(0.9e18, uint40(block.timestamp));
chainlinkOracle2.updateData(2.5e18, uint40(block.timestamp));
vm.warp(block.timestamp+1000);
updateWeightRunner.performUpdate(quantAMMWeightedPool);
uint256[] memory weightsData = QuantAMMWeightedPool(quantAMMWeightedPool).getNormalizedWeights();
assertNotEq(weightsData[0],uint256(params._initialWeights[0]));
assertNotEq(weightsData[1],uint256(params._initialWeights[1]));
params = _createPoolParams(false,bytes32("TheKhans"));
params._initialWeights[0] = 0.80e18;
params._initialWeights[1] = 0.20e18;
( quantAMMWeightedPool, ) = quantAMMWeightedPoolFactory.create(params);
weights = QuantAMMWeightedPool(quantAMMWeightedPool).getNormalizedWeights();
vm.prank(owner);
updateWeightRunner.setApprovedActionsForPool(quantAMMWeightedPool,1);
updateWeightRunner.performUpdate(quantAMMWeightedPool);
chainlinkOracle.updateData(0.9e18, uint40(block.timestamp));
chainlinkOracle2.updateData(2.5e18, uint40(block.timestamp));
vm.warp(block.timestamp+1000);
vm.expectRevert();
updateWeightRunner.performUpdate(quantAMMWeightedPool);
}
function _createPoolParams(bool useScalerKappa,bytes32 salt) internal returns (QuantAMMWeightedPoolFactory.NewPoolParams memory retParams) {
PoolRoleAccounts memory roleAccounts;
IERC20[] memory tokens = [address(dai), address(usdc)].toMemoryArray().asIERC20();
MockAntiMomentumRule momentumRule = new MockAntiMomentumRule(address(updateWeightRunner));
uint32[] memory weights = new uint32[]();
weights[0] = uint32(uint256(0.5e18));
weights[1] = uint32(uint256(0.5e18));
int256[] memory initialWeights = new int256[]();
initialWeights[0] = 0.5e18;
initialWeights[1] = 0.5e18;
uint256[] memory initialWeightsUint = new uint256[]();
initialWeightsUint[0] = 0.5e18;
initialWeightsUint[1] = 0.5e18;
uint64[] memory lambdas = new uint64[]();
lambdas[0] = 0.2e18;
int256[][] memory parameters;
if(!useScalerKappa){
parameters = new int256[][]();
parameters[0] = new int256[]();
parameters[0][0] = 0.6e18;
parameters[0][1] = 0.6e18;
}else{
parameters = new int256[][]();
parameters[0] = new int256[]();
parameters[0][0] = 0.6e18;
}
address[][] memory oracles = new address[][]();
oracles[0] = new address[]();
oracles[1] = new address[]();
oracles[0][0] = address(chainlinkOracle);
oracles[1][0] = address(chainlinkOracle2);
retParams = QuantAMMWeightedPoolFactory.NewPoolParams(
"Pool With Donation",
"PwD",
vault.buildTokenConfig(tokens),
initialWeightsUint,
roleAccounts,
MAX_SWAP_FEE_PERCENTAGE,
address(0),
true,
false,
salt,
initialWeights,
IQuantAMMWeightedPool.PoolSettings(
new IERC20[](2),
IUpdateRule(momentumRule),
oracles,
60,
lambdas,
0.2e18,
0.02e18,
0.2e18,
parameters,
address(0)
),
initialWeights,
initialWeights,
3600,
0,
new string[][]()
);
}
}
In the case of vector kappa, the weights are not updated and continue using the old values, which is incorrect given the latest price changes. However, with single kappa, the update proceeds as expected, reflecting the new prices.