Vulnerability: Vault's `totalStrategyTokenGlobal` will not be in sync
Severity: high

Description:
The `strategyContext.vaultState.totalStrategyTokenGlobal` variable that tracks the number of strategy tokens held in the vault will not be in sync and will cause accounting issues within the vault.\\nThis affects both the TwoToken and Boosted3Token vaults\\nThe `StrategyUtils._convertStrategyTokensToBPTClaim` function might return zero if a small number of `strategyTokenAmount` is passed into the function. If `(strategyTokenAmount * context.vaultState.totalBPTHeld)` is smaller than `context.vaultState.totalStrategyTokenGlobal`, the `bptClaim` will be zero.\\n```\\nFile: StrategyUtils.sol\\n    /// @notice Converts strategy tokens to BPT\\n    function _convertStrategyTokensToBPTClaim(StrategyContext memory context, uint256 strategyTokenAmount)\\n        internal pure returns (uint256 bptClaim) {\\n        require(strategyTokenAmount <= context.vaultState.totalStrategyTokenGlobal);\\n        if (context.vaultState.totalStrategyTokenGlobal > 0) {\\n            bptClaim = (strategyTokenAmount * context.vaultState.totalBPTHeld) / context.vaultState.totalStrategyTokenGlobal;\\n        }\\n    }\\n```\\n\\nIn Line 441 of the `Boosted3TokenPoolUtils._redeem` function below, if `bptClaim` is zero, it will return zero and exit the function immediately.\\nIf a small number of `strategyTokens` is passed into the `_redeem` function and the `bptClaim` ends up as zero, the caller of the `_redeem` function will assume that all the `strategyTokens` have been redeemed.\\n```\\nFile: Boosted3TokenPoolUtils.sol\\n    function _redeem(\\n        ThreeTokenPoolContext memory poolContext,\\n        StrategyContext memory strategyContext,\\n        AuraStakingContext memory stakingContext,\\n        uint256 strategyTokens,\\n        uint256 minPrimary\\n    ) internal returns (uint256 finalPrimaryBalance) {\\n        uint256 bptClaim = strategyContext._convertStrategyTokensToBPTClaim(strategyTokens);\\n\\n        if (bptClaim == 0) return 0;\\n\\n        finalPrimaryBalance = _unstakeAndExitPool({\\n            stakingContext: stakingContext,\\n            poolContext: poolContext,\\n            bptClaim: bptClaim,\\n            minPrimary: minPrimary\\n        });\\n\\n        strategyContext.vaultState.totalBPTHeld -= bptClaim;\\n        strategyContext.vaultState.totalStrategyTokenGlobal -= strategyTokens.toUint80();\\n        strategyContext.vaultState.setStrategyVaultState(); \\n    }\\n```\\n\\nThe following function shows an example of the caller of the `_redeem` function at Line 171 below accepting the zero value as it does not revert when the zero value is returned by the `_redeem` function. Thus, it will consider the small number of `strategyTokens` to be redeemed. Note that the `_redeemFromNotional` function calls the `_redeem` function under the hood.\\n```\\nFile: BaseStrategyVault.sol\\n    function redeemFromNotional(\\n        address account,\\n        address receiver,\\n        uint256 strategyTokens,\\n        uint256 maturity,\\n        uint256 underlyingToRepayDebt,\\n        bytes calldata data\\n    ) external onlyNotional returns (uint256 transferToReceiver) {\\n        uint256 borrowedCurrencyAmount = _redeemFromNotional(account, strategyTokens, maturity, data);\\n\\n        uint256 transferToNotional;\\n        if (account == address(this) || borrowedCurrencyAmount <= underlyingToRepayDebt) {\\n            // It may be the case that insufficient tokens were redeemed to repay the debt. If this\\n            // happens the Notional will attempt to recover the shortfall from the account directly.\\n            // This can happen if an account wants to reduce their leverage by paying off debt but\\n            // does not want to sell strategy tokens to do so.\\n            // The other situation would be that the vault is calling redemption to deleverage or\\n            // settle. In that case all tokens go back to Notional.\\n            transferToNotional = borrowedCurrencyAmount;\\n        } else {\\n            transferToNotional = underlyingToRepayDebt;\\n            unchecked { transferToReceiver = borrowedCurrencyAmount - underlyingToRepayDebt; }\\n        }\\n\\n        if (_UNDERLYING_IS_ETH) {\\n            if (transferToReceiver > 0) payable(receiver).transfer(transferToReceiver);\\n            if (transferToNotional > 0) payable(address(NOTIONAL)).transfer(transferToNotional);\\n        } else {\\n            if (transferToReceiver > 0) _UNDERLYING_TOKEN.checkTransfer(receiver, transferToReceiver);\\n            if (transferToNotional > 0) _UNDERLYING_TOKEN.checkTransfer(address(NOTIONAL), transferToNotional);\\n        }\\n    }\\n```\\n\\nSubsequently, on Notional side, it will deduct the redeemed strategy tokens from itsvaultState.totalStrategyTokens state (Refer to Line 177 below)\\n```\\nFile: VaultAction.sol\\n    /// @notice Redeems strategy tokens to cash\\n    function _redeemStrategyTokensToCashInternal(\\n        VaultConfig memory vaultConfig,\\n        uint256 maturity,\\n        uint256 strategyTokensToRedeem,\\n        bytes calldata vaultData\\n    ) private nonReentrant returns (int256 assetCashRequiredToSettle, int256 underlyingCashRequiredToSettle) {\\n        // If the vault allows further re-entrancy then set the status back to the default\\n        if (vaultConfig.getFlag(VaultConfiguration.ALLOW_REENTRANCY)) {\\n            reentrancyStatus = _NOT_ENTERED;\\n        }\\n\\n        VaultState memory vaultState = VaultStateLib.getVaultState(vaultConfig.vault, maturity);\\n        (int256 assetCashReceived, uint256 underlyingToReceiver) = vaultConfig.redeemWithoutDebtRepayment(\\n            vaultConfig.vault, strategyTokensToRedeem, maturity, vaultData\\n        );\\n        require(assetCashReceived > 0);\\n        // Safety check to ensure that the vault does not somehow receive tokens in this scenario\\n        require(underlyingToReceiver == 0);\\n\\n        vaultState.totalAssetCash = vaultState.totalAssetCash.add(uint256(assetCashReceived));\\n        vaultState.totalStrategyTokens = vaultState.totalStrategyTokens.sub(strategyTokensToRedeem);\\n        vaultState.setVaultState(vaultConfig.vault);\\n\\n        emit VaultRedeemStrategyToken(vaultConfig.vault, maturity, assetCashReceived, strategyTokensToRedeem);\\n        return _getCashRequiredToSettle(vaultConfig, vaultState, maturity);\\n    }\\n```\\n\\nHowever, the main issue is that when a small number of `strategyTokens` are redeemed and `bptClaim` is zero, the `_redeem` function will exit at Line 441 immediately. Thus, the redeemed strategy tokens are not deducted from the `strategyContext.vaultState.totalStrategyTokenGlobal` accounting variable on the Vault side.\\nThus, `strategyContext.vaultState.totalStrategyTokenGlobal` on the Vault side will not be in sync with the `vaultState.totalStrategyTokens` on the Notional side.\\n```\\nFile: Boosted3TokenPoolUtils.sol\\n    function _redeem(\\n        ThreeTokenPoolContext memory poolContext,\\n        StrategyContext memory strategyContext,\\n        AuraStakingContext memory stakingContext,\\n        uint256 strategyTokens,\\n        uint256 minPrimary\\n    ) internal returns (uint256 finalPrimaryBalance) {\\n        uint256 bptClaim = strategyContext._convertStrategyTokensToBPTClaim(strategyTokens);\\n\\n        if (bptClaim == 0) return 0;\\n\\n        finalPrimaryBalance = _unstakeAndExitPool({\\n            stakingContext: stakingContext,\\n            poolContext: poolContext,\\n            bptClaim: bptClaim,\\n            minPrimary: minPrimary\\n        });\\n\\n        strategyContext.vaultState.totalBPTHeld -= bptClaim;\\n        strategyContext.vaultState.totalStrategyTokenGlobal -= strategyTokens.toUint80();\\n        strategyContext.vaultState.setStrategyVaultState(); \\n    }\\n```\\n

Impact:
The `strategyContext.vaultState.totalStrategyTokenGlobal` variable that tracks the number of strategy tokens held in the vault will not be in sync and will cause accounting issues within the vault. This means that the actual total strategy tokens in circulation and the `strategyContext.vaultState.totalStrategyTokenGlobal` will be different. The longer the issue is left unfixed, the larger the differences between them.\\nThe `strategyContext.vaultState.totalStrategyTokenGlobal` will be larger than expected because it does not deduct the number of strategy tokens when it should be under certain conditions.\\nOne example of the impact is as follows: The affected variable is used within the `_convertStrategyTokensToBPTClaim` and `_convertBPTClaimToStrategyTokens`, `_getBPTHeldInMaturity` functions. These functions are used within the deposit and redeem functions of the vault. Therefore, the number of strategy tokens or assets the users receive will not be accurate and might be less or more than expected.

Recommended mitigation:
The number of strategy tokens redeemed needs to be deducted from the vault's `totalStrategyTokenGlobal` regardless of the `bptClaim` value. Otherwise, the vault's `totalStrategyTokenGlobal` will not be in sync.\\nWhen `bptClaim` is zero, it does not always mean that no strategy token has been redeemed. Based on the current vault implementation, the `bptClaim` might be zero because the number of strategy tokens to be redeemed is too small and thus it causes Solidity to round down to zero.\\n```\\nfunction _redeem(\\n    ThreeTokenPoolContext memory poolContext,\\n    StrategyContext memory strategyContext,\\n    AuraStakingContext memory stakingContext,\\n    uint256 strategyTokens,\\n    uint256 minPrimary\\n) internal returns (uint256 finalPrimaryBalance) {\\n    uint256 bptClaim = strategyContext._convertStrategyTokensToBPTClaim(strategyTokens);\\n// Add the line below\\n strategyContext.vaultState.totalStrategyTokenGlobal // Remove the line below\\n= strategyTokens.toUint80();\\n// Add the line below\\n strategyContext.vaultState.setStrategyVaultState();\\n// Add the line below\\n\\n    if (bptClaim == 0) return 0;\\n\\n    finalPrimaryBalance = _unstakeAndExitPool({\\n        stakingContext: stakingContext,\\n        poolContext: poolContext,\\n        bptClaim: bptClaim,\\n        minPrimary: minPrimary\\n    });\\n\\n    strategyContext.vaultState.totalBPTHeld // Remove the line below\\n= bptClaim;\\n// Remove the line below\\n   strategyContext.vaultState.totalStrategyTokenGlobal // Remove the line below\\n= strategyTokens.toUint80();\\n    strategyContext.vaultState.setStrategyVaultState(); \\n}\\n```\\n