├── .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 | 
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 |
--------------------------------------------------------------------------------