Skip to content

Commit 1c23715

Browse files
committed
Merge remote-tracking branch 'origin/feat/enable-lltv' into test/estimate-liquidation-gas
2 parents d6f6f2a + 3f73928 commit 1c23715

File tree

13 files changed

+1087
-151
lines changed

13 files changed

+1087
-151
lines changed

.github/workflows/test.yml

Lines changed: 6 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,6 @@ name: Foundry CI
33
on:
44
workflow_dispatch:
55
push:
6-
branches:
7-
- main
86
pull_request:
97

108
jobs:
@@ -20,16 +18,11 @@ jobs:
2018

2119
- name: Install Foundry
2220
uses: foundry-rs/foundry-toolchain@v1
23-
with:
24-
version: nightly
25-
26-
- name: Run Forge build
27-
run: |
28-
forge --version
29-
forge build --sizes
30-
id: build
3121

3222
- name: Run Forge tests
33-
run: |
34-
forge test -vvv
35-
id: test
23+
run: forge test -vvv
24+
env:
25+
FOUNDRY_FUZZ_RUNS: 100000
26+
FOUNDRY_FUZZ_MAX_TEST_REJECTS: 500000
27+
FOUNDRY_INVARIANT_RUNS: 1000
28+
FOUNDRY_INVARIANT_DEPTH: 100

.husky/commit-msg

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
#!/usr/bin/env sh
2+
. "$(dirname -- "$0")/_/husky.sh"
3+
4+
yarn commitlint --edit "${1}"

.husky/post-checkout

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
#!/bin/sh
2+
. "$(dirname "$0")/_/husky.sh"
3+
4+
forge install
5+
yarn

.husky/post-merge

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
#!/bin/sh
2+
. "$(dirname "$0")/_/husky.sh"
3+
4+
forge install
5+
yarn

.husky/pre-commit

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
#!/bin/sh
2+
. "$(dirname "$0")/_/husky.sh"
3+
4+
yarn lint-staged

foundry.toml

Lines changed: 0 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,6 @@
11
[profile.default]
2-
src = "src"
3-
out = "out"
4-
libs = ["lib"]
52
via-ir = true
63

7-
[fuzz]
8-
runs = 2048
9-
10-
[invariant]
11-
runs = 2048
12-
depth = 32
13-
144
[fmt]
155
int_types = "short"
166

package.json

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@
1414
"openzeppelin-contracts": "^4.0.0"
1515
},
1616
"devDependencies": {
17+
"@commitlint/cli": "^17.6.6",
18+
"@commitlint/config-conventional": "^17.6.6",
1719
"@nomicfoundation/hardhat-chai-matchers": "^1.0.6",
1820
"@nomicfoundation/hardhat-foundry": "^1.0.1",
1921
"@nomicfoundation/hardhat-network-helpers": "^1.0.8",
@@ -45,5 +47,10 @@
4547
"*.ts": "yarn prettier",
4648
"*.json": "yarn prettier",
4749
"*.yml": "yarn prettier"
50+
},
51+
"commitlint": {
52+
"extends": [
53+
"@commitlint/config-conventional"
54+
]
4855
}
4956
}

src/Blue.sol

Lines changed: 65 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,15 @@
11
// SPDX-License-Identifier: UNLICENSED
22
pragma solidity 0.8.20;
33

4+
import {IIrm} from "src/interfaces/IIrm.sol";
45
import {IERC20} from "src/interfaces/IERC20.sol";
56
import {IOracle} from "src/interfaces/IOracle.sol";
67

78
import {MathLib} from "src/libraries/MathLib.sol";
89
import {SafeTransferLib} from "src/libraries/SafeTransferLib.sol";
910

1011
uint constant WAD = 1e18;
11-
12-
uint constant alpha = 0.5e18;
12+
uint constant ALPHA = 0.5e18;
1313

1414
// Market id.
1515
type Id is bytes32;
@@ -20,26 +20,24 @@ struct Market {
2020
IERC20 collateralAsset;
2121
IOracle borrowableOracle;
2222
IOracle collateralOracle;
23-
uint lLTV;
23+
IIrm irm;
24+
uint lltv;
2425
}
2526

2627
using {toId} for Market;
28+
2729
function toId(Market calldata market) pure returns (Id) {
2830
return Id.wrap(keccak256(abi.encode(market)));
2931
}
3032

