HIGH severity: Hardcoded useEth = true in remove_liquidity_one_coin or remove_liquidity lead to stuck fund. The vulnerability arises when exiting a Curve V2 pool via the _exitPool function in single-sided strategies, where the useEth parameter is hardcoded to true during remove_liquidity_one_coin and remove_liquidity calls. This causes the Curve pool to return native ETH instead of WETH. However, the prot...

Vulnerable code:
// SPDX-License-Identifier: BUSL-1.1
pragma solidity >=0.8.29;

import {IWithdrawRequestManager} from "../interfaces/IWithdrawRequestManager.sol";
import {AbstractSingleSidedLP, BaseLPLib} from "./AbstractSingleSidedLP.sol";
import {ERC20} from "@openzeppelin/contracts/token/ERC20/ERC20.sol";
import {TokenUtils} from "../utils/TokenUtils.sol";
import {ETH_ADDRESS, ALT_ETH_ADDRESS, WETH, CHAIN_ID_MAINNET} from "../utils/Constants.sol";
import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
import "../interfaces/Curve/ICurve.sol";
import "../interfaces/Curve/IConvex.sol";

struct DeploymentParams {
    address pool;
    address poolToken;
    address gauge;
    address convexRewardPool;
    CurveInterface curveInterface;
}

contract CurveConvex2Token is AbstractSingleSidedLP {

    uint256 internal constant _NUM_TOKENS = 2;

    ERC20 internal immutable CURVE_POOL_TOKEN;
    uint8 internal immutable _PRIMARY_INDEX;
    address internal immutable TOKEN_1;
    address internal immutable TOKEN_2;

    function NUM_TOKENS() internal pure override returns (uint256) { return _NUM_TOKENS; }
    function PRIMARY_INDEX() internal view override returns (uint256) { return _PRIMARY_INDEX; }
    function TOKENS() internal view override returns (ERC20[] memory) {
        ERC20[] memory tokens = new ERC20[](_NUM_TOKENS);
        tokens[0] = ERC20(TOKEN_1);
        tokens[1] = ERC20(TOKEN_2);
        return tokens;
    }

    constructor(
        uint256 _maxPoolShare,
        address _asset,
        address _yieldToken,
        uint256 _feeRate,
        address _rewardManager,
        DeploymentParams memory params,
        IWithdrawRequestManager _withdrawRequestManager
    ) AbstractSingleSidedLP(_maxPoolShare, _asset, _yieldToken, _feeRate, _rewardManager, 18, _withdrawRequestManager) {
        CURVE_POOL_TOKEN = ERC20(params.poolToken);

        // We interact with curve pools directly so we never pass the token addresses back
        // to the curve pools. The amounts are passed back based on indexes instead. Therefore
        // we can rewrite the token addresses from ALT Eth (0xeeee...) back to (0x0000...) which
        // is used by the vault internally to represent ETH.
        TOKEN_1 = _rewriteAltETH(ICurvePool(params.pool).coins(0));
        TOKEN_2 = _rewriteAltETH(ICurvePool(params.pool).coins(1));

        // Assets may be WETH, so we need to unwrap it in this case.
        _PRIMARY_INDEX =
            (TOKEN_1 == _asset || (TOKEN_1 == ETH_ADDRESS && _asset == address(WETH))) ? 0 :
            (TOKEN_2 == _asset || (TOKEN_2 == ETH_ADDRESS && _asset == address(WETH))) ? 1 :
            // Otherwise the primary index is not set and we will not be able to enter or exit
            // single sided.
            type(uint8).max;

        LP_LIB = address(new CurveConvexLib(TOKEN_1, TOKEN_2, _asset, _PRIMARY_INDEX, params));
    }

    function _rewriteAltETH(address token) private pure returns (address) {
        return token == address(ALT_ETH_ADDRESS) ? ETH_ADDRESS : address(token);
    }

    function _transferYieldTokenToOwner(address owner, uint256 yieldTokens) internal override {
        _delegateCall(LP_LIB, abi.encodeWithSelector(
            CurveConvexLib.transferYieldTokenToOwner.selector, owner, yieldTokens)
        );
    }

    function _totalPoolSupply() internal view override returns (uint256) {
        return CURVE_POOL_TOKEN.totalSupply();
    }

    function _checkReentrancyContext() internal override {
        CurveConvexLib(payable(LP_LIB)).checkReentrancyContext();
    }
}

