PendlePT_sUSDe

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/staking/PendlePT_sUSDe.sol": {
      "content": "// SPDX-License-Identifier: BUSL-1.1
pragma solidity >=0.8.29;

import { PendlePT, PendleRedeemParams } from "./PendlePT.sol";
import { sUSDe, sDAI, DAI } from "../interfaces/IEthena.sol";
import { IWithdrawRequestManager } from "../interfaces/IWithdrawRequestManager.sol";
import { Trade, TradeType, DexId, CurveV2SingleData } from "../interfaces/ITradingModule.sol";
import { SlippageTooHigh } from "../interfaces/Errors.sol";
import { SafeERC20 } from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
import { ERC20 } from "@openzeppelin/contracts/token/ERC20/ERC20.sol";

contract PendlePT_sUSDe is PendlePT {
    using SafeERC20 for ERC20;

    constructor(
        address market,
        address tokenInSY,
        address tokenOutSY,
        address asset,
        address yieldToken,
        uint256 feeRate,
        IWithdrawRequestManager withdrawRequestManager
    )
        PendlePT(market, tokenInSY, tokenOutSY, asset, yieldToken, feeRate, withdrawRequestManager)
    {
        require(tokenOutSY == address(sUSDe));
    }

    /// @notice The vast majority of the sUSDe liquidity is in an sDAI/sUSDe curve pool.
    /// sDAI has much greater liquidity once it is unwrapped as DAI so that is done manually
    /// in this method.
    function _executeInstantRedemption(
        uint256 yieldTokensToRedeem,
        bytes memory redeemData
    )
        internal
        virtual
        override
        returns (uint256 assetsPurchased)
    {
        PendleRedeemParams memory params = abi.decode(redeemData, (PendleRedeemParams));
        uint256 netTokenOut = _redeemPT(yieldTokensToRedeem, params.limitOrderData);

        Trade memory sDAITrade = Trade({
            tradeType: TradeType.EXACT_IN_SINGLE,
            sellToken: address(sUSDe),
            buyToken: address(sDAI),
            amount: netTokenOut,
            limit: 0, // NOTE: no slippage guard is set here, it is enforced in the second leg
                // of the trade.
            deadline: block.timestamp,
            exchangeData: abi.encode(
                CurveV2SingleData({
                    pool: 0x167478921b907422F8E88B43C4Af2B8BEa278d3A,
                    fromIndex: 1, // sUSDe
                    toIndex: 0 // sDAI
                 })
            )
        });

        ( /* */ , uint256 sDAIAmount) = _executeTrade(sDAITrade, uint16(DexId.CURVE_V2));

        // Unwraps the sDAI to DAI
        uint256 daiAmount = sDAI.redeem(sDAIAmount, address(this), address(this));
        emit TradeExecuted(address(sDAI), address(DAI), sDAIAmount, daiAmount);

        if (asset != address(DAI)) {
            Trade memory trade = Trade({
                tradeType: TradeType.EXACT_IN_SINGLE,
                sellToken: address(DAI),
                buyToken: asset,
                amount: daiAmount,
                limit: params.minPurchaseAmount,
                deadline: block.timestamp,
                exchangeData: params.exchangeData
            });

            // Trades the unwrapped DAI back to the given token.
            ( /* */ , assetsPurchased) = _executeTrade(trade, params.dexId);
        } else {
            if (params.minPurchaseAmount > daiAmount) revert SlippageTooHigh(daiAmount, params.minPurchaseAmount);
            assetsPurchased = daiAmount;
        }
    }
}
"
    },
    "src/staking/PendlePT.sol": {
      "content": "// SPDX-License-Identifier: BUSL-1.1
pragma solidity >=0.8.29;

import { AbstractStakingStrategy } from "./AbstractStakingStrategy.sol";
import { IPMarket, IStandardizedYield, IPPrincipalToken, IPYieldToken } from "../interfaces/IPendle.sol";
import { IWithdrawRequestManager } from "../interfaces/IWithdrawRequestManager.sol";
import { Trade, TradeType } from "../interfaces/ITradingModule.sol";
import { SlippageTooHigh } from "../interfaces/Errors.sol";
import { ERC20 } from "@openzeppelin/contracts/token/ERC20/ERC20.sol";
import { PendlePTLib } from "./PendlePTLib.sol";
import { TokenUtils } from "../utils/TokenUtils.sol";

struct PendleDepositParams {
    uint16 dexId;
    uint256 minPurchaseAmount;
    bytes exchangeData;
    bytes pendleData;
}

struct PendleRedeemParams {
    uint8 dexId;
    uint256 minPurchaseAmount;
    bytes exchangeData;
    bytes limitOrderData;
}

/**
 * Base implementation for Pendle PT vaults
 */
contract PendlePT is AbstractStakingStrategy {
    using TokenUtils for ERC20;

    IPMarket public immutable MARKET;
    address public immutable TOKEN_OUT_SY;

    address public immutable TOKEN_IN_SY;
    IStandardizedYield public immutable SY;
    IPPrincipalToken public immutable PT;
    IPYieldToken public immutable YT;

    constructor(
        address market,
        address tokenInSY,
        address tokenOutSY,
        address asset,
        address yieldToken,
        uint256 feeRate,
        IWithdrawRequestManager withdrawRequestManager
    )
        AbstractStakingStrategy(asset, yieldToken, feeRate, withdrawRequestManager)
    {
        MARKET = IPMarket(market);
        (address sy, address pt, address yt) = MARKET.readTokens();
        SY = IStandardizedYield(sy);
        PT = IPPrincipalToken(pt);
        YT = IPYieldToken(yt);
        require(address(PT) == yieldToken);
        require(SY.isValidTokenIn(tokenInSY));
        // This may not be the same as valid token in, for LRT you can
        // put ETH in but you would only get weETH or eETH out
        require(SY.isValidTokenOut(tokenOutSY));

        TOKEN_IN_SY = tokenInSY;
        TOKEN_OUT_SY = tokenOutSY;

        if (address(withdrawRequestManager) == address(0)) {
            // If no withdraw request manager is set then use the token in sy
            // as the accounting asset.
            accountingAsset = tokenInSY;
        }
    }

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

    function _mintYieldTokens(uint256 assets, address, /* receiver */ bytes memory data) internal override {
        require(!PT.isExpired(), "Expired");
        PendleDepositParams memory params = abi.decode(data, (PendleDepositParams));
        uint256 tokenInAmount;

        if (TOKEN_IN_SY != asset) {
            Trade memory trade = Trade({
                tradeType: TradeType.EXACT_IN_SINGLE,
                sellToken: asset,
                buyToken: TOKEN_IN_SY,
                amount: assets,
                limit: params.minPurchaseAmount,
                deadline: block.timestamp,
                exchangeData: params.exchangeData
            });

            // Executes a trade on the given Dex, the vault must have permissions set for
            // each dex and token it wants to sell.
            ( /* */ , tokenInAmount) = _executeTrade(trade, params.dexId);
        } else {
            tokenInAmount = assets;
        }

        PendlePTLib.swapExactTokenForPt(TOKEN_IN_SY, address(MARKET), address(PT), tokenInAmount, params.pendleData);
    }

    /// @notice Handles PT redemption whether it is expired or not
    function _redeemPT(uint256 netPtIn, bytes memory limitOrderData) internal returns (uint256 netTokenOut) {
        if (PT.isExpired()) {
            netTokenOut = PendlePTLib.redeemExpiredPT(PT, YT, SY, TOKEN_OUT_SY, netPtIn);
        } else {
            netTokenOut =
                PendlePTLib.swapExactPtForToken(address(PT), address(MARKET), TOKEN_OUT_SY, netPtIn, limitOrderData);
        }
    }

    function _executeInstantRedemption(
        uint256 yieldTokensToRedeem,
        bytes memory redeemData
    )
        internal
        virtual
        override
        returns (uint256 assetsPurchased)
    {
        PendleRedeemParams memory params = abi.decode(redeemData, (PendleRedeemParams));
        uint256 netTokenOut = _redeemPT(yieldTokensToRedeem, params.limitOrderData);

        if (TOKEN_OUT_SY != asset) {
            Trade memory trade = Trade({
                tradeType: TradeType.EXACT_IN_SINGLE,
                sellToken: TOKEN_OUT_SY,
                buyToken: asset,
                amount: netTokenOut,
                limit: params.minPurchaseAmount,
                deadline: block.timestamp,
                exchangeData: params.exchangeData
            });

            // Executes a trade on the given Dex, the vault must have permissions set for
            // each dex and token it wants to sell.
            ( /* */ , assetsPurchased) = _executeTrade(trade, params.dexId);
        } else {
            if (params.minPurchaseAmount > netTokenOut) revert SlippageTooHigh(netTokenOut, params.minPurchaseAmount);
            assetsPurchased = netTokenOut;
        }
    }

    function _initiateWithdraw(
        address account,
        uint256 ptAmount,
        uint256 sharesHeld,
        bytes memory data,
        address forceWithdrawFrom
    )
        internal
        override
        returns (uint256 requestId)
    {
        // Withdraws can only be initiated for expired PTs
        require(PT.isExpired(), "Cannot initiate withdraw for non-expired PTs");
        // When doing a direct withdraw for PTs, we first redeem the expired PT
        // and then initiate a withdraw on the TOKEN_OUT_SY. Since the vault shares are
        // stored in PT terms, we pass tokenOutSy terms (i.e. weETH or sUSDe) to the withdraw
        // implementation.
        uint256 tokenOutSy = _redeemPT(ptAmount, bytes(""));

        ERC20(TOKEN_OUT_SY).checkApprove(address(withdrawRequestManager), tokenOutSy);
        return withdrawRequestManager.initiateWithdraw({
            account: account,
            yieldTokenAmount: tokenOutSy,
            sharesAmount: sharesHeld,
            data: data,
            forceWithdrawFrom: forceWithdrawFrom
        });
    }
}
"
    },
    "src/interfaces/IEthena.sol": {
      "content": "// SPDX-License-Identifier: BUSL-1.1
pragma solidity >=0.8.29;

import { IERC4626 } from "@openzeppelin/contracts/interfaces/IERC4626.sol";
import { ERC20 } from "@openzeppelin/contracts/token/ERC20/ERC20.sol";

// Mainnet Ethena contract addresses
IsUSDe constant sUSDe = IsUSDe(0x9D39A5DE30e57443BfF2A8307A4256c8797A3497);
ERC20 constant USDe = ERC20(0x4c9EDD5852cd905f086C759E8383e09bff1E68B3);
// Dai and sDAI are required for trading out of sUSDe
ERC20 constant DAI = ERC20(0x6B175474E89094C44Da98b954EedeAC495271d0F);
IERC4626 constant sDAI = IERC4626(0x83F20F44975D03b1b09e64809B757c47f942BEeA);

interface IsUSDe is IERC4626 {
    struct UserCooldown {
        uint104 cooldownEnd;
        uint152 underlyingAmount;
    }

    function cooldownDuration() external view returns (uint24);
    function cooldowns(address account) external view returns (UserCooldown memory);
    function cooldownShares(uint256 shares) external returns (uint256 assets);
    function unstake(address receiver) external;
    function setCooldownDuration(uint24 duration) external;
    function owner() external view returns (address);
}
"
    },
    "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/interfaces/ITradingModule.sol": {
      "content": "// SPDX-License-Identifier: MIT
pragma solidity >=0.8.28;

import { AggregatorV2V3Interface } from "./AggregatorV2V3Interface.sol";

enum DexId {
    _UNUSED, // flag = 1,  enum = 0
    UNISWAP_V2, // flag = 2,  enum = 1
    UNISWAP_V3, // flag = 4,  enum = 2
    ZERO_EX, // flag = 8,  enum = 3
    BALANCER_V2, // flag = 16, enum = 4
    // NOTE: this id is unused in the TradingModule
    CURVE, // flag = 32, enum = 5
    NOTIONAL_VAULT, // flag = 64, enum = 6
    CURVE_V2, // flag = 128, enum = 7
    CAMELOT_V3 // flag = 256, enum = 8

}

enum TradeType {
    EXACT_IN_SINGLE, // flag = 1
    EXACT_OUT_SINGLE, // flag = 2
    EXACT_IN_BATCH, // flag = 4
    EXACT_OUT_BATCH, // flag = 8
    STAKE_TOKEN // flag = 16

}

struct UniV3SingleData {
    uint24 fee;
}

// Path is packed encoding `token, fee, token, fee, outToken`
struct UniV3BatchData {
    bytes path;
}

struct CurveV2SingleData {
    // Address of the pool to use for the swap
    address pool;
    int128 fromIndex;
    int128 toIndex;
}

struct CurveV2BatchData {
    // Array of [initial token, pool, token, pool, token, ...]
    // The array is iterated until a pool address of 0x00, then the last
    // given token is transferred to `_receiver`
    address[9] route;
    // Multidimensional array of [i, j, swap type] where i and j are the correct
    // values for the n'th pool in `_route`. The swap type should be
    // 1 for a stableswap `exchange`,
    // 2 for stableswap `exchange_underlying`,
    // 3 for a cryptoswap `exchange`,
    // 4 for a cryptoswap `exchange_underlying`,
    // 5 for factory metapools with lending base pool `exchange_underlying`,
    // 6 for factory crypto-meta pools underlying exchange (`exchange` method in zap),
    // 7-11 for wrapped coin (underlying for lending or fake pool) -> LP token "exchange" (actually `add_liquidity`),
    // 12-14 for LP token -> wrapped coin (underlying for lending pool) "exchange" (actually
    // `remove_liquidity_one_coin`)
    // 15 for WETH -> ETH "exchange" (actually deposit/withdraw)
    uint256[3][4] swapParams;
}

struct Trade {
    TradeType tradeType;
    address sellToken;
    address buyToken;
    uint256 amount;
    /// minBuyAmount or maxSellAmount
    uint256 limit;
    uint256 deadline;
    bytes exchangeData;
}

error InvalidTrade();
error DynamicTradeFailed();
error TradeFailed();

interface nProxy {
    function getImplementation() external view returns (address);
}

interface ITradingModule {
    struct TokenPermissions {
        bool allowSell;
        /// @notice allowed DEXes
        uint32 dexFlags;
        /// @notice allowed trade types
        uint32 tradeTypeFlags;
    }

    event TradeExecuted(address indexed sellToken, address indexed buyToken, uint256 sellAmount, uint256 buyAmount);

    event PriceOracleUpdated(address token, address oracle);
    event MaxOracleFreshnessUpdated(uint32 currentValue, uint32 newValue);
    event TokenPermissionsUpdated(address sender, address token, TokenPermissions permissions);

    function tokenWhitelist(
        address spender,
        address token
    )
        external
        view
        returns (bool allowSell, uint32 dexFlags, uint32 tradeTypeFlags);

    function priceOracles(address token) external view returns (AggregatorV2V3Interface oracle, uint8 rateDecimals);

    function getExecutionData(
        uint16 dexId,
        address from,
        Trade calldata trade
    )
        external
        view
        returns (address spender, address target, uint256 value, bytes memory params);

    function setMaxOracleFreshness(uint32 newMaxOracleFreshnessInSeconds) external;

    function setPriceOracle(address token, AggregatorV2V3Interface oracle) external;

    function setTokenPermissions(address sender, address token, TokenPermissions calldata permissions) external;

    function getOraclePrice(address inToken, address outToken) external view returns (int256 answer, int256 decimals);

    function executeTrade(
        uint16 dexId,
        Trade calldata trade
    )
        external
        payable
        returns (uint256 amountSold, uint256 amountBought);

    function executeTradeWithDynamicSlippage(
        uint16 dexId,
        Trade memory trade,
        uint32 dynamicSlippageLimit
    )
        external
        payable
        returns (uint256 amountSold, uint256 amountBought);

    function getLimitAmount(
        address from,
        TradeType tradeType,
        address sellToken,
        address buyToken,
        uint256 amount,
        uint32 slippageLimit
    )
        external
        view
        returns (uint256 limitAmount);

    function canExecuteTrade(address from, uint16 dexId, Trade calldata trade) external view returns (bool);
}

ITradingModule constant TRADING_MODULE = ITradingModule(0x594734c7e06C3D483466ADBCe401C6Bd269746C8);
"
    },
    "src/interfaces/Errors.sol": {
      "content": "// SPDX-License-Identifier: BUSL-1.1
pragma solidity >=0.8.28;

error NotAuthorized(address operator, address user);
error Unauthorized(address caller);
error UnauthorizedLendingMarketTransfer(address from, address to, uint256 value);
error InsufficientYieldTokenBalance();
error InsufficientAssetsForRepayment(uint256 assetsToRepay, uint256 assetsWithdrawn);
error CannotLiquidate(uint256 maxLiquidateShares, uint256 seizedAssets);
error CannotLiquidateZeroShares();
error Paused();
error CannotExitPositionWithinCooldownPeriod();
error CannotTokenizeWithdrawRequest();
error CurrentAccountAlreadySet();
error InvalidVault(address vault);

error WithdrawRequestNotFinalized(uint256 requestId);
error CannotInitiateWithdraw(address account);
error CannotForceWithdraw(address account);
error InsufficientSharesHeld();
error SlippageTooHigh(uint256 actualTokensOut, uint256 minTokensOut);

error CannotEnterPosition();
error NoExistingPosition();
error LiquidatorHasPosition();
error InvalidUpgrade();
error InvalidInitialization();
error InvalidLendingRouter();

error ExistingWithdrawRequest(address vault, address account, uint256 requestId);
error NoWithdrawRequest(address vault, address account);
error InvalidWithdrawRequestTokenization();

error InvalidPrice(uint256 oraclePrice, uint256 spotPrice);
error PoolShareTooHigh(uint256 poolClaim, uint256 maxSupplyThreshold);
error AssetRemaining(uint256 assetRemaining);
"
    },
    "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) {
            safeTransfer(token, to, value);
        } else if (!token.transferAndCall(to, value, data)) {
            revert SafeERC20FailedOperation(address(token));
        }
    }

    /**
     * @dev Performs an {ERC1363} transferFromAndCall, with a fallback to the simple {ERC20} transferFrom 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 transferFromAndCallRelaxed(
        IERC1363 token,
        address from,
        address to,
        uint256 value,
        bytes memory data
    ) internal {
        if (to.code.length == 0) {
            safeTransferFrom(token, from, to, value);
        } else if (!token.transferFromAndCall(from, to, value, data)) {
            revert SafeERC20FailedOperation(address(token));
        }
    }

    /**
     * @dev Performs an {ERC1363} approveAndCall, with a fallback to the simple {ERC20} approve 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.
     *
     * NOTE: When the recipient address (`to`) has no code (i.e. is an EOA), this function behaves as {forceApprove}.
     * Opposedly, when the recipient address (`to`) has code, this function only attempts to call {ERC1363-approveAndCall}
     * once without retrying, and relies on the returned value to be true.
     *
     * Reverts if the returned value is other than `true`.
     */
    function approveAndCallRelaxed(IERC1363 token, address to, uint256 value, bytes memory data) internal {
        if (to.code.length == 0) {
            forceApprove(token, to, value);
        } else if (!token.approveAndCall(to, value, data)) {
            revert SafeERC20FailedOperation(address(token));
        }
    }

    /**
     * @dev Imitates a Solidity high-level call (i.e. a regular function call to a contract), relaxing the requirement
     * on the return value: the return value is optional (but if data is returned, it must not be false).
     * @param token The token targeted by the call.
     * @param data The call data (encoded using abi.encode or one of its variants).
     *
     * This is a variant of {_callOptionalReturnBool} that reverts if call fails to meet the requirements.
     */
    function _callOptionalReturn(IERC20 token, bytes memory data) private {
        uint256 returnSize;
        uint256 returnValue;
        assembly ("memory-safe") {
            let success := call(gas(), token, 0, add(data, 0x20), mload(data), 0, 0x20)
            // bubble errors
            if iszero(success) {
                let ptr := mload(0x40)
                returndatacopy(ptr, 0, returndatasize())
                revert(ptr, returndatasize())
            }
            returnSize := returndatasize()
            returnValue := mload(0)
        }

        if (returnSize == 0 ? address(token).code.length == 0 : returnValue != 1) {
            revert SafeERC20FailedOperation(address(token));
        }
    }

    /**
     * @dev Imitates a Solidity high-level call (i.e. a regular function call to a contract), relaxing the requirement
     * on the return value: the return value is optional (but if data is returned, it must not be false).
     * @param token The token targeted by the call.
     * @param data The call data (encoded using abi.encode or one of its variants).
     *
     * This is a variant of {_callOptionalReturn} that silently catches all reverts and returns a bool instead.
     */
    function _callOptionalReturnBool(IERC20 token, bytes memory data) private returns (bool) {
        bool success;
        uint256 returnSize;
        uint256 returnValue;
        assembly ("memory-safe") {
            success := call(gas(), token, 0, add(data, 0x20), mload(data), 0, 0x20)
            returnSize := returndatasize()
            returnValue := mload(0)
        }
        return success && (returnSize == 0 ? address(token).code.length > 0 : returnValue == 1);
    }
}
"
    },
    "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/staking/AbstractStakingStrategy.sol": {
      "content": "// SPDX-License-Identifier: BUSL-1.1
pragma solidity >=0.8.29;

import { AbstractYieldStrategy } from "../AbstractYieldStrategy.sol";
import { IWithdrawRequestManager, WithdrawRequest } from "../interfaces/IWithdrawRequestManager.sol";
import { Trade, TradeType } from "../interfaces/ITradingModule.sol";
import { ERC20 } from "@openzeppelin/contracts/token/ERC20/ERC20.sol";
import { TokenUtils } from "../utils/TokenUtils.sol";

struct RedeemParams {
    uint8 dexId;
    uint256 minPurchaseAmount;
    bytes exchangeData;
}

struct DepositParams {
    uint8 dexId;
    uint256 minPurchaseAmount;
    bytes exchangeData;
}

/**
 * Supports vaults that borrow a token and stake it into a token that earns yield but may
 * require some illiquid redemption period.
 */
abstract contract AbstractStakingStrategy is AbstractYieldStrategy {
    using TokenUtils for ERC20;

    address internal immutable withdrawToken;

    constructor(
        address _asset,
        address _yieldToken,
        uint256 _feeRate,
        IWithdrawRequestManager _withdrawRequestManager
    )
        AbstractYieldStrategy(_asset, _yieldToken, _feeRate, ERC20(_yieldToken).decimals())
    {
        // For Pendle PT the yield token does not define the withdraw request manager,
        // it is the token out sy
        withdrawRequestManager = _withdrawRequestManager;

        if (address(withdrawRequestManager) != address(0)) {
            accountingAsset = withdrawRequestManager.STAKING_TOKEN();
            withdrawToken = withdrawRequestManager.WITHDRAW_TOKEN();
        } else {
            withdrawToken = address(0);
            // Accounting asset will be set to the asset itself if no withdraw
            // request manager is set. For Pendle PT strategies this will be
            // set to the token in sy.
        }
    }

    /// @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)) {
            (bool hasRequest, uint256 value) =
                withdrawRequestManager.getWithdrawRequestValue(address(this), t_CurrentAccount, asset, shares);

            // If the account does not have a withdraw request then this will fall through
            // to the super implementation.
            if (hasRequest) return value;
        }

        return super.convertToAssets(shares);
    }

    function _initiateWithdraw(
        address account,
        uint256 yieldTokenAmount,
        uint256 sharesHeld,
        bytes memory data,
        address forceWithdrawFrom
    )
        internal
        virtual
        override
        returns (uint256 requestId)
    {
        ERC20(yieldToken).checkApprove(address(withdrawRequestManager), yieldTokenAmount);
        requestId = withdrawRequestManager.initiateWithdraw({
            account: account,
            yieldTokenAmount: yieldTokenAmount,
            sharesAmount: sharesHeld,
            data: data,
            forceWithdrawFrom: forceWithdrawFrom
        });
    }

    /// @dev By default we can use the withdraw request manager to stake the tokens
    function _mintYieldTokens(
        uint256 assets,
        address, /* receiver */
        bytes memory depositData
    )
        internal
        virtual
        override
    {
        ERC20(asset).checkApprove(address(withdrawRequestManager), assets);
        withdrawRequestManager.stakeTokens(address(asset), assets, depositData);
    }

    function _redeemShares(
        uint256 sharesToRedeem,
        address sharesOwner,
        bool isEscrowed,
        bytes memory redeemData
    )
        internal
        override
    {
        if (isEscrowed) {
            (WithdrawRequest memory w, /* */ ) = withdrawRequestManager.getWithdrawRequest(address(this), sharesOwner);
            uint256 yieldTokensBurned = uint256(w.yieldTokenAmount) * sharesToRedeem / w.sharesAmount;

            uint256 tokensClaimed = withdrawRequestManager.finalizeAndRedeemWithdrawRequest({
                account: sharesOwner,
                withdrawYieldTokenAmount: yieldTokensBurned,
                sharesToBurn: sharesToRedeem
            });

            // Trades may be required here if the borrowed token is not the same as what is
            // received when redeeming.
            if (asset != withdrawToken) {
                RedeemParams memory params = abi.decode(redeemData, (RedeemParams));
                Trade memory trade = Trade({
                    tradeType: TradeType.EXACT_IN_SINGLE,
                    sellToken: address(withdrawToken),
                    buyToken: address(asset),
                    amount: tokensClaimed,
                    limit: params.minPurchaseAmount,
                    deadline: block.timestamp,
                    exchangeData: params.exchangeData
                });

                _executeTrade(trade, params.dexId);
            }
        } else {
            uint256 yieldTokensBurned = convertSharesToYieldToken(sharesToRedeem);
            _executeInstantRedemption(yieldTokensBurned, redeemData);
        }
    }

    /// @notice Default implementation for an instant redemption is to sell the staking token to the
    /// borrow token through the trading module. Can be overridden if required for different implementations.
    function _executeInstantRedemption(
        uint256 yieldTokensToRedeem,
        bytes memory redeemData
    )
        internal
        virtual
        returns (uint256 assetsPurchased)
    {
        RedeemParams memory params = abi.decode(redeemData, (RedeemParams));
        Trade memory trade = Trade({
            tradeType: TradeType.EXACT_IN_SINGLE,
            sellToken: address(yieldToken),
            buyToken: address(asset),
            amount: yieldTokensToRedeem,
            limit: params.minPurchaseAmount,
            deadline: block.timestamp,
            exchangeData: params.exchangeData
        });

        // Executes a trade on the given Dex, the vault must have permissions set for
        // each dex and token it wants to sell.
        ( /* */ , assetsPurchased) = _executeTrade(trade, params.dexId);
    }

    /* solhint-disable no-empty-blocks */
    function _preLiquidation(address, address, uint256, uint256) internal override { /* no-op */ }
    /* solhint-enable no-empty-blocks */

    function _postLiquidation(
        address liquidator,
        address liquidateAccount,
        uint256 sharesToLiquidator
    )
        internal
        override
        returns (bool didTokenize)
    {
        if (address(withdrawRequestManager) != address(0)) {
            // No need to accrue fees because neither the total supply or total yield token balance is changing. If
            // there
            // is no withdraw request then this will be a noop.
            didTokenize =
                withdrawRequestManager.tokenizeWithdrawRequest(liquidateAccount, liquidator, sharesToLiquidator);
        }
    }
}
"
    },
    "src/interfaces/IPendle.sol": {
      "content": "// SPDX-License-Identifier: GPL-3.0-only
pragma solidity >=0.8.28;

import { IERC20Metadata } from "@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol";

event OrderFilledV2(
    bytes32 indexed orderHash,
    IPRouter.OrderType indexed orderType,
    address indexed YT,
    address token,
    uint256 netInputFromMaker,
    uint256 netOutputToMaker,
    uint256 feeAmount,
    uint256 notionalVolume,
    address maker,
    address taker
);

interface IPOracle {
    function getPtToAssetRate(address market, uint32 duration) external view returns (uint256);

    function getPtToSyRate(address market, uint32 duration) external view returns (uint256);

    function getOracleState(
        address market,
        uint32 duration
    )
        external
        view
        returns (bool increaseCardinalityRequired, uint16 cardinalityRequired, bool oldestObservationSatisfied);
}

interface IPRouter {
    struct SwapData {
        SwapType swapType;
        address extRouter;
        bytes extCalldata;
        bool needScale;
    }

    enum SwapType {
        NONE,
        KYBERSWAP,
        ONE_INCH,
        // ETH_WETH not used in Aggregator
        ETH_WETH
    }

    struct TokenInput {
        // TOKEN DATA
        address tokenIn;
        uint256 netTokenIn;
        address tokenMintSy;
        // AGGREGATOR DATA
        address pendleSwap;
        SwapData swapData;
    }

    struct TokenOutput {
        // TOKEN DATA
        address tokenOut;
        uint256 minTokenOut;
        address tokenRedeemSy;
        // AGGREGATOR DATA
        address pendleSwap;
        SwapData swapData;
    }

    struct LimitOrderData {
        address limitRouter;
        uint256 epsSkipMarket; // only used for swap operations, will be ignored otherwise
        FillOrderParams[] normalFills;
        FillOrderParams[] flashFills;
        bytes optData;
    }

    enum OrderType {
        SY_FOR_PT,
        PT_FOR_SY,
        SY_FOR_YT,
        YT_FOR_SY
    }

    struct Order {
        uint256 salt;
        uint256 expiry;
        uint256 nonce;
        OrderType orderType;
        address token;
        address YT;
        address maker;
        address receiver;
        uint256 makingAmount;
        uint256 lnImpliedRate;
        uint256 failSafeRate;
        bytes permit;
    }

    struct FillOrderParams {
        Order order;
        bytes signature;
        uint256 makingAmount;
    }

    struct ApproxParams {
        uint256 guessMin;
        uint256 guessMax;
        uint256 guessOffchain; // pass 0 in to skip this variable
        uint256 maxIteration; // every iteration, the diff between guessMin and guessMax will be divided by 2
        uint256 eps; // the max eps between the returned result & the correct result, base 1e18. Normally this number
            // will be set
            // to 1e15 (1e18/1000 = 0.1%)
    }

    function swapExactTokenForPt(
        address receiver,
        address market,
        uint256 minPtOut,
        ApproxParams calldata guessPtOut,
        TokenInput calldata input,
        LimitOrderData calldata limit
    )
        external
        payable
        returns (uint256 netPtOut, uint256 netSyFee, uint256 netSyInterm);

    function swapExactPtForToken(
        address receiver,
        address market,
        uint256 exactPtIn,
        TokenOutput calldata output,
        LimitOrderData calldata limit
    )
        external
        returns (uint256 netTokenOut, uint256 netSyFee, uint256 netSyInterm);

    function redeemPyToToken(
        address receiver,
        address YT,
        uint256 netPyIn,
        TokenOutput calldata output
    )
        external
        returns (uint256 netTokenOut, uint256 netSyInterm);
}

interface IPMarket {
    function mint(
        address receiver,
        uint256 netSyDesired,
        uint256 netPtDesired
    )
        external
        returns (uint256 netLpOut, uint256 netSyUsed, uint256 netPtUsed);

    function burn(
        address receiverSy,
        address receiverPt,
        uint256 netLpToBurn
    )
        external
        returns (uint256 netSyOut, uint256 netPtOut);

    function swapExactPtForSy(
        address receiver,
        uint256 exactPtIn,
        bytes calldata data
    )
        external
        returns (uint256 netSyOut, uint256 netSyFee);

    function swapSyForExactPt(
        address receiver,
        uint256 exactPtOut,
        bytes calldata data
    )
        external
        returns (uint256 netSyIn, uint256 netSyFee);

    function redeemRewards(address user) external returns (uint256[] memory);

    // function readState(address router) external view returns (MarketState memory market);

    function observe(uint32[] memory secondsAgos) external view returns (uint216[] memory lnImpliedRateCumulative);

    function increaseObservationsCardinalityNext(uint16 cardinalityNext) external;

    function readTokens() external view returns (address _SY, address _PT, address _YT);

    function getRewardTokens() external view returns (address[] memory);

    function isExpired() external view returns (bool);

    function expiry() external view returns (uint256);

    function observations(uint256 index)
        external
        view
        returns (uint32 blockTimestamp, uint216 lnImpliedRateCumulative, bool initialized);

    function _storage()
        external
        view
        returns (
            int128 totalPt,
            int128 totalSy,
            uint96 lastLnImpliedRate,
            uint16 observationIndex,
            uint16 observationCardinality,
            uint16 observationCardinalityNext
        );
}

interface IStandardizedYield is IERC20Metadata {
    /// @dev Emitted when any base tokens is deposited to mint shares
    event Deposit(
        address indexed caller,
        address indexed receiver,
        address indexed tokenIn,
        uint256 amountDeposited,
        uint256 amountSyOut
    );

    /// @dev Emitted when any shares are redeemed for base tokens
    event Redeem(
        address indexed caller,
        address indexed receiver,
        address indexed tokenOut,
        uint256 amountSyToRedeem,
        uint256 amountTokenOut
    );

    /// @dev check `assetInfo()` for more information
    enum AssetType {
        TOKEN,
        LIQUIDITY
    }

    /// @dev Emitted when (`user`) claims their rewards
    event ClaimRewards(address indexed user, address[] rewardTokens, uint256[] rewardAmounts);

    /**
     * @notice mints an amount of shares by depositing a base token.
     * @param receiver shares recipient address
     * @param tokenIn address of the base tokens to mint shares
     * @param amountTokenToDeposit amount of base tokens to be transferred from (`msg.sender`)
     * @param minSharesOut reverts if amount of shares minted is lower than this
     * @return amountSharesOut amount of shares minted
     * @dev Emits a {Deposit} event
     *
     * Requirements:
     * - (`tokenIn`) must be a valid base token.
     */
    function deposit(
        address receiver,
        address tokenIn,
        uint256 amountTokenToDeposit,
        uint256 minSharesOut
    )
        external
        payable
        returns (uint256 amountSharesOut);

    /**
     * @notice redeems an amount of base tokens by burning some shares
     * @param receiver recipient address
     * @param amountSharesToRedeem amount of shares to be burned
     * @param tokenOut address of the base token to be redeemed
     * @param minTokenOut reverts if amount of base token redeemed is lower than this
     * @param burnFromInternalBalance if true, burns from balance of `address(this)`, otherwise burns from `msg.sender`
     * @return amountTokenOut amount of base tokens redeemed
     * @dev Emits a {Redeem} event
     *
     * Requirements:
     * - (`tokenOut`) must be a valid base token.
     */
    function redeem(
        address receiver,
        uint256 amountSharesToRedeem,
        address tokenOut,
        uint256 minTokenOut,
        bool burnFromInternalBalance
    )
        external
        returns (uint256 amountTokenOut);

    /**
     * @notice exchangeRate * syBalance / 1e18 must return the asset balance of the account
     * @notice vice-versa, if a user uses some amount of tokens equivalent to X asset, the amount of sy
     *  he can mint must be X * exchangeRate / 1e18
     * @dev SYUtils's assetToSy & syToAsset should be used instead of raw multiplication
     *  & division
     */
    function exchangeRate() external view returns (uint256 res);

    /**
     * @notice claims reward for (`user`)
     * @param user the user receiving their rewards
     * @return rewardAmounts an array of reward amounts in the same order as `getRewardTokens`
     * @dev
     * Emits a `ClaimRewards` event
     * See {getRewardTokens} for list of reward tokens
     */
    function claimRewards(address user) external returns (uint256[] memory rewardAmounts);

    /**
     * @notice get the amount of unclaimed rewards for (`user`)
     * @param user the user to check for
     * @return rewardAmounts an array of reward amounts in the same order as `getRewardTokens`
     */
    function accruedRewards(address user) external view returns (uint256[] memory rewardAmounts);

    function rewardIndexesCurrent() external returns (uint256[] memory indexes);

    function rewardIndexesStored() external view returns (uint256[] memory indexes);

    /**
     * @notice returns the list of reward token addresses
     */
    function g

Tags:
ERC20, ERC165, Multisig, Mintable, Burnable, Swap, Liquidity, Staking, Yield, Upgradeable, Multi-Signature, Factory, Oracle|addr:0x7eeba23b5f89bd8711951c8d3a0bace8810e2490|verified:true|block:23449858|tx:0xd5224e94cbfc6678041c5d04b3d6246a03d7b41301dfa21f297591860ffeb631|first_check:1758962982

Submitted on: 2025-09-27 10:49:44

Comments

Log in to comment.

No comments yet.