// SPDX-License-Identifier: GPL-2.0-or-laterpragmasolidity ^0.8.0;/// @dev The maximum fee a market can have (25%).uint256constant MAX_FEE =0.25e18;
/// @dev Oracle price scale.uint256constant ORACLE_PRICE_SCALE =1e36;
/// @dev Liquidation cursor.uint256constant LIQUIDATION_CURSOR =0.3e18;
/// @dev Max liquidation incentive factor.uint256constant MAX_LIQUIDATION_INCENTIVE_FACTOR =1.15e18;
/// @dev The EIP-712 typeHash for EIP712Domain.bytes32constant DOMAIN_TYPEHASH =keccak256("EIP712Domain(uint256 chainId,address verifyingContract)");
/// @dev The EIP-712 typeHash for Authorization.bytes32constant AUTHORIZATION_TYPEHASH =keccak256("Authorization(address authorizer,address authorized,bool isAuthorized,uint256 nonce,uint256 deadline)");
Contract Source Code
File 2 of 14: ErrorsLib.sol
// SPDX-License-Identifier: GPL-2.0-or-laterpragmasolidity ^0.8.0;/// @title ErrorsLib/// @author Morpho Labs/// @custom:contact security@morpho.org/// @notice Library exposing error messages.libraryErrorsLib{
/// @notice Thrown when the caller is not the owner.stringinternalconstant NOT_OWNER ="not owner";
/// @notice Thrown when the LLTV to enable exceeds the maximum LLTV.stringinternalconstant MAX_LLTV_EXCEEDED ="max LLTV exceeded";
/// @notice Thrown when the fee to set exceeds the maximum fee.stringinternalconstant MAX_FEE_EXCEEDED ="max fee exceeded";
/// @notice Thrown when the value is already set.stringinternalconstant ALREADY_SET ="already set";
/// @notice Thrown when the IRM is not enabled at market creation.stringinternalconstant IRM_NOT_ENABLED ="IRM not enabled";
/// @notice Thrown when the LLTV is not enabled at market creation.stringinternalconstant LLTV_NOT_ENABLED ="LLTV not enabled";
/// @notice Thrown when the market is already created.stringinternalconstant MARKET_ALREADY_CREATED ="market already created";
/// @notice Thrown when a token to transfer doesn't have code.stringinternalconstant NO_CODE ="no code";
/// @notice Thrown when the market is not created.stringinternalconstant MARKET_NOT_CREATED ="market not created";
/// @notice Thrown when not exactly one of the input amount is zero.stringinternalconstant INCONSISTENT_INPUT ="inconsistent input";
/// @notice Thrown when zero assets is passed as input.stringinternalconstant ZERO_ASSETS ="zero assets";
/// @notice Thrown when a zero address is passed as input.stringinternalconstant ZERO_ADDRESS ="zero address";
/// @notice Thrown when the caller is not authorized to conduct an action.stringinternalconstant UNAUTHORIZED ="unauthorized";
/// @notice Thrown when the collateral is insufficient to `borrow` or `withdrawCollateral`.stringinternalconstant INSUFFICIENT_COLLATERAL ="insufficient collateral";
/// @notice Thrown when the liquidity is insufficient to `withdraw` or `borrow`.stringinternalconstant INSUFFICIENT_LIQUIDITY ="insufficient liquidity";
/// @notice Thrown when the position to liquidate is healthy.stringinternalconstant HEALTHY_POSITION ="position is healthy";
/// @notice Thrown when the authorization signature is invalid.stringinternalconstant INVALID_SIGNATURE ="invalid signature";
/// @notice Thrown when the authorization signature is expired.stringinternalconstant SIGNATURE_EXPIRED ="signature expired";
/// @notice Thrown when the nonce is invalid.stringinternalconstant INVALID_NONCE ="invalid nonce";
/// @notice Thrown when a token transfer reverted.stringinternalconstant TRANSFER_REVERTED ="transfer reverted";
/// @notice Thrown when a token transfer returned false.stringinternalconstant TRANSFER_RETURNED_FALSE ="transfer returned false";
/// @notice Thrown when a token transferFrom reverted.stringinternalconstant TRANSFER_FROM_REVERTED ="transferFrom reverted";
/// @notice Thrown when a token transferFrom returned falsestringinternalconstant TRANSFER_FROM_RETURNED_FALSE ="transferFrom returned false";
/// @notice Thrown when the maximum uint128 is exceeded.stringinternalconstant MAX_UINT128_EXCEEDED ="max uint128 exceeded";
}
Contract Source Code
File 3 of 14: EventsLib.sol
// SPDX-License-Identifier: GPL-2.0-or-laterpragmasolidity ^0.8.0;import {Id, MarketParams} from"../interfaces/IMorpho.sol";
/// @title EventsLib/// @author Morpho Labs/// @custom:contact security@morpho.org/// @notice Library exposing events.libraryEventsLib{
/// @notice Emitted when setting a new owner./// @param newOwner The new owner of the contract.eventSetOwner(addressindexed newOwner);
/// @notice Emitted when setting a new fee./// @param id The market id./// @param newFee The new fee.eventSetFee(Id indexed id, uint256 newFee);
/// @notice Emitted when setting a new fee recipient./// @param newFeeRecipient The new fee recipient.eventSetFeeRecipient(addressindexed newFeeRecipient);
/// @notice Emitted when enabling an IRM./// @param irm The IRM that was enabled.eventEnableIrm(addressindexed irm);
/// @notice Emitted when enabling an LLTV./// @param lltv The LLTV that was enabled.eventEnableLltv(uint256 lltv);
/// @notice Emitted when creating a market./// @param id The market id./// @param marketParams The market that was created.eventCreateMarket(Id indexed id, MarketParams marketParams);
/// @notice Emitted on supply of assets./// @dev Warning: `feeRecipient` receives some shares during interest accrual without any supply event emitted./// @param id The market id./// @param caller The caller./// @param onBehalf The owner of the modified position./// @param assets The amount of assets supplied./// @param shares The amount of shares minted.eventSupply(Id indexed id, addressindexed caller, addressindexed onBehalf, uint256 assets, uint256 shares);
/// @notice Emitted on withdrawal of assets./// @param id The market id./// @param caller The caller./// @param onBehalf The owner of the modified position./// @param receiver The address that received the withdrawn assets./// @param assets The amount of assets withdrawn./// @param shares The amount of shares burned.eventWithdraw(
Id indexed id,
address caller,
addressindexed onBehalf,
addressindexed receiver,
uint256 assets,
uint256 shares
);
/// @notice Emitted on borrow of assets./// @param id The market id./// @param caller The caller./// @param onBehalf The owner of the modified position./// @param receiver The address that received the borrowed assets./// @param assets The amount of assets borrowed./// @param shares The amount of shares minted.eventBorrow(
Id indexed id,
address caller,
addressindexed onBehalf,
addressindexed receiver,
uint256 assets,
uint256 shares
);
/// @notice Emitted on repayment of assets./// @param id The market id./// @param caller The caller./// @param onBehalf The owner of the modified position./// @param assets The amount of assets repaid. May be 1 over the corresponding market's `totalBorrowAssets`./// @param shares The amount of shares burned.eventRepay(Id indexed id, addressindexed caller, addressindexed onBehalf, uint256 assets, uint256 shares);
/// @notice Emitted on supply of collateral./// @param id The market id./// @param caller The caller./// @param onBehalf The owner of the modified position./// @param assets The amount of collateral supplied.eventSupplyCollateral(Id indexed id, addressindexed caller, addressindexed onBehalf, uint256 assets);
/// @notice Emitted on withdrawal of collateral./// @param id The market id./// @param caller The caller./// @param onBehalf The owner of the modified position./// @param receiver The address that received the withdrawn collateral./// @param assets The amount of collateral withdrawn.eventWithdrawCollateral(
Id indexed id, address caller, addressindexed onBehalf, addressindexed receiver, uint256 assets
);
/// @notice Emitted on liquidation of a position./// @param id The market id./// @param caller The caller./// @param borrower The borrower of the position./// @param repaidAssets The amount of assets repaid. May be 1 over the corresponding market's `totalBorrowAssets`./// @param repaidShares The amount of shares burned./// @param seizedAssets The amount of collateral seized./// @param badDebtAssets The amount of assets of bad debt realized./// @param badDebtShares The amount of borrow shares of bad debt realized.eventLiquidate(
Id indexed id,
addressindexed caller,
addressindexed borrower,
uint256 repaidAssets,
uint256 repaidShares,
uint256 seizedAssets,
uint256 badDebtAssets,
uint256 badDebtShares
);
/// @notice Emitted on flash loan./// @param caller The caller./// @param token The token that was flash loaned./// @param assets The amount that was flash loaned.eventFlashLoan(addressindexed caller, addressindexed token, uint256 assets);
/// @notice Emitted when setting an authorization./// @param caller The caller./// @param authorizer The authorizer address./// @param authorized The authorized address./// @param newIsAuthorized The new authorization status.eventSetAuthorization(addressindexed caller, addressindexed authorizer, addressindexed authorized, bool newIsAuthorized
);
/// @notice Emitted when setting an authorization with a signature./// @param caller The caller./// @param authorizer The authorizer address./// @param usedNonce The nonce that was used.eventIncrementNonce(addressindexed caller, addressindexed authorizer, uint256 usedNonce);
/// @notice Emitted when accruing interest./// @param id The market id./// @param prevBorrowRate The previous borrow rate./// @param interest The amount of interest accrued./// @param feeShares The amount of shares minted as fee.eventAccrueInterest(Id indexed id, uint256 prevBorrowRate, uint256 interest, uint256 feeShares);
}
Contract Source Code
File 4 of 14: IERC20.sol
// SPDX-License-Identifier: GPL-2.0-or-laterpragmasolidity >=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).interfaceIERC20{}
Contract Source Code
File 5 of 14: IIrm.sol
// SPDX-License-Identifier: GPL-2.0-or-laterpragmasolidity >=0.5.0;import {MarketParams, Market} from"./IMorpho.sol";
/// @title IIrm/// @author Morpho Labs/// @custom:contact security@morpho.org/// @notice Interface that Interest Rate Models (IRMs) used by Morpho must implement.interfaceIIrm{
/// @notice Returns the borrow rate per second (scaled by WAD) of the market `marketParams`./// @dev Assumes that `market` corresponds to `marketParams`.functionborrowRate(MarketParams memory marketParams, Market memory market) externalreturns (uint256);
/// @notice Returns the borrow rate per second (scaled by WAD) of the market `marketParams` without modifying any/// storage./// @dev Assumes that `market` corresponds to `marketParams`.functionborrowRateView(MarketParams memory marketParams, Market memory market) externalviewreturns (uint256);
}
Contract Source Code
File 6 of 14: IMorpho.sol
// SPDX-License-Identifier: GPL-2.0-or-laterpragmasolidity >=0.5.0;type Id isbytes32;
structMarketParams {
address loanToken;
address collateralToken;
address oracle;
address irm;
uint256 lltv;
}
/// @dev Warning: For `feeRecipient`, `supplyShares` does not contain the accrued shares since the last interest/// accrual.structPosition {
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.structMarket {
uint128 totalSupplyAssets;
uint128 totalSupplyShares;
uint128 totalBorrowAssets;
uint128 totalBorrowShares;
uint128 lastUpdate;
uint128 fee;
}
structAuthorization {
address authorizer;
address authorized;
bool isAuthorized;
uint256 nonce;
uint256 deadline;
}
structSignature {
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.interfaceIMorphoBase{
/// @notice The EIP-712 domain separator./// @dev Warning: Every EIP-712 signed message based on this domain separator can be reused on another chain sharing/// the same chain id because the domain separator would be the same.functionDOMAIN_SEPARATOR() externalviewreturns (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.functionowner() externalviewreturns (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.functionfeeRecipient() externalviewreturns (address);
/// @notice Whether the `irm` is enabled.functionisIrmEnabled(address irm) externalviewreturns (bool);
/// @notice Whether the `lltv` is enabled.functionisLltvEnabled(uint256 lltv) externalviewreturns (bool);
/// @notice Whether `authorized` is authorized to modify `authorizer`'s position on all markets./// @dev Anyone is authorized to modify their own positions, regardless of this variable.functionisAuthorized(address authorizer, address authorized) externalviewreturns (bool);
/// @notice The `authorizer`'s current nonce. Used to prevent replay attacks with EIP-712 signatures.functionnonce(address authorizer) externalviewreturns (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.functionsetOwner(address newOwner) external;
/// @notice Enables `irm` as a possible IRM for market creation./// @dev Warning: It is not possible to disable an IRM.functionenableIrm(address irm) external;
/// @notice Enables `lltv` as a possible LLTV for market creation./// @dev Warning: It is not possible to disable a LLTV.functionenableLltv(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.functionsetFee(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.functionsetFeeRecipient(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 properties on the market's dependencies that could break Morpho's liveness properties/// (funds could get stuck):/// - The token can revert on `transfer` and `transferFrom` for a reason other than an approval or balance issue./// - A very high amount of assets (~1e35) supplied or borrowed can make the computation of `toSharesUp` and/// `toSharesDown` overflow./// - The IRM can revert on `borrowRate`./// - A very high borrow rate returned by the IRM can make the computation of `interest` in `_accrueInterest`/// overflow./// - The oracle can revert on `price`. Note that this can be used to prevent `borrow`, `withdrawCollateral` and/// `liquidate` from being used under certain market conditions./// - A very high price returned by the oracle can make the computation of `maxBorrow` in `_isHealthy` overflow, or/// the computation of `assetsRepaid` in `liquidate` 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.functioncreateMarket(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.functionsupply(
MarketParams memory marketParams,
uint256 assets,
uint256 shares,
address onBehalf,
bytesmemory data
) externalreturns (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.functionwithdraw(
MarketParams memory marketParams,
uint256 assets,
uint256 shares,
address onBehalf,
address receiver
) externalreturns (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.functionborrow(
MarketParams memory marketParams,
uint256 assets,
uint256 shares,
address onBehalf,
address receiver
) externalreturns (uint256 assetsBorrowed, uint256 sharesBorrowed);
/// @notice Repays `assets` or `shares` on behalf of `onBehalf`, optionally calling back the caller's/// `onMorphoReplay` 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.functionrepay(
MarketParams memory marketParams,
uint256 assets,
uint256 shares,
address onBehalf,
bytesmemory data
) externalreturns (uint256 assetsRepaid, uint256 sharesRepaid);
/// @notice Supplies `assets` of collateral on behalf of `onBehalf`, optionally calling back the caller's/// `onMorphoSupplyCollateral` function with the given `data`./// @dev Interest are not accrued since it's not required and it saves gas./// @dev Supplying a large amount can revert for overflow./// @param marketParams The market to supply collateral to./// @param assets The amount of collateral to supply./// @param onBehalf The address that will own the increased collateral position./// @param data Arbitrary data to pass to the `onMorphoSupplyCollateral` callback. Pass empty data if not needed.functionsupplyCollateral(MarketParams memory marketParams, uint256 assets, address onBehalf, bytesmemory data)
external;
/// @notice Withdraws `assets` of collateral on behalf of `onBehalf` and sends the assets to `receiver`./// @dev `msg.sender` must be authorized to manage `onBehalf`'s positions./// @dev Withdrawing an amount corresponding to more collateral than supplied will revert for underflow./// @param marketParams The market to withdraw collateral from./// @param assets The amount of collateral to withdraw./// @param onBehalf The address of the owner of the collateral position./// @param receiver The address that will receive the collateral assets.functionwithdrawCollateral(MarketParams memory marketParams, uint256 assets, address onBehalf, address receiver)
external;
/// @notice Liquidates the given `repaidShares` of debt asset or seize the given `seizedAssets` of collateral on the/// given market `marketParams` of the given `borrower`'s position, optionally calling back the caller's/// `onMorphoLiquidate` function with the given `data`./// @dev Either `seizedAssets` or `repaidShares` should be zero./// @dev Seizing more than the collateral balance will underflow and revert without any error message./// @dev Repaying more than the borrow balance will underflow and revert without any error message./// @dev An attacker can front-run a liquidation with a small repay making the transaction revert for underflow./// @param marketParams The market of the position./// @param borrower The owner of the position./// @param seizedAssets The amount of collateral to seize./// @param repaidShares The amount of shares to repay./// @param data Arbitrary data to pass to the `onMorphoLiquidate` callback. Pass empty data if not needed./// @return The amount of assets seized./// @return The amount of assets repaid.functionliquidate(
MarketParams memory marketParams,
address borrower,
uint256 seizedAssets,
uint256 repaidShares,
bytesmemory data
) externalreturns (uint256, uint256);
/// @notice Executes a flash loan./// @dev Flash loans have access to the whole balance of the contract (the liquidity and deposited collateral of all/// markets combined, plus donations)./// @dev Warning: Not ERC-3156 compliant but compatibility is easily reached:/// - `flashFee` is zero./// - `maxFlashLoan` is the token's balance of this contract./// - The receiver of `assets` is the caller./// @param token The token to flash loan./// @param assets The amount of assets to flash loan./// @param data Arbitrary data to pass to the `onMorphoFlashLoan` callback.functionflashLoan(address token, uint256 assets, bytescalldata data) external;
/// @notice Sets the authorization for `authorized` to manage `msg.sender`'s positions./// @param authorized The authorized address./// @param newIsAuthorized The new authorization status.functionsetAuthorization(address authorized, bool newIsAuthorized) external;
/// @notice Sets the authorization for `authorization.authorized` to manage `authorization.authorizer`'s positions./// @dev Warning: Reverts if the signature has already been submitted./// @dev The signature is malleable, but it has no impact on the security here./// @dev The nonce is passed as argument to be able to revert with a different error message./// @param authorization The `Authorization` struct./// @param signature The signature.functionsetAuthorizationWithSig(Authorization calldata authorization, Signature calldata signature) external;
/// @notice Accrues interest for the given market `marketParams`.functionaccrueInterest(MarketParams memory marketParams) external;
/// @notice Returns the data stored on the different `slots`.functionextSloads(bytes32[] memory slots) externalviewreturns (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.interfaceIMorphoStaticTypingisIMorphoBase{
/// @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.functionposition(Id id, address user)
externalviewreturns (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.functionmarket(Id id)
externalviewreturns (uint128 totalSupplyAssets,
uint128 totalSupplyShares,
uint128 totalBorrowAssets,
uint128 totalBorrowShares,
uint128 lastUpdate,
uint128 fee
);
/// @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`.functionidToMarketParams(Id id)
externalviewreturns (address loanToken, address collateralToken, address oracle, address irm, uint256 lltv);
}
/// @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.interfaceIMorphoisIMorphoBase{
/// @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.functionposition(Id id, address user) externalviewreturns (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.functionmarket(Id id) externalviewreturns (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`.functionidToMarketParams(Id id) externalviewreturns (MarketParams memory);
}
Contract Source Code
File 7 of 14: IMorphoCallbacks.sol
// SPDX-License-Identifier: GPL-2.0-or-laterpragmasolidity >=0.5.0;/// @title IMorphoLiquidateCallback/// @notice Interface that liquidators willing to use `liquidate`'s callback must implement.interfaceIMorphoLiquidateCallback{
/// @notice Callback called when a liquidation occurs./// @dev The callback is called only if data is not empty./// @param repaidAssets The amount of repaid assets./// @param data Arbitrary data passed to the `liquidate` function.functiononMorphoLiquidate(uint256 repaidAssets, bytescalldata data) external;
}
/// @title IMorphoRepayCallback/// @notice Interface that users willing to use `repay`'s callback must implement.interfaceIMorphoRepayCallback{
/// @notice Callback called when a repayment occurs./// @dev The callback is called only if data is not empty./// @param assets The amount of repaid assets./// @param data Arbitrary data passed to the `repay` function.functiononMorphoRepay(uint256 assets, bytescalldata data) external;
}
/// @title IMorphoSupplyCallback/// @notice Interface that users willing to use `supply`'s callback must implement.interfaceIMorphoSupplyCallback{
/// @notice Callback called when a supply occurs./// @dev The callback is called only if data is not empty./// @param assets The amount of supplied assets./// @param data Arbitrary data passed to the `supply` function.functiononMorphoSupply(uint256 assets, bytescalldata data) external;
}
/// @title IMorphoSupplyCollateralCallback/// @notice Interface that users willing to use `supplyCollateral`'s callback must implement.interfaceIMorphoSupplyCollateralCallback{
/// @notice Callback called when a supply of collateral occurs./// @dev The callback is called only if data is not empty./// @param assets The amount of supplied collateral./// @param data Arbitrary data passed to the `supplyCollateral` function.functiononMorphoSupplyCollateral(uint256 assets, bytescalldata data) external;
}
/// @title IMorphoFlashLoanCallback/// @notice Interface that users willing to use `flashLoan`'s callback must implement.interfaceIMorphoFlashLoanCallback{
/// @notice Callback called when a flash loan occurs./// @dev The callback is called only if data is not empty./// @param assets The amount of assets that was flash loaned./// @param data Arbitrary data passed to the `flashLoan` function.functiononMorphoFlashLoan(uint256 assets, bytescalldata data) external;
}
Contract Source Code
File 8 of 14: IOracle.sol
// SPDX-License-Identifier: GPL-2.0-or-laterpragmasolidity >=0.5.0;/// @title IOracle/// @author Morpho Labs/// @custom:contact security@morpho.org/// @notice Interface that oracles used by Morpho must implement./// @dev It is the user's responsibility to select markets with safe oracles.interfaceIOracle{
/// @notice Returns the price of 1 asset of collateral token quoted in 1 asset of loan token, scaled by 1e36./// @dev It corresponds to the price of 10**(collateral token decimals) assets of collateral token quoted in/// 10**(loan token decimals) assets of loan token with `36 + loan token decimals - collateral token decimals`/// decimals of precision.functionprice() externalviewreturns (uint256);
}
Contract Source Code
File 9 of 14: MarketParamsLib.sol
// SPDX-License-Identifier: GPL-2.0-or-laterpragmasolidity ^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.libraryMarketParamsLib{
/// @notice The length of the data used to compute the id of a market./// @dev The length is 5 * 32 because `MarketParams` has 5 variables of 32 bytes each.uint256internalconstant MARKET_PARAMS_BYTES_LENGTH =5*32;
/// @notice Returns the id of the market `marketParams`.functionid(MarketParams memory marketParams) internalpurereturns (Id marketParamsId) {
assembly ("memory-safe") {
marketParamsId :=keccak256(marketParams, MARKET_PARAMS_BYTES_LENGTH)
}
}
}
// SPDX-License-Identifier: BUSL-1.1pragmasolidity 0.8.19;import {
Id,
IMorphoStaticTyping,
IMorphoBase,
MarketParams,
Position,
Market,
Authorization,
Signature
} from"./interfaces/IMorpho.sol";
import {
IMorphoLiquidateCallback,
IMorphoRepayCallback,
IMorphoSupplyCallback,
IMorphoSupplyCollateralCallback,
IMorphoFlashLoanCallback
} from"./interfaces/IMorphoCallbacks.sol";
import {IIrm} from"./interfaces/IIrm.sol";
import {IERC20} from"./interfaces/IERC20.sol";
import {IOracle} from"./interfaces/IOracle.sol";
import"./libraries/ConstantsLib.sol";
import {UtilsLib} from"./libraries/UtilsLib.sol";
import {EventsLib} from"./libraries/EventsLib.sol";
import {ErrorsLib} from"./libraries/ErrorsLib.sol";
import {MathLib, WAD} from"./libraries/MathLib.sol";
import {SharesMathLib} from"./libraries/SharesMathLib.sol";
import {MarketParamsLib} from"./libraries/MarketParamsLib.sol";
import {SafeTransferLib} from"./libraries/SafeTransferLib.sol";
/// @title Morpho/// @author Morpho Labs/// @custom:contact security@morpho.org/// @notice The Morpho contract.contractMorphoisIMorphoStaticTyping{
usingMathLibforuint128;
usingMathLibforuint256;
usingUtilsLibforuint256;
usingSharesMathLibforuint256;
usingSafeTransferLibforIERC20;
usingMarketParamsLibforMarketParams;
/* IMMUTABLES *//// @inheritdoc IMorphoBasebytes32publicimmutable DOMAIN_SEPARATOR;
/* STORAGE *//// @inheritdoc IMorphoBaseaddresspublic owner;
/// @inheritdoc IMorphoBaseaddresspublic feeRecipient;
/// @inheritdoc IMorphoStaticTypingmapping(Id =>mapping(address=> Position)) public position;
/// @inheritdoc IMorphoStaticTypingmapping(Id => Market) public market;
/// @inheritdoc IMorphoBasemapping(address=>bool) public isIrmEnabled;
/// @inheritdoc IMorphoBasemapping(uint256=>bool) public isLltvEnabled;
/// @inheritdoc IMorphoBasemapping(address=>mapping(address=>bool)) public isAuthorized;
/// @inheritdoc IMorphoBasemapping(address=>uint256) public nonce;
/// @inheritdoc IMorphoStaticTypingmapping(Id => MarketParams) public idToMarketParams;
/* CONSTRUCTOR *//// @param newOwner The new owner of the contract.constructor(address newOwner) {
require(newOwner !=address(0), ErrorsLib.ZERO_ADDRESS);
DOMAIN_SEPARATOR =keccak256(abi.encode(DOMAIN_TYPEHASH, block.chainid, address(this)));
owner = newOwner;
emit EventsLib.SetOwner(newOwner);
}
/* MODIFIERS *//// @dev Reverts if the caller is not the owner.modifieronlyOwner() {
require(msg.sender== owner, ErrorsLib.NOT_OWNER);
_;
}
/* ONLY OWNER FUNCTIONS *//// @inheritdoc IMorphoBasefunctionsetOwner(address newOwner) externalonlyOwner{
require(newOwner != owner, ErrorsLib.ALREADY_SET);
owner = newOwner;
emit EventsLib.SetOwner(newOwner);
}
/// @inheritdoc IMorphoBasefunctionenableIrm(address irm) externalonlyOwner{
require(!isIrmEnabled[irm], ErrorsLib.ALREADY_SET);
isIrmEnabled[irm] =true;
emit EventsLib.EnableIrm(irm);
}
/// @inheritdoc IMorphoBasefunctionenableLltv(uint256 lltv) externalonlyOwner{
require(!isLltvEnabled[lltv], ErrorsLib.ALREADY_SET);
require(lltv < WAD, ErrorsLib.MAX_LLTV_EXCEEDED);
isLltvEnabled[lltv] =true;
emit EventsLib.EnableLltv(lltv);
}
/// @inheritdoc IMorphoBasefunctionsetFee(MarketParams memory marketParams, uint256 newFee) externalonlyOwner{
Id id = marketParams.id();
require(market[id].lastUpdate !=0, ErrorsLib.MARKET_NOT_CREATED);
require(newFee != market[id].fee, ErrorsLib.ALREADY_SET);
require(newFee <= MAX_FEE, ErrorsLib.MAX_FEE_EXCEEDED);
// Accrue interest using the previous fee set before changing it.
_accrueInterest(marketParams, id);
// Safe "unchecked" cast.
market[id].fee =uint128(newFee);
emit EventsLib.SetFee(id, newFee);
}
/// @inheritdoc IMorphoBasefunctionsetFeeRecipient(address newFeeRecipient) externalonlyOwner{
require(newFeeRecipient != feeRecipient, ErrorsLib.ALREADY_SET);
feeRecipient = newFeeRecipient;
emit EventsLib.SetFeeRecipient(newFeeRecipient);
}
/* MARKET CREATION *//// @inheritdoc IMorphoBasefunctioncreateMarket(MarketParams memory marketParams) external{
Id id = marketParams.id();
require(isIrmEnabled[marketParams.irm], ErrorsLib.IRM_NOT_ENABLED);
require(isLltvEnabled[marketParams.lltv], ErrorsLib.LLTV_NOT_ENABLED);
require(market[id].lastUpdate ==0, ErrorsLib.MARKET_ALREADY_CREATED);
// Safe "unchecked" cast.
market[id].lastUpdate =uint128(block.timestamp);
idToMarketParams[id] = marketParams;
emit EventsLib.CreateMarket(id, marketParams);
// Call to initialize the IRM in case it is stateful.if (marketParams.irm !=address(0)) IIrm(marketParams.irm).borrowRate(marketParams, market[id]);
}
/* SUPPLY MANAGEMENT *//// @inheritdoc IMorphoBasefunctionsupply(
MarketParams memory marketParams,
uint256 assets,
uint256 shares,
address onBehalf,
bytescalldata data
) externalreturns (uint256, uint256) {
Id id = marketParams.id();
require(market[id].lastUpdate !=0, ErrorsLib.MARKET_NOT_CREATED);
require(UtilsLib.exactlyOneZero(assets, shares), ErrorsLib.INCONSISTENT_INPUT);
require(onBehalf !=address(0), ErrorsLib.ZERO_ADDRESS);
_accrueInterest(marketParams, id);
if (assets >0) shares = assets.toSharesDown(market[id].totalSupplyAssets, market[id].totalSupplyShares);
else assets = shares.toAssetsUp(market[id].totalSupplyAssets, market[id].totalSupplyShares);
position[id][onBehalf].supplyShares += shares;
market[id].totalSupplyShares += shares.toUint128();
market[id].totalSupplyAssets += assets.toUint128();
emit EventsLib.Supply(id, msg.sender, onBehalf, assets, shares);
if (data.length>0) IMorphoSupplyCallback(msg.sender).onMorphoSupply(assets, data);
IERC20(marketParams.loanToken).safeTransferFrom(msg.sender, address(this), assets);
return (assets, shares);
}
/// @inheritdoc IMorphoBasefunctionwithdraw(
MarketParams memory marketParams,
uint256 assets,
uint256 shares,
address onBehalf,
address receiver
) externalreturns (uint256, uint256) {
Id id = marketParams.id();
require(market[id].lastUpdate !=0, ErrorsLib.MARKET_NOT_CREATED);
require(UtilsLib.exactlyOneZero(assets, shares), ErrorsLib.INCONSISTENT_INPUT);
require(receiver !=address(0), ErrorsLib.ZERO_ADDRESS);
// No need to verify that onBehalf != address(0) thanks to the following authorization check.require(_isSenderAuthorized(onBehalf), ErrorsLib.UNAUTHORIZED);
_accrueInterest(marketParams, id);
if (assets >0) shares = assets.toSharesUp(market[id].totalSupplyAssets, market[id].totalSupplyShares);
else assets = shares.toAssetsDown(market[id].totalSupplyAssets, market[id].totalSupplyShares);
position[id][onBehalf].supplyShares -= shares;
market[id].totalSupplyShares -= shares.toUint128();
market[id].totalSupplyAssets -= assets.toUint128();
require(market[id].totalBorrowAssets <= market[id].totalSupplyAssets, ErrorsLib.INSUFFICIENT_LIQUIDITY);
emit EventsLib.Withdraw(id, msg.sender, onBehalf, receiver, assets, shares);
IERC20(marketParams.loanToken).safeTransfer(receiver, assets);
return (assets, shares);
}
/* BORROW MANAGEMENT *//// @inheritdoc IMorphoBasefunctionborrow(
MarketParams memory marketParams,
uint256 assets,
uint256 shares,
address onBehalf,
address receiver
) externalreturns (uint256, uint256) {
Id id = marketParams.id();
require(market[id].lastUpdate !=0, ErrorsLib.MARKET_NOT_CREATED);
require(UtilsLib.exactlyOneZero(assets, shares), ErrorsLib.INCONSISTENT_INPUT);
require(receiver !=address(0), ErrorsLib.ZERO_ADDRESS);
// No need to verify that onBehalf != address(0) thanks to the following authorization check.require(_isSenderAuthorized(onBehalf), ErrorsLib.UNAUTHORIZED);
_accrueInterest(marketParams, id);
if (assets >0) shares = assets.toSharesUp(market[id].totalBorrowAssets, market[id].totalBorrowShares);
else assets = shares.toAssetsDown(market[id].totalBorrowAssets, market[id].totalBorrowShares);
position[id][onBehalf].borrowShares += shares.toUint128();
market[id].totalBorrowShares += shares.toUint128();
market[id].totalBorrowAssets += assets.toUint128();
require(_isHealthy(marketParams, id, onBehalf), ErrorsLib.INSUFFICIENT_COLLATERAL);
require(market[id].totalBorrowAssets <= market[id].totalSupplyAssets, ErrorsLib.INSUFFICIENT_LIQUIDITY);
emit EventsLib.Borrow(id, msg.sender, onBehalf, receiver, assets, shares);
IERC20(marketParams.loanToken).safeTransfer(receiver, assets);
return (assets, shares);
}
/// @inheritdoc IMorphoBasefunctionrepay(
MarketParams memory marketParams,
uint256 assets,
uint256 shares,
address onBehalf,
bytescalldata data
) externalreturns (uint256, uint256) {
Id id = marketParams.id();
require(market[id].lastUpdate !=0, ErrorsLib.MARKET_NOT_CREATED);
require(UtilsLib.exactlyOneZero(assets, shares), ErrorsLib.INCONSISTENT_INPUT);
require(onBehalf !=address(0), ErrorsLib.ZERO_ADDRESS);
_accrueInterest(marketParams, id);
if (assets >0) shares = assets.toSharesDown(market[id].totalBorrowAssets, market[id].totalBorrowShares);
else assets = shares.toAssetsUp(market[id].totalBorrowAssets, market[id].totalBorrowShares);
position[id][onBehalf].borrowShares -= shares.toUint128();
market[id].totalBorrowShares -= shares.toUint128();
market[id].totalBorrowAssets = UtilsLib.zeroFloorSub(market[id].totalBorrowAssets, assets).toUint128();
// `assets` may be greater than `totalBorrowAssets` by 1.emit EventsLib.Repay(id, msg.sender, onBehalf, assets, shares);
if (data.length>0) IMorphoRepayCallback(msg.sender).onMorphoRepay(assets, data);
IERC20(marketParams.loanToken).safeTransferFrom(msg.sender, address(this), assets);
return (assets, shares);
}
/* COLLATERAL MANAGEMENT *//// @inheritdoc IMorphoBasefunctionsupplyCollateral(MarketParams memory marketParams, uint256 assets, address onBehalf, bytescalldata data)
external{
Id id = marketParams.id();
require(market[id].lastUpdate !=0, ErrorsLib.MARKET_NOT_CREATED);
require(assets !=0, ErrorsLib.ZERO_ASSETS);
require(onBehalf !=address(0), ErrorsLib.ZERO_ADDRESS);
// Don't accrue interest because it's not required and it saves gas.
position[id][onBehalf].collateral += assets.toUint128();
emit EventsLib.SupplyCollateral(id, msg.sender, onBehalf, assets);
if (data.length>0) IMorphoSupplyCollateralCallback(msg.sender).onMorphoSupplyCollateral(assets, data);
IERC20(marketParams.collateralToken).safeTransferFrom(msg.sender, address(this), assets);
}
/// @inheritdoc IMorphoBasefunctionwithdrawCollateral(MarketParams memory marketParams, uint256 assets, address onBehalf, address receiver)
external{
Id id = marketParams.id();
require(market[id].lastUpdate !=0, ErrorsLib.MARKET_NOT_CREATED);
require(assets !=0, ErrorsLib.ZERO_ASSETS);
require(receiver !=address(0), ErrorsLib.ZERO_ADDRESS);
// No need to verify that onBehalf != address(0) thanks to the following authorization check.require(_isSenderAuthorized(onBehalf), ErrorsLib.UNAUTHORIZED);
_accrueInterest(marketParams, id);
position[id][onBehalf].collateral -= assets.toUint128();
require(_isHealthy(marketParams, id, onBehalf), ErrorsLib.INSUFFICIENT_COLLATERAL);
emit EventsLib.WithdrawCollateral(id, msg.sender, onBehalf, receiver, assets);
IERC20(marketParams.collateralToken).safeTransfer(receiver, assets);
}
/* LIQUIDATION *//// @inheritdoc IMorphoBasefunctionliquidate(
MarketParams memory marketParams,
address borrower,
uint256 seizedAssets,
uint256 repaidShares,
bytescalldata data
) externalreturns (uint256, uint256) {
Id id = marketParams.id();
require(market[id].lastUpdate !=0, ErrorsLib.MARKET_NOT_CREATED);
require(UtilsLib.exactlyOneZero(seizedAssets, repaidShares), ErrorsLib.INCONSISTENT_INPUT);
_accrueInterest(marketParams, id);
{
uint256 collateralPrice = IOracle(marketParams.oracle).price();
require(!_isHealthy(marketParams, id, borrower, collateralPrice), ErrorsLib.HEALTHY_POSITION);
// The liquidation incentive factor is min(maxLiquidationIncentiveFactor, 1/(1 - cursor*(1 - lltv))).uint256 liquidationIncentiveFactor = UtilsLib.min(
MAX_LIQUIDATION_INCENTIVE_FACTOR,
WAD.wDivDown(WAD - LIQUIDATION_CURSOR.wMulDown(WAD - marketParams.lltv))
);
if (seizedAssets >0) {
uint256 seizedAssetsQuoted = seizedAssets.mulDivUp(collateralPrice, ORACLE_PRICE_SCALE);
repaidShares = seizedAssetsQuoted.wDivUp(liquidationIncentiveFactor).toSharesUp(
market[id].totalBorrowAssets, market[id].totalBorrowShares
);
} else {
seizedAssets = repaidShares.toAssetsDown(market[id].totalBorrowAssets, market[id].totalBorrowShares)
.wMulDown(liquidationIncentiveFactor).mulDivDown(ORACLE_PRICE_SCALE, collateralPrice);
}
}
uint256 repaidAssets = repaidShares.toAssetsUp(market[id].totalBorrowAssets, market[id].totalBorrowShares);
position[id][borrower].borrowShares -= repaidShares.toUint128();
market[id].totalBorrowShares -= repaidShares.toUint128();
market[id].totalBorrowAssets = UtilsLib.zeroFloorSub(market[id].totalBorrowAssets, repaidAssets).toUint128();
position[id][borrower].collateral -= seizedAssets.toUint128();
uint256 badDebtShares;
uint256 badDebtAssets;
if (position[id][borrower].collateral ==0) {
badDebtShares = position[id][borrower].borrowShares;
badDebtAssets = UtilsLib.min(
market[id].totalBorrowAssets,
badDebtShares.toAssetsUp(market[id].totalBorrowAssets, market[id].totalBorrowShares)
);
market[id].totalBorrowAssets -= badDebtAssets.toUint128();
market[id].totalSupplyAssets -= badDebtAssets.toUint128();
market[id].totalBorrowShares -= badDebtShares.toUint128();
position[id][borrower].borrowShares =0;
}
// `repaidAssets` may be greater than `totalBorrowAssets` by 1.emit EventsLib.Liquidate(
id, msg.sender, borrower, repaidAssets, repaidShares, seizedAssets, badDebtAssets, badDebtShares
);
IERC20(marketParams.collateralToken).safeTransfer(msg.sender, seizedAssets);
if (data.length>0) IMorphoLiquidateCallback(msg.sender).onMorphoLiquidate(repaidAssets, data);
IERC20(marketParams.loanToken).safeTransferFrom(msg.sender, address(this), repaidAssets);
return (seizedAssets, repaidAssets);
}
/* FLASH LOANS *//// @inheritdoc IMorphoBasefunctionflashLoan(address token, uint256 assets, bytescalldata data) external{
require(assets !=0, ErrorsLib.ZERO_ASSETS);
emit EventsLib.FlashLoan(msg.sender, token, assets);
IERC20(token).safeTransfer(msg.sender, assets);
IMorphoFlashLoanCallback(msg.sender).onMorphoFlashLoan(assets, data);
IERC20(token).safeTransferFrom(msg.sender, address(this), assets);
}
/* AUTHORIZATION *//// @inheritdoc IMorphoBasefunctionsetAuthorization(address authorized, bool newIsAuthorized) external{
require(newIsAuthorized != isAuthorized[msg.sender][authorized], ErrorsLib.ALREADY_SET);
isAuthorized[msg.sender][authorized] = newIsAuthorized;
emit EventsLib.SetAuthorization(msg.sender, msg.sender, authorized, newIsAuthorized);
}
/// @inheritdoc IMorphoBasefunctionsetAuthorizationWithSig(Authorization memory authorization, Signature calldata signature) external{
/// Do not check whether authorization is already set because the nonce increment is a desired side effect.require(block.timestamp<= authorization.deadline, ErrorsLib.SIGNATURE_EXPIRED);
require(authorization.nonce == nonce[authorization.authorizer]++, ErrorsLib.INVALID_NONCE);
bytes32 hashStruct =keccak256(abi.encode(AUTHORIZATION_TYPEHASH, authorization));
bytes32 digest =keccak256(bytes.concat("\x19\x01", DOMAIN_SEPARATOR, hashStruct));
address signatory =ecrecover(digest, signature.v, signature.r, signature.s);
require(signatory !=address(0) && authorization.authorizer == signatory, ErrorsLib.INVALID_SIGNATURE);
emit EventsLib.IncrementNonce(msg.sender, authorization.authorizer, authorization.nonce);
isAuthorized[authorization.authorizer][authorization.authorized] = authorization.isAuthorized;
emit EventsLib.SetAuthorization(
msg.sender, authorization.authorizer, authorization.authorized, authorization.isAuthorized
);
}
/// @dev Returns whether the sender is authorized to manage `onBehalf`'s positions.function_isSenderAuthorized(address onBehalf) internalviewreturns (bool) {
returnmsg.sender== onBehalf || isAuthorized[onBehalf][msg.sender];
}
/* INTEREST MANAGEMENT *//// @inheritdoc IMorphoBasefunctionaccrueInterest(MarketParams memory marketParams) external{
Id id = marketParams.id();
require(market[id].lastUpdate !=0, ErrorsLib.MARKET_NOT_CREATED);
_accrueInterest(marketParams, id);
}
/// @dev Accrues interest for the given market `marketParams`./// @dev Assumes that the inputs `marketParams` and `id` match.function_accrueInterest(MarketParams memory marketParams, Id id) internal{
uint256 elapsed =block.timestamp- market[id].lastUpdate;
if (elapsed ==0) return;
if (marketParams.irm !=address(0)) {
uint256 borrowRate = IIrm(marketParams.irm).borrowRate(marketParams, market[id]);
uint256 interest = market[id].totalBorrowAssets.wMulDown(borrowRate.wTaylorCompounded(elapsed));
market[id].totalBorrowAssets += interest.toUint128();
market[id].totalSupplyAssets += interest.toUint128();
uint256 feeShares;
if (market[id].fee !=0) {
uint256 feeAmount = interest.wMulDown(market[id].fee);
// The fee amount is subtracted from the total supply in this calculation to compensate for the fact// that total supply is already increased by the full interest (including the fee amount).
feeShares =
feeAmount.toSharesDown(market[id].totalSupplyAssets - feeAmount, market[id].totalSupplyShares);
position[id][feeRecipient].supplyShares += feeShares;
market[id].totalSupplyShares += feeShares.toUint128();
}
emit EventsLib.AccrueInterest(id, borrowRate, interest, feeShares);
}
// Safe "unchecked" cast.
market[id].lastUpdate =uint128(block.timestamp);
}
/* HEALTH CHECK *//// @dev Returns whether the position of `borrower` in the given market `marketParams` is healthy./// @dev Assumes that the inputs `marketParams` and `id` match.function_isHealthy(MarketParams memory marketParams, Id id, address borrower) internalviewreturns (bool) {
if (position[id][borrower].borrowShares ==0) returntrue;
uint256 collateralPrice = IOracle(marketParams.oracle).price();
return _isHealthy(marketParams, id, borrower, collateralPrice);
}
/// @dev Returns whether the position of `borrower` in the given market `marketParams` with the given/// `collateralPrice` is healthy./// @dev Assumes that the inputs `marketParams` and `id` match./// @dev Rounds in favor of the protocol, so one might not be able to borrow exactly `maxBorrow` but one unit less.function_isHealthy(MarketParams memory marketParams, Id id, address borrower, uint256 collateralPrice)
internalviewreturns (bool)
{
uint256 borrowed =uint256(position[id][borrower].borrowShares).toAssetsUp(
market[id].totalBorrowAssets, market[id].totalBorrowShares
);
uint256 maxBorrow =uint256(position[id][borrower].collateral).mulDivDown(collateralPrice, ORACLE_PRICE_SCALE)
.wMulDown(marketParams.lltv);
return maxBorrow >= borrowed;
}
/* STORAGE VIEW *//// @inheritdoc IMorphoBasefunctionextSloads(bytes32[] calldata slots) externalviewreturns (bytes32[] memory res) {
uint256 nSlots = slots.length;
res =newbytes32[](nSlots);
for (uint256 i; i < nSlots;) {
bytes32 slot = slots[i++];
assembly ("memory-safe") {
mstore(add(res, mul(i, 32)), sload(slot))
}
}
}
}
Contract Source Code
File 12 of 14: SafeTransferLib.sol
// SPDX-License-Identifier: GPL-2.0-or-laterpragmasolidity ^0.8.0;import {IERC20} from"../interfaces/IERC20.sol";
import {ErrorsLib} from"../libraries/ErrorsLib.sol";
interfaceIERC20Internal{
functiontransfer(address to, uint256 value) externalreturns (bool);
functiontransferFrom(addressfrom, address to, uint256 value) externalreturns (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.librarySafeTransferLib{
functionsafeTransfer(IERC20 token, address to, uint256 value) internal{
require(address(token).code.length>0, ErrorsLib.NO_CODE);
(bool success, bytesmemory returndata) =address(token).call(abi.encodeCall(IERC20Internal.transfer, (to, value)));
require(success, ErrorsLib.TRANSFER_REVERTED);
require(returndata.length==0||abi.decode(returndata, (bool)), ErrorsLib.TRANSFER_RETURNED_FALSE);
}
functionsafeTransferFrom(IERC20 token, addressfrom, address to, uint256 value) internal{
require(address(token).code.length>0, ErrorsLib.NO_CODE);
(bool success, bytesmemory returndata) =address(token).call(abi.encodeCall(IERC20Internal.transferFrom, (from, to, value)));
require(success, ErrorsLib.TRANSFER_FROM_REVERTED);
require(returndata.length==0||abi.decode(returndata, (bool)), ErrorsLib.TRANSFER_FROM_RETURNED_FALSE);
}
}
Contract Source Code
File 13 of 14: SharesMathLib.sol
// SPDX-License-Identifier: GPL-2.0-or-laterpragmasolidity ^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.librarySharesMathLib{
usingMathLibforuint256;
/// @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.uint256internalconstant VIRTUAL_SHARES =1e6;
/// @dev A number of virtual assets of 1 enforces a conversion rate between shares and assets when a market is/// empty.uint256internalconstant VIRTUAL_ASSETS =1;
/// @dev Calculates the value of `assets` quoted in shares, rounding down.functiontoSharesDown(uint256 assets, uint256 totalAssets, uint256 totalShares) internalpurereturns (uint256) {
return assets.mulDivDown(totalShares + VIRTUAL_SHARES, totalAssets + VIRTUAL_ASSETS);
}
/// @dev Calculates the value of `shares` quoted in assets, rounding down.functiontoAssetsDown(uint256 shares, uint256 totalAssets, uint256 totalShares) internalpurereturns (uint256) {
return shares.mulDivDown(totalAssets + VIRTUAL_ASSETS, totalShares + VIRTUAL_SHARES);
}
/// @dev Calculates the value of `assets` quoted in shares, rounding up.functiontoSharesUp(uint256 assets, uint256 totalAssets, uint256 totalShares) internalpurereturns (uint256) {
return assets.mulDivUp(totalShares + VIRTUAL_SHARES, totalAssets + VIRTUAL_ASSETS);
}
/// @dev Calculates the value of `shares` quoted in assets, rounding up.functiontoAssetsUp(uint256 shares, uint256 totalAssets, uint256 totalShares) internalpurereturns (uint256) {
return shares.mulDivUp(totalAssets + VIRTUAL_ASSETS, totalShares + VIRTUAL_SHARES);
}
}
Contract Source Code
File 14 of 14: UtilsLib.sol
// SPDX-License-Identifier: GPL-2.0-or-laterpragmasolidity ^0.8.0;import {ErrorsLib} from"../libraries/ErrorsLib.sol";
/// @title UtilsLib/// @author Morpho Labs/// @custom:contact security@morpho.org/// @notice Library exposing helpers./// @dev Inspired by https://github.com/morpho-org/morpho-utils.libraryUtilsLib{
/// @dev Returns true if there is exactly one zero among `x` and `y`.functionexactlyOneZero(uint256 x, uint256 y) internalpurereturns (bool z) {
assembly {
z :=xor(iszero(x), iszero(y))
}
}
/// @dev Returns the min of `x` and `y`.functionmin(uint256 x, uint256 y) internalpurereturns (uint256 z) {
assembly {
z :=xor(x, mul(xor(x, y), lt(y, x)))
}
}
/// @dev Returns `x` safely cast to uint128.functiontoUint128(uint256 x) internalpurereturns (uint128) {
require(x <=type(uint128).max, ErrorsLib.MAX_UINT128_EXCEEDED);
returnuint128(x);
}
/// @dev Returns max(0, x - y).functionzeroFloorSub(uint256 x, uint256 y) internalpurereturns (uint256 z) {
assembly {
z :=mul(gt(x, y), sub(x, y))
}
}
}