Unable to reinvest if the reward token equals one of the pool tokens
If the reward token is the same as one of the pool tokens, the protocol would not be able to reinvest such a reward token. Thus leading to a loss of assets for the vault shareholders.\\nDuring the reinvestment process, the `reinvestReward` function will be executed once for each reward token. The length of the `trades` listing defined in the payload must be the same as the number of tokens in the pool per Line 339 below.\\n```\\nFile: SingleSidedLPVaultBase.sol\\n    function reinvestReward(\\n        SingleSidedRewardTradeParams[] calldata trades,\\n        uint256 minPoolClaim\\n    ) external whenNotLocked onlyRole(REWARD_REINVESTMENT_ROLE) returns (\\n        address rewardToken,\\n        uint256 amountSold,\\n        uint256 poolClaimAmount\\n    ) {\\n        // Will revert if spot prices are not in line with the oracle values\\n        _checkPriceAndCalculateValue();\\n\\n        // Require one trade per token, if we do not want to buy any tokens at a\\n        // given index then the amount should be set to zero. This applies to pool\\n        // tokens like in the ComposableStablePool.\\n        require(trades.length == NUM_TOKENS());\\n        uint256[] memory amounts;\\n        (rewardToken, amountSold, amounts) = _executeRewardTrades(trades);\\n```\\n\\nIn addition, due to the requirement at Line 105, each element in the `trades` listing must be a token within a pool and must be ordered in sequence according to the token index of the pool.\\n```\\nFile: StrategyUtils.sol\\n    function executeRewardTrades(\\n        IERC20[] memory tokens,\\n        SingleSidedRewardTradeParams[] calldata trades,\\n        address rewardToken,\\n        address poolToken\\n    ) external returns(uint256[] memory amounts, uint256 amountSold) {\\n        amounts = new uint256[](trades.length);\\n        for (uint256 i; i < trades.length; i++) {\\n            // All trades must sell the same token.\\n            require(trades[i].sellToken == rewardToken);\\n            // Bypass certain invalid trades\\n            if (trades[i].amount == 0) continue;\\n            if (trades[i].buyToken == poolToken) continue;\\n\\n            // The reward trade can only purchase tokens that go into the pool\\n            require(trades[i].buyToken == address(tokens[i]));\\n```\\n\\nAssuming the TriCRV Curve pool (crvUSD+WETH+CRV) has two reward tokens (CRV & CVX). This example is taken from a live Curve pool on Ethereum (Reference 1 Reference 2)\\nThe pool will consist of the following tokens:\\n```\\ntokens[0] = crvUSD\\ntokens[1] = WETH\\ntokens[2] = CRV\\n```\\n\\nThus, if the protocol receives 3000 CVX reward tokens and it intends to sell 1000 CVX for crvUSD and 1000 CVX for WETH.\\nThe `trades` list has to be defined as below.\\n```\\ntrades[0].sellToken[0] = CRV (rewardToken) | trades[0].buyToken = crvUSD | trades[0].amount = 1000\\ntrades[1].sellToken[1] = CRV (rewardToken) | trades[1].buyToken = WETH    | trades[0].amount = 1000\\ntrades[1].sellToken[2] = CRV (rewardToken) | trades[1].buyToken = CRV    | trades[0].amount = 0\\n```\\n\\nThe same issue also affects the Balancer pools. Thus, the example is omitted for brevity. One of the affected Balancer pools is as follows, where the reward token is also one of the pool tokens.\\nWETH-AURA - Reference 1 Reference 2 (Reward Tokens = [BAL, AURA])\\nHowever, the issue is that the `_isInvalidRewardToken` function within the `_executeRewardTrades` will always revert.\\n```\\nFile: SingleSidedLPVaultBase.sol\\n    function _executeRewardTrades(SingleSidedRewardTradeParams[] calldata trades) internal returns (\\n        address rewardToken,\\n        uint256 amountSold,\\n        uint256[] memory amounts\\n    ) {\\n        // The sell token on all trades must be the same (checked inside executeRewardTrades) so\\n        // just validate here that the sellToken is a valid reward token (i.e. none of the tokens\\n        // used in the regular functioning of the vault).\\n        rewardToken = trades[0].sellToken;\\n        if (_isInvalidRewardToken(rewardToken)) revert Errors.InvalidRewardToken(rewardToken);\\n        (IERC20[] memory tokens, /* */) = TOKENS();\\n        (amounts, amountSold) = StrategyUtils.executeRewardTrades(\\n            tokens, trades, rewardToken, address(POOL_TOKEN())\\n        );\\n    }\\n```\\n\\nThe reason is that within the `_isInvalidRewardToken` function it checks if the reward token to be sold is any of the pool tokens. In this case, the condition will be evaluated to be true, and a revert will occur. As a result, the protocol would not be able to reinvest such reward tokens.\\n```\\nFile: AuraStakingMixin.sol\\n    function _isInvalidRewardToken(address token) internal override view returns (bool) {\\n        return (\\n            token == TOKEN_1 ||\\n            token == TOKEN_2 ||\\n            token == TOKEN_3 ||\\n            token == TOKEN_4 ||\\n            token == TOKEN_5 ||\\n            token == address(AURA_BOOSTER) ||\\n            token == address(AURA_REWARD_POOL) ||\\n            token == address(Deployments.WETH)\\n        );\\n    }\\n```\\n\\n```\\nFile: ConvexStakingMixin.sol\\n    function _isInvalidRewardToken(address token) internal override view returns (bool) {\\n        return (\\n            token == TOKEN_1 ||\\n            token == TOKEN_2 ||\\n            token == address(CURVE_POOL_TOKEN) ||\\n            token == address(CONVEX_REWARD_POOL) ||\\n            token == address(CONVEX_BOOSTER) ||\\n            token == Deployments.ALT_ETH_ADDRESS\\n        );\\n    }\\n```\\n
Consider tracking the number of pool tokens received during an emergency exit, and segregate these tokens with the reward tokens. For instance, the vault has 3000 CVX, 1000 of them are received during the emergency exit, while the rest are reward tokens emitted from Convex/Aura. In this case, the protocol can sell all CVX on the vault except for the 1000 CVX reserved.
The reinvestment of reward tokens is a critical component of the vault. The value per vault share increases when reward tokens are sold for the pool tokens and reinvested back into the Curve/Balancer pool to obtain more LP tokens. If this feature does not work as intended, it will lead to a loss of assets for the vault shareholders.
```\\nFile: SingleSidedLPVaultBase.sol\\n    function reinvestReward(\\n        SingleSidedRewardTradeParams[] calldata trades,\\n        uint256 minPoolClaim\\n    ) external whenNotLocked onlyRole(REWARD_REINVESTMENT_ROLE) returns (\\n        address rewardToken,\\n        uint256 amountSold,\\n        uint256 poolClaimAmount\\n    ) {\\n        // Will revert if spot prices are not in line with the oracle values\\n        _checkPriceAndCalculateValue();\\n\\n        // Require one trade per token, if we do not want to buy any tokens at a\\n        // given index then the amount should be set to zero. This applies to pool\\n        // tokens like in the ComposableStablePool.\\n        require(trades.length == NUM_TOKENS());\\n        uint256[] memory amounts;\\n        (rewardToken, amountSold, amounts) = _executeRewardTrades(trades);\\n```\\n