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 all 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
35 changes: 19 additions & 16 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 @@ -19,20 +20,16 @@ struct Market {
IERC20 collateralAsset;
IOracle borrowableOracle;
IOracle collateralOracle;
IIrm irm;
uint lLTV;
}

using {toId} for Market;

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

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 {
using MathLib for uint;
using SafeTransferLib for IERC20;
Expand All @@ -57,6 +54,8 @@ contract Blue {
mapping(Id => uint) public totalBorrowShares;
// Interests last update (used to check if a market has been created).
mapping(Id => uint) public lastUpdate;
// Enabled IRMs.
mapping(IIrm => bool) public isIrmEnabled;

// Constructor.

Expand All @@ -77,13 +76,18 @@ contract Blue {
owner = newOwner;
}

function enableIrm(IIrm irm) external onlyOwner {
isIrmEnabled[irm] = true;
}

// Markets management.

function createMarket(Market calldata market) external {
Id id = market.toId();
require(isIrmEnabled[market.irm], "IRM not enabled");
require(lastUpdate[id] == 0, "market already exists");

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

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

accrueInterests(id);
accrueInterests(market, id);

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

accrueInterests(id);
accrueInterests(market, id);

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

accrueInterests(id);
accrueInterests(market, id);

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

accrueInterests(id);
accrueInterests(market, id);

uint shares = amount.wMul(totalBorrowShares[id]).wDiv(totalBorrow[id]);
borrowShare[id][msg.sender] -= shares;
Expand Down Expand Up @@ -189,7 +193,7 @@ contract Blue {
require(lastUpdate[id] != 0, "unknown market");
require(amount != 0, "zero amount");

accrueInterests(id);
accrueInterests(market, id);

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

Expand All @@ -205,7 +209,7 @@ contract Blue {
require(lastUpdate[id] != 0, "unknown market");
require(seized != 0, "zero amount");

accrueInterests(id);
accrueInterests(market, id);

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

Expand Down Expand Up @@ -233,13 +237,12 @@ contract Blue {

// Interests management.

function accrueInterests(Id id) private {
function accrueInterests(Market calldata market, Id id) private {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is not completely elegant to have the two in the interface but

  • recomputing id might be expensive (not worth)
  • with the typing system it's ok
  • the function is not public

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes it involves a keccack at least...

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I had the same in my PoC: I had to pass the storage pointer aside from its corresponding id, for the same efficiency purposes.

It's not obvious though that keeping the id onto the stack is more efficient than keccaking-it again.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@MerlinEgalite can you give it a try please ?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

And do the gas diff as well right?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Crazy, but ok!

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

weird, SHA3 is 30 gas.. https://www.evm.codes/#20?fork=shanghai

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It depends on the size of the input. Here it's the market so

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

20 bytes * 5 + 32 bytes = 132 bytes.

cost = 75

But I agree there's something else.

Note that supplyCollateral gas cost is not changing which is expected.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe @Rubilmax you can checkout on both branches to check my results?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

According to my tests, computing the id of a market takes 340 gas. It's coherent because we need to load from calldata to memory 6 word of 32 bytes, and hash them. Additionally, there are some Solidity checks, which easily sum up to >300 gas.

But this explanation alone doesn't account for the 1k gas difference - even if we add the costs of the additional jumps. The only plausible explanation I see is that the Solidity compiler adds some additional logic in the second case. Anyway, I'm always in favor of giving the minimum set of arguments required - in this case the id, the irm and the lltv.

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 borrowRate = market.irm.borrowRate(market);
uint accruedInterests = marketTotalBorrow.wMul(borrowRate).wMul(block.timestamp - lastUpdate[id]);
totalSupply[id] = marketTotalSupply + accruedInterests;
totalBorrow[id] = marketTotalBorrow + accruedInterests;
Expand Down
8 changes: 8 additions & 0 deletions src/interfaces/IIrm.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
// SPDX-License-Identifier: UNLICENSED
pragma solidity >=0.5.0;

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

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

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

import "src/Blue.sol";

contract IrmMock is IIrm {
using MathLib for uint;

Blue public immutable blue;

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

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

// 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;
}
}
Loading