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
}
}}
Submitted on: 2025-10-23 16:06:48
Comments
Log in to comment.
No comments yet.