Skip to content

Commit 9ea64dd

Browse files
committed
[Release Candidate] Improve DSR Integration and Jump Rate Model
This patch makes a few important changes. We first fix an optimization of CDaiDelegate that simplifies a variety of logic in certain calculations. Secondly, we fix the jump rate model to have a fixed slope past the kink, as opposed to a certain multiplier.
1 parent 8e40ba1 commit 9ea64dd

File tree

8 files changed

+187
-85
lines changed

8 files changed

+187
-85
lines changed

contracts/CDaiDelegate.sol

Lines changed: 2 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -157,18 +157,13 @@ contract CDaiDelegate is CErc20Delegate {
157157
function doTransferOut(address payable to, uint amount) internal {
158158
DaiJoinLike daiJoin = DaiJoinLike(daiJoinAddress);
159159
PotLike pot = PotLike(potAddress);
160-
VatLike vat = VatLike(vatAddress);
161160

162161
// Calculate the percentage decrease from the pot, and move that much out
163162
// Note: Use a slightly larger pie size to ensure that we get at least amount in the vat
164-
uint pie = mul(add(amount, 1), RAY) / pot.chi();
163+
uint pie = add(mul(amount, RAY) / pot.chi(), 1);
165164
pot.exit(pie);
166165

167-
// Checks the actual balance of DAI in the vat after the pot exit
168-
uint bal = vat.dai(address(this));
169-
170-
// Remove our whole balance if rounding would lead us to remove more than we have
171-
daiJoin.exit(to, bal >= mul(amount, RAY) ? amount : bal / RAY);
166+
daiJoin.exit(to, amount);
172167
}
173168

174169
/*** Maker Internals ***/

contracts/DAIInterestRateModel.sol

Lines changed: 28 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -11,19 +11,29 @@ import "./SafeMath.sol";
1111
contract DAIInterestRateModel is JumpRateModel {
1212
using SafeMath for uint;
1313

14+
/**
15+
* @notice The additional margin per block separating the base borrow rate from the roof (0.05% / block)
16+
*/
17+
uint public constant gapPerBlock = 0.05e16 / blocksPerYear;
18+
19+
/**
20+
* @notice The assumed (1 - reserve factor) used to calculate the minimum borrow rate (reserve factor = 0.05)
21+
*/
22+
uint public constant assumedOneMinusReserveFactorMantissa = 0.95e18;
23+
1424
PotLike pot;
1525
JugLike jug;
1626

1727
/**
1828
* @notice Construct an interest rate model
19-
* @param _pot The approximate target base APR, as a mantissa (scaled by 1e18)
20-
* @param _jug The rate of increase in interest rate wrt utilization (scaled by 1e18)
21-
* @param _kink The utilization point at which an additional multiplier is applied
22-
* @param _jump The additional multiplier to be applied to multiplierPerBlock after hitting a specified utilization point
29+
* @param jumpMultiplierPerYear The multiplierPerBlock after hitting a specified utilization point
30+
* @param kink_ The utilization point at which the jump multiplier is applied
31+
* @param pot_ The address of the Dai pot (where DSR is earned)
32+
* @param jug_ The address of the Dai jug (where SF is kept)
2333
*/
24-
constructor(address _pot, address _jug, uint _kink, uint _jump) JumpRateModel(0, 0, _kink, _jump) public {
25-
pot = PotLike(_pot);
26-
jug = JugLike(_jug);
34+
constructor(uint jumpMultiplierPerYear, uint kink_, address pot_, address jug_) JumpRateModel(0, 0, jumpMultiplierPerYear, kink_) public {
35+
pot = PotLike(pot_);
36+
jug = JugLike(jug_);
2737
poke();
2838
}
2939

@@ -58,18 +68,24 @@ contract DAIInterestRateModel is JumpRateModel {
5868
.mul(15); // 15 seconds per block
5969
}
6070

61-
6271
/**
6372
* @notice Resets the baseRate and multiplier per block based on the stability fee and Dai savings rate
6473
*/
6574
function poke() public {
6675
(uint duty, ) = jug.ilks("ETH-A");
67-
uint stabilityFee = duty.add(jug.base()).sub(1e27).mul(1e18).div(1e27).mul(15);
76+
uint stabilityFeePerBlock = duty.add(jug.base()).sub(1e27).mul(1e18).div(1e27).mul(15);
77+
78+
// We ensure the minimum borrow rate >= DSR / (1 - reserve factor)
79+
baseRatePerBlock = dsrPerBlock().mul(1e18).div(assumedOneMinusReserveFactorMantissa);
6880

69-
baseRatePerBlock = dsrPerBlock().mul(1e18).div(0.9e18); // ensure borrow rate is higher than savings rate
70-
multiplierPerBlock = stabilityFee.sub(baseRatePerBlock).mul(1e18).div(kink);
81+
// The roof borrow rate is max(base rate, stability fee) + gap, from which we derive the slope
82+
if (baseRatePerBlock < stabilityFeePerBlock) {
83+
multiplierPerBlock = stabilityFeePerBlock.sub(baseRatePerBlock).add(gapPerBlock).mul(1e18).div(kink);
84+
} else {
85+
multiplierPerBlock = gapPerBlock.mul(1e18).div(kink);
86+
}
7187

72-
emit NewInterestParams(baseRatePerBlock, multiplierPerBlock, kink, jump);
88+
emit NewInterestParams(baseRatePerBlock, multiplierPerBlock, jumpMultiplierPerBlock, kink);
7389
}
7490
}
7591

contracts/JumpRateModel.sol

Lines changed: 13 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ import "./SafeMath.sol";
1010
contract JumpRateModel is InterestRateModel {
1111
using SafeMath for uint;
1212

13-
event NewInterestParams(uint baseRatePerBlock, uint multiplierPerBlock, uint kink, uint jump);
13+
event NewInterestParams(uint baseRatePerBlock, uint multiplierPerBlock, uint jumpMultiplierPerBlock, uint kink);
1414

1515
/**
1616
* @notice Indicator that this is an InterestRateModel contract (for inspection)
@@ -33,29 +33,29 @@ contract JumpRateModel is InterestRateModel {
3333
uint public baseRatePerBlock;
3434

3535
/**
36-
* @notice the utilization point at which an additional multiplier is applied
37-
*/
38-
uint public kink;
36+
* @notice The multiplierPerBlock after hitting a specified utilization point
37+
*/
38+
uint public jumpMultiplierPerBlock;
3939

4040
/**
41-
* @notice the additional multiplier to be applied to multiplierPerBlock after hitting a specified utilization point
42-
*/
43-
uint public jump;
41+
* @notice The utilization point at which the jump multiplier is applied
42+
*/
43+
uint public kink;
4444

4545
/**
4646
* @notice Construct an interest rate model
4747
* @param baseRatePerYear The approximate target base APR, as a mantissa (scaled by 1e18)
4848
* @param multiplierPerYear The rate of increase in interest rate wrt utilization (scaled by 1e18)
49-
* @param kink_ The utilization point at which an additional multiplier is applied
50-
* @param jump_ The additional multiplier to be applied to multiplierPerBlock after hitting a specified utilization point
49+
* @param jumpMultiplierPerYear The multiplierPerBlock after hitting a specified utilization point
50+
* @param kink_ The utilization point at which the jump multiplier is applied
5151
*/
52-
constructor(uint baseRatePerYear, uint multiplierPerYear, uint kink_, uint jump_) public {
52+
constructor(uint baseRatePerYear, uint multiplierPerYear, uint jumpMultiplierPerYear, uint kink_) public {
5353
baseRatePerBlock = baseRatePerYear.div(blocksPerYear);
5454
multiplierPerBlock = multiplierPerYear.div(blocksPerYear);
55+
jumpMultiplierPerBlock = jumpMultiplierPerYear.div(blocksPerYear);
5556
kink = kink_;
56-
jump = jump_;
5757

58-
emit NewInterestParams(baseRatePerBlock, multiplierPerBlock, kink, jump);
58+
emit NewInterestParams(baseRatePerBlock, multiplierPerBlock, jumpMultiplierPerBlock, kink);
5959
}
6060

6161
/**
@@ -89,8 +89,7 @@ contract JumpRateModel is InterestRateModel {
8989
} else {
9090
uint normalRate = kink.mul(multiplierPerBlock).div(1e18).add(baseRatePerBlock);
9191
uint excessUtil = util.sub(kink);
92-
uint jumpMultiplier = multiplierPerBlock.mul(jump);
93-
return excessUtil.mul(jumpMultiplier).div(1e18).add(normalRate);
92+
return excessUtil.mul(jumpMultiplierPerBlock).div(1e18).add(normalRate);
9493
}
9594
}
9695

spec/scenario/MCDai.scen

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -101,7 +101,6 @@ Test "Basic mint and redeem cDAI2 (upgrade to swept DSR)"
101101
UpgradeToDSR
102102
CheckBasicMintRedeemWithDSR
103103

104-
105104
Test "Basic borrow and repay cDAI2 (upgrade to swept DSR)"
106105
DeployCDAI
107106
CheckBasicMintRedeem

test/Models/DAIInterestRateModelTest.js

Lines changed: 31 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -5,33 +5,41 @@ const BigNum = require('bignumber.js')
55
const { call, etherUnsigned, getContract, getContractDefaults, getTestContract } = require('../Utils/MochaTruffle');
66
const { getBorrowRate, getSupplyRate } = require('../Utils/Compound');
77

8+
const blocksPerYear = 2102400;
89
const secondsPerYear = 60 * 60 * 24 * 365;
910

1011
function utilizationRate(cash, borrows, reserves) {
1112
return borrows ? borrows / (cash + borrows - reserves) : 0;
1213
}
1314

14-
function baseRoofRateFn(dsr, duty, mkrBase, kink, jump, cash, borrows, reserves) {
15-
const stabilityFee = (duty + mkrBase - 1) * 15;
15+
function baseRoofRateFn(dsr, duty, mkrBase, jump, kink, cash, borrows, reserves) {
16+
const assumedOneMinusReserveFactor = 0.95;
17+
const stabilityFeePerBlock = (duty + mkrBase - 1) * 15;
1618
const dsrPerBlock = (dsr - 1) * 15;
17-
const base = dsrPerBlock / 0.9;
18-
const slope = (stabilityFee - base) / kink;
19+
const gapPerBlock = 0.0005 / blocksPerYear;
20+
const jumpPerBlock = jump / blocksPerYear;
21+
22+
let baseRatePerBlock = dsrPerBlock / assumedOneMinusReserveFactor, multiplierPerBlock;
23+
if (baseRatePerBlock < stabilityFeePerBlock) {
24+
multiplierPerBlock = (stabilityFeePerBlock - baseRatePerBlock + gapPerBlock) / kink;
25+
} else {
26+
multiplierPerBlock = gapPerBlock / kink;
27+
}
1928

2029
const ur = utilizationRate(cash, borrows, reserves);
2130

2231
if (ur <= kink) {
23-
return ur * slope + base;
32+
return ur * multiplierPerBlock + baseRatePerBlock;
2433
} else {
2534
const excessUtil = ur - kink;
26-
const jumpMultiplier = jump * slope;
27-
return (excessUtil * jumpMultiplier) + (kink * slope) + base;
35+
return (excessUtil * jumpPerBlock) + (kink * multiplierPerBlock) + baseRatePerBlock;
2836
}
2937
}
3038

31-
function daiSupplyRate(dsr, duty, mkrBase, kink, jump, cash, borrows, reserves, reserveFactor = 0.1) {
39+
function daiSupplyRate(dsr, duty, mkrBase, jump, kink, cash, borrows, reserves, reserveFactor = 0.1) {
3240
const dsrPerBlock = (dsr - 1) * 15;
3341
const ur = utilizationRate(cash, borrows, reserves);
34-
const borrowRate = baseRoofRateFn(dsr, duty, mkrBase, kink, jump, cash, borrows, reserves);
42+
const borrowRate = baseRoofRateFn(dsr, duty, mkrBase, jump, kink, cash, borrows, reserves);
3543
const underlying = cash + borrows - reserves;
3644
const lendingSupplyRate = borrowRate * (1 - reserveFactor) * ur;
3745

@@ -66,10 +74,10 @@ contract('DAIInterestRateModel', async function (_accounts) {
6674

6775
let model = await contract.deploy({
6876
arguments: [
69-
"0xea190dbdc7adf265260ec4da6e9675fd4f5a78bb",
70-
"0xcbb7718c9f39d05aeede1c472ca8bf804b2f1ead",
77+
etherUnsigned(0.8e18),
7178
etherUnsigned(0.9e18),
72-
etherUnsigned(5)
79+
"0xea190dbdc7adf265260ec4da6e9675fd4f5a78bb",
80+
"0xcbb7718c9f39d05aeede1c472ca8bf804b2f1ead"
7381
]
7482
})
7583
.send({ from: root });
@@ -129,8 +137,8 @@ contract('DAIInterestRateModel', async function (_accounts) {
129137
[0e27, 0.1e27, 0.005e27, 3e18, 500],
130138

131139
].map(vs => vs.map(Number))
132-
.forEach(([dsr, duty, base, cash, borrows, reserves = 0, kink = 0.9e18, jump = 5]) => {
133-
it(`calculates correct borrow value for dsr=${(dsr / 1e25)}%, duty=${(duty / 1e25)}%, base=${(base / 1e25)}%, cash=${cash}, borrows=${borrows}, reserves=${reserves}`, async () => {
140+
.forEach(([dsr, duty, base, cash, borrows, reserves = 0, jump = 0.8e18, kink = 0.9e18]) => {
141+
it(`calculates correct borrow value for dsr=${(dsr / 1e25)}%, duty=${(duty / 1e25)}%, base=${(base / 1e25)}%, jump=${jump / 1e18}, cash=${cash}, borrows=${borrows}, reserves=${reserves}`, async () => {
134142
const [root] = _accounts;
135143

136144
const onePlusPerSecondDsr = 1e27 + (dsr / secondsPerYear);
@@ -156,14 +164,14 @@ contract('DAIInterestRateModel', async function (_accounts) {
156164

157165
const daiIRM = await DAIInterestRateModel.deploy({
158166
arguments: [
159-
pot.options.address,
160-
jug.options.address,
167+
etherUnsigned(jump),
161168
etherUnsigned(kink),
162-
etherUnsigned(jump)
169+
pot.options.address,
170+
jug.options.address
163171
]
164172
}).send({ from: root });
165173

166-
const expected = baseRoofRateFn(onePlusPerSecondDsr / 1e27, onePlusPerSecondDuty / 1e27, perSecondBase / 1e27, kink / 1e18, jump, cash, borrows, reserves);
174+
const expected = baseRoofRateFn(onePlusPerSecondDsr / 1e27, onePlusPerSecondDuty / 1e27, perSecondBase / 1e27, jump / 1e18, kink / 1e18, cash, borrows, reserves);
167175
assert.like(
168176
await getBorrowRate(daiIRM, cash, borrows, reserves),
169177
(x) => assert.approximately(Number(x) / 1e18, expected, 1e-8)
@@ -221,7 +229,7 @@ contract('DAIInterestRateModel', async function (_accounts) {
221229
[0e27, 0.1e27, 0.005e27, 3e18, 500],
222230

223231
].map(vs => vs.map(Number))
224-
.forEach(([dsr, duty, base, cash, borrows, reserves = 0, kink = 0.9e18, jump = 5, reserveFactor = 0.1e18]) => {
232+
.forEach(([dsr, duty, base, cash, borrows, reserves = 0, jump = 0.8e18, kink = 0.9e18, reserveFactor = 0.1e18]) => {
225233
it(`calculates correct supply value for dsr=${(dsr / 1e25)}%, duty=${(duty / 1e25)}%, base=${(base / 1e25)}%, cash=${cash}, borrows=${borrows}, reserves=${reserves}`, async () => {
226234
const [root] = _accounts;
227235

@@ -248,14 +256,14 @@ contract('DAIInterestRateModel', async function (_accounts) {
248256

249257
const daiIRM = await DAIInterestRateModel.deploy({
250258
arguments: [
251-
pot.options.address,
252-
jug.options.address,
259+
etherUnsigned(jump),
253260
etherUnsigned(kink),
254-
etherUnsigned(jump)
261+
pot.options.address,
262+
jug.options.address
255263
]
256264
}).send({ from: root });
257265

258-
const expected = daiSupplyRate(onePlusPerSecondDsr / 1e27, onePlusPerSecondDuty / 1e27, perSecondBase / 1e27, kink / 1e18, jump, cash, borrows, reserves, reserveFactor / 1e18);
266+
const expected = daiSupplyRate(onePlusPerSecondDsr / 1e27, onePlusPerSecondDuty / 1e27, perSecondBase / 1e27, jump / 1e18, kink / 1e18, cash, borrows, reserves, reserveFactor / 1e18);
259267
assert.like(
260268
await getSupplyRate(daiIRM, cash, borrows, reserves, reserveFactor),
261269
(x) => assert.approximately(Number(x) / 1e18, expected, 1e-8)

0 commit comments

Comments
 (0)