MorphoLendingRouter

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

import { InsufficientAssetsForRepayment } from "../interfaces/Errors.sol";
import { ERC20 } from "@openzeppelin/contracts/token/ERC20/ERC20.sol";
import { TokenUtils } from "../utils/TokenUtils.sol";
import { SafeERC20 } from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
import { IYieldStrategy } from "../interfaces/IYieldStrategy.sol";
import {
    IMorphoLiquidateCallback,
    IMorphoFlashLoanCallback,
    IMorphoRepayCallback
} from "../interfaces/Morpho/IMorphoCallbacks.sol";
import { ADDRESS_REGISTRY } from "../utils/Constants.sol";
import { AbstractLendingRouter } from "./AbstractLendingRouter.sol";
import {
    MORPHO, MarketParams, Id, Position, Market, Withdrawal, PUBLIC_ALLOCATOR
} from "../interfaces/Morpho/IMorpho.sol";

struct MorphoParams {
    address irm;
    uint256 lltv;
}

struct MorphoAllocation {
    address vault;
    uint256 feeAmount;
    Withdrawal[] withdrawals;
}

contract MorphoLendingRouter is
    AbstractLendingRouter,
    IMorphoLiquidateCallback,
    IMorphoFlashLoanCallback,
    IMorphoRepayCallback
{
    using SafeERC20 for ERC20;
    using TokenUtils for ERC20;

    mapping(address vault => MorphoParams params) private s_morphoParams;
    uint256 private transient t_vaultSharesReceived;
    uint256 private transient t_borrowShares;
    uint256 private transient t_profitsWithdrawn;

    // Used for the health factor calculation to replicate Morpho's behavior
    uint256 private constant VIRTUAL_ASSETS = 1;
    uint256 private constant VIRTUAL_SHARES = 1e6;

    function name() external pure override returns (string memory) {
        return "Morpho";
    }

    function initializeMarket(address vault, address irm, uint256 lltv) external {
        require(ADDRESS_REGISTRY.upgradeAdmin() == msg.sender);
        // Cannot override parameters once they are set
        require(s_morphoParams[vault].irm == address(0));
        require(s_morphoParams[vault].lltv == 0);

        s_morphoParams[vault] = MorphoParams({ irm: irm, lltv: lltv });

        // If the market already exists this call will revert. This is okay because there should
        // be no reason that the market would already exist unless something has gone wrong. In that
        // case we would want to assess why the market was created and perhaps change the market
        // parameters in order to fix the issue.
        MORPHO.createMarket(marketParams(vault));
    }

    function marketParams(address vault) public view returns (MarketParams memory) {
        return marketParams(vault, IYieldStrategy(vault).asset());
    }

    function marketParams(address vault, address asset) internal view returns (MarketParams memory) {
        MorphoParams memory params = s_morphoParams[vault];

        return MarketParams({
            loanToken: asset,
            collateralToken: vault,
            oracle: vault,
            irm: params.irm,
            lltv: params.lltv
        });
    }

    function morphoId(MarketParams memory m) internal pure returns (Id) {
        return Id.wrap(keccak256(abi.encode(m)));
    }

    /// @dev Allows integration with the public allocator so that accounts can
    /// ensure there is sufficient liquidity in the lending market before entering
    function _allocate(address vault, MorphoAllocation[] calldata allocationData) internal {
        MarketParams memory m = marketParams(vault);

        uint256 totalFeeAmount;
        for (uint256 i = 0; i < allocationData.length; i++) {
            PUBLIC_ALLOCATOR.reallocateTo{ value: allocationData[i].feeAmount }(
                allocationData[i].vault, allocationData[i].withdrawals, m
            );
            totalFeeAmount += allocationData[i].feeAmount;
        }
        require(msg.value == totalFeeAmount, "Insufficient fee amount");
    }

    function allocateAndEnterPosition(
        address onBehalf,
        address vault,
        uint256 depositAssetAmount,
        uint256 borrowAmount,
        bytes calldata depositData,
        MorphoAllocation[] calldata allocationData
    )
        external
        payable
        isAuthorized(onBehalf, vault)
        nonReentrant
    {
        _allocate(vault, allocationData);
        _enterPosition(onBehalf, vault, depositAssetAmount, borrowAmount, depositData, address(0));
    }

    function allocateAndMigratePosition(
        address onBehalf,
        address vault,
        address migrateFrom,
        MorphoAllocation[] calldata allocationData
    )
        external
        payable
        isAuthorized(onBehalf, vault)
        nonReentrant
    {
        _allocate(vault, allocationData);
        _migratePosition(onBehalf, vault, migrateFrom);
    }

    function _flashBorrowAndEnter(
        address onBehalf,
        address vault,
        address asset,
        uint256 depositAssetAmount,
        uint256 borrowAmount,
        bytes memory depositData,
        address migrateFrom
    )
        internal
        override
        returns (uint256 vaultSharesReceived, uint256 borrowShares)
    {
        // At this point we will flash borrow funds from the lending market and then
        // receive control in a different function on a callback.
        bytes memory flashLoanData = abi.encode(onBehalf, vault, asset, depositAssetAmount, depositData, migrateFrom);
        MORPHO.flashLoan(asset, borrowAmount, flashLoanData);

        // These are only used to get these values back from the flash loan callback
        // so that we can emit the event with the correct values
        vaultSharesReceived = t_vaultSharesReceived;
        borrowShares = t_borrowShares;
    }

    function onMorphoFlashLoan(uint256 assets, bytes calldata data) external override {
        require(msg.sender == address(MORPHO));

        (
            address onBehalf,
            address vault,
            address asset,
            uint256 depositAssetAmount,
            bytes memory depositData,
            address migrateFrom
        ) = abi.decode(data, (address, address, address, uint256, bytes, address));

        t_vaultSharesReceived =
            _enterOrMigrate(onBehalf, vault, asset, assets + depositAssetAmount, depositData, migrateFrom);

        MarketParams memory m = marketParams(vault, asset);
        // Borrow the assets in order to repay the flash loan
        ( /* */ , t_borrowShares) = MORPHO.borrow(m, assets, 0, onBehalf, address(this));

        // Allow for flash loan to be repaid
        ERC20(asset).checkApprove(address(MORPHO), assets);
    }

    function _supplyCollateral(
        address onBehalf,
        address vault,
        address asset,
        uint256 sharesReceived
    )
        internal
        override
    {
        MarketParams memory m = marketParams(vault, asset);

        // Allows the transfer from the lending market to the Morpho contract
        IYieldStrategy(vault).allowTransfer(address(MORPHO), sharesReceived, onBehalf);

        // We should receive shares in return
        ERC20(vault).approve(address(MORPHO), sharesReceived);
        MORPHO.supplyCollateral(m, sharesReceived, onBehalf, "");
    }

    function _withdrawCollateral(
        address vault,
        address asset,
        uint256 sharesToRedeem,
        address sharesOwner,
        address receiver
    )
        internal
        override
    {
        MarketParams memory m = marketParams(vault, asset);
        MORPHO.withdrawCollateral(m, sharesToRedeem, sharesOwner, receiver);
    }

    function _exitWithRepay(
        address onBehalf,
        address vault,
        address asset,
        address receiver,
        uint256 sharesToRedeem,
        uint256 assetToRepay,
        bytes memory redeemData
    )
        internal
        override
        returns (uint256 borrowSharesRepaid, uint256 profitsWithdrawn)
    {
        uint256 sharesToRepay;
        if (assetToRepay == type(uint256).max) {
            // If assetToRepay is uint256.max then get the morpho borrow shares amount to
            // get a full exit.
            sharesToRepay = balanceOfBorrowShares(onBehalf, vault);
            assetToRepay = 0;
        }

        if (assetToRepay == 0 && sharesToRepay == 0) {
            // Allows migration in the edge case where the user has no debt but
            // still wants to migrate their position.
            require(_isMigrate(receiver));
            profitsWithdrawn = _redeemShares(onBehalf, vault, asset, receiver, sharesToRedeem, redeemData);
        } else {
            bytes memory repayData =
                abi.encode(onBehalf, vault, asset, receiver, sharesToRedeem, redeemData, _isMigrate(receiver));

            // Will trigger a callback to onMorphoRepay
            borrowSharesRepaid = _repay(vault, asset, assetToRepay, sharesToRepay, onBehalf, repayData);
            profitsWithdrawn = t_profitsWithdrawn;
        }
    }

    function _repay(
        address vault,
        address asset,
        uint256 assetToRepay,
        uint256 sharesToRepay,
        address onBehalf,
        bytes memory repayData
    )
        internal
        returns (uint256 borrowSharesRepaid)
    {
        MarketParams memory m = marketParams(vault, asset);
        ( /* */ , borrowSharesRepaid) = MORPHO.repay(m, assetToRepay, sharesToRepay, onBehalf, repayData);
    }

    function onMorphoRepay(uint256 assetToRepay, bytes calldata data) external override {
        require(msg.sender == address(MORPHO));

        (
            address sharesOwner,
            address vault,
            address asset,
            address receiver,
            uint256 sharesToRedeem,
            bytes memory redeemData,
            bool isMigrate
        ) = abi.decode(data, (address, address, address, address, uint256, bytes, bool));

        uint256 assetsWithdrawn =
            _redeemShares(sharesOwner, vault, asset, isMigrate ? receiver : address(0), sharesToRedeem, redeemData);

        if (isMigrate) {
            // When migrating we do not withdraw any assets and we must repay the entire debt
            // from the previous lending router.
            if (0 < assetToRepay) ERC20(asset).safeTransferFrom(receiver, address(this), assetToRepay);
            assetsWithdrawn = assetToRepay;
        }

        // Transfer any profits to the receiver
        if (assetsWithdrawn < assetToRepay) {
            // We have to revert in this case because we've already redeemed the yield tokens
            revert InsufficientAssetsForRepayment(assetToRepay, assetsWithdrawn);
        }

        uint256 profitsWithdrawn;
        unchecked {
            profitsWithdrawn = assetsWithdrawn - assetToRepay;
        }
        if (0 < profitsWithdrawn) ERC20(asset).safeTransfer(receiver, profitsWithdrawn);

        // Allow morpho to repay the debt
        ERC20(asset).checkApprove(address(MORPHO), assetToRepay);

        // Set the transient variable to be used for later event emission
        t_profitsWithdrawn = profitsWithdrawn;
    }

    function _liquidate(
        address liquidator,
        address vault,
        address liquidateAccount,
        uint256 sharesToLiquidate,
        uint256 borrowSharesToRepay
    )
        internal
        override
        returns (uint256 sharesToLiquidator, uint256 borrowSharesRepaid)
    {
        MarketParams memory m = marketParams(vault);
        uint256 borrowSharesBefore = balanceOfBorrowShares(liquidateAccount, vault);

        // If the account's borrow shares are less than when the liquidator is trying to repay,
        // set it to the account's borrow shares to prevent an underflow inside Morpho.
        if (borrowSharesBefore < borrowSharesToRepay) borrowSharesToRepay = borrowSharesBefore;

        // This does not return borrow shares repaid so we have to calculate it manually
        (sharesToLiquidator, /* */ ) = MORPHO.liquidate(
            m, liquidateAccount, sharesToLiquidate, borrowSharesToRepay, abi.encode(m.loanToken, liquidator)
        );
        borrowSharesRepaid = borrowSharesBefore - balanceOfBorrowShares(liquidateAccount, vault);
    }

    function onMorphoLiquidate(uint256 repaidAssets, bytes calldata data) external override {
        require(msg.sender == address(MORPHO));
        (address asset, address liquidator) = abi.decode(data, (address, address));

        ERC20(asset).safeTransferFrom(liquidator, address(this), repaidAssets);
        ERC20(asset).checkApprove(address(MORPHO), repaidAssets);
    }

    function balanceOfCollateral(
        address account,
        address vault
    )
        public
        view
        override
        returns (uint256 collateralBalance)
    {
        MarketParams memory m = marketParams(vault);
        collateralBalance = MORPHO.position(morphoId(m), account).collateral;
    }

    function balanceOfBorrowShares(
        address account,
        address vault
    )
        public
        view
        override
        returns (uint256 borrowShares)
    {
        MarketParams memory m = marketParams(vault);
        borrowShares = MORPHO.position(morphoId(m), account).borrowShares;
    }

    function convertBorrowSharesToAssets(
        address vault,
        uint256 borrowShares
    )
        external
        override
        returns (uint256 assets)
    {
        MarketParams memory m = marketParams(vault);
        MORPHO.accrueInterest(m);
        Market memory market = MORPHO.market(morphoId(m));
        return (borrowShares * uint256(market.totalBorrowAssets)) / uint256(market.totalBorrowShares);
    }

    function _mulDivUp(uint256 x, uint256 y, uint256 d) internal pure returns (uint256) {
        return (x * y + (d - 1)) / d;
    }

    function _toAssetsUp(
        uint256 shares,
        uint256 totalShares,
        uint256 totalAssets
    )
        internal
        pure
        returns (uint256 assets)
    {
        return _mulDivUp(shares, totalAssets + VIRTUAL_ASSETS, totalShares + VIRTUAL_SHARES);
    }

    function healthFactor(
        address borrower,
        address vault
    )
        public
        override
        returns (uint256 borrowed, uint256 collateralValue, uint256 maxBorrow)
    {
        MarketParams memory m = marketParams(vault);
        Id id = morphoId(m);
        // Ensure interest is accrued before calculating health factor
        MORPHO.accrueInterest(m);
        Position memory position = MORPHO.position(id, borrower);
        Market memory market = MORPHO.market(id);

        if (position.borrowShares > 0) {
            borrowed = _toAssetsUp(position.borrowShares, market.totalBorrowShares, market.totalBorrowAssets);
        } else {
            borrowed = 0;
        }
        collateralValue = (uint256(position.collateral) * IYieldStrategy(vault).price(borrower)) / 1e36;
        maxBorrow = collateralValue * m.lltv / 1e18;
    }
}
"
    },
    "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/ERC20.sol": {
      "content": "// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.2.0) (token/ERC20/ERC20.sol)

pragma solidity ^0.8.20;

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

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

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

    uint256 private _totalSupply;

    string private _name;
    string private _symbol;

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

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

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

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

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

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

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

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

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

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

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

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

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

        emit Transfer(from, to, value);
    }

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

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

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

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

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

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

