Skip to content

Commit

Permalink
[Release Candidate] Connect cDAI with the Dai Savings Rate
Browse files Browse the repository at this point in the history
This patch connects cDAI to the Dai Saving Rate, giving users the cominbation of DSR interest and borrowing interest. Any unborrowed assets "cash" are swept into the DSR and that interest is given to suppliers. That cash can still be borrowed and any interest from borrowers will still go back to suppliers. In this way, Compound suppliers will see a supply rate that is always higher than the Dai Savings Rate.
  • Loading branch information
hayesgm committed Dec 3, 2019
1 parent 0564802 commit 8e40ba1
Showing 18 changed files with 2,549 additions and 95 deletions.
223 changes: 223 additions & 0 deletions contracts/CDaiDelegate.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,223 @@
pragma solidity ^0.5.12;

import "./CErc20Delegate.sol";

/**
* @title Compound's CDai Contract
* @notice CToken which wraps Multi-Collateral DAI
* @author Compound
*/
contract CDaiDelegate is CErc20Delegate {
/**
* @notice DAI adapter address
*/
address public daiJoinAddress;

/**
* @notice DAI Savings Rate (DSR) pot address
*/
address public potAddress;

/**
* @notice DAI vat address
*/
address public vatAddress;

/**
* @notice Delegate interface to become the implementation
* @param data The encoded arguments for becoming
*/
function _becomeImplementation(bytes memory data) public {
require(msg.sender == admin, "only the admin may initialize the implementation");

(address daiJoinAddress_, address potAddress_) = abi.decode(data, (address, address));
return _becomeImplementation(daiJoinAddress_, potAddress_);
}

/**
* @notice Explicit interface to become the implementation
* @param daiJoinAddress_ DAI adapter address
* @param potAddress_ DAI Savings Rate (DSR) pot address
*/
function _becomeImplementation(address daiJoinAddress_, address potAddress_) internal {
// Get dai and vat and sanity check the underlying
DaiJoinLike daiJoin = DaiJoinLike(daiJoinAddress_);
PotLike pot = PotLike(potAddress_);
GemLike dai = daiJoin.dai();
VatLike vat = daiJoin.vat();
require(address(dai) == underlying, "DAI must be the same as underlying");

// Remember the relevant addresses
daiJoinAddress = daiJoinAddress_;
potAddress = potAddress_;
vatAddress = address(vat);

// Approve moving our DAI into the vat through daiJoin
dai.approve(daiJoinAddress, uint(-1));

// Approve the pot to transfer our funds within the vat
vat.hope(potAddress);
vat.hope(daiJoinAddress);

// Accumulate DSR interest -- must do this in order to doTransferIn
pot.drip();

// Transfer all cash in (doTransferIn does this regardless of amount)
doTransferIn(address(this), 0);
}

/**
* @notice Delegate interface to resign the implementation
*/
function _resignImplementation() public {
require(msg.sender == admin, "only the admin may abandon the implementation");

// Transfer all cash out of the DSR - note that this relies on self-transfer
DaiJoinLike daiJoin = DaiJoinLike(daiJoinAddress);
PotLike pot = PotLike(potAddress);
VatLike vat = VatLike(vatAddress);

// Accumulate interest
pot.drip();

// Calculate the total amount in the pot, and move it out
uint pie = pot.pie(address(this));
pot.exit(pie);

// Checks the actual balance of DAI in the vat after the pot exit
uint bal = vat.dai(address(this));

// Remove our whole balance
daiJoin.exit(address(this), bal / RAY);
}

/*** CToken Overrides ***/

/**
* @notice Accrues DSR then applies accrued interest to total borrows and reserves
* @dev This calculates interest accrued from the last checkpointed block
* up to the current block and writes new checkpoint to storage.
*/
function accrueInterest() public returns (uint) {
// Accumulate DSR interest
PotLike(potAddress).drip();

// Accumulate CToken interest
return super.accrueInterest();
}

/*** Safe Token ***/

/**
* @notice Gets balance of this contract in terms of the underlying
* @dev This excludes the value of the current message, if any
* @return The quantity of underlying tokens owned by this contract
*/
function getCashPrior() internal view returns (uint) {
PotLike pot = PotLike(potAddress);
uint pie = pot.pie(address(this));
return mul(pot.chi(), pie) / RAY;
}

/**
* @notice Transfer the underlying to this contract and sweep into DSR pot
* @param from Address to transfer funds from
* @param amount Amount of underlying to transfer
* @return The actual amount that is transferred
*/
function doTransferIn(address from, uint amount) internal returns (uint) {
// Perform the EIP-20 transfer in
EIP20Interface token = EIP20Interface(underlying);
require(token.transferFrom(from, address(this), amount), "unexpected EIP-20 transfer in return");

DaiJoinLike daiJoin = DaiJoinLike(daiJoinAddress);
GemLike dai = GemLike(underlying);
PotLike pot = PotLike(potAddress);
VatLike vat = VatLike(vatAddress);

// Convert all our DAI to internal DAI in the vat
daiJoin.join(address(this), dai.balanceOf(address(this)));

// Checks the actual balance of DAI in the vat after the join
uint bal = vat.dai(address(this));

// Calculate the percentage increase to th pot for the entire vat, and move it in
// Note: We may leave a tiny bit of DAI in the vat...but we do the whole thing every time
uint pie = bal / pot.chi();
pot.join(pie);

return amount;
}

/**
* @notice Transfer the underlying from this contract, after sweeping out of DSR pot
* @param to Address to transfer funds to
* @param amount Amount of underlying to transfer
*/
function doTransferOut(address payable to, uint amount) internal {
DaiJoinLike daiJoin = DaiJoinLike(daiJoinAddress);
PotLike pot = PotLike(potAddress);
VatLike vat = VatLike(vatAddress);

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

// Checks the actual balance of DAI in the vat after the pot exit
uint bal = vat.dai(address(this));

// Remove our whole balance if rounding would lead us to remove more than we have
daiJoin.exit(to, bal >= mul(amount, RAY) ? amount : bal / RAY);
}

/*** Maker Internals ***/

uint256 constant RAY = 10 ** 27;

function add(uint x, uint y) internal pure returns (uint z) {
require((z = x + y) >= x, "add-overflow");
}

function mul(uint x, uint y) internal pure returns (uint z) {
require(y == 0 || (z = x * y) / y == x, "mul-overflow");
}
}

