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 IRM whitelist #59

Merged
merged 25 commits into from
Jul 8, 2023
Merged
Show file tree
Hide file tree
Changes from 7 commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
1b48119
feat: add IRM whitelist
MerlinEgalite Jul 5, 2023
2224683
feat: rename whitelist mapping
MerlinEgalite Jul 5, 2023
a4d3046
feat: use interface
MerlinEgalite Jul 5, 2023
ac5e328
feat: more general IRM
MerlinEgalite Jul 5, 2023
5bc9d6e
refactor: naming
MerlinEgalite Jul 5, 2023
e0ce0f5
test: add tests + remove console log
MerlinEgalite Jul 5, 2023
78c4ca1
perf: save gas
MerlinEgalite Jul 5, 2023
5363c1d
refactor: apply changes
MerlinEgalite Jul 6, 2023
36802db
feat: move function to admin place
MerlinEgalite Jul 6, 2023
2692eb8
refactor: get back to bool
MerlinEgalite Jul 6, 2023
cc22ada
refactor: whitelist -> enable
MerlinEgalite Jul 6, 2023
ffc05c2
test: add missing test + format
MerlinEgalite Jul 6, 2023
de937e7
feat: use market instead of id
MerlinEgalite Jul 6, 2023
4160f54
Merge branch 'feat/owner' of github.com:morpho-labs/blue into feat/ir…
MerlinEgalite Jul 6, 2023
93f0f1c
refactor: renaming irm
MerlinEgalite Jul 6, 2023
6dd7fca
test: better tests + add one
MerlinEgalite Jul 6, 2023
fe71f20
feat: cleaner code
MerlinEgalite Jul 6, 2023
ab45331
Merge branch 'dev' of github.com:morpho-labs/blue into feat/irm-white…
MerlinEgalite Jul 6, 2023
4f9fb84
fix: issue with naming
MerlinEgalite Jul 7, 2023
d6963be
feat: remove useless ownable
MerlinEgalite Jul 7, 2023
32337d6
refactor: recreate borrowRate var
MerlinEgalite Jul 7, 2023
79a07d7
test: fix hardat tests
MerlinEgalite Jul 7, 2023
2c89aa9
test: capital letters for constants
MerlinEgalite Jul 7, 2023
057e0d7
test: fix test
MerlinEgalite Jul 7, 2023
6c1d842
refactor: remove useless imports
MerlinEgalite Jul 8, 2023
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
37 changes: 19 additions & 18 deletions src/Blue.sol
MerlinEgalite marked this conversation as resolved.
Show resolved Hide resolved
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
// SPDX-License-Identifier: UNLICENSED
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";

Expand All @@ -22,15 +23,10 @@ struct Market {
IERC20 collateralAsset;
IOracle borrowableOracle;
IOracle collateralOracle;
IIRM irm;
MerlinEgalite marked this conversation as resolved.
Show resolved Hide resolved
uint lLTV;
}

function irm(uint utilization) pure returns (uint) {
// Divide by the number of seconds in a year.
// This is a very simple model (to refine later) where x% utilization corresponds to x% APR.
return utilization / 365 days;
}

