Reinvest will return sub-optimal return if the pool is imbalanced
Reinvesting only allows proportional deposit. If the pool is imbalanced due to unexpected circumstances, performing a proportional deposit is not optimal. This result in fewer pool tokens in return due to sub-optimal trade, eventually leading to a loss of gain for the vault shareholder.\\nDuring reinvest rewards, the vault will ensure that the amount of primary and secondary tokens deposited is of the right proportion per the comment in Line 163 below.\\n```\\nFile: Curve2TokenConvexHelper.sol\\n    function reinvestReward(\\n        Curve2TokenConvexStrategyContext calldata context,\\n        ReinvestRewardParams calldata params\\n    ) external {\\n..SNIP..\\n        // Make sure we are joining with the right proportion to minimize slippage\\n        poolContext._validateSpotPriceAndPairPrice({\\n            strategyContext: strategyContext,\\n            oraclePrice: poolContext.basePool._getOraclePairPrice(strategyContext),\\n            primaryAmount: primaryAmount,\\n            secondaryAmount: secondaryAmount\\n        });\\n```\\n\\nThe `Curve2TokenConvexHelper.reinvestReward` function will internally call the `Curve2TokenPoolUtils._checkPrimarySecondaryRatio`, which will check that the primary and secondary tokens deposited are of the right proportion.\\n```\\nFile: Curve2TokenPoolUtils.sol\\n    function _checkPrimarySecondaryRatio(\\n        StrategyContext memory strategyContext,\\n        uint256 primaryAmount, \\n        uint256 secondaryAmount, \\n        uint256 primaryPoolBalance, \\n        uint256 secondaryPoolBalance\\n    ) private pure {\\n        uint256 totalAmount = primaryAmount + secondaryAmount;\\n        uint256 totalPoolBalance = primaryPoolBalance + secondaryPoolBalance;\\n\\n        uint256 primaryPercentage = primaryAmount * CurveConstants.CURVE_PRECISION / totalAmount;        \\n        uint256 expectedPrimaryPercentage = primaryPoolBalance * CurveConstants.CURVE_PRECISION / totalPoolBalance;\\n\\n        strategyContext._checkPriceLimit(expectedPrimaryPercentage, primaryPercentage);\\n\\n        uint256 secondaryPercentage = secondaryAmount * CurveConstants.CURVE_PRECISION / totalAmount;\\n        uint256 expectedSecondaryPercentage = secondaryPoolBalance * CurveConstants.CURVE_PRECISION / totalPoolBalance;\\n\\n        strategyContext._checkPriceLimit(expectedSecondaryPercentage, secondaryPercentage);\\n    }\\n```\\n\\nThis concept of proportional join appears to be taken from the design of earlier Notional's Balancer leverage vaults. For Balancer Pools, it is recommended to join with all the pool's tokens in exact proportions to minimize the price impact of the join (Reference).\\nHowever, the concept of proportional join to minimize slippage does not always hold for Curve Pools as they operate differently.\\nA Curve pool is considered imbalanced when there is an imbalance between the assets within it. For instance, the Curve stETH/ETH pool is considered imbalanced if it has the following reserves:\\nETH: 340,472.34 (31.70%)\\nstETH: 733,655.65 (68.30%)\\nIf a Curve Pool is imbalanced, attempting to perform a proportional join will not give an optimal return (e.g. result in fewer Pool LP tokens received).\\nIn Curve Pool, there are penalties/bonuses when depositing to a pool. The pools are always trying to balance themselves. If a deposit helps the pool to reach that desired balance, a deposit bonus will be given (receive extra tokens). On the other hand, if a deposit deviates from the pool from the desired balance, a deposit penalty will be applied (receive fewer tokens).\\n```\\ndef add_liquidity(amounts: uint256[N_COINS], min_mint_amount: uint256) -> uint256:\\n..SNIP..\\n    if token_supply > 0:\\n        # Only account for fees if we are not the first to deposit\\n        fee: uint256 = self.fee * N_COINS / (4 * (N_COINS - 1))\\n        admin_fee: uint256 = self.admin_fee\\n        for i in range(N_COINS):\\n            ideal_balance: uint256 = D1 * old_balances[i] / D0\\n            difference: uint256 = 0\\n            if ideal_balance > new_balances[i]:\\n                difference = ideal_balance - new_balances[i]\\n            else:\\n                difference = new_balances[i] - ideal_balance\\n            fees[i] = fee * difference / FEE_DENOMINATOR\\n            if admin_fee != 0:\\n                self.admin_balances[i] += fees[i] * admin_fee / FEE_DENOMINATOR\\n            new_balances[i] -= fees[i]\\n        D2 = self.get_D(new_balances, amp)\\n        mint_amount = token_supply * (D2 - D0) / D0\\n    else:\\n        mint_amount = D1  # Take the dust if there was any\\n..SNIP..\\n```\\n\\nFollowing is the mathematical explanation of the penalties/bonuses extracted from Curve's Discord channel:\\nThere is a “natural” amount of D increase that corresponds to a given total deposit amount; when the pool is perfectly balanced, this D increase is optimally achieved by a balanced deposit. Any other deposit proportions for the same total amount will give you less D.\\nHowever, when the pool is imbalanced, a balanced deposit is no longer optimal for the D increase.
Consider removing the `_checkPrimarySecondaryRatio` function from the `_validateSpotPriceAndPairPrice` function to give the callers the option to deposit the reward tokens in a "non-proportional" manner if a Curve Pool becomes imbalanced so that the deposit penalty could be minimized or the deposit bonus can be exploited to increase the return.
There is no guarantee that a Curve Pool will always be balanced. Historically, there are multiple instances where the largest Curve pool (stETH/ETH) becomes imbalanced (Reference #1 and #2).\\nIf the pool is imbalanced due to unexpected circumstances, performing a proportional deposit is not optimal, leading to the trade resulting in fewer tokens than possible due to the deposit penalty. In addition, the trade also misses out on the potential gain from the deposit bonus.\\nThe side-effect is that reinvesting the reward tokens will result in fewer pool tokens in return due to sub-optimal trade, eventually leading to a loss of gain for the vault shareholder.
```\\nFile: Curve2TokenConvexHelper.sol\\n    function reinvestReward(\\n        Curve2TokenConvexStrategyContext calldata context,\\n        ReinvestRewardParams calldata params\\n    ) external {\\n..SNIP..\\n        // Make sure we are joining with the right proportion to minimize slippage\\n        poolContext._validateSpotPriceAndPairPrice({\\n            strategyContext: strategyContext,\\n            oraclePrice: poolContext.basePool._getOraclePairPrice(strategyContext),\\n            primaryAmount: primaryAmount,\\n            secondaryAmount: secondaryAmount\\n        });\\n```\\n