contract CurveConvexLib is BaseLPLib {
    using SafeERC20 for ERC20;
    using TokenUtils for ERC20;

    uint256 internal constant _NUM_TOKENS = 2;

    address internal immutable CURVE_POOL;
    ERC20 internal immutable CURVE_POOL_TOKEN;

    /// @dev Curve gauge contract used when there is no convex reward pool
    address internal immutable CURVE_GAUGE;
    /// @dev Convex booster contract used for staking BPT
    address internal immutable CONVEX_BOOSTER;
    /// @dev Convex reward pool contract used for unstaking and claiming reward tokens
    address internal immutable CONVEX_REWARD_POOL;
    uint256 internal immutable CONVEX_POOL_ID;

    uint8 internal immutable _PRIMARY_INDEX;
    address internal immutable ASSET;
    address internal immutable TOKEN_1;
    address internal immutable TOKEN_2;
    CurveInterface internal immutable CURVE_INTERFACE;

    // Payable is required for the CurveV1 interface which will execute a transfer
    // when the remove_liquidity function is called, it only will be done to this contract
    // during the checkReentrancyContext function.
    receive() external payable {}

    constructor(
        address _token1,
        address _token2,
        address _asset,
        uint8 _primaryIndex,
        DeploymentParams memory params
    ) {
        TOKEN_1 = _token1;
        TOKEN_2 = _token2;
        ASSET = _asset;
        _PRIMARY_INDEX = _primaryIndex;

        CURVE_POOL = params.pool;
        CURVE_GAUGE = params.gauge;
        CURVE_POOL_TOKEN = ERC20(params.poolToken);
        CURVE_INTERFACE = params.curveInterface;

        // If the convex reward pool is set then get the booster and pool id, if not then
        // we will stake on the curve gauge directly.
        CONVEX_REWARD_POOL = params.convexRewardPool;
        address convexBooster;
        uint256 poolId;
        if (block.chainid == CHAIN_ID_MAINNET && CONVEX_REWARD_POOL != address(0)) {
            convexBooster = IConvexRewardPool(CONVEX_REWARD_POOL).operator();
            poolId = IConvexRewardPool(CONVEX_REWARD_POOL).pid();
        }

        CONVEX_POOL_ID = poolId;
        CONVEX_BOOSTER = convexBooster;
    }

    function checkReentrancyContext() external {
        uint256[2] memory minAmounts;
        if (CURVE_INTERFACE == CurveInterface.V1) {
            ICurve2TokenPoolV1(CURVE_POOL).remove_liquidity(0, minAmounts);
        } else if (CURVE_INTERFACE == CurveInterface.StableSwapNG) {
            // Total supply on stable swap has a non-reentrant lock
            ICurveStableSwapNG(CURVE_POOL).totalSupply();
        } else if (CURVE_INTERFACE == CurveInterface.V2) {
            // Curve V2 does a `-1` on the liquidity amount so set the amount removed to 1 to
            // avoid an underflow.
            ICurve2TokenPoolV2(CURVE_POOL).remove_liquidity(1, minAmounts, true, address(this));
        } else {
            revert();
        }
    }

    function TOKENS() internal view override returns (ERC20[] memory) {
        ERC20[] memory tokens = new ERC20[](_NUM_TOKENS);
        tokens[0] = ERC20(TOKEN_1);
        tokens[1] = ERC20(TOKEN_2);
        return tokens;
    }

    function initialApproveTokens() external {
        // If either token is ETH_ADDRESS the check approve will short circuit
        ERC20(TOKEN_1).checkApprove(address(CURVE_POOL), type(uint256).max);
        ERC20(TOKEN_2).checkApprove(address(CURVE_POOL), type(uint256).max);
        if (CONVEX_BOOSTER != address(0)) {
            CURVE_POOL_TOKEN.checkApprove(address(CONVEX_BOOSTER), type(uint256).max);
        } else {
            CURVE_POOL_TOKEN.checkApprove(address(CURVE_GAUGE), type(uint256).max);
        }
    }

    function joinPoolAndStake(
        uint256[] memory _amounts, uint256 minPoolClaim
    ) external {
        // Although Curve uses ALT_ETH to represent native ETH, it is rewritten in the Curve2TokenPoolMixin
        // to the Deployments.ETH_ADDRESS which we use internally.
        uint256 msgValue;
        if (TOKEN_1 == ETH_ADDRESS) {
            msgValue = _amounts[0];
        } else if (TOKEN_2 == ETH_ADDRESS) {
            msgValue = _amounts[1];
        }
        if (msgValue > 0) WETH.withdraw(msgValue);

        uint256 lpTokens = _enterPool(_amounts, minPoolClaim, msgValue);

        _stakeLpTokens(lpTokens);
    }

    function unstakeAndExitPool(
        uint256 poolClaim, uint256[] memory _minAmounts, bool isSingleSided
    ) external returns (uint256[] memory exitBalances) {
        _unstakeLpTokens(poolClaim);

        exitBalances = _exitPool(poolClaim, _minAmounts, isSingleSided);

        if (ASSET == address(WETH)) {
            if (TOKEN_1 == ETH_ADDRESS) {
                WETH.deposit{value: exitBalances[0]}();
            } else if (TOKEN_2 == ETH_ADDRESS) {
                WETH.deposit{value: exitBalances[1]}();
            }
        }
    }

    function transferYieldTokenToOwner(address owner, uint256 yieldTokens) external {
        _unstakeLpTokens(yieldTokens);
        CURVE_POOL_TOKEN.safeTransfer(owner, yieldTokens);
    }

    function _enterPool(
        uint256[] memory _amounts, uint256 minPoolClaim, uint256 msgValue
    ) internal returns (uint256) {
        if (CURVE_INTERFACE == CurveInterface.StableSwapNG) {
            return ICurveStableSwapNG(CURVE_POOL).add_liquidity{value: msgValue}(
                _amounts, minPoolClaim
            );
        } 

        uint256[2] memory amounts;
        amounts[0] = _amounts[0];
        amounts[1] = _amounts[1];
        if (CURVE_INTERFACE == CurveInterface.V1) {
            return ICurve2TokenPoolV1(CURVE_POOL).add_liquidity{value: msgValue}(
                amounts, minPoolClaim
            );
        } else if (CURVE_INTERFACE == CurveInterface.V2) {
            return ICurve2TokenPoolV2(CURVE_POOL).add_liquidity{value: msgValue}(
                amounts, minPoolClaim, 0 < msgValue // use_eth = true if msgValue > 0
            );
        }

        revert();
    }

    function _exitPool(
        uint256 poolClaim, uint256[] memory _minAmounts, bool isSingleSided
    ) internal returns (uint256[] memory exitBalances) {
        if (isSingleSided) {
            exitBalances = new uint256[](_NUM_TOKENS);
            if (CURVE_INTERFACE == CurveInterface.V1 || CURVE_INTERFACE == CurveInterface.StableSwapNG) {
                // Method signature is the same for v1 and stable swap ng
                exitBalances[_PRIMARY_INDEX] = ICurve2TokenPoolV1(CURVE_POOL).remove_liquidity_one_coin(
                    poolClaim, int8(_PRIMARY_INDEX), _minAmounts[_PRIMARY_INDEX]
                );
            } else {
                exitBalances[_PRIMARY_INDEX] = ICurve2TokenPoolV2(CURVE_POOL).remove_liquidity_one_coin(
                    // Last two parameters are useEth = true and receiver = this contract
                    poolClaim, _PRIMARY_INDEX, _minAmounts[_PRIMARY_INDEX], true, address(this)
                );
            }
        } else {
            // Two sided exit
            if (CURVE_INTERFACE == CurveInterface.StableSwapNG) {
                return ICurveStableSwapNG(CURVE_POOL).remove_liquidity(poolClaim, _minAmounts);
            }
            
            // Redeem proportionally, min amounts are rewritten to a fixed length array
            uint256[2] memory minAmounts;
            minAmounts[0] = _minAmounts[0];
            minAmounts[1] = _minAmounts[1];

            exitBalances = new uint256[](_NUM_TOKENS);
            if (CURVE_INTERFACE == CurveInterface.V1) {
                uint256[2] memory _exitBalances = ICurve2TokenPoolV1(CURVE_POOL).remove_liquidity(poolClaim, minAmounts);
                exitBalances[0] = _exitBalances[0];
                exitBalances[1] = _exitBalances[1];
            } else {
                exitBalances[0] = TokenUtils.tokenBalance(TOKEN_1);
                exitBalances[1] = TokenUtils.tokenBalance(TOKEN_2);
                // Remove liquidity on CurveV2 does not return the exit amounts so we have to measure
                // them before and after.
                ICurve2TokenPoolV2(CURVE_POOL).remove_liquidity(
                    // Last two parameters are useEth = true and receiver = this contract
                    poolClaim, minAmounts, true, address(this)
                );
                exitBalances[0] = TokenUtils.tokenBalance(TOKEN_1) - exitBalances[0];
                exitBalances[1] = TokenUtils.tokenBalance(TOKEN_2) - exitBalances[1];
            }
        }
    }

    function _stakeLpTokens(uint256 lpTokens) internal {
        if (CONVEX_BOOSTER != address(0)) {
            bool success = IConvexBooster(CONVEX_BOOSTER).deposit(CONVEX_POOL_ID, lpTokens, true);
            require(success);
        } else {
            ICurveGauge(CURVE_GAUGE).deposit(lpTokens);
        }
    }


    function _unstakeLpTokens(uint256 poolClaim) internal {
        if (CONVEX_REWARD_POOL != address(0)) {
            bool success = IConvexRewardPool(CONVEX_REWARD_POOL).withdrawAndUnwrap(poolClaim, false);
            require(success);
        } else {
            ICurveGauge(CURVE_GAUGE).withdraw(poolClaim);
        }
    }

}
// SPDX-License-Identifier: BUSL-1.1
pragma solidity >=0.8.29;

