├── .gitattributes ├── .gitignore ├── .gitmodules ├── Makefile ├── README.md └── src ├── crop.sol ├── interfaces.sol ├── pip.sol ├── spell ├── DssSpell.sol ├── DssSpell.t.sol └── test │ └── rates.sol ├── test ├── base.sol └── crop.t.sol └── wind.sol /.gitattributes: -------------------------------------------------------------------------------- 1 | *.sol linguist-language=Solidity 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /out 2 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "lib/ds-test"] 2 | path = lib/ds-test 3 | url = https://github.com/dapphub/ds-test 4 | [submodule "lib/dss-interfaces"] 5 | path = lib/dss-interfaces 6 | url = https://github.com/makerdao/dss-interfaces 7 | [submodule "lib/ds-value"] 8 | path = lib/ds-value 9 | url = https://github.com/dapphub/ds-value 10 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | export DAPP_TEST_NUMBER = 10950483 2 | 3 | all :; dapp build 4 | clean :; dapp clean 5 | test :; dapp test --rpc 6 | test-now :; DAPP_TEST_NUMBER=$$(seth block latest number) dapp test --rpc 7 | deploy :; dapp create Usdc 8 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | This is a collateral adapter for Dai that allows for the distribution of 2 | rewards given to holders of the collateral. 3 | 4 | This adapter is then used to implement a leveraged cUSDC strategy, 5 | depositing the underlying USDC collateral into Compound and farming COMP. 6 | 7 | ## Rewards Adapter 8 | 9 | We distinguish between `push` and `pull` rewards: 10 | 11 | - in `push` rewards, the rewards are sent to holders without their input. 12 | - in `pull` rewards, holders must actively claim their rewards. 13 | 14 | `push` rewards are supported by default. In principle any pull-based 15 | token rewards are supported, e.g. Compound `claimComp()` and SNX-staking 16 | `getReward()`, with a little custom claiming logic. 17 | 18 | ### Usage 19 | 20 | Developers need to 21 | 22 | 1) Override the `crop` function with the logic for claiming the given reward 23 | token and then return the tokens gained since the last crop. The default 24 | is the difference in the token balance and will work for push-reward tokens. 25 | 26 | 2) Override the `nav` function to show the underlying balance of the adapter 27 | (the Net Asset Valuation). The default is `gem.balanceOf(adapter)`. 28 | 29 | 30 | Users can `join` and `exit` as with regular adapters. The user receives 31 | their pending rewards on every `join` / `exit` and can use e.g. `join(0)` 32 | to receive their rewards without depositing additional collateral. 33 | 34 | There are two additional functions: 35 | 36 | - `flee` allows for `exit` without invoking `crop`, in case of some 37 | issue with the `crop` function. 38 | 39 | - `tack` is for transferring `stake` between users, following collateral 40 | transfers inside the `vat`. 41 | 42 | 43 | #### `tack` 44 | 45 | Collateral can be transferred in [dss] in several ways: simply via 46 | `flux`, but also via `grab` and `frob`. `frob` and `flux` are publically 47 | callable, which means that the rewards for a user may not match their 48 | collateral balance. This is not a problem in itself as it isn't possible 49 | to exit collateral without having the appropriate `stake`, so it isn't 50 | possible to game rewards through e.g. `join($$$); flux(me, me2, $$$); flee($$$)`. 51 | 52 | However, recipients of auction proceeds will need the appropriate 53 | `stake` if they wish to exit. The winner of a collateral auction claims 54 | their collateral via `flip.deal`. This increases their collateral 55 | balance, but not their `stake`. `tack` allows this stake to be acquired, 56 | from other users that have an excess of stake. 57 | 58 | It isn't strictly necessary to alter the collateral auction contract, as 59 | `tack` can be called by users, but it would be convenient to add a 60 | `tack` after every `flux`: 61 | 62 | vat.flux(ilk, a, b, x); 63 | join.tack(a, b, x); 64 | 65 | Then rewards will continue to accumulate throughout the auction and will 66 | be distributed appropriately to the winner and the CDP owner, with the winner 67 | able to reap their rewards following `deal`. 68 | 69 | 70 | ### Terms 71 | 72 | - `gem`: the underlying collateral token 73 | - `nav`: Net Asset Valuation, total underlying gems held by adapter 74 | - `nps`: `nav` per stake 75 | - `stake`: gems per user 76 | - `total`: total `stake` 77 | - `bonus`: the reward token, e.g. COMP 78 | - `stock`: last recorded balance of reward token 79 | - `share`: accumulated `bonus` per `gem` 80 | - `crops`: accumulated `bonus` per `gem` per user 81 | 82 | 83 | ## cUSDC Strategy 84 | 85 | Compound allows supplying and borrowing against the same asset. COMP is 86 | distributed according to the total amount borrowed and supplied. This allows 87 | for a low risk strategy: supply USDC and then borrow USDC against it. We will 88 | continually lose USDC to interest, but this is more than offset by COMP income. 89 | 90 | Given an initial supply `s0` (the amount of underlying USDC in the adapter), 91 | and a USDC Collateral Factor of 75% `cf = 0.75`, we can supply a maximum of 92 | 93 | s = s0 / (1 - cf) 94 | 95 | i.e. `s = 4 * s0` for USDC, where the amount borrowed `b = s * cf` and 96 | the utlisation `u = b / s = cf`. 97 | 98 | The real value of the underlying collateral, our Net Asset Valuation, is 99 | given by 100 | 101 | nav = g + s - b 102 | 103 | where `g` accounts for any underlying that remains in the contract. 104 | 105 | 106 | ### `wind` 107 | 108 | In order to approach this supply it is necessary to go through several 109 | rounds of `mint` / `borrow`, as we cannot exceed `cf` at any point. The 110 | maximum amount that can be borrowed in each round is given by 111 | 112 | x <= cf * s - b 113 | 114 | Which will achieve a new utilisation `u'` of 115 | 116 | u' = cf / (1 + cf - u) 117 | 118 | We can reduce the number of rounds necessary by providing a loan `L`, then 119 | 120 | x1 <= cf * (s + L) - b 121 | 122 | but we must also remain under our target collateral factor `tf` after 123 | paying back any loan 124 | 125 | x2 <= (tf * s - b) / (1 - tf) 126 | 127 | therefore 128 | 129 | x <= min(x1, x2) 130 | 131 | Wind performs this calculation for each round of borrowing, ensuring 132 | that we never exceed our target utilisation. 133 | 134 | We can determine the minimum necessary loan to achieve a target 135 | utilisation in a single round given an initial `(s, b) = (s0, 0)`, 136 | 137 | L / s0 >= (u' / cf - 1 + u') / (1 - u') 138 | 139 | e.g. for `u' = 0.675` (90% of cf), we require a loan of 177% of the 140 | collateral amount. 141 | 142 | L / s0 >= 1.77 143 | 144 | 145 | ### `unwind` 146 | 147 | Our utilisation may increase over time due to interest, and if we 148 | exceed `u > cf` we may be subject to compound liquidation. To lower the 149 | utlisation we must go through rounds of `redeem` / `repay`, with the 150 | maximum redeem amount given by 151 | 152 | x <= L + s - b / cf 153 | 154 | We must also redeem an extra amount `e` if we are to allow a user to 155 | `exit`, giving a minimum redeem amount of 156 | 157 | x >= (b - s * u' + e * u') / (1 - u') 158 | 159 | We have three different regimes depending on the value of `u'`, if we 160 | are to have only one redeem / repay round. When `u > cf`, then `u' <= cf` 161 | and 162 | 163 | L / s0 >= (u / cf - 1) / ((1 - u) * (1 - cf)) 164 | + (e / s0) * cf / (1 - cf) 165 | 166 | i.e. we must always provide a loan. 167 | 168 | When `tf < u < cf`, then `u' < u` and 169 | 170 | L / s0 >= (u / cf - 1) / (1 - u) 171 | + (e / s0) * u / (1 - u) 172 | 173 | i.e. a loan is necessary for 174 | 175 | e / s0 >= 1 / u - 1 / cf 176 | 177 | and our maximum exit is given by 178 | 179 | e / s0 <= 1 / u - 1 / cf + (L / s0) * (1 - u) / u 180 | 181 | Finally when `u < tf`, `u' = tf` and 182 | 183 | L / s0 >= (u / cf - 1 + u - u * tf / cf) / ((1 - u) * (1 - tf)) 184 | + (e / s0) * tf / (1 - tf) 185 | 186 | Then for a full exit of all of the underlying collateral, `e / s0 = 1`, 187 | from `u = 0.675, cf = 0.75`, we again find that we need a 177% loan, 188 | 189 | L / s0 >= 1.77 190 | 191 | Without a loan we are limited to 192 | 193 | e / s0 <= 1 / tf - 1 / cf 194 | 195 | or 14.8% of the underlying collateral for `tf = 0.675`. Adding further 196 | rounds will allow for larger exits and smaller loans. 197 | 198 | 199 | ### Risks 200 | 201 | - Liquidation 202 | - Compound Governance 203 | - Compound 204 | -------------------------------------------------------------------------------- /src/crop.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.6.7; 2 | pragma experimental ABIEncoderV2; 3 | 4 | interface ERC20 { 5 | function balanceOf(address owner) external view returns (uint); 6 | function transfer(address dst, uint amount) external returns (bool); 7 | function transferFrom(address src, address dst, uint amount) external returns (bool); 8 | function approve(address spender, uint amount) external returns (bool); 9 | function allowance(address owner, address spender) external view returns (uint); 10 | function decimals() external returns (uint8); 11 | } 12 | 13 | struct Urn { 14 | uint256 ink; // Locked Collateral [wad] 15 | uint256 art; // Normalised Debt [wad] 16 | } 17 | 18 | interface VatLike { 19 | function slip(bytes32 ilk, address usr, int256 wad) external; 20 | function flux(bytes32 ilk, address src, address dst, uint256 wad) external; 21 | function gem(bytes32 ilk, address usr) external returns (uint); 22 | function urns(bytes32 ilk, address usr) external returns (Urn memory); 23 | } 24 | 25 | // receives tokens and shares them among holders 26 | contract CropJoin { 27 | VatLike public immutable vat; // cdp engine 28 | bytes32 public immutable ilk; // collateral type 29 | ERC20 public immutable gem; // collateral token 30 | uint256 public immutable dec; // gem decimals 31 | ERC20 public immutable bonus; // rewards token 32 | 33 | uint256 public share; // crops per gem [ray] 34 | uint256 public total; // total gems [wad] 35 | uint256 public stock; // crop balance [wad] 36 | 37 | mapping (address => uint) public crops; // crops per user [wad] 38 | mapping (address => uint) public stake; // gems per user [wad] 39 | 40 | constructor(address vat_, bytes32 ilk_, address gem_, address bonus_) public { 41 | vat = VatLike(vat_); 42 | ilk = ilk_; 43 | gem = ERC20(gem_); 44 | uint dec_ = ERC20(gem_).decimals(); 45 | require(dec_ <= 18); 46 | dec = dec_; 47 | 48 | bonus = ERC20(bonus_); 49 | } 50 | 51 | function add(uint x, uint y) public pure returns (uint z) { 52 | require((z = x + y) >= x, "ds-math-add-overflow"); 53 | } 54 | function sub(uint x, uint y) public pure returns (uint z) { 55 | require((z = x - y) <= x, "ds-math-sub-underflow"); 56 | } 57 | function mul(uint x, uint y) public pure returns (uint z) { 58 | require(y == 0 || (z = x * y) / y == x, "ds-math-mul-overflow"); 59 | } 60 | uint256 constant WAD = 10 ** 18; 61 | function wmul(uint x, uint y) public pure returns (uint z) { 62 | z = mul(x, y) / WAD; 63 | } 64 | function wdiv(uint x, uint y) public pure returns (uint z) { 65 | z = mul(x, WAD) / y; 66 | } 67 | uint256 constant RAY = 10 ** 27; 68 | function rmul(uint x, uint y) public pure returns (uint z) { 69 | z = mul(x, y) / RAY; 70 | } 71 | function rdiv(uint x, uint y) public pure returns (uint z) { 72 | z = mul(x, RAY) / y; 73 | } 74 | 75 | // Net Asset Valuation [wad] 76 | function nav() public virtual returns (uint) { 77 | uint _nav = gem.balanceOf(address(this)); 78 | return mul(_nav, 10 ** (18 - dec)); 79 | } 80 | 81 | // Net Assets per Share [wad] 82 | function nps() public returns (uint) { 83 | if (total == 0) return WAD; 84 | else return wdiv(nav(), total); 85 | } 86 | 87 | function crop() internal virtual returns (uint) { 88 | return sub(bonus.balanceOf(address(this)), stock); 89 | } 90 | 91 | // decimals: underlying=dec cToken=8 comp=18 gem=18 92 | function join(uint256 val) public virtual { 93 | uint wad = wdiv(mul(val, 10 ** (18 - dec)), nps()); 94 | require(int(wad) >= 0); 95 | 96 | if (total > 0) share = add(share, rdiv(crop(), total)); 97 | 98 | address usr = msg.sender; 99 | require(bonus.transfer(msg.sender, sub(rmul(stake[usr], share), crops[usr]))); 100 | stock = bonus.balanceOf(address(this)); 101 | if (wad > 0) { 102 | require(gem.transferFrom(usr, address(this), val)); 103 | vat.slip(ilk, usr, int(wad)); 104 | 105 | total = add(total, wad); 106 | stake[usr] = add(stake[usr], wad); 107 | } 108 | crops[usr] = rmul(stake[usr], share); 109 | } 110 | 111 | function exit(uint val) public virtual { 112 | uint wad = wdiv(mul(val, 10 ** (18 - dec)), nps()); 113 | require(int(wad) >= 0); 114 | 115 | if (total > 0) share = add(share, rdiv(crop(), total)); 116 | 117 | address usr = msg.sender; 118 | require(bonus.transfer(msg.sender, sub(rmul(stake[usr], share), crops[usr]))); 119 | stock = bonus.balanceOf(address(this)); 120 | if (wad > 0) { 121 | require(gem.transfer(usr, val)); 122 | vat.slip(ilk, usr, -int(wad)); 123 | 124 | total = sub(total, wad); 125 | stake[usr] = sub(stake[usr], wad); 126 | } 127 | crops[usr] = rmul(stake[usr], share); 128 | } 129 | 130 | function flee() public virtual { 131 | address usr = msg.sender; 132 | 133 | uint wad = vat.gem(ilk, usr); 134 | uint val = wmul(wmul(wad, nps()), 10 ** dec); 135 | 136 | require(gem.transfer(usr, val)); 137 | vat.slip(ilk, usr, -int(wad)); 138 | 139 | total = sub(total, wad); 140 | stake[usr] = sub(stake[usr], wad); 141 | crops[usr] = rmul(stake[usr], share); 142 | } 143 | 144 | function tack(address src, address dst, uint wad) public { 145 | stake[src] = sub(stake[src], wad); 146 | stake[dst] = add(stake[dst], wad); 147 | 148 | crops[src] = sub(crops[src], rmul(share, wad)); 149 | crops[dst] = add(crops[dst], rmul(share, wad)); 150 | 151 | require(stake[src] >= add(vat.gem(ilk, src), vat.urns(ilk, src).ink)); 152 | require(stake[dst] <= add(vat.gem(ilk, dst), vat.urns(ilk, dst).ink)); 153 | } 154 | } 155 | -------------------------------------------------------------------------------- /src/interfaces.sol: -------------------------------------------------------------------------------- 1 | pragma experimental ABIEncoderV2; 2 | 3 | abstract contract ComptrollerInterface { 4 | /// @notice Indicator that this is a Comptroller contract (for inspection) 5 | bool public constant isComptroller = true; 6 | 7 | /*** Assets You Are In ***/ 8 | 9 | function enterMarkets(address[] calldata cTokens) external virtual returns (uint[] memory); 10 | function exitMarket(address cToken) external virtual returns (uint); 11 | 12 | /*** Policy Hooks ***/ 13 | 14 | function mintAllowed(address cToken, address minter, uint mintAmount) external virtual returns (uint); 15 | function mintVerify(address cToken, address minter, uint mintAmount, uint mintTokens) external virtual; 16 | 17 | function redeemAllowed(address cToken, address redeemer, uint redeemTokens) external virtual returns (uint); 18 | function redeemVerify(address cToken, address redeemer, uint redeemAmount, uint redeemTokens) external virtual; 19 | 20 | function borrowAllowed(address cToken, address borrower, uint borrowAmount) external virtual returns (uint); 21 | function borrowVerify(address cToken, address borrower, uint borrowAmount) external virtual; 22 | 23 | function repayBorrowAllowed( 24 | address cToken, 25 | address payer, 26 | address borrower, 27 | uint repayAmount) external virtual returns (uint); 28 | function repayBorrowVerify( 29 | address cToken, 30 | address payer, 31 | address borrower, 32 | uint repayAmount, 33 | uint borrowerIndex) external virtual; 34 | 35 | function liquidateBorrowAllowed( 36 | address cTokenBorrowed, 37 | address cTokenCollateral, 38 | address liquidator, 39 | address borrower, 40 | uint repayAmount) external virtual returns (uint); 41 | function liquidateBorrowVerify( 42 | address cTokenBorrowed, 43 | address cTokenCollateral, 44 | address liquidator, 45 | address borrower, 46 | uint repayAmount, 47 | uint seizeTokens) external virtual; 48 | 49 | function seizeAllowed( 50 | address cTokenCollateral, 51 | address cTokenBorrowed, 52 | address liquidator, 53 | address borrower, 54 | uint seizeTokens) external virtual returns (uint); 55 | function seizeVerify( 56 | address cTokenCollateral, 57 | address cTokenBorrowed, 58 | address liquidator, 59 | address borrower, 60 | uint seizeTokens) external virtual; 61 | 62 | function transferAllowed(address cToken, address src, address dst, uint transferTokens) external virtual returns (uint); 63 | function transferVerify(address cToken, address src, address dst, uint transferTokens) external virtual; 64 | 65 | /*** Liquidity/Liquidation Calculations ***/ 66 | 67 | function liquidateCalculateSeizeTokens( 68 | address cTokenBorrowed, 69 | address cTokenCollateral, 70 | uint repayAmount) external virtual view returns (uint, uint); 71 | } 72 | 73 | abstract contract InterestRateModel { 74 | /// @notice Indicator that this is an InterestRateModel contract (for inspection) 75 | bool public constant isInterestRateModel = true; 76 | 77 | /** 78 | * @notice Calculates the current borrow interest rate per block 79 | * @param cash The total amount of cash the market has 80 | * @param borrows The total amount of borrows the market has outstanding 81 | * @param reserves The total amount of reserves the market has 82 | * @return The borrow rate per block (as a percentage, and scaled by 1e18) 83 | */ 84 | function getBorrowRate(uint cash, uint borrows, uint reserves) external virtual view returns (uint); 85 | 86 | /** 87 | * @notice Calculates the current supply interest rate per block 88 | * @param cash The total amount of cash the market has 89 | * @param borrows The total amount of borrows the market has outstanding 90 | * @param reserves The total amount of reserves the market has 91 | * @param reserveFactorMantissa The current reserve factor the market has 92 | * @return The supply rate per block (as a percentage, and scaled by 1e18) 93 | */ 94 | function getSupplyRate(uint cash, uint borrows, uint reserves, uint reserveFactorMantissa) external virtual view returns (uint); 95 | } 96 | 97 | abstract contract CTokenStorage { 98 | /** 99 | * @dev Guard variable for re-entrancy checks 100 | */ 101 | bool internal _notEntered; 102 | 103 | /** 104 | * @notice EIP-20 token name for this token 105 | */ 106 | string public name; 107 | 108 | /** 109 | * @notice EIP-20 token symbol for this token 110 | */ 111 | string public symbol; 112 | 113 | /** 114 | * @notice EIP-20 token decimals for this token 115 | */ 116 | uint8 public decimals; 117 | 118 | /** 119 | * @notice Maximum borrow rate that can ever be applied (.0005% / block) 120 | */ 121 | 122 | uint internal constant borrowRateMaxMantissa = 0.0005e16; 123 | 124 | /** 125 | * @notice Maximum fraction of interest that can be set aside for reserves 126 | */ 127 | uint internal constant reserveFactorMaxMantissa = 1e18; 128 | 129 | /** 130 | * @notice Administrator for this contract 131 | */ 132 | address payable public admin; 133 | 134 | /** 135 | * @notice Pending administrator for this contract 136 | */ 137 | address payable public pendingAdmin; 138 | 139 | /** 140 | * @notice Contract which oversees inter-cToken operations 141 | */ 142 | ComptrollerInterface public comptroller; 143 | 144 | /** 145 | * @notice Model which tells what the current interest rate should be 146 | */ 147 | InterestRateModel public interestRateModel; 148 | 149 | /** 150 | * @notice Initial exchange rate used when minting the first CTokens (used when totalSupply = 0) 151 | */ 152 | uint internal initialExchangeRateMantissa; 153 | 154 | /** 155 | * @notice Fraction of interest currently set aside for reserves 156 | */ 157 | uint public reserveFactorMantissa; 158 | 159 | /** 160 | * @notice Block number that interest was last accrued at 161 | */ 162 | uint public accrualBlockNumber; 163 | 164 | /** 165 | * @notice Accumulator of the total earned interest rate since the opening of the market 166 | */ 167 | uint public borrowIndex; 168 | 169 | /** 170 | * @notice Total amount of outstanding borrows of the underlying in this market 171 | */ 172 | uint public totalBorrows; 173 | 174 | /** 175 | * @notice Total amount of reserves of the underlying held in this market 176 | */ 177 | uint public totalReserves; 178 | 179 | /** 180 | * @notice Total number of tokens in circulation 181 | */ 182 | uint public totalSupply; 183 | 184 | /** 185 | * @notice Official record of token balances for each account 186 | */ 187 | mapping (address => uint) internal accountTokens; 188 | 189 | /** 190 | * @notice Approved token transfer amounts on behalf of others 191 | */ 192 | mapping (address => mapping (address => uint)) internal transferAllowances; 193 | 194 | /** 195 | * @notice Container for borrow balance information 196 | * @member principal Total balance (with accrued interest), after applying the most recent balance-changing action 197 | * @member interestIndex Global borrowIndex as of the most recent balance-changing action 198 | */ 199 | struct BorrowSnapshot { 200 | uint principal; 201 | uint interestIndex; 202 | } 203 | 204 | /** 205 | * @notice Mapping of account addresses to outstanding borrow balances 206 | */ 207 | mapping(address => BorrowSnapshot) internal accountBorrows; 208 | } 209 | 210 | abstract contract CTokenInterface is CTokenStorage { 211 | /** 212 | * @notice Indicator that this is a CToken contract (for inspection) 213 | */ 214 | bool public constant isCToken = true; 215 | 216 | 217 | /*** Market Events ***/ 218 | 219 | /** 220 | * @notice Event emitted when interest is accrued 221 | */ 222 | event AccrueInterest(uint cashPrior, uint interestAccumulated, uint borrowIndex, uint totalBorrows); 223 | 224 | /** 225 | * @notice Event emitted when tokens are minted 226 | */ 227 | event Mint(address minter, uint mintAmount, uint mintTokens); 228 | 229 | /** 230 | * @notice Event emitted when tokens are redeemed 231 | */ 232 | event Redeem(address redeemer, uint redeemAmount, uint redeemTokens); 233 | 234 | /** 235 | * @notice Event emitted when underlying is borrowed 236 | */ 237 | event Borrow(address borrower, uint borrowAmount, uint accountBorrows, uint totalBorrows); 238 | 239 | /** 240 | * @notice Event emitted when a borrow is repaid 241 | */ 242 | event RepayBorrow(address payer, address borrower, uint repayAmount, uint accountBorrows, uint totalBorrows); 243 | 244 | /** 245 | * @notice Event emitted when a borrow is liquidated 246 | */ 247 | event LiquidateBorrow(address liquidator, address borrower, uint repayAmount, address cTokenCollateral, uint seizeTokens); 248 | 249 | 250 | /*** Admin Events ***/ 251 | 252 | /** 253 | * @notice Event emitted when pendingAdmin is changed 254 | */ 255 | event NewPendingAdmin(address oldPendingAdmin, address newPendingAdmin); 256 | 257 | /** 258 | * @notice Event emitted when pendingAdmin is accepted, which means admin is updated 259 | */ 260 | event NewAdmin(address oldAdmin, address newAdmin); 261 | 262 | /** 263 | * @notice Event emitted when comptroller is changed 264 | */ 265 | event NewComptroller(ComptrollerInterface oldComptroller, ComptrollerInterface newComptroller); 266 | 267 | /** 268 | * @notice Event emitted when interestRateModel is changed 269 | */ 270 | event NewMarketInterestRateModel(InterestRateModel oldInterestRateModel, InterestRateModel newInterestRateModel); 271 | 272 | /** 273 | * @notice Event emitted when the reserve factor is changed 274 | */ 275 | event NewReserveFactor(uint oldReserveFactorMantissa, uint newReserveFactorMantissa); 276 | 277 | /** 278 | * @notice Event emitted when the reserves are added 279 | */ 280 | event ReservesAdded(address benefactor, uint addAmount, uint newTotalReserves); 281 | 282 | /** 283 | * @notice Event emitted when the reserves are reduced 284 | */ 285 | event ReservesReduced(address admin, uint reduceAmount, uint newTotalReserves); 286 | 287 | /** 288 | * @notice EIP20 Transfer event 289 | */ 290 | event Transfer(address indexed from, address indexed to, uint amount); 291 | 292 | /** 293 | * @notice EIP20 Approval event 294 | */ 295 | event Approval(address indexed owner, address indexed spender, uint amount); 296 | 297 | /** 298 | * @notice Failure event 299 | */ 300 | event Failure(uint error, uint info, uint detail); 301 | 302 | 303 | /*** User Interface ***/ 304 | 305 | function transfer(address dst, uint amount) external virtual returns (bool); 306 | function transferFrom(address src, address dst, uint amount) external virtual returns (bool); 307 | function approve(address spender, uint amount) external virtual returns (bool); 308 | function allowance(address owner, address spender) external virtual view returns (uint); 309 | function balanceOf(address owner) external virtual view returns (uint); 310 | function balanceOfUnderlying(address owner) external virtual returns (uint); 311 | function getAccountSnapshot(address account) external virtual view returns (uint, uint, uint, uint); 312 | function borrowRatePerBlock() external virtual view returns (uint); 313 | function supplyRatePerBlock() external virtual view returns (uint); 314 | function totalBorrowsCurrent() external virtual returns (uint); 315 | function borrowBalanceCurrent(address account) external virtual returns (uint); 316 | function borrowBalanceStored(address account) public virtual view returns (uint); 317 | function exchangeRateCurrent() public virtual returns (uint); 318 | function exchangeRateStored() public virtual view returns (uint); 319 | function getCash() external virtual view returns (uint); 320 | function accrueInterest() public virtual returns (uint); 321 | function seize(address liquidator, address borrower, uint seizeTokens) external virtual returns (uint); 322 | 323 | 324 | /*** Admin Functions ***/ 325 | 326 | function _setPendingAdmin(address payable newPendingAdmin) external virtual returns (uint); 327 | function _acceptAdmin() external virtual returns (uint); 328 | function _setComptroller(ComptrollerInterface newComptroller) public virtual returns (uint); 329 | function _setReserveFactor(uint newReserveFactorMantissa) external virtual returns (uint); 330 | function _reduceReserves(uint reduceAmount) external virtual returns (uint); 331 | function _setInterestRateModel(InterestRateModel newInterestRateModel) public virtual returns (uint); 332 | } 333 | 334 | abstract contract CErc20Storage { 335 | /** 336 | * @notice Underlying asset for this CToken 337 | */ 338 | address public underlying; 339 | } 340 | 341 | abstract contract CErc20Interface is CErc20Storage { 342 | 343 | /*** User Interface ***/ 344 | 345 | function mint(uint mintAmount) external virtual returns (uint); 346 | function redeem(uint redeemTokens) external virtual returns (uint); 347 | function redeemUnderlying(uint redeemAmount) external virtual returns (uint); 348 | function borrow(uint borrowAmount) external virtual returns (uint); 349 | function repayBorrow(uint repayAmount) external virtual returns (uint); 350 | function repayBorrowBehalf(address borrower, uint repayAmount) external virtual returns (uint); 351 | function liquidateBorrow(address borrower, uint repayAmount, CTokenInterface cTokenCollateral) external virtual returns (uint); 352 | 353 | 354 | /*** Admin Functions ***/ 355 | 356 | function _addReserves(uint addAmount) external virtual returns (uint); 357 | } 358 | 359 | abstract contract CDelegationStorage { 360 | /** 361 | * @notice Implementation address for this contract 362 | */ 363 | address public implementation; 364 | } 365 | 366 | abstract contract CDelegatorInterface is CDelegationStorage { 367 | /** 368 | * @notice Emitted when implementation is changed 369 | */ 370 | event NewImplementation(address oldImplementation, address newImplementation); 371 | 372 | /** 373 | * @notice Called by the admin to update the implementation of the delegator 374 | * @param implementation_ The address of the new implementation for delegation 375 | * @param allowResign Flag to indicate whether to call _resignImplementation on the old implementation 376 | * @param becomeImplementationData The encoded bytes data to be passed to _becomeImplementation 377 | */ 378 | function _setImplementation(address implementation_, bool allowResign, bytes memory becomeImplementationData) public virtual; 379 | } 380 | 381 | abstract contract CDelegateInterface is CDelegationStorage { 382 | /** 383 | * @notice Called by the delegator on a delegate to initialize it for duty 384 | * @dev Should revert if any issues arise which make it unfit for delegation 385 | * @param data The encoded bytes data for any initialization 386 | */ 387 | function _becomeImplementation(bytes memory data) public virtual; 388 | 389 | /** 390 | * @notice Called by the delegator on a delegate to forfeit its responsibility 391 | */ 392 | function _resignImplementation() public virtual; 393 | } 394 | 395 | abstract contract CompoundEvents { 396 | event AccrueInterest(uint cashPrior, uint interestAccumulated, uint borrowIndex, uint totalBorrows); 397 | event ActionPaused(CToken cToken, string action, bool pauseState); 398 | event ActionPaused(string action, bool pauseState); 399 | event Approval(address indexed owner, address indexed spender, uint256 amount); 400 | event Borrow(address borrower, uint borrowAmount, uint accountBorrows, uint totalBorrows); 401 | event CancelTransaction(bytes32 indexed txHash, address indexed target, uint value, string signature, bytes data, uint eta); 402 | event CompSpeedUpdated(CToken indexed cToken, uint newSpeed); 403 | event DelegateChanged(address indexed delegator, address indexed fromDelegate, address indexed toDelegate); 404 | event DelegateVotesChanged(address indexed delegate, uint previousBalance, uint newBalance); 405 | event DistributedBorrowerComp(CToken indexed cToken, address indexed borrower, uint compDelta, uint compBorrowIndex); 406 | event DistributedSupplierComp(CToken indexed cToken, address indexed supplier, uint compDelta, uint compSupplyIndex); 407 | event ExecuteTransaction(bytes32 indexed txHash, address indexed target, uint value, string signature, bytes data, uint eta); 408 | event Failure(uint error, uint info, uint detail); 409 | event LiquidateBorrow(address liquidator, address borrower, uint repayAmount, address cTokenCollateral, uint seizeTokens); 410 | event MarketComped(CToken cToken, bool isComped); 411 | event MarketEntered(CToken cToken, address account); 412 | event MarketExited(CToken cToken, address account); 413 | event MarketListed(CToken cToken); 414 | event Mint(address minter, uint mintAmount, uint mintTokens); 415 | event NewAdmin(address indexed newAdmin); 416 | event NewAdmin(address oldAdmin, address newAdmin); 417 | event NewBorrowCap(CToken indexed cToken, uint newBorrowCap); 418 | event NewBorrowCapGuardian(address oldBorrowCapGuardian, address newBorrowCapGuardian); 419 | event NewCloseFactor(uint oldCloseFactorMantissa, uint newCloseFactorMantissa); 420 | event NewCollateralFactor(CToken cToken, uint oldCollateralFactorMantissa, uint newCollateralFactorMantissa); 421 | event NewCompRate(uint oldCompRate, uint newCompRate); 422 | event NewComptroller(ComptrollerInterface oldComptroller, ComptrollerInterface newComptroller); 423 | event NewDelay(uint indexed newDelay); 424 | event NewImplementation(address oldImplementation, address newImplementation); 425 | event NewInterestParams(uint baseRatePerBlock, uint multiplierPerBlock); 426 | event NewInterestParams(uint baseRatePerBlock, uint multiplierPerBlock, uint jumpMultiplierPerBlock, uint kink); 427 | event NewLiquidationIncentive(uint oldLiquidationIncentiveMantissa, uint newLiquidationIncentiveMantissa); 428 | event NewMarketInterestRateModel(InterestRateModel oldInterestRateModel, InterestRateModel newInterestRateModel); 429 | event NewMaxAssets(uint oldMaxAssets, uint newMaxAssets); 430 | event NewPauseGuardian(address oldPauseGuardian, address newPauseGuardian); 431 | event NewPendingAdmin(address indexed newPendingAdmin); 432 | event NewPendingAdmin(address oldPendingAdmin, address newPendingAdmin); 433 | event NewPendingImplementation(address oldPendingImplementation, address newPendingImplementation); 434 | event NewPriceOracle(PriceOracle oldPriceOracle, PriceOracle newPriceOracle); 435 | event NewReserveFactor(uint oldReserveFactorMantissa, uint newReserveFactorMantissa); 436 | event PricePosted(address asset, uint previousPriceMantissa, uint requestedPriceMantissa, uint newPriceMantissa); 437 | event ProposalCanceled(uint id); 438 | event ProposalCreated(uint id, address proposer, address[] targets, uint[] values, string[] signatures, bytes[] calldatas, uint startBlock, uint endBlock, string description); 439 | event ProposalExecuted(uint id); 440 | event ProposalQueued(uint id, uint eta); 441 | event QueueTransaction(bytes32 indexed txHash, address indexed target, uint value, string signature, bytes data, uint eta); 442 | event Redeem(address redeemer, uint redeemAmount, uint redeemTokens); 443 | event RepayBorrow(address payer, address borrower, uint repayAmount, uint accountBorrows, uint totalBorrows); 444 | event ReservesAdded(address benefactor, uint addAmount, uint newTotalReserves); 445 | event ReservesReduced(address admin, uint reduceAmount, uint newTotalReserves); 446 | event Transfer(address indexed from, address indexed to, uint256 amount); 447 | event VoteCast(address voter, uint proposalId, bool support, uint votes); 448 | } 449 | 450 | contract CToken {} 451 | contract PriceOracle {} 452 | -------------------------------------------------------------------------------- /src/pip.sol: -------------------------------------------------------------------------------- 1 | import "ds-thing/thing.sol"; 2 | import "ds-value/value.sol"; 3 | 4 | import "./crop.sol"; 5 | 6 | contract CropValue is DSThing { 7 | address constant PIP_USDC = 0x77b68899b99b686F415d074278a9a16b336085A0; 8 | address immutable join; 9 | constructor(address join_) public { 10 | join = join_; 11 | } 12 | function peek() public returns (bytes32, bool) { 13 | (bytes32 usdc_price, bool valid) = DSValue(PIP_USDC).peek(); 14 | return (bytes32(wmul(CropJoin(join).nps(), uint256(usdc_price))), valid); 15 | } 16 | function read() public returns (bytes32) { 17 | bytes32 wut; bool haz; 18 | (wut, haz) = peek(); 19 | require(haz, "haz-not"); 20 | return wut; 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/spell/DssSpell.sol: -------------------------------------------------------------------------------- 1 | // Copyright (C) Rain 2 | // Copyright (C) 2020 Maker Ecosystem Growth Holdings, INC. 3 | // 4 | // This program is free software: you can redistribute it and/or modify 5 | // it under the terms of the GNU Affero General Public License as published by 6 | // the Free Software Foundation, either version 3 of the License, or 7 | // (at your option) any later version. 8 | // 9 | // This program is distributed in the hope that it will be useful, 10 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | // GNU Affero General Public License for more details. 13 | // 14 | // You should have received a copy of the GNU Affero General Public License 15 | // along with this program. If not, see . 16 | 17 | import "lib/dss-interfaces/src/dapp/DSPauseAbstract.sol"; 18 | import "lib/dss-interfaces/src/dss/CatAbstract.sol"; 19 | import "lib/dss-interfaces/src/dss/FlipAbstract.sol"; 20 | import "lib/dss-interfaces/src/dss/FlipperMomAbstract.sol"; 21 | import "lib/dss-interfaces/src/dss/IlkRegistryAbstract.sol"; 22 | import "lib/dss-interfaces/src/dss/GemJoinAbstract.sol"; 23 | import "lib/dss-interfaces/src/dss/JugAbstract.sol"; 24 | import "lib/dss-interfaces/src/dss/MedianAbstract.sol"; 25 | import "lib/dss-interfaces/src/dss/OsmAbstract.sol"; 26 | import "lib/dss-interfaces/src/dss/OsmMomAbstract.sol"; 27 | import "lib/dss-interfaces/src/dss/SpotAbstract.sol"; 28 | import "lib/dss-interfaces/src/dss/VatAbstract.sol"; 29 | import "lib/dss-interfaces/src/dss/ChainlogAbstract.sol"; 30 | 31 | import "../pip.sol"; 32 | import "../wind.sol"; 33 | 34 | interface FlipFab { 35 | function newFlip(address,address,bytes32) external returns (address); 36 | } 37 | 38 | contract SpellAction { 39 | // MAINNET ADDRESSES 40 | // 41 | // The contracts in this list should correspond to MCD core contracts, verify 42 | // against the current release list at: 43 | // https://changelog.makerdao.com/releases/mainnet/1.1.4/contracts.json 44 | ChainlogAbstract constant CHANGELOG = ChainlogAbstract(0xdA0Ab1e0017DEbCd72Be8599041a2aa3bA7e740F); 45 | 46 | address constant FLIP_FAB = 0x4ACdbe9dd0d00b36eC2050E805012b8Fc9974f2b; 47 | 48 | address constant CUSDC = 0x39AA39c021dfbaE8faC545936693aC917d5E7563; 49 | address constant COMPTROLLER = 0x3d9819210A31b4961b30EF54bE2aeD79B9c9Cd3B; 50 | bytes32 constant ILK = "USDC-C"; 51 | 52 | // Decimals & precision 53 | uint256 constant THOUSAND = 10 ** 3; 54 | uint256 constant MILLION = 10 ** 6; 55 | uint256 constant WAD = 10 ** 18; 56 | uint256 constant RAY = 10 ** 27; 57 | uint256 constant RAD = 10 ** 45; 58 | 59 | // Many of the settings that change weekly rely on the rate accumulator 60 | // described at https://docs.makerdao.com/smart-contract-modules/rates-module 61 | // To check this yourself, use the following rate calculation (example 8%): 62 | // 63 | // $ bc -l <<< 'scale=27; e( l(1.08)/(60 * 60 * 24 * 365) )' 64 | // 65 | // A table of rates can be found at 66 | // https://ipfs.io/ipfs/QmefQMseb3AiTapiAKKexdKHig8wroKuZbmLtPLv4u2YwW 67 | uint256 constant FOUR_PERCENT_RATE = 1000000001243680656318820312; 68 | 69 | function execute() external { 70 | address MCD_VAT = CHANGELOG.getAddress("MCD_VAT"); 71 | address MCD_CAT = CHANGELOG.getAddress("MCD_CAT"); 72 | address MCD_JUG = CHANGELOG.getAddress("MCD_JUG"); 73 | address MCD_SPOT = CHANGELOG.getAddress("MCD_SPOT"); 74 | address MCD_END = CHANGELOG.getAddress("MCD_END"); 75 | address FLIPPER_MOM = CHANGELOG.getAddress("FLIPPER_MOM"); 76 | //address OSM_MOM = CHANGELOG.getAddress("OSM_MOM"); 77 | address ILK_REGISTRY = CHANGELOG.getAddress("ILK_REGISTRY"); 78 | address USDC = CHANGELOG.getAddress("USDC"); 79 | address COMP = CHANGELOG.getAddress("COMP"); 80 | 81 | // set up adapter, flipper and pip 82 | address MCD_USDC_C_STRATEGY = address(new CompStrat( USDC 83 | , CUSDC 84 | , COMP 85 | , COMPTROLLER 86 | )); 87 | address MCD_JOIN_USDC_C = address(new USDCJoin( MCD_VAT 88 | , ILK 89 | , USDC 90 | , COMP 91 | , MCD_USDC_C_STRATEGY 92 | )); 93 | address MCD_FLIP_USDC_C = FlipFab(FLIP_FAB).newFlip(MCD_VAT, MCD_CAT, ILK); 94 | address PIP_USDC_C = address(new CropValue(MCD_JOIN_USDC_C)); 95 | 96 | // Add USDC-C contracts to the changelog 97 | CHANGELOG.setAddress("CUSDC", CUSDC); 98 | CHANGELOG.setAddress("MCD_JOIN_USDC_C", MCD_JOIN_USDC_C); 99 | CHANGELOG.setAddress("MCD_FLIP_USDC_C", MCD_FLIP_USDC_C); 100 | CHANGELOG.setAddress("PIP_USDC_C", PIP_USDC_C); 101 | 102 | CHANGELOG.setVersion("1.1.5"); // TODO:?? 103 | 104 | // Sanity checks 105 | require(MCD_JOIN_USDC_C != 0x0000000000000000000000000000000000000000, "join not created"); 106 | require(MCD_FLIP_USDC_C != 0x0000000000000000000000000000000000000000, "flip not created"); 107 | require(PIP_USDC_C != 0x0000000000000000000000000000000000000000, "pip not created"); 108 | 109 | require(GemJoinAbstract(MCD_JOIN_USDC_C).vat() == MCD_VAT, "join-vat-not-match"); 110 | require(GemJoinAbstract(MCD_JOIN_USDC_C).ilk() == ILK, "join-ilk-not-match"); 111 | require(GemJoinAbstract(MCD_JOIN_USDC_C).gem() == USDC, "join-gem-not-match"); 112 | require(GemJoinAbstract(MCD_JOIN_USDC_C).dec() == 6, "join-dec-not-match"); 113 | require(FlipAbstract(MCD_FLIP_USDC_C).vat() == MCD_VAT, "flip-vat-not-match"); 114 | require(FlipAbstract(MCD_FLIP_USDC_C).cat() == MCD_CAT, "flip-cat-not-match"); 115 | require(FlipAbstract(MCD_FLIP_USDC_C).ilk() == ILK, "flip-ilk-not-match"); 116 | 117 | // Set the USDC-C PIP in the Spotter 118 | SpotAbstract(MCD_SPOT).file(ILK, "pip", PIP_USDC_C); 119 | 120 | // Set the USDC-C Flipper in the Cat 121 | CatAbstract(MCD_CAT).file(ILK, "flip", MCD_FLIP_USDC_C); 122 | 123 | // Init USDC-C ilk in Vat & Jug 124 | VatAbstract(MCD_VAT).init(ILK); 125 | JugAbstract(MCD_JUG).init(ILK); 126 | 127 | // Allow USDC-C Join to modify Vat registry 128 | VatAbstract(MCD_VAT).rely(MCD_JOIN_USDC_C); 129 | // Allow the USDC-C Flipper to reduce the Cat litterbox on deal() 130 | CatAbstract(MCD_CAT).rely(MCD_FLIP_USDC_C); 131 | // Allow Cat to kick auctions in USDC-C Flipper 132 | FlipAbstract(MCD_FLIP_USDC_C).rely(MCD_CAT); 133 | // Allow End to yank auctions in USDC-C Flipper 134 | FlipAbstract(MCD_FLIP_USDC_C).rely(MCD_END); 135 | // Allow FlipperMom to access to the USDC-C Flipper 136 | FlipAbstract(MCD_FLIP_USDC_C).rely(FLIPPER_MOM); 137 | // Disallow Cat to kick auctions in USDC-C Flipper 138 | // !!!!!!!! Only for certain collaterals that do not trigger liquidations like USDC-A) 139 | // TODO: disable auctions? 140 | FlipperMomAbstract(FLIPPER_MOM).deny(MCD_FLIP_USDC_C); 141 | 142 | // Set the global debt ceiling 143 | VatAbstract(MCD_VAT).file("Line", 1_468_750_000 * RAD); 144 | // Set the USDC-C debt ceiling 145 | VatAbstract(MCD_VAT).file(ILK, "line", 5 * MILLION * RAD); 146 | // Set the USDC-C dust 147 | VatAbstract(MCD_VAT).file(ILK, "dust", 100 * RAD); 148 | // Set the Lot size 149 | CatAbstract(MCD_CAT).file(ILK, "dunk", 50 * THOUSAND * RAD); 150 | // Set the USDC-C liquidation penalty (e.g. 13% => X = 113) 151 | CatAbstract(MCD_CAT).file(ILK, "chop", 113 * WAD / 100); 152 | // Set the USDC-C stability fee (e.g. 1% = 1000000000315522921573372069) 153 | JugAbstract(MCD_JUG).file(ILK, "duty", FOUR_PERCENT_RATE); 154 | // Set the USDC-C percentage between bids (e.g. 3% => X = 103) 155 | FlipAbstract(MCD_FLIP_USDC_C).file("beg", 103 * WAD / 100); 156 | // Set the USDC-C time max time between bids 157 | FlipAbstract(MCD_FLIP_USDC_C).file("ttl", 6 hours); 158 | // Set the USDC-C max auction duration to 159 | FlipAbstract(MCD_FLIP_USDC_C).file("tau", 6 hours); 160 | // Set the USDC-C min collateralization ratio (e.g. 150% => X = 150) 161 | SpotAbstract(MCD_SPOT).file(ILK, "mat", 101 * RAY / 100); 162 | 163 | // Update USDC-C spot value in Vat 164 | SpotAbstract(MCD_SPOT).poke(ILK); 165 | 166 | // Add new ilk to the IlkRegistry 167 | IlkRegistryAbstract(ILK_REGISTRY).add(MCD_JOIN_USDC_C); 168 | } 169 | } 170 | 171 | contract DssSpell { 172 | DSPauseAbstract public pause = 173 | DSPauseAbstract(0xbE286431454714F511008713973d3B053A2d38f3); 174 | address public action; 175 | bytes32 public tag; 176 | uint256 public eta; 177 | bytes public sig; 178 | uint256 public expiration; 179 | bool public done; 180 | 181 | // Provides a descriptive tag for bot consumption 182 | // This should be modified weekly to provide a summary of the actions 183 | // Hash: seth keccak -- "$(wget https://raw.githubusercontent.com/makerdao/community/a67032a357000839ae08c7523abcf9888c8cca3a/governance/votes/Executive%20vote%20-%20November%2013%2C%202020.md -q -O - 2>/dev/null)" 184 | string constant public description = 185 | "2020-xx-yy MakerDAO Executive Spell | Hash: 0xTODO"; 186 | 187 | constructor() public { 188 | sig = abi.encodeWithSignature("execute()"); 189 | action = address(new SpellAction()); 190 | bytes32 _tag; 191 | address _action = action; 192 | assembly { _tag := extcodehash(_action) } 193 | tag = _tag; 194 | expiration = now + 30 days; 195 | } 196 | 197 | modifier officeHours { 198 | uint day = (now / 1 days + 3) % 7; 199 | require(day < 5, "Can only be cast on a weekday"); 200 | uint hour = now / 1 hours % 24; 201 | require(hour >= 14 && hour < 21, "Outside office hours"); 202 | _; 203 | } 204 | 205 | function schedule() public { 206 | require(now <= expiration, "This contract has expired"); 207 | require(eta == 0, "This spell has already been scheduled"); 208 | eta = now + DSPauseAbstract(pause).delay(); 209 | pause.plot(action, tag, sig, eta); 210 | } 211 | 212 | function cast() public { 213 | require(!done, "spell-already-cast"); 214 | done = true; 215 | pause.exec(action, tag, sig, eta); 216 | } 217 | } 218 | -------------------------------------------------------------------------------- /src/spell/DssSpell.t.sol: -------------------------------------------------------------------------------- 1 | import "ds-math/math.sol"; 2 | import "ds-test/test.sol"; 3 | import "lib/dss-interfaces/src/Interfaces.sol"; 4 | import "./test/rates.sol"; 5 | 6 | import {DssSpell, SpellAction} from "./DssSpell.sol"; 7 | 8 | interface Hevm { 9 | function warp(uint256) external; 10 | function store(address,bytes32,bytes32) external; 11 | } 12 | 13 | interface MedianizerV1Abstract { 14 | function authority() external view returns (address); 15 | function owner() external view returns (address); 16 | function peek() external view returns (uint256, bool); 17 | function poke() external; 18 | } 19 | 20 | contract DssSpellTest is DSTest, DSMath { 21 | // populate with mainnet spell if needed 22 | address constant MAINNET_SPELL = address(0xa24311446583f22432b335B53282CF8ecbfBC7A9); 23 | // this needs to be updated 24 | uint256 constant SPELL_CREATED = 1605283866; 25 | 26 | struct CollateralValues { 27 | uint256 line; 28 | uint256 dust; 29 | uint256 chop; 30 | uint256 dunk; 31 | uint256 pct; 32 | uint256 mat; 33 | uint256 beg; 34 | uint48 ttl; 35 | uint48 tau; 36 | uint256 liquidations; 37 | } 38 | 39 | struct SystemValues { 40 | uint256 pot_dsr; 41 | uint256 vat_Line; 42 | uint256 pause_delay; 43 | uint256 vow_wait; 44 | uint256 vow_dump; 45 | uint256 vow_sump; 46 | uint256 vow_bump; 47 | uint256 vow_hump; 48 | uint256 cat_box; 49 | address osm_mom_authority; 50 | address flipper_mom_authority; 51 | uint256 ilk_count; 52 | mapping (bytes32 => CollateralValues) collaterals; 53 | } 54 | 55 | SystemValues afterSpell; 56 | 57 | Hevm hevm; 58 | Rates rates; 59 | 60 | // MAINNET ADDRESSES 61 | DSPauseAbstract pause = DSPauseAbstract( 0xbE286431454714F511008713973d3B053A2d38f3); 62 | address pauseProxy = 0xBE8E3e3618f7474F8cB1d074A26afFef007E98FB; 63 | DSChiefAbstract chief = DSChiefAbstract( 0x9eF05f7F6deB616fd37aC3c959a2dDD25A54E4F5); 64 | VatAbstract vat = VatAbstract( 0x35D1b3F3D7966A1DFe207aa4514C12a259A0492B); 65 | VowAbstract vow = VowAbstract( 0xA950524441892A31ebddF91d3cEEFa04Bf454466); 66 | CatAbstract cat = CatAbstract( 0xa5679C04fc3d9d8b0AaB1F0ab83555b301cA70Ea); 67 | PotAbstract pot = PotAbstract( 0x197E90f9FAD81970bA7976f33CbD77088E5D7cf7); 68 | JugAbstract jug = JugAbstract( 0x19c0976f590D67707E62397C87829d896Dc0f1F1); 69 | SpotAbstract spot = SpotAbstract( 0x65C79fcB50Ca1594B025960e539eD7A9a6D434A3); 70 | 71 | DSTokenAbstract gov = DSTokenAbstract( 0x9f8F72aA9304c8B593d555F12eF6589cC3A579A2); 72 | EndAbstract end = EndAbstract( 0xaB14d3CE3F733CACB76eC2AbE7d2fcb00c99F3d5); 73 | IlkRegistryAbstract reg = IlkRegistryAbstract(0x8b4ce5DCbb01e0e1f0521cd8dCfb31B308E52c24); 74 | 75 | OsmMomAbstract osmMom = OsmMomAbstract( 0x76416A4d5190d071bfed309861527431304aA14f); 76 | FlipperMomAbstract flipMom = FlipperMomAbstract( 0xc4bE7F74Ee3743bDEd8E0fA218ee5cf06397f472); 77 | 78 | ChainlogAbstract chainlog = ChainlogAbstract( 0xdA0Ab1e0017DEbCd72Be8599041a2aa3bA7e740F); 79 | 80 | address makerDeployer06 = 0xda0fab060e6cc7b1C0AA105d29Bd50D71f036711; 81 | 82 | // GUSD-A specific 83 | DSTokenAbstract gusd = DSTokenAbstract( 0x056Fd409E1d7A124BD7017459dFEa2F387b6d5Cd); 84 | DSTokenAbstract gusd_store = DSTokenAbstract( 0xc42B14e49744538e3C239f8ae48A1Eaaf35e68a0); 85 | GemJoinAbstract joinGUSDA = GemJoinAbstract( 0xe29A14bcDeA40d83675aa43B72dF07f649738C8b); 86 | OsmAbstract pipGUSD = OsmAbstract( 0xf45Ae69CcA1b9B043dAE2C83A5B65Bc605BEc5F5); 87 | FlipAbstract flipGUSDA = FlipAbstract( 0xCAa8D152A8b98229fB77A213BE16b234cA4f612f); 88 | //MedianAbstract medGUSDA = MedianAbstract( 0); 89 | 90 | 91 | DssSpell spell; 92 | 93 | // CHEAT_CODE = 0x7109709ECfa91a80626fF3989D68f67F5b1DD12D 94 | bytes20 constant CHEAT_CODE = 95 | bytes20(uint160(uint256(keccak256('hevm cheat code')))); 96 | 97 | uint256 constant HUNDRED = 10 ** 2; 98 | uint256 constant THOUSAND = 10 ** 3; 99 | uint256 constant MILLION = 10 ** 6; 100 | uint256 constant BILLION = 10 ** 9; 101 | // uint256 constant WAD = 10 ** 18; // defined in DSMath 102 | // uint256 constant RAY = 10 ** 27; // defined in DSMath 103 | uint256 constant RAD = 10 ** 45; 104 | 105 | event Debug(uint256 index, uint256 val); 106 | event Debug(uint256 index, address addr); 107 | event Debug(uint256 index, bytes32 what); 108 | 109 | // not provided in DSMath 110 | function rpow(uint256 x, uint256 n, uint256 b) internal pure returns (uint256 z) { 111 | assembly { 112 | switch x case 0 {switch n case 0 {z := b} default {z := 0}} 113 | default { 114 | switch mod(n, 2) case 0 { z := b } default { z := x } 115 | let half := div(b, 2) // for rounding. 116 | for { n := div(n, 2) } n { n := div(n,2) } { 117 | let xx := mul(x, x) 118 | if iszero(eq(div(xx, x), x)) { revert(0,0) } 119 | let xxRound := add(xx, half) 120 | if lt(xxRound, xx) { revert(0,0) } 121 | x := div(xxRound, b) 122 | if mod(n,2) { 123 | let zx := mul(z, x) 124 | if and(iszero(iszero(x)), iszero(eq(div(zx, x), z))) { revert(0,0) } 125 | let zxRound := add(zx, half) 126 | if lt(zxRound, zx) { revert(0,0) } 127 | z := div(zxRound, b) 128 | } 129 | } 130 | } 131 | } 132 | } 133 | // 10^-5 (tenth of a basis point) as a RAY 134 | uint256 TOLERANCE = 10 ** 22; 135 | 136 | function yearlyYield(uint256 duty) public pure returns (uint256) { 137 | return rpow(duty, (365 * 24 * 60 *60), RAY); 138 | } 139 | 140 | function expectedRate(uint256 percentValue) public pure returns (uint256) { 141 | return (10000 + percentValue) * (10 ** 23); 142 | } 143 | 144 | function diffCalc(uint256 expectedRate_, uint256 yearlyYield_) public pure returns (uint256) { 145 | return (expectedRate_ > yearlyYield_) ? expectedRate_ - yearlyYield_ : yearlyYield_ - expectedRate_; 146 | } 147 | 148 | function setUp() public { 149 | hevm = Hevm(address(CHEAT_CODE)); 150 | rates = new Rates(); 151 | 152 | spell = MAINNET_SPELL != address(0) ? DssSpell(MAINNET_SPELL) : new DssSpell(); 153 | 154 | // 155 | // Test for all system configuration changes 156 | // 157 | afterSpell = SystemValues({ 158 | pot_dsr: 0, // In basis points 159 | vat_Line: 146875 * MILLION / 100, // In whole Dai units 160 | pause_delay: 72 hours, // In seconds 161 | vow_wait: 156 hours, // In seconds 162 | vow_dump: 250, // In whole Dai units 163 | vow_sump: 50000, // In whole Dai units 164 | vow_bump: 10000, // In whole Dai units 165 | vow_hump: 4 * MILLION, // In whole Dai units 166 | cat_box: 15 * MILLION, // In whole Dai units 167 | osm_mom_authority: address(0), // OsmMom authority 168 | flipper_mom_authority: address(0), // FlipperMom authority 169 | ilk_count: 18 // Num expected in system 170 | }); 171 | 172 | // 173 | // Test for all collateral based changes here 174 | // 175 | afterSpell.collaterals["ETH-A"] = CollateralValues({ 176 | line: 490 * MILLION, // In whole Dai units 177 | dust: 100, // In whole Dai units 178 | pct: 200, // In basis points 179 | chop: 1300, // In basis points 180 | dunk: 50 * THOUSAND, // In whole Dai units 181 | mat: 15000, // In basis points 182 | beg: 300, // In basis points 183 | ttl: 6 hours, // In seconds 184 | tau: 6 hours, // In seconds 185 | liquidations: 1 // 1 if enabled 186 | }); 187 | afterSpell.collaterals["ETH-B"] = CollateralValues({ 188 | line: 10 * MILLION, 189 | dust: 100, 190 | pct: 400, 191 | chop: 1300, 192 | dunk: 50 * THOUSAND, 193 | mat: 13000, 194 | beg: 300, 195 | ttl: 6 hours, 196 | tau: 6 hours, 197 | liquidations: 1 198 | }); 199 | afterSpell.collaterals["BAT-A"] = CollateralValues({ 200 | line: 10 * MILLION, 201 | dust: 100, 202 | pct: 400, 203 | chop: 1300, 204 | dunk: 50 * THOUSAND, 205 | mat: 15000, 206 | beg: 300, 207 | ttl: 6 hours, 208 | tau: 6 hours, 209 | liquidations: 1 210 | }); 211 | afterSpell.collaterals["USDC-A"] = CollateralValues({ 212 | line: 485 * MILLION, 213 | dust: 100, 214 | pct: 400, 215 | chop: 1300, 216 | dunk: 50 * THOUSAND, 217 | mat: 10100, 218 | beg: 300, 219 | ttl: 6 hours, 220 | tau: 3 days, 221 | liquidations: 0 222 | }); 223 | afterSpell.collaterals["USDC-B"] = CollateralValues({ 224 | line: 30 * MILLION, 225 | dust: 100, 226 | pct: 5000, 227 | chop: 1300, 228 | dunk: 50 * THOUSAND, 229 | mat: 12000, 230 | beg: 300, 231 | ttl: 6 hours, 232 | tau: 3 days, 233 | liquidations: 0 234 | }); 235 | afterSpell.collaterals["WBTC-A"] = CollateralValues({ 236 | line: 160 * MILLION, 237 | dust: 100, 238 | pct: 400, 239 | chop: 1300, 240 | dunk: 50 * THOUSAND, 241 | mat: 15000, 242 | beg: 300, 243 | ttl: 6 hours, 244 | tau: 6 hours, 245 | liquidations: 1 246 | }); 247 | afterSpell.collaterals["TUSD-A"] = CollateralValues({ 248 | line: 135 * MILLION, 249 | dust: 100, 250 | pct: 400, 251 | chop: 1300, 252 | dunk: 50 * THOUSAND, 253 | mat: 10100, 254 | beg: 300, 255 | ttl: 6 hours, 256 | tau: 3 days, 257 | liquidations: 0 258 | }); 259 | afterSpell.collaterals["KNC-A"] = CollateralValues({ 260 | line: 5 * MILLION, 261 | dust: 100, 262 | pct: 400, 263 | chop: 1300, 264 | dunk: 50 * THOUSAND, 265 | mat: 17500, 266 | beg: 300, 267 | ttl: 6 hours, 268 | tau: 6 hours, 269 | liquidations: 1 270 | }); 271 | afterSpell.collaterals["ZRX-A"] = CollateralValues({ 272 | line: 5 * MILLION, 273 | dust: 100, 274 | pct: 400, 275 | chop: 1300, 276 | dunk: 50 * THOUSAND, 277 | mat: 17500, 278 | beg: 300, 279 | ttl: 6 hours, 280 | tau: 6 hours, 281 | liquidations: 1 282 | }); 283 | afterSpell.collaterals["MANA-A"] = CollateralValues({ 284 | line: 250 * THOUSAND, 285 | dust: 100, 286 | pct: 1200, 287 | chop: 1300, 288 | dunk: 50 * THOUSAND, 289 | mat: 17500, 290 | beg: 300, 291 | ttl: 6 hours, 292 | tau: 6 hours, 293 | liquidations: 1 294 | }); 295 | afterSpell.collaterals["USDT-A"] = CollateralValues({ 296 | line: 25 * MILLION / 10, 297 | dust: 100, 298 | pct: 800, 299 | chop: 1300, 300 | dunk: 50 * THOUSAND, 301 | mat: 15000, 302 | beg: 300, 303 | ttl: 6 hours, 304 | tau: 6 hours, 305 | liquidations: 1 306 | }); 307 | afterSpell.collaterals["PAXUSD-A"] = CollateralValues({ 308 | line: 100 * MILLION, 309 | dust: 100, 310 | pct: 400, 311 | chop: 1300, 312 | dunk: 50 * THOUSAND, 313 | mat: 10100, 314 | beg: 300, 315 | ttl: 6 hours, 316 | tau: 6 hours, 317 | liquidations: 0 318 | }); 319 | afterSpell.collaterals["COMP-A"] = CollateralValues({ 320 | line: 7 * MILLION, 321 | dust: 100, 322 | pct: 300, 323 | chop: 1300, 324 | dunk: 50 * THOUSAND, 325 | mat: 17500, 326 | beg: 300, 327 | ttl: 6 hours, 328 | tau: 6 hours, 329 | liquidations: 1 330 | }); 331 | afterSpell.collaterals["LRC-A"] = CollateralValues({ 332 | line: 3 * MILLION, 333 | dust: 100, 334 | pct: 300, 335 | chop: 1300, 336 | dunk: 50 * THOUSAND, 337 | mat: 17500, 338 | beg: 300, 339 | ttl: 6 hours, 340 | tau: 6 hours, 341 | liquidations: 1 342 | }); 343 | afterSpell.collaterals["LINK-A"] = CollateralValues({ 344 | line: 10 * MILLION, 345 | dust: 100, 346 | pct: 200, 347 | chop: 1300, 348 | dunk: 50 * THOUSAND, 349 | mat: 17500, 350 | beg: 300, 351 | ttl: 6 hours, 352 | tau: 6 hours, 353 | liquidations: 1 354 | }); 355 | afterSpell.collaterals["BAL-A"] = CollateralValues({ 356 | line: 4 * MILLION, 357 | dust: 100, 358 | pct: 500, 359 | chop: 1300, 360 | dunk: 50000, 361 | mat: 17500, 362 | beg: 300, 363 | ttl: 6 hours, 364 | tau: 6 hours, 365 | liquidations: 1 366 | }); 367 | afterSpell.collaterals["YFI-A"] = CollateralValues({ 368 | line: 7 * MILLION, 369 | dust: 100, 370 | pct: 400, 371 | chop: 1300, 372 | dunk: 50000, 373 | mat: 17500, 374 | beg: 300, 375 | ttl: 6 hours, 376 | tau: 6 hours, 377 | liquidations: 1 378 | }); 379 | afterSpell.collaterals["GUSD-A"] = CollateralValues({ 380 | line: 5 * MILLION, 381 | dust: 100, 382 | pct: 400, 383 | chop: 1300, 384 | dunk: 50000, 385 | mat: 10100, 386 | beg: 300, 387 | ttl: 6 hours, 388 | tau: 6 hours, 389 | liquidations: 0 390 | }); 391 | } 392 | 393 | function scheduleWaitAndCastFailDay() public { 394 | spell.schedule(); 395 | 396 | uint256 castTime = now + pause.delay(); 397 | uint256 day = (castTime / 1 days + 3) % 7; 398 | if (day < 5) { 399 | castTime += 5 days - day * 86400; 400 | } 401 | 402 | hevm.warp(castTime); 403 | spell.cast(); 404 | } 405 | 406 | function scheduleWaitAndCastFailEarly() public { 407 | spell.schedule(); 408 | 409 | uint256 castTime = now + pause.delay() + 24 hours; 410 | uint256 hour = castTime / 1 hours % 24; 411 | if (hour >= 14) { 412 | castTime -= hour * 3600 - 13 hours; 413 | } 414 | 415 | hevm.warp(castTime); 416 | spell.cast(); 417 | } 418 | 419 | function scheduleWaitAndCastFailLate() public { 420 | spell.schedule(); 421 | 422 | uint256 castTime = now + pause.delay(); 423 | uint256 hour = castTime / 1 hours % 24; 424 | if (hour < 21) { 425 | castTime += 21 hours - hour * 3600; 426 | } 427 | 428 | hevm.warp(castTime); 429 | spell.cast(); 430 | } 431 | 432 | function vote() private { 433 | if (chief.hat() != address(spell)) { 434 | hevm.store( 435 | address(gov), 436 | keccak256(abi.encode(address(this), uint256(1))), 437 | bytes32(uint256(999999999999 ether)) 438 | ); 439 | gov.approve(address(chief), uint256(-1)); 440 | chief.lock(sub(gov.balanceOf(address(this)), 1 ether)); 441 | 442 | assertTrue(!spell.done()); 443 | 444 | address[] memory yays = new address[](1); 445 | yays[0] = address(spell); 446 | 447 | chief.vote(yays); 448 | chief.lift(address(spell)); 449 | } 450 | assertEq(chief.hat(), address(spell)); 451 | } 452 | 453 | function scheduleWaitAndCast() public { 454 | spell.schedule(); 455 | 456 | uint256 castTime = now + pause.delay(); 457 | 458 | uint256 day = (castTime / 1 days + 3) % 7; 459 | if(day >= 5) { 460 | castTime += 7 days - day * 86400; 461 | } 462 | 463 | uint256 hour = castTime / 1 hours % 24; 464 | if (hour >= 21) { 465 | castTime += 24 hours - hour * 3600 + 14 hours; 466 | } else if (hour < 14) { 467 | castTime += 14 hours - hour * 3600; 468 | } 469 | 470 | hevm.warp(castTime); 471 | spell.cast(); 472 | } 473 | 474 | function stringToBytes32(string memory source) public pure returns (bytes32 result) { 475 | assembly { 476 | result := mload(add(source, 32)) 477 | } 478 | } 479 | 480 | function checkSystemValues(SystemValues storage values) internal { 481 | // dsr 482 | uint256 expectedDSRRate = rates.rates(values.pot_dsr); 483 | // make sure dsr is less than 100% APR 484 | // bc -l <<< 'scale=27; e( l(2.00)/(60 * 60 * 24 * 365) )' 485 | // 1000000021979553151239153027 486 | assertTrue( 487 | pot.dsr() >= RAY && pot.dsr() < 1000000021979553151239153027 488 | ); 489 | assertTrue(diffCalc(expectedRate(values.pot_dsr), yearlyYield(expectedDSRRate)) <= TOLERANCE); 490 | 491 | { 492 | // Line values in RAD 493 | uint256 normalizedLine = values.vat_Line * RAD; 494 | assertEq(vat.Line(), normalizedLine); 495 | assertTrue( 496 | (vat.Line() >= RAD && vat.Line() < 100 * BILLION * RAD) || 497 | vat.Line() == 0 498 | ); 499 | } 500 | 501 | // Pause delay 502 | assertEq(pause.delay(), values.pause_delay); 503 | 504 | // wait 505 | assertEq(vow.wait(), values.vow_wait); 506 | 507 | { 508 | // dump values in WAD 509 | uint256 normalizedDump = values.vow_dump * WAD; 510 | assertEq(vow.dump(), normalizedDump); 511 | assertTrue( 512 | (vow.dump() >= WAD && vow.dump() < 2 * THOUSAND * WAD) || 513 | vow.dump() == 0 514 | ); 515 | } 516 | { 517 | // sump values in RAD 518 | uint256 normalizedSump = values.vow_sump * RAD; 519 | assertEq(vow.sump(), normalizedSump); 520 | assertTrue( 521 | (vow.sump() >= RAD && vow.sump() < 500 * THOUSAND * RAD) || 522 | vow.sump() == 0 523 | ); 524 | } 525 | { 526 | // bump values in RAD 527 | uint normalizedBump = values.vow_bump * RAD; 528 | assertEq(vow.bump(), normalizedBump); 529 | assertTrue( 530 | (vow.bump() >= RAD && vow.bump() < HUNDRED * THOUSAND * RAD) || 531 | vow.bump() == 0 532 | ); 533 | } 534 | { 535 | // hump values in RAD 536 | uint256 normalizedHump = values.vow_hump * RAD; 537 | assertEq(vow.hump(), normalizedHump); 538 | assertTrue( 539 | (vow.hump() >= RAD && vow.hump() < HUNDRED * MILLION * RAD) || 540 | vow.hump() == 0 541 | ); 542 | } 543 | 544 | // box values in RAD 545 | { 546 | uint256 normalizedBox = values.cat_box * RAD; 547 | assertEq(cat.box(), normalizedBox); 548 | } 549 | 550 | // check OsmMom authority 551 | assertEq(osmMom.authority(), values.osm_mom_authority); 552 | 553 | // check FlipperMom authority 554 | assertEq(flipMom.authority(), values.flipper_mom_authority); 555 | 556 | // check number of ilks 557 | assertEq(reg.count(), values.ilk_count); 558 | } 559 | 560 | function checkCollateralValues(SystemValues storage values) internal { 561 | uint256 sumlines; 562 | bytes32[] memory ilks = reg.list(); 563 | for(uint256 i = 0; i < ilks.length; i++) { 564 | bytes32 ilk = ilks[i]; 565 | (uint256 duty,) = jug.ilks(ilk); 566 | 567 | assertEq(duty, rates.rates(values.collaterals[ilk].pct)); 568 | // make sure duty is less than 1000% APR 569 | // bc -l <<< 'scale=27; e( l(10.00)/(60 * 60 * 24 * 365) )' 570 | // 1000000073014496989316680335 571 | assertTrue(duty >= RAY && duty < 1000000073014496989316680335); // gt 0 and lt 1000% 572 | assertTrue(diffCalc(expectedRate(values.collaterals[ilk].pct), yearlyYield(rates.rates(values.collaterals[ilk].pct))) <= TOLERANCE); 573 | assertTrue(values.collaterals[ilk].pct < THOUSAND * THOUSAND); // check value lt 1000% 574 | { 575 | (,,, uint256 line, uint256 dust) = vat.ilks(ilk); 576 | // Convert whole Dai units to expected RAD 577 | uint256 normalizedTestLine = values.collaterals[ilk].line * RAD; 578 | sumlines += values.collaterals[ilk].line; 579 | assertEq(line, normalizedTestLine); 580 | assertTrue((line >= RAD && line < BILLION * RAD) || line == 0); // eq 0 or gt eq 1 RAD and lt 1B 581 | uint256 normalizedTestDust = values.collaterals[ilk].dust * RAD; 582 | assertEq(dust, normalizedTestDust); 583 | assertTrue((dust >= RAD && dust < 10 * THOUSAND * RAD) || dust == 0); // eq 0 or gt eq 1 and lt 10k 584 | } 585 | { 586 | (, uint256 chop, uint256 dunk) = cat.ilks(ilk); 587 | // Convert BP to system expected value 588 | uint256 normalizedTestChop = (values.collaterals[ilk].chop * 10**14) + WAD; 589 | assertEq(chop, normalizedTestChop); 590 | // make sure chop is less than 100% 591 | assertTrue(chop >= WAD && chop < 2 * WAD); // penalty gt eq 0% and lt 100% 592 | // Convert whole Dai units to expected RAD 593 | uint256 normalizedTestDunk = values.collaterals[ilk].dunk * RAD; 594 | assertEq(dunk, normalizedTestDunk); 595 | // put back in after LIQ-1.2 596 | assertTrue(dunk >= RAD && dunk < MILLION * RAD); 597 | } 598 | { 599 | (,uint256 mat) = spot.ilks(ilk); 600 | // Convert BP to system expected value 601 | uint256 normalizedTestMat = (values.collaterals[ilk].mat * 10**23); 602 | assertEq(mat, normalizedTestMat); 603 | assertTrue(mat >= RAY && mat < 10 * RAY); // cr eq 100% and lt 1000% 604 | } 605 | { 606 | (address flipper,,) = cat.ilks(ilk); 607 | FlipAbstract flip = FlipAbstract(flipper); 608 | // Convert BP to system expected value 609 | uint256 normalizedTestBeg = (values.collaterals[ilk].beg + 10000) * 10**14; 610 | assertEq(uint256(flip.beg()), normalizedTestBeg); 611 | assertTrue(flip.beg() >= WAD && flip.beg() < 105 * WAD / 100); // gt eq 0% and lt 5% 612 | assertEq(uint256(flip.ttl()), values.collaterals[ilk].ttl); 613 | assertTrue(flip.ttl() >= 600 && flip.ttl() < 10 hours); // gt eq 10 minutes and lt 10 hours 614 | assertEq(uint256(flip.tau()), values.collaterals[ilk].tau); 615 | assertTrue(flip.tau() >= 600 && flip.tau() <= 3 days); // gt eq 10 minutes and lt eq 3 days 616 | 617 | assertEq(flip.wards(address(cat)), values.collaterals[ilk].liquidations); // liquidations == 1 => on 618 | assertEq(flip.wards(address(makerDeployer06)), 0); // Check deployer denied 619 | assertEq(flip.wards(address(pauseProxy)), 1); // Check pause_proxy ward 620 | } 621 | { 622 | GemJoinAbstract join = GemJoinAbstract(reg.join(ilk)); 623 | assertEq(join.wards(address(makerDeployer06)), 0); // Check deployer denied 624 | assertEq(join.wards(address(pauseProxy)), 1); // Check pause_proxy ward 625 | } 626 | } 627 | assertEq(sumlines, values.vat_Line); 628 | } 629 | 630 | function testFailWrongDay() public { 631 | vote(); 632 | scheduleWaitAndCastFailDay(); 633 | } 634 | 635 | function testFailTooEarly() public { 636 | vote(); 637 | scheduleWaitAndCastFailEarly(); 638 | } 639 | 640 | function testFailTooLate() public { 641 | vote(); 642 | scheduleWaitAndCastFailLate(); 643 | } 644 | 645 | function testSpellIsCast() public { 646 | string memory description = new DssSpell().description(); 647 | assertTrue(bytes(description).length > 0); 648 | // DS-Test can't handle strings directly, so cast to a bytes32. 649 | assertEq(stringToBytes32(spell.description()), 650 | stringToBytes32(description)); 651 | 652 | if(address(spell) != address(MAINNET_SPELL)) { 653 | assertEq(spell.expiration(), (now + 30 days)); 654 | } else { 655 | assertEq(spell.expiration(), (SPELL_CREATED + 30 days)); 656 | } 657 | 658 | vote(); 659 | scheduleWaitAndCast(); 660 | assertTrue(spell.done()); 661 | 662 | checkSystemValues(afterSpell); 663 | 664 | checkCollateralValues(afterSpell); 665 | } 666 | 667 | function testChainlogValues() public { 668 | vote(); 669 | scheduleWaitAndCast(); 670 | assertTrue(spell.done()); 671 | 672 | assertEq(chainlog.getAddress("FLIP_FAB"), 0x4ACdbe9dd0d00b36eC2050E805012b8Fc9974f2b); 673 | assertEq(chainlog.getAddress("GUSD"), 0x056Fd409E1d7A124BD7017459dFEa2F387b6d5Cd); 674 | assertEq(chainlog.getAddress("MCD_JOIN_GUSD_A"), 0xe29A14bcDeA40d83675aa43B72dF07f649738C8b); 675 | assertEq(chainlog.getAddress("MCD_FLIP_GUSD_A"), 0xCAa8D152A8b98229fB77A213BE16b234cA4f612f); 676 | assertEq(chainlog.getAddress("PIP_GUSD"), 0xf45Ae69CcA1b9B043dAE2C83A5B65Bc605BEc5F5); 677 | } 678 | 679 | function testSpellIsCast_GUSD_INTEGRATION() public { 680 | vote(); 681 | scheduleWaitAndCast(); 682 | assertTrue(spell.done()); 683 | 684 | //pipGUSD.poke(); 685 | hevm.warp(now + 3601); 686 | //pipGUSD.poke(); 687 | spot.poke("GUSD-A"); 688 | 689 | // Add balance to the test address 690 | uint256 ilkAmt = 1 * THOUSAND * 100; // GUSD has 2 decimals 691 | uint256 ilkAmt18 = ilkAmt * 10**16; 692 | 693 | hevm.store( 694 | address(gusd_store), 695 | keccak256(abi.encode(address(this), uint256(6))), 696 | bytes32(ilkAmt) 697 | ); 698 | assertEq(gusd.balanceOf(address(this)), ilkAmt); 699 | 700 | // Check median matches pip.src() 701 | //assertEq(pipGUSD.src(), address(medGUSDA)); 702 | 703 | // Authorization 704 | assertEq(joinGUSDA.wards(pauseProxy), 1); 705 | assertEq(vat.wards(address(joinGUSDA)), 1); 706 | assertEq(flipGUSDA.wards(address(end)), 1); 707 | assertEq(flipGUSDA.wards(address(flipMom)), 1); 708 | //assertEq(pipGUSD.wards(address(osmMom)), 1); 709 | //assertEq(pipGUSD.bud(address(spot)), 1); 710 | //assertEq(pipGUSD.bud(address(end)), 1); 711 | //assertEq(MedianAbstract(pipGUSD.src()).bud(address(pipGUSD)), 1); 712 | 713 | // Join to adapter 714 | assertEq(vat.gem("GUSD-A", address(this)), 0); 715 | gusd.approve(address(joinGUSDA), ilkAmt); 716 | joinGUSDA.join(address(this), ilkAmt); 717 | assertEq(gusd.balanceOf(address(this)), 0); 718 | assertEq(vat.gem("GUSD-A", address(this)), ilkAmt18); 719 | 720 | // Deposit collateral, generate DAI 721 | assertEq(vat.dai(address(this)), 0); 722 | vat.frob("GUSD-A", address(this), address(this), address(this), int(ilkAmt18), int(100 * WAD)); 723 | assertEq(vat.gem("GUSD-A", address(this)), 0); 724 | assertEq(vat.dai(address(this)), 100 * RAD); 725 | 726 | // Payback DAI, withdraw collateral 727 | vat.frob("GUSD-A", address(this), address(this), address(this), -int(ilkAmt18), -int(100 * WAD)); 728 | assertEq(vat.gem("GUSD-A", address(this)), ilkAmt18); 729 | assertEq(vat.dai(address(this)), 0); 730 | 731 | // Withdraw from adapter 732 | joinGUSDA.exit(address(this), ilkAmt); 733 | assertEq(gusd.balanceOf(address(this)), ilkAmt); 734 | assertEq(vat.gem("GUSD-A", address(this)), 0); 735 | 736 | // Generate new DAI to force a liquidation 737 | gusd.approve(address(joinGUSDA), ilkAmt); 738 | joinGUSDA.join(address(this), ilkAmt); 739 | (,,uint256 spotV,,) = vat.ilks("GUSD-A"); 740 | // dart max amount of DAI 741 | vat.frob("GUSD-A", address(this), address(this), address(this), int(ilkAmt18), int(mul(ilkAmt18, spotV) / RAY)); 742 | hevm.warp(now + 1); 743 | jug.drip("GUSD-A"); 744 | assertEq(flipGUSDA.kicks(), 0); 745 | //cat.bite("GUSD-A", address(this)); // liquidatons off 746 | //assertEq(flipGUSDA.kicks(), 1); 747 | } 748 | 749 | function testFail_GUSD_liquidations() public { 750 | vote(); 751 | scheduleWaitAndCast(); 752 | assertTrue(spell.done()); 753 | 754 | //pipGUSD.poke(); 755 | hevm.warp(now + 3601); 756 | //pipGUSD.poke(); 757 | spot.poke("GUSD-A"); 758 | 759 | // Add balance to the test address 760 | uint256 ilkAmt = 1 * THOUSAND * 100; // GUSD has 2 decimals 761 | uint256 ilkAmt18 = ilkAmt * 10**16; 762 | 763 | hevm.store( 764 | address(gusd_store), 765 | keccak256(abi.encode(address(this), uint256(6))), 766 | bytes32(ilkAmt) 767 | ); 768 | assertEq(gusd.balanceOf(address(this)), ilkAmt); 769 | 770 | // Check median matches pip.src() 771 | //assertEq(pipGUSD.src(), address(medGUSDA)); 772 | 773 | // Authorization 774 | assertEq(joinGUSDA.wards(pauseProxy), 1); 775 | assertEq(vat.wards(address(joinGUSDA)), 1); 776 | assertEq(flipGUSDA.wards(address(end)), 1); 777 | assertEq(flipGUSDA.wards(address(flipMom)), 1); 778 | 779 | 780 | // Generate new DAI to force a liquidation 781 | gusd.approve(address(joinGUSDA), ilkAmt); 782 | joinGUSDA.join(address(this), ilkAmt); 783 | (,,uint256 spotV,,) = vat.ilks("GUSD-A"); 784 | // dart max amount of DAI 785 | vat.frob("GUSD-A", address(this), address(this), address(this), int(ilkAmt18), int(mul(ilkAmt18, spotV) / RAY)); 786 | hevm.warp(now + 1); 787 | jug.drip("GUSD-A"); 788 | assertEq(flipGUSDA.kicks(), 0); 789 | 790 | cat.bite("GUSD-A", address(this)); // fail here 791 | } 792 | } 793 | -------------------------------------------------------------------------------- /src/spell/test/rates.sol: -------------------------------------------------------------------------------- 1 | contract Rates { 2 | 3 | mapping (uint256 => uint256) public rates; 4 | 5 | constructor() public { 6 | rates[0] = 1000000000000000000000000000; 7 | rates[25] = 1000000000079175551708715274; 8 | rates[50] = 1000000000158153903837946257; 9 | rates[75] = 1000000000236936036262880196; 10 | rates[100] = 1000000000315522921573372069; 11 | rates[125] = 1000000000393915525145987602; 12 | rates[150] = 1000000000472114805215157978; 13 | rates[175] = 1000000000550121712943459312; 14 | rates[200] = 1000000000627937192491029810; 15 | rates[225] = 1000000000705562181084137268; 16 | rates[250] = 1000000000782997609082909351; 17 | rates[275] = 1000000000860244400048238898; 18 | rates[300] = 1000000000937303470807876289; 19 | rates[325] = 1000000001014175731521720677; 20 | rates[350] = 1000000001090862085746321732; 21 | rates[375] = 1000000001167363430498603315; 22 | rates[400] = 1000000001243680656318820312; 23 | rates[425] = 1000000001319814647332759691; 24 | rates[450] = 1000000001395766281313196627; 25 | rates[475] = 1000000001471536429740616381; 26 | rates[500] = 1000000001547125957863212448; 27 | rates[525] = 1000000001622535724756171269; 28 | rates[550] = 1000000001697766583380253701; 29 | rates[575] = 1000000001772819380639683201; 30 | rates[600] = 1000000001847694957439350562; 31 | rates[625] = 1000000001922394148741344865; 32 | rates[650] = 1000000001996917783620820123; 33 | rates[675] = 1000000002071266685321207000; 34 | rates[700] = 1000000002145441671308778766; 35 | rates[725] = 1000000002219443553326580536; 36 | rates[750] = 1000000002293273137447730714; 37 | rates[775] = 1000000002366931224128103346; 38 | rates[800] = 1000000002440418608258400030; 39 | rates[825] = 1000000002513736079215619839; 40 | rates[850] = 1000000002586884420913935572; 41 | rates[875] = 1000000002659864411854984565; 42 | rates[900] = 1000000002732676825177582095; 43 | rates[925] = 1000000002805322428706865331; 44 | rates[950] = 1000000002877801985002875644; 45 | rates[975] = 1000000002950116251408586949; 46 | rates[1000] = 1000000003022265980097387650; 47 | rates[1025] = 1000000003094251918120023627; 48 | rates[1050] = 1000000003166074807451009595; 49 | rates[1075] = 1000000003237735385034516037; 50 | rates[1100] = 1000000003309234382829738808; 51 | rates[1125] = 1000000003380572527855758393; 52 | rates[1150] = 1000000003451750542235895695; 53 | rates[1175] = 1000000003522769143241571114; 54 | rates[1200] = 1000000003593629043335673582; 55 | rates[1225] = 1000000003664330950215446102; 56 | rates[1250] = 1000000003734875566854894261; 57 | rates[1275] = 1000000003805263591546724039; 58 | rates[1300] = 1000000003875495717943815211; 59 | rates[1325] = 1000000003945572635100236468; 60 | rates[1350] = 1000000004015495027511808328; 61 | rates[1375] = 1000000004085263575156219812; 62 | rates[1400] = 1000000004154878953532704765; 63 | rates[1425] = 1000000004224341833701283597; 64 | rates[1450] = 1000000004293652882321576158; 65 | rates[1475] = 1000000004362812761691191350; 66 | rates[1500] = 1000000004431822129783699001; 67 | rates[1525] = 1000000004500681640286189459; 68 | rates[1550] = 1000000004569391942636426248; 69 | rates[1575] = 1000000004637953682059597074; 70 | rates[1600] = 1000000004706367499604668374; 71 | rates[1625] = 1000000004774634032180348552; 72 | rates[1650] = 1000000004842753912590664903; 73 | rates[1675] = 1000000004910727769570159235; 74 | rates[1700] = 1000000004978556227818707070; 75 | rates[1725] = 1000000005046239908035965222; 76 | rates[1750] = 1000000005113779426955452540; 77 | rates[1775] = 1000000005181175397378268462; 78 | rates[1800] = 1000000005248428428206454010; 79 | rates[1825] = 1000000005315539124475999751; 80 | rates[1850] = 1000000005382508087389505206; 81 | rates[1875] = 1000000005449335914348494113; 82 | rates[1900] = 1000000005516023198985389892; 83 | rates[1925] = 1000000005582570531195155575; 84 | rates[1950] = 1000000005648978497166602432; 85 | rates[1975] = 1000000005715247679413371444; 86 | rates[2000] = 1000000005781378656804591712; 87 | rates[2025] = 1000000005847372004595219844; 88 | rates[2050] = 1000000005913228294456064283; 89 | rates[2075] = 1000000005978948094503498507; 90 | rates[2100] = 1000000006044531969328866955; 91 | rates[2125] = 1000000006109980480027587488; 92 | rates[2150] = 1000000006175294184227954125; 93 | rates[2175] = 1000000006240473636119643770; 94 | rates[2200] = 1000000006305519386481930552; 95 | rates[2225] = 1000000006370431982711611382; 96 | rates[2250] = 1000000006435211968850646270; 97 | rates[2275] = 1000000006499859885613516871; 98 | rates[2300] = 1000000006564376270414306730; 99 | rates[2325] = 1000000006628761657393506584; 100 | rates[2350] = 1000000006693016577444548094; 101 | rates[2375] = 1000000006757141558240069277; 102 | rates[2400] = 1000000006821137124257914908; 103 | rates[2425] = 1000000006885003796806875073; 104 | rates[2450] = 1000000006948742094052165050; 105 | rates[2475] = 1000000007012352531040649627; 106 | rates[2500] = 1000000007075835619725814915; 107 | rates[2525] = 1000000007139191868992490695; 108 | rates[2550] = 1000000007202421784681326287; 109 | rates[2575] = 1000000007265525869613022867; 110 | rates[2600] = 1000000007328504623612325153; 111 | rates[2625] = 1000000007391358543531775311; 112 | rates[2650] = 1000000007454088123275231904; 113 | rates[2675] = 1000000007516693853821156670; 114 | rates[2700] = 1000000007579176223245671878; 115 | rates[2725] = 1000000007641535716745390957; 116 | rates[2750] = 1000000007703772816660025079; 117 | rates[2775] = 1000000007765888002494768329; 118 | rates[2800] = 1000000007827881750942464045; 119 | rates[2825] = 1000000007889754535905554913; 120 | rates[2850] = 1000000007951506828517819323; 121 | rates[2875] = 1000000008013139097165896490; 122 | rates[2900] = 1000000008074651807510602798; 123 | rates[2925] = 1000000008136045422508041783; 124 | rates[2950] = 1000000008197320402430510158; 125 | rates[2975] = 1000000008258477204887202245; 126 | rates[3000] = 1000000008319516284844715115; 127 | rates[3025] = 1000000008380438094647356774; 128 | rates[3050] = 1000000008441243084037259619; 129 | rates[3075] = 1000000008501931700174301437; 130 | rates[3100] = 1000000008562504387655836125; 131 | rates[3125] = 1000000008622961588536236324; 132 | rates[3150] = 1000000008683303742346250114; 133 | rates[3175] = 1000000008743531286112173869; 134 | rates[3200] = 1000000008803644654374843395; 135 | rates[3225] = 1000000008863644279208445392; 136 | rates[3250] = 1000000008923530590239151272; 137 | rates[3275] = 1000000008983304014663575373; 138 | rates[3300] = 1000000009042964977267059505; 139 | rates[3325] = 1000000009102513900441785827; 140 | rates[3350] = 1000000009161951204204719966; 141 | rates[3375] = 1000000009221277306215386279; 142 | rates[3400] = 1000000009280492621793477151; 143 | rates[3425] = 1000000009339597563936298181; 144 | rates[3450] = 1000000009398592543336051086; 145 | rates[3475] = 1000000009457477968396956129; 146 | rates[3500] = 1000000009516254245252215861; 147 | rates[3525] = 1000000009574921777780821942; 148 | rates[3550] = 1000000009633480967624206760; 149 | rates[3575] = 1000000009691932214202741592; 150 | rates[3600] = 1000000009750275914732082986; 151 | rates[3625] = 1000000009808512464239369028; 152 | rates[3650] = 1000000009866642255579267166; 153 | rates[3675] = 1000000009924665679449875210; 154 | rates[3700] = 1000000009982583124408477109; 155 | rates[3725] = 1000000010040394976887155106; 156 | rates[3750] = 1000000010098101621208259840; 157 | rates[3775] = 1000000010155703439599739931; 158 | rates[3800] = 1000000010213200812210332586; 159 | rates[3825] = 1000000010270594117124616733; 160 | rates[3850] = 1000000010327883730377930177; 161 | rates[3875] = 1000000010385070025971152244; 162 | rates[3900] = 1000000010442153375885353361; 163 | rates[3925] = 1000000010499134150096313024; 164 | rates[3950] = 1000000010556012716588907553; 165 | rates[3975] = 1000000010612789441371369043; 166 | rates[4000] = 1000000010669464688489416886; 167 | rates[4025] = 1000000010726038820040263233; 168 | rates[4050] = 1000000010782512196186493739; 169 | rates[4075] = 1000000010838885175169824929; 170 | rates[4100] = 1000000010895158113324739488; 171 | rates[4125] = 1000000010951331365092000772; 172 | rates[4150] = 1000000011007405283032047846; 173 | rates[4175] = 1000000011063380217838272275; 174 | rates[4200] = 1000000011119256518350177948; 175 | rates[4225] = 1000000011175034531566425160; 176 | rates[4250] = 1000000011230714602657760176; 177 | rates[4275] = 1000000011286297074979831462; 178 | rates[4300] = 1000000011341782290085893805; 179 | rates[4325] = 1000000011397170587739401474; 180 | rates[4350] = 1000000011452462305926491579; 181 | rates[4375] = 1000000011507657780868358802; 182 | rates[4400] = 1000000011562757347033522598; 183 | rates[4425] = 1000000011617761337149988016; 184 | rates[4450] = 1000000011672670082217301219; 185 | rates[4475] = 1000000011727483911518500818; 186 | rates[4500] = 1000000011782203152631966084; 187 | rates[4525] = 1000000011836828131443163102; 188 | rates[4550] = 1000000011891359172156289942; 189 | rates[4575] = 1000000011945796597305821848; 190 | rates[4600] = 1000000012000140727767957524; 191 | rates[4625] = 1000000012054391882771967477; 192 | rates[4650] = 1000000012108550379911445472; 193 | rates[4675] = 1000000012162616535155464050; 194 | rates[4700] = 1000000012216590662859635112; 195 | rates[4725] = 1000000012270473075777076530; 196 | rates[4750] = 1000000012324264085069285747; 197 | rates[4775] = 1000000012377964000316921287; 198 | rates[4800] = 1000000012431573129530493155; 199 | rates[4825] = 1000000012485091779160962996; 200 | rates[4850] = 1000000012538520254110254976; 201 | rates[4875] = 1000000012591858857741678240; 202 | rates[4900] = 1000000012645107891890261872; 203 | rates[4925] = 1000000012698267656873003228; 204 | rates[4950] = 1000000012751338451499030498; 205 | rates[4975] = 1000000012804320573079680371; 206 | rates[5000] = 1000000012857214317438491659; 207 | rates[5025] = 1000000012910019978921115695; 208 | rates[5050] = 1000000012962737850405144363; 209 | rates[5075] = 1000000013015368223309856554; 210 | rates[5100] = 1000000013067911387605883890; 211 | rates[5125] = 1000000013120367631824796485; 212 | rates[5150] = 1000000013172737243068609553; 213 | rates[5175] = 1000000013225020507019211652; 214 | rates[5200] = 1000000013277217707947715318; 215 | rates[5225] = 1000000013329329128723730871; 216 | rates[5250] = 1000000013381355050824564143; 217 | rates[5275] = 1000000013433295754344338876; 218 | rates[5300] = 1000000013485151518003044532; 219 | rates[5325] = 1000000013536922619155510237; 220 | rates[5350] = 1000000013588609333800305597; 221 | rates[5375] = 1000000013640211936588569081; 222 | rates[5400] = 1000000013691730700832764691; 223 | rates[5425] = 1000000013743165898515367617; 224 | rates[5450] = 1000000013794517800297479554; 225 | rates[5475] = 1000000013845786675527374380; 226 | rates[5500] = 1000000013896972792248974855; 227 | rates[5525] = 1000000013948076417210261020; 228 | rates[5550] = 1000000013999097815871610946; 229 | rates[5575] = 1000000014050037252414074493; 230 | rates[5600] = 1000000014100894989747580713; 231 | rates[5625] = 1000000014151671289519079548; 232 | rates[5650] = 1000000014202366412120618444; 233 | rates[5675] = 1000000014252980616697354502; 234 | rates[5700] = 1000000014303514161155502800; 235 | rates[5725] = 1000000014353967302170221464; 236 | rates[5750] = 1000000014404340295193434124; 237 | rates[5775] = 1000000014454633394461590334; 238 | rates[5800] = 1000000014504846853003364537; 239 | rates[5825] = 1000000014554980922647294184; 240 | rates[5850] = 1000000014605035854029357558; 241 | rates[5875] = 1000000014655011896600491882; 242 | rates[5900] = 1000000014704909298634052283; 243 | rates[5925] = 1000000014754728307233212158; 244 | rates[5950] = 1000000014804469168338305494; 245 | rates[5975] = 1000000014854132126734111701; 246 | rates[6000] = 1000000014903717426057083481; 247 | rates[6025] = 1000000014953225308802518272; 248 | rates[6050] = 1000000015002656016331673799; 249 | rates[6075] = 1000000015052009788878828253; 250 | rates[6100] = 1000000015101286865558285606; 251 | rates[6125] = 1000000015150487484371326590; 252 | rates[6150] = 1000000015199611882213105818; 253 | rates[6175] = 1000000015248660294879495575; 254 | rates[6200] = 1000000015297632957073876761; 255 | rates[6225] = 1000000015346530102413877471; 256 | rates[6250] = 1000000015395351963438059699; 257 | rates[6275] = 1000000015444098771612554646; 258 | rates[6300] = 1000000015492770757337647112; 259 | rates[6325] = 1000000015541368149954309419; 260 | rates[6350] = 1000000015589891177750685357; 261 | rates[6375] = 1000000015638340067968524580; 262 | rates[6400] = 1000000015686715046809567945; 263 | rates[6425] = 1000000015735016339441884188; 264 | rates[6450] = 1000000015783244170006158447; 265 | rates[6475] = 1000000015831398761621933006; 266 | rates[6500] = 1000000015879480336393800741; 267 | rates[6525] = 1000000015927489115417551681; 268 | rates[6550] = 1000000015975425318786273105; 269 | rates[6575] = 1000000016023289165596403599; 270 | rates[6600] = 1000000016071080873953741499; 271 | rates[6625] = 1000000016118800660979408115; 272 | rates[6650] = 1000000016166448742815766155; 273 | rates[6675] = 1000000016214025334632293755; 274 | rates[6700] = 1000000016261530650631414500; 275 | rates[6725] = 1000000016308964904054283846; 276 | rates[6750] = 1000000016356328307186532328; 277 | rates[6775] = 1000000016403621071363965932; 278 | rates[6800] = 1000000016450843406978224029; 279 | rates[6825] = 1000000016497995523482395247; 280 | rates[6850] = 1000000016545077629396591637; 281 | rates[6875] = 1000000016592089932313481533; 282 | rates[6900] = 1000000016639032638903781446; 283 | rates[6925] = 1000000016685905954921707380; 284 | rates[6950] = 1000000016732710085210385903; 285 | rates[6975] = 1000000016779445233707225354; 286 | rates[7000] = 1000000016826111603449247521; 287 | rates[7025] = 1000000016872709396578380147; 288 | rates[7050] = 1000000016919238814346710603; 289 | rates[7075] = 1000000016965700057121701072; 290 | rates[7100] = 1000000017012093324391365593; 291 | rates[7125] = 1000000017058418814769409273; 292 | rates[7150] = 1000000017104676726000330021; 293 | rates[7175] = 1000000017150867254964483131; 294 | rates[7200] = 1000000017196990597683109018; 295 | rates[7225] = 1000000017243046949323324453; 296 | rates[7250] = 1000000017289036504203077600; 297 | rates[7275] = 1000000017334959455796067168; 298 | rates[7300] = 1000000017380815996736626004; 299 | rates[7325] = 1000000017426606318824569415; 300 | rates[7350] = 1000000017472330613030008543; 301 | rates[7375] = 1000000017517989069498129080; 302 | rates[7400] = 1000000017563581877553935633; 303 | rates[7425] = 1000000017609109225706962029; 304 | rates[7450] = 1000000017654571301655947851; 305 | rates[7475] = 1000000017699968292293481503; 306 | rates[7500] = 1000000017745300383710610088; 307 | rates[7525] = 1000000017790567761201416374; 308 | rates[7550] = 1000000017835770609267563142; 309 | rates[7575] = 1000000017880909111622805195; 310 | rates[7600] = 1000000017925983451197469286; 311 | rates[7625] = 1000000017970993810142902264; 312 | rates[7650] = 1000000018015940369835887686; 313 | rates[7675] = 1000000018060823310883031179; 314 | rates[7700] = 1000000018105642813125114801; 315 | rates[7725] = 1000000018150399055641420686; 316 | rates[7750] = 1000000018195092216754024201; 317 | rates[7775] = 1000000018239722474032056911; 318 | rates[7800] = 1000000018284290004295939569; 319 | rates[7825] = 1000000018328794983621585414; 320 | rates[7850] = 1000000018373237587344574003; 321 | rates[7875] = 1000000018417617990064295840; 322 | rates[7900] = 1000000018461936365648068049; 323 | rates[7925] = 1000000018506192887235221305; 324 | rates[7950] = 1000000018550387727241158310; 325 | rates[7975] = 1000000018594521057361384012; 326 | rates[8000] = 1000000018638593048575507813; 327 | rates[8025] = 1000000018682603871151218019; 328 | rates[8050] = 1000000018726553694648228732; 329 | rates[8075] = 1000000018770442687922199432; 330 | rates[8100] = 1000000018814271019128627481; 331 | rates[8125] = 1000000018858038855726713746; 332 | rates[8150] = 1000000018901746364483201594; 333 | rates[8175] = 1000000018945393711476189463; 334 | rates[8200] = 1000000018988981062098917230; 335 | rates[8225] = 1000000019032508581063526585; 336 | rates[8250] = 1000000019075976432404795643; 337 | rates[8275] = 1000000019119384779483847985; 338 | rates[8300] = 1000000019162733784991836346; 339 | rates[8325] = 1000000019206023610953601168; 340 | rates[8350] = 1000000019249254418731304205; 341 | rates[8375] = 1000000019292426369028037391; 342 | rates[8400] = 1000000019335539621891407188; 343 | rates[8425] = 1000000019378594336717094581; 344 | rates[8450] = 1000000019421590672252390959; 345 | rates[8475] = 1000000019464528786599710033; 346 | rates[8500] = 1000000019507408837220076029; 347 | rates[8525] = 1000000019550230980936588320; 348 | rates[8550] = 1000000019592995373937862689; 349 | rates[8575] = 1000000019635702171781449432; 350 | rates[8600] = 1000000019678351529397228463; 351 | rates[8625] = 1000000019720943601090781625; 352 | rates[8650] = 1000000019763478540546742376; 353 | rates[8675] = 1000000019805956500832123050; 354 | rates[8700] = 1000000019848377634399619849; 355 | rates[8725] = 1000000019890742093090895767; 356 | rates[8750] = 1000000019933050028139841613; 357 | rates[8775] = 1000000019975301590175815296; 358 | rates[8800] = 1000000020017496929226859581; 359 | rates[8825] = 1000000020059636194722898437; 360 | rates[8850] = 1000000020101719535498912200; 361 | rates[8875] = 1000000020143747099798091677; 362 | rates[8900] = 1000000020185719035274971385; 363 | rates[8925] = 1000000020227635488998542076; 364 | rates[8950] = 1000000020269496607455342719; 365 | rates[8975] = 1000000020311302536552532106; 366 | rates[9000] = 1000000020353053421620940223; 367 | rates[9025] = 1000000020394749407418099573; 368 | rates[9050] = 1000000020436390638131256590; 369 | rates[9075] = 1000000020477977257380363298; 370 | rates[9100] = 1000000020519509408221049399; 371 | rates[9125] = 1000000020560987233147574896; 372 | rates[9150] = 1000000020602410874095763456; 373 | rates[9175] = 1000000020643780472445916617; 374 | rates[9200] = 1000000020685096169025709028; 375 | rates[9225] = 1000000020726358104113064837; 376 | rates[9250] = 1000000020767566417439015395; 377 | rates[9275] = 1000000020808721248190538424; 378 | rates[9300] = 1000000020849822735013378765; 379 | rates[9325] = 1000000020890871016014850891; 380 | rates[9350] = 1000000020931866228766623286; 381 | rates[9375] = 1000000020972808510307484860; 382 | rates[9400] = 1000000021013697997146093523; 383 | rates[9425] = 1000000021054534825263707061; 384 | rates[9450] = 1000000021095319130116896449; 385 | rates[9475] = 1000000021136051046640241741; 386 | rates[9500] = 1000000021176730709249010667; 387 | rates[9525] = 1000000021217358251841820063; 388 | rates[9550] = 1000000021257933807803280285; 389 | rates[9575] = 1000000021298457510006622716; 390 | rates[9600] = 1000000021338929490816310513; 391 | rates[9625] = 1000000021379349882090632705; 392 | rates[9650] = 1000000021419718815184281790; 393 | rates[9675] = 1000000021460036420950914938; 394 | rates[9700] = 1000000021500302829745698932; 395 | rates[9725] = 1000000021540518171427838973; 396 | rates[9750] = 1000000021580682575363091474; 397 | rates[9775] = 1000000021620796170426260951; 398 | rates[9800] = 1000000021660859085003681151; 399 | rates[9825] = 1000000021700871446995680519; 400 | rates[9850] = 1000000021740833383819032127; 401 | rates[9875] = 1000000021780745022409388199; 402 | rates[9900] = 1000000021820606489223699321; 403 | rates[9925] = 1000000021860417910242618463; 404 | rates[9950] = 1000000021900179410972889943; 405 | rates[9975] = 1000000021939891116449723415; 406 | rates[10000] = 1000000021979553151239153027; 407 | } 408 | 409 | } 410 | -------------------------------------------------------------------------------- /src/test/base.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.6.7; 2 | pragma experimental ABIEncoderV2; 3 | 4 | import "ds-test/test.sol"; 5 | 6 | interface Hevm { 7 | function warp(uint256) external; 8 | function roll(uint256) external; 9 | function store(address,bytes32,bytes32) external; 10 | function load(address,bytes32) external returns (bytes32); 11 | } 12 | 13 | contract CanCall { 14 | function try_call(address addr, bytes calldata data) external returns (bool) { 15 | bytes memory _data = data; 16 | assembly { 17 | let ok := call(gas(), addr, 0, add(_data, 0x20), mload(_data), 0, 0) 18 | let free := mload(0x40) 19 | mstore(free, ok) 20 | mstore(0x40, add(free, 32)) 21 | revert(free, 32) 22 | } 23 | } 24 | function can_call(address addr, bytes memory data) internal returns (bool) { 25 | (bool ok, bytes memory success) = address(this).call( 26 | abi.encodeWithSignature( 27 | "try_call(address,bytes)" 28 | , addr 29 | , data 30 | )); 31 | ok = abi.decode(success, (bool)); 32 | if (ok) return true; 33 | } 34 | } 35 | 36 | contract TestBase is DSTest { 37 | Hevm hevm = Hevm(HEVM_ADDRESS); 38 | 39 | function add(uint x, uint y) public pure returns (uint z) { 40 | require((z = x + y) >= x, "ds-math-add-overflow"); 41 | } 42 | function sub(uint x, uint y) public pure returns (uint z) { 43 | require((z = x - y) <= x, "ds-math-sub-underflow"); 44 | } 45 | function mul(uint x, uint y) public pure returns (uint z) { 46 | require(y == 0 || (z = x * y) / y == x, "ds-math-mul-overflow"); 47 | } 48 | uint256 constant WAD = 10 ** 18; 49 | function wmul(uint x, uint y) public pure returns (uint z) { 50 | z = mul(x, y) / WAD; 51 | } 52 | function wdiv(uint x, uint y) public pure returns (uint z) { 53 | z = mul(x, WAD) / y; 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /src/test/crop.t.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.6.7; 2 | pragma experimental ABIEncoderV2; 3 | 4 | import "./base.sol"; 5 | 6 | import "../wind.sol"; 7 | 8 | contract MockVat is VatLike { 9 | mapping (bytes32 => mapping (address => uint)) public override gem; 10 | function urns(bytes32,address) external override returns (Urn memory) { 11 | return Urn(0, 0); 12 | } 13 | function add(uint x, int y) internal pure returns (uint z) { 14 | z = x + uint(y); 15 | require(y >= 0 || z <= x, "vat/add-fail"); 16 | require(y <= 0 || z >= x, "vat/add-fail"); 17 | } 18 | function add(uint x, uint y) internal pure returns (uint z) { 19 | require((z = x + y) >= x, "vat/add-fail"); 20 | } 21 | function sub(uint x, uint y) internal pure returns (uint z) { 22 | require((z = x - y) <= x, "vat/sub-fail"); 23 | } 24 | function slip(bytes32 ilk, address usr, int256 wad) external override { 25 | gem[ilk][usr] = add(gem[ilk][usr], wad); 26 | } 27 | function flux(bytes32 ilk, address src, address dst, uint256 wad) external override { 28 | gem[ilk][src] = sub(gem[ilk][src], wad); 29 | gem[ilk][dst] = add(gem[ilk][dst], wad); 30 | } 31 | function hope(address usr) external {} 32 | } 33 | 34 | contract Token { 35 | uint8 public decimals; 36 | mapping (address => uint) public balanceOf; 37 | mapping (address => mapping (address => uint)) public allowance; 38 | constructor(uint8 dec, uint wad) public { 39 | decimals = dec; 40 | balanceOf[msg.sender] = wad; 41 | } 42 | function transfer(address usr, uint wad) public returns (bool) { 43 | require(balanceOf[msg.sender] >= wad, "transfer/insufficient"); 44 | balanceOf[msg.sender] -= wad; 45 | balanceOf[usr] += wad; 46 | return true; 47 | } 48 | function transferFrom(address src, address dst, uint wad) public returns (bool) { 49 | require(balanceOf[src] >= wad, "transferFrom/insufficient"); 50 | balanceOf[src] -= wad; 51 | balanceOf[dst] += wad; 52 | return true; 53 | } 54 | function mint(address dst, uint wad) public returns (uint) { 55 | balanceOf[dst] += wad; 56 | } 57 | function approve(address usr, uint wad) public returns (bool) { 58 | } 59 | function mint(uint wad) public returns (uint) { 60 | mint(msg.sender, wad); 61 | } 62 | } 63 | 64 | contract cToken is Token { 65 | constructor(uint8 dec, uint wad) Token(dec, wad) public {} 66 | function underlying() public returns (address a) {} 67 | function balanceOfUnderlying(address owner) external returns (uint) {} 68 | function borrowBalanceStored(address account) external view returns (uint) {} 69 | function borrowBalanceCurrent(address account) external view returns (uint) {} 70 | function accrueInterest() external returns (uint) {} 71 | function seize(address liquidator, address borrower, uint seizeTokens) external returns (uint) {} 72 | function liquidateBorrow(address borrower, uint repayAmount, CToken cTokenCollateral) external returns (uint) {} 73 | function getAccountLiquidity(address account) public view returns (uint, uint, uint) {} 74 | function exchangeRateCurrent() external returns (uint) {} 75 | function borrowIndex() external returns (uint) {} 76 | } 77 | 78 | contract Troll { 79 | Token comp; 80 | constructor(address comp_) public { 81 | comp = Token(comp_); 82 | } 83 | mapping (address => uint) public compAccrued; 84 | function reward(address usr, uint wad) public { 85 | compAccrued[usr] += wad; 86 | } 87 | function claimComp(address[] memory, address[] memory, bool, bool) public { 88 | comp.mint(msg.sender, compAccrued[msg.sender]); 89 | compAccrued[msg.sender] = 0; 90 | } 91 | function claimComp() public { 92 | comp.mint(msg.sender, compAccrued[msg.sender]); 93 | compAccrued[msg.sender] = 0; 94 | } 95 | function enterMarkets(address[] memory ctokens) public returns (uint[] memory) { 96 | comp; ctokens; 97 | uint[] memory err = new uint[](1); 98 | err[0] = 0; 99 | return err; 100 | } 101 | function compBorrowerIndex(address c, address b) public returns (uint) {} 102 | function mintAllowed(address ctoken, address minter, uint256 mintAmount) public returns (uint) {} 103 | function getBlockNumber() public view returns (uint) { 104 | return block.number; 105 | } 106 | function getAccountLiquidity(address) external returns (uint,uint,uint) {} 107 | function liquidateBorrowAllowed( 108 | address cTokenBorrowed, 109 | address cTokenCollateral, 110 | address liquidator, 111 | address borrower, 112 | uint repayAmount) external returns (uint) {} 113 | } 114 | 115 | contract ComptrollerStorage { 116 | struct Market { 117 | bool isListed; 118 | uint collateralFactorMantissa; 119 | mapping(address => bool) accountMembership; 120 | bool isComped; 121 | } 122 | mapping(address => Market) public markets; 123 | } 124 | 125 | contract CanJoin is CanCall { 126 | USDCJoin adapter; 127 | CompStrat strategy; 128 | function can_exit(uint val) public returns (bool) { 129 | bytes memory call = abi.encodeWithSignature 130 | ("exit(uint256)", val); 131 | return can_call(address(adapter), call); 132 | } 133 | function can_wind(uint borrow, uint n, uint loan) public returns (bool) { 134 | bytes memory call = abi.encodeWithSignature 135 | ("wind(uint256,uint256,uint256)", borrow, n, loan); 136 | return can_call(address(strategy), call); 137 | } 138 | function can_unwind(uint repay, uint n, uint exit_, uint loan_) public returns (bool) { 139 | bytes memory call = abi.encodeWithSignature 140 | ("unwind(uint256,uint256,uint256,uint256)", repay, n, exit_, loan_); 141 | return can_call(address(adapter), call); 142 | } 143 | function can_unwind_exit(uint val) public returns (bool) { 144 | return can_unwind_exit(val, 0); 145 | } 146 | function can_unwind_exit(uint val, uint loan) public returns (bool) { 147 | return can_unwind(0, 1, val, loan); 148 | } 149 | function can_unwind(uint repay, uint n) public returns (bool) { 150 | return can_unwind(repay, n, 0, 0); 151 | } 152 | } 153 | 154 | contract Usr is CanJoin { 155 | constructor(USDCJoin join_) public { 156 | adapter = join_; 157 | strategy = CompStrat(address(adapter.strategy())); 158 | } 159 | function approve(address coin, address usr) public { 160 | Token(coin).approve(usr, uint(-1)); 161 | } 162 | function join(uint wad) public { 163 | adapter.join(wad); 164 | } 165 | function exit(uint wad) public { 166 | adapter.exit(wad); 167 | } 168 | function reap() public { 169 | adapter.join(0); 170 | } 171 | function flee() public { 172 | adapter.flee(); 173 | } 174 | function unwind_exit(uint wad) public { 175 | adapter.unwind(0, 1, wad, 0); 176 | } 177 | function liquidateBorrow(address borrower, uint repayAmount) external 178 | returns (uint) 179 | { 180 | CToken ctoken = CToken(strategy.cgem()); 181 | return ctoken.liquidateBorrow(borrower, repayAmount, ctoken); 182 | } 183 | function hope(address vat, address usr) public { 184 | MockVat(vat).hope(usr); 185 | } 186 | } 187 | 188 | contract CropTestBase is TestBase, CanJoin { 189 | Token usdc; 190 | cToken cusdc; 191 | Token comp; 192 | Troll troll; 193 | MockVat vat; 194 | address self; 195 | bytes32 ilk = "USDC-C"; 196 | 197 | function set_usdc(address usr, uint val) internal { 198 | hevm.store( 199 | address(usdc), 200 | keccak256(abi.encode(usr, uint256(9))), 201 | bytes32(uint(val)) 202 | ); 203 | } 204 | 205 | function init_user() internal returns (Usr a, Usr b) { 206 | return init_user(200 * 1e6); 207 | } 208 | function init_user(uint cash) internal returns (Usr a, Usr b) { 209 | a = new Usr(adapter); 210 | b = new Usr(adapter); 211 | 212 | if (cash * 2 > usdc.balanceOf(self)) { 213 | set_usdc(address(a), cash); 214 | set_usdc(address(b), cash); 215 | } else { 216 | usdc.transfer(address(a), cash); 217 | usdc.transfer(address(b), cash); 218 | } 219 | 220 | a.approve(address(usdc), address(adapter)); 221 | b.approve(address(usdc), address(adapter)); 222 | 223 | a.hope(address(vat), address(this)); 224 | } 225 | } 226 | 227 | // Here we test the basics of the adapter with push rewards 228 | // and a mock vat 229 | contract SimpleCropTest is CropTestBase { 230 | function setUp() public virtual { 231 | self = address(this); 232 | usdc = new Token(6, 1000 * 1e6); 233 | cusdc = new cToken(8, 0); 234 | comp = new Token(18, 0); 235 | troll = new Troll(address(comp)); 236 | vat = new MockVat(); 237 | strategy = new CompStrat( address(usdc) 238 | , address(cusdc) 239 | , address(comp) 240 | , address(troll) 241 | ); 242 | adapter = new USDCJoin( address(vat) 243 | , ilk 244 | , address(usdc) 245 | , address(comp) 246 | , address(strategy) 247 | ); 248 | strategy.rely(address(adapter)); 249 | strategy.tune(0.75e18, 0.675e18, 0.674e18); 250 | } 251 | 252 | function reward(address usr, uint wad) internal virtual { 253 | comp.mint(usr, wad); 254 | } 255 | 256 | function test_reward() public virtual { 257 | reward(self, 100 ether); 258 | assertEq(comp.balanceOf(self), 100 ether); 259 | } 260 | 261 | function test_simple_multi_user() public { 262 | (Usr a, Usr b) = init_user(); 263 | 264 | a.join(60 * 1e6); 265 | b.join(40 * 1e6); 266 | 267 | reward(address(strategy), 50 * 1e18); 268 | 269 | a.join(0); 270 | assertEq(comp.balanceOf(address(a)), 30 * 1e18); 271 | assertEq(comp.balanceOf(address(b)), 0 * 1e18); 272 | 273 | b.join(0); 274 | assertEq(comp.balanceOf(address(a)), 30 * 1e18); 275 | assertEq(comp.balanceOf(address(b)), 20 * 1e18); 276 | } 277 | function test_simple_multi_reap() public { 278 | (Usr a, Usr b) = init_user(); 279 | 280 | a.join(60 * 1e6); 281 | b.join(40 * 1e6); 282 | 283 | reward(address(strategy), 50 * 1e18); 284 | 285 | a.join(0); 286 | assertEq(comp.balanceOf(address(a)), 30 * 1e18); 287 | assertEq(comp.balanceOf(address(b)), 0 * 1e18); 288 | 289 | b.join(0); 290 | assertEq(comp.balanceOf(address(a)), 30 * 1e18); 291 | assertEq(comp.balanceOf(address(b)), 20 * 1e18); 292 | 293 | a.join(0); b.reap(); 294 | assertEq(comp.balanceOf(address(a)), 30 * 1e18); 295 | assertEq(comp.balanceOf(address(b)), 20 * 1e18); 296 | } 297 | function test_simple_join_exit() public { 298 | usdc.approve(address(adapter), uint(-1)); 299 | 300 | adapter.join(100 * 1e6); 301 | assertEq(comp.balanceOf(self), 0 * 1e18, "no initial rewards"); 302 | 303 | reward(address(strategy), 10 * 1e18); 304 | adapter.join(0); adapter.join(0); // have to do it twice for some comptroller reason 305 | assertEq(comp.balanceOf(self), 10 * 1e18, "rewards increase with reap"); 306 | 307 | adapter.join(100 * 1e6); 308 | assertEq(comp.balanceOf(self), 10 * 1e18, "rewards invariant over join"); 309 | 310 | adapter.exit(200 * 1e6); 311 | assertEq(comp.balanceOf(self), 10 * 1e18, "rewards invariant over exit"); 312 | 313 | adapter.join(50 * 1e6); 314 | 315 | assertEq(comp.balanceOf(self), 10 * 1e18); 316 | reward(address(strategy), 10 * 1e18); 317 | adapter.join(10 * 1e6); 318 | assertEq(comp.balanceOf(self), 20 * 1e18); 319 | } 320 | function test_complex_scenario() public { 321 | (Usr a, Usr b) = init_user(); 322 | 323 | a.join(60 * 1e6); 324 | b.join(40 * 1e6); 325 | 326 | reward(address(strategy), 50 * 1e18); 327 | 328 | a.join(0); 329 | assertEq(comp.balanceOf(address(a)), 30 * 1e18); 330 | assertEq(comp.balanceOf(address(b)), 0 * 1e18); 331 | 332 | b.join(0); 333 | assertEq(comp.balanceOf(address(a)), 30 * 1e18); 334 | assertEq(comp.balanceOf(address(b)), 20 * 1e18); 335 | 336 | a.join(0); b.reap(); 337 | assertEq(comp.balanceOf(address(a)), 30 * 1e18); 338 | assertEq(comp.balanceOf(address(b)), 20 * 1e18); 339 | 340 | reward(address(strategy), 50 * 1e18); 341 | a.join(20 * 1e6); 342 | a.join(0); b.reap(); 343 | assertEq(comp.balanceOf(address(a)), 60 * 1e18); 344 | assertEq(comp.balanceOf(address(b)), 40 * 1e18); 345 | 346 | reward(address(strategy), 30 * 1e18); 347 | a.join(0); b.reap(); 348 | assertEq(comp.balanceOf(address(a)), 80 * 1e18); 349 | assertEq(comp.balanceOf(address(b)), 50 * 1e18); 350 | 351 | b.exit(20 * 1e6); 352 | } 353 | 354 | // a user's balance can be altered with vat.flux, check that this 355 | // can only be disadvantageous 356 | function test_flux_transfer() public { 357 | (Usr a, Usr b) = init_user(); 358 | 359 | a.join(100 * 1e6); 360 | reward(address(strategy), 50 * 1e18); 361 | 362 | a.join(0); a.join(0); // have to do it twice for some comptroller reason 363 | assertEq(comp.balanceOf(address(a)), 50 * 1e18, "rewards increase with reap"); 364 | 365 | reward(address(strategy), 50 * 1e18); 366 | vat.flux(ilk, address(a), address(b), 50 * 1e18); 367 | b.join(0); 368 | assertEq(comp.balanceOf(address(b)), 0 * 1e18, "if nonzero we have a problem"); 369 | } 370 | // if the users's balance has been altered with flux, check that 371 | // all parties can still exit 372 | function test_flux_exit() public { 373 | (Usr a, Usr b) = init_user(); 374 | 375 | a.join(100 * 1e6); 376 | reward(address(strategy), 50 * 1e18); 377 | 378 | a.join(0); a.join(0); // have to do it twice for some comptroller reason 379 | assertEq(comp.balanceOf(address(a)), 50 * 1e18, "rewards increase with reap"); 380 | 381 | reward(address(strategy), 50 * 1e18); 382 | vat.flux(ilk, address(a), address(b), 50 * 1e18); 383 | 384 | assertEq(usdc.balanceOf(address(a)), 100e6, "a balance before exit"); 385 | assertEq(adapter.stake(address(a)), 100e18, "a join balance before"); 386 | a.exit(50 * 1e6); 387 | assertEq(usdc.balanceOf(address(a)), 150e6, "a balance after exit"); 388 | assertEq(adapter.stake(address(a)), 50e18, "a join balance after"); 389 | 390 | assertEq(usdc.balanceOf(address(b)), 200e6, "b balance before exit"); 391 | assertEq(adapter.stake(address(b)), 0e18, "b join balance before"); 392 | adapter.tack(address(a), address(b), 50e18); 393 | b.flee(); 394 | assertEq(usdc.balanceOf(address(b)), 250e6, "b balance after exit"); 395 | assertEq(adapter.stake(address(b)), 0e18, "b join balance after"); 396 | } 397 | function test_reap_after_flux() public { 398 | (Usr a, Usr b) = init_user(); 399 | 400 | a.join(100 * 1e6); 401 | reward(address(strategy), 50 * 1e18); 402 | 403 | a.join(0); a.join(0); // have to do it twice for some comptroller reason 404 | assertEq(comp.balanceOf(address(a)), 50 * 1e18, "rewards increase with reap"); 405 | 406 | assertTrue( a.can_exit( 50e6), "can exit before flux"); 407 | vat.flux(ilk, address(a), address(b), 100e18); 408 | reward(address(strategy), 50e18); 409 | 410 | // if x gems are transferred from a to b, a will continue to earn 411 | // rewards on x, while b will not earn anything on x, until we 412 | // reset balances with `tack` 413 | assertTrue(!a.can_exit(100e6), "can't full exit after flux"); 414 | assertEq(adapter.stake(address(a)), 100e18); 415 | a.exit(0); 416 | 417 | assertEq(comp.balanceOf(address(a)), 100e18, "can claim remaining rewards"); 418 | 419 | reward(address(strategy), 50e18); 420 | a.exit(0); 421 | 422 | assertEq(comp.balanceOf(address(a)), 150e18, "rewards continue to accrue"); 423 | 424 | assertEq(adapter.stake(address(a)), 100e18, "balance is unchanged"); 425 | 426 | adapter.tack(address(a), address(b), 100e18); 427 | reward(address(strategy), 50e18); 428 | a.exit(0); 429 | 430 | assertEq(comp.balanceOf(address(a)), 150e18, "rewards no longer increase"); 431 | 432 | assertEq(adapter.stake(address(a)), 0e18, "balance is zeroed"); 433 | assertEq(comp.balanceOf(address(b)), 0e18, "b has no rewards yet"); 434 | b.join(0); 435 | assertEq(comp.balanceOf(address(b)), 50e18, "b now receives rewards"); 436 | } 437 | 438 | // flee is an emergency exit with no rewards, check that these are 439 | // not given out 440 | function test_flee() public { 441 | usdc.approve(address(adapter), uint(-1)); 442 | 443 | adapter.join(100 * 1e6); 444 | assertEq(comp.balanceOf(self), 0 * 1e18, "no initial rewards"); 445 | 446 | reward(address(strategy), 10 * 1e18); 447 | adapter.join(0); adapter.join(0); // have to do it twice for some comptroller reason 448 | assertEq(comp.balanceOf(self), 10 * 1e18, "rewards increase with reap"); 449 | 450 | reward(address(strategy), 10 * 1e18); 451 | adapter.exit(50 * 1e6); 452 | assertEq(comp.balanceOf(self), 20 * 1e18, "rewards increase with exit"); 453 | 454 | reward(address(strategy), 10 * 1e18); 455 | assertEq(usdc.balanceOf(self), 950e6, "balance before flee"); 456 | adapter.flee(); 457 | assertEq(comp.balanceOf(self), 20 * 1e18, "rewards invariant over flee"); 458 | assertEq(usdc.balanceOf(self), 1000e6, "balance after flee"); 459 | } 460 | 461 | function test_tack() public { 462 | /* 463 | A user's pending rewards, assuming no further crop income, is 464 | given by 465 | 466 | stake[usr] * share - crops[usr] 467 | 468 | After join/exit we set 469 | 470 | crops[usr] = stake[usr] * share 471 | 472 | Such that the pending rewards are zero. 473 | 474 | With `tack` we transfer stake from one user to another, but 475 | we must ensure that we also either (a) transfer crops or 476 | (b) reap the rewards concurrently. 477 | 478 | Here we check that tack accounts for rewards appropriately, 479 | regardless of whether we use (a) or (b). 480 | */ 481 | (Usr a, Usr b) = init_user(); 482 | 483 | // concurrent reap 484 | a.join(100e6); 485 | reward(address(strategy), 50e18); 486 | 487 | a.join(0); 488 | vat.flux(ilk, address(a), address(b), 100e18); 489 | adapter.tack(address(a), address(b), 100e18); 490 | b.join(0); 491 | 492 | reward(address(strategy), 50e18); 493 | a.exit(0); 494 | b.exit(100e6); 495 | assertEq(comp.balanceOf(address(a)), 50e18, "a rewards"); 496 | assertEq(comp.balanceOf(address(b)), 50e18, "b rewards"); 497 | 498 | // crop transfer 499 | a.join(100e6); 500 | reward(address(strategy), 50e18); 501 | 502 | // a doesn't reap their rewards before flux so all their pending 503 | // rewards go to b 504 | vat.flux(ilk, address(a), address(b), 100e18); 505 | adapter.tack(address(a), address(b), 100e18); 506 | 507 | reward(address(strategy), 50e18); 508 | a.exit(0); 509 | b.exit(100e6); 510 | assertEq(comp.balanceOf(address(a)), 50e18, "a rewards alt"); 511 | assertEq(comp.balanceOf(address(b)), 150e18, "b rewards alt"); 512 | } 513 | } 514 | 515 | // Here we use a mock cToken, comptroller and vat 516 | contract CropTest is SimpleCropTest { 517 | function setUp() public override virtual { 518 | self = address(this); 519 | usdc = new Token(6, 1000 * 1e6); 520 | cusdc = new cToken(8, 0); 521 | comp = new Token(18, 0); 522 | troll = new Troll(address(comp)); 523 | vat = new MockVat(); 524 | strategy = new CompStrat( address(usdc) 525 | , address(cusdc) 526 | , address(comp) 527 | , address(troll) 528 | ); 529 | adapter = new USDCJoin( address(vat) 530 | , ilk 531 | , address(usdc) 532 | , address(comp) 533 | , address(strategy) 534 | ); 535 | strategy.rely(address(adapter)); 536 | strategy.tune(0.75e18, 0.675e18, 0.674e18); 537 | } 538 | 539 | function reward(address usr, uint wad) internal override { 540 | troll.reward(usr, wad); 541 | } 542 | 543 | function test_reward() public override { 544 | reward(self, 100 ether); 545 | assertEq(troll.compAccrued(self), 100 ether); 546 | } 547 | } 548 | 549 | // Here we run the basic CropTest tests against mainnet, overriding 550 | // the Comptroller to accrue us COMP on demand 551 | contract CompTest is SimpleCropTest { 552 | function setUp() public override { 553 | self = address(this); 554 | vat = new MockVat(); 555 | 556 | usdc = Token(0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48); 557 | cusdc = cToken(0x39AA39c021dfbaE8faC545936693aC917d5E7563); 558 | comp = Token(0xc00e94Cb662C3520282E6f5717214004A7f26888); 559 | troll = Troll(0x3d9819210A31b4961b30EF54bE2aeD79B9c9Cd3B); 560 | 561 | strategy = new CompStrat( address(usdc) 562 | , address(cusdc) 563 | , address(comp) 564 | , address(troll) 565 | ); 566 | adapter = new USDCJoin( address(vat) 567 | , ilk 568 | , address(usdc) 569 | , address(comp) 570 | , address(strategy) 571 | ); 572 | strategy.rely(address(adapter)); 573 | strategy.tune(0.75e18, 0.675e18, 0.674e18); 574 | 575 | // give ourselves some usdc 576 | set_usdc(address(this), 1000 * 1e6); 577 | 578 | hevm.roll(block.number + 10); 579 | } 580 | 581 | function reward(address usr, uint wad) internal override { 582 | // override compAccrued in the comptroller 583 | uint old = uint(hevm.load( 584 | address(troll), 585 | keccak256(abi.encode(usr, uint256(20))) 586 | )); 587 | hevm.store( 588 | address(troll), 589 | keccak256(abi.encode(usr, uint256(20))), 590 | bytes32(old + wad) 591 | ); 592 | } 593 | function test_reward() public override { 594 | reward(self, 100 ether); 595 | assertEq(troll.compAccrued(self), 100 ether); 596 | } 597 | 598 | function test_borrower_index() public { 599 | assertEq(troll.compBorrowerIndex(address(cusdc), address(adapter)), 0); 600 | } 601 | 602 | function test_setup() public { 603 | assertEq(usdc.balanceOf(self), 1000 * 1e6, "hack the usdc"); 604 | } 605 | 606 | function test_block_number() public { 607 | assertEq(troll.getBlockNumber(), block.number); 608 | } 609 | 610 | function test_join() public { 611 | usdc.approve(address(adapter), uint(-1)); 612 | adapter.join(100 * 1e6); 613 | } 614 | } 615 | 616 | // Here we run some tests against the real Compound on mainnet 617 | contract RealCompTest is CropTestBase { 618 | function setUp() public { 619 | self = address(this); 620 | vat = new MockVat(); 621 | 622 | usdc = Token(0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48); 623 | cusdc = cToken(0x39AA39c021dfbaE8faC545936693aC917d5E7563); 624 | comp = Token(0xc00e94Cb662C3520282E6f5717214004A7f26888); 625 | troll = Troll(0x3d9819210A31b4961b30EF54bE2aeD79B9c9Cd3B); 626 | 627 | strategy = new CompStrat( address(usdc) 628 | , address(cusdc) 629 | , address(comp) 630 | , address(troll) 631 | ); 632 | adapter = new USDCJoin( address(vat) 633 | , ilk 634 | , address(usdc) 635 | , address(comp) 636 | , address(strategy) 637 | ); 638 | strategy.rely(address(adapter)); 639 | strategy.tune(0.75e18, 0.675e18, 0.674e18); 640 | 641 | // give ourselves some usdc 642 | set_usdc(address(this), 1000 * 1e6); 643 | 644 | hevm.roll(block.number + 10); 645 | 646 | usdc.approve(address(adapter), uint(-1)); 647 | usdc.approve(address(strategy), uint(-1)); 648 | } 649 | 650 | function get_s() internal returns (uint256 cf) { 651 | require(cToken(address(cusdc)).accrueInterest() == 0); 652 | return cToken(address(cusdc)).balanceOfUnderlying(address(strategy)); 653 | } 654 | function get_b() internal returns (uint256 cf) { 655 | require(cToken(address(cusdc)).accrueInterest() == 0); 656 | return cToken(address(cusdc)).borrowBalanceStored(address(strategy)); 657 | } 658 | function get_cf() internal returns (uint256 cf) { 659 | require(cToken(address(cusdc)).accrueInterest() == 0); 660 | cf = wdiv(cToken(address(cusdc)).borrowBalanceStored(address(strategy)), 661 | cToken(address(cusdc)).balanceOfUnderlying(address(strategy))); 662 | } 663 | 664 | function test_underlying() public { 665 | assertEq(cToken(address(cusdc)).underlying(), address(usdc)); 666 | } 667 | 668 | function reward(uint256 tic) internal { 669 | log_named_uint("=== tic ==>", tic); 670 | // accrue ~1 day of rewards 671 | hevm.warp(block.timestamp + tic); 672 | // unneeded? 673 | hevm.roll(block.number + tic / 15); 674 | } 675 | 676 | function test_reward_unwound() public { 677 | (Usr a,) = init_user(); 678 | assertEq(comp.balanceOf(address(a)), 0 ether); 679 | 680 | a.join(100 * 1e6); 681 | assertEq(comp.balanceOf(address(a)), 0 ether); 682 | 683 | strategy.wind(0, 0, 0); 684 | 685 | reward(1 days); 686 | 687 | a.join(0); 688 | // ~ 0.012 COMP per year 689 | assertGt(comp.balanceOf(address(a)), 0.000025 ether); 690 | assertLt(comp.balanceOf(address(a)), 0.000045 ether); 691 | } 692 | 693 | function test_reward_wound() public { 694 | (Usr a,) = init_user(); 695 | assertEq(comp.balanceOf(address(a)), 0 ether); 696 | 697 | a.join(100 * 1e6); 698 | assertEq(comp.balanceOf(address(a)), 0 ether); 699 | 700 | strategy.wind(50 * 10**6, 0, 0); 701 | 702 | reward(1 days); 703 | 704 | a.join(0); 705 | // ~ 0.035 COMP per year 706 | assertGt(comp.balanceOf(address(a)), 0.00008 ether); 707 | assertLt(comp.balanceOf(address(a)), 0.00011 ether); 708 | 709 | assertLt(get_cf(), strategy.maxf()); 710 | assertLt(get_cf(), strategy.minf()); 711 | } 712 | 713 | function test_reward_wound_fully() public { 714 | (Usr a,) = init_user(); 715 | assertEq(comp.balanceOf(address(a)), 0 ether); 716 | 717 | a.join(100 * 1e6); 718 | assertEq(comp.balanceOf(address(a)), 0 ether); 719 | 720 | strategy.wind(0, 5, 0); 721 | 722 | reward(1 days); 723 | 724 | a.join(0); 725 | // ~ 0.11 COMP per year 726 | assertGt(comp.balanceOf(address(a)), 0.00015 ether); 727 | assertLt(comp.balanceOf(address(a)), 0.00035 ether); 728 | 729 | assertLt(get_cf(), strategy.maxf(), "cf < maxf"); 730 | assertGt(get_cf(), strategy.minf(), "cf > minf"); 731 | } 732 | 733 | function test_wind_unwind() public { 734 | require(cToken(address(cusdc)).accrueInterest() == 0); 735 | (Usr a,) = init_user(); 736 | assertEq(comp.balanceOf(address(a)), 0 ether); 737 | 738 | a.join(100 * 1e6); 739 | assertEq(comp.balanceOf(address(a)), 0 ether); 740 | 741 | strategy.wind(0, 5, 0); 742 | 743 | reward(1 days); 744 | 745 | assertLt(get_cf(), strategy.maxf(), "under target"); 746 | assertGt(get_cf(), strategy.minf(), "over minimum"); 747 | 748 | log_named_uint("cf", get_cf()); 749 | reward(1000 days); 750 | log_named_uint("cf", get_cf()); 751 | 752 | assertGt(get_cf(), strategy.maxf(), "over target after interest"); 753 | 754 | // unwind is used for deleveraging our position. Here we have 755 | // gone over the target due to accumulated interest, so we 756 | // unwind to bring us back under the target leverage. 757 | assertTrue( can_unwind(0, 1), "able to unwind if over target"); 758 | strategy.unwind(0, 1, 0, 0); 759 | 760 | assertLt(get_cf(), 0.676e18, "near target post unwind"); 761 | assertGt(get_cf(), 0.674e18, "over minimum post unwind"); 762 | } 763 | 764 | function test_unwind_multiple() public { 765 | adapter.join(100e6); 766 | 767 | set_cf(0.72e18); 768 | strategy.unwind(0, 1, 0, 0); 769 | log_named_uint("cf", get_cf()); 770 | strategy.unwind(0, 1, 0, 0); 771 | log_named_uint("cf", get_cf()); 772 | strategy.unwind(0, 1, 0, 0); 773 | log_named_uint("cf", get_cf()); 774 | strategy.unwind(0, 1, 0, 0); 775 | log_named_uint("cf", get_cf()); 776 | assertGt(get_cf(), 0.674e18); 777 | assertLt(get_cf(), 0.675e18); 778 | 779 | set_cf(0.72e18); 780 | strategy.unwind(0, 8, 0, 0); 781 | log_named_uint("cf", get_cf()); 782 | assertGt(get_cf(), 0.674e18); 783 | assertLt(get_cf(), 0.675e18); 784 | } 785 | 786 | // if utilisation ends up over the limit we will need to use a loan 787 | // to unwind 788 | function test_unwind_over_limit() public { 789 | // we need a loan of 790 | // L / s0 >= (u - cf) / (cf * (1 - u) * (1 - cf)) 791 | adapter.join(100e6); 792 | set_cf(0.77e18); 793 | log_named_uint("cf", get_cf()); 794 | 795 | uint cf = 0.75e18; 796 | uint u = get_cf(); 797 | uint Lmin = wmul(100e6, wdiv(sub(u, cf), wmul(wmul(cf, 1e18 - u), 1e18 - cf))); 798 | log_named_uint("s", get_s()); 799 | log_named_uint("b", get_s()); 800 | log_named_uint("L", Lmin); 801 | 802 | assertTrue(!can_unwind(0, 1, 0, 0), "can't unwind without a loan"); 803 | assertTrue(!can_unwind(0, 1, 0, Lmin - 1e2), "can't unwind without enough loan"); 804 | assertTrue( can_unwind(0, 1, 0, Lmin), "can unwind with sufficient loan"); 805 | } 806 | 807 | function test_unwind_under_limit() public { 808 | adapter.join(100e6); 809 | set_cf(0.673e18); 810 | log_named_uint("cf", get_cf()); 811 | assertTrue(!can_unwind(0, 1, 0, 0)); 812 | // todo: minimum exit amount 813 | } 814 | 815 | function test_flash_wind_necessary_loan() public { 816 | // given nav s0, we can calculate the minimum loan L needed to 817 | // effect a wind up to a given u', 818 | // 819 | // L/s0 >= (u'/cf - 1 + u' - u*u'/cf) / [(1 - u') * (1 - u)] 820 | // 821 | // e.g. for u=0, u'=0.675, L/s0 ~= 1.77 822 | // 823 | // we can also write the maximum u' for a given L, 824 | // 825 | // u' <= (1 + (1 - u) * L / s0) / (1 + (1 - u) * (L / s0 + 1 / cf)) 826 | // 827 | // and the borrow to directly achieve a given u' 828 | // 829 | // x = s0 (1 / (1 - u') - 1 / (1 - u)) 830 | // 831 | // e.g. for u=0, u'=0.675, x/s0 ~= 2.0769 832 | // 833 | // here we test the u' that we achieve with given L 834 | 835 | (Usr a,) = init_user(); 836 | a.join(100 * 1e6); 837 | 838 | assertTrue(!can_wind(207.69 * 1e6, 0, 176 * 1e6), "insufficient loan"); 839 | assertTrue( can_wind(207.69 * 1e6, 0, 177 * 1e6), "sufficient loan"); 840 | 841 | strategy.wind(207.69 * 1e6, 0, 177 * 1e6); 842 | log_named_uint("cf", get_cf()); 843 | assertGt(get_cf(), 0.674e18); 844 | assertLt(get_cf(), 0.675e18); 845 | } 846 | 847 | function test_flash_wind_sufficient_loan() public { 848 | // we can also have wind determine the maximum borrow itself 849 | (Usr a,) = init_user(); 850 | set_usdc(address(a), 900e6); 851 | 852 | a.join(100 * 1e6); 853 | strategy.wind(0, 1, 200 * 1e6); 854 | log_named_uint("cf", get_cf()); 855 | assertGt(get_cf(), 0.673e18); 856 | assertLt(get_cf(), 0.675e18); 857 | 858 | return; 859 | a.join(100 * 1e6); 860 | logs("200"); 861 | strategy.wind(0, 1, 200 * 1e6); 862 | log_named_uint("cf", get_cf()); 863 | 864 | a.join(100 * 1e6); 865 | logs("100"); 866 | strategy.wind(0, 1, 100 * 1e6); 867 | log_named_uint("cf", get_cf()); 868 | 869 | a.join(100 * 1e6); 870 | logs("100"); 871 | strategy.wind(0, 1, 100 * 1e6); 872 | log_named_uint("cf", get_cf()); 873 | 874 | a.join(100 * 1e6); 875 | logs("150"); 876 | strategy.wind(0, 1, 150 * 1e6); 877 | log_named_uint("cf", get_cf()); 878 | 879 | a.join(100 * 1e6); 880 | logs("175"); 881 | strategy.wind(0, 1, 175 * 1e6); 882 | log_named_uint("cf", get_cf()); 883 | 884 | assertGt(get_cf(), 0.673e18); 885 | assertLt(get_cf(), 0.675e18); 886 | } 887 | // compare gas costs of a flash loan wind and a iteration wind 888 | function test_wind_gas_flash() public { 889 | (Usr a,) = init_user(); 890 | 891 | a.join(100 * 1e6); 892 | uint gas_before = gasleft(); 893 | strategy.wind(0, 1, 200 * 1e6); 894 | uint gas_after = gasleft(); 895 | log_named_uint("s ", get_s()); 896 | log_named_uint("b ", get_b()); 897 | log_named_uint("s + b", get_s() + get_b()); 898 | log_named_uint("cf", get_cf()); 899 | assertGt(get_cf(), 0.673e18); 900 | assertLt(get_cf(), 0.675e18); 901 | log_named_uint("gas", gas_before - gas_after); 902 | } 903 | function test_wind_gas_iteration() public { 904 | (Usr a,) = init_user(); 905 | 906 | a.join(100 * 1e6); 907 | uint gas_before = gasleft(); 908 | strategy.wind(0, 5, 0); 909 | uint gas_after = gasleft(); 910 | 911 | assertGt(get_cf(), 0.673e18); 912 | assertLt(get_cf(), 0.675e18); 913 | log_named_uint("s ", get_s()); 914 | log_named_uint("b ", get_b()); 915 | log_named_uint("s + b", get_s() + get_b()); 916 | log_named_uint("cf", get_cf()); 917 | log_named_uint("gas", gas_before - gas_after); 918 | } 919 | function test_wind_gas_partial_loan() public { 920 | (Usr a,) = init_user(); 921 | 922 | a.join(100 * 1e6); 923 | uint gas_before = gasleft(); 924 | strategy.wind(0, 3, 50e6); 925 | uint gas_after = gasleft(); 926 | 927 | assertGt(get_cf(), 0.673e18); 928 | assertLt(get_cf(), 0.675e18); 929 | log_named_uint("s ", get_s()); 930 | log_named_uint("b ", get_b()); 931 | log_named_uint("s + b", get_s() + get_b()); 932 | log_named_uint("cf", get_cf()); 933 | log_named_uint("gas", gas_before - gas_after); 934 | } 935 | 936 | function set_cf(uint cf) internal { 937 | uint nav = adapter.nav(); 938 | 939 | // desired supply and borrow in terms of underlying 940 | uint x = cusdc.exchangeRateCurrent(); 941 | uint s = (nav * 1e18 / (1e18 - cf)) / 1e12; 942 | uint b = s * cf / 1e18 - 1; 943 | 944 | log_named_uint("nav ", nav); 945 | log_named_uint("new s", s); 946 | log_named_uint("new b", b); 947 | log_named_uint("set u", cf); 948 | 949 | set_usdc(address(strategy), 0); 950 | // cusdc.accountTokens 951 | hevm.store( 952 | address(cusdc), 953 | keccak256(abi.encode(address(strategy), uint256(15))), 954 | bytes32((s * 1e18) / x) 955 | ); 956 | // cusdc.accountBorrows.principal 957 | hevm.store( 958 | address(cusdc), 959 | keccak256(abi.encode(address(strategy), uint256(17))), 960 | bytes32(b) 961 | ); 962 | // cusdc.accountBorrows.interestIndex 963 | hevm.store( 964 | address(cusdc), 965 | bytes32(uint(keccak256(abi.encode(address(strategy), uint256(17)))) + 1), 966 | bytes32(cusdc.borrowIndex()) 967 | ); 968 | 969 | log_named_uint("new u", get_cf()); 970 | log_named_uint("nav ", adapter.nav()); 971 | } 972 | 973 | // simple test of `cage` where we set the target leverage to zero 974 | // and then seek to withdraw all of the collateral 975 | function test_cage_single_user() public { 976 | adapter.join(100 * 1e6); 977 | set_cf(0.6745e18); 978 | 979 | // log("unwind 1"); 980 | // strategy.unwind(0, 6, 0, 0); 981 | 982 | set_cf(0.675e18); 983 | 984 | // this causes a sub overflow unless we use zsub 985 | // strategy.tune(0.75e18, 0.673e18, 0); 986 | 987 | strategy.tune(0.75e18, 0, 0); 988 | 989 | assertEq(usdc.balanceOf(address(this)), 900 * 1e6); 990 | strategy.unwind(0, 6, 100 * 1e6, 0); 991 | assertEq(usdc.balanceOf(address(this)), 1000 * 1e6); 992 | } 993 | // test of `cage` with two users, where the strategy is unwound 994 | // by a third party and the two users then exit separately 995 | function test_cage_multi_user() public { 996 | cage_multi_user(60 * 1e6, 40 * 1e6, 200 * 1e6); 997 | } 998 | // the same test but fuzzing over various ranges: 999 | // - uint32 is up to $4.5k 1000 | // - uint40 is up to $1.1m 1001 | // - uint48 is up to $280m, but we cap it at $50m due to liquidity 1002 | function test_cage_multi_user_small(uint32 a_join, uint32 b_join) public { 1003 | if (a_join < 100e6 || b_join < 100e6) return; 1004 | cage_multi_user(a_join, b_join, uint32(-1)); 1005 | } 1006 | function test_cage_multi_user_medium(uint40 a_join, uint40 b_join) public { 1007 | if (a_join < uint32(-1) || b_join < uint32(-1)) return; 1008 | cage_multi_user(a_join, b_join, uint40(-1)); 1009 | } 1010 | function test_cage_multi_user_large(uint48 a_join, uint48 b_join) public { 1011 | if (a_join < uint40(-1) || b_join < uint40(-1)) return; 1012 | if (a_join > 50e6 * 1e6 || b_join > 50e6 * 1e6) return; 1013 | cage_multi_user(a_join, b_join, uint48(-1)); 1014 | } 1015 | 1016 | function cage_multi_user(uint a_join, uint b_join, uint cash) public { 1017 | // this would truncate to whole usdc amounts, but there don't 1018 | // seem to be any failures for that 1019 | // a_join = a_join / 1e6 * 1e6; 1020 | // b_join = b_join / 1e6 * 1e6; 1021 | 1022 | log_named_decimal_uint("a_join", a_join, 6); 1023 | log_named_decimal_uint("b_join", b_join, 6); 1024 | (Usr a, Usr b) = init_user(cash); 1025 | assertEq(usdc.balanceOf(address(a)), cash); 1026 | assertEq(usdc.balanceOf(address(b)), cash); 1027 | a.join(a_join); 1028 | b.join(b_join); 1029 | 1030 | assertEq(usdc.balanceOf(address(a)), cash - a_join); 1031 | assertEq(usdc.balanceOf(address(b)), cash - b_join); 1032 | 1033 | strategy.wind(0, 6, 0); 1034 | reward(30 days); 1035 | strategy.tune(0.75e18, 0, 0); 1036 | 1037 | strategy.unwind(0, 6, 0, 0); 1038 | 1039 | a.unwind_exit(a_join); 1040 | b.unwind_exit(b_join); 1041 | 1042 | assertEq(usdc.balanceOf(address(a)), cash); 1043 | assertEq(usdc.balanceOf(address(b)), cash); 1044 | } 1045 | // wind / unwind make the underlying unavailable as it is deposited 1046 | // into the ctoken. In order to exit we will have to free up some 1047 | // underlying. 1048 | function wound_unwind_exit(bool loan) public { 1049 | adapter.join(100 * 1e6); 1050 | 1051 | assertEq(comp.balanceOf(self), 0 ether, "no initial rewards"); 1052 | 1053 | set_cf(0.675e18); 1054 | 1055 | assertTrue(get_cf() < strategy.maxf(), "cf under target"); 1056 | assertTrue(get_cf() > strategy.minf(), "cf over minimum"); 1057 | 1058 | // we can't exit as there is no available usdc 1059 | assertTrue(!can_exit(10 * 1e6), "cannot 10% exit initially"); 1060 | 1061 | // however we can exit with unwind 1062 | assertTrue( can_unwind_exit(14.7 * 1e6), "ok exit with 14.7%"); 1063 | assertTrue(!can_unwind_exit(14.9 * 1e6), "no exit with 14.9%"); 1064 | 1065 | if (loan) { 1066 | // with a loan we can exit an extra (L * (1 - u) / u) ~= 0.481L 1067 | assertTrue( can_unwind_exit(19.5 * 1e6, 10 * 1e6), "ok loan exit"); 1068 | assertTrue(!can_unwind_exit(19.7 * 1e6, 10 * 1e6), "no loan exit"); 1069 | 1070 | log_named_uint("s ", CToken(address(cusdc)).balanceOfUnderlying(address(strategy))); 1071 | log_named_uint("b ", CToken(address(cusdc)).borrowBalanceStored(address(strategy))); 1072 | log_named_uint("u ", get_cf()); 1073 | 1074 | uint prev = usdc.balanceOf(address(this)); 1075 | adapter.unwind(0, 1, 10 * 1e6, 10 * 1e6); 1076 | assertEq(usdc.balanceOf(address(this)) - prev, 10 * 1e6); 1077 | 1078 | log_named_uint("s'", CToken(address(cusdc)).balanceOfUnderlying(address(strategy))); 1079 | log_named_uint("b'", CToken(address(cusdc)).borrowBalanceStored(address(strategy))); 1080 | log_named_uint("u'", get_cf()); 1081 | 1082 | } else { 1083 | log_named_uint("s ", CToken(address(cusdc)).balanceOfUnderlying(address(strategy))); 1084 | log_named_uint("b ", CToken(address(cusdc)).borrowBalanceStored(address(strategy))); 1085 | log_named_uint("u ", get_cf()); 1086 | 1087 | uint prev = usdc.balanceOf(address(this)); 1088 | adapter.unwind(0, 1, 10 * 1e6, 0); 1089 | assertEq(usdc.balanceOf(address(this)) - prev, 10 * 1e6); 1090 | 1091 | log_named_uint("s'", CToken(address(cusdc)).balanceOfUnderlying(address(strategy))); 1092 | log_named_uint("b'", CToken(address(cusdc)).borrowBalanceStored(address(strategy))); 1093 | log_named_uint("u'", get_cf()); 1094 | } 1095 | } 1096 | function test_unwind_exit() public { 1097 | wound_unwind_exit(false); 1098 | } 1099 | function test_unwind_exit_with_loan() public { 1100 | wound_unwind_exit(true); 1101 | } 1102 | function test_unwind_full_exit() public { 1103 | adapter.join(100 * 1e6); 1104 | set_cf(0.675e18); 1105 | 1106 | // we can unwind in a single cycle using a loan 1107 | adapter.unwind(0, 1, 100e6 - 1e4, 177 * 1e6); 1108 | 1109 | adapter.join(100 * 1e6); 1110 | set_cf(0.675e18); 1111 | 1112 | // or we can unwind by iteration without a loan 1113 | adapter.unwind(0, 6, 100e6 - 1e4, 0); 1114 | } 1115 | function test_unwind_gas_flash() public { 1116 | adapter.join(100 * 1e6); 1117 | set_cf(0.675e18); 1118 | uint gas_before = gasleft(); 1119 | strategy.unwind(0, 1, 100e6 - 1e4, 177e6); 1120 | uint gas_after = gasleft(); 1121 | 1122 | assertGt(get_cf(), 0.674e18); 1123 | assertLt(get_cf(), 0.675e18); 1124 | log_named_uint("s ", get_s()); 1125 | log_named_uint("b ", get_b()); 1126 | log_named_uint("s + b", get_s() + get_b()); 1127 | log_named_uint("cf", get_cf()); 1128 | log_named_uint("gas", gas_before - gas_after); 1129 | } 1130 | function test_unwind_gas_iteration() public { 1131 | adapter.join(100 * 1e6); 1132 | set_cf(0.675e18); 1133 | uint gas_before = gasleft(); 1134 | strategy.unwind(0, 5, 100e6 - 1e4, 0); 1135 | uint gas_after = gasleft(); 1136 | 1137 | assertGt(get_cf(), 0.674e18); 1138 | assertLt(get_cf(), 0.675e18); 1139 | log_named_uint("s ", get_s()); 1140 | log_named_uint("b ", get_b()); 1141 | log_named_uint("s + b", get_s() + get_b()); 1142 | log_named_uint("cf", get_cf()); 1143 | log_named_uint("gas", gas_before - gas_after); 1144 | } 1145 | function test_unwind_gas_shallow() public { 1146 | // we can withdraw a fraction of the pool without loans or 1147 | // iterations 1148 | adapter.join(100 * 1e6); 1149 | set_cf(0.675e18); 1150 | uint gas_before = gasleft(); 1151 | strategy.unwind(0, 1, 14e6, 0); 1152 | uint gas_after = gasleft(); 1153 | 1154 | assertGt(get_cf(), 0.674e18); 1155 | assertLt(get_cf(), 0.675e18); 1156 | log_named_uint("s ", get_s()); 1157 | log_named_uint("b ", get_b()); 1158 | log_named_uint("s + b", get_s() + get_b()); 1159 | log_named_uint("cf", get_cf()); 1160 | log_named_uint("gas", gas_before - gas_after); 1161 | } 1162 | 1163 | // The nav of the adapter will drop over time, due to interest 1164 | // accrual, check that this is well behaved. 1165 | function test_nav_drop_with_interest() public { 1166 | require(cToken(address(cusdc)).accrueInterest() == 0); 1167 | (Usr a,) = init_user(); 1168 | 1169 | adapter.join(600 * 1e6); 1170 | 1171 | assertEq(usdc.balanceOf(address(a)), 200 * 1e6); 1172 | a.join(100 * 1e6); 1173 | assertEq(usdc.balanceOf(address(a)), 100 * 1e6); 1174 | assertEq(adapter.nps(), 1 ether, "initial nps is 1"); 1175 | 1176 | log_named_uint("nps before wind ", adapter.nps()); 1177 | strategy.wind(0, 5, 0); 1178 | 1179 | assertGt(get_cf(), 0.673e18, "near minimum"); 1180 | assertLt(get_cf(), 0.675e18, "under target"); 1181 | 1182 | log_named_uint("nps before interest", adapter.nps()); 1183 | reward(100 days); 1184 | assertLt(adapter.nps(), 1e18, "nps falls after interest"); 1185 | log_named_uint("nps after interest ", adapter.nps()); 1186 | 1187 | assertEq(usdc.balanceOf(address(a)), 100 * 1e6, "usdc before exit"); 1188 | assertEq(adapter.stake(address(a)), 100 ether, "balance before exit"); 1189 | 1190 | uint max_usdc = mul(adapter.nps(), adapter.stake(address(a))) / 1e30; 1191 | logs("==="); 1192 | log_named_uint("max usdc ", max_usdc); 1193 | log_named_uint("adapter.balance", adapter.stake(address(a))); 1194 | log_named_uint("vat.gem ", vat.gem(adapter.ilk(), address(a))); 1195 | log_named_uint("usdc ", usdc.balanceOf(address(strategy))); 1196 | log_named_uint("cf", get_cf()); 1197 | logs("exit ==="); 1198 | a.unwind_exit(max_usdc); 1199 | log_named_uint("nps after exit ", adapter.nps()); 1200 | log_named_uint("adapter.balance", adapter.stake(address(a))); 1201 | log_named_uint("adapter.balance", adapter.stake(address(a)) / 1e12); 1202 | log_named_uint("vat.gem ", vat.gem(adapter.ilk(), address(a))); 1203 | log_named_uint("usdc ", usdc.balanceOf(address(strategy))); 1204 | log_named_uint("cf", get_cf()); 1205 | assertLt(usdc.balanceOf(address(a)), 200 * 1e6, "less usdc after"); 1206 | assertGt(usdc.balanceOf(address(a)), 199 * 1e6, "less usdc after"); 1207 | 1208 | assertLt(adapter.stake(address(a)), 1e18/1e6, "zero balance after full exit"); 1209 | } 1210 | function test_nav_drop_with_liquidation() public { 1211 | require(cToken(address(cusdc)).accrueInterest() == 0); 1212 | enable_seize(address(this)); 1213 | 1214 | (Usr a,) = init_user(); 1215 | 1216 | adapter.join(600 * 1e6); 1217 | 1218 | assertEq(usdc.balanceOf(address(a)), 200 * 1e6); 1219 | a.join(100 * 1e6); 1220 | assertEq(usdc.balanceOf(address(a)), 100 * 1e6); 1221 | 1222 | logs("wind==="); 1223 | strategy.wind(0, 5, 0); 1224 | 1225 | assertGt(get_cf(), 0.673e18, "near minimum"); 1226 | assertLt(get_cf(), 0.675e18, "under target"); 1227 | 1228 | uint liquidity; uint shortfall; uint supp; uint borr; 1229 | supp = CToken(address(cusdc)).balanceOfUnderlying(address(strategy)); 1230 | borr = CToken(address(cusdc)).borrowBalanceStored(address(strategy)); 1231 | (, liquidity, shortfall) = 1232 | troll.getAccountLiquidity(address(strategy)); 1233 | log_named_uint("cf ", get_cf()); 1234 | log_named_uint("s ", supp); 1235 | log_named_uint("b ", borr); 1236 | log_named_uint("liquidity", liquidity); 1237 | log_named_uint("shortfall", shortfall); 1238 | 1239 | uint nps_before = adapter.nps(); 1240 | logs("time...==="); 1241 | reward(5000 days); 1242 | assertLt(adapter.nps(), nps_before, "nps falls after interest"); 1243 | 1244 | supp = CToken(address(cusdc)).balanceOfUnderlying(address(strategy)); 1245 | borr = CToken(address(cusdc)).borrowBalanceStored(address(strategy)); 1246 | (, liquidity, shortfall) = 1247 | troll.getAccountLiquidity(address(strategy)); 1248 | log_named_uint("cf' ", get_cf()); 1249 | log_named_uint("s' ", supp); 1250 | log_named_uint("b' ", borr); 1251 | log_named_uint("liquidity", liquidity); 1252 | log_named_uint("shortfall", shortfall); 1253 | 1254 | cusdc.approve(address(cusdc), uint(-1)); 1255 | usdc.approve(address(cusdc), uint(-1)); 1256 | log_named_uint("allowance", cusdc.allowance(address(this), address(cusdc))); 1257 | set_usdc(address(this), 1000e6); 1258 | log_named_uint("usdc ", usdc.balanceOf(address(this))); 1259 | log_named_uint("cusdc", cusdc.balanceOf(address(this))); 1260 | require(cusdc.mint(100e6) == 0); 1261 | logs("mint==="); 1262 | log_named_uint("usdc ", usdc.balanceOf(address(this))); 1263 | log_named_uint("cusdc", cusdc.balanceOf(address(this))); 1264 | logs("liquidate==="); 1265 | return; 1266 | // liquidation is not possible for cusdc-cusdc pairs, as it is 1267 | // blocked by a re-entrancy guard???? 1268 | uint repay = 20; // units of underlying 1269 | assertTrue(!can_call( address(cusdc) 1270 | , abi.encodeWithSignature( 1271 | "liquidateBorrow(address,uint256,address)", 1272 | address(strategy), repay, CToken(address(cusdc)))), 1273 | "can't perform liquidation"); 1274 | cusdc.liquidateBorrow(address(strategy), repay, CToken(address(cusdc))); 1275 | 1276 | supp = CToken(address(cusdc)).balanceOfUnderlying(address(strategy)); 1277 | borr = CToken(address(cusdc)).borrowBalanceStored(address(strategy)); 1278 | (, liquidity, shortfall) = 1279 | troll.getAccountLiquidity(address(strategy)); 1280 | log_named_uint("cf' ", get_cf()); 1281 | log_named_uint("s' ", supp); 1282 | log_named_uint("b' ", borr); 1283 | log_named_uint("liquidity", liquidity); 1284 | log_named_uint("shortfall", shortfall); 1285 | 1286 | // check how long it would take for us to get to 100% utilisation 1287 | reward(30 * 365 days); 1288 | log_named_uint("cf' ", get_cf()); 1289 | assertGt(get_cf(), 1e18, "cf > 1"); 1290 | } 1291 | 1292 | // allow the test contract to seize collateral from a borrower 1293 | // (normally only cTokens can do this). This allows us to mock 1294 | // liquidations. 1295 | function enable_seize(address usr) internal { 1296 | hevm.store( 1297 | address(troll), 1298 | keccak256(abi.encode(usr, uint256(9))), 1299 | bytes32(uint256(1)) 1300 | ); 1301 | } 1302 | // comptroller expects this to be available if we're pretending to 1303 | // be a cToken 1304 | function comptroller() external returns (address) { 1305 | return address(troll); 1306 | } 1307 | function test_enable_seize() public { 1308 | ComptrollerStorage stroll = ComptrollerStorage(address(troll)); 1309 | bool isListed; 1310 | (isListed,,) = stroll.markets(address(this)); 1311 | assertTrue(!isListed); 1312 | 1313 | enable_seize(address(this)); 1314 | 1315 | (isListed,,) = stroll.markets(address(this)); 1316 | assertTrue(isListed); 1317 | } 1318 | function test_can_seize() public { 1319 | enable_seize(address(this)); 1320 | 1321 | adapter.join(100 * 1e6); 1322 | strategy.wind(0, 4, 0); 1323 | 1324 | uint seize = 100 * 1e8; 1325 | 1326 | uint cusdc_before = cusdc.balanceOf(address(strategy)); 1327 | assertEq(cusdc.balanceOf(address(this)), 0, "no cusdc before"); 1328 | 1329 | uint s = CToken(address(cusdc)).seize(address(this), address(strategy), seize); 1330 | assertEq(s, 0, "seize successful"); 1331 | 1332 | uint cusdc_after = cusdc.balanceOf(address(strategy)); 1333 | assertEq(cusdc.balanceOf(address(this)), seize, "cusdc after"); 1334 | assertEq(cusdc_before - cusdc_after, seize, "join supply decreased"); 1335 | } 1336 | function test_nav_drop_with_seizure() public { 1337 | enable_seize(address(this)); 1338 | 1339 | (Usr a,) = init_user(); 1340 | 1341 | adapter.join(600 * 1e6); 1342 | a.join(100 * 1e6); 1343 | log_named_uint("nps", adapter.nps()); 1344 | log_named_uint("usdc ", usdc.balanceOf(address(strategy))); 1345 | log_named_uint("cusdc", cusdc.balanceOf(address(strategy))); 1346 | 1347 | logs("wind==="); 1348 | strategy.wind(0, 5, 0); 1349 | log_named_uint("nps", adapter.nps()); 1350 | log_named_uint("cf", get_cf()); 1351 | log_named_uint("adapter usdc ", usdc.balanceOf(address(strategy))); 1352 | log_named_uint("adapter cusdc", cusdc.balanceOf(address(strategy))); 1353 | log_named_uint("adapter nav ", adapter.nav()); 1354 | log_named_uint("a max usdc ", mul(adapter.stake(address(a)), adapter.nps()) / 1e18); 1355 | 1356 | assertGt(get_cf(), 0.673e18, "near minimum"); 1357 | assertLt(get_cf(), 0.675e18, "under target"); 1358 | 1359 | logs("seize==="); 1360 | uint seize = 350 * 1e6 * 1e18 / cusdc.exchangeRateCurrent(); 1361 | log_named_uint("seize", seize); 1362 | uint s = CToken(address(cusdc)).seize(address(this), address(strategy), seize); 1363 | assertEq(s, 0, "seize successful"); 1364 | log_named_uint("nps", adapter.nps()); 1365 | log_named_uint("cf", get_cf()); 1366 | log_named_uint("adapter usdc ", usdc.balanceOf(address(strategy))); 1367 | log_named_uint("adapter cusdc", cusdc.balanceOf(address(strategy))); 1368 | log_named_uint("adapter nav ", adapter.nav()); 1369 | log_named_uint("a max usdc ", mul(adapter.stake(address(a)), adapter.nps()) / 1e18); 1370 | 1371 | assertLt(adapter.nav(), 350 * 1e18, "nav is halved"); 1372 | } 1373 | } 1374 | 1375 | import "../spell/DssSpell.sol"; 1376 | 1377 | interface Pause { 1378 | function owner() external view returns (address); 1379 | function delay() external view returns (uint256); 1380 | } 1381 | 1382 | contract RealMakerTest is SimpleCropTest { 1383 | address pause = 0xbE286431454714F511008713973d3B053A2d38f3; 1384 | DssSpell spell; 1385 | 1386 | ChainlogAbstract constant CHANGELOG = ChainlogAbstract(0xdA0Ab1e0017DEbCd72Be8599041a2aa3bA7e740F); 1387 | 1388 | function setUp() public override { 1389 | spell = new DssSpell(); 1390 | 1391 | // take control of the pause 1392 | hevm.store(pause, bytes32(uint(1)), bytes32(uint256(uint160(address(spell))))); 1393 | // and set zero delay 1394 | hevm.store(pause, bytes32(uint(4)), bytes32(uint256(0))); 1395 | 1396 | self = address(this); 1397 | init(); 1398 | 1399 | usdc = Token(CHANGELOG.getAddress("USDC")); 1400 | cusdc = cToken(CHANGELOG.getAddress("CUSDC")); 1401 | comp = Token(CHANGELOG.getAddress("COMP")); 1402 | troll = Troll(0x3d9819210A31b4961b30EF54bE2aeD79B9c9Cd3B); 1403 | vat = MockVat(CHANGELOG.getAddress("MCD_VAT")); 1404 | adapter = USDCJoin(CHANGELOG.getAddress("MCD_JOIN_USDC_C")); 1405 | 1406 | // give ourselves some usdc 1407 | set_usdc(address(this), 1000 * 1e6); 1408 | } 1409 | 1410 | function init() internal { 1411 | uint gas_before_schedule = gasleft(); 1412 | hevm.warp(now - 6 hours); 1413 | spell.schedule(); 1414 | 1415 | uint gas_before_execute = gasleft(); 1416 | hevm.warp(now + 1); 1417 | spell.cast(); 1418 | uint gas_after_execute = gasleft(); 1419 | 1420 | log_named_uint("schedule gas", gas_before_schedule - gas_before_execute); 1421 | log_named_uint("execute gas", gas_before_execute - gas_after_execute); 1422 | } 1423 | 1424 | function reward(address usr, uint wad) internal override { 1425 | // override compAccrued in the comptroller 1426 | uint old = uint(hevm.load( 1427 | address(troll), 1428 | keccak256(abi.encode(usr, uint256(20))) 1429 | )); 1430 | hevm.store( 1431 | address(troll), 1432 | keccak256(abi.encode(usr, uint256(20))), 1433 | bytes32(old + wad) 1434 | ); 1435 | } 1436 | function test_reward() public override { 1437 | reward(self, 100 ether); 1438 | assertEq(troll.compAccrued(self), 100 ether); 1439 | } 1440 | 1441 | function test_setup() public { 1442 | assertEq(Pause(pause).owner(), address(spell)); 1443 | assertEq(Pause(pause).delay(), 0); 1444 | } 1445 | } 1446 | -------------------------------------------------------------------------------- /src/wind.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.6.7; 2 | pragma experimental ABIEncoderV2; 3 | 4 | import "./crop.sol"; 5 | 6 | interface CToken is ERC20 { 7 | function admin() external returns (address); 8 | function pendingAdmin() external returns (address); 9 | function comptroller() external returns (address); 10 | function interestRateModel() external returns (address); 11 | function initialExchangeRateMantissa() external returns (uint); 12 | function reserveFactorMantissa() external returns (uint); 13 | function accrualBlockNumber() external returns (uint); 14 | function borrowIndex() external returns (uint); 15 | function totalBorrows() external returns (uint); 16 | function totalReserves() external returns (uint); 17 | function totalSupply() external returns (uint); 18 | function accountTokens(address) external returns (uint); 19 | function transferAllowances(address,address) external returns (uint); 20 | 21 | function balanceOfUnderlying(address owner) external returns (uint); 22 | function getAccountSnapshot(address account) external view returns (uint, uint, uint, uint); 23 | function borrowRatePerBlock() external view returns (uint); 24 | function supplyRatePerBlock() external view returns (uint); 25 | function totalBorrowsCurrent() external returns (uint); 26 | function borrowBalanceCurrent(address account) external returns (uint); 27 | function borrowBalanceStored(address account) external view returns (uint); 28 | function exchangeRateCurrent() external returns (uint); 29 | function exchangeRateStored() external view returns (uint); 30 | function getCash() external view returns (uint); 31 | function accrueInterest() external returns (uint); 32 | function seize(address liquidator, address borrower, uint seizeTokens) external returns (uint); 33 | 34 | function mint(uint mintAmount) external returns (uint); 35 | function redeem(uint redeemTokens) external returns (uint); 36 | function redeemUnderlying(uint redeemAmount) external returns (uint); 37 | function borrow(uint borrowAmount) external returns (uint); 38 | function repayBorrow(uint repayAmount) external returns (uint); 39 | function repayBorrowBehalf(address borrower, uint repayAmount) external returns (uint); 40 | function liquidateBorrow(address borrower, uint repayAmount, CToken cTokenCollateral) external returns (uint); 41 | } 42 | 43 | interface Comptroller { 44 | function enterMarkets(address[] calldata cTokens) external returns (uint[] memory); 45 | function claimComp(address[] calldata holders, address[] calldata cTokens, bool borrowers, bool suppliers) external; 46 | function compAccrued(address) external returns (uint); 47 | function compBorrowerIndex(address,address) external returns (uint); 48 | function compSupplierIndex(address,address) external returns (uint); 49 | function seizeAllowed( 50 | address cTokenCollateral, 51 | address cTokenBorrowed, 52 | address liquidator, 53 | address borrower, 54 | uint seizeTokens) external returns (uint); 55 | function getAccountLiquidity(address) external returns (uint,uint,uint); 56 | } 57 | 58 | interface Strategy { 59 | function nav() external returns (uint); 60 | function harvest() external; 61 | function join(uint) external; 62 | function exit(uint) external; 63 | // temporary 64 | function wind(uint,uint,uint) external; 65 | function unwind(uint,uint,uint,uint) external; 66 | function cgem() external returns (address); 67 | function maxf() external returns (uint256); 68 | function minf() external returns (uint256); 69 | } 70 | 71 | contract USDCJoin is CropJoin { 72 | Strategy public immutable strategy; 73 | constructor(address vat_, bytes32 ilk_, address gem_, address comp_, address strategy_) 74 | public 75 | CropJoin(vat_, ilk_, gem_, comp_) 76 | { 77 | strategy = Strategy(strategy_); 78 | ERC20(gem_).approve(strategy_, uint(-1)); 79 | } 80 | function nav() public override returns (uint) { 81 | uint _nav = add(strategy.nav(), gem.balanceOf(address(this))); 82 | return mul(_nav, 10 ** (18 - dec)); 83 | } 84 | function crop() internal override returns (uint) { 85 | strategy.harvest(); 86 | return super.crop(); 87 | } 88 | function join(uint val) public override { 89 | super.join(val); 90 | strategy.join(val); 91 | } 92 | function exit(uint val) public override { 93 | strategy.exit(val); 94 | super.exit(val); 95 | } 96 | function flee() public override { 97 | address usr = msg.sender; 98 | uint wad = vat.gem(ilk, usr); 99 | uint val = wmul(wmul(wad, nps()), 10 ** dec); 100 | strategy.exit(val); 101 | super.flee(); 102 | } 103 | 104 | // todo: remove? 105 | // need to deal with instances of adapter.unwind in tests 106 | function unwind(uint repay_, uint loops_, uint exit_, uint loan_) external { 107 | gem.transferFrom(msg.sender, address(this), loan_); 108 | strategy.unwind(repay_, loops_, exit_, loan_); 109 | super.exit(exit_); 110 | gem.transfer(msg.sender, loan_); 111 | } 112 | } 113 | 114 | contract CompStrat { 115 | ERC20 public immutable gem; 116 | CToken public immutable cgem; 117 | CToken public immutable comp; 118 | Comptroller public immutable comptroller; 119 | 120 | uint256 public cf = 0; // ctoken max collateral factor [wad] 121 | uint256 public maxf = 0; // maximum target collateral factor [wad] 122 | uint256 public minf = 0; // minimum target collateral factor [wad] 123 | 124 | uint256 constant DUST = 1e6; // value (in usdc) below which to stop looping 125 | 126 | constructor(address gem_, address cgem_, address comp_, address comptroller_) 127 | public 128 | { 129 | wards[msg.sender] = 1; 130 | 131 | gem = ERC20(gem_); 132 | cgem = CToken(cgem_); 133 | comp = CToken(comp_); 134 | comptroller = Comptroller(comptroller_); 135 | 136 | ERC20(gem_).approve(cgem_, uint(-1)); 137 | 138 | address[] memory ctokens = new address[](1); 139 | ctokens[0] = cgem_; 140 | uint256[] memory errors = new uint[](1); 141 | errors = Comptroller(comptroller_).enterMarkets(ctokens); 142 | require(errors[0] == 0); 143 | } 144 | 145 | function add(uint x, uint y) public pure returns (uint z) { 146 | require((z = x + y) >= x, "ds-math-add-overflow"); 147 | } 148 | function sub(uint x, uint y) public pure returns (uint z) { 149 | require((z = x - y) <= x, "ds-math-sub-underflow"); 150 | } 151 | function zsub(uint x, uint y) public pure returns (uint z) { 152 | return sub(x, min(x, y)); 153 | } 154 | function mul(uint x, uint y) public pure returns (uint z) { 155 | require(y == 0 || (z = x * y) / y == x, "ds-math-mul-overflow"); 156 | } 157 | uint256 constant WAD = 10 ** 18; 158 | function wmul(uint x, uint y) public pure returns (uint z) { 159 | z = mul(x, y) / WAD; 160 | } 161 | function wdiv(uint x, uint y) public pure returns (uint z) { 162 | z = mul(x, WAD) / y; 163 | } 164 | function min(uint x, uint y) internal pure returns (uint z) { 165 | return x <= y ? x : y; 166 | } 167 | 168 | function nav() public returns (uint) { 169 | uint _nav = add(gem.balanceOf(address(this)), 170 | sub(cgem.balanceOfUnderlying(address(this)), 171 | cgem.borrowBalanceCurrent(address(this)))); 172 | return _nav; 173 | } 174 | 175 | function harvest() external auth { 176 | address[] memory ctokens = new address[](1); 177 | address[] memory users = new address[](1); 178 | ctokens[0] = address(cgem); 179 | users [0] = address(this); 180 | 181 | comptroller.claimComp(users, ctokens, true, true); 182 | comp.transfer(msg.sender, comp.balanceOf(address(this))); 183 | } 184 | 185 | // --- Auth --- 186 | mapping (address => uint) public wards; 187 | function rely(address usr) external auth { wards[usr] = 1; } 188 | function deny(address usr) external auth { wards[usr] = 0; } 189 | modifier auth { 190 | require(wards[msg.sender] == 1, "GemJoin/not-authorized"); 191 | _; 192 | } 193 | 194 | function join(uint256 val) public auth { 195 | gem.transferFrom(msg.sender, address(this), val); 196 | } 197 | function exit(uint256 val) public auth { 198 | gem.transfer(msg.sender, val); 199 | } 200 | 201 | function tune(uint cf_, uint maxf_, uint minf_) external auth { 202 | cf = cf_; 203 | maxf = maxf_; 204 | minf = minf_; 205 | } 206 | 207 | // borrow_: how much underlying to borrow (dec decimals) 208 | // loops_: how many times to repeat a max borrow loop before the 209 | // specified borrow/mint 210 | // loan_: how much underlying to lend to the contract for this 211 | // transaction 212 | function wind(uint borrow_, uint loops_, uint loan_) external { 213 | require(cgem.accrueInterest() == 0); 214 | if (loan_ > 0) { 215 | require(gem.transferFrom(msg.sender, address(this), loan_)); 216 | } 217 | uint gems = gem.balanceOf(address(this)); 218 | if (gems > 0) { 219 | require(cgem.mint(gems) == 0); 220 | } 221 | 222 | for (uint i=0; i < loops_; i++) { 223 | uint s = cgem.balanceOfUnderlying(address(this)); 224 | uint b = cgem.borrowBalanceStored(address(this)); 225 | // math overflow if 226 | // - b / (s + L) > cf [insufficient loan to unwind] 227 | // - minf > 1e18 [bad configuration] 228 | // - minf < u [can't wind over minf] 229 | uint x1 = sub(wmul(s, cf), b); 230 | uint x2 = wdiv(sub(wmul(sub(s, loan_), minf), b), 231 | sub(1e18, minf)); 232 | uint max_borrow = min(x1, x2); 233 | if (max_borrow < DUST) break; 234 | require(cgem.borrow(max_borrow) == 0); 235 | require(cgem.mint(max_borrow) == 0); 236 | } 237 | if (borrow_ > 0) { 238 | require(cgem.borrow(borrow_) == 0); 239 | require(cgem.mint(borrow_) == 0); 240 | } 241 | if (loan_ > 0) { 242 | require(cgem.redeemUnderlying(loan_) == 0); 243 | require(gem.transfer(msg.sender, loan_)); 244 | } 245 | 246 | uint u = wdiv(cgem.borrowBalanceStored(address(this)), 247 | cgem.balanceOfUnderlying(address(this))); 248 | require(u < maxf, "bad-wind"); 249 | } 250 | // repay_: how much underlying to repay (dec decimals) 251 | // loops_: how many times to repeat a max repay loop before the 252 | // specified redeem/repay 253 | // exit_: how much underlying to remove following unwind 254 | // loan_: how much underlying to lend to the contract for this 255 | // transaction 256 | function unwind(uint repay_, uint loops_, uint exit_, uint loan_) external { 257 | require(cgem.accrueInterest() == 0); 258 | uint u = wdiv(cgem.borrowBalanceStored(address(this)), 259 | cgem.balanceOfUnderlying(address(this))); 260 | if (loan_ > 0) { 261 | require(gem.transferFrom(msg.sender, address(this), loan_)); 262 | } 263 | require(cgem.mint(gem.balanceOf(address(this))) == 0, "failed-mint"); 264 | 265 | for (uint i=0; i < loops_; i++) { 266 | uint s = cgem.balanceOfUnderlying(address(this)); 267 | uint b = cgem.borrowBalanceStored(address(this)); 268 | // math overflow if 269 | // - [insufficient loan to unwind] 270 | // - [insufficient loan for exit] 271 | // - [bad configuration] 272 | uint x1 = wdiv(sub(wmul(s, cf), b), cf); 273 | uint x2 = wdiv(this.zsub(add(b, wmul(exit_, maxf)), 274 | wmul(sub(s, loan_), maxf)), 275 | sub(1e18, maxf)); 276 | uint max_repay = min(x1, x2); 277 | if (max_repay < DUST) break; 278 | require(cgem.redeemUnderlying(max_repay) == 0, "failed-redeem"); 279 | require(cgem.repayBorrow(max_repay) == 0, "failed-repay"); 280 | } 281 | if (repay_ > 0) { 282 | require(cgem.redeemUnderlying(repay_) == 0, "failed-redeem"); 283 | require(cgem.repayBorrow(repay_) == 0, "failed-repay"); 284 | } 285 | if (exit_ > 0 || loan_ > 0) { 286 | require(cgem.redeemUnderlying(add(exit_, loan_)) == 0, "failed-redeem"); 287 | } 288 | if (loan_ > 0) { 289 | require(gem.transfer(msg.sender, loan_), "failed-transfer"); 290 | } 291 | if (exit_ > 0) { 292 | exit(exit_); 293 | } 294 | 295 | uint u_ = wdiv(cgem.borrowBalanceStored(address(this)), 296 | cgem.balanceOfUnderlying(address(this))); 297 | bool ramping = u < minf && u_ > u && u_ < maxf; 298 | bool damping = u > maxf && u_ < u && u_ > minf; 299 | bool tamping = u_ >= minf && u_ <= maxf; 300 | require(ramping || damping || tamping, "bad-unwind"); 301 | } 302 | } 303 | --------------------------------------------------------------------------------