library TokenUtils {
    using SafeERC20 for ERC20;

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

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

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

        token.forceApprove(spender, amount);
    }

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

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

import { IERC20 } from "@openzeppelin/contracts/interfaces/IERC20.sol";
import { IERC20Metadata } from "@openzeppelin/contracts/interfaces/IERC20Metadata.sol";
import { IOracle } from "./Morpho/IOracle.sol";

/**
 * @notice A strategy vault that is specifically designed for leveraged yield
 * strategies. Minting and burning shares are restricted to the `enterPosition`
 * and `exitPosition` functions respectively. This means that shares will be
 * exclusively held on lending markets as collateral unless the LendingMarket is
 * set to NONE. In this case, the user will just be holding the yield token without
 * any leverage.
 *
 * The `transfer` function is non-standard in that transfers off of a lending market
 * are restricted to ensure that liquidation conditions are met.
 *
 * This contract also serves as its own oracle.
 */
interface IYieldStrategy is IERC20, IERC20Metadata, IOracle {
    event VaultCreated(address indexed vault);

    // These can be emitted by the reward manager
    event VaultRewardTransfer(address indexed token, address indexed account, uint256 amount);
    event VaultRewardUpdate(address indexed rewardToken, uint128 emissionRatePerYear, uint32 endTime);

