Incorrect invariant used for Balancer's composable pools
Only two balances instead of all balances were used when computing the invariant for Balancer's composable pools, which is incorrect. As a result, pool manipulation might not be detected. This could lead to the transaction being executed on the manipulated pool, resulting in a loss of assets.\\n```\\nFile: BalancerSpotPrice.sol\\n    function _calculateStableMathSpotPrice(\\n..SNIP..\\n        // Apply scale factors\\n        uint256 secondary = balances[index2] * scalingFactors[index2] / BALANCER_PRECISION;\\n\\n        uint256 invariant = StableMath._calculateInvariant(\\n            ampParam, StableMath._balances(scaledPrimary, secondary), true // round up\\n        );\\n\\n        spotPrice = StableMath._calcSpotPrice(ampParam, invariant, scaledPrimary, secondary);\\n..SNIP..\\n```\\n\\nA composable pool can support up to 5 tokens (excluding the BPT). When computing the invariant for a composable pool, one needs to pass in the balances of all the tokens within the pool except for BPT. However, the existing code always only passes in the balance of two tokens, which will return an incorrect invariant if the composable pool supports more than two tokens.\\nFollowing is the formula for computing the invariant of a composable pool taken from Balancer's Composable Pool. The `balances` passed into this function consist of all `balances` except for BPT (Reference)\\n```\\nfunction _calculateInvariant(uint256 amplificationParameter, uint256[] memory balances)\\n    internal\\n    pure\\n    returns (uint256)\\n{\\n    /**********************************************************************************************\\n    // invariant                                                                                 //\\n    // D = invariant                                                  D^(n+1)                    //\\n    // A = amplification coefficient      A  n^n S + D = A D n^n + -----------                   //\\n    // S = sum of balances                                             n^n P                     //\\n    // P = product of balances                                                                   //\\n    // n = number of tokens                                                                      //\\n    **********************************************************************************************/\\n```\\n\\nWithin the `_poolDerivatives` function, the `balances` used to compute the invariant consist of the balance of all tokens in the pool, except for BPT, which is aligned with the earlier understanding.\\n```\\nexport function _poolDerivatives(\\n    A: BigNumber,\\n    balances: OldBigNumber[],\\n    tokenIndexIn: number,\\n    tokenIndexOut: number,\\n    is_first_derivative: boolean,\\n    wrt_out: boolean\\n): OldBigNumber {\\n    const totalCoins = balances.length;\\n    const D = _invariant(A, balances);\\n```\\n
Review if there is any specific reason for passing in only the balance of two tokens when computing the invariant. Otherwise, the balance of all tokens (except BPT) should be used to compute the invariant.\\nIn addition, it is recommended to include additional tests to ensure that the computed spot price is aligned with the market price.
An incorrect invariant will lead to an incorrect spot price being computed. The spot price is used within the `_checkPriceAndCalculateValue` function that is intended to revert if the spot price on the pool is not within some deviation tolerance of the implied oracle price to prevent any pool manipulation. As a result, incorrect spot price leads to false positives or false negatives, where, in the worst-case scenario, pool manipulation was not caught by this function, and the transaction proceeded to be executed.\\nThe `_checkPriceAndCalculateValue` function was found to be used within the following functions:\\n`reinvestReward` - If the `_checkPriceAndCalculateValue` function is malfunctioning, it will cause the vault to add liquidity into a pool that has been manipulated, leading to a loss of assets.\\n`convertStrategyToUnderlying` - This function is used by Notional V3 for the purpose of computing the collateral values and the account's health factor. If the `_checkPriceAndCalculateValue` function reverts unexpectedly due to an incorrect invariant/spot price, many of Notional's core functions will break. In addition, the collateral values and the account's health factor might be inflated if it fails to detect a manipulated pool due to incorrect invariant/spot price, potentially allowing the malicious actors to drain the main protocol.
```\\nFile: BalancerSpotPrice.sol\\n    function _calculateStableMathSpotPrice(\\n..SNIP..\\n        // Apply scale factors\\n        uint256 secondary = balances[index2] * scalingFactors[index2] / BALANCER_PRECISION;\\n\\n        uint256 invariant = StableMath._calculateInvariant(\\n            ampParam, StableMath._balances(scaledPrimary, secondary), true // round up\\n        );\\n\\n        spotPrice = StableMath._calcSpotPrice(ampParam, invariant, scaledPrimary, secondary);\\n..SNIP..\\n```\\n