Token amounts are scaled up twice causing the amounts to be inflated in two token vault
Token amounts are scaled up twice causing the amounts to be inflated in two token vault when performing computation. This in turn causes the reinvest function to break leading to a loss of assets for vault users, and the value of their strategy tokens will be struck and will not appreciate.\\nIn Line 121-124, the `primaryAmount` and `secondaryAmount` are scaled up to `BALANCER_PRECISION` (1e18). The reason for doing so is that balancer math functions expect all amounts to be in `BALANCER_PRECISION` (1e18).\\nThen, the scaled `primaryAmount` and `secondaryAmount` are passed into the `_getSpotPrice` function at Line 126.\\n```\\nFile: Stable2TokenOracleMath.sol\\n    function _validateSpotPriceAndPairPrice(\\n        StableOracleContext calldata oracleContext,\\n        TwoTokenPoolContext calldata poolContext,\\n        StrategyContext memory strategyContext,\\n        uint256 oraclePrice,\\n        uint256 primaryAmount, \\n        uint256 secondaryAmount\\n    ) internal view {\\n        // Oracle price is always specified in terms of primary, so tokenIndex == 0 for primary\\n        uint256 spotPrice = _getSpotPrice({\\n            oracleContext: oracleContext,\\n            poolContext: poolContext,\\n            primaryBalance: poolContext.primaryBalance,\\n            secondaryBalance: poolContext.secondaryBalance,\\n            tokenIndex: 0\\n        });\\n\\n        /// @notice Check spotPrice against oracle price to make sure that \\n        /// the pool is not being manipulated\\n        _checkPriceLimit(strategyContext, oraclePrice, spotPrice);\\n\\n        /// @notice Balancer math functions expect all amounts to be in BALANCER_PRECISION\\n        uint256 primaryPrecision = 10 ** poolContext.primaryDecimals;\\n        uint256 secondaryPrecision = 10 ** poolContext.secondaryDecimals;\\n        primaryAmount = primaryAmount * BalancerConstants.BALANCER_PRECISION / primaryPrecision;\\n        secondaryAmount = secondaryAmount * BalancerConstants.BALANCER_PRECISION / secondaryPrecision;\\n\\n        uint256 calculatedPairPrice = _getSpotPrice({\\n            oracleContext: oracleContext,\\n            poolContext: poolContext,\\n            primaryBalance: primaryAmount,\\n            secondaryBalance: secondaryAmount,\\n            tokenIndex: 0\\n        });\\n```\\n\\nWithin the `_getSpotPrice` function, the `primaryBalance` and `secondaryBalance` are scaled up again at Line 25 - 28. As such, any token (e.g. USDC) with a decimal of less than `BALANCER_PRECISION` (1e18) will be scaled up twice. This will cause the `balanceX` or `balanceY` to be inflated.\\n```\\nFile: Stable2TokenOracleMath.sol\\n    function _getSpotPrice(\\n        StableOracleContext memory oracleContext, \\n        TwoTokenPoolContext memory poolContext, \\n        uint256 primaryBalance,\\n        uint256 secondaryBalance,\\n        uint256 tokenIndex\\n    ) internal view returns (uint256 spotPrice) {\\n        require(tokenIndex < 2); /// @dev invalid token index\\n\\n        /// Apply scale factors\\n        uint256 scaledPrimaryBalance = primaryBalance * poolContext.primaryScaleFactor \\n            / BalancerConstants.BALANCER_PRECISION;\\n        uint256 scaledSecondaryBalance = secondaryBalance * poolContext.secondaryScaleFactor \\n            / BalancerConstants.BALANCER_PRECISION;\\n\\n        /// @notice poolContext balances are always in BALANCER_PRECISION (1e18)\\n        (uint256 balanceX, uint256 balanceY) = tokenIndex == 0 ?\\n            (scaledPrimaryBalance, scaledSecondaryBalance) :\\n            (scaledSecondaryBalance, scaledPrimaryBalance);\\n\\n        uint256 invariant = StableMath._calculateInvariant(\\n            oracleContext.ampParam, StableMath._balances(balanceX, balanceY), true // round up\\n        );\\n\\n        spotPrice = StableMath._calcSpotPrice({\\n            amplificationParameter: oracleContext.ampParam,\\n            invariant: invariant,\\n            balanceX: balanceX,\\n            balanceY: balanceY\\n        });\\n\\n        /// Apply secondary scale factor in reverse\\n        uint256 scaleFactor = tokenIndex == 0 ?\\n            poolContext.secondaryScaleFactor * BalancerConstants.BALANCER_PRECISION / poolContext.primaryScaleFactor :\\n            poolContext.primaryScaleFactor * BalancerConstants.BALANCER_PRECISION / poolContext.secondaryScaleFactor;\\n        spotPrice = spotPrice * BalancerConstants.BALANCER_PRECISION / scaleFactor;\\n    }\\n```\\n\\nBalancer's Scaling Factors\\nIt is important to know the underlying mechanism of scaling factors within Balancer to understand this issue.\\nWithin Balancer, all stable math calculations within the Balancer's pools are performed in 1e18. Thus, before passing the token balances to the stable math functions, all the balances need to be normalized to 18 decimals.\\nFor instance, assume that 100 USDC needs to be passed into the stable math functions for some computation. 100 USDC is equal to `100e6` since the decimals of USDC is `6`. To normalize it to 18 decimals, 100 USDC (100e6) will be multiplied by its scaling factor (1e12), and the result will be `100e18`.\\nThe following code taken from Balancer shows that the scaling factor is comprised of the scaling factor multiplied by the token rate. The scaling factor is the value needed to normalize the token balance to 18 decimals.\\n```\\n    /**\\n     * @dev Overrides scaling factor getter to introduce the tokens' price rate.\\n     * Note that it may update the price rate cache if necessary.\\n     */\\n    function _scalingFactors() internal view virtual override returns (uint256[] memory scalingFactors) {\\n        // There is no need to check the arrays length since both are based on `_getTotalTokens`\\n        // Given there is no generic direction for this rounding, it simply follows the same strategy as the BasePool.\\n        scalingFactors = super._scalingFactors();\\n        scalingFactors[0] = scalingFactors[0].mulDown(_priceRate(_token0));\\n        scalingFactors[1] = scalingFactors[1].mulDown(_priceRate(_token1));\\n    }\\n```\\n\\nAnother point to note is that Balancer's stable math functions perform calculations in fixed point format. Therefore, the scaling factor will consist of the `FixedPoint.ONE` (1e18) multiplied by the value needed to normalize the token balance to 18 decimals. If it is a USDC with 6 decimals, the scaling factor will be 1e30:\\n```\\nFixedPoint.ONE * 10**decimalsDifference\\n1e18 * 1e12 = 1e30\\n```\\n\\n```\\n    /**\\n     * @dev Returns a scaling factor that, when multiplied to a token amount for `token`, normalizes its balance as if\\n     * it had 18 decimals.\\n     */\\n    function _computeScalingFactor(IERC20 token) internal view returns (uint256) {\\n        // Tokens that don't implement the `decimals` method are not supported.\\n        uint256 tokenDecimals = ERC20(address(token)).decimals();\\n\\n        // Tokens with more than 18 decimals are not supported.\\n        uint256 decimalsDifference = Math.sub(18, tokenDecimals);\\n        return FixedPoint.ONE * 10**decimalsDifference;\\n    }\\n```\\n\\nProof-of-Concept\\nAssume that one of the tokens in Notional's two token leverage vault has a decimal of less than 18. Let's take USDC as an example.\\n100 USDC (1e6) is passed into the `_validateSpotPriceAndPairPrice` function as the `primaryAmount`. In Line 121-124 of the `_validateSpotPriceAndPairPrice` function, the `primaryAmount` will be scaled up to `BALANCER_PRECISION` (1e18).\\n`primaryAmount` = `primaryAmount` * BalancerConstants.BALANCER_PRECISION / primaryPrecision;\\n`primaryAmount` = 100e6 * 1e18 / 1e6\\n`primaryAmount` = 100e18\\nWithin the `_getSpotPrice` function, the `primaryBalance` is scaled up again at Line 25 - 28 of the `_getSpotPrice` function.\\n`scaledPrimaryBalance = `primaryBalance` * poolContext.primaryScaleFactor / BalancerConstants.BALANCER_PRECISION;\\nscaledPrimaryBalance = 100e18 * 1e30 / 1e18\\nscaledPrimaryBalance = 1e30\\nscaledPrimaryBalance = 1000000000000e18`\\nAs shown above, normalized 100 USDC (100e18) ended up becoming normalized 1000000000000 USDC (1000000000000e18). Therefore, the stable math functions are computed with an inflated balance of 1000000000000 USDC instead of 100 USDC.
Since the token balances are already normalized to 18 decimals within the `_getSpotPrice` function, the code to normalize the token balances in the `_validateSpotPriceAndPairPrice` function can be removed.\\n```\\n    function _validateSpotPriceAndPairPrice(\\n        StableOracleContext calldata oracleContext,\\n        TwoTokenPoolContext calldata poolContext,\\n        StrategyContext memory strategyContext,\\n        uint256 oraclePrice,\\n        uint256 primaryAmount, \\n        uint256 secondaryAmount\\n    ) internal view {\\n        // Oracle price is always specified in terms of primary, so tokenIndex == 0 for primary\\n        uint256 spotPrice = _getSpotPrice({\\n            oracleContext: oracleContext,\\n            poolContext: poolContext,\\n            primaryBalance: poolContext.primaryBalance,\\n            secondaryBalance: poolContext.secondaryBalance,\\n            tokenIndex: 0\\n        });\\n\\n        /// @notice Check spotPrice against oracle price to make sure that \\n        /// the pool is not being manipulated\\n        _checkPriceLimit(strategyContext, oraclePrice, spotPrice);\\n\\n// Remove the line below\\n        /// @notice Balancer math functions expect all amounts to be in BALANCER_PRECISION\\n// Remove the line below\\n        uint256 primaryPrecision = 10 ** poolContext.primaryDecimals;\\n// Remove the line below\\n        uint256 secondaryPrecision = 10 ** poolContext.secondaryDecimals;\\n// Remove the line below\\n        primaryAmount = primaryAmount * BalancerConstants.BALANCER_PRECISION / primaryPrecision;\\n// Remove the line below\\n        secondaryAmount = secondaryAmount * BalancerConstants.BALANCER_PRECISION / secondaryPrecision;\\n\\n        uint256 calculatedPairPrice = _getSpotPrice({\\n            oracleContext: oracleContext,\\n            poolContext: poolContext,\\n            primaryBalance: primaryAmount,\\n            secondaryBalance: secondaryAmount,\\n            tokenIndex: 0\\n        });\\n\\n        /// @notice Check the calculated primary/secondary price against the oracle price\\n        /// to make sure that we are joining the pool proportionally\\n        _checkPriceLimit(strategyContext, oraclePrice, calculatedPairPrice);\\n    }\\n```\\n
The spot price computed by the `Stable2TokenOracleMath._getSpotPrice` function will deviate from the actual price because inflated balances were passed into it. The deviated spot price will then be passed to the `_checkPriceLimit` function to verify if the spot price has deviated from the oracle price. The check will fail and cause a revert. This will in turn cause the `Stable2TokenOracleMath._validateSpotPriceAndPairPrice` function to revert.\\nTherefore, any function that relies on the `Stable2TokenOracleMath._validateSpotPriceAndPairPrice` function will be affected. It was found that the `MetaStable2TokenAuraHelper.reinvestReward` relies on the `Stable2TokenOracleMath._validateSpotPriceAndPairPrice` function. As such, reinvest feature of the vault will be broken and the vault will not be able to reinvest its rewards.\\nThis in turn led to a loss of assets for vault users, and the value of their strategy tokens will be struck and will not appreciate.
```\\nFile: Stable2TokenOracleMath.sol\\n    function _validateSpotPriceAndPairPrice(\\n        StableOracleContext calldata oracleContext,\\n        TwoTokenPoolContext calldata poolContext,\\n        StrategyContext memory strategyContext,\\n        uint256 oraclePrice,\\n        uint256 primaryAmount, \\n        uint256 secondaryAmount\\n    ) internal view {\\n        // Oracle price is always specified in terms of primary, so tokenIndex == 0 for primary\\n        uint256 spotPrice = _getSpotPrice({\\n            oracleContext: oracleContext,\\n            poolContext: poolContext,\\n            primaryBalance: poolContext.primaryBalance,\\n            secondaryBalance: poolContext.secondaryBalance,\\n            tokenIndex: 0\\n        });\\n\\n        /// @notice Check spotPrice against oracle price to make sure that \\n        /// the pool is not being manipulated\\n        _checkPriceLimit(strategyContext, oraclePrice, spotPrice);\\n\\n        /// @notice Balancer math functions expect all amounts to be in BALANCER_PRECISION\\n        uint256 primaryPrecision = 10 ** poolContext.primaryDecimals;\\n        uint256 secondaryPrecision = 10 ** poolContext.secondaryDecimals;\\n        primaryAmount = primaryAmount * BalancerConstants.BALANCER_PRECISION / primaryPrecision;\\n        secondaryAmount = secondaryAmount * BalancerConstants.BALANCER_PRECISION / secondaryPrecision;\\n\\n        uint256 calculatedPairPrice = _getSpotPrice({\\n            oracleContext: oracleContext,\\n            poolContext: poolContext,\\n            primaryBalance: primaryAmount,\\n            secondaryBalance: secondaryAmount,\\n            tokenIndex: 0\\n        });\\n```\\n