diff --git a/contracts/protocols/mainnet/metamorpho/interface.sol b/contracts/protocols/mainnet/metamorpho/interface.sol new file mode 100644 index 0000000..a6d0d92 --- /dev/null +++ b/contracts/protocols/mainnet/metamorpho/interface.sol @@ -0,0 +1,69 @@ +// SPDX-License-Identifier: MIT +pragma solidity >=0.8.19; + +import {Id} from "./interfaces/IMorpho.sol"; + +interface TokenInterface { + function balanceOf(address) external view returns (uint256); + + function allowance(address owner, address spender) external view returns (uint256); + + function decimals() external view returns (uint256); + + function name() external view returns (string memory); + + function symbol() external view returns (string memory); +} + +interface VaultInterface { + function decimals() external view returns (uint256); + + function name() external view returns (string memory); + + function symbol() external view returns (string memory); + + function asset() external view returns (address); + + function totalAssets() external view returns (uint256); + + function totalSupply() external view returns (uint256); + + function balanceOf(address) external view returns (uint256); + + function allowance(address owner, address spender) external view returns (uint256); + + function nonces(address) external view returns (uint256); + + function convertToShares(uint256 assets) external view returns (uint256); + + function convertToAssets(uint256 shares) external view returns (uint256); + + function previewDeposit(uint256 assets) external view returns (uint256); + + function previewMint(uint256 shares) external view returns (uint256); + + function previewWithdraw(uint256 assets) external view returns (uint256); + + function previewRedeem(uint256 shares) external view returns (uint256); + + function maxDeposit(address) external view returns (uint256); + + function maxMint(address) external view returns (uint256); + + function maxWithdraw(address owner) external view returns (uint256); + + function maxRedeem(address owner) external view returns (uint256); +} + +interface MorphoBlueInterface { + function idToMarketParams(Id id) + external + view + returns (address loanToken, address collateralToken, address oracle, address irm, uint256 lltv); +} + +interface MetaMorphoInterface { + function fee() external view returns (uint96); + function supplyQueue(uint256) external view returns (Id); + function config(Id) external view returns (uint184 cap, bool enabled, uint64 removableAt); +} \ No newline at end of file diff --git a/contracts/protocols/mainnet/metamorpho/interfaces/IERC20.sol b/contracts/protocols/mainnet/metamorpho/interfaces/IERC20.sol new file mode 100644 index 0000000..4d16188 --- /dev/null +++ b/contracts/protocols/mainnet/metamorpho/interfaces/IERC20.sol @@ -0,0 +1,9 @@ +// 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 {} diff --git a/contracts/protocols/mainnet/metamorpho/interfaces/IIrm.sol b/contracts/protocols/mainnet/metamorpho/interfaces/IIrm.sol new file mode 100644 index 0000000..3de0bc1 --- /dev/null +++ b/contracts/protocols/mainnet/metamorpho/interfaces/IIrm.sol @@ -0,0 +1,19 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +pragma solidity >=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. +interface IIrm { + /// @notice Returns the borrow rate per second (scaled by WAD) of the market `marketParams`. + /// @dev Assumes that `market` corresponds to `marketParams`. + function borrowRate(MarketParams memory marketParams, Market memory market) external returns (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`. + function borrowRateView(MarketParams memory marketParams, Market memory market) external view returns (uint256); +} diff --git a/contracts/protocols/mainnet/metamorpho/interfaces/IMetaMorpho.sol b/contracts/protocols/mainnet/metamorpho/interfaces/IMetaMorpho.sol new file mode 100644 index 0000000..bd3b68d --- /dev/null +++ b/contracts/protocols/mainnet/metamorpho/interfaces/IMetaMorpho.sol @@ -0,0 +1,89 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +pragma solidity >=0.5.0; + +import {IMorpho, Id, MarketParams} from "./IMorpho.sol"; + +struct MarketConfig { + /// @notice The maximum amount of assets that can be allocated to the market. + uint192 cap; + /// @notice Whether the market is in the withdraw queue. + bool enabled; + /// @notice The timestamp at which the market can be instantly removed from the withdraw queue. + uint56 removableAt; +} + +/// @dev Either `assets` or `shares` should be zero. +struct MarketAllocation { + /// @notice The market to allocate. + MarketParams marketParams; + /// @notice The amount of assets to allocate. + uint256 assets; +} + +/// @dev This interface is used for factorizing IMetaMorphoStaticTyping and IMetaMorpho. +/// @dev Consider using the IMetaMorpho interface instead of this one. +interface IMetaMorphoBase { + function MORPHO() external view returns (IMorpho); + + function curator() external view returns (address); + function isAllocator(address target) external view returns (bool); + function guardian() external view returns (address); + + function fee() external view returns (uint96); + function feeRecipient() external view returns (address); + function skimRecipient() external view returns (address); + function timelock() external view returns (uint256); + function supplyQueue(uint256) external view returns (Id); + function supplyQueueLength() external view returns (uint256); + function withdrawQueue(uint256) external view returns (Id); + function withdrawQueueLength() external view returns (uint256); + + function lastTotalAssets() external view returns (uint256); + + function submitTimelock(uint256 newTimelock) external; + function acceptTimelock() external; + function revokePendingTimelock() external; + + function submitCap(MarketParams memory marketParams, uint256 supplyCap) external; + function acceptCap(Id id) external; + function revokePendingCap(Id id) external; + + function submitMarketRemoval(Id id) external; + function revokePendingMarketRemoval(Id id) external; + + function submitGuardian(address newGuardian) external; + function acceptGuardian() external; + function revokePendingGuardian() external; + + function skim(address) external; + + function setIsAllocator(address newAllocator, bool newIsAllocator) external; + function setCurator(address newCurator) external; + function setFee(uint256 newFee) external; + function setFeeRecipient(address newFeeRecipient) external; + function setSkimRecipient(address) external; + + function setSupplyQueue(Id[] calldata newSupplyQueue) external; + function updateWithdrawQueue(uint256[] calldata indexes) external; + function reallocate(MarketAllocation[] calldata allocations) external; +} + +/// @dev This interface is inherited by MetaMorpho so that function signatures are checked by the compiler. +/// @dev Consider using the IMetaMorpho interface instead of this one. +interface IMetaMorphoStaticTyping is IMetaMorphoBase { + function config(Id) external view returns (uint192 cap, bool enabled, uint56 removableAt); + function pendingGuardian() external view returns (address guardian, uint56 validAt); + function pendingCap(Id) external view returns (uint192 value, uint56 validAt); + function pendingTimelock() external view returns (uint192 value, uint56 validAt); +} + +/// @title IMetaMorpho +/// @author Morpho Labs +/// @custom:contact security@morpho.org +/// @dev Use this interface for MetaMorpho to have access to all the functions with the appropriate function signatures. +interface IMetaMorpho is IMetaMorphoBase { + function config(Id) external view returns (MarketConfig memory); + + // From IERC4626 + function totalAssets() external view returns (uint256); +} diff --git a/contracts/protocols/mainnet/metamorpho/interfaces/IMorpho.sol b/contracts/protocols/mainnet/metamorpho/interfaces/IMorpho.sol new file mode 100644 index 0000000..9c82524 --- /dev/null +++ b/contracts/protocols/mainnet/metamorpho/interfaces/IMorpho.sol @@ -0,0 +1,353 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +pragma solidity >=0.5.0; + +type Id is bytes32; + +struct MarketParams { + 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. +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. +struct Market { + uint128 totalSupplyAssets; + uint128 totalSupplyShares; + uint128 totalBorrowAssets; + uint128 totalBorrowShares; + uint128 lastUpdate; + uint128 fee; +} + +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 another chain sharing + /// the same chain id 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 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. + function isAuthorized(address authorizer, address authorized) 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 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. + 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 + /// `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. + function repay( + MarketParams memory marketParams, + uint256 assets, + uint256 shares, + address onBehalf, + bytes memory data + ) external returns (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. + function supplyCollateral(MarketParams memory marketParams, uint256 assets, address onBehalf, bytes memory 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. + function withdrawCollateral(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. + function liquidate( + MarketParams memory marketParams, + address borrower, + uint256 seizedAssets, + uint256 repaidShares, + bytes memory data + ) external returns (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. + function flashLoan(address token, uint256 assets, bytes calldata 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. + function setAuthorization(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. + function setAuthorizationWithSig(Authorization calldata authorization, Signature calldata signature) external; + + /// @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 + ); + + /// @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); +} + +/// @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); +} diff --git a/contracts/protocols/mainnet/metamorpho/interfaces/IMorphoCallbacks.sol b/contracts/protocols/mainnet/metamorpho/interfaces/IMorphoCallbacks.sol new file mode 100644 index 0000000..b4c7898 --- /dev/null +++ b/contracts/protocols/mainnet/metamorpho/interfaces/IMorphoCallbacks.sol @@ -0,0 +1,52 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +pragma solidity >=0.5.0; + +/// @title IMorphoLiquidateCallback +/// @notice Interface that liquidators willing to use `liquidate`'s callback must implement. +interface IMorphoLiquidateCallback { + /// @notice Callback called when a liquidation occurs. + /// @dev The callback is called only if data is not empty. + /// @param repaidAssets The amount of repaid assets. + /// @param data Arbitrary data passed to the `liquidate` function. + function onMorphoLiquidate(uint256 repaidAssets, bytes calldata data) external; +} + +/// @title IMorphoRepayCallback +/// @notice Interface that users willing to use `repay`'s callback must implement. +interface IMorphoRepayCallback { + /// @notice Callback called when a repayment occurs. + /// @dev The callback is called only if data is not empty. + /// @param assets The amount of repaid assets. + /// @param data Arbitrary data passed to the `repay` function. + function onMorphoRepay(uint256 assets, bytes calldata data) external; +} + +/// @title IMorphoSupplyCallback +/// @notice Interface that users willing to use `supply`'s callback must implement. +interface IMorphoSupplyCallback { + /// @notice Callback called when a supply occurs. + /// @dev The callback is called only if data is not empty. + /// @param assets The amount of supplied assets. + /// @param data Arbitrary data passed to the `supply` function. + function onMorphoSupply(uint256 assets, bytes calldata data) external; +} + +/// @title IMorphoSupplyCollateralCallback +/// @notice Interface that users willing to use `supplyCollateral`'s callback must implement. +interface IMorphoSupplyCollateralCallback { + /// @notice Callback called when a supply of collateral occurs. + /// @dev The callback is called only if data is not empty. + /// @param assets The amount of supplied collateral. + /// @param data Arbitrary data passed to the `supplyCollateral` function. + function onMorphoSupplyCollateral(uint256 assets, bytes calldata data) external; +} + +/// @title IMorphoFlashLoanCallback +/// @notice Interface that users willing to use `flashLoan`'s callback must implement. +interface IMorphoFlashLoanCallback { + /// @notice Callback called when a flash loan occurs. + /// @dev The callback is called only if data is not empty. + /// @param assets The amount of assets that was flash loaned. + /// @param data Arbitrary data passed to the `flashLoan` function. + function onMorphoFlashLoan(uint256 assets, bytes calldata data) external; +} diff --git a/contracts/protocols/mainnet/metamorpho/interfaces/IOracle.sol b/contracts/protocols/mainnet/metamorpho/interfaces/IOracle.sol new file mode 100644 index 0000000..482737e --- /dev/null +++ b/contracts/protocols/mainnet/metamorpho/interfaces/IOracle.sol @@ -0,0 +1,15 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +pragma solidity >=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. +interface IOracle { + /// @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. + function price() external view returns (uint256); +} diff --git a/contracts/protocols/mainnet/metamorpho/interfaces/LICENSE b/contracts/protocols/mainnet/metamorpho/interfaces/LICENSE new file mode 100644 index 0000000..aec4e2a --- /dev/null +++ b/contracts/protocols/mainnet/metamorpho/interfaces/LICENSE @@ -0,0 +1,389 @@ +This software is available under your choice of the GNU General Public +License, version 2 or later, or the Business Source License, as set +forth below. + + GNU GENERAL PUBLIC LICENSE + Version 2, June 1991 + + Copyright (C) 1989, 1991 Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +License is intended to guarantee your freedom to share and change free +software--to make sure the software is free for all its users. This +General Public License applies to most of the Free Software +Foundation's software and to any other program whose authors commit to +using it. (Some other Free Software Foundation software is covered by +the GNU Lesser General Public License instead.) You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +this service if you wish), that you receive source code or can get it +if you want it, that you can change the software or use pieces of it +in new free programs; and that you know you can do these things. + + To protect your rights, we need to make restrictions that forbid +anyone to deny you these rights or to ask you to surrender the rights. +These restrictions translate to certain responsibilities for you if you +distribute copies of the software, or if you modify it. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must give the recipients all the rights that +you have. You must make sure that they, too, receive or can get the +source code. And you must show them these terms so they know their +rights. + + We protect your rights with two steps: (1) copyright the software, and +(2) offer you this license which gives you legal permission to copy, +distribute and/or modify the software. + + Also, for each author's protection and ours, we want to make certain +that everyone understands that there is no warranty for this free +software. If the software is modified by someone else and passed on, we +want its recipients to know that what they have is not the original, so +that any problems introduced by others will not reflect on the original +authors' reputations. + + Finally, any free program is threatened constantly by software +patents. We wish to avoid the danger that redistributors of a free +program will individually obtain patent licenses, in effect making the +program proprietary. To prevent this, we have made it clear that any +patent must be licensed for everyone's free use or not licensed at all. + + The precise terms and conditions for copying, distribution and +modification follow. + + GNU GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License applies to any program or other work which contains +a notice placed by the copyright holder saying it may be distributed +under the terms of this General Public License. The "Program", below, +refers to any such program or work, and a "work based on the Program" +means either the Program or any derivative work under copyright law: +that is to say, a work containing the Program or a portion of it, +either verbatim or with modifications and/or translated into another +language. (Hereinafter, translation is included without limitation in +the term "modification".) Each licensee is addressed as "you". + +Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running the Program is not restricted, and the output from the Program +is covered only if its contents constitute a work based on the +Program (independent of having been made by running the Program). +Whether that is true depends on what the Program does. + + 1. You may copy and distribute verbatim copies of the Program's +source code as you receive it, in any medium, provided that you +conspicuously and appropriately publish on each copy an appropriate +copyright notice and disclaimer of warranty; keep intact all the +notices that refer to this License and to the absence of any warranty; +and give any other recipients of the Program a copy of this License +along with the Program. + +You may charge a fee for the physical act of transferring a copy, and +you may at your option offer warranty protection in exchange for a fee. + + 2. You may modify your copy or copies of the Program or any portion +of it, thus forming a work based on the Program, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) You must cause the modified files to carry prominent notices + stating that you changed the files and the date of any change. + + b) You must cause any work that you distribute or publish, that in + whole or in part contains or is derived from the Program or any + part thereof, to be licensed as a whole at no charge to all third + parties under the terms of this License. + + c) If the modified program normally reads commands interactively + when run, you must cause it, when started running for such + interactive use in the most ordinary way, to print or display an + announcement including an appropriate copyright notice and a + notice that there is no warranty (or else, saying that you provide + a warranty) and that users may redistribute the program under + these conditions, and telling the user how to view a copy of this + License. (Exception: if the Program itself is interactive but + does not normally print such an announcement, your work based on + the Program is not required to print an announcement.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Program, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Program, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Program. + +In addition, mere aggregation of another work not based on the Program +with the Program (or with a work based on the Program) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may copy and distribute the Program (or a work based on it, +under Section 2) in object code or executable form under the terms of +Sections 1 and 2 above provided that you also do one of the following: + + a) Accompany it with the complete corresponding machine-readable + source code, which must be distributed under the terms of Sections + 1 and 2 above on a medium customarily used for software interchange; or, + + b) Accompany it with a written offer, valid for at least three + years, to give any third party, for a charge no more than your + cost of physically performing source distribution, a complete + machine-readable copy of the corresponding source code, to be + distributed under the terms of Sections 1 and 2 above on a medium + customarily used for software interchange; or, + + c) Accompany it with the information you received as to the offer + to distribute corresponding source code. (This alternative is + allowed only for noncommercial distribution and only if you + received the program in object code or executable form with such + an offer, in accord with Subsection b above.) + +The source code for a work means the preferred form of the work for +making modifications to it. For an executable work, complete source +code means all the source code for all modules it contains, plus any +associated interface definition files, plus the scripts used to +control compilation and installation of the executable. However, as a +special exception, the source code distributed need not include +anything that is normally distributed (in either source or binary +form) with the major components (compiler, kernel, and so on) of the +operating system on which the executable runs, unless that component +itself accompanies the executable. + +If distribution of executable or object code is made by offering +access to copy from a designated place, then offering equivalent +access to copy the source code from the same place counts as +distribution of the source code, even though third parties are not +compelled to copy the source along with the object code. + + 4. You may not copy, modify, sublicense, or distribute the Program +except as expressly provided under this License. Any attempt +otherwise to copy, modify, sublicense or distribute the Program is +void, and will automatically terminate your rights under this License. +However, parties who have received copies, or rights, from you under +this License will not have their licenses terminated so long as such +parties remain in full compliance. + + 5. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Program or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Program (or any work based on the +Program), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Program or works based on it. + + 6. Each time you redistribute the Program (or any work based on the +Program), the recipient automatically receives a license from the +original licensor to copy, distribute or modify the Program subject to +these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties to +this License. + + 7. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Program at all. For example, if a patent +license would not permit royalty-free redistribution of the Program by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Program. + +If any portion of this section is held invalid or unenforceable under +any particular circumstance, the balance of the section is intended to +apply and the section as a whole is intended to apply in other +circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system, which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 8. If the distribution and/or use of the Program is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Program under this License +may add an explicit geographical distribution limitation excluding +those countries, so that distribution is permitted only in or among +countries not thus excluded. In such case, this License incorporates +the limitation as if written in the body of this License. + + 9. The Free Software Foundation may publish revised and/or new versions +of the General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + +Each version is given a distinguishing version number. If the Program +specifies a version number of this License which applies to it and "any +later version", you have the option of following the terms and conditions +either of that version or of any later version published by the Free +Software Foundation. If the Program does not specify a version number of +this License, you may choose any version ever published by the Free Software +Foundation. + + 10. If you wish to incorporate parts of the Program into other free +programs whose distribution conditions are different, write to the author +to ask for permission. For software which is copyrighted by the Free +Software Foundation, write to the Free Software Foundation; we sometimes +make exceptions for this. Our decision will be guided by the two goals +of preserving the free status of all derivatives of our free software and +of promoting the sharing and reuse of software generally. + + NO WARRANTY + + 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY +FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN +OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES +PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED +OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS +TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE +PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, +REPAIR OR CORRECTION. + + 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR +REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, +INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING +OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED +TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY +YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER +PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE +POSSIBILITY OF SUCH DAMAGES. + + END OF TERMS AND CONDITIONS + + +Business Source License 1.1 + +License text copyright (c) 2017 MariaDB Corporation Ab, All Rights Reserved. +"Business Source License" is a trademark of MariaDB Corporation Ab. + +----------------------------------------------------------------------------- + +Parameters + +Licensor: Morpho Association + +Licensed Work: Morpho Blue Core + The Licensed Work is (c) 2023 Morpho Association + +Additional Use Grant: Any uses listed and defined at + morpho-blue-core-license-grants.morpho.eth + +Change Date: The earlier of (i) 2026-01-01, or (ii) a date specified + at morpho-blue-core-license-date.morpho.eth, or (iii) + upon the activation of the setFee function of the + Licensed Work’s applicable protocol smart contracts + deployed for production use. + +Change License: GNU General Public License v2.0 or later + +----------------------------------------------------------------------------- + +Terms + +The Licensor hereby grants you the right to copy, modify, create derivative +works, redistribute, and make non-production use of the Licensed Work. The +Licensor may make an Additional Use Grant, above, permitting limited +production use. + +Effective on the Change Date, or the fourth anniversary of the first publicly +available distribution of a specific version of the Licensed Work under this +License, whichever comes first, the Licensor hereby grants you rights under +the terms of the Change License, and the rights granted in the paragraph +above terminate. + +If your use of the Licensed Work does not comply with the requirements +currently in effect as described in this License, you must purchase a +commercial license from the Licensor, its affiliated entities, or authorized +resellers, or you must refrain from using the Licensed Work. + +All copies of the original and modified Licensed Work, and derivative works +of the Licensed Work, are subject to this License. This License applies +separately for each version of the Licensed Work and the Change Date may vary +for each version of the Licensed Work released by Licensor. + +You must conspicuously display this License on each original or modified copy +of the Licensed Work. If you receive the Licensed Work in original or +modified form from a third party, the terms and conditions set forth in this +License apply to your use of that work. + +Any use of the Licensed Work in violation of this License will automatically +terminate your rights under this License for the current and all other +versions of the Licensed Work. + +This License does not grant you any right in any trademark or logo of +Licensor or its affiliates (provided that you may use a trademark or logo of +Licensor as expressly required by this License). + +TO THE EXTENT PERMITTED BY APPLICABLE LAW, THE LICENSED WORK IS PROVIDED ON +AN "AS IS" BASIS. LICENSOR HEREBY DISCLAIMS ALL WARRANTIES AND CONDITIONS, +EXPRESS OR IMPLIED, INCLUDING (WITHOUT LIMITATION) WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, NON-INFRINGEMENT, AND +TITLE. + +MariaDB hereby grants you permission to use this License’s text to license +your works, and to refer to it using the trademark "Business Source License", +as long as you comply with the Covenants of Licensor below. + +----------------------------------------------------------------------------- + +Covenants of Licensor + +In consideration of the right to use this License’s text and the "Business +Source License" name and trademark, Licensor covenants to MariaDB, and to all +other recipients of the licensed work to be provided by Licensor: + +1. To specify as the Change License the GPL Version 2.0 or any later version, + or a license that is compatible with GPL Version 2.0 or a later version, + where "compatible" means that software provided under the Change License can + be included in a program with software provided under GPL Version 2.0 or a + later version. Licensor may specify additional Change Licenses without + limitation. + +2. To either: (a) specify an additional grant of rights to use that does not + impose any additional restriction on the right granted in this License, as + the Additional Use Grant; or (b) insert the text "None". + +3. To specify a Change Date. + +4. Not to modify this License in any other way. + +----------------------------------------------------------------------------- + +Notice + +The Business Source License (this document, or the "License") is not an Open +Source license. However, the Licensed Work will eventually be made available +under an Open Source License, as stated in this License. diff --git a/contracts/protocols/mainnet/metamorpho/libraries/ConstantsLib.sol b/contracts/protocols/mainnet/metamorpho/libraries/ConstantsLib.sol new file mode 100644 index 0000000..c4e49fe --- /dev/null +++ b/contracts/protocols/mainnet/metamorpho/libraries/ConstantsLib.sol @@ -0,0 +1,21 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +pragma solidity ^0.8.0; + +/// @dev The maximum fee a market can have (25%). +uint256 constant MAX_FEE = 0.25e18; + +/// @dev Oracle price scale. +uint256 constant ORACLE_PRICE_SCALE = 1e36; + +/// @dev Liquidation cursor. +uint256 constant LIQUIDATION_CURSOR = 0.3e18; + +/// @dev Max liquidation incentive factor. +uint256 constant MAX_LIQUIDATION_INCENTIVE_FACTOR = 1.15e18; + +/// @dev The EIP-712 typeHash for EIP712Domain. +bytes32 constant DOMAIN_TYPEHASH = keccak256("EIP712Domain(uint256 chainId,address verifyingContract)"); + +/// @dev The EIP-712 typeHash for Authorization. +bytes32 constant AUTHORIZATION_TYPEHASH = + keccak256("Authorization(address authorizer,address authorized,bool isAuthorized,uint256 nonce,uint256 deadline)"); diff --git a/contracts/protocols/mainnet/metamorpho/libraries/ErrorsLib.sol b/contracts/protocols/mainnet/metamorpho/libraries/ErrorsLib.sol new file mode 100644 index 0000000..02cc944 --- /dev/null +++ b/contracts/protocols/mainnet/metamorpho/libraries/ErrorsLib.sol @@ -0,0 +1,80 @@ +// 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 error messages. +library ErrorsLib { + /// @notice Thrown when the caller is not the owner. + string internal constant NOT_OWNER = "not owner"; + + /// @notice Thrown when the LLTV to enable exceeds the maximum LLTV. + string internal constant MAX_LLTV_EXCEEDED = "max LLTV exceeded"; + + /// @notice Thrown when the fee to set exceeds the maximum fee. + string internal constant MAX_FEE_EXCEEDED = "max fee exceeded"; + + /// @notice Thrown when the value is already set. + string internal constant ALREADY_SET = "already set"; + + /// @notice Thrown when the IRM is not enabled at market creation. + string internal constant IRM_NOT_ENABLED = "IRM not enabled"; + + /// @notice Thrown when the LLTV is not enabled at market creation. + string internal constant LLTV_NOT_ENABLED = "LLTV not enabled"; + + /// @notice Thrown when the market is already created. + string internal constant MARKET_ALREADY_CREATED = "market already created"; + + /// @notice Thrown when a token to transfer doesn't have code. + string internal constant NO_CODE = "no code"; + + /// @notice Thrown when the market is not created. + string internal constant MARKET_NOT_CREATED = "market not created"; + + /// @notice Thrown when not exactly one of the input amount is zero. + string internal constant INCONSISTENT_INPUT = "inconsistent input"; + + /// @notice Thrown when zero assets is passed as input. + string internal constant ZERO_ASSETS = "zero assets"; + + /// @notice Thrown when a zero address is passed as input. + string internal constant ZERO_ADDRESS = "zero address"; + + /// @notice Thrown when the caller is not authorized to conduct an action. + string internal constant UNAUTHORIZED = "unauthorized"; + + /// @notice Thrown when the collateral is insufficient to `borrow` or `withdrawCollateral`. + string internal constant INSUFFICIENT_COLLATERAL = "insufficient collateral"; + + /// @notice Thrown when the liquidity is insufficient to `withdraw` or `borrow`. + string internal constant INSUFFICIENT_LIQUIDITY = "insufficient liquidity"; + + /// @notice Thrown when the position to liquidate is healthy. + string internal constant HEALTHY_POSITION = "position is healthy"; + + /// @notice Thrown when the authorization signature is invalid. + string internal constant INVALID_SIGNATURE = "invalid signature"; + + /// @notice Thrown when the authorization signature is expired. + string internal constant SIGNATURE_EXPIRED = "signature expired"; + + /// @notice Thrown when the nonce is invalid. + string internal constant INVALID_NONCE = "invalid nonce"; + + /// @notice Thrown when a token transfer reverted. + string internal constant TRANSFER_REVERTED = "transfer reverted"; + + /// @notice Thrown when a token transfer returned false. + string internal constant TRANSFER_RETURNED_FALSE = "transfer returned false"; + + /// @notice Thrown when a token transferFrom reverted. + string internal constant TRANSFER_FROM_REVERTED = "transferFrom reverted"; + + /// @notice Thrown when a token transferFrom returned false + string internal constant TRANSFER_FROM_RETURNED_FALSE = "transferFrom returned false"; + + /// @notice Thrown when the maximum uint128 is exceeded. + string internal constant MAX_UINT128_EXCEEDED = "max uint128 exceeded"; +} diff --git a/contracts/protocols/mainnet/metamorpho/libraries/EventsLib.sol b/contracts/protocols/mainnet/metamorpho/libraries/EventsLib.sol new file mode 100644 index 0000000..2ff9b82 --- /dev/null +++ b/contracts/protocols/mainnet/metamorpho/libraries/EventsLib.sol @@ -0,0 +1,150 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +pragma solidity ^0.8.0; + +import {Id, MarketParams} from "../interfaces/IMorpho.sol"; + +/// @title EventsLib +/// @author Morpho Labs +/// @custom:contact security@morpho.org +/// @notice Library exposing events. +library EventsLib { + /// @notice Emitted when setting a new owner. + /// @param newOwner The new owner of the contract. + event SetOwner(address indexed newOwner); + + /// @notice Emitted when setting a new fee. + /// @param id The market id. + /// @param newFee The new fee. + event SetFee(Id indexed id, uint256 newFee); + + /// @notice Emitted when setting a new fee recipient. + /// @param newFeeRecipient The new fee recipient. + event SetFeeRecipient(address indexed newFeeRecipient); + + /// @notice Emitted when enabling an IRM. + /// @param irm The IRM that was enabled. + event EnableIrm(address indexed irm); + + /// @notice Emitted when enabling an LLTV. + /// @param lltv The LLTV that was enabled. + event EnableLltv(uint256 lltv); + + /// @notice Emitted when creating a market. + /// @param id The market id. + /// @param marketParams The market that was created. + event CreateMarket(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. + event Supply(Id indexed id, address indexed caller, address indexed 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. + event Withdraw( + Id indexed id, + address caller, + address indexed onBehalf, + address indexed 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. + event Borrow( + Id indexed id, + address caller, + address indexed onBehalf, + address indexed 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. + event Repay(Id indexed id, address indexed caller, address indexed 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. + event SupplyCollateral(Id indexed id, address indexed caller, address indexed 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. + event WithdrawCollateral( + Id indexed id, address caller, address indexed onBehalf, address indexed 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. + event Liquidate( + Id indexed id, + address indexed caller, + address indexed 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. + event FlashLoan(address indexed caller, address indexed 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. + event SetAuthorization( + address indexed caller, address indexed authorizer, address indexed 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. + event IncrementNonce(address indexed caller, address indexed 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. + event AccrueInterest(Id indexed id, uint256 prevBorrowRate, uint256 interest, uint256 feeShares); +} diff --git a/contracts/protocols/mainnet/metamorpho/libraries/LICENSE b/contracts/protocols/mainnet/metamorpho/libraries/LICENSE new file mode 100644 index 0000000..aec4e2a --- /dev/null +++ b/contracts/protocols/mainnet/metamorpho/libraries/LICENSE @@ -0,0 +1,389 @@ +This software is available under your choice of the GNU General Public +License, version 2 or later, or the Business Source License, as set +forth below. + + GNU GENERAL PUBLIC LICENSE + Version 2, June 1991 + + Copyright (C) 1989, 1991 Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +License is intended to guarantee your freedom to share and change free +software--to make sure the software is free for all its users. This +General Public License applies to most of the Free Software +Foundation's software and to any other program whose authors commit to +using it. (Some other Free Software Foundation software is covered by +the GNU Lesser General Public License instead.) You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +this service if you wish), that you receive source code or can get it +if you want it, that you can change the software or use pieces of it +in new free programs; and that you know you can do these things. + + To protect your rights, we need to make restrictions that forbid +anyone to deny you these rights or to ask you to surrender the rights. +These restrictions translate to certain responsibilities for you if you +distribute copies of the software, or if you modify it. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must give the recipients all the rights that +you have. You must make sure that they, too, receive or can get the +source code. And you must show them these terms so they know their +rights. + + We protect your rights with two steps: (1) copyright the software, and +(2) offer you this license which gives you legal permission to copy, +distribute and/or modify the software. + + Also, for each author's protection and ours, we want to make certain +that everyone understands that there is no warranty for this free +software. If the software is modified by someone else and passed on, we +want its recipients to know that what they have is not the original, so +that any problems introduced by others will not reflect on the original +authors' reputations. + + Finally, any free program is threatened constantly by software +patents. We wish to avoid the danger that redistributors of a free +program will individually obtain patent licenses, in effect making the +program proprietary. To prevent this, we have made it clear that any +patent must be licensed for everyone's free use or not licensed at all. + + The precise terms and conditions for copying, distribution and +modification follow. + + GNU GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License applies to any program or other work which contains +a notice placed by the copyright holder saying it may be distributed +under the terms of this General Public License. The "Program", below, +refers to any such program or work, and a "work based on the Program" +means either the Program or any derivative work under copyright law: +that is to say, a work containing the Program or a portion of it, +either verbatim or with modifications and/or translated into another +language. (Hereinafter, translation is included without limitation in +the term "modification".) Each licensee is addressed as "you". + +Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running the Program is not restricted, and the output from the Program +is covered only if its contents constitute a work based on the +Program (independent of having been made by running the Program). +Whether that is true depends on what the Program does. + + 1. You may copy and distribute verbatim copies of the Program's +source code as you receive it, in any medium, provided that you +conspicuously and appropriately publish on each copy an appropriate +copyright notice and disclaimer of warranty; keep intact all the +notices that refer to this License and to the absence of any warranty; +and give any other recipients of the Program a copy of this License +along with the Program. + +You may charge a fee for the physical act of transferring a copy, and +you may at your option offer warranty protection in exchange for a fee. + + 2. You may modify your copy or copies of the Program or any portion +of it, thus forming a work based on the Program, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) You must cause the modified files to carry prominent notices + stating that you changed the files and the date of any change. + + b) You must cause any work that you distribute or publish, that in + whole or in part contains or is derived from the Program or any + part thereof, to be licensed as a whole at no charge to all third + parties under the terms of this License. + + c) If the modified program normally reads commands interactively + when run, you must cause it, when started running for such + interactive use in the most ordinary way, to print or display an + announcement including an appropriate copyright notice and a + notice that there is no warranty (or else, saying that you provide + a warranty) and that users may redistribute the program under + these conditions, and telling the user how to view a copy of this + License. (Exception: if the Program itself is interactive but + does not normally print such an announcement, your work based on + the Program is not required to print an announcement.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Program, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Program, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Program. + +In addition, mere aggregation of another work not based on the Program +with the Program (or with a work based on the Program) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may copy and distribute the Program (or a work based on it, +under Section 2) in object code or executable form under the terms of +Sections 1 and 2 above provided that you also do one of the following: + + a) Accompany it with the complete corresponding machine-readable + source code, which must be distributed under the terms of Sections + 1 and 2 above on a medium customarily used for software interchange; or, + + b) Accompany it with a written offer, valid for at least three + years, to give any third party, for a charge no more than your + cost of physically performing source distribution, a complete + machine-readable copy of the corresponding source code, to be + distributed under the terms of Sections 1 and 2 above on a medium + customarily used for software interchange; or, + + c) Accompany it with the information you received as to the offer + to distribute corresponding source code. (This alternative is + allowed only for noncommercial distribution and only if you + received the program in object code or executable form with such + an offer, in accord with Subsection b above.) + +The source code for a work means the preferred form of the work for +making modifications to it. For an executable work, complete source +code means all the source code for all modules it contains, plus any +associated interface definition files, plus the scripts used to +control compilation and installation of the executable. However, as a +special exception, the source code distributed need not include +anything that is normally distributed (in either source or binary +form) with the major components (compiler, kernel, and so on) of the +operating system on which the executable runs, unless that component +itself accompanies the executable. + +If distribution of executable or object code is made by offering +access to copy from a designated place, then offering equivalent +access to copy the source code from the same place counts as +distribution of the source code, even though third parties are not +compelled to copy the source along with the object code. + + 4. You may not copy, modify, sublicense, or distribute the Program +except as expressly provided under this License. Any attempt +otherwise to copy, modify, sublicense or distribute the Program is +void, and will automatically terminate your rights under this License. +However, parties who have received copies, or rights, from you under +this License will not have their licenses terminated so long as such +parties remain in full compliance. + + 5. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Program or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Program (or any work based on the +Program), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Program or works based on it. + + 6. Each time you redistribute the Program (or any work based on the +Program), the recipient automatically receives a license from the +original licensor to copy, distribute or modify the Program subject to +these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties to +this License. + + 7. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Program at all. For example, if a patent +license would not permit royalty-free redistribution of the Program by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Program. + +If any portion of this section is held invalid or unenforceable under +any particular circumstance, the balance of the section is intended to +apply and the section as a whole is intended to apply in other +circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system, which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 8. If the distribution and/or use of the Program is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Program under this License +may add an explicit geographical distribution limitation excluding +those countries, so that distribution is permitted only in or among +countries not thus excluded. In such case, this License incorporates +the limitation as if written in the body of this License. + + 9. The Free Software Foundation may publish revised and/or new versions +of the General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + +Each version is given a distinguishing version number. If the Program +specifies a version number of this License which applies to it and "any +later version", you have the option of following the terms and conditions +either of that version or of any later version published by the Free +Software Foundation. If the Program does not specify a version number of +this License, you may choose any version ever published by the Free Software +Foundation. + + 10. If you wish to incorporate parts of the Program into other free +programs whose distribution conditions are different, write to the author +to ask for permission. For software which is copyrighted by the Free +Software Foundation, write to the Free Software Foundation; we sometimes +make exceptions for this. Our decision will be guided by the two goals +of preserving the free status of all derivatives of our free software and +of promoting the sharing and reuse of software generally. + + NO WARRANTY + + 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY +FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN +OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES +PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED +OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS +TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE +PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, +REPAIR OR CORRECTION. + + 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR +REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, +INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING +OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED +TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY +YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER +PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE +POSSIBILITY OF SUCH DAMAGES. + + END OF TERMS AND CONDITIONS + + +Business Source License 1.1 + +License text copyright (c) 2017 MariaDB Corporation Ab, All Rights Reserved. +"Business Source License" is a trademark of MariaDB Corporation Ab. + +----------------------------------------------------------------------------- + +Parameters + +Licensor: Morpho Association + +Licensed Work: Morpho Blue Core + The Licensed Work is (c) 2023 Morpho Association + +Additional Use Grant: Any uses listed and defined at + morpho-blue-core-license-grants.morpho.eth + +Change Date: The earlier of (i) 2026-01-01, or (ii) a date specified + at morpho-blue-core-license-date.morpho.eth, or (iii) + upon the activation of the setFee function of the + Licensed Work’s applicable protocol smart contracts + deployed for production use. + +Change License: GNU General Public License v2.0 or later + +----------------------------------------------------------------------------- + +Terms + +The Licensor hereby grants you the right to copy, modify, create derivative +works, redistribute, and make non-production use of the Licensed Work. The +Licensor may make an Additional Use Grant, above, permitting limited +production use. + +Effective on the Change Date, or the fourth anniversary of the first publicly +available distribution of a specific version of the Licensed Work under this +License, whichever comes first, the Licensor hereby grants you rights under +the terms of the Change License, and the rights granted in the paragraph +above terminate. + +If your use of the Licensed Work does not comply with the requirements +currently in effect as described in this License, you must purchase a +commercial license from the Licensor, its affiliated entities, or authorized +resellers, or you must refrain from using the Licensed Work. + +All copies of the original and modified Licensed Work, and derivative works +of the Licensed Work, are subject to this License. This License applies +separately for each version of the Licensed Work and the Change Date may vary +for each version of the Licensed Work released by Licensor. + +You must conspicuously display this License on each original or modified copy +of the Licensed Work. If you receive the Licensed Work in original or +modified form from a third party, the terms and conditions set forth in this +License apply to your use of that work. + +Any use of the Licensed Work in violation of this License will automatically +terminate your rights under this License for the current and all other +versions of the Licensed Work. + +This License does not grant you any right in any trademark or logo of +Licensor or its affiliates (provided that you may use a trademark or logo of +Licensor as expressly required by this License). + +TO THE EXTENT PERMITTED BY APPLICABLE LAW, THE LICENSED WORK IS PROVIDED ON +AN "AS IS" BASIS. LICENSOR HEREBY DISCLAIMS ALL WARRANTIES AND CONDITIONS, +EXPRESS OR IMPLIED, INCLUDING (WITHOUT LIMITATION) WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, NON-INFRINGEMENT, AND +TITLE. + +MariaDB hereby grants you permission to use this License’s text to license +your works, and to refer to it using the trademark "Business Source License", +as long as you comply with the Covenants of Licensor below. + +----------------------------------------------------------------------------- + +Covenants of Licensor + +In consideration of the right to use this License’s text and the "Business +Source License" name and trademark, Licensor covenants to MariaDB, and to all +other recipients of the licensed work to be provided by Licensor: + +1. To specify as the Change License the GPL Version 2.0 or any later version, + or a license that is compatible with GPL Version 2.0 or a later version, + where "compatible" means that software provided under the Change License can + be included in a program with software provided under GPL Version 2.0 or a + later version. Licensor may specify additional Change Licenses without + limitation. + +2. To either: (a) specify an additional grant of rights to use that does not + impose any additional restriction on the right granted in this License, as + the Additional Use Grant; or (b) insert the text "None". + +3. To specify a Change Date. + +4. Not to modify this License in any other way. + +----------------------------------------------------------------------------- + +Notice + +The Business Source License (this document, or the "License") is not an Open +Source license. However, the Licensed Work will eventually be made available +under an Open Source License, as stated in this License. diff --git a/contracts/protocols/mainnet/metamorpho/libraries/MarketParamsLib.sol b/contracts/protocols/mainnet/metamorpho/libraries/MarketParamsLib.sol new file mode 100644 index 0000000..456b0e1 --- /dev/null +++ b/contracts/protocols/mainnet/metamorpho/libraries/MarketParamsLib.sol @@ -0,0 +1,21 @@ +// 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 5 * 32 because `MarketParams` has 5 variables of 32 bytes each. + uint256 internal constant MARKET_PARAMS_BYTES_LENGTH = 5 * 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) + } + } +} diff --git a/contracts/protocols/mainnet/metamorpho/libraries/MathLib.sol b/contracts/protocols/mainnet/metamorpho/libraries/MathLib.sol new file mode 100644 index 0000000..653db4f --- /dev/null +++ b/contracts/protocols/mainnet/metamorpho/libraries/MathLib.sol @@ -0,0 +1,45 @@ +// 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; + } +} diff --git a/contracts/protocols/mainnet/metamorpho/libraries/SafeTransferLib.sol b/contracts/protocols/mainnet/metamorpho/libraries/SafeTransferLib.sol new file mode 100644 index 0000000..02c3c0a --- /dev/null +++ b/contracts/protocols/mainnet/metamorpho/libraries/SafeTransferLib.sol @@ -0,0 +1,36 @@ +// 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 { + require(address(token).code.length > 0, ErrorsLib.NO_CODE); + + (bool success, bytes memory 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); + } + + function safeTransferFrom(IERC20 token, address from, address to, uint256 value) internal { + require(address(token).code.length > 0, ErrorsLib.NO_CODE); + + (bool success, bytes memory 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); + } +} diff --git a/contracts/protocols/mainnet/metamorpho/libraries/SharesMathLib.sol b/contracts/protocols/mainnet/metamorpho/libraries/SharesMathLib.sol new file mode 100644 index 0000000..3ed7115 --- /dev/null +++ b/contracts/protocols/mainnet/metamorpho/libraries/SharesMathLib.sol @@ -0,0 +1,45 @@ +// 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); + } +} diff --git a/contracts/protocols/mainnet/metamorpho/libraries/UtilsLib.sol b/contracts/protocols/mainnet/metamorpho/libraries/UtilsLib.sol new file mode 100644 index 0000000..f343ef7 --- /dev/null +++ b/contracts/protocols/mainnet/metamorpho/libraries/UtilsLib.sol @@ -0,0 +1,38 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +pragma solidity ^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. +library UtilsLib { + /// @dev Returns true if there is exactly one zero among `x` and `y`. + function exactlyOneZero(uint256 x, uint256 y) internal pure returns (bool z) { + assembly { + z := xor(iszero(x), iszero(y)) + } + } + + /// @dev Returns the min of `x` and `y`. + function min(uint256 x, uint256 y) internal pure returns (uint256 z) { + assembly { + z := xor(x, mul(xor(x, y), lt(y, x))) + } + } + + /// @dev Returns `x` safely cast to uint128. + function toUint128(uint256 x) internal pure returns (uint128) { + require(x <= type(uint128).max, ErrorsLib.MAX_UINT128_EXCEEDED); + return uint128(x); + } + + /// @dev Returns max(0, x - y). + function zeroFloorSub(uint256 x, uint256 y) internal pure returns (uint256 z) { + assembly { + z := mul(gt(x, y), sub(x, y)) + } + } +} diff --git a/contracts/protocols/mainnet/metamorpho/libraries/periphery/MorphoBalancesLib.sol b/contracts/protocols/mainnet/metamorpho/libraries/periphery/MorphoBalancesLib.sol new file mode 100644 index 0000000..94b3b85 --- /dev/null +++ b/contracts/protocols/mainnet/metamorpho/libraries/periphery/MorphoBalancesLib.sol @@ -0,0 +1,120 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +pragma solidity ^0.8.0; + +import {Id, MarketParams, Market, IMorpho} from "../../interfaces/IMorpho.sol"; +import {IIrm} from "../../interfaces/IIrm.sol"; + +import {MathLib} from "../MathLib.sol"; +import {UtilsLib} from "../UtilsLib.sol"; +import {MorphoLib} from "./MorphoLib.sol"; +import {SharesMathLib} from "../SharesMathLib.sol"; +import {MarketParamsLib} from "../MarketParamsLib.sol"; + +/// @title MorphoBalancesLib +/// @author Morpho Labs +/// @custom:contact security@morpho.org +/// @notice Helper library exposing getters with the expected value after interest accrual. +/// @dev This library is not used in Morpho itself and is intended to be used by integrators. +/// @dev The getter to retrieve the expected total borrow shares is not exposed because interest accrual does not apply +/// to it. The value can be queried directly on Morpho using `totalBorrowShares`. +library MorphoBalancesLib { + using MathLib for uint256; + using MathLib for uint128; + using UtilsLib for uint256; + using MorphoLib for IMorpho; + using SharesMathLib for uint256; + using MarketParamsLib for MarketParams; + + /// @notice Returns the expected market balances of a market after having accrued interest. + /// @return The expected total supply assets. + /// @return The expected total supply shares. + /// @return The expected total borrow assets. + /// @return The expected total borrow shares. + function expectedMarketBalances(IMorpho morpho, MarketParams memory marketParams) + internal + view + returns (uint256, uint256, uint256, uint256) + { + Id id = marketParams.id(); + Market memory market = morpho.market(id); + + uint256 elapsed = block.timestamp - market.lastUpdate; + + // Skipped if elapsed == 0 or totalBorrowAssets == 0 because interest would be null, or if irm == address(0). + if (elapsed != 0 && market.totalBorrowAssets != 0 && marketParams.irm != address(0)) { + uint256 borrowRate = IIrm(marketParams.irm).borrowRateView(marketParams, market); + uint256 interest = market.totalBorrowAssets.wMulDown(borrowRate.wTaylorCompounded(elapsed)); + market.totalBorrowAssets += interest.toUint128(); + market.totalSupplyAssets += interest.toUint128(); + + if (market.fee != 0) { + uint256 feeAmount = interest.wMulDown(market.fee); + // The fee amount is subtracted from the total supply in this calculation to compensate for the fact + // that total supply is already updated. + uint256 feeShares = + feeAmount.toSharesDown(market.totalSupplyAssets - feeAmount, market.totalSupplyShares); + market.totalSupplyShares += feeShares.toUint128(); + } + } + + return (market.totalSupplyAssets, market.totalSupplyShares, market.totalBorrowAssets, market.totalBorrowShares); + } + + /// @notice Returns the expected total supply assets of a market after having accrued interest. + function expectedTotalSupplyAssets(IMorpho morpho, MarketParams memory marketParams) + internal + view + returns (uint256 totalSupplyAssets) + { + (totalSupplyAssets,,,) = expectedMarketBalances(morpho, marketParams); + } + + /// @notice Returns the expected total borrow assets of a market after having accrued interest. + function expectedTotalBorrowAssets(IMorpho morpho, MarketParams memory marketParams) + internal + view + returns (uint256 totalBorrowAssets) + { + (,, totalBorrowAssets,) = expectedMarketBalances(morpho, marketParams); + } + + /// @notice Returns the expected total supply shares of a market after having accrued interest. + function expectedTotalSupplyShares(IMorpho morpho, MarketParams memory marketParams) + internal + view + returns (uint256 totalSupplyShares) + { + (, totalSupplyShares,,) = expectedMarketBalances(morpho, marketParams); + } + + /// @notice Returns the expected supply assets balance of `user` on a market after having accrued interest. + /// @dev Warning: Wrong for `feeRecipient` because their supply shares increase is not taken into account. + /// @dev Warning: Withdrawing using the expected supply assets can lead to a revert due to conversion roundings from + /// assets to shares. + function expectedSupplyAssets(IMorpho morpho, MarketParams memory marketParams, address user) + internal + view + returns (uint256) + { + Id id = marketParams.id(); + uint256 supplyShares = morpho.supplyShares(id, user); + (uint256 totalSupplyAssets, uint256 totalSupplyShares,,) = expectedMarketBalances(morpho, marketParams); + + return supplyShares.toAssetsDown(totalSupplyAssets, totalSupplyShares); + } + + /// @notice Returns the expected borrow assets balance of `user` on a market after having accrued interest. + /// @dev Warning: The expected balance is rounded up, so it may be greater than the market's expected total borrow + /// assets. + function expectedBorrowAssets(IMorpho morpho, MarketParams memory marketParams, address user) + internal + view + returns (uint256) + { + Id id = marketParams.id(); + uint256 borrowShares = morpho.borrowShares(id, user); + (,, uint256 totalBorrowAssets, uint256 totalBorrowShares) = expectedMarketBalances(morpho, marketParams); + + return borrowShares.toAssetsUp(totalBorrowAssets, totalBorrowShares); + } +} diff --git a/contracts/protocols/mainnet/metamorpho/libraries/periphery/MorphoLib.sol b/contracts/protocols/mainnet/metamorpho/libraries/periphery/MorphoLib.sol new file mode 100644 index 0000000..c366d1a --- /dev/null +++ b/contracts/protocols/mainnet/metamorpho/libraries/periphery/MorphoLib.sol @@ -0,0 +1,63 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +pragma solidity ^0.8.0; + +import {IMorpho, Id} from "../../interfaces/IMorpho.sol"; +import {MorphoStorageLib} from "./MorphoStorageLib.sol"; + +/// @title MorphoLib +/// @author Morpho Labs +/// @custom:contact security@morpho.org +/// @notice Helper library to access Morpho storage variables. +/// @dev Warning: Supply and borrow getters may return outdated values that do not include accrued interest. +library MorphoLib { + function supplyShares(IMorpho morpho, Id id, address user) internal view returns (uint256) { + bytes32[] memory slot = _array(MorphoStorageLib.positionSupplySharesSlot(id, user)); + return uint256(morpho.extSloads(slot)[0]); + } + + function borrowShares(IMorpho morpho, Id id, address user) internal view returns (uint256) { + bytes32[] memory slot = _array(MorphoStorageLib.positionBorrowSharesAndCollateralSlot(id, user)); + return uint128(uint256(morpho.extSloads(slot)[0])); + } + + function collateral(IMorpho morpho, Id id, address user) internal view returns (uint256) { + bytes32[] memory slot = _array(MorphoStorageLib.positionBorrowSharesAndCollateralSlot(id, user)); + return uint256(morpho.extSloads(slot)[0] >> 128); + } + + function totalSupplyAssets(IMorpho morpho, Id id) internal view returns (uint256) { + bytes32[] memory slot = _array(MorphoStorageLib.marketTotalSupplyAssetsAndSharesSlot(id)); + return uint128(uint256(morpho.extSloads(slot)[0])); + } + + function totalSupplyShares(IMorpho morpho, Id id) internal view returns (uint256) { + bytes32[] memory slot = _array(MorphoStorageLib.marketTotalSupplyAssetsAndSharesSlot(id)); + return uint256(morpho.extSloads(slot)[0] >> 128); + } + + function totalBorrowAssets(IMorpho morpho, Id id) internal view returns (uint256) { + bytes32[] memory slot = _array(MorphoStorageLib.marketTotalBorrowAssetsAndSharesSlot(id)); + return uint128(uint256(morpho.extSloads(slot)[0])); + } + + function totalBorrowShares(IMorpho morpho, Id id) internal view returns (uint256) { + bytes32[] memory slot = _array(MorphoStorageLib.marketTotalBorrowAssetsAndSharesSlot(id)); + return uint256(morpho.extSloads(slot)[0] >> 128); + } + + function lastUpdate(IMorpho morpho, Id id) internal view returns (uint256) { + bytes32[] memory slot = _array(MorphoStorageLib.marketLastUpdateAndFeeSlot(id)); + return uint128(uint256(morpho.extSloads(slot)[0])); + } + + function fee(IMorpho morpho, Id id) internal view returns (uint256) { + bytes32[] memory slot = _array(MorphoStorageLib.marketLastUpdateAndFeeSlot(id)); + return uint256(morpho.extSloads(slot)[0] >> 128); + } + + function _array(bytes32 x) private pure returns (bytes32[] memory) { + bytes32[] memory res = new bytes32[](1); + res[0] = x; + return res; + } +} diff --git a/contracts/protocols/mainnet/metamorpho/libraries/periphery/MorphoStorageLib.sol b/contracts/protocols/mainnet/metamorpho/libraries/periphery/MorphoStorageLib.sol new file mode 100644 index 0000000..07e3900 --- /dev/null +++ b/contracts/protocols/mainnet/metamorpho/libraries/periphery/MorphoStorageLib.sol @@ -0,0 +1,109 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +pragma solidity ^0.8.0; + +import {Id} from "../../interfaces/IMorpho.sol"; + +/// @title MorphoStorageLib +/// @author Morpho Labs +/// @custom:contact security@morpho.org +/// @notice Helper library exposing getters to access Morpho storage variables' slot. +/// @dev This library is not used in Morpho itself and is intended to be used by integrators. +library MorphoStorageLib { + /* SLOTS */ + + uint256 internal constant OWNER_SLOT = 0; + uint256 internal constant FEE_RECIPIENT_SLOT = 1; + uint256 internal constant POSITION_SLOT = 2; + uint256 internal constant MARKET_SLOT = 3; + uint256 internal constant IS_IRM_ENABLED_SLOT = 4; + uint256 internal constant IS_LLTV_ENABLED_SLOT = 5; + uint256 internal constant IS_AUTHORIZED_SLOT = 6; + uint256 internal constant NONCE_SLOT = 7; + uint256 internal constant ID_TO_MARKET_PARAMS_SLOT = 8; + + /* SLOT OFFSETS */ + + uint256 internal constant LOAN_TOKEN_OFFSET = 0; + uint256 internal constant COLLATERAL_TOKEN_OFFSET = 1; + uint256 internal constant ORACLE_OFFSET = 2; + uint256 internal constant IRM_OFFSET = 3; + uint256 internal constant LLTV_OFFSET = 4; + + uint256 internal constant SUPPLY_SHARES_OFFSET = 0; + uint256 internal constant BORROW_SHARES_AND_COLLATERAL_OFFSET = 1; + + uint256 internal constant TOTAL_SUPPLY_ASSETS_AND_SHARES_OFFSET = 0; + uint256 internal constant TOTAL_BORROW_ASSETS_AND_SHARES_OFFSET = 1; + uint256 internal constant LAST_UPDATE_AND_FEE_OFFSET = 2; + + /* GETTERS */ + + function ownerSlot() internal pure returns (bytes32) { + return bytes32(OWNER_SLOT); + } + + function feeRecipientSlot() internal pure returns (bytes32) { + return bytes32(FEE_RECIPIENT_SLOT); + } + + function positionSupplySharesSlot(Id id, address user) internal pure returns (bytes32) { + return bytes32( + uint256(keccak256(abi.encode(user, keccak256(abi.encode(id, POSITION_SLOT))))) + SUPPLY_SHARES_OFFSET + ); + } + + function positionBorrowSharesAndCollateralSlot(Id id, address user) internal pure returns (bytes32) { + return bytes32( + uint256(keccak256(abi.encode(user, keccak256(abi.encode(id, POSITION_SLOT))))) + + BORROW_SHARES_AND_COLLATERAL_OFFSET + ); + } + + function marketTotalSupplyAssetsAndSharesSlot(Id id) internal pure returns (bytes32) { + return bytes32(uint256(keccak256(abi.encode(id, MARKET_SLOT))) + TOTAL_SUPPLY_ASSETS_AND_SHARES_OFFSET); + } + + function marketTotalBorrowAssetsAndSharesSlot(Id id) internal pure returns (bytes32) { + return bytes32(uint256(keccak256(abi.encode(id, MARKET_SLOT))) + TOTAL_BORROW_ASSETS_AND_SHARES_OFFSET); + } + + function marketLastUpdateAndFeeSlot(Id id) internal pure returns (bytes32) { + return bytes32(uint256(keccak256(abi.encode(id, MARKET_SLOT))) + LAST_UPDATE_AND_FEE_OFFSET); + } + + function isIrmEnabledSlot(address irm) internal pure returns (bytes32) { + return keccak256(abi.encode(irm, IS_IRM_ENABLED_SLOT)); + } + + function isLltvEnabledSlot(uint256 lltv) internal pure returns (bytes32) { + return keccak256(abi.encode(lltv, IS_LLTV_ENABLED_SLOT)); + } + + function isAuthorizedSlot(address authorizer, address authorizee) internal pure returns (bytes32) { + return keccak256(abi.encode(authorizee, keccak256(abi.encode(authorizer, IS_AUTHORIZED_SLOT)))); + } + + function nonceSlot(address authorizer) internal pure returns (bytes32) { + return keccak256(abi.encode(authorizer, NONCE_SLOT)); + } + + function idToLoanTokenSlot(Id id) internal pure returns (bytes32) { + return bytes32(uint256(keccak256(abi.encode(id, ID_TO_MARKET_PARAMS_SLOT))) + LOAN_TOKEN_OFFSET); + } + + function idToCollateralTokenSlot(Id id) internal pure returns (bytes32) { + return bytes32(uint256(keccak256(abi.encode(id, ID_TO_MARKET_PARAMS_SLOT))) + COLLATERAL_TOKEN_OFFSET); + } + + function idToOracleSlot(Id id) internal pure returns (bytes32) { + return bytes32(uint256(keccak256(abi.encode(id, ID_TO_MARKET_PARAMS_SLOT))) + ORACLE_OFFSET); + } + + function idToIrmSlot(Id id) internal pure returns (bytes32) { + return bytes32(uint256(keccak256(abi.encode(id, ID_TO_MARKET_PARAMS_SLOT))) + IRM_OFFSET); + } + + function idToLltvSlot(Id id) internal pure returns (bytes32) { + return bytes32(uint256(keccak256(abi.encode(id, ID_TO_MARKET_PARAMS_SLOT))) + LLTV_OFFSET); + } +} diff --git a/contracts/protocols/mainnet/metamorpho/main.sol b/contracts/protocols/mainnet/metamorpho/main.sol new file mode 100644 index 0000000..7c36133 --- /dev/null +++ b/contracts/protocols/mainnet/metamorpho/main.sol @@ -0,0 +1,251 @@ +// SPDX-License-Identifier: MIT +pragma solidity >=0.8.0; +import "./interface.sol"; +import { MarketParams, Market, IMorpho } from "./interfaces/IMorpho.sol"; +import { IIrm } from "./interfaces/IIrm.sol"; +import { IMetaMorpho } from "./interfaces/IMetaMorpho.sol"; +import { MathLib } from "./libraries/MathLib.sol"; +import { MorphoBalancesLib } from "./libraries/periphery/MorphoBalancesLib.sol"; + +contract MetamorphoResolver { + using MathLib for uint256; + using MorphoBalancesLib for IMorpho; + + address internal constant MORPHO_BLUE = 0xBBBBBbbBBb9cC5e90e3b3Af64bdAF62C37EEFFCb; + + struct VaultData { + bool isToken; + string name; + string symbol; + uint256 decimals; + address asset; + uint256 totalAssets; + uint256 totalSupply; + uint256 convertToShares; + uint256 convertToAssets; + } + + struct UserPosition { + uint256 underlyingBalance; + uint256 vaultBalance; + } + + struct VaultPreview { + uint256 previewDeposit; + uint256 previewMint; + uint256 previewWithdraw; + uint256 previewRedeem; + uint256 decimals; + uint256 underlyingDecimals; + } + + struct MetaMorphoDetails { + uint256 totalCap; + address loanToken; + address collateralToken; + uint256 lltv; + uint256 fee; + bool enabled; // Whether the market is in the withdraw queue + uint256 apy; + VaultData vaultData; + } + + function getVaultDetails(address[] memory vaultAddresses) public view returns (VaultData[] memory) { + VaultData[] memory _vaultData = new VaultData[](vaultAddresses.length); + for (uint256 i = 0; i < vaultAddresses.length; i++) { + VaultInterface vaultToken = VaultInterface(vaultAddresses[i]); + bool isToken = true; + + try vaultToken.symbol() {} catch { + isToken = false; + continue; + } + + try vaultToken.name() {} catch { + isToken = false; + continue; + } + + try vaultToken.decimals() {} catch { + isToken = false; + continue; + } + + try vaultToken.asset() {} catch { + isToken = false; + continue; + } + + TokenInterface _underlyingToken = TokenInterface(vaultToken.asset()); + + _vaultData[i] = VaultData( + isToken, + vaultToken.name(), + vaultToken.symbol(), + vaultToken.decimals(), + vaultToken.asset(), + vaultToken.totalAssets(), + vaultToken.totalSupply(), + vaultToken.convertToShares(10 ** _underlyingToken.decimals()), // example convertToShares + vaultToken.convertToAssets(10 ** vaultToken.decimals()) // example convertToAssets for 10 ** decimal + ); + } + + return _vaultData; + } + + function getPositions(address owner, address[] memory vaultAddress) public view returns (UserPosition[] memory) { + UserPosition[] memory _userPosition = new UserPosition[](vaultAddress.length); + for (uint256 i = 0; i < vaultAddress.length; i++) { + VaultInterface vaultToken = VaultInterface(vaultAddress[i]); + + address _underlyingAddress = vaultToken.asset(); + TokenInterface underlyingToken = TokenInterface(_underlyingAddress); + + _userPosition[i].underlyingBalance = underlyingToken.balanceOf(owner); + _userPosition[i].vaultBalance = vaultToken.balanceOf(owner); + } + + return _userPosition; + } + + function getAllowances(address owner, address[] memory vaultAddresses) public view returns (uint256[] memory) { + uint256[] memory _tokenAllowance = new uint256[](vaultAddresses.length); + + for (uint256 i = 0; i < vaultAddresses.length; i++) { + VaultInterface vaultToken = VaultInterface(vaultAddresses[i]); + _tokenAllowance[i] = vaultToken.allowance(owner, vaultAddresses[i]); + } + + return _tokenAllowance; + } + + function getVaultPreview( + uint256 amount, + address[] memory vaultAddresses + ) public view returns (VaultPreview[] memory) { + VaultPreview[] memory _vaultPreview = new VaultPreview[](vaultAddresses.length); + + for (uint256 i = 0; i < vaultAddresses.length; i++) { + VaultInterface vaultToken = VaultInterface(vaultAddresses[i]); + + _vaultPreview[i].previewDeposit = vaultToken.previewDeposit(amount); + _vaultPreview[i].previewMint = vaultToken.previewMint(amount); + _vaultPreview[i].previewWithdraw = vaultToken.previewWithdraw(amount); + _vaultPreview[i].previewRedeem = vaultToken.previewRedeem(amount); + _vaultPreview[i].decimals = vaultToken.decimals(); + TokenInterface _underlyingToken = TokenInterface(vaultToken.asset()); + _vaultPreview[i].underlyingDecimals = _underlyingToken.decimals(); + } + + return _vaultPreview; + } + + function getMetaMorphoDetails(address[] memory vaultAddresses) public view returns (MetaMorphoDetails[] memory) { + MetaMorphoDetails[] memory _metaMorphotData = new MetaMorphoDetails[](vaultAddresses.length); + + VaultData[] memory _vaultDatas = getVaultDetails(vaultAddresses); + + for (uint256 i = 0; i < vaultAddresses.length; i++) { + MetaMorphoInterface vaultToken = MetaMorphoInterface(vaultAddresses[i]); + + _metaMorphotData[i].apy = supplyAPYVault(vaultAddresses[i]); + + try vaultToken.fee() {} catch { + continue; + } + + try vaultToken.supplyQueue(0) {} catch { + continue; + } + + ( + _metaMorphotData[i].loanToken, + _metaMorphotData[i].collateralToken, + , + , + _metaMorphotData[i].lltv + ) = MorphoBlueInterface(MORPHO_BLUE).idToMarketParams(vaultToken.supplyQueue(0)); + + uint184 cap; + (cap, _metaMorphotData[i].enabled, ) = vaultToken.config(vaultToken.supplyQueue(0)); + + _metaMorphotData[i].totalCap = uint256(cap); + + _metaMorphotData[i].fee = vaultToken.fee(); + + _metaMorphotData[i].vaultData = _vaultDatas[i]; + } + + return _metaMorphotData; + } + + /// @notice Returns the current APY of the vault on a Morpho Blue market. + /// @param marketParams The morpho blue market parameters. + /// @param market The morpho blue market state. + function supplyAPYMarket(MarketParams memory marketParams, Market memory market) + public + view + returns (uint256 supplyRate) + { + // Get the borrow rate + uint256 borrowRate; + if (marketParams.irm == address(0)) { + return 0; + } else { + borrowRate = IIrm(marketParams.irm).borrowRateView(marketParams, market).wTaylorCompounded(365 days); + } + + (uint256 totalSupplyAssets,, uint256 totalBorrowAssets,) = + IMorpho(MORPHO_BLUE).expectedMarketBalances(marketParams); + + // Get the supply rate + uint256 utilization = totalBorrowAssets == 0 ? 0 : totalBorrowAssets.wDivUp(totalSupplyAssets); + + supplyRate = borrowRate.wMulDown(1 ether - market.fee).wMulDown(utilization); + } + + /// @notice Returns the current APY of a MetaMorpho vault. + /// @dev It is computed as the sum of all APY of enabled markets weighted by the supply on these markets. + /// @param vault The address of the MetaMorpho vault. + function supplyAPYVault(address vault) public view returns (uint256 avgSupplyRate) { + uint256 ratio; + uint256 queueLength = IMetaMorpho(vault).withdrawQueueLength(); + + uint256 totalAmount = totalDepositVault(vault); + + for (uint256 i; i < queueLength; ++i) { + Id idMarket = IMetaMorpho(vault).withdrawQueue(i); + + MarketParams memory marketParams = IMorpho(MORPHO_BLUE).idToMarketParams(idMarket); + Market memory market = IMorpho(MORPHO_BLUE).market(idMarket); + + uint256 currentSupplyAPY = supplyAPYMarket(marketParams, market); + uint256 vaultAsset = vaultAssetsInMarket(vault, marketParams); + ratio += currentSupplyAPY.wMulDown(vaultAsset); + } + + avgSupplyRate = ratio.wDivUp(totalAmount); + } + + /// @notice Returns the total assets deposited into a MetaMorpho `vault`. + /// @param vault The address of the MetaMorpho vault. + function totalDepositVault(address vault) public view returns (uint256 totalAssets) { + totalAssets = IMetaMorpho(vault).totalAssets(); + } + + /// @notice Returns the total assets supplied into a specific morpho blue market by a MetaMorpho `vault`. + /// @param vault The address of the MetaMorpho vault. + /// @param marketParams The morpho blue market. + function vaultAssetsInMarket(address vault, MarketParams memory marketParams) + public + view + returns (uint256 assets) + { + assets = IMorpho(MORPHO_BLUE).expectedSupplyAssets(marketParams, vault); + } +} + +contract InstaMetamorphoResolver is MetamorphoResolver { + string public constant name = "Metamorpho-Resolver-v1.0"; +} diff --git a/test/mainnet/metamorpho.ts b/test/mainnet/metamorpho.ts new file mode 100644 index 0000000..948c513 --- /dev/null +++ b/test/mainnet/metamorpho.ts @@ -0,0 +1,91 @@ +import { SignerWithAddress } from "@nomiclabs/hardhat-ethers/signers"; +import { expect } from "chai"; +import { parseEther } from "ethers/lib/utils"; +import { ethers } from "hardhat"; +import { InstaMetamorphoResolver, InstaMetamorphoResolver__factory } from "../../typechain"; +import hre from "hardhat"; + +describe("Metamorpho Resolver", () => { + let signer: SignerWithAddress; + const account = "0xa58cfe09f3bb372b513d5799cacc25e5e62c90ea"; + + const vaultAddresses: string[] = [ + '0x38989bba00bdf8181f4082995b3deae96163ac5d', + '0xBEEF01735c132Ada46AA9aA4c54623cAA92A64CB', + ]; + + before(async () => { + await hre.network.provider.request({ + method: "hardhat_reset", + params: [ + { + forking: { + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + jsonRpcUrl: hre.config.networks.hardhat.forking.url, + blockNumber: 19106931, + }, + }, + ], + }); + + [signer] = await ethers.getSigners(); + }); + + describe("Metamorpho Resolver", () => { + let resolver: InstaMetamorphoResolver; + + before(async () => { + const deployer = new InstaMetamorphoResolver__factory(signer); + resolver = await deployer.deploy(); + }); + + it("get token details should revert with a wrong token address", async () => { + await expect( + resolver.getVaultDetails([ + await ethers.Wallet.createRandom().getAddress(), // add a random address + ]), + ).to.revertedWith("function call to a non-contract account"); + }); + + it("token balances reverts if not a token", async () => { + await expect(resolver.getPositions(account, [await ethers.Wallet.createRandom().getAddress()])).to.revertedWith( + "function call to a non-contract account", + ); + }); + + it("gets user positions properly", async () => { + const tokenData = await resolver.getPositions(account, vaultAddresses); + + console.log("tokenData :>> ", tokenData); + }); + + it("token allowances reverts if not a token", async () => { + await expect(resolver.getAllowances(account, [await ethers.Wallet.createRandom().getAddress()])).to.revertedWith( + "function call to a non-contract account", + ); + }); + + it("gets token allowances properly", async () => { + const res = await resolver.getAllowances(account, vaultAddresses); + + console.log("res :>> ", res); + }); + + it("gets Vault Preview", async () => { + const _vaultPreview = await resolver.getVaultPreview(parseEther("1"), vaultAddresses); + + console.log("_vaultPreview :>> ", _vaultPreview); + }); + + it("gets MetaMorpho Details", async () => { + const metaMorphoMarkets = [ + '0x38989bba00bdf8181f4082995b3deae96163ac5d', + '0xBEEF01735c132Ada46AA9aA4c54623cAA92A64CB', + ] + const _metamorphpDetails = await resolver.getMetaMorphoDetails(metaMorphoMarkets); + + console.log("_metamorphpDetails :>> ", _metamorphpDetails); + }); + }); +});