Helper

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/Helper.sol": {
      "content": "// SPDX-License-Identifier: GPL-2.0-or-later
pragma solidity >=0.5.0;

import {IMorpho, IMorphoCredit, Id, Market, Position} from "./interfaces/IMorpho.sol";
import {MarketParams} from "./interfaces/IMorpho.sol";
import {IHelper} from "./interfaces/IHelper.sol";
import {IUSD3} from "./interfaces/IUSD3.sol";
import {IERC20} from "./interfaces/IERC20.sol";

import {ErrorsLib} from "./libraries/ErrorsLib.sol";
import {SafeTransferLib} from "./libraries/SafeTransferLib.sol";
import {SharesMathLib} from "./libraries/SharesMathLib.sol";
import {MarketParamsLib} from "./libraries/MarketParamsLib.sol";

import {IERC4626} from "../lib/forge-std/src/interfaces/IERC4626.sol";

/// @title Helper
/// @author 3Jane
/// @custom:contact support@3jane.xyz
contract Helper is IHelper {
    using SafeTransferLib for IERC20;
    using SharesMathLib for uint256;
    using MarketParamsLib for MarketParams;

    /// @inheritdoc IHelper
    address public immutable MORPHO;
    /// @inheritdoc IHelper
    address public immutable USD3;
    /// @inheritdoc IHelper
    address public immutable sUSD3;
    /// @inheritdoc IHelper
    address public immutable USDC;
    /// @inheritdoc IHelper
    address public immutable WAUSDC;

    /* CONSTRUCTOR */

    constructor(address morpho, address usd3, address susd3, address usdc, address wausdc) {
        if (morpho == address(0)) revert ErrorsLib.ZeroAddress();
        if (usd3 == address(0)) revert ErrorsLib.ZeroAddress();
        if (susd3 == address(0)) revert ErrorsLib.ZeroAddress();
        if (usdc == address(0)) revert ErrorsLib.ZeroAddress();
        if (wausdc == address(0)) revert ErrorsLib.ZeroAddress();

        MORPHO = morpho;
        USD3 = usd3;
        sUSD3 = susd3;
        USDC = usdc;
        WAUSDC = wausdc;

        // Set max approvals
        IERC20(USDC).approve(WAUSDC, type(uint256).max);
        IERC20(USDC).approve(USD3, type(uint256).max);
        IERC20(WAUSDC).approve(MORPHO, type(uint256).max);
        IERC20(USD3).approve(sUSD3, type(uint256).max);
    }

    /// @inheritdoc IHelper
    function deposit(uint256 assets, address receiver, bool hop, bytes32 referral) external returns (uint256 shares) {
        (shares) = deposit(assets, receiver, hop);
        emit DepositReferred(msg.sender, assets, referral);
    }

    /// @inheritdoc IHelper
    function deposit(uint256 assets, address receiver, bool hop) public returns (uint256) {
        if (msg.sender != receiver) revert ErrorsLib.Unauthorized();
        IERC20(USDC).safeTransferFrom(msg.sender, address(this), assets);

        if (hop) {
            require(IUSD3(USD3).availableDepositLimit(receiver) >= assets, "Deposit exceeds limit");
            uint256 usd3Shares = IUSD3(USD3).deposit(assets, address(this));
            return IERC4626(sUSD3).deposit(usd3Shares, receiver);
        } else {
            return IUSD3(USD3).deposit(assets, receiver);
        }
    }

    /// @inheritdoc IHelper
    function redeem(uint256 shares, address receiver) external returns (uint256) {
        uint256 usdcAssets = IUSD3(USD3).redeem(shares, receiver, msg.sender);
        return usdcAssets;
    }

    /// @inheritdoc IHelper
    function borrow(MarketParams memory marketParams, uint256 assets, bytes32 referral)
        external
        returns (uint256 amount, uint256 shares)
    {
        (amount, shares) = borrow(marketParams, assets);
        emit BorrowReferred(msg.sender, amount, referral);
    }

    /// @inheritdoc IHelper
    function borrow(MarketParams memory marketParams, uint256 assets) public returns (uint256, uint256) {
        uint256 waUsdcShares = IERC4626(WAUSDC).convertToShares(assets);
        (uint256 waUSDCAmount, uint256 shares) =
            IMorpho(MORPHO).borrow(marketParams, waUsdcShares, 0, msg.sender, address(this));
        uint256 usdcAmount = IERC4626(WAUSDC).redeem(waUSDCAmount, msg.sender, address(this));
        return (usdcAmount, shares);
    }

    /// @inheritdoc IHelper
    function repay(MarketParams memory marketParams, uint256 assets, address onBehalf, bytes calldata data)
        external
        returns (uint256, uint256)
    {
        // Check if this is a full repayment request
        if (assets == type(uint256).max) {
            return _repayFull(marketParams, onBehalf, data);
        } else {
            // Normal partial repayment flow
            uint256 waUSDCAmount = _wrap(msg.sender, assets);
            (, uint256 shares) = IMorpho(MORPHO).repay(marketParams, waUSDCAmount, 0, onBehalf, data);
            return (assets, shares);
        }
    }

    function _repayFull(MarketParams memory marketParams, address onBehalf, bytes calldata data)
        internal
        returns (uint256, uint256)
    {
        Id id = marketParams.id();

        // Accrue premium first to get accurate borrow shares
        _accruePremiumsForBorrower(id, onBehalf);

        // Get current borrow shares after premium accrual
        Position memory pos = IMorpho(MORPHO).position(id, onBehalf);

        // If no debt, return early
        if (pos.borrowShares == 0) {
            return (0, 0);
        }

        // Get market state to calculate assets needed
        Market memory market = IMorpho(MORPHO).market(id);

        // Calculate waUSDC assets needed (rounds up like Morpho does)
        uint256 waUsdcNeeded = uint256(pos.borrowShares).toAssetsUp(market.totalBorrowAssets, market.totalBorrowShares);

        // Convert to USDC amount needed (preview how much USDC needed to mint waUsdcNeeded)
        uint256 usdcNeeded = IERC4626(WAUSDC).previewMint(waUsdcNeeded);

        // Pull USDC from user and wrap to waUSDC
        _wrap(msg.sender, usdcNeeded);

        // Repay with shares to ensure complete repayment
        (, uint256 sharesRepaid) = IMorpho(MORPHO).repay(marketParams, 0, pos.borrowShares, onBehalf, data);

        return (usdcNeeded, sharesRepaid);
    }

    function _wrap(address from, uint256 assets) internal returns (uint256) {
        IERC20(USDC).safeTransferFrom(from, address(this), assets);
        return IERC4626(WAUSDC).deposit(assets, address(this));
    }

    function _accruePremiumsForBorrower(Id id, address borrower) internal {
        address[] memory borrowers = new address[](1);
        borrowers[0] = borrower;
        IMorphoCredit(MORPHO).accruePremiumsForBorrowers(id, borrowers);
    }
}
"
    },
    "src/interfaces/IMorpho.sol": {
      "content": "// SPDX-License-Identifier: GPL-2.0-or-later
pragma solidity ^0.8.18;

type Id is bytes32;

struct MarketParams {
    address loanToken;
    address collateralToken;
    address oracle;
    address irm;
    uint256 lltv;
    address creditLine;
}

/// @dev Warning: For `feeRecipient`, `supplyShares` does not contain the accrued shares since the last interest
/// accrual.
struct Position {
    uint256 supplyShares;
    uint128 borrowShares;
    uint128 collateral;
}

/// @dev Warning: `totalSupplyAssets` does not contain the accrued interest since the last interest accrual.
/// @dev Warning: `totalBorrowAssets` does not contain the accrued interest since the last interest accrual.
/// @dev Warning: `totalSupplyShares` does not contain the additional shares accrued by `feeRecipient` since the last
/// interest accrual.
/// @dev Warning: `totalMarkdownAmount` may be stale as markdowns are only updated when borrowers are touched.
struct Market {
    uint128 totalSupplyAssets;
    uint128 totalSupplyShares;
    uint128 totalBorrowAssets;
    uint128 totalBorrowShares;
    uint128 lastUpdate;
    uint128 fee;
    uint128 totalMarkdownAmount; // Running tally of all borrower markdowns
}

/// @notice Per-borrower premium tracking
/// @param lastAccrualTime Timestamp of the last premium accrual for this borrower
/// @param rate Current risk premium rate per second (scaled by WAD)
/// @param borrowAssetsAtLastAccrual Snapshot of borrow position at last premium accrual
struct BorrowerPremium {
    uint128 lastAccrualTime;
    uint128 rate;
    uint128 borrowAssetsAtLastAccrual;
}

/// @notice Repayment tracking structures
enum RepaymentStatus {
    Current,
    GracePeriod,
    Delinquent,
    Default
}

struct PaymentCycle {
    uint256 endDate;
}

struct RepaymentObligation {
    uint128 paymentCycleId;
    uint128 amountDue;
    uint128 endingBalance;
}

/// @notice Markdown state for tracking defaulted debt value reduction
/// @param lastCalculatedMarkdown Last calculated markdown amount
struct MarkdownState {
    uint128 lastCalculatedMarkdown;
}

struct Authorization {
    address authorizer;
    address authorized;
    bool isAuthorized;
    uint256 nonce;
    uint256 deadline;
}

struct Signature {
    uint8 v;
    bytes32 r;
    bytes32 s;
}

/// @dev This interface is used for factorizing IMorphoStaticTyping and IMorpho.
/// @dev Consider using the IMorpho interface instead of this one.
interface IMorphoBase {
    /// @notice The EIP-712 domain separator.
    /// @dev Warning: Every EIP-712 signed message based on this domain separator can be reused on chains sharing the
    /// same chain id and on forks because the domain separator would be the same.
    function DOMAIN_SEPARATOR() external view returns (bytes32);

    /// @notice The owner of the contract.
    /// @dev It has the power to change the owner.
    /// @dev It has the power to set fees on markets and set the fee recipient.
    /// @dev It has the power to enable but not disable IRMs and LLTVs.
    function owner() external view returns (address);

    /// @notice The fee recipient of all markets.
    /// @dev The recipient receives the fees of a given market through a supply position on that market.
    function feeRecipient() external view returns (address);

    /// @notice Whether the `irm` is enabled.
    function isIrmEnabled(address irm) external view returns (bool);

    /// @notice Whether the `lltv` is enabled.
    function isLltvEnabled(uint256 lltv) external view returns (bool);

    /// @notice The `authorizer`'s current nonce. Used to prevent replay attacks with EIP-712 signatures.
    function nonce(address authorizer) external view returns (uint256);

    /// @notice Sets `newOwner` as `owner` of the contract.
    /// @dev Warning: No two-step transfer ownership.
    /// @dev Warning: The owner can be set to the zero address.
    function setOwner(address newOwner) external;

    /// @notice Enables `irm` as a possible IRM for market creation.
    /// @dev Warning: It is not possible to disable an IRM.
    function enableIrm(address irm) external;

    /// @notice Enables `lltv` as a possible LLTV for market creation.
    /// @dev Warning: It is not possible to disable a LLTV.
    function enableLltv(uint256 lltv) external;

    /// @notice Sets the `newFee` for the given market `marketParams`.
    /// @param newFee The new fee, scaled by WAD.
    /// @dev Warning: The recipient can be the zero address.
    function setFee(MarketParams memory marketParams, uint256 newFee) external;

    /// @notice Sets `newFeeRecipient` as `feeRecipient` of the fee.
    /// @dev Warning: If the fee recipient is set to the zero address, fees will accrue there and will be lost.
    /// @dev Modifying the fee recipient will allow the new recipient to claim any pending fees not yet accrued. To
    /// ensure that the current recipient receives all due fees, accrue interest manually prior to making any changes.
    function setFeeRecipient(address newFeeRecipient) external;

    /// @notice Creates the market `marketParams`.
    /// @dev Here is the list of assumptions on the market's dependencies (tokens, IRM and oracle) that guarantees
    /// Morpho behaves as expected:
    /// - The token should be ERC-20 compliant, except that it can omit return values on `transfer` and `transferFrom`.
    /// - The token balance of Morpho should only decrease on `transfer` and `transferFrom`. In particular, tokens with
    /// burn functions are not supported.
    /// - The token should not re-enter Morpho on `transfer` nor `transferFrom`.
    /// - The token balance of the sender (resp. receiver) should decrease (resp. increase) by exactly the given amount
    /// on `transfer` and `transferFrom`. In particular, tokens with fees on transfer are not supported.
    /// - The IRM should not re-enter Morpho.
    /// - The oracle should return a price with the correct scaling.
    /// @dev Here is a list of assumptions on the market's dependencies which, if broken, could break Morpho's liveness
    /// properties (funds could get stuck):
    /// - The token should not revert on `transfer` and `transferFrom` if balances and approvals are right.
    /// - The amount of assets supplied and borrowed should not go above ~1e35 (otherwise the computation of
    /// `toSharesUp` and `toSharesDown` can overflow).
    /// - The IRM should not revert on `borrowRate`.
    /// - The IRM should not return a very high borrow rate (otherwise the computation of `interest` in
    /// `_accrueInterest` can overflow).
    /// - The oracle should not revert `price`.
    /// - The oracle should not return a very high price (otherwise the computation of `maxBorrow` in `_isHealthy` or of
    /// `assetsRepaid` in `liquidate` can overflow).
    /// @dev The borrow share price of a market with less than 1e4 assets borrowed can be decreased by manipulations, to
    /// the point where `totalBorrowShares` is very large and borrowing overflows.
    function createMarket(MarketParams memory marketParams) external;

    /// @notice Supplies `assets` or `shares` on behalf of `onBehalf`, optionally calling back the caller's
    /// `onMorphoSupply` function with the given `data`.
    /// @dev Either `assets` or `shares` should be zero. Most use cases should rely on `assets` as an input so the
    /// caller is guaranteed to have `assets` tokens pulled from their balance, but the possibility to mint a specific
    /// amount of shares is given for full compatibility and precision.
    /// @dev Supplying a large amount can revert for overflow.
    /// @dev Supplying an amount of shares may lead to supply more or fewer assets than expected due to slippage.
    /// Consider using the `assets` parameter to avoid this.
    /// @param marketParams The market to supply assets to.
    /// @param assets The amount of assets to supply.
    /// @param shares The amount of shares to mint.
    /// @param onBehalf The address that will own the increased supply position.
    /// @param data Arbitrary data to pass to the `onMorphoSupply` callback. Pass empty data if not needed.
    /// @return assetsSupplied The amount of assets supplied.
    /// @return sharesSupplied The amount of shares minted.
    function supply(
        MarketParams memory marketParams,
        uint256 assets,
        uint256 shares,
        address onBehalf,
        bytes memory data
    ) external returns (uint256 assetsSupplied, uint256 sharesSupplied);

    /// @notice Withdraws `assets` or `shares` on behalf of `onBehalf` and sends the assets to `receiver`.
    /// @dev Either `assets` or `shares` should be zero. To withdraw max, pass the `shares`'s balance of `onBehalf`.
    /// @dev `msg.sender` must be authorized to manage `onBehalf`'s positions.
    /// @dev Withdrawing an amount corresponding to more shares than supplied will revert for underflow.
    /// @dev It is advised to use the `shares` input when withdrawing the full position to avoid reverts due to
    /// conversion roundings between shares and assets.
    /// @param marketParams The market to withdraw assets from.
    /// @param assets The amount of assets to withdraw.
    /// @param shares The amount of shares to burn.
    /// @param onBehalf The address of the owner of the supply position.
    /// @param receiver The address that will receive the withdrawn assets.
    /// @return assetsWithdrawn The amount of assets withdrawn.
    /// @return sharesWithdrawn The amount of shares burned.
    function withdraw(
        MarketParams memory marketParams,
        uint256 assets,
        uint256 shares,
        address onBehalf,
        address receiver
    ) external returns (uint256 assetsWithdrawn, uint256 sharesWithdrawn);

    /// @notice Borrows `assets` or `shares` on behalf of `onBehalf` and sends the assets to `receiver`.
    /// @dev Either `assets` or `shares` should be zero. Most use cases should rely on `assets` as an input so the
    /// caller is guaranteed to borrow `assets` of tokens, but the possibility to mint a specific amount of shares is
    /// given for full compatibility and precision.
    /// @dev `msg.sender` must be authorized to manage `onBehalf`'s positions.
    /// @dev Borrowing a large amount can revert for overflow.
    /// @dev Borrowing an amount of shares may lead to borrow fewer assets than expected due to slippage.
    /// Consider using the `assets` parameter to avoid this.
    /// @param marketParams The market to borrow assets from.
    /// @param assets The amount of assets to borrow.
    /// @param shares The amount of shares to mint.
    /// @param onBehalf The address that will own the increased borrow position.
    /// @param receiver The address that will receive the borrowed assets.
    /// @return assetsBorrowed The amount of assets borrowed.
    /// @return sharesBorrowed The amount of shares minted.
    function borrow(
        MarketParams memory marketParams,
        uint256 assets,
        uint256 shares,
        address onBehalf,
        address receiver
    ) external returns (uint256 assetsBorrowed, uint256 sharesBorrowed);

    /// @notice Repays `assets` or `shares` on behalf of `onBehalf`, optionally calling back the caller's
    /// `onMorphoRepay` function with the given `data`.
    /// @dev Either `assets` or `shares` should be zero. To repay max, pass the `shares`'s balance of `onBehalf`.
    /// @dev Repaying an amount corresponding to more shares than borrowed will revert for underflow.
    /// @dev It is advised to use the `shares` input when repaying the full position to avoid reverts due to conversion
    /// roundings between shares and assets.
    /// @dev An attacker can front-run a repay with a small repay making the transaction revert for underflow.
    /// @param marketParams The market to repay assets to.
    /// @param assets The amount of assets to repay.
    /// @param shares The amount of shares to burn.
    /// @param onBehalf The address of the owner of the debt position.
    /// @param data Arbitrary data to pass to the `onMorphoRepay` callback. Pass empty data if not needed.
    /// @return assetsRepaid The amount of assets repaid.
    /// @return sharesRepaid The amount of shares burned.
    function repay(
        MarketParams memory marketParams,
        uint256 assets,
        uint256 shares,
        address onBehalf,
        bytes memory data
    ) external returns (uint256 assetsRepaid, uint256 sharesRepaid);

    /// @notice Accrues interest for the given market `marketParams`.
    function accrueInterest(MarketParams memory marketParams) external;

    /// @notice Returns the data stored on the different `slots`.
    function extSloads(bytes32[] memory slots) external view returns (bytes32[] memory);
}

/// @dev This interface is inherited by Morpho so that function signatures are checked by the compiler.
/// @dev Consider using the IMorpho interface instead of this one.
interface IMorphoStaticTyping is IMorphoBase {
    /// @notice The state of the position of `user` on the market corresponding to `id`.
    /// @dev Warning: For `feeRecipient`, `supplyShares` does not contain the accrued shares since the last interest
    /// accrual.
    function position(Id id, address user)
        external
        view
        returns (uint256 supplyShares, uint128 borrowShares, uint128 collateral);

    /// @notice The state of the market corresponding to `id`.
    /// @dev Warning: `totalSupplyAssets` does not contain the accrued interest since the last interest accrual.
    /// @dev Warning: `totalBorrowAssets` does not contain the accrued interest since the last interest accrual.
    /// @dev Warning: `totalSupplyShares` does not contain the accrued shares by `feeRecipient` since the last interest
    /// accrual.
    function market(Id id)
        external
        view
        returns (
            uint128 totalSupplyAssets,
            uint128 totalSupplyShares,
            uint128 totalBorrowAssets,
            uint128 totalBorrowShares,
            uint128 lastUpdate,
            uint128 fee,
            uint128 totalMarkdownAmount
        );

    /// @notice The market params corresponding to `id`.
    /// @dev This mapping is not used in Morpho. It is there to enable reducing the cost associated to calldata on layer
    /// 2s by creating a wrapper contract with functions that take `id` as input instead of `marketParams`.
    function idToMarketParams(Id id)
        external
        view
        returns (
            address loanToken,
            address collateralToken,
            address oracle,
            address irm,
            uint256 lltv,
            address creditLine
        );
}

/// @title IMorpho
/// @author Morpho Labs
/// @custom:contact security@morpho.org
/// @dev Use this interface for Morpho to have access to all the functions with the appropriate function signatures.
interface IMorpho is IMorphoBase {
    /// @notice The state of the position of `user` on the market corresponding to `id`.
    /// @dev Warning: For `feeRecipient`, `p.supplyShares` does not contain the accrued shares since the last interest
    /// accrual.
    function position(Id id, address user) external view returns (Position memory p);

    /// @notice The state of the market corresponding to `id`.
    /// @dev Warning: `m.totalSupplyAssets` does not contain the accrued interest since the last interest accrual.
    /// @dev Warning: `m.totalBorrowAssets` does not contain the accrued interest since the last interest accrual.
    /// @dev Warning: `m.totalSupplyShares` does not contain the accrued shares by `feeRecipient` since the last
    /// interest accrual.
    function market(Id id) external view returns (Market memory m);

    /// @notice The market params corresponding to `id`.
    /// @dev This mapping is not used in Morpho. It is there to enable reducing the cost associated to calldata on layer
    /// 2s by creating a wrapper contract with functions that take `id` as input instead of `marketParams`.
    function idToMarketParams(Id id) external view returns (MarketParams memory);
}

/// @title IMorphoCredit
/// @author Morpho Labs
/// @custom:contact security@morpho.org
/// @dev Use this interface for Morpho to have access to all the functions with the appropriate function signatures.
interface IMorphoCredit {
    /// @notice The helper of the contract.
    function helper() external view returns (address);

    /// @notice The usd3 contract
    function usd3() external view returns (address);

    /// @notice The protocol config of the contract.
    function protocolConfig() external view returns (address);

    /// @notice Sets `helper` as `helper` of the contract.
    /// @param newHelper The new helper address
    function setHelper(address newHelper) external;

    /// @notice Sets `usd3` as `usd3` of the contract.
    /// @param newUsd3 The new usd3 address
    function setUsd3(address newUsd3) external;

    /// @notice Sets the credit line and premium rate for a borrower
    /// @param id The market ID
    /// @param borrower The borrower address
    /// @param credit The credit line amount
    /// @param drp The drp per second in WAD
    function setCreditLine(Id id, address borrower, uint256 credit, uint128 drp) external;

    /// @notice Returns the premium data for a specific borrower in a market
    /// @param id The market ID
    /// @param borrower The borrower address
    /// @return lastAccrualTime Timestamp of the last premium accrual
    /// @return rate Current risk premium rate per second (scaled by WAD)
    /// @return borrowAssetsAtLastAccrual Snapshot of borrow position at last premium accrual
    function borrowerPremium(Id id, address borrower)
        external
        view
        returns (uint128 lastAccrualTime, uint128 rate, uint128 borrowAssetsAtLastAccrual);

    /// @notice Batch accrue premiums for multiple borrowers
    /// @param id Market ID
    /// @param borrowers Array of borrower addresses
    /// @dev Gas usage scales linearly with array size. Callers should manage batch sizes based on block gas limits.
    function accruePremiumsForBorrowers(Id id, address[] calldata borrowers) external;

    /// @notice Close a payment cycle and post obligations for multiple borrowers
    /// @param id Market ID
    /// @param endDate Cycle end date
    /// @param borrowers Array of borrower addresses
    /// @param repaymentBps Array of repayment basis points (e.g., 500 = 5%)
    /// @param endingBalances Array of ending balances for penalty calculations
    function closeCycleAndPostObligations(
        Id id,
        uint256 endDate,
        address[] calldata borrowers,
        uint256[] calldata repaymentBps,
        uint256[] calldata endingBalances
    ) external;

    /// @notice Add obligations to the latest payment cycle
    /// @param id Market ID
    /// @param borrowers Array of borrower addresses
    /// @param repaymentBps Array of repayment basis points (e.g., 500 = 5%)
    /// @param endingBalances Array of ending balances
    function addObligationsToLatestCycle(
        Id id,
        address[] calldata borrowers,
        uint256[] calldata repaymentBps,
        uint256[] calldata endingBalances
    ) external;

    /// @notice Get repayment obligation for a borrower
    /// @param id Market ID
    /// @param borrower Borrower address
    /// @return cycleId The payment cycle ID
    /// @return amountDue The amount due
    /// @return endingBalance The ending balance for penalty calculations
    function repaymentObligation(Id id, address borrower)
        external
        view
        returns (uint128 cycleId, uint128 amountDue, uint128 endingBalance);

    /// @notice Get payment cycle end date
    /// @param id Market ID
    /// @param cycleId Cycle ID
    /// @return endDate The cycle end date
    function paymentCycle(Id id, uint256 cycleId) external view returns (uint256 endDate);

    /// @notice Settle a borrower's account by writing off all remaining debt
    /// @dev Only callable by credit line contract
    /// @dev Should be called after any partial repayments have been made
    /// @param marketParams The market parameters
    /// @param borrower The borrower whose account to settle
    /// @return writtenOffAssets Amount of assets written off
    /// @return writtenOffShares Amount of shares written off
    function settleAccount(MarketParams memory marketParams, address borrower)
        external
        returns (uint256 writtenOffAssets, uint256 writtenOffShares);

    /// @notice Get markdown state for a borrower
    /// @param id Market ID
    /// @param borrower Borrower address
    /// @return lastCalculatedMarkdown Last calculated markdown amount
    function markdownState(Id id, address borrower) external view returns (uint128 lastCalculatedMarkdown);
}
"
    },
    "src/interfaces/IHelper.sol": {
      "content": "// SPDX-License-Identifier: GPL-2.0-or-later
pragma solidity >=0.5.0;

import {IERC4626} from "../../lib/forge-std/src/interfaces/IERC4626.sol";

import {MarketParams} from "./IMorpho.sol";

/// @title IHelper
/// @author Morpho Labs
/// @custom:contact security@morpho.org
/// @notice Helper contract to simplify interactions with 3Jane's token ecosystem and Morpho protocol
/// @dev This contract handles token conversions for USD3 (post-reinitialize) and MorphoCredit operations
/// @dev After USD3's reinitialize(), the flow is: USDC → USD3 (→ sUSD3 if hop=true)
/// @dev MorphoCredit still uses waUSDC, so borrow/repay operations handle the USDC ↔ waUSDC conversion
interface IHelper {
    event DepositReferred(address indexed depositor, uint256 amount, bytes32 code);
    event BorrowReferred(address indexed borrower, uint256 amount, bytes32 code);

    /// @notice The Morpho protocol contract address
    /// @return The address of the Morpho contract
    function MORPHO() external view returns (address);

    /// @notice The USD3 token address (ERC4626 vault, accepts USDC directly after reinitialize)
    /// @return The address of the USD3 token
    function USD3() external view returns (address);

    /// @notice The sUSD3 token address (subordinate/staked USD3, also ERC4626)
    /// @return The address of the sUSD3 token
    function sUSD3() external view returns (address);

    /// @notice The USDC token address (base stablecoin)
    /// @return The address of the USDC token
    function USDC() external view returns (address);

    /// @notice The waUSDC token address (wrapped asset USDC, ERC4626 vault)
    /// @return The address of the waUSDC token
    function WAUSDC() external view returns (address);

    /// @notice Deposits USDC into USD3 (and optionally sUSD3)
    /// @dev After USD3's reinitialize(), flow is: USDC → USD3 (→ sUSD3 if hop=true)
    /// @dev USD3 handles USDC directly and manages waUSDC wrapping internally
    /// @param assets The amount of USDC to deposit
    /// @param receiver The address that will receive the USD3/sUSD3 shares
    /// @param hop If true, deposits into sUSD3; if false, stops at USD3
    /// @return The amount of shares minted (USD3 or sUSD3 depending on hop)
    function deposit(uint256 assets, address receiver, bool hop) external returns (uint256);

    /// @notice Deposits USDC into USD3 (and optionally sUSD3) with referral tracking
    /// @dev Same as deposit() but emits DepositReferred event for referral tracking
    /// @param assets The amount of USDC to deposit
    /// @param receiver The address that will receive the USD3/sUSD3 shares
    /// @param hop If true, deposits into sUSD3; if false, stops at USD3
    /// @param referral Referral code for tracking purposes
    /// @return The amount of shares minted (USD3 or sUSD3 depending on hop)
    function deposit(uint256 assets, address receiver, bool hop, bytes32 referral) external returns (uint256);

    /// @notice Redeems USD3 shares back to USDC
    /// @dev After USD3's reinitialize(), USD3 returns USDC directly
    /// @dev Caller must have approved Helper for USD3 spending
    /// @param shares The amount of USD3 shares to redeem
    /// @param receiver The address that will receive the USDC
    /// @return The amount of USDC received
    function redeem(uint256 shares, address receiver) external returns (uint256);

    /// @notice Borrows assets from a Morpho market and unwraps to USDC
    /// @dev The borrowed waUSDC is automatically unwrapped to USDC for the borrower
    /// @param marketParams The market parameters defining which market to borrow from
    /// @param assets The amount of assets to borrow (in waUSDC terms)
    /// @return usdcAmount The amount of USDC received by the borrower
    /// @return shares The amount of borrow shares created
    function borrow(MarketParams memory marketParams, uint256 assets) external returns (uint256, uint256);

    /// @notice Borrows assets from a Morpho market and unwraps to USDC with referral tracking
    /// @dev Same as borrow() but emits BorrowReferred event for referral tracking
    /// @param marketParams The market parameters defining which market to borrow from
    /// @param assets The amount of assets to borrow (in waUSDC terms)
    /// @param referral Referral code for tracking purposes
    /// @return usdcAmount The amount of USDC received by the borrower
    /// @return shares The amount of borrow shares created
    function borrow(MarketParams memory marketParams, uint256 assets, bytes32 referral)
        external
        returns (uint256, uint256);

    /// @notice Repays a loan by wrapping USDC to waUSDC
    /// @dev Flow: USDC → waUSDC → Morpho repay. Caller must have approved Helper for USDC spending
    /// @param marketParams The market parameters defining which market to repay
    /// @param assets The amount of USDC to repay
    /// @param onBehalf The address whose debt is being repaid
    /// @param data Additional data for the repay operation (e.g., for callbacks)
    /// @return usdcAmount The amount of USDC used from the caller
    /// @return shares The amount of borrow shares repaid
    function repay(MarketParams memory marketParams, uint256 assets, address onBehalf, bytes calldata data)
        external
        returns (uint256, uint256);
}
"
    },
    "src/interfaces/IUSD3.sol": {
      "content": "// SPDX-License-Identifier: GPL-2.0-or-later
pragma solidity >=0.5.0;

import {IERC4626} from "../../lib/forge-std/src/interfaces/IERC4626.sol";

/// @title IUSD3
/// @author Morpho Labs
/// @custom:contact security@morpho.org
/// @notice Interface for USD3 token, which is an ERC4626 vault
interface IUSD3 is IERC4626 {
    // USD3 inherits all ERC4626 functions including:
    // - deposit(uint256 assets, address receiver) returns (uint256 shares)
    // - redeem(uint256 shares, address receiver, address owner) returns (uint256 assets)
    // - And all other ERC4626 standard functions
    function whitelist(address user) external view returns (bool);
    function availableDepositLimit(address owner) external view returns (uint256);
}
"
    },
    "src/interfaces/IERC20.sol": {
      "content": "// SPDX-License-Identifier: GPL-2.0-or-later
pragma solidity >=0.5.0;

/// @title IERC20
/// @author Morpho Labs
/// @custom:contact security@morpho.org
/// @dev Empty because we only call library functions. It prevents calling transfer (transferFrom) instead of
/// safeTransfer (safeTransferFrom).
interface IERC20 {
    function approve(address spender, uint256 value) external returns (bool);
}
"
    },
    "src/libraries/ErrorsLib.sol": {
      "content": "// SPDX-License-Identifier: GPL-2.0-or-later
pragma solidity ^0.8.0;

/// @title ErrorsLib
/// @author Morpho Labs
/// @custom:contact security@morpho.org
/// @notice Library exposing custom errors.
library ErrorsLib {
    /// @notice Thrown when the caller is not the owner.
    error NotOwner();

    /// @notice Thrown when the caller is not the market's credit line.
    error NotCreditLine();

    /// @notice Thrown when the caller is not the market's helper.
    error NotHelper();

    /// @notice Thrown when the caller is not the market's usd3.
    error NotUsd3();

    /// @notice Thrown when the caller is not the owner or ozd.
    error NotOwnerOrOzd();

    /// @notice Thrown when the user is unverified.
    error Unverified();

    /// @notice Thrown when the LLTV to enable exceeds the maximum LLTV.
    error MaxLltvExceeded();

    /// @notice Thrown when the LTV to enable exceeds the maximum LTV.
    error MaxLtvExceeded();

    /// @notice Thrown when the VV to enable exceeds the maximum VV.
    error MaxVvExceeded();

    /// @notice Thrown when the credit to enable exceeds the maximum credit.
    error MaxCreditLineExceeded();

    /// @notice Thrown when the credit to enable is below the minimum credit.
    error MinCreditLineExceeded();

    /// @notice Thrown when the fee to set exceeds the maximum fee.
    error MaxFeeExceeded();

    /// @notice Thrown when the value is already set.
    error AlreadySet();

    /// @notice Thrown when the IRM is not enabled at market creation.
    error IrmNotEnabled();

    /// @notice Thrown when the LLTV is not enabled at market creation.
    error LltvNotEnabled();

    /// @notice Thrown when the market is already created.
    error MarketAlreadyCreated();

    /// @notice Thrown when a token to transfer doesn't have code.
    error NoCode();

    /// @notice Thrown when the market is not created.
    error MarketNotCreated();

    /// @notice Thrown when not exactly one of the input amount is zero.
    error InconsistentInput();

    /// @notice Thrown when zero assets is passed as input.
    error ZeroAssets();

    /// @notice Thrown when a zero address is passed as input.
    error ZeroAddress();

    /// @notice Thrown when an array has an invalid length.
    error InvalidArrayLength();

    /// @notice Thrown when the caller is not authorized to conduct an action.
    error Unauthorized();

    /// @notice Thrown when the collateral is insufficient to `borrow` or `withdrawCollateral`.
    error InsufficientCollateral();

    /// @notice Thrown when the liquidity is insufficient to `withdraw` or `borrow`.
    error InsufficientLiquidity();

    /// @notice Thrown when borrowing shares would result in borrowing zero assets.
    error InsufficientBorrowAmount();

    /// @notice Thrown when a token transfer reverted.
    error TransferReverted();

    /// @notice Thrown when a token transfer returned false.
    error TransferReturnedFalse();

    /// @notice Thrown when a token transferFrom reverted.
    error TransferFromReverted();

    /// @notice Thrown when a token transferFrom returned false
    error TransferFromReturnedFalse();

    /// @notice Thrown when the maximum uint128 is exceeded.
    error MaxUint128Exceeded();

    /// @notice Thrown when the premium rate exceeds the maximum allowed.
    error MaxDrpExceeded();

    /// @notice Thrown when the borrower has outstanding repayment obligations.
    error OutstandingRepayment();

    /// @notice Thrown when the protocol is paused.
    error Paused();

    /// @notice Thrown when trying to close a future cycle.
    error CannotCloseFutureCycle();

    /// @notice Thrown when cycle duration is invalid.
    error InvalidCycleDuration();

    /// @notice Thrown when no payment cycles exist.
    error NoCyclesExist();

    /// @notice Thrown when cycle ID is invalid.
    error InvalidCycleId();

    /// @notice Thrown when partial payment is attempted but full obligation payment is required.
    error MustPayFullObligation();

    /// @notice Thrown when repayment basis points exceed 100%.
    error RepaymentExceedsHundredPercent();

    /// @notice Thrown when an invalid markdown manager is set.
    error InvalidMarkdownManager();

    /// @notice Thrown when trying to settle non-existent debt.
    error NoAccountToSettle();

    /// @notice Thrown when the cover amount exceeds the assets amount.
    error InvalidCoverAmount();

    /// @notice Thrown when attempting operations on a frozen market.
    error MarketFrozen();

    /// @notice Thrown when a borrow would exceed the protocol debt cap.
    error DebtCapExceeded();

    /// @notice Thrown when borrow or repay would result in debt below minimum borrow amount.
    error BelowMinimumBorrow();
}
"
    },
    "src/libraries/SafeTransferLib.sol": {
      "content": "// SPDX-License-Identifier: GPL-2.0-or-later
pragma solidity ^0.8.0;

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

import {ErrorsLib} from "../libraries/ErrorsLib.sol";

interface IERC20Internal {
    function transfer(address to, uint256 value) external returns (bool);
    function transferFrom(address from, address to, uint256 value) external returns (bool);
}

/// @title SafeTransferLib
/// @author Morpho Labs
/// @custom:contact security@morpho.org
/// @notice Library to manage transfers of tokens, even if calls to the transfer or transferFrom functions are not
/// returning a boolean.
library SafeTransferLib {
    function safeTransfer(IERC20 token, address to, uint256 value) internal {
        if (address(token).code.length == 0) revert ErrorsLib.NoCode();

        (bool success, bytes memory returndata) =
            address(token).call(abi.encodeCall(IERC20Internal.transfer, (to, value)));
        if (!success) revert ErrorsLib.TransferReverted();
        if (returndata.length != 0 && !abi.decode(returndata, (bool))) revert ErrorsLib.TransferReturnedFalse();
    }

    function safeTransferFrom(IERC20 token, address from, address to, uint256 value) internal {
        if (address(token).code.length == 0) revert ErrorsLib.NoCode();

        (bool success, bytes memory returndata) =
            address(token).call(abi.encodeCall(IERC20Internal.transferFrom, (from, to, value)));
        if (!success) revert ErrorsLib.TransferFromReverted();
        if (returndata.length != 0 && !abi.decode(returndata, (bool))) revert ErrorsLib.TransferFromReturnedFalse();
    }
}
"
    },
    "src/libraries/SharesMathLib.sol": {
      "content": "// SPDX-License-Identifier: GPL-2.0-or-later
pragma solidity ^0.8.0;

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

/// @title SharesMathLib
/// @author Morpho Labs
/// @custom:contact security@morpho.org
/// @notice Shares management library.
/// @dev This implementation mitigates share price manipulations, using OpenZeppelin's method of virtual shares:
/// https://docs.openzeppelin.com/contracts/4.x/erc4626#inflation-attack.
library SharesMathLib {
    using MathLib for uint256;

    /// @dev The number of virtual shares has been chosen low enough to prevent overflows, and high enough to ensure
    /// high precision computations.
    /// @dev Virtual shares can never be redeemed for the assets they are entitled to, but it is assumed the share price
    /// stays low enough not to inflate these assets to a significant value.
    /// @dev Warning: The assets to which virtual borrow shares are entitled behave like unrealizable bad debt.
    uint256 internal constant VIRTUAL_SHARES = 1e6;

    /// @dev A number of virtual assets of 1 enforces a conversion rate between shares and assets when a market is
    /// empty.
    uint256 internal constant VIRTUAL_ASSETS = 1;

    /// @dev Calculates the value of `assets` quoted in shares, rounding down.
    function toSharesDown(uint256 assets, uint256 totalAssets, uint256 totalShares) internal pure returns (uint256) {
        return assets.mulDivDown(totalShares + VIRTUAL_SHARES, totalAssets + VIRTUAL_ASSETS);
    }

    /// @dev Calculates the value of `shares` quoted in assets, rounding down.
    function toAssetsDown(uint256 shares, uint256 totalAssets, uint256 totalShares) internal pure returns (uint256) {
        return shares.mulDivDown(totalAssets + VIRTUAL_ASSETS, totalShares + VIRTUAL_SHARES);
    }

    /// @dev Calculates the value of `assets` quoted in shares, rounding up.
    function toSharesUp(uint256 assets, uint256 totalAssets, uint256 totalShares) internal pure returns (uint256) {
        return assets.mulDivUp(totalShares + VIRTUAL_SHARES, totalAssets + VIRTUAL_ASSETS);
    }

    /// @dev Calculates the value of `shares` quoted in assets, rounding up.
    function toAssetsUp(uint256 shares, uint256 totalAssets, uint256 totalShares) internal pure returns (uint256) {
        return shares.mulDivUp(totalAssets + VIRTUAL_ASSETS, totalShares + VIRTUAL_SHARES);
    }
}
"
    },
    "src/libraries/MarketParamsLib.sol": {
      "content": "// SPDX-License-Identifier: GPL-2.0-or-later
pragma solidity ^0.8.0;

import {Id, MarketParams} from "../interfaces/IMorpho.sol";

/// @title MarketParamsLib
/// @author Morpho Labs
/// @custom:contact security@morpho.org
/// @notice Library to convert a market to its id.
library MarketParamsLib {
    /// @notice The length of the data used to compute the id of a market.
    /// @dev The length is 6 * 32 because `MarketParams` has 6 variables of 32 bytes each.
    uint256 internal constant MARKET_PARAMS_BYTES_LENGTH = 6 * 32;

    /// @notice Returns the id of the market `marketParams`.
    function id(MarketParams memory marketParams) internal pure returns (Id marketParamsId) {
        assembly ("memory-safe") {
            marketParamsId := keccak256(marketParams, MARKET_PARAMS_BYTES_LENGTH)
        }
    }
}
"
    },
    "lib/forge-std/src/interfaces/IERC4626.sol": {
      "content": "// SPDX-License-Identifier: MIT
pragma solidity >=0.6.2;

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

/// @dev Interface of the ERC4626 "Tokenized Vault Standard", as defined in
/// https://eips.ethereum.org/EIPS/eip-4626
interface IERC4626 is IERC20 {
    event Deposit(address indexed sender, address indexed owner, uint256 assets, uint256 shares);

    event Withdraw(
        address indexed sender, address indexed receiver, address indexed owner, uint256 assets, uint256 shares
    );

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

    /// @notice Returns the total amount of the underlying asset that is “managed” by Vault.
    /// @dev
    /// - 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);

    /// @notice 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.
    /// @dev
    /// - 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);

    /// @notice 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.
    /// @dev
    /// - 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 convertToAssets(uint256 shares) external view returns (uint256 assets);

    /// @notice Returns the maximum amount of the underlying asset that can be deposited into the Vault for the receiver,
    /// through a deposit call.
    /// @dev
    /// - MUST return a limited value if receiver is subject to some deposit limit.
    /// - MUST return 2 ** 256 - 1 if there is no limit on the maximum amount of assets that may be deposited.
    /// - MUST NOT revert.
    function maxDeposit(address receiver) external view returns (uint256 maxAssets);

    /// @notice Allows an on-chain or off-chain user to simulate the effects of their deposit at the current block, given
    /// current on-chain conditions.
    /// @dev
    /// - MUST return as close to and no more than the exact amount of Vault shares that would be minted in a deposit
    ///   call in the same transaction. I.e. deposit should return the same or more shares as previewDeposit if called
    ///   in the same transaction.
    /// - MUST NOT account for deposit limits like those returned from maxDeposit and should always act as though the
    ///   deposit would be accepted, regardless if the user has enough tokens approved, etc.
    /// - MUST be inclusive of deposit fees. Integrators should be aware of the existence of deposit fees.
    /// - MUST NOT revert.
    ///
    /// NOTE: any unfavorable discrepancy between convertToShares and previewDeposit SHOULD be considered slippage in
    /// share price or some other type of condition, meaning the depositor will lose assets by depositing.
    function previewDeposit(uint256 assets) external view returns (uint256 shares);

    /// @notice Mints shares Vault shares to receiver by depositing exactly amount of underlying tokens.
    /// @dev
    /// - MUST emit the Deposit event.
    /// - MAY support an additional flow in which the underlying tokens are owned by the Vault contract before the
    ///   deposit execution, and are accounted for during deposit.
    /// - MUST revert if all of assets cannot be deposited (due to deposit limit being reached, slippage, the user not
    ///   approving enough underlying tokens to the Vault contract, etc).
    ///
    /// NOTE: most implementations will require pre-approval of the Vault with the Vault’s underlying asset token.
    function deposit(uint256 assets, address receiver) external returns (uint256 shares);

    /// @notice Returns the maximum amount of the Vault shares that can be minted for the receiver, through a mint call.
    /// @dev
    /// - MUST return a limited value if receiver is subject to some mint limit.
    /// - MUST return 2 ** 256 - 1 if there is no limit on the maximum amount of shares that may be minted.
    /// - MUST NOT revert.
    function maxMint(address receiver) external view returns (uint256 maxShares);

    /// @notice Allows an on-chain or off-chain user to simulate the effects of their mint at the current block, given
    /// current on-chain conditions.
    /// @dev
    /// - MUST return as close to and no fewer than the exact amount of assets that would be deposited in a mint call
    ///   in the same transaction. I.e. mint should return the same or fewer assets as previewMint if called in the
    ///   same transaction.
    /// - MUST NOT account for mint limits like those returned from maxMint and should always act as though the mint
    ///   would be accepted, regardless if the user has enough tokens approved, etc.
    /// - MUST be inclusive of deposit fees. Integrators should be aware of the existence of deposit fees.
    /// - MUST NOT revert.
    ///
    /// NOTE: any unfavorable discrepancy between convertToAssets and previewMint SHOULD be considered slippage in
    /// share price or some other type of condition, meaning the depositor will lose assets by minting.
    function previewMint(uint256 shares) external view returns (uint256 assets);

    /// @notice Mints exactly shares Vault shares to receiver by depositing amount of underlying tokens.
    /// @dev
    /// - MUST emit the Deposit event.
    /// - MAY support an additional flow in which the underlying tokens are owned by the Vault contract before the mint
    ///   execution, and are accounted for during mint.
    /// - MUST revert if all of shares cannot be minted (due to deposit limit being reached, slippage, the user not
    ///   approving enough underlying tokens to the Vault contract, etc).
    ///
    /// NOTE: most implementations will require pre-approval of the Vault with the Vault’s underlying asset token.
    function mint(uint256 shares, address receiver) external returns (uint256 assets);

    /// @notice Returns the maximum amount of the underlying asset that can be withdrawn from the owner balance in the
    /// Vault, through a withdrawal call.
    /// @dev
    /// - MUST return a limited value if owner is subject to some withdrawal limit or timelock.
    /// - MUST NOT revert.
    function maxWithdraw(address owner) external view returns (uint256 maxAssets);

    /// @notice Allows an on-chain or off-chain user to simulate the effects of their withdrawal at the current block,
    /// given current on-chain conditions.
    /// @dev
    /// - MUST return as close to and no fewer than the exact amount of Vault shares that would be burned in a withdraw
    ///   call in the same transaction. I.e. withdraw should return the same or fewer shares as previewWithdraw if
    ///   called
    ///   in the same transaction.
    /// - MUST NOT account for withdrawal limits like those returned from maxWithdraw and should always act as though
    ///   the withdrawal would be accepted, regardless if the user has enough shares, etc.
    /// - MUST be inclusive of withdrawal fees. Integrators should be aware of the existence of withdrawal fees.
    /// - MUST NOT revert.
    ///
    /// NOTE: any unfavorable discrepancy between convertToShares and previewWithdraw SHOULD be considered slippage in
    /// share price or some other type of condition, meaning the depositor will lose assets by depositing.
    function previewWithdraw(uint256 assets) external view returns (uint256 shares);

    /// @notice Burns shares from owner and sends exactly assets of underlying tokens to receiver.
    /// @dev
    /// - MUST emit the Withdraw event.
    /// - MAY support an additional flow in which the underlying tokens are owned by the Vault contract before the
    ///   withdraw execution, and are accounted for during withdrawal.
    /// - MUST revert if all of assets cannot be withdrawn (due to withdrawal limit being reached, slippage, the owner
    ///   not having enough shares, etc).
    ///
    /// Note that some implementations will require pre-requesting to the Vault before a withdrawal may be performed.
    /// Those methods should be performed separately.
    function withdraw(uint256 assets, address receiver, address owner) external returns (uint256 shares);

    /// @notice Returns the maximum amount of Vault shares that can be redeemed from the owner balance in the Vault,
    /// through a redeem call.
    /// @dev
    /// - MUST return a limited value if owner is subject to some withdrawal limit or timelock.
    /// - MUST return balanceOf(owner) if owner is not subject to any withdrawal limit or timelock.
    /// - MUST NOT revert.
    function maxRedeem(address owner) external view returns (uint256 maxShares);

    /// @notice Allows an on-chain or off-chain user to simulate the effects of their redeemption at the current block,
    /// given current on-chain conditions.
    /// @dev
    /// - MUST return as close to and no more than the exact amount of assets that would be withdrawn in a redeem call
    ///   in the same transaction. I.e. redeem should return the same or more assets as previewRedeem if called in the
    ///   same transaction.
    /// - MUST NOT account for redemption limits like those returned from maxRedeem and should always act as though the
    ///   redemption would be accepted, regardless if the user has enough shares, etc.
    /// - MUST be inclusive of withdrawal fees. Integrators should be aware of the existence of withdrawal fees.
    /// - MUST NOT revert.
    ///
    /// NOTE: any unfavorable discrepancy between convertToAssets and previewRedeem SHOULD be considered slippage in
    /// share price or some other type of condition, meaning the depositor will lose assets by redeeming.
    function previewRedeem(uint256 shares) external view returns (uint256 assets);

    /// @notice Burns exactly shares from owner and sends assets of underlying tokens to receiver.
    /// @dev
    /// - MUST emit the Withdraw event.
    /// - MAY support an additional flow in which the underlying tokens are owned by the Vault contract before the
    ///   redeem execution, and are accounted for during redeem.
    /// - MUST revert if all of shares cannot be redeemed (due to withdrawal limit being reached, slippage, the owner
    ///   not having enough shares, etc).
    ///
    /// NOTE: some implementations will require pre-requesting to the Vault before a withdrawal may be performed.
    /// Those methods should be performed separately.
    function redeem(uint256 shares, address receiver, address owner) external returns (uint256 assets);
}
"
    },
    "src/libraries/MathLib.sol": {
      "content": "// SPDX-License-Identifier: GPL-2.0-or-later
pragma solidity ^0.8.0;

uint256 constant WAD = 1e18;

/// @title MathLib
/// @author Morpho Labs
/// @custom:contact security@morpho.org
/// @notice Library to manage fixed-point arithmetic.
library MathLib {
    /// @dev Returns (`x` * `y`) / `WAD` rounded down.
    function wMulDown(uint256 x, uint256 y) internal pure returns (uint256) {
        return mulDivDown(x, y, WAD);
    }

    /// @dev Returns (`x` * `WAD`) / `y` rounded down.
    function wDivDown(uint256 x, uint256 y) internal pure returns (uint256) {
        return mulDivDown(x, WAD, y);
    }

    /// @dev Returns (`x` * `WAD`) / `y` rounded up.
    function wDivUp(uint256 x, uint256 y) internal pure returns (uint256) {
        return mulDivUp(x, WAD, y);
    }

    /// @dev Returns (`x` * `y`) / `d` rounded down.
    function mulDivDown(uint256 x, uint256 y, uint256 d) internal pure returns (uint256) {
        return (x * y) / d;
    }

    /// @dev Returns (`x` * `y`) / `d` rounded up.
    function mulDivUp(uint256 x, uint256 y, uint256 d) internal pure returns (uint256) {
        return (x * y + (d - 1)) / d;
    }

    /// @dev Returns the sum of the first three non-zero terms of a Taylor expansion of e^(nx) - 1, to approximate a
    /// continuous compound interest rate.
    function wTaylorCompounded(uint256 x, uint256 n) internal pure returns (uint256) {
        uint256 firstTerm = x * n;
        uint256 secondTerm = mulDivDown(firstTerm, firstTerm, 2 * WAD);
        uint256 thirdTerm = mulDivDown(secondTerm, firstTerm, 3 * WAD);

        return firstTerm + secondTerm + thirdTerm;
    }

    /// @dev Computes the inverse of wTaylorCompounded, finding the rate that produces the given growth factor.
    /// Uses a 3-term Taylor series approximation of ln(x) to solve for rate in the compound interest formula.
    /// Formula: rate = ln(x) / n ≈ [(x-1) - (x-1)²/2 + (x-1)³/3] / n
    ///
    /// Accuracy notes:
    /// - The Taylor approximation of ln(x) is most accurate for x close to 1
    /// - At growth factor x = 1.69*WAD (69% growth), approximation error < 2%
    /// - At growth factor x = 2*WAD (100% growth, where ln(2) ≈ 0.69), approximation error < 5%
    /// - Accuracy decreases for larger growth factors; not recommended for x > 2.5*WAD (150% growth)
    ///
    /// Example: If debt grew from 1000 to 1105 over 1 year (10.5% growth):
    /// - x = 1.105*WAD (growth factor)
    /// - n = 365 days (time period)
    /// - Returns ~10% APR as rate per second
    ///
    /// @param x The growth factor scaled by WAD (e.g., 1.1*WAD for 10% growth). Must be >= WAD.
    /// @param n The time period over which the growth occurred (in seconds)
    /// @return The continuously compounded rate per second that would produce this growth (scaled by WAD)
    function wInverseTaylorCompounded(uint256 x, uint256 n) internal pure returns (uint256) {
        require(x >= WAD, "ln undefined");

        uint256 firstTerm = x - WAD;
        uint256 secondTerm = wMulDown(firstTerm, firstTerm);
        uint256 thirdTerm = wMulDown(secondTerm, firstTerm);

        uint256 series = firstTerm - secondTerm / 2 + thirdTerm / 3;

        return series / n;
    }
}
"
    },
    "lib/forge-std/src/interfaces/IERC20.sol": {
      "content": "// SPDX-License-Identifier: MIT
pragma solidity >=0.6.2;

/// @dev Interface of the ERC20 standard as defined in the EIP.
/// @dev This includes the optional name, symbol, and decimals metadata.
interface IERC20 {
    /// @dev Emitted when `value` tokens are moved from one account (`from`) to another (`to`).
    event Transfer(address indexed from, address indexed to, uint256 value);

    /// @dev Emitted when the allowance of a `spender` for an `owner` is set, where `value`
    /// is the new allowance.
    event Approval(address indexed owner, address indexed spender, uint256 value);

    /// @notice Returns the amount of tokens in existence.
    function totalSupply() external view returns (uint256);

    /// @notice Returns the amount of tokens owned by `account`.
    function balanceOf(address account) external view returns (uint256);

    /// @notice Moves `amount` tokens from the caller's account to `to`.
    function transfer(address to, uint256 amount) external returns (bool);

    /// @notice Returns the remaining number of tokens that `spender` is allowed
    /// to spend on behalf of `owner`
    function allowance(address owner, address spender) external view returns (uint256);

    /// @notice Sets `amount` as the allowance of `spender` over the caller's tokens.
    /// @dev Be aware of front-running risks: https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729
    function approve(address spender, uint256 amount) external returns (bool);

    /// @notice Moves `amount` tokens from `from` to `to` using the allowance mechanism.
    /// `amount` is then deducted from the caller's allowance.
    function transferFrom(address from, address to, uint256 amount) external returns (bool);

    /// @notice Returns the name of the token.
    function name() external view returns (string memory);

    /// @notice Returns the symbol of the token.
    function symbol() external view returns (string memory);

    /// @notice Returns the decimals places of the token.
    function decimals() external view returns (uint8);
}
"
    }
  },
  "settings": {
    "remappings": [
      "forge-std/=lib/forge-std/src/",
      "halmos-cheatcodes/=lib/halmos-cheatcodes/src/",
      "@tokenized-strategy/=lib/tokenized-strategy/src/",
      "@periphery/=lib/tokenized-strategy-periphery/src/",
      "openzeppelin-foundry-upgrades/=lib/openzeppelin-foundry-upgrades/src/",
      "@openzeppelin/=lib/tokenized-strategy-periphery/lib/openzeppelin-contracts/",
      "@yearn-vaults/=lib/tokenized-strategy-periphery/lib/yearn-vaults-v3/contracts/",
      "ds-test/=lib/forge-std/lib/ds-test/src/",
      "erc4626-tests/=lib/tokenized-strategy/lib/erc4626-tests/",
      "openzeppelin-contracts/=lib/tokenized-strategy/lib/openzeppelin-contracts/",
      "openzeppelin/=lib/openzeppelin/",
      "tokenized-strategy-periphery/=lib/tokenized-strategy-periphery/",
      "tokenized-strategy/=lib/tokenized-strategy/",
      "yearn-vaults-v3/=lib/tokenized-strategy-periphery/lib/yearn-vaults-v3/"
    ],
    "optimizer": {
      "enabled": true,
      "runs": 999999
    },
    "metadata": {
      "useLiteralContent": false,
      "bytecodeHash": "none",
      "appendCBOR": false
    },
    "outputSelection": {
      "*": {
        "*": [
          "evm.bytecode",
          "evm.deployedBytecode",
          "devdoc",
          "userdoc",
          "metadata",
          "abi"
        ]
      }
    },
    "evmVersion": "shanghai",
    "viaIR": true
  }
}}

Tags:
ERC20, Multisig, Mintable, Liquidity, Yield, Upgradeable, Multi-Signature, Factory, Oracle|addr:0x2a66f992bf227d2e50ef19edd21503c3c4f3f682|verified:true|block:23628226|tx:0x7aabe74ce44d096fdff902aff25cd1fe13ca2ad0b125b894977f596e11592c73|first_check:1761228406

Submitted on: 2025-10-23 16:06:48

Comments

Log in to comment.

No comments yet.