    // This is emitted by the trading module
    event TradeExecuted(address indexed sellToken, address indexed buyToken, uint256 sellAmount, uint256 buyAmount);

    event FeesCollected(uint256 feesCollected);

    /**
     * @dev Returns the address of the underlying token used for the Vault for accounting, depositing, and withdrawing.
     *
     * - MUST be an ERC-20 token contract.
     * - MUST NOT revert.
     */
    function asset() external view returns (address assetTokenAddress);

    /**
     * @dev Returns the address of the accounting asset used for the
     * to mark the price of the yield token excluding any market profit and loss.
     * This is only used for off chain accounting.
     */
    function accountingAsset() external view returns (address accountingAssetAddress);

    /**
     * @dev Returns the name of the strategy.
     */
    function strategy() external view returns (string memory strategyName);

    /**
     * @dev Returns the address of the yield token held by the vault. Does not equal the share token,
     * which represents each user's share of the yield tokens held by the vault.
     *
     * - MUST be an ERC-20 token contract.
     * - MUST NOT revert.
     */
    function yieldToken() external view returns (address yieldTokenAddress);

    /**
     * @dev Returns the total amount of the underlying asset that is “managed” by Vault.
     *
     * - SHOULD include any compounding that occurs from yield.
     * - MUST be inclusive of any fees that are charged against assets in the Vault.
     * - MUST NOT revert.
     */
    function totalAssets() external view returns (uint256 totalManagedAssets);

