TermMaxOrderV2

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": {
    "contracts/v2/TermMaxOrderV2.sol": {
      "content": "// SPDX-License-Identifier: BUSL-1.1
pragma solidity ^0.8.27;

import {
    OwnableUpgradeable,
    Ownable2StepUpgradeable
} from "@openzeppelin/contracts-upgradeable/access/Ownable2StepUpgradeable.sol";
import {ReentrancyGuardUpgradeable} from "@openzeppelin/contracts-upgradeable/utils/ReentrancyGuardUpgradeable.sol";
import {PausableUpgradeable} from "@openzeppelin/contracts-upgradeable/utils/PausableUpgradeable.sol";
import {SafeCast} from "@openzeppelin/contracts/utils/math/SafeCast.sol";
import {IERC4626} from "@openzeppelin/contracts/interfaces/IERC4626.sol";
import {ITermMaxOrder, IERC20} from "../v1/ITermMaxOrder.sol";
import {ITermMaxMarket} from "../v1/ITermMaxMarket.sol";
import {IGearingToken} from "../v1/tokens/IGearingToken.sol";
import {Constants} from "../v1/lib/Constants.sol";
import {TermMaxCurve, MathLib} from "../v1/lib/TermMaxCurve.sol";
import {OrderErrors} from "../v1/errors/OrderErrors.sol";
import {OrderEvents} from "../v1/events/OrderEvents.sol";
import {OrderConfig, MarketConfig, CurveCuts, CurveCut, FeeConfig} from "../v1/storage/TermMaxStorage.sol";
import {ISwapCallback} from "../v1/ISwapCallback.sol";
import {TransferUtils} from "../v1/lib/TransferUtils.sol";
import {ITermMaxOrderV2, OrderInitialParams} from "./ITermMaxOrderV2.sol";
import {OrderEventsV2} from "./events/OrderEventsV2.sol";
import {OrderErrorsV2} from "./errors/OrderErrorsV2.sol";
import {VersionV2} from "./VersionV2.sol";

/**
 * @title TermMax Order V2
 * @notice Support deposit idle funds to the pool to earn yield
 * @author Term Structure Labs
 */
contract TermMaxOrderV2 is
    ITermMaxOrder,
    ITermMaxOrderV2,
    ReentrancyGuardUpgradeable,
    Ownable2StepUpgradeable,
    PausableUpgradeable,
    OrderErrors,
    OrderEvents,
    VersionV2
{
    using SafeCast for uint256;
    using SafeCast for int256;
    using TransferUtils for IERC20;
    using TransferUtils for IERC4626;
    using MathLib for *;

    // =============================================================================
    // STATE VARIABLES
    // =============================================================================

    ITermMaxMarket public market;

    IERC20 private ft;
    IERC20 private xt;
    IERC20 private debtToken;
    IGearingToken private gt;

    OrderConfig private _orderConfig;

    uint64 private maturity;

    /// @notice The virtual xt reserve can present current price, which only changed when swap happens
    uint256 public virtualXtReserve;
    IERC4626 public pool;

    // =============================================================================
    // MODIFIERS
    // =============================================================================

    /// @notice Check if the market is borrowing allowed
    modifier onlyBorrowingIsAllowed(OrderConfig memory config) {
        if (config.curveCuts.lendCurveCuts.length == 0) {
            revert BorrowIsNotAllowed();
        }
        _;
    }

    /// @notice Check if the market is lending allowed
    modifier onlyLendingIsAllowed(OrderConfig memory config) {
        if (config.curveCuts.borrowCurveCuts.length == 0) {
            revert LendIsNotAllowed();
        }
        _;
    }

    /// @notice Check if the order is tradable
    modifier onlyOpen() {
        _requireNotPaused();
        if (block.timestamp >= maturity) {
            revert TermIsNotOpen();
        }
        _;
    }

    modifier onlyMarket() {
        if (msg.sender != address(market)) revert OnlyMarket();
        _;
    }

    // =============================================================================
    // CONSTRUCTOR & INITIALIZATION
    // =============================================================================

    constructor() {
        _disableInitializers();
    }

    /**
     * @notice Initialize the order with V1 parameters (deprecated)
     * @inheritdoc ITermMaxOrder
     */
    function initialize(
        address,
        IERC20[3] memory,
        IGearingToken,
        uint256,
        ISwapCallback,
        CurveCuts memory,
        MarketConfig memory
    ) external virtual override initializer {
        revert OrderErrorsV2.UseOrderInitializationFunctionV2();
    }

    /**
     * @notice Initialize the order with V2 parameters
     * @inheritdoc ITermMaxOrderV2
     */
    function initialize(OrderInitialParams memory params) external virtual override initializer {
        __Ownable_init_unchained(params.maker);
        __ReentrancyGuard_init_unchained();
        __Pausable_init_unchained();
        address _market = _msgSender();
        market = ITermMaxMarket(_market);
        maturity = params.maturity;
        ft = params.ft;
        xt = params.xt;
        debtToken = params.debtToken;
        gt = params.gt;
        _setPool(params.pool);
        _setCurveAndPrice(params.virtualXtReserve, params.orderConfig.maxXtReserve, params.orderConfig.curveCuts);
        _updateGeneralConfig(params.orderConfig.gtId, params.orderConfig.swapTrigger);
        emit OrderEventsV2.OrderInitialized(params.maker, _market);
    }

    // =============================================================================
    // VIEW FUNCTIONS - STATE GETTERS
    // =============================================================================

    /**
     * @notice Get the maker (owner) of the order
     * @return The maker address
     */
    function maker() public view returns (address) {
        return owner();
    }

    /**
     * @notice Get the current order configuration
     * @inheritdoc ITermMaxOrder
     * @return orderConfig_ The current order configuration
     */
    function orderConfig() external view virtual returns (OrderConfig memory orderConfig_) {
        orderConfig_ = _orderConfig;
        orderConfig_.feeConfig = market.config().feeConfig;
    }

    /**
     * @notice Get the token reserves in the order
     * @return ftReserve FT reserve
     * @return xtReserve XT reserve
     */
    function tokenReserves() public view override returns (uint256 ftReserve, uint256 xtReserve) {
        ftReserve = ft.balanceOf(address(this));
        xtReserve = xt.balanceOf(address(this));
    }

    /**
     * @notice Get real reserves including assets in pool
     * @return ftReserve FT reserve including pool assets
     * @return xtReserve XT reserve including pool assets
     */
    function getRealReserves() external view virtual returns (uint256 ftReserve, uint256 xtReserve) {
        uint256 assetsInPool = _assetsInPool();
        ftReserve = ft.balanceOf(address(this)) + assetsInPool;
        xtReserve = xt.balanceOf(address(this)) + assetsInPool;
    }

    /**
     * @notice Calculate current APR for lending and borrowing
     * @inheritdoc ITermMaxOrder
     * @return lendApr_ Current lending APR
     * @return borrowApr_ Current borrowing APR
     */
    function apr() external view virtual override returns (uint256 lendApr_, uint256 borrowApr_) {
        uint256 daysToMaturity = _daysToMaturity();
        uint256 oriXtReserve = xt.balanceOf(address(this));

        CurveCuts memory curveCuts = _orderConfig.curveCuts;
        if (curveCuts.lendCurveCuts.length == 0) {
            lendApr_ = 0;
        } else {
            uint256 lendCutId = TermMaxCurve.calcCutId(curveCuts.lendCurveCuts, oriXtReserve);
            (, uint256 lendVXtReserve, uint256 lendVFtReserve) = TermMaxCurve.calcIntervalProps(
                Constants.DECIMAL_BASE, daysToMaturity, curveCuts.lendCurveCuts[lendCutId], oriXtReserve
            );
            lendApr_ =
                ((lendVFtReserve * Constants.DECIMAL_BASE * Constants.DAYS_IN_YEAR) / (lendVXtReserve * daysToMaturity));
        }
        if (curveCuts.borrowCurveCuts.length == 0) {
            borrowApr_ = type(uint256).max;
        } else {
            uint256 borrowCutId = TermMaxCurve.calcCutId(curveCuts.borrowCurveCuts, oriXtReserve);
            (, uint256 borrowVXtReserve, uint256 borrowVFtReserve) = TermMaxCurve.calcIntervalProps(
                Constants.DECIMAL_BASE, daysToMaturity, curveCuts.borrowCurveCuts[borrowCutId], oriXtReserve
            );

            borrowApr_ = (
                (borrowVFtReserve * Constants.DECIMAL_BASE * Constants.DAYS_IN_YEAR)
                    / (borrowVXtReserve * daysToMaturity)
            );
        }
    }

    // =============================================================================
    // INTERNAL VIEW FUNCTIONS - HELPERS
    // =============================================================================

    /**
     * @notice Get assets amount in the staking pool
     * @return assets Amount of assets in pool
     */
    function _assetsInPool() internal view returns (uint256 assets) {
        if (pool != IERC4626(address(0))) {
            assets = pool.convertToAssets(pool.balanceOf(address(this)));
        }
    }

    /**
     * @notice Calculate days until maturity
     * @return daysToMaturity Number of days to maturity
     */
    function _daysToMaturity() internal view returns (uint256 daysToMaturity) {
        daysToMaturity = (maturity - block.timestamp + Constants.SECONDS_IN_DAY - 1) / Constants.SECONDS_IN_DAY;
    }

    // =============================================================================
    // ADMIN FUNCTIONS - CONFIGURATION
    // =============================================================================

    /**
     * @notice This function is disabled and will always revert
     */
    function updateOrder(OrderConfig memory newOrderConfig, int256 ftChangeAmt, int256 xtChangeAmt)
        external
        virtual
        override
        onlyOwner
    {
        revert OrderErrorsV2.UpdateOrderFunctionDisabled();
    }

    function setCurveAndPrice(
        uint256 originalVirtualXtReserve,
        uint256 virtualXtReserve_,
        uint256 maxXtReserve_,
        CurveCuts memory newCurveCuts
    ) external virtual onlyOwner {
        require(originalVirtualXtReserve == virtualXtReserve, OrderErrorsV2.PriceChangedBeforeSet());
        _setCurveAndPrice(virtualXtReserve_, maxXtReserve_, newCurveCuts);
    }

    /**
     * @notice Internal function to update curve cuts
     * @param newCurveCuts New curve cuts to validate and set
     */
    function _setCurveAndPrice(uint256 virtualXtReserve_, uint256 maxXtReserve_, CurveCuts memory newCurveCuts)
        internal
    {
        if (newCurveCuts.lendCurveCuts.length > 0) {
            if (newCurveCuts.lendCurveCuts[0].liqSquare == 0 || newCurveCuts.lendCurveCuts[0].xtReserve != 0) {
                revert InvalidCurveCuts();
            }
        }
        for (uint256 i = 1; i < newCurveCuts.lendCurveCuts.length; i++) {
            if (
                newCurveCuts.lendCurveCuts[i].liqSquare == 0
                    || newCurveCuts.lendCurveCuts[i].xtReserve <= newCurveCuts.lendCurveCuts[i - 1].xtReserve
            ) {
                revert InvalidCurveCuts();
            }
            /*
                    R := (x' + beta') ^ 2 * DECIMAL_BASE / (x' + beta) ^ 2
                    L' ^ 2 := L ^ 2 * R / DECIMAL_BASE
                */
            if (
                newCurveCuts.lendCurveCuts[i].liqSquare
                    != (
                        newCurveCuts.lendCurveCuts[i - 1].liqSquare
                            * (
                                newCurveCuts.lendCurveCuts[i].xtReserve.plusInt256(newCurveCuts.lendCurveCuts[i].offset) ** 2
                                    * Constants.DECIMAL_BASE
                                    / newCurveCuts.lendCurveCuts[i].xtReserve.plusInt256(newCurveCuts.lendCurveCuts[i - 1].offset)
                                        ** 2
                            )
                    ) / Constants.DECIMAL_BASE
            ) revert InvalidCurveCuts();
        }
        if (newCurveCuts.borrowCurveCuts.length > 0) {
            if (newCurveCuts.borrowCurveCuts[0].liqSquare == 0 || newCurveCuts.borrowCurveCuts[0].xtReserve != 0) {
                revert InvalidCurveCuts();
            }
        }
        for (uint256 i = 1; i < newCurveCuts.borrowCurveCuts.length; i++) {
            if (
                newCurveCuts.borrowCurveCuts[i].liqSquare == 0
                    || newCurveCuts.borrowCurveCuts[i].xtReserve <= newCurveCuts.borrowCurveCuts[i - 1].xtReserve
            ) {
                revert InvalidCurveCuts();
            }
            if (
                newCurveCuts.borrowCurveCuts[i].liqSquare
                    != (
                        newCurveCuts.borrowCurveCuts[i - 1].liqSquare
                            * (
                                newCurveCuts.borrowCurveCuts[i].xtReserve.plusInt256(newCurveCuts.borrowCurveCuts[i].offset)
                                    ** 2 * Constants.DECIMAL_BASE
                                    / newCurveCuts.borrowCurveCuts[i].xtReserve.plusInt256(
                                        newCurveCuts.borrowCurveCuts[i - 1].offset
                                    ) ** 2
                            )
                    ) / Constants.DECIMAL_BASE
            ) revert InvalidCurveCuts();
        }
        _orderConfig.curveCuts = newCurveCuts;
        _orderConfig.maxXtReserve = maxXtReserve_;
        virtualXtReserve = virtualXtReserve_;
        emit OrderEventsV2.CurveAndPriceUpdated(virtualXtReserve_, maxXtReserve_, newCurveCuts);
    }

    function setGeneralConfig(uint256 gtId, ISwapCallback swapTrigger) external virtual onlyOwner {
        _updateGeneralConfig(gtId, swapTrigger);
    }

    function setPool(IERC4626 newPool) external virtual onlyOwner {
        _setPool(newPool);
    }

    function _setPool(IERC4626 newPool) internal {
        IERC4626 oldPool = pool;
        uint256 debtTokenAmt;
        if (address(oldPool) != address(0)) {
            // Withdraw all assets from the old pool
            uint256 shares = oldPool.balanceOf(address(this));
            if (shares != 0) {
                debtTokenAmt = oldPool.redeem(shares, address(this), address(this));
            }
        }
        if (address(newPool) != address(0)) {
            uint256 ftBalance = ft.balanceOf(address(this));
            uint256 xtBalance = xt.balanceOf(address(this));
            uint256 maxBurned = ftBalance > xtBalance ? xtBalance : ftBalance;
            if (maxBurned != 0) {
                ITermMaxMarket _market = market;
                _market.burn(address(this), maxBurned);
                debtTokenAmt += maxBurned;
            }
            if (debtTokenAmt != 0) {
                // If new pool is set, deposit all debt token to the new pool
                debtToken.safeIncreaseAllowance(address(newPool), debtTokenAmt);
                newPool.deposit(debtTokenAmt, address(this));
            }
        } else if (debtTokenAmt != 0) {
            ITermMaxMarket _market = market;
            debtToken.safeIncreaseAllowance(address(_market), debtTokenAmt);
            _market.mint(address(this), debtTokenAmt);
        }
        pool = newPool;
        emit OrderEventsV2.PoolUpdated(address(newPool));
    }

    /**
     * @notice This function is disabled and will always revert
     * @inheritdoc ITermMaxOrder
     * @param newFeeConfig New fee configuration
     */
    function updateFeeConfig(FeeConfig memory newFeeConfig) external virtual override onlyMarket {
        revert OrderErrorsV2.FeeConfigCanNotBeUpdated();
    }

    /**
     * @notice Internal function to update general configuration
     * @param gtId Gearing token ID
     * @param swapTrigger Swap callback trigger
     */
    function _updateGeneralConfig(uint256 gtId, ISwapCallback swapTrigger) internal {
        _orderConfig.gtId = gtId;
        _orderConfig.swapTrigger = swapTrigger;
        emit OrderEventsV2.GeneralConfigUpdated(gtId, swapTrigger);
    }

    /**
     * @notice Pause the order
     * @inheritdoc ITermMaxOrder
     */
    function pause() external virtual override onlyOwner {
        _pause();
    }

    /**
     * @notice Unpause the order
     * @inheritdoc ITermMaxOrder
     */
    function unpause() external virtual override onlyOwner {
        _unpause();
    }

    // =============================================================================
    // LIQUIDITY MANAGEMENT FUNCTIONS
    // =============================================================================

    function addLiquidity(IERC20 asset, uint256 amount) external nonReentrant onlyOwner {
        _addLiquidity(asset, amount);
    }

    function removeLiquidity(IERC20 asset, uint256 amount, address recipient) external nonReentrant onlyOwner {
        _removeLiquidity(asset, amount, recipient);
    }

    function _addLiquidity(IERC20 asset, uint256 amount) internal {
        asset.safeTransferFrom(msg.sender, address(this), amount);
        IERC4626 _pool = pool;
        ITermMaxMarket _market = market;
        IERC20 _debtToken = debtToken;
        // If asset is debt token, either mint or deposit to pool
        if (asset == _debtToken) {
            if (address(_pool) == address(0)) {
                // mint debt toke to ft and xt if pool is not set
                _debtToken.safeIncreaseAllowance(address(_market), amount);
                _market.mint(address(this), amount);
            } else {
                // burn ft and xt to debt token and deposit to pool
                uint256 ftBalance = ft.balanceOf(address(this));
                uint256 xtBalance = xt.balanceOf(address(this));
                uint256 maxBurned = ftBalance > xtBalance ? xtBalance : ftBalance;
                if (maxBurned != 0) {
                    _market.burn(address(this), maxBurned);
                }
                // if pool is set and asset is debt token, deposit to get shares
                amount += maxBurned;
                asset.safeIncreaseAllowance(address(_pool), amount);
                _pool.deposit(amount, address(this));
            }
        }
    }

    function _removeLiquidity(IERC20 asset, uint256 amount, address recipient) internal {
        IERC4626 _pool = pool;
        ITermMaxMarket _market = market;
        IERC20 _debtToken = debtToken;

        if (address(_pool) == address(0)) {
            // if pool is not set, always burn ft and xt to recipient
            _market.burn(recipient, amount);
        } else {
            uint256 ftBalance = ft.balanceOf(address(this));
            uint256 xtBalance = xt.balanceOf(address(this));
            uint256 maxBurned = ftBalance > xtBalance ? xtBalance : ftBalance;

            if (asset == _debtToken) {
                if (maxBurned >= amount) {
                    // if enough ft and xt, burn to debt token
                    _market.burn(recipient, amount);
                } else {
                    // if not enough ft and xt, burn all and withdraw from pool
                    _market.burn(recipient, maxBurned);
                    _pool.withdraw(amount - maxBurned, recipient, address(this));
                }
            } else {
                if (maxBurned != 0) {
                    // transform ft and xt to debt token and deposit to pool
                    _market.burn(address(this), maxBurned);
                    // approve pool to pull the resulting debt tokens
                    _debtToken.safeIncreaseAllowance(address(_pool), maxBurned);
                    // deposit to receive pool shares
                    _pool.deposit(maxBurned, address(this));
                }
                _pool.safeTransfer(recipient, amount);
            }
        }
    }

    /**
     * @notice Withdraw assets from the contract
     * @param token Token to withdraw
     * @param recipient Recipient address
     * @param amount Amount to withdraw
     */
    function withdrawAssets(IERC20 token, address recipient, uint256 amount) external virtual onlyOwner {
        token.safeTransfer(recipient, amount);
        emit WithdrawAssets(token, _msgSender(), recipient, amount);
    }

    /**
     * @notice Redeem all assets and close the order
     * @param recipient Recipient address for redeemed assets
     * @return badDebt Amount of bad debt if any
     */
    function redeemAll(address recipient)
        external
        virtual
        nonReentrant
        onlyOwner
        returns (uint256 badDebt, bytes memory deliveryData)
    {
        IERC4626 _pool = pool;
        uint256 ftBalance = ft.balanceOf(address(this));
        uint256 received;
        uint256 receivedFromPool;
        if (ftBalance != 0) (received, deliveryData) = market.redeem(ftBalance, recipient);
        // if pool is set, redeem all shares
        if (address(_pool) != address(0)) {
            uint256 shares = _pool.balanceOf(address(this));
            if (shares != 0) receivedFromPool = _pool.redeem(shares, recipient, address(this));
        }
        // Calculate bad debt
        badDebt = ftBalance - received;
        // Clear order configuration
        delete _orderConfig;
        emit OrderEventsV2.Redeemed(recipient, received + receivedFromPool, badDebt, deliveryData);
    }

    function withdrawAllAssetsBeforeMaturity(address recipient)
        external
        virtual
        nonReentrant
        onlyOwner
        returns (uint256 debtTokenAmount, uint256 ftAmount, uint256 xtAmount)
    {
        IERC4626 _pool = pool;
        IERC20 _ft = ft;
        IERC20 _xt = xt;
        if (_pool != IERC4626(address(0))) {
            uint256 shares = _pool.balanceOf(address(this));
            if (shares != 0) {
                debtTokenAmount += _pool.redeem(shares, recipient, address(this));
            }
        }
        ftAmount = _ft.balanceOf(address(this));
        xtAmount = _xt.balanceOf(address(this));
        uint256 maxBurned = ftAmount.min(xtAmount);
        if (maxBurned != 0) {
            market.burn(recipient, maxBurned);
            ftAmount -= maxBurned;
            xtAmount -= maxBurned;
            debtTokenAmount += maxBurned;
        }
        if (ftAmount != 0) {
            _ft.safeTransfer(recipient, ftAmount);
        }
        if (xtAmount != 0) {
            _xt.safeTransfer(recipient, xtAmount);
        }

        emit OrderEventsV2.RedeemedAllBeforeMaturity(recipient, debtTokenAmount, ftAmount, xtAmount);
    }

    function borrowToken(address recipient, uint256 amount) external virtual nonReentrant onlyOwner {
        uint256 ftAmount = ft.balanceOf(address(this));
        uint256 xtAmount = xt.balanceOf(address(this));
        require(amount <= xtAmount, NotEnoughFtOrXtToWithdraw());
        uint256 maxBurned = ftAmount.min(xtAmount);
        if (maxBurned < amount) {
            uint256 remainingAmount = amount - maxBurned;
            // issue ft from existing gt
            _issueFtToSelf(remainingAmount, _orderConfig.gtId);
        }
        market.burn(recipient, amount);
        emit OrderEventsV2.Borrowed(recipient, amount);
    }

    // =============================================================================
    // SWAP FUNCTIONS - PUBLIC INTERFACES
    // =============================================================================

    /**
     * @notice Swap exact amount of input token for output token
     * @inheritdoc ITermMaxOrder
     * @param tokenIn Input token address
     * @param tokenOut Output token address
     * @param recipient Recipient of output tokens
     * @param tokenAmtIn Exact input token amount
     * @param minTokenOut Minimum output token amount
     * @param deadline Transaction deadline
     * @return netTokenOut Actual output token amount
     */
    function swapExactTokenToToken(
        IERC20 tokenIn,
        IERC20 tokenOut,
        address recipient,
        uint128 tokenAmtIn,
        uint128 minTokenOut,
        uint256 deadline
    ) external virtual override nonReentrant onlyOpen returns (uint256 netTokenOut) {
        if (block.timestamp > deadline) revert DeadlineExpired();
        if (tokenIn == tokenOut) revert CantSwapSameToken();
        uint256 feeAmt;
        if (tokenAmtIn != 0) {
            IERC20 _debtToken = debtToken;
            IERC20 _ft = ft;
            IERC20 _xt = xt;
            OrderConfig memory orderConfig_ = _orderConfig;
            orderConfig_.feeConfig = market.config().feeConfig;
            int256 deltaFt;
            int256 deltaXt;
            if (tokenIn == _ft && tokenOut == _debtToken) {
                (netTokenOut, feeAmt, deltaFt, deltaXt) =
                    _swapAndUpdateReserves(tokenAmtIn, minTokenOut, orderConfig_, _sellFt);
            } else if (tokenIn == _xt && tokenOut == _debtToken) {
                (netTokenOut, feeAmt, deltaFt, deltaXt) =
                    _swapAndUpdateReserves(tokenAmtIn, minTokenOut, orderConfig_, _sellXt);
            } else if (tokenIn == _debtToken && tokenOut == _ft) {
                (netTokenOut, feeAmt, deltaFt, deltaXt) =
                    _swapAndUpdateReserves(tokenAmtIn, minTokenOut, orderConfig_, _buyFt);
            } else if (tokenIn == _debtToken && tokenOut == _xt) {
                (netTokenOut, feeAmt, deltaFt, deltaXt) =
                    _swapAndUpdateReserves(tokenAmtIn, minTokenOut, orderConfig_, _buyXt);
            } else {
                revert CantNotSwapToken(tokenIn, tokenOut);
            }

            uint256 ftBalance = ft.balanceOf(address(this));
            uint256 xtBalance = xt.balanceOf(address(this));
            if (orderConfig_.swapTrigger != ISwapCallback(address(0))) {
                orderConfig_.swapTrigger.afterSwap(ftBalance, xtBalance, deltaFt, deltaXt);
            }

            // transfer token in
            tokenIn.safeTransferFrom(msg.sender, address(this), tokenAmtIn);

            _rebalance(
                _ft, _xt, _debtToken, tokenAmtIn, tokenOut, netTokenOut, feeAmt, orderConfig_.gtId, deltaFt, deltaXt
            );

            // transfer output tokens
            tokenOut.safeTransfer(recipient, netTokenOut);
        }

        emit SwapExactTokenToToken(
            tokenIn, tokenOut, msg.sender, recipient, tokenAmtIn, netTokenOut.toUint128(), feeAmt.toUint128()
        );
    }

    /**
     * @notice Swap input token for exact amount of output token
     * @inheritdoc ITermMaxOrder
     * @param tokenIn Input token address
     * @param tokenOut Output token address
     * @param recipient Recipient of output tokens
     * @param tokenAmtOut Exact output token amount
     * @param maxTokenIn Maximum input token amount
     * @param deadline Transaction deadline
     * @return netTokenIn Actual input token amount used
     */
    function swapTokenToExactToken(
        IERC20 tokenIn,
        IERC20 tokenOut,
        address recipient,
        uint128 tokenAmtOut,
        uint128 maxTokenIn,
        uint256 deadline
    ) external virtual override nonReentrant onlyOpen returns (uint256 netTokenIn) {
        if (block.timestamp > deadline) revert DeadlineExpired();
        if (tokenIn == tokenOut) revert CantSwapSameToken();
        uint256 feeAmt;
        if (tokenAmtOut != 0 && maxTokenIn != 0) {
            IERC20 _debtToken = debtToken;
            IERC20 _ft = ft;
            IERC20 _xt = xt;
            int256 deltaFt;
            int256 deltaXt;
            OrderConfig memory orderConfig_ = _orderConfig;
            orderConfig_.feeConfig = market.config().feeConfig;
            if (tokenIn == _debtToken && tokenOut == _ft) {
                (netTokenIn, feeAmt, deltaFt, deltaXt) =
                    _swapAndUpdateReserves(tokenAmtOut, maxTokenIn, orderConfig_, _buyExactFt);
            } else if (tokenIn == _debtToken && tokenOut == _xt) {
                (netTokenIn, feeAmt, deltaFt, deltaXt) =
                    _swapAndUpdateReserves(tokenAmtOut, maxTokenIn, orderConfig_, _buyExactXt);
            } else if (tokenIn == _ft && tokenOut == _debtToken) {
                (netTokenIn, feeAmt, deltaFt, deltaXt) =
                    _swapAndUpdateReserves(tokenAmtOut, maxTokenIn, orderConfig_, _sellFtForExactToken);
            } else if (tokenIn == _xt && tokenOut == _debtToken) {
                (netTokenIn, feeAmt, deltaFt, deltaXt) =
                    _swapAndUpdateReserves(tokenAmtOut, maxTokenIn, orderConfig_, _sellXtForExactToken);
            } else {
                revert CantNotSwapToken(tokenIn, tokenOut);
            }
            // trigger call back function
            if (orderConfig_.swapTrigger != ISwapCallback(address(0))) {
                orderConfig_.swapTrigger.afterSwap(
                    ft.balanceOf(address(this)), xt.balanceOf(address(this)), deltaFt, deltaXt
                );
            }

            // transfer token in
            tokenIn.safeTransferFrom(msg.sender, address(this), netTokenIn);
            _rebalance(
                _ft, _xt, _debtToken, netTokenIn, tokenOut, tokenAmtOut, feeAmt, orderConfig_.gtId, deltaFt, deltaXt
            );

            // transfer output tokens
            tokenOut.safeTransfer(recipient, tokenAmtOut);
        }

        emit SwapTokenToExactToken(
            tokenIn, tokenOut, msg.sender, recipient, tokenAmtOut, netTokenIn.toUint128(), feeAmt.toUint128()
        );
    }

    function _rebalance(
        IERC20 _ft,
        IERC20 _xt,
        IERC20 _debtToken,
        uint256 inputAmt,
        IERC20 tokenOut,
        uint256 outputAmt,
        uint256 feeAmt,
        uint256 gtId,
        int256 deltaFt,
        int256 deltaXt
    ) internal {
        // Rebalance the reserves after swap
        int256 newFtReserve = _ft.balanceOf(address(this)).toInt256() + deltaFt;
        int256 newXtReserve = _xt.balanceOf(address(this)).toInt256() + deltaXt;
        // check if need to release liquidity from pool or gt
        uint256 releaseAmount;
        if (newFtReserve < 0) {
            if (gtId != 0) {
                _issueFtToSelf(uint256(-newFtReserve), gtId);
            } else {
                releaseAmount = uint256(-newFtReserve);
            }
        } else if (newXtReserve < 0) {
            releaseAmount = uint256(-newXtReserve);
        }
        if (releaseAmount != 0) {
            IERC4626 pool_ = pool;
            if (pool_ == IERC4626(address(0))) {
                revert TermMaxCurve.InsufficientLiquidity();
            }
            pool_.withdraw(releaseAmount, address(this), address(this));
        }
        ITermMaxMarket _market = market;
        uint256 tokenToMint;
        if (tokenOut == _debtToken) {
            if (outputAmt > releaseAmount) {
                uint256 tokenToBurn = outputAmt - releaseAmount;
                _market.burn(address(this), tokenToBurn);
            } else {
                // If output token is debt token, we need to mint it
                tokenToMint = releaseAmount - outputAmt;
            }
        } else {
            tokenToMint = releaseAmount + inputAmt;
        }
        if (tokenToMint != 0) {
            _debtToken.safeIncreaseAllowance(address(_market), tokenToMint);
            _market.mint(address(this), tokenToMint);
        }

        // Pay fee
        _ft.safeTransfer(_market.config().treasurer, feeAmt);
    }

    // =============================================================================
    // INTERNAL FUNCTIONS - SWAP LOGIC
    // =============================================================================

    /**
     * @notice Internal function to execute swap and update reserves
     * @param tokenAmtInOrOut Token amount input or output
     * @param limitTokenAmt Limit for slippage protection
     * @param func Function pointer for specific swap calculation
     * @return netAmt Net amount after swap
     * @return feeAmt Fee amount charged
     * @return deltaFt Change in FT reserve
     * @return deltaXt Change in XT reserve
     */
    function _swapAndUpdateReserves(
        uint256 tokenAmtInOrOut,
        uint256 limitTokenAmt,
        OrderConfig memory orderConfig_,
        function(
        uint256,
        uint256,
        OrderConfig memory) internal view returns (uint256, uint256, uint256, uint256, bool) func
    ) private returns (uint256, uint256, int256, int256) {
        (uint256 netAmt, uint256 feeAmt, uint256 deltaFt, uint256 deltaXt, bool isNegetiveXt) =
            func(tokenAmtInOrOut, limitTokenAmt, orderConfig_);

        uint256 newXtReserve = virtualXtReserve;
        if (isNegetiveXt) {
            virtualXtReserve = newXtReserve - deltaXt;
            return (netAmt, feeAmt, deltaFt.toInt256(), -deltaXt.toInt256());
        } else {
            newXtReserve += deltaXt;
            // check xt reserve when lending to order
            if (newXtReserve > orderConfig_.maxXtReserve) {
                revert XtReserveTooHigh();
            }
            virtualXtReserve = newXtReserve;
            return (netAmt, feeAmt, -deltaFt.toInt256(), deltaXt.toInt256());
        }
    }

    /**
     * @notice Calculate mint amount needed for token operations
     * @param tokenToMint Total tokens needed to mint
     * @param netTokenOut Net output tokens
     * @param ftBalance Current FT balance
     * @param xtBalance Current XT balance
     * @return Calculated mint amount
     */
    function _calculateMintAmount(uint256 tokenToMint, uint256 netTokenOut, uint256 ftBalance, uint256 xtBalance)
        private
        pure
        returns (uint256)
    {
        uint256 mintAmount = tokenToMint - ftBalance;
        uint256 xtShortfall = netTokenOut > xtBalance ? netTokenOut - xtBalance : 0;
        return mintAmount > xtShortfall ? mintAmount : xtShortfall;
    }

    /**
     * @notice Issue FT tokens to self when needed
     * @param amount Amount of FT to issue
     * @param gtId The gearing token ID
     */
    function _issueFtToSelf(uint256 amount, uint256 gtId) internal {
        if (gtId == 0) revert CantNotIssueFtWithoutGt();
        uint256 debtAmtToIssue = (amount * Constants.DECIMAL_BASE) / (Constants.DECIMAL_BASE - market.mintGtFeeRatio());
        market.issueFtByExistedGt(address(this), (debtAmtToIssue).toUint128(), gtId);
    }

    // =============================================================================
    // INTERNAL FUNCTIONS - TOKEN BUYING (LENDING)
    // =============================================================================

    /**
     * @notice Buy FT tokens with debt tokens (lending operation)
     * @param debtTokenAmtIn Debt token input amount
     * @param minTokenOut Minimum FT output
     * @param config Order configuration
     * @return netOut Net FT output
     * @return feeAmt Fee amount
     * @return deltaFt Change in FT reserve
     * @return deltaXt Change in XT reserve
     * @return isNegetiveXt Whether XT change is negative
     */
    function _buyFt(uint256 debtTokenAmtIn, uint256 minTokenOut, OrderConfig memory config)
        internal
        view
        onlyLendingIsAllowed(config)
        returns (uint256 netOut, uint256 feeAmt, uint256 deltaFt, uint256 deltaXt, bool isNegetiveXt)
    {
        (netOut, feeAmt, deltaFt, deltaXt, isNegetiveXt) = _buyToken(debtTokenAmtIn, minTokenOut, config, _buyFtStep);
    }

    /**
     * @notice Buy XT tokens with debt tokens (borrowing operation)
     * @param debtTokenAmtIn Debt token input amount
     * @param minTokenOut Minimum XT output
     * @param config Order configuration
     * @return netOut Net XT output
     * @return feeAmt Fee amount
     * @return deltaFt Change in FT reserve
     * @return deltaXt Change in XT reserve
     * @return isNegetiveXt Whether XT change is negative
     */
    function _buyXt(uint256 debtTokenAmtIn, uint256 minTokenOut, OrderConfig memory config)
        internal
        view
        onlyBorrowingIsAllowed(config)
        returns (uint256 netOut, uint256 feeAmt, uint256 deltaFt, uint256 deltaXt, bool isNegetiveXt)
    {
        (netOut, feeAmt, deltaFt, deltaXt, isNegetiveXt) = _buyToken(debtTokenAmtIn, minTokenOut, config, _buyXtStep);
    }

    /**
     * @notice Buy exact amount of FT tokens
     * @param tokenAmtOut Exact FT amount to buy
     * @param maxTokenIn Maximum debt token input
     * @param config Order configuration
     * @return netTokenIn Debt token input used
     * @return feeAmt Fee amount
     * @return deltaFt Change in FT reserve
     * @return deltaXt Change in XT reserve
     * @return isNegetiveXt Whether XT change is negative
     */
    function _buyExactFt(uint256 tokenAmtOut, uint256 maxTokenIn, OrderConfig memory config)
        internal
        view
        onlyLendingIsAllowed(config)
        returns (uint256 netTokenIn, uint256 feeAmt, uint256 deltaFt, uint256 deltaXt, bool isNegetiveXt)
    {
        (netTokenIn, feeAmt, deltaFt, deltaXt, isNegetiveXt) =
            _buyExactToken(tokenAmtOut, maxTokenIn, config, _buyExactFtStep);
    }

    /**
     * @notice Buy exact amount of XT tokens
     * @param tokenAmtOut Exact XT amount to buy
     * @param maxTokenIn Maximum debt token input
     * @param config Order configuration
     * @return netTokenIn Debt token input used
     * @return feeAmt Fee amount
     * @return deltaFt Change in FT reserve
     * @return deltaXt Change in XT reserve
     * @return isNegetiveXt Whether XT change is negative
     */
    function _buyExactXt(uint256 tokenAmtOut, uint256 maxTokenIn, OrderConfig memory config)
        internal
        view
        onlyBorrowingIsAllowed(config)
        returns (uint256 netTokenIn, uint256 feeAmt, uint256 deltaFt, uint256 deltaXt, bool isNegetiveXt)
    {
        (netTokenIn, feeAmt, deltaFt, deltaXt, isNegetiveXt) =
            _buyExactToken(tokenAmtOut, maxTokenIn, config, _buyExactXtStep);
    }

    // =============================================================================
    // INTERNAL FUNCTIONS - TOKEN SELLING
    // =============================================================================

    /**
     * @notice Sell FT tokens for debt tokens (borrowing operation)
     * @param ftAmtIn FT input amount
     * @param minDebtTokenOut Minimum debt token output
     * @param config Order configuration
     * @return netOut Net debt token output
     * @return feeAmt Fee amount
     * @return deltaFt Change in FT reserve
     * @return deltaXt Change in XT reserve
     * @return isNegetiveXt Whether XT change is negative
     */
    function _sellFt(uint256 ftAmtIn, uint256 minDebtTokenOut, OrderConfig memory config)
        internal
        view
        onlyBorrowingIsAllowed(config)
        returns (uint256 netOut, uint256 feeAmt, uint256 deltaFt, uint256 deltaXt, bool isNegetiveXt)
    {
        (netOut, feeAmt, deltaFt, deltaXt, isNegetiveXt) = _sellToken(ftAmtIn, minDebtTokenOut, config, _sellFtStep);
    }

    /**
     * @notice Sell XT tokens for debt tokens (lending operation)
     * @param xtAmtIn XT input amount
     * @param minDebtTokenOut Minimum debt token output
     * @param config Order configuration
     * @return netOut Net debt token output
     * @return feeAmt Fee amount
     * @return deltaFt Change in FT reserve
     * @return deltaXt Change in XT reserve
     * @return isNegetiveXt Whether XT change is negative
     */
    function _sellXt(uint256 xtAmtIn, uint256 minDebtTokenOut, OrderConfig memory config)
        internal
        view
        onlyLendingIsAllowed(config)
        returns (uint256 netOut, uint256 feeAmt, uint256 deltaFt, uint256 deltaXt, bool isNegetiveXt)
    {
        (netOut, feeAmt, deltaFt, deltaXt, isNegetiveXt) = _sellToken(xtAmtIn, minDebtTokenOut, config, _sellXtStep);
    }

    /**
     * @notice Sell FT for exact amount of debt tokens
     * @param debtTokenAmtOut Exact debt token output
     * @param maxFtIn Maximum FT input
     * @param config Order configuration
     * @return netIn FT input used
     * @return feeAmt Fee amount
     * @return deltaFt Change in FT reserve
     * @return deltaXt Change in XT reserve
     * @return isNegetiveXt Whether XT change is negative
     */
    function _sellFtForExactToken(uint256 debtTokenAmtOut, uint256 maxFtIn, OrderConfig memory config)
        internal
        view
        onlyBorrowingIsAllowed(config)
        returns (uint256 netIn, uint256 feeAmt, uint256 deltaFt, uint256 deltaXt, bool isNegetiveXt)
    {
        (netIn, feeAmt, deltaFt, deltaXt, isNegetiveXt) =
            _sellTokenForExactToken(debtTokenAmtOut, maxFtIn, config, _sellFtForExactTokenStep);
    }

    /**
     * @notice Sell XT for exact amount of debt tokens
     * @param debtTokenAmtOut Exact debt token output
     * @param maxXtIn Maximum XT input
     * @param config Order configuration
     * @return netIn XT input used
     * @return feeAmt Fee amount
     * @return deltaFt Change in FT reserve
     * @return deltaXt Change in XT reserve
     * @return isNegetiveXt Whether XT change is negative
     */
    function _sellXtForExactToken(uint256 debtTokenAmtOut, uint256 maxXtIn, OrderConfig memory config)
        internal
        view
        onlyLendingIsAllowed(config)
        returns (uint256 netIn, uint256 feeAmt, uint256 deltaFt, uint256 deltaXt, bool isNegetiveXt)
    {
        (netIn, feeAmt, deltaFt, deltaXt, isNegetiveXt) =
            _sellTokenForExactToken(debtTokenAmtOut, maxXtIn, config, _sellXtForExactTokenStep);
    }

    // =============================================================================
    // INTERNAL FUNCTIONS - SWAP CALCULATION HELPERS
    // =============================================================================

    /**
     * @notice Generic token buying logic
     * @param debtTokenAmtIn Debt token input
     * @param minTokenOut Minimum token output
     * @param config Order configuration
     * @param func Specific step function
     * @return netOut Net output
     * @return feeAmt Fee amount
     * @return deltaFt FT reserve change
     * @return deltaXt XT reserve change
     * @return isNegetiveXt Whether XT change is negative
     */
    function _buyToken(
        uint256 debtTokenAmtIn,
        uint256 minTokenOut,
        OrderConfig memory config,
        function(uint256, uint256, uint256, OrderConfig memory) internal pure returns (uint256, uint256, uint256, uint256, bool)
            func
    ) internal view returns (uint256 netOut, uint256 feeAmt, uint256 deltaFt, uint256 deltaXt, bool isNegetiveXt) {
        (netOut, feeAmt, deltaFt, deltaXt, isNegetiveXt) =
            func(_daysToMaturity(), virtualXtReserve, debtTokenAmtIn, config);

        netOut += debtTokenAmtIn;
        if (netOut < minTokenOut) revert UnexpectedAmount(minTokenOut, netOut);
        return (netOut, feeAmt, deltaFt, deltaXt, isNegetiveXt);
    }

    /**
     * @notice Generic exact token buying logic
     * @param tokenAmtOut Exact token output
     * @param maxTokenIn Maximum token input
     * @param config Order configuration
     * @param func Specific step function
     * @return netTokenIn Token input used
     * @return feeAmt Fee amount
     * @return deltaFt FT reserve change
     * @return deltaXt XT reserve change
     * @return isNegetiveXt Whether XT change is negative
     */
    function _buyExactToken(
        uint256 tokenAmtOut,
        uint256 maxTokenIn,
        OrderConfig memory config,
        function(uint256, uint256, uint256, OrderConfig memory) internal pure returns (uint256, uint256, uint256, uint256, bool)
            func
    ) internal view returns (uint256, uint256, uint256, uint256, bool) {
        (uint256 netTokenIn, uint256 feeAmt, uint256 deltaFt, uint256 deltaXt, bool isNegetiveXt) =
            func(_daysToMaturity(), virtualXtReserve, tokenAmtOut, config);

        if (netTokenIn > maxTokenIn) revert UnexpectedAmount(maxTokenIn, netTokenIn);

        return (netTokenIn, feeAmt, deltaFt, deltaXt, isNegetiveXt);
    }

    /**
     * @notice Generic token selling logic
     * @param tokenAmtIn Token input
     * @param minDebtTokenOut Minimum debt token output
     * @param config Order configuration
     * @param func Specific step function
     * @return netOut Net output
     * @return feeAmt Fee amount
     * @return deltaFt FT reserve change
     * @return deltaXt XT reserve change
     * @return isNegetiveXt Whether XT change is negative
     */
    function _sellToken(
        uint256 tokenAmtIn,
        uint256 minDebtTokenOut,
        OrderConfig memory config,
        function(uint256, uint256, uint256, OrderConfig memory) internal pure returns (uint256, uint256, uint256, uint256, bool)
            func
    ) internal view returns (uint256, uint256, uint256, uint256, bool) {
        (uint256 netOut, uint256 feeAmt, uint256 deltaFt, uint256 deltaXt, bool isNegetiveXt) =
            func(_daysToMaturity(), virtualXtReserve, tokenAmtIn, config);
        if (netOut < minDebtTokenOut) revert UnexpectedAmount(minDebtTokenOut, netOut);
        return (netOut, feeAmt, deltaFt, deltaXt, isNegetiveXt);
    }

    /**
     * @notice Generic exact token selling logic
     * @param debtTokenAmtOut Exact debt token output
     * @param maxTokenIn Maximum token input
     * @param config Order configuration
     * @param func Specific step function
     * @return netTokenIn Token input used
     * @return feeAmt Fee amount
     * @return deltaFt FT reserve change
     * @return deltaXt XT reserve change
     * @return isNegetiveXt Whether XT change is negative
     */
    function _sellTokenForExactToken(
        uint256 debtTokenAmtOut,
        uint256 maxTokenIn,
        OrderConfig memory config,
        function(uint256, uint256, uint256, OrderConfig memory) internal pure returns (uint256, uint256, uint256, uint256, bool)
            func
    ) internal view returns (uint256, uint256, uint256, uint256, bool) {
        (uint256 netTokenIn, uint256 feeAmt, uint256 deltaFt, uint256 deltaXt, bool isNegetiveXt) =
            func(_daysToMaturity(), virtualXtReserve, debtTokenAmtOut, config);

        if (netTokenIn > maxTokenIn) revert UnexpectedAmount(maxTokenIn, netTokenIn);

        return (netTokenIn, feeAmt, deltaFt, deltaXt, isNegetiveXt);
    }

    // =============================================================================
    // INTERNAL FUNCTIONS - CURVE CALCULATION STEPS
    // =============================================================================

    /**
     * @notice Calculate FT buying step using curve mathematics
     * @param daysToMaturity Days until maturity
     * @param oriXtReserve Original XT reserve
     * @param debtTokenAmtIn Debt token input
     * @param config Order configuration
     * @return tokenAmtOut FT tokens output
     * @return feeAmt Fee amount
     * @return deltaFt FT reserve change
     * @return deltaXt XT reserve change
     * @return isNegetiveXt Whether XT change is negative
     */
    function _buyFtStep(uint256 daysToMaturity, uint256 oriXtReserve, uint256 debtTokenAmtIn, OrderConfig memory config)
        internal
        pure
        returns (uint256 tokenAmtOut, uint256 feeAmt, uint256 deltaFt, uint256 deltaXt, bool isNegetiveXt)
    {
        FeeConfig memory feeConfig = config.feeConfig;
        CurveCut[] memory cuts = config.curveCuts.borrowCurveCuts;
        uint256 nif = Constants.DECIMAL_BASE - uint256(feeConfig.lendTakerFeeRatio);
        (deltaXt, tokenAmtOut) = TermMaxCurve.buyFt(nif, daysToMaturity, cuts, oriXtReserve, debtTokenAmtIn);
        feeAmt = (tokenAmtOut * (Constants.DECIMAL_BASE + uint256(feeConfig.borrowMakerFeeRatio))) / nif - tokenAmtOut;

        // ft reserve decrease, xt reserve increase
        deltaFt = tokenAmtOut + feeAmt;
        isNegetiveXt = false;
    }

    /**
     * @notice Calculate XT buying step using curve mathematics
     * @param daysToMaturity Days until maturity
     * @param oriXtReserve Original XT reserve
     * @param debtTokenAmtIn Debt token input
     * @param config Order configuration
     * @return tokenAmtOut XT tokens output
     * @return feeAmt Fee amount
     * @return deltaFt FT reserve change
     * @return deltaXt XT reserve change
     * @return isNegetiveXt Whether XT change is negative
     */
    function _buyXtStep(uint256 daysToMaturity, uint256 oriXtReserve, uint256 debtTokenAmtIn, OrderConfig memory config)
        internal
        pure
        returns (uint256 tokenAmtOut, uint256 feeAmt, uint256 deltaFt, uint256 deltaXt, bool isNegetiveXt)
    {
        FeeConfig memory feeConfig = config.feeConfig;
        CurveCut[] memory cuts = config.curveCuts.lendCurveCuts;
        uint256 nif = Constants.DECIMAL_BASE + uint256(feeConfig.borrowTakerFeeRatio);
        (tokenAmtOut, deltaFt) = TermMaxCurve.buyXt(nif, daysToMaturity, cuts, oriXtReserve, debtTokenAmtIn);
        feeAmt = deltaFt - (deltaFt * (Constants.DECIMAL_BASE - uint256(feeConfig.lendMakerFeeRatio))) / nif;

        // ft reserve increase, xt reserve decrease
        deltaFt -= feeAmt;
        deltaXt = tokenAmtOut;
        isNegetiveXt = true;
    }

    /**
     * @notice Calculate exact FT buying step
     * @param daysToMaturity Days until maturity
     * @param oriXtReserve Original XT reserve
     * @param ftAmtOut Exact FT output
     * @param config Order configuration
     * @return debtTokenAmtIn Debt token input needed
     * @return feeAmt Fee amount
     * @return deltaFt FT reserve change
     * @return deltaXt XT reserve change
     * @return isNegetiveXt Whether XT change is negative
     */
    function _buyExactFtStep(uint256 daysToMaturity, uint256 oriXtReserve, uint256 ftAmtOut, OrderConfig memory config)
        internal
        pure
        returns (uint256 debtTokenAmtIn, uint256 feeAmt, uint256 deltaFt, uint256 deltaXt, bool isNegetiveXt)
    {
        FeeConfig memory feeConfig = config.feeConfig;
        CurveCut[] memory cuts = config.curveCuts.borrowCurveCuts;
        uint256 nif = Constants.DECIMAL_BASE - uint256(feeConfig.lendTakerFeeRatio);
        (deltaXt, deltaFt) = TermMaxCurve.buyExactFt(nif, daysToMaturity, cuts, oriXtReserve, ftAmtOut);
        debtTokenAmtIn = deltaXt;
        feeAmt = (deltaFt * (Constants.DECIMAL_BASE + uint256(feeConfig.borrowMakerFeeRatio))) / nif - deltaFt;

        // ft reserve decrease, xt reserve increase
        deltaFt += feeAmt;
        isNegetiveXt = false;
    }

    /**
     * @notice Calculate exact XT buying step
     * @param daysToMaturity Days until maturity
     * @param oriXtReserve Original XT reserve
     * @param xtAmtOut Exact XT output
     * @param config Order configuration
     * @return debtTokenAmtIn Debt token input needed
     * @return feeAmt Fee amount
     * @return deltaFt FT reserve change
     * @return deltaXt XT reserve change
     * @return isNegetiveXt Whether XT change is negative
     */
    function _buyExactXtStep(uint256 daysToMaturity, uint256 oriXtReserve, uint256 xtAmtOut, OrderConfig memory config)
        internal
        pure
        returns (uint256 debtTokenAmtIn, uint256 feeAmt, uint256 deltaFt, uint256 deltaXt, bool isNegetiveXt)
    {
        FeeConfig memory feeConfig = config.feeConfig;
        CurveCut[] memory cuts = config.curveCuts.lendCurveCuts;
        uint256 nif = Constants.DECIMAL_BASE + uint256(feeConfig.borrowTakerFeeRatio);
        (deltaXt, deltaFt) = TermMaxCurve.buyExactXt(nif, daysToMaturity, cuts, oriXtReserve, xtAmtOut);
        debtTokenAmtIn = deltaFt;
        feeAmt = deltaFt - (deltaFt * (Constants.DECIMAL_BASE - uint256(feeConfig.lendMakerFeeRatio))) / nif;

        // ft reserve increase, xt reserve decrease
        deltaFt -= feeAmt;
        isNegetiveXt = true;
    }

    /**
     * @notice Calculate FT selling step
     * @param daysToMaturity Days until maturity
     * @param oriXtReserve Original XT reserve
     * @param tokenAmtIn FT input
     * @param config Order configuration
     * @return debtTokenAmtOut Debt token output
     * @return feeAmt Fee amount
     * @return deltaFt FT reserve change
     * @return deltaXt XT reserve change
     * @return isNegetiveXt Whether XT change is negative
     */
    function _sellFtStep(uint256 daysToMaturity, uint256 oriXtReserve, uint256 tokenAmtIn, OrderConfig memory config)
        internal
        pure
        returns (uint256 debtTokenAmtOut, uint256 feeAmt, uint256 deltaFt, uint256 deltaXt, bool isNegetiveXt)
    {
        FeeConfig memory feeConfig = config.feeConfig;
        CurveCut[] memory cuts = config.curveCuts.lendCurveCuts;
        uint256 nif = Constants.DECIMAL_BASE + uint256(feeConfig.borrowTakerFeeRatio);
        (debtTokenAmtOut, deltaFt) = TermMaxCurve.sellFt(nif, daysToMaturity, cuts, oriXtReserve, tokenAmtIn);
        feeAmt = deltaFt - (deltaFt * (Constants.DECIMAL_BASE - uint256(feeConfig.lendMakerFeeRatio))) / nif;

        // ft reserve increase, xt reserve decrease
        deltaFt -= feeAmt;
        deltaXt = debtTokenAmtOut;
        isNegetiveXt = true;
    }

    /**
     * @notice Calculate XT selling step
     * @param daysToMaturity Days until maturity
     * @param oriXtReserve Original XT reserve
     * @param tokenAmtIn XT input
     * @param config Order configuration
     * @return debtTokenAmtOut Debt token output
     * @return feeAmt Fee amount
     * @return deltaFt FT reserve change
     * @return deltaXt XT reserve change
     * @return isNegetiveXt Whether XT change is negative
     */
    function _sellXtStep(uint256 daysToMaturity, uint256 oriXtReserve, uint256 tokenAmtIn, OrderConfig memory config)
        internal
        pure
        returns (uint256 debtTokenAmtOut, uint256 feeAmt, uint256 deltaFt, uint256 deltaXt, bool isNegetiveXt)
    {
        FeeConfig memory feeConfig = config.feeConfig;
        CurveCut[] memory cuts = config.curveCuts.borrowCurveCuts;
        uint256 nif = Constants.DECIMAL_BASE - uint256(feeConfig.lendTakerFeeRatio);
        (deltaXt, debtTokenAmtOut) = TermMaxCurve.sellXt(nif, daysToMaturity, cuts, oriXtReserve, tokenAmtIn);
        feeAmt = (debtTokenAmtOut * (Constants.DECIMAL_BASE + uint256(feeConfig.borrowMakerFeeRatio))) / nif
            - debtTokenAmtOut;

        // ft reserve decrease, xt reserve increase
        deltaFt = debtTokenAmtOut + feeAmt;
        isNegetiveXt = false;
    }

    /**
     * @notice Calculate FT selling for exact debt token step
     * @param daysToMaturity Days until maturity
     * @param oriXtReserve Original XT reserve
     * @param debtTokenOut Exact debt token output
     * @param config Order configuration
     * @return ftAmtIn FT input needed
     * @return feeAmt Fee amount
     * @return deltaFt FT reserve change
     * @return deltaXt XT reserve change
     * @return isNegetiveXt Whether XT change is negative
     */
    function _sellFtForExactTokenStep(
        uint256 daysToMaturity,
        uint256 oriXtReserve,
        uint256 debtTokenOut,
        OrderConfig memory config
    ) internal pure returns (uint256 ftAmtIn, uint256 feeAmt, uint256 deltaFt, uint256 deltaXt, bool isNegetiveXt) {
        FeeConfig memory feeConfig = config.feeConfig;
        CurveCut[] memory cuts = config.curveCuts.lendCurveCuts;
        uint256 nif = Constants.DECIMAL_BASE + uint256(feeConfig.borrowTakerFeeRatio);

        (deltaXt, deltaFt) = TermMaxCurve.sellFtForExactDebtToken(nif, daysToMaturity, cuts, oriXtReserve, debtTokenOut);
        ftAmtIn = deltaFt + debtTokenOut;

        feeAmt = deltaFt - (deltaFt * (Constants.DECIMAL_BASE - uint256(feeConfig.lendMakerFeeRatio))) / nif;

        // ft reserve increase, xt reserve decrease
        deltaFt -= feeAmt;
        isNegetiveXt = true;
    }

    /**
     * @notice Calculate XT selling for exact debt token step
     * @param daysToMaturity Days until maturity
     * @param oriXtReserve Original XT reserve
     * @param debtTokenOut Exact debt token output
     * @param config Order configuration
     * @return xtAmtIn XT input needed
     * @return feeAmt Fee amount
     * @return deltaFt FT reserve change
     * @return deltaXt XT reserve change
     * @return isNegetiveXt Whether XT change is negative
     */
    function _sellXtForExactTokenStep(
        uint256 daysToMaturity,
        uint256 oriXtReserve,
        uint256 debtTokenOut,
        OrderConfig memory config
    ) internal pure returns (uint256 xtAmtIn, uint256 feeAmt, uint256 deltaFt, uint256 deltaXt, bool isNegetiveXt) {
        FeeConfig memory feeConfig = config.feeConfig;
        CurveCut[] memory cuts = config.curveCuts.borrowCurveCuts;
        uint256 nif = Constants.DECIMAL_BASE - uint256(feeConfig.lendTakerFeeRatio);
        (deltaXt, deltaFt) = TermMaxCurve.sellXtForExactDebtToken(nif, daysToMaturity, cuts, oriXtReserve, debtTokenOut);
        xtAmtIn = deltaXt + debtTokenOut;

        feeAmt = (deltaFt * (Constants.DECIMAL_BASE + uint256(feeConfig.borrowMakerFeeRatio))) / nif - deltaFt;

        // ft reserve decrease, xt reserve increase
        deltaFt += feeAmt;
        isNegetiveXt = false;
    }
}
"
    },
    "dependencies/@openzeppelin-contracts-upgradeable-5.2.0/access/Ownable2StepUpgradeable.sol": {
      "content": "// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.1.0) (access/Ownable2Step.sol)

pragma solidity ^0.8.20;

import {OwnableUpgradeable} from "./OwnableUpgradeable.sol";
import {Initializable} from "../proxy/utils/Initializable.sol";

/**
 * @dev Contract module which provides access control mechanism, where
 * there is an account (an owner) that can be granted exclusive access to
 * specific functions.
 *
 * This extension of the {Ownable} contract includes a two-step mechanism to transfer
 * ownership, where the new owner must call {acceptOwnership} in order to replace the
 * old one. This can help prevent common mistakes, such as transfers of ownership to
 * incorrect accounts, or to contracts that are unable to interact with the
 * permission system.
 *
 * The initial owner is specified at deployment time in the constructor for `Ownable`. This
 * can later be changed with {transferOwnership} and {acceptOwnership}.
 *
 * This module is used through inheritance. It will make available all functions
 * from parent (Ownable).
 */
abstract contract Ownable2StepUpgradeable is Initializable, OwnableUpgradeable {
    /// @custom:storage-location erc7201:openzeppelin.storage.Ownable2Step
    struct Ownable2StepStorage {
        address _pendingOwner;
    }

    // keccak256(abi.encode(uint256(keccak256("openzeppelin.storage.Ownable2Step")) - 1)) & ~bytes32(uint256(0xff))
    bytes32 private constant Ownable2StepStorageLocation = 0x237e158222e3e6968b72b9db0d8043aacf074ad9f650f0d1606b4d82ee432c00;

    function _getOwnable2StepStorage() private pure returns (Ownable2StepStorage storage $) {
        assembly {
            $.slot := Ownable2StepStorageLocation
        }
    }

    event OwnershipTransferStarted(address indexed previousOwner, address indexed newOwner);

    function __Ownable2Step_init() internal onlyInitializing {
    }

    function __Ownable2Step_init_unchained() internal onlyInitializing {
    }
    /**
     * @dev Returns the address of the pending owner.
     */
    function pendingOwner() public view virtual returns (address) {
        Ownable2StepStorage storage $ = _getOwnable2StepStorage();
        return $._pendingOwner;
    }

    /**
     * @dev Starts the ownership transfer of the contract to a new account. Replaces the pending transfer if there is one.
     * Can only be called by the current owner.
     *
     * Setting `newOwner` to the zero address is allowed; this can be used to cancel an initiated ownership transfer.
     */
    function transferOwnership(address newOwner) public virtual override onlyOwner {
        Ownable2StepStorage storage $ = _getOwnable2StepStorage();
        $._pendingOwner = newOwner;
        emit OwnershipTransferStarted(owner(), newOwner);
    }

    /**
     * @dev Transfers ownership of the contract to a new account (`newOwner`) and deletes any pending owner.
     * Internal function without access restriction.
     */
    function _transferOwnership(address newOwner) internal virtual override {
        Ownable2StepStorage storage $ = _getOwnable2StepStorage();
        delete $._pendingOwner;
        super._transferOwnership(newOwner);
    }

    /**
     * @dev The new owner accepts the ownership transfer.
     */
    function acceptOwnership() public virtual {
        address sender = _msgSender();
        if (pendingOwner() != sender) {
            revert OwnableUnauthorizedAccount(sender);
        }
        _transferOwnership(sender);
    }
}
"
    },
    "dependencies/@openzeppelin-contracts-upgradeable-5.2.0/utils/ReentrancyGuardUpgradeable.sol": {
      "content": "// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.1.0) (utils/ReentrancyGuard.sol)

pragma solidity ^0.8.20;
import {Initializable} from "../proxy/utils/Initializable.sol";

/**
 * @dev Contract module that helps prevent reentrant calls to a function.
 *
 * Inheriting from `ReentrancyGuard` will make the {nonReentrant} modifier
 * available, which can be applied to functions to make sure there are no nested
 * (reentrant) calls to them.
 *
 * Note that because there is a single `nonReentrant` guard, functions marked as
 * `nonReentrant` may not call one another. This can be worked around by making
 * those functions `private`, and then adding `external` `nonReentrant` entry
 * points to them.
 *
 * TIP: If EIP-1153 (transient storage) is available on the chain you're deploying at,
 * consider using {ReentrancyGuardTransient} instead.
 *
 * TIP: If you would like to learn more about reentrancy and alternative ways
 * to protect against it, check out our blog post
 * https://blog.openzeppelin.com/reentrancy-after-istanbul/[Reentrancy After Istanbul].
 */
abstract contract ReentrancyGuardUpgradeable is Initializable {
    // Booleans are more expensive than uint256 or any type that takes up a full
    // word because each write operation emits an extra SLOAD to first read the
    // slot's contents, replace the bits taken up by the boolean, and then write
    // back. This is the compiler's defense against contract upgrades and
    // pointer aliasing, and it cannot be disabled.

    // The values being non-zero value makes deployment a bit more expensive,
    // but in exchange the refund on every call to nonReentrant will be lower in
    // amount. Since refunds are capped to a percentage of the total
    // transaction's gas, it is best to keep them low in cases like this one, to
    // increase the likelihood of the full refund coming into effect.
    uint256 private constant NOT_ENTERED = 1;
    uint256 private constant ENTERED = 2;

    /// @custom:storage-location erc7201:openzeppelin.storage.ReentrancyGuard
    struct ReentrancyGuardStorage {
        uint256 _status;
    }

    // keccak256(abi.encode(uint256(keccak256("openzeppelin.storage.ReentrancyGuard")) - 1)) & ~bytes32(uint256(0xff))
    bytes32 private constant ReentrancyGuardStorageLocation = 0x9b779b17422d0df92223018b32b4d1fa46e071723d6817e2486d003becc55f00;

    function _getReentrancyGuardStorage() private pure returns (ReentrancyGuardStorage storage $) {
        assembly {
            $.slot := ReentrancyGuardStorageLocation
        }
    }

    /**
     * @dev Unauthorized reentrant call.
     */
    error ReentrancyGuardReentrantCall();

    function __ReentrancyGuard_init() internal onlyInitializing {
        __ReentrancyGuard_init_unchained();
    }

    function __ReentrancyGuard_init_unchained() internal onlyInitializing {
        ReentrancyGuardStorage storage $ = _getReentrancyGuardStorage();
        $._status = NOT_ENTERED;
    }

    /**
     * @dev Prevents a contract from calling itself, directly or indirectly.
     * Calling a `nonReentrant` function from another `nonReentrant`
     * function is not supported. It is possible to prevent this from happening
     * by making the `nonReentrant` function external, and making it call a
     * `private` function that does the actual work.
     */
    modifier nonReentrant() {
     

Tags:
ERC20, ERC721, ERC165, Multisig, Mintable, Burnable, Pausable, Non-Fungible, Swap, Liquidity, Staking, Yield, Upgradeable, Multi-Signature, Factory, Oracle|addr:0x9aa18ab8d24c5cf3faf7e5706017602989feb4ff|verified:true|block:23479695|tx:0xf6129df7ee15d22b3633cc1c80c33f63723314997c0bb328cbd2a84d9e08bb72|first_check:1759318521

Submitted on: 2025-10-01 13:35:22

Comments

Log in to comment.

No comments yet.