contract Blue is Ownable {
using MathLib for uint;
using SafeTransferLib for IERC20;
Expand All @@ -53,16 +49,23 @@ contract Blue is Ownable {
mapping(Id => uint) public totalBorrowShares;
// Interests last update (used to check if a market has been created).
mapping(Id => uint) public lastUpdate;
// IRM whitelist.
mapping(address => uint) public isIRMWhitelisted;
MerlinEgalite marked this conversation as resolved.
Show resolved Hide resolved
MathisGD marked this conversation as resolved.
Show resolved Hide resolved

constructor(address owner) Ownable(owner) {}

// Markets management.

function createMarket(Market calldata market) external {
Id id = Id.wrap(keccak256(abi.encode(market)));
require(isIRMWhitelisted[address(market.irm)] == 1, "IRM not whitelisted");
require(lastUpdate[id] == 0, "market already exists");

accrueInterests(id);
accrueInterests(id, market.irm);
}

function whitelistIRM(address irm) external onlyOwner {
isIRMWhitelisted[irm] = 1;
}
MerlinEgalite marked this conversation as resolved.
Show resolved Hide resolved

// Supply management.
Expand All @@ -72,7 +75,7 @@ contract Blue is Ownable {
require(lastUpdate[id] != 0, "unknown market");
require(amount > 0, "zero amount");

accrueInterests(id);
accrueInterests(id, market.irm);

if (totalSupply[id] == 0) {
supplyShare[id][msg.sender] = WAD;
Expand All @@ -93,7 +96,7 @@ contract Blue is Ownable {
require(lastUpdate[id] != 0, "unknown market");
require(amount > 0, "zero amount");

accrueInterests(id);
accrueInterests(id, market.irm);

uint shares = amount.wMul(totalSupplyShares[id]).wDiv(totalSupply[id]);
supplyShare[id][msg.sender] -= shares;
Expand All @@ -113,7 +116,7 @@ contract Blue is Ownable {
require(lastUpdate[id] != 0, "unknown market");
require(amount > 0, "zero amount");

accrueInterests(id);
accrueInterests(id, market.irm);

if (totalBorrow[id] == 0) {
borrowShare[id][msg.sender] = WAD;
Expand All @@ -137,7 +140,7 @@ contract Blue is Ownable {
require(lastUpdate[id] != 0, "unknown market");
require(amount > 0, "zero amount");

accrueInterests(id);
accrueInterests(id, market.irm);

uint shares = amount.wMul(totalBorrowShares[id]).wDiv(totalBorrow[id]);
borrowShare[id][msg.sender] -= shares;
Expand All @@ -155,7 +158,7 @@ contract Blue is Ownable {
require(lastUpdate[id] != 0, "unknown market");
require(amount > 0, "zero amount");

accrueInterests(id);
accrueInterests(id, market.irm);

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

Expand All @@ -167,7 +170,7 @@ contract Blue is Ownable {
require(lastUpdate[id] != 0, "unknown market");
require(amount > 0, "zero amount");

accrueInterests(id);
accrueInterests(id, market.irm);

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

Expand All @@ -183,7 +186,7 @@ contract Blue is Ownable {
require(lastUpdate[id] != 0, "unknown market");
require(seized > 0, "zero amount");

accrueInterests(id);
accrueInterests(id, market.irm);

require(!isHealthy(market, id, borrower), "cannot liquidate a healthy position");

Expand Down Expand Up @@ -211,14 +214,12 @@ contract Blue is Ownable {

// Interests management.

function accrueInterests(Id id) private {
function accrueInterests(Id id, IIRM irm) private {
uint marketTotalSupply = totalSupply[id];

if (marketTotalSupply != 0) {
uint marketTotalBorrow = totalBorrow[id];
uint utilization = marketTotalBorrow.wDiv(marketTotalSupply);
uint borrowRate = irm(utilization);
MerlinEgalite marked this conversation as resolved.
Show resolved Hide resolved
uint accruedInterests = marketTotalBorrow.wMul(borrowRate).wMul(block.timestamp - lastUpdate[id]);
uint accruedInterests = marketTotalBorrow.wMul(irm.rate()).wMul(block.timestamp - lastUpdate[id]);
totalSupply[id] = marketTotalSupply + accruedInterests;
totalBorrow[id] = marketTotalBorrow + accruedInterests;
}
Expand Down
6 changes: 6 additions & 0 deletions src/interfaces/IIRM.sol
MerlinEgalite marked this conversation as resolved.
Show resolved Hide resolved
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
// SPDX-License-Identifier: UNLICENSED
pragma solidity >=0.5.0;

interface IIRM {
function rate() external returns (uint);
MerlinEgalite marked this conversation as resolved.
Show resolved Hide resolved
}
32 changes: 32 additions & 0 deletions src/mocks/IRMMock.sol
MerlinEgalite marked this conversation as resolved.
Show resolved Hide resolved
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
// SPDX-License-Identifier: UNLICENSED
pragma solidity 0.8.20;

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

import {MathLib} from "src/libraries/MathLib.sol";

import "src/Blue.sol";

contract IRMMock is IIRM {
using MathLib for uint;

Blue public blue;
MerlinEgalite marked this conversation as resolved.
Show resolved Hide resolved
Id public marketId;

constructor (Blue blueInstance) {
blue = Blue(blueInstance);
}

function setId(Id id) external {
marketId = id;
}

function rate() external view returns (uint) {
uint utilization = blue.totalBorrow(marketId).wDiv(blue.totalSupply(marketId));

// Divide by the number of seconds in a year.
// This is a very simple model (to refine later) where x% utilization corresponds to x% APR.
return utilization / 365 days;
}
}
23 changes: 21 additions & 2 deletions test/forge/Blue.t.sol
MathisGD marked this conversation as resolved.
Show resolved Hide resolved
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import "forge-std/console.sol";
import "src/Blue.sol";
import {ERC20Mock as ERC20} from "src/mocks/ERC20Mock.sol";
import {OracleMock as Oracle} from "src/mocks/OracleMock.sol";
import {IRMMock as IRM} from "src/mocks/IRMMock.sol";

contract BlueTest is Test {
using MathLib for uint;
Expand All @@ -23,23 +24,27 @@ contract BlueTest is Test {
ERC20 private collateralAsset;
Oracle private borrowableOracle;
Oracle private collateralOracle;
IRM private irm;
Market public market;
Id public id;

function setUp() public {
// Create Blue.
blue = new Blue(msg.sender);
blue = new Blue(address(this));
MerlinEgalite marked this conversation as resolved.
Show resolved Hide resolved

// List a market.
borrowableAsset = new ERC20("borrowable", "B", 18);
collateralAsset = new ERC20("collateral", "C", 18);
borrowableOracle = new Oracle();
collateralOracle = new Oracle();
irm = new IRM(blue);
market = Market(
IERC20(address(borrowableAsset)), IERC20(address(collateralAsset)), borrowableOracle, collateralOracle, lLTV
IERC20(address(borrowableAsset)), IERC20(address(collateralAsset)), borrowableOracle, collateralOracle, irm, lLTV
);
blue.whitelistIRM(address(irm));
id = Id.wrap(keccak256(abi.encode(market)));

irm.setId(id);
blue.createMarket(market);

// We set the price of the borrowable asset to zero so that borrowers
Expand Down Expand Up @@ -115,6 +120,20 @@ contract BlueTest is Test {
blue2.transferOwnership(newOwner);
}

function testWhitelistIRMWhenNotOwner(address attacker) public {
vm.assume(attacker != blue.owner());

vm.prank(attacker);
vm.expectRevert("not owner");
blue.whitelistIRM(address(0xdead));
}

function testWhitelistIRM(address newIRM) public {
blue.whitelistIRM(newIRM);

assertEq(blue.isIRMWhitelisted(newIRM), 1);
}

function testSupply(uint amount) public {
amount = bound(amount, 1, 2 ** 64);

Expand Down