    /**
     * @dev Returns the effective supply which excludes any escrowed shares.
     */
    function effectiveSupply() external view returns (uint256);

    /**
     * @dev Returns the amount of shares that the Vault would exchange for the amount of assets provided, in an ideal
     * scenario where all the conditions are met.
     *
     * - MUST NOT be inclusive of any fees that are charged against assets in the Vault.
     * - MUST NOT show any variations depending on the caller.
     * - MUST NOT reflect slippage or other on-chain conditions, when performing the actual exchange.
     * - MUST NOT revert.
     *
     * NOTE: This calculation MAY NOT reflect the “per-user” price-per-share, and instead should reflect the
     * “average-user’s” price-per-share, meaning what the average user should expect to see when exchanging to and
     * from.
     */
    function convertToShares(uint256 assets) external view returns (uint256 shares);

    /**
     * @dev Returns the amount of assets that the Vault would exchange for the amount of shares provided, in an ideal
     * scenario where all the conditions are met.
     *
     * - MUST NOT be inclusive of any fees that are charged against assets in the Vault.
     * - MUST NOT show any variations depending on the caller.
     * - MUST NOT reflect slippage or other on-chain conditions, when performing the actual exchange.
     */
    function convertToAssets(uint256 shares) external view returns (uint256 assets);

    /**
     * @dev Returns the amount of yield tokens that the Vault would exchange for the amount of shares provided, in an
     * ideal
     * scenario where all the conditions are met.
     */
    function convertSharesToYieldToken(uint256 shares) external view returns (uint256 yieldTokens);

    /**
     * @dev Returns the amount of yield tokens that the account would receive for the amount of shares provided.
     */
    function convertYieldTokenToShares(uint256 shares) external view returns (uint256 yieldTokens);

