From 76272bcd047672bbf902aac9b789fc1025e63387 Mon Sep 17 00:00:00 2001 From: MerlinEgalite Date: Tue, 8 Aug 2023 14:43:21 +0200 Subject: [PATCH 01/12] docs: add Natspecs --- src/Blue.sol | 114 ++++++++++----- src/interfaces/IBlue.sol | 230 +++++++++++++++++++++++++++--- src/interfaces/IBlueCallbacks.sol | 32 +++++ src/interfaces/IERC20.sol | 7 +- src/interfaces/IFlashLender.sol | 10 +- src/interfaces/IIrm.sol | 5 + src/interfaces/IOracle.sol | 5 + src/libraries/BlueLib.sol | 4 + src/libraries/Errors.sol | 20 +++ src/libraries/MarketLib.sol | 7 +- src/libraries/SharesMath.sol | 21 +++ 11 files changed, 388 insertions(+), 67 deletions(-) diff --git a/src/Blue.sol b/src/Blue.sol index 660ffd91c..3d00d3ed3 100644 --- a/src/Blue.sol +++ b/src/Blue.sol @@ -11,96 +11,107 @@ import { import {IIrm} from "./interfaces/IIrm.sol"; import {IERC20} from "./interfaces/IERC20.sol"; import {IOracle} from "./interfaces/IOracle.sol"; -import {Id, Market, Signature, IBlue} from "./interfaces/IBlue.sol"; +import {Id, Market, Signature, IBlue, IFlashLender} from "./interfaces/IBlue.sol"; import {Errors} from "./libraries/Errors.sol"; -import {SharesMath} from "./libraries/SharesMath.sol"; -import {FixedPointMathLib} from "./libraries/FixedPointMathLib.sol"; import {MarketLib} from "./libraries/MarketLib.sol"; +import {SharesMath} from "./libraries/SharesMath.sol"; import {SafeTransferLib} from "./libraries/SafeTransferLib.sol"; +import {FixedPointMathLib} from "./libraries/FixedPointMathLib.sol"; +/// @dev The maximum fee a market can have (25%). uint256 constant MAX_FEE = 0.25e18; +/// @dev The alpha parameter used to compute the incentive during a liquidation. uint256 constant ALPHA = 0.5e18; - /// @dev The EIP-712 typeHash for EIP712Domain. bytes32 constant DOMAIN_TYPEHASH = keccak256("EIP712Domain(string name,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)"); +/// @title Blue +/// @author Morpho Labs +/// @custom:contact security@morpho.xyz +/// @notice The Blue contract. contract Blue is IBlue { - using SharesMath for uint256; using FixedPointMathLib for uint256; using SafeTransferLib for IERC20; + using SharesMath for uint256; using MarketLib for Market; - // Immutables. + /* IMMUTABLES */ + /// @inheritdoc IBlue bytes32 public immutable DOMAIN_SEPARATOR; - // Storage. + /* STORAGE */ - // Owner. + /// @inheritdoc IBlue address public owner; - // Fee recipient. + /// @inheritdoc IBlue address public feeRecipient; - // User' supply balances. + /// @inheritdoc IBlue mapping(Id => mapping(address => uint256)) public supplyShares; - // User' borrow balances. + /// @inheritdoc IBlue mapping(Id => mapping(address => uint256)) public borrowShares; - // User' collateral balance. + /// @inheritdoc IBlue mapping(Id => mapping(address => uint256)) public collateral; - // Market total supply. + /// @inheritdoc IBlue mapping(Id => uint256) public totalSupply; - // Market total supply shares. + /// @inheritdoc IBlue mapping(Id => uint256) public totalSupplyShares; - // Market total borrow. + /// @inheritdoc IBlue mapping(Id => uint256) public totalBorrow; - // Market total borrow shares. + /// @inheritdoc IBlue mapping(Id => uint256) public totalBorrowShares; - // Interests last update (used to check if a market has been created). + /// @inheritdoc IBlue mapping(Id => uint256) public lastUpdate; - // Fee. + /// @inheritdoc IBlue mapping(Id => uint256) public fee; - // Enabled IRMs. + /// @inheritdoc IBlue mapping(address => bool) public isIrmEnabled; - // Enabled LLTVs. + /// @inheritdoc IBlue mapping(uint256 => bool) public isLltvEnabled; - // User's authorizations. Note that by default, msg.sender is authorized by themself. + /// @inheritdoc IBlue mapping(address => mapping(address => bool)) public isAuthorized; - // User's nonces. Used to prevent replay attacks with EIP-712 signatures. + /// @inheritdoc IBlue mapping(address => uint256) public nonce; - // Constructor. + /* CONSTRUCTOR */ + /// @notice Initializes the contract. + /// @param newOwner The new owner of the contract. constructor(address newOwner) { owner = newOwner; DOMAIN_SEPARATOR = keccak256(abi.encode(DOMAIN_TYPEHASH, keccak256("Blue"), block.chainid, address(this))); } - // Modifiers. + /* MODIFIERS */ + /// @notice Reverts if the caller is not the owner. modifier onlyOwner() { require(msg.sender == owner, Errors.NOT_OWNER); _; } - // Only owner functions. + /* ADMIN FUNCTIONS */ + /// @inheritdoc IBlue function setOwner(address newOwner) external onlyOwner { owner = newOwner; emit SetOwner(newOwner); } + /// @inheritdoc IBlue function enableIrm(address irm) external onlyOwner { isIrmEnabled[irm] = true; emit EnableIrm(address(irm)); } + /// @inheritdoc IBlue function enableLltv(uint256 lltv) external onlyOwner { require(lltv < FixedPointMathLib.WAD, Errors.LLTV_TOO_HIGH); isLltvEnabled[lltv] = true; @@ -108,7 +119,7 @@ contract Blue is IBlue { emit EnableLltv(lltv); } - /// @notice It is the owner's responsibility to ensure a fee recipient is set before setting a non-zero fee. + /// @inheritdoc IBlue function setFee(Market memory market, uint256 newFee) external onlyOwner { Id id = market.id(); require(lastUpdate[id] != 0, Errors.MARKET_NOT_CREATED); @@ -118,14 +129,16 @@ contract Blue is IBlue { emit SetFee(id, newFee); } + /// @inheritdoc IBlue function setFeeRecipient(address recipient) external onlyOwner { feeRecipient = recipient; emit SetFeeRecipient(recipient); } - // Markets management. + /* MARKET CREATION */ + /// @inheritdoc IBlue function createMarket(Market memory market) external { Id id = market.id(); require(isIrmEnabled[market.irm], Errors.IRM_NOT_ENABLED); @@ -136,8 +149,9 @@ contract Blue is IBlue { emit CreateMarket(id, market); } - // Supply management. + /* SUPPLY MANAGEMENT */ + /// @inheritdoc IBlue function supply(Market memory market, uint256 amount, address onBehalf, bytes calldata data) external { Id id = market.id(); require(lastUpdate[id] != 0, Errors.MARKET_NOT_CREATED); @@ -159,6 +173,7 @@ contract Blue is IBlue { IERC20(market.borrowableAsset).safeTransferFrom(msg.sender, address(this), amount); } + /// @inheritdoc IBlue function withdraw(Market memory market, uint256 shares, address onBehalf, address receiver) external { Id id = market.id(); require(lastUpdate[id] != 0, Errors.MARKET_NOT_CREATED); @@ -182,8 +197,9 @@ contract Blue is IBlue { IERC20(market.borrowableAsset).safeTransfer(receiver, amount); } - // Borrow management. + /* BORROW MANAGEMENT */ + /// @inheritdoc IBlue function borrow(Market memory market, uint256 amount, address onBehalf, address receiver) external { Id id = market.id(); require(lastUpdate[id] != 0, Errors.MARKET_NOT_CREATED); @@ -208,6 +224,7 @@ contract Blue is IBlue { IERC20(market.borrowableAsset).safeTransfer(receiver, amount); } + /// @inheritdoc IBlue function repay(Market memory market, uint256 shares, address onBehalf, bytes calldata data) external { Id id = market.id(); require(lastUpdate[id] != 0, Errors.MARKET_NOT_CREATED); @@ -229,9 +246,9 @@ contract Blue is IBlue { IERC20(market.borrowableAsset).safeTransferFrom(msg.sender, address(this), amount); } - // Collateral management. + /* COLLATERAL MANAGEMENT */ - /// @dev Don't accrue interests because it's not required and it saves gas. + /// @inheritdoc IBlue function supplyCollateral(Market memory market, uint256 amount, address onBehalf, bytes calldata data) external { Id id = market.id(); require(lastUpdate[id] != 0, Errors.MARKET_NOT_CREATED); @@ -249,6 +266,7 @@ contract Blue is IBlue { IERC20(market.collateralAsset).safeTransferFrom(msg.sender, address(this), amount); } + /// @inheritdoc IBlue function withdrawCollateral(Market memory market, uint256 amount, address onBehalf, address receiver) external { Id id = market.id(); require(lastUpdate[id] != 0, Errors.MARKET_NOT_CREATED); @@ -268,8 +286,9 @@ contract Blue is IBlue { IERC20(market.collateralAsset).safeTransfer(receiver, amount); } - // Liquidation. + /* LIQUIDATION */ + /// @inheritdoc IBlue function liquidate(Market memory market, address borrower, uint256 seized, bytes calldata data) external { Id id = market.id(); require(lastUpdate[id] != 0, Errors.MARKET_NOT_CREATED); @@ -314,8 +333,9 @@ contract Blue is IBlue { IERC20(market.borrowableAsset).safeTransferFrom(msg.sender, address(this), repaid); } - // Flash Loans. + /* FLASH LOANS */ + /// @inheritdoc IFlashLender function flashLoan(address token, uint256 amount, bytes calldata data) external { IERC20(token).safeTransfer(msg.sender, amount); @@ -326,9 +346,9 @@ contract Blue is IBlue { IERC20(token).safeTransferFrom(msg.sender, address(this), amount); } - // Authorizations. + /* AUTHORIZATION */ - /// @dev The signature is malleable, but it has no impact on the security here. + /// @inheritdoc IBlue function setAuthorization( address authorizer, address authorized, @@ -353,18 +373,23 @@ contract Blue is IBlue { emit SetAuthorization(msg.sender, authorizer, authorized, newIsAuthorized); } + /// @inheritdoc IBlue function setAuthorization(address authorized, bool newIsAuthorized) external { isAuthorized[msg.sender][authorized] = newIsAuthorized; emit SetAuthorization(msg.sender, msg.sender, authorized, newIsAuthorized); } + /// @dev Returns whether `user` is authorized to manage `msg.sender`'s positions. function _isSenderAuthorized(address user) internal view returns (bool) { return msg.sender == user || isAuthorized[user][msg.sender]; } - // Interests management. + /* INTEREST MANAGEMENT */ + /// @dev Accrues interests for a market. + /// @param market The market to accrue interests for. + /// @param id The market's id. function _accrueInterests(Market memory market, Id id) internal { uint256 elapsed = block.timestamp - lastUpdate[id]; @@ -393,8 +418,12 @@ contract Blue is IBlue { lastUpdate[id] = block.timestamp; } - // Health check. + /* HEALTH CHECK */ + /// @notice Returns whether a position is healthy. + /// @param market The market on which to check the health of `user`. + /// @param id The market's id. + /// @param user The user to check the health of. function _isHealthy(Market memory market, Id id, address user) internal view returns (bool) { if (borrowShares[id][user] == 0) return true; @@ -404,6 +433,12 @@ contract Blue is IBlue { return _isHealthy(market, id, user, collateralPrice, borrowablePrice); } + /// @notice Returns whether a position is healthy. + /// @param market The market on which to check the health of `user`. + /// @param id The market's id. + /// @param user The user to check the health of. + /// @param collateralPrice The collateral asset price. + /// @param borrowablePrice The borrowable asset price. function _isHealthy(Market memory market, Id id, address user, uint256 collateralPrice, uint256 borrowablePrice) internal view @@ -416,8 +451,9 @@ contract Blue is IBlue { return collateralValue.mulWadDown(market.lltv) >= borrowValue; } - // Storage view. + /* STORAGE VIEW */ + /// @inheritdoc IBlue function extsload(bytes32[] calldata slots) external view returns (bytes32[] memory res) { uint256 nSlots = slots.length; diff --git a/src/interfaces/IBlue.sol b/src/interfaces/IBlue.sol index 48b1424b3..15031bb17 100644 --- a/src/interfaces/IBlue.sol +++ b/src/interfaces/IBlue.sol @@ -21,13 +21,52 @@ struct Signature { bytes32 s; } +/// @title IBlue +/// @author Morpho Labs +/// @custom:contact security@morpho.xyz +/// @notice Interface of Blue. interface IBlue is IFlashLender { - event SupplyCollateral(Id indexed id, address indexed caller, address indexed onBehalf, uint256 amount); - event WithdrawCollateral( - Id indexed id, address caller, address indexed onBehalf, address indexed receiver, uint256 amount - ); + /// @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 fee The new fee. + event SetFee(Id indexed id, uint256 fee); + + /// @notice Emitted when setting a new fee recipient. + /// @param feeRecipient The new fee recipient. + event SetFeeRecipient(address indexed feeRecipient); + + /// @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 market The market that was created. + event CreateMarket(Id indexed id, Market market); + /// @notice Emitted on supply of assets. + /// @param id The market id. + /// @param caller The caller of the `supply` function. + /// @param onBehalf The address that will receive the position. + /// @param amount The amount of assets supplied. + /// @param shares The amount of shares minted. event Supply(Id indexed id, address indexed caller, address indexed onBehalf, uint256 amount, uint256 shares); + + /// @notice Emitted on withdrawal of assets. + /// @param id The market id. + /// @param caller The caller of the `withdraw` function. + /// @param onBehalf The address from which the assets are withdrawn. + /// @param receiver The address that will receive the withdrawn assets. + /// @param amount The amount of assets withdrawn. + /// @param shares The amount of shares burned. event Withdraw( Id indexed id, address caller, @@ -37,6 +76,13 @@ interface IBlue is IFlashLender { uint256 shares ); + /// @notice Emitted on borrow of assets. + /// @param id The market id. + /// @param caller The caller of the `borrow` function. + /// @param onBehalf The address from which the assets are borrowed. + /// @param receiver The address that will receive the borrowed assets. + /// @param amount The amount of assets borrowed. + /// @param shares The amount of shares minted. event Borrow( Id indexed id, address caller, @@ -45,8 +91,40 @@ interface IBlue is IFlashLender { uint256 amount, uint256 shares ); + + /// @notice Emitted on repayment of assets. + /// @param id The market id. + /// @param caller The caller of the `repay` function. + /// @param onBehalf The address for which the assets are repaid. + /// @param amount The amount of assets repaid. + /// @param shares The amount of shares burned. event Repay(Id indexed id, address indexed caller, address indexed onBehalf, uint256 amount, uint256 shares); + /// @notice Emitted on supply of collateral. + /// @param id The market id. + /// @param caller The caller of the `supplyCollateral` function. + /// @param onBehalf The address that will receive the position. + /// @param amount The amount of collateral supplied. + event SupplyCollateral(Id indexed id, address indexed caller, address indexed onBehalf, uint256 amount); + + /// @notice Emitted on withdrawal of collateral. + /// @param id The market id. + /// @param caller The caller of the `withdrawCollateral` function. + /// @param onBehalf The address from which the collateral is withdrawn. + /// @param receiver The address that will receive the withdrawn collateral. + /// @param amount The amount of collateral withdrawn. + event WithdrawCollateral( + Id indexed id, address caller, address indexed onBehalf, address indexed receiver, uint256 amount + ); + + /// @notice Emitted on liquidation of a position. + /// @param id The market id. + /// @param caller The caller of the `liquidate` function. + /// @param borrower The borrower of the position. + /// @param repaid The amount of assets repaid. + /// @param repaidShares The amount of shares burned. + /// @param seized The amount of collateral seized. + /// @param badDebtShares The amount of shares minted as bad debt. event Liquidate( Id indexed id, address indexed caller, @@ -57,65 +135,170 @@ interface IBlue is IFlashLender { uint256 badDebtShares ); + /// @notice Emitted on flash loan. + /// @param caller The caller of the `flashLoan` function. + /// @param token The token that was flash loaned. + /// @param amount The amount that was flash loaned. event FlashLoan(address indexed caller, address indexed token, uint256 amount); - event SetOwner(address indexed newOwner); - - event SetFee(Id indexed id, uint256 fee); - - event SetFeeRecipient(address indexed feeRecipient); - - event CreateMarket(Id indexed id, Market market); - + /// @notice Emitted when setting an authorization. + /// @param caller The caller of the authorization function. + /// @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 isAuthorized + address indexed caller, address indexed authorizer, address indexed authorized, bool newIsAuthorized ); - event IncrementNonce(address indexed caller, address indexed signatory, uint256 usedNonce); - - event EnableIrm(address indexed irm); - - event EnableLltv(uint256 lltv); + /// @notice Emitted when setting an authorization with a signature. + /// @param caller The caller of the authorization function. + /// @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 interests. + /// @param id The market id. + /// @param borrowRate The borrow rate. + /// @param accruedInterests The amount of interests accrued. + /// @param feeShares The amount of shares minted as fee. event AccrueInterests(Id indexed id, uint256 borrowRate, uint256 accruedInterests, uint256 feeShares); + /// @notice The EIP-712 domain separator. function DOMAIN_SEPARATOR() external view returns (bytes32); + /// @notice The owner of the contract. function owner() external view returns (address); + + /// @notice The fee recipient. function feeRecipient() external view returns (address); - function supplyShares(Id, address) external view returns (uint256); - function borrowShares(Id, address) external view returns (uint256); - function collateral(Id, address) external view returns (uint256); + /// @notice Users' supply balance. + function supplyShares(Id, address user) external view returns (uint256); + + /// @notice Users' borrow balances. + function borrowShares(Id, address user) external view returns (uint256); + + /// @notice Users' collateral balance. + function collateral(Id, address user) external view returns (uint256); + + /// @notice Market's total supply. function totalSupply(Id) external view returns (uint256); + + /// @notice Market's total supply shares. function totalSupplyShares(Id) external view returns (uint256); + + /// @notice Market's total borrow. function totalBorrow(Id) external view returns (uint256); + + /// @notice Market's total borrow shares. function totalBorrowShares(Id) external view returns (uint256); + + /// @notice Interests last update (used to check if a market has been created). function lastUpdate(Id) external view returns (uint256); + + /// @notice The market's fee. function fee(Id) external view returns (uint256); - function isIrmEnabled(address) external view returns (bool); + /// @notice Whether the `irm` is enabled. + function isIrmEnabled(address irm) external view returns (bool); + + /// @notice Whether the `lltv` is enabled. function isLltvEnabled(uint256) external view returns (bool); + + /// @notice User's authorizations. Note that by default, `msg.sender` is authorized by themself. function isAuthorized(address, address) external view returns (bool); + + /// @notice User's nonces. Used to prevent replay attacks with EIP-712 signatures. function nonce(address) external view returns (uint256); + /// @notice Sets the owner of the contract. + /// @param newOwner The new owner of the contract. function setOwner(address newOwner) external; + + /// @notice Enables an IRM. + /// @param irm The IRM to enable. function enableIrm(address irm) external; + + /// @notice Enables an LLTV. + /// @param lltv The LLTV to enable. function enableLltv(uint256 lltv) external; + + /// @notice Sets the fee for a market. + /// @param market The market to set the fee for. + /// @param newFee The new fee for the market. + /// @dev It is the `owner`'s responsibility to ensure a fee recipient is set before setting a non-zero fee. function setFee(Market memory market, uint256 newFee) external; + + /// @notice Sets the fee recipient. + /// @param recipient The new fee recipient. function setFeeRecipient(address recipient) external; + + /// @notice Creates a market. + /// @param market The market to create. function createMarket(Market memory market) external; + /// @notice Supplies assets to a market. + /// @param market The market to supply assets to. + /// @param amount The amount of assets to supply. + /// @param onBehalf The address that will receive the position. + /// @param data Arbitrary data to pass to the `onBlueSupply` callback. Pass empty data if not needed. function supply(Market memory market, uint256 amount, address onBehalf, bytes memory data) external; + + /// @notice Withdraws assets from a market. + /// @param market The market to withdraw assets from. + /// @param onBehalf The address from which to withdraw. + /// @param receiver The address that will receive the withdrawn assets. + /// @dev If `msg.sender != onBehalf`, `msg.sender` must be authorized to withdraw from `onBehalf`. function withdraw(Market memory market, uint256 amount, address onBehalf, address receiver) external; + + /// @notice Borrows assets from a market. + /// @param market The market to borrow assets from. + /// @param amount The amount of assets to borrow. + /// @param onBehalf The address from which to borrow. + /// @param receiver The address that will receive the borrowed assets. + /// @dev If `msg.sender != onBehalf`, `msg.sender` must be authorized to withdraw from `onBehalf`. function borrow(Market memory market, uint256 amount, address onBehalf, address receiver) external; + + /// @notice Repays assets to a market. + /// @param market The market to repay assets to. + /// @param onBehalf The address for which to repay. + /// @param data Arbitrary data to pass to the `onBlueRepay` callback. Pass empty data if not needed. function repay(Market memory market, uint256 amount, address onBehalf, bytes memory data) external; + + /// @notice Supplies collateral to a market. + /// @param market The market to supply collateral to. + /// @param amount The amount of collateral to supply. + /// @param onBehalf The address that will receive the position. + /// @param data Arbitrary data to pass to the `onBlueSupplyCollateral` callback. Pass empty data if not needed. + /// @dev Don't accrue interests because it's not required and it saves gas. function supplyCollateral(Market memory market, uint256 amount, address onBehalf, bytes memory data) external; + + /// @notice Withdraws collateral from a market. + /// @param market The market to withdraw collateral from. + /// @param amount The amount of collateral to withdraw. + /// @param onBehalf The address from which to withdraw. + /// @param receiver The address that will receive the withdrawn collateral. + /// @dev If `msg.sender != onBehalf`, `msg.sender` must be authorized to withdraw from `onBehalf`. function withdrawCollateral(Market memory market, uint256 amount, address onBehalf, address receiver) external; + + /// @notice Liquidates a position. + /// @param market The market of the position. + /// @param borrower The borrower of the position. + /// @param seized The amount of collateral to seize. + /// @param data Arbitrary data to pass to the `onBlueLiquidate` callback. Pass empty data if not needed function liquidate(Market memory market, address borrower, uint256 seized, bytes memory data) external; - function flashLoan(address token, uint256 amount, 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 `authorized` to manage `authorizer`'s positions. + /// @param authorizer The authorizer address. + /// @param authorized The authorized address. + /// @param newIsAuthorized The new authorization status. + /// @param deadline The deadline after which the signature is invalid. + /// @dev The signature is malleable, but it has no impact on the security here. function setAuthorization( address authorizer, address authorized, @@ -124,5 +307,6 @@ interface IBlue is IFlashLender { Signature calldata signature ) external; + /// @notice Returns the data stored on the different `slots`. function extsload(bytes32[] memory slots) external view returns (bytes32[] memory res); } diff --git a/src/interfaces/IBlueCallbacks.sol b/src/interfaces/IBlueCallbacks.sol index 902e2ddad..ddaf3c43e 100644 --- a/src/interfaces/IBlueCallbacks.sol +++ b/src/interfaces/IBlueCallbacks.sol @@ -1,22 +1,54 @@ // SPDX-License-Identifier: UNLICENSED pragma solidity >=0.5.0; +/// @title IBlueLiquidateCallback +/// @notice Interface that liquidators willing to use `liquidate`'s callback must implement. interface IBlueLiquidateCallback { + /// @notice Callback called when a liquidation occurs. + /// @dev The callback is called only if data is not empty. + /// @param seized The amount of seized tokens. + /// @param repaid The amount of repaid tokens. + /// @param data Arbitrary data passed to the `liquidate` function. function onBlueLiquidate(uint256 seized, uint256 repaid, bytes calldata data) external; } +/// @title IBlueRepayCallback +/// @notice Interface that users willing to use `repay`'s callback must implement. interface IBlueRepayCallback { + /// @notice Callback called when a repayment occurs. + /// @dev The callback is called only if data is not empty. + /// @param amount The amount of repaid tokens. + /// @param data Arbitrary data passed to the `repay` function. function onBlueRepay(uint256 amount, bytes calldata data) external; } +/// @title IBlueSupplyCallback +/// @notice Interface that users willing to use `supply`'s callback must implement. interface IBlueSupplyCallback { + /// @notice Callback called when a supply occurs. + /// @dev The callback is called only if data is not empty. + /// @param amount The amount of supplied tokens. + /// @param data Arbitrary data passed to the `supply` function. function onBlueSupply(uint256 amount, bytes calldata data) external; } +/// @title IBlueSupplyCollateralCallback +/// @notice Interface that users willing to use `supplyCollateral`'s callback must implement. interface IBlueSupplyCollateralCallback { + /// @notice Callback called when a supply occurs. + /// @dev The callback is called only if data is not empty. + /// @param amount The amount of supplied tokens. + /// @param data Arbitrary data passed to the `supplyCollateral` function. function onBlueSupplyCollateral(uint256 amount, bytes calldata data) external; } +/// @title IBlueWithdrawCallback +/// @notice Interface that users willing to use `withdraw`'s callback must implement. interface IBlueFlashLoanCallback { + /// @notice Callback called when a flash loan occurs. + /// @dev The callback is called only if data is not empty. + /// @param token The token that was flash loaned. + /// @param amount The amount that was flash loaned. + /// @param data Arbitrary data passed to the `flashLoan` function. function onBlueFlashLoan(address token, uint256 amount, bytes calldata data) external; } diff --git a/src/interfaces/IERC20.sol b/src/interfaces/IERC20.sol index 31f0cc95f..46f0c2cec 100644 --- a/src/interfaces/IERC20.sol +++ b/src/interfaces/IERC20.sol @@ -1,6 +1,9 @@ // SPDX-License-Identifier: UNLICENSED pragma solidity >=0.5.0; -/// @dev Empty because we only call functions in assembly. It prevents calling -/// transfer (transferFrom) instead of safeTransfer (safeTransferFrom). +/// @title IERC20 +/// @author Morpho Labs +/// @custom:contact security@morpho.xyz +/// @notice Library exposing errors used in Blue. +/// @dev Empty because we only call functions in assembly. It prevents calling transfer (transferFrom) instead of safeTransfer (safeTransferFrom). interface IERC20 {} diff --git a/src/interfaces/IFlashLender.sol b/src/interfaces/IFlashLender.sol index a2d9c8623..f23682ec7 100644 --- a/src/interfaces/IFlashLender.sol +++ b/src/interfaces/IFlashLender.sol @@ -1,8 +1,14 @@ // SPDX-License-Identifier: UNLICENSED pragma solidity >=0.5.0; -import {IBlueFlashLoanCallback} from "./IBlueCallbacks.sol"; - +/// @title IFlashLender +/// @author Morpho Labs +/// @custom:contact security@morpho.xyz +/// @notice Flash lender interface exposing a flash loan function. interface IFlashLender { + /// @notice Executes a flash loan. + /// @param token The token to flash loan. + /// @param amount The amount to flash loan. + /// @param data Arbitrary data to pass to the `onBlueFlashLoan` callback. function flashLoan(address token, uint256 amount, bytes calldata data) external; } diff --git a/src/interfaces/IIrm.sol b/src/interfaces/IIrm.sol index cc0a3c527..12bc6c82d 100644 --- a/src/interfaces/IIrm.sol +++ b/src/interfaces/IIrm.sol @@ -3,6 +3,11 @@ pragma solidity >=0.5.0; import {Market} from "./IBlue.sol"; +/// @title IIrm +/// @author Morpho Labs +/// @custom:contact security@morpho.xyz +/// @notice Interface that IRMs used by Blue must implement. interface IIrm { + /// @notice Returns the borrow rate of a `market`. function borrowRate(Market memory market) external returns (uint256); } diff --git a/src/interfaces/IOracle.sol b/src/interfaces/IOracle.sol index 6da75e903..e2ce2eb15 100644 --- a/src/interfaces/IOracle.sol +++ b/src/interfaces/IOracle.sol @@ -1,6 +1,11 @@ // SPDX-License-Identifier: UNLICENSED pragma solidity >=0.5.0; +/// @title IOracle +/// @author Morpho Labs +/// @custom:contact security@morpho.xyz +/// @notice Interface that oracles used by Blue must implement. interface IOracle { + /// @notice Returns the price of the underlying asset. function price() external view returns (uint256); } diff --git a/src/libraries/BlueLib.sol b/src/libraries/BlueLib.sol index 580c13e53..e26cbd222 100644 --- a/src/libraries/BlueLib.sol +++ b/src/libraries/BlueLib.sol @@ -6,6 +6,10 @@ import {Id, Market, IBlue} from "../interfaces/IBlue.sol"; import {MarketLib} from "./MarketLib.sol"; import {SharesMath} from "./SharesMath.sol"; +/// @title BlueLib +/// @author Morpho Labs +/// @custom:contact security@morpho.xyz +/// @notice A library to ease interactions with Blue. library BlueLib { using MarketLib for Market; using SharesMath for uint256; diff --git a/src/libraries/Errors.sol b/src/libraries/Errors.sol index d4be41a56..7597d9a60 100644 --- a/src/libraries/Errors.sol +++ b/src/libraries/Errors.sol @@ -1,36 +1,56 @@ // SPDX-License-Identifier: UNLICENSED pragma solidity ^0.8.0; +/// @title Errors +/// @author Morpho Labs +/// @custom:contact security@morpho.xyz +/// @notice Library exposing errors used in Blue. library Errors { + /// @notice Thrown when the caller is not the owner. string internal constant NOT_OWNER = "not owner"; + /// @notice Thrown when the LLTV to enable is too high. string internal constant LLTV_TOO_HIGH = "LLTV too high"; + /// @notice Thrown when the fee to set exceeds the maximum fee. string internal constant MAX_FEE_EXCEEDED = "MAX_FEE exceeded"; + /// @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_CREATED = "market created"; + /// @notice Thrown when the market is not created. string internal constant MARKET_NOT_CREATED = "market not created"; + /// @notice Thrown when a zero amount is passed as input. string internal constant ZERO_AMOUNT = "zero amount"; + /// @notice Thrown when a zero shares amount is passed as input. string internal constant ZERO_SHARES = "zero shares"; + /// @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 is healthy preventing its liquidation. 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"; } diff --git a/src/libraries/MarketLib.sol b/src/libraries/MarketLib.sol index 03a2b0ecb..271b5be38 100644 --- a/src/libraries/MarketLib.sol +++ b/src/libraries/MarketLib.sol @@ -1,9 +1,14 @@ // SPDX-License-Identifier: UNLICENSED pragma solidity ^0.8.0; -import {Id, Market, IBlue} from "../interfaces/IBlue.sol"; +import {Id, Market} from "../interfaces/IBlue.sol"; +/// @title MarketLib +/// @author Morpho Labs +/// @custom:contact security@morpho.xyz +/// @notice Library to convert a market to its id. library MarketLib { + /// @notice Returns the id of a `market`. function id(Market memory market) internal pure returns (Id) { return Id.wrap(keccak256(abi.encode(market))); } diff --git a/src/libraries/SharesMath.sol b/src/libraries/SharesMath.sol index 9f1dab24f..80a2ed3d4 100644 --- a/src/libraries/SharesMath.sol +++ b/src/libraries/SharesMath.sol @@ -3,33 +3,50 @@ pragma solidity ^0.8.0; import {FixedPointMathLib} from "./FixedPointMathLib.sol"; +/// @title MarketLib +/// @author Morpho Labs +/// @custom:contact security@morpho.xyz /// @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 SharesMath { using FixedPointMathLib for uint256; + /// @dev The number of virutal shares. uint256 internal constant VIRTUAL_SHARES = 1e18; + + /// @dev The number of virtual assets. uint256 internal constant VIRTUAL_ASSETS = 1; /// @dev Calculates the value of the given assets quoted in shares, rounding down. /// Note: provided that assets <= totalAssets, this function satisfies the invariant: shares <= totalShares. + /// @param assets The amount of assets to convert. + /// @param totalAssets The total amount of assets. + /// @param totalShares The total amount of shares. 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 the given shares quoted in assets, rounding down. /// Note: provided that shares <= totalShares, this function satisfies the invariant: assets <= totalAssets. + /// @param shares The amount of shares to convert. + /// @param totalAssets The total amount of assets. + /// @param totalShares The total amount of shares. 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 the given assets quoted in shares, rounding up. /// Note: provided that assets <= totalAssets, this function satisfies the invariant: shares <= totalShares + VIRTUAL_SHARES. + /// @param assets The amount of assets to convert. + /// @param totalAssets The total amount of assets. 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 the given shares quoted in assets, rounding up. + /// @param shares The amount of shares to convert. + /// @param totalAssets The total amount of assets. + /// @param totalShares The total amount of shares. /// Note: provided that shares <= totalShares, this function satisfies the invariant: assets <= totalAssets + VIRTUAL_SHARES. function toAssetsUp(uint256 shares, uint256 totalAssets, uint256 totalShares) internal pure returns (uint256) { return shares.mulDivUp(totalAssets + VIRTUAL_ASSETS, totalShares + VIRTUAL_SHARES); @@ -37,6 +54,8 @@ library SharesMath { /// @dev Calculates the amount of shares corresponding to an exact amount of supply to withdraw. /// Note: only works as long as totalSupplyShares + VIRTUAL_SHARES >= totalSupply + VIRTUAL_ASSETS. + /// @param amount The amount of supply to withdraw. + /// @param totalSupply The total amount of supply. function toWithdrawShares(uint256 amount, uint256 totalSupply, uint256 totalSupplyShares) internal pure @@ -50,6 +69,8 @@ library SharesMath { /// @dev Calculates the amount of shares corresponding to an exact amount of debt to repay. /// Note: only works as long as totalBorrowShares + VIRTUAL_SHARES >= totalBorrow + VIRTUAL_ASSETS. + /// @param amount The amount of debt to repay. + /// @param totalBorrow The total amount of debt. function toRepayShares(uint256 amount, uint256 totalBorrow, uint256 totalBorrowShares) internal pure From eddc7818ac7301c087001f72aa402e088b54b70c Mon Sep 17 00:00:00 2001 From: MerlinEgalite Date: Tue, 8 Aug 2023 14:53:35 +0200 Subject: [PATCH 02/12] docs: add missing natspecs --- src/libraries/SharesMath.sol | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/libraries/SharesMath.sol b/src/libraries/SharesMath.sol index 80a2ed3d4..175937634 100644 --- a/src/libraries/SharesMath.sol +++ b/src/libraries/SharesMath.sol @@ -3,7 +3,7 @@ pragma solidity ^0.8.0; import {FixedPointMathLib} from "./FixedPointMathLib.sol"; -/// @title MarketLib +/// @title SharesMath /// @author Morpho Labs /// @custom:contact security@morpho.xyz /// @notice Shares management library. @@ -56,6 +56,7 @@ library SharesMath { /// Note: only works as long as totalSupplyShares + VIRTUAL_SHARES >= totalSupply + VIRTUAL_ASSETS. /// @param amount The amount of supply to withdraw. /// @param totalSupply The total amount of supply. + /// @param totalSupplyShares The total amount of supply shares. function toWithdrawShares(uint256 amount, uint256 totalSupply, uint256 totalSupplyShares) internal pure @@ -71,6 +72,7 @@ library SharesMath { /// Note: only works as long as totalBorrowShares + VIRTUAL_SHARES >= totalBorrow + VIRTUAL_ASSETS. /// @param amount The amount of debt to repay. /// @param totalBorrow The total amount of debt. + /// @param totalBorrowShares The total amount of debt shares. function toRepayShares(uint256 amount, uint256 totalBorrow, uint256 totalBorrowShares) internal pure From 0be8610337206515b6bcce2c760016536f1e9069 Mon Sep 17 00:00:00 2001 From: MerlinEgalite Date: Tue, 8 Aug 2023 22:47:45 +0200 Subject: [PATCH 03/12] docs: apply some suggestions --- src/Blue.sol | 21 ++++++---------- src/interfaces/IBlue.sol | 47 +++++++++++++++--------------------- src/libraries/SharesMath.sol | 17 ------------- 3 files changed, 27 insertions(+), 58 deletions(-) diff --git a/src/Blue.sol b/src/Blue.sol index 3d00d3ed3..c8d3d52a3 100644 --- a/src/Blue.sol +++ b/src/Blue.sol @@ -21,10 +21,13 @@ import {FixedPointMathLib} from "./libraries/FixedPointMathLib.sol"; /// @dev The maximum fee a market can have (25%). uint256 constant MAX_FEE = 0.25e18; + /// @dev The alpha parameter used to compute the incentive during a liquidation. uint256 constant ALPHA = 0.5e18; + /// @dev The EIP-712 typeHash for EIP712Domain. bytes32 constant DOMAIN_TYPEHASH = keccak256("EIP712Domain(string name,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)"); @@ -95,7 +98,7 @@ contract Blue is IBlue { _; } - /* ADMIN FUNCTIONS */ + /* ONLY OWNER FUNCTIONS */ /// @inheritdoc IBlue function setOwner(address newOwner) external onlyOwner { @@ -387,9 +390,7 @@ contract Blue is IBlue { /* INTEREST MANAGEMENT */ - /// @dev Accrues interests for a market. - /// @param market The market to accrue interests for. - /// @param id The market's id. + /// @dev Accrues interests for `market`. function _accrueInterests(Market memory market, Id id) internal { uint256 elapsed = block.timestamp - lastUpdate[id]; @@ -420,10 +421,7 @@ contract Blue is IBlue { /* HEALTH CHECK */ - /// @notice Returns whether a position is healthy. - /// @param market The market on which to check the health of `user`. - /// @param id The market's id. - /// @param user The user to check the health of. + /// @notice Returns whether the position of `user` is healthy in the given `market`. function _isHealthy(Market memory market, Id id, address user) internal view returns (bool) { if (borrowShares[id][user] == 0) return true; @@ -433,12 +431,7 @@ contract Blue is IBlue { return _isHealthy(market, id, user, collateralPrice, borrowablePrice); } - /// @notice Returns whether a position is healthy. - /// @param market The market on which to check the health of `user`. - /// @param id The market's id. - /// @param user The user to check the health of. - /// @param collateralPrice The collateral asset price. - /// @param borrowablePrice The borrowable asset price. + /// @notice Returns whether the position of `user` is healthy in the given `market` with the given prices. function _isHealthy(Market memory market, Id id, address user, uint256 collateralPrice, uint256 borrowablePrice) internal view diff --git a/src/interfaces/IBlue.sol b/src/interfaces/IBlue.sol index 15031bb17..e4a97fed6 100644 --- a/src/interfaces/IBlue.sol +++ b/src/interfaces/IBlue.sol @@ -54,7 +54,7 @@ interface IBlue is IFlashLender { /// @notice Emitted on supply of assets. /// @param id The market id. - /// @param caller The caller of the `supply` function. + /// @param caller The caller. /// @param onBehalf The address that will receive the position. /// @param amount The amount of assets supplied. /// @param shares The amount of shares minted. @@ -62,7 +62,7 @@ interface IBlue is IFlashLender { /// @notice Emitted on withdrawal of assets. /// @param id The market id. - /// @param caller The caller of the `withdraw` function. + /// @param caller The caller. /// @param onBehalf The address from which the assets are withdrawn. /// @param receiver The address that will receive the withdrawn assets. /// @param amount The amount of assets withdrawn. @@ -78,7 +78,7 @@ interface IBlue is IFlashLender { /// @notice Emitted on borrow of assets. /// @param id The market id. - /// @param caller The caller of the `borrow` function. + /// @param caller The caller. /// @param onBehalf The address from which the assets are borrowed. /// @param receiver The address that will receive the borrowed assets. /// @param amount The amount of assets borrowed. @@ -94,7 +94,7 @@ interface IBlue is IFlashLender { /// @notice Emitted on repayment of assets. /// @param id The market id. - /// @param caller The caller of the `repay` function. + /// @param caller The caller. /// @param onBehalf The address for which the assets are repaid. /// @param amount The amount of assets repaid. /// @param shares The amount of shares burned. @@ -102,14 +102,14 @@ interface IBlue is IFlashLender { /// @notice Emitted on supply of collateral. /// @param id The market id. - /// @param caller The caller of the `supplyCollateral` function. + /// @param caller The caller. /// @param onBehalf The address that will receive the position. /// @param amount The amount of collateral supplied. event SupplyCollateral(Id indexed id, address indexed caller, address indexed onBehalf, uint256 amount); /// @notice Emitted on withdrawal of collateral. /// @param id The market id. - /// @param caller The caller of the `withdrawCollateral` function. + /// @param caller The caller. /// @param onBehalf The address from which the collateral is withdrawn. /// @param receiver The address that will receive the withdrawn collateral. /// @param amount The amount of collateral withdrawn. @@ -119,7 +119,7 @@ interface IBlue is IFlashLender { /// @notice Emitted on liquidation of a position. /// @param id The market id. - /// @param caller The caller of the `liquidate` function. + /// @param caller The caller. /// @param borrower The borrower of the position. /// @param repaid The amount of assets repaid. /// @param repaidShares The amount of shares burned. @@ -136,13 +136,13 @@ interface IBlue is IFlashLender { ); /// @notice Emitted on flash loan. - /// @param caller The caller of the `flashLoan` function. + /// @param caller The caller.. /// @param token The token that was flash loaned. /// @param amount The amount that was flash loaned. event FlashLoan(address indexed caller, address indexed token, uint256 amount); /// @notice Emitted when setting an authorization. - /// @param caller The caller of the authorization function. + /// @param caller The caller. /// @param authorizer The authorizer address. /// @param authorized The authorized address. /// @param newIsAuthorized The new authorization status. @@ -151,7 +151,7 @@ interface IBlue is IFlashLender { ); /// @notice Emitted when setting an authorization with a signature. - /// @param caller The caller of the authorization function. + /// @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); @@ -203,38 +203,31 @@ interface IBlue is IFlashLender { function isIrmEnabled(address irm) external view returns (bool); /// @notice Whether the `lltv` is enabled. - function isLltvEnabled(uint256) external view returns (bool); + function isLltvEnabled(uint256 lltv) external view returns (bool); /// @notice User's authorizations. Note that by default, `msg.sender` is authorized by themself. - function isAuthorized(address, address) external view returns (bool); + function isAuthorized(address authorizer, address authorized) external view returns (bool); - /// @notice User's nonces. Used to prevent replay attacks with EIP-712 signatures. - function nonce(address) external view returns (uint256); + /// @notice User's current nonce. Used to prevent replay attacks with EIP-712 signatures. + function nonce(address user) external view returns (uint256); - /// @notice Sets the owner of the contract. - /// @param newOwner The new owner of the contract. + /// @notice Sets `newOwner` as owner of the contract. function setOwner(address newOwner) external; - /// @notice Enables an IRM. - /// @param irm The IRM to enable. + /// @notice Enables `irm` as possible IRM for market creation. function enableIrm(address irm) external; - /// @notice Enables an LLTV. - /// @param lltv The LLTV to enable. + /// @notice Enables `lltv` as possible LLTV for market creation. function enableLltv(uint256 lltv) external; - /// @notice Sets the fee for a market. - /// @param market The market to set the fee for. - /// @param newFee The new fee for the market. + /// @notice Sets the `newFee` for `market`. /// @dev It is the `owner`'s responsibility to ensure a fee recipient is set before setting a non-zero fee. function setFee(Market memory market, uint256 newFee) external; - /// @notice Sets the fee recipient. - /// @param recipient The new fee recipient. + /// @notice Sets `recipient` as recipient of the fee. function setFeeRecipient(address recipient) external; - /// @notice Creates a market. - /// @param market The market to create. + /// @notice Creates `market`. function createMarket(Market memory market) external; /// @notice Supplies assets to a market. diff --git a/src/libraries/SharesMath.sol b/src/libraries/SharesMath.sol index 175937634..c9aed1f6a 100644 --- a/src/libraries/SharesMath.sol +++ b/src/libraries/SharesMath.sol @@ -19,34 +19,23 @@ library SharesMath { /// @dev Calculates the value of the given assets quoted in shares, rounding down. /// Note: provided that assets <= totalAssets, this function satisfies the invariant: shares <= totalShares. - /// @param assets The amount of assets to convert. - /// @param totalAssets The total amount of assets. - /// @param totalShares The total amount of shares. 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 the given shares quoted in assets, rounding down. /// Note: provided that shares <= totalShares, this function satisfies the invariant: assets <= totalAssets. - /// @param shares The amount of shares to convert. - /// @param totalAssets The total amount of assets. - /// @param totalShares The total amount of shares. 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 the given assets quoted in shares, rounding up. /// Note: provided that assets <= totalAssets, this function satisfies the invariant: shares <= totalShares + VIRTUAL_SHARES. - /// @param assets The amount of assets to convert. - /// @param totalAssets The total amount of assets. 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 the given shares quoted in assets, rounding up. - /// @param shares The amount of shares to convert. - /// @param totalAssets The total amount of assets. - /// @param totalShares The total amount of shares. /// Note: provided that shares <= totalShares, this function satisfies the invariant: assets <= totalAssets + VIRTUAL_SHARES. function toAssetsUp(uint256 shares, uint256 totalAssets, uint256 totalShares) internal pure returns (uint256) { return shares.mulDivUp(totalAssets + VIRTUAL_ASSETS, totalShares + VIRTUAL_SHARES); @@ -54,9 +43,6 @@ library SharesMath { /// @dev Calculates the amount of shares corresponding to an exact amount of supply to withdraw. /// Note: only works as long as totalSupplyShares + VIRTUAL_SHARES >= totalSupply + VIRTUAL_ASSETS. - /// @param amount The amount of supply to withdraw. - /// @param totalSupply The total amount of supply. - /// @param totalSupplyShares The total amount of supply shares. function toWithdrawShares(uint256 amount, uint256 totalSupply, uint256 totalSupplyShares) internal pure @@ -70,9 +56,6 @@ library SharesMath { /// @dev Calculates the amount of shares corresponding to an exact amount of debt to repay. /// Note: only works as long as totalBorrowShares + VIRTUAL_SHARES >= totalBorrow + VIRTUAL_ASSETS. - /// @param amount The amount of debt to repay. - /// @param totalBorrow The total amount of debt. - /// @param totalBorrowShares The total amount of debt shares. function toRepayShares(uint256 amount, uint256 totalBorrow, uint256 totalBorrowShares) internal pure From 939ee954fb3ac3b6014aca269c6be3721983e56f Mon Sep 17 00:00:00 2001 From: MerlinEgalite Date: Wed, 9 Aug 2023 11:25:57 +0200 Subject: [PATCH 04/12] refactor: borrowRate -> prevBorrowRate --- src/Blue.sol | 6 +++--- src/interfaces/IBlue.sol | 4 ++-- src/interfaces/IIrm.sol | 2 +- src/mocks/IrmMock.sol | 2 +- 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/Blue.sol b/src/Blue.sol index c8d3d52a3..10931d97d 100644 --- a/src/Blue.sol +++ b/src/Blue.sol @@ -399,8 +399,8 @@ contract Blue is IBlue { uint256 marketTotalBorrow = totalBorrow[id]; if (marketTotalBorrow != 0) { - uint256 borrowRate = IIrm(market.irm).borrowRate(market); - uint256 accruedInterests = marketTotalBorrow.mulWadDown(borrowRate * elapsed); + uint256 prevBorrowRate = IIrm(market.irm).prevBorrowRate(market); + uint256 accruedInterests = marketTotalBorrow.mulWadDown(prevBorrowRate * elapsed); totalBorrow[id] = marketTotalBorrow + accruedInterests; totalSupply[id] += accruedInterests; @@ -413,7 +413,7 @@ contract Blue is IBlue { totalSupplyShares[id] += feeShares; } - emit AccrueInterests(id, borrowRate, accruedInterests, feeShares); + emit AccrueInterests(id, prevBorrowRate, accruedInterests, feeShares); } lastUpdate[id] = block.timestamp; diff --git a/src/interfaces/IBlue.sol b/src/interfaces/IBlue.sol index e4a97fed6..c45ba15f5 100644 --- a/src/interfaces/IBlue.sol +++ b/src/interfaces/IBlue.sol @@ -158,10 +158,10 @@ interface IBlue is IFlashLender { /// @notice Emitted when accruing interests. /// @param id The market id. - /// @param borrowRate The borrow rate. + /// @param prevBorrowRate The previous borrow rate. /// @param accruedInterests The amount of interests accrued. /// @param feeShares The amount of shares minted as fee. - event AccrueInterests(Id indexed id, uint256 borrowRate, uint256 accruedInterests, uint256 feeShares); + event AccrueInterests(Id indexed id, uint256 prevBorrowRate, uint256 accruedInterests, uint256 feeShares); /// @notice The EIP-712 domain separator. function DOMAIN_SEPARATOR() external view returns (bytes32); diff --git a/src/interfaces/IIrm.sol b/src/interfaces/IIrm.sol index 12bc6c82d..3b4b4dddd 100644 --- a/src/interfaces/IIrm.sol +++ b/src/interfaces/IIrm.sol @@ -9,5 +9,5 @@ import {Market} from "./IBlue.sol"; /// @notice Interface that IRMs used by Blue must implement. interface IIrm { /// @notice Returns the borrow rate of a `market`. - function borrowRate(Market memory market) external returns (uint256); + function prevBorrowRate(Market memory market) external returns (uint256); } diff --git a/src/mocks/IrmMock.sol b/src/mocks/IrmMock.sol index f0558d2ad..d776eca27 100644 --- a/src/mocks/IrmMock.sol +++ b/src/mocks/IrmMock.sol @@ -17,7 +17,7 @@ contract IrmMock is IIrm { BLUE = blue; } - function borrowRate(Market memory market) external view returns (uint256) { + function prevBorrowRate(Market memory market) external view returns (uint256) { Id id = market.id(); uint256 utilization = BLUE.totalBorrow(id).divWadDown(BLUE.totalSupply(id)); From 6eea668a1757f96d634db1417d55f154c59efa8a Mon Sep 17 00:00:00 2001 From: MerlinEgalite Date: Wed, 9 Aug 2023 11:48:10 +0200 Subject: [PATCH 05/12] docs: update natspecs in interface --- src/interfaces/IBlue.sol | 43 +++++++++++++++++++++------------------- 1 file changed, 23 insertions(+), 20 deletions(-) diff --git a/src/interfaces/IBlue.sol b/src/interfaces/IBlue.sol index c45ba15f5..41561bb63 100644 --- a/src/interfaces/IBlue.sol +++ b/src/interfaces/IBlue.sol @@ -172,32 +172,34 @@ interface IBlue is IFlashLender { /// @notice The fee recipient. function feeRecipient() external view returns (address); - /// @notice Users' supply balance. - function supplyShares(Id, address user) external view returns (uint256); + /// @notice The `user`'s supply shares on the market defined by the given `id`. + function supplyShares(Id id, address user) external view returns (uint256); - /// @notice Users' borrow balances. + /// @notice The `user`'s borrow shares on the market defined by the given `id`. function borrowShares(Id, address user) external view returns (uint256); - /// @notice Users' collateral balance. - function collateral(Id, address user) external view returns (uint256); + /// @notice The `user`'s collateral balance on the market defined by the given `id`. + function collateral(Id id, address user) external view returns (uint256); - /// @notice Market's total supply. - function totalSupply(Id) external view returns (uint256); + /// @notice The total amount of assets supplied to the market defined by the given `id`. + /// @dev The value can be incaccurate since it does not take into account the accrued interests. + function totalSupply(Id id) external view returns (uint256); - /// @notice Market's total supply shares. - function totalSupplyShares(Id) external view returns (uint256); + /// @notice The total supply shares of the market defined by the given `id`. + function totalSupplyShares(Id id) external view returns (uint256); - /// @notice Market's total borrow. - function totalBorrow(Id) external view returns (uint256); + /// @notice The total amount of assets borrowed from the market defined by the given `id`. + /// @dev The value can be incaccurate since it does not take into account the accrued interests. + function totalBorrow(Id id) external view returns (uint256); - /// @notice Market's total borrow shares. - function totalBorrowShares(Id) external view returns (uint256); + /// @notice The total borrow shares of the market defined by the given `id`. + function totalBorrowShares(Id id) external view returns (uint256); - /// @notice Interests last update (used to check if a market has been created). - function lastUpdate(Id) external view returns (uint256); + /// @notice The last update of the market defined by the given `id` (used to check if a market has been created). + function lastUpdate(Id id) external view returns (uint256); - /// @notice The market's fee. - function fee(Id) external view returns (uint256); + /// @notice The fee of the market defined by the given `id`. + function fee(Id id) external view returns (uint256); /// @notice Whether the `irm` is enabled. function isIrmEnabled(address irm) external view returns (bool); @@ -205,10 +207,11 @@ interface IBlue is IFlashLender { /// @notice Whether the `lltv` is enabled. function isLltvEnabled(uint256 lltv) external view returns (bool); - /// @notice User's authorizations. Note that by default, `msg.sender` is authorized by themself. + /// @notice Whether `aurothized` is authorized to modify `authorizer`'s positions. + /// @dev By default, `msg.sender` is authorized by themself. function isAuthorized(address authorizer, address authorized) external view returns (bool); - /// @notice User's current nonce. Used to prevent replay attacks with EIP-712 signatures. + /// @notice The `user`'s current nonce. Used to prevent replay attacks with EIP-712 signatures. function nonce(address user) external view returns (uint256); /// @notice Sets `newOwner` as owner of the contract. @@ -221,7 +224,7 @@ interface IBlue is IFlashLender { function enableLltv(uint256 lltv) external; /// @notice Sets the `newFee` for `market`. - /// @dev It is the `owner`'s responsibility to ensure a fee recipient is set before setting a non-zero fee. + /// @dev It is the `owner`'s responsibility to ensure `feeRecipient` is set before setting a non-zero fee. function setFee(Market memory market, uint256 newFee) external; /// @notice Sets `recipient` as recipient of the fee. From a48e79a4307a0affc93df061b3ac0e12def94271 Mon Sep 17 00:00:00 2001 From: MerlinEgalite Date: Thu, 10 Aug 2023 11:57:19 +0200 Subject: [PATCH 06/12] feat: fix #244 --- src/interfaces/IBlue.sol | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/interfaces/IBlue.sol b/src/interfaces/IBlue.sol index 2385a4032..8ff7c812c 100644 --- a/src/interfaces/IBlue.sol +++ b/src/interfaces/IBlue.sol @@ -169,6 +169,8 @@ interface IBlue is IFlashLender { function owner() external view returns (address); /// @notice The fee recipient. + /// @dev The recipient receives the fees through a supply position. + /// As every other supplier, the recipient is subject to illiquidity risks. function feeRecipient() external view returns (address); /// @notice The `user`'s supply shares on the market defined by the given `id`. From c17637a72aebdebf6c5736f914bc027d4a767f1f Mon Sep 17 00:00:00 2001 From: MerlinEgalite Date: Thu, 10 Aug 2023 18:04:10 +0200 Subject: [PATCH 07/12] docs: improve comments --- src/interfaces/IBlue.sol | 58 ++++++++++++++++++++++++++++------------ 1 file changed, 41 insertions(+), 17 deletions(-) diff --git a/src/interfaces/IBlue.sol b/src/interfaces/IBlue.sol index f47009fe7..bf89bca59 100644 --- a/src/interfaces/IBlue.sol +++ b/src/interfaces/IBlue.sol @@ -234,57 +234,81 @@ interface IBlue is IFlashLender { /// @notice Creates `market`. function createMarket(Market memory market) external; - /// @notice Supplies assets to a market. + /// @notice Supplies the given `amount` of assets or `shares` to the given `market` on behalf of `onBehalf`, + /// optionally calling back the caller's `onBlueSupply` function with the given `data`. + /// @dev Either `amount` or `shares` should be zero. + /// Most usecases should rely on `amount` as an input so the caller + /// is guaranteed to have `amount` tokens pulled from their balance, + /// but the possibility to mint a specific amount of shares is given + /// for full compatibility and precision. /// @param market The market to supply assets to. /// @param amount The amount of assets to supply. + /// @param shares The amount of shares to mint. /// @param onBehalf The address that will receive the position. /// @param data Arbitrary data to pass to the `onBlueSupply` callback. Pass empty data if not needed. function supply(Market memory market, uint256 amount, uint256 shares, address onBehalf, bytes memory data) external; - /// @notice Withdraws assets from a market. + /// @notice Withdraws the given `amount` of assets or `shares` from the given `market` on behalf of `onBehalf`. + /// @dev Either `amount` or `shares` should be zero. + /// To withdraw the whole position, pass the `shares`'s balance of `onBehalf`. + /// @dev If `msg.sender != onBehalf`, `msg.sender` must be authorized to withdraw from `onBehalf`. /// @param market The market to withdraw assets from. - /// @param onBehalf The address from which to withdraw. + /// @param shares The amount of amount to withdraw. + /// @param shares The amount of shares to burn. + /// @param onBehalf The address of the owner of the withdrawn assets. /// @param receiver The address that will receive the withdrawn assets. - /// @dev If `msg.sender != onBehalf`, `msg.sender` must be authorized to withdraw from `onBehalf`. function withdraw(Market memory market, uint256 amount, uint256 shares, address onBehalf, address receiver) external; - /// @notice Borrows assets from a market. + /// @notice Borrows the given `amount` of assets or `shares` from the given `market` on behalf of `onBehalf`. + /// @dev Either `amount` or `shares` should be zero. + /// Most usecases should rely on `amount` as an input so the caller + /// is guaranteed to borrow `amount` of tokens, + /// but the possibility to burn a specific amount of shares is given + /// for full compatibility and precision. /// @param market The market to borrow assets from. /// @param amount The amount of assets to borrow. - /// @param onBehalf The address from which to borrow. - /// @param receiver The address that will receive the borrowed assets. + /// @param shares The amount of shares to mint. + /// @param onBehalf The address of the owner of the debt. + /// @param receiver The address that will receive the debt. /// @dev If `msg.sender != onBehalf`, `msg.sender` must be authorized to withdraw from `onBehalf`. function borrow(Market memory market, uint256 amount, uint256 shares, address onBehalf, address receiver) external; - /// @notice Repays assets to a market. + /// @notice Repays the given `amount` of assets or `shares` to the given `market` on behalf of `onBehalf`, + /// optionally calling back the caller's `onBlueReplay` function with the given `data`. + /// @dev Either `amount` or `shares` should be zero. + /// To repay the whole debt, pass the `shares`'s balance of `onBehalf`. /// @param market The market to repay assets to. - /// @param onBehalf The address for which to repay. + /// @param amount The amount of assets to repay. + /// @param shares The amount of shares to burn. + /// @param onBehalf The address of the owner of the debt. /// @param data Arbitrary data to pass to the `onBlueRepay` callback. Pass empty data if not needed. function repay(Market memory market, uint256 amount, uint256 shares, address onBehalf, bytes memory data) external; - /// @notice Supplies collateral to a market. + /// @notice Supplies the given `amount` of collateral to the given `market` on behalf of `onBehalf`, + /// optionally calling back the caller's `onBlueSupplyCollateral` function with the given `data`. + /// @dev Interests are not accrued since it's not required and it saves gas. /// @param market The market to supply collateral to. /// @param amount The amount of collateral to supply. - /// @param onBehalf The address that will receive the position. + /// @param onBehalf The address that will receive the collateral. /// @param data Arbitrary data to pass to the `onBlueSupplyCollateral` callback. Pass empty data if not needed. - /// @dev Don't accrue interests because it's not required and it saves gas. function supplyCollateral(Market memory market, uint256 amount, address onBehalf, bytes memory data) external; - /// @notice Withdraws collateral from a market. + /// @notice Withdraws the given `amount` of collateral from the given `market` on behalf of `onBehalf`. + /// @dev If `msg.sender != onBehalf`, `msg.sender` must be authorized to withdraw from `onBehalf`. /// @param market The market to withdraw collateral from. /// @param amount The amount of collateral to withdraw. - /// @param onBehalf The address from which to withdraw. + /// @param onBehalf The address of the owner of the collateral. /// @param receiver The address that will receive the withdrawn collateral. - /// @dev If `msg.sender != onBehalf`, `msg.sender` must be authorized to withdraw from `onBehalf`. function withdrawCollateral(Market memory market, uint256 amount, address onBehalf, address receiver) external; - /// @notice Liquidates a position. + /// @notice Liquidates the given `seized` amount to the given `market` of the given `borrower`'s position, + /// optionally calling back the caller's `onBlueLiquidate` function with the given `data`. /// @param market The market of the position. - /// @param borrower The borrower of the position. + /// @param borrower The owner of the position. /// @param seized The amount of collateral to seize. /// @param data Arbitrary data to pass to the `onBlueLiquidate` callback. Pass empty data if not needed function liquidate(Market memory market, address borrower, uint256 seized, bytes memory data) external; From a29e8e3e78bbffacaaf86ec3d300ecd81ed41f31 Mon Sep 17 00:00:00 2001 From: MerlinEgalite Date: Thu, 10 Aug 2023 18:14:26 +0200 Subject: [PATCH 08/12] docs: apply suggestion --- src/libraries/SharesMathLib.sol | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/src/libraries/SharesMathLib.sol b/src/libraries/SharesMathLib.sol index 87aadccbc..f31c9fa3e 100644 --- a/src/libraries/SharesMathLib.sol +++ b/src/libraries/SharesMathLib.sol @@ -7,36 +7,35 @@ import {FixedPointMathLib} from "./FixedPointMathLib.sol"; /// @author Morpho Labs /// @custom:contact security@morpho.xyz /// @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. +/// @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 FixedPointMathLib for uint256; - /// @dev The number of virutal shares. uint256 internal constant VIRTUAL_SHARES = 1e18; - /// @dev The number of virtual assets. uint256 internal constant VIRTUAL_ASSETS = 1; /// @dev Calculates the value of the given assets quoted in shares, rounding down. - /// Note: provided that assets <= totalAssets, this function satisfies the invariant: shares <= totalShares. + /// @dev Provided that assets <= totalAssets, this function satisfies the invariant: shares <= totalShares. 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 the given shares quoted in assets, rounding down. - /// Note: provided that shares <= totalShares, this function satisfies the invariant: assets <= totalAssets. + /// @dev Provided that shares <= totalShares, this function satisfies the invariant: assets <= totalAssets. 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 the given assets quoted in shares, rounding up. - /// Note: provided that assets <= totalAssets, this function satisfies the invariant: shares <= totalShares + VIRTUAL_SHARES. + /// @dev Provided that assets <= totalAssets, this function satisfies the invariant: shares <= totalShares + VIRTUAL_SHARES. 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 the given shares quoted in assets, rounding up. - /// Note: provided that shares <= totalShares, this function satisfies the invariant: assets <= totalAssets + VIRTUAL_SHARES. + /// @dev Provided that shares <= totalShares, this function satisfies the invariant: assets <= totalAssets + VIRTUAL_SHARES. function toAssetsUp(uint256 shares, uint256 totalAssets, uint256 totalShares) internal pure returns (uint256) { return shares.mulDivUp(totalAssets + VIRTUAL_ASSETS, totalShares + VIRTUAL_SHARES); } From 22291578227c846c1169d5951f87c27220357e17 Mon Sep 17 00:00:00 2001 From: MerlinEgalite Date: Thu, 10 Aug 2023 18:14:48 +0200 Subject: [PATCH 09/12] refactor: fix events location --- src/interfaces/IBlue.sol | 137 ------------------------------------ src/libraries/EventsLib.sol | 118 ++++++++++++++++++++++++++----- 2 files changed, 99 insertions(+), 156 deletions(-) diff --git a/src/interfaces/IBlue.sol b/src/interfaces/IBlue.sol index bf89bca59..70662edd5 100644 --- a/src/interfaces/IBlue.sol +++ b/src/interfaces/IBlue.sol @@ -25,143 +25,6 @@ struct Signature { /// @custom:contact security@morpho.xyz /// @notice Interface of Blue. interface IBlue is IFlashLender { - /// @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 fee The new fee. - event SetFee(Id indexed id, uint256 fee); - - /// @notice Emitted when setting a new fee recipient. - /// @param feeRecipient The new fee recipient. - event SetFeeRecipient(address indexed feeRecipient); - - /// @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 market The market that was created. - event CreateMarket(Id indexed id, Market market); - - /// @notice Emitted on supply of assets. - /// @param id The market id. - /// @param caller The caller. - /// @param onBehalf The address that will receive the position. - /// @param amount The amount of assets supplied. - /// @param shares The amount of shares minted. - event Supply(Id indexed id, address indexed caller, address indexed onBehalf, uint256 amount, uint256 shares); - - /// @notice Emitted on withdrawal of assets. - /// @param id The market id. - /// @param caller The caller. - /// @param onBehalf The address from which the assets are withdrawn. - /// @param receiver The address that will receive the withdrawn assets. - /// @param amount 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 amount, - uint256 shares - ); - - /// @notice Emitted on borrow of assets. - /// @param id The market id. - /// @param caller The caller. - /// @param onBehalf The address from which the assets are borrowed. - /// @param receiver The address that will receive the borrowed assets. - /// @param amount 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 amount, - uint256 shares - ); - - /// @notice Emitted on repayment of assets. - /// @param id The market id. - /// @param caller The caller. - /// @param onBehalf The address for which the assets are repaid. - /// @param amount The amount of assets repaid. - /// @param shares The amount of shares burned. - event Repay(Id indexed id, address indexed caller, address indexed onBehalf, uint256 amount, uint256 shares); - - /// @notice Emitted on supply of collateral. - /// @param id The market id. - /// @param caller The caller. - /// @param onBehalf The address that will receive the position. - /// @param amount The amount of collateral supplied. - event SupplyCollateral(Id indexed id, address indexed caller, address indexed onBehalf, uint256 amount); - - /// @notice Emitted on withdrawal of collateral. - /// @param id The market id. - /// @param caller The caller. - /// @param onBehalf The address from which the collateral is withdrawn. - /// @param receiver The address that will receive the withdrawn collateral. - /// @param amount The amount of collateral withdrawn. - event WithdrawCollateral( - Id indexed id, address caller, address indexed onBehalf, address indexed receiver, uint256 amount - ); - - /// @notice Emitted on liquidation of a position. - /// @param id The market id. - /// @param caller The caller. - /// @param borrower The borrower of the position. - /// @param repaid The amount of assets repaid. - /// @param repaidShares The amount of shares burned. - /// @param seized The amount of collateral seized. - /// @param badDebtShares The amount of shares minted as bad debt. - event Liquidate( - Id indexed id, - address indexed caller, - address indexed borrower, - uint256 repaid, - uint256 repaidShares, - uint256 seized, - uint256 badDebtShares - ); - - /// @notice Emitted on flash loan. - /// @param caller The caller.. - /// @param token The token that was flash loaned. - /// @param amount The amount that was flash loaned. - event FlashLoan(address indexed caller, address indexed token, uint256 amount); - - /// @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 interests. - /// @param id The market id. - /// @param prevBorrowRate The previous borrow rate. - /// @param accruedInterests The amount of interests accrued. - /// @param feeShares The amount of shares minted as fee. - event AccrueInterests(Id indexed id, uint256 prevBorrowRate, uint256 accruedInterests, uint256 feeShares); - /// @notice The EIP-712 domain separator. function DOMAIN_SEPARATOR() external view returns (bytes32); diff --git a/src/libraries/EventsLib.sol b/src/libraries/EventsLib.sol index 19d80d386..b366d1967 100644 --- a/src/libraries/EventsLib.sol +++ b/src/libraries/EventsLib.sol @@ -4,12 +4,47 @@ pragma solidity ^0.8.0; import {Id, Market} from "src/interfaces/IBlue.sol"; library EventsLib { - event SupplyCollateral(Id indexed id, address indexed caller, address indexed onBehalf, uint256 amount); - event WithdrawCollateral( - Id indexed id, address caller, address indexed onBehalf, address indexed receiver, uint256 amount - ); + /// @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 fee The new fee. + event SetFee(Id indexed id, uint256 fee); + + /// @notice Emitted when setting a new fee recipient. + /// @param feeRecipient The new fee recipient. + event SetFeeRecipient(address indexed feeRecipient); + /// @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 market The market that was created. + event CreateMarket(Id indexed id, Market market); + + /// @notice Emitted on supply of assets. + /// @param id The market id. + /// @param caller The caller. + /// @param onBehalf The address that will receive the position. + /// @param amount The amount of assets supplied. + /// @param shares The amount of shares minted. event Supply(Id indexed id, address indexed caller, address indexed onBehalf, uint256 amount, uint256 shares); + + /// @notice Emitted on withdrawal of assets. + /// @param id The market id. + /// @param caller The caller. + /// @param onBehalf The address from which the assets are withdrawn. + /// @param receiver The address that will receive the withdrawn assets. + /// @param amount The amount of assets withdrawn. + /// @param shares The amount of shares burned. event Withdraw( Id indexed id, address caller, @@ -19,6 +54,13 @@ library EventsLib { uint256 shares ); + /// @notice Emitted on borrow of assets. + /// @param id The market id. + /// @param caller The caller. + /// @param onBehalf The address from which the assets are borrowed. + /// @param receiver The address that will receive the borrowed assets. + /// @param amount The amount of assets borrowed. + /// @param shares The amount of shares minted. event Borrow( Id indexed id, address caller, @@ -27,8 +69,40 @@ library EventsLib { uint256 amount, uint256 shares ); + + /// @notice Emitted on repayment of assets. + /// @param id The market id. + /// @param caller The caller. + /// @param onBehalf The address for which the assets are repaid. + /// @param amount The amount of assets repaid. + /// @param shares The amount of shares burned. event Repay(Id indexed id, address indexed caller, address indexed onBehalf, uint256 amount, uint256 shares); + /// @notice Emitted on supply of collateral. + /// @param id The market id. + /// @param caller The caller. + /// @param onBehalf The address that will receive the position. + /// @param amount The amount of collateral supplied. + event SupplyCollateral(Id indexed id, address indexed caller, address indexed onBehalf, uint256 amount); + + /// @notice Emitted on withdrawal of collateral. + /// @param id The market id. + /// @param caller The caller. + /// @param onBehalf The address from which the collateral is withdrawn. + /// @param receiver The address that will receive the withdrawn collateral. + /// @param amount The amount of collateral withdrawn. + event WithdrawCollateral( + Id indexed id, address caller, address indexed onBehalf, address indexed receiver, uint256 amount + ); + + /// @notice Emitted on liquidation of a position. + /// @param id The market id. + /// @param caller The caller. + /// @param borrower The borrower of the position. + /// @param repaid The amount of assets repaid. + /// @param repaidShares The amount of shares burned. + /// @param seized The amount of collateral seized. + /// @param badDebtShares The amount of shares minted as bad debt. event Liquidate( Id indexed id, address indexed caller, @@ -39,25 +113,31 @@ library EventsLib { uint256 badDebtShares ); + /// @notice Emitted on flash loan. + /// @param caller The caller.. + /// @param token The token that was flash loaned. + /// @param amount The amount that was flash loaned. event FlashLoan(address indexed caller, address indexed token, uint256 amount); - event SetOwner(address indexed newOwner); - - event SetFee(Id indexed id, uint256 fee); - - event SetFeeRecipient(address indexed feeRecipient); - - event CreateMarket(Id indexed id, Market market); - + /// @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 isAuthorized + address indexed caller, address indexed authorizer, address indexed authorized, bool newIsAuthorized ); - event IncrementNonce(address indexed caller, address indexed signatory, uint256 usedNonce); - - event EnableIrm(address indexed irm); - - event EnableLltv(uint256 lltv); + /// @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); - event AccrueInterests(Id indexed id, uint256 borrowRate, uint256 accruedInterests, uint256 feeShares); + /// @notice Emitted when accruing interests. + /// @param id The market id. + /// @param prevBorrowRate The previous borrow rate. + /// @param accruedInterests The amount of interests accrued. + /// @param feeShares The amount of shares minted as fee. + event AccrueInterests(Id indexed id, uint256 prevBorrowRate, uint256 accruedInterests, uint256 feeShares); } From 28014bd6ca20adee63cf398a2a492e1865ef57ac Mon Sep 17 00:00:00 2001 From: MerlinEgalite Date: Thu, 10 Aug 2023 18:19:48 +0200 Subject: [PATCH 10/12] refactor: borrow rate naming --- src/Blue.sol | 6 +++--- src/interfaces/IIrm.sol | 2 +- src/mocks/IrmMock.sol | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/Blue.sol b/src/Blue.sol index 5f811bddf..85e4eacea 100644 --- a/src/Blue.sol +++ b/src/Blue.sol @@ -410,8 +410,8 @@ contract Blue is IBlue { uint256 marketTotalBorrow = totalBorrow[id]; if (marketTotalBorrow != 0) { - uint256 prevBorrowRate = IIrm(market.irm).prevBorrowRate(market); - uint256 accruedInterests = marketTotalBorrow.wMulDown(prevBorrowRate.wTaylorCompounded(elapsed)); + uint256 borrowRate = IIrm(market.irm).borrowRate(market); + uint256 accruedInterests = marketTotalBorrow.wMulDown(borrowRate.wTaylorCompounded(elapsed)); totalBorrow[id] = marketTotalBorrow + accruedInterests; totalSupply[id] += accruedInterests; @@ -424,7 +424,7 @@ contract Blue is IBlue { totalSupplyShares[id] += feeShares; } - emit EventsLib.AccrueInterests(id, prevBorrowRate, accruedInterests, feeShares); + emit EventsLib.AccrueInterests(id, borrowRate, accruedInterests, feeShares); } lastUpdate[id] = block.timestamp; diff --git a/src/interfaces/IIrm.sol b/src/interfaces/IIrm.sol index 3b4b4dddd..12bc6c82d 100644 --- a/src/interfaces/IIrm.sol +++ b/src/interfaces/IIrm.sol @@ -9,5 +9,5 @@ import {Market} from "./IBlue.sol"; /// @notice Interface that IRMs used by Blue must implement. interface IIrm { /// @notice Returns the borrow rate of a `market`. - function prevBorrowRate(Market memory market) external returns (uint256); + function borrowRate(Market memory market) external returns (uint256); } diff --git a/src/mocks/IrmMock.sol b/src/mocks/IrmMock.sol index ab51b32c0..9ab5a07f3 100644 --- a/src/mocks/IrmMock.sol +++ b/src/mocks/IrmMock.sol @@ -17,7 +17,7 @@ contract IrmMock is IIrm { BLUE = blue; } - function prevBorrowRate(Market memory market) external view returns (uint256) { + function borrowRate(Market memory market) external view returns (uint256) { Id id = market.id(); uint256 utilization = BLUE.totalBorrow(id).wDivDown(BLUE.totalSupply(id)); From d1c6d5cea41ec8409539ab65dcf58961aac293be Mon Sep 17 00:00:00 2001 From: Merlin Egalite <44097430+MerlinEgalite@users.noreply.github.com> Date: Fri, 11 Aug 2023 10:35:34 +0200 Subject: [PATCH 11/12] docs: apply suggestions 1 Co-authored-by: MathisGD <74971347+MathisGD@users.noreply.github.com> Signed-off-by: Merlin Egalite <44097430+MerlinEgalite@users.noreply.github.com> --- src/Blue.sol | 5 ++--- src/interfaces/IBlue.sol | 27 +++++++++++++-------------- src/interfaces/IERC20.sol | 1 - src/libraries/ErrorsLib.sol | 7 ++----- 4 files changed, 17 insertions(+), 23 deletions(-) diff --git a/src/Blue.sol b/src/Blue.sol index 85e4eacea..c567da815 100644 --- a/src/Blue.sol +++ b/src/Blue.sol @@ -17,7 +17,6 @@ import {FixedPointMathLib, WAD} from "./libraries/FixedPointMathLib.sol"; /// @dev The maximum fee a market can have (25%). uint256 constant MAX_FEE = 0.25e18; - /// @dev The alpha parameter used to compute the incentive during a liquidation. uint256 constant ALPHA = 0.5e18; @@ -432,7 +431,7 @@ contract Blue is IBlue { /* HEALTH CHECK */ - /// @notice Returns whether the position of `user` is healthy in the given `market`. + /// @notice Returns whether the position of `user` in the given `market` is healthy. function _isHealthy(Market memory market, Id id, address user) internal view returns (bool) { if (borrowShares[id][user] == 0) return true; @@ -441,7 +440,7 @@ contract Blue is IBlue { return _isHealthy(market, id, user, collateralPrice, priceScale); } - /// @notice Returns whether the position of `user` is healthy in the given `market` with the given `collateralPrice` and `priceScale`. + /// @notice Returns whether the position of `user` in the given `market` with the given `collateralPrice` and `priceScale` is healthy. function _isHealthy(Market memory market, Id id, address user, uint256 collateralPrice, uint256 priceScale) internal view diff --git a/src/interfaces/IBlue.sol b/src/interfaces/IBlue.sol index 70662edd5..ad7f05ada 100644 --- a/src/interfaces/IBlue.sol +++ b/src/interfaces/IBlue.sol @@ -33,36 +33,35 @@ interface IBlue is IFlashLender { /// @notice The fee recipient. /// @dev The recipient receives the fees through a supply position. - /// As every other supplier, the recipient is subject to illiquidity risks. function feeRecipient() external view returns (address); - /// @notice The `user`'s supply shares on the market defined by the given `id`. + /// @notice The `user`'s supply shares on the market `id`. function supplyShares(Id id, address user) external view returns (uint256); - /// @notice The `user`'s borrow shares on the market defined by the given `id`. + /// @notice The `user`'s borrow shares on the market `id`. function borrowShares(Id, address user) external view returns (uint256); - /// @notice The `user`'s collateral balance on the market defined by the given `id`. + /// @notice The `user`'s collateral balance on the market `id`. function collateral(Id id, address user) external view returns (uint256); - /// @notice The total amount of assets supplied to the market defined by the given `id`. - /// @dev The value can be incaccurate since it does not take into account the accrued interests. + /// @notice The total supply of the market `id`. + /// @dev Does not contain the accrued interest since the last interaction. function totalSupply(Id id) external view returns (uint256); - /// @notice The total supply shares of the market defined by the given `id`. + /// @notice The total supply shares of the market `id`. function totalSupplyShares(Id id) external view returns (uint256); - /// @notice The total amount of assets borrowed from the market defined by the given `id`. - /// @dev The value can be incaccurate since it does not take into account the accrued interests. + /// @notice The total borrow of the market `id`. + /// @dev Does not contain the accrued interest since the last interaction. function totalBorrow(Id id) external view returns (uint256); - /// @notice The total borrow shares of the market defined by the given `id`. + /// @notice The total borrow shares of the market `id`. function totalBorrowShares(Id id) external view returns (uint256); - /// @notice The last update of the market defined by the given `id` (used to check if a market has been created). + /// @notice The last update timestamp of the market `id` (also used to check if a market has been created). function lastUpdate(Id id) external view returns (uint256); - /// @notice The fee of the market defined by the given `id`. + /// @notice The fee of the market `id`. function fee(Id id) external view returns (uint256); /// @notice Whether the `irm` is enabled. @@ -71,8 +70,8 @@ interface IBlue is IFlashLender { /// @notice Whether the `lltv` is enabled. function isLltvEnabled(uint256 lltv) external view returns (bool); - /// @notice Whether `aurothized` is authorized to modify `authorizer`'s positions. - /// @dev By default, `msg.sender` is authorized by themself. + /// @notice Whether `authorized` is authorized to modify `authorizer`'s positions. + /// @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 `user`'s current nonce. Used to prevent replay attacks with EIP-712 signatures. diff --git a/src/interfaces/IERC20.sol b/src/interfaces/IERC20.sol index 46f0c2cec..e4628359f 100644 --- a/src/interfaces/IERC20.sol +++ b/src/interfaces/IERC20.sol @@ -4,6 +4,5 @@ pragma solidity >=0.5.0; /// @title IERC20 /// @author Morpho Labs /// @custom:contact security@morpho.xyz -/// @notice Library exposing errors used in Blue. /// @dev Empty because we only call functions in assembly. It prevents calling transfer (transferFrom) instead of safeTransfer (safeTransferFrom). interface IERC20 {} diff --git a/src/libraries/ErrorsLib.sol b/src/libraries/ErrorsLib.sol index 38b7b42c0..e32f33be9 100644 --- a/src/libraries/ErrorsLib.sol +++ b/src/libraries/ErrorsLib.sol @@ -23,15 +23,12 @@ library ErrorsLib { /// @notice Thrown when the market is not created. string internal constant MARKET_NOT_CREATED = "market not created"; - /// @notice Thrown when one of the input is not consistent. + /// @notice Thrown when not exactly one of the amount inputs is zero. string constant INCONSISTENT_INPUT = "inconsistent input"; /// @notice Thrown when a zero amount is passed as input. string internal constant ZERO_AMOUNT = "zero amount"; - /// @notice Thrown when a zero shares amount is passed as input. - string internal constant ZERO_SHARES = "zero shares"; - /// @notice Thrown when a zero address is passed as input. string internal constant ZERO_ADDRESS = "zero address"; @@ -44,7 +41,7 @@ library ErrorsLib { /// @notice Thrown when the liquidity is insufficient to `withdraw` or `borrow`. string internal constant INSUFFICIENT_LIQUIDITY = "insufficient liquidity"; - /// @notice Thrown when the position is healthy preventing its liquidation. + /// @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. From 8a176f9d0dc9cb4e3b8713d99a3b8f4473bc9864 Mon Sep 17 00:00:00 2001 From: MerlinEgalite Date: Fri, 11 Aug 2023 10:47:32 +0200 Subject: [PATCH 12/12] docs: apply suggestions 2 --- src/interfaces/IBlue.sol | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/src/interfaces/IBlue.sol b/src/interfaces/IBlue.sol index ad7f05ada..36e38272c 100644 --- a/src/interfaces/IBlue.sol +++ b/src/interfaces/IBlue.sol @@ -5,6 +5,12 @@ import {IFlashLender} from "./IFlashLender.sol"; type Id is bytes32; +/// @notice Contains the parameters defining market. +/// @param borrowableAsset The address of the borrowable asset. +/// @param collateralAsset The address of the collateral asset. +/// @param oracle The address of the oracle. +/// @param irm The address of the interest rate model. +/// @param lltv The Liquidation LTV. struct Market { address borrowableAsset; address collateralAsset; @@ -114,7 +120,7 @@ interface IBlue is IFlashLender { /// @notice Withdraws the given `amount` of assets or `shares` from the given `market` on behalf of `onBehalf`. /// @dev Either `amount` or `shares` should be zero. /// To withdraw the whole position, pass the `shares`'s balance of `onBehalf`. - /// @dev If `msg.sender != onBehalf`, `msg.sender` must be authorized to withdraw from `onBehalf`. + /// @dev `msg.sender` must be authorized to manage `onBehalf`'s positions. /// @param market The market to withdraw assets from. /// @param shares The amount of amount to withdraw. /// @param shares The amount of shares to burn. @@ -129,12 +135,12 @@ interface IBlue is IFlashLender { /// is guaranteed to borrow `amount` of tokens, /// but the possibility to burn a specific amount of shares is given /// for full compatibility and precision. + /// @dev `msg.sender` must be authorized to manage `onBehalf`'s positions. /// @param market The market to borrow assets from. /// @param amount The amount of assets to borrow. /// @param shares The amount of shares to mint. /// @param onBehalf The address of the owner of the debt. /// @param receiver The address that will receive the debt. - /// @dev If `msg.sender != onBehalf`, `msg.sender` must be authorized to withdraw from `onBehalf`. function borrow(Market memory market, uint256 amount, uint256 shares, address onBehalf, address receiver) external; @@ -160,7 +166,7 @@ interface IBlue is IFlashLender { function supplyCollateral(Market memory market, uint256 amount, address onBehalf, bytes memory data) external; /// @notice Withdraws the given `amount` of collateral from the given `market` on behalf of `onBehalf`. - /// @dev If `msg.sender != onBehalf`, `msg.sender` must be authorized to withdraw from `onBehalf`. + /// @dev `msg.sender` must be authorized to manage `onBehalf`'s positions. /// @param market The market to withdraw collateral from. /// @param amount The amount of collateral to withdraw. /// @param onBehalf The address of the owner of the collateral.