31-
function irm(uint utilization) pure returns (uint) {
32-
// Divide by the number of seconds in a year.
33-
// This is a very simple model (to refine later) where x% utilization corresponds to x% APR.
34-
return utilization / 365 days;
35-
}
36-
3733
contract Blue {
3834
using MathLib for uint;
3935
using SafeTransferLib for IERC20;
4036

4137
// Storage.
4238

39+
// Owner.
40+
address public owner;
4341
// User' supply balances.
4442
mapping(Id => mapping(address => uint)) public supplyShare;
4543
// User' borrow balances.
@@ -56,24 +54,58 @@ contract Blue {
5654
mapping(Id => uint) public totalBorrowShares;
5755
// Interests last update (used to check if a market has been created).
5856
mapping(Id => uint) public lastUpdate;
57+
// Enabled IRMs.
58+
mapping(IIrm => bool) public isIrmEnabled;
59+
// Enabled LLTVs.
60+
mapping(uint => bool) public isLltvEnabled;
61+
62+
// Constructor.
63+
64+
constructor(address newOwner) {
65+
owner = newOwner;
66+
}
67+
68+
// Modifiers.
69+
70+
modifier onlyOwner() {
71+
require(msg.sender == owner, "not owner");
72+
_;
73+
}
74+
75+
// Only owner functions.
76+
77+
function transferOwnership(address newOwner) external onlyOwner {
78+
owner = newOwner;
79+
}
80+
81+
function enableIrm(IIrm irm) external onlyOwner {
82+
isIrmEnabled[irm] = true;
83+
}
84+
85+
function enableLltv(uint lltv) external onlyOwner {
86+
require(lltv < WAD, "LLTV too high");
87+
isLltvEnabled[lltv] = true;
88+
}
5989

6090
// Markets management.
6191

6292
function createMarket(Market calldata market) external {
6393
Id id = market.toId();
94+
require(isIrmEnabled[market.irm], "IRM not enabled");
95+
require(isLltvEnabled[market.lltv], "LLTV not enabled");
6496
require(lastUpdate[id] == 0, "market already exists");
6597

66-
accrueInterests(id);
98+
accrueInterests(market, id);
6799
}
68100

69101
// Supply management.
70102

71103
function supply(Market calldata market, uint amount) external {
72104
Id id = market.toId();
73105
require(lastUpdate[id] != 0, "unknown market");
74-
require(amount > 0, "zero amount");
106+
require(amount != 0, "zero amount");
75107

76-
accrueInterests(id);
108+
accrueInterests(market, id);
77109

78110
if (totalSupply[id] == 0) {
79111
supplyShare[id][msg.sender] = WAD;
@@ -92,9 +124,9 @@ contract Blue {
92124
function withdraw(Market calldata market, uint amount) external {
93125
Id id = market.toId();
94126
require(lastUpdate[id] != 0, "unknown market");
95-
require(amount > 0, "zero amount");
127+
require(amount != 0, "zero amount");
96128

97-
accrueInterests(id);
129+
accrueInterests(market, id);
98130

99131
uint shares = amount.wMul(totalSupplyShares[id]).wDiv(totalSupply[id]);
100132
supplyShare[id][msg.sender] -= shares;
@@ -112,9 +144,9 @@ contract Blue {
112144
function borrow(Market calldata market, uint amount) external {
113145
Id id = market.toId();
114146
require(lastUpdate[id] != 0, "unknown market");
115-
require(amount > 0, "zero amount");
147+
require(amount != 0, "zero amount");
116148

117-
accrueInterests(id);
149+
accrueInterests(market, id);
118150

119151
if (totalBorrow[id] == 0) {
120152
borrowShare[id][msg.sender] = WAD;
@@ -136,9 +168,9 @@ contract Blue {
136168
function repay(Market calldata market, uint amount) external {
137169
Id id = market.toId();
138170
require(lastUpdate[id] != 0, "unknown market");
139-
require(amount > 0, "zero amount");
171+
require(amount != 0, "zero amount");
140172

141-
accrueInterests(id);
173+
accrueInterests(market, id);
142174

143175
uint shares = amount.wMul(totalBorrowShares[id]).wDiv(totalBorrow[id]);
144176
borrowShare[id][msg.sender] -= shares;
@@ -151,12 +183,13 @@ contract Blue {
151183

152184
// Collateral management.
153185

186+
/// @dev Don't accrue interests because it's not required and it saves gas.
154187
function supplyCollateral(Market calldata market, uint amount) external {
155188
Id id = market.toId();
156189
require(lastUpdate[id] != 0, "unknown market");
157-
require(amount > 0, "zero amount");
190+
require(amount != 0, "zero amount");
158191

159-
accrueInterests(id);
192+
// Don't accrue interests because it's not required and it saves gas.
160193

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

@@ -166,9 +199,9 @@ contract Blue {
166199
function withdrawCollateral(Market calldata market, uint amount) external {
167200
Id id = market.toId();
168201
require(lastUpdate[id] != 0, "unknown market");
169-
require(amount > 0, "zero amount");
202+
require(amount != 0, "zero amount");
170203

171-
accrueInterests(id);
204+
accrueInterests(market, id);
172205

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

@@ -182,14 +215,14 @@ contract Blue {
182215
function liquidate(Market calldata market, address borrower, uint seized) external {
183216
Id id = market.toId();
184217
require(lastUpdate[id] != 0, "unknown market");
185-
require(seized > 0, "zero amount");
218+
require(seized != 0, "zero amount");
186219

187-
accrueInterests(id);
220+
accrueInterests(market, id);
188221

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

191-
// The liquidation incentive is 1 + alpha * (1 / LLTV - 1).
192-
uint incentive = WAD + alpha.wMul(WAD.wDiv(market.lLTV) - WAD);
224+
// The liquidation incentive is 1 + ALPHA * (1 / LLTV - 1).
225+
uint incentive = WAD + ALPHA.wMul(WAD.wDiv(market.lltv) - WAD);
193226
uint repaid = seized.wMul(market.collateralOracle.price()).wDiv(incentive).wDiv(market.borrowableOracle.price());
194227
uint repaidShares = repaid.wMul(totalBorrowShares[id]).wDiv(totalBorrow[id]);
195228

@@ -212,13 +245,12 @@ contract Blue {
212245

213246
// Interests management.
214247

215-
function accrueInterests(Id id) private {
248+
function accrueInterests(Market calldata market, Id id) private {
216249
uint marketTotalSupply = totalSupply[id];
217250

218251
if (marketTotalSupply != 0) {
219252
uint marketTotalBorrow = totalBorrow[id];
220-
uint utilization = marketTotalBorrow.wDiv(marketTotalSupply);
221-
uint borrowRate = irm(utilization);
253+
uint borrowRate = market.irm.borrowRate(market);
222254
uint accruedInterests = marketTotalBorrow.wMul(borrowRate).wMul(block.timestamp - lastUpdate[id]);
223255
totalSupply[id] = marketTotalSupply + accruedInterests;
224256
totalBorrow[id] = marketTotalBorrow + accruedInterests;
@@ -231,11 +263,11 @@ contract Blue {
231263

232264
function isHealthy(Market calldata market, Id id, address user) private view returns (bool) {
233265
uint borrowShares = borrowShare[id][user];
266+
if (borrowShares == 0) return true;
234267
// totalBorrowShares[id] > 0 when borrowShares > 0.
235-
uint borrowValue = borrowShares > 0
236-
? borrowShares.wMul(totalBorrow[id]).wDiv(totalBorrowShares[id]).wMul(market.borrowableOracle.price())
237-
: 0;
268+
uint borrowValue =
269+
borrowShares.wMul(totalBorrow[id]).wDiv(totalBorrowShares[id]).wMul(market.borrowableOracle.price());
238270
uint collateralValue = collateral[id][user].wMul(market.collateralOracle.price());
239-
return collateralValue.wMul(market.lLTV) >= borrowValue;
271+
return collateralValue.wMul(market.lltv) >= borrowValue;
240272
}
241273
}

src/interfaces/IIrm.sol

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
// SPDX-License-Identifier: UNLICENSED
2+
pragma solidity >=0.5.0;
3+
4+
import {Market} from "src/Blue.sol";
5+
6+
interface IIrm {
7+
function borrowRate(Market calldata market) external returns (uint);
8+
}

src/mocks/IrmMock.sol

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
// SPDX-License-Identifier: UNLICENSED
2+
pragma solidity 0.8.20;
3+
4+
import {MathLib} from "src/libraries/MathLib.sol";
5+
6+
import "src/Blue.sol";
7+
8+
contract IrmMock is IIrm {
9+
using MathLib for uint;
10+
11+
Blue public immutable blue;
12+
13+
constructor(Blue blueInstance) {
14+
blue = Blue(blueInstance);
15+
}
16+
17+
function borrowRate(Market calldata market) external view returns (uint) {
18+
Id id = Id.wrap(keccak256(abi.encode(market)));
19+
uint utilization = blue.totalBorrow(id).wDiv(blue.totalSupply(id));
20+
21+
// Divide by the number of seconds in a year.
22+
// This is a very simple model (to refine later) where x% utilization corresponds to x% APR.
23+
return utilization / 365 days;
24+
}
25+
}

0 commit comments

Comments
 (0)