/*** Maker Interfaces ***/

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

contract GemLike {
function approve(address, uint) public;
function balanceOf(address) public view returns (uint);
function transfer(address, uint) public;
function transferFrom(address, address, uint) public;
function deposit() public payable;
function withdraw(uint) public;
}

contract VatLike {
function can(address, address) public view returns (uint);
function ilks(bytes32) public view returns (uint, uint, uint, uint, uint);
function dai(address) public view returns (uint);
function urns(bytes32, address) public view returns (uint, uint);
function frob(bytes32, address, address, address, int, int) public;
function hope(address) public;
function move(address, address, uint) public;
}

contract DaiJoinLike {
function vat() public returns (VatLike);
function dai() public returns (GemLike);
function join(address, uint) public payable;
function exit(address, uint) public;
}
99 changes: 99 additions & 0 deletions contracts/DAIInterestRateModel.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
pragma solidity ^0.5.12;

import "./JumpRateModel.sol";
import "./SafeMath.sol";

/**
* @title Compound's DAIInterestRateModel Contract
* @author Compound
* @notice The parameterized model described in section 2.4 of the original Compound Protocol whitepaper
*/
contract DAIInterestRateModel is JumpRateModel {
using SafeMath for uint;

PotLike pot;
JugLike jug;

/**
* @notice Construct an interest rate model
* @param _pot The approximate target base APR, as a mantissa (scaled by 1e18)
* @param _jug The rate of increase in interest rate wrt utilization (scaled by 1e18)
* @param _kink The utilization point at which an additional multiplier is applied
* @param _jump The additional multiplier to be applied to multiplierPerBlock after hitting a specified utilization point
*/
constructor(address _pot, address _jug, uint _kink, uint _jump) JumpRateModel(0, 0, _kink, _jump) public {
pot = PotLike(_pot);
jug = JugLike(_jug);
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 1e18)
*/
function getSupplyRate(uint cash, uint borrows, uint reserves, uint reserveFactorMantissa) public view returns (uint) {
uint protocolRate = super.getSupplyRate(cash, borrows, reserves, reserveFactorMantissa);

uint underlying = cash.add(borrows).sub(reserves);
if (underlying == 0) {
return protocolRate;
} else {
uint cashRate = cash.mul(dsrPerBlock()).div(underlying);
return cashRate.add(protocolRate);
}
}

/**
* @notice Calculates the Dai savings rate per block
* @return The Dai savings rate per block (as a percentage, and scaled by 1e18)
*/
function dsrPerBlock() public view returns (uint) {
return pot
.dsr().sub(1e27) // scaled 1e27 aka RAY, and includes an extra "ONE" before subraction
.div(1e9) // descale to 1e18
.mul(15); // 15 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 stabilityFee = duty.add(jug.base()).sub(1e27).mul(1e18).div(1e27).mul(15);

baseRatePerBlock = dsrPerBlock().mul(1e18).div(0.9e18); // ensure borrow rate is higher than savings rate
multiplierPerBlock = stabilityFee.sub(baseRatePerBlock).mul(1e18).div(kink);

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


/*** Maker Interfaces ***/

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

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

mapping (bytes32 => Ilk) public ilks;
uint256 public base;
}

Loading

0 comments on commit 8e40ba1

Please sign in to comment.