CurveConvex2Token

Description:

Multi-signature wallet contract requiring multiple confirmations for transaction execution.

Blockchain: Ethereum

Source Code: View Code On The Blockchain

Solidity Source Code:

{{
  "language": "Solidity",
  "sources": {
    "src/single-sided-lp/CurveConvex2Token.sol": {
      "content": "// 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 {
    ICurvePool,
    ICurve2TokenPoolV1,
    ICurve2TokenPoolV2,
    ICurveStableSwapNG,
    ICurveGauge,
    CurveInterface
} from "../interfaces/Curve/ICurve.sol";
import { IConvexBooster, IConvexRewardPool } from "../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 public 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() public view override returns (uint256) {
        return _PRIMARY_INDEX;
    }

    function TOKENS() public 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
    )
        AbstractSingleSidedLP(_maxPoolShare, _asset, _yieldToken, _feeRate, _rewardManager, 18)
    {
        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));

        // Validate that withdraw request managers are either all address(0) or all non-zero
        _validateWithdrawRequestManagers();
    }

    function strategy() public pure override returns (string memory) {
        return "CurveConvex2Token";
    }

    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) {
            ICurve2TokenPoolV2(CURVE_POOL).claim_admin_fees();
        } 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 (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, ERC20[] memory tokens)
    {
        _unstakeLpTokens(poolClaim);

        exitBalances = _exitPool(poolClaim, _minAmounts, isSingleSided);
        tokens = TOKENS();

        // Any ETH received from exit pool needs to be wrapped back into WETH. Change the
        // reported token array to WETH accordingly. This also allows us to use a WETH withdraw
        // request manager.
        if (ASSET == address(WETH)) {
            if (TOKEN_1 == ETH_ADDRESS) {
                WETH.deposit{ value: exitBalances[0] }();
                tokens[0] = ERC20(address(WETH));
            } else if (TOKEN_2 == ETH_ADDRESS) {
                WETH.deposit{ value: exitBalances[1] }();
                tokens[1] = ERC20(address(WETH));
            }
        }
    }

    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 (0 < _amounts[0]) ERC20(TOKEN_1).checkApprove(address(CURVE_POOL), _amounts[0]);
        if (0 < _amounts[1]) ERC20(TOKEN_2).checkApprove(address(CURVE_POOL), _amounts[1]);

        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 and receiver = this contract. Check if one of the tokens
                    // is ETH and have it return ETH in this case so that we can wrap it back into WETH if required.
                    poolClaim,
                    _PRIMARY_INDEX,
                    _minAmounts[_PRIMARY_INDEX],
                    TOKEN_1 == ETH_ADDRESS || TOKEN_2 == ETH_ADDRESS,
                    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 and receiver = this contract. Check if one of the tokens
                    // is ETH and have it return ETH in this case so that we can wrap it back into WETH if required.
                    poolClaim,
                    minAmounts,
                    TOKEN_1 == ETH_ADDRESS || TOKEN_2 == ETH_ADDRESS,
                    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);
        }
    }
}
"
    },
    "src/interfaces/IWithdrawRequestManager.sol": {
      "content": "// SPDX-License-Identifier: BUSL-1.1
pragma solidity >=0.8.29;

import { TradeType } from "./ITradingModule.sol";

/// Each withdraw request manager contract is responsible for managing withdraws of a token
/// from a specific token (i.e. wstETH, weETH, sUSDe, etc). Each yield strategy can call the
/// appropriate withdraw request manager to initiate a withdraw of a given yield token.

struct StakingTradeParams {
    TradeType tradeType;
    uint256 minPurchaseAmount;
    bytes exchangeData;
    uint16 dexId;
    bytes stakeData;
}

struct WithdrawRequest {
    uint256 requestId;
    uint120 yieldTokenAmount;
    uint120 sharesAmount;
}

struct TokenizedWithdrawRequest {
    uint120 totalYieldTokenAmount;
    uint120 totalWithdraw;
    bool finalized;
}

interface IWithdrawRequestManager {
    event ApprovedVault(address indexed vault, bool indexed isApproved);
    event InitiateWithdrawRequest(
        address indexed account,
        address indexed vault,
        uint256 yieldTokenAmount,
        uint256 sharesAmount,
        uint256 requestId
    );

    event WithdrawRequestTokenized(
        address indexed from, address indexed to, address indexed vault, uint256 requestId, uint256 sharesAmount
    );

    event WithdrawRequestFinalized(
        address indexed vault, address indexed account, uint256 requestId, uint256 totalWithdraw
    );

    event WithdrawRequestRedeemed(
        address indexed vault,
        address indexed account,
        uint256 requestId,
        uint256 withdrawYieldTokenAmount,
        uint256 sharesBurned,
        bool isCleared
    );

    /// @notice Returns the token that will be the result of staking
    /// @return yieldToken the yield token of the withdraw request manager
    function YIELD_TOKEN() external view returns (address);

    /// @notice Returns the token that will be the result of the withdraw request
    /// @return withdrawToken the withdraw token of the withdraw request manager
    function WITHDRAW_TOKEN() external view returns (address);

    /// @notice Returns the token that will be used to stake
    /// @return stakingToken the staking token of the withdraw request manager
    function STAKING_TOKEN() external view returns (address);

    /// @notice Returns whether a vault is approved to initiate withdraw requests
    /// @param vault the vault to check the approval for
    /// @return isApproved whether the vault is approved
    function isApprovedVault(address vault) external view returns (bool);

    /// @notice Returns whether a vault has a pending withdraw request
    /// @param vault the vault to check the pending withdraw request for
    /// @param account the account to check the pending withdraw request for
    /// @return isPending whether the vault has a pending withdraw request
    function isPendingWithdrawRequest(address vault, address account) external view returns (bool);

    /// @notice Sets whether a vault is approved to initiate withdraw requests
    /// @param vault the vault to set the approval for
    /// @param isApproved whether the vault is approved
    function setApprovedVault(address vault, bool isApproved) external;

    /// @notice Stakes the deposit token to the yield token and transfers it back to the vault
    /// @dev Only approved vaults can stake tokens
    /// @param depositToken the token to stake, will be transferred from the vault
    /// @param amount the amount of tokens to stake
    /// @param data additional data for the stake
    function stakeTokens(
        address depositToken,
        uint256 amount,
        bytes calldata data
    )
        external
        returns (uint256 yieldTokensMinted);

    /// @notice Initiates a withdraw request
    /// @dev Only approved vaults can initiate withdraw requests
    /// @param account the account to initiate the withdraw request for
    /// @param yieldTokenAmount the amount of yield tokens to withdraw
    /// @param sharesAmount the amount of shares to withdraw, used to mark the shares to
    /// yield token ratio at the time of the withdraw request
    /// @param data additional data for the withdraw request
    /// @return requestId the request id of the withdraw request
    function initiateWithdraw(
        address account,
        uint256 yieldTokenAmount,
        uint256 sharesAmount,
        bytes calldata data,
        address forceWithdrawFrom
    )
        external
        returns (uint256 requestId);

    /// @notice Attempts to redeem active withdraw requests during vault exit
    /// @dev Will revert if the withdraw request is not finalized
    /// @param account the account to finalize and redeem the withdraw request for
    /// @param withdrawYieldTokenAmount the amount of yield tokens to withdraw
    /// @param sharesToBurn the amount of shares to burn for the yield token
    /// @return tokensWithdrawn amount of withdraw tokens redeemed from the withdraw requests
    function finalizeAndRedeemWithdrawRequest(
        address account,
        uint256 withdrawYieldTokenAmount,
        uint256 sharesToBurn
    )
        external
        returns (uint256 tokensWithdrawn);

    /// @notice Finalizes withdraw requests outside of a vault exit. This may be required in cases if an
    /// account is negligent in exiting their vault position and letting the withdraw request sit idle
    /// could result in losses. The withdraw request is finalized and stored in a tokenized withdraw request
    /// where the account has the full claim on the withdraw.
    /// @dev No access control is enforced on this function but no tokens are transferred off the request
    /// manager either.
    /// @dev Will revert if the withdraw request is not finalized
    function finalizeRequestManual(address vault, address account) external returns (uint256 tokensWithdrawn);

    /// @notice If an account has an illiquid withdraw request, this method will tokenize their
    /// claim on it during liquidation.
    /// @dev Only approved vaults can tokenize withdraw requests
    /// @param from the account that is being liquidated
    /// @param to the liquidator
    /// @param sharesAmount the amount of shares to the liquidator
    function tokenizeWithdrawRequest(
        address from,
        address to,
        uint256 sharesAmount
    )
        external
        returns (bool didTokenize);

    /// @notice Allows the emergency exit role to rescue tokens from the withdraw request manager
    /// @param cooldownHolder the cooldown holder to rescue tokens from
    /// @param token the token to rescue
    /// @param receiver the receiver of the rescued tokens
    /// @param amount the amount of tokens to rescue
    function rescueTokens(address cooldownHolder, address token, address receiver, uint256 amount) external;

    /// @notice Returns whether a withdraw request can be finalized, only used off chain
    /// to determine if a withdraw request can be finalized.
    /// @param requestId the request id of the withdraw request
    /// @return canFinalize whether the withdraw request can be finalized
    function canFinalizeWithdrawRequest(uint256 requestId) external view returns (bool);

    /// @notice Returns the withdraw request and tokenized withdraw request for an account
    /// @param vault the vault to get the withdraw request for
    /// @param account the account to get the withdraw request for
    /// @return w the withdraw request
    /// @return s the tokenized withdraw request
    function getWithdrawRequest(
        address vault,
        address account
    )
        external
        view
        returns (WithdrawRequest memory w, TokenizedWithdrawRequest memory s);

    /// @notice Returns the value of a withdraw request in terms of the asset
    /// @param vault the vault to get the withdraw request for
    /// @param account the account to get the withdraw request for
    /// @param asset the asset to get the value for
    /// @param shares the amount of shares to get the value for
    /// @return hasRequest whether the account has a withdraw request
    /// @return value the value of the withdraw request in terms of the asset
    function getWithdrawRequestValue(
        address vault,
        address account,
        address asset,
        uint256 shares
    )
        external
        view
        returns (bool hasRequest, uint256 value);

    /// @notice Returns the protocol reported exchange rate between the yield token
    /// and then withdraw token.
    /// @return exchangeRate the exchange rate of the yield token to the withdraw token
    function getExchangeRate() external view returns (uint256 exchangeRate);

    /// @notice Returns the known amount of withdraw tokens for a withdraw request
    /// @param requestId the request id of the withdraw request
    /// @return hasKnownAmount whether the amount is known
    /// @return amount the amount of withdraw tokens
    function getKnownWithdrawTokenAmount(uint256 requestId)
        external
        view
        returns (bool hasKnownAmount, uint256 amount);
}
"
    },
    "src/single-sided-lp/AbstractSingleSidedLP.sol": {
      "content": "// SPDX-License-Identifier: BUSL-1.1
pragma solidity >=0.8.29;

import { AbstractYieldStrategy } from "../AbstractYieldStrategy.sol";
import { DEFAULT_PRECISION, ADDRESS_REGISTRY, WETH } from "../utils/Constants.sol";
import { ERC20 } from "@openzeppelin/contracts/token/ERC20/ERC20.sol";
import { Trade, TradeType } from "../interfaces/ITradingModule.sol";
import { RewardManagerMixin } from "../rewards/RewardManagerMixin.sol";
import { IWithdrawRequestManager, WithdrawRequest } from "../interfaces/IWithdrawRequestManager.sol";
import { TokenUtils } from "../utils/TokenUtils.sol";
import { PoolShareTooHigh, AssetRemaining } from "../interfaces/Errors.sol";
import { ILPLib, TradeParams, DepositParams, RedeemParams, WithdrawParams } from "../interfaces/ISingleSidedLP.sol";

/**
 * @notice Base contract for the SingleSidedLP strategy. This strategy deposits into an LP
 * pool given a single borrowed currency. Allows for users to trade via external exchanges
 * during entry and exit, but the general expected behavior is single sided entries and
 * exits. Inheriting contracts will fill in the implementation details for integration with
 * the external DEX pool.
 */
abstract contract AbstractSingleSidedLP is RewardManagerMixin {
    using TokenUtils for ERC20;

    uint256 public immutable MAX_POOL_SHARE;
    address internal immutable LP_LIB;

    /**
     *
     * VIRTUAL FUNCTIONS                                                    *
     * These virtual functions are used to isolate implementation specific  *
     * behavior.                                                            *
     *
     */

    /// @notice Total number of tokens held by the LP token
    function NUM_TOKENS() internal view virtual returns (uint256);

    /// @notice Addresses of tokens held and decimal places of each token. ETH will always be
    /// recorded in this array as address(0)
    function TOKENS() public view virtual returns (ERC20[] memory);

    /// @notice Index of the TOKENS() array that refers to the primary borrowed currency by the
    /// leveraged vault. All valuations are done in terms of this currency.
    function PRIMARY_INDEX() public view virtual returns (uint256);

    /// @notice Returns the total supply of the pool token. Is a virtual function because
    /// ComposableStablePools use a "virtual supply" and a different method must be called
    /// to get the actual total supply.
    function _totalPoolSupply() internal view virtual returns (uint256);

    /// @dev Checks that the reentrancy context is valid
    function _checkReentrancyContext() internal virtual;

    /// @notice Called once during initialization to set the initial token approvals.
    function _initialApproveTokens() internal virtual {
        _delegateCall(LP_LIB, abi.encodeWithSelector(ILPLib.initialApproveTokens.selector));
    }

    /// @notice Implementation specific wrapper for joining a pool with the given amounts. Will also
    /// stake on the relevant booster protocol.
    function _joinPoolAndStake(uint256[] memory amounts, uint256 minPoolClaim) internal virtual {
        _delegateCall(LP_LIB, abi.encodeWithSelector(ILPLib.joinPoolAndStake.selector, amounts, minPoolClaim));
    }

    /// @notice Implementation specific wrapper for unstaking from the booster protocol and withdrawing
    /// funds from the LP pool
    function _unstakeAndExitPool(
        uint256 poolClaim,
        uint256[] memory minAmounts,
        bool isSingleSided
    )
        internal
        virtual
        returns (uint256[] memory exitBalances, ERC20[] memory tokens)
    {
        bytes memory result = _delegateCall(
            LP_LIB, abi.encodeWithSelector(ILPLib.unstakeAndExitPool.selector, poolClaim, minAmounts, isSingleSided)
        );
        (exitBalances, tokens) = abi.decode(result, (uint256[], ERC20[]));
    }

    /**
     *
     * CLASS FUNCTIONS                                                      *
     * Below are class functions that represent the base implementation     *
     * of the Single Sided LP strategy.                                     *
     *
     */
    constructor(
        uint256 _maxPoolShare,
        address _asset,
        address _yieldToken,
        uint256 _feeRate,
        address _rewardManager,
        uint8 _yieldTokenDecimals
    )
        RewardManagerMixin(_asset, _yieldToken, _feeRate, _rewardManager, _yieldTokenDecimals)
    {
        MAX_POOL_SHARE = _maxPoolShare;
    }

    /// @notice Validates that all withdraw request managers are either all address(0) or all non-zero
    /// @dev this must be called in the child contract constructor since the tokens are not yet
    /// initialized in this constructor.
    function _validateWithdrawRequestManagers() internal view {
        ERC20[] memory tokens = TOKENS();
        bool hasNonZeroManager = false;
        bool hasZeroManager = false;

        for (uint256 i; i < tokens.length; i++) {
            address token = address(tokens[i]);
            // If the token is address(0), then we use WETH as the withdraw request token
            if (token == address(0)) token = address(WETH);
            address withdrawRequestManager = address(ADDRESS_REGISTRY.getWithdrawRequestManager(address(token)));

            if (withdrawRequestManager == address(0)) {
                hasZeroManager = true;
            } else {
                hasNonZeroManager = true;
            }

            // If we have both zero and non-zero managers, revert
            if (hasZeroManager && hasNonZeroManager) {
                revert("Inconsistent withdraw request managers: must be all zero or all non-zero");
            }
        }
    }

    function _initialize(bytes calldata data) internal override {
        super._initialize(data);
        _initialApproveTokens();
    }

    function _mintYieldTokens(uint256 assets, address, /* receiver */ bytes memory depositData) internal override {
        DepositParams memory params = abi.decode(depositData, (DepositParams));
        uint256[] memory amounts = new uint256[](NUM_TOKENS());

        // If depositTrades are specified, then parts of the initial deposit are traded
        // for corresponding amounts of the other pool tokens via external exchanges. If
        // these amounts are not specified then the pool will just be joined single sided.
        // Deposit trades are not automatically enabled on vaults since the trading module
        // requires explicit permission for every token that can be sold by an address.
        if (params.depositTrades.length > 0) {
            // NOTE: amounts is modified in place
            _executeDepositTrades(assets, amounts, params.depositTrades);
        } else {
            // This is a single sided entry, will revert if index is out of bounds
            amounts[PRIMARY_INDEX()] = assets;
        }

        _joinPoolAndStake(amounts, params.minPoolClaim);

        _checkPoolShare();
    }

    function _checkPoolShare() internal view virtual {
        // Checks that the vault does not own too large of a portion of the pool. If this is the case,
        // single sided exits may have a detrimental effect on the liquidity.
        uint256 maxSupplyThreshold = (_totalPoolSupply() * MAX_POOL_SHARE) / DEFAULT_PRECISION;
        // This is incumbent on a 1-1 ratio between the lpToken and the yieldToken, if that is not the
        // case then this function must be overridden.
        uint256 poolClaim = ERC20(yieldToken).balanceOf(address(this));
        if (maxSupplyThreshold < poolClaim) revert PoolShareTooHigh(poolClaim, maxSupplyThreshold);
    }

    function _redeemShares(
        uint256 sharesToRedeem,
        address sharesOwner,
        bool isEscrowed,
        bytes memory redeemData
    )
        internal
        override
    {
        RedeemParams memory params = abi.decode(redeemData, (RedeemParams));

        // Stores the amount of each token that has been withdrawn from the pool.
        uint256[] memory exitBalances;
        bool isSingleSided;
        ERC20[] memory tokens;
        if (isEscrowed) {
            // Attempt to withdraw all pending requests, tokens may be different if there
            // is a withdraw request.
            (exitBalances, tokens) = _withdrawPendingRequests(sharesOwner, sharesToRedeem);
            // If there are pending requests, then we are not single sided by definition
            isSingleSided = false;
        } else {
            isSingleSided = params.redemptionTrades.length == 0;
            uint256 yieldTokensBurned = convertSharesToYieldToken(sharesToRedeem);
            (exitBalances, tokens) = _unstakeAndExitPool(yieldTokensBurned, params.minAmounts, isSingleSided);
        }

        if (!isSingleSided) {
            // If not a single sided trade, will execute trades back to the primary token on
            // external exchanges. This method will execute EXACT_IN trades to ensure that
            // all of the balance in the other tokens is sold for primary.
            // Redemption trades are not automatically enabled on vaults since the trading module
            // requires explicit permission for every token that can be sold by an address.
            _executeRedemptionTrades(tokens, exitBalances, params.redemptionTrades);
        }
    }

    /// @dev Trades the amount of primary token into other secondary tokens prior to entering a pool.
    function _executeDepositTrades(
        uint256 assets,
        uint256[] memory amounts,
        TradeParams[] memory depositTrades
    )
        internal
    {
        ERC20[] memory tokens = TOKENS();
        Trade memory trade;
        uint256 assetRemaining = assets;

        for (uint256 i; i < amounts.length; i++) {
            if (i == PRIMARY_INDEX()) continue;
            TradeParams memory t = depositTrades[i];

            if (t.tradeAmount > 0) {
                if (t.tradeType != TradeType.EXACT_IN_SINGLE && t.tradeType != TradeType.STAKE_TOKEN) revert();

                trade = Trade({
                    tradeType: t.tradeType,
                    sellToken: address(asset),
                    buyToken: address(tokens[i]),
                    amount: t.tradeAmount,
                    limit: t.minPurchaseAmount,
                    deadline: block.timestamp,
                    exchangeData: t.exchangeData
                });
                // Always selling the primaryToken and buying the secondary token.
                (uint256 amountSold, uint256 amountBought) = _executeTrade(trade, t.dexId);

                amounts[i] = amountBought;
                // Will revert on underflow if over-selling the primary borrowed
                assetRemaining -= amountSold;
            }
        }

        if (PRIMARY_INDEX() < amounts.length) {
            amounts[PRIMARY_INDEX()] = assetRemaining;
        } else if (0 < assetRemaining) {
            // This can happen if the asset is not in the pool and we need to trade all
            // of the remaining asset for tokens in the pool.
            revert AssetRemaining(assetRemaining);
        }
    }

    /// @dev Trades the amount of secondary tokens into the primary token after exiting a pool.
    function _executeRedemptionTrades(
        ERC20[] memory tokens,
        uint256[] memory exitBalances,
        TradeParams[] memory redemptionTrades
    )
        internal
        returns (uint256 finalPrimaryBalance)
    {
        for (uint256 i; i < exitBalances.length; i++) {
            if (address(tokens[i]) == address(asset)) {
                finalPrimaryBalance += exitBalances[i];
                continue;
            }

            TradeParams memory t = redemptionTrades[i];
            // Always sell the entire exit balance to the primary token
            if (exitBalances[i] > 0) {
                Trade memory trade = Trade({
                    tradeType: TradeType.EXACT_IN_SINGLE,
                    sellToken: address(tokens[i]),
                    buyToken: address(asset),
                    amount: exitBalances[i],
                    limit: t.minPurchaseAmount,
                    deadline: block.timestamp,
                    exchangeData: t.exchangeData
                });
                ( /* */ , uint256 amountBought) = _executeTrade(trade, t.dexId);

                finalPrimaryBalance += amountBought;
            }
        }
    }

    function _preLiquidation(
        address liquidateAccount,
        address liquidator,
        uint256 sharesToLiquidate,
        uint256 accountSharesHeld
    )
        internal
        override
    {
        _checkReentrancyContext();
        return super._preLiquidation(liquidateAccount, liquidator, sharesToLiquidate, accountSharesHeld);
    }

    function __postLiquidation(
        address liquidator,
        address liquidateAccount,
        uint256 sharesToLiquidator
    )
        internal
        override
        returns (bool didTokenize)
    {
        bytes memory result = _delegateCall(
            LP_LIB,
            abi.encodeWithSelector(
                ILPLib.tokenizeWithdrawRequest.selector, liquidateAccount, liquidator, sharesToLiquidator
            )
        );
        didTokenize = abi.decode(result, (bool));
    }

    function __initiateWithdraw(
        address account,
        uint256 yieldTokenAmount,
        uint256 sharesHeld,
        bytes memory data,
        address forceWithdrawFrom
    )
        internal
        override
        returns (uint256 requestId)
    {
        WithdrawParams memory params = abi.decode(data, (WithdrawParams));

        (uint256[] memory exitBalances, /* */ ) = _unstakeAndExitPool({
            poolClaim: yieldTokenAmount,
            minAmounts: params.minAmounts,
            // When initiating a withdraw, we always exit proportionally
            isSingleSided: false
        });

        bytes memory result = _delegateCall(
            LP_LIB,
            abi.encodeWithSelector(
                ILPLib.initiateWithdraw.selector,
                account,
                sharesHeld,
                exitBalances,
                params.withdrawData,
                forceWithdrawFrom
            )
        );
        uint256[] memory requestIds = abi.decode(result, (uint256[]));
        for (uint256 i; i < requestIds.length; i++) {
            // Return the first non-zero request id since the base function requires it.
            if (requestIds[i] > 0) return requestIds[i];
        }
        // Revert if there are no non-zero request ids.
        revert();
    }

    function _withdrawPendingRequests(
        address sharesOwner,
        uint256 sharesToRedeem
    )
        internal
        returns (uint256[] memory exitBalances, ERC20[] memory tokens)
    {
        bytes memory result = _delegateCall(
            LP_LIB,
            abi.encodeWithSelector(ILPLib.finalizeAndRedeemWithdrawRequest.selector, sharesOwner, sharesToRedeem)
        );
        (exitBalances, tokens) = abi.decode(result, (uint256[], ERC20[]));
    }

    /// @notice Returns the total value in terms of the borrowed token of the account's position
    function convertToAssets(uint256 shares) public view override returns (uint256) {
        if (t_CurrentAccount != address(0) && _isWithdrawRequestPending(t_CurrentAccount)) {
            return ILPLib(LP_LIB).getWithdrawRequestValue(t_CurrentAccount, asset, shares);
        }

        return super.convertToAssets(shares);
    }

    function _isWithdrawRequestPending(address account) internal view override returns (bool) {
        return ILPLib(LP_LIB).hasPendingWithdrawals(account);
    }
}

abstract contract BaseLPLib is ILPLib {
    using TokenUtils for ERC20;

    function TOKENS() internal view virtual returns (ERC20[] memory);

    function _tokensForWithdrawRequest() internal view returns (ERC20[] memory) {
        ERC20[] memory tokens = TOKENS();
        for (uint256 i; i < tokens.length; i++) {
            // In withdraw requests, ETH is always wrapped to WETH.
            if (address(tokens[i]) == address(0)) tokens[i] = ERC20(address(WETH));
        }
        return tokens;
    }

    /// @inheritdoc ILPLib
    function getWithdrawRequestValue(
        address account,
        address asset,
        uint256 shares
    )
        external
        view
        returns (uint256 totalValue)
    {
        ERC20[] memory tokens = _tokensForWithdrawRequest();

        for (uint256 i; i < tokens.length; i++) {
            IWithdrawRequestManager manager = ADDRESS_REGISTRY.getWithdrawRequestManager(address(tokens[i]));
            // This is called as a view function, not a delegate call so use the msg.sender to get
            // the correct vault address
            (bool hasRequest, uint256 value) = manager.getWithdrawRequestValue(msg.sender, account, asset, shares);
            // Ensure that this is true so that we do not lose any value.
            require(hasRequest);
            totalValue += value;
        }
    }

    /// @inheritdoc ILPLib
    function hasPendingWithdrawals(address account) external view override returns (bool) {
        ERC20[] memory tokens = _tokensForWithdrawRequest();
        for (uint256 i; i < tokens.length; i++) {
            IWithdrawRequestManager manager = ADDRESS_REGISTRY.getWithdrawRequestManager(address(tokens[i]));
            // If there is no withdraw request manager for the first token then there are no withdraw
            // requests to check for. There must be a withdraw request manager for each token.
            if (address(manager) == address(0)) return false;
            // This is called as a view function, not a delegate call so use the msg.sender to get
            // the correct vault address
            (WithdrawRequest memory w, /* */ ) = manager.getWithdrawRequest(msg.sender, account);
            if (w.requestId != 0) return true;
        }

        return false;
    }

    /// @inheritdoc ILPLib
    function initiateWithdraw(
        address account,
        uint256 sharesHeld,
        uint256[] calldata exitBalances,
        bytes[] calldata withdrawData,
        address forceWithdrawFrom
    )
        external
        override
        returns (uint256[] memory requestIds)
    {
        ERC20[] memory tokens = _tokensForWithdrawRequest();
        requestIds = new uint256[](exitBalances.length);
        for (uint256 i; i < exitBalances.length; i++) {
            // For liquidity curve based pools (non-UniswapV3 tick based vaults), it is exceedingly
            // difficult to push one of the tokens to a very low balance such that the exitBalance
            // actually returns zero. Having a zero balance withdraw request will cause a lot of issues
            // so instead we revert here.
            if (exitBalances[i] == 0) revert();
            IWithdrawRequestManager manager = ADDRESS_REGISTRY.getWithdrawRequestManager(address(tokens[i]));

            tokens[i].checkApprove(address(manager), exitBalances[i]);
            // Will revert if there is already a pending withdraw
            requestIds[i] = manager.initiateWithdraw({
                account: account,
                yieldTokenAmount: exitBalances[i],
                sharesAmount: sharesHeld,
                data: withdrawData[i],
                forceWithdrawFrom: forceWithdrawFrom
            });
        }
    }

    /// @inheritdoc ILPLib
    function finalizeAndRedeemWithdrawRequest(
        address sharesOwner,
        uint256 sharesToRedeem
    )
        external
        override
        returns (uint256[] memory exitBalances, ERC20[] memory withdrawTokens)
    {
        ERC20[] memory tokens = _tokensForWithdrawRequest();

        exitBalances = new uint256[](tokens.length);
        withdrawTokens = new ERC20[](tokens.length);

        WithdrawRequest memory w;
        for (uint256 i; i < tokens.length; i++) {
            IWithdrawRequestManager manager = ADDRESS_REGISTRY.getWithdrawRequestManager(address(tokens[i]));
            (w, /* */ ) = manager.getWithdrawRequest(address(this), sharesOwner);
            withdrawTokens[i] = ERC20(manager.WITHDRAW_TOKEN());

            // There must be a corresponding withdraw request for each token.
            if (w.sharesAmount == 0 || w.requestId == 0) revert();
            uint256 yieldTokensBurned = uint256(w.yieldTokenAmount) * sharesToRedeem / w.sharesAmount;
            exitBalances[i] = manager.finalizeAndRedeemWithdrawRequest({
                account: sharesOwner,
                withdrawYieldTokenAmount: yieldTokensBurned,
                sharesToBurn: sharesToRedeem
            });
        }
    }

    /// @inheritdoc ILPLib
    function tokenizeWithdrawRequest(
        address liquidateAccount,
        address liquidator,
        uint256 sharesToLiquidator
    )
        external
        override
        returns (bool didTokenize)
    {
        ERC20[] memory tokens = _tokensForWithdrawRequest();
        for (uint256 i; i < tokens.length; i++) {
            IWithdrawRequestManager manager = ADDRESS_REGISTRY.getWithdrawRequestManager(address(tokens[i]));
            // If there is no withdraw request manager for the first token then there are no
            // withdraw requests to tokenize. There must be a withdraw request manager for each token.
            if (address(manager) == address(0)) return false;
            // If there is no withdraw request then this will be a noop, make sure to OR with the previous result
            // to ensure that the result is always set but it is done after so the tokenizeWithdrawRequest call
            // is not short circuited.
            didTokenize =
                manager.tokenizeWithdrawRequest(liquidateAccount, liquidator, sharesToLiquidator) || didTokenize;
        }
    }
}
"
    },
    "node_modules/@openzeppelin/contracts/token/ERC20/ERC20.sol": {
      "content": "// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.2.0) (token/ERC20/ERC20.sol)

pragma solidity ^0.8.20;

import {IERC20} from "./IERC20.sol";
import {IERC20Metadata} from "./extensions/IERC20Metadata.sol";
import {Context} from "../../utils/Context.sol";
import {IERC20Errors} from "../../interfaces/draft-IERC6093.sol";

/**
 * @dev Implementation of the {IERC20} interface.
 *
 * This implementation is agnostic to the way tokens are created. This means
 * that a supply mechanism has to be added in a derived contract using {_mint}.
 *
 * TIP: For a detailed writeup see our guide
 * https://forum.openzeppelin.com/t/how-to-implement-erc20-supply-mechanisms/226[How
 * to implement supply mechanisms].
 *
 * The default value of {decimals} is 18. To change this, you should override
 * this function so it returns a different value.
 *
 * We have followed general OpenZeppelin Contracts guidelines: functions revert
 * instead returning `false` on failure. This behavior is nonetheless
 * conventional and does not conflict with the expectations of ERC-20
 * applications.
 */
abstract contract ERC20 is Context, IERC20, IERC20Metadata, IERC20Errors {
    mapping(address account => uint256) private _balances;

    mapping(address account => mapping(address spender => uint256)) private _allowances;

    uint256 private _totalSupply;

    string private _name;
    string private _symbol;

    /**
     * @dev Sets the values for {name} and {symbol}.
     *
     * All two of these values are immutable: they can only be set once during
     * construction.
     */
    constructor(string memory name_, string memory symbol_) {
        _name = name_;
        _symbol = symbol_;
    }

    /**
     * @dev Returns the name of the token.
     */
    function name() public view virtual returns (string memory) {
        return _name;
    }

    /**
     * @dev Returns the symbol of the token, usually a shorter version of the
     * name.
     */
    function symbol() public view virtual returns (string memory) {
        return _symbol;
    }

    /**
     * @dev Returns the number of decimals used to get its user representation.
     * For example, if `decimals` equals `2`, a balance of `505` tokens should
     * be displayed to a user as `5.05` (`505 / 10 ** 2`).
     *
     * Tokens usually opt for a value of 18, imitating the relationship between
     * Ether and Wei. This is the default value returned by this function, unless
     * it's overridden.
     *
     * NOTE: This information is only used for _display_ purposes: it in
     * no way affects any of the arithmetic of the contract, including
     * {IERC20-balanceOf} and {IERC20-transfer}.
     */
    function decimals() public view virtual returns (uint8) {
        return 18;
    }

    /**
     * @dev See {IERC20-totalSupply}.
     */
    function totalSupply() public view virtual returns (uint256) {
        return _totalSupply;
    }

    /**
     * @dev See {IERC20-balanceOf}.
     */
    function balanceOf(address account) public view virtual returns (uint256) {
        return _balances[account];
    }

    /**
     * @dev See {IERC20-transfer}.
     *
     * Requirements:
     *
     * - `to` cannot be the zero address.
     * - the caller must have a balance of at least `value`.
     */
    function transfer(address to, uint256 value) public virtual returns (bool) {
        address owner = _msgSender();
        _transfer(owner, to, value);
        return true;
    }

    /**
     * @dev See {IERC20-allowance}.
     */
    function allowance(address owner, address spender) public view virtual returns (uint256) {
        return _allowances[owner][spender];
    }

    /**
     * @dev See {IERC20-approve}.
     *
     * NOTE: If `value` is the maximum `uint256`, the allowance is not updated on
     * `transferFrom`. This is semantically equivalent to an infinite approval.
     *
     * Requirements:
     *
     * - `spender` cannot be the zero address.
     */
    function approve(address spender, uint256 value) public virtual returns (bool) {
        address owner = _msgSender();
        _approve(owner, spender, value);
        return true;
    }

    /**
     * @dev See {IERC20-transferFrom}.
     *
     * Skips emitting an {Approval} event indicating an allowance update. This is not
     * required by the ERC. See {xref-ERC20-_approve-address-address-uint256-bool-}[_approve].
     *
     * NOTE: Does not update the allowance if the current allowance
     * is the maximum `uint256`.
     *
     * Requirements:
     *
     * - `from` and `to` cannot be the zero address.
     * - `from` must have a balance of at least `value`.
     * - the caller must have allowance for ``from``'s tokens of at least
     * `value`.
     */
    function transferFrom(address from, address to, uint256 value) public virtual returns (bool) {
        address spender = _msgSender();
        _spendAllowance(from, spender, value);
        _transfer(from, to, value);
        return true;
    }

    /**
     * @dev Moves a `value` amount of tokens from `from` to `to`.
     *
     * This internal function is equivalent to {transfer}, and can be used to
     * e.g. implement automatic token fees, slashing mechanisms, etc.
     *
     * Emits a {Transfer} event.
     *
     * NOTE: This function is not virtual, {_update} should be overridden instead.
     */
    function _transfer(address from, address to, uint256 value) internal {
        if (from == address(0)) {
            revert ERC20InvalidSender(address(0));
        }
        if (to == address(0)) {
            revert ERC20InvalidReceiver(address(0));
        }
        _update(from, to, value);
    }

    /**
     * @dev Transfers a `value` amount of tokens from `from` to `to`, or alternatively mints (or burns) if `from`
     * (or `to`) is the zero address. All customizations to transfers, mints, and burns should be done by overriding
     * this function.
     *
     * Emits a {Transfer} event.
     */
    function _update(address from, address to, uint256 value) internal virtual {
        if (from == address(0)) {
            // Overflow check required: The rest of the code assumes that totalSupply never overflows
            _totalSupply += value;
        } else {
            uint256 fromBalance = _balances[from];
            if (fromBalance < value) {
                revert ERC20InsufficientBalance(from, fromBalance, value);
            }
            unchecked {
                // Overflow not possible: value <= fromBalance <= totalSupply.
                _balances[from] = fromBalance - value;
            }
        }

        if (to == address(0)) {
            unchecked {
                // Overflow not possible: value <= totalSupply or value <= fromBalance <= totalSupply.
                _totalSupply -= value;
            }
        } else {
            unchecked {
                // Overflow not possible: balance + value is at most totalSupply, which we know fits into a uint256.
                _balances[to] += value;
            }
        }

        emit Transfer(from, to, value);
    }

    /**
     * @dev Creates a `value` amount of tokens and assigns them to `account`, by transferring it from address(0).
     * Relies on the `_update` mechanism
     *
     * Emits a {Transfer} event with `from` set to the zero address.
     *
     * NOTE: This function is not virtual, {_update} should be overridden instead.
     */
    function _mint(address account, uint256 value) internal {
        if (account == address(0)) {
            revert ERC20InvalidReceiver(address(0));
        }
        _update(address(0), account, value);
    }

    /**
     * @dev Destroys a `value` amount of tokens from `account`, lowering the total supply.
     * Relies on the `_update` mechanism.
     *
     * Emits a {Transfer} event with `to` set to the zero address.
     *
     * NOTE: This function is not virtual, {_update} should be overridden instead
     */
    function _burn(address account, uint256 value) internal {
        if (account == address(0)) {
            revert ERC20InvalidSender(address(0));
        }
        _update(account, address(0), value);
    }

    /**
     * @dev Sets `value` as the allowance of `spender` over the `owner` s tokens.
     *
     * This internal function is equivalent to `approve`, and can be used to
     * e.g. set automatic allowances for certain subsystems, etc.
     *
     * Emits an {Approval} event.
     *
     * Requirements:
     *
     * - `owner` cannot be the zero address.
     * - `spender` cannot be the zero address.
     *
     * Overrides to this logic should be done to the variant with an additional `bool emitEvent` argument.
     */
    function _approve(address owner, address spender, uint256 value) internal {
        _approve(owner, spender, value, true);
    }

    /**
     * @dev Variant of {_approve} with an optional flag to enable or disable the {Approval} event.
     *
     * By default (when calling {_approve}) the flag is set to true. On the other hand, approval changes made by
     * `_spendAllowance` during the `transferFrom` operation set the flag to false. This saves gas by not emitting any
     * `Approval` event during `transferFrom` operations.
     *
     * Anyone who wishes to continue emitting `Approval` events on the`transferFrom` operation can force the flag to
     * true using the following override:
     *
     * ```solidity
     * function _approve(address owner, address spender, uint256 value, bool) internal virtual override {
     *     super._approve(owner, spender, value, true);
     * }
     * ```
     *
     * Requirements are the same as {_approve}.
     */
    function _approve(address owner, address spender, uint256 value, bool emitEvent) internal virtual {
        if (owner == address(0)) {
            revert ERC20InvalidApprover(address(0));
        }
        if (spender == address(0)) {
            revert ERC20InvalidSpender(address(0));
        }
        _allowances[owner][spender] = value;
        if (emitEvent) {
            emit Approval(owner, spender, value);
        }
    }

    /**
     * @dev Updates `owner` s allowance for `spender` based on spent `value`.
     *
     * Does not update the allowance value in case of infinite allowance.
     * Revert if not enough allowance is available.
     *
     * Does not emit an {Approval} event.
     */
    function _spendAllowance(address owner, address spender, uint256 value) internal virtual {
        uint256 currentAllowance = allowance(owner, spender);
        if (currentAllowance < type(uint256).max) {
            if (currentAllowance < value) {
                revert ERC20InsufficientAllowance(spender, currentAllowance, value);
            }
            unchecked {
                _approve(owner, spender, currentAllowance - value, false);
            }
        }
    }
}
"
    },
    "src/utils/TokenUtils.sol": {
      "content": "// SPDX-License-Identifier: BUSL-1.1
pragma solidity >=0.8.29;

import { ERC20 } from "@openzeppelin/contracts/token/ERC20/ERC20.sol";
import { ETH_ADDRESS, ALT_ETH_ADDRESS } from "./Constants.sol";
import { SafeERC20 } from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";

library TokenUtils {
    using SafeERC20 for ERC20;

    function getDecimals(address token) internal view returns (uint8 decimals) {
        decimals = (token == ETH_ADDRESS || token == ALT_ETH_ADDRESS) ? 18 : ERC20(token).decimals();
        require(decimals <= 18);
    }

    function tokenBalance(address token) internal view returns (uint256) {
        return token == ETH_ADDRESS ? address(this).balance : ERC20(token).balanceOf(address(this));
    }

    function checkApprove(ERC20 token, address spender, uint256 amount) internal {
        if (address(token) == address(0)) return;

        token.forceApprove(spender, amount);
    }

    function checkRevoke(ERC20 token, address spender) internal {
        if (address(token) == address(0)) return;
        token.forceApprove(spender, 0);
    }

    function checkReturnCode() internal pure returns (bool success) {
        uint256[1] memory result;
        assembly {
            switch returndatasize()
            case 0 {
                // This is a non-standard ERC-20
                success := 1 // set success to true
            }
            case 32 {
                // This is a compliant ERC-20
                returndatacopy(result, 0, 32)
                success := mload(result) // Set `success = returndata` of external call
            }
            default {
                // This is an excessively non-compliant ERC-20, revert.
                revert(0, 0)
            }
        }
    }
}
"
    },
    "src/utils/Constants.sol": {
      "content": "// SPDX-License-Identifier: BUSL-1.1
pragma solidity >=0.8.29;

import { WETH9 } from "../interfaces/IWETH.sol";
import { AddressRegistry } from "../proxy/AddressRegistry.sol";

address constant ETH_ADDRESS = address(0);
address constant ALT_ETH_ADDRESS = 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE;
uint256 constant DEFAULT_PRECISION = 1e18;
uint256 constant DEFAULT_DECIMALS = 18;
uint256 constant SHARE_PRECISION = 1e24;
uint256 constant VIRTUAL_SHARES = 1e6;

uint256 constant COOLDOWN_PERIOD = 5 minutes;
uint256 constant YEAR = 365 days;

// Will move these to a deployment file when we go to multiple chains
uint256 constant CHAIN_ID_MAINNET = 1;
WETH9 constant WETH = WETH9(0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2);
AddressRegistry constant ADDRESS_REGISTRY = AddressRegistry(0xe335d314BD4eF7DD44F103dC124FEFb7Ce63eC95);
"
    },
    "node_modules/@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol": {
      "content": "// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.2.0) (token/ERC20/utils/SafeERC20.sol)

pragma solidity ^0.8.20;

import {IERC20} from "../IERC20.sol";
import {IERC1363} from "../../../interfaces/IERC1363.sol";

/**
 * @title SafeERC20
 * @dev Wrappers around ERC-20 operations that throw on failure (when the token
 * contract returns false). Tokens that return no value (and instead revert or
 * throw on failure) are also supported, non-reverting calls are assumed to be
 * successful.
 * To use this library you can add a `using SafeERC20 for IERC20;` statement to your contract,
 * which allows you to call the safe operations as `token.safeTransfer(...)`, etc.
 */
library SafeERC20 {
    /**
     * @dev An operation with an ERC-20 token failed.
     */
    error SafeERC20FailedOperation(address token);

    /**
     * @dev Indicates a failed `decreaseAllowance` request.
     */
    error SafeERC20FailedDecreaseAllowance(address spender, uint256 currentAllowance, uint256 requestedDecrease);

    /**
     * @dev Transfer `value` amount of `token` from the calling contract to `to`. If `token` returns no value,
     * non-reverting calls are assumed to be successful.
     */
    function safeTransfer(IERC20 token, address to, uint256 value) internal {
        _callOptionalReturn(token, abi.encodeCall(token.transfer, (to, value)));
    }

    /**
     * @dev Transfer `value` amount of `token` from `from` to `to`, spending the approval given by `from` to the
     * calling contract. If `token` returns no value, non-reverting calls are assumed to be successful.
     */
    function safeTransferFrom(IERC20 token, address from, address to, uint256 value) internal {
        _callOptionalReturn(token, abi.encodeCall(token.transferFrom, (from, to, value)));
    }

    /**
     * @dev Increase the calling contract's allowance toward `spender` by `value`. If `token` returns no value,
     * non-reverting calls are assumed to be successful.
     *
     * IMPORTANT: If the token implements ERC-7674 (ERC-20 with temporary allowance), and if the "client"
     * smart contract uses ERC-7674 to set temporary allowances, then the "client" smart contract should avoid using
     * this function. Performing a {safeIncreaseAllowance} or {safeDecreaseAllowance} operation on a token contract
     * that has a non-zero temporary allowance (for that particular owner-spender) will result in unexpected behavior.
     */
    function safeIncreaseAllowance(IERC20 token, address spender, uint256 value) internal {
        uint256 oldAllowance = token.allowance(address(this), spender);
        forceApprove(token, spender, oldAllowance + value);
    }

    /**
     * @dev Decrease the calling contract's allowance toward `spender` by `requestedDecrease`. If `token` returns no
     * value, non-reverting calls are assumed to be successful.
     *
     * IMPORTANT: If the token implements ERC-7674 (ERC-20 with temporary allowance), and if the "client"
     * smart contract uses ERC-7674 to set temporary allowances, then the "client" smart contract should avoid using
     * this function. Performing a {safeIncreaseAllowance} or {safeDecreaseAllowance} operation on a token contract
     * that has a non-zero temporary allowance (for that particular owner-spender) will result in unexpected behavior.
     */
    function safeDecreaseAllowance(IERC20 token, address spender, uint256 requestedDecrease) internal {
        unchecked {
            uint256 currentAllowance = token.allowance(address(this), spender);
            if (currentAllowance < requestedDecrease) {
                revert SafeERC20FailedDecreaseAllowance(spender, currentAllowance, requestedDecrease);
            }
            forceApprove(token, spender, currentAllowance - requestedDecrease);
        }
    }

    /**
     * @dev Set the calling contract's allowance toward `spender` to `value`. If `token` returns no value,
     * non-reverting calls are assumed to be successful. Meant to be used with tokens that require the approval
     * to be set to zero before setting it to a non-zero value, such as USDT.
     *
     * NOTE: If the token implements ERC-7674, this function will not modify any temporary allowance. This function
     * only sets the "standard" allowance. Any temporary allowance will remain active, in addition to the value being
     * set here.
     */
    function forceApprove(IERC20 token, address spender, uint256 value) internal {
        bytes memory approvalCall = abi.encodeCall(token.approve, (spender, value));

        if (!_callOptionalReturnBool(token, approvalCall)) {
            _callOptionalReturn(token, abi.encodeCall(token.approve, (spender, 0)));
            _callOptionalReturn(token, approvalCall);
        }
    }

    /**
     * @dev Performs an {ERC1363} transferAndCall, with a fallback to the simple {ERC20} transfer if the target has no
     * code. This can be used to implement an {ERC721}-like safe transfer that rely on {ERC1363} checks when
     * targeting contracts.
     *
     * Reverts if the returned value is other than `true`.
     */
    function transferAndCallRelaxed(IERC1363 token, address to, uint256 value, bytes memory data) internal {
        if (to.code.length == 0) {
            

Tags:
ERC20, ERC165, Multisig, Mintable, Swap, Liquidity, Staking, Yield, Upgradeable, Multi-Signature, Factory, Oracle|addr:0x8ab1aac565883ba1e66ebb46dc0eae88fe6d446b|verified:true|block:23471128|tx:0x4ed0e50181266e9f29f4359cdce094f8ee4b176269058e1933d2184a85d0d89d|first_check:1759219718

Submitted on: 2025-09-30 10:08:39

Comments

Log in to comment.

No comments yet.