Skip to content

Commit

Permalink
Merge branch 'main' of github.com:morpho-labs/blue into refactor/accr…
Browse files Browse the repository at this point in the history
…ue-interests
  • Loading branch information
Rubilmax committed Jul 19, 2023
2 parents 5464dcf + 28e96a5 commit 4d8a502
Show file tree
Hide file tree
Showing 6 changed files with 198 additions and 110 deletions.
87 changes: 42 additions & 45 deletions src/Blue.sol
Original file line number Diff line number Diff line change
Expand Up @@ -3,35 +3,17 @@ pragma solidity 0.8.20;

import {IIrm} from "src/interfaces/IIrm.sol";
import {IERC20} from "src/interfaces/IERC20.sol";
import {IOracle} from "src/interfaces/IOracle.sol";

import {MathLib} from "src/libraries/MathLib.sol";
import {Id, Market, MarketLib} from "src/libraries/MarketLib.sol";
import {SafeTransferLib} from "src/libraries/SafeTransferLib.sol";

uint256 constant WAD = 1e18;
uint256 constant ALPHA = 0.5e18;

// Market id.
type Id is bytes32;

// Market.
struct Market {
IERC20 borrowableAsset;
IERC20 collateralAsset;
IOracle borrowableOracle;
IOracle collateralOracle;
IIrm irm;
uint256 lltv;
}

using {toId} for Market;

function toId(Market calldata market) pure returns (Id) {
return Id.wrap(keccak256(abi.encode(market)));
}

