diff --git a/src/Blue.sol b/src/Blue.sol index 52e59c070..d4fa4b938 100644 --- a/src/Blue.sol +++ b/src/Blue.sol @@ -15,7 +15,9 @@ import {SharesMathLib} from "./libraries/SharesMathLib.sol"; import {SafeTransferLib} from "./libraries/SafeTransferLib.sol"; 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; /// @dev The EIP-712 typeHash for EIP712Domain. @@ -25,78 +27,89 @@ bytes32 constant DOMAIN_TYPEHASH = keccak256("EIP712Domain(string name,uint256 c 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 MarketLib for Market; using SharesMathLib for uint256; using SafeTransferLib for IERC20; using FixedPointMathLib for uint256; - // 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, ErrorsLib.NOT_OWNER); _; } - // Only owner functions. + /* ONLY OWNER FUNCTIONS */ + /// @inheritdoc IBlue function setOwner(address newOwner) external onlyOwner { owner = newOwner; emit EventsLib.SetOwner(newOwner); } + /// @inheritdoc IBlue function enableIrm(address irm) external onlyOwner { isIrmEnabled[irm] = true; emit EventsLib.EnableIrm(address(irm)); } + /// @inheritdoc IBlue function enableLltv(uint256 lltv) external onlyOwner { require(lltv < WAD, ErrorsLib.LLTV_TOO_HIGH); isLltvEnabled[lltv] = true; @@ -104,7 +117,7 @@ contract Blue is IBlue { emit EventsLib.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, ErrorsLib.MARKET_NOT_CREATED); @@ -118,14 +131,16 @@ contract Blue is IBlue { emit EventsLib.SetFee(id, newFee); } + /// @inheritdoc IBlue function setFeeRecipient(address recipient) external onlyOwner { feeRecipient = recipient; emit EventsLib.SetFeeRecipient(recipient); } - // Markets management. + /* MARKET CREATION */ + /// @inheritdoc IBlue function createMarket(Market memory market) external { Id id = market.id(); require(isIrmEnabled[market.irm], ErrorsLib.IRM_NOT_ENABLED); @@ -137,8 +152,9 @@ contract Blue is IBlue { emit EventsLib.CreateMarket(id, market); } - // Supply management. + /* SUPPLY MANAGEMENT */ + /// @inheritdoc IBlue function supply(Market memory market, uint256 amount, uint256 shares, address onBehalf, bytes calldata data) external { @@ -163,6 +179,7 @@ contract Blue is IBlue { IERC20(market.borrowableAsset).safeTransferFrom(msg.sender, address(this), amount); } + /// @inheritdoc IBlue function withdraw(Market memory market, uint256 amount, uint256 shares, address onBehalf, address receiver) external { @@ -189,8 +206,9 @@ contract Blue is IBlue { IERC20(market.borrowableAsset).safeTransfer(receiver, amount); } - // Borrow management. + /* BORROW MANAGEMENT */ + /// @inheritdoc IBlue function borrow(Market memory market, uint256 amount, uint256 shares, address onBehalf, address receiver) external { @@ -218,6 +236,7 @@ contract Blue is IBlue { IERC20(market.borrowableAsset).safeTransfer(receiver, amount); } + /// @inheritdoc IBlue function repay(Market memory market, uint256 amount, uint256 shares, address onBehalf, bytes calldata data) external { @@ -242,9 +261,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, ErrorsLib.MARKET_NOT_CREATED); @@ -262,6 +281,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, ErrorsLib.MARKET_NOT_CREATED); @@ -281,8 +301,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, ErrorsLib.MARKET_NOT_CREATED); @@ -325,8 +346,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); @@ -337,14 +359,16 @@ contract Blue is IBlue { IERC20(token).safeTransferFrom(msg.sender, address(this), amount); } - // Authorizations. + /* AUTHORIZATION */ + /// @inheritdoc IBlue function setAuthorization(address authorized, bool newIsAuthorized) external { isAuthorized[msg.sender][authorized] = newIsAuthorized; emit EventsLib.SetAuthorization(msg.sender, msg.sender, authorized, newIsAuthorized); } + /// @inheritdoc IBlue /// @dev The signature is malleable, but it has no impact on the security here. function setAuthorizationWithSig( address authorizer, @@ -374,8 +398,9 @@ contract Blue is IBlue { return msg.sender == user || isAuthorized[user][msg.sender]; } - // Interests management. + /* INTEREST MANAGEMENT */ + /// @dev Accrues interests for `market`. function _accrueInterests(Market memory market, Id id) internal { uint256 elapsed = block.timestamp - lastUpdate[id]; @@ -404,8 +429,9 @@ contract Blue is IBlue { lastUpdate[id] = block.timestamp; } - // Health check. + /* HEALTH CHECK */ + /// @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; @@ -414,6 +440,7 @@ contract Blue is IBlue { return _isHealthy(market, id, user, collateralPrice, 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 @@ -425,8 +452,9 @@ contract Blue is IBlue { return maxBorrow >= borrowed; } - // 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 f19f10e2e..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; @@ -20,48 +26,172 @@ struct Signature { bytes32 s; } +/// @title IBlue +/// @author Morpho Labs +/// @custom:contact security@morpho.xyz +/// @notice Interface of Blue. interface IBlue is IFlashLender { + /// @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. + /// @dev The recipient receives the fees through a supply position. 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); - function totalSupply(Id) external view returns (uint256); - function totalSupplyShares(Id) external view returns (uint256); - function totalBorrow(Id) external view returns (uint256); - function totalBorrowShares(Id) external view returns (uint256); - function lastUpdate(Id) external view returns (uint256); - function fee(Id) external view returns (uint256); - - function isIrmEnabled(address) external view returns (bool); - function isLltvEnabled(uint256) external view returns (bool); - function isAuthorized(address, address) external view returns (bool); - function nonce(address) external view returns (uint256); + /// @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 `id`. + function borrowShares(Id, address user) external view returns (uint256); + + /// @notice The `user`'s collateral balance on the market `id`. + function collateral(Id id, address user) external view returns (uint256); + + /// @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 `id`. + function totalSupplyShares(Id id) external view returns (uint256); + + /// @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 `id`. + function totalBorrowShares(Id id) external view returns (uint256); + + /// @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 `id`. + function fee(Id id) external view returns (uint256); + + /// @notice Whether the `irm` is enabled. + function isIrmEnabled(address irm) external view returns (bool); + + /// @notice Whether the `lltv` is enabled. + function isLltvEnabled(uint256 lltv) external view returns (bool); + + /// @notice Whether `authorized` is authorized to modify `authorizer`'s 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. + function nonce(address user) external view returns (uint256); + /// @notice Sets `newOwner` as owner of the contract. function setOwner(address newOwner) external; + + /// @notice Enables `irm` as possible IRM for market creation. function enableIrm(address irm) external; + + /// @notice Enables `lltv` as possible LLTV for market creation. function enableLltv(uint256 lltv) external; + + /// @notice Sets the `newFee` for `market`. + /// @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. function setFeeRecipient(address recipient) external; + + /// @notice Creates `market`. function createMarket(Market memory market) external; + /// @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 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 `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. + /// @param onBehalf The address of the owner of the withdrawn assets. + /// @param receiver The address that will receive the withdrawn assets. function withdraw(Market memory market, uint256 amount, uint256 shares, address onBehalf, address receiver) external; + + /// @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. + /// @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. function borrow(Market memory market, uint256 amount, uint256 shares, address onBehalf, address receiver) external; + + /// @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 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 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 collateral. + /// @param data Arbitrary data to pass to the `onBlueSupplyCollateral` callback. Pass empty data if not needed. 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 `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. + /// @param receiver The address that will receive the withdrawn collateral. function withdrawCollateral(Market memory market, uint256 amount, address onBehalf, address receiver) external; + + /// @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 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; - 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 setAuthorizationWithSig( address authorizer, address authorized, @@ -70,5 +200,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 89521ef11..7c5a5c185 100644 --- a/src/interfaces/IBlueCallbacks.sol +++ b/src/interfaces/IBlueCallbacks.sol @@ -1,22 +1,52 @@ // 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 amount The amount of repaid tokens. + /// @param data Arbitrary data passed to the `liquidate` function. function onBlueLiquidate(uint256 amount, 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 amount The amount that was flash loaned. + /// @param data Arbitrary data passed to the `flashLoan` function. function onBlueFlashLoan(uint256 amount, bytes calldata data) external; } diff --git a/src/interfaces/IERC20.sol b/src/interfaces/IERC20.sol index 31f0cc95f..e4628359f 100644 --- a/src/interfaces/IERC20.sol +++ b/src/interfaces/IERC20.sol @@ -1,6 +1,8 @@ // 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 +/// @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 1cb7f9daf..b309ffe7f 100644 --- a/src/interfaces/IOracle.sol +++ b/src/interfaces/IOracle.sol @@ -1,6 +1,10 @@ // 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 collateral asset quoted in the borrowable asset and the price's unit scale. function price() external view returns (uint256 collateralPrice, uint256 scale); diff --git a/src/libraries/ErrorsLib.sol b/src/libraries/ErrorsLib.sol index 885a5d3e9..e32f33be9 100644 --- a/src/libraries/ErrorsLib.sol +++ b/src/libraries/ErrorsLib.sol @@ -2,20 +2,51 @@ pragma solidity ^0.8.0; library ErrorsLib { - string constant NOT_OWNER = "not owner"; - string constant LLTV_TOO_HIGH = "LLTV too high"; - string constant MAX_FEE_EXCEEDED = "MAX_FEE exceeded"; - string constant IRM_NOT_ENABLED = "IRM not enabled"; - string constant LLTV_NOT_ENABLED = "LLTV not enabled"; - string constant MARKET_CREATED = "market created"; - string constant MARKET_NOT_CREATED = "market not created"; + /// @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 not exactly one of the amount inputs is zero. string constant INCONSISTENT_INPUT = "inconsistent input"; - string constant ZERO_AMOUNT = "zero amount"; - string constant ZERO_ADDRESS = "zero address"; - string constant UNAUTHORIZED = "unauthorized"; - string constant INSUFFICIENT_COLLATERAL = "insufficient collateral"; - string constant INSUFFICIENT_LIQUIDITY = "insufficient liquidity"; - string constant HEALTHY_POSITION = "position is healthy"; - string constant INVALID_SIGNATURE = "invalid signature"; - string constant SIGNATURE_EXPIRED = "signature expired"; + + /// @notice Thrown when a zero amount is passed as input. + string internal constant ZERO_AMOUNT = "zero amount"; + + /// @notice Thrown when a zero address is passed as input. + string internal constant ZERO_ADDRESS = "zero address"; + + /// @notice Thrown when the caller is not authorized to conduct an action. + string internal constant UNAUTHORIZED = "unauthorized"; + + /// @notice Thrown when the collateral is insufficient to `borrow` or `withdrawCollateral`. + string internal constant INSUFFICIENT_COLLATERAL = "insufficient collateral"; + + /// @notice Thrown when the liquidity is insufficient to `withdraw` or `borrow`. + string internal constant INSUFFICIENT_LIQUIDITY = "insufficient liquidity"; + + /// @notice Thrown when the position to liquidate is healthy. + string internal constant HEALTHY_POSITION = "position is healthy"; + + /// @notice Thrown when the authorization signature is invalid. + string internal constant INVALID_SIGNATURE = "invalid signature"; + + /// @notice Thrown when the authorization signature is expired. + string internal constant SIGNATURE_EXPIRED = "signature expired"; } diff --git a/src/libraries/EventsLib.sol b/src/libraries/EventsLib.sol index a3bdd3ba3..114c99b83 100644 --- a/src/libraries/EventsLib.sol +++ b/src/libraries/EventsLib.sol @@ -4,12 +4,47 @@ pragma solidity ^0.8.0; import {Id, Market} from "../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); } 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/SharesMathLib.sol b/src/libraries/SharesMathLib.sol index a19741766..f31c9fa3e 100644 --- a/src/libraries/SharesMathLib.sol +++ b/src/libraries/SharesMathLib.sol @@ -3,34 +3,39 @@ pragma solidity ^0.8.0; import {FixedPointMathLib} from "./FixedPointMathLib.sol"; +/// @title SharesMath +/// @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; uint256 internal constant VIRTUAL_SHARES = 1e18; + 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); }