Skip to content

Commit

Permalink
Compound (#51)
Browse files Browse the repository at this point in the history
* initial separate into d3m target and d3m join contracts

* initial update of tests and file reorg/rename

* add approval for the join to allow the target to move gems and update the target withdraw to pull the adai first

* update interfaces and require successful transferFrom"

* update tests and fix reward claimer

* allow filing a new target

* minor clean up

* remove unneeded interface

* update interface and add validTarget helper

* add missing arg

* initial Test Target contract

* update test forge path to accept new match arguments

* add new line

* update Target to not need wat parameter

* add test gem for Join testing

* allow for bar = 0 case in tests

* add give allowance to test token and transfers to target

* update Join to fix target cage bug

* we don't need internal balance tracking for the test target

* add a test rewards contract

* add join specific tests for covering the interactions with the Vat

* make targets joins and have them hold balance

Co-authored-by: Julien <[email protected]>

* update testing and d3m Join so that the Target holds the balances in ERC and Vat

* rename Join to Hub

* rename Target to Join

* make Hub compatible with multiple d3m joins:

* add balance check to join

* update test for hub+joins model

* update helper for Hub approach

* update copyrights

* update tests and require d3m be live for admin actions

* use ilks instead

* organize files and change Join->Pool

* switch to pool internally on hub

* update test to use Pool naming

* initial update for separation of Plans from Pools

* minor test updates

* initial breakdown of pool into base

* add Plan base

* clean up

* Update to Eip 4626 (#34)

* initial update of tests and file reorg/rename

* update tests and fix reward claimer

* organize files and change Join->Pool

* switch to pool internally on hub

* initial update for separation of Plans from Pools

* update language for EIP-4626

* fix tests after rename

* maxWithdraw and asset balances - PR comments from @hexonaut

* fix share transfer amounts

* fix tests

* remove helper function

* remove helper function tests

* fix unwind balance

* fix asset balance in test pool

* move bar to plan

* use plan base

* Fix tests

* Modularize upd 325 (#37)

* Clean up vars and add Cage event

* Upd revert names

* Upd revert names in Pool Base

* emit Hope and Nope events on Pool Base

* Add Quit event

* Add emit for clarity

* Add Exit event

* uint => uint256

* rm math

* wards ocd

* start updating aave to use base

* shorten names

* remove chainlog from d3m hub

* remove dai daiJoin from hub

* make common interfaces to use in tests

* remove ds-value submodule

* update aave tests for no ds-value

* add todos

* do daiJoin work in hub @gbalabasquer

* PR comments to clean up hub

* clean up add TODO

* join directly to vow

* PR feedback @talbaneth and @gbalabasquer

* Apply suggestions from code review

Co-authored-by: talbaneth <[email protected]>

* remove collect and move plan to Hub from pool

* abstract bar to individual plan implementations

* read and assign end on when necessary

* don't read the plan

* adjust unwind daiJoin interaction to match wind

* fix aave tests - WIP

* Apply suggestions from code review

Co-authored-by: talbaneth <[email protected]>

* PR clean up comments

* add recover tokens function to pool

* remove convertToAssets from base

* PR comments: remove bar and pool from bases and gas efficiency

* change aave assetBalance calc

* fix mom bar filing

* update test for Aave share balance transfer in exit

* revert convert to shares in exit since we should already be dealing with shares in wad

* fix permissionless repay aave test

* fix remaining aave test

* reorganize interfaces into tests

* move constant

* remove unused interface functions

* remove maxBar from base

* remove events

* Apply suggestions from code review

Co-authored-by: talbaneth <[email protected]>

* fix silly variable setting

Co-authored-by: talbaneth <[email protected]>

* move plans to separate folder

* PR comments - update auths

* move share to pools out of base

* initial switch to returning target assets

* WIP change to return targetAsset

* accrued if needed before getting balance

* integrate @hexonaut 's Aave calc fix

* Delete empty file

* plan base unit tests

* add pool base tests

* re-order accrueIfNeeded and maxWithdraw

Co-authored-by: talbaneth <[email protected]>

* remove unneeded math

Co-authored-by: talbaneth <[email protected]>

* consistent filing for ilks

* better documentations of Ilk in Hub

Co-authored-by: talbaneth <[email protected]>

* remove unneeded safe math

* don't store culled

* unused return variable

Co-authored-by: talbaneth <[email protected]>

* update getTargetAssets

* pr feedback

* update share balance for aave to be scaled

* Mom can disable plans

* make files external

* initial AaveDai plan tests

* more AaveDaiPlan unit tests

* add disable to plan base

* fix missing override

* check dai balance in wind

* denominate shares in hub in dai/gems and add transferAllShares function

* add approxEq for checking dai depositing

* AaveDaiPool tests

* move deposit check to pool

* clean up remaining hub tests

* add Mom tests

* update base and share calculations

* update aaave deposit checking

* remove named return

Co-authored-by: talbaneth <[email protected]>

* remove debugging

* update comments and errors

* fix rounding error

* fix possible bug with debt > debt ceiling

* pr comments and clean up

* add file events

* pr comments on events and requires

* add missing auth and test coverage

* consolidate debt usage to totalDebt instead of adding it multiple times

* update unwind comment

Co-authored-by: talbaneth <[email protected]>

* fix adai pool comment

Co-authored-by: talbaneth <[email protected]>

* update comment

* D3MCompoundDaiPlan - initial implementation, untested

* Neat

* Add initial version of D3MCompoundDaiPool, missing logic and tests

* More ppol work, actually add D3MCompoundDaiPool.sol

* Test supply for current rate

* D3MCompoundDaiPlan testing

* Add test for calculating supplies

* Align to latest modularize branch

* Add accrueIfNeeded to compound pool

* D3MCompoundDai.t.sol - WIP

* Align to latest modularize changes

* Remove auth modifier from compound's collect()

* Support also 0 jump multiplier

* Neat

* Unify return values

* Neat - fix indentation

* Add plan test from hub

* During test cleanup

* Align to modularize latest changes

* Support explicit disable() from mom

* Make disable overriding on Compound Plan

* Align to transferShares changes in hub

* Align to modularize - validaion in deposit

* Add underlying() validation also in plan ctr

* Remove unused function in pool and add overrride

* Align to modularize pool changes

* Add Compound plan events

* Add Compound pool auth and events

* Align to compound-cd-audit fixes, commit fa793a3

This is the commit with the response to CS audit for Plan.
The base plan and compound plan code are now identical to there.

* Align to v2, wip

* Align to v2, tests pass

* Port test from cs-audit-fixes

* Fix merge mistake with filing rate model

* Finish porting all new plan tests

* Add D3MCompoundDaiPool.t.sol

* Align Compound plan test to run base plan tests

* Cleare comparisons in D3MCompoundDai.t.sol

* Simplify to use just _setRelBorrowTarget in D3MCompoundDai.t.sol

* Align to use Like suffix for interfaces

* Linter run - break require, remove test comment

* Add clarification for Compound APY

* Slightly improve plan comment + small neats

* Add comp amount comparison to test

* Refactors based on AAVE comments

-Move administration parts to the top so wards will be in first slot.
-Move internal functions to after external related ones.

* Add amt to Compund's Collect() event

* Fix merge of D3MAaveDaiPool.t.sol

* Use compSupplySpeeds in comp claiming tests

* Align line break style to rest of the file

* Use internal constant, Add TODOs

* add 2021 Nov Aave D3M Audit (#52)

* fix end address

Co-authored-by: Nazzareno Massari <[email protected]>

* update end in aave tests

Co-authored-by: Nazzareno Massari <[email protected]>

* Update End address in D3MCompoundDai.t.sol

* Fix typo

* Tighten underlying dai comparisons, check deposit cdai

* Add nope to compound pool, support hope/nope base tests

* align to use >= in solidity pragma

* Group storage together for Compound pool and plan

* Flip expected and ectual in assertEqAbsolute

* Align to V2 branch

* Align layout of events/ctr/auth/math/admin to aave

* Revert if getAccountSnapshot returns an error

* Add TODOs

* Add Compound links and explanation for using exchangeRateStored

* Algin to V2 changes

* Renamings and avoiding unused warning on hooks

* Add SPDX-FileCopyrightText to Compound files

* Align to V2

* deactivate if cdai implementation changes

* Add boundary for barb

* Make comp immutable, get dai from cdai

* Support collect without claim.

 Since COMP can be claimed to the pool directly through Compound by
 a 3rd party make sure it can be recovered later even if comptroller
 would revert for some reason.

* Improve interface links

* align to v2 - wip

* Finish aliging to asserRevert change

* Align to V2

* Add Compound to README

* Update to permanent links

* Fix failures due to rounding

* Validate delegate is not address(0)

* Use address instead of token in cDai etherscan link

Co-authored-by: Nazzareno Massari <[email protected]>

* Move tau to general place in the Readme

* Move _calculateTargetSupply up

* Add comment about normal rate

* Use _hub in pool ctr

* Reorder cdai assignment and Avoid casting in plan

* Support utilization > 100%

* Comment why multiplierPerBlock != 0 and can de divided by

* dapp upgrade dss-test (reverting un-intentional downgrade)

* _assertEqApprox => assertEq

Co-authored-by: Gonzalo Balabasquer <[email protected]>

* Remove safe math in tests

* Fix copyrights

Co-authored-by: Nazzareno Massari <[email protected]>

* Handle several review comments in tests

* Fix copyrights also in Compound tests

* Substract before addition

* Neat - better spacings

Co-authored-by: Chris Smith <[email protected]>
Co-authored-by: Julien <[email protected]>
Co-authored-by: Brian McMichael <[email protected]>
Co-authored-by: Nazzareno Massari <[email protected]>
Co-authored-by: Gonzalo Balabasquer <[email protected]>
  • Loading branch information
6 people authored Jun 29, 2022
1 parent 885fced commit 41f3993
Show file tree
Hide file tree
Showing 8 changed files with 2,281 additions and 2 deletions.
19 changes: 17 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,15 +23,30 @@ A "dumb" adapter which is responsible for depositing or withdrawing DAI from the

The D3MPlan can be viewed as the targeting logic for D3M instances. The plan is responsible for reading all relevant information from its state (i.e. the target rate) and possibly from the external protocol (i.e. current balance of supply and borrowing in the market) and condensing this down to a debt target. This desired target debt is forwarded to the Hub to take action to reach this target debt level asap.

### General Configuration

The below parameter exists for each D3M implementation:

- `tau` [seconds] - The expiry for when bad debt is sent to the vow debt queue. This must be set during initialization to enforce a deadline for when this module is considered to be in a failure mode where no more liquidity is available in the pool to unwind. Unwinding can still occur after this period elapses.

# Specific Implementations

## Aave D3M

### Configuration

Below are the configurable parameters for the Aave DAI D3M:
Below is a configurable parameter for the Aave DAI D3M:

- `tau` [seconds] - The expiry for when bad debt is sent to the vow debt queue. This must be set during initialization to enforce a deadline for when this module is considered to be in a failure mode where no more liquidity is available in the pool to unwind. Unwinding can still occur after this period elapses.
- `bar` [ray] - The target borrow rate on Aave for the DAI market. This module will aim to enforce that borrow limit.

Any stkAave that is accured can be permissionlessly collected into the pause proxy by calling `collect()`.

## Compound D3M

### Configuration

Below is a configurable parameter for the Compound DAI D3M:

- `barb` [wad] - The target borrow rate per block on Compound for the DAI market. This module will aim to enforce that borrow limit.

Any Comp that is accured can be permissionlessly collected into the pause proxy by calling `collect()`.
Binary file not shown.
198 changes: 198 additions & 0 deletions src/plans/D3MCompoundPlan.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,198 @@
// SPDX-FileCopyrightText: © 2022 Dai Foundation <www.daifoundation.org>
// SPDX-License-Identifier: AGPL-3.0-or-later
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.

////////////////////////////////////////////////////////////////////////////////
// //
// Methodology for Calculating Target Supply from Target Rate Per Block //
// //
////////////////////////////////////////////////////////////////////////////////

// apy to rate per block off-chain conversion info:
// apy = ((1 + (borrowRatePerBlock / WAD) * blocksPerDay) ^ 365 - 1) * 100
// (0) borrowRatePerBlock = ((((apy / 100) + 1) ^ (1 / 365) - 1) / blocksPerDay) * WAD
// ATTOW the formula matches the borrow apy on (https://compound.finance/markets/DAI) using blocksPerDay = 6570
//
// https://github.com/compound-finance/compound-protocol/blob/a3214f67b73310d547e00fc578e8355911c9d376/contracts/BaseJumpRateModelV2.sol#L96
//
// util > kink:
// normalRate = kink * multiplierPerBlock / WAD + baseRatePerBlock;
// targetInterestRate = normalRate + jumpMultiplierPerBlock * (util - kink) / WAD
// util * jumpMultiplierPerBlock = (targetInterestRate - normalRate) * WAD + kink * jumpMultiplierPerBlock
// (1) util = kink + (targetInterestRate - normalRate) * WAD / jumpMultiplierPerBlock
//
// util <= kink:
// targetInterestRate = util * multiplierPerBlock / WAD + baseRatePerBlock
// (2) util = (targetInterestRate - baseRatePerBlock) * WAD / multiplierPerBlock
//
// https://github.com/compound-finance/compound-protocol/blob/a3214f67b73310d547e00fc578e8355911c9d376/contracts/BaseJumpRateModelV2.sol#L80
//
// util = borrows * WAD / (cash + borrows - reserves);
// (3) cash + borrows - reserves = borrows * WAD / util

pragma solidity ^0.8.14;

import "./ID3MPlan.sol";

// cDai - https://etherscan.io/address/0x5d3a536E4D6DbD6114cc1Ead35777bAB948E3643
interface CErc20Like {
function totalBorrows() external view returns (uint256);
function totalReserves() external view returns (uint256);
function interestRateModel() external view returns (address);
function getCash() external view returns (uint256);
function implementation() external view returns (address);
}

// JumpRateModelV2 - https://etherscan.io/address/0xfb564da37b41b2f6b6edcc3e56fbf523bd9f2012
interface InterestRateModelLike {
function baseRatePerBlock() external view returns (uint256);
function kink() external view returns (uint256);
function multiplierPerBlock() external view returns (uint256);
function jumpMultiplierPerBlock() external view returns (uint256);
}

contract D3MCompoundPlan is ID3MPlan {

mapping (address => uint256) public wards;
InterestRateModelLike public tack;
address public delegate; // cDai implementation
uint256 public barb; // target Interest Rate Per Block [wad] (0)

CErc20Like public immutable cDai;

// https://github.com/compound-finance/compound-protocol/blob/a3214f67b73310d547e00fc578e8355911c9d376/contracts/CTokenInterfaces.sol#L31
uint256 internal constant MAX_BORROW_RATE = 0.0005e16;

// --- Events ---
event Rely(address indexed usr);
event Deny(address indexed usr);
event File(bytes32 indexed what, uint256 data);
event File(bytes32 indexed what, address data);

constructor(address cDai_) {
cDai = CErc20Like(cDai_);

address rateModel_ = cDai.interestRateModel();
address delegate_ = cDai.implementation();

require(rateModel_ != address(0), "D3MCompoundPlan/invalid-rateModel");
require(delegate_ != address(0), "D3MCompoundPlan/invalid-delegate");

tack = InterestRateModelLike(rateModel_);
delegate = delegate_;

wards[msg.sender] = 1;
emit Rely(msg.sender);
}

modifier auth {
require(wards[msg.sender] == 1, "D3MCompoundPlan/not-authorized");
_;
}

// --- Math ---
uint256 internal constant WAD = 10 ** 18;
function _wmul(uint256 x, uint256 y) internal pure returns (uint256 z) {
z = (x * y) / WAD;
}
function _wdiv(uint256 x, uint256 y) internal pure returns (uint256 z) {
z = (x * WAD) / y;
}

// --- Admin ---
function rely(address usr) external auth {
wards[usr] = 1;
emit Rely(usr);
}
function deny(address usr) external auth {
wards[usr] = 0;
emit Deny(usr);
}

function file(bytes32 what, uint256 data) external auth {
if (what == "barb") {
require(data <= MAX_BORROW_RATE, "D3MCompoundPlan/barb-too-high");
barb = data;
} else revert("D3MCompoundPlan/file-unrecognized-param");
emit File(what, data);
}
function file(bytes32 what, address data) external auth {
if (what == "tack") tack = InterestRateModelLike(data);
else if (what == "delegate") delegate = data;
else revert("D3MCompoundPlan/file-unrecognized-param");
emit File(what, data);
}

function _calculateTargetSupply(uint256 targetInterestRate, uint256 borrows) internal view returns (uint256) {
uint256 kink = tack.kink();
uint256 multiplierPerBlock = tack.multiplierPerBlock();
uint256 baseRatePerBlock = tack.baseRatePerBlock();
uint256 jumpMultiplierPerBlock = tack.jumpMultiplierPerBlock();

// The normal rate is a Compound term for the rate at kink utillization
uint256 normalRate = _wmul(kink, multiplierPerBlock) + baseRatePerBlock;

uint256 targetUtil;
if (targetInterestRate > normalRate) {
if (jumpMultiplierPerBlock == 0) return 0; // illegal rate, max is normal rate for this case
targetUtil = kink + _wdiv(targetInterestRate - normalRate, jumpMultiplierPerBlock); // (1)
} else if (targetInterestRate > baseRatePerBlock) {
// multiplierPerBlock != 0, as otherwise normalRate == baseRatePerBlock, matching the first case
targetUtil = _wdiv(targetInterestRate - baseRatePerBlock, multiplierPerBlock); // (2)
} else {
// if (target == base) => (borrows == 0) => supply does not matter
// if (target < base) => illegal rate
return 0;
}

return _wdiv(borrows, targetUtil); // (3)
}

function getTargetAssets(uint256 currentAssets) external override view returns (uint256) {
uint256 targetInterestRate = barb;
if (targetInterestRate == 0) return 0; // De-activated

uint256 borrows = cDai.totalBorrows();
uint256 targetTotalPoolSize = _calculateTargetSupply(targetInterestRate, borrows);
uint256 totalPoolSize = cDai.getCash() + borrows - cDai.totalReserves();

if (targetTotalPoolSize >= totalPoolSize) {
// Increase debt (or same)
return currentAssets + (targetTotalPoolSize - totalPoolSize);
} else {
// Decrease debt
unchecked {
uint256 decrease = totalPoolSize - targetTotalPoolSize;
if (currentAssets >= decrease) {
return currentAssets - decrease;
} else {
return 0;
}
}
}
}

function active() public view override returns (bool) {
if (barb == 0) return false;
return cDai.interestRateModel() == address(tack) &&
cDai.implementation() == delegate;
}

function disable() external override {
require(wards[msg.sender] == 1 || !active(), "D3MCompoundPlan/not-authorized");
barb = 0; // ensure deactivation even if active conditions return later
emit Disable();
}
}
Loading

0 comments on commit 41f3993

Please sign in to comment.