contract Blue {
using MathLib for uint256;
using MarketLib for Market;
using SafeTransferLib for IERC20;

// Storage.
Expand Down Expand Up @@ -62,6 +44,8 @@ contract Blue {
mapping(IIrm => bool) public isIrmEnabled;
// Enabled LLTVs.
mapping(uint256 => bool) public isLltvEnabled;
// User's managers.
mapping(address => mapping(address => bool)) public isApproved;

// Constructor.

Expand Down Expand Up @@ -93,7 +77,7 @@ contract Blue {

// @notice It is the owner's responsibility to ensure a fee recipient is set before setting a non-zero fee.
function setFee(Market calldata market, uint256 newFee) external onlyOwner {
Id id = market.toId();
Id id = market.id();
require(lastUpdate[id] != 0, "unknown market");
require(newFee <= WAD, "fee must be <= 1");
fee[id] = newFee;
Expand All @@ -106,7 +90,7 @@ contract Blue {
// Markets management.

function createMarket(Market calldata market) external {
Id id = market.toId();
Id id = market.id();
require(isIrmEnabled[market.irm], "IRM not enabled");
require(isLltvEnabled[market.lltv], "LLTV not enabled");
require(lastUpdate[id] == 0, "market already exists");
Expand All @@ -116,19 +100,19 @@ contract Blue {

// Supply management.

function supply(Market calldata market, uint256 amount) external {
Id id = market.toId();
function supply(Market calldata market, uint256 amount, address onBehalf) external {
Id id = market.id();
require(lastUpdate[id] != 0, "unknown market");
require(amount != 0, "zero amount");

_accrueInterests(market, id);

if (totalSupply[id] == 0) {
supplyShare[id][msg.sender] = WAD;
supplyShare[id][onBehalf] = WAD;
totalSupplyShares[id] = WAD;
} else {
uint256 shares = amount.wMul(totalSupplyShares[id]).wDiv(totalSupply[id]);
supplyShare[id][msg.sender] += shares;
supplyShare[id][onBehalf] += shares;
totalSupplyShares[id] += shares;
}

Expand All @@ -137,15 +121,16 @@ contract Blue {
market.borrowableAsset.safeTransferFrom(msg.sender, address(this), amount);
}

function withdraw(Market calldata market, uint256 amount) external {
Id id = market.toId();
function withdraw(Market calldata market, uint256 amount, address onBehalf) external {
Id id = market.id();
require(lastUpdate[id] != 0, "unknown market");
require(amount != 0, "zero amount");
require(isSenderOrIsApproved(onBehalf), "not approved");

_accrueInterests(market, id);

uint256 shares = amount.wMul(totalSupplyShares[id]).wDiv(totalSupply[id]);
supplyShare[id][msg.sender] -= shares;
supplyShare[id][onBehalf] -= shares;
totalSupplyShares[id] -= shares;

totalSupply[id] -= amount;
Expand All @@ -157,39 +142,40 @@ contract Blue {

// Borrow management.

function borrow(Market calldata market, uint256 amount) external {
Id id = market.toId();
function borrow(Market calldata market, uint256 amount, address onBehalf) external {
Id id = market.id();
require(lastUpdate[id] != 0, "unknown market");
require(amount != 0, "zero amount");
require(isSenderOrIsApproved(onBehalf), "not approved");

_accrueInterests(market, id);

if (totalBorrow[id] == 0) {
borrowShare[id][msg.sender] = WAD;
borrowShare[id][onBehalf] = WAD;
totalBorrowShares[id] = WAD;
} else {
uint256 shares = amount.wMul(totalBorrowShares[id]).wDiv(totalBorrow[id]);
borrowShare[id][msg.sender] += shares;
borrowShare[id][onBehalf] += shares;
totalBorrowShares[id] += shares;
}

totalBorrow[id] += amount;

require(isHealthy(market, id, msg.sender), "not enough collateral");
require(isHealthy(market, id, onBehalf), "not enough collateral");
require(totalBorrow[id] <= totalSupply[id], "not enough liquidity");

market.borrowableAsset.safeTransfer(msg.sender, amount);
}

function repay(Market calldata market, uint256 amount) external {
Id id = market.toId();
function repay(Market calldata market, uint256 amount, address onBehalf) external {
Id id = market.id();
require(lastUpdate[id] != 0, "unknown market");
require(amount != 0, "zero amount");

_accrueInterests(market, id);

uint256 shares = amount.wMul(totalBorrowShares[id]).wDiv(totalBorrow[id]);
borrowShare[id][msg.sender] -= shares;
borrowShare[id][onBehalf] -= shares;
totalBorrowShares[id] -= shares;

totalBorrow[id] -= amount;
Expand All @@ -200,36 +186,37 @@ contract Blue {
// Collateral management.

/// @dev Don't accrue interests because it's not required and it saves gas.
function supplyCollateral(Market calldata market, uint256 amount) external {
Id id = market.toId();
function supplyCollateral(Market calldata market, uint256 amount, address onBehalf) external {
Id id = market.id();
require(lastUpdate[id] != 0, "unknown market");
require(amount != 0, "zero amount");

// Don't accrue interests because it's not required and it saves gas.

collateral[id][msg.sender] += amount;
collateral[id][onBehalf] += amount;

market.collateralAsset.safeTransferFrom(msg.sender, address(this), amount);
}

function withdrawCollateral(Market calldata market, uint256 amount) external {
Id id = market.toId();
function withdrawCollateral(Market calldata market, uint256 amount, address onBehalf) external {
Id id = market.id();
require(lastUpdate[id] != 0, "unknown market");
require(amount != 0, "zero amount");
require(isSenderOrIsApproved(onBehalf), "not approved");

_accrueInterests(market, id);

collateral[id][msg.sender] -= amount;
collateral[id][onBehalf] -= amount;

require(isHealthy(market, id, msg.sender), "not enough collateral");
require(isHealthy(market, id, onBehalf), "not enough collateral");

market.collateralAsset.safeTransfer(msg.sender, amount);
}

// Liquidation.

function liquidate(Market calldata market, address borrower, uint256 seized) external {
Id id = market.toId();
Id id = market.id();
require(lastUpdate[id] != 0, "unknown market");
require(seized != 0, "zero amount");

Expand Down Expand Up @@ -262,6 +249,16 @@ contract Blue {
market.borrowableAsset.safeTransferFrom(msg.sender, address(this), repaid);
}

// Position management.

function setApproval(address manager, bool isAllowed) external {
isApproved[msg.sender][manager] = isAllowed;
}

function isSenderOrIsApproved(address user) internal view returns (bool) {
return msg.sender == user || isApproved[user][msg.sender];
}

// Interests management.

function accrued(Market calldata market) external view returns (uint256 accruedInterests, uint256 feeShares) {
Expand Down
2 changes: 1 addition & 1 deletion src/interfaces/IIrm.sol
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
// SPDX-License-Identifier: UNLICENSED
pragma solidity >=0.5.0;

import {Market} from "src/Blue.sol";
import {Market} from "src/libraries/MarketLib.sol";

interface IIrm {
function borrowRate(Market calldata market) external view returns (uint256);
Expand Down
23 changes: 23 additions & 0 deletions src/libraries/MarketLib.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.0;

import {IIrm} from "src/interfaces/IIrm.sol";
import {IERC20} from "src/interfaces/IERC20.sol";
import {IOracle} from "src/interfaces/IOracle.sol";

type Id is bytes32;

struct Market {
IERC20 borrowableAsset;
IERC20 collateralAsset;
IOracle borrowableOracle;
IOracle collateralOracle;
IIrm irm;
uint256 lltv;
}

library MarketLib {
function id(Market calldata market) internal pure returns (Id) {
return Id.wrap(keccak256(abi.encode(market)));
}
}
8 changes: 6 additions & 2 deletions src/mocks/IrmMock.sol
Original file line number Diff line number Diff line change
@@ -1,12 +1,16 @@
// SPDX-License-Identifier: UNLICENSED
pragma solidity 0.8.20;

import {IIrm} from "src/interfaces/IIrm.sol";

import {MathLib} from "src/libraries/MathLib.sol";
import {Id, Market, MarketLib} from "src/libraries/MarketLib.sol";

import "src/Blue.sol";
import {Blue} from "src/Blue.sol";

contract IrmMock is IIrm {
using MathLib for uint256;
using MarketLib for Market;

Blue public immutable blue;

Expand All @@ -15,7 +19,7 @@ contract IrmMock is IIrm {
}

function borrowRate(Market calldata market) external view returns (uint256) {
Id id = Id.wrap(keccak256(abi.encode(market)));
Id id = market.id();
uint256 utilization = blue.totalBorrow(id).wDiv(blue.totalSupply(id));

// Divide by the number of seconds in a year.
Expand Down
Loading

0 comments on commit 4d8a502

Please sign in to comment.