    /**
     * @dev Returns the oracle price of a yield token in terms of the asset token.
     */
    function convertYieldTokenToAsset() external view returns (uint256 price);

    /**
     * @dev Returns the fee rate of the vault where 100% = 1e18.
     */
    function feeRate() external view returns (uint256 feeRate);

    /**
     * @dev Returns the balance of yield tokens accrued by the vault.
     */
    function feesAccrued() external view returns (uint256 feesAccruedInYieldToken);

    /**
     * @dev Collects the fees accrued by the vault. Only callable by the owner.
     */
    function collectFees() external returns (uint256 feesCollected);

    /**
     * @dev Returns the price of a yield token in terms of the asset token for the
     * given borrower taking into account withdrawals.
     */
    function price(address borrower) external returns (uint256 price);

    /**
     * @notice Mints shares for a given number of assets.
     *
     * @param assets The amount of assets to mint shares for.
     * @param receiver The address to mint the shares to.
     * @param depositData calldata used to deposit the assets.
     */
    function mintShares(
        uint256 assets,
        address receiver,
        bytes memory depositData
    )
        external
        returns (uint256 sharesMinted);

    /**
     * @notice Burns shares for a given number of shares.
     *
     * @param sharesOwner The address of the account to burn the shares for.
     * @param sharesToBurn The amount of shares to burn.
     * @param redeemData calldata used to redeem the yield token.
     */
    function burnShares(
        address sharesOwner,
        uint256 sharesToBurn,
        uint256 sharesHeld,
        bytes memory redeemData
    )
        external
        returns (uint256 assetsWithdrawn);

    /**
     * @notice Allows the lending market to transfer shares on exit position
     * or liquidation.
     *
     * @param to The address to allow the transfer to.
     * @param amount The amount of shares to allow the transfer of.
     * @param currentAccount The address of the current account.
     */
    function allowTransfer(address to, uint256 amount, address currentAccount) external;

    /**
     * @notice Pre-liquidation function.
     *
     * @param liquidator The address of the liquidator.
     * @param liquidateAccount The address of the account to liquidate.
     * @param sharesToLiquidate The amount of shares to liquidate.
     * @param accountSharesHeld The amount of shares the account holds.
     */
    function preLiquidation(
        address liquidator,
        address liquidateAccount,
        uint256 sharesToLiquidate,
        uint256 accountSharesHeld
    )
        external;

    /**
     * @notice Post-liquidation function.
     *
     * @param liquidator The address of the liquidator.
     * @param liquidateAccount The address of the account to liquidate.
     * @param sharesToLiquidator The amount of shares to liquidate.
     */
    function postLiquidation(address liquidator, address liquidateAccount, uint256 sharesToLiquidator) external;

    /**
     * @notice Redeems shares for assets for a native token.
     *
     * @param sharesToRedeem The amount of shares to redeem.
     * @param redeemData calldata used to redeem the yield token.
     */
    function redeemNative(uint256 sharesToRedeem, bytes memory redeemData) external returns (uint256 assetsWithdrawn);

    /**
     * @notice Initiates a withdraw for a given number of shares.
     *
     * @param account The address of the account to initiate the withdraw for.
     * @param sharesHeld The number of shares the account holds.
     * @param data calldata used to initiate the withdraw.
     */
    function initiateWithdraw(
        address account,
        uint256 sharesHeld,
        bytes calldata data,
        address forceWithdrawFrom
    )
        external
        returns (uint256 requestId);

    /**
     * @notice Initiates a withdraw for the native balance of the account.
     *
     * @param data calldata used to initiate the withdraw.
     */
    function initiateWithdrawNative(bytes calldata data) external returns (uint256 requestId);

