Skip to content

Commit

Permalink
docs: add Natspecs
Browse files Browse the repository at this point in the history
  • Loading branch information
MerlinEgalite committed Aug 8, 2023
1 parent 7a7b0f3 commit 76272bc
Show file tree
Hide file tree
Showing 11 changed files with 388 additions and 67 deletions.
114 changes: 75 additions & 39 deletions src/Blue.sol
Original file line number Diff line number Diff line change
Expand Up @@ -11,104 +11,115 @@ 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 [email protected]
/// @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;

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);
Expand All @@ -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);
Expand All @@ -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);
Expand All @@ -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);
Expand All @@ -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);
Expand All @@ -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);
Expand All @@ -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);
Expand All @@ -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);
Expand All @@ -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);
Expand Down Expand Up @@ -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);

Expand All @@ -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,
Expand All @@ -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];

Expand Down Expand Up @@ -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;

Expand All @@ -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
Expand All @@ -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;

Expand Down
Loading

0 comments on commit 76272bc

Please sign in to comment.