Skip to content

Commit

Permalink
Update DAIInterestRateModel to v4 (compound-finance#230)
Browse files Browse the repository at this point in the history
  • Loading branch information
fmorisan committed Dec 22, 2022
1 parent a3214f6 commit 561d469
Show file tree
Hide file tree
Showing 3 changed files with 141 additions and 8 deletions.
132 changes: 132 additions & 0 deletions contracts/DAIInterestRateModelV4.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
pragma solidity ^0.8.10;

import "./JumpRateModelV2.sol";

/**
* @title Compound's DAIInterestRateModel Contract (version 4)
* @author Compound, Dharma (modified by Maker Growth)
* @notice The change from v3 to v4 of this contract was just the `SECONDS_PER_BLOCK` constant,
* as noted in https://github.com/compound-finance/compound-protocol/issues/230
*/
contract DAIInterestRateModelV4 is JumpRateModelV2 {
uint256 private constant BASE = 1e18;
uint256 private constant RAY_BASE = 1e27;
uint256 private constant RAY_TO_BASE_SCALE = 1e9;
uint256 private constant SECONDS_PER_BLOCK = 12;

/**
* @notice The additional margin per block separating the base borrow rate from the roof.
*/
uint public gapPerBlock;

/**
* @notice The assumed (1 - reserve factor) used to calculate the minimum borrow rate (reserve factor = 0.05)
*/
uint public constant assumedOneMinusReserveFactorMantissa = 0.95e18;

PotLike pot;
JugLike jug;

/**
* @notice Construct an interest rate model
* @param jumpMultiplierPerYear The multiplierPerBlock after hitting a specified utilization point
* @param kink_ The utilization point at which the jump multiplier is applied
* @param pot_ The address of the Dai pot (where DSR is earned)
* @param jug_ The address of the Dai jug (where SF is kept)
* @param owner_ The address of the owner, i.e. the Timelock contract (which has the ability to update parameters directly)
*/
constructor(uint jumpMultiplierPerYear, uint kink_, address pot_, address jug_, address owner_) JumpRateModelV2(0, 0, jumpMultiplierPerYear, kink_, owner_) public {
gapPerBlock = 4e16 / blocksPerYear;
pot = PotLike(pot_);
jug = JugLike(jug_);
poke();
}

/**
* @notice External function to update the parameters of the interest rate model
* @param baseRatePerYear The approximate target base APR, as a mantissa (scaled by BASE). For DAI, this is calculated from DSR and SF. Input not used.
* @param gapPerYear The Additional margin per year separating the base borrow rate from the roof. (scaled by BASE)
* @param jumpMultiplierPerYear The jumpMultiplierPerYear after hitting a specified utilization point
* @param kink_ The utilization point at which the jump multiplier is applied
*/
function updateJumpRateModel(uint baseRatePerYear, uint gapPerYear, uint jumpMultiplierPerYear, uint kink_) override external {
require(msg.sender == owner, "only the owner may call this function.");
gapPerBlock = gapPerYear / blocksPerYear;
updateJumpRateModelInternal(0, 0, jumpMultiplierPerYear, kink_);
poke();
}

/**
* @notice Calculates the current supply interest rate per block including the Dai savings rate
* @param cash The total amount of cash the market has
* @param borrows The total amount of borrows the market has outstanding
* @param reserves The total amnount of reserves the market has
* @param reserveFactorMantissa The current reserve factor the market has
* @return The supply rate per block (as a percentage, and scaled by BASE)
*/
function getSupplyRate(uint cash, uint borrows, uint reserves, uint reserveFactorMantissa) override public view returns (uint) {
uint protocolRate = super.getSupplyRate(cash, borrows, reserves, reserveFactorMantissa);

uint underlying = cash + borrows - reserves;
if (underlying == 0) {
return protocolRate;
} else {
uint cashRate = cash * dsrPerBlock() / underlying;
return cashRate + protocolRate;
}
}

/**
* @notice Calculates the Dai savings rate per block
* @return The Dai savings rate per block (as a percentage, and scaled by BASE)
*/
function dsrPerBlock() public view returns (uint) {
return (pot.dsr() - RAY_BASE) // scaled RAY_BASE aka RAY, and includes an extra "ONE" before subtraction
/ RAY_TO_BASE_SCALE // descale to BASE
* SECONDS_PER_BLOCK; // seconds per block
}

/**
* @notice Resets the baseRate and multiplier per block based on the stability fee and Dai savings rate
*/
function poke() public {
(uint duty, ) = jug.ilks("ETH-A");
uint stabilityFeePerBlock = (duty + jug.base() - RAY_BASE) / RAY_TO_BASE_SCALE * SECONDS_PER_BLOCK;

// We ensure the minimum borrow rate >= DSR / (1 - reserve factor)
baseRatePerBlock = dsrPerBlock() * BASE / assumedOneMinusReserveFactorMantissa;

// The roof borrow rate is max(base rate, stability fee) + gap, from which we derive the slope
if (baseRatePerBlock < stabilityFeePerBlock) {
multiplierPerBlock = (stabilityFeePerBlock - baseRatePerBlock + gapPerBlock) * BASE / kink;
} else {
multiplierPerBlock = gapPerBlock * BASE / kink;
}

emit NewInterestParams(baseRatePerBlock, multiplierPerBlock, jumpMultiplierPerBlock, kink);
}
}


/*** Maker Interfaces ***/

interface PotLike {
function chi() external view returns (uint);
function dsr() external view returns (uint);
function rho() external view returns (uint);
function pie(address) external view returns (uint);
function drip() external returns (uint);
function join(uint) external;
function exit(uint) external;
}

contract JugLike {
// --- Data ---
struct Ilk {
uint256 duty;
uint256 rho;
}

mapping (bytes32 => Ilk) public ilks;
uint256 public base;
}
2 changes: 1 addition & 1 deletion scenario/src/Builder/InterestRateModelBuilder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ import {getContract, getTestContract} from '../Contract';
const FixedInterestRateModel = getTestContract('InterestRateModelHarness');
const WhitePaperInterestRateModel = getContract('WhitePaperInterestRateModel');
const JumpRateModel = getContract('JumpRateModel');
const DAIInterestRateModel = getContract('DAIInterestRateModelV3');
const DAIInterestRateModel = getContract('DAIInterestRateModelV4');
const JumpRateModelV2 = getContract('JumpRateModelV2');
const LegacyJumpRateModelV2 = getContract('LegacyJumpRateModelV2');

Expand Down
15 changes: 8 additions & 7 deletions tests/Models/DAIInterestRateModelTest.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ const {
getSupplyRate
} = require('../Utils/Compound');

const assumedSecondsPerBlock = 12;
const blocksPerYear = 2102400;
const secondsPerYear = 60 * 60 * 24 * 365;

Expand All @@ -16,8 +17,8 @@ function utilizationRate(cash, borrows, reserves) {

function baseRoofRateFn(dsr, duty, mkrBase, jump, kink, cash, borrows, reserves) {
const assumedOneMinusReserveFactor = 0.95;
const stabilityFeePerBlock = (duty + mkrBase - 1) * 15;
const dsrPerBlock = (dsr - 1) * 15;
const stabilityFeePerBlock = (duty + mkrBase - 1) * assumedSecondsPerBlock;
const dsrPerBlock = (dsr - 1) * assumedSecondsPerBlock;
const gapPerBlock = 0.04 / blocksPerYear;
const jumpPerBlock = jump / blocksPerYear;

Expand All @@ -39,7 +40,7 @@ function baseRoofRateFn(dsr, duty, mkrBase, jump, kink, cash, borrows, reserves)
}

function daiSupplyRate(dsr, duty, mkrBase, jump, kink, cash, borrows, reserves, reserveFactor = 0.1) {
const dsrPerBlock = (dsr - 1) * 15;
const dsrPerBlock = (dsr - 1) * assumedSecondsPerBlock;
const ur = utilizationRate(cash, borrows, reserves);
const borrowRate = baseRoofRateFn(dsr, duty, mkrBase, jump, kink, cash, borrows, reserves);
const underlying = cash + borrows - reserves;
Expand Down Expand Up @@ -68,15 +69,15 @@ async function getKovanFork() {
return {kovan, root, accounts};
}

describe('DAIInterestRateModelV3', () => {
describe('DAIInterestRateModelV4', () => {
describe("constructor", () => {
it("sets jug and ilk address and pokes", async () => {
// NB: Going back a certain distance requires an archive node, currently that add-on is $250/mo
// https://community.infura.io/t/error-returned-error-project-id-does-not-have-access-to-archive-state/847
const {kovan, root, accounts} = await getKovanFork();

// TODO: Get contract craz
let {contract: model} = await saddle.deployFull('DAIInterestRateModelV3', [
let {contract: model} = await saddle.deployFull('DAIInterestRateModelV4', [
etherUnsigned(0.8e18),
etherUnsigned(0.9e18),
"0xea190dbdc7adf265260ec4da6e9675fd4f5a78bb",
Expand Down Expand Up @@ -152,7 +153,7 @@ describe('DAIInterestRateModelV3', () => {
etherUnsigned(perSecondBase)
]);

const daiIRM = await deploy('DAIInterestRateModelV3', [
const daiIRM = await deploy('DAIInterestRateModelV4', [
etherUnsigned(jump),
etherUnsigned(kink),
pot._address,
Expand Down Expand Up @@ -229,7 +230,7 @@ describe('DAIInterestRateModelV3', () => {
etherUnsigned(perSecondBase)
]);

const daiIRM = await deploy('DAIInterestRateModelV3', [
const daiIRM = await deploy('DAIInterestRateModelV4', [
etherUnsigned(jump),
etherUnsigned(kink),
pot._address,
Expand Down

0 comments on commit 561d469

Please sign in to comment.