Skip to content
This repository was archived by the owner on Aug 26, 2024. It is now read-only.

Commit 21619f8

Browse files
committed
Add PrudentiaInterestRateModel
1 parent 487e185 commit 21619f8

File tree

2 files changed

+395
-0
lines changed

2 files changed

+395
-0
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,126 @@
1+
// SPDX-License-Identifier: UNLICENSED
2+
pragma solidity >=0.8.0;
3+
4+
import { InterestRateModel } from "../../compound/InterestRateModel.sol";
5+
6+
import { IRateComputer } from "adrastia-periphery/rates/IRateComputer.sol";
7+
8+
import { Math } from "@openzeppelin/contracts/utils/math/Math.sol";
9+
10+
/**
11+
* @title Adrastia Prudentia Interest Rate Model
12+
* @author TRILEZ SOFTWARE INC.
13+
*/
14+
contract PrudentiaInterestRateModel is InterestRateModel {
15+
using Math for uint256;
16+
17+
/**
18+
* @notice The address of the underlying token for which the interest rate model calculates rates.
19+
*/
20+
address public immutable underlyingToken;
21+
22+
/**
23+
* @notice The address of the Adrastia Prudentia interest rate controller.
24+
*/
25+
IRateComputer public immutable rateController;
26+
27+
/**
28+
* @notice The approximate number of blocks per year that is assumed by the interest rate model.
29+
*/
30+
uint256 public immutable blocksPerYear;
31+
32+
/**
33+
* @notice Construct a new interest rate model that reads from an Adrastia Prudentia interest rate controller.
34+
*
35+
* @param blocksPerYear_ The approximate number of blocks per year that is assumed by the interest rate model.
36+
* @param underlyingToken_ The address of the underlying token for which the interest rate model calculates rates.
37+
* @param rateController_ The address of the Adrastia Prudentia interest rate controller.
38+
*/
39+
constructor(
40+
uint256 blocksPerYear_,
41+
address underlyingToken_,
42+
IRateComputer rateController_
43+
) {
44+
if (underlyingToken_ == address(0)) {
45+
revert("PrudentiaInterestRateModel: underlyingToken is the zero address");
46+
}
47+
if (address(rateController_) == address(0)) {
48+
revert("PrudentiaInterestRateModel: rateController is the zero address");
49+
}
50+
51+
blocksPerYear = blocksPerYear_;
52+
underlyingToken = underlyingToken_;
53+
rateController = rateController_;
54+
}
55+
56+
/**
57+
* @notice Calculates the utilization rate of the market: `borrows / (cash + borrows - reserves)`.
58+
*
59+
* @param cash The amount of cash in the market.
60+
* @param borrows The amount of borrows in the market.
61+
* @param reserves The amount of reserves in the market.
62+
*
63+
* @return The utilization rate as a mantissa between [0, 1e18].
64+
*/
65+
function utilizationRate(
66+
uint256 cash,
67+
uint256 borrows,
68+
uint256 reserves
69+
) public pure returns (uint256) {
70+
uint256 total = cash + borrows - reserves;
71+
if (total == 0) {
72+
// Utilization rate is zero when nothing is available (prevents division by zero)
73+
return 0;
74+
}
75+
76+
return (borrows * 1e18) / total;
77+
}
78+
79+
/**
80+
* @notice Calculates the current borrow rate per block by reading the current rate from the Adrastia Prudentia
81+
* interest rate controller.
82+
*
83+
* @param cash Not used.
84+
* @param borrows Not used.
85+
* @param reserves Not used.
86+
*
87+
* @return The borrow rate percentage per block as a mantissa (scaled by 1e18).
88+
*/
89+
function getBorrowRate(
90+
uint256 cash,
91+
uint256 borrows,
92+
uint256 reserves
93+
) public view override returns (uint256) {
94+
// Silence unused variable warnings
95+
cash;
96+
borrows;
97+
reserves;
98+
99+
uint256 annualRate = rateController.computeRate(underlyingToken);
100+
101+
return annualRate.ceilDiv(blocksPerYear); // Convert the annual rate to a per-block rate, rounding up
102+
}
103+
104+
/**
105+
* @notice Calculates the current supply rate per block.
106+
*
107+
* @param cash The amount of cash in the market.
108+
* @param borrows The amount of borrows in the market.
109+
* @param reserves The amount of reserves in the market.
110+
* @param reserveFactorMantissa The current reserve factor for the market.
111+
*
112+
* @return The supply rate percentage per block as a mantissa (scaled by 1e18).
113+
*/
114+
function getSupplyRate(
115+
uint256 cash,
116+
uint256 borrows,
117+
uint256 reserves,
118+
uint256 reserveFactorMantissa
119+
) public view virtual override returns (uint256) {
120+
uint256 oneMinusReserveFactor = 1e18 - reserveFactorMantissa;
121+
uint256 borrowRate = getBorrowRate(cash, borrows, reserves);
122+
uint256 rateToPool = (borrowRate * oneMinusReserveFactor) / 1e18;
123+
124+
return (utilizationRate(cash, borrows, reserves) * rateToPool) / 1e18;
125+
}
126+
}
+269
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,269 @@
1+
// SPDX-License-Identifier: UNLICENSED
2+
pragma solidity >=0.8.0;
3+
4+
import { Math } from "@openzeppelin/contracts/utils/math/Math.sol";
5+
6+
import { IRateComputer } from "adrastia-periphery/rates/IRateComputer.sol";
7+
8+
import { BaseTest } from "../config/BaseTest.t.sol";
9+
10+
import { PrudentiaInterestRateModel } from "../../ionic/irms/PrudentiaInterestRateModel.sol";
11+
12+
contract MockRateComputer is IRateComputer {
13+
mapping(address => uint64) public rates;
14+
15+
function computeRate(address token) external view override returns (uint64) {
16+
return rates[token];
17+
}
18+
19+
function setRate(address token, uint64 rate) public {
20+
rates[token] = rate;
21+
}
22+
}
23+
24+
contract PrudentiaIrmTest is BaseTest {
25+
using Math for uint64;
26+
27+
MockRateComputer rateComputer;
28+
address token;
29+
PrudentiaInterestRateModel irm;
30+
uint256 blocksPerYear;
31+
32+
function setUp() public {
33+
rateComputer = new MockRateComputer();
34+
token = address(0x1);
35+
blocksPerYear = 10512000;
36+
irm = new PrudentiaInterestRateModel(blocksPerYear, token, rateComputer);
37+
}
38+
39+
function test_utilizationRate_zeroTotal() public {
40+
uint256 cash = 0;
41+
uint256 borrows = 0;
42+
uint256 reserves = 0;
43+
44+
assertEq(irm.utilizationRate(cash, borrows, reserves), 0);
45+
}
46+
47+
function test_utilizationRate_zero() public {
48+
uint256 cash = 100;
49+
uint256 borrows = 0;
50+
uint256 reserves = 0;
51+
52+
assertEq(irm.utilizationRate(cash, borrows, reserves), 0);
53+
}
54+
55+
function test_utilizationRate_50() public {
56+
uint256 cash = 100;
57+
uint256 borrows = 100;
58+
uint256 reserves = 0;
59+
60+
assertEq(irm.utilizationRate(cash, borrows, reserves), 5e17);
61+
}
62+
63+
function test_utilizationRate_100() public {
64+
uint256 cash = 0;
65+
uint256 borrows = 100;
66+
uint256 reserves = 0;
67+
68+
assertEq(irm.utilizationRate(cash, borrows, reserves), 1e18);
69+
}
70+
71+
function test_getBorrowRate_100_a() public {
72+
uint64 rate = 1e18;
73+
rateComputer.setRate(token, rate);
74+
75+
// These should have no effect
76+
uint256 cash = 0;
77+
uint256 borrows = 100;
78+
uint256 reserves = 0;
79+
80+
assertEq(irm.getBorrowRate(cash, borrows, reserves), rate.ceilDiv(blocksPerYear));
81+
}
82+
83+
function test_getBorrowRate_100_b() public {
84+
uint64 rate = 1e18;
85+
rateComputer.setRate(token, rate);
86+
87+
// These should have no effect
88+
uint256 cash = 100;
89+
uint256 borrows = 100;
90+
uint256 reserves = 0;
91+
92+
assertEq(irm.getBorrowRate(cash, borrows, reserves), rate.ceilDiv(blocksPerYear));
93+
}
94+
95+
function test_getBorrowRate_50() public {
96+
uint64 rate = 5e17;
97+
rateComputer.setRate(token, rate);
98+
99+
// These should have no effect
100+
uint256 cash = 0;
101+
uint256 borrows = 0;
102+
uint256 reserves = 0;
103+
104+
assertEq(irm.getBorrowRate(cash, borrows, reserves), rate.ceilDiv(blocksPerYear));
105+
}
106+
107+
function test_getBorrowRate_1() public {
108+
uint64 rate = 1e16;
109+
rateComputer.setRate(token, rate);
110+
111+
// These should have no effect
112+
uint256 cash = 0;
113+
uint256 borrows = 0;
114+
uint256 reserves = 0;
115+
116+
assertEq(irm.getBorrowRate(cash, borrows, reserves), rate.ceilDiv(blocksPerYear));
117+
}
118+
119+
function test_getBorrowRate_0() public {
120+
uint64 rate = 0;
121+
rateComputer.setRate(token, rate);
122+
123+
// These should have no effect
124+
uint256 cash = 0;
125+
uint256 borrows = 0;
126+
uint256 reserves = 0;
127+
128+
assertEq(irm.getBorrowRate(cash, borrows, reserves), rate.ceilDiv(blocksPerYear));
129+
}
130+
131+
function test_getBorrowRate_1mantissa() public {
132+
uint64 rate = 1;
133+
rateComputer.setRate(token, rate);
134+
135+
// These should have no effect
136+
uint256 cash = 0;
137+
uint256 borrows = 0;
138+
uint256 reserves = 0;
139+
140+
assertEq(irm.getBorrowRate(cash, borrows, reserves), 1); // Rounds up to 1. We don't want to return 0.
141+
}
142+
143+
function test_getSupplyRate_100_100util() public {
144+
uint64 rate = 1e18;
145+
rateComputer.setRate(token, rate);
146+
147+
uint256 cash = 0;
148+
uint256 borrows = 100;
149+
uint256 reserves = 0;
150+
uint256 reserveFactorMantissa = 0;
151+
152+
assertEq(irm.getSupplyRate(cash, borrows, reserves, reserveFactorMantissa), rate.ceilDiv(blocksPerYear));
153+
}
154+
155+
function test_getSupplyRate_100_50util() public {
156+
uint64 rate = 1e18;
157+
rateComputer.setRate(token, rate);
158+
159+
uint256 cash = 100;
160+
uint256 borrows = 100;
161+
uint256 reserves = 0;
162+
uint256 reserveFactorMantissa = 0;
163+
164+
assertEq(irm.getSupplyRate(cash, borrows, reserves, reserveFactorMantissa), rate.ceilDiv(blocksPerYear) / 2);
165+
}
166+
167+
function test_getSupplyRate_100_1util() public {
168+
uint64 rate = 1e18;
169+
rateComputer.setRate(token, rate);
170+
171+
uint256 cash = 99;
172+
uint256 borrows = 1;
173+
uint256 reserves = 0;
174+
uint256 reserveFactorMantissa = 0;
175+
176+
assertEq(irm.getSupplyRate(cash, borrows, reserves, reserveFactorMantissa), rate.ceilDiv(blocksPerYear) / 100);
177+
}
178+
179+
function test_getSupplyRate_100_0util() public {
180+
uint64 rate = 1e18;
181+
rateComputer.setRate(token, rate);
182+
183+
uint256 cash = 100;
184+
uint256 borrows = 0;
185+
uint256 reserves = 0;
186+
uint256 reserveFactorMantissa = 0;
187+
188+
assertEq(irm.getSupplyRate(cash, borrows, reserves, reserveFactorMantissa), 0);
189+
}
190+
191+
function test_getSupplyRate_0_0util() public {
192+
uint64 rate = 0;
193+
rateComputer.setRate(token, rate);
194+
195+
uint256 cash = 0;
196+
uint256 borrows = 0;
197+
uint256 reserves = 0;
198+
uint256 reserveFactorMantissa = 0;
199+
200+
assertEq(irm.getSupplyRate(cash, borrows, reserves, reserveFactorMantissa), 0);
201+
}
202+
203+
function test_getSupplyRate_0_100util() public {
204+
uint64 rate = 0;
205+
rateComputer.setRate(token, rate);
206+
207+
uint256 cash = 0;
208+
uint256 borrows = 100;
209+
uint256 reserves = 0;
210+
uint256 reserveFactorMantissa = 0;
211+
212+
assertEq(irm.getSupplyRate(cash, borrows, reserves, reserveFactorMantissa), 0);
213+
}
214+
215+
function test_getSupplyRate_0_50util() public {
216+
uint64 rate = 0;
217+
rateComputer.setRate(token, rate);
218+
219+
uint256 cash = 50;
220+
uint256 borrows = 50;
221+
uint256 reserves = 0;
222+
uint256 reserveFactorMantissa = 0;
223+
224+
assertEq(irm.getSupplyRate(cash, borrows, reserves, reserveFactorMantissa), 0);
225+
}
226+
227+
function test_getSupplyRate_0_1util() public {
228+
uint64 rate = 0;
229+
rateComputer.setRate(token, rate);
230+
231+
uint256 cash = 99;
232+
uint256 borrows = 1;
233+
uint256 reserves = 0;
234+
uint256 reserveFactorMantissa = 0;
235+
236+
assertEq(irm.getSupplyRate(cash, borrows, reserves, reserveFactorMantissa), 0);
237+
}
238+
239+
function test_getSupplyRate_100_50util_10rf() public {
240+
uint64 rate = 1e18;
241+
rateComputer.setRate(token, rate);
242+
243+
uint256 cash = 100;
244+
uint256 borrows = 100;
245+
uint256 reserves = 0;
246+
uint256 reserveFactorMantissa = 1e17;
247+
248+
assertEq(
249+
irm.getSupplyRate(cash, borrows, reserves, reserveFactorMantissa),
250+
(uint256(rate.ceilDiv(blocksPerYear) / 2) * (1e18 - reserveFactorMantissa)) / 1e18
251+
);
252+
}
253+
254+
function test_getSupplyRate_100_50util_10rf_10reserves() public {
255+
uint64 rate = 1e18;
256+
rateComputer.setRate(token, rate);
257+
258+
uint256 cash = 100;
259+
uint256 borrows = 100;
260+
uint256 reserves = 10;
261+
cash += reserves;
262+
uint256 reserveFactorMantissa = 1e17;
263+
264+
assertEq(
265+
irm.getSupplyRate(cash, borrows, reserves, reserveFactorMantissa),
266+
(uint256(rate.ceilDiv(blocksPerYear) / 2) * (1e18 - reserveFactorMantissa)) / 1e18
267+
);
268+
}
269+
}

0 commit comments

Comments
 (0)