Morpho
0x9d53d5e3bd5e8d4cbfa6db1ca238aea02e651010
Verification
Verified
v0.8.19+commit.7dd6d404
Type
Contract
15,582 bytes
ABI entries
29
11 read · 17 write
License
none
Contract information
- Address
- 0x9d53d5e3bd5e8d4cbfa6db1ca238aea02e651010
- Chain
- Robinhood Chain (4663)
- Compiler
- v0.8.19+commit.7dd6d404
- Optimization
- Enabled
- Creator
- 0xC67335D90E…C8bebDdd2b
- Creation tx
- 0xe1927e1ab3…5b64f74d20
Token
Not a token
This contract does not expose ERC-20 metadata.
Read contract (11)
DOMAIN_SEPARATOR() → bytes32
extSloads(bytes32[]) → bytes32[]
feeRecipient() → address
idToMarketParams(bytes32) → address, address, address, address, uint256
isAuthorized(address, address) → bool
isIrmEnabled(address) → bool
isLltvEnabled(uint256) → bool
market(bytes32) → uint128, uint128, uint128, uint128, uint128, uint128
nonce(address) → uint256
owner() → address
position(bytes32, address) → uint256, uint128, uint128
ABI
[
{
"inputs": [
{
"internalType": "address",
"name": "newOwner",
"type": "address"
}
],
"stateMutability": "nonpayable",
"type": "constructor"
},
{
"inputs": [],
"name": "DOMAIN_SEPARATOR",
"outputs": [
{
"internalType": "bytes32",
"name": "",
"type": "bytes32"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"components": [
{
"internalType": "address",
"name": "loanToken",
"type": "address"
},
{
"internalType": "address",
"name": "collateralToken",
"type": "address"
},
{
"internalType": "address",
"name": "oracle",
"type": "address"
},
{
"internalType": "address",
"name": "irm",
"type": "address"
},
{
"internalType": "uint256",
"name": "lltv",
"type": "uint256"
}
],
"internalType": "struct MarketParams",
"name": "marketParams",
"type": "tuple"
}
],
"name": "accrueInterest",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"components": [
{
"internalType": "address",
"name": "loanToken",
"type": "address"
},
{
"internalType": "address",
"name": "collateralToken",
"type": "address"
},
{
"internalType": "address",
"name": "oracle",
"type": "address"
},
{
"internalType": "address",
"name": "irm",
"type": "address"
},
{
"internalType": "uint256",
"name": "lltv",
"type": "uint256"
}
],
"internalType": "struct MarketParams",
"name": "marketParams",
"type": "tuple"
},
{
"internalType": "uint256",
"name": "assets",
"type": "uint256"
},
{
"internalType": "uint256",
"name": "shares",
"type": "uint256"
},
{
"internalType": "address",
"name": "onBehalf",
"type": "address"
},
{
"internalType": "address",
"name": "receiver",
"type": "address"
}
],
"name": "borrow",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
},
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"components": [
{
"internalType": "address",
"name": "loanToken",
"type": "address"
},
{
"internalType": "address",
"name": "collateralToken",
"type": "address"
},
{
"internalType": "address",
"name": "oracle",
"type": "address"
},
{
"internalType": "address",
"name": "irm",
"type": "address"
},
{
"internalType": "uint256",
"name": "lltv",
"type": "uint256"
}
],
"internalType": "struct MarketParams",
"name": "marketParams",
"type": "tuple"
}
],
"name": "createMarket",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "irm",
"type": "address"
}
],
"name": "enableIrm",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "uint256",
"name": "lltv",
"type": "uint256"
}
],
"name": "enableLltv",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "bytes32[]",
"name": "slots",
"type": "bytes32[]"
}
],
"name": "extSloads",
"outputs": [
{
"internalType": "bytes32[]",
"name": "res",
"type": "bytes32[]"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "feeRecipient",
"outputs": [
{
"internalType": "address",
"name": "",
"type": "address"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "token",
"type": "address"
},
{
"internalType": "uint256",
"name": "assets",
"type": "uint256"
},
{
"internalType": "bytes",
"name": "data",
"type": "bytes"
}
],
"name": "flashLoan",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "Id",
"name": "",
"type": "bytes32"
}
],
"name": "idToMarketParams",
"outputs": [
{
"internalType": "address",
"name": "loanToken",
"type": "address"
},
{
"internalType": "address",
"name": "collateralToken",
"type": "address"
},
{
"internalType": "address",
"name": "oracle",
"type": "address"
},
{
"internalType": "address",
"name": "irm",
"type": "address"
},
{
"internalType": "uint256",
"name": "lltv",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "",
"type": "address"
},
{
"internalType": "address",
"name": "",
"type": "address"
}
],
"name": "isAuthorized",
"outputs": [
{
"internalType": "bool",
"name": "",
"type": "bool"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "",
"type": "address"
}
],
"name": "isIrmEnabled",
"outputs": [
{
"internalType": "bool",
"name": "",
"type": "bool"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"name": "isLltvEnabled",
"outputs": [
{
"internalType": "bool",
"name": "",
"type": "bool"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"components": [
{
"internalType": "address",
"name": "loanToken",
"type": "address"
},
{
"internalType": "address",
"name": "collateralToken",
"type": "address"
},
{
"internalType": "address",
"name": "oracle",
"type": "address"
},
{
"internalType": "address",
"name": "irm",
"type": "address"
},
{
"internalType": "uint256",
"name": "lltv",
"type": "uint256"
}
],
"internalType": "struct MarketParams",
"name": "marketParams",
"type": "tuple"
},
{
"internalType": "address",
"name": "borrower",
"type": "address"
},
{
"internalType": "uint256",
"name": "seizedAssets",
"type": "uint256"
},
{
"internalType": "uint256",
"name": "repaidShares",
"type": "uint256"
},
{
"internalType": "bytes",
"name": "data",
"type": "bytes"
}
],
"name": "liquidate",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
},
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "Id",
"name": "",
"type": "bytes32"
}
],
"name": "market",
"outputs": [
{
"internalType": "uint128",
"name": "totalSupplyAssets",
"type": "uint128"
},
{
"internalType": "uint128",
"name": "totalSupplyShares",
"type": "uint128"
},
{
"internalType": "uint128",
"name": "totalBorrowAssets",
"type": "uint128"
},
{
"internalType": "uint128",
"name": "totalBorrowShares",
"type": "uint128"
},
{
"internalType": "uint128",
"name": "lastUpdate",
"type": "uint128"
},
{
"internalType": "uint128",
"name": "fee",
"type": "uint128"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "",
"type": "address"
}
],
"name": "nonce",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "owner",
"outputs": [
{
"internalType": "address",
"name": "",
"type": "address"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "Id",
"name": "",
"type": "bytes32"
},
{
"internalType": "address",
"name": "",
"type": "address"
}
],
"name": "position",
"outputs": [
{
"internalType": "uint256",
"name": "supplyShares",
"type": "uint256"
},
{
"internalType": "uint128",
"name": "borrowShares",
"type": "uint128"
},
{
"internalType": "uint128",
"name": "collateral",
"type": "uint128"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"components": [
{
"internalType": "address",
"name": "loanToken",
"type": "address"
},
{
"internalType": "address",
"name": "collateralToken",
"type": "address"
},
{
"internalType": "address",
"name": "oracle",
"type": "address"
},
{
"internalType": "address",
"name": "irm",
"type": "address"
},
{
"internalType": "uint256",
"name": "lltv",
"type": "uint256"
}
],
"internalType": "struct MarketParams",
"name": "marketParams",
"type": "tuple"
},
{
"internalType": "uint256",
"name": "assets",
"type": "uint256"
},
{
"internalType": "uint256",
"name": "shares",
"type": "uint256"
},
{
"internalType": "address",
"name": "onBehalf",
"type": "address"
},
{
"internalType": "bytes",
"name": "data",
"type": "bytes"
}
],
"name": "repay",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
},
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "authorized",
"type": "address"
},
{
"internalType": "bool",
"name": "newIsAuthorized",
"type": "bool"
}
],
"name": "setAuthorization",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"components": [
{
"internalType": "address",
"name": "authorizer",
"type": "address"
},
{
"internalType": "address",
"name": "authorized",
"type": "address"
},
{
"internalType": "bool",
"name": "isAuthorized",
"type": "bool"
},
{
"internalType": "uint256",
"name": "nonce",
"type": "uint256"
},
{
"internalType": "uint256",
"name": "deadline",
"type": "uint256"
}
],
"internalType": "struct Authorization",
"name": "authorization",
"type": "tuple"
},
{
"components": [
{
"internalType": "uint8",
"name": "v",
"type": "uint8"
},
{
"internalType": "bytes32",
"name": "r",
"type": "bytes32"
},
{
"internalType": "bytes32",
"name": "s",
"type": "bytes32"
}
],
"internalType": "struct Signature",
"name": "signature",
"type": "tuple"
}
],
"name": "setAuthorizationWithSig",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"components": [
{
"internalType": "address",
"name": "loanToken",
"type": "address"
},
{
"internalType": "address",
"name": "collateralToken",
"type": "address"
},
{
"internalType": "address",
"name": "oracle",
"type": "address"
},
{
"internalType": "address",
"name": "irm",
"type": "address"
},
{
"internalType": "uint256",
"name": "lltv",
"type": "uint256"
}
],
"internalType": "struct MarketParams",
"name": "marketParams",
"type": "tuple"
},
{
"internalType": "uint256",
"name": "newFee",
"type": "uint256"
}
],
"name": "setFee",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "newFeeRecipient",
"type": "address"
}
],
"name": "setFeeRecipient",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "newOwner",
"type": "address"
}
],
"name": "setOwner",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"components": [
{
"internalType": "address",
"name": "loanToken",
"type": "address"
},
{
"internalType": "address",
"name": "collateralToken",
"type": "address"
},
{
"internalType": "address",
"name": "oracle",
"type": "address"
},
{
"internalType": "address",
"name": "irm",
"type": "address"
},
{
"internalType": "uint256",
"name": "lltv",
"type": "uint256"
}
],
"internalType": "struct MarketParams",
"name": "marketParams",
"type": "tuple"
},
{
"internalType": "uint256",
"name": "assets",
"type": "uint256"
},
{
"internalType": "uint256",
"name": "shares",
"type": "uint256"
},
{
"internalType": "address",
"name": "onBehalf",
"type": "address"
},
{
"internalType": "bytes",
"name": "data",
"type": "bytes"
}
],
"name": "supply",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
},
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"components": [
{
"internalType": "address",
"name": "loanToken",
"type": "address"
},
{
"internalType": "address",
"name": "collateralToken",
"type": "address"
},
{
"internalType": "address",
"name": "oracle",
"type": "address"
},
{
"internalType": "address",
"name": "irm",
"type": "address"
},
{
"internalType": "uint256",
"name": "lltv",
"type": "uint256"
}
],
"internalType": "struct MarketParams",
"name": "marketParams",
"type": "tuple"
},
{
"internalType": "uint256",
"name": "assets",
"type": "uint256"
},
{
"internalType": "address",
"name": "onBehalf",
"type": "address"
},
{
"internalType": "bytes",
"name": "data",
"type": "bytes"
}
],
"name": "supplyCollateral",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"components": [
{
"internalType": "address",
"name": "loanToken",
"type": "address"
},
{
"internalType": "address",
"name": "collateralToken",
"type": "address"
},
{
"internalType": "address",
"name": "oracle",
"type": "address"
},
{
"internalType": "address",
"name": "irm",
"type": "address"
},
{
"internalType": "uint256",
"name": "lltv",
"type": "uint256"
}
],
"internalType": "struct MarketParams",
"name": "marketParams",
"type": "tuple"
},
{
"internalType": "uint256",
"name": "assets",
"type": "uint256"
},
{
"internalType": "uint256",
"name": "shares",
"type": "uint256"
},
{
"internalType": "address",
"name": "onBehalf",
"type": "address"
},
{
"internalType": "address",
"name": "receiver",
"type": "address"
}
],
"name": "withdraw",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
},
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"components": [
{
"internalType": "address",
"name": "loanToken",
"type": "address"
},
{
"internalType": "address",
"name": "collateralToken",
"type": "address"
},
{
"internalType": "address",
"name": "oracle",
"type": "address"
},
{
"internalType": "address",
"name": "irm",
"type": "address"
},
{
"internalType": "uint256",
"name": "lltv",
"type": "uint256"
}
],
"internalType": "struct MarketParams",
"name": "marketParams",
"type": "tuple"
},
{
"internalType": "uint256",
"name": "assets",
"type": "uint256"
},
{
"internalType": "address",
"name": "onBehalf",
"type": "address"
},
{
"internalType": "address",
"name": "receiver",
"type": "address"
}
],
"name": "withdrawCollateral",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
}
]Source code
// SPDX-License-Identifier: BUSL-1.1
pragma solidity ^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.
contract Morpho is IMorphoStaticTyping {
using MathLib for uint128;
using MathLib for uint256;
using UtilsLib for uint256;
using SharesMathLib for uint256;
using SafeTransferLib for IERC20;
using MarketParamsLib for MarketParams;
/* IMMUTABLES */
/// @inheritdoc IMorphoBase
bytes32 public immutable DOMAIN_SEPARATOR;
/* STORAGE */
/// @inheritdoc IMorphoBase
address public owner;
/// @inheritdoc IMorphoBase
address public feeRecipient;
/// @inheritdoc IMorphoStaticTyping
mapping(Id => mapping(address => Position)) public position;
/// @inheritdoc IMorphoStaticTyping
mapping(Id => Market) public market;
/// @inheritdoc IMorphoBase
mapping(address => bool) public isIrmEnabled;
/// @inheritdoc IMorphoBase
mapping(uint256 => bool) public isLltvEnabled;
/// @inheritdoc IMorphoBase
mapping(address => mapping(address => bool)) public isAuthorized;
/// @inheritdoc IMorphoBase
mapping(address => uint256) public nonce;
/// @inheritdoc IMorphoStaticTyping
mapping(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.
modifier onlyOwner() {
require(msg.sender == owner, ErrorsLib.NOT_OWNER);
_;
}
/* ONLY OWNER FUNCTIONS */
/// @inheritdoc IMorphoBase
function setOwner(address newOwner) external onlyOwner {
require(newOwner != owner, ErrorsLib.ALREADY_SET);
owner = newOwner;
emit EventsLib.SetOwner(newOwner);
}
/// @inheritdoc IMorphoBase
function enableIrm(address irm) external onlyOwner {
require(!isIrmEnabled[irm], ErrorsLib.ALREADY_SET);
isIrmEnabled[irm] = true;
emit EventsLib.EnableIrm(irm);
}
/// @inheritdoc IMorphoBase
function enableLltv(uint256 lltv) external onlyOwner {
require(!isLltvEnabled[lltv], ErrorsLib.ALREADY_SET);
require(lltv < WAD, ErrorsLib.MAX_LLTV_EXCEEDED);
isLltvEnabled[lltv] = true;
emit EventsLib.EnableLltv(lltv);
}
/// @inheritdoc IMorphoBase
function setFee(MarketParams memory marketParams, uint256 newFee) external onlyOwner {
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 IMorphoBase
function setFeeRecipient(address newFeeRecipient) external onlyOwner {
require(newFeeRecipient != feeRecipient, ErrorsLib.ALREADY_SET);
feeRecipient = newFeeRecipient;
emit EventsLib.SetFeeRecipient(newFeeRecipient);
}
/* MARKET CREATION */
/// @inheritdoc IMorphoBase
function createMarket(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 IMorphoBase
function supply(
MarketParams memory marketParams,
uint256 assets,
uint256 shares,
address onBehalf,
bytes calldata data
) external returns (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 IMorphoBase
function withdraw(
MarketParams memory marketParams,
uint256 assets,
uint256 shares,
address onBehalf,
address receiver
) external returns (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 IMorphoBase
function borrow(
MarketParams memory marketParams,
uint256 assets,
uint256 shares,
address onBehalf,
address receiver
) external returns (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 IMorphoBase
function repay(
MarketParams memory marketParams,
uint256 assets,
uint256 shares,
address onBehalf,
bytes calldata data
) external returns (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 IMorphoBase
function supplyCollateral(MarketParams memory marketParams, uint256 assets, address onBehalf, bytes calldata 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 IMorphoBase
function withdrawCollateral(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 IMorphoBase
function liquidate(
MarketParams memory marketParams,
address borrower,
uint256 seizedAssets,
uint256 repaidShares,
bytes calldata data
) external returns (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 IMorphoBase
function flashLoan(address token, uint256 assets, bytes calldata 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 IMorphoBase
function setAuthorization(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 IMorphoBase
function setAuthorizationWithSig(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) internal view returns (bool) {
return msg.sender == onBehalf || isAuthorized[onBehalf][msg.sender];
}
/* INTEREST MANAGEMENT */
/// @inheritdoc IMorphoBase
function accrueInterest(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) internal view returns (bool) {
if (position[id][borrower].borrowShares == 0) return true;
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)
internal
view
returns (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 IMorphoBase
function extSloads(bytes32[] calldata slots) external view returns (bytes32[] memory res) {
uint256 nSlots = slots.length;
res = new bytes32[](nSlots);
for (uint256 i; i < nSlots;) {
bytes32 slot = slots[i++];
assembly ("memory-safe") {
mstore(add(res, mul(i, 32)), sload(slot))
}
}
}
}
Chain explorer2473msChain node85ms