Vulnerability: Rounding differences when computing the invariant
Severity: high

Description:
The invariant is used to compute the spot price to verify if the pool has been manipulated before executing certain key vault actions (e.g. reinvest rewards). If the inputted invariant is inaccurate, the spot price computed might not be accurate and might not match the actual spot price of the Balancer Pool. In the worst-case scenario, it might potentially fail to detect the pool has been manipulated, and the trade proceeds to execute against the manipulated pool, leading to a loss of assets.\\nThe Balancer's Composable Pool codebase relies on the old version of the `StableMath._calculateInvariant` that allows the caller to specify if the computation should round up or down via the `roundUp` parameter.\\n```\\nFile: StableMath.sol\\n    function _calculateInvariant(\\n        uint256 amplificationParameter,\\n        uint256[] memory balances,\\n        bool roundUp\\n    ) internal pure returns (uint256) {\\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        *********x************************************************************************************/\\n\\n        unchecked {\\n            // We support rounding up or down.\\n            uint256 sum = 0;\\n            uint256 numTokens = balances.length;\\n            for (uint256 i = 0; i < numTokens; i++) {\\n                sum = sum.add(balances[i]);\\n            }\\n            if (sum == 0) {\\n                return 0;\\n            }\\n\\n            uint256 prevInvariant = 0;\\n            uint256 invariant = sum;\\n            uint256 ampTimesTotal = amplificationParameter * numTokens;\\n\\n            for (uint256 i = 0; i < 255; i++) {\\n                uint256 P_D = balances[0] * numTokens;\\n                for (uint256 j = 1; j < numTokens; j++) {\\n                    P_D = Math.div(Math.mul(Math.mul(P_D, balances[j]), numTokens), invariant, roundUp);\\n                }\\n                prevInvariant = invariant;\\n                invariant = Math.div(\\n                    Math.mul(Math.mul(numTokens, invariant), invariant).add(\\n                        Math.div(Math.mul(Math.mul(ampTimesTotal, sum), P_D), _AMP_PRECISION, roundUp)\\n                    ),\\n                    Math.mul(numTokens + 1, invariant).add(\\n                        // No need to use checked arithmetic for the amp precision, the amp is guaranteed to be at least 1\\n                        Math.div(Math.mul(ampTimesTotal - _AMP_PRECISION, P_D), _AMP_PRECISION, !roundUp)\\n                    ),\\n                    roundUp\\n                );\\n\\n                if (invariant > prevInvariant) {\\n                    if (invariant - prevInvariant <= 1) {\\n                        return invariant;\\n                    }\\n                } else if (prevInvariant - invariant <= 1) {\\n                    return invariant;\\n                }\\n            }\\n        }\\n\\n        revert CalculationDidNotConverge();\\n    }\\n```\\n\\nWithin the `BalancerSpotPrice._calculateStableMathSpotPrice` function, the `StableMath._calculateInvariant` is computed rounding up per Line 90 below\\n```\\nFile: BalancerSpotPrice.sol\\n    function _calculateStableMathSpotPrice(\\n        uint256 ampParam,\\n        uint256[] memory scalingFactors,\\n        uint256[] memory balances,\\n        uint256 scaledPrimary,\\n        uint256 primaryIndex,\\n        uint256 index2\\n    ) internal pure returns (uint256 spotPrice) {\\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\\nThe new Composable Pool contract uses a newer version of the StableMath library where the `StableMath._calculateInvariant` function always rounds down. Following is the StableMath.sol of one of the popular composable pools in Arbitrum that uses the new StableMath library\\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    // Always round down, to match Vyper's arithmetic (which always truncates).\\n    ..SNIP..\\n```\\n\\nThus, Notional rounds up when calculating the invariant, while Balancer's Composable Pool rounds down when calculating the invariant. This inconsistency will result in a different invariant

Impact:
The invariant is used to compute the spot price to verify if the pool has been manipulated before executing certain key vault actions (e.g. reinvest rewards). If the inputted invariant is inaccurate, the spot price computed might not be accurate and might not match the actual spot price of the Balancer Pool. In the worst-case scenario, it might potentially fail to detect the pool has been manipulated, and the trade proceeds to execute against the manipulated pool, leading to a loss of assets.

Recommended mitigation:
To avoid any discrepancy in the result, ensure that the StableMath library used by Balancer's Composable Pool and Notional's leverage vault are aligned, and the implementation of the StableMath functions is the same between them.