├── .codecov.yml ├── .gitattributes ├── .github └── workflows │ └── tests.yaml ├── .gitignore ├── .gitmodules ├── DEVELOPING.md ├── LICENSE ├── Makefile ├── README.md ├── shell.nix └── src ├── abaci.sol ├── cat.sol ├── clip.sol ├── cure.sol ├── dai.sol ├── dog.sol ├── end.sol ├── flap.sol ├── flip.sol ├── flop.sol ├── join.sol ├── jug.sol ├── pot.sol ├── spot.sol ├── test ├── abaci.t.sol ├── clip.t.sol ├── cure.t.sol ├── dai.t.sol ├── dog.t.sol ├── end.t.sol ├── flap.t.sol ├── flip.t.sol ├── flop.t.sol ├── fork.t.sol ├── jug.t.sol ├── pot.t.sol ├── vat.t.sol └── vow.t.sol ├── vat.sol └── vow.sol /.codecov.yml: -------------------------------------------------------------------------------- 1 | codecov: 2 | require_ci_to_pass: yes 3 | 4 | coverage: 5 | precision: 2 6 | round: up 7 | range: "70...100" 8 | 9 | parsers: 10 | gcov: 11 | branch_detection: 12 | conditional: yes 13 | loop: yes 14 | method: no 15 | macro: no 16 | 17 | comment: 18 | layout: "reach,diff,flags,tree" 19 | behavior: default 20 | require_changes: no 21 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | *.sol linguist-language=Solidity 2 | -------------------------------------------------------------------------------- /.github/workflows/tests.yaml: -------------------------------------------------------------------------------- 1 | on: [push, pull_request] 2 | 3 | jobs: 4 | tests: 5 | runs-on: ubuntu-latest 6 | steps: 7 | - name: Checkout repository and submodules 8 | uses: actions/checkout@v2 9 | with: 10 | submodules: recursive 11 | 12 | - name: Install nix 2.3.6 13 | uses: cachix/install-nix-action@v13 14 | with: 15 | install_url: https://releases.nixos.org/nix/nix-2.3.6/install 16 | nix_path: nixpkgs=channel:nixos-unstable 17 | 18 | - name: Use maker cachix 19 | uses: cachix/cachix-action@v10 20 | with: 21 | name: maker 22 | 23 | - name: Run tests 24 | run: nix-shell --pure --run 'dapp test' 25 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /out 2 | .DS_Store 3 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "lib/ds-test"] 2 | path = lib/ds-test 3 | url = https://github.com/dapphub/ds-test 4 | [submodule "lib/ds-value"] 5 | path = lib/ds-value 6 | url = https://github.com/dapphub/ds-value 7 | [submodule "lib/ds-token"] 8 | path = lib/ds-token 9 | url = https://github.com/dapphub/ds-token 10 | -------------------------------------------------------------------------------- /DEVELOPING.md: -------------------------------------------------------------------------------- 1 | # Multi Collateral Dai Developer Guide 2 | 3 | *work in progress* 4 | 5 | This is a more in depth description of the Dai core contracts. The 6 | previous iteration of Dai was called Single Collateral Dai (SCD), or 7 | `sai`, and is found at https://github.com/makerdao/sai 8 | 9 | 10 | ## Tooling 11 | 12 | - dapp.tools 13 | - solc v0.5.0 14 | - tests use ds-test and are in files ending .t.sol 15 | 16 | 17 | ## Units 18 | 19 | Dai has three different numerical units: `wad`, `ray` and `rad` 20 | 21 | - `wad`: fixed point decimal with 18 decimals (for basic quantities, e.g. balances) 22 | - `ray`: fixed point decimal with 27 decimals (for precise quantites, e.g. ratios) 23 | - `rad`: fixed point decimal with 45 decimals (result of integer multiplication with a `wad` and a `ray`) 24 | 25 | `wad` and `ray` units will be familiar from SCD. `rad` is a new unit and 26 | exists to prevent precision loss in the core CDP engine. 27 | 28 | The base of `ray` is `ONE = 10 ** 27`. 29 | 30 | A good explanation of fixed point arithmetic can be found at [Wikipedia](https://en.wikipedia.org/wiki/Fixed-point_arithmetic). 31 | 32 | ## Multiplication 33 | 34 | Generally, `wad` should be used additively and `ray` should be used 35 | multiplicatively. It usually doesn't make sense to multiply a `wad` by a 36 | `wad` (or a `rad` by a `rad`). 37 | 38 | Two multiplaction operators are used in `dss`: 39 | 40 | - `mul`: standard integer multiplcation. No loss of precision. 41 | - `rmul`: used for multiplications involving `ray`'s. Precision is lost. 42 | 43 | They can only be used sensibly with the following combination of units: 44 | 45 | - `mul(wad, ray) -> rad` 46 | - `rmul(wad, ray) -> wad` 47 | - `rmul(ray, ray) -> ray` 48 | - `rmul(rad, ray) -> rad` 49 | 50 | ## Code style 51 | 52 | This is obviously opinionated and you may even disagree, but here are 53 | the considerations that make this code look like it does: 54 | 55 | - Distinct things should have distinct names ("memes") 56 | 57 | - Lack of symmetry and typographic alignment is a code smell. 58 | 59 | - Inheritance masks complexity and encourages over abstraction, be 60 | explicit about what you want. 61 | 62 | - In this modular system, contracts generally shouldn't call or jump 63 | into themselves, except for math. Again, this masks complexity. 64 | 65 | 66 | ## CDP Engine 67 | 68 | The core CDP, Dai, and collateral state is kept in the `Vat`. This 69 | contract has no external dependencies and maintains the central 70 | "Accounting Invariants" of Dai. 71 | 72 | Dai cannot exist without collateral: 73 | 74 | - An `ilk` is a particular type of collateral. 75 | - Collateral `gem` is assigned to users with `slip`. 76 | - Collateral `gem` is transferred between users with `flux`. 77 | 78 | The CDP data structure is the `Urn`: 79 | 80 | - it has `ink` encumbered collateral 81 | - it has `art` encumbered debt 82 | 83 | Similarly, a collateral `Ilk`: 84 | 85 | - it has `Ink` encumbered collateral 86 | - it has `Art` encumbered debt 87 | - it has `take` collateral scaling factor (discussed further below) 88 | - it has `rate` debt scaling factor (discussed further below) 89 | 90 | Here, "encumbered" means "locked in a CDP". 91 | 92 | CDPs are managed via `frob(i, u, v, w, dink, dart)`, which modifies the 93 | CDP of user `u`, using `gem` from user `v` and creating `dai` for user 94 | `w`. 95 | 96 | CDPs are confiscated via `grab(i, u, v, w, dink, dart)`, which modifies 97 | the CDP of user `u`, giving `gem` to user `v` and creating `sin` for 98 | user `w`. `grab` is the means by which CDPs are liquidated, transferring 99 | debt from the CDP to a users `sin` balance. 100 | 101 | Sin represents "seized" or "bad" debt and can be cancelled out with an 102 | equal quantity of Dai using `heal(u, v, rad)`: take `sin` from `u` and 103 | `dai` from `v`. 104 | 105 | Note that `heal` can also be used to *create* Dai, balanced by an equal 106 | quantity of Sin. 107 | 108 | Finally, the quantity `dai` can be transferred between users with `move`. 109 | 110 | ### Rate 111 | 112 | The ilk quantity `rate` define the ratio of exchange 113 | between un-encumbered and encumbered Debt. 114 | 115 | This quantity allows for manipulation of debt balances 116 | across a whole Ilk. 117 | 118 | Debt can be seized or injected into an ilk using `fold(i, u, rate)`, 119 | which increases the `dai` balance of the user `u` by increasing the 120 | encumbered debt balance of all urns in the ilk by the ratio `rate`. 121 | 122 | ## CDP Interface 123 | 124 | The `Vat` contains risk parameters for each `ilk`: 125 | 126 | - `spot`: the maximum amount of Dai drawn per unit collateral 127 | - `line`: the maximum total Dai drawn 128 | 129 | And a global risk parameter: 130 | 131 | - `Line`: the maximum total Dai drawn across all ilks 132 | 133 | The `Vat` exposes the public function: 134 | 135 | - `frob(ilk, dink, dart)`: manipulate the callers CDP in the given `ilk` 136 | by `dink` and `dart`, subject to the risk parameters 137 | 138 | ## Liquidation Interface 139 | 140 | The companion to CDP management is CDP liquidation, which is defined via 141 | the `Cat`. 142 | 143 | The `Cat` contains liquidation parameters for each `ilk`: 144 | 145 | - `flip`: the address of the collateral liquidator 146 | - `chop`: the liquidation penalty 147 | - `lump`: the liquidation quantity 148 | 149 | The `Cat` exposes two public functions 150 | 151 | - `bite(ilk, urn)`: mark a specific CDP for liquidation 152 | - `flip(n, wad)`: initiate liquidation 153 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | .PHONY: build clean test test-gas 2 | 3 | build :; DAPP_BUILD_OPTIMIZE=0 DAPP_BUILD_OPTIMIZE_RUNS=0 dapp --use solc:0.6.12 build 4 | clean :; dapp clean 5 | test :; DAPP_BUILD_OPTIMIZE=0 DAPP_BUILD_OPTIMIZE_RUNS=0 DAPP_TEST_ADDRESS=0xAb5801a7D398351b8bE11C439e05C5B3259aeC9B dapp --use solc:0.6.12 test -v ${TEST_FLAGS} 6 | test-gas : build 7 | LANG=C.UTF-8 hevm dapp-test --rpc="${ETH_RPC_URL}" --json-file=out/dapp.sol.json --dapp-root=. --verbose 2 --match "test_gas" 8 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Multi Collateral Dai 2 | ![Build Status](https://github.com/makerdao/dss/actions/workflows/.github/workflows/tests.yaml/badge.svg?branch=master) 3 | 4 | This repository contains the core smart contract code for Multi 5 | Collateral Dai. This is a high level description of the system, assuming 6 | familiarity with the basic economic mechanics as described in the 7 | whitepaper. 8 | 9 | ## Additional Documentation 10 | 11 | `dss` is also documented in the [wiki](https://github.com/makerdao/dss/wiki) and in [DEVELOPING.md](https://github.com/makerdao/dss/blob/master/DEVELOPING.md) 12 | 13 | ## Design Considerations 14 | 15 | - Token agnostic 16 | - system doesn't care about the implementation of external tokens 17 | - can operate entirely independently of other systems, provided an authority assigns 18 | initial collateral to users in the system and provides price data. 19 | 20 | - Verifiable 21 | - designed from the bottom up to be amenable to formal verification 22 | - the core cdp and balance database makes *no* external calls and 23 | contains *no* precision loss (i.e. no division) 24 | 25 | - Modular 26 | - multi contract core system is made to be very adaptable to changing 27 | requirements. 28 | - allows for implementations of e.g. auctions, liquidation, CDP risk 29 | conditions, to be altered on a live system. 30 | - allows for the addition of novel collateral types (e.g. whitelisting) 31 | 32 | 33 | ## Collateral, Adapters and Wrappers 34 | 35 | Collateral is the foundation of Dai and Dai creation is not possible 36 | without it. There are many potential candidates for collateral, whether 37 | native ether, ERC20 tokens, other fungible token standards like ERC777, 38 | non-fungible tokens, or any number of other financial instruments. 39 | 40 | Token wrappers are one solution to the need to standardise collateral 41 | behaviour in Dai. Inconsistent decimals and transfer semantics are 42 | reasons for wrapping. For example, the WETH token is an ERC20 wrapper 43 | around native ether. 44 | 45 | In MCD, we abstract all of these different token behaviours away behind 46 | *Adapters*. 47 | 48 | Adapters manipulate a single core system function: `slip`, which 49 | modifies user collateral balances. 50 | 51 | Adapters should be very small and well defined contracts. Adapters are 52 | very powerful and should be carefully vetted by MKR holders. Some 53 | examples are given in `join.sol`. Note that the adapter is the only 54 | connection between a given collateral type and the concrete on-chain 55 | token that it represents. 56 | 57 | There can be a multitude of adapters for each collateral type, for 58 | different requirements. For example, ETH collateral could have an 59 | adapter for native ether and *also* for WETH. 60 | 61 | 62 | ## The Dai Token 63 | 64 | The fundamental state of a Dai balance is given by the balance in the 65 | core (`vat.dai`, sometimes referred to as `D`). 66 | 67 | Given this, there are a number of ways to implement the Dai that is used 68 | outside of the system, with different trade offs. 69 | 70 | *Fundamentally, "Dai" is any token that is directly fungible with the 71 | core.* 72 | 73 | In the Kovan deployment, "Dai" is represented by an ERC20 DSToken. 74 | After interacting with CDPs and auctions, users must `exit` from the 75 | system to gain a balance of this token, which can then be used in Oasis 76 | etc. 77 | 78 | It is possible to have multiple fungible Dai tokens, allowing for the 79 | adoption of new token standards. This needs careful consideration from a 80 | UX perspective, with the notion of a canonical token address becoming 81 | increasingly restrictive. In the future, cross-chain communication and 82 | scalable sidechains will likely lead to a proliferation of multiple Dai 83 | tokens. Users of the core could `exit` into a Plasma sidechain, an 84 | Ethereum shard, or a different blockchain entirely via e.g. the Cosmos 85 | Hub. 86 | 87 | 88 | ## Price Feeds 89 | 90 | Price feeds are a crucial part of the Dai system. The code here assumes 91 | that there are working price feeds and that their values are being 92 | pushed to the contracts. 93 | 94 | Specifically, the price that is required is the highest acceptable 95 | quantity of CDP Dai debt per unit of collateral. 96 | 97 | 98 | ## Liquidation and Auctions 99 | 100 | An important difference between SCD and MCD is the switch from fixed 101 | price sell offs to auctions as the means of liquidating collateral. 102 | 103 | The auctions implemented here are simple and expect liquidations to 104 | occur in *fixed size lots* (say 10,000 ETH). 105 | 106 | 107 | ## Settlement 108 | 109 | Another important difference between SCD and MCD is in the handling of 110 | System Debt. System Debt is debt that has been taken from risky CDPs. 111 | In SCD this is covered by diluting the collateral pool via the PETH 112 | mechanism. In MCD this is covered by dilution of an external token, 113 | namely MKR. 114 | 115 | As in collateral liquidation, this dilution occurs by an auction 116 | (`flop`), using a fixed-size lot. 117 | 118 | In order to reduce the collateral intensity of large CDP liquidations, 119 | MKR dilution is delayed by a configurable period (e.g 1 week). 120 | 121 | Similarly, System Surplus is handled by an auction (`flap`), which sells 122 | off Dai surplus in return for the highest bidder in MKR. 123 | 124 | 125 | ## Authentication 126 | 127 | The contracts here use a very simple multi-owner authentication system, 128 | where a contract totally trusts multiple other contracts to call its 129 | functions and configure it. 130 | 131 | It is expected that modification of this state will be via an interface 132 | that is used by the Governance layer. 133 | -------------------------------------------------------------------------------- /shell.nix: -------------------------------------------------------------------------------- 1 | { dappPkgs ? ( 2 | import (fetchTarball "https://github.com/makerdao/makerpkgs/tarball/master") {} 3 | ).dappPkgsVersions.hevm-0_43_1 4 | }: with dappPkgs; 5 | 6 | mkShell { 7 | DAPP_SOLC = solc-static-versions.solc_0_6_12 + "/bin/solc-0.6.12"; 8 | DAPP_TEST_ADDRESS = "0xAb5801a7D398351b8bE11C439e05C5B3259aeC9B"; 9 | # No optimizations 10 | SOLC_FLAGS = ""; 11 | buildInputs = [ 12 | dapp 13 | ]; 14 | } 15 | -------------------------------------------------------------------------------- /src/abaci.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: AGPL-3.0-or-later 2 | 3 | /// abaci.sol -- price decrease functions for auctions 4 | 5 | // Copyright (C) 2020-2022 Dai Foundation 6 | // 7 | // This program is free software: you can redistribute it and/or modify 8 | // it under the terms of the GNU Affero General Public License as published 9 | // by the Free Software Foundation, either version 3 of the License, or 10 | // (at your option) any later version. 11 | // 12 | // This program is distributed in the hope that it will be useful, 13 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | // GNU Affero General Public License for more details. 16 | // 17 | // You should have received a copy of the GNU Affero General Public License 18 | // along with this program. If not, see . 19 | 20 | pragma solidity ^0.6.12; 21 | 22 | interface Abacus { 23 | // 1st arg: initial price [ray] 24 | // 2nd arg: seconds since auction start [seconds] 25 | // returns: current auction price [ray] 26 | function price(uint256, uint256) external view returns (uint256); 27 | } 28 | 29 | contract LinearDecrease is Abacus { 30 | 31 | // --- Auth --- 32 | mapping (address => uint256) public wards; 33 | function rely(address usr) external auth { wards[usr] = 1; emit Rely(usr); } 34 | function deny(address usr) external auth { wards[usr] = 0; emit Deny(usr); } 35 | modifier auth { 36 | require(wards[msg.sender] == 1, "LinearDecrease/not-authorized"); 37 | _; 38 | } 39 | 40 | // --- Data --- 41 | uint256 public tau; // Seconds after auction start when the price reaches zero [seconds] 42 | 43 | // --- Events --- 44 | event Rely(address indexed usr); 45 | event Deny(address indexed usr); 46 | 47 | event File(bytes32 indexed what, uint256 data); 48 | 49 | // --- Init --- 50 | constructor() public { 51 | wards[msg.sender] = 1; 52 | emit Rely(msg.sender); 53 | } 54 | 55 | // --- Administration --- 56 | function file(bytes32 what, uint256 data) external auth { 57 | if (what == "tau") tau = data; 58 | else revert("LinearDecrease/file-unrecognized-param"); 59 | emit File(what, data); 60 | } 61 | 62 | // --- Math --- 63 | uint256 constant RAY = 10 ** 27; 64 | function add(uint256 x, uint256 y) internal pure returns (uint256 z) { 65 | require((z = x + y) >= x); 66 | } 67 | function mul(uint256 x, uint256 y) internal pure returns (uint256 z) { 68 | require(y == 0 || (z = x * y) / y == x); 69 | } 70 | function rmul(uint256 x, uint256 y) internal pure returns (uint256 z) { 71 | z = x * y; 72 | require(y == 0 || z / y == x); 73 | z = z / RAY; 74 | } 75 | 76 | // Price calculation when price is decreased linearly in proportion to time: 77 | // tau: The number of seconds after the start of the auction where the price will hit 0 78 | // top: Initial price 79 | // dur: current seconds since the start of the auction 80 | // 81 | // Returns y = top * ((tau - dur) / tau) 82 | // 83 | // Note the internal call to mul multiples by RAY, thereby ensuring that the rmul calculation 84 | // which utilizes top and tau (RAY values) is also a RAY value. 85 | function price(uint256 top, uint256 dur) override external view returns (uint256) { 86 | if (dur >= tau) return 0; 87 | return rmul(top, mul(tau - dur, RAY) / tau); 88 | } 89 | } 90 | 91 | contract StairstepExponentialDecrease is Abacus { 92 | 93 | // --- Auth --- 94 | mapping (address => uint256) public wards; 95 | function rely(address usr) external auth { wards[usr] = 1; emit Rely(usr); } 96 | function deny(address usr) external auth { wards[usr] = 0; emit Deny(usr); } 97 | modifier auth { 98 | require(wards[msg.sender] == 1, "StairstepExponentialDecrease/not-authorized"); 99 | _; 100 | } 101 | 102 | // --- Data --- 103 | uint256 public step; // Length of time between price drops [seconds] 104 | uint256 public cut; // Per-step multiplicative factor [ray] 105 | 106 | // --- Events --- 107 | event Rely(address indexed usr); 108 | event Deny(address indexed usr); 109 | 110 | event File(bytes32 indexed what, uint256 data); 111 | 112 | // --- Init --- 113 | // @notice: `cut` and `step` values must be correctly set for 114 | // this contract to return a valid price 115 | constructor() public { 116 | wards[msg.sender] = 1; 117 | emit Rely(msg.sender); 118 | } 119 | 120 | // --- Administration --- 121 | function file(bytes32 what, uint256 data) external auth { 122 | if (what == "cut") require((cut = data) <= RAY, "StairstepExponentialDecrease/cut-gt-RAY"); 123 | else if (what == "step") step = data; 124 | else revert("StairstepExponentialDecrease/file-unrecognized-param"); 125 | emit File(what, data); 126 | } 127 | 128 | // --- Math --- 129 | uint256 constant RAY = 10 ** 27; 130 | function rmul(uint256 x, uint256 y) internal pure returns (uint256 z) { 131 | z = x * y; 132 | require(y == 0 || z / y == x); 133 | z = z / RAY; 134 | } 135 | // optimized version from dss PR #78 136 | function rpow(uint256 x, uint256 n, uint256 b) internal pure returns (uint256 z) { 137 | assembly { 138 | switch n case 0 { z := b } 139 | default { 140 | switch x case 0 { z := 0 } 141 | default { 142 | switch mod(n, 2) case 0 { z := b } default { z := x } 143 | let half := div(b, 2) // for rounding. 144 | for { n := div(n, 2) } n { n := div(n,2) } { 145 | let xx := mul(x, x) 146 | if shr(128, x) { revert(0,0) } 147 | let xxRound := add(xx, half) 148 | if lt(xxRound, xx) { revert(0,0) } 149 | x := div(xxRound, b) 150 | if mod(n,2) { 151 | let zx := mul(z, x) 152 | if and(iszero(iszero(x)), iszero(eq(div(zx, x), z))) { revert(0,0) } 153 | let zxRound := add(zx, half) 154 | if lt(zxRound, zx) { revert(0,0) } 155 | z := div(zxRound, b) 156 | } 157 | } 158 | } 159 | } 160 | } 161 | } 162 | 163 | // top: initial price 164 | // dur: seconds since the auction has started 165 | // step: seconds between a price drop 166 | // cut: cut encodes the percentage to decrease per step. 167 | // For efficiency, the values is set as (1 - (% value / 100)) * RAY 168 | // So, for a 1% decrease per step, cut would be (1 - 0.01) * RAY 169 | // 170 | // returns: top * (cut ^ dur) 171 | // 172 | // 173 | function price(uint256 top, uint256 dur) override external view returns (uint256) { 174 | return rmul(top, rpow(cut, dur / step, RAY)); 175 | } 176 | } 177 | 178 | // While an equivalent function can be obtained by setting step = 1 in StairstepExponentialDecrease, 179 | // this continous (i.e. per-second) exponential decrease has be implemented as it is more gas-efficient 180 | // than using the stairstep version with step = 1 (primarily due to 1 fewer SLOAD per price calculation). 181 | contract ExponentialDecrease is Abacus { 182 | 183 | // --- Auth --- 184 | mapping (address => uint256) public wards; 185 | function rely(address usr) external auth { wards[usr] = 1; emit Rely(usr); } 186 | function deny(address usr) external auth { wards[usr] = 0; emit Deny(usr); } 187 | modifier auth { 188 | require(wards[msg.sender] == 1, "ExponentialDecrease/not-authorized"); 189 | _; 190 | } 191 | 192 | // --- Data --- 193 | uint256 public cut; // Per-second multiplicative factor [ray] 194 | 195 | // --- Events --- 196 | event Rely(address indexed usr); 197 | event Deny(address indexed usr); 198 | 199 | event File(bytes32 indexed what, uint256 data); 200 | 201 | // --- Init --- 202 | // @notice: `cut` value must be correctly set for 203 | // this contract to return a valid price 204 | constructor() public { 205 | wards[msg.sender] = 1; 206 | emit Rely(msg.sender); 207 | } 208 | 209 | // --- Administration --- 210 | function file(bytes32 what, uint256 data) external auth { 211 | if (what == "cut") require((cut = data) <= RAY, "ExponentialDecrease/cut-gt-RAY"); 212 | else revert("ExponentialDecrease/file-unrecognized-param"); 213 | emit File(what, data); 214 | } 215 | 216 | // --- Math --- 217 | uint256 constant RAY = 10 ** 27; 218 | function rmul(uint256 x, uint256 y) internal pure returns (uint256 z) { 219 | z = x * y; 220 | require(y == 0 || z / y == x); 221 | z = z / RAY; 222 | } 223 | // optimized version from dss PR #78 224 | function rpow(uint256 x, uint256 n, uint256 b) internal pure returns (uint256 z) { 225 | assembly { 226 | switch n case 0 { z := b } 227 | default { 228 | switch x case 0 { z := 0 } 229 | default { 230 | switch mod(n, 2) case 0 { z := b } default { z := x } 231 | let half := div(b, 2) // for rounding. 232 | for { n := div(n, 2) } n { n := div(n,2) } { 233 | let xx := mul(x, x) 234 | if shr(128, x) { revert(0,0) } 235 | let xxRound := add(xx, half) 236 | if lt(xxRound, xx) { revert(0,0) } 237 | x := div(xxRound, b) 238 | if mod(n,2) { 239 | let zx := mul(z, x) 240 | if and(iszero(iszero(x)), iszero(eq(div(zx, x), z))) { revert(0,0) } 241 | let zxRound := add(zx, half) 242 | if lt(zxRound, zx) { revert(0,0) } 243 | z := div(zxRound, b) 244 | } 245 | } 246 | } 247 | } 248 | } 249 | } 250 | 251 | // top: initial price 252 | // dur: seconds since the auction has started 253 | // cut: cut encodes the percentage to decrease per second. 254 | // For efficiency, the values is set as (1 - (% value / 100)) * RAY 255 | // So, for a 1% decrease per second, cut would be (1 - 0.01) * RAY 256 | // 257 | // returns: top * (cut ^ dur) 258 | // 259 | function price(uint256 top, uint256 dur) override external view returns (uint256) { 260 | return rmul(top, rpow(cut, dur, RAY)); 261 | } 262 | } 263 | -------------------------------------------------------------------------------- /src/cat.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: AGPL-3.0-or-later 2 | 3 | /// cat.sol -- Dai liquidation module 4 | 5 | // Copyright (C) 2018 Rain 6 | // 7 | // This program is free software: you can redistribute it and/or modify 8 | // it under the terms of the GNU Affero General Public License as published by 9 | // the Free Software Foundation, either version 3 of the License, or 10 | // (at your option) any later version. 11 | // 12 | // This program is distributed in the hope that it will be useful, 13 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | // GNU Affero General Public License for more details. 16 | // 17 | // You should have received a copy of the GNU Affero General Public License 18 | // along with this program. If not, see . 19 | 20 | pragma solidity >=0.5.12; 21 | 22 | // FIXME: This contract was altered compared to the production version. 23 | // It doesn't use LibNote anymore. 24 | // New deployments of this contract will need to include custom events (TO DO). 25 | 26 | interface Kicker { 27 | function kick(address urn, address gal, uint256 tab, uint256 lot, uint256 bid) 28 | external returns (uint256); 29 | } 30 | 31 | interface VatLike { 32 | function ilks(bytes32) external view returns ( 33 | uint256 Art, // [wad] 34 | uint256 rate, // [ray] 35 | uint256 spot, // [ray] 36 | uint256 line, // [rad] 37 | uint256 dust // [rad] 38 | ); 39 | function urns(bytes32,address) external view returns ( 40 | uint256 ink, // [wad] 41 | uint256 art // [wad] 42 | ); 43 | function grab(bytes32,address,address,address,int256,int256) external; 44 | function hope(address) external; 45 | function nope(address) external; 46 | } 47 | 48 | interface VowLike { 49 | function fess(uint256) external; 50 | } 51 | 52 | contract Cat { 53 | // --- Auth --- 54 | mapping (address => uint256) public wards; 55 | function rely(address usr) external auth { wards[usr] = 1; } 56 | function deny(address usr) external auth { wards[usr] = 0; } 57 | modifier auth { 58 | require(wards[msg.sender] == 1, "Cat/not-authorized"); 59 | _; 60 | } 61 | 62 | // --- Data --- 63 | struct Ilk { 64 | address flip; // Liquidator 65 | uint256 chop; // Liquidation Penalty [wad] 66 | uint256 dunk; // Liquidation Quantity [rad] 67 | } 68 | 69 | mapping (bytes32 => Ilk) public ilks; 70 | 71 | uint256 public live; // Active Flag 72 | VatLike public vat; // CDP Engine 73 | VowLike public vow; // Debt Engine 74 | uint256 public box; // Max Dai out for liquidation [rad] 75 | uint256 public litter; // Balance of Dai out for liquidation [rad] 76 | 77 | // --- Events --- 78 | event Bite( 79 | bytes32 indexed ilk, 80 | address indexed urn, 81 | uint256 ink, 82 | uint256 art, 83 | uint256 tab, 84 | address flip, 85 | uint256 id 86 | ); 87 | 88 | // --- Init --- 89 | constructor(address vat_) public { 90 | wards[msg.sender] = 1; 91 | vat = VatLike(vat_); 92 | live = 1; 93 | } 94 | 95 | // --- Math --- 96 | uint256 constant WAD = 10 ** 18; 97 | 98 | function min(uint256 x, uint256 y) internal pure returns (uint256 z) { 99 | if (x > y) { z = y; } else { z = x; } 100 | } 101 | function add(uint256 x, uint256 y) internal pure returns (uint256 z) { 102 | require((z = x + y) >= x); 103 | } 104 | function sub(uint256 x, uint256 y) internal pure returns (uint256 z) { 105 | require((z = x - y) <= x); 106 | } 107 | function mul(uint256 x, uint256 y) internal pure returns (uint256 z) { 108 | require(y == 0 || (z = x * y) / y == x); 109 | } 110 | 111 | // --- Administration --- 112 | function file(bytes32 what, address data) external auth { 113 | if (what == "vow") vow = VowLike(data); 114 | else revert("Cat/file-unrecognized-param"); 115 | } 116 | function file(bytes32 what, uint256 data) external auth { 117 | if (what == "box") box = data; 118 | else revert("Cat/file-unrecognized-param"); 119 | } 120 | function file(bytes32 ilk, bytes32 what, uint256 data) external auth { 121 | if (what == "chop") ilks[ilk].chop = data; 122 | else if (what == "dunk") ilks[ilk].dunk = data; 123 | else revert("Cat/file-unrecognized-param"); 124 | } 125 | function file(bytes32 ilk, bytes32 what, address flip) external auth { 126 | if (what == "flip") { 127 | vat.nope(ilks[ilk].flip); 128 | ilks[ilk].flip = flip; 129 | vat.hope(flip); 130 | } 131 | else revert("Cat/file-unrecognized-param"); 132 | } 133 | 134 | // --- CDP Liquidation --- 135 | function bite(bytes32 ilk, address urn) external returns (uint256 id) { 136 | (,uint256 rate,uint256 spot,,uint256 dust) = vat.ilks(ilk); 137 | (uint256 ink, uint256 art) = vat.urns(ilk, urn); 138 | 139 | require(live == 1, "Cat/not-live"); 140 | require(spot > 0 && mul(ink, spot) < mul(art, rate), "Cat/not-unsafe"); 141 | 142 | Ilk memory milk = ilks[ilk]; 143 | uint256 dart; 144 | { 145 | uint256 room = sub(box, litter); 146 | 147 | // test whether the remaining space in the litterbox is dusty 148 | require(litter < box && room >= dust, "Cat/liquidation-limit-hit"); 149 | 150 | dart = min(art, mul(min(milk.dunk, room), WAD) / rate / milk.chop); 151 | } 152 | 153 | uint256 dink = min(ink, mul(ink, dart) / art); 154 | 155 | require(dart > 0 && dink > 0 , "Cat/null-auction"); 156 | require(dart <= 2**255 && dink <= 2**255, "Cat/overflow" ); 157 | 158 | // This may leave the CDP in a dusty state 159 | vat.grab( 160 | ilk, urn, address(this), address(vow), -int256(dink), -int256(dart) 161 | ); 162 | vow.fess(mul(dart, rate)); 163 | 164 | { // Avoid stack too deep 165 | // This calcuation will overflow if dart*rate exceeds ~10^14, 166 | // i.e. the maximum dunk is roughly 100 trillion DAI. 167 | uint256 tab = mul(mul(dart, rate), milk.chop) / WAD; 168 | litter = add(litter, tab); 169 | 170 | id = Kicker(milk.flip).kick({ 171 | urn: urn, 172 | gal: address(vow), 173 | tab: tab, 174 | lot: dink, 175 | bid: 0 176 | }); 177 | } 178 | 179 | emit Bite(ilk, urn, dink, dart, mul(dart, rate), milk.flip, id); 180 | } 181 | 182 | function claw(uint256 rad) external auth { 183 | litter = sub(litter, rad); 184 | } 185 | 186 | function cage() external auth { 187 | live = 0; 188 | } 189 | } 190 | -------------------------------------------------------------------------------- /src/cure.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: AGPL-3.0-or-later 2 | 3 | /// cure.sol -- Debt Rectifier contract 4 | 5 | // Copyright (C) 2022 Dai Foundation 6 | // 7 | // This program is free software: you can redistribute it and/or modify 8 | // it under the terms of the GNU Affero General Public License as published by 9 | // the Free Software Foundation, either version 3 of the License, or 10 | // (at your option) any later version. 11 | // 12 | // This program is distributed in the hope that it will be useful, 13 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | // GNU Affero General Public License for more details. 16 | // 17 | // You should have received a copy of the GNU Affero General Public License 18 | // along with this program. If not, see . 19 | 20 | pragma solidity >=0.6.12; 21 | 22 | interface SourceLike { 23 | function cure() external view returns (uint256); 24 | } 25 | 26 | contract Cure { 27 | mapping (address => uint256) public wards; 28 | uint256 public live; 29 | address[] public srcs; 30 | uint256 public wait; 31 | uint256 public when; 32 | mapping (address => uint256) public pos; // position in srcs + 1, 0 means a source does not exist 33 | mapping (address => uint256) public amt; 34 | mapping (address => uint256) public loaded; 35 | uint256 public lCount; 36 | uint256 public say; 37 | 38 | // --- Events --- 39 | event Rely(address indexed usr); 40 | event Deny(address indexed usr); 41 | event File(bytes32 indexed what, uint256 data); 42 | event Lift(address indexed src); 43 | event Drop(address indexed src); 44 | event Load(address indexed src); 45 | event Cage(); 46 | 47 | modifier auth { 48 | require(wards[msg.sender] == 1, "Cure/not-authorized"); 49 | _; 50 | } 51 | 52 | // --- Internal --- 53 | function _add(uint256 x, uint256 y) internal pure returns (uint256 z) { 54 | require((z = x + y) >= x, "Cure/add-overflow"); 55 | } 56 | 57 | function _sub(uint256 x, uint256 y) internal pure returns (uint256 z) { 58 | require((z = x - y) <= x, "Cure/sub-underflow"); 59 | } 60 | 61 | constructor() public { 62 | live = 1; 63 | wards[msg.sender] = 1; 64 | emit Rely(msg.sender); 65 | } 66 | 67 | function tCount() external view returns (uint256 count_) { 68 | count_ = srcs.length; 69 | } 70 | 71 | function list() external view returns (address[] memory) { 72 | return srcs; 73 | } 74 | 75 | function tell() external view returns (uint256) { 76 | require(live == 0 && (lCount == srcs.length || block.timestamp >= when), "Cure/missing-load-and-time-not-passed"); 77 | return say; 78 | } 79 | 80 | function rely(address usr) external auth { 81 | require(live == 1, "Cure/not-live"); 82 | wards[usr] = 1; 83 | emit Rely(usr); 84 | } 85 | 86 | function deny(address usr) external auth { 87 | require(live == 1, "Cure/not-live"); 88 | wards[usr] = 0; 89 | emit Deny(usr); 90 | } 91 | 92 | function file(bytes32 what, uint256 data) external auth { 93 | require(live == 1, "Cure/not-live"); 94 | if (what == "wait") wait = data; 95 | else revert("Cure/file-unrecognized-param"); 96 | emit File(what, data); 97 | } 98 | 99 | function lift(address src) external auth { 100 | require(live == 1, "Cure/not-live"); 101 | require(pos[src] == 0, "Cure/already-existing-source"); 102 | srcs.push(src); 103 | pos[src] = srcs.length; 104 | emit Lift(src); 105 | } 106 | 107 | function drop(address src) external auth { 108 | require(live == 1, "Cure/not-live"); 109 | uint256 pos_ = pos[src]; 110 | require(pos_ > 0, "Cure/non-existing-source"); 111 | uint256 last = srcs.length; 112 | if (pos_ < last) { 113 | address move = srcs[last - 1]; 114 | srcs[pos_ - 1] = move; 115 | pos[move] = pos_; 116 | } 117 | srcs.pop(); 118 | delete pos[src]; 119 | delete amt[src]; 120 | emit Drop(src); 121 | } 122 | 123 | function cage() external auth { 124 | require(live == 1, "Cure/not-live"); 125 | live = 0; 126 | when = _add(block.timestamp, wait); 127 | emit Cage(); 128 | } 129 | 130 | function load(address src) external { 131 | require(live == 0, "Cure/still-live"); 132 | require(pos[src] > 0, "Cure/non-existing-source"); 133 | uint256 oldAmt_ = amt[src]; 134 | uint256 newAmt_ = amt[src] = SourceLike(src).cure(); 135 | say = _add(_sub(say, oldAmt_), newAmt_); 136 | if (loaded[src] == 0) { 137 | loaded[src] = 1; 138 | lCount++; 139 | } 140 | emit Load(src); 141 | } 142 | } 143 | -------------------------------------------------------------------------------- /src/dai.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: AGPL-3.0-or-later 2 | 3 | /// dai.sol -- Dai Stablecoin ERC-20 Token 4 | 5 | // Copyright (C) 2017, 2018, 2019 dbrock, rain, mrchico 6 | 7 | // This program is free software: you can redistribute it and/or modify 8 | // it under the terms of the GNU Affero General Public License as published by 9 | // the Free Software Foundation, either version 3 of the License, or 10 | // (at your option) any later version. 11 | // 12 | // This program is distributed in the hope that it will be useful, 13 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | // GNU Affero General Public License for more details. 16 | // 17 | // You should have received a copy of the GNU Affero General Public License 18 | // along with this program. If not, see . 19 | 20 | pragma solidity ^0.6.12; 21 | 22 | // FIXME: This contract was altered compared to the production version. 23 | // It doesn't use LibNote anymore. 24 | // New deployments of this contract will need to include custom events (TO DO). 25 | 26 | contract Dai { 27 | // --- Auth --- 28 | mapping (address => uint) public wards; 29 | function rely(address guy) external auth { wards[guy] = 1; } 30 | function deny(address guy) external auth { wards[guy] = 0; } 31 | modifier auth { 32 | require(wards[msg.sender] == 1, "Dai/not-authorized"); 33 | _; 34 | } 35 | 36 | // --- ERC20 Data --- 37 | string public constant name = "Dai Stablecoin"; 38 | string public constant symbol = "DAI"; 39 | string public constant version = "1"; 40 | uint8 public constant decimals = 18; 41 | uint256 public totalSupply; 42 | 43 | mapping (address => uint) public balanceOf; 44 | mapping (address => mapping (address => uint)) public allowance; 45 | mapping (address => uint) public nonces; 46 | 47 | event Approval(address indexed src, address indexed guy, uint wad); 48 | event Transfer(address indexed src, address indexed dst, uint wad); 49 | 50 | // --- Math --- 51 | function add(uint x, uint y) internal pure returns (uint z) { 52 | require((z = x + y) >= x); 53 | } 54 | function sub(uint x, uint y) internal pure returns (uint z) { 55 | require((z = x - y) <= x); 56 | } 57 | 58 | // --- EIP712 niceties --- 59 | bytes32 public DOMAIN_SEPARATOR; 60 | // bytes32 public constant PERMIT_TYPEHASH = keccak256("Permit(address holder,address spender,uint256 nonce,uint256 expiry,bool allowed)"); 61 | bytes32 public constant PERMIT_TYPEHASH = 0xea2aa0a1be11a07ed86d755c93467f4f82362b452371d1ba94d1715123511acb; 62 | 63 | constructor(uint256 chainId_) public { 64 | wards[msg.sender] = 1; 65 | DOMAIN_SEPARATOR = keccak256(abi.encode( 66 | keccak256("EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)"), 67 | keccak256(bytes(name)), 68 | keccak256(bytes(version)), 69 | chainId_, 70 | address(this) 71 | )); 72 | } 73 | 74 | // --- Token --- 75 | function transfer(address dst, uint wad) external returns (bool) { 76 | return transferFrom(msg.sender, dst, wad); 77 | } 78 | function transferFrom(address src, address dst, uint wad) 79 | public returns (bool) 80 | { 81 | require(balanceOf[src] >= wad, "Dai/insufficient-balance"); 82 | if (src != msg.sender && allowance[src][msg.sender] != uint(-1)) { 83 | require(allowance[src][msg.sender] >= wad, "Dai/insufficient-allowance"); 84 | allowance[src][msg.sender] = sub(allowance[src][msg.sender], wad); 85 | } 86 | balanceOf[src] = sub(balanceOf[src], wad); 87 | balanceOf[dst] = add(balanceOf[dst], wad); 88 | emit Transfer(src, dst, wad); 89 | return true; 90 | } 91 | function mint(address usr, uint wad) external auth { 92 | balanceOf[usr] = add(balanceOf[usr], wad); 93 | totalSupply = add(totalSupply, wad); 94 | emit Transfer(address(0), usr, wad); 95 | } 96 | function burn(address usr, uint wad) external { 97 | require(balanceOf[usr] >= wad, "Dai/insufficient-balance"); 98 | if (usr != msg.sender && allowance[usr][msg.sender] != uint(-1)) { 99 | require(allowance[usr][msg.sender] >= wad, "Dai/insufficient-allowance"); 100 | allowance[usr][msg.sender] = sub(allowance[usr][msg.sender], wad); 101 | } 102 | balanceOf[usr] = sub(balanceOf[usr], wad); 103 | totalSupply = sub(totalSupply, wad); 104 | emit Transfer(usr, address(0), wad); 105 | } 106 | function approve(address usr, uint wad) external returns (bool) { 107 | allowance[msg.sender][usr] = wad; 108 | emit Approval(msg.sender, usr, wad); 109 | return true; 110 | } 111 | 112 | // --- Alias --- 113 | function push(address usr, uint wad) external { 114 | transferFrom(msg.sender, usr, wad); 115 | } 116 | function pull(address usr, uint wad) external { 117 | transferFrom(usr, msg.sender, wad); 118 | } 119 | function move(address src, address dst, uint wad) external { 120 | transferFrom(src, dst, wad); 121 | } 122 | 123 | // --- Approve by signature --- 124 | function permit(address holder, address spender, uint256 nonce, uint256 expiry, 125 | bool allowed, uint8 v, bytes32 r, bytes32 s) external 126 | { 127 | bytes32 digest = 128 | keccak256(abi.encodePacked( 129 | "\x19\x01", 130 | DOMAIN_SEPARATOR, 131 | keccak256(abi.encode(PERMIT_TYPEHASH, 132 | holder, 133 | spender, 134 | nonce, 135 | expiry, 136 | allowed)) 137 | )); 138 | 139 | require(holder != address(0), "Dai/invalid-address-0"); 140 | require(holder == ecrecover(digest, v, r, s), "Dai/invalid-permit"); 141 | require(expiry == 0 || now <= expiry, "Dai/permit-expired"); 142 | require(nonce == nonces[holder]++, "Dai/invalid-nonce"); 143 | uint wad = allowed ? uint(-1) : 0; 144 | allowance[holder][spender] = wad; 145 | emit Approval(holder, spender, wad); 146 | } 147 | } 148 | -------------------------------------------------------------------------------- /src/dog.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: AGPL-3.0-or-later 2 | 3 | /// dog.sol -- Dai liquidation module 2.0 4 | 5 | // Copyright (C) 2020-2022 Dai Foundation 6 | // 7 | // This program is free software: you can redistribute it and/or modify 8 | // it under the terms of the GNU Affero General Public License as published by 9 | // the Free Software Foundation, either version 3 of the License, or 10 | // (at your option) any later version. 11 | // 12 | // This program is distributed in the hope that it will be useful, 13 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | // GNU Affero General Public License for more details. 16 | // 17 | // You should have received a copy of the GNU Affero General Public License 18 | // along with this program. If not, see . 19 | 20 | pragma solidity ^0.6.12; 21 | 22 | interface ClipperLike { 23 | function ilk() external view returns (bytes32); 24 | function kick( 25 | uint256 tab, 26 | uint256 lot, 27 | address usr, 28 | address kpr 29 | ) external returns (uint256); 30 | } 31 | 32 | interface VatLike { 33 | function ilks(bytes32) external view returns ( 34 | uint256 Art, // [wad] 35 | uint256 rate, // [ray] 36 | uint256 spot, // [ray] 37 | uint256 line, // [rad] 38 | uint256 dust // [rad] 39 | ); 40 | function urns(bytes32,address) external view returns ( 41 | uint256 ink, // [wad] 42 | uint256 art // [wad] 43 | ); 44 | function grab(bytes32,address,address,address,int256,int256) external; 45 | function hope(address) external; 46 | function nope(address) external; 47 | } 48 | 49 | interface VowLike { 50 | function fess(uint256) external; 51 | } 52 | 53 | contract Dog { 54 | // --- Auth --- 55 | mapping (address => uint256) public wards; 56 | function rely(address usr) external auth { wards[usr] = 1; emit Rely(usr); } 57 | function deny(address usr) external auth { wards[usr] = 0; emit Deny(usr); } 58 | modifier auth { 59 | require(wards[msg.sender] == 1, "Dog/not-authorized"); 60 | _; 61 | } 62 | 63 | // --- Data --- 64 | struct Ilk { 65 | address clip; // Liquidator 66 | uint256 chop; // Liquidation Penalty [wad] 67 | uint256 hole; // Max DAI needed to cover debt+fees of active auctions per ilk [rad] 68 | uint256 dirt; // Amt DAI needed to cover debt+fees of active auctions per ilk [rad] 69 | } 70 | 71 | VatLike immutable public vat; // CDP Engine 72 | 73 | mapping (bytes32 => Ilk) public ilks; 74 | 75 | VowLike public vow; // Debt Engine 76 | uint256 public live; // Active Flag 77 | uint256 public Hole; // Max DAI needed to cover debt+fees of active auctions [rad] 78 | uint256 public Dirt; // Amt DAI needed to cover debt+fees of active auctions [rad] 79 | 80 | // --- Events --- 81 | event Rely(address indexed usr); 82 | event Deny(address indexed usr); 83 | 84 | event File(bytes32 indexed what, uint256 data); 85 | event File(bytes32 indexed what, address data); 86 | event File(bytes32 indexed ilk, bytes32 indexed what, uint256 data); 87 | event File(bytes32 indexed ilk, bytes32 indexed what, address clip); 88 | 89 | event Bark( 90 | bytes32 indexed ilk, 91 | address indexed urn, 92 | uint256 ink, 93 | uint256 art, 94 | uint256 due, 95 | address clip, 96 | uint256 indexed id 97 | ); 98 | event Digs(bytes32 indexed ilk, uint256 rad); 99 | event Cage(); 100 | 101 | // --- Init --- 102 | constructor(address vat_) public { 103 | vat = VatLike(vat_); 104 | live = 1; 105 | wards[msg.sender] = 1; 106 | emit Rely(msg.sender); 107 | } 108 | 109 | // --- Math --- 110 | uint256 constant WAD = 10 ** 18; 111 | 112 | function min(uint256 x, uint256 y) internal pure returns (uint256 z) { 113 | z = x <= y ? x : y; 114 | } 115 | function add(uint256 x, uint256 y) internal pure returns (uint256 z) { 116 | require((z = x + y) >= x); 117 | } 118 | function sub(uint256 x, uint256 y) internal pure returns (uint256 z) { 119 | require((z = x - y) <= x); 120 | } 121 | function mul(uint256 x, uint256 y) internal pure returns (uint256 z) { 122 | require(y == 0 || (z = x * y) / y == x); 123 | } 124 | 125 | // --- Administration --- 126 | function file(bytes32 what, address data) external auth { 127 | if (what == "vow") vow = VowLike(data); 128 | else revert("Dog/file-unrecognized-param"); 129 | emit File(what, data); 130 | } 131 | function file(bytes32 what, uint256 data) external auth { 132 | if (what == "Hole") Hole = data; 133 | else revert("Dog/file-unrecognized-param"); 134 | emit File(what, data); 135 | } 136 | function file(bytes32 ilk, bytes32 what, uint256 data) external auth { 137 | if (what == "chop") { 138 | require(data >= WAD, "Dog/file-chop-lt-WAD"); 139 | ilks[ilk].chop = data; 140 | } else if (what == "hole") ilks[ilk].hole = data; 141 | else revert("Dog/file-unrecognized-param"); 142 | emit File(ilk, what, data); 143 | } 144 | function file(bytes32 ilk, bytes32 what, address clip) external auth { 145 | if (what == "clip") { 146 | require(ilk == ClipperLike(clip).ilk(), "Dog/file-ilk-neq-clip.ilk"); 147 | ilks[ilk].clip = clip; 148 | } else revert("Dog/file-unrecognized-param"); 149 | emit File(ilk, what, clip); 150 | } 151 | 152 | function chop(bytes32 ilk) external view returns (uint256) { 153 | return ilks[ilk].chop; 154 | } 155 | 156 | // --- CDP Liquidation: all bark and no bite --- 157 | // 158 | // Liquidate a Vault and start a Dutch auction to sell its collateral for DAI. 159 | // 160 | // The third argument is the address that will receive the liquidation reward, if any. 161 | // 162 | // The entire Vault will be liquidated except when the target amount of DAI to be raised in 163 | // the resulting auction (debt of Vault + liquidation penalty) causes either Dirt to exceed 164 | // Hole or ilk.dirt to exceed ilk.hole by an economically significant amount. In that 165 | // case, a partial liquidation is performed to respect the global and per-ilk limits on 166 | // outstanding DAI target. The one exception is if the resulting auction would likely 167 | // have too little collateral to be interesting to Keepers (debt taken from Vault < ilk.dust), 168 | // in which case the function reverts. Please refer to the code and comments within if 169 | // more detail is desired. 170 | function bark(bytes32 ilk, address urn, address kpr) external returns (uint256 id) { 171 | require(live == 1, "Dog/not-live"); 172 | 173 | (uint256 ink, uint256 art) = vat.urns(ilk, urn); 174 | Ilk memory milk = ilks[ilk]; 175 | uint256 dart; 176 | uint256 rate; 177 | uint256 dust; 178 | { 179 | uint256 spot; 180 | (,rate, spot,, dust) = vat.ilks(ilk); 181 | require(spot > 0 && mul(ink, spot) < mul(art, rate), "Dog/not-unsafe"); 182 | 183 | // Get the minimum value between: 184 | // 1) Remaining space in the general Hole 185 | // 2) Remaining space in the collateral hole 186 | require(Hole > Dirt && milk.hole > milk.dirt, "Dog/liquidation-limit-hit"); 187 | uint256 room = min(Hole - Dirt, milk.hole - milk.dirt); 188 | 189 | // uint256.max()/(RAD*WAD) = 115,792,089,237,316 190 | dart = min(art, mul(room, WAD) / rate / milk.chop); 191 | 192 | // Partial liquidation edge case logic 193 | if (art > dart) { 194 | if (mul(art - dart, rate) < dust) { 195 | 196 | // If the leftover Vault would be dusty, just liquidate it entirely. 197 | // This will result in at least one of dirt_i > hole_i or Dirt > Hole becoming true. 198 | // The amount of excess will be bounded above by ceiling(dust_i * chop_i / WAD). 199 | // This deviation is assumed to be small compared to both hole_i and Hole, so that 200 | // the extra amount of target DAI over the limits intended is not of economic concern. 201 | dart = art; 202 | } else { 203 | 204 | // In a partial liquidation, the resulting auction should also be non-dusty. 205 | require(mul(dart, rate) >= dust, "Dog/dusty-auction-from-partial-liquidation"); 206 | } 207 | } 208 | } 209 | 210 | uint256 dink = mul(ink, dart) / art; 211 | 212 | require(dink > 0, "Dog/null-auction"); 213 | require(dart <= 2**255 && dink <= 2**255, "Dog/overflow"); 214 | 215 | vat.grab( 216 | ilk, urn, milk.clip, address(vow), -int256(dink), -int256(dart) 217 | ); 218 | 219 | uint256 due = mul(dart, rate); 220 | vow.fess(due); 221 | 222 | { // Avoid stack too deep 223 | // This calcuation will overflow if dart*rate exceeds ~10^14 224 | uint256 tab = mul(due, milk.chop) / WAD; 225 | Dirt = add(Dirt, tab); 226 | ilks[ilk].dirt = add(milk.dirt, tab); 227 | 228 | id = ClipperLike(milk.clip).kick({ 229 | tab: tab, 230 | lot: dink, 231 | usr: urn, 232 | kpr: kpr 233 | }); 234 | } 235 | 236 | emit Bark(ilk, urn, dink, dart, due, milk.clip, id); 237 | } 238 | 239 | function digs(bytes32 ilk, uint256 rad) external auth { 240 | Dirt = sub(Dirt, rad); 241 | ilks[ilk].dirt = sub(ilks[ilk].dirt, rad); 242 | emit Digs(ilk, rad); 243 | } 244 | 245 | function cage() external auth { 246 | live = 0; 247 | emit Cage(); 248 | } 249 | } 250 | -------------------------------------------------------------------------------- /src/flap.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: AGPL-3.0-or-later 2 | 3 | /// flap.sol -- Surplus auction 4 | 5 | // Copyright (C) 2018 Rain 6 | // Copyright (C) 2022 Dai Foundation 7 | // 8 | // This program is free software: you can redistribute it and/or modify 9 | // it under the terms of the GNU Affero General Public License as published by 10 | // the Free Software Foundation, either version 3 of the License, or 11 | // (at your option) any later version. 12 | // 13 | // This program is distributed in the hope that it will be useful, 14 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 15 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 | // GNU Affero General Public License for more details. 17 | // 18 | // You should have received a copy of the GNU Affero General Public License 19 | // along with this program. If not, see . 20 | 21 | pragma solidity ^0.6.12; 22 | 23 | // FIXME: This contract was altered compared to the production version. 24 | // It doesn't use LibNote anymore. 25 | // New deployments of this contract will need to include custom events (TO DO). 26 | 27 | interface VatLike { 28 | function move(address,address,uint) external; 29 | } 30 | interface GemLike { 31 | function move(address,address,uint) external; 32 | function burn(address,uint) external; 33 | } 34 | 35 | /* 36 | This thing lets you sell some dai in return for gems. 37 | 38 | - `lot` dai in return for bid 39 | - `bid` gems paid 40 | - `ttl` single bid lifetime 41 | - `beg` minimum bid increase 42 | - `end` max auction duration 43 | */ 44 | 45 | contract Flapper { 46 | // --- Auth --- 47 | mapping (address => uint) public wards; 48 | function rely(address usr) external auth { wards[usr] = 1; } 49 | function deny(address usr) external auth { wards[usr] = 0; } 50 | modifier auth { 51 | require(wards[msg.sender] == 1, "Flapper/not-authorized"); 52 | _; 53 | } 54 | 55 | // --- Data --- 56 | struct Bid { 57 | uint256 bid; // gems paid [wad] 58 | uint256 lot; // dai in return for bid [rad] 59 | address guy; // high bidder 60 | uint48 tic; // bid expiry time [unix epoch time] 61 | uint48 end; // auction expiry time [unix epoch time] 62 | } 63 | 64 | mapping (uint => Bid) public bids; 65 | 66 | VatLike public vat; // CDP Engine 67 | GemLike public gem; 68 | 69 | uint256 constant ONE = 1.00E18; 70 | uint256 public beg = 1.05E18; // 5% minimum bid increase 71 | uint48 public ttl = 3 hours; // 3 hours bid duration [seconds] 72 | uint48 public tau = 2 days; // 2 days total auction length [seconds] 73 | uint256 public kicks = 0; 74 | uint256 public live; // Active Flag 75 | uint256 public lid; // max dai to be in auction at one time [rad] 76 | uint256 public fill; // current dai in auction [rad] 77 | 78 | // --- Events --- 79 | event Kick( 80 | uint256 id, 81 | uint256 lot, 82 | uint256 bid 83 | ); 84 | 85 | // --- Init --- 86 | constructor(address vat_, address gem_) public { 87 | wards[msg.sender] = 1; 88 | vat = VatLike(vat_); 89 | gem = GemLike(gem_); 90 | live = 1; 91 | } 92 | 93 | // --- Math --- 94 | function add(uint48 x, uint48 y) internal pure returns (uint48 z) { 95 | require((z = x + y) >= x); 96 | } 97 | function add256(uint x, uint y) internal pure returns (uint z) { 98 | require((z = x + y) >= x); 99 | } 100 | function sub(uint x, uint y) internal pure returns (uint z) { 101 | require((z = x - y) <= x); 102 | } 103 | function mul(uint x, uint y) internal pure returns (uint z) { 104 | require(y == 0 || (z = x * y) / y == x); 105 | } 106 | 107 | // --- Admin --- 108 | function file(bytes32 what, uint data) external auth { 109 | if (what == "beg") beg = data; 110 | else if (what == "ttl") ttl = uint48(data); 111 | else if (what == "tau") tau = uint48(data); 112 | else if (what == "lid") lid = data; 113 | else revert("Flapper/file-unrecognized-param"); 114 | } 115 | 116 | // --- Auction --- 117 | function kick(uint lot, uint bid) external auth returns (uint id) { 118 | require(live == 1, "Flapper/not-live"); 119 | require(kicks < uint(-1), "Flapper/overflow"); 120 | fill = add256(fill, lot); 121 | require(fill <= lid, "Flapper/over-lid"); 122 | id = ++kicks; 123 | 124 | bids[id].bid = bid; 125 | bids[id].lot = lot; 126 | bids[id].guy = msg.sender; // configurable?? 127 | bids[id].end = add(uint48(now), tau); 128 | 129 | vat.move(msg.sender, address(this), lot); 130 | 131 | emit Kick(id, lot, bid); 132 | } 133 | function tick(uint id) external { 134 | require(bids[id].end < now, "Flapper/not-finished"); 135 | require(bids[id].tic == 0, "Flapper/bid-already-placed"); 136 | bids[id].end = add(uint48(now), tau); 137 | } 138 | function tend(uint id, uint lot, uint bid) external { 139 | require(live == 1, "Flapper/not-live"); 140 | require(bids[id].guy != address(0), "Flapper/guy-not-set"); 141 | require(bids[id].tic > now || bids[id].tic == 0, "Flapper/already-finished-tic"); 142 | require(bids[id].end > now, "Flapper/already-finished-end"); 143 | 144 | require(lot == bids[id].lot, "Flapper/lot-not-matching"); 145 | require(bid > bids[id].bid, "Flapper/bid-not-higher"); 146 | require(mul(bid, ONE) >= mul(beg, bids[id].bid), "Flapper/insufficient-increase"); 147 | 148 | if (msg.sender != bids[id].guy) { 149 | gem.move(msg.sender, bids[id].guy, bids[id].bid); 150 | bids[id].guy = msg.sender; 151 | } 152 | gem.move(msg.sender, address(this), bid - bids[id].bid); 153 | 154 | bids[id].bid = bid; 155 | bids[id].tic = add(uint48(now), ttl); 156 | } 157 | function deal(uint id) external { 158 | require(live == 1, "Flapper/not-live"); 159 | require(bids[id].tic != 0 && (bids[id].tic < now || bids[id].end < now), "Flapper/not-finished"); 160 | uint256 lot = bids[id].lot; 161 | vat.move(address(this), bids[id].guy, lot); 162 | gem.burn(address(this), bids[id].bid); 163 | delete bids[id]; 164 | fill = sub(fill, lot); 165 | } 166 | 167 | function cage(uint rad) external auth { 168 | live = 0; 169 | vat.move(address(this), msg.sender, rad); 170 | } 171 | function yank(uint id) external { 172 | require(live == 0, "Flapper/still-live"); 173 | require(bids[id].guy != address(0), "Flapper/guy-not-set"); 174 | gem.move(address(this), bids[id].guy, bids[id].bid); 175 | delete bids[id]; 176 | } 177 | } 178 | -------------------------------------------------------------------------------- /src/flip.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: AGPL-3.0-or-later 2 | 3 | /// flip.sol -- Collateral auction 4 | 5 | // Copyright (C) 2018 Rain 6 | // 7 | // This program is free software: you can redistribute it and/or modify 8 | // it under the terms of the GNU Affero General Public License as published by 9 | // the Free Software Foundation, either version 3 of the License, or 10 | // (at your option) any later version. 11 | // 12 | // This program is distributed in the hope that it will be useful, 13 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | // GNU Affero General Public License for more details. 16 | // 17 | // You should have received a copy of the GNU Affero General Public License 18 | // along with this program. If not, see . 19 | 20 | pragma solidity >=0.5.12; 21 | 22 | // FIXME: This contract was altered compared to the production version. 23 | // It doesn't use LibNote anymore. 24 | // New deployments of this contract will need to include custom events (TO DO). 25 | 26 | interface VatLike { 27 | function move(address,address,uint256) external; 28 | function flux(bytes32,address,address,uint256) external; 29 | } 30 | 31 | interface CatLike { 32 | function claw(uint256) external; 33 | } 34 | 35 | /* 36 | This thing lets you flip some gems for a given amount of dai. 37 | Once the given amount of dai is raised, gems are forgone instead. 38 | 39 | - `lot` gems in return for bid 40 | - `tab` total dai wanted 41 | - `bid` dai paid 42 | - `gal` receives dai income 43 | - `usr` receives gem forgone 44 | - `ttl` single bid lifetime 45 | - `beg` minimum bid increase 46 | - `end` max auction duration 47 | */ 48 | 49 | contract Flipper { 50 | // --- Auth --- 51 | mapping (address => uint256) public wards; 52 | function rely(address usr) external auth { wards[usr] = 1; } 53 | function deny(address usr) external auth { wards[usr] = 0; } 54 | modifier auth { 55 | require(wards[msg.sender] == 1, "Flipper/not-authorized"); 56 | _; 57 | } 58 | 59 | // --- Data --- 60 | struct Bid { 61 | uint256 bid; // dai paid [rad] 62 | uint256 lot; // gems in return for bid [wad] 63 | address guy; // high bidder 64 | uint48 tic; // bid expiry time [unix epoch time] 65 | uint48 end; // auction expiry time [unix epoch time] 66 | address usr; 67 | address gal; 68 | uint256 tab; // total dai wanted [rad] 69 | } 70 | 71 | mapping (uint256 => Bid) public bids; 72 | 73 | VatLike public vat; // CDP Engine 74 | bytes32 public ilk; // collateral type 75 | 76 | uint256 constant ONE = 1.00E18; 77 | uint256 public beg = 1.05E18; // 5% minimum bid increase 78 | uint48 public ttl = 3 hours; // 3 hours bid duration [seconds] 79 | uint48 public tau = 2 days; // 2 days total auction length [seconds] 80 | uint256 public kicks = 0; 81 | CatLike public cat; // cat liquidation module 82 | 83 | // --- Events --- 84 | event Kick( 85 | uint256 id, 86 | uint256 lot, 87 | uint256 bid, 88 | uint256 tab, 89 | address indexed usr, 90 | address indexed gal 91 | ); 92 | 93 | // --- Init --- 94 | constructor(address vat_, address cat_, bytes32 ilk_) public { 95 | vat = VatLike(vat_); 96 | cat = CatLike(cat_); 97 | ilk = ilk_; 98 | wards[msg.sender] = 1; 99 | } 100 | 101 | // --- Math --- 102 | function add(uint48 x, uint48 y) internal pure returns (uint48 z) { 103 | require((z = x + y) >= x); 104 | } 105 | function mul(uint256 x, uint256 y) internal pure returns (uint256 z) { 106 | require(y == 0 || (z = x * y) / y == x); 107 | } 108 | 109 | // --- Admin --- 110 | function file(bytes32 what, uint256 data) external auth { 111 | if (what == "beg") beg = data; 112 | else if (what == "ttl") ttl = uint48(data); 113 | else if (what == "tau") tau = uint48(data); 114 | else revert("Flipper/file-unrecognized-param"); 115 | } 116 | function file(bytes32 what, address data) external auth { 117 | if (what == "cat") cat = CatLike(data); 118 | else revert("Flipper/file-unrecognized-param"); 119 | } 120 | 121 | // --- Auction --- 122 | function kick(address usr, address gal, uint256 tab, uint256 lot, uint256 bid) 123 | public auth returns (uint256 id) 124 | { 125 | require(kicks < uint256(-1), "Flipper/overflow"); 126 | id = ++kicks; 127 | 128 | bids[id].bid = bid; 129 | bids[id].lot = lot; 130 | bids[id].guy = msg.sender; // configurable?? 131 | bids[id].end = add(uint48(now), tau); 132 | bids[id].usr = usr; 133 | bids[id].gal = gal; 134 | bids[id].tab = tab; 135 | 136 | vat.flux(ilk, msg.sender, address(this), lot); 137 | 138 | emit Kick(id, lot, bid, tab, usr, gal); 139 | } 140 | function tick(uint256 id) external { 141 | require(bids[id].end < now, "Flipper/not-finished"); 142 | require(bids[id].tic == 0, "Flipper/bid-already-placed"); 143 | bids[id].end = add(uint48(now), tau); 144 | } 145 | function tend(uint256 id, uint256 lot, uint256 bid) external { 146 | require(bids[id].guy != address(0), "Flipper/guy-not-set"); 147 | require(bids[id].tic > now || bids[id].tic == 0, "Flipper/already-finished-tic"); 148 | require(bids[id].end > now, "Flipper/already-finished-end"); 149 | 150 | require(lot == bids[id].lot, "Flipper/lot-not-matching"); 151 | require(bid <= bids[id].tab, "Flipper/higher-than-tab"); 152 | require(bid > bids[id].bid, "Flipper/bid-not-higher"); 153 | require(mul(bid, ONE) >= mul(beg, bids[id].bid) || bid == bids[id].tab, "Flipper/insufficient-increase"); 154 | 155 | if (msg.sender != bids[id].guy) { 156 | vat.move(msg.sender, bids[id].guy, bids[id].bid); 157 | bids[id].guy = msg.sender; 158 | } 159 | vat.move(msg.sender, bids[id].gal, bid - bids[id].bid); 160 | 161 | bids[id].bid = bid; 162 | bids[id].tic = add(uint48(now), ttl); 163 | } 164 | function dent(uint256 id, uint256 lot, uint256 bid) external { 165 | require(bids[id].guy != address(0), "Flipper/guy-not-set"); 166 | require(bids[id].tic > now || bids[id].tic == 0, "Flipper/already-finished-tic"); 167 | require(bids[id].end > now, "Flipper/already-finished-end"); 168 | 169 | require(bid == bids[id].bid, "Flipper/not-matching-bid"); 170 | require(bid == bids[id].tab, "Flipper/tend-not-finished"); 171 | require(lot < bids[id].lot, "Flipper/lot-not-lower"); 172 | require(mul(beg, lot) <= mul(bids[id].lot, ONE), "Flipper/insufficient-decrease"); 173 | 174 | if (msg.sender != bids[id].guy) { 175 | vat.move(msg.sender, bids[id].guy, bid); 176 | bids[id].guy = msg.sender; 177 | } 178 | vat.flux(ilk, address(this), bids[id].usr, bids[id].lot - lot); 179 | 180 | bids[id].lot = lot; 181 | bids[id].tic = add(uint48(now), ttl); 182 | } 183 | function deal(uint256 id) external { 184 | require(bids[id].tic != 0 && (bids[id].tic < now || bids[id].end < now), "Flipper/not-finished"); 185 | cat.claw(bids[id].tab); 186 | vat.flux(ilk, address(this), bids[id].guy, bids[id].lot); 187 | delete bids[id]; 188 | } 189 | 190 | function yank(uint256 id) external auth { 191 | require(bids[id].guy != address(0), "Flipper/guy-not-set"); 192 | require(bids[id].bid < bids[id].tab, "Flipper/already-dent-phase"); 193 | cat.claw(bids[id].tab); 194 | vat.flux(ilk, address(this), msg.sender, bids[id].lot); 195 | vat.move(msg.sender, bids[id].guy, bids[id].bid); 196 | delete bids[id]; 197 | } 198 | } 199 | -------------------------------------------------------------------------------- /src/flop.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: AGPL-3.0-or-later 2 | 3 | /// flop.sol -- Debt auction 4 | 5 | // Copyright (C) 2018 Rain 6 | // 7 | // This program is free software: you can redistribute it and/or modify 8 | // it under the terms of the GNU Affero General Public License as published by 9 | // the Free Software Foundation, either version 3 of the License, or 10 | // (at your option) any later version. 11 | // 12 | // This program is distributed in the hope that it will be useful, 13 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | // GNU Affero General Public License for more details. 16 | // 17 | // You should have received a copy of the GNU Affero General Public License 18 | // along with this program. If not, see . 19 | 20 | pragma solidity ^0.6.12; 21 | 22 | // FIXME: This contract was altered compared to the production version. 23 | // It doesn't use LibNote anymore. 24 | // New deployments of this contract will need to include custom events (TO DO). 25 | 26 | interface VatLike { 27 | function move(address,address,uint) external; 28 | function suck(address,address,uint) external; 29 | } 30 | interface GemLike { 31 | function mint(address,uint) external; 32 | } 33 | interface VowLike { 34 | function Ash() external returns (uint); 35 | function kiss(uint) external; 36 | } 37 | 38 | /* 39 | This thing creates gems on demand in return for dai. 40 | 41 | - `lot` gems in return for bid 42 | - `bid` dai paid 43 | - `gal` receives dai income 44 | - `ttl` single bid lifetime 45 | - `beg` minimum bid increase 46 | - `end` max auction duration 47 | */ 48 | 49 | contract Flopper { 50 | // --- Auth --- 51 | mapping (address => uint) public wards; 52 | function rely(address usr) external auth { wards[usr] = 1; } 53 | function deny(address usr) external auth { wards[usr] = 0; } 54 | modifier auth { 55 | require(wards[msg.sender] == 1, "Flopper/not-authorized"); 56 | _; 57 | } 58 | 59 | // --- Data --- 60 | struct Bid { 61 | uint256 bid; // dai paid [rad] 62 | uint256 lot; // gems in return for bid [wad] 63 | address guy; // high bidder 64 | uint48 tic; // bid expiry time [unix epoch time] 65 | uint48 end; // auction expiry time [unix epoch time] 66 | } 67 | 68 | mapping (uint => Bid) public bids; 69 | 70 | VatLike public vat; // CDP Engine 71 | GemLike public gem; 72 | 73 | uint256 constant ONE = 1.00E18; 74 | uint256 public beg = 1.05E18; // 5% minimum bid increase 75 | uint256 public pad = 1.50E18; // 50% lot increase for tick 76 | uint48 public ttl = 3 hours; // 3 hours bid lifetime [seconds] 77 | uint48 public tau = 2 days; // 2 days total auction length [seconds] 78 | uint256 public kicks = 0; 79 | uint256 public live; // Active Flag 80 | address public vow; // not used until shutdown 81 | 82 | // --- Events --- 83 | event Kick( 84 | uint256 id, 85 | uint256 lot, 86 | uint256 bid, 87 | address indexed gal 88 | ); 89 | 90 | // --- Init --- 91 | constructor(address vat_, address gem_) public { 92 | wards[msg.sender] = 1; 93 | vat = VatLike(vat_); 94 | gem = GemLike(gem_); 95 | live = 1; 96 | } 97 | 98 | // --- Math --- 99 | function add(uint48 x, uint48 y) internal pure returns (uint48 z) { 100 | require((z = x + y) >= x); 101 | } 102 | function mul(uint x, uint y) internal pure returns (uint z) { 103 | require(y == 0 || (z = x * y) / y == x); 104 | } 105 | function min(uint x, uint y) internal pure returns (uint z) { 106 | if (x > y) { z = y; } else { z = x; } 107 | } 108 | 109 | // --- Admin --- 110 | function file(bytes32 what, uint data) external auth { 111 | if (what == "beg") beg = data; 112 | else if (what == "pad") pad = data; 113 | else if (what == "ttl") ttl = uint48(data); 114 | else if (what == "tau") tau = uint48(data); 115 | else revert("Flopper/file-unrecognized-param"); 116 | } 117 | 118 | // --- Auction --- 119 | function kick(address gal, uint lot, uint bid) external auth returns (uint id) { 120 | require(live == 1, "Flopper/not-live"); 121 | require(kicks < uint(-1), "Flopper/overflow"); 122 | id = ++kicks; 123 | 124 | bids[id].bid = bid; 125 | bids[id].lot = lot; 126 | bids[id].guy = gal; 127 | bids[id].end = add(uint48(now), tau); 128 | 129 | emit Kick(id, lot, bid, gal); 130 | } 131 | function tick(uint id) external { 132 | require(bids[id].end < now, "Flopper/not-finished"); 133 | require(bids[id].tic == 0, "Flopper/bid-already-placed"); 134 | bids[id].lot = mul(pad, bids[id].lot) / ONE; 135 | bids[id].end = add(uint48(now), tau); 136 | } 137 | function dent(uint id, uint lot, uint bid) external { 138 | require(live == 1, "Flopper/not-live"); 139 | require(bids[id].guy != address(0), "Flopper/guy-not-set"); 140 | require(bids[id].tic > now || bids[id].tic == 0, "Flopper/already-finished-tic"); 141 | require(bids[id].end > now, "Flopper/already-finished-end"); 142 | 143 | require(bid == bids[id].bid, "Flopper/not-matching-bid"); 144 | require(lot < bids[id].lot, "Flopper/lot-not-lower"); 145 | require(mul(beg, lot) <= mul(bids[id].lot, ONE), "Flopper/insufficient-decrease"); 146 | 147 | if (msg.sender != bids[id].guy) { 148 | vat.move(msg.sender, bids[id].guy, bid); 149 | 150 | // on first dent, clear as much Ash as possible 151 | if (bids[id].tic == 0) { 152 | uint Ash = VowLike(bids[id].guy).Ash(); 153 | VowLike(bids[id].guy).kiss(min(bid, Ash)); 154 | } 155 | 156 | bids[id].guy = msg.sender; 157 | } 158 | 159 | bids[id].lot = lot; 160 | bids[id].tic = add(uint48(now), ttl); 161 | } 162 | function deal(uint id) external { 163 | require(live == 1, "Flopper/not-live"); 164 | require(bids[id].tic != 0 && (bids[id].tic < now || bids[id].end < now), "Flopper/not-finished"); 165 | gem.mint(bids[id].guy, bids[id].lot); 166 | delete bids[id]; 167 | } 168 | 169 | // --- Shutdown --- 170 | function cage() external auth { 171 | live = 0; 172 | vow = msg.sender; 173 | } 174 | function yank(uint id) external { 175 | require(live == 0, "Flopper/still-live"); 176 | require(bids[id].guy != address(0), "Flopper/guy-not-set"); 177 | vat.suck(vow, bids[id].guy, bids[id].bid); 178 | delete bids[id]; 179 | } 180 | } 181 | -------------------------------------------------------------------------------- /src/join.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: AGPL-3.0-or-later 2 | 3 | /// join.sol -- Basic token adapters 4 | 5 | // Copyright (C) 2018 Rain 6 | // 7 | // This program is free software: you can redistribute it and/or modify 8 | // it under the terms of the GNU Affero General Public License as published by 9 | // the Free Software Foundation, either version 3 of the License, or 10 | // (at your option) any later version. 11 | // 12 | // This program is distributed in the hope that it will be useful, 13 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | // GNU Affero General Public License for more details. 16 | // 17 | // You should have received a copy of the GNU Affero General Public License 18 | // along with this program. If not, see . 19 | 20 | pragma solidity ^0.6.12; 21 | 22 | // FIXME: This contract was altered compared to the production version. 23 | // It doesn't use LibNote anymore. 24 | // New deployments of this contract will need to include custom events (TO DO). 25 | 26 | interface GemLike { 27 | function decimals() external view returns (uint); 28 | function transfer(address,uint) external returns (bool); 29 | function transferFrom(address,address,uint) external returns (bool); 30 | } 31 | 32 | interface DSTokenLike { 33 | function mint(address,uint) external; 34 | function burn(address,uint) external; 35 | } 36 | 37 | interface VatLike { 38 | function slip(bytes32,address,int) external; 39 | function move(address,address,uint) external; 40 | } 41 | 42 | /* 43 | Here we provide *adapters* to connect the Vat to arbitrary external 44 | token implementations, creating a bounded context for the Vat. The 45 | adapters here are provided as working examples: 46 | 47 | - `GemJoin`: For well behaved ERC20 tokens, with simple transfer 48 | semantics. 49 | 50 | - `ETHJoin`: For native Ether. 51 | 52 | - `DaiJoin`: For connecting internal Dai balances to an external 53 | `DSToken` implementation. 54 | 55 | In practice, adapter implementations will be varied and specific to 56 | individual collateral types, accounting for different transfer 57 | semantics and token standards. 58 | 59 | Adapters need to implement two basic methods: 60 | 61 | - `join`: enter collateral into the system 62 | - `exit`: remove collateral from the system 63 | 64 | */ 65 | 66 | contract GemJoin { 67 | // --- Auth --- 68 | mapping (address => uint) public wards; 69 | function rely(address usr) external auth { 70 | wards[usr] = 1; 71 | emit Rely(usr); 72 | } 73 | function deny(address usr) external auth { 74 | wards[usr] = 0; 75 | emit Deny(usr); 76 | } 77 | modifier auth { 78 | require(wards[msg.sender] == 1, "GemJoin/not-authorized"); 79 | _; 80 | } 81 | 82 | VatLike public vat; // CDP Engine 83 | bytes32 public ilk; // Collateral Type 84 | GemLike public gem; 85 | uint public dec; 86 | uint public live; // Active Flag 87 | 88 | // Events 89 | event Rely(address indexed usr); 90 | event Deny(address indexed usr); 91 | event Join(address indexed usr, uint256 wad); 92 | event Exit(address indexed usr, uint256 wad); 93 | event Cage(); 94 | 95 | constructor(address vat_, bytes32 ilk_, address gem_) public { 96 | wards[msg.sender] = 1; 97 | live = 1; 98 | vat = VatLike(vat_); 99 | ilk = ilk_; 100 | gem = GemLike(gem_); 101 | dec = gem.decimals(); 102 | emit Rely(msg.sender); 103 | } 104 | function cage() external auth { 105 | live = 0; 106 | emit Cage(); 107 | } 108 | function join(address usr, uint wad) external { 109 | require(live == 1, "GemJoin/not-live"); 110 | require(int(wad) >= 0, "GemJoin/overflow"); 111 | vat.slip(ilk, usr, int(wad)); 112 | require(gem.transferFrom(msg.sender, address(this), wad), "GemJoin/failed-transfer"); 113 | emit Join(usr, wad); 114 | } 115 | function exit(address usr, uint wad) external { 116 | require(wad <= 2 ** 255, "GemJoin/overflow"); 117 | vat.slip(ilk, msg.sender, -int(wad)); 118 | require(gem.transfer(usr, wad), "GemJoin/failed-transfer"); 119 | emit Exit(usr, wad); 120 | } 121 | } 122 | 123 | contract DaiJoin { 124 | // --- Auth --- 125 | mapping (address => uint) public wards; 126 | function rely(address usr) external auth { 127 | wards[usr] = 1; 128 | emit Rely(usr); 129 | } 130 | function deny(address usr) external auth { 131 | wards[usr] = 0; 132 | emit Deny(usr); 133 | } 134 | modifier auth { 135 | require(wards[msg.sender] == 1, "DaiJoin/not-authorized"); 136 | _; 137 | } 138 | 139 | VatLike public vat; // CDP Engine 140 | DSTokenLike public dai; // Stablecoin Token 141 | uint public live; // Active Flag 142 | 143 | // Events 144 | event Rely(address indexed usr); 145 | event Deny(address indexed usr); 146 | event Join(address indexed usr, uint256 wad); 147 | event Exit(address indexed usr, uint256 wad); 148 | event Cage(); 149 | 150 | constructor(address vat_, address dai_) public { 151 | wards[msg.sender] = 1; 152 | live = 1; 153 | vat = VatLike(vat_); 154 | dai = DSTokenLike(dai_); 155 | } 156 | function cage() external auth { 157 | live = 0; 158 | emit Cage(); 159 | } 160 | uint constant ONE = 10 ** 27; 161 | function mul(uint x, uint y) internal pure returns (uint z) { 162 | require(y == 0 || (z = x * y) / y == x); 163 | } 164 | function join(address usr, uint wad) external { 165 | vat.move(address(this), usr, mul(ONE, wad)); 166 | dai.burn(msg.sender, wad); 167 | emit Join(usr, wad); 168 | } 169 | function exit(address usr, uint wad) external { 170 | require(live == 1, "DaiJoin/not-live"); 171 | vat.move(msg.sender, address(this), mul(ONE, wad)); 172 | dai.mint(usr, wad); 173 | emit Exit(usr, wad); 174 | } 175 | } 176 | -------------------------------------------------------------------------------- /src/jug.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: AGPL-3.0-or-later 2 | 3 | /// jug.sol -- Dai Lending Rate 4 | 5 | // Copyright (C) 2018 Rain 6 | // 7 | // This program is free software: you can redistribute it and/or modify 8 | // it under the terms of the GNU Affero General Public License as published by 9 | // the Free Software Foundation, either version 3 of the License, or 10 | // (at your option) any later version. 11 | // 12 | // This program is distributed in the hope that it will be useful, 13 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | // GNU Affero General Public License for more details. 16 | // 17 | // You should have received a copy of the GNU Affero General Public License 18 | // along with this program. If not, see . 19 | 20 | pragma solidity ^0.6.12; 21 | 22 | // FIXME: This contract was altered compared to the production version. 23 | // It doesn't use LibNote anymore. 24 | // New deployments of this contract will need to include custom events (TO DO). 25 | 26 | interface VatLike { 27 | function ilks(bytes32) external returns ( 28 | uint256 Art, // [wad] 29 | uint256 rate // [ray] 30 | ); 31 | function fold(bytes32,address,int) external; 32 | } 33 | 34 | contract Jug { 35 | // --- Auth --- 36 | mapping (address => uint) public wards; 37 | function rely(address usr) external auth { wards[usr] = 1; } 38 | function deny(address usr) external auth { wards[usr] = 0; } 39 | modifier auth { 40 | require(wards[msg.sender] == 1, "Jug/not-authorized"); 41 | _; 42 | } 43 | 44 | // --- Data --- 45 | struct Ilk { 46 | uint256 duty; // Collateral-specific, per-second stability fee contribution [ray] 47 | uint256 rho; // Time of last drip [unix epoch time] 48 | } 49 | 50 | mapping (bytes32 => Ilk) public ilks; 51 | VatLike public vat; // CDP Engine 52 | address public vow; // Debt Engine 53 | uint256 public base; // Global, per-second stability fee contribution [ray] 54 | 55 | // --- Init --- 56 | constructor(address vat_) public { 57 | wards[msg.sender] = 1; 58 | vat = VatLike(vat_); 59 | } 60 | 61 | // --- Math --- 62 | function _rpow(uint x, uint n, uint b) internal pure returns (uint z) { 63 | assembly { 64 | switch x case 0 {switch n case 0 {z := b} default {z := 0}} 65 | default { 66 | switch mod(n, 2) case 0 { z := b } default { z := x } 67 | let half := div(b, 2) // for rounding. 68 | for { n := div(n, 2) } n { n := div(n,2) } { 69 | let xx := mul(x, x) 70 | if iszero(eq(div(xx, x), x)) { revert(0,0) } 71 | let xxRound := add(xx, half) 72 | if lt(xxRound, xx) { revert(0,0) } 73 | x := div(xxRound, b) 74 | if mod(n,2) { 75 | let zx := mul(z, x) 76 | if and(iszero(iszero(x)), iszero(eq(div(zx, x), z))) { revert(0,0) } 77 | let zxRound := add(zx, half) 78 | if lt(zxRound, zx) { revert(0,0) } 79 | z := div(zxRound, b) 80 | } 81 | } 82 | } 83 | } 84 | } 85 | uint256 constant ONE = 10 ** 27; 86 | function _add(uint x, uint y) internal pure returns (uint z) { 87 | z = x + y; 88 | require(z >= x); 89 | } 90 | function _diff(uint x, uint y) internal pure returns (int z) { 91 | z = int(x) - int(y); 92 | require(int(x) >= 0 && int(y) >= 0); 93 | } 94 | function _rmul(uint x, uint y) internal pure returns (uint z) { 95 | z = x * y; 96 | require(y == 0 || z / y == x); 97 | z = z / ONE; 98 | } 99 | 100 | // --- Administration --- 101 | function init(bytes32 ilk) external auth { 102 | Ilk storage i = ilks[ilk]; 103 | require(i.duty == 0, "Jug/ilk-already-init"); 104 | i.duty = ONE; 105 | i.rho = now; 106 | } 107 | function file(bytes32 ilk, bytes32 what, uint data) external auth { 108 | require(now == ilks[ilk].rho, "Jug/rho-not-updated"); 109 | if (what == "duty") ilks[ilk].duty = data; 110 | else revert("Jug/file-unrecognized-param"); 111 | } 112 | function file(bytes32 what, uint data) external auth { 113 | if (what == "base") base = data; 114 | else revert("Jug/file-unrecognized-param"); 115 | } 116 | function file(bytes32 what, address data) external auth { 117 | if (what == "vow") vow = data; 118 | else revert("Jug/file-unrecognized-param"); 119 | } 120 | 121 | // --- Stability Fee Collection --- 122 | function drip(bytes32 ilk) external returns (uint rate) { 123 | require(now >= ilks[ilk].rho, "Jug/invalid-now"); 124 | (, uint prev) = vat.ilks(ilk); 125 | rate = _rmul(_rpow(_add(base, ilks[ilk].duty), now - ilks[ilk].rho, ONE), prev); 126 | vat.fold(ilk, vow, _diff(rate, prev)); 127 | ilks[ilk].rho = now; 128 | } 129 | } 130 | -------------------------------------------------------------------------------- /src/pot.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: AGPL-3.0-or-later 2 | 3 | /// pot.sol -- Dai Savings Rate 4 | 5 | // Copyright (C) 2018 Rain 6 | // 7 | // This program is free software: you can redistribute it and/or modify 8 | // it under the terms of the GNU Affero General Public License as published by 9 | // the Free Software Foundation, either version 3 of the License, or 10 | // (at your option) any later version. 11 | // 12 | // This program is distributed in the hope that it will be useful, 13 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | // GNU Affero General Public License for more details. 16 | // 17 | // You should have received a copy of the GNU Affero General Public License 18 | // along with this program. If not, see . 19 | 20 | pragma solidity ^0.6.12; 21 | 22 | // FIXME: This contract was altered compared to the production version. 23 | // It doesn't use LibNote anymore. 24 | // New deployments of this contract will need to include custom events (TO DO). 25 | 26 | /* 27 | "Savings Dai" is obtained when Dai is deposited into 28 | this contract. Each "Savings Dai" accrues Dai interest 29 | at the "Dai Savings Rate". 30 | 31 | This contract does not implement a user tradeable token 32 | and is intended to be used with adapters. 33 | 34 | --- `save` your `dai` in the `pot` --- 35 | 36 | - `dsr`: the Dai Savings Rate 37 | - `pie`: user balance of Savings Dai 38 | 39 | - `join`: start saving some dai 40 | - `exit`: remove some dai 41 | - `drip`: perform rate collection 42 | 43 | */ 44 | 45 | interface VatLike { 46 | function move(address,address,uint256) external; 47 | function suck(address,address,uint256) external; 48 | } 49 | 50 | contract Pot { 51 | // --- Auth --- 52 | mapping (address => uint) public wards; 53 | function rely(address guy) external auth { wards[guy] = 1; } 54 | function deny(address guy) external auth { wards[guy] = 0; } 55 | modifier auth { 56 | require(wards[msg.sender] == 1, "Pot/not-authorized"); 57 | _; 58 | } 59 | 60 | // --- Data --- 61 | mapping (address => uint256) public pie; // Normalised Savings Dai [wad] 62 | 63 | uint256 public Pie; // Total Normalised Savings Dai [wad] 64 | uint256 public dsr; // The Dai Savings Rate [ray] 65 | uint256 public chi; // The Rate Accumulator [ray] 66 | 67 | VatLike public vat; // CDP Engine 68 | address public vow; // Debt Engine 69 | uint256 public rho; // Time of last drip [unix epoch time] 70 | 71 | uint256 public live; // Active Flag 72 | 73 | // --- Init --- 74 | constructor(address vat_) public { 75 | wards[msg.sender] = 1; 76 | vat = VatLike(vat_); 77 | dsr = ONE; 78 | chi = ONE; 79 | rho = now; 80 | live = 1; 81 | } 82 | 83 | // --- Math --- 84 | uint256 constant ONE = 10 ** 27; 85 | function _rpow(uint x, uint n, uint base) internal pure returns (uint z) { 86 | assembly { 87 | switch x case 0 {switch n case 0 {z := base} default {z := 0}} 88 | default { 89 | switch mod(n, 2) case 0 { z := base } default { z := x } 90 | let half := div(base, 2) // for rounding. 91 | for { n := div(n, 2) } n { n := div(n,2) } { 92 | let xx := mul(x, x) 93 | if iszero(eq(div(xx, x), x)) { revert(0,0) } 94 | let xxRound := add(xx, half) 95 | if lt(xxRound, xx) { revert(0,0) } 96 | x := div(xxRound, base) 97 | if mod(n,2) { 98 | let zx := mul(z, x) 99 | if and(iszero(iszero(x)), iszero(eq(div(zx, x), z))) { revert(0,0) } 100 | let zxRound := add(zx, half) 101 | if lt(zxRound, zx) { revert(0,0) } 102 | z := div(zxRound, base) 103 | } 104 | } 105 | } 106 | } 107 | } 108 | 109 | function _rmul(uint x, uint y) internal pure returns (uint z) { 110 | z = _mul(x, y) / ONE; 111 | } 112 | 113 | function _add(uint x, uint y) internal pure returns (uint z) { 114 | require((z = x + y) >= x); 115 | } 116 | 117 | function _sub(uint x, uint y) internal pure returns (uint z) { 118 | require((z = x - y) <= x); 119 | } 120 | 121 | function _mul(uint x, uint y) internal pure returns (uint z) { 122 | require(y == 0 || (z = x * y) / y == x); 123 | } 124 | 125 | // --- Administration --- 126 | function file(bytes32 what, uint256 data) external auth { 127 | require(live == 1, "Pot/not-live"); 128 | require(now == rho, "Pot/rho-not-updated"); 129 | if (what == "dsr") dsr = data; 130 | else revert("Pot/file-unrecognized-param"); 131 | } 132 | 133 | function file(bytes32 what, address addr) external auth { 134 | if (what == "vow") vow = addr; 135 | else revert("Pot/file-unrecognized-param"); 136 | } 137 | 138 | function cage() external auth { 139 | live = 0; 140 | dsr = ONE; 141 | } 142 | 143 | // --- Savings Rate Accumulation --- 144 | function drip() external returns (uint tmp) { 145 | require(now >= rho, "Pot/invalid-now"); 146 | tmp = _rmul(_rpow(dsr, now - rho, ONE), chi); 147 | uint chi_ = _sub(tmp, chi); 148 | chi = tmp; 149 | rho = now; 150 | vat.suck(address(vow), address(this), _mul(Pie, chi_)); 151 | } 152 | 153 | // --- Savings Dai Management --- 154 | function join(uint wad) external { 155 | require(now == rho, "Pot/rho-not-updated"); 156 | pie[msg.sender] = _add(pie[msg.sender], wad); 157 | Pie = _add(Pie, wad); 158 | vat.move(msg.sender, address(this), _mul(chi, wad)); 159 | } 160 | 161 | function exit(uint wad) external { 162 | pie[msg.sender] = _sub(pie[msg.sender], wad); 163 | Pie = _sub(Pie, wad); 164 | vat.move(address(this), msg.sender, _mul(chi, wad)); 165 | } 166 | } 167 | -------------------------------------------------------------------------------- /src/spot.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: AGPL-3.0-or-later 2 | 3 | /// spot.sol -- Spotter 4 | 5 | // This program is free software: you can redistribute it and/or modify 6 | // it under the terms of the GNU Affero General Public License as published by 7 | // the Free Software Foundation, either version 3 of the License, or 8 | // (at your option) any later version. 9 | // 10 | // This program is distributed in the hope that it will be useful, 11 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | // GNU Affero General Public License for more details. 14 | // 15 | // You should have received a copy of the GNU Affero General Public License 16 | // along with this program. If not, see . 17 | 18 | pragma solidity ^0.6.12; 19 | 20 | // FIXME: This contract was altered compared to the production version. 21 | // It doesn't use LibNote anymore. 22 | // New deployments of this contract will need to include custom events (TO DO). 23 | 24 | interface VatLike { 25 | function file(bytes32, bytes32, uint) external; 26 | } 27 | 28 | interface PipLike { 29 | function peek() external returns (bytes32, bool); 30 | } 31 | 32 | contract Spotter { 33 | // --- Auth --- 34 | mapping (address => uint) public wards; 35 | function rely(address guy) external auth { wards[guy] = 1; } 36 | function deny(address guy) external auth { wards[guy] = 0; } 37 | modifier auth { 38 | require(wards[msg.sender] == 1, "Spotter/not-authorized"); 39 | _; 40 | } 41 | 42 | // --- Data --- 43 | struct Ilk { 44 | PipLike pip; // Price Feed 45 | uint256 mat; // Liquidation ratio [ray] 46 | } 47 | 48 | mapping (bytes32 => Ilk) public ilks; 49 | 50 | VatLike public vat; // CDP Engine 51 | uint256 public par; // ref per dai [ray] 52 | 53 | uint256 public live; 54 | 55 | // --- Events --- 56 | event Poke( 57 | bytes32 ilk, 58 | bytes32 val, // [wad] 59 | uint256 spot // [ray] 60 | ); 61 | 62 | // --- Init --- 63 | constructor(address vat_) public { 64 | wards[msg.sender] = 1; 65 | vat = VatLike(vat_); 66 | par = ONE; 67 | live = 1; 68 | } 69 | 70 | // --- Math --- 71 | uint constant ONE = 10 ** 27; 72 | 73 | function mul(uint x, uint y) internal pure returns (uint z) { 74 | require(y == 0 || (z = x * y) / y == x); 75 | } 76 | function rdiv(uint x, uint y) internal pure returns (uint z) { 77 | z = mul(x, ONE) / y; 78 | } 79 | 80 | // --- Administration --- 81 | function file(bytes32 ilk, bytes32 what, address pip_) external auth { 82 | require(live == 1, "Spotter/not-live"); 83 | if (what == "pip") ilks[ilk].pip = PipLike(pip_); 84 | else revert("Spotter/file-unrecognized-param"); 85 | } 86 | function file(bytes32 what, uint data) external auth { 87 | require(live == 1, "Spotter/not-live"); 88 | if (what == "par") par = data; 89 | else revert("Spotter/file-unrecognized-param"); 90 | } 91 | function file(bytes32 ilk, bytes32 what, uint data) external auth { 92 | require(live == 1, "Spotter/not-live"); 93 | if (what == "mat") ilks[ilk].mat = data; 94 | else revert("Spotter/file-unrecognized-param"); 95 | } 96 | 97 | // --- Update value --- 98 | function poke(bytes32 ilk) external { 99 | (bytes32 val, bool has) = ilks[ilk].pip.peek(); 100 | uint256 spot = has ? rdiv(rdiv(mul(uint(val), 10 ** 9), par), ilks[ilk].mat) : 0; 101 | vat.file(ilk, "spot", spot); 102 | emit Poke(ilk, val, spot); 103 | } 104 | 105 | function cage() external auth { 106 | live = 0; 107 | } 108 | } 109 | -------------------------------------------------------------------------------- /src/test/abaci.t.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: AGPL-3.0-or-later 2 | 3 | // abaci.t.sol -- tests for abaci.sol 4 | 5 | // Copyright (C) 2021-2022 Dai Foundation 6 | // 7 | // This program is free software: you can redistribute it and/or modify 8 | // it under the terms of the GNU Affero General Public License as published by 9 | // the Free Software Foundation, either version 3 of the License, or 10 | // (at your option) any later version. 11 | // 12 | // This program is distributed in the hope that it will be useful, 13 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | // GNU Affero General Public License for more details. 16 | // 17 | // You should have received a copy of the GNU Affero General Public License 18 | // along with this program. If not, see . 19 | 20 | pragma solidity ^0.6.12; 21 | 22 | import "ds-test/test.sol"; 23 | 24 | import "../abaci.sol"; 25 | 26 | interface Hevm { 27 | function warp(uint256) external; 28 | function store(address,bytes32,bytes32) external; 29 | } 30 | 31 | contract ClipperTest is DSTest { 32 | Hevm hevm; 33 | 34 | uint256 RAY = 10 ** 27; 35 | 36 | // CHEAT_CODE = 0x7109709ECfa91a80626fF3989D68f67F5b1DD12D 37 | bytes20 constant CHEAT_CODE = 38 | bytes20(uint160(uint256(keccak256('hevm cheat code')))); 39 | 40 | uint256 constant startTime = 604411200; // Used to avoid issues with `now` 41 | 42 | function setUp() public { 43 | hevm = Hevm(address(CHEAT_CODE)); 44 | hevm.warp(startTime); 45 | } 46 | 47 | function assertEqWithinTolerance( 48 | uint256 x, 49 | uint256 y, 50 | uint256 tolerance) internal { 51 | uint256 diff; 52 | if (x >= y) { 53 | diff = x - y; 54 | } else { 55 | diff = y - x; 56 | } 57 | assertTrue(diff <= tolerance); 58 | } 59 | 60 | function checkExpDecrease( 61 | StairstepExponentialDecrease calc, 62 | uint256 cut, 63 | uint256 step, 64 | uint256 top, 65 | uint256 tic, 66 | uint256 percentDecrease, 67 | uint256 testTime, 68 | uint256 tolerance 69 | ) 70 | public 71 | { 72 | uint256 price; 73 | uint256 lastPrice; 74 | uint256 testPrice; 75 | 76 | hevm.warp(startTime); 77 | calc.file(bytes32("step"), step); 78 | calc.file(bytes32("cut"), cut); 79 | price = calc.price(top, now - tic); 80 | assertEq(price, top); 81 | 82 | for(uint256 i = 1; i < testTime; i += 1) { 83 | hevm.warp(startTime + i); 84 | lastPrice = price; 85 | price = calc.price(top, now - tic); 86 | // Stairstep calculation 87 | if (i % step == 0) { testPrice = lastPrice * percentDecrease / RAY; } 88 | else { testPrice = lastPrice; } 89 | assertEqWithinTolerance(testPrice, price, tolerance); 90 | } 91 | } 92 | 93 | function test_stairstep_exp_decrease() public { 94 | StairstepExponentialDecrease calc = new StairstepExponentialDecrease(); 95 | uint256 tic = now; // Start of auction 96 | uint256 percentDecrease; 97 | uint256 step; 98 | uint256 testTime = 10 minutes; 99 | 100 | 101 | /*** Extreme high collateral price ($50m) ***/ 102 | 103 | uint256 tolerance = 100000000; // Tolerance scales with price 104 | uint256 top = 50000000 * RAY; 105 | 106 | // 1.1234567890% decrease every 1 second 107 | // TODO: Check if there's a cleaner way to do this. I was getting rational_const errors. 108 | percentDecrease = RAY - 1.1234567890E27 / 100; 109 | step = 1; 110 | checkExpDecrease(calc, percentDecrease, step, top, tic, percentDecrease, testTime, tolerance); 111 | 112 | // 2.1234567890% decrease every 1 second 113 | percentDecrease = RAY - 2.1234567890E27 / 100; 114 | step = 1; 115 | checkExpDecrease(calc, percentDecrease, step, top, tic, percentDecrease, testTime, tolerance); 116 | 117 | // 1.1234567890% decrease every 5 seconds 118 | percentDecrease = RAY - 1.1234567890E27 / 100; 119 | step = 5; 120 | checkExpDecrease(calc, percentDecrease, step, top, tic, percentDecrease, testTime, tolerance); 121 | 122 | // 2.1234567890% decrease every 5 seconds 123 | percentDecrease = RAY - 2.1234567890E27 / 100; 124 | step = 5; 125 | checkExpDecrease(calc, percentDecrease, step, top, tic, percentDecrease, testTime, tolerance); 126 | 127 | // 1.1234567890% decrease every 5 minutes 128 | percentDecrease = RAY - 1.1234567890E27 / 100; 129 | step = 5 minutes; 130 | checkExpDecrease(calc, percentDecrease, step, top, tic, percentDecrease, testTime, tolerance); 131 | 132 | 133 | /*** Extreme low collateral price ($0.0000001) ***/ 134 | 135 | tolerance = 1; // Lowest tolerance is 1e-27 136 | top = 1 * RAY / 10000000; 137 | 138 | // 1.1234567890% decrease every 1 second 139 | percentDecrease = RAY - 1.1234567890E27 / 100; 140 | step = 1; 141 | checkExpDecrease(calc, percentDecrease, step, top, tic, percentDecrease, testTime, tolerance); 142 | 143 | // 2.1234567890% decrease every 1 second 144 | percentDecrease = RAY - 2.1234567890E27 / 100; 145 | step = 1; 146 | checkExpDecrease(calc, percentDecrease, step, top, tic, percentDecrease, testTime, tolerance); 147 | 148 | // 1.1234567890% decrease every 5 seconds 149 | percentDecrease = RAY - 1.1234567890E27 / 100; 150 | step = 5; 151 | checkExpDecrease(calc, percentDecrease, step, top, tic, percentDecrease, testTime, tolerance); 152 | 153 | // 2.1234567890% decrease every 5 seconds 154 | percentDecrease = RAY - 2.1234567890E27 / 100; 155 | step = 5; 156 | checkExpDecrease(calc, percentDecrease, step, top, tic, percentDecrease, testTime, tolerance); 157 | 158 | // 1.1234567890% decrease every 5 minutes 159 | percentDecrease = RAY - 1.1234567890E27 / 100; 160 | step = 5 minutes; 161 | checkExpDecrease(calc, percentDecrease, step, top, tic, percentDecrease, testTime, tolerance); 162 | } 163 | 164 | function test_continuous_exp_decrease() public { 165 | ExponentialDecrease calc = new ExponentialDecrease(); 166 | uint256 tHalf = 900; 167 | uint256 cut = 0.999230132966E27; // ~15 half life, cut ~= e^(ln(1/2)/900) 168 | calc.file("cut", cut); 169 | 170 | uint256 top = 4000 * RAY; 171 | uint256 expectedPrice = top; 172 | uint256 tolerance = RAY / 1000; // 0.001, i.e 0.1% 173 | for (uint256 i = 0; i < 5; i++) { // will cover initial value + four half-lives 174 | assertEqWithinTolerance(calc.price(top, i*tHalf), expectedPrice, tolerance); 175 | // each loop iteration advances one half-life, so expectedPrice decreases by a factor of 2 176 | expectedPrice /= 2; 177 | } 178 | } 179 | 180 | function test_linear_decrease() public { 181 | hevm.warp(startTime); 182 | LinearDecrease calc = new LinearDecrease(); 183 | calc.file(bytes32("tau"), 3600); 184 | 185 | uint256 top = 1000 * RAY; 186 | uint256 tic = now; // Start of auction 187 | uint256 price = calc.price(top, now - tic); 188 | assertEq(price, top); 189 | 190 | hevm.warp(startTime + 360); // 6min in, 1/10 done 191 | price = calc.price(top, now - tic); 192 | assertEq(price, (1000 - 100) * RAY); 193 | 194 | hevm.warp(startTime + 360 * 2); // 12min in, 2/10 done 195 | price = calc.price(top, now - tic); 196 | assertEq(price, (1000 - 100 * 2) * RAY); 197 | 198 | hevm.warp(startTime + 360 * 3); // 18min in, 3/10 done 199 | price = calc.price(top, now - tic); 200 | assertEq(price, (1000 - 100 * 3) * RAY); 201 | 202 | hevm.warp(startTime + 360 * 4); // 24min in, 4/10 done 203 | price = calc.price(top, now - tic); 204 | assertEq(price, (1000 - 100 * 4) * RAY); 205 | 206 | hevm.warp(startTime + 360 * 5); // 30min in, 5/10 done 207 | price = calc.price(top, now - tic); 208 | assertEq(price, (1000 - 100 * 5) * RAY); 209 | 210 | hevm.warp(startTime + 360 * 6); // 36min in, 6/10 done 211 | price = calc.price(top, now - tic); 212 | assertEq(price, (1000 - 100 * 6) * RAY); 213 | 214 | hevm.warp(startTime + 360 * 7); // 42min in, 7/10 done 215 | price = calc.price(top, now - tic); 216 | assertEq(price, (1000 - 100 * 7) * RAY); 217 | 218 | hevm.warp(startTime + 360 * 8); // 48min in, 8/10 done 219 | price = calc.price(top, now - tic); 220 | assertEq(price, (1000 - 100 * 8) * RAY); 221 | 222 | hevm.warp(startTime + 360 * 9); // 54min in, 9/10 done 223 | price = calc.price(top, now - tic); 224 | assertEq(price, (1000 - 100 * 9) * RAY); 225 | 226 | hevm.warp(startTime + 360 * 10); // 60min in, 10/10 done 227 | price = calc.price(top, now - tic); 228 | assertEq(price, 0); 229 | } 230 | } 231 | -------------------------------------------------------------------------------- /src/test/cure.t.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: AGPL-3.0-or-later 2 | 3 | pragma solidity >=0.6.12; 4 | 5 | import { DSTest } from "ds-test/test.sol"; 6 | import { Cure } from "../cure.sol"; 7 | 8 | interface Hevm { 9 | function warp(uint256) external; 10 | function store(address,bytes32,bytes32) external; 11 | } 12 | 13 | contract SourceMock { 14 | uint256 public cure; 15 | 16 | constructor(uint256 cure_) public { 17 | cure = cure_; 18 | } 19 | 20 | function update(uint256 cure_) external { 21 | cure = cure_; 22 | } 23 | } 24 | 25 | contract CureTest is DSTest { 26 | Hevm hevm; 27 | Cure cure; 28 | 29 | bytes20 constant CHEAT_CODE = 30 | bytes20(uint160(uint256(keccak256('hevm cheat code')))); 31 | 32 | function setUp() public { 33 | hevm = Hevm(address(CHEAT_CODE)); 34 | cure = new Cure(); 35 | } 36 | 37 | function testRelyDeny() public { 38 | assertEq(cure.wards(address(123)), 0); 39 | cure.rely(address(123)); 40 | assertEq(cure.wards(address(123)), 1); 41 | cure.deny(address(123)); 42 | assertEq(cure.wards(address(123)), 0); 43 | } 44 | 45 | function testFailRely() public { 46 | cure.deny(address(this)); 47 | cure.rely(address(123)); 48 | } 49 | 50 | function testFailDeny() public { 51 | cure.deny(address(this)); 52 | cure.deny(address(123)); 53 | } 54 | 55 | function testFile() public { 56 | assertEq(cure.wait(), 0); 57 | cure.file("wait", 10); 58 | assertEq(cure.wait(), 10); 59 | } 60 | 61 | function testFailFile() public { 62 | cure.deny(address(this)); 63 | cure.file("wait", 10); 64 | } 65 | 66 | function testAddSourceDelSource() public { 67 | assertEq(cure.tCount(), 0); 68 | 69 | address addr1 = address(new SourceMock(0)); 70 | cure.lift(addr1); 71 | assertEq(cure.tCount(), 1); 72 | 73 | address addr2 = address(new SourceMock(0)); 74 | cure.lift(addr2); 75 | assertEq(cure.tCount(), 2); 76 | 77 | address addr3 = address(new SourceMock(0)); 78 | cure.lift(addr3); 79 | assertEq(cure.tCount(), 3); 80 | 81 | assertEq(cure.srcs(0), addr1); 82 | assertEq(cure.pos(addr1), 1); 83 | assertEq(cure.srcs(1), addr2); 84 | assertEq(cure.pos(addr2), 2); 85 | assertEq(cure.srcs(2), addr3); 86 | assertEq(cure.pos(addr3), 3); 87 | 88 | cure.drop(addr3); 89 | assertEq(cure.tCount(), 2); 90 | assertEq(cure.srcs(0), addr1); 91 | assertEq(cure.pos(addr1), 1); 92 | assertEq(cure.srcs(1), addr2); 93 | assertEq(cure.pos(addr2), 2); 94 | 95 | cure.lift(addr3); 96 | assertEq(cure.tCount(), 3); 97 | assertEq(cure.srcs(0), addr1); 98 | assertEq(cure.pos(addr1), 1); 99 | assertEq(cure.srcs(1), addr2); 100 | assertEq(cure.pos(addr2), 2); 101 | assertEq(cure.srcs(2), addr3); 102 | assertEq(cure.pos(addr3), 3); 103 | 104 | cure.drop(addr1); 105 | assertEq(cure.tCount(), 2); 106 | assertEq(cure.srcs(0), addr3); 107 | assertEq(cure.pos(addr3), 1); 108 | assertEq(cure.srcs(1), addr2); 109 | assertEq(cure.pos(addr2), 2); 110 | 111 | cure.lift(addr1); 112 | assertEq(cure.tCount(), 3); 113 | assertEq(cure.srcs(0), addr3); 114 | assertEq(cure.pos(addr3), 1); 115 | assertEq(cure.srcs(1), addr2); 116 | assertEq(cure.pos(addr2), 2); 117 | assertEq(cure.srcs(2), addr1); 118 | assertEq(cure.pos(addr1), 3); 119 | 120 | address addr4 = address(new SourceMock(0)); 121 | cure.lift(addr4); 122 | assertEq(cure.tCount(), 4); 123 | assertEq(cure.srcs(0), addr3); 124 | assertEq(cure.pos(addr3), 1); 125 | assertEq(cure.srcs(1), addr2); 126 | assertEq(cure.pos(addr2), 2); 127 | assertEq(cure.srcs(2), addr1); 128 | assertEq(cure.pos(addr1), 3); 129 | assertEq(cure.srcs(3), addr4); 130 | assertEq(cure.pos(addr4), 4); 131 | 132 | cure.drop(addr2); 133 | assertEq(cure.tCount(), 3); 134 | assertEq(cure.srcs(0), addr3); 135 | assertEq(cure.pos(addr3), 1); 136 | assertEq(cure.srcs(1), addr4); 137 | assertEq(cure.pos(addr4), 2); 138 | assertEq(cure.srcs(2), addr1); 139 | assertEq(cure.pos(addr1), 3); 140 | } 141 | 142 | function testFailAddSourceAuth() public { 143 | cure.deny(address(this)); 144 | address addr = address(new SourceMock(0)); 145 | cure.lift(addr); 146 | } 147 | 148 | function testFailDelSourceAuth() public { 149 | address addr = address(new SourceMock(0)); 150 | cure.lift(addr); 151 | cure.deny(address(this)); 152 | cure.drop(addr); 153 | } 154 | 155 | function testFailDelSourceNonExisting() public { 156 | address addr1 = address(new SourceMock(0)); 157 | cure.lift(addr1); 158 | address addr2 = address(new SourceMock(0)); 159 | cure.drop(addr2); 160 | } 161 | 162 | function testCage() public { 163 | assertEq(cure.live(), 1); 164 | cure.cage(); 165 | assertEq(cure.live(), 0); 166 | } 167 | 168 | function testCure() public { 169 | address source1 = address(new SourceMock(15_000)); 170 | address source2 = address(new SourceMock(30_000)); 171 | address source3 = address(new SourceMock(50_000)); 172 | cure.lift(source1); 173 | cure.lift(source2); 174 | cure.lift(source3); 175 | 176 | cure.cage(); 177 | 178 | cure.load(source1); 179 | assertEq(cure.say(), 15_000); 180 | assertEq(cure.tell(), 15_000); // It doesn't fail as wait == 0 181 | cure.load(source2); 182 | assertEq(cure.say(), 45_000); 183 | assertEq(cure.tell(), 45_000); 184 | cure.load(source3); 185 | assertEq(cure.say(), 95_000); 186 | assertEq(cure.tell(), 95_000); 187 | } 188 | 189 | function testCureAllLoaded() public { 190 | address source1 = address(new SourceMock(15_000)); 191 | address source2 = address(new SourceMock(30_000)); 192 | address source3 = address(new SourceMock(50_000)); 193 | cure.lift(source1); 194 | assertEq(cure.tCount(), 1); 195 | cure.lift(source2); 196 | assertEq(cure.tCount(), 2); 197 | cure.lift(source3); 198 | assertEq(cure.tCount(), 3); 199 | 200 | cure.file("wait", 10); 201 | 202 | cure.cage(); 203 | 204 | cure.load(source1); 205 | assertEq(cure.lCount(), 1); 206 | assertEq(cure.say(), 15_000); 207 | cure.load(source2); 208 | assertEq(cure.lCount(), 2); 209 | assertEq(cure.say(), 45_000); 210 | cure.load(source3); 211 | assertEq(cure.lCount(), 3); 212 | assertEq(cure.say(), 95_000); 213 | assertEq(cure.tell(), 95_000); 214 | } 215 | 216 | function testCureWaitPassed() public { 217 | address source1 = address(new SourceMock(15_000)); 218 | address source2 = address(new SourceMock(30_000)); 219 | address source3 = address(new SourceMock(50_000)); 220 | cure.lift(source1); 221 | cure.lift(source2); 222 | cure.lift(source3); 223 | 224 | cure.file("wait", 10); 225 | 226 | cure.cage(); 227 | 228 | cure.load(source1); 229 | cure.load(source2); 230 | hevm.warp(block.timestamp + 10); 231 | assertEq(cure.tell(), 45_000); 232 | } 233 | 234 | function testFailWait() public { 235 | address source1 = address(new SourceMock(15_000)); 236 | address source2 = address(new SourceMock(30_000)); 237 | address source3 = address(new SourceMock(50_000)); 238 | cure.lift(source1); 239 | cure.lift(source2); 240 | cure.lift(source3); 241 | 242 | cure.file("wait", 10); 243 | 244 | cure.cage(); 245 | 246 | cure.load(source1); 247 | cure.load(source2); 248 | hevm.warp(block.timestamp + 9); 249 | cure.tell(); 250 | } 251 | 252 | function testLoadMultipleTimes() public { 253 | address source1 = address(new SourceMock(2_000)); 254 | address source2 = address(new SourceMock(3_000)); 255 | cure.lift(source1); 256 | cure.lift(source2); 257 | 258 | cure.cage(); 259 | 260 | cure.load(source1); 261 | assertEq(cure.lCount(), 1); 262 | cure.load(source2); 263 | assertEq(cure.lCount(), 2); 264 | assertEq(cure.tell(), 5_000); 265 | 266 | SourceMock(source1).update(4_000); 267 | assertEq(cure.tell(), 5_000); 268 | 269 | cure.load(source1); 270 | assertEq(cure.lCount(), 2); 271 | assertEq(cure.tell(), 7_000); 272 | 273 | SourceMock(source2).update(6_000); 274 | assertEq(cure.tell(), 7_000); 275 | 276 | cure.load(source2); 277 | assertEq(cure.lCount(), 2); 278 | assertEq(cure.tell(), 10_000); 279 | } 280 | 281 | function testLoadNoChange() public { 282 | address source = address(new SourceMock(2_000)); 283 | cure.lift(source); 284 | 285 | cure.cage(); 286 | 287 | cure.load(source); 288 | assertEq(cure.tell(), 2_000); 289 | 290 | cure.load(source); 291 | assertEq(cure.tell(), 2_000); 292 | } 293 | 294 | function testFailLoadNotCaged() public { 295 | address source = address(new SourceMock(2_000)); 296 | cure.lift(source); 297 | 298 | cure.load(source); 299 | } 300 | 301 | function testFailLoadNotAdded() public { 302 | address source = address(new SourceMock(2_000)); 303 | 304 | cure.cage(); 305 | 306 | cure.load(source); 307 | } 308 | 309 | function testFailCagedRely() public { 310 | cure.cage(); 311 | cure.rely(address(123)); 312 | } 313 | 314 | function testFailCagedDeny() public { 315 | cure.cage(); 316 | cure.deny(address(123)); 317 | } 318 | 319 | function testFailCagedAddSource() public { 320 | cure.cage(); 321 | address source = address(new SourceMock(0)); 322 | cure.lift(source); 323 | } 324 | 325 | function testFailCagedDelSource() public { 326 | address source = address(new SourceMock(0)); 327 | cure.lift(source); 328 | cure.cage(); 329 | cure.drop(source); 330 | } 331 | } 332 | -------------------------------------------------------------------------------- /src/test/dai.t.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: AGPL-3.0-or-later 2 | 3 | /// dai.t.sol -- tests for dai.sol 4 | 5 | // Copyright (C) 2015-2019 DappHub, LLC 6 | 7 | // This program is free software: you can redistribute it and/or modify 8 | // it under the terms of the GNU General Public License as published by 9 | // the Free Software Foundation, either version 3 of the License, or 10 | // (at your option) any later version. 11 | 12 | // This program is distributed in the hope that it will be useful, 13 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | // GNU General Public License for more details. 16 | 17 | // You should have received a copy of the GNU General Public License 18 | // along with this program. If not, see . 19 | 20 | pragma solidity ^0.6.12; 21 | 22 | import "ds-test/test.sol"; 23 | 24 | import "../dai.sol"; 25 | 26 | contract TokenUser { 27 | Dai token; 28 | 29 | constructor(Dai token_) public { 30 | token = token_; 31 | } 32 | 33 | function doTransferFrom(address from, address to, uint amount) 34 | public 35 | returns (bool) 36 | { 37 | return token.transferFrom(from, to, amount); 38 | } 39 | 40 | function doTransfer(address to, uint amount) 41 | public 42 | returns (bool) 43 | { 44 | return token.transfer(to, amount); 45 | } 46 | 47 | function doApprove(address recipient, uint amount) 48 | public 49 | returns (bool) 50 | { 51 | return token.approve(recipient, amount); 52 | } 53 | 54 | function doAllowance(address owner, address spender) 55 | public 56 | view 57 | returns (uint) 58 | { 59 | return token.allowance(owner, spender); 60 | } 61 | 62 | function doBalanceOf(address who) public view returns (uint) { 63 | return token.balanceOf(who); 64 | } 65 | 66 | function doApprove(address guy) 67 | public 68 | returns (bool) 69 | { 70 | return token.approve(guy, uint(-1)); 71 | } 72 | function doMint(uint wad) public { 73 | token.mint(address(this), wad); 74 | } 75 | function doBurn(uint wad) public { 76 | token.burn(address(this), wad); 77 | } 78 | function doMint(address guy, uint wad) public { 79 | token.mint(guy, wad); 80 | } 81 | function doBurn(address guy, uint wad) public { 82 | token.burn(guy, wad); 83 | } 84 | 85 | } 86 | 87 | interface Hevm { 88 | function warp(uint256) external; 89 | } 90 | 91 | contract DaiTest is DSTest { 92 | uint constant initialBalanceThis = 1000; 93 | uint constant initialBalanceCal = 100; 94 | 95 | Dai token; 96 | Hevm hevm; 97 | address user1; 98 | address user2; 99 | address self; 100 | 101 | uint amount = 2; 102 | uint fee = 1; 103 | uint nonce = 0; 104 | uint deadline = 0; 105 | address cal = 0x29C76e6aD8f28BB1004902578Fb108c507Be341b; 106 | address del = 0xdd2d5D3f7f1b35b7A0601D6A00DbB7D44Af58479; 107 | bytes32 r = 0x8e30095d9e5439a4f4b8e4b5c94e7639756474d72aded20611464c8f002efb06; 108 | bytes32 s = 0x49a0ed09658bc768d6548689bcbaa430cefa57846ef83cb685673a9b9a575ff4; 109 | uint8 v = 27; 110 | bytes32 _r = 0x85da10f8af2cf512620c07d800f8e17a2a4cd2e91bf0835a34bf470abc6b66e5; 111 | bytes32 _s = 0x7e8e641e5e8bef932c3a55e7365e0201196fc6385d942c47d749bf76e73ee46f; 112 | uint8 _v = 27; 113 | 114 | 115 | function setUp() public { 116 | hevm = Hevm(0x7109709ECfa91a80626fF3989D68f67F5b1DD12D); 117 | hevm.warp(604411200); 118 | token = createToken(); 119 | token.mint(address(this), initialBalanceThis); 120 | token.mint(cal, initialBalanceCal); 121 | user1 = address(new TokenUser(token)); 122 | user2 = address(new TokenUser(token)); 123 | self = address(this); 124 | } 125 | 126 | function createToken() internal returns (Dai) { 127 | return new Dai(99); 128 | } 129 | 130 | function testSetupPrecondition() public { 131 | assertEq(token.balanceOf(self), initialBalanceThis); 132 | } 133 | 134 | function testTransferCost() public logs_gas { 135 | token.transfer(address(0), 10); 136 | } 137 | 138 | function testAllowanceStartsAtZero() public logs_gas { 139 | assertEq(token.allowance(user1, user2), 0); 140 | } 141 | 142 | function testValidTransfers() public logs_gas { 143 | uint sentAmount = 250; 144 | emit log_named_address("token11111", address(token)); 145 | token.transfer(user2, sentAmount); 146 | assertEq(token.balanceOf(user2), sentAmount); 147 | assertEq(token.balanceOf(self), initialBalanceThis - sentAmount); 148 | } 149 | 150 | function testFailWrongAccountTransfers() public logs_gas { 151 | uint sentAmount = 250; 152 | token.transferFrom(user2, self, sentAmount); 153 | } 154 | 155 | function testFailInsufficientFundsTransfers() public logs_gas { 156 | uint sentAmount = 250; 157 | token.transfer(user1, initialBalanceThis - sentAmount); 158 | token.transfer(user2, sentAmount + 1); 159 | } 160 | 161 | function testApproveSetsAllowance() public logs_gas { 162 | emit log_named_address("Test", self); 163 | emit log_named_address("Token", address(token)); 164 | emit log_named_address("Me", self); 165 | emit log_named_address("User 2", user2); 166 | token.approve(user2, 25); 167 | assertEq(token.allowance(self, user2), 25); 168 | } 169 | 170 | function testChargesAmountApproved() public logs_gas { 171 | uint amountApproved = 20; 172 | token.approve(user2, amountApproved); 173 | assertTrue(TokenUser(user2).doTransferFrom(self, user2, amountApproved)); 174 | assertEq(token.balanceOf(self), initialBalanceThis - amountApproved); 175 | } 176 | 177 | function testFailTransferWithoutApproval() public logs_gas { 178 | token.transfer(user1, 50); 179 | token.transferFrom(user1, self, 1); 180 | } 181 | 182 | function testFailChargeMoreThanApproved() public logs_gas { 183 | token.transfer(user1, 50); 184 | TokenUser(user1).doApprove(self, 20); 185 | token.transferFrom(user1, self, 21); 186 | } 187 | function testTransferFromSelf() public { 188 | token.transferFrom(self, user1, 50); 189 | assertEq(token.balanceOf(user1), 50); 190 | } 191 | function testFailTransferFromSelfNonArbitrarySize() public { 192 | // you shouldn't be able to evade balance checks by transferring 193 | // to yourself 194 | token.transferFrom(self, self, token.balanceOf(self) + 1); 195 | } 196 | function testMintself() public { 197 | uint mintAmount = 10; 198 | token.mint(address(this), mintAmount); 199 | assertEq(token.balanceOf(self), initialBalanceThis + mintAmount); 200 | } 201 | function testMintGuy() public { 202 | uint mintAmount = 10; 203 | token.mint(user1, mintAmount); 204 | assertEq(token.balanceOf(user1), mintAmount); 205 | } 206 | function testFailMintGuyNoAuth() public { 207 | TokenUser(user1).doMint(user2, 10); 208 | } 209 | function testMintGuyAuth() public { 210 | token.rely(user1); 211 | TokenUser(user1).doMint(user2, 10); 212 | } 213 | 214 | function testBurn() public { 215 | uint burnAmount = 10; 216 | token.burn(address(this), burnAmount); 217 | assertEq(token.totalSupply(), initialBalanceThis + initialBalanceCal - burnAmount); 218 | } 219 | function testBurnself() public { 220 | uint burnAmount = 10; 221 | token.burn(address(this), burnAmount); 222 | assertEq(token.balanceOf(self), initialBalanceThis - burnAmount); 223 | } 224 | function testBurnGuyWithTrust() public { 225 | uint burnAmount = 10; 226 | token.transfer(user1, burnAmount); 227 | assertEq(token.balanceOf(user1), burnAmount); 228 | 229 | TokenUser(user1).doApprove(self); 230 | token.burn(user1, burnAmount); 231 | assertEq(token.balanceOf(user1), 0); 232 | } 233 | function testBurnAuth() public { 234 | token.transfer(user1, 10); 235 | token.rely(user1); 236 | TokenUser(user1).doBurn(10); 237 | } 238 | function testBurnGuyAuth() public { 239 | token.transfer(user2, 10); 240 | // token.rely(user1); 241 | TokenUser(user2).doApprove(user1); 242 | TokenUser(user1).doBurn(user2, 10); 243 | } 244 | 245 | function testFailUntrustedTransferFrom() public { 246 | assertEq(token.allowance(self, user2), 0); 247 | TokenUser(user1).doTransferFrom(self, user2, 200); 248 | } 249 | function testTrusting() public { 250 | assertEq(token.allowance(self, user2), 0); 251 | token.approve(user2, uint(-1)); 252 | assertEq(token.allowance(self, user2), uint(-1)); 253 | token.approve(user2, 0); 254 | assertEq(token.allowance(self, user2), 0); 255 | } 256 | function testTrustedTransferFrom() public { 257 | token.approve(user1, uint(-1)); 258 | TokenUser(user1).doTransferFrom(self, user2, 200); 259 | assertEq(token.balanceOf(user2), 200); 260 | } 261 | function testApproveWillModifyAllowance() public { 262 | assertEq(token.allowance(self, user1), 0); 263 | assertEq(token.balanceOf(user1), 0); 264 | token.approve(user1, 1000); 265 | assertEq(token.allowance(self, user1), 1000); 266 | TokenUser(user1).doTransferFrom(self, user1, 500); 267 | assertEq(token.balanceOf(user1), 500); 268 | assertEq(token.allowance(self, user1), 500); 269 | } 270 | function testApproveWillNotModifyAllowance() public { 271 | assertEq(token.allowance(self, user1), 0); 272 | assertEq(token.balanceOf(user1), 0); 273 | token.approve(user1, uint(-1)); 274 | assertEq(token.allowance(self, user1), uint(-1)); 275 | TokenUser(user1).doTransferFrom(self, user1, 1000); 276 | assertEq(token.balanceOf(user1), 1000); 277 | assertEq(token.allowance(self, user1), uint(-1)); 278 | } 279 | function testDaiAddress() public { 280 | //The dai address generated by hevm 281 | //used for signature generation testing 282 | assertEq(address(token), address(0x11Ee1eeF5D446D07Cf26941C7F2B4B1Dfb9D030B)); 283 | } 284 | 285 | function testTypehash() public { 286 | assertEq(token.PERMIT_TYPEHASH(), 0xea2aa0a1be11a07ed86d755c93467f4f82362b452371d1ba94d1715123511acb); 287 | } 288 | 289 | function testDomain_Separator() public { 290 | assertEq(token.DOMAIN_SEPARATOR(), 0x68a9504c1a7fba795f7730732abab11cb5fa5113edd2396392abd5c1bbda4043); 291 | } 292 | 293 | function testPermit() public { 294 | assertEq(token.nonces(cal), 0); 295 | assertEq(token.allowance(cal, del), 0); 296 | token.permit(cal, del, 0, 0, true, v, r, s); 297 | assertEq(token.allowance(cal, del),uint(-1)); 298 | assertEq(token.nonces(cal),1); 299 | } 300 | 301 | function testFailPermitAddress0() public { 302 | v = 0; 303 | token.permit(address(0), del, 0, 0, true, v, r, s); 304 | } 305 | 306 | function testPermitWithExpiry() public { 307 | assertEq(now, 604411200); 308 | token.permit(cal, del, 0, 604411200 + 1 hours, true, _v, _r, _s); 309 | assertEq(token.allowance(cal, del),uint(-1)); 310 | assertEq(token.nonces(cal),1); 311 | } 312 | 313 | function testFailPermitWithExpiry() public { 314 | hevm.warp(now + 2 hours); 315 | assertEq(now, 604411200 + 2 hours); 316 | token.permit(cal, del, 0, 1, true, _v, _r, _s); 317 | } 318 | 319 | function testFailReplay() public { 320 | token.permit(cal, del, 0, 0, true, v, r, s); 321 | token.permit(cal, del, 0, 0, true, v, r, s); 322 | } 323 | } 324 | -------------------------------------------------------------------------------- /src/test/dog.t.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: AGPL-3.0-or-later 2 | 3 | // dog.t.sol -- tests for dog.sol 4 | 5 | // Copyright (C) 2021-2022 Dai Foundation 6 | // 7 | // This program is free software: you can redistribute it and/or modify 8 | // it under the terms of the GNU Affero General Public License as published by 9 | // the Free Software Foundation, either version 3 of the License, or 10 | // (at your option) any later version. 11 | // 12 | // This program is distributed in the hope that it will be useful, 13 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | // GNU Affero General Public License for more details. 16 | // 17 | // You should have received a copy of the GNU Affero General Public License 18 | // along with this program. If not, see . 19 | 20 | pragma solidity ^0.6.12; 21 | 22 | import { DSTest } from "ds-test/test.sol"; 23 | import { Vat } from "../vat.sol"; 24 | import { Dog } from "../dog.sol"; 25 | 26 | contract VowMock { 27 | function fess (uint256 due) public {} 28 | } 29 | 30 | contract ClipperMock { 31 | bytes32 public ilk; 32 | function setIlk(bytes32 wat) external { 33 | ilk = wat; 34 | } 35 | function kick(uint256, uint256, address, address) 36 | external pure returns (uint256 id) { 37 | id = 42; 38 | } 39 | } 40 | 41 | contract DogTest is DSTest { 42 | 43 | bytes32 constant ilk = "gold"; 44 | address constant usr = address(1337); 45 | uint256 constant THOUSAND = 1E3; 46 | uint256 constant WAD = 1E18; 47 | uint256 constant RAY = 1E27; 48 | uint256 constant RAD = 1E45; 49 | Vat vat; 50 | VowMock vow; 51 | ClipperMock clip; 52 | Dog dog; 53 | 54 | function setUp() public { 55 | vat = new Vat(); 56 | vat.init(ilk); 57 | vat.file(ilk, "spot", THOUSAND * RAY); 58 | vat.file(ilk, "dust", 100 * RAD); 59 | vow = new VowMock(); 60 | clip = new ClipperMock(); 61 | clip.setIlk(ilk); 62 | dog = new Dog(address(vat)); 63 | vat.rely(address(dog)); 64 | dog.file(ilk, "chop", 11 * WAD / 10); 65 | dog.file("vow", address(vow)); 66 | dog.file(ilk, "clip", address(clip)); 67 | dog.file("Hole", 10 * THOUSAND * RAD); 68 | dog.file(ilk, "hole", 10 * THOUSAND * RAD); 69 | } 70 | 71 | function test_file_chop() public { 72 | dog.file(ilk, "chop", WAD); 73 | dog.file(ilk, "chop", WAD * 113 / 100); 74 | } 75 | 76 | function testFail_file_chop_lt_WAD() public { 77 | dog.file(ilk, "chop", WAD - 1); 78 | } 79 | 80 | function testFail_file_chop_eq_zero() public { 81 | dog.file(ilk, "chop", 0); 82 | } 83 | 84 | function testFail_file_clip_wrong_ilk() public { 85 | dog.file("mismatched_ilk", "clip", address(clip)); 86 | } 87 | 88 | function setUrn(uint256 ink, uint256 art) internal { 89 | vat.slip(ilk, usr, int256(ink)); 90 | (, uint256 rate,,,) = vat.ilks(ilk); 91 | vat.suck(address(vow), address(vow), art * rate); 92 | vat.grab(ilk, usr, usr, address(vow), int256(ink), int256(art)); 93 | (uint256 actualInk, uint256 actualArt) = vat.urns(ilk, usr); 94 | assertEq(ink, actualInk); 95 | assertEq(art, actualArt); 96 | } 97 | 98 | function isDusty() internal view returns (bool dusty) { 99 | (, uint256 rate,,, uint256 dust) = vat.ilks(ilk); 100 | (, uint256 art) = vat.urns(ilk, usr); 101 | uint256 due = art * rate; 102 | dusty = due > 0 && due < dust; 103 | } 104 | 105 | function test_bark_basic() public { 106 | setUrn(WAD, 2 * THOUSAND * WAD); 107 | dog.bark(ilk, usr, address(this)); 108 | (uint256 ink, uint256 art) = vat.urns(ilk, usr); 109 | assertEq(ink, 0); 110 | assertEq(art, 0); 111 | } 112 | 113 | function testFail_bark_not_unsafe() public { 114 | setUrn(WAD, 500 * WAD); 115 | dog.bark(ilk, usr, address(this)); 116 | } 117 | 118 | // dog.bark will liquidate vaults even if they are dusty 119 | function test_bark_dusty_vault() public { 120 | uint256 dust = 200; 121 | vat.file(ilk, "dust", dust * RAD); 122 | setUrn(1, (dust / 2) * WAD); 123 | assertTrue(isDusty()); 124 | dog.bark(ilk, usr, address(this)); 125 | } 126 | 127 | function test_bark_partial_liquidation_dirt_exceeds_hole_to_avoid_dusty_remnant() public { 128 | uint256 dust = 200; 129 | vat.file(ilk, "dust", dust * RAD); 130 | uint256 hole = 5 * THOUSAND; 131 | dog.file(ilk, "hole", hole * RAD); 132 | (, uint256 chop,,) = dog.ilks(ilk); 133 | uint256 artStart = hole * WAD * WAD / chop + dust * WAD - 1; 134 | setUrn(WAD, artStart); 135 | dog.bark(ilk, usr, address(this)); 136 | assertTrue(!isDusty()); 137 | (, uint256 art) = vat.urns(ilk, usr); 138 | 139 | // The full vault has been liquidated so as not to leave a dusty remnant, 140 | // at the expense of slightly exceeding hole. 141 | assertEq(art, 0); 142 | (,,, uint256 dirt) = dog.ilks(ilk); 143 | assertTrue(dirt > hole * RAD); 144 | assertEq(dirt, artStart * RAY * chop / WAD); 145 | } 146 | 147 | function test_bark_partial_liquidation_dirt_does_not_exceed_hole_if_remnant_is_nondusty() public { 148 | uint256 dust = 200; 149 | vat.file(ilk, "dust", dust * RAD); 150 | uint256 hole = 5 * THOUSAND; 151 | dog.file(ilk, "hole", hole * RAD); 152 | (, uint256 chop,,) = dog.ilks(ilk); 153 | setUrn(WAD, hole * WAD * WAD / chop + dust * WAD); 154 | dog.bark(ilk, usr, address(this)); 155 | assertTrue(!isDusty()); 156 | (, uint256 art) = vat.urns(ilk, usr); 157 | 158 | // The vault remnant respects the dust limit, so we don't exceed hole to liquidate it. 159 | assertEq(art, dust * WAD); 160 | (,,, uint256 dirt) = dog.ilks(ilk); 161 | assertTrue(dirt <= hole * RAD); 162 | assertEq(dirt, hole * RAD * WAD / RAY / chop * RAY * chop / WAD); 163 | } 164 | 165 | function test_bark_partial_liquidation_Dirt_exceeds_Hole_to_avoid_dusty_remnant() public { 166 | uint256 dust = 200; 167 | vat.file(ilk, "dust", dust * RAD); 168 | uint256 Hole = 5 * THOUSAND; 169 | dog.file("Hole", Hole * RAD); 170 | (, uint256 chop,,) = dog.ilks(ilk); 171 | uint256 artStart = Hole * WAD * WAD / chop + dust * WAD - 1; 172 | setUrn(WAD, artStart); 173 | dog.bark(ilk, usr, address(this)); 174 | assertTrue(!isDusty()); 175 | 176 | // The full vault has been liquidated so as not to leave a dusty remnant, 177 | // at the expense of slightly exceeding hole. 178 | (, uint256 art) = vat.urns(ilk, usr); 179 | assertEq(art, 0); 180 | assertTrue(dog.Dirt() > Hole * RAD); 181 | assertEq(dog.Dirt(), artStart * RAY * chop / WAD); 182 | } 183 | 184 | function test_bark_partial_liquidation_Dirt_does_not_exceed_Hole_if_remnant_is_nondusty() public { 185 | uint256 dust = 200; 186 | vat.file(ilk, "dust", dust * RAD); 187 | uint256 Hole = 5 * THOUSAND; 188 | dog.file("Hole", Hole * RAD); 189 | (, uint256 chop,,) = dog.ilks(ilk); 190 | setUrn(WAD, Hole * WAD * WAD / chop + dust * WAD); 191 | dog.bark(ilk, usr, address(this)); 192 | assertTrue(!isDusty()); 193 | 194 | // The full vault has been liquidated so as not to leave a dusty remnant, 195 | // at the expense of slightly exceeding hole. 196 | (, uint256 art) = vat.urns(ilk, usr); 197 | assertEq(art, dust * WAD); 198 | assertTrue(dog.Dirt() <= Hole * RAD); 199 | assertEq(dog.Dirt(), Hole * RAD * WAD / RAY / chop * RAY * chop / WAD); 200 | } 201 | 202 | // A previous version reverted if room was dusty, even if the Vault being liquidated 203 | // was also dusty and would fit in the remaining hole/Hole room. 204 | function test_bark_dusty_vault_dusty_room() public { 205 | // Use a chop that will give nice round numbers 206 | uint256 CHOP = 110 * WAD / 100; // 10% 207 | dog.file(ilk, "chop", CHOP); 208 | 209 | // set both hole_i and Hole to the same value for this test 210 | uint256 ROOM = 200; 211 | uint256 HOLE = 33 * THOUSAND + ROOM; 212 | dog.file( "Hole", HOLE * RAD); 213 | dog.file(ilk, "hole", HOLE * RAD); 214 | 215 | // Test using a non-zero rate to ensure the code is handling stability fees correctly. 216 | vat.fold(ilk, address(vow), (5 * int256(RAY)) / 10); 217 | (, uint256 rate,,,) = vat.ilks(ilk); 218 | assertEq(rate, (15 * RAY) / 10); 219 | 220 | // First, make both holes nearly full. 221 | setUrn(WAD, (HOLE - ROOM) * RAD / rate * WAD / CHOP); 222 | dog.bark(ilk, usr, address(this)); 223 | assertEq(HOLE * RAD - dog.Dirt(), ROOM * RAD); 224 | (,,, uint256 dirt) = dog.ilks(ilk); 225 | assertEq(HOLE * RAD - dirt, ROOM * RAD); 226 | 227 | // Create a small vault 228 | uint256 DUST_1 = 30; 229 | vat.file(ilk, "dust", DUST_1 * RAD); 230 | setUrn(WAD / 10**4, DUST_1 * RAD / rate); 231 | 232 | // Dust limit goes up! 233 | uint256 DUST_2 = 1500; 234 | vat.file(ilk, "dust", DUST_2 * RAD); 235 | 236 | // The testing vault is now dusty 237 | assertTrue(isDusty()); 238 | 239 | // In fact, there is only room to create dusty auctions at this point. 240 | assertTrue(dog.Hole() - dog.Dirt() < DUST_2 * RAD * CHOP / WAD); 241 | uint256 hole; 242 | (,, hole, dirt) = dog.ilks(ilk); 243 | assertTrue(hole - dirt < DUST_2 * RAD * CHOP / WAD); 244 | 245 | // But...our Vault is small enough to fit in ROOM 246 | assertTrue(DUST_1 * RAD * CHOP / WAD < ROOM * RAD); 247 | 248 | // bark should still succeed 249 | dog.bark(ilk, usr, address(this)); 250 | } 251 | 252 | function try_bark(bytes32 ilk_, address usr_, address kpr_) internal returns (bool ok) { 253 | string memory sig = "bark(bytes32,address,address)"; 254 | (ok,) = address(dog).call(abi.encodeWithSignature(sig, ilk_, usr_, kpr_)); 255 | } 256 | 257 | function test_bark_do_not_create_dusty_auction_hole() public { 258 | uint256 dust = 300; 259 | vat.file(ilk, "dust", dust * RAD); 260 | uint256 hole = 3 * THOUSAND; 261 | dog.file(ilk, "hole", hole * RAD); 262 | 263 | // Test using a non-zero rate to ensure the code is handling stability fees correctly. 264 | vat.fold(ilk, address(vow), (5 * int256(RAY)) / 10); 265 | (, uint256 rate,,,) = vat.ilks(ilk); 266 | assertEq(rate, (15 * RAY) / 10); 267 | 268 | (, uint256 chop,,) = dog.ilks(ilk); 269 | setUrn(WAD, (hole - dust / 2) * RAD / rate * WAD / chop); 270 | dog.bark(ilk, usr, address(this)); 271 | 272 | // Make sure any partial liquidation would be dusty (assuming non-dusty remnant) 273 | (,,, uint256 dirt) = dog.ilks(ilk); 274 | uint256 room = hole * RAD - dirt; 275 | uint256 dart = room * WAD / rate / chop; 276 | assertTrue(dart * rate < dust * RAD); 277 | 278 | // This will need to be partially liquidated 279 | setUrn(WAD, hole * WAD * WAD / chop); 280 | assertTrue(!try_bark(ilk, usr, address(this))); // should revert, as the auction would be dusty 281 | } 282 | 283 | function test_bark_do_not_create_dusty_auction_Hole() public { 284 | uint256 dust = 300; 285 | vat.file(ilk, "dust", dust * RAD); 286 | uint256 Hole = 3 * THOUSAND; 287 | dog.file("Hole", Hole * RAD); 288 | 289 | // Test using a non-zero rate to ensure the code is handling stability fees correctly. 290 | vat.fold(ilk, address(vow), (5 * int256(RAY)) / 10); 291 | (, uint256 rate,,,) = vat.ilks(ilk); 292 | assertEq(rate, (15 * RAY) / 10); 293 | 294 | (, uint256 chop,,) = dog.ilks(ilk); 295 | setUrn(WAD, (Hole - dust / 2) * RAD / rate * WAD / chop); 296 | dog.bark(ilk, usr, address(this)); 297 | 298 | // Make sure any partial liquidation would be dusty (assuming non-dusty remnant) 299 | uint256 room = Hole * RAD - dog.Dirt(); 300 | uint256 dart = room * WAD / rate / chop; 301 | assertTrue(dart * rate < dust * RAD); 302 | 303 | // This will need to be partially liquidated 304 | setUrn(WAD, Hole * WAD * WAD / chop); 305 | assertTrue(!try_bark(ilk, usr, address(this))); // should revert, as the auction would be dusty 306 | } 307 | } 308 | -------------------------------------------------------------------------------- /src/test/flap.t.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: AGPL-3.0-or-later 2 | 3 | // flap.t.sol -- tests for flap.sol 4 | 5 | // Copyright (C) 2022 Dai Foundation 6 | // 7 | // This program is free software: you can redistribute it and/or modify 8 | // it under the terms of the GNU Affero General Public License as published by 9 | // the Free Software Foundation, either version 3 of the License, or 10 | // (at your option) any later version. 11 | // 12 | // This program is distributed in the hope that it will be useful, 13 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | // GNU Affero General Public License for more details. 16 | // 17 | // You should have received a copy of the GNU Affero General Public License 18 | // along with this program. If not, see . 19 | 20 | pragma solidity ^0.6.12; 21 | 22 | import "ds-test/test.sol"; 23 | import {DSToken} from "ds-token/token.sol"; 24 | import "../flap.sol"; 25 | import "../vat.sol"; 26 | 27 | 28 | interface Hevm { 29 | function warp(uint256) external; 30 | } 31 | 32 | contract Guy { 33 | Flapper flap; 34 | constructor(Flapper flap_) public { 35 | flap = flap_; 36 | Vat(address(flap.vat())).hope(address(flap)); 37 | DSToken(address(flap.gem())).approve(address(flap)); 38 | } 39 | function tend(uint id, uint lot, uint bid) public { 40 | flap.tend(id, lot, bid); 41 | } 42 | function deal(uint id) public { 43 | flap.deal(id); 44 | } 45 | function try_tend(uint id, uint lot, uint bid) 46 | public returns (bool ok) 47 | { 48 | string memory sig = "tend(uint256,uint256,uint256)"; 49 | (ok,) = address(flap).call(abi.encodeWithSignature(sig, id, lot, bid)); 50 | } 51 | function try_deal(uint id) 52 | public returns (bool ok) 53 | { 54 | string memory sig = "deal(uint256)"; 55 | (ok,) = address(flap).call(abi.encodeWithSignature(sig, id)); 56 | } 57 | function try_tick(uint id) 58 | public returns (bool ok) 59 | { 60 | string memory sig = "tick(uint256)"; 61 | (ok,) = address(flap).call(abi.encodeWithSignature(sig, id)); 62 | } 63 | } 64 | 65 | contract FlapTest is DSTest { 66 | Hevm hevm; 67 | 68 | Flapper flap; 69 | Vat vat; 70 | DSToken gem; 71 | 72 | address ali; 73 | address bob; 74 | 75 | function setUp() public { 76 | hevm = Hevm(0x7109709ECfa91a80626fF3989D68f67F5b1DD12D); 77 | hevm.warp(604411200); 78 | 79 | vat = new Vat(); 80 | gem = new DSToken(''); 81 | 82 | flap = new Flapper(address(vat), address(gem)); 83 | flap.file("lid", 500 ether); 84 | 85 | ali = address(new Guy(flap)); 86 | bob = address(new Guy(flap)); 87 | 88 | vat.hope(address(flap)); 89 | gem.approve(address(flap)); 90 | 91 | vat.suck(address(this), address(this), 1000 ether); 92 | 93 | gem.mint(1000 ether); 94 | gem.setOwner(address(flap)); 95 | 96 | gem.push(ali, 200 ether); 97 | gem.push(bob, 200 ether); 98 | } 99 | function test_kick() public { 100 | assertEq(vat.dai(address(this)), 1000 ether); 101 | assertEq(vat.dai(address(flap)), 0 ether); 102 | assertEq(flap.fill(), 0 ether); 103 | flap.kick({ lot: 100 ether 104 | , bid: 0 105 | }); 106 | assertEq(vat.dai(address(this)), 900 ether); 107 | assertEq(vat.dai(address(flap)), 100 ether); 108 | assertEq(flap.fill(), 100 ether); 109 | } 110 | function test_tend() public { 111 | uint id = flap.kick({ lot: 100 ether 112 | , bid: 0 113 | }); 114 | // lot taken from creator 115 | assertEq(vat.dai(address(this)), 900 ether); 116 | 117 | Guy(ali).tend(id, 100 ether, 1 ether); 118 | // bid taken from bidder 119 | assertEq(gem.balanceOf(ali), 199 ether); 120 | // payment remains in auction 121 | assertEq(gem.balanceOf(address(flap)), 1 ether); 122 | 123 | Guy(bob).tend(id, 100 ether, 2 ether); 124 | // bid taken from bidder 125 | assertEq(gem.balanceOf(bob), 198 ether); 126 | // prev bidder refunded 127 | assertEq(gem.balanceOf(ali), 200 ether); 128 | // excess remains in auction 129 | assertEq(gem.balanceOf(address(flap)), 2 ether); 130 | 131 | hevm.warp(now + 5 weeks); 132 | Guy(bob).deal(id); 133 | // high bidder gets the lot 134 | assertEq(vat.dai(address(flap)), 0 ether); 135 | assertEq(vat.dai(bob), 100 ether); 136 | // income is burned 137 | assertEq(gem.balanceOf(address(flap)), 0 ether); 138 | } 139 | function test_tend_same_bidder() public { 140 | uint id = flap.kick({ lot: 100 ether 141 | , bid: 0 142 | }); 143 | Guy(ali).tend(id, 100 ether, 190 ether); 144 | assertEq(gem.balanceOf(ali), 10 ether); 145 | Guy(ali).tend(id, 100 ether, 200 ether); 146 | assertEq(gem.balanceOf(ali), 0); 147 | } 148 | function test_beg() public { 149 | uint id = flap.kick({ lot: 100 ether 150 | , bid: 0 151 | }); 152 | assertTrue( Guy(ali).try_tend(id, 100 ether, 1.00 ether)); 153 | assertTrue(!Guy(bob).try_tend(id, 100 ether, 1.01 ether)); 154 | // high bidder is subject to beg 155 | assertTrue(!Guy(ali).try_tend(id, 100 ether, 1.01 ether)); 156 | assertTrue( Guy(bob).try_tend(id, 100 ether, 1.07 ether)); 157 | } 158 | function test_tick() public { 159 | // start an auction 160 | uint id = flap.kick({ lot: 100 ether 161 | , bid: 0 162 | }); 163 | // check no tick 164 | assertTrue(!Guy(ali).try_tick(id)); 165 | // run past the end 166 | hevm.warp(now + 2 weeks); 167 | // check not biddable 168 | assertTrue(!Guy(ali).try_tend(id, 100 ether, 1 ether)); 169 | assertTrue( Guy(ali).try_tick(id)); 170 | // check biddable 171 | assertTrue( Guy(ali).try_tend(id, 100 ether, 1 ether)); 172 | } 173 | function testFail_kick_over_lid() public { 174 | flap.kick({ lot: 501 ether 175 | , bid: 0 176 | }); 177 | } 178 | function testFail_kick_over_lid_2_auctions() public { 179 | // Just up to the lid 180 | flap.kick({ lot: 500 ether 181 | , bid: 0 182 | }); 183 | // Just over the lid 184 | flap.kick({ lot: 1 185 | , bid: 0 186 | }); 187 | } 188 | function test_deal() public { 189 | uint256 id = flap.kick({ lot: 400 ether 190 | , bid: 0 191 | }); 192 | assertEq(flap.fill(), 400 ether); 193 | Guy(ali).tend(id, 400 ether, 1 ether); 194 | assertEq(flap.fill(), 400 ether); 195 | hevm.warp(block.timestamp + 30 days); 196 | flap.deal(id); 197 | assertEq(flap.fill(), 0); 198 | flap.kick({ lot: 400 ether 199 | , bid: 0 200 | }); 201 | assertEq(flap.fill(), 400 ether); 202 | } 203 | function test_multiple_auctions() public { 204 | uint256 id1 = flap.kick({ lot: 200 ether 205 | , bid: 0 206 | }); 207 | assertEq(flap.fill(), 200 ether); 208 | uint256 id2 = flap.kick({ lot: 200 ether 209 | , bid: 0 210 | }); 211 | assertEq(flap.fill(), 400 ether); 212 | Guy(ali).tend(id1, 200 ether, 1 ether); 213 | assertEq(flap.fill(), 400 ether); 214 | hevm.warp(block.timestamp + 30 days); 215 | flap.deal(id1); 216 | assertEq(flap.fill(), 200 ether); 217 | flap.kick({ lot: 300 ether 218 | , bid: 0 219 | }); 220 | assertEq(flap.fill(), 500 ether); 221 | flap.tick(id2); 222 | Guy(ali).tend(id2, 200 ether, 1 ether); 223 | assertEq(flap.fill(), 500 ether); 224 | hevm.warp(block.timestamp + 30 days); 225 | flap.deal(id2); 226 | assertEq(flap.fill(), 300 ether); 227 | } 228 | function test_mod_lid_in_flight() public { 229 | uint256 id = flap.kick({ lot: 400 ether 230 | , bid: 0 231 | }); 232 | assertEq(flap.fill(), 400 ether); 233 | assertEq(flap.lid(), 500 ether); 234 | 235 | // Reduce lid while auction is active 236 | flap.file("lid", 300 ether); 237 | 238 | Guy(ali).tend(id, 400 ether, 1 ether); 239 | assertEq(flap.fill(), 400 ether); 240 | assertEq(flap.lid(), 300 ether); 241 | hevm.warp(block.timestamp + 30 days); 242 | flap.deal(id); 243 | assertEq(flap.fill(), 0); 244 | flap.kick({ lot: 300 ether 245 | , bid: 0 246 | }); 247 | assertEq(flap.fill(), 300 ether); 248 | } 249 | 250 | } 251 | -------------------------------------------------------------------------------- /src/test/flip.t.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: AGPL-3.0-or-later 2 | 3 | pragma solidity >=0.5.12; 4 | 5 | import "ds-test/test.sol"; 6 | 7 | import {Vat} from "../vat.sol"; 8 | import {Cat} from "../cat.sol"; 9 | import {Flipper} from "../flip.sol"; 10 | 11 | interface Hevm { 12 | function warp(uint256) external; 13 | } 14 | 15 | contract Guy { 16 | Flipper flip; 17 | constructor(Flipper flip_) public { 18 | flip = flip_; 19 | } 20 | function hope(address usr) public { 21 | Vat(address(flip.vat())).hope(usr); 22 | } 23 | function tend(uint id, uint lot, uint bid) public { 24 | flip.tend(id, lot, bid); 25 | } 26 | function dent(uint id, uint lot, uint bid) public { 27 | flip.dent(id, lot, bid); 28 | } 29 | function deal(uint id) public { 30 | flip.deal(id); 31 | } 32 | function try_tend(uint id, uint lot, uint bid) 33 | public returns (bool ok) 34 | { 35 | string memory sig = "tend(uint256,uint256,uint256)"; 36 | (ok,) = address(flip).call(abi.encodeWithSignature(sig, id, lot, bid)); 37 | } 38 | function try_dent(uint id, uint lot, uint bid) 39 | public returns (bool ok) 40 | { 41 | string memory sig = "dent(uint256,uint256,uint256)"; 42 | (ok,) = address(flip).call(abi.encodeWithSignature(sig, id, lot, bid)); 43 | } 44 | function try_deal(uint id) 45 | public returns (bool ok) 46 | { 47 | string memory sig = "deal(uint256)"; 48 | (ok,) = address(flip).call(abi.encodeWithSignature(sig, id)); 49 | } 50 | function try_tick(uint id) 51 | public returns (bool ok) 52 | { 53 | string memory sig = "tick(uint256)"; 54 | (ok,) = address(flip).call(abi.encodeWithSignature(sig, id)); 55 | } 56 | function try_yank(uint id) 57 | public returns (bool ok) 58 | { 59 | string memory sig = "yank(uint256)"; 60 | (ok,) = address(flip).call(abi.encodeWithSignature(sig, id)); 61 | } 62 | } 63 | 64 | 65 | contract Gal {} 66 | 67 | contract Cat_ is Cat { 68 | uint256 constant public RAD = 10 ** 45; 69 | uint256 constant public MLN = 10 ** 6; 70 | 71 | constructor(address vat_) Cat(vat_) public { 72 | litter = 5 * MLN * RAD; 73 | } 74 | } 75 | 76 | contract Vat_ is Vat { 77 | function mint(address usr, uint wad) public { 78 | dai[usr] += wad; 79 | } 80 | function dai_balance(address usr) public view returns (uint) { 81 | return dai[usr]; 82 | } 83 | bytes32 ilk; 84 | function set_ilk(bytes32 ilk_) public { 85 | ilk = ilk_; 86 | } 87 | function gem_balance(address usr) public view returns (uint) { 88 | return gem[ilk][usr]; 89 | } 90 | } 91 | 92 | contract FlipTest is DSTest { 93 | Hevm hevm; 94 | 95 | Vat_ vat; 96 | Cat_ cat; 97 | Flipper flip; 98 | 99 | address ali; 100 | address bob; 101 | address gal; 102 | address usr = address(0xacab); 103 | 104 | uint256 constant public RAY = 10 ** 27; 105 | uint256 constant public RAD = 10 ** 45; 106 | uint256 constant public MLN = 10 ** 6; 107 | 108 | function setUp() public { 109 | hevm = Hevm(0x7109709ECfa91a80626fF3989D68f67F5b1DD12D); 110 | hevm.warp(604411200); 111 | 112 | vat = new Vat_(); 113 | cat = new Cat_(address(vat)); 114 | 115 | vat.init("gems"); 116 | vat.set_ilk("gems"); 117 | 118 | flip = new Flipper(address(vat), address(cat), "gems"); 119 | cat.rely(address(flip)); 120 | 121 | ali = address(new Guy(flip)); 122 | bob = address(new Guy(flip)); 123 | gal = address(new Gal()); 124 | 125 | Guy(ali).hope(address(flip)); 126 | Guy(bob).hope(address(flip)); 127 | vat.hope(address(flip)); 128 | 129 | vat.slip("gems", address(this), 1000 ether); 130 | vat.mint(ali, 200 ether); 131 | vat.mint(bob, 200 ether); 132 | } 133 | function rad(uint wad) internal pure returns (uint) { 134 | return wad * 10 ** 27; 135 | } 136 | function test_kick() public { 137 | flip.kick({ lot: 100 ether 138 | , tab: 50 ether 139 | , usr: usr 140 | , gal: gal 141 | , bid: 0 142 | }); 143 | } 144 | function testFail_tend_empty() public { 145 | // can't tend on non-existent 146 | flip.tend(42, 0, 0); 147 | } 148 | function test_tend() public { 149 | uint id = flip.kick({ lot: 100 ether 150 | , tab: 50 ether 151 | , usr: usr 152 | , gal: gal 153 | , bid: 0 154 | }); 155 | 156 | Guy(ali).tend(id, 100 ether, 1 ether); 157 | // bid taken from bidder 158 | assertEq(vat.dai_balance(ali), 199 ether); 159 | // gal receives payment 160 | assertEq(vat.dai_balance(gal), 1 ether); 161 | 162 | Guy(bob).tend(id, 100 ether, 2 ether); 163 | // bid taken from bidder 164 | assertEq(vat.dai_balance(bob), 198 ether); 165 | // prev bidder refunded 166 | assertEq(vat.dai_balance(ali), 200 ether); 167 | // gal receives excess 168 | assertEq(vat.dai_balance(gal), 2 ether); 169 | 170 | hevm.warp(now + 5 hours); 171 | Guy(bob).deal(id); 172 | // bob gets the winnings 173 | assertEq(vat.gem_balance(bob), 100 ether); 174 | } 175 | function test_tend_later() public { 176 | uint id = flip.kick({ lot: 100 ether 177 | , tab: 50 ether 178 | , usr: usr 179 | , gal: gal 180 | , bid: 0 181 | }); 182 | hevm.warp(now + 5 hours); 183 | 184 | Guy(ali).tend(id, 100 ether, 1 ether); 185 | // bid taken from bidder 186 | assertEq(vat.dai_balance(ali), 199 ether); 187 | // gal receives payment 188 | assertEq(vat.dai_balance(gal), 1 ether); 189 | } 190 | function test_dent() public { 191 | uint id = flip.kick({ lot: 100 ether 192 | , tab: 50 ether 193 | , usr: usr 194 | , gal: gal 195 | , bid: 0 196 | }); 197 | Guy(ali).tend(id, 100 ether, 1 ether); 198 | Guy(bob).tend(id, 100 ether, 50 ether); 199 | 200 | Guy(ali).dent(id, 95 ether, 50 ether); 201 | // plop the gems 202 | assertEq(vat.gem_balance(address(0xacab)), 5 ether); 203 | assertEq(vat.dai_balance(ali), 150 ether); 204 | assertEq(vat.dai_balance(bob), 200 ether); 205 | } 206 | function test_tend_dent_same_bidder() public { 207 | uint id = flip.kick({ lot: 100 ether 208 | , tab: 200 ether 209 | , usr: usr 210 | , gal: gal 211 | , bid: 0 212 | }); 213 | 214 | assertEq(vat.dai_balance(ali), 200 ether); 215 | Guy(ali).tend(id, 100 ether, 190 ether); 216 | assertEq(vat.dai_balance(ali), 10 ether); 217 | Guy(ali).tend(id, 100 ether, 200 ether); 218 | assertEq(vat.dai_balance(ali), 0); 219 | Guy(ali).dent(id, 80 ether, 200 ether); 220 | } 221 | function test_beg() public { 222 | uint id = flip.kick({ lot: 100 ether 223 | , tab: 50 ether 224 | , usr: usr 225 | , gal: gal 226 | , bid: 0 227 | }); 228 | assertTrue( Guy(ali).try_tend(id, 100 ether, 1.00 ether)); 229 | assertTrue(!Guy(bob).try_tend(id, 100 ether, 1.01 ether)); 230 | // high bidder is subject to beg 231 | assertTrue(!Guy(ali).try_tend(id, 100 ether, 1.01 ether)); 232 | assertTrue( Guy(bob).try_tend(id, 100 ether, 1.07 ether)); 233 | 234 | // can bid by less than beg at flip 235 | assertTrue( Guy(ali).try_tend(id, 100 ether, 49 ether)); 236 | assertTrue( Guy(bob).try_tend(id, 100 ether, 50 ether)); 237 | 238 | assertTrue(!Guy(ali).try_dent(id, 100 ether, 50 ether)); 239 | assertTrue(!Guy(ali).try_dent(id, 99 ether, 50 ether)); 240 | assertTrue( Guy(ali).try_dent(id, 95 ether, 50 ether)); 241 | } 242 | function test_deal() public { 243 | uint id = flip.kick({ lot: 100 ether 244 | , tab: 50 ether 245 | , usr: usr 246 | , gal: gal 247 | , bid: 0 248 | }); 249 | 250 | // only after ttl 251 | Guy(ali).tend(id, 100 ether, 1 ether); 252 | assertTrue(!Guy(bob).try_deal(id)); 253 | hevm.warp(now + 4.1 hours); 254 | assertTrue( Guy(bob).try_deal(id)); 255 | 256 | uint ie = flip.kick({ lot: 100 ether 257 | , tab: 50 ether 258 | , usr: usr 259 | , gal: gal 260 | , bid: 0 261 | }); 262 | 263 | // or after end 264 | hevm.warp(now + 44 hours); 265 | Guy(ali).tend(ie, 100 ether, 1 ether); 266 | assertTrue(!Guy(bob).try_deal(ie)); 267 | hevm.warp(now + 1 days); 268 | assertTrue( Guy(bob).try_deal(ie)); 269 | } 270 | function test_tick() public { 271 | // start an auction 272 | uint id = flip.kick({ lot: 100 ether 273 | , tab: 50 ether 274 | , usr: usr 275 | , gal: gal 276 | , bid: 0 277 | }); 278 | // check no tick 279 | assertTrue(!Guy(ali).try_tick(id)); 280 | // run past the end 281 | hevm.warp(now + 2 weeks); 282 | // check not biddable 283 | assertTrue(!Guy(ali).try_tend(id, 100 ether, 1 ether)); 284 | assertTrue( Guy(ali).try_tick(id)); 285 | // check biddable 286 | assertTrue( Guy(ali).try_tend(id, 100 ether, 1 ether)); 287 | } 288 | function test_no_deal_after_end() public { 289 | // if there are no bids and the auction ends, then it should not 290 | // be refundable to the creator. Rather, it ticks indefinitely. 291 | uint id = flip.kick({ lot: 100 ether 292 | , tab: 50 ether 293 | , usr: usr 294 | , gal: gal 295 | , bid: 0 296 | }); 297 | assertTrue(!Guy(ali).try_deal(id)); 298 | hevm.warp(now + 2 weeks); 299 | assertTrue(!Guy(ali).try_deal(id)); 300 | assertTrue( Guy(ali).try_tick(id)); 301 | assertTrue(!Guy(ali).try_deal(id)); 302 | } 303 | function test_yank_tend() public { 304 | uint id = flip.kick({ lot: 100 ether 305 | , tab: rad(50 ether) 306 | , usr: usr 307 | , gal: gal 308 | , bid: 0 309 | }); 310 | 311 | Guy(ali).tend(id, 100 ether, 1 ether); 312 | 313 | // bid taken from bidder 314 | assertEq(vat.dai_balance(ali), 199 ether); 315 | assertEq(vat.dai_balance(gal), 1 ether); 316 | 317 | // we have some amount of litter in the box 318 | assertEq(cat.litter(), 5 * MLN * RAD); 319 | 320 | vat.mint(address(this), 1 ether); 321 | flip.yank(id); 322 | 323 | // bid is refunded to bidder from caller 324 | assertEq(vat.dai_balance(ali), 200 ether); 325 | assertEq(vat.dai_balance(address(this)), 0 ether); 326 | 327 | // gems go to caller 328 | assertEq(vat.gem_balance(address(this)), 1000 ether); 329 | 330 | // cat.scoop(tab) is called decrementing the litter accumulator 331 | assertEq(cat.litter(), (5 * MLN * RAD) - rad(50 ether)); 332 | } 333 | function test_yank_dent() public { 334 | uint id = flip.kick({ lot: 100 ether 335 | , tab: 50 ether 336 | , usr: usr 337 | , gal: gal 338 | , bid: 0 339 | }); 340 | 341 | // we have some amount of litter in the box 342 | assertEq(cat.litter(), 5 * MLN * RAD); 343 | 344 | Guy(ali).tend(id, 100 ether, 1 ether); 345 | Guy(bob).tend(id, 100 ether, 50 ether); 346 | Guy(ali).dent(id, 95 ether, 50 ether); 347 | 348 | // cannot yank in the dent phase 349 | assertTrue(!Guy(ali).try_yank(id)); 350 | 351 | // we have same amount of litter in the box 352 | assertEq(cat.litter(), 5 * MLN * RAD); 353 | } 354 | } 355 | -------------------------------------------------------------------------------- /src/test/flop.t.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: AGPL-3.0-or-later 2 | 3 | // flop.t.sol -- tests for flop.sol 4 | 5 | // This program is free software: you can redistribute it and/or modify 6 | // it under the terms of the GNU Affero General Public License as published by 7 | // the Free Software Foundation, either version 3 of the License, or 8 | // (at your option) any later version. 9 | // 10 | // This program is distributed in the hope that it will be useful, 11 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | // GNU Affero General Public License for more details. 14 | // 15 | // You should have received a copy of the GNU Affero General Public License 16 | // along with this program. If not, see . 17 | 18 | pragma solidity ^0.6.12; 19 | 20 | import {DSTest} from "ds-test/test.sol"; 21 | import {DSToken} from "ds-token/token.sol"; 22 | import "../flop.sol"; 23 | import "../vat.sol"; 24 | 25 | 26 | interface Hevm { 27 | function warp(uint256) external; 28 | } 29 | 30 | contract Guy { 31 | Flopper flop; 32 | constructor(Flopper flop_) public { 33 | flop = flop_; 34 | Vat(address(flop.vat())).hope(address(flop)); 35 | DSToken(address(flop.gem())).approve(address(flop)); 36 | } 37 | function dent(uint id, uint lot, uint bid) public { 38 | flop.dent(id, lot, bid); 39 | } 40 | function deal(uint id) public { 41 | flop.deal(id); 42 | } 43 | function try_dent(uint id, uint lot, uint bid) 44 | public returns (bool ok) 45 | { 46 | string memory sig = "dent(uint256,uint256,uint256)"; 47 | (ok,) = address(flop).call(abi.encodeWithSignature(sig, id, lot, bid)); 48 | } 49 | function try_deal(uint id) 50 | public returns (bool ok) 51 | { 52 | string memory sig = "deal(uint256)"; 53 | (ok,) = address(flop).call(abi.encodeWithSignature(sig, id)); 54 | } 55 | function try_tick(uint id) 56 | public returns (bool ok) 57 | { 58 | string memory sig = "tick(uint256)"; 59 | (ok,) = address(flop).call(abi.encodeWithSignature(sig, id)); 60 | } 61 | } 62 | 63 | contract Gal { 64 | uint public Ash; 65 | function sub(uint x, uint y) internal pure returns (uint z) { 66 | require((z = x - y) <= x); 67 | } 68 | function kick(Flopper flop, uint lot, uint bid) external returns (uint) { 69 | Ash += bid; 70 | return flop.kick(address(this), lot, bid); 71 | } 72 | function kiss(uint rad) external { 73 | Ash = sub(Ash, rad); 74 | } 75 | function cage(Flopper flop) external { 76 | flop.cage(); 77 | } 78 | } 79 | 80 | contract Vatish is DSToken('') { 81 | uint constant ONE = 10 ** 27; 82 | function hope(address usr) public { 83 | approve(usr, uint(-1)); 84 | } 85 | function dai(address usr) public view returns (uint) { 86 | return balanceOf[usr]; 87 | } 88 | } 89 | 90 | contract FlopTest is DSTest { 91 | Hevm hevm; 92 | 93 | Flopper flop; 94 | Vat vat; 95 | DSToken gem; 96 | 97 | address ali; 98 | address bob; 99 | address gal; 100 | 101 | function kiss(uint) public pure { } // arbitrary callback 102 | 103 | function setUp() public { 104 | hevm = Hevm(0x7109709ECfa91a80626fF3989D68f67F5b1DD12D); 105 | hevm.warp(604411200); 106 | 107 | vat = new Vat(); 108 | gem = new DSToken(''); 109 | 110 | flop = new Flopper(address(vat), address(gem)); 111 | 112 | ali = address(new Guy(flop)); 113 | bob = address(new Guy(flop)); 114 | gal = address(new Gal()); 115 | 116 | flop.rely(gal); 117 | flop.deny(address(this)); 118 | 119 | vat.hope(address(flop)); 120 | vat.rely(address(flop)); 121 | gem.approve(address(flop)); 122 | 123 | vat.suck(address(this), address(this), 1000 ether); 124 | 125 | vat.move(address(this), ali, 200 ether); 126 | vat.move(address(this), bob, 200 ether); 127 | } 128 | 129 | function test_kick() public { 130 | assertEq(vat.dai(gal), 0); 131 | assertEq(gem.balanceOf(gal), 0 ether); 132 | uint id = Gal(gal).kick(flop, /*lot*/ 200 ether, /*bid*/ 5000 ether); 133 | // no value transferred 134 | assertEq(vat.dai(gal), 0); 135 | assertEq(gem.balanceOf(gal), 0 ether); 136 | // auction created with appropriate values 137 | assertEq(flop.kicks(), id); 138 | (uint256 bid, uint256 lot, address guy, uint48 tic, uint48 end) = flop.bids(id); 139 | assertEq(bid, 5000 ether); 140 | assertEq(lot, 200 ether); 141 | assertTrue(guy == gal); 142 | assertEq(uint256(tic), 0); 143 | assertEq(uint256(end), now + flop.tau()); 144 | } 145 | 146 | function test_dent() public { 147 | uint id = Gal(gal).kick(flop, /*lot*/ 200 ether, /*bid*/ 10 ether); 148 | 149 | Guy(ali).dent(id, 100 ether, 10 ether); 150 | // bid taken from bidder 151 | assertEq(vat.dai(ali), 190 ether); 152 | // gal receives payment 153 | assertEq(vat.dai(gal), 10 ether); 154 | assertEq(Gal(gal).Ash(), 0 ether); 155 | 156 | Guy(bob).dent(id, 80 ether, 10 ether); 157 | // bid taken from bidder 158 | assertEq(vat.dai(bob), 190 ether); 159 | // prev bidder refunded 160 | assertEq(vat.dai(ali), 200 ether); 161 | // gal receives no more 162 | assertEq(vat.dai(gal), 10 ether); 163 | 164 | hevm.warp(now + 5 weeks); 165 | assertEq(gem.totalSupply(), 0 ether); 166 | gem.setOwner(address(flop)); 167 | Guy(bob).deal(id); 168 | // gems minted on demand 169 | assertEq(gem.totalSupply(), 80 ether); 170 | // bob gets the winnings 171 | assertEq(gem.balanceOf(bob), 80 ether); 172 | } 173 | 174 | function test_dent_Ash_less_than_bid() public { 175 | uint id = Gal(gal).kick(flop, /*lot*/ 200 ether, /*bid*/ 10 ether); 176 | assertEq(vat.dai(gal), 0 ether); 177 | 178 | Gal(gal).kiss(1 ether); 179 | assertEq(Gal(gal).Ash(), 9 ether); 180 | 181 | Guy(ali).dent(id, 100 ether, 10 ether); 182 | // bid taken from bidder 183 | assertEq(vat.dai(ali), 190 ether); 184 | // gal receives payment 185 | assertEq(vat.dai(gal), 10 ether); 186 | assertEq(Gal(gal).Ash(), 0 ether); 187 | 188 | Guy(bob).dent(id, 80 ether, 10 ether); 189 | // bid taken from bidder 190 | assertEq(vat.dai(bob), 190 ether); 191 | // prev bidder refunded 192 | assertEq(vat.dai(ali), 200 ether); 193 | // gal receives no more 194 | assertEq(vat.dai(gal), 10 ether); 195 | 196 | hevm.warp(now + 5 weeks); 197 | assertEq(gem.totalSupply(), 0 ether); 198 | gem.setOwner(address(flop)); 199 | Guy(bob).deal(id); 200 | // gems minted on demand 201 | assertEq(gem.totalSupply(), 80 ether); 202 | // bob gets the winnings 203 | assertEq(gem.balanceOf(bob), 80 ether); 204 | } 205 | 206 | function test_dent_same_bidder() public { 207 | uint id = Gal(gal).kick(flop, /*lot*/ 200 ether, /*bid*/ 200 ether); 208 | 209 | Guy(ali).dent(id, 100 ether, 200 ether); 210 | assertEq(vat.dai(ali), 0); 211 | Guy(ali).dent(id, 50 ether, 200 ether); 212 | } 213 | 214 | function test_tick() public { 215 | // start an auction 216 | uint id = Gal(gal).kick(flop, /*lot*/ 200 ether, /*bid*/ 10 ether); 217 | // check no tick 218 | assertTrue(!Guy(ali).try_tick(id)); 219 | // run past the end 220 | hevm.warp(now + 2 weeks); 221 | // check not biddable 222 | assertTrue(!Guy(ali).try_dent(id, 100 ether, 10 ether)); 223 | assertTrue( Guy(ali).try_tick(id)); 224 | // check biddable 225 | (, uint _lot,,,) = flop.bids(id); 226 | // tick should increase the lot by pad (50%) and restart the auction 227 | assertEq(_lot, 300 ether); 228 | assertTrue( Guy(ali).try_dent(id, 100 ether, 10 ether)); 229 | } 230 | 231 | function test_no_deal_after_end() public { 232 | // if there are no bids and the auction ends, then it should not 233 | // be refundable to the creator. Rather, it ticks indefinitely. 234 | uint id = Gal(gal).kick(flop, /*lot*/ 200 ether, /*bid*/ 10 ether); 235 | assertTrue(!Guy(ali).try_deal(id)); 236 | hevm.warp(now + 2 weeks); 237 | assertTrue(!Guy(ali).try_deal(id)); 238 | assertTrue( Guy(ali).try_tick(id)); 239 | assertTrue(!Guy(ali).try_deal(id)); 240 | } 241 | 242 | function test_yank() public { 243 | // yanking the auction should refund the last bidder's dai, credit a 244 | // corresponding amount of sin to the caller of cage, and delete the auction. 245 | // in practice, gal == (caller of cage) == (vow address) 246 | uint id = Gal(gal).kick(flop, /*lot*/ 200 ether, /*bid*/ 10 ether); 247 | 248 | // confrim initial state expectations 249 | assertEq(vat.dai(ali), 200 ether); 250 | assertEq(vat.dai(bob), 200 ether); 251 | assertEq(vat.dai(gal), 0); 252 | assertEq(vat.sin(gal), 0); 253 | 254 | Guy(ali).dent(id, 100 ether, 10 ether); 255 | Guy(bob).dent(id, 80 ether, 10 ether); 256 | 257 | // confirm the proper state updates have occurred 258 | assertEq(vat.dai(ali), 200 ether); // ali's dai balance is unchanged 259 | assertEq(vat.dai(bob), 190 ether); 260 | assertEq(vat.dai(gal), 10 ether); 261 | assertEq(vat.sin(address(this)), 1000 ether); 262 | 263 | Gal(gal).cage(flop); 264 | flop.yank(id); 265 | 266 | // confirm final state 267 | assertEq(vat.dai(ali), 200 ether); 268 | assertEq(vat.dai(bob), 200 ether); // bob's bid has been refunded 269 | assertEq(vat.dai(gal), 10 ether); 270 | assertEq(vat.sin(gal), 10 ether); // sin assigned to caller of cage() 271 | (uint256 _bid, uint256 _lot, address _guy, uint48 _tic, uint48 _end) = flop.bids(id); 272 | assertEq(_bid, 0); 273 | assertEq(_lot, 0); 274 | assertEq(_guy, address(0)); 275 | assertEq(uint256(_tic), 0); 276 | assertEq(uint256(_end), 0); 277 | } 278 | 279 | function test_yank_no_bids() public { 280 | // with no bidder to refund, yanking the auction should simply create equal 281 | // amounts of dai (credited to the gal) and sin (credited to the caller of cage) 282 | // in practice, gal == (caller of cage) == (vow address) 283 | uint id = Gal(gal).kick(flop, /*lot*/ 200 ether, /*bid*/ 10 ether); 284 | 285 | // confrim initial state expectations 286 | assertEq(vat.dai(ali), 200 ether); 287 | assertEq(vat.dai(bob), 200 ether); 288 | assertEq(vat.dai(gal), 0); 289 | assertEq(vat.sin(gal), 0); 290 | 291 | Gal(gal).cage(flop); 292 | flop.yank(id); 293 | 294 | // confirm final state 295 | assertEq(vat.dai(ali), 200 ether); 296 | assertEq(vat.dai(bob), 200 ether); 297 | assertEq(vat.dai(gal), 10 ether); 298 | assertEq(vat.sin(gal), 10 ether); // sin assigned to caller of cage() 299 | (uint256 _bid, uint256 _lot, address _guy, uint48 _tic, uint48 _end) = flop.bids(id); 300 | assertEq(_bid, 0); 301 | assertEq(_lot, 0); 302 | assertEq(_guy, address(0)); 303 | assertEq(uint256(_tic), 0); 304 | assertEq(uint256(_end), 0); 305 | } 306 | } 307 | -------------------------------------------------------------------------------- /src/test/fork.t.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: AGPL-3.0-or-later 2 | 3 | // fork.t.sol -- tests for vat.fork() 4 | 5 | // This program is free software: you can redistribute it and/or modify 6 | // it under the terms of the GNU Affero General Public License as published by 7 | // the Free Software Foundation, either version 3 of the License, or 8 | // (at your option) any later version. 9 | // 10 | // This program is distributed in the hope that it will be useful, 11 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | // GNU Affero General Public License for more details. 14 | // 15 | // You should have received a copy of the GNU Affero General Public License 16 | // along with this program. If not, see . 17 | 18 | pragma solidity ^0.6.12; 19 | 20 | import "ds-test/test.sol"; 21 | import "ds-token/token.sol"; 22 | 23 | import {Vat} from '../vat.sol'; 24 | 25 | contract Usr { 26 | Vat public vat; 27 | constructor(Vat vat_) public { 28 | vat = vat_; 29 | } 30 | function try_call(address addr, bytes calldata data) external returns (bool) { 31 | bytes memory _data = data; 32 | assembly { 33 | let ok := call(gas(), addr, 0, add(_data, 0x20), mload(_data), 0, 0) 34 | let free := mload(0x40) 35 | mstore(free, ok) 36 | mstore(0x40, add(free, 32)) 37 | revert(free, 32) 38 | } 39 | } 40 | function can_frob(bytes32 ilk, address u, address v, address w, int dink, int dart) public returns (bool) { 41 | string memory sig = "frob(bytes32,address,address,address,int256,int256)"; 42 | bytes memory data = abi.encodeWithSignature(sig, ilk, u, v, w, dink, dart); 43 | 44 | bytes memory can_call = abi.encodeWithSignature("try_call(address,bytes)", vat, data); 45 | (bool ok, bytes memory success) = address(this).call(can_call); 46 | 47 | ok = abi.decode(success, (bool)); 48 | if (ok) return true; 49 | } 50 | function can_fork(bytes32 ilk, address src, address dst, int dink, int dart) public returns (bool) { 51 | string memory sig = "fork(bytes32,address,address,int256,int256)"; 52 | bytes memory data = abi.encodeWithSignature(sig, ilk, src, dst, dink, dart); 53 | 54 | bytes memory can_call = abi.encodeWithSignature("try_call(address,bytes)", vat, data); 55 | (bool ok, bytes memory success) = address(this).call(can_call); 56 | 57 | ok = abi.decode(success, (bool)); 58 | if (ok) return true; 59 | } 60 | function frob(bytes32 ilk, address u, address v, address w, int dink, int dart) public { 61 | vat.frob(ilk, u, v, w, dink, dart); 62 | } 63 | function fork(bytes32 ilk, address src, address dst, int dink, int dart) public { 64 | vat.fork(ilk, src, dst, dink, dart); 65 | } 66 | function hope(address usr) public { 67 | vat.hope(usr); 68 | } 69 | function pass() public {} 70 | } 71 | 72 | contract ForkTest is DSTest { 73 | Vat vat; 74 | Usr ali; 75 | Usr bob; 76 | address a; 77 | address b; 78 | 79 | function ray(uint wad) internal pure returns (uint) { 80 | return wad * 10 ** 9; 81 | } 82 | function rad(uint wad) internal pure returns (uint) { 83 | return wad * 10 ** 27; 84 | } 85 | 86 | function setUp() public { 87 | vat = new Vat(); 88 | ali = new Usr(vat); 89 | bob = new Usr(vat); 90 | a = address(ali); 91 | b = address(bob); 92 | 93 | vat.init("gems"); 94 | vat.file("gems", "spot", ray(0.5 ether)); 95 | vat.file("gems", "line", rad(1000 ether)); 96 | vat.file("Line", rad(1000 ether)); 97 | 98 | vat.slip("gems", a, 8 ether); 99 | } 100 | function test_fork_to_self() public { 101 | ali.frob("gems", a, a, a, 8 ether, 4 ether); 102 | assertTrue( ali.can_fork("gems", a, a, 8 ether, 4 ether)); 103 | assertTrue( ali.can_fork("gems", a, a, 4 ether, 2 ether)); 104 | assertTrue(!ali.can_fork("gems", a, a, 9 ether, 4 ether)); 105 | } 106 | function test_give_to_other() public { 107 | ali.frob("gems", a, a, a, 8 ether, 4 ether); 108 | assertTrue(!ali.can_fork("gems", a, b, 8 ether, 4 ether)); 109 | bob.hope(address(ali)); 110 | assertTrue( ali.can_fork("gems", a, b, 8 ether, 4 ether)); 111 | } 112 | function test_fork_to_other() public { 113 | ali.frob("gems", a, a, a, 8 ether, 4 ether); 114 | bob.hope(address(ali)); 115 | assertTrue( ali.can_fork("gems", a, b, 4 ether, 2 ether)); 116 | assertTrue(!ali.can_fork("gems", a, b, 4 ether, 3 ether)); 117 | assertTrue(!ali.can_fork("gems", a, b, 4 ether, 1 ether)); 118 | } 119 | function test_fork_dust() public { 120 | ali.frob("gems", a, a, a, 8 ether, 4 ether); 121 | bob.hope(address(ali)); 122 | assertTrue( ali.can_fork("gems", a, b, 4 ether, 2 ether)); 123 | vat.file("gems", "dust", rad(1 ether)); 124 | assertTrue( ali.can_fork("gems", a, b, 2 ether, 1 ether)); 125 | assertTrue(!ali.can_fork("gems", a, b, 1 ether, 0.5 ether)); 126 | } 127 | } 128 | -------------------------------------------------------------------------------- /src/test/jug.t.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: AGPL-3.0-or-later 2 | 3 | // jug.t.sol -- tests for jug.sol 4 | 5 | // This program is free software: you can redistribute it and/or modify 6 | // it under the terms of the GNU Affero General Public License as published by 7 | // the Free Software Foundation, either version 3 of the License, or 8 | // (at your option) any later version. 9 | // 10 | // This program is distributed in the hope that it will be useful, 11 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | // GNU Affero General Public License for more details. 14 | // 15 | // You should have received a copy of the GNU Affero General Public License 16 | // along with this program. If not, see . 17 | 18 | pragma solidity ^0.6.12; 19 | 20 | import "ds-test/test.sol"; 21 | 22 | import {Jug} from "../jug.sol"; 23 | import {Vat} from "../vat.sol"; 24 | 25 | 26 | interface Hevm { 27 | function warp(uint256) external; 28 | } 29 | 30 | interface VatLike { 31 | function ilks(bytes32) external view returns ( 32 | uint256 Art, 33 | uint256 rate, 34 | uint256 spot, 35 | uint256 line, 36 | uint256 dust 37 | ); 38 | } 39 | 40 | contract Rpow is Jug { 41 | constructor(address vat_) public Jug(vat_){} 42 | 43 | function pRpow(uint x, uint n, uint b) public pure returns(uint) { 44 | return _rpow(x, n, b); 45 | } 46 | } 47 | 48 | 49 | contract JugTest is DSTest { 50 | Hevm hevm; 51 | Jug jug; 52 | Vat vat; 53 | 54 | function rad(uint wad_) internal pure returns (uint) { 55 | return wad_ * 10 ** 27; 56 | } 57 | function wad(uint rad_) internal pure returns (uint) { 58 | return rad_ / 10 ** 27; 59 | } 60 | function rho(bytes32 ilk) internal view returns (uint) { 61 | (uint duty, uint rho_) = jug.ilks(ilk); duty; 62 | return rho_; 63 | } 64 | function Art(bytes32 ilk) internal view returns (uint ArtV) { 65 | (ArtV,,,,) = VatLike(address(vat)).ilks(ilk); 66 | } 67 | function rate(bytes32 ilk) internal view returns (uint rateV) { 68 | (, rateV,,,) = VatLike(address(vat)).ilks(ilk); 69 | } 70 | function line(bytes32 ilk) internal view returns (uint lineV) { 71 | (,,, lineV,) = VatLike(address(vat)).ilks(ilk); 72 | } 73 | 74 | address ali = address(bytes20("ali")); 75 | 76 | function setUp() public { 77 | hevm = Hevm(0x7109709ECfa91a80626fF3989D68f67F5b1DD12D); 78 | hevm.warp(604411200); 79 | 80 | vat = new Vat(); 81 | jug = new Jug(address(vat)); 82 | vat.rely(address(jug)); 83 | vat.init("i"); 84 | 85 | draw("i", 100 ether); 86 | } 87 | function draw(bytes32 ilk, uint dai) internal { 88 | vat.file("Line", vat.Line() + rad(dai)); 89 | vat.file(ilk, "line", line(ilk) + rad(dai)); 90 | vat.file(ilk, "spot", 10 ** 27 * 10000 ether); 91 | address self = address(this); 92 | vat.slip(ilk, self, 10 ** 27 * 1 ether); 93 | vat.frob(ilk, self, self, self, int(1 ether), int(dai)); 94 | } 95 | 96 | function test_drip_setup() public { 97 | hevm.warp(0); 98 | assertEq(uint(now), 0); 99 | hevm.warp(1); 100 | assertEq(uint(now), 1); 101 | hevm.warp(2); 102 | assertEq(uint(now), 2); 103 | assertEq(Art("i"), 100 ether); 104 | } 105 | function test_drip_updates_rho() public { 106 | jug.init("i"); 107 | assertEq(rho("i"), now); 108 | 109 | jug.file("i", "duty", 10 ** 27); 110 | jug.drip("i"); 111 | assertEq(rho("i"), now); 112 | hevm.warp(now + 1); 113 | assertEq(rho("i"), now - 1); 114 | jug.drip("i"); 115 | assertEq(rho("i"), now); 116 | hevm.warp(now + 1 days); 117 | jug.drip("i"); 118 | assertEq(rho("i"), now); 119 | } 120 | function test_drip_file() public { 121 | jug.init("i"); 122 | jug.file("i", "duty", 10 ** 27); 123 | jug.drip("i"); 124 | jug.file("i", "duty", 1000000564701133626865910626); // 5% / day 125 | } 126 | function test_drip_0d() public { 127 | jug.init("i"); 128 | jug.file("i", "duty", 1000000564701133626865910626); // 5% / day 129 | assertEq(vat.dai(ali), rad(0 ether)); 130 | jug.drip("i"); 131 | assertEq(vat.dai(ali), rad(0 ether)); 132 | } 133 | function test_drip_1d() public { 134 | jug.init("i"); 135 | jug.file("vow", ali); 136 | 137 | jug.file("i", "duty", 1000000564701133626865910626); // 5% / day 138 | hevm.warp(now + 1 days); 139 | assertEq(wad(vat.dai(ali)), 0 ether); 140 | jug.drip("i"); 141 | assertEq(wad(vat.dai(ali)), 5 ether); 142 | } 143 | function test_drip_2d() public { 144 | jug.init("i"); 145 | jug.file("vow", ali); 146 | jug.file("i", "duty", 1000000564701133626865910626); // 5% / day 147 | 148 | hevm.warp(now + 2 days); 149 | assertEq(wad(vat.dai(ali)), 0 ether); 150 | jug.drip("i"); 151 | assertEq(wad(vat.dai(ali)), 10.25 ether); 152 | } 153 | function test_drip_3d() public { 154 | jug.init("i"); 155 | jug.file("vow", ali); 156 | 157 | jug.file("i", "duty", 1000000564701133626865910626); // 5% / day 158 | hevm.warp(now + 3 days); 159 | assertEq(wad(vat.dai(ali)), 0 ether); 160 | jug.drip("i"); 161 | assertEq(wad(vat.dai(ali)), 15.7625 ether); 162 | } 163 | function test_drip_negative_3d() public { 164 | jug.init("i"); 165 | jug.file("vow", ali); 166 | 167 | jug.file("i", "duty", 999999706969857929985428567); // -2.5% / day 168 | hevm.warp(now + 3 days); 169 | assertEq(wad(vat.dai(address(this))), 100 ether); 170 | vat.move(address(this), ali, rad(100 ether)); 171 | assertEq(wad(vat.dai(ali)), 100 ether); 172 | jug.drip("i"); 173 | assertEq(wad(vat.dai(ali)), 92.6859375 ether); 174 | } 175 | 176 | function test_drip_multi() public { 177 | jug.init("i"); 178 | jug.file("vow", ali); 179 | 180 | jug.file("i", "duty", 1000000564701133626865910626); // 5% / day 181 | hevm.warp(now + 1 days); 182 | jug.drip("i"); 183 | assertEq(wad(vat.dai(ali)), 5 ether); 184 | jug.file("i", "duty", 1000001103127689513476993127); // 10% / day 185 | hevm.warp(now + 1 days); 186 | jug.drip("i"); 187 | assertEq(wad(vat.dai(ali)), 15.5 ether); 188 | assertEq(wad(vat.debt()), 115.5 ether); 189 | assertEq(rate("i") / 10 ** 9, 1.155 ether); 190 | } 191 | function test_drip_base() public { 192 | vat.init("j"); 193 | draw("j", 100 ether); 194 | 195 | jug.init("i"); 196 | jug.init("j"); 197 | jug.file("vow", ali); 198 | 199 | jug.file("i", "duty", 1050000000000000000000000000); // 5% / second 200 | jug.file("j", "duty", 1000000000000000000000000000); // 0% / second 201 | jug.file("base", uint(50000000000000000000000000)); // 5% / second 202 | hevm.warp(now + 1); 203 | jug.drip("i"); 204 | assertEq(wad(vat.dai(ali)), 10 ether); 205 | } 206 | function test_file_duty() public { 207 | jug.init("i"); 208 | hevm.warp(now + 1); 209 | jug.drip("i"); 210 | jug.file("i", "duty", 1); 211 | } 212 | function testFail_file_duty() public { 213 | jug.init("i"); 214 | hevm.warp(now + 1); 215 | jug.file("i", "duty", 1); 216 | } 217 | function test_rpow() public { 218 | Rpow r = new Rpow(address(vat)); 219 | uint result = r.pRpow(uint(1000234891009084238901289093), uint(3724), uint(1e27)); 220 | // python calc = 2.397991232255757e27 = 2397991232255757e12 221 | // expect 10 decimal precision 222 | assertEq(result / uint(1e17), uint(2397991232255757e12) / 1e17); 223 | } 224 | } 225 | -------------------------------------------------------------------------------- /src/test/pot.t.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: AGPL-3.0-or-later 2 | 3 | // pot.t.sol -- tests for pot.sol 4 | 5 | // This program is free software: you can redistribute it and/or modify 6 | // it under the terms of the GNU Affero General Public License as published by 7 | // the Free Software Foundation, either version 3 of the License, or 8 | // (at your option) any later version. 9 | // 10 | // This program is distributed in the hope that it will be useful, 11 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | // GNU Affero General Public License for more details. 14 | // 15 | // You should have received a copy of the GNU Affero General Public License 16 | // along with this program. If not, see . 17 | 18 | pragma solidity ^0.6.12; 19 | 20 | import "ds-test/test.sol"; 21 | import {Vat} from '../vat.sol'; 22 | import {Pot} from '../pot.sol'; 23 | 24 | interface Hevm { 25 | function warp(uint256) external; 26 | } 27 | 28 | contract DSRTest is DSTest { 29 | Hevm hevm; 30 | 31 | Vat vat; 32 | Pot pot; 33 | 34 | address vow; 35 | address self; 36 | address potb; 37 | 38 | function rad(uint wad_) internal pure returns (uint) { 39 | return wad_ * 10 ** 27; 40 | } 41 | function wad(uint rad_) internal pure returns (uint) { 42 | return rad_ / 10 ** 27; 43 | } 44 | 45 | function setUp() public { 46 | hevm = Hevm(0x7109709ECfa91a80626fF3989D68f67F5b1DD12D); 47 | hevm.warp(604411200); 48 | 49 | vat = new Vat(); 50 | pot = new Pot(address(vat)); 51 | vat.rely(address(pot)); 52 | self = address(this); 53 | potb = address(pot); 54 | 55 | vow = address(bytes20("vow")); 56 | pot.file("vow", vow); 57 | 58 | vat.suck(self, self, rad(100 ether)); 59 | vat.hope(address(pot)); 60 | } 61 | function test_save_0d() public { 62 | assertEq(vat.dai(self), rad(100 ether)); 63 | 64 | pot.join(100 ether); 65 | assertEq(wad(vat.dai(self)), 0 ether); 66 | assertEq(pot.pie(self), 100 ether); 67 | 68 | pot.drip(); 69 | 70 | pot.exit(100 ether); 71 | assertEq(wad(vat.dai(self)), 100 ether); 72 | } 73 | function test_save_1d() public { 74 | pot.join(100 ether); 75 | pot.file("dsr", uint(1000000564701133626865910626)); // 5% / day 76 | hevm.warp(now + 1 days); 77 | pot.drip(); 78 | assertEq(pot.pie(self), 100 ether); 79 | pot.exit(100 ether); 80 | assertEq(wad(vat.dai(self)), 105 ether); 81 | } 82 | function test_drip_multi() public { 83 | pot.join(100 ether); 84 | pot.file("dsr", uint(1000000564701133626865910626)); // 5% / day 85 | hevm.warp(now + 1 days); 86 | pot.drip(); 87 | assertEq(wad(vat.dai(potb)), 105 ether); 88 | pot.file("dsr", uint(1000001103127689513476993127)); // 10% / day 89 | hevm.warp(now + 1 days); 90 | pot.drip(); 91 | assertEq(wad(vat.sin(vow)), 15.5 ether); 92 | assertEq(wad(vat.dai(potb)), 115.5 ether); 93 | assertEq(pot.Pie(), 100 ether); 94 | assertEq(pot.chi() / 10 ** 9, 1.155 ether); 95 | } 96 | function test_drip_multi_inBlock() public { 97 | pot.drip(); 98 | uint rho = pot.rho(); 99 | assertEq(rho, now); 100 | hevm.warp(now + 1 days); 101 | rho = pot.rho(); 102 | assertEq(rho, now - 1 days); 103 | pot.drip(); 104 | rho = pot.rho(); 105 | assertEq(rho, now); 106 | pot.drip(); 107 | rho = pot.rho(); 108 | assertEq(rho, now); 109 | } 110 | function test_save_multi() public { 111 | pot.join(100 ether); 112 | pot.file("dsr", uint(1000000564701133626865910626)); // 5% / day 113 | hevm.warp(now + 1 days); 114 | pot.drip(); 115 | pot.exit(50 ether); 116 | assertEq(wad(vat.dai(self)), 52.5 ether); 117 | assertEq(pot.Pie(), 50.0 ether); 118 | 119 | pot.file("dsr", uint(1000001103127689513476993127)); // 10% / day 120 | hevm.warp(now + 1 days); 121 | pot.drip(); 122 | pot.exit(50 ether); 123 | assertEq(wad(vat.dai(self)), 110.25 ether); 124 | assertEq(pot.Pie(), 0.00 ether); 125 | } 126 | function test_fresh_chi() public { 127 | uint rho = pot.rho(); 128 | assertEq(rho, now); 129 | hevm.warp(now + 1 days); 130 | assertEq(rho, now - 1 days); 131 | pot.drip(); 132 | pot.join(100 ether); 133 | assertEq(pot.pie(self), 100 ether); 134 | pot.exit(100 ether); 135 | // if we exit in the same transaction we should not earn DSR 136 | assertEq(wad(vat.dai(self)), 100 ether); 137 | } 138 | function testFail_stale_chi() public { 139 | pot.file("dsr", uint(1000000564701133626865910626)); // 5% / day 140 | pot.drip(); 141 | hevm.warp(now + 1 days); 142 | pot.join(100 ether); 143 | } 144 | function test_file() public { 145 | hevm.warp(now + 1); 146 | pot.drip(); 147 | pot.file("dsr", uint(1)); 148 | } 149 | function testFail_file() public { 150 | hevm.warp(now + 1); 151 | pot.file("dsr", uint(1)); 152 | } 153 | } 154 | -------------------------------------------------------------------------------- /src/test/vow.t.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: AGPL-3.0-or-later 2 | 3 | // vow.t.sol -- tests for vow.sol 4 | 5 | // This program is free software: you can redistribute it and/or modify 6 | // it under the terms of the GNU Affero General Public License as published by 7 | // the Free Software Foundation, either version 3 of the License, or 8 | // (at your option) any later version. 9 | // 10 | // This program is distributed in the hope that it will be useful, 11 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | // GNU Affero General Public License for more details. 14 | // 15 | // You should have received a copy of the GNU Affero General Public License 16 | // along with this program. If not, see . 17 | 18 | pragma solidity ^0.6.12; 19 | 20 | import "ds-test/test.sol"; 21 | 22 | import {Flopper as Flop} from './flop.t.sol'; 23 | import {Flapper as Flap} from './flap.t.sol'; 24 | import {TestVat as Vat} from './vat.t.sol'; 25 | import {Vow} from '../vow.sol'; 26 | 27 | interface Hevm { 28 | function warp(uint256) external; 29 | } 30 | 31 | contract Gem { 32 | mapping (address => uint256) public balanceOf; 33 | function mint(address usr, uint rad) public { 34 | balanceOf[usr] += rad; 35 | } 36 | } 37 | 38 | contract VowTest is DSTest { 39 | Hevm hevm; 40 | 41 | Vat vat; 42 | Vow vow; 43 | Flop flop; 44 | Flap flap; 45 | Gem gov; 46 | 47 | function setUp() public { 48 | hevm = Hevm(0x7109709ECfa91a80626fF3989D68f67F5b1DD12D); 49 | hevm.warp(604411200); 50 | 51 | vat = new Vat(); 52 | 53 | gov = new Gem(); 54 | flop = new Flop(address(vat), address(gov)); 55 | flap = new Flap(address(vat), address(gov)); 56 | 57 | vow = new Vow(address(vat), address(flap), address(flop)); 58 | flap.rely(address(vow)); 59 | flop.rely(address(vow)); 60 | flap.file("lid", rad(1000 ether)); 61 | 62 | vow.file("bump", rad(100 ether)); 63 | vow.file("sump", rad(100 ether)); 64 | vow.file("dump", 200 ether); 65 | 66 | vat.hope(address(flop)); 67 | } 68 | 69 | function try_flog(uint era) internal returns (bool ok) { 70 | string memory sig = "flog(uint256)"; 71 | (ok,) = address(vow).call(abi.encodeWithSignature(sig, era)); 72 | } 73 | function try_dent(uint id, uint lot, uint bid) internal returns (bool ok) { 74 | string memory sig = "dent(uint256,uint256,uint256)"; 75 | (ok,) = address(flop).call(abi.encodeWithSignature(sig, id, lot, bid)); 76 | } 77 | function try_call(address addr, bytes calldata data) external returns (bool) { 78 | bytes memory _data = data; 79 | assembly { 80 | let ok := call(gas(), addr, 0, add(_data, 0x20), mload(_data), 0, 0) 81 | let free := mload(0x40) 82 | mstore(free, ok) 83 | mstore(0x40, add(free, 32)) 84 | revert(free, 32) 85 | } 86 | } 87 | function can_flap() public returns (bool) { 88 | string memory sig = "flap()"; 89 | bytes memory data = abi.encodeWithSignature(sig); 90 | 91 | bytes memory can_call = abi.encodeWithSignature("try_call(address,bytes)", vow, data); 92 | (bool ok, bytes memory success) = address(this).call(can_call); 93 | 94 | ok = abi.decode(success, (bool)); 95 | if (ok) return true; 96 | } 97 | function can_flop() public returns (bool) { 98 | string memory sig = "flop()"; 99 | bytes memory data = abi.encodeWithSignature(sig); 100 | 101 | bytes memory can_call = abi.encodeWithSignature("try_call(address,bytes)", vow, data); 102 | (bool ok, bytes memory success) = address(this).call(can_call); 103 | 104 | ok = abi.decode(success, (bool)); 105 | if (ok) return true; 106 | } 107 | 108 | uint constant ONE = 10 ** 27; 109 | function rad(uint wad) internal pure returns (uint) { 110 | return wad * ONE; 111 | } 112 | 113 | function suck(address who, uint wad) internal { 114 | vow.fess(rad(wad)); 115 | vat.init(''); 116 | vat.suck(address(vow), who, rad(wad)); 117 | } 118 | function flog(uint wad) internal { 119 | suck(address(0), wad); // suck dai into the zero address 120 | vow.flog(now); 121 | } 122 | function heal(uint wad) internal { 123 | vow.heal(rad(wad)); 124 | } 125 | 126 | function test_change_flap_flop() public { 127 | Flap newFlap = new Flap(address(vat), address(gov)); 128 | Flop newFlop = new Flop(address(vat), address(gov)); 129 | 130 | newFlap.rely(address(vow)); 131 | newFlop.rely(address(vow)); 132 | 133 | assertEq(vat.can(address(vow), address(flap)), 1); 134 | assertEq(vat.can(address(vow), address(newFlap)), 0); 135 | 136 | vow.file('flapper', address(newFlap)); 137 | vow.file('flopper', address(newFlop)); 138 | 139 | assertEq(address(vow.flapper()), address(newFlap)); 140 | assertEq(address(vow.flopper()), address(newFlop)); 141 | 142 | assertEq(vat.can(address(vow), address(flap)), 0); 143 | assertEq(vat.can(address(vow), address(newFlap)), 1); 144 | } 145 | 146 | function test_flog_wait() public { 147 | assertEq(vow.wait(), 0); 148 | vow.file('wait', uint(100 seconds)); 149 | assertEq(vow.wait(), 100 seconds); 150 | 151 | uint tic = now; 152 | vow.fess(100 ether); 153 | hevm.warp(tic + 99 seconds); 154 | assertTrue(!try_flog(tic) ); 155 | hevm.warp(tic + 100 seconds); 156 | assertTrue( try_flog(tic) ); 157 | } 158 | 159 | function test_no_reflop() public { 160 | flog(100 ether); 161 | assertTrue( can_flop() ); 162 | vow.flop(); 163 | assertTrue(!can_flop() ); 164 | } 165 | 166 | function test_no_flop_pending_joy() public { 167 | flog(200 ether); 168 | 169 | vat.mint(address(vow), 100 ether); 170 | assertTrue(!can_flop() ); 171 | 172 | heal(100 ether); 173 | assertTrue( can_flop() ); 174 | } 175 | 176 | function test_flap() public { 177 | vat.mint(address(vow), 100 ether); 178 | assertTrue( can_flap() ); 179 | } 180 | 181 | function test_no_flap_pending_sin() public { 182 | vow.file("bump", uint256(0 ether)); 183 | flog(100 ether); 184 | 185 | vat.mint(address(vow), 50 ether); 186 | assertTrue(!can_flap() ); 187 | } 188 | function test_no_flap_nonzero_woe() public { 189 | vow.file("bump", uint256(0 ether)); 190 | flog(100 ether); 191 | vat.mint(address(vow), 50 ether); 192 | assertTrue(!can_flap() ); 193 | } 194 | function test_no_flap_pending_flop() public { 195 | flog(100 ether); 196 | vow.flop(); 197 | 198 | vat.mint(address(vow), 100 ether); 199 | 200 | assertTrue(!can_flap() ); 201 | } 202 | function test_no_flap_pending_heal() public { 203 | flog(100 ether); 204 | uint id = vow.flop(); 205 | 206 | vat.mint(address(this), 100 ether); 207 | flop.dent(id, 0 ether, rad(100 ether)); 208 | 209 | assertTrue(!can_flap() ); 210 | } 211 | 212 | function test_no_surplus_after_good_flop() public { 213 | flog(100 ether); 214 | uint id = vow.flop(); 215 | vat.mint(address(this), 100 ether); 216 | 217 | flop.dent(id, 0 ether, rad(100 ether)); // flop succeeds.. 218 | 219 | assertTrue(!can_flap() ); 220 | } 221 | 222 | function test_multiple_flop_dents() public { 223 | flog(100 ether); 224 | uint id = vow.flop(); 225 | 226 | vat.mint(address(this), 100 ether); 227 | assertTrue(try_dent(id, 2 ether, rad(100 ether))); 228 | 229 | vat.mint(address(this), 100 ether); 230 | assertTrue(try_dent(id, 1 ether, rad(100 ether))); 231 | } 232 | } 233 | -------------------------------------------------------------------------------- /src/vat.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: AGPL-3.0-or-later 2 | 3 | /// vat.sol -- Dai CDP database 4 | 5 | // Copyright (C) 2018 Rain 6 | // 7 | // This program is free software: you can redistribute it and/or modify 8 | // it under the terms of the GNU Affero General Public License as published by 9 | // the Free Software Foundation, either version 3 of the License, or 10 | // (at your option) any later version. 11 | // 12 | // This program is distributed in the hope that it will be useful, 13 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | // GNU Affero General Public License for more details. 16 | // 17 | // You should have received a copy of the GNU Affero General Public License 18 | // along with this program. If not, see . 19 | 20 | pragma solidity ^0.6.12; 21 | 22 | // FIXME: This contract was altered compared to the production version. 23 | // It doesn't use LibNote anymore. 24 | // New deployments of this contract will need to include custom events (TO DO). 25 | 26 | contract Vat { 27 | // --- Auth --- 28 | mapping (address => uint) public wards; 29 | function rely(address usr) external auth { require(live == 1, "Vat/not-live"); wards[usr] = 1; } 30 | function deny(address usr) external auth { require(live == 1, "Vat/not-live"); wards[usr] = 0; } 31 | modifier auth { 32 | require(wards[msg.sender] == 1, "Vat/not-authorized"); 33 | _; 34 | } 35 | 36 | mapping(address => mapping (address => uint)) public can; 37 | function hope(address usr) external { can[msg.sender][usr] = 1; } 38 | function nope(address usr) external { can[msg.sender][usr] = 0; } 39 | function wish(address bit, address usr) internal view returns (bool) { 40 | return either(bit == usr, can[bit][usr] == 1); 41 | } 42 | 43 | // --- Data --- 44 | struct Ilk { 45 | uint256 Art; // Total Normalised Debt [wad] 46 | uint256 rate; // Accumulated Rates [ray] 47 | uint256 spot; // Price with Safety Margin [ray] 48 | uint256 line; // Debt Ceiling [rad] 49 | uint256 dust; // Urn Debt Floor [rad] 50 | } 51 | struct Urn { 52 | uint256 ink; // Locked Collateral [wad] 53 | uint256 art; // Normalised Debt [wad] 54 | } 55 | 56 | mapping (bytes32 => Ilk) public ilks; 57 | mapping (bytes32 => mapping (address => Urn )) public urns; 58 | mapping (bytes32 => mapping (address => uint)) public gem; // [wad] 59 | mapping (address => uint256) public dai; // [rad] 60 | mapping (address => uint256) public sin; // [rad] 61 | 62 | uint256 public debt; // Total Dai Issued [rad] 63 | uint256 public vice; // Total Unbacked Dai [rad] 64 | uint256 public Line; // Total Debt Ceiling [rad] 65 | uint256 public live; // Active Flag 66 | 67 | // --- Init --- 68 | constructor() public { 69 | wards[msg.sender] = 1; 70 | live = 1; 71 | } 72 | 73 | // --- Math --- 74 | function _add(uint x, int y) internal pure returns (uint z) { 75 | z = x + uint(y); 76 | require(y >= 0 || z <= x); 77 | require(y <= 0 || z >= x); 78 | } 79 | function _sub(uint x, int y) internal pure returns (uint z) { 80 | z = x - uint(y); 81 | require(y <= 0 || z <= x); 82 | require(y >= 0 || z >= x); 83 | } 84 | function _mul(uint x, int y) internal pure returns (int z) { 85 | z = int(x) * y; 86 | require(int(x) >= 0); 87 | require(y == 0 || z / y == int(x)); 88 | } 89 | function _add(uint x, uint y) internal pure returns (uint z) { 90 | require((z = x + y) >= x); 91 | } 92 | function _sub(uint x, uint y) internal pure returns (uint z) { 93 | require((z = x - y) <= x); 94 | } 95 | function _mul(uint x, uint y) internal pure returns (uint z) { 96 | require(y == 0 || (z = x * y) / y == x); 97 | } 98 | 99 | // --- Administration --- 100 | function init(bytes32 ilk) external auth { 101 | require(ilks[ilk].rate == 0, "Vat/ilk-already-init"); 102 | ilks[ilk].rate = 10 ** 27; 103 | } 104 | function file(bytes32 what, uint data) external auth { 105 | require(live == 1, "Vat/not-live"); 106 | if (what == "Line") Line = data; 107 | else revert("Vat/file-unrecognized-param"); 108 | } 109 | function file(bytes32 ilk, bytes32 what, uint data) external auth { 110 | require(live == 1, "Vat/not-live"); 111 | if (what == "spot") ilks[ilk].spot = data; 112 | else if (what == "line") ilks[ilk].line = data; 113 | else if (what == "dust") ilks[ilk].dust = data; 114 | else revert("Vat/file-unrecognized-param"); 115 | } 116 | function cage() external auth { 117 | live = 0; 118 | } 119 | 120 | // --- Fungibility --- 121 | function slip(bytes32 ilk, address usr, int256 wad) external auth { 122 | gem[ilk][usr] = _add(gem[ilk][usr], wad); 123 | } 124 | function flux(bytes32 ilk, address src, address dst, uint256 wad) external { 125 | require(wish(src, msg.sender), "Vat/not-allowed"); 126 | gem[ilk][src] = _sub(gem[ilk][src], wad); 127 | gem[ilk][dst] = _add(gem[ilk][dst], wad); 128 | } 129 | function move(address src, address dst, uint256 rad) external { 130 | require(wish(src, msg.sender), "Vat/not-allowed"); 131 | dai[src] = _sub(dai[src], rad); 132 | dai[dst] = _add(dai[dst], rad); 133 | } 134 | 135 | function either(bool x, bool y) internal pure returns (bool z) { 136 | assembly{ z := or(x, y)} 137 | } 138 | function both(bool x, bool y) internal pure returns (bool z) { 139 | assembly{ z := and(x, y)} 140 | } 141 | 142 | // --- CDP Manipulation --- 143 | function frob(bytes32 i, address u, address v, address w, int dink, int dart) external { 144 | // system is live 145 | require(live == 1, "Vat/not-live"); 146 | 147 | Urn memory urn = urns[i][u]; 148 | Ilk memory ilk = ilks[i]; 149 | // ilk has been initialised 150 | require(ilk.rate != 0, "Vat/ilk-not-init"); 151 | 152 | urn.ink = _add(urn.ink, dink); 153 | urn.art = _add(urn.art, dart); 154 | ilk.Art = _add(ilk.Art, dart); 155 | 156 | int dtab = _mul(ilk.rate, dart); 157 | uint tab = _mul(ilk.rate, urn.art); 158 | debt = _add(debt, dtab); 159 | 160 | // either debt has decreased, or debt ceilings are not exceeded 161 | require(either(dart <= 0, both(_mul(ilk.Art, ilk.rate) <= ilk.line, debt <= Line)), "Vat/ceiling-exceeded"); 162 | // urn is either less risky than before, or it is safe 163 | require(either(both(dart <= 0, dink >= 0), tab <= _mul(urn.ink, ilk.spot)), "Vat/not-safe"); 164 | 165 | // urn is either more safe, or the owner consents 166 | require(either(both(dart <= 0, dink >= 0), wish(u, msg.sender)), "Vat/not-allowed-u"); 167 | // collateral src consents 168 | require(either(dink <= 0, wish(v, msg.sender)), "Vat/not-allowed-v"); 169 | // debt dst consents 170 | require(either(dart >= 0, wish(w, msg.sender)), "Vat/not-allowed-w"); 171 | 172 | // urn has no debt, or a non-dusty amount 173 | require(either(urn.art == 0, tab >= ilk.dust), "Vat/dust"); 174 | 175 | gem[i][v] = _sub(gem[i][v], dink); 176 | dai[w] = _add(dai[w], dtab); 177 | 178 | urns[i][u] = urn; 179 | ilks[i] = ilk; 180 | } 181 | // --- CDP Fungibility --- 182 | function fork(bytes32 ilk, address src, address dst, int dink, int dart) external { 183 | Urn storage u = urns[ilk][src]; 184 | Urn storage v = urns[ilk][dst]; 185 | Ilk storage i = ilks[ilk]; 186 | 187 | u.ink = _sub(u.ink, dink); 188 | u.art = _sub(u.art, dart); 189 | v.ink = _add(v.ink, dink); 190 | v.art = _add(v.art, dart); 191 | 192 | uint utab = _mul(u.art, i.rate); 193 | uint vtab = _mul(v.art, i.rate); 194 | 195 | // both sides consent 196 | require(both(wish(src, msg.sender), wish(dst, msg.sender)), "Vat/not-allowed"); 197 | 198 | // both sides safe 199 | require(utab <= _mul(u.ink, i.spot), "Vat/not-safe-src"); 200 | require(vtab <= _mul(v.ink, i.spot), "Vat/not-safe-dst"); 201 | 202 | // both sides non-dusty 203 | require(either(utab >= i.dust, u.art == 0), "Vat/dust-src"); 204 | require(either(vtab >= i.dust, v.art == 0), "Vat/dust-dst"); 205 | } 206 | // --- CDP Confiscation --- 207 | function grab(bytes32 i, address u, address v, address w, int dink, int dart) external auth { 208 | Urn storage urn = urns[i][u]; 209 | Ilk storage ilk = ilks[i]; 210 | 211 | urn.ink = _add(urn.ink, dink); 212 | urn.art = _add(urn.art, dart); 213 | ilk.Art = _add(ilk.Art, dart); 214 | 215 | int dtab = _mul(ilk.rate, dart); 216 | 217 | gem[i][v] = _sub(gem[i][v], dink); 218 | sin[w] = _sub(sin[w], dtab); 219 | vice = _sub(vice, dtab); 220 | } 221 | 222 | // --- Settlement --- 223 | function heal(uint rad) external { 224 | address u = msg.sender; 225 | sin[u] = _sub(sin[u], rad); 226 | dai[u] = _sub(dai[u], rad); 227 | vice = _sub(vice, rad); 228 | debt = _sub(debt, rad); 229 | } 230 | function suck(address u, address v, uint rad) external auth { 231 | sin[u] = _add(sin[u], rad); 232 | dai[v] = _add(dai[v], rad); 233 | vice = _add(vice, rad); 234 | debt = _add(debt, rad); 235 | } 236 | 237 | // --- Rates --- 238 | function fold(bytes32 i, address u, int rate) external auth { 239 | require(live == 1, "Vat/not-live"); 240 | Ilk storage ilk = ilks[i]; 241 | ilk.rate = _add(ilk.rate, rate); 242 | int rad = _mul(ilk.Art, rate); 243 | dai[u] = _add(dai[u], rad); 244 | debt = _add(debt, rad); 245 | } 246 | } 247 | -------------------------------------------------------------------------------- /src/vow.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: AGPL-3.0-or-later 2 | 3 | /// vow.sol -- Dai settlement module 4 | 5 | // Copyright (C) 2018 Rain 6 | // 7 | // This program is free software: you can redistribute it and/or modify 8 | // it under the terms of the GNU Affero General Public License as published by 9 | // the Free Software Foundation, either version 3 of the License, or 10 | // (at your option) any later version. 11 | // 12 | // This program is distributed in the hope that it will be useful, 13 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | // GNU Affero General Public License for more details. 16 | // 17 | // You should have received a copy of the GNU Affero General Public License 18 | // along with this program. If not, see . 19 | 20 | pragma solidity ^0.6.12; 21 | 22 | // FIXME: This contract was altered compared to the production version. 23 | // It doesn't use LibNote anymore. 24 | // New deployments of this contract will need to include custom events (TO DO). 25 | 26 | interface FlopLike { 27 | function kick(address gal, uint lot, uint bid) external returns (uint); 28 | function cage() external; 29 | function live() external returns (uint); 30 | } 31 | 32 | interface FlapLike { 33 | function kick(uint lot, uint bid) external returns (uint); 34 | function cage(uint) external; 35 | function live() external returns (uint); 36 | } 37 | 38 | interface VatLike { 39 | function dai (address) external view returns (uint); 40 | function sin (address) external view returns (uint); 41 | function heal(uint256) external; 42 | function hope(address) external; 43 | function nope(address) external; 44 | } 45 | 46 | contract Vow { 47 | // --- Auth --- 48 | mapping (address => uint) public wards; 49 | function rely(address usr) external auth { require(live == 1, "Vow/not-live"); wards[usr] = 1; } 50 | function deny(address usr) external auth { wards[usr] = 0; } 51 | modifier auth { 52 | require(wards[msg.sender] == 1, "Vow/not-authorized"); 53 | _; 54 | } 55 | 56 | // --- Data --- 57 | VatLike public vat; // CDP Engine 58 | FlapLike public flapper; // Surplus Auction House 59 | FlopLike public flopper; // Debt Auction House 60 | 61 | mapping (uint256 => uint256) public sin; // debt queue 62 | uint256 public Sin; // Queued debt [rad] 63 | uint256 public Ash; // On-auction debt [rad] 64 | 65 | uint256 public wait; // Flop delay [seconds] 66 | uint256 public dump; // Flop initial lot size [wad] 67 | uint256 public sump; // Flop fixed bid size [rad] 68 | 69 | uint256 public bump; // Flap fixed lot size [rad] 70 | uint256 public hump; // Surplus buffer [rad] 71 | 72 | uint256 public live; // Active Flag 73 | 74 | // --- Init --- 75 | constructor(address vat_, address flapper_, address flopper_) public { 76 | wards[msg.sender] = 1; 77 | vat = VatLike(vat_); 78 | flapper = FlapLike(flapper_); 79 | flopper = FlopLike(flopper_); 80 | vat.hope(flapper_); 81 | live = 1; 82 | } 83 | 84 | // --- Math --- 85 | function add(uint x, uint y) internal pure returns (uint z) { 86 | require((z = x + y) >= x); 87 | } 88 | function sub(uint x, uint y) internal pure returns (uint z) { 89 | require((z = x - y) <= x); 90 | } 91 | function min(uint x, uint y) internal pure returns (uint z) { 92 | return x <= y ? x : y; 93 | } 94 | 95 | // --- Administration --- 96 | function file(bytes32 what, uint data) external auth { 97 | if (what == "wait") wait = data; 98 | else if (what == "bump") bump = data; 99 | else if (what == "sump") sump = data; 100 | else if (what == "dump") dump = data; 101 | else if (what == "hump") hump = data; 102 | else revert("Vow/file-unrecognized-param"); 103 | } 104 | 105 | function file(bytes32 what, address data) external auth { 106 | if (what == "flapper") { 107 | vat.nope(address(flapper)); 108 | flapper = FlapLike(data); 109 | vat.hope(data); 110 | } 111 | else if (what == "flopper") flopper = FlopLike(data); 112 | else revert("Vow/file-unrecognized-param"); 113 | } 114 | 115 | // Push to debt-queue 116 | function fess(uint tab) external auth { 117 | sin[now] = add(sin[now], tab); 118 | Sin = add(Sin, tab); 119 | } 120 | // Pop from debt-queue 121 | function flog(uint era) external { 122 | require(add(era, wait) <= now, "Vow/wait-not-finished"); 123 | Sin = sub(Sin, sin[era]); 124 | sin[era] = 0; 125 | } 126 | 127 | // Debt settlement 128 | function heal(uint rad) external { 129 | require(rad <= vat.dai(address(this)), "Vow/insufficient-surplus"); 130 | require(rad <= sub(sub(vat.sin(address(this)), Sin), Ash), "Vow/insufficient-debt"); 131 | vat.heal(rad); 132 | } 133 | function kiss(uint rad) external { 134 | require(rad <= Ash, "Vow/not-enough-ash"); 135 | require(rad <= vat.dai(address(this)), "Vow/insufficient-surplus"); 136 | Ash = sub(Ash, rad); 137 | vat.heal(rad); 138 | } 139 | 140 | // Debt auction 141 | function flop() external returns (uint id) { 142 | require(sump <= sub(sub(vat.sin(address(this)), Sin), Ash), "Vow/insufficient-debt"); 143 | require(vat.dai(address(this)) == 0, "Vow/surplus-not-zero"); 144 | Ash = add(Ash, sump); 145 | id = flopper.kick(address(this), dump, sump); 146 | } 147 | // Surplus auction 148 | function flap() external returns (uint id) { 149 | require(vat.dai(address(this)) >= add(add(vat.sin(address(this)), bump), hump), "Vow/insufficient-surplus"); 150 | require(sub(sub(vat.sin(address(this)), Sin), Ash) == 0, "Vow/debt-not-zero"); 151 | id = flapper.kick(bump, 0); 152 | } 153 | 154 | function cage() external auth { 155 | require(live == 1, "Vow/not-live"); 156 | live = 0; 157 | Sin = 0; 158 | Ash = 0; 159 | flapper.cage(vat.dai(address(flapper))); 160 | flopper.cage(); 161 | vat.heal(min(vat.dai(address(this)), vat.sin(address(this)))); 162 | } 163 | } 164 | --------------------------------------------------------------------------------