    /**
     * @notice Clears the current account.
     */
    function clearCurrentAccount() external;
}
"
    },
    "src/interfaces/Morpho/IMorphoCallbacks.sol": {
      "content": "// SPDX-License-Identifier: GPL-2.0-or-later
pragma solidity >=0.8.28;

/// @title IMorphoLiquidateCallback
/// @notice Interface that liquidators willing to use `liquidate`'s callback must implement.
interface IMorphoLiquidateCallback {
    /// @notice Callback called when a liquidation occurs.
    /// @dev The callback is called only if data is not empty.
    /// @param repaidAssets The amount of repaid assets.
    /// @param data Arbitrary data passed to the `liquidate` function.
    function onMorphoLiquidate(uint256 repaidAssets, bytes calldata data) external;
}

/// @title IMorphoRepayCallback
/// @notice Interface that users willing to use `repay`'s callback must implement.
interface IMorphoRepayCallback {
    /// @notice Callback called when a repayment occurs.
    /// @dev The callback is called only if data is not empty.
    /// @param assets The amount of repaid assets.
    /// @param data Arbitrary data passed to the `repay` function.
    function onMorphoRepay(uint256 assets, bytes calldata data) external;
}

/// @title IMorphoSupplyCallback
/// @notice Interface that users willing to use `supply`'s callback must implement.
interface IMorphoSupplyCallback {
    /// @notice Callback called when a supply occurs.
    /// @dev The callback is called only if data is not empty.
    /// @param assets The amount of supplied assets.
    /// @param data Arbitrary data passed to the `supply` function.
    function onMorphoSupply(uint256 assets, bytes calldata data) external;
}

/// @title IMorphoSupplyCollateralCallback
/// @notice Interface that users willing to use `supplyCollateral`'s callback must implement.
interface IMorphoSupplyCollateralCallback {
    /// @notice Callback called when a supply of collateral occurs.
    /// @dev The callback is called only if data is not empty.
    /// @param assets The amount of supplied collateral.
    /// @param data Arbitrary data passed to the `supplyCollateral` function.
    function onMorphoSupplyCollateral(uint256 assets, bytes calldata data) external;
}

/// @title IMorphoFlashLoanCallback
/// @notice Interface that users willing to use `flashLoan`'s callback must implement.
interface IMorphoFlashLoanCallback {
    /// @notice Callback called when a flash loan occurs.
    /// @dev The callback is called only if data is not empty.
    /// @param assets The amount of assets that was flash loaned.
    /// @param data Arbitrary data passed to the `flashLoan` function.
    function onMorphoFlashLoan(uint256 assets, bytes calldata data) external;
}
"
    },
    "src/utils/Constants.sol": {
      "content": "// SPDX-License-Identifier: BUSL-1.1
pragma solidity >=0.8.29;

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

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

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

// Will move these to a deployment file when we go to multiple chains
uint256 constant CHAIN_ID_MAINNET = 1;
WETH9 constant WETH = WETH9(0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2);
AddressRegistry constant ADDRESS_REGISTRY = AddressRegistry(0xe335d314BD4eF7DD44F103dC124FEFb7Ce63eC95);
"
    },
    "src/routers/AbstractLendingRouter.sol": {
      "content": "// SPDX-License-Identifier: BUSL-1.1
pragma solidity >=0.8.29;

import { ILendingRouter, VaultPosition } from "../interfaces/ILendingRouter.sol";
import {
    NotAuthorized,
    CannotExitPositionWithinCooldownPeriod,
    CannotForceWithdraw,
    InvalidLendingRouter,
    NoExistingPosition,
    LiquidatorHasPosition,
    CannotEnterPosition,
    CannotLiquidateZeroShares,
    InsufficientSharesHeld
} from "../interfaces/Errors.sol";
import { ERC20 } from "@openzeppelin/contracts/token/ERC20/ERC20.sol";
import { TokenUtils } from "../utils/TokenUtils.sol";
import { SafeERC20 } from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
import { IYieldStrategy } from "../interfaces/IYieldStrategy.sol";
import { RewardManagerMixin } from "../rewards/RewardManagerMixin.sol";
import { ADDRESS_REGISTRY, COOLDOWN_PERIOD } from "../utils/Constants.sol";
import { ReentrancyGuardTransient } from "@openzeppelin/contracts/utils/ReentrancyGuardTransient.sol";

abstract contract AbstractLendingRouter is ILendingRouter, ReentrancyGuardTransient {
    using SafeERC20 for ERC20;
    using TokenUtils for ERC20;

    mapping(address user => mapping(address operator => bool approved)) private s_isApproved;

    /**
     * Authorization Methods **
     */
    modifier isAuthorized(address onBehalf, address vault) {
        // In this case msg.sender is the operator
        if (msg.sender != onBehalf && !isApproved(onBehalf, msg.sender)) {
            revert NotAuthorized(msg.sender, onBehalf);
        }

        _;

        // Clear the current account after the transaction is finished
        IYieldStrategy(vault).clearCurrentAccount();
    }

    /// @inheritdoc ILendingRouter
    function setApproval(address operator, bool approved) external override {
        if (operator == msg.sender) revert NotAuthorized(msg.sender, operator);
        s_isApproved[msg.sender][operator] = approved;
        emit ApprovalUpdated(msg.sender, operator, approved);
    }

    /// @inheritdoc ILendingRouter
    function isApproved(address user, address operator) public view override returns (bool) {
        return s_isApproved[user][operator];
    }

    /// @inheritdoc ILendingRouter
    function enterPosition(
        address onBehalf,
        address vault,
        uint256 depositAssetAmount,
        uint256 borrowAmount,
        bytes calldata depositData
    )
        public
        override
        isAuthorized(onBehalf, vault)
        nonReentrant
    {
        _enterPosition(onBehalf, vault, depositAssetAmount, borrowAmount, depositData, address(0));
    }

    /// @inheritdoc ILendingRouter
    function migratePosition(
        address onBehalf,
        address vault,
        address migrateFrom
    )
        public
        override
        isAuthorized(onBehalf, vault)
        nonReentrant
    {
        _migratePosition(onBehalf, vault, migrateFrom);
    }

    function _migratePosition(address onBehalf, address vault, address migrateFrom) internal {
        if (!ADDRESS_REGISTRY.isLendingRouter(migrateFrom)) revert InvalidLendingRouter();
        // Borrow amount is set to the amount of debt owed to the previous lending router
        (uint256 borrowAmount, /* */, /* */ ) = ILendingRouter(migrateFrom).healthFactor(onBehalf, vault);

        _enterPosition(onBehalf, vault, 0, borrowAmount, bytes(""), migrateFrom);
    }

    function _enterPosition(
        address onBehalf,
        address vault,
        uint256 depositAssetAmount,
        uint256 borrowAmount,
        bytes memory depositData,
        address migrateFrom
    )
        internal
    {
        address asset = IYieldStrategy(vault).asset();
        // Cannot enter a position if the account already has a native share balance
        if (IYieldStrategy(vault).balanceOf(onBehalf) > 0) revert CannotEnterPosition();

        if (depositAssetAmount > 0) {
            // Take any margin deposit from the sender initially
            ERC20(asset).safeTransferFrom(msg.sender, address(this), depositAssetAmount);
        }

        uint256 borrowShares;
        uint256 vaultSharesReceived;
        if (borrowAmount > 0) {
            (vaultSharesReceived, borrowShares) =
                _flashBorrowAndEnter(onBehalf, vault, asset, depositAssetAmount, borrowAmount, depositData, migrateFrom);
        } else {
            vaultSharesReceived = _enterOrMigrate(onBehalf, vault, asset, depositAssetAmount, depositData, migrateFrom);
        }

        ADDRESS_REGISTRY.setPosition(onBehalf, vault);

        emit EnterPosition(
            onBehalf, vault, depositAssetAmount, borrowShares, vaultSharesReceived, migrateFrom != address(0)
        );
    }

    /// @inheritdoc ILendingRouter
    function exitPosition(
        address onBehalf,
        address vault,
        address receiver,
        uint256 sharesToRedeem,
        uint256 assetToRepay,
        bytes calldata redeemData
    )
        external
        override
        isAuthorized(onBehalf, vault)
        nonReentrant
    {
        _checkExit(onBehalf, vault);

        address asset = IYieldStrategy(vault).asset();
        uint256 borrowSharesRepaid;
        uint256 profitsWithdrawn;
        if (0 < assetToRepay) {
            (borrowSharesRepaid, profitsWithdrawn) =
                _exitWithRepay(onBehalf, vault, asset, receiver, sharesToRedeem, assetToRepay, redeemData);
        } else {
            // Migrate to is always set to address(0) since assetToRepay is always set to uint256.max
            profitsWithdrawn = _redeemShares(onBehalf, vault, asset, address(0), sharesToRedeem, redeemData);
            if (0 < profitsWithdrawn) ERC20(asset).safeTransfer(receiver, profitsWithdrawn);
        }

        if (balanceOfCollateral(onBehalf, vault) == 0) {
            ADDRESS_REGISTRY.clearPosition(onBehalf, vault);
        }

        emit ExitPosition(onBehalf, vault, borrowSharesRepaid, sharesToRedeem, profitsWithdrawn);
    }

    /// @inheritdoc ILendingRouter
    function liquidate(
        address liquidateAccount,
        address vault,
        uint256 sharesToLiquidate,
        uint256 debtToRepay
    )
        external
        override
        nonReentrant
        returns (uint256 sharesToLiquidator)
    {
        if (sharesToLiquidate == 0) revert CannotLiquidateZeroShares();

        address liquidator = msg.sender;
        VaultPosition memory position = ADDRESS_REGISTRY.getVaultPosition(liquidator, vault);
        // If the liquidator has a position then they cannot liquidate or they will have
        // a native balance and a balance on the lending market.
        if (position.lendingRouter != address(0)) revert LiquidatorHasPosition();

        uint256 balanceBefore = balanceOfCollateral(liquidateAccount, vault);
        if (balanceBefore == 0) revert InsufficientSharesHeld();

        // Runs any checks on the vault to ensure that the liquidation can proceed, whitelists the lending platform
        // to transfer collateral to the lending router. The current account is set in this method.
        IYieldStrategy(vault).preLiquidation(liquidator, liquidateAccount, sharesToLiquidate, balanceBefore);

        // After this call, address(this) will have the liquidated shares
        uint256 borrowSharesRepaid;
        (sharesToLiquidator, borrowSharesRepaid) =
            _liquidate(liquidator, vault, liquidateAccount, sharesToLiquidate, debtToRepay);

        // Transfers the shares to the liquidator from the lending router and does any post liquidation logic. The
        // current account is cleared in this method.
        IYieldStrategy(vault).postLiquidation(liquidator, liquidateAccount, sharesToLiquidator);

        // The liquidator will receive shares in their native balance and then they can call redeem
        // on the yield strategy to get the assets.

        // Clear the position if the liquidator has taken all the shares, in the case of an insolvency,
        // the account's position will just be left on the lending market with zero collateral. The account
        // would be able to create a new position on this lending router or a new position on a different
        // lending router. If they do create a new position on an insolvent account their old debt may
        // be applied to their new position.
        if (sharesToLiquidator == balanceBefore) ADDRESS_REGISTRY.clearPosition(liquidateAccount, vault);

        emit LiquidatePosition(liquidator, liquidateAccount, vault, borrowSharesRepaid, sharesToLiquidator);
    }

    /// @inheritdoc ILendingRouter
    function initiateWithdraw(
        address onBehalf,
        address vault,
        bytes calldata data
    )
        external
        override
        isAuthorized(onBehalf, vault)
        nonReentrant
        returns (uint256 requestId)
    {
        requestId = _initiateWithdraw(vault, onBehalf, data, false);
    }

    /// @inheritdoc ILendingRouter
    function forceWithdraw(
        address account,
        address vault,
        bytes calldata data
    )
        external
        nonReentrant
        returns (uint256 requestId)
    {
        // Can only force a withdraw if health factor is negative, this allows a liquidator to
        // force a withdraw and liquidate a position at a later time.
        (uint256 borrowed, /* */, uint256 maxBorrow) = healthFactor(account, vault);
        if (borrowed <= maxBorrow) revert CannotForceWithdraw(account);

        requestId = _initiateWithdraw(vault, account, data, true);

        // Clear the current account since this method is not called using isAuthorized
        IYieldStrategy(vault).clearCurrentAccount();

        emit ForceWithdraw(account, vault, requestId);
    }

    /// @inheritdoc ILendingRouter
    function claimRewards(
        address onBehalf,
        address vault
    )
        external
        override
        isAuthorized(onBehalf, vault)
        nonReentrant
        returns (uint256[] memory rewards)
    {
        return RewardManagerMixin(vault).claimAccountRewards(onBehalf, balanceOfCollateral(onBehalf, vault));
    }

    /// @inheritdoc ILendingRouter
    function healthFactor(
        address borrower,
        address vault
    )
        public
        virtual
        override
        returns (uint256 borrowed, uint256 collateralValue, uint256 maxBorrow);

    /// @inheritdoc ILendingRouter
    function balanceOfCollateral(
        address account,
        address vault
    )
        public
        view
        virtual
        override
        returns (uint256 collateralBalance);

    /**
     * Internal Methods **
     */
    function _checkExit(address onBehalf, address vault) internal view {
        VaultPosition memory position = ADDRESS_REGISTRY.getVaultPosition(onBehalf, vault);
        if (position.lendingRouter != address(this)) revert NoExistingPosition();
        if (block.timestamp - position.lastEntryTime < COOLDOWN_PERIOD) {
            revert CannotExitPositionWithinCooldownPeriod();
        }
    }

    /// @dev Checks if an exitPosition call is a migration, this would be called via a lending router
    function _isMigrate(address receiver) internal view returns (bool) {
        return receiver == msg.sender && ADDRESS_REGISTRY.isLendingRouter(msg.sender);
    }

    /// @dev Enters a position or migrates shares from a previous lending router
    function _enterOrMigrate(
        address onBehalf,
        address vault,
        address asset,
        uint256 assetAmount,
        bytes memory depositData,
        address migrateFrom
    )
        internal
        returns (uint256 sharesReceived)
    {
        if (migrateFrom != address(0)) {
            // Allow the previous lending router to repay the debt from assets held here.
            ERC20(asset).checkApprove(migrateFrom, assetAmount);
            sharesReceived = ILendingRouter(migrateFrom).balanceOfCollateral(onBehalf, vault);

            // Must migrate the entire position
            ILendingRouter(migrateFrom).exitPosition(
                onBehalf, vault, address(this), sharesReceived, type(uint256).max, bytes("")
            );
        } else {
            ERC20(asset).checkApprove(vault, assetAmoun

Tags:
ERC20, ERC165, Multisig, Swap, Liquidity, Staking, Yield, Upgradeable, Multi-Signature, Factory, Oracle|addr:0xd5005bb3317899765edaedd38ebcfc1cd567ca58|verified:true|block:23449840|tx:0xd5325f5c09a5eb93c9a50a03f72be614a0558d3068f0da2c09d776b3ab744a7d|first_check:1758962974

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

Comments

Log in to comment.

No comments yet.