Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Natspecs #238

Merged
merged 15 commits into from
Aug 11, 2023
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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";
MathisGD marked this conversation as resolved.
Show resolved Hide resolved

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)");
MerlinEgalite marked this conversation as resolved.
Show resolved Hide resolved

/// @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 */
MerlinEgalite marked this conversation as resolved.
Show resolved Hide resolved

/// @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.
MerlinEgalite marked this conversation as resolved.
Show resolved Hide resolved
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