From 19dd322655e27638c3076d039c9c4ad324bdecde Mon Sep 17 00:00:00 2001 From: Shriya Tyagi Date: Wed, 10 Jan 2024 20:36:26 +0400 Subject: [PATCH] feat: update morpho libraries --- .../morpho-blue/interfaces/IIrm.sol | 5 +-- .../morpho-blue/interfaces/IMorpho.sol | 31 ++++++++++--------- .../morpho-blue/libraries/ErrorsLib.sol | 3 ++ .../morpho-blue/libraries/EventsLib.sol | 17 +++++----- .../morpho-blue/libraries/SafeTransferLib.sol | 7 +++-- .../morpho-blue/libraries/SharesMathLib.sol | 3 ++ .../morpho-blue/libraries/UtilsLib.sol | 2 +- .../libraries/periphery/MorphoBalancesLib.sol | 13 ++++---- 8 files changed, 47 insertions(+), 34 deletions(-) diff --git a/contracts/mainnet/connectors/morpho-blue/interfaces/IIrm.sol b/contracts/mainnet/connectors/morpho-blue/interfaces/IIrm.sol index db2aaf8..3de0bc1 100644 --- a/contracts/mainnet/connectors/morpho-blue/interfaces/IIrm.sol +++ b/contracts/mainnet/connectors/morpho-blue/interfaces/IIrm.sol @@ -8,11 +8,12 @@ import {MarketParams, Market} from "./IMorpho.sol"; /// @custom:contact security@morpho.org /// @notice Interface that Interest Rate Models (IRMs) used by Morpho must implement. interface IIrm { - /// @notice Returns the borrow rate of the market `marketParams`. + /// @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 of the market `marketParams` without modifying any storage. + /// @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/mainnet/connectors/morpho-blue/interfaces/IMorpho.sol b/contracts/mainnet/connectors/morpho-blue/interfaces/IMorpho.sol index b928e50..9c82524 100644 --- a/contracts/mainnet/connectors/morpho-blue/interfaces/IMorpho.sol +++ b/contracts/mainnet/connectors/morpho-blue/interfaces/IMorpho.sol @@ -70,7 +70,7 @@ interface IMorphoBase { /// @notice Whether the `lltv` is enabled. function isLltvEnabled(uint256 lltv) external view returns (bool); - /// @notice Whether `authorized` is authorized to modify `authorizer`'s positions. + /// @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); @@ -91,6 +91,7 @@ interface IMorphoBase { 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; @@ -129,12 +130,12 @@ interface IMorphoBase { /// @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 usecases 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 If the supply of a market gets depleted, the supply share price instantly resets to - /// `VIRTUAL_ASSETS`:`VIRTUAL_SHARES`. + /// @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. @@ -150,7 +151,7 @@ interface IMorphoBase { bytes memory data ) external returns (uint256 assetsSupplied, uint256 sharesSupplied); - /// @notice Withdraws `assets` or `shares` on behalf of `onBehalf` to `receiver`. + /// @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. @@ -171,14 +172,14 @@ interface IMorphoBase { address receiver ) external returns (uint256 assetsWithdrawn, uint256 sharesWithdrawn); - /// @notice Borrows `assets` or `shares` on behalf of `onBehalf` to `receiver`. - /// @dev Either `assets` or `shares` should be zero. Most usecases 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 If the borrow of a market gets depleted, the borrow share price instantly resets to - /// `VIRTUAL_ASSETS`:`VIRTUAL_SHARES`. + /// @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. @@ -200,6 +201,7 @@ interface IMorphoBase { /// @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. @@ -226,7 +228,7 @@ interface IMorphoBase { function supplyCollateral(MarketParams memory marketParams, uint256 assets, address onBehalf, bytes memory data) external; - /// @notice Withdraws `assets` of collateral on behalf of `onBehalf` to `receiver`. + /// @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. @@ -242,6 +244,7 @@ interface IMorphoBase { /// @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. diff --git a/contracts/mainnet/connectors/morpho-blue/libraries/ErrorsLib.sol b/contracts/mainnet/connectors/morpho-blue/libraries/ErrorsLib.sol index 893a6b3..02cc944 100644 --- a/contracts/mainnet/connectors/morpho-blue/libraries/ErrorsLib.sol +++ b/contracts/mainnet/connectors/morpho-blue/libraries/ErrorsLib.sol @@ -27,6 +27,9 @@ library ErrorsLib { /// @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"; diff --git a/contracts/mainnet/connectors/morpho-blue/libraries/EventsLib.sol b/contracts/mainnet/connectors/morpho-blue/libraries/EventsLib.sol index 825ea17..2ff9b82 100644 --- a/contracts/mainnet/connectors/morpho-blue/libraries/EventsLib.sol +++ b/contracts/mainnet/connectors/morpho-blue/libraries/EventsLib.sol @@ -35,9 +35,10 @@ library EventsLib { 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 address that received the supply. + /// @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); @@ -45,7 +46,7 @@ library EventsLib { /// @notice Emitted on withdrawal of assets. /// @param id The market id. /// @param caller The caller. - /// @param onBehalf The address from which the assets were withdrawn. + /// @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. @@ -61,7 +62,7 @@ library EventsLib { /// @notice Emitted on borrow of assets. /// @param id The market id. /// @param caller The caller. - /// @param onBehalf The address from which the assets were borrowed. + /// @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. @@ -77,7 +78,7 @@ library EventsLib { /// @notice Emitted on repayment of assets. /// @param id The market id. /// @param caller The caller. - /// @param onBehalf The address for which the assets were repaid. + /// @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); @@ -85,14 +86,14 @@ library EventsLib { /// @notice Emitted on supply of collateral. /// @param id The market id. /// @param caller The caller. - /// @param onBehalf The address that received the collateral. + /// @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 address from which the collateral was withdrawn. + /// @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( @@ -106,7 +107,8 @@ library EventsLib { /// @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 badDebtShares The amount of shares minted as bad debt. + /// @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, @@ -114,6 +116,7 @@ library EventsLib { uint256 repaidAssets, uint256 repaidShares, uint256 seizedAssets, + uint256 badDebtAssets, uint256 badDebtShares ); diff --git a/contracts/mainnet/connectors/morpho-blue/libraries/SafeTransferLib.sol b/contracts/mainnet/connectors/morpho-blue/libraries/SafeTransferLib.sol index d31a791..02c3c0a 100644 --- a/contracts/mainnet/connectors/morpho-blue/libraries/SafeTransferLib.sol +++ b/contracts/mainnet/connectors/morpho-blue/libraries/SafeTransferLib.sol @@ -15,18 +15,19 @@ interface IERC20Internal { /// @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. -/// @dev It is the responsibility of the market creator to make sure that the address of the token has non-zero code. library SafeTransferLib { - /// @dev Warning: It does not revert on `token` with no code. 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); } - /// @dev Warning: It does not revert on `token` with no code. 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); diff --git a/contracts/mainnet/connectors/morpho-blue/libraries/SharesMathLib.sol b/contracts/mainnet/connectors/morpho-blue/libraries/SharesMathLib.sol index 5147606..3ed7115 100644 --- a/contracts/mainnet/connectors/morpho-blue/libraries/SharesMathLib.sol +++ b/contracts/mainnet/connectors/morpho-blue/libraries/SharesMathLib.sol @@ -14,6 +14,9 @@ library SharesMathLib { /// @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 diff --git a/contracts/mainnet/connectors/morpho-blue/libraries/UtilsLib.sol b/contracts/mainnet/connectors/morpho-blue/libraries/UtilsLib.sol index 066043d..f343ef7 100644 --- a/contracts/mainnet/connectors/morpho-blue/libraries/UtilsLib.sol +++ b/contracts/mainnet/connectors/morpho-blue/libraries/UtilsLib.sol @@ -29,7 +29,7 @@ library UtilsLib { return uint128(x); } - /// @dev Returns max(x - y, 0). + /// @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/mainnet/connectors/morpho-blue/libraries/periphery/MorphoBalancesLib.sol b/contracts/mainnet/connectors/morpho-blue/libraries/periphery/MorphoBalancesLib.sol index 3afabfd..94b3b85 100644 --- a/contracts/mainnet/connectors/morpho-blue/libraries/periphery/MorphoBalancesLib.sol +++ b/contracts/mainnet/connectors/morpho-blue/libraries/periphery/MorphoBalancesLib.sol @@ -36,13 +36,12 @@ library MorphoBalancesLib { 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 of if totalBorrowAssets == 0 because interest would be null. - if (elapsed != 0 && market.totalBorrowAssets != 0) { + // 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(); @@ -90,8 +89,8 @@ library MorphoBalancesLib { /// @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 a supply position using the expected assets balance can lead to a revert due to - /// conversion roundings between shares and assets. + /// @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 @@ -105,8 +104,8 @@ library MorphoBalancesLib { } /// @notice Returns the expected borrow assets balance of `user` on a market after having accrued interest. - /// @dev Warning: repaying a borrow position using the expected assets balance can lead to a revert due to - /// conversion roundings between shares and assets. + /// @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