import {ERC20} from "@openzeppelin/contracts/token/ERC20/ERC20.sol";
import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
import {IERC20Metadata} from "@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol";
import {ReentrancyGuardTransient} from "@openzeppelin/contracts/utils/ReentrancyGuardTransient.sol";
import {DEFAULT_DECIMALS, DEFAULT_PRECISION, YEAR, ADDRESS_REGISTRY} from "./utils/Constants.sol";

import {
    Unauthorized,
    UnauthorizedLendingMarketTransfer,
    InsufficientSharesHeld,
    CannotLiquidate,
    CannotEnterPosition,
    CurrentAccountAlreadySet
} from "./interfaces/Errors.sol";
import {IYieldStrategy} from "./interfaces/IYieldStrategy.sol";
import {IOracle} from "./interfaces/Morpho/IOracle.sol";
import {TokenUtils} from "./utils/TokenUtils.sol";
import {Trade, TradeType, TRADING_MODULE, nProxy, TradeFailed} from "./interfaces/ITradingModule.sol";
import {IWithdrawRequestManager} from "./interfaces/IWithdrawRequestManager.sol";
import {Initializable} from "./proxy/Initializable.sol";
import {ADDRESS_REGISTRY} from "./utils/Constants.sol";
import {ILendingRouter} from "./interfaces/ILendingRouter.sol";

/// @title AbstractYieldStrategy
/// @notice This is the base contract for all yield strategies, it implements the core logic for
/// minting, burning and the valuation of tokens.
abstract contract AbstractYieldStrategy is Initializable, ERC20, ReentrancyGuardTransient, IYieldStrategy {
    using TokenUtils for ERC20;
    using SafeERC20 for ERC20;

    uint256 internal constant VIRTUAL_SHARES = 1e6;
    uint256 internal constant VIRTUAL_YIELD_TOKENS = 1;
    uint256 internal constant SHARE_PRECISION = DEFAULT_PRECISION * VIRTUAL_SHARES;

    /// @inheritdoc IYieldStrategy
    address public immutable override asset;
    /// @inheritdoc IYieldStrategy
    address public immutable override yieldToken;
    /// @inheritdoc IYieldStrategy
    uint256 public immutable override feeRate;

    IWithdrawRequestManager internal immutable withdrawRequestManager;

    uint8 internal immutable _yieldTokenDecimals;
    uint8 internal immutable _assetDecimals;

    /********* Storage Variables *********/
    string private s_name;
    string private s_symbol;

    uint32 private s_lastFeeAccrualTime;
    uint256 private s_accruedFeesInYieldToken;
    uint256 private s_escrowedShares;
    /****** End Storage Variables ******/

    /********* Transient Variables *********/
    // Used to adjust the valuation call of price(), is set on some methods and
    // cleared by the lending router using clearCurrentAccount(). This is required to
    // ensure that the variable is set throughout the entire context of the lending router
    // call.
    address internal transient t_CurrentAccount;
    // Set and cleared on every call to a lending router authorized method
    address internal transient t_CurrentLendingRouter;
    // Used to authorize transfers off of the lending market
    address internal transient t_AllowTransfer_To;
    uint256 internal transient t_AllowTransfer_Amount;
    /****** End Transient Variables ******/

    constructor(
        address _asset,
        address _yieldToken,
        uint256 _feeRate,
        uint8 __yieldTokenDecimals
    ) ERC20("", "") {
        feeRate = _feeRate;
        asset = address(_asset);
        yieldToken = address(_yieldToken);
        // Not all yield tokens have a decimals() function (i.e. Convex staked tokens), so we
        // do have to pass in the decimals as a parameter.
        _yieldTokenDecimals = __yieldTokenDecimals;
        _assetDecimals = TokenUtils.getDecimals(_asset);
    }

    function name() public view override(ERC20, IERC20Metadata) returns (string memory) {
        return s_name;
    }

    function symbol() public view override(ERC20, IERC20Metadata) returns (string memory) {
        return s_symbol;
    }

    /*** Valuation and Conversion Functions ***/

    /// @inheritdoc IYieldStrategy
    function convertSharesToYieldToken(uint256 shares) public view override returns (uint256) {
        // NOTE: rounds down on division
        return (shares * (_yieldTokenBalance() - feesAccrued() + VIRTUAL_YIELD_TOKENS)) / (effectiveSupply());
    }

    /// @inheritdoc IYieldStrategy
    function convertYieldTokenToShares(uint256 yieldTokens) public view returns (uint256) {
        // NOTE: rounds down on division
        return (yieldTokens * effectiveSupply()) / (_yieldTokenBalance() - feesAccrued() + VIRTUAL_YIELD_TOKENS);
    }

    /// @inheritdoc IYieldStrategy
    function convertToShares(uint256 assets) public view override returns (uint256) {
        // NOTE: rounds down on division
        uint256 yieldTokens = assets * (10 ** (_yieldTokenDecimals + DEFAULT_DECIMALS)) / 
            (convertYieldTokenToAsset() * (10 ** _assetDecimals));
        return convertYieldTokenToShares(yieldTokens);
    }

    /// @inheritdoc IOracle
    function price() public view override returns (uint256) {
        return convertToAssets(SHARE_PRECISION) * (10 ** (36 - 24));
    }

    /// @inheritdoc IYieldStrategy
    function price(address borrower) external override returns (uint256) {
        // Do not change the current account in this method since this method is not
        // authenticated and we do not want to have any unexpected side effects.
        address prevCurrentAccount = t_CurrentAccount;

        t_CurrentAccount = borrower;
        uint256 p = convertToAssets(SHARE_PRECISION) * (10 ** (36 - 24));

        t_CurrentAccount = prevCurrentAccount;
        return p;
    }

    /// @inheritdoc IYieldStrategy
    function totalAssets() public view override returns (uint256) {
        return convertToAssets(totalSupply());
    }

    /// @inheritdoc IYieldStrategy
    function convertYieldTokenToAsset() public view returns (uint256) {
        // The trading module always returns a positive rate in 18 decimals so we can safely
        // cast to uint256
        (int256 rate , /* */) = TRADING_MODULE.getOraclePrice(yieldToken, asset);
        return uint256(rate);
    }

    /// @inheritdoc IYieldStrategy
    function effectiveSupply() public view returns (uint256) {
        return (totalSupply() - s_escrowedShares + VIRTUAL_SHARES);
    }

    /*** Fee Methods ***/

    /// @inheritdoc IYieldStrategy
    function feesAccrued() public view override returns (uint256 feesAccruedInYieldToken) {
        return s_accruedFeesInYieldToken + _calculateAdditionalFeesInYieldToken();
    }

    /// @inheritdoc IYieldStrategy
    function collectFees() external override {
        _accrueFees();
        _transferYieldTokenToOwner(ADDRESS_REGISTRY.feeReceiver(), s_accruedFeesInYieldToken);

        delete s_accruedFeesInYieldToken;
    }

    /*** Core Functions ***/
    modifier onlyLendingRouter() {
        if (ADDRESS_REGISTRY.isLendingRouter(msg.sender) == false) revert Unauthorized(msg.sender);
        t_CurrentLendingRouter = msg.sender;
        _;
        delete t_CurrentLendingRouter;
    }

    modifier setCurrentAccount(address onBehalf) {
        if (t_CurrentAccount == address(0)) {
            t_CurrentAccount = onBehalf;
        } else if (t_CurrentAccount != onBehalf) {
            revert CurrentAccountAlreadySet();
        }

        _;
    }

    /// @inheritdoc IYieldStrategy
    function clearCurrentAccount() external override onlyLendingRouter {
        delete t_CurrentAccount;
    }

    function mintShares(
        uint256 assetAmount,
        address receiver,
        bytes calldata depositData
    ) external override onlyLendingRouter setCurrentAccount(receiver) nonReentrant returns (uint256 sharesMinted) {
        // Cannot mint shares if the receiver has an active withdraw request
        if (_isWithdrawRequestPending(receiver)) revert CannotEnterPosition();
        ERC20(asset).safeTransferFrom(t_CurrentLendingRouter, address(this), assetAmount);
        sharesMinted = _mintSharesGivenAssets(assetAmount, depositData, receiver);

        t_AllowTransfer_To = t_CurrentLendingRouter;
        t_AllowTransfer_Amount = sharesMinted;
        // Transfer the shares to the lending router so it can supply collateral
        _transfer(receiver, t_CurrentLendingRouter, sharesMinted);
    }

    function burnShares(
        address sharesOwner,
        uint256 sharesToBurn,
        uint256 sharesHeld,
        bytes calldata redeemData
    ) external override onlyLendingRouter setCurrentAccount(sharesOwner) nonReentrant returns (uint256 assetsWithdrawn) {
        assetsWithdrawn = _burnShares(sharesToBurn, sharesHeld, redeemData, sharesOwner);

        // Send all the assets back to the lending router
        ERC20(asset).safeTransfer(t_CurrentLendingRouter, assetsWithdrawn);
    }

    function allowTransfer(
        address to, uint256 amount, address currentAccount
    ) external setCurrentAccount(currentAccount) onlyLendingRouter {
        // Sets the transient variables to allow the lending market to transfer shares on exit position
        // or liquidation.
        t_AllowTransfer_To = to;
        t_AllowTransfer_Amount = amount;
    }

    function preLiquidation(
        address liquidator,
        address liquidateAccount,
        uint256 sharesToLiquidate,
        uint256 accountSharesHeld
    ) external onlyLendingRouter {
        t_CurrentAccount = liquidateAccount;
        // Liquidator cannot liquidate if they have an active withdraw request, including a tokenized
        // withdraw request.
        if (_isWithdrawRequestPending(liquidator)) revert CannotEnterPosition();
        // Cannot receive a pending withdraw request if the liquidator has a balanceOf
        if (_isWithdrawRequestPending(liquidateAccount) && balanceOf(liquidator) > 0) {
            revert CannotEnterPosition();
        }
        _preLiquidation(liquidateAccount, liquidator, sharesToLiquidate, accountSharesHeld);

        // Allow transfers to the lending router which will proxy the call to liquidate.
        t_AllowTransfer_To = msg.sender;
        t_AllowTransfer_Amount = sharesToLiquidate;
    }

    function postLiquidation(
        address liquidator,
        address liquidateAccount,
        uint256 sharesToLiquidator
    ) external onlyLendingRouter {
        t_AllowTransfer_To = liquidator;
        t_AllowTransfer_Amount = sharesToLiquidator;
        // Transfer the shares to the liquidator from the lending router
        _transfer(t_CurrentLendingRouter, liquidator, sharesToLiquidator);

        _postLiquidation(liquidator, liquidateAccount, sharesToLiquidator);

        // Clear the transient variables to prevent re-use in a future call.
        delete t_CurrentAccount;
    }

    /// @inheritdoc IYieldStrategy
    /// @dev We do not set the current account here because valuation is not done in this method.
    /// A native balance does not require a collateral check.
    function redeemNative(
        uint256 sharesToRedeem,
        bytes memory redeemData
    ) external override nonReentrant returns (uint256 assetsWithdrawn) {
        uint256 sharesHeld = balanceOf(msg.sender);
        if (sharesHeld == 0) revert InsufficientSharesHeld();

        assetsWithdrawn = _burnShares(sharesToRedeem, sharesHeld, redeemData, msg.sender);
        ERC20(asset).safeTransfer(msg.sender, assetsWithdrawn);
    }

    /// @inheritdoc IYieldStrategy
    function initiateWithdraw(
        address account,
        uint256 sharesHeld,
        bytes calldata data
    ) external onlyLendingRouter setCurrentAccount(account) override returns (uint256 requestId) {
        requestId = _withdraw(account, sharesHeld, data);
    }

    /// @inheritdoc IYieldStrategy
    /// @dev We do not set the current account here because valuation is not done in this method. A
    /// native balance does not require a collateral check.
    function initiateWithdrawNative(
        bytes memory data
    ) external override returns (uint256 requestId) {
        requestId = _withdraw(msg.sender, balanceOf(msg.sender), data);
    }

    function _withdraw(address account, uint256 sharesHeld, bytes memory data) internal returns (uint256 requestId) {
        if (sharesHeld == 0) revert InsufficientSharesHeld();

        // Accrue fees before initiating a withdraw since it will change the effective supply
        _accrueFees();
        uint256 yieldTokenAmount = convertSharesToYieldToken(sharesHeld);
        requestId = _initiateWithdraw(account, yieldTokenAmount, sharesHeld, data);
        // Escrow the shares after the withdraw since it will change the effective supply
        // during reward claims when using the RewardManagerMixin.
        s_escrowedShares += sharesHeld;
    }

    /*** Private Functions ***/

    function _calculateAdditionalFeesInYieldToken() private view returns (uint256 additionalFeesInYieldToken) {
        uint256 timeSinceLastFeeAccrual = block.timestamp - s_lastFeeAccrualTime;
        // e ^ (feeRate * timeSinceLastFeeAccrual / YEAR)
        uint256 x = (feeRate * timeSinceLastFeeAccrual) / YEAR;
        if (x == 0) return 0;

        uint256 preFeeUserHeldYieldTokens = _yieldTokenBalance() - s_accruedFeesInYieldToken;
        // Taylor approximation of e ^ x = 1 + x + x^2 / 2! + x^3 / 3! + ...
        uint256 eToTheX = DEFAULT_PRECISION + x + (x * x) / (2 * DEFAULT_PRECISION) + (x * x * x) / (6 * DEFAULT_PRECISION * DEFAULT_PRECISION);
        // Decay the user's yield tokens by e ^ (feeRate * timeSinceLastFeeAccrual / YEAR)
        uint256 postFeeUserHeldYieldTokens = preFeeUserHeldYieldTokens * DEFAULT_PRECISION / eToTheX;

        additionalFeesInYieldToken = preFeeUserHeldYieldTokens - postFeeUserHeldYieldTokens;
    }

    function _accrueFees() private {
        if (s_lastFeeAccrualTime == block.timestamp) return;
        // NOTE: this has to be called before any mints or burns.
        s_accruedFeesInYieldToken += _calculateAdditionalFeesInYieldToken();
        s_lastFeeAccrualTime = uint32(block.timestamp);
    }

    function _update(address from, address to, uint256 value) internal override {
        if (from != address(0) && to != address(0)) {
            // Any transfers off of the lending market must be authorized here, this means that native balances
            // held cannot be transferred.
            if (t_AllowTransfer_To != to) revert UnauthorizedLendingMarketTransfer(from, to, value);
            if (t_AllowTransfer_Amount < value) revert UnauthorizedLendingMarketTransfer(from, to, value);

            delete t_AllowTransfer_To;
            delete t_AllowTransfer_Amount;
        }

        super._update(from, to, value);
    }

    /*** Internal Helper Functions ***/

    function _isWithdrawRequestPending(address account) virtual internal view returns (bool) {
        return address(withdrawRequestManager) != address(0)
            && withdrawRequestManager.isPendingWithdrawRequest(address(this), account);
    }

    function _yieldTokenBalance() internal view returns (uint256) {
        return ERC20(yieldToken).balanceOf(address(this));
    }

    /// @dev Can be used to delegate call to the TradingModule's implementation in order to execute a trade
    function _executeTrade(
        Trade memory trade,
        uint16 dexId
    ) internal returns (uint256 amountSold, uint256 amountBought) {
        if (trade.tradeType == TradeType.STAKE_TOKEN) {
            IWithdrawRequestManager wrm = ADDRESS_REGISTRY.getWithdrawRequestManager(trade.buyToken);
            ERC20(trade.sellToken).checkApprove(address(wrm), trade.amount);
            amountBought = wrm.stakeTokens(trade.sellToken, trade.amount, trade.exchangeData);
            return (trade.amount, amountBought);
        } else {
            address implementation = nProxy(payable(address(TRADING_MODULE))).getImplementation();
            bytes memory result = _delegateCall(
                implementation, abi.encodeWithSelector(TRADING_MODULE.executeTrade.selector, dexId, trade)
            );
            (amountSold, amountBought) = abi.decode(result, (uint256, uint256));
        }
    }

    function _delegateCall(address target, bytes memory data) internal returns (bytes memory result) {
        bool success;
        (success, result) = target.delegatecall(data);
        if (!success) {
            assembly {
                // Copy the return data to memory
                returndatacopy(0, 0, returndatasize())
                // Revert with the return data
                revert(0, returndatasize())
            }
        }
    }

    /*** Virtual Functions ***/

    function _initialize(bytes calldata data) internal override virtual {
        (string memory _name, string memory _symbol) = abi.decode(data, (string, string));
        s_name = _name;
        s_symbol = _symbol;

        s_lastFeeAccrualTime = uint32(block.timestamp);
        emit VaultCreated(address(this));
    }

    /// @dev Marked as virtual to allow for RewardManagerMixin to override
    function _mintSharesGivenAssets(uint256 assets, bytes memory depositData, address receiver) internal virtual returns (uint256 sharesMinted) {
        if (assets == 0) return 0;

        // First accrue fees on the yield token
        _accrueFees();
        uint256 initialYieldTokenBalance = _yieldTokenBalance();
        _mintYieldTokens(assets, receiver, depositData);
        uint256 yieldTokensMinted = _yieldTokenBalance() - initialYieldTokenBalance;

        sharesMinted = (yieldTokensMinted * effectiveSupply()) / (initialYieldTokenBalance - feesAccrued() + VIRTUAL_YIELD_TOKENS);
        _mint(receiver, sharesMinted);
    }

    /// @dev Marked as virtual to allow for RewardManagerMixin to override
    function _burnShares(
        uint256 sharesToBurn,
        uint256 /* sharesHeld */,
        bytes memory redeemData,
        address sharesOwner
    ) internal virtual returns (uint256 assetsWithdrawn) {
        if (sharesToBurn == 0) return 0;
        bool isEscrowed = _isWithdrawRequestPending(sharesOwner);

        uint256 initialAssetBalance = TokenUtils.tokenBalance(asset);

        // First accrue fees on the yield token
        _accrueFees();
        _redeemShares(sharesToBurn, sharesOwner, isEscrowed, redeemData);
        if (isEscrowed) s_escrowedShares -= sharesToBurn;

        uint256 finalAssetBalance = TokenUtils.tokenBalance(asset);
        assetsWithdrawn = finalAssetBalance - initialAssetBalance;

        // This burns the shares from the sharesOwner's balance
        _burn(sharesOwner, sharesToBurn);
    }

    /// @dev Some yield tokens (such as Convex staked tokens) cannot be transferred, so we may need
    /// to override this function.
    function _transferYieldTokenToOwner(address owner, uint256 yieldTokens) internal virtual {
        ERC20(yieldToken).safeTransfer(owner, yieldTokens);
    }

    /// @dev Returns the maximum number of shares that can be liquidated. Allows the strategy to override the
    /// underlying lending market's liquidation logic.
    function _preLiquidation(address liquidateAccount, address liquidator, uint256 sharesToLiquidate, uint256 accountSharesHeld) internal virtual;

    /// @dev Called after liquidation
    function _postLiquidation(address liquidator, address liquidateAccount, uint256 sharesToLiquidator) internal virtual returns (bool didTokenize);

    /// @dev Mints yield tokens given a number of assets.
    function _mintYieldTokens(uint256 assets, address receiver, bytes memory depositData) internal virtual;

    /// @dev Redeems shares
    function _redeemShares(
        uint256 sharesToRedeem,
        address sharesOwner,
        bool isEscrowed,
        bytes memory redeemData
    ) internal virtual;

    function _initiateWithdraw(
        address account,
        uint256 yieldTokenAmount,
        uint256 sharesHeld,
        bytes memory data
    ) internal virtual returns (uint256 requestId);

    /// @inheritdoc IYieldStrategy
    function convertToAssets(uint256 shares) public view virtual override returns (uint256) {
        uint256 yieldTokens = convertSharesToYieldToken(shares);
        // NOTE: rounds down on division
        return (yieldTokens * convertYieldTokenToAsset() * (10 ** _assetDecimals)) /
            (10 ** (_yieldTokenDecimals + DEFAULT_DECIMALS));
    }

}



Vulnerability type: CWE-691|CWE-670