├── .gitignore
├── foundry.toml
├── contracts
├── misc
│ └── mock.sol
├── oracle
│ ├── value.sol
│ ├── mom.sol
│ ├── osm.sol
│ └── median.sol
├── fileDuty.sol
├── auth
│ └── auth.sol
├── proxy
│ ├── proxy.sol
│ └── registry.sol
├── token
│ ├── math.sol
│ └── token.sol
├── multicall.sol
├── system
│ ├── ethjoin.sol
│ ├── spot.sol
│ ├── vow.sol
│ ├── jug.sol
│ ├── join.sol
│ ├── zar.sol
│ ├── dog.sol
│ ├── abaci.sol
│ ├── vat.sol
│ ├── end.sol
│ └── clip.sol
├── pause
│ └── pause.sol
├── cdpmanager.sol
└── deployment.sol
├── README.md
└── test
├── mocks
└── Token.sol
├── osm.t.sol
├── jug.t.sol
├── abaci.t.sol
├── vow.t.sol
├── median.t.sol
└── dog.t.sol
/.gitignore:
--------------------------------------------------------------------------------
1 | #Buidler files
2 | cache
3 | artifacts
4 | typechain-types
5 | node_modules
6 | dist/
7 | build/
8 | /out
9 | .vscode
10 | .idea
11 | .DS_Store
12 |
--------------------------------------------------------------------------------
/foundry.toml:
--------------------------------------------------------------------------------
1 | [profile.default]
2 | src = 'contracts'
3 | out = 'out'
4 | libs = ['lib']
5 | test = 'test'
6 | broadcast = 'broadcast'
7 |
8 | [profile.test]
9 | src = 'test'
10 |
--------------------------------------------------------------------------------
/contracts/misc/mock.sol:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: GNU-3
2 | pragma solidity ^0.8.13;
3 |
4 | contract Mockcontract {
5 | bool has;
6 | bytes32 val;
7 |
8 | function peek() public view returns (bytes32, bool) {
9 | return (val, has);
10 | }
11 |
12 | function void() public {
13 | // unset the value
14 | has = false;
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/contracts/oracle/value.sol:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: GNU-3
2 | // This program is free software: you can redistribute it and/or modify
3 | // it under the terms of the GNU General Public License as published by
4 | // the Free Software Foundation, either version 3 of the License, or
5 | // (at your option) any later version.
6 |
7 | // This program is distributed in the hope that it will be useful,
8 | // but WITHOUT ANY WARRANTY; without even the implied warranty of
9 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10 | // GNU General Public License for more details.
11 |
12 | // You should have received a copy of the GNU General Public License
13 | // along with this program. If not, see .
14 |
15 | pragma solidity ^0.8.13;
16 |
17 | contract Value {
18 | bool has;
19 | bytes32 val;
20 |
21 | function peek() public view returns (bytes32, bool) {
22 | return (val, has);
23 | }
24 |
25 | function read() public view returns (bytes32) {
26 | bytes32 wut;
27 | bool haz;
28 | (wut, haz) = peek();
29 | require(haz, "haz-not");
30 | return wut;
31 | }
32 |
33 | function poke(bytes32 wut) public {
34 | val = wut;
35 | has = true;
36 | }
37 |
38 | function void() public {
39 | // unset the value
40 | has = false;
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/contracts/fileDuty.sol:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: AGPL-3.0-or-later
2 | pragma solidity ^0.8.13;
3 | // This program is free software: you can redistribute it and/or modify
4 | // it under the terms of the GNU Affero General Public License as published by
5 | // the Free Software Foundation, either version 3 of the License, or
6 | // (at your option) any later version.
7 | //
8 | // This program is distributed in the hope that it will be useful,
9 | // but WITHOUT ANY WARRANTY; without even the implied warranty of
10 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 | // GNU Affero General Public License for more details.
12 | //
13 | // You should have received a copy of the GNU Affero General Public License
14 | // along with this program. If not, see .
15 |
16 | interface JugLike {
17 | function file(bytes32 ilk, bytes32 what, uint256 data) external;
18 |
19 | function drip(bytes32 ilk) external returns (uint256 rate);
20 | }
21 |
22 | contract FileDuty {
23 | // --- Auth ---
24 | mapping(address => uint256) public wards;
25 |
26 | function rely(address usr) external auth {
27 | wards[usr] = 1;
28 | emit Rely(usr);
29 | }
30 |
31 | function deny(address usr) external auth {
32 | wards[usr] = 0;
33 | emit Deny(usr);
34 | }
35 |
36 | modifier auth() {
37 | require(wards[msg.sender] == 1, "FileDuty/not-authorized");
38 | _;
39 | }
40 |
41 | JugLike jug;
42 |
43 | event Rely(address indexed usr);
44 | event Deny(address indexed usr);
45 |
46 | constructor(address jug_) {
47 | wards[msg.sender] = 1;
48 | jug = JugLike(jug_);
49 | }
50 |
51 | function fileDuty(bytes32 ilk, bytes32 what, uint256 data) auth external {
52 | jug.drip(ilk);
53 | jug.file(ilk, what, data);
54 | }
55 | }
56 |
--------------------------------------------------------------------------------
/contracts/auth/auth.sol:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: GNU-3
2 | // This program is free software: you can redistribute it and/or modify
3 | // it under the terms of the GNU General Public License as published by
4 | // the Free Software Foundation, either version 3 of the License, or
5 | // (at your option) any later version.
6 |
7 | // This program is distributed in the hope that it will be useful,
8 | // but WITHOUT ANY WARRANTY; without even the implied warranty of
9 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10 | // GNU General Public License for more details.
11 |
12 | // You should have received a copy of the GNU General Public License
13 | // along with this program. If not, see .
14 |
15 | pragma solidity ^0.8.13;
16 |
17 | interface Authority {
18 | function canCall(address src, address dst, bytes4 sig) external view returns (bool);
19 | }
20 |
21 | contract AuthEvents {
22 | event LogSetAuthority(address indexed authority);
23 | event LogSetOwner(address indexed owner);
24 | }
25 |
26 | contract Auth is AuthEvents {
27 | Authority public authority;
28 | address public owner;
29 |
30 | constructor() public {
31 | owner = msg.sender;
32 | emit LogSetOwner(msg.sender);
33 | }
34 |
35 | function setOwner(address owner_) public auth {
36 | owner = owner_;
37 | emit LogSetOwner(owner);
38 | }
39 |
40 | function setAuthority(Authority authority_) public auth {
41 | authority = authority_;
42 | emit LogSetAuthority(address(authority));
43 | }
44 |
45 | modifier auth() {
46 | require(isAuthorized(msg.sender, msg.sig), "auth-unauthorized");
47 | _;
48 | }
49 |
50 | function isAuthorized(address src, bytes4 sig) internal view returns (bool) {
51 | if (src == address(this)) {
52 | return true;
53 | } else if (src == owner) {
54 | return true;
55 | } else if (authority == Authority(address(0))) {
56 | return false;
57 | } else {
58 | return authority.canCall(src, address(this), sig);
59 | }
60 | }
61 | }
62 |
--------------------------------------------------------------------------------
/contracts/proxy/proxy.sol:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: AGPL-3.0-or-later
2 | pragma solidity ^0.8.13;
3 | // This program is free software: you can redistribute it and/or modify
4 | // it under the terms of the GNU Affero General Public License as published by
5 | // the Free Software Foundation, either version 3 of the License, or
6 | // (at your option) any later version.
7 | //
8 | // This program is distributed in the hope that it will be useful,
9 | // but WITHOUT ANY WARRANTY; without even the implied warranty of
10 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 | // GNU Affero General Public License for more details.
12 | //
13 | // You should have received a copy of the GNU Affero General Public License
14 | // along with this program. If not, see .
15 |
16 | interface AuthorityLike {
17 | function canCall(address src, address dst, bytes4 sig) external view returns (bool);
18 | }
19 |
20 | contract Proxy {
21 | address public owner;
22 | address public authority;
23 |
24 | event SetOwner(address indexed owner);
25 | event SetAuthority(address indexed authority);
26 |
27 | constructor(address owner_) {
28 | owner = owner_;
29 | emit SetOwner(owner_);
30 | }
31 |
32 | receive() external payable {}
33 |
34 | modifier auth() {
35 | require(
36 | msg.sender == owner
37 | || authority != address(0) && AuthorityLike(authority).canCall(msg.sender, address(this), msg.sig),
38 | "Proxy/not-authorized"
39 | );
40 | _;
41 | }
42 |
43 | function setOwner(address owner_) external auth {
44 | owner = owner_;
45 | emit SetOwner(owner_);
46 | }
47 |
48 | function setAuthority(address authority_) external auth {
49 | authority = authority_;
50 | emit SetAuthority(authority_);
51 | }
52 |
53 | function execute(address target_, bytes memory data_) external payable auth returns (bytes memory response) {
54 | require(target_ != address(0), "Proxy/target-address-required");
55 |
56 | assembly {
57 | let succeeded := delegatecall(gas(), target_, add(data_, 0x20), mload(data_), 0, 0)
58 | let size := returndatasize()
59 |
60 | response := mload(0x40)
61 | mstore(0x40, add(response, and(add(add(size, 0x20), 0x1f), not(0x1f))))
62 | mstore(response, size)
63 | returndatacopy(add(response, 0x20), 0, size)
64 |
65 | switch succeeded
66 | case 0 { revert(add(response, 0x20), size) }
67 | }
68 | }
69 | }
70 |
--------------------------------------------------------------------------------
/contracts/proxy/registry.sol:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: AGPL-3.0-or-later
2 | pragma solidity ^0.8.13;
3 | // This program is free software: you can redistribute it and/or modify
4 | // it under the terms of the GNU Affero General Public License as published by
5 | // the Free Software Foundation, either version 3 of the License, or
6 | // (at your option) any later version.
7 | //
8 | // This program is distributed in the hope that it will be useful,
9 | // but WITHOUT ANY WARRANTY; without even the implied warranty of
10 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 | // GNU Affero General Public License for more details.
12 | //
13 | // You should have received a copy of the GNU Affero General Public License
14 | // along with this program. If not, see .
15 |
16 | import "./proxy.sol";
17 |
18 | contract ProxyRegistry {
19 | mapping(address => uint256) public seed;
20 | mapping(address => address) public proxies;
21 | mapping(address => uint256) public isProxy;
22 |
23 | function build(address usr) external returns (address payable proxy) {
24 | proxy = payable(proxies[usr]);
25 | if (proxy != address(0)) {
26 | (, bytes memory owner) = proxy.call(abi.encodeWithSignature("owner()"));
27 | // Using low level call in case proxy was self destructed
28 | require(
29 | owner.length != 32 || abi.decode(owner, (address)) != usr,
30 | "ProxyRegistry/proxy-already-registered-to-user"
31 | );
32 | // Not allow new proxy if the user already has one and remains being the owner
33 | }
34 |
35 | uint256 salt = uint256(keccak256(abi.encode(usr, ++seed[usr])));
36 | bytes memory code = abi.encodePacked(type(Proxy).creationCode, abi.encode(usr));
37 | assembly {
38 | proxy := create2(0, add(code, 0x20), mload(code), salt)
39 | }
40 | require(proxy != address(0), "ProxyRegistry/creation-failed");
41 |
42 | proxies[usr] = proxy;
43 | isProxy[proxy] = 1;
44 | }
45 |
46 | // This function needs to be used carefully, you should only claim a proxy you trust on.
47 | // A proxy might be set up with an authority or just simple allowances that might make an
48 | // attacker to take funds that are sitting in the proxy.
49 | function claim(address proxy) external {
50 | require(isProxy[proxy] != 0, "ProxyRegistry/not-proxy-from-this-registry");
51 | address owner = Proxy(payable(proxy)).owner();
52 | require(owner == msg.sender, "ProxyRegistry/only-owner-can-claim");
53 | proxies[owner] = proxy;
54 | }
55 | }
56 |
--------------------------------------------------------------------------------
/contracts/oracle/mom.sol:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: GNU-3
2 | pragma solidity ^0.8.13;
3 |
4 | interface OsmLike {
5 | function stop() external;
6 | }
7 |
8 | interface AuthorityLike {
9 | function canCall(address src, address dst, bytes4 sig) external view returns (bool);
10 | }
11 |
12 | interface VatLike {
13 | function file(bytes32 ilk, bytes32 what, uint256 data) external;
14 | }
15 |
16 | interface AutoLineLike {
17 | function remIlk(bytes32 ilk) external;
18 | }
19 |
20 | contract OsmMom {
21 | address public owner;
22 | address public authority;
23 | address public autoLine;
24 |
25 | mapping(bytes32 => address) public osms;
26 |
27 | address public immutable vat;
28 |
29 | event SetOwner(address indexed owner);
30 | event SetAuthority(address indexed authority);
31 | event File(bytes32 indexed what, address data);
32 | event SetOsm(bytes32 indexed ilk, address osm);
33 | event Stop(bytes32 indexed ilk);
34 |
35 | modifier onlyOwner() {
36 | require(msg.sender == owner, "OsmMom/only-owner");
37 | _;
38 | }
39 |
40 | modifier auth() {
41 | require(isAuthorized(msg.sender, msg.sig), "OsmMom/not-authorized");
42 | _;
43 | }
44 |
45 | function isAuthorized(address src, bytes4 sig) internal view returns (bool) {
46 | if (src == address(this)) {
47 | return true;
48 | } else if (src == owner) {
49 | return true;
50 | } else if (authority == address(0)) {
51 | return false;
52 | } else {
53 | return AuthorityLike(authority).canCall(src, address(this), sig);
54 | }
55 | }
56 |
57 | constructor(address vat_) public {
58 | vat = vat_;
59 | owner = msg.sender;
60 | emit SetOwner(msg.sender);
61 | }
62 |
63 | function setOwner(address owner_) external onlyOwner {
64 | owner = owner_;
65 | emit SetOwner(owner_);
66 | }
67 |
68 | function setAuthority(address authority_) external onlyOwner {
69 | authority = authority_;
70 | emit SetAuthority(authority_);
71 | }
72 |
73 | function file(bytes32 what, address data) external onlyOwner {
74 | if (what == "autoLine") autoLine = data;
75 | else revert("OsmMom/file-unrecognized-param");
76 | emit File(what, data);
77 | }
78 |
79 | function setOsm(bytes32 ilk, address osm) external onlyOwner {
80 | osms[ilk] = osm;
81 | emit SetOsm(ilk, osm);
82 | }
83 |
84 | function stop(bytes32 ilk) external auth {
85 | OsmLike(osms[ilk]).stop();
86 | VatLike(vat).file(ilk, "line", 0);
87 | AutoLineLike(autoLine).remIlk(ilk);
88 | emit Stop(ilk);
89 | }
90 | }
91 |
--------------------------------------------------------------------------------
/contracts/token/math.sol:
--------------------------------------------------------------------------------
1 | /// math.sol -- mixin for inline numerical wizardry
2 |
3 | // This program is free software: you can redistribute it and/or modify
4 | // it under the terms of the GNU General Public License as published by
5 | // the Free Software Foundation, either version 3 of the License, or
6 | // (at your option) any later version.
7 |
8 | // This program is distributed in the hope that it will be useful,
9 | // but WITHOUT ANY WARRANTY; without even the implied warranty of
10 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 | // GNU General Public License for more details.
12 |
13 | // You should have received a copy of the GNU General Public License
14 | // along with this program. If not, see .
15 |
16 | pragma solidity ^0.8.13;
17 |
18 | contract Math {
19 | function mul(uint256 x, uint256 y) internal pure returns (uint256 z) {
20 | require(y == 0 || (z = x * y) / y == x, "math-mul-overflow");
21 | }
22 |
23 | function min(uint256 x, uint256 y) internal pure returns (uint256 z) {
24 | return x <= y ? x : y;
25 | }
26 |
27 | function max(uint256 x, uint256 y) internal pure returns (uint256 z) {
28 | return x >= y ? x : y;
29 | }
30 |
31 | function imin(int256 x, int256 y) internal pure returns (int256 z) {
32 | return x <= y ? x : y;
33 | }
34 |
35 | function imax(int256 x, int256 y) internal pure returns (int256 z) {
36 | return x >= y ? x : y;
37 | }
38 |
39 | uint256 constant WAD = 10 ** 18;
40 | uint256 constant RAY = 10 ** 27;
41 |
42 | //rounds to zero if x*y < WAD / 2
43 | function wmul(uint256 x, uint256 y) internal pure returns (uint256 z) {
44 | z = (mul(x, y) + (WAD / 2)) / WAD;
45 | }
46 |
47 | //rounds to zero if x*y < WAD / 2
48 | function rmul(uint256 x, uint256 y) internal pure returns (uint256 z) {
49 | z = (mul(x, y) + (RAY / 2)) / RAY;
50 | }
51 |
52 | //rounds to zero if x*y < WAD / 2
53 | function wdiv(uint256 x, uint256 y) internal pure returns (uint256 z) {
54 | z = (mul(x, WAD) + (y / 2)) / y;
55 | }
56 |
57 | //rounds to zero if x*y < RAY / 2
58 | function rdiv(uint256 x, uint256 y) internal pure returns (uint256 z) {
59 | z = (mul(x, RAY) + (y / 2)) / y;
60 | }
61 |
62 | // This famous algorithm is called "exponentiation by squaring"
63 | // and calculates x^n with x as fixed-point and n as regular unsigned.
64 | //
65 | // It's O(log n), instead of O(n) for naive repeated multiplication.
66 | //
67 | // These facts are why it works:
68 | //
69 | // If n is even, then x^n = (x^2)^(n/2).
70 | // If n is odd, then x^n = x * x^(n-1),
71 | // and applying the equation for even x gives
72 | // x^n = x * (x^2)^((n-1) / 2).
73 | //
74 | // Also, EVM division is flooring and
75 | // floor[(n-1) / 2] = floor[n / 2].
76 | //
77 | function rpow(uint256 x, uint256 n) internal pure returns (uint256 z) {
78 | z = n % 2 != 0 ? x : RAY;
79 |
80 | for (n /= 2; n != 0; n /= 2) {
81 | x = rmul(x, x);
82 |
83 | if (n % 2 != 0) {
84 | z = rmul(z, x);
85 | }
86 | }
87 | }
88 | }
89 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Zarban Stable Coin System Contracts
2 |
3 | This repository contains the smart contracts source code for Zarban stable coin system contracts.
4 |
5 | ## Test
6 | 1. Install [foundry](https://book.getfoundry.sh/getting-started/installation)
7 |
8 | 2. Use the following command to install dependencies:
9 | ```
10 | forge install foundry-rs/forge-std --no-commit
11 | forge install dapphub/ds-value --no-commit
12 | forge install makerdao/dss-test --no-commit
13 | ```
14 | 3. Run the tests with ```forge test```
15 |
16 | ## Contracts Addresses
17 | | Contract Name | Contract Address |
18 | | :---------------- | :----------------------------------------------: |
19 | | Deployment | [`0x7569D671D95b6215203f091FeBEa65e1369c5037`](https://arbiscan.io/address/0x7569D671D95b6215203f091FeBEa65e1369c5037#code) |
20 | | FileDuty | [`0xcd9c9b83Fae0D4EC97b033Ea71bF9484424D32C3`](https://arbiscan.io/address/0xcd9c9b83Fae0D4EC97b033Ea71bF9484424D32C3#code) |
21 | | MedianDAI | [`0x0cF62d57f5832D73DdF1297454d7a51A6344F6b3`](https://arbiscan.io/address/0x0cF62d57f5832D73DdF1297454d7a51A6344F6b3#code) |
22 | | PipDAI | [`0xF5A9e665c7B9AE781dE7D3b70ef18aBfAAf43316`](https://arbiscan.io/address/0xF5A9e665c7B9AE781dE7D3b70ef18aBfAAf43316#code) |
23 | | JoinDAIA | [`0x19aE4b8993023Eb25dF4948f8D0dd2cD5AA33b10`](https://arbiscan.io/address/0x19aE4b8993023Eb25dF4948f8D0dd2cD5AA33b10#code) |
24 | | ClipCalcDAIA | [`0x92F3f24e6B8c02a864Bf71A1a17Ec5035d0184C9`](https://arbiscan.io/address/0x92F3f24e6B8c02a864Bf71A1a17Ec5035d0184C9#code) |
25 | | JoinDAIB | [`0xE234F872C90b9b0F38541D3D7f0f5C96e7774860`](https://arbiscan.io/address/0xE234F872C90b9b0F38541D3D7f0f5C96e7774860#code) |
26 | | ClipCalcDAIB | [`0x7358e77c89CBe1b70Bb4774Cb6a09308Dec10E9C`](https://arbiscan.io/address/0x7358e77c89CBe1b70Bb4774Cb6a09308Dec10E9C#code) |
27 | | MedianETH | [`0x269766ab73E0C15afE219804AC775dc6AB542e47`](https://arbiscan.io/address/0x269766ab73E0C15afE219804AC775dc6AB542e47#code) |
28 | | PipETH | [`0xd5f4a83bE979e5f9D22f702763993Cc57a4F98E4`](https://arbiscan.io/address/0xd5f4a83bE979e5f9D22f702763993Cc57a4F98E4#code) |
29 | | JoinETHA | [`0x5a3eeF82ee3D2f24613653E049a43f9bA44968e0`](https://arbiscan.io/address/0x5a3eeF82ee3D2f24613653E049a43f9bA44968e0#code) |
30 | | ClipCalcETHA | [`0xadb0737e4436AEd2c090c1e253A33196C1ff1248`](https://arbiscan.io/address/0xadb0737e4436AEd2c090c1e253A33196C1ff1248#code) |
31 | | JoinETHB | [`0x305Be7D96C7A95A4F94D0d7a7a02825de031Ecb3`](https://arbiscan.io/address/0x305Be7D96C7A95A4F94D0d7a7a02825de031Ecb3#code) |
32 | | ClipCalcETHB | [`0xe42620AAa6C6b40F863FC6a5Bd01841356f00786`](https://arbiscan.io/address/0xe42620AAa6C6b40F863FC6a5Bd01841356f00786#code) |
33 | | CdpManager | [`0xe1063E05f94BeCAA1cACc917D0Dd19F6b2f3CB8a`](https://arbiscan.io/address/0xe1063E05f94BeCAA1cACc917D0Dd19F6b2f3CB8a#code) |
34 | | ProxyRegistry | [`0xA461e289a95659827e246e84B49E51eCe29196B7`](https://arbiscan.io/address/0xA461e289a95659827e246e84B49E51eCe29196B7#code) |
35 | | ProxyActions | [`0x2a13AFfa606628885C9aba2103C42c648F7a9342`](https://arbiscan.io/address/0x2a13AFfa606628885C9aba2103C42c648F7a9342#code) |
36 | | ProxyActionsEnd | [`0xe8DA431c67f6581Ad2719d1F09be72fC9E174f9d`](https://arbiscan.io/address/0xe8DA431c67f6581Ad2719d1F09be72fC9E174f9d#code) |
37 |
--------------------------------------------------------------------------------
/contracts/multicall.sol:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: AGPL-3.0-or-later
2 | pragma solidity ^0.8.13;
3 | pragma experimental ABIEncoderV2;
4 |
5 | /// @title Multicall2 - Aggregate results from multiple read-only function calls
6 | /// @author Michael Elliot
7 | /// @author Joshua Levine
8 | /// @author Nick Johnson
9 |
10 | contract Multicall2 {
11 | struct Call {
12 | address target;
13 | bytes callData;
14 | }
15 |
16 | struct Result {
17 | bool success;
18 | bytes returnData;
19 | }
20 |
21 | function aggregate(Call[] memory calls) public returns (uint256 blockNumber, bytes[] memory returnData) {
22 | blockNumber = block.number;
23 | returnData = new bytes[](calls.length);
24 | for (uint256 i = 0; i < calls.length; i++) {
25 | (bool success, bytes memory ret) = calls[i].target.call(calls[i].callData);
26 | require(success, "Multicall aggregate: call failed");
27 | returnData[i] = ret;
28 | }
29 | }
30 |
31 | function blockAndAggregate(Call[] memory calls)
32 | public
33 | returns (uint256 blockNumber, bytes32 blockHash, Result[] memory returnData)
34 | {
35 | (blockNumber, blockHash, returnData) = tryBlockAndAggregate(true, calls);
36 | }
37 |
38 | function getBlockHash(uint256 blockNumber) public view returns (bytes32 blockHash) {
39 | blockHash = blockhash(blockNumber);
40 | }
41 |
42 | function getBlockNumber() public view returns (uint256 blockNumber) {
43 | blockNumber = block.number;
44 | }
45 |
46 | function getCurrentBlockCoinbase() public view returns (address coinbase) {
47 | coinbase = block.coinbase;
48 | }
49 |
50 | function getCurrentBlockDifficulty() public view returns (uint256 difficulty) {
51 | difficulty = block.difficulty;
52 | }
53 |
54 | function getCurrentBlockGasLimit() public view returns (uint256 gaslimit) {
55 | gaslimit = block.gaslimit;
56 | }
57 |
58 | function getCurrentBlockTimestamp() public view returns (uint256 timestamp) {
59 | timestamp = block.timestamp;
60 | }
61 |
62 | function getEthBalance(address addr) public view returns (uint256 balance) {
63 | balance = addr.balance;
64 | }
65 |
66 | function getLastBlockHash() public view returns (bytes32 blockHash) {
67 | blockHash = blockhash(block.number - 1);
68 | }
69 |
70 | function tryAggregate(bool requireSuccess, Call[] memory calls) public returns (Result[] memory returnData) {
71 | returnData = new Result[](calls.length);
72 | for (uint256 i = 0; i < calls.length; i++) {
73 | (bool success, bytes memory ret) = calls[i].target.call(calls[i].callData);
74 |
75 | if (requireSuccess) {
76 | require(success, "Multicall2 aggregate: call failed");
77 | }
78 |
79 | returnData[i] = Result(success, ret);
80 | }
81 | }
82 |
83 | function tryBlockAndAggregate(bool requireSuccess, Call[] memory calls)
84 | public
85 | returns (uint256 blockNumber, bytes32 blockHash, Result[] memory returnData)
86 | {
87 | blockNumber = block.number;
88 | blockHash = blockhash(block.number);
89 | returnData = tryAggregate(requireSuccess, calls);
90 | }
91 | }
92 |
--------------------------------------------------------------------------------
/contracts/system/ethjoin.sol:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: GNU-3
2 | pragma solidity ^0.8.13;
3 | // This program is free software: you can redistribute it and/or modify
4 | // it under the terms of the GNU Affero General Public License as published by
5 | // the Free Software Foundation, either version 3 of the License, or
6 | // (at your option) any later version.
7 | //
8 | // This program is distributed in the hope that it will be useful,
9 | // but WITHOUT ANY WARRANTY; without even the implied warranty of
10 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 | // GNU Affero General Public License for more details.
12 | //
13 | // You should have received a copy of the GNU Affero General Public License
14 | // along with this program. If not, see .
15 |
16 | interface DSTokenLike {
17 | function mint(address, uint256) external;
18 |
19 | function burn(address, uint256) external;
20 | }
21 |
22 | interface VatLike {
23 | function slip(bytes32, address, int256) external;
24 |
25 | function move(address, address, uint256) external;
26 | }
27 |
28 | /*
29 | Here we provide *adapters* to connect the Vat to arbitrary external
30 | token implementations, creating a bounded context for the Vat. The
31 | adapters here are provided as working examples:
32 |
33 | - `GemJoin`: For well behaved ERC20 tokens, with simple transfer
34 | semantics.
35 |
36 | - `ETHJoin`: For native Ether.
37 |
38 | - `ZarJoin`: For connecting internal Zar balances to an external
39 | `DSToken` implementation.
40 |
41 | In practice, adapter implementations will be varied and specific to
42 | individual collateral types, accounting for different transfer
43 | semantics and token standards.
44 |
45 | Adapters need to implement two basic methods:
46 |
47 | - `join`: enter collateral into the system
48 | - `exit`: remove collateral from the system*/
49 |
50 | contract ETHJoin {
51 | // --- ---
52 | mapping(address => uint256) public wards;
53 |
54 | function rely(address usr) external auth {
55 | wards[usr] = 1;
56 | emit Rely(usr);
57 | }
58 |
59 | function deny(address usr) external auth {
60 | wards[usr] = 0;
61 | emit Deny(usr);
62 | }
63 |
64 | modifier auth() {
65 | require(wards[msg.sender] == 1, "Vow/not-authorized");
66 | _;
67 | }
68 |
69 | VatLike public vat;
70 | bytes32 public ilk;
71 | uint256 public live; // Access Flag
72 |
73 | // --- Events ---
74 | event Rely(address indexed usr);
75 | event Deny(address indexed usr);
76 |
77 | constructor(address vat_, bytes32 ilk_) public {
78 | wards[msg.sender] = 1;
79 | live = 1;
80 | vat = VatLike(vat_);
81 | ilk = ilk_;
82 | }
83 |
84 | function cage() external auth {
85 | live = 0;
86 | }
87 |
88 | function join(address usr) external payable {
89 | require(live == 1, "ETHJoin/not-live");
90 | require(int256(msg.value) >= 0, "ETHJoin/overflow");
91 | vat.slip(ilk, usr, int256(msg.value));
92 | }
93 |
94 | function exit(address payable usr, uint256 wad) external {
95 | require(int256(wad) >= 0, "ETHJoin/overflow");
96 | vat.slip(ilk, msg.sender, -int256(wad));
97 | usr.transfer(wad);
98 | }
99 | }
100 |
--------------------------------------------------------------------------------
/test/mocks/Token.sol:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: GPL-3.0-or-later
2 |
3 | /// token.sol -- ERC20 implementation with minting and burning
4 |
5 | // Copyright (C) 2015, 2016, 2017 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.8.13;
21 |
22 | contract MockToken {
23 | uint256 public totalSupply;
24 | mapping(address => uint256) public balanceOf;
25 | mapping(address => mapping(address => uint256)) public allowance;
26 | string public symbol;
27 | uint8 public decimals = 18; // standard token precision. override to customize
28 |
29 | constructor(string memory symbol_) {
30 | symbol = symbol_;
31 | }
32 |
33 | function approve(address guy) external returns (bool) {
34 | return approve(guy, type(uint256).max);
35 | }
36 |
37 | function approve(address guy, uint256 wad) public returns (bool) {
38 | allowance[msg.sender][guy] = wad;
39 |
40 | return true;
41 | }
42 |
43 | function transfer(address dst, uint256 wad) external returns (bool) {
44 | return transferFrom(msg.sender, dst, wad);
45 | }
46 |
47 | function transferFrom(address src, address dst, uint256 wad) public returns (bool) {
48 | if (src != msg.sender && allowance[src][msg.sender] != type(uint256).max) {
49 | require(allowance[src][msg.sender] >= wad, "token-insufficient-approval");
50 | allowance[src][msg.sender] = allowance[src][msg.sender] - wad;
51 | }
52 |
53 | require(balanceOf[src] >= wad, "token-insufficient-balance");
54 | balanceOf[src] = balanceOf[src] - wad;
55 | balanceOf[dst] = balanceOf[dst] + wad;
56 |
57 | return true;
58 | }
59 |
60 | function push(address dst, uint wad) external {
61 | transferFrom(msg.sender, dst, wad);
62 | }
63 |
64 | function pull(address src, uint wad) external {
65 | transferFrom(src, msg.sender, wad);
66 | }
67 |
68 | function move(address src, address dst, uint wad) external {
69 | transferFrom(src, dst, wad);
70 | }
71 |
72 | function mint(uint256 wad) external {
73 | mint(msg.sender, wad);
74 | }
75 |
76 | function burn(uint256 wad) external {
77 | burn(msg.sender, wad);
78 | }
79 |
80 | function mint(address guy, uint256 wad) public {
81 | balanceOf[guy] = balanceOf[guy] + wad;
82 | totalSupply = totalSupply + wad;
83 | }
84 |
85 | function burn(address guy, uint256 wad) public {
86 | if (guy != msg.sender && allowance[guy][msg.sender] != type(uint256).max) {
87 | require(allowance[guy][msg.sender] >= wad, "token-insufficient-approval");
88 | allowance[guy][msg.sender] = allowance[guy][msg.sender] - wad;
89 | }
90 |
91 | require(balanceOf[guy] >= wad, "token-insufficient-balance");
92 | balanceOf[guy] = balanceOf[guy] - wad;
93 | totalSupply = totalSupply - wad;
94 | }
95 | }
96 |
--------------------------------------------------------------------------------
/contracts/system/spot.sol:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: AGPL-3.0-or-later
2 | pragma solidity ^0.8.13;
3 | // This program is free software: you can redistribute it and/or modify
4 | // it under the terms of the GNU Affero General Public License as published by
5 | // the Free Software Foundation, either version 3 of the License, or
6 | // (at your option) any later version.
7 | //
8 | // This program is distributed in the hope that it will be useful,
9 | // but WITHOUT ANY WARRANTY; without even the implied warranty of
10 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 | // GNU Affero General Public License for more details.
12 | //
13 | // You should have received a copy of the GNU Affero General Public License
14 | // along with this program. If not, see .
15 |
16 | interface VatLike {
17 | function file(bytes32, bytes32, uint256) external;
18 | }
19 |
20 | interface PipLike {
21 | function peek() external returns (bytes32, bool);
22 | }
23 |
24 | contract Spotter {
25 | // --- ---
26 | mapping(address => uint256) public wards;
27 |
28 | function rely(address guy) external auth {
29 | wards[guy] = 1;
30 | emit Rely(guy);
31 | }
32 |
33 | function deny(address guy) external auth {
34 | wards[guy] = 0;
35 | emit Deny(guy);
36 | }
37 |
38 | modifier auth() {
39 | require(wards[msg.sender] == 1, "Spotter/not-authorized");
40 | _;
41 | }
42 |
43 | // --- Data ---
44 | struct Ilk {
45 | PipLike pip; // Price Feed
46 | uint256 mat; // Liquidation ratio [ray]
47 | }
48 |
49 | mapping(bytes32 => Ilk) public ilks;
50 |
51 | VatLike public vat; // CDP Engine
52 | uint256 public par; // ref per zar [ray]
53 |
54 | uint256 public live;
55 |
56 | // --- Events ---
57 | event Rely(address indexed guy);
58 | event Deny(address indexed guy);
59 |
60 | event Poke( // [wad]
61 | // [ray]
62 | bytes32 ilk, bytes32 val, uint256 spot);
63 |
64 | event File(bytes32 indexed ilk, bytes32 indexed what, uint256 data);
65 | event File(bytes32 indexed ilk, bytes32 indexed what, address pip_);
66 | event File(bytes32 what, uint256 data);
67 |
68 | // --- Init ---
69 | constructor(address vat_) public {
70 | wards[msg.sender] = 1;
71 | vat = VatLike(vat_);
72 | par = RAY;
73 | live = 1;
74 | }
75 |
76 | // --- Math ---
77 | uint256 constant RAY = 10 ** 27;
78 |
79 | function mul(uint256 x, uint256 y) internal pure returns (uint256 z) {
80 | require(y == 0 || (z = x * y) / y == x);
81 | }
82 |
83 | function rdiv(uint256 x, uint256 y) internal pure returns (uint256 z) {
84 | z = mul(x, RAY) / y;
85 | }
86 |
87 | // --- Administration ---
88 | function file(bytes32 ilk, bytes32 what, address pip_) external auth {
89 | require(live == 1, "Spotter/not-live");
90 | if (what == "pip") ilks[ilk].pip = PipLike(pip_);
91 | else revert("Spotter/file-unrecognized-param");
92 | emit File(ilk, what, pip_);
93 | }
94 |
95 | function file(bytes32 what, uint256 data) external auth {
96 | require(live == 1, "Spotter/not-live");
97 | if (what == "par") par = data;
98 | else revert("Spotter/file-unrecognized-param");
99 | emit File(what, data);
100 | }
101 |
102 | function file(bytes32 ilk, bytes32 what, uint256 data) external auth {
103 | require(live == 1, "Spotter/not-live");
104 | if (what == "mat") ilks[ilk].mat = data;
105 | else revert("Spotter/file-unrecognized-param");
106 | emit File(ilk, what, data);
107 | }
108 |
109 | // --- Update value ---
110 | function poke(bytes32 ilk) external {
111 | (bytes32 val, bool has) = ilks[ilk].pip.peek();
112 | uint256 spot = has ? rdiv(rdiv(mul(uint256(val), 10 ** 9), par), ilks[ilk].mat) : 0;
113 | vat.file(ilk, "spot", spot);
114 | emit Poke(ilk, val, spot);
115 | }
116 |
117 | function cage() external auth {
118 | live = 0;
119 | }
120 | }
121 |
--------------------------------------------------------------------------------
/contracts/token/token.sol:
--------------------------------------------------------------------------------
1 | /// token.sol -- ERC20 implementation with minting and burning
2 |
3 | // Copyright (C) 2015, 2016, 2017 DappHub, LLC
4 |
5 | // This program is free software: you can redistribute it and/or modify
6 | // it under the terms of the GNU 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 General Public License for more details.
14 |
15 | // You should have received a copy of the GNU General Public License
16 | // along with this program. If not, see .
17 |
18 | pragma solidity ^0.8.13;
19 |
20 | import "./math.sol";
21 |
22 | contract Token is Math {
23 | uint256 public totalSupply;
24 | mapping(address => uint256) public balanceOf;
25 | mapping(address => mapping(address => uint256)) public allowance;
26 | string public symbol;
27 | uint8 public decimals = 18; // standard token precision. override to customize
28 | string public name = "Tes Col"; // Optional token name
29 |
30 | event Approval(address indexed src, address indexed guy, uint256 wad);
31 | event Transfer(address indexed src, address indexed dst, uint256 wad);
32 | event Mint(address indexed guy, uint256 wad);
33 | event Burn(address indexed guy, uint256 wad);
34 |
35 | constructor(string memory symbol_) public {
36 | symbol = symbol_;
37 | }
38 |
39 | function approve(address guy) external returns (bool) {
40 | return approve(guy, type(uint256).max);
41 | }
42 |
43 | function approve(address guy, uint256 wad) public returns (bool) {
44 | allowance[msg.sender][guy] = wad;
45 |
46 | emit Approval(msg.sender, guy, wad);
47 |
48 | return true;
49 | }
50 |
51 | function transfer(address dst, uint256 wad) external returns (bool) {
52 | return transferFrom(msg.sender, dst, wad);
53 | }
54 |
55 | function transferFrom(address src, address dst, uint256 wad) public returns (bool) {
56 | uint256 allowed = allowance[src][msg.sender];
57 | if (src != msg.sender && allowed != type(uint256).max) {
58 | require(allowed >= wad, "ds-token-insufficient-approval");
59 |
60 | unchecked {
61 | allowance[src][msg.sender] = allowed - wad;
62 | }
63 | }
64 |
65 | require(balanceOf[src] >= wad, "ds-token-insufficient-balance");
66 |
67 | unchecked {
68 | balanceOf[src] = balanceOf[src] - wad;
69 | balanceOf[dst] = balanceOf[dst] + wad;
70 | }
71 |
72 | emit Transfer(src, dst, wad);
73 |
74 | return true;
75 | }
76 |
77 | function mint(uint256 wad) external {
78 | mint(msg.sender, wad);
79 | }
80 |
81 | function burn(uint256 wad) external {
82 | burn(msg.sender, wad);
83 | }
84 |
85 | function mint(address guy, uint256 wad) public {
86 | unchecked {
87 | balanceOf[guy] = balanceOf[guy] + wad;
88 | }
89 | totalSupply = totalSupply + wad;
90 | emit Mint(guy, wad);
91 | }
92 |
93 | function burn(address guy, uint256 wad) public {
94 | uint256 allowed = allowance[guy][msg.sender];
95 | if (guy != msg.sender && allowed != type(uint256).max) {
96 | require(allowed >= wad, "token-insufficient-approval");
97 |
98 | unchecked {
99 | allowance[guy][msg.sender] = allowed - wad;
100 | }
101 | }
102 |
103 | require(balanceOf[guy] >= wad, "token-insufficient-balance");
104 |
105 | unchecked {
106 | balanceOf[guy] = balanceOf[guy] - wad;
107 | totalSupply = totalSupply - wad;
108 | }
109 |
110 | emit Burn(guy, wad);
111 | }
112 |
113 | function setName(string memory name_) public {
114 | name = name_;
115 | }
116 | }
117 |
--------------------------------------------------------------------------------
/contracts/oracle/osm.sol:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: GNU-3
2 | // This program is free software: you can redistribute it and/or modify
3 | // it under the terms of the GNU Affero General Public License as published by
4 | // the Free Software Foundation, either version 3 of the License, or
5 | // (at your option) any later version.
6 | //
7 | // This program is distributed in the hope that it will be useful,
8 | // but WITHOUT ANY WARRANTY; without even the implied warranty of
9 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10 | // GNU Affero General Public License for more details.
11 | //
12 | // You should have received a copy of the GNU Affero General Public License
13 | // along with this program. If not, see .
14 |
15 | pragma solidity ^0.8.13;
16 |
17 | import "./value.sol";
18 |
19 | contract OSM {
20 | // --- Auth ---
21 | mapping(address => uint256) public wards;
22 |
23 | function rely(address usr) external auth {
24 | wards[usr] = 1;
25 | emit Rely(usr);
26 | }
27 |
28 | function deny(address usr) external auth {
29 | wards[usr] = 0;
30 | emit Deny(usr);
31 | }
32 |
33 | modifier auth() {
34 | require(wards[msg.sender] == 1, "OSM/not-authorized");
35 | _;
36 | }
37 |
38 | // --- Stop ---
39 | uint256 public stopped;
40 |
41 | modifier stoppable() {
42 | require(stopped == 0, "OSM/is-stopped");
43 | _;
44 | }
45 |
46 | address public src;
47 | uint16 constant ONE_HOUR = uint16(3600);
48 | uint16 public hop = ONE_HOUR;
49 | uint64 public zzz;
50 |
51 | struct Feed {
52 | uint128 val;
53 | uint128 has;
54 | }
55 |
56 | Feed cur;
57 | Feed nxt;
58 |
59 | // Whitelisted contracts
60 | mapping(address => uint256) public bud;
61 |
62 | modifier toll() {
63 | require(bud[msg.sender] == 1, "OSM/contract-not-whitelisted");
64 | _;
65 | }
66 |
67 | // --- Events ---
68 | event Rely(address indexed usr);
69 | event Deny(address indexed usr);
70 |
71 | event LogValue(bytes32 val);
72 |
73 | constructor(address src_) public {
74 | wards[msg.sender] = 1;
75 | src = src_;
76 | }
77 |
78 | function stop() external auth {
79 | stopped = 1;
80 | }
81 |
82 | function start() external auth {
83 | stopped = 0;
84 | }
85 |
86 | function change(address src_) external auth {
87 | src = src_;
88 | }
89 |
90 | function era() internal view returns (uint256) {
91 | return block.timestamp;
92 | }
93 |
94 | function prev(uint256 ts) internal view returns (uint64) {
95 | require(hop != 0, "OSM/hop-is-zero");
96 | return uint64(ts - (ts % hop));
97 | }
98 |
99 | function step(uint16 ts) external auth {
100 | require(ts > 0, "OSM/ts-is-zero");
101 | hop = ts;
102 | }
103 |
104 | function void() external auth {
105 | cur = nxt = Feed(0, 0);
106 | stopped = 1;
107 | }
108 |
109 | function pass() public view returns (bool ok) {
110 | return era() >= zzz + hop;
111 | }
112 |
113 | function poke() external stoppable {
114 | require(pass(), "OSM/not-passed");
115 | (bytes32 wut, bool ok) = Value(src).peek();
116 | if (ok) {
117 | cur = nxt;
118 | nxt = Feed(uint128(uint256(wut)), 1);
119 | zzz = prev(era());
120 | emit LogValue(bytes32(uint256(cur.val)));
121 | }
122 | }
123 |
124 | function peek() external view toll returns (bytes32, bool) {
125 | return (bytes32(uint256(cur.val)), cur.has == 1);
126 | }
127 |
128 | function peep() external view toll returns (bytes32, bool) {
129 | return (bytes32(uint256(nxt.val)), nxt.has == 1);
130 | }
131 |
132 | function read() external view toll returns (bytes32) {
133 | require(cur.has == 1, "OSM/no-current-value");
134 | return (bytes32(uint256(cur.val)));
135 | }
136 |
137 | function kiss(address a) external auth {
138 | require(a != address(0), "OSM/no-contract-0");
139 | bud[a] = 1;
140 | }
141 |
142 | function diss(address a) external auth {
143 | bud[a] = 0;
144 | }
145 |
146 | function kiss(address[] calldata a) external auth {
147 | for (uint256 i = 0; i < a.length; i++) {
148 | require(a[i] != address(0), "OSM/no-contract-0");
149 | bud[a[i]] = 1;
150 | }
151 | }
152 |
153 | function diss(address[] calldata a) external auth {
154 | for (uint256 i = 0; i < a.length; i++) {
155 | bud[a[i]] = 0;
156 | }
157 | }
158 | }
159 |
--------------------------------------------------------------------------------
/contracts/pause/pause.sol:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: GNU-3
2 | // This program is free software: you can redistribute it and/or modify
3 | // it under the terms of the GNU Affero General Public License as published by
4 | // the Free Software Foundation, either version 3 of the License, or
5 | // (at your option) any later version.
6 | //
7 | // This program is distributed in the hope that it will be useful,
8 | // but WITHOUT ANY WARRANTY; without even the implied warranty of
9 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10 | // GNU Affero General Public License for more details.
11 | //
12 | // You should have received a copy of the GNU Affero General Public License
13 | // along with this program. If not, see .
14 |
15 | pragma solidity ^0.8.13;
16 |
17 | interface Authority {
18 | function canCall(address src, address dst, bytes4 sig) external view returns (bool);
19 | }
20 |
21 | contract AuthEvents {
22 | event LogSetAuthority(address indexed authority);
23 | event LogSetOwner(address indexed owner);
24 | }
25 |
26 | contract Auth is AuthEvents {
27 | address public authority;
28 | address public owner;
29 |
30 | modifier auth() {
31 | require(isAuthorized(msg.sender, msg.sig), "auth-unauthorized");
32 | _;
33 | }
34 |
35 | function isAuthorized(address src, bytes4 sig) internal view returns (bool) {
36 | if (src == address(this)) {
37 | return true;
38 | } else if (src == owner) {
39 | return true;
40 | } else if (authority == address(0)) {
41 | return false;
42 | } else {
43 | return Authority(authority).canCall(src, address(this), sig);
44 | }
45 | }
46 | }
47 |
48 | contract Pause is Auth {
49 | // --- admin ---
50 |
51 | modifier wait() {
52 | require(msg.sender == address(proxy), "pause-undelayed-call");
53 | _;
54 | }
55 |
56 | function setOwner(address owner_) public wait {
57 | owner = owner_;
58 | emit LogSetOwner(owner);
59 | }
60 |
61 | function setAuthority(address authority_) public wait {
62 | authority = authority_;
63 | emit LogSetAuthority(authority);
64 | }
65 |
66 | function setDelay(uint256 delay_) public wait {
67 | delay = delay_;
68 | }
69 |
70 | // --- data ---
71 |
72 | mapping(bytes32 => bool) public plans;
73 | PauseProxy public proxy;
74 | uint256 public delay;
75 |
76 | // --- init ---
77 |
78 | constructor(uint256 delay_, address owner_, address authority_) public {
79 | delay = delay_;
80 | owner = owner_;
81 | authority = authority_;
82 | proxy = new PauseProxy();
83 | }
84 |
85 | // --- util ---
86 |
87 | function hash(address usr, bytes32 tag, bytes memory fax, uint256 eta) internal pure returns (bytes32) {
88 | return keccak256(abi.encode(usr, tag, fax, eta));
89 | }
90 |
91 | function soul(address usr) internal view returns (bytes32 tag) {
92 | assembly {
93 | tag := extcodehash(usr)
94 | }
95 | }
96 |
97 | // --- operations ---
98 |
99 | function plot(address usr, bytes32 tag, bytes memory fax, uint256 eta) public auth {
100 | require(eta >= block.timestamp + delay, "pause-delay-not-respected");
101 | plans[hash(usr, tag, fax, eta)] = true;
102 | }
103 |
104 | function drop(address usr, bytes32 tag, bytes memory fax, uint256 eta) public auth {
105 | plans[hash(usr, tag, fax, eta)] = false;
106 | }
107 |
108 | function exec(address usr, bytes32 tag, bytes memory fax, uint256 eta) public returns (bytes memory out) {
109 | require(plans[hash(usr, tag, fax, eta)], "pause-unplotted-plan");
110 | require(soul(usr) == tag, "pause-wrong-codehash");
111 | require(block.timestamp >= eta, "pause-premature-exec");
112 |
113 | plans[hash(usr, tag, fax, eta)] = false;
114 |
115 | out = proxy.exec(usr, fax);
116 | require(proxy.owner() == address(this), "pause-illegal-storage-change");
117 | }
118 | }
119 |
120 | // plans are executed in an isolated storage context to protect the pause from
121 | // malicious storage modification during plan execution
122 | contract PauseProxy {
123 | address public owner;
124 |
125 | modifier auth() {
126 | require(msg.sender == owner, "pause-proxy-unauthorized");
127 | _;
128 | }
129 |
130 | constructor() public {
131 | owner = msg.sender;
132 | }
133 |
134 | function exec(address usr, bytes memory fax) public auth returns (bytes memory out) {
135 | bool ok;
136 | (ok, out) = usr.delegatecall(fax);
137 | require(ok, "pause-delegatecall-error");
138 | }
139 | }
140 |
--------------------------------------------------------------------------------
/contracts/oracle/median.sol:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: GNU-3
2 | pragma solidity ^0.8.13;
3 |
4 | contract Median {
5 | // --- Auth ---
6 | mapping(address => uint256) public wards;
7 |
8 | function rely(address usr) external auth {
9 | wards[usr] = 1;
10 | emit Rely(usr);
11 | }
12 |
13 | function deny(address usr) external auth {
14 | wards[usr] = 0;
15 | emit Deny(usr);
16 | }
17 |
18 | modifier auth() {
19 | require(wards[msg.sender] == 1, "Median/not-authorized");
20 | _;
21 | }
22 |
23 | uint128 val;
24 | uint32 public age;
25 | bytes32 public wat; // You want to change this every deploy
26 | uint256 public bar = 1;
27 |
28 | // orized oracles, set by an
29 | mapping(address => uint256) public orcl;
30 |
31 | // Whitelisted contracts, set by an
32 | mapping(address => uint256) public bud;
33 |
34 | // Mapping for at most 256 oracles
35 | mapping(uint8 => address) public slot;
36 |
37 | modifier toll() {
38 | require(bud[msg.sender] == 1, "Median/contract-not-whitelisted");
39 | _;
40 | }
41 |
42 | // --- Events ---
43 | event Rely(address indexed usr);
44 | event Deny(address indexed usr);
45 |
46 | event LogMedianPrice(uint256 val, uint256 age);
47 |
48 | //Set type of Oracle
49 |
50 | function read() external view toll returns (uint256) {
51 | require(val > 0, "Median/invalid-price-feed");
52 | return val;
53 | }
54 |
55 | function peek() external view toll returns (uint256, bool) {
56 | return (val, val > 0);
57 | }
58 |
59 | constructor(bytes32 _wat) public {
60 | wat = _wat;
61 | wards[msg.sender] = 1;
62 | emit Rely(msg.sender);
63 | }
64 |
65 | function recover(uint256 val_, uint256 age_, uint8 v, bytes32 r, bytes32 s) internal view returns (address) {
66 | return ecrecover(
67 | keccak256(
68 | abi.encodePacked("\x19Ethereum Signed Message:\n32", keccak256(abi.encodePacked(val_, age_, wat)))
69 | ),
70 | v,
71 | r,
72 | s
73 | );
74 | }
75 |
76 | function poke(
77 | uint256[] calldata val_,
78 | uint256[] calldata age_,
79 | uint8[] calldata v,
80 | bytes32[] calldata r,
81 | bytes32[] calldata s
82 | ) external {
83 | require(val_.length == bar, "Median/bar-too-low");
84 |
85 | uint256 bloom = 0;
86 | uint256 last = 0;
87 | uint256 zzz = age;
88 |
89 | for (uint256 i = 0; i < val_.length; i++) {
90 | // Validate the values were signed by an orized oracle
91 | address signer = recover(val_[i], age_[i], v[i], r[i], s[i]);
92 | // Check that signer is an oracle
93 | require(orcl[signer] == 1, "Median/invalid-oracle");
94 | // Price feed age greater than last medianizer age
95 | require(age_[i] > zzz, "Median/stale-message");
96 | // Check for ordered values
97 | require(val_[i] >= last, "Median/messages-not-in-order");
98 | last = val_[i];
99 | // Bloom filter for signer uniqueness
100 | uint8 sl = uint8(uint256(uint160(signer)) >> 152);
101 | require((bloom >> sl) % 2 == 0, "Median/oracle-already-signed");
102 | bloom += uint256(2) ** sl;
103 | }
104 |
105 | val = uint128(val_[val_.length >> 1]);
106 | age = uint32(block.timestamp);
107 |
108 | emit LogMedianPrice(val, age);
109 | }
110 |
111 | function lift(address[] calldata a) external auth {
112 | for (uint256 i = 0; i < a.length; i++) {
113 | require(a[i] != address(0), "Median/no-oracle-0");
114 | uint8 s = uint8(uint256(uint160(a[i])) >> 152);
115 | require(slot[s] == address(0), "Median/signer-already-exists");
116 | orcl[a[i]] = 1;
117 | slot[s] = a[i];
118 | }
119 | }
120 |
121 | function drop(address[] calldata a) external auth {
122 | for (uint256 i = 0; i < a.length; i++) {
123 | orcl[a[i]] = 0;
124 | slot[uint8(uint256(uint160(a[i])) >> 152)] = address(0);
125 | }
126 | }
127 |
128 | function setBar(uint256 bar_) external auth {
129 | require(bar_ > 0, "Median/quorum-is-zero");
130 | require(bar_ % 2 != 0, "Median/quorum-not-odd-number");
131 | bar = bar_;
132 | }
133 |
134 | function kiss(address a) external auth {
135 | require(a != address(0), "Median/no-contract-0");
136 | bud[a] = 1;
137 | }
138 |
139 | function diss(address a) external auth {
140 | bud[a] = 0;
141 | }
142 |
143 | function kiss(address[] calldata a) external auth {
144 | for (uint256 i = 0; i < a.length; i++) {
145 | require(a[i] != address(0), "Median/no-contract-0");
146 | bud[a[i]] = 1;
147 | }
148 | }
149 |
150 | function diss(address[] calldata a) external auth {
151 | for (uint256 i = 0; i < a.length; i++) {
152 | bud[a[i]] = 0;
153 | }
154 | }
155 | }
156 |
--------------------------------------------------------------------------------
/contracts/system/vow.sol:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: AGPL-3.0-or-later
2 | pragma solidity ^0.8.13;
3 | // This program is free software: you can redistribute it and/or modify
4 | // it under the terms of the GNU Affero General Public License as published by
5 | // the Free Software Foundation, either version 3 of the License, or
6 | // (at your option) any later version.
7 | //
8 | // This program is distributed in the hope that it will be useful,
9 | // but WITHOUT ANY WARRANTY; without even the implied warranty of
10 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 | // GNU Affero General Public License for more details.
12 | //
13 | // You should have received a copy of the GNU Affero General Public License
14 | // along with this program. If not, see .
15 |
16 | interface VatLike {
17 | function move(address, address, uint256) external;
18 |
19 | function zar(address) external view returns (uint256);
20 |
21 | function sin(address) external view returns (uint256);
22 |
23 | function heal(uint256) external;
24 |
25 | function hope(address) external;
26 |
27 | function nope(address) external;
28 | }
29 |
30 | contract Vow {
31 | // --- Auth ---
32 | mapping(address => uint256) public wards;
33 |
34 | function rely(address usr) external auth {
35 | require(live == 1, "Vow/not-live");
36 | wards[usr] = 1;
37 | emit Rely(usr);
38 | }
39 |
40 | function deny(address usr) external auth {
41 | wards[usr] = 0;
42 | emit Deny(usr);
43 | }
44 |
45 | modifier auth() {
46 | require(wards[msg.sender] == 1, "Vow/not-authorized");
47 | _;
48 | }
49 |
50 | // --- Data ---
51 | VatLike public vat; // CDP Engine
52 |
53 | mapping(uint256 => uint256) public sin; // debt queue
54 | uint256 public Sin; // queued debt [rad]
55 |
56 | uint256 public wait; // Flop delay [seconds]
57 | uint256 public sump; // Flop min size [rad]
58 |
59 | uint256 public hump; // Surplus buffer [rad]
60 | uint256 public bump; // flap fixed lot size [rad]
61 |
62 | address public collector;
63 |
64 | uint256 public live; // Active Flag
65 |
66 | event Rely(address indexed usr);
67 | event Deny(address indexed usr);
68 |
69 | event File(bytes32 indexed what, uint256 data);
70 | event File(bytes32 indexed what, address data);
71 | event Heal(uint256 indexed rad);
72 |
73 | event Fess(uint256 tab);
74 | event Flog(uint256 era);
75 | event Flop(address usr, uint256 rad);
76 | event Flap(address usr, uint256 rad);
77 |
78 | event Cage();
79 |
80 | // --- Init ---
81 | constructor(address vat_) public {
82 | wards[msg.sender] = 1;
83 | vat = VatLike(vat_);
84 | live = 1;
85 | }
86 |
87 | // --- Math ---
88 | function min(uint256 x, uint256 y) internal pure returns (uint256 z) {
89 | return x <= y ? x : y;
90 | }
91 | // --- Administration ---
92 |
93 | function file(bytes32 what, uint256 data) external auth {
94 | if (what == "wait") wait = data;
95 | else if (what == "sump") sump = data;
96 | else if (what == "hump") hump = data;
97 | else if (what == "bump") bump = data;
98 | else revert("Vow/file-unrecognized-param");
99 | emit File(what, data);
100 | }
101 |
102 | function file(bytes32 what, address data) external auth {
103 | if (what == "collector") collector = data;
104 | else revert("Vow/file-unrecognized-param");
105 | emit File(what, data);
106 | }
107 |
108 | // Push to debt-queue
109 | function fess(uint256 tab) external auth {
110 | sin[block.timestamp] = sin[block.timestamp] + tab;
111 | Sin = Sin + tab;
112 | emit Fess(tab);
113 | }
114 |
115 | // Pop from debt-queue
116 | function flog(uint256 era) external {
117 | require(era + wait <= block.timestamp, "Vow/wait-not-finished");
118 | Sin = Sin - sin[era];
119 | sin[era] = 0;
120 | emit Flog(era);
121 | }
122 |
123 | // Debt settlement
124 | function heal(uint256 rad) external {
125 | require(rad <= vat.zar(address(this)), "Vow/insufficient-surplus");
126 | require(rad <= vat.sin(address(this)) - Sin, "Vow/insufficient-debt");
127 | vat.heal(rad);
128 | emit Heal(rad);
129 | }
130 |
131 | function flap() external {
132 | require(vat.zar(address(this)) >= vat.sin(address(this)) + bump + hump, "Vow/insufficient-surplus");
133 | require(vat.sin(address(this)) - Sin == 0, "Vow/debt-not-zero");
134 | vat.move(address(this), collector, bump);
135 | emit Flap(collector, bump);
136 | }
137 |
138 | function flop(uint256 rad) external {
139 | require(sump <= rad, "Vow/insufficient-flop-rad");
140 | require(rad <= vat.sin(address(this)) - Sin, "Vow/insufficient-debt");
141 | require(vat.zar(address(this)) == 0, "Vow/surplus-not-zero");
142 | vat.move(msg.sender, address(this), rad);
143 | vat.heal(rad);
144 | emit Flop(msg.sender, rad);
145 | }
146 |
147 | function cage() external auth {
148 | require(live == 1, "Vow/not-live");
149 | live = 0;
150 | Sin = 0;
151 | vat.heal(min(vat.zar(address(this)), vat.sin(address(this))));
152 | emit Cage();
153 | }
154 | }
155 |
--------------------------------------------------------------------------------
/test/osm.t.sol:
--------------------------------------------------------------------------------
1 | pragma solidity >=0.5.10;
2 |
3 | import "ds-test/test.sol";
4 | import {DSValue} from "ds-value/value.sol";
5 | import {OSM} from "contracts/oracle/osm.sol";
6 |
7 | interface Hevm {
8 | function warp(uint256) external;
9 | }
10 |
11 | contract OSMTest is DSTest {
12 | Hevm hevm;
13 |
14 | DSValue feed;
15 | OSM osm;
16 | address bud;
17 |
18 | function setUp() public {
19 | feed = new DSValue(); //create new feed
20 | feed.poke(bytes32(uint256(100 ether))); //set feed to 100
21 | osm = new OSM(address(feed)); //create new osm linked to feed
22 | hevm = Hevm(0x7109709ECfa91a80626fF3989D68f67F5b1DD12D); //get hevm instance
23 | hevm.warp(uint256(osm.hop())); //warp 1 hop
24 | osm.poke(); //set new next osm value
25 | bud = address(this); //authorized reader
26 | }
27 |
28 | function testChangeValue() public {
29 | assertEq(osm.src(), address(feed)); //verify osm source is feed
30 | DSValue feed2 = new DSValue(); //create new feed
31 | osm.change(address(feed2)); //change osm source to new feed
32 | assertEq(osm.src(), address(feed2)); //verify osm source is new feed
33 | }
34 |
35 | function testSetHop() public {
36 | assertEq(uint256(osm.hop()), 3600); //verify interval is 1 hour
37 | osm.step(uint16(7200)); //change interval to 2 hours
38 | assertEq(uint256(osm.hop()), 7200); //verify interval is 2 hours
39 | }
40 |
41 | function testFailSetHopZero() public {
42 | osm.step(uint16(0)); //attempt to change interval to 0
43 | }
44 |
45 | function testVoid() public {
46 | assertTrue(osm.stopped() == 0); //verify osm is active
47 | osm.kiss(bud); //whitelist caller
48 | hevm.warp(uint256(osm.hop() * 2)); //warp 2 hops
49 | osm.poke(); //set new curent and next osm value
50 | (bytes32 val, bool has) = osm.peek(); //pull current osm value
51 | assertEq(uint256(val), 100 ether); //verify osm value is 100
52 | assertTrue(has); //verify osm value is valid
53 | (val, has) = osm.peep(); //pull next osm value
54 | assertEq(uint256(val), 100 ether); //verify next osm value is 100
55 | assertTrue(has); //verify next osm value is valid
56 | osm.void(); //void all osm values
57 | assertTrue(osm.stopped() == 1); //verify osm is inactive
58 | (val, has) = osm.peek(); //pull current osm value
59 | assertEq(uint256(val), 0); //verify current osm value is 0
60 | assertTrue(!has); //verify current osm value is invalid
61 | (val, has) = osm.peep(); //pull next osm value
62 | assertEq(uint256(val), 0); //verify next osm value is 0
63 | assertTrue(!has); //verify next osm value is invalid
64 | }
65 |
66 | function testPoke() public {
67 | feed.poke(bytes32(uint256(101 ether))); //set new feed value
68 | hevm.warp(uint256(osm.hop() * 2)); //warp 2 hops
69 | osm.poke(); //set new current and next osm value
70 | osm.kiss(bud); //whitelist caller
71 | (bytes32 val, bool has) = osm.peek(); //pull current osm value
72 | assertEq(uint256(val), 100 ether); //verify current osm value is 100
73 | assertTrue(has); //verify current osm value is valid
74 | (val, has) = osm.peep(); //pull next osm value
75 | assertEq(uint256(val), 101 ether); //verify next osm value is 101
76 | assertTrue(has); //verify next osm value is valid
77 | hevm.warp(uint256(osm.hop() * 3)); //warp 3 hops
78 | osm.poke(); //set new current and next osm value
79 | (val, has) = osm.peek(); //pull current osm value
80 | assertEq(uint256(val), 101 ether); //verify current osm value is 101
81 | assertTrue(has); //verify current osm value is valid
82 | }
83 |
84 | function testFailPoke() public {
85 | feed.poke(bytes32(uint256(101 ether))); //set new current and next osm value
86 | hevm.warp(uint256(osm.hop() * 2 - 1)); //warp 2 hops - 1 second
87 | osm.poke(); //attempt to set new current and next osm value
88 | }
89 |
90 | function testFailWhitelistPeep() public view {
91 | osm.peep(); //attempt to pull next osm value
92 | }
93 |
94 | function testWhitelistPeep() public {
95 | osm.kiss(bud); //whitelist caller
96 | (bytes32 val, bool has) = osm.peep(); //pull next osm value
97 | assertEq(uint256(val), 100 ether); //verify next osm value is 100
98 | assertTrue(has); //verify next osm value is valid
99 | }
100 |
101 | function testFailWhitelistPeek() public view {
102 | osm.peek(); //attempt to pull current osm value
103 | }
104 |
105 | function testWhitelistPeek() public {
106 | osm.kiss(bud); //whitelist caller
107 | osm.peek(); //pull current osm value
108 | }
109 |
110 | function testKiss() public {
111 | assertTrue(osm.bud(address(this)) == 0); //verify caller is not whitelisted
112 | osm.kiss(bud); //whitelist caller
113 | assertTrue(osm.bud(address(this)) == 1); //verify caller is whitelisted
114 | }
115 |
116 | function testDiss() public {
117 | osm.kiss(bud); //whitelist caller
118 | assertTrue(osm.bud(address(this)) == 1); //verify caller is whitelisted
119 | osm.diss(bud); //remove caller from whitelist
120 | assertTrue(osm.bud(address(this)) == 0); //verify caller is not whitelisted
121 | }
122 | }
123 |
--------------------------------------------------------------------------------
/contracts/system/jug.sol:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: AGPL-3.0-or-later
2 | pragma solidity ^0.8.13;
3 | // This program is free software: you can redistribute it and/or modify
4 | // it under the terms of the GNU Affero General Public License as published by
5 | // the Free Software Foundation, either version 3 of the License, or
6 | // (at your option) any later version.
7 | //
8 | // This program is distributed in the hope that it will be useful,
9 | // but WITHOUT ANY WARRANTY; without even the implied warranty of
10 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 | // GNU Affero General Public License for more details.
12 | //
13 | // You should have received a copy of the GNU Affero General Public License
14 | // along with this program. If not, see .
15 |
16 | interface VatLike {
17 | function ilks(bytes32)
18 | external
19 | returns (
20 | uint256 Art, // [wad]
21 | uint256 rate
22 | ); // [ray]
23 |
24 | function fold(bytes32, address, int256) external;
25 | }
26 |
27 | contract Jug {
28 | // --- Auth ---
29 | mapping(address => uint256) public wards;
30 |
31 | function rely(address usr) external auth {
32 | wards[usr] = 1;
33 | emit Rely(usr);
34 | }
35 |
36 | function deny(address usr) external auth {
37 | wards[usr] = 0;
38 | emit Deny(usr);
39 | }
40 |
41 | modifier auth() {
42 | require(wards[msg.sender] == 1, "Jug/not-authorized");
43 | _;
44 | }
45 |
46 | // --- Data ---
47 | struct Ilk {
48 | uint256 duty; // Collateral-specific, per-second stability fee contribution [ray]
49 | uint256 rho; // Time of last drip [unix epoch time]
50 | }
51 |
52 | mapping(bytes32 => Ilk) public ilks;
53 | VatLike public vat; // CDP Engine
54 | address public vow; // Debt Engine
55 | uint256 public base; // Global, per-second stability fee contribution [ray]
56 |
57 | // --- Events ---
58 | event Rely(address indexed usr);
59 | event Deny(address indexed usr);
60 |
61 | event Init(bytes32 indexed ilk);
62 |
63 | event File(bytes32 indexed ilk, bytes32 indexed what, uint256 indexed data);
64 |
65 | event File(bytes32 indexed what, uint256 data);
66 | event File(bytes32 indexed what, address data);
67 |
68 | event Drip(bytes32 indexed ilk, uint256 indexed rate);
69 |
70 | // --- Init ---
71 | constructor(address vat_) public {
72 | wards[msg.sender] = 1;
73 | vat = VatLike(vat_);
74 | }
75 |
76 | // --- Math ---
77 | function _rpow(uint256 x, uint256 n, uint256 b) internal pure returns (uint256 z) {
78 | assembly {
79 | switch x
80 | case 0 {
81 | switch n
82 | case 0 { z := b }
83 | default { z := 0 }
84 | }
85 | default {
86 | switch mod(n, 2)
87 | case 0 { z := b }
88 | default { z := x }
89 | let half := div(b, 2) // for rounding.
90 | for { n := div(n, 2) } n { n := div(n, 2) } {
91 | let xx := mul(x, x)
92 | if iszero(eq(div(xx, x), x)) { revert(0, 0) }
93 | let xxRound := add(xx, half)
94 | if lt(xxRound, xx) { revert(0, 0) }
95 | x := div(xxRound, b)
96 | if mod(n, 2) {
97 | let zx := mul(z, x)
98 | if and(iszero(iszero(x)), iszero(eq(div(zx, x), z))) { revert(0, 0) }
99 | let zxRound := add(zx, half)
100 | if lt(zxRound, zx) { revert(0, 0) }
101 | z := div(zxRound, b)
102 | }
103 | }
104 | }
105 | }
106 | }
107 |
108 | uint256 constant RAY = 10 ** 27;
109 |
110 | function _diff(uint256 x, uint256 y) internal pure returns (int256 z) {
111 | z = int256(x) - int256(y);
112 | require(int256(x) >= 0 && int256(y) >= 0);
113 | }
114 |
115 | function _rmul(uint256 x, uint256 y) internal pure returns (uint256 z) {
116 | z = x * y;
117 | require(y == 0 || z / y == x);
118 | z = z / RAY;
119 | }
120 |
121 | // --- Administration ---
122 | function init(bytes32 ilk) external auth {
123 | Ilk storage i = ilks[ilk];
124 | require(i.duty == 0, "Jug/ilk-already-init");
125 | i.duty = RAY;
126 | i.rho = block.timestamp;
127 | emit Init(ilk);
128 | }
129 |
130 | function file(bytes32 ilk, bytes32 what, uint256 data) external auth {
131 | require(block.timestamp == ilks[ilk].rho, "Jug/rho-not-updated");
132 | if (what == "duty") ilks[ilk].duty = data;
133 | else revert("Jug/file-unrecognized-param");
134 | emit File(ilk, what, data);
135 | }
136 |
137 | function file(bytes32 what, uint256 data) external auth {
138 | if (what == "base") base = data;
139 | else revert("Jug/file-unrecognized-param");
140 | emit File(what, data);
141 | }
142 |
143 | function file(bytes32 what, address data) external auth {
144 | if (what == "vow") vow = data;
145 | else revert("Jug/file-unrecognized-param");
146 | emit File(what, data);
147 | }
148 |
149 | // --- Stability Fee Collection ---
150 | function drip(bytes32 ilk) external returns (uint256 rate) {
151 | require(block.timestamp >= ilks[ilk].rho, "Jug/invalid-now");
152 | (, uint256 prev) = vat.ilks(ilk);
153 | rate = _rmul(_rpow(base + ilks[ilk].duty, block.timestamp - ilks[ilk].rho, RAY), prev);
154 | vat.fold(ilk, vow, _diff(rate, prev));
155 | ilks[ilk].rho = block.timestamp;
156 | emit Drip(ilk, rate);
157 | }
158 | }
159 |
--------------------------------------------------------------------------------
/contracts/system/join.sol:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: AGPL-3.0-or-later
2 | pragma solidity ^0.8.13;
3 | // This program is free software: you can redistribute it and/or modify
4 | // it under the terms of the GNU Affero General Public License as published by
5 | // the Free Software Foundation, either version 3 of the License, or
6 | // (at your option) any later version.
7 | //
8 | // This program is distributed in the hope that it will be useful,
9 | // but WITHOUT ANY WARRANTY; without even the implied warranty of
10 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 | // GNU Affero General Public License for more details.
12 | //
13 | // You should have received a copy of the GNU Affero General Public License
14 | // along with this program. If not, see .
15 |
16 | interface GemLike {
17 | function decimals() external view returns (uint256);
18 |
19 | function transfer(address, uint256) external returns (bool);
20 |
21 | function transferFrom(address, address, uint256) external returns (bool);
22 | }
23 |
24 | interface TokenLike {
25 | function mint(address, uint256) external;
26 |
27 | function burn(address, uint256) external;
28 | }
29 |
30 | interface VatLike {
31 | function slip(bytes32, address, int256) external;
32 |
33 | function move(address, address, uint256) external;
34 | }
35 |
36 | /*
37 | Here we provide *adapters* to connect the Vat to arbitrary external
38 | token implementations, creating a bounded context for the Vat. The
39 | adapters here are provided as working examples:
40 |
41 | - `GemJoin`: For well behaved ERC20 tokens, with simple transfer
42 | semantics.
43 |
44 | - `ETHJoin`: For native Ether.
45 |
46 | - `ZarJoin`: For connecting internal zar balances to an external
47 | `Token` implementation.
48 |
49 | In practice, adapter implementations will be varied and specific to
50 | individual collateral types, accounting for different transfer
51 | semantics and token standards.
52 |
53 | Adapters need to implement two basic methods:
54 |
55 | - `join`: enter collateral into the system
56 | - `exit`: remove collateral from the system*/
57 |
58 | contract GemJoin {
59 | // --- Auth ---
60 | mapping(address => uint256) public wards;
61 |
62 | function rely(address usr) external auth {
63 | wards[usr] = 1;
64 | emit Rely(usr);
65 | }
66 |
67 | function deny(address usr) external auth {
68 | wards[usr] = 0;
69 | emit Deny(usr);
70 | }
71 |
72 | modifier auth() {
73 | require(wards[msg.sender] == 1, "GemJoin/not-authorized");
74 | _;
75 | }
76 |
77 | VatLike public vat; // CDP Engine
78 | bytes32 public ilk; // Collateral Type
79 | GemLike public gem;
80 | uint256 public dec;
81 | uint256 public live; // Active Flag
82 |
83 | // Events
84 | event Rely(address indexed usr);
85 | event Deny(address indexed usr);
86 | event Join(address indexed usr, uint256 wad);
87 | event Exit(address indexed usr, uint256 wad);
88 | event Cage();
89 |
90 | constructor(address vat_, bytes32 ilk_, address gem_) public {
91 | wards[msg.sender] = 1;
92 | live = 1;
93 | vat = VatLike(vat_);
94 | ilk = ilk_;
95 | gem = GemLike(gem_);
96 | dec = gem.decimals();
97 | emit Rely(msg.sender);
98 | }
99 |
100 | function cage() external auth {
101 | live = 0;
102 | emit Cage();
103 | }
104 |
105 | function join(address usr, uint256 wad) external {
106 | require(live == 1, "GemJoin/not-live");
107 | require(int256(wad) >= 0, "GemJoin/overflow");
108 | vat.slip(ilk, usr, int256(wad));
109 | require(gem.transferFrom(msg.sender, address(this), wad), "GemJoin/failed-transfer");
110 | emit Join(usr, wad);
111 | }
112 |
113 | function exit(address usr, uint256 wad) external {
114 | require(wad <= 2 ** 255, "GemJoin/overflow");
115 | vat.slip(ilk, msg.sender, -int256(wad));
116 | require(gem.transfer(usr, wad), "GemJoin/failed-transfer");
117 | emit Exit(usr, wad);
118 | }
119 | }
120 |
121 | contract ZarJoin {
122 | // --- Auth ---
123 | mapping(address => uint256) public wards;
124 |
125 | function rely(address usr) external auth {
126 | wards[usr] = 1;
127 | emit Rely(usr);
128 | }
129 |
130 | function deny(address usr) external auth {
131 | wards[usr] = 0;
132 | emit Deny(usr);
133 | }
134 |
135 | modifier auth() {
136 | require(wards[msg.sender] == 1, "DaiJoin/not-authorized");
137 | _;
138 | }
139 |
140 | VatLike public vat; // CDP Engine
141 | TokenLike public zar; // Stablecoin Token
142 | uint256 public live; // Active Flag
143 |
144 | // Events
145 | event Rely(address indexed usr);
146 | event Deny(address indexed usr);
147 | event Join(address indexed usr, uint256 wad);
148 | event Exit(address indexed usr, uint256 wad);
149 | event Cage();
150 |
151 | constructor(address vat_, address zar_) public {
152 | wards[msg.sender] = 1;
153 | live = 1;
154 | vat = VatLike(vat_);
155 | zar = TokenLike(zar_);
156 | }
157 |
158 | function cage() external auth {
159 | live = 0;
160 | emit Cage();
161 | }
162 |
163 | uint256 constant ONE = 10 ** 27;
164 |
165 | function mul(uint256 x, uint256 y) internal pure returns (uint256 z) {
166 | require(y == 0 || (z = x * y) / y == x);
167 | }
168 |
169 | function join(address usr, uint256 wad) external {
170 | vat.move(address(this), usr, mul(ONE, wad));
171 | zar.burn(msg.sender, wad);
172 | emit Join(usr, wad);
173 | }
174 |
175 | function exit(address usr, uint256 wad) external {
176 | require(live == 1, "ZarJoin/not-live");
177 | vat.move(msg.sender, address(this), mul(ONE, wad));
178 | zar.mint(usr, wad);
179 | emit Exit(usr, wad);
180 | }
181 | }
182 |
--------------------------------------------------------------------------------
/test/jug.t.sol:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: AGPL-3.0-or-later
2 |
3 | pragma solidity ^0.8.13;
4 |
5 | import "ds-test/test.sol";
6 |
7 | import {Jug} from "contracts/system/jug.sol";
8 | import {Vat} from "contracts/system/vat.sol";
9 |
10 | interface Hevm {
11 | function warp(uint256) external;
12 | }
13 |
14 | interface VatLike {
15 | function ilks(bytes32)
16 | external
17 | view
18 | returns (uint256 Art, uint256 rate, uint256 spot, uint256 line, uint256 dust);
19 | }
20 |
21 | contract Rpow is Jug {
22 | constructor(address vat_) Jug(vat_) {}
23 |
24 | function pRpow(uint256 x, uint256 n, uint256 b) public pure returns (uint256) {
25 | return _rpow(x, n, b);
26 | }
27 | }
28 |
29 | contract JugTest is DSTest {
30 | Hevm hevm;
31 | Jug jug;
32 | Vat vat;
33 |
34 | function rad(uint256 wad_) internal pure returns (uint256) {
35 | return wad_ * 10 ** 27;
36 | }
37 |
38 | function wad(uint256 rad_) internal pure returns (uint256) {
39 | return rad_ / 10 ** 27;
40 | }
41 |
42 | function rho(bytes32 ilk) internal view returns (uint256) {
43 | (uint256 duty, uint256 rho_) = jug.ilks(ilk);
44 | duty;
45 | return rho_;
46 | }
47 |
48 | function Art(bytes32 ilk) internal view returns (uint256 ArtV) {
49 | (ArtV,,,,) = VatLike(address(vat)).ilks(ilk);
50 | }
51 |
52 | function rate(bytes32 ilk) internal view returns (uint256 rateV) {
53 | (, rateV,,,) = VatLike(address(vat)).ilks(ilk);
54 | }
55 |
56 | function line(bytes32 ilk) internal view returns (uint256 lineV) {
57 | (,,, lineV,) = VatLike(address(vat)).ilks(ilk);
58 | }
59 |
60 | address ali = address(bytes20("ali"));
61 |
62 | function setUp() public {
63 | hevm = Hevm(0x7109709ECfa91a80626fF3989D68f67F5b1DD12D);
64 | hevm.warp(604411200);
65 |
66 | vat = new Vat();
67 | jug = new Jug(address(vat));
68 | vat.rely(address(jug));
69 | vat.init("i");
70 |
71 | draw("i", 100 ether);
72 | }
73 |
74 | function draw(bytes32 ilk, uint256 zar) internal {
75 | vat.file("Line", vat.Line() + rad(zar));
76 | vat.file(ilk, "line", line(ilk) + rad(zar));
77 | vat.file(ilk, "spot", 10 ** 27 * 10000 ether);
78 | address self = address(this);
79 | vat.slip(ilk, self, 10 ** 27 * 1 ether);
80 | vat.frob(ilk, self, self, self, int256(1 ether), int256(zar));
81 | }
82 |
83 | function test_drip_setup() public {
84 | hevm.warp(0);
85 | assertEq(uint256(block.timestamp), 0);
86 | hevm.warp(1);
87 | assertEq(uint256(block.timestamp), 1);
88 | hevm.warp(2);
89 | assertEq(uint256(block.timestamp), 2);
90 | assertEq(Art("i"), 100 ether);
91 | }
92 |
93 | function test_drip_updates_rho() public {
94 | jug.init("i");
95 | assertEq(rho("i"), block.timestamp);
96 |
97 | jug.file("i", "duty", 10 ** 27);
98 | jug.drip("i");
99 | assertEq(rho("i"), block.timestamp);
100 | hevm.warp(block.timestamp + 1);
101 | assertEq(rho("i"), block.timestamp - 1);
102 | jug.drip("i");
103 | assertEq(rho("i"), block.timestamp);
104 | hevm.warp(block.timestamp + 1 days);
105 | jug.drip("i");
106 | assertEq(rho("i"), block.timestamp);
107 | }
108 |
109 | function test_drip_file() public {
110 | jug.init("i");
111 | jug.file("i", "duty", 10 ** 27);
112 | jug.drip("i");
113 | jug.file("i", "duty", 1000000564701133626865910626); // 5% / day
114 | }
115 |
116 | function test_drip_0d() public {
117 | jug.init("i");
118 | jug.file("i", "duty", 1000000564701133626865910626); // 5% / day
119 | assertEq(vat.zar(ali), rad(0 ether));
120 | jug.drip("i");
121 | assertEq(vat.zar(ali), rad(0 ether));
122 | }
123 |
124 | function test_drip_1d() public {
125 | jug.init("i");
126 | jug.file("vow", ali);
127 |
128 | jug.file("i", "duty", 1000000564701133626865910626); // 5% / day
129 | hevm.warp(block.timestamp + 1 days);
130 | assertEq(wad(vat.zar(ali)), 0 ether);
131 | jug.drip("i");
132 | assertEq(wad(vat.zar(ali)), 5 ether);
133 | }
134 |
135 | function test_drip_2d() public {
136 | jug.init("i");
137 | jug.file("vow", ali);
138 | jug.file("i", "duty", 1000000564701133626865910626); // 5% / day
139 |
140 | hevm.warp(block.timestamp + 2 days);
141 | assertEq(wad(vat.zar(ali)), 0 ether);
142 | jug.drip("i");
143 | assertEq(wad(vat.zar(ali)), 10.25 ether);
144 | }
145 |
146 | function test_drip_3d() public {
147 | jug.init("i");
148 | jug.file("vow", ali);
149 |
150 | jug.file("i", "duty", 1000000564701133626865910626); // 5% / day
151 | hevm.warp(block.timestamp + 3 days);
152 | assertEq(wad(vat.zar(ali)), 0 ether);
153 | jug.drip("i");
154 | assertEq(wad(vat.zar(ali)), 15.7625 ether);
155 | }
156 |
157 | function test_drip_negative_3d() public {
158 | jug.init("i");
159 | jug.file("vow", ali);
160 |
161 | jug.file("i", "duty", 999999706969857929985428567); // -2.5% / day
162 | hevm.warp(block.timestamp + 3 days);
163 | assertEq(wad(vat.zar(address(this))), 100 ether);
164 | vat.move(address(this), ali, rad(100 ether));
165 | assertEq(wad(vat.zar(ali)), 100 ether);
166 | jug.drip("i");
167 | assertEq(wad(vat.zar(ali)), 92.6859375 ether);
168 | }
169 |
170 | function test_drip_multi() public {
171 | jug.init("i");
172 | jug.file("vow", ali);
173 |
174 | jug.file("i", "duty", 1000000564701133626865910626); // 5% / day
175 | hevm.warp(block.timestamp + 1 days);
176 | jug.drip("i");
177 | assertEq(wad(vat.zar(ali)), 5 ether);
178 | jug.file("i", "duty", 1000001103127689513476993127); // 10% / day
179 | hevm.warp(block.timestamp + 1 days);
180 | jug.drip("i");
181 | assertEq(wad(vat.zar(ali)), 15.5 ether);
182 | assertEq(wad(vat.debt()), 115.5 ether);
183 | assertEq(rate("i") / 10 ** 9, 1.155 ether);
184 | }
185 |
186 | function test_drip_base() public {
187 | vat.init("j");
188 | draw("j", 100 ether);
189 |
190 | jug.init("i");
191 | jug.init("j");
192 | jug.file("vow", ali);
193 |
194 | jug.file("i", "duty", 1050000000000000000000000000); // 5% / second
195 | jug.file("j", "duty", 1000000000000000000000000000); // 0% / second
196 | jug.file("base", uint256(50000000000000000000000000)); // 5% / second
197 | hevm.warp(block.timestamp + 1);
198 | jug.drip("i");
199 | assertEq(wad(vat.zar(ali)), 10 ether);
200 | }
201 |
202 | function test_file_duty() public {
203 | jug.init("i");
204 | hevm.warp(block.timestamp + 1);
205 | jug.drip("i");
206 | jug.file("i", "duty", 1);
207 | }
208 |
209 | function testFail_file_duty() public {
210 | jug.init("i");
211 | hevm.warp(block.timestamp + 1);
212 | jug.file("i", "duty", 1);
213 | }
214 |
215 | function test_rpow() public {
216 | Rpow r = new Rpow(address(vat));
217 | uint256 result = r.pRpow(uint256(1000234891009084238901289093), uint256(3724), uint256(1e27));
218 | // python calc = 2.397991232255757e27 = 2397991232255757e12
219 | // expect 10 decimal precision
220 | assertEq(result / uint256(1e17), uint256(2397991232255757e12) / 1e17);
221 | }
222 | }
223 |
--------------------------------------------------------------------------------
/contracts/cdpmanager.sol:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: AGPL-3.0-or-later
2 | pragma solidity ^0.8.13;
3 | // This program is free software: you can redistribute it and/or modify
4 | // it under the terms of the GNU Affero General Public License as published by
5 | // the Free Software Foundation, either version 3 of the License, or
6 | // (at your option) any later version.
7 | //
8 | // This program is distributed in the hope that it will be useful,
9 | // but WITHOUT ANY WARRANTY; without even the implied warranty of
10 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 | // GNU Affero General Public License for more details.
12 | //
13 | // You should have received a copy of the GNU Affero General Public License
14 | // along with this program. If not, see .
15 |
16 |
17 | interface VatLike {
18 | function urns(bytes32, address) external view returns (uint256, uint256);
19 | function hope(address) external;
20 | function flux(bytes32, address, address, uint256) external;
21 | function move(address, address, uint256) external;
22 | function frob(bytes32, address, address, address, int256, int256) external;
23 | function fork(bytes32, address, address, int256, int256) external;
24 | }
25 |
26 | contract UrnHandler {
27 | constructor(address vat) public {
28 | VatLike(vat).hope(msg.sender);
29 | }
30 | }
31 |
32 | contract CdpManager {
33 | address public vat;
34 | uint256 public cdpi; // Auto incremental
35 | mapping(uint256 => address) public urns; // CDPId => UrnHandler
36 | mapping(uint256 => List) public list; // CDPId => Prev & Next CDPIds (double linked list)
37 | mapping(uint256 => address) public owns; // CDPId => Owner
38 | mapping(uint256 => bytes32) public ilks; // CDPId => Ilk
39 |
40 | mapping(address => uint256) public first; // Owner => First CDPId
41 | mapping(address => uint256) public last; // Owner => Last CDPId
42 | mapping(address => uint256) public count; // Owner => Amount of CDPs
43 |
44 | mapping(address => mapping(uint256 => mapping(address => uint256))) public cdpCan; // Owner => CDPId => Allowed Addr => True/False
45 |
46 | mapping(address => mapping(address => uint256)) public urnCan; // Urn => Allowed Addr => True/False
47 |
48 | struct List {
49 | uint256 prev;
50 | uint256 next;
51 | }
52 |
53 | event NewCdp(address indexed usr, address indexed own, uint256 indexed cdp);
54 |
55 | modifier cdpAllowed(uint256 cdp) {
56 | require(msg.sender == owns[cdp] || cdpCan[owns[cdp]][cdp][msg.sender] == 1, "cdp-not-allowed");
57 | _;
58 | }
59 |
60 | modifier urnAllowed(address urn) {
61 | require(msg.sender == urn || urnCan[urn][msg.sender] == 1, "urn-not-allowed");
62 | _;
63 | }
64 |
65 | constructor(address vat_) public {
66 | vat = vat_;
67 | }
68 |
69 | function toInt256(uint256 x) internal pure returns (int256 y) {
70 | y = int256(x);
71 | require(y >= 0);
72 | }
73 |
74 | // Allow/disallow a usr address to manage the cdp.
75 | function cdpAllow(uint256 cdp, address usr, uint256 ok) public cdpAllowed(cdp) {
76 | cdpCan[owns[cdp]][cdp][usr] = ok;
77 | }
78 |
79 | // Allow/disallow a usr address to quit to the the sender urn.
80 | function urnAllow(address usr, uint256 ok) public {
81 | urnCan[msg.sender][usr] = ok;
82 | }
83 |
84 | // Open a new cdp for a given usr address.
85 | function open(bytes32 ilk, address usr) public returns (uint256) {
86 | require(usr != address(0), "usr-address-0");
87 |
88 | cdpi = cdpi + 1;
89 | urns[cdpi] = address(new UrnHandler(vat));
90 | owns[cdpi] = usr;
91 | ilks[cdpi] = ilk;
92 |
93 | // Add new CDP to double linked list and pointers
94 | if (first[usr] == 0) {
95 | first[usr] = cdpi;
96 | }
97 | if (last[usr] != 0) {
98 | list[cdpi].prev = last[usr];
99 | list[last[usr]].next = cdpi;
100 | }
101 | last[usr] = cdpi;
102 | count[usr] = count[usr] + 1;
103 |
104 | emit NewCdp(msg.sender, usr, cdpi);
105 | return cdpi;
106 | }
107 |
108 | // Give the cdp ownership to a dst address.
109 | function give(uint256 cdp, address dst) public cdpAllowed(cdp) {
110 | require(dst != address(0), "dst-address-0");
111 | require(dst != owns[cdp], "dst-already-owner");
112 |
113 | // Remove transferred CDP from double linked list of origin user and pointers
114 | if (list[cdp].prev != 0) {
115 | list[list[cdp].prev].next = list[cdp].next; // Set the next pointer of the prev cdp (if exists) to the next of the transferred one
116 | }
117 | if (list[cdp].next != 0) {
118 | // If wasn't the last one
119 | list[list[cdp].next].prev = list[cdp].prev; // Set the prev pointer of the next cdp to the prev of the transferred one
120 | } else {
121 | // If was the last one
122 | last[owns[cdp]] = list[cdp].prev; // Update last pointer of the owner
123 | }
124 | if (first[owns[cdp]] == cdp) {
125 | // If was the first one
126 | first[owns[cdp]] = list[cdp].next; // Update first pointer of the owner
127 | }
128 | count[owns[cdp]] = count[owns[cdp]] - 1;
129 |
130 | // Transfer ownership
131 | owns[cdp] = dst;
132 |
133 | // Add transferred CDP to double linked list of destiny user and pointers
134 | list[cdp].prev = last[dst];
135 | list[cdp].next = 0;
136 | if (last[dst] != 0) {
137 | list[last[dst]].next = cdp;
138 | }
139 | if (first[dst] == 0) {
140 | first[dst] = cdp;
141 | }
142 | last[dst] = cdp;
143 | count[dst] = count[dst] + 1;
144 | }
145 |
146 | // Frob the cdp keeping the generated ZAR or collateral freed in the cdp urn address.
147 | function frob(uint256 cdp, int256 dink, int256 dart) public cdpAllowed(cdp) {
148 | address urn = urns[cdp];
149 | VatLike(vat).frob(ilks[cdp], urn, urn, urn, dink, dart);
150 | }
151 |
152 | // Transfer wad amount of cdp collateral from the cdp address to a dst address.
153 | function flux(uint256 cdp, address dst, uint256 wad) public cdpAllowed(cdp) {
154 | VatLike(vat).flux(ilks[cdp], urns[cdp], dst, wad);
155 | }
156 |
157 | // Transfer wad amount of any type of collateral (ilk) from the cdp address to a dst address.
158 | // This function has the purpose to take away collateral from the system that doesn't correspond to the cdp but was sent there wrongly.
159 | function flux(bytes32 ilk, uint256 cdp, address dst, uint256 wad) public cdpAllowed(cdp) {
160 | VatLike(vat).flux(ilk, urns[cdp], dst, wad);
161 | }
162 |
163 | // Transfer wad amount of ZAR from the cdp address to a dst address.
164 | function move(uint256 cdp, address dst, uint256 rad) public cdpAllowed(cdp) {
165 | VatLike(vat).move(urns[cdp], dst, rad);
166 | }
167 |
168 | // Quit the system, migrating the cdp (ink, art) to a different dst urn
169 | function quit(uint256 cdp, address dst) public cdpAllowed(cdp) urnAllowed(dst) {
170 | (uint256 ink, uint256 art) = VatLike(vat).urns(ilks[cdp], urns[cdp]);
171 | VatLike(vat).fork(ilks[cdp], urns[cdp], dst, toInt256(ink), toInt256(art));
172 | }
173 |
174 | // Import a position from src urn to the urn owned by cdp
175 | function enter(address src, uint256 cdp) public urnAllowed(src) cdpAllowed(cdp) {
176 | (uint256 ink, uint256 art) = VatLike(vat).urns(ilks[cdp], src);
177 | VatLike(vat).fork(ilks[cdp], src, urns[cdp], toInt256(ink), toInt256(art));
178 | }
179 |
180 | // Move a position from cdpSrc urn to the cdpDst urn
181 | function shift(uint256 cdpSrc, uint256 cdpDst) public cdpAllowed(cdpSrc) cdpAllowed(cdpDst) {
182 | require(ilks[cdpSrc] == ilks[cdpDst], "non-matching-cdps");
183 | (uint256 ink, uint256 art) = VatLike(vat).urns(ilks[cdpSrc], urns[cdpSrc]);
184 | VatLike(vat).fork(ilks[cdpSrc], urns[cdpSrc], urns[cdpDst], toInt256(ink), toInt256(art));
185 | }
186 | }
187 |
--------------------------------------------------------------------------------
/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 Zar 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.8.13;
21 |
22 | import "ds-test/test.sol";
23 |
24 | import "contracts/system/abaci.sol";
25 |
26 | interface Hevm {
27 | function warp(uint256) external;
28 |
29 | function store(address, bytes32, bytes32) external;
30 | }
31 |
32 | contract ClipperTest is DSTest {
33 | Hevm hevm;
34 |
35 | uint256 RAY = 10 ** 27;
36 |
37 | // CHEAT_CODE = 0x7109709ECfa91a80626fF3989D68f67F5b1DD12D
38 | bytes20 constant CHEAT_CODE = 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(uint256 x, uint256 y, uint256 tolerance) internal {
48 | uint256 diff;
49 | if (x >= y) {
50 | diff = x - y;
51 | } else {
52 | diff = y - x;
53 | }
54 | assertTrue(diff <= tolerance);
55 | }
56 |
57 | function checkExpDecrease(
58 | StairstepExponentialDecrease calc,
59 | uint256 cut,
60 | uint256 step,
61 | uint256 top,
62 | uint256 tic,
63 | uint256 percentDecrease,
64 | uint256 testTime,
65 | uint256 tolerance
66 | ) public {
67 | uint256 price;
68 | uint256 lastPrice;
69 | uint256 testPrice;
70 |
71 | hevm.warp(startTime);
72 | calc.file(bytes32("step"), step);
73 | calc.file(bytes32("cut"), cut);
74 | price = calc.price(top, block.timestamp - tic);
75 | assertEq(price, top);
76 |
77 | for (uint256 i = 1; i < testTime; i += 1) {
78 | hevm.warp(startTime + i);
79 | lastPrice = price;
80 | price = calc.price(top, block.timestamp - tic);
81 | // Stairstep calculation
82 | if (i % step == 0) {
83 | testPrice = (lastPrice * percentDecrease) / RAY;
84 | } else {
85 | testPrice = lastPrice;
86 | }
87 | assertEqWithinTolerance(testPrice, price, tolerance);
88 | }
89 | }
90 |
91 | function test_stairstep_exp_decrease() public {
92 | StairstepExponentialDecrease calc = new StairstepExponentialDecrease();
93 | uint256 tic = block.timestamp; // Start of auction
94 | uint256 percentDecrease;
95 | uint256 step;
96 | uint256 testTime = 10 minutes;
97 |
98 | /**
99 | * Extreme high collateral price ($50m) **
100 | */
101 |
102 | uint256 tolerance = 100000000; // Tolerance scales with price
103 | uint256 top = 50000000 * RAY;
104 |
105 | // 1.1234567890% decrease every 1 second
106 | // TODO: Check if there's a cleaner way to do this. I was getting rational_const errors.
107 | percentDecrease = RAY - 1.123456789e27 / 100;
108 | step = 1;
109 | checkExpDecrease(calc, percentDecrease, step, top, tic, percentDecrease, testTime, tolerance);
110 |
111 | // 2.1234567890% decrease every 1 second
112 | percentDecrease = RAY - 2.123456789e27 / 100;
113 | step = 1;
114 | checkExpDecrease(calc, percentDecrease, step, top, tic, percentDecrease, testTime, tolerance);
115 |
116 | // 1.1234567890% decrease every 5 seconds
117 | percentDecrease = RAY - 1.123456789e27 / 100;
118 | step = 5;
119 | checkExpDecrease(calc, percentDecrease, step, top, tic, percentDecrease, testTime, tolerance);
120 |
121 | // 2.1234567890% decrease every 5 seconds
122 | percentDecrease = RAY - 2.123456789e27 / 100;
123 | step = 5;
124 | checkExpDecrease(calc, percentDecrease, step, top, tic, percentDecrease, testTime, tolerance);
125 |
126 | // 1.1234567890% decrease every 5 minutes
127 | percentDecrease = RAY - 1.123456789e27 / 100;
128 | step = 5 minutes;
129 | checkExpDecrease(calc, percentDecrease, step, top, tic, percentDecrease, testTime, tolerance);
130 |
131 | /**
132 | * Extreme low collateral price ($0.0000001) **
133 | */
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.123456789e27 / 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.123456789e27 / 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.123456789e27 / 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.123456789e27 / 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.123456789e27 / 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++) {
174 | // will cover initial value + four half-lives
175 | assertEqWithinTolerance(calc.price(top, i * tHalf), expectedPrice, tolerance);
176 | // each loop iteration advances one half-life, so expectedPrice decreases by a factor of 2
177 | expectedPrice /= 2;
178 | }
179 | }
180 |
181 | function test_linear_decrease() public {
182 | hevm.warp(startTime);
183 | LinearDecrease calc = new LinearDecrease();
184 | calc.file(bytes32("tau"), 3600);
185 |
186 | uint256 top = 1000 * RAY;
187 | uint256 tic = block.timestamp; // Start of auction
188 | uint256 price = calc.price(top, block.timestamp - tic);
189 | assertEq(price, top);
190 |
191 | hevm.warp(startTime + 360); // 6min in, 1/10 done
192 | price = calc.price(top, block.timestamp - tic);
193 | assertEq(price, (1000 - 100) * RAY);
194 |
195 | hevm.warp(startTime + 360 * 2); // 12min in, 2/10 done
196 | price = calc.price(top, block.timestamp - tic);
197 | assertEq(price, (1000 - 100 * 2) * RAY);
198 |
199 | hevm.warp(startTime + 360 * 3); // 18min in, 3/10 done
200 | price = calc.price(top, block.timestamp - tic);
201 | assertEq(price, (1000 - 100 * 3) * RAY);
202 |
203 | hevm.warp(startTime + 360 * 4); // 24min in, 4/10 done
204 | price = calc.price(top, block.timestamp - tic);
205 | assertEq(price, (1000 - 100 * 4) * RAY);
206 |
207 | hevm.warp(startTime + 360 * 5); // 30min in, 5/10 done
208 | price = calc.price(top, block.timestamp - tic);
209 | assertEq(price, (1000 - 100 * 5) * RAY);
210 |
211 | hevm.warp(startTime + 360 * 6); // 36min in, 6/10 done
212 | price = calc.price(top, block.timestamp - tic);
213 | assertEq(price, (1000 - 100 * 6) * RAY);
214 |
215 | hevm.warp(startTime + 360 * 7); // 42min in, 7/10 done
216 | price = calc.price(top, block.timestamp - tic);
217 | assertEq(price, (1000 - 100 * 7) * RAY);
218 |
219 | hevm.warp(startTime + 360 * 8); // 48min in, 8/10 done
220 | price = calc.price(top, block.timestamp - tic);
221 | assertEq(price, (1000 - 100 * 8) * RAY);
222 |
223 | hevm.warp(startTime + 360 * 9); // 54min in, 9/10 done
224 | price = calc.price(top, block.timestamp - tic);
225 | assertEq(price, (1000 - 100 * 9) * RAY);
226 |
227 | hevm.warp(startTime + 360 * 10); // 60min in, 10/10 done
228 | price = calc.price(top, block.timestamp - tic);
229 | assertEq(price, 0);
230 | }
231 | }
232 |
--------------------------------------------------------------------------------
/contracts/system/zar.sol:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: AGPL-3.0-or-later
2 | pragma solidity ^0.8.13;
3 | // This program is free software: you can redistribute it and/or modify
4 | // it under the terms of the GNU Affero General Public License as published by
5 | // the Free Software Foundation, either version 3 of the License, or
6 | // (at your option) any later version.
7 | //
8 | // This program is distributed in the hope that it will be useful,
9 | // but WITHOUT ANY WARRANTY; without even the implied warranty of
10 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 | // GNU Affero General Public License for more details.
12 | //
13 | // You should have received a copy of the GNU Affero General Public License
14 | // along with this program. If not, see .
15 |
16 | interface IERC1271 {
17 | function isValidSignature(bytes32, bytes memory) external view returns (bytes4);
18 | }
19 |
20 | contract Zar {
21 | mapping(address => uint256) public wards;
22 |
23 | // --- ERC20 Data ---
24 | string public constant name = "Zar Stablecoin";
25 | string public constant symbol = "ZAR";
26 | string public constant version = "1";
27 | uint8 public constant decimals = 18;
28 | uint256 public totalSupply;
29 |
30 | mapping(address => uint256) public balanceOf;
31 | mapping(address => mapping(address => uint256)) public allowance;
32 | mapping(address => uint256) public nonces;
33 |
34 | // --- Events ---
35 | event Rely(address indexed usr);
36 | event Deny(address indexed usr);
37 | event Approval(address indexed owner, address indexed spender, uint256 value);
38 | event Transfer(address indexed from, address indexed to, uint256 value);
39 |
40 | // --- EIP712 niceties ---
41 | uint256 public immutable deploymentChainId;
42 | bytes32 private immutable _DOMAIN_SEPARATOR;
43 | bytes32 public constant PERMIT_TYPEHASH =
44 | keccak256("Permit(address owner,address spender,uint256 value,uint256 nonce,uint256 deadline)");
45 |
46 | modifier auth() {
47 | require(wards[msg.sender] == 1, "Zar/not-authorized");
48 | _;
49 | }
50 |
51 | constructor() {
52 | wards[msg.sender] = 1;
53 | emit Rely(msg.sender);
54 |
55 | deploymentChainId = block.chainid;
56 | _DOMAIN_SEPARATOR = _calculateDomainSeparator(block.chainid);
57 | }
58 |
59 | function _calculateDomainSeparator(uint256 chainId) private view returns (bytes32) {
60 | return keccak256(
61 | abi.encode(
62 | keccak256("EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)"),
63 | keccak256(bytes(name)),
64 | keccak256(bytes(version)),
65 | chainId,
66 | address(this)
67 | )
68 | );
69 | }
70 |
71 | function DOMAIN_SEPARATOR() external view returns (bytes32) {
72 | return block.chainid == deploymentChainId ? _DOMAIN_SEPARATOR : _calculateDomainSeparator(block.chainid);
73 | }
74 |
75 | // --- Administration ---
76 | function rely(address usr) external auth {
77 | wards[usr] = 1;
78 | emit Rely(usr);
79 | }
80 |
81 | function deny(address usr) external auth {
82 | wards[usr] = 0;
83 | emit Deny(usr);
84 | }
85 |
86 | // --- ERC20 Mutations ---
87 | function transfer(address to, uint256 value) external returns (bool) {
88 | require(to != address(0) && to != address(this), "Zar/invalid-address");
89 | uint256 balance = balanceOf[msg.sender];
90 | require(balance >= value, "Zar/insufficient-balance");
91 |
92 | unchecked {
93 | balanceOf[msg.sender] = balance - value;
94 | balanceOf[to] += value;
95 | }
96 |
97 | emit Transfer(msg.sender, to, value);
98 |
99 | return true;
100 | }
101 |
102 | function transferFrom(address from, address to, uint256 value) external returns (bool) {
103 | require(to != address(0) && to != address(this), "Zar/invalid-address");
104 | uint256 balance = balanceOf[from];
105 | require(balance >= value, "Zar/insufficient-balance");
106 |
107 | if (from != msg.sender) {
108 | uint256 allowed = allowance[from][msg.sender];
109 | if (allowed != type(uint256).max) {
110 | require(allowed >= value, "Zar/insufficient-allowance");
111 |
112 | unchecked {
113 | allowance[from][msg.sender] = allowed - value;
114 | }
115 | }
116 | }
117 |
118 | unchecked {
119 | balanceOf[from] = balance - value;
120 | balanceOf[to] += value;
121 | }
122 |
123 | emit Transfer(from, to, value);
124 |
125 | return true;
126 | }
127 |
128 | function approve(address spender, uint256 value) external returns (bool) {
129 | allowance[msg.sender][spender] = value;
130 |
131 | emit Approval(msg.sender, spender, value);
132 |
133 | return true;
134 | }
135 |
136 | function increaseAllowance(address spender, uint256 addedValue) external returns (bool) {
137 | uint256 newValue = allowance[msg.sender][spender] + addedValue;
138 | allowance[msg.sender][spender] = newValue;
139 |
140 | emit Approval(msg.sender, spender, newValue);
141 |
142 | return true;
143 | }
144 |
145 | function decreaseAllowance(address spender, uint256 subtractedValue) external returns (bool) {
146 | uint256 allowed = allowance[msg.sender][spender];
147 | require(allowed >= subtractedValue, "Zar/insufficient-allowance");
148 | unchecked {
149 | allowed = allowed - subtractedValue;
150 | }
151 | allowance[msg.sender][spender] = allowed;
152 |
153 | emit Approval(msg.sender, spender, allowed);
154 |
155 | return true;
156 | }
157 |
158 | // --- Mint/Burn ---
159 | function mint(address to, uint256 value) external auth {
160 | require(to != address(0) && to != address(this), "Zar/invalid-address");
161 | unchecked {
162 | balanceOf[to] = balanceOf[to] + value; // note: we don't need an overflow check here b/c balanceOf[to] <= totalSupply and there is an overflow check below
163 | }
164 | totalSupply = totalSupply + value;
165 |
166 | emit Transfer(address(0), to, value);
167 | }
168 |
169 | function burn(address from, uint256 value) external {
170 | uint256 balance = balanceOf[from];
171 | require(balance >= value, "Zar/insufficient-balance");
172 |
173 | if (from != msg.sender) {
174 | uint256 allowed = allowance[from][msg.sender];
175 | if (allowed != type(uint256).max) {
176 | require(allowed >= value, "Zar/insufficient-allowance");
177 |
178 | unchecked {
179 | allowance[from][msg.sender] = allowed - value;
180 | }
181 | }
182 | }
183 |
184 | unchecked {
185 | balanceOf[from] = balance - value; // note: we don't need overflow checks b/c require(balance >= value) and balance <= totalSupply
186 | totalSupply = totalSupply - value;
187 | }
188 |
189 | emit Transfer(from, address(0), value);
190 | }
191 |
192 | // --- Approve by signature ---
193 |
194 | function _isValidSignature(address signer, bytes32 digest, bytes memory signature) internal view returns (bool) {
195 | if (signature.length == 65) {
196 | bytes32 r;
197 | bytes32 s;
198 | uint8 v;
199 | assembly {
200 | r := mload(add(signature, 0x20))
201 | s := mload(add(signature, 0x40))
202 | v := byte(0, mload(add(signature, 0x60)))
203 | }
204 | if (signer == ecrecover(digest, v, r, s)) {
205 | return true;
206 | }
207 | }
208 |
209 | (bool success, bytes memory result) =
210 | signer.staticcall(abi.encodeWithSelector(IERC1271.isValidSignature.selector, digest, signature));
211 | return (success && result.length == 32 && abi.decode(result, (bytes4)) == IERC1271.isValidSignature.selector);
212 | }
213 |
214 | function permit(address owner, address spender, uint256 value, uint256 deadline, bytes memory signature) public {
215 | require(block.timestamp <= deadline, "Zar/permit-expired");
216 | require(owner != address(0), "Zar/invalid-owner");
217 |
218 | uint256 nonce;
219 | unchecked {
220 | nonce = nonces[owner]++;
221 | }
222 |
223 | bytes32 digest = keccak256(
224 | abi.encodePacked(
225 | "\x19\x01",
226 | block.chainid == deploymentChainId ? _DOMAIN_SEPARATOR : _calculateDomainSeparator(block.chainid),
227 | keccak256(abi.encode(PERMIT_TYPEHASH, owner, spender, value, nonce, deadline))
228 | )
229 | );
230 |
231 | require(_isValidSignature(owner, digest, signature), "Zar/invalid-permit");
232 |
233 | allowance[owner][spender] = value;
234 | emit Approval(owner, spender, value);
235 | }
236 |
237 | function permit(address owner, address spender, uint256 value, uint256 deadline, uint8 v, bytes32 r, bytes32 s)
238 | external
239 | {
240 | permit(owner, spender, value, deadline, abi.encodePacked(r, s, v));
241 | }
242 | }
243 |
--------------------------------------------------------------------------------
/contracts/system/dog.sol:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: AGPL-3.0-or-later
2 | pragma solidity ^0.8.13;
3 | // This program is free software: you can redistribute it and/or modify
4 | // it under the terms of the GNU Affero General Public License as published by
5 | // the Free Software Foundation, either version 3 of the License, or
6 | // (at your option) any later version.
7 | //
8 | // This program is distributed in the hope that it will be useful,
9 | // but WITHOUT ANY WARRANTY; without even the implied warranty of
10 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 | // GNU Affero General Public License for more details.
12 | //
13 | // You should have received a copy of the GNU Affero General Public License
14 | // along with this program. If not, see .
15 |
16 |
17 | interface ClipperLike {
18 | function ilk() external view returns (bytes32);
19 |
20 | function kick(uint256 tab, uint256 lot, address usr, address kpr) external returns (uint256);
21 | }
22 |
23 | interface VatLike {
24 | function ilks(bytes32)
25 | external
26 | view
27 | returns (
28 | uint256 Art, // [wad]
29 | uint256 rate, // [ray]
30 | uint256 spot, // [ray]
31 | uint256 line, // [rad]
32 | uint256 dust
33 | ); // [rad]
34 |
35 | function urns(bytes32, address)
36 | external
37 | view
38 | returns (
39 | uint256 ink, // [wad]
40 | uint256 art
41 | ); // [wad]
42 |
43 | function grab(bytes32, address, address, address, int256, int256) external;
44 |
45 | function hope(address) external;
46 |
47 | function nope(address) external;
48 | }
49 |
50 | interface VowLike {
51 | function fess(uint256) external;
52 | }
53 |
54 | contract Dog {
55 | // --- Auth ---
56 | mapping(address => uint256) public wards;
57 |
58 | function rely(address usr) external auth {
59 | wards[usr] = 1;
60 | emit Rely(usr);
61 | }
62 |
63 | function deny(address usr) external auth {
64 | wards[usr] = 0;
65 | emit Deny(usr);
66 | }
67 |
68 | modifier auth() {
69 | require(wards[msg.sender] == 1, "Dog/not-authorized");
70 | _;
71 | }
72 |
73 | // --- Data ---
74 | struct Ilk {
75 | address clip; // Liquidator
76 | uint256 chop; // Liquidation Penalty [wad]
77 | uint256 hole; // Max ZAR needed to cover debt+fees of active auctions per ilk [rad]
78 | uint256 dirt; // Amt ZAR needed to cover debt+fees of active auctions per ilk [rad]
79 | }
80 |
81 | VatLike public immutable vat; // CDP Engine
82 |
83 | mapping(bytes32 => Ilk) public ilks;
84 |
85 | VowLike public vow; // Debt Engine
86 | uint256 public live; // Active Flag
87 | uint256 public Hole; // Max ZAR needed to cover debt+fees of active auctions [rad]
88 | uint256 public Dirt; // Amt ZAR needed to cover debt+fees of active auctions [rad]
89 |
90 | // --- Events ---
91 | event Rely(address indexed usr);
92 | event Deny(address indexed usr);
93 |
94 | event File(bytes32 indexed what, uint256 data);
95 | event File(bytes32 indexed what, address data);
96 | event File(bytes32 indexed ilk, bytes32 indexed what, uint256 data);
97 | event File(bytes32 indexed ilk, bytes32 indexed what, address clip);
98 |
99 | event Bark(
100 | bytes32 indexed ilk,
101 | address indexed urn,
102 | uint256 ink,
103 | uint256 art,
104 | uint256 due,
105 | address clip,
106 | uint256 indexed id
107 | );
108 | event Digs(bytes32 indexed ilk, uint256 rad);
109 | event Cage();
110 |
111 | // --- Init ---
112 | constructor(address vat_) public {
113 | vat = VatLike(vat_);
114 | live = 1;
115 | wards[msg.sender] = 1;
116 | emit Rely(msg.sender);
117 | }
118 |
119 | // --- Math ---
120 | uint256 constant WAD = 10 ** 18;
121 |
122 | function min(uint256 x, uint256 y) internal pure returns (uint256 z) {
123 | z = x <= y ? x : y;
124 | }
125 |
126 | function mul(uint256 x, uint256 y) internal pure returns (uint256 z) {
127 | require(y == 0 || (z = x * y) / y == x);
128 | }
129 |
130 | // --- Administration ---
131 | function file(bytes32 what, address data) external auth {
132 | if (what == "vow") vow = VowLike(data);
133 | else revert("Dog/file-unrecognized-param");
134 | emit File(what, data);
135 | }
136 |
137 | function file(bytes32 what, uint256 data) external auth {
138 | if (what == "Hole") Hole = data;
139 | else revert("Dog/file-unrecognized-param");
140 | emit File(what, data);
141 | }
142 |
143 | function file(bytes32 ilk, bytes32 what, uint256 data) external auth {
144 | if (what == "chop") {
145 | require(data >= WAD, "Dog/file-chop-lt-WAD");
146 | ilks[ilk].chop = data;
147 | } else if (what == "hole") {
148 | ilks[ilk].hole = data;
149 | } else {
150 | revert("Dog/file-unrecognized-param");
151 | }
152 | emit File(ilk, what, data);
153 | }
154 |
155 | function file(bytes32 ilk, bytes32 what, address clip) external auth {
156 | if (what == "clip") {
157 | require(ilk == ClipperLike(clip).ilk(), "Dog/file-ilk-neq-clip.ilk");
158 | ilks[ilk].clip = clip;
159 | } else {
160 | revert("Dog/file-unrecognized-param");
161 | }
162 | emit File(ilk, what, clip);
163 | }
164 |
165 | function chop(bytes32 ilk) external view returns (uint256) {
166 | return ilks[ilk].chop;
167 | }
168 |
169 | // --- CDP Liquidation: all bark and no bite ---
170 | //
171 | // Liquidate a Vault and start a Dutch auction to sell its collateral for ZAR.
172 | //
173 | // The third argument is the address that will receive the liquidation reward, if any.
174 | //
175 | // The entire Vault will be liquidated except when the target amount of ZAR to be raised in
176 | // the resulting auction (debt of Vault + liquidation penalty) causes either Dirt to exceed
177 | // Hole or ilk.dirt to exceed ilk.hole by an economically significant amount. In that
178 | // case, a partial liquidation is performed to respect the global and per-ilk limits on
179 | // outstanding ZAR target. The one exception is if the resulting auction would likely
180 | // have too little collateral to be interesting to Keepers (debt taken from Vault < ilk.dust),
181 | // in which case the function reverts. Please refer to the code and comments within if
182 | // more detail is desired.
183 | function bark(bytes32 ilk, address urn, address kpr) external returns (uint256 id) {
184 | require(live == 1, "Dog/not-live");
185 |
186 | (uint256 ink, uint256 art) = vat.urns(ilk, urn);
187 | Ilk memory milk = ilks[ilk];
188 | uint256 dart;
189 | uint256 rate;
190 | uint256 dust;
191 | {
192 | uint256 spot;
193 | (, rate, spot,, dust) = vat.ilks(ilk);
194 | require(spot > 0 && mul(ink, spot) < mul(art, rate), "Dog/not-unsafe");
195 |
196 | // Get the minimum value between:
197 | // 1) Remaining space in the general Hole
198 | // 2) Remaining space in the collateral hole
199 | require(Hole > Dirt && milk.hole > milk.dirt, "Dog/liquidation-limit-hit");
200 | uint256 room = min(Hole - Dirt, milk.hole - milk.dirt);
201 |
202 | // uint256.max()/(RAD*WAD) = 115,792,089,237,316
203 | dart = min(art, mul(room, WAD) / rate / milk.chop);
204 |
205 | // Partial liquidation edge case logic
206 | if (art > dart) {
207 | if (mul(art - dart, rate) < dust) {
208 | // If the leftover Vault would be dusty, just liquidate it entirely.
209 | // This will result in at least one of dirt_i > hole_i or Dirt > Hole becoming true.
210 | // The amount of excess will be bounded above by ceiling(dust_i * chop_i / WAD).
211 | // This deviation is assumed to be small compared to both hole_i and Hole, so that
212 | // the extra amount of target ZAR over the limits intended is not of economic concern.
213 | dart = art;
214 | } else {
215 | // In a partial liquidation, the resulting auction should also be non-dusty.
216 | require(mul(dart, rate) >= dust, "Dog/dusty-auction-from-partial-liquidation");
217 | }
218 | }
219 | }
220 |
221 | uint256 dink = mul(ink, dart) / art;
222 |
223 | require(dink > 0, "Dog/null-auction");
224 | require(dart <= 2 ** 255 && dink <= 2 ** 255, "Dog/overflow");
225 |
226 | vat.grab(ilk, urn, milk.clip, address(vow), -int256(dink), -int256(dart));
227 |
228 | uint256 due = mul(dart, rate);
229 | vow.fess(due);
230 |
231 | {
232 | // Avoid stack too deep
233 | // This calcuation will overflow if dart*rate exceeds ~10^14
234 | uint256 tab = mul(due, milk.chop) / WAD;
235 | Dirt = Dirt + tab;
236 | ilks[ilk].dirt = milk.dirt + tab;
237 |
238 | id = ClipperLike(milk.clip).kick({tab: tab, lot: dink, usr: urn, kpr: kpr});
239 | }
240 |
241 | emit Bark(ilk, urn, dink, dart, due, milk.clip, id);
242 | }
243 |
244 | function digs(bytes32 ilk, uint256 rad) external auth {
245 | Dirt = Dirt - rad;
246 | ilks[ilk].dirt = ilks[ilk].dirt - rad;
247 | emit Digs(ilk, rad);
248 | }
249 |
250 | function cage() external auth {
251 | live = 0;
252 | emit Cage();
253 | }
254 | }
255 |
--------------------------------------------------------------------------------
/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.8.13;
19 |
20 | import "ds-test/test.sol";
21 | import "dss-test/DssTest.sol";
22 |
23 | import {Vow} from "contracts/system/vow.sol";
24 | import {Vat} from "contracts/system/vat.sol";
25 |
26 | interface Hevm {
27 | function warp(uint256) external;
28 | }
29 |
30 | contract Gem {
31 | mapping(address => uint256) public balanceOf;
32 |
33 | function mint(address usr, uint256 rad) public {
34 | balanceOf[usr] += rad;
35 | }
36 | }
37 |
38 | contract Collector {}
39 |
40 | contract VowTest is DSTest, DssTest {
41 | Hevm hevm;
42 |
43 | Vat vat;
44 | Vow vow;
45 | Gem gov;
46 | Collector collector;
47 |
48 | function setUp() public {
49 | hevm = Hevm(0x7109709ECfa91a80626fF3989D68f67F5b1DD12D);
50 | hevm.warp(604411200);
51 |
52 | collector = new Collector();
53 | vat = new Vat();
54 | gov = new Gem();
55 | vow = new Vow(address(vat));
56 |
57 | vow.file("bump", rad(100 ether));
58 | vow.file("sump", rad(100 ether));
59 | vow.file("collector", address(collector));
60 |
61 | vat.hope(address(vow));
62 | }
63 |
64 | function try_flog(uint256 era) internal returns (bool ok) {
65 | string memory sig = "flog(uint256)";
66 | (ok,) = address(vow).call(abi.encodeWithSignature(sig, era));
67 | }
68 |
69 | function try_call(address addr, bytes calldata data) external returns (bool) {
70 | bytes memory _data = data;
71 | assembly {
72 | let ok := call(gas(), addr, 0, add(_data, 0x20), mload(_data), 0, 0)
73 | let free := mload(0x40)
74 | mstore(free, ok)
75 | mstore(0x40, add(free, 32))
76 | revert(free, 32)
77 | }
78 | }
79 |
80 | function can_flap() public returns (bool) {
81 | string memory sig = "flap()";
82 | bytes memory data = abi.encodeWithSignature(sig);
83 |
84 | bytes memory can_call = abi.encodeWithSignature("try_call(address,bytes)", vow, data);
85 | (bool ok, bytes memory success) = address(this).call(can_call);
86 |
87 | ok = abi.decode(success, (bool));
88 | if (ok) return true;
89 | }
90 |
91 | function can_flop(uint256 rad) public returns (bool) {
92 | string memory sig = "flop(uint256)";
93 | bytes memory data = abi.encodeWithSignature(sig, rad);
94 |
95 | bytes memory can_call = abi.encodeWithSignature("try_call(address,bytes)", vow, data);
96 | (bool ok, bytes memory success) = address(this).call(can_call);
97 |
98 | ok = abi.decode(success, (bool));
99 | if (ok) return true;
100 | }
101 |
102 | uint256 constant ONE = 10 ** 27;
103 |
104 | function rad(uint256 wad) internal pure returns (uint256) {
105 | return wad * ONE;
106 | }
107 |
108 | function suck(address who, uint256 wad) internal {
109 | vow.fess(rad(wad));
110 | vat.init("");
111 | vat.suck(address(vow), who, rad(wad));
112 | }
113 |
114 | function flog(uint256 wad) internal {
115 | suck(address(0), wad); // suck dai into the zero address
116 | vow.flog(block.timestamp);
117 | }
118 |
119 | function heal(uint256 wad) internal {
120 | vow.heal(rad(wad));
121 | }
122 |
123 | function test_fail_auth() public {
124 | vow.deny(address(this));
125 | vm.expectRevert("Vow/not-authorized");
126 | vow.file("wait", uint256(100 seconds));
127 | }
128 |
129 | function test_fail_file() public {
130 | vm.expectRevert("Vow/file-unrecognized-param");
131 | vow.file("invalidName", uint256(1 ether));
132 | vm.expectRevert("Vow/file-unrecognized-param");
133 | vow.file("invalidName2", address(this));
134 | }
135 |
136 | function test_fail_rely_after_cage() public {
137 | vow.cage();
138 | vm.expectRevert("Vow/not-live");
139 | vow.rely(address(1));
140 | }
141 |
142 | function test_rely() public {
143 | vow.rely(address(1));
144 | vm.prank(address(1));
145 | vow.rely(address(2));
146 | }
147 |
148 | function test_deny() public {
149 | vow.deny(address(this));
150 | vm.expectRevert("Vow/not-authorized");
151 | vow.rely(address(this));
152 | }
153 |
154 | function test_cage_twice() public {
155 | vow.cage();
156 | vm.expectRevert("Vow/not-live");
157 | vow.cage();
158 | }
159 |
160 | function test_file_multiple() public {
161 | vow.file("bump", rad(100 ether));
162 | vow.file("collector", address(this));
163 | }
164 |
165 | function test_flog_wait() public {
166 | assertEq(vow.wait(), 0);
167 | vow.file("wait", uint256(100 seconds));
168 | assertEq(vow.wait(), 100 seconds);
169 |
170 | uint256 tic = block.timestamp;
171 | vow.fess(100 ether);
172 | hevm.warp(tic + 99 seconds);
173 | assertTrue(!try_flog(tic));
174 | hevm.warp(tic + 100 seconds);
175 | assertTrue(try_flog(tic));
176 | }
177 |
178 | function test_flap() public {
179 | vat.suck(address(0), address(vow), rad(100 ether));
180 | assertTrue(can_flap());
181 | }
182 |
183 | function test_no_reflop() public {
184 | uint256 mad = rad(100 ether);
185 | flog(100 ether);
186 | vat.suck(address(0), address(this), mad);
187 |
188 | assertTrue(can_flop(mad));
189 | vow.flop(mad);
190 | assertTrue(!can_flop(mad));
191 | }
192 |
193 | function test_no_flop_pending_surplus() public {
194 | flog(200 ether);
195 |
196 | vat.suck(address(0), address(vow), (rad(100 ether)));
197 | assertTrue(!can_flop(rad(100 ether)));
198 |
199 | heal(100 ether);
200 | vat.suck(address(0), address(this), (rad(100 ether)));
201 |
202 | assertTrue(can_flop(rad(100 ether)));
203 | }
204 |
205 | function test_no_flop_min_insufficient_flop_rad() public {
206 | assertTrue(!can_flop(rad(99 ether)));
207 | }
208 |
209 | function test_no_flap_pending_sin() public {
210 | vow.file("bump", uint256(0 ether));
211 | flog(100 ether);
212 |
213 | vat.suck(address(vow), address(this), (rad(50 ether)));
214 | assertTrue(!can_flap());
215 | }
216 |
217 | function test_no_flap_nonzero_surplus() public {
218 | vow.file("bump", uint256(0 ether));
219 | flog(100 ether);
220 | vat.suck(address(0), address(vow), (rad(50 ether)));
221 | assertTrue(!can_flap());
222 | }
223 |
224 | function test_no_flap_nonzero_surplus2() public {
225 | vow.file("bump", uint256(0 ether));
226 | vow.file("hump", uint256(50 ether));
227 | flog(100 ether);
228 | vat.suck(address(0), address(vow), (rad(151 ether)));
229 | assertTrue(!can_flap());
230 | }
231 |
232 | function test_no_flap_nonzero_debt() public {
233 | vow.file("bump", uint256(0 ether));
234 | vow.file("hump", uint256(50 ether));
235 | flog(100 ether);
236 | vat.suck(address(0), address(vow), (rad(150 ether)));
237 | assertTrue(!can_flap());
238 | }
239 |
240 | function test_no_flap_insufficient_rad() public {
241 | vat.suck(address(0), address(vow), (rad(50 ether)));
242 | assertTrue(!can_flop(50 ether));
243 | }
244 |
245 | function test_no_flap_insufficient_debt() public {
246 | vow.file("sump", uint256(10 ether));
247 | flog(100 ether);
248 | vow.fess(rad(10 ether));
249 | vat.suck(address(0), address(vow), (rad(100 ether)));
250 | assertTrue(!can_flop(rad(91 ether)));
251 | }
252 |
253 | function test_cage() public {
254 | flog(50 ether);
255 | vat.suck(address(0), address(vow), (rad(100 ether)));
256 | vow.cage();
257 | assertTrue(vat.zar(address(vow)) == rad(50 ether));
258 | }
259 |
260 | function test_cage2() public {
261 | flog(100 ether);
262 | vat.suck(address(0), address(vow), (rad(50 ether)));
263 | vow.cage();
264 | assertTrue(vat.sin(address(vow)) == rad(50 ether));
265 | }
266 |
267 | function test_cage3() public {
268 | flog(100 ether);
269 | vat.suck(address(0), address(vow), rad(100 ether));
270 | vow.cage();
271 | assertTrue(vat.sin(address(vow)) == 0);
272 | assertTrue(vat.zar(address(vow)) == 0);
273 | }
274 |
275 | function test_heal_insufficient_surplus() public {
276 | vat.suck(address(0), address(vow), (rad(100 ether)));
277 | assertEq(vat.zar(address(vow)), rad(100 ether));
278 | vm.expectRevert("Vow/insufficient-surplus");
279 | heal(101 ether);
280 | }
281 |
282 | function test_heal_insufficient_debt() public {
283 | flog(100 ether);
284 | vow.fess(rad(50 ether));
285 | vat.suck(address(0), address(vow), (rad(100 ether)));
286 |
287 | assertEq(vat.zar(address(vow)), rad(100 ether));
288 | assertEq(vow.Sin(), rad(50 ether));
289 | vm.expectRevert("Vow/insufficient-debt");
290 | heal(51 ether);
291 |
292 | heal(50 ether);
293 | }
294 |
295 | function test_heal_insufficient_debt2() public {
296 | flog(100 ether);
297 | vat.suck(address(0), address(vow), (rad(200 ether)));
298 |
299 | assertEq(vat.zar(address(vow)), rad(200 ether));
300 | assertEq(vow.Sin(), 0);
301 | vm.expectRevert("Vow/insufficient-debt");
302 | heal(101 ether);
303 | }
304 | }
305 |
--------------------------------------------------------------------------------
/contracts/deployment.sol:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: AGPL-3.0-or-later
2 | pragma solidity ^0.8.13;
3 | // This program is free software: you can redistribute it and/or modify
4 | // it under the terms of the GNU Affero General Public License as published by
5 | // the Free Software Foundation, either version 3 of the License, or
6 | // (at your option) any later version.
7 | //
8 | // This program is distributed in the hope that it will be useful,
9 | // but WITHOUT ANY WARRANTY; without even the implied warranty of
10 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 | // GNU Affero General Public License for more details.
12 | //
13 | // You should have received a copy of the GNU Affero General Public License
14 | // along with this program. If not, see .
15 |
16 | import {Auth, Authority} from "./auth/auth.sol";
17 |
18 | import {Vat} from "./system/vat.sol";
19 | import {Jug} from "./system/jug.sol";
20 | import {Vow} from "./system/vow.sol";
21 | import {Dog} from "./system/dog.sol";
22 |
23 | import {ZarJoin} from "./system/join.sol";
24 | import {Clipper} from "./system/clip.sol";
25 | import {LinearDecrease, StairstepExponentialDecrease, ExponentialDecrease} from "./system/abaci.sol";
26 | import {Zar} from "./system/zar.sol";
27 | import {End} from "./system/end.sol";
28 | import {Spotter} from "./system/spot.sol";
29 |
30 | contract VatFab {
31 | function newVat(address owner) public returns (Vat vat) {
32 | vat = new Vat();
33 | vat.rely(owner);
34 | vat.deny(address(this));
35 | }
36 | }
37 |
38 | contract JugFab {
39 | function newJug(address owner, address vat) public returns (Jug jug) {
40 | jug = new Jug(vat);
41 | jug.rely(owner);
42 | jug.deny(address(this));
43 | }
44 | }
45 |
46 | contract VowFab {
47 | function newVow(address owner, address vat) public returns (Vow vow) {
48 | vow = new Vow(vat);
49 | vow.rely(owner);
50 | vow.deny(address(this));
51 | }
52 | }
53 |
54 | contract DogFab {
55 | function newDog(address owner, address vat) public returns (Dog dog) {
56 | dog = new Dog(vat);
57 | dog.rely(owner);
58 | dog.deny(address(this));
59 | }
60 | }
61 |
62 | contract ZarFab {
63 | function newZar(address owner) public returns (Zar zar) {
64 | zar = new Zar();
65 | zar.rely(owner);
66 | zar.deny(address(this));
67 | }
68 | }
69 |
70 | contract ZarJoinFab {
71 | function newZarJoin(address vat, address zar) public returns (ZarJoin zarJoin) {
72 | zarJoin = new ZarJoin(vat, zar);
73 | }
74 | }
75 |
76 | contract ClipFab {
77 | function newClip(address owner, address vat, address spotter, address dog, bytes32 ilk)
78 | public
79 | returns (Clipper clip)
80 | {
81 | clip = new Clipper(vat, spotter, dog, ilk);
82 | clip.rely(owner);
83 | clip.deny(address(this));
84 | }
85 | }
86 |
87 | contract CalcFab {
88 | function newLinearDecrease(address owner) public returns (LinearDecrease calc) {
89 | calc = new LinearDecrease();
90 | calc.rely(owner);
91 | calc.deny(address(this));
92 | }
93 |
94 | function newStairstepExponentialDecrease(address owner) public returns (StairstepExponentialDecrease calc) {
95 | calc = new StairstepExponentialDecrease();
96 | calc.rely(owner);
97 | calc.deny(address(this));
98 | }
99 |
100 | function newExponentialDecrease(address owner) public returns (ExponentialDecrease calc) {
101 | calc = new ExponentialDecrease();
102 | calc.rely(owner);
103 | calc.deny(address(this));
104 | }
105 | }
106 |
107 | contract SpotFab {
108 | function newSpotter(address owner, address vat) public returns (Spotter spotter) {
109 | spotter = new Spotter(vat);
110 | spotter.rely(owner);
111 | spotter.deny(address(this));
112 | }
113 | }
114 |
115 |
116 | contract EndFab {
117 | function newEnd(address owner) public returns (End end) {
118 | end = new End();
119 | end.rely(owner);
120 | end.deny(address(this));
121 | }
122 | }
123 |
124 |
125 | contract Deployment is Auth {
126 | VatFab public vatFab;
127 | JugFab public jugFab;
128 | VowFab public vowFab;
129 | DogFab public dogFab;
130 | ZarFab public zarFab;
131 | ZarJoinFab public zarJoinFab;
132 | ClipFab public clipFab;
133 | CalcFab public calcFab;
134 | SpotFab public spotFab;
135 | EndFab public endFab;
136 |
137 | Vat public vat;
138 | Jug public jug;
139 | Vow public vow;
140 | Dog public dog;
141 | Zar public zar;
142 | ZarJoin public zarJoin;
143 | Spotter public spotter;
144 | End public end;
145 |
146 | mapping(bytes32 => Ilk) public ilks;
147 |
148 | uint8 public step = 0;
149 |
150 | uint256 constant ONE = 10 ** 27;
151 |
152 | struct Ilk {
153 | Clipper clip;
154 | address join;
155 | }
156 |
157 | function addFabs1(
158 | VatFab vatFab_,
159 | JugFab jugFab_,
160 | VowFab vowFab_,
161 | DogFab dogFab_,
162 | ZarFab zarFab_,
163 | ZarJoinFab zarJoinFab_
164 | ) public auth {
165 | require(address(vatFab) == address(0), "Fabs 1 already saved");
166 | vatFab = vatFab_;
167 | jugFab = jugFab_;
168 | vowFab = vowFab_;
169 | dogFab = dogFab_;
170 | zarFab = zarFab_;
171 | zarJoinFab = zarJoinFab_;
172 | }
173 |
174 | function addFabs2(
175 | ClipFab clipFab_,
176 | CalcFab calcFab_,
177 | SpotFab spotFab_,
178 | EndFab endFab_
179 | ) public auth {
180 | require(address(clipFab) == address(0), "Fabs 2 already saved");
181 | clipFab = clipFab_;
182 | calcFab = calcFab_;
183 | spotFab = spotFab_;
184 | endFab = endFab_;
185 | }
186 |
187 | function rad(uint256 wad) internal pure returns (uint256) {
188 | return wad * 10 ** 27;
189 | }
190 |
191 | function deployVat() public auth {
192 | require(address(vatFab) != address(0), "Missing Fabs 1");
193 | require(address(vat) == address(0), "VAT already deployed");
194 | vat = vatFab.newVat(address(this));
195 | spotter = spotFab.newSpotter(address(this), address(vat));
196 |
197 | // Internal auth
198 | vat.rely(address(spotter));
199 | }
200 |
201 | function deployZar() public auth {
202 | require(address(vat) != address(0), "Missing previous step");
203 |
204 | zar = zarFab.newZar(address(this));
205 | zarJoin = zarJoinFab.newZarJoin(address(vat), address(zar));
206 | zar.rely(address(zarJoin));
207 | }
208 |
209 | function deployTaxation() public auth {
210 | require(address(vat) != address(0), "Missing previous step");
211 |
212 | // Deploy
213 | jug = jugFab.newJug(address(this), address(vat));
214 |
215 | // Internal auth
216 | vat.rely(address(jug));
217 | }
218 |
219 | function deployAuctions(address gov) public auth {
220 | require(gov != address(0), "Missing GOV address");
221 | require(address(jug) != address(0), "Missing previous step");
222 |
223 | // Deploy
224 | vow = vowFab.newVow(address(this), address(vat));
225 |
226 | // Internal references set up
227 | jug.file("vow", address(vow));
228 | }
229 |
230 | function deployLiquidator() public auth {
231 | require(address(vow) != address(0), "Missing previous step");
232 |
233 | // Deploy
234 | dog = dogFab.newDog(address(this), address(vat));
235 |
236 | // Internal references set up
237 | dog.file("vow", address(vow));
238 |
239 | // Internal auth
240 | vat.rely(address(dog));
241 | vow.rely(address(dog));
242 | }
243 |
244 | function deployEnd() public auth {
245 | // Deploy
246 | end = endFab.newEnd(address(this));
247 |
248 | // Internal references set up
249 | end.file("vat", address(vat));
250 | end.file("dog", address(dog));
251 | end.file("vow", address(vow));
252 | end.file("spot", address(spotter));
253 |
254 | // Internal auth
255 | vat.rely(address(end));
256 | dog.rely(address(end));
257 | vow.rely(address(end));
258 | spotter.rely(address(end));
259 | }
260 |
261 | function relyAuthority(address authority) public auth {
262 | require(address(zar) != address(0), "Missing previous step");
263 | require(address(end) != address(0), "Missing previous step");
264 |
265 | vat.rely(authority);
266 | dog.rely(authority);
267 | vow.rely(authority);
268 | jug.rely(authority);
269 | spotter.rely(authority);
270 | end.rely(authority);
271 | }
272 |
273 | function deployCollateralClip(bytes32 ilk, address join, address pip, address calc, address authority)
274 | public
275 | auth
276 | {
277 | require(ilk != bytes32(""), "Missing ilk name");
278 | require(join != address(0), "Missing join address");
279 | require(pip != address(0), "Missing pip address");
280 |
281 | // Deploy
282 | ilks[ilk].clip = clipFab.newClip(address(this), address(vat), address(spotter), address(dog), ilk);
283 | ilks[ilk].join = join;
284 | Spotter(spotter).file(ilk, "pip", address(pip)); // Set pip
285 |
286 | // Internal references set up
287 | dog.file(ilk, "clip", address(ilks[ilk].clip));
288 | ilks[ilk].clip.file("vow", address(vow));
289 |
290 | // Use calc with safe default if not configured
291 | if (calc == address(0)) {
292 | calc = address(calcFab.newLinearDecrease(address(this)));
293 | LinearDecrease(calc).file(bytes32("tau"), 1 hours);
294 | }
295 | ilks[ilk].clip.file("calc", calc);
296 | vat.init(ilk);
297 | jug.init(ilk);
298 |
299 | // Internal auth
300 | vat.rely(join);
301 | vat.rely(address(ilks[ilk].clip));
302 | dog.rely(address(ilks[ilk].clip));
303 | ilks[ilk].clip.rely(address(dog));
304 | ilks[ilk].clip.rely(address(end));
305 | ilks[ilk].clip.rely(authority);
306 | }
307 |
308 | function releaseAuth() public auth {
309 | vat.deny(address(this));
310 | dog.deny(address(this));
311 | vow.deny(address(this));
312 | jug.deny(address(this));
313 | zar.deny(address(this));
314 | spotter.deny(address(this));
315 | end.deny(address(this));
316 | }
317 |
318 | function releaseAuthClip(bytes32 ilk) public auth {
319 | ilks[ilk].clip.deny(address(this));
320 | }
321 | }
322 |
--------------------------------------------------------------------------------
/contracts/system/abaci.sol:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: AGPL-3.0-or-later
2 | pragma solidity ^0.8.13;
3 | // This program is free software: you can redistribute it and/or modify
4 | // it under the terms of the GNU Affero General Public License as published
5 | // by the Free Software Foundation, either version 3 of the License, or
6 | // (at your option) any later version.
7 | //
8 | // This program is distributed in the hope that it will be useful,
9 | // but WITHOUT ANY WARRANTY; without even the implied warranty of
10 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 | // GNU Affero General Public License for more details.
12 | //
13 | // You should have received a copy of the GNU Affero General Public License
14 | // along with this program. If not, see .
15 |
16 | interface Abacus {
17 | // 1st arg: initial price [ray]
18 | // 2nd arg: seconds since auction start [seconds]
19 | // returns: current auction price [ray]
20 | function price(uint256, uint256) external view returns (uint256);
21 | }
22 |
23 | contract LinearDecrease is Abacus {
24 | // --- Auth ---
25 | mapping(address => uint256) public wards;
26 |
27 | function rely(address usr) external auth {
28 | wards[usr] = 1;
29 | emit Rely(usr);
30 | }
31 |
32 | function deny(address usr) external auth {
33 | wards[usr] = 0;
34 | emit Deny(usr);
35 | }
36 |
37 | modifier auth() {
38 | require(wards[msg.sender] == 1, "LinearDecrease/not-authorized");
39 | _;
40 | }
41 |
42 | // --- Data ---
43 | uint256 public tau; // Seconds after auction start when the price reaches zero [seconds]
44 |
45 | // --- Events ---
46 | event Rely(address indexed usr);
47 | event Deny(address indexed usr);
48 |
49 | event File(bytes32 indexed what, uint256 data);
50 |
51 | // --- Init ---
52 | constructor() public {
53 | wards[msg.sender] = 1;
54 | emit Rely(msg.sender);
55 | }
56 |
57 | // --- Administration ---
58 | function file(bytes32 what, uint256 data) external auth {
59 | if (what == "tau") tau = data;
60 | else revert("LinearDecrease/file-unrecognized-param");
61 | emit File(what, data);
62 | }
63 |
64 | // --- Math ---
65 | uint256 constant RAY = 10 ** 27;
66 |
67 | function mul(uint256 x, uint256 y) internal pure returns (uint256 z) {
68 | require(y == 0 || (z = x * y) / y == x);
69 | }
70 |
71 | function rmul(uint256 x, uint256 y) internal pure returns (uint256 z) {
72 | z = x * y;
73 | require(y == 0 || z / y == x);
74 | z = z / RAY;
75 | }
76 |
77 | // Price calculation when price is decreased linearly in proportion to time:
78 | // tau: The number of seconds after the start of the auction where the price will hit 0
79 | // top: Initial price
80 | // dur: current seconds since the start of the auction
81 | //
82 | // Returns y = top * ((tau - dur) / tau)
83 | //
84 | // Note the internal call to mul multiples by RAY, thereby ensuring that the rmul calculation
85 | // which utilizes top and tau (RAY values) is also a RAY value.
86 | function price(uint256 top, uint256 dur) external view override returns (uint256) {
87 | if (dur >= tau) return 0;
88 | return rmul(top, mul(tau - dur, RAY) / tau);
89 | }
90 | }
91 |
92 | contract StairstepExponentialDecrease is Abacus {
93 | // --- Auth ---
94 | mapping(address => uint256) public wards;
95 |
96 | function rely(address usr) external auth {
97 | wards[usr] = 1;
98 | emit Rely(usr);
99 | }
100 |
101 | function deny(address usr) external auth {
102 | wards[usr] = 0;
103 | emit Deny(usr);
104 | }
105 |
106 | modifier auth() {
107 | require(wards[msg.sender] == 1, "StairstepExponentialDecrease/not-authorized");
108 | _;
109 | }
110 |
111 | // --- Data ---
112 | uint256 public step; // Length of time between price drops [seconds]
113 | uint256 public cut; // Per-step multiplicative factor [ray]
114 |
115 | // --- Events ---
116 | event Rely(address indexed usr);
117 | event Deny(address indexed usr);
118 |
119 | event File(bytes32 indexed what, uint256 data);
120 |
121 | // --- Init ---
122 | // @notice: `cut` and `step` values must be correctly set for
123 | // this contract to return a valid price
124 | constructor() public {
125 | wards[msg.sender] = 1;
126 | emit Rely(msg.sender);
127 | }
128 |
129 | // --- Administration ---
130 | function file(bytes32 what, uint256 data) external auth {
131 | if (what == "cut") {
132 | require((cut = data) <= RAY, "StairstepExponentialDecrease/cut-gt-RAY");
133 | } else if (what == "step") {
134 | step = data;
135 | } else {
136 | revert("StairstepExponentialDecrease/file-unrecognized-param");
137 | }
138 | emit File(what, data);
139 | }
140 |
141 | // --- Math ---
142 | uint256 constant RAY = 10 ** 27;
143 |
144 | function rmul(uint256 x, uint256 y) internal pure returns (uint256 z) {
145 | z = x * y;
146 | require(y == 0 || z / y == x);
147 | z = z / RAY;
148 | }
149 |
150 | function rpow(uint256 x, uint256 n, uint256 b) internal pure returns (uint256 z) {
151 | assembly {
152 | switch x
153 | case 0 {
154 | switch n
155 | case 0 { z := b }
156 | default { z := 0 }
157 | }
158 | default {
159 | switch mod(n, 2)
160 | case 0 { z := b }
161 | default { z := x }
162 | let half := div(b, 2) // for rounding.
163 | for { n := div(n, 2) } n { n := div(n, 2) } {
164 | let xx := mul(x, x)
165 | if iszero(eq(div(xx, x), x)) { revert(0, 0) }
166 | let xxRound := add(xx, half)
167 | if lt(xxRound, xx) { revert(0, 0) }
168 | x := div(xxRound, b)
169 | if mod(n, 2) {
170 | let zx := mul(z, x)
171 | if and(iszero(iszero(x)), iszero(eq(div(zx, x), z))) { revert(0, 0) }
172 | let zxRound := add(zx, half)
173 | if lt(zxRound, zx) { revert(0, 0) }
174 | z := div(zxRound, b)
175 | }
176 | }
177 | }
178 | }
179 | }
180 |
181 | // top: initial price
182 | // dur: seconds since the auction has started
183 | // step: seconds between a price drop
184 | // cut: cut encodes the percentage to decrease per step.
185 | // For efficiency, the values is set as (1 - (% value / 100)) * RAY
186 | // So, for a 1% decrease per step, cut would be (1 - 0.01) * RAY
187 | //
188 | // returns: top * (cut ^ dur)
189 | //
190 | //
191 | function price(uint256 top, uint256 dur) external view override returns (uint256) {
192 | return rmul(top, rpow(cut, dur / step, RAY));
193 | }
194 | }
195 |
196 | // While an equivalent function can be obtained by setting step = 1 in StairstepExponentialDecrease,
197 | // this continous (i.e. per-second) exponential decrease has be implemented as it is more gas-efficient
198 | // than using the stairstep version with step = 1 (primarily due to 1 fewer SLOAD per price calculation).
199 | contract ExponentialDecrease is Abacus {
200 | // --- Auth ---
201 | mapping(address => uint256) public wards;
202 |
203 | function rely(address usr) external auth {
204 | wards[usr] = 1;
205 | emit Rely(usr);
206 | }
207 |
208 | function deny(address usr) external auth {
209 | wards[usr] = 0;
210 | emit Deny(usr);
211 | }
212 |
213 | modifier auth() {
214 | require(wards[msg.sender] == 1, "ExponentialDecrease/not-authorized");
215 | _;
216 | }
217 |
218 | // --- Data ---
219 | uint256 public cut; // Per-second multiplicative factor [ray]
220 |
221 | // --- Events ---
222 | event Rely(address indexed usr);
223 | event Deny(address indexed usr);
224 |
225 | event File(bytes32 indexed what, uint256 data);
226 |
227 | // --- Init ---
228 | // @notice: `cut` value must be correctly set for
229 | // this contract to return a valid price
230 | constructor() public {
231 | wards[msg.sender] = 1;
232 | emit Rely(msg.sender);
233 | }
234 |
235 | // --- Administration ---
236 | function file(bytes32 what, uint256 data) external auth {
237 | if (what == "cut") {
238 | require((cut = data) <= RAY, "ExponentialDecrease/cut-gt-RAY");
239 | } else {
240 | revert("ExponentialDecrease/file-unrecognized-param");
241 | }
242 | emit File(what, data);
243 | }
244 |
245 | // --- Math ---
246 | uint256 constant RAY = 10 ** 27;
247 |
248 | function rmul(uint256 x, uint256 y) internal pure returns (uint256 z) {
249 | z = x * y;
250 | require(y == 0 || z / y == x);
251 | z = z / RAY;
252 | }
253 |
254 | function rpow(uint256 x, uint256 n, uint256 b) internal pure returns (uint256 z) {
255 | assembly {
256 | switch x
257 | case 0 {
258 | switch n
259 | case 0 { z := b }
260 | default { z := 0 }
261 | }
262 | default {
263 | switch mod(n, 2)
264 | case 0 { z := b }
265 | default { z := x }
266 | let half := div(b, 2) // for rounding.
267 | for { n := div(n, 2) } n { n := div(n, 2) } {
268 | let xx := mul(x, x)
269 | if iszero(eq(div(xx, x), x)) { revert(0, 0) }
270 | let xxRound := add(xx, half)
271 | if lt(xxRound, xx) { revert(0, 0) }
272 | x := div(xxRound, b)
273 | if mod(n, 2) {
274 | let zx := mul(z, x)
275 | if and(iszero(iszero(x)), iszero(eq(div(zx, x), z))) { revert(0, 0) }
276 | let zxRound := add(zx, half)
277 | if lt(zxRound, zx) { revert(0, 0) }
278 | z := div(zxRound, b)
279 | }
280 | }
281 | }
282 | }
283 | }
284 |
285 | // top: initial price
286 | // dur: seconds since the auction has started
287 | // cut: cut encodes the percentage to decrease per second.
288 | // For efficiency, the values is set as (1 - (% value / 100)) * RAY
289 | // So, for a 1% decrease per second, cut would be (1 - 0.01) * RAY
290 | //
291 | // returns: top * (cut ^ dur)
292 | //
293 | function price(uint256 top, uint256 dur) external view override returns (uint256) {
294 | return rmul(top, rpow(cut, dur, RAY));
295 | }
296 | }
297 |
--------------------------------------------------------------------------------
/test/median.t.sol:
--------------------------------------------------------------------------------
1 | pragma solidity >=0.5.10;
2 |
3 | import "ds-test/test.sol";
4 |
5 | import "contracts/oracle/median.sol";
6 |
7 | contract UnauthorizedPeek {
8 | Median m;
9 |
10 | constructor(Median m_) public {
11 | m = m_;
12 | }
13 |
14 | function doPeek() public view returns (uint256, bool) {
15 | return m.peek();
16 | }
17 |
18 | function doRead() public view returns (uint256) {
19 | return m.read();
20 | }
21 | }
22 |
23 | contract MedianTest is DSTest {
24 | Median m;
25 | UnauthorizedPeek u;
26 |
27 | function setUp() public {
28 | m = new Median("ethusd");
29 | u = new UnauthorizedPeek(m);
30 | }
31 |
32 | function test_slot() public {
33 | address[] memory a = new address[](1);
34 | a[0] = address(0x0a00000000000000000000000000000000000000);
35 | address[] memory b = new address[](1);
36 | b[0] = address(0x0B00000000000000000000000000000000000000);
37 | m.lift(a);
38 | m.lift(b);
39 | m.drop(a);
40 | m.lift(a);
41 | }
42 |
43 | function testFail_slot() public {
44 | address[] memory a = new address[](1);
45 | a[0] = address(0x0a00000000000000000000000000000000000000);
46 | address[] memory b = new address[](1);
47 | b[0] = address(0x0A11111111111111111111111111111111111111);
48 | m.lift(a);
49 | m.lift(b);
50 | }
51 |
52 | function test_Median() public {
53 | address[] memory orcl = new address[](15);
54 | orcl[0] = address(0x2d6691a7Ca09FcFC8a069953AD4Ba4De11DbFFd6);
55 | orcl[1] = address(0xEF7a293Adaec73c5E134040DDAd13a15CEB7231A);
56 | orcl[2] = address(0xEd1fBB08C70D1d510cF6C6a8B31f69917F0eCd46);
57 | orcl[3] = address(0xd4D2CBda7CA421A68aFdb72f16Ad38b8f0Ea3199);
58 | orcl[4] = address(0x94e71Afc1C876762aF8aaEd569596E6Fe2d42d86);
59 | orcl[5] = address(0x1379F663AE24cFD7cDaad6d8E0fa0dBf2F7D51fb);
60 | orcl[6] = address(0x2a4B7b59323B8bC4a78d04a88E853469ED6ea1d4);
61 | orcl[7] = address(0x8797FDdF08612100a8B821CD52f8B71dB75Fa9aC);
62 | orcl[8] = address(0xdB3E64F17f5E6Af7161dCd01401464835136Af6C);
63 | orcl[9] = address(0xCD63177834dDD54aDdD2d9F9845042A21360023A);
64 | orcl[10] = address(0x832A0149Beea1e4cb7175b3062Edd10E1b40A951);
65 | orcl[11] = address(0xb158f2EC0E44c7cE533C5e41ca5FB09575f1e210);
66 | orcl[12] = address(0x555faE91fb4b03473704045737b8b5F628E9E5E5);
67 | orcl[13] = address(0x8b8668B708D4edee400Dfd00e9A9038781eb5904);
68 | orcl[14] = address(0x06B80b4034FEc8566857f0B9180b025e933093e4);
69 |
70 | uint256[] memory price = new uint256[](15);
71 |
72 | price[0] = uint256(
73 | 0x00000000000000000000000000000000000000000000000da04773c0e7dc8000
74 | );
75 | price[1] = uint256(
76 | 0x00000000000000000000000000000000000000000000000dadaf5fa2ace38000
77 | );
78 | price[2] = uint256(
79 | 0x00000000000000000000000000000000000000000000000dc37cafcfdb070000
80 | );
81 | price[3] = uint256(
82 | 0x00000000000000000000000000000000000000000000000dd2cb5477ce488000
83 | );
84 | price[4] = uint256(
85 | 0x00000000000000000000000000000000000000000000000dda50e698aa8b8000
86 | );
87 | price[5] = uint256(
88 | 0x00000000000000000000000000000000000000000000000dee1b120a84408000
89 | );
90 | price[6] = uint256(
91 | 0x00000000000000000000000000000000000000000000000df1f6b99173cf8000
92 | );
93 | price[7] = uint256(
94 | 0x00000000000000000000000000000000000000000000000e05e46bf5bd458000
95 | );
96 | price[8] = uint256(
97 | 0x00000000000000000000000000000000000000000000000e0d89f78a64830000
98 | );
99 | price[9] = uint256(
100 | 0x00000000000000000000000000000000000000000000000e25afb05259b10000
101 | );
102 | price[10] = uint256(
103 | 0x00000000000000000000000000000000000000000000000e2f0a37c02c4e0000
104 | );
105 | price[11] = uint256(
106 | 0x00000000000000000000000000000000000000000000000e39eb8b98cc360000
107 | );
108 | price[12] = uint256(
109 | 0x00000000000000000000000000000000000000000000000e4cab42f05fc38000
110 | );
111 | price[13] = uint256(
112 | 0x00000000000000000000000000000000000000000000000e549b69e88b498000
113 | );
114 | price[14] = uint256(
115 | 0x00000000000000000000000000000000000000000000000e68f023a57f3c0000
116 | );
117 |
118 | uint256[] memory ts = new uint256[](15);
119 |
120 | ts[0] = uint256(
121 | 0x000000000000000000000000000000000000000000000000000000005c4a3710
122 | );
123 | ts[1] = uint256(
124 | 0x000000000000000000000000000000000000000000000000000000005c4a3711
125 | );
126 | ts[2] = uint256(
127 | 0x000000000000000000000000000000000000000000000000000000005c4a3712
128 | );
129 | ts[3] = uint256(
130 | 0x000000000000000000000000000000000000000000000000000000005c4a3713
131 | );
132 | ts[4] = uint256(
133 | 0x000000000000000000000000000000000000000000000000000000005c4a3714
134 | );
135 | ts[5] = uint256(
136 | 0x000000000000000000000000000000000000000000000000000000005c4a3715
137 | );
138 | ts[6] = uint256(
139 | 0x000000000000000000000000000000000000000000000000000000005c4a3716
140 | );
141 | ts[7] = uint256(
142 | 0x000000000000000000000000000000000000000000000000000000005c4a3717
143 | );
144 | ts[8] = uint256(
145 | 0x000000000000000000000000000000000000000000000000000000005c4a3719
146 | );
147 | ts[9] = uint256(
148 | 0x000000000000000000000000000000000000000000000000000000005c4a371a
149 | );
150 | ts[10] = uint256(
151 | 0x000000000000000000000000000000000000000000000000000000005c4a371b
152 | );
153 | ts[11] = uint256(
154 | 0x000000000000000000000000000000000000000000000000000000005c4a371c
155 | );
156 | ts[12] = uint256(
157 | 0x000000000000000000000000000000000000000000000000000000005c4a371d
158 | );
159 | ts[13] = uint256(
160 | 0x000000000000000000000000000000000000000000000000000000005c4a371e
161 | );
162 | ts[14] = uint256(
163 | 0x000000000000000000000000000000000000000000000000000000005c4a371f
164 | );
165 |
166 | uint8[] memory v = new uint8[](15);
167 | v[0] = uint8(0x1c);
168 | v[1] = uint8(0x1b);
169 | v[2] = uint8(0x1c);
170 | v[3] = uint8(0x1b);
171 | v[4] = uint8(0x1b);
172 | v[5] = uint8(0x1b);
173 | v[6] = uint8(0x1c);
174 | v[7] = uint8(0x1c);
175 | v[8] = uint8(0x1b);
176 | v[9] = uint8(0x1c);
177 | v[10] = uint8(0x1b);
178 | v[11] = uint8(0x1b);
179 | v[12] = uint8(0x1c);
180 | v[13] = uint8(0x1c);
181 | v[14] = uint8(0x1b);
182 |
183 | bytes32[] memory r = new bytes32[](15);
184 |
185 | r[0] = bytes32(
186 | 0xcde732167a15601b67d9a5a03c14739f05a4128966d5e14157a97178c2b66268
187 | );
188 | r[1] = bytes32(
189 | 0x5f7533150ce566f568f0157a9bcb119e84bc0fcee585a8e8fff14b10b7e87ce9
190 | );
191 | r[2] = bytes32(
192 | 0xa5e83de72de8cd96edc991b774dbef19dfec5905a3b0438c8b4b14d799c234fb
193 | );
194 | r[3] = bytes32(
195 | 0x9a13768dad10e3b2d22e37c5ba74b5fa5d71569efeaa45f8333fdcc799826861
196 | );
197 | r[4] = bytes32(
198 | 0x18f7edbf9fa29b6965cd2b63f4a771847af0a1f5e29c0542d14659c3d22d9f39
199 | );
200 | r[5] = bytes32(
201 | 0xa9f717be8c0f61aa4a9313858ef46defe4080e81565abe6f3c7b691be81b7512
202 | );
203 | r[6] = bytes32(
204 | 0x1d4ddab4935b842e58a4f68813508693c968345d965f0ea65e2cb66d2d87278b
205 | );
206 | r[7] = bytes32(
207 | 0xdb29ff83b98180bffb0a369972efa7f75a377621f4be9abd98bac8497b6cc7d7
208 | );
209 | r[8] = bytes32(
210 | 0xbfe4434091e228a0d57a67ae1cec2d1f24eb470acbc99d3e44477e5ba86ec192
211 | );
212 | r[9] = bytes32(
213 | 0xbfe9e874ce4b86886167310e252cb3e792f7577c78c6317131b3b23bd2bac23a
214 | );
215 | r[10] = bytes32(
216 | 0x494a00afbf51e94a00fb802464a329788b1787cca413e9606e48b0d4c5db186a
217 | );
218 | r[11] = bytes32(
219 | 0xd48a4227257fe62489dd5a876213f0c73dd28b5bbd0062b97c97ad359341a6d0
220 | );
221 | r[12] = bytes32(
222 | 0x1036209fd741421b13c947b723c6c36723337831f261261a9f972c92c1024e9c
223 | );
224 | r[13] = bytes32(
225 | 0xddbf5d9d124da617f20aabeadce531bc7bf5a5cc87eee996cd7a7acff335e659
226 | );
227 | r[14] = bytes32(
228 | 0x46ad81c37b4fd40b16c428accb368bba91312a5b4491a747abb31594faaa30df
229 | );
230 |
231 | bytes32[] memory s = new bytes32[](15);
232 |
233 | s[0] = bytes32(
234 | 0x7db1ca5ef537cd229d35c88395393f23c8f2bb4708d65d66bb625879686e87b5
235 | );
236 | s[1] = bytes32(
237 | 0x6c2ee3a98dfeca39f1b9b79ddcb446be70e771e0737c296c537bfb01ed9f5eb4
238 | );
239 | s[2] = bytes32(
240 | 0x1c29866da2db9480c8a7f2a691c194e3deb1c69b50c68005c1f70f20845ae127
241 | );
242 | s[3] = bytes32(
243 | 0x7f6aa4bc4be9b59e95653563e6e82c44b26543a7e7f76e4ca5981d3a061f0c06
244 | );
245 | s[4] = bytes32(
246 | 0x34fa2d01cd9d6d90376754d63f064079b8369c301545a55d47b1d281ddbe6c0e
247 | );
248 | s[5] = bytes32(
249 | 0x7f414a67c20e574065134c43562956ae0c5831540b2a11d27f0cbf55c1a17838
250 | );
251 | s[6] = bytes32(
252 | 0x54923524bf791d2e53955ca9016ac24f26c509a28a3bd297a4e2bf92be5c143a
253 | );
254 | s[7] = bytes32(
255 | 0x4d81a95311ed8d44ec77725aaa9d7e376156de27a1400c61858e47945102df0a
256 | );
257 | s[8] = bytes32(
258 | 0x304b355b420a75f432002c527ea1d1d073bbbe9383e8cc0b35a73e6ab4f8e643
259 | );
260 | s[9] = bytes32(
261 | 0x6b115625e7b015434b85d5d3c2a0627564b78df43a12b8ea6f5fc778395fafde
262 | );
263 | s[10] = bytes32(
264 | 0x036ff783f19deb152c42ec06238d9cb9de8697765103b32936d6d2cb441fada8
265 | );
266 | s[11] = bytes32(
267 | 0x525cd8d3baf77dad049c7092cbbef6979e36924b88cc90faf09256c24552cf9d
268 | );
269 | s[12] = bytes32(
270 | 0x242043c823bf48009cbf79e6114de1ce57fd2a031190966d00b89a16871534ed
271 | );
272 | s[13] = bytes32(
273 | 0x69dd6213ef7c960ce7123a50dabd2a45d477c8ae3eca2bb860ec75902a23ca81
274 | );
275 | s[14] = bytes32(
276 | 0x6573f1f517c89503a1116377f7ac80cbfe2b24bbc5dc1147d03da725198f8cc5
277 | );
278 |
279 | m.setBar(15);
280 |
281 | m.lift(orcl);
282 |
283 | address[] memory f = new address[](2);
284 | f[0] = address(this);
285 | f[1] = address(u);
286 | m.kiss(f);
287 |
288 | uint256 gas = gasleft();
289 | m.poke(price, ts, v, r, s);
290 | gas = gas - gasleft();
291 | emit log_named_uint("gas", gas);
292 | (uint256 val, bool ok) = m.peek();
293 |
294 | emit log_named_decimal_uint("median", val, 18);
295 |
296 | (val, ok) = u.doPeek();
297 |
298 | assertTrue(ok);
299 | }
300 | }
301 |
--------------------------------------------------------------------------------
/test/dog.t.sol:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: AGPL-3.0-or-later
2 |
3 | pragma solidity ^0.8.13;
4 |
5 | import {DSTest} from "ds-test/test.sol";
6 |
7 | import {Vat} from "contracts/system/vat.sol";
8 | import {Dog} from "contracts/system/dog.sol";
9 |
10 | contract VowMock {
11 | function fess(uint256 due) public {}
12 | }
13 |
14 | contract ClipperMock {
15 | bytes32 public ilk;
16 |
17 | function setIlk(bytes32 wat) external {
18 | ilk = wat;
19 | }
20 |
21 | function kick(uint256, uint256, address, address) external pure returns (uint256 id) {
22 | id = 42;
23 | }
24 | }
25 |
26 | contract DogTest is DSTest {
27 | bytes32 constant ilk = "gold";
28 | address constant usr = address(1337);
29 | uint256 constant THOUSAND = 1e3;
30 | uint256 constant WAD = 1e18;
31 | uint256 constant RAY = 1e27;
32 | uint256 constant RAD = 1e45;
33 | Vat vat;
34 | VowMock vow;
35 | ClipperMock clip;
36 | Dog dog;
37 |
38 | function setUp() public {
39 | vat = new Vat();
40 | vat.init(ilk);
41 | vat.file(ilk, "spot", THOUSAND * RAY);
42 | vat.file(ilk, "dust", 100 * RAD);
43 | vow = new VowMock();
44 | clip = new ClipperMock();
45 | clip.setIlk(ilk);
46 | dog = new Dog(address(vat));
47 | vat.rely(address(dog));
48 | dog.file(ilk, "chop", (11 * WAD) / 10);
49 | dog.file("vow", address(vow));
50 | dog.file(ilk, "clip", address(clip));
51 | dog.file("Hole", 10 * THOUSAND * RAD);
52 | dog.file(ilk, "hole", 10 * THOUSAND * RAD);
53 | }
54 |
55 | function test_file_chop() public {
56 | dog.file(ilk, "chop", WAD);
57 | dog.file(ilk, "chop", (WAD * 113) / 100);
58 | }
59 |
60 | function testFail_file_chop_lt_WAD() public {
61 | dog.file(ilk, "chop", WAD - 1);
62 | }
63 |
64 | function testFail_file_chop_eq_zero() public {
65 | dog.file(ilk, "chop", 0);
66 | }
67 |
68 | function testFail_file_clip_wrong_ilk() public {
69 | dog.file("mismatched_ilk", "clip", address(clip));
70 | }
71 |
72 | function setUrn(uint256 ink, uint256 art) internal {
73 | vat.slip(ilk, usr, int256(ink));
74 | (, uint256 rate,,,) = vat.ilks(ilk);
75 | vat.suck(address(vow), address(vow), art * rate);
76 | vat.grab(ilk, usr, usr, address(vow), int256(ink), int256(art));
77 | (uint256 actualInk, uint256 actualArt) = vat.urns(ilk, usr);
78 | assertEq(ink, actualInk);
79 | assertEq(art, actualArt);
80 | }
81 |
82 | function isDusty() internal view returns (bool dusty) {
83 | (, uint256 rate,,, uint256 dust) = vat.ilks(ilk);
84 | (, uint256 art) = vat.urns(ilk, usr);
85 | uint256 due = art * rate;
86 | dusty = due > 0 && due < dust;
87 | }
88 |
89 | function test_bark_basic() public {
90 | setUrn(WAD, 2 * THOUSAND * WAD);
91 | dog.bark(ilk, usr, address(this));
92 | (uint256 ink, uint256 art) = vat.urns(ilk, usr);
93 | assertEq(ink, 0);
94 | assertEq(art, 0);
95 | }
96 |
97 | function testFail_bark_not_unsafe() public {
98 | setUrn(WAD, 500 * WAD);
99 | dog.bark(ilk, usr, address(this));
100 | }
101 |
102 | // dog.bark will liquidate vaults even if they are dusty
103 | function test_bark_dusty_vault() public {
104 | uint256 dust = 200;
105 | vat.file(ilk, "dust", dust * RAD);
106 | setUrn(1, (dust / 2) * WAD);
107 | assertTrue(isDusty());
108 | dog.bark(ilk, usr, address(this));
109 | }
110 |
111 | function test_bark_partial_liquidation_dirt_exceeds_hole_to_avoid_dusty_remnant() public {
112 | uint256 dust = 200;
113 | vat.file(ilk, "dust", dust * RAD);
114 | uint256 hole = 5 * THOUSAND;
115 | dog.file(ilk, "hole", hole * RAD);
116 | (, uint256 chop,,) = dog.ilks(ilk);
117 | uint256 artStart = (hole * WAD * WAD) / chop + dust * WAD - 1;
118 | setUrn(WAD, artStart);
119 | dog.bark(ilk, usr, address(this));
120 | assertTrue(!isDusty());
121 | (, uint256 art) = vat.urns(ilk, usr);
122 |
123 | // The full vault has been liquidated so as not to leave a dusty remnant,
124 | // at the expense of slightly exceeding hole.
125 | assertEq(art, 0);
126 | (,,, uint256 dirt) = dog.ilks(ilk);
127 | assertTrue(dirt > hole * RAD);
128 | assertEq(dirt, (artStart * RAY * chop) / WAD);
129 | }
130 |
131 | function test_bark_partial_liquidation_dirt_does_not_exceed_hole_if_remnant_is_nondusty() public {
132 | uint256 dust = 200;
133 | vat.file(ilk, "dust", dust * RAD);
134 | uint256 hole = 5 * THOUSAND;
135 | dog.file(ilk, "hole", hole * RAD);
136 | (, uint256 chop,,) = dog.ilks(ilk);
137 | setUrn(WAD, (hole * WAD * WAD) / chop + dust * WAD);
138 | dog.bark(ilk, usr, address(this));
139 | assertTrue(!isDusty());
140 | (, uint256 art) = vat.urns(ilk, usr);
141 |
142 | // The vault remnant respects the dust limit, so we don't exceed hole to liquidate it.
143 | assertEq(art, dust * WAD);
144 | (,,, uint256 dirt) = dog.ilks(ilk);
145 | assertTrue(dirt <= hole * RAD);
146 | assertEq(dirt, (((hole * RAD * WAD) / RAY / chop) * RAY * chop) / WAD);
147 | }
148 |
149 | function test_bark_partial_liquidation_Dirt_exceeds_Hole_to_avoid_dusty_remnant() public {
150 | uint256 dust = 200;
151 | vat.file(ilk, "dust", dust * RAD);
152 | uint256 Hole = 5 * THOUSAND;
153 | dog.file("Hole", Hole * RAD);
154 | (, uint256 chop,,) = dog.ilks(ilk);
155 | uint256 artStart = (Hole * WAD * WAD) / chop + dust * WAD - 1;
156 | setUrn(WAD, artStart);
157 | dog.bark(ilk, usr, address(this));
158 | assertTrue(!isDusty());
159 |
160 | // The full vault has been liquidated so as not to leave a dusty remnant,
161 | // at the expense of slightly exceeding hole.
162 | (, uint256 art) = vat.urns(ilk, usr);
163 | assertEq(art, 0);
164 | assertTrue(dog.Dirt() > Hole * RAD);
165 | assertEq(dog.Dirt(), (artStart * RAY * chop) / WAD);
166 | }
167 |
168 | function test_bark_partial_liquidation_Dirt_does_not_exceed_Hole_if_remnant_is_nondusty() public {
169 | uint256 dust = 200;
170 | vat.file(ilk, "dust", dust * RAD);
171 | uint256 Hole = 5 * THOUSAND;
172 | dog.file("Hole", Hole * RAD);
173 | (, uint256 chop,,) = dog.ilks(ilk);
174 | setUrn(WAD, (Hole * WAD * WAD) / chop + dust * WAD);
175 | dog.bark(ilk, usr, address(this));
176 | assertTrue(!isDusty());
177 |
178 | // The full vault has been liquidated so as not to leave a dusty remnant,
179 | // at the expense of slightly exceeding hole.
180 | (, uint256 art) = vat.urns(ilk, usr);
181 | assertEq(art, dust * WAD);
182 | assertTrue(dog.Dirt() <= Hole * RAD);
183 | assertEq(dog.Dirt(), (((Hole * RAD * WAD) / RAY / chop) * RAY * chop) / WAD);
184 | }
185 |
186 | // A previous version reverted if room was dusty, even if the Vault being liquidated
187 | // was also dusty and would fit in the remaining hole/Hole room.
188 | function test_bark_dusty_vault_dusty_room() public {
189 | // Use a chop that will give nice round numbers
190 | uint256 CHOP = (110 * WAD) / 100; // 10%
191 | dog.file(ilk, "chop", CHOP);
192 |
193 | // set both hole_i and Hole to the same value for this test
194 | uint256 ROOM = 200;
195 | uint256 HOLE = 33 * THOUSAND + ROOM;
196 | dog.file("Hole", HOLE * RAD);
197 | dog.file(ilk, "hole", HOLE * RAD);
198 |
199 | // Test using a non-zero rate to ensure the code is handling stability fees correctly.
200 | vat.fold(ilk, address(vow), (5 * int256(RAY)) / 10);
201 | (, uint256 rate,,,) = vat.ilks(ilk);
202 | assertEq(rate, (15 * RAY) / 10);
203 |
204 | // First, make both holes nearly full.
205 | setUrn(WAD, ((((HOLE - ROOM) * RAD) / rate) * WAD) / CHOP);
206 | dog.bark(ilk, usr, address(this));
207 | assertEq(HOLE * RAD - dog.Dirt(), ROOM * RAD);
208 | (,,, uint256 dirt) = dog.ilks(ilk);
209 | assertEq(HOLE * RAD - dirt, ROOM * RAD);
210 |
211 | // Create a small vault
212 | uint256 DUST_1 = 30;
213 | vat.file(ilk, "dust", DUST_1 * RAD);
214 | setUrn(WAD / 10 ** 4, (DUST_1 * RAD) / rate);
215 |
216 | // Dust limit goes up!
217 | uint256 DUST_2 = 1500;
218 | vat.file(ilk, "dust", DUST_2 * RAD);
219 |
220 | // The testing vault is now dusty
221 | assertTrue(isDusty());
222 |
223 | // In fact, there is only room to create dusty auctions at this point.
224 | assertTrue(dog.Hole() - dog.Dirt() < (DUST_2 * RAD * CHOP) / WAD);
225 | uint256 hole;
226 | (,, hole, dirt) = dog.ilks(ilk);
227 | assertTrue(hole - dirt < (DUST_2 * RAD * CHOP) / WAD);
228 |
229 | // But...our Vault is small enough to fit in ROOM
230 | assertTrue((DUST_1 * RAD * CHOP) / WAD < ROOM * RAD);
231 |
232 | // bark should still succeed
233 | dog.bark(ilk, usr, address(this));
234 | }
235 |
236 | function try_bark(bytes32 ilk_, address usr_, address kpr_) internal returns (bool ok) {
237 | string memory sig = "bark(bytes32,address,address)";
238 | (ok,) = address(dog).call(abi.encodeWithSignature(sig, ilk_, usr_, kpr_));
239 | }
240 |
241 | function test_bark_do_not_create_dusty_auction_hole() public {
242 | uint256 dust = 300;
243 | vat.file(ilk, "dust", dust * RAD);
244 | uint256 hole = 3 * THOUSAND;
245 | dog.file(ilk, "hole", hole * RAD);
246 |
247 | // Test using a non-zero rate to ensure the code is handling stability fees correctly.
248 | vat.fold(ilk, address(vow), (5 * int256(RAY)) / 10);
249 | (, uint256 rate,,,) = vat.ilks(ilk);
250 | assertEq(rate, (15 * RAY) / 10);
251 |
252 | (, uint256 chop,,) = dog.ilks(ilk);
253 | setUrn(WAD, ((((hole - dust / 2) * RAD) / rate) * WAD) / chop);
254 | dog.bark(ilk, usr, address(this));
255 |
256 | // Make sure any partial liquidation would be dusty (assuming non-dusty remnant)
257 | (,,, uint256 dirt) = dog.ilks(ilk);
258 | uint256 room = hole * RAD - dirt;
259 | uint256 dart = (room * WAD) / rate / chop;
260 | assertTrue(dart * rate < dust * RAD);
261 |
262 | // This will need to be partially liquidated
263 | setUrn(WAD, (hole * WAD * WAD) / chop);
264 | assertTrue(!try_bark(ilk, usr, address(this))); // should revert, as the auction would be dusty
265 | }
266 |
267 | function test_bark_do_not_create_dusty_auction_Hole() public {
268 | uint256 dust = 300;
269 | vat.file(ilk, "dust", dust * RAD);
270 | uint256 Hole = 3 * THOUSAND;
271 | dog.file("Hole", Hole * RAD);
272 |
273 | // Test using a non-zero rate to ensure the code is handling stability fees correctly.
274 | vat.fold(ilk, address(vow), (5 * int256(RAY)) / 10);
275 | (, uint256 rate,,,) = vat.ilks(ilk);
276 | assertEq(rate, (15 * RAY) / 10);
277 |
278 | (, uint256 chop,,) = dog.ilks(ilk);
279 | setUrn(WAD, ((((Hole - dust / 2) * RAD) / rate) * WAD) / chop);
280 | dog.bark(ilk, usr, address(this));
281 |
282 | // Make sure any partial liquidation would be dusty (assuming non-dusty remnant)
283 | uint256 room = Hole * RAD - dog.Dirt();
284 | uint256 dart = (room * WAD) / rate / chop;
285 | assertTrue(dart * rate < dust * RAD);
286 |
287 | // This will need to be partially liquidated
288 | setUrn(WAD, (Hole * WAD * WAD) / chop);
289 | assertTrue(!try_bark(ilk, usr, address(this))); // should revert, as the auction would be dusty
290 | }
291 | }
292 |
--------------------------------------------------------------------------------
/contracts/system/vat.sol:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: AGPL-3.0-or-later
2 | pragma solidity ^0.8.13;
3 | // This program is free software: you can redistribute it and/or modify
4 | // it under the terms of the GNU Affero General Public License as published by
5 | // the Free Software Foundation, either version 3 of the License, or
6 | // (at your option) any later version.
7 | //
8 | // This program is distributed in the hope that it will be useful,
9 | // but WITHOUT ANY WARRANTY; without even the implied warranty of
10 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 | // GNU Affero General Public License for more details.
12 | //
13 | // You should have received a copy of the GNU Affero General Public License
14 | // along with this program. If not, see .
15 |
16 |
17 | contract Vat {
18 | // --- Auth ---
19 | mapping(address => uint256) public wards;
20 |
21 | function rely(address usr) external auth {
22 | require(live == 1, "Vat/not-live");
23 | wards[usr] = 1;
24 | emit Rely(usr);
25 | }
26 |
27 | function deny(address usr) external auth {
28 | require(live == 1, "Vat/not-live");
29 | wards[usr] = 0;
30 | emit Deny(usr);
31 | }
32 |
33 | modifier auth() {
34 | require(wards[msg.sender] == 1, "Vat/not-authorized");
35 | _;
36 | }
37 |
38 | mapping(address => mapping(address => uint256)) public can;
39 |
40 | function hope(address usr) external {
41 | can[msg.sender][usr] = 1;
42 | emit Hope(usr);
43 | }
44 |
45 | function nope(address usr) external {
46 | can[msg.sender][usr] = 0;
47 | emit Nope(usr);
48 | }
49 |
50 | function wish(address bit, address usr) internal view returns (bool) {
51 | return either(bit == usr, can[bit][usr] == 1);
52 | }
53 |
54 | // --- Data ---
55 | struct Ilk {
56 | uint256 Art; // Total Normalised Debt [wad]
57 | uint256 rate; // Accumulated Rates [ray]
58 | uint256 spot; // Price with Safety Margin [ray]
59 | uint256 line; // Debt Ceiling [rad]
60 | uint256 dust; // Urn Debt Floor [rad]
61 | }
62 |
63 | struct Urn {
64 | uint256 ink; // Locked Collateral [wad]
65 | uint256 art; // Normalised Debt [wad]
66 | }
67 |
68 | mapping(bytes32 => Ilk) public ilks;
69 | mapping(bytes32 => mapping(address => Urn)) public urns;
70 | mapping(bytes32 => mapping(address => uint256)) public gem; // [wad]
71 | mapping(address => uint256) public zar; // [rad]
72 | mapping(address => uint256) public sin; // [rad]
73 |
74 | uint256 public debt; // Total ZAR Issued [rad]
75 | uint256 public vice; // Total Unbacked ZAR [rad]
76 | uint256 public Line; // Total Debt Ceiling [rad]
77 | uint256 public live; // Active Flag
78 |
79 | event Rely(address indexed usr);
80 | event Deny(address indexed usr);
81 |
82 | event Hope(address indexed usr);
83 | event Nope(address indexed usr);
84 |
85 | event Init(bytes32 indexed ilk);
86 |
87 | event File(bytes32 indexed what, uint256 data);
88 | event File(bytes32 indexed ilk, bytes32 indexed what, uint256 indexed data);
89 | event Cage();
90 | event Slip(bytes32 indexed ilk, address indexed usr, int256 indexed wad);
91 | event Flux(bytes32 indexed ilk, address indexed src, address indexed dst, uint256 wad);
92 |
93 | event Move(address indexed src, address indexed dst, uint256 rad);
94 | event Frob(bytes32 i, address indexed u, address indexed v, address indexed w, int256 dink, int256 dart);
95 |
96 | event Fork(bytes32 indexed ilk, address indexed src, address indexed dst, int256 dink, int256 dart);
97 |
98 | event Grab(bytes32 i, address indexed u, address indexed v, address indexed w, int256 dink, int256 dart);
99 | event Heal(uint256 indexed rad);
100 |
101 | event Suck(address indexed u, address indexed v, uint256 indexed rad);
102 |
103 | event Kiss(uint256 indexed rad);
104 | event Flop(uint256 indexed id);
105 | event Fold(bytes32 indexed i, address indexed u, int256 indexed rate);
106 |
107 | // --- Init ---
108 | constructor() public {
109 | wards[msg.sender] = 1;
110 | live = 1;
111 | }
112 |
113 | // --- Math ---
114 | string private constant ARITHMETIC_ERROR = string(abi.encodeWithSignature("Panic(uint256)", 0x11));
115 |
116 | function _add(uint256 x, int256 y) internal pure returns (uint256 z) {
117 | unchecked {
118 | z = x + uint256(y);
119 | }
120 | require(y >= 0 || z <= x, ARITHMETIC_ERROR);
121 | require(y <= 0 || z >= x, ARITHMETIC_ERROR);
122 | }
123 |
124 | function _sub(uint256 x, int256 y) internal pure returns (uint256 z) {
125 | unchecked {
126 | z = x - uint256(y);
127 | }
128 | require(y <= 0 || z <= x, ARITHMETIC_ERROR);
129 | require(y >= 0 || z >= x, ARITHMETIC_ERROR);
130 | }
131 |
132 | function _mul(uint256 x, int256 y) internal pure returns (int256 z) {
133 | z = int256(x) * y;
134 | require(int256(x) >= 0);
135 | require(y == 0 || z / y == int256(x));
136 | }
137 |
138 | function _mul(uint256 x, uint256 y) internal pure returns (uint256 z) {
139 | require(y == 0 || (z = x * y) / y == x);
140 | }
141 |
142 | // --- Administration ---
143 | function init(bytes32 ilk) external auth {
144 | require(ilks[ilk].rate == 0, "Vat/ilk-already-init");
145 | ilks[ilk].rate = 10 ** 27;
146 | emit Init(ilk);
147 | }
148 |
149 | function file(bytes32 what, uint256 data) external auth {
150 | require(live == 1, "Vat/not-live");
151 | if (what == "Line") Line = data;
152 | else revert("Vat/file-unrecognized-param");
153 | emit File(what, data);
154 | }
155 |
156 | function file(bytes32 ilk, bytes32 what, uint256 data) external auth {
157 | require(live == 1, "Vat/not-live");
158 | if (what == "spot") ilks[ilk].spot = data;
159 | else if (what == "line") ilks[ilk].line = data;
160 | else if (what == "dust") ilks[ilk].dust = data;
161 | else revert("Vat/file-unrecognized-param");
162 | emit File(ilk, what, data);
163 | }
164 |
165 | function cage() external auth {
166 | live = 0;
167 | emit Cage();
168 | }
169 |
170 | // --- Fungibility ---
171 | function slip(bytes32 ilk, address usr, int256 wad) external auth {
172 | gem[ilk][usr] = _add(gem[ilk][usr], wad);
173 | emit Slip(ilk, usr, wad);
174 | }
175 |
176 | function flux(bytes32 ilk, address src, address dst, uint256 wad) external {
177 | require(wish(src, msg.sender), "Vat/not-allowed1");
178 | gem[ilk][src] = gem[ilk][src] - wad;
179 | gem[ilk][dst] = gem[ilk][dst] + wad;
180 | emit Flux(ilk, src, dst, wad);
181 | }
182 |
183 | function move(address src, address dst, uint256 rad) external {
184 | require(wish(src, msg.sender), "Vat/not-allowed2");
185 | zar[src] = zar[src] - rad;
186 | zar[dst] = zar[dst] + rad;
187 | emit Move(src, dst, rad);
188 | }
189 |
190 | function either(bool x, bool y) internal pure returns (bool z) {
191 | assembly {
192 | z := or(x, y)
193 | }
194 | }
195 |
196 | function both(bool x, bool y) internal pure returns (bool z) {
197 | assembly {
198 | z := and(x, y)
199 | }
200 | }
201 |
202 | // --- CDP Manipulation ---
203 | function frob(bytes32 i, address u, address v, address w, int256 dink, int256 dart) external {
204 | // system is live
205 | require(live == 1, "Vat/not-live");
206 |
207 | Urn memory urn = urns[i][u];
208 | Ilk memory ilk = ilks[i];
209 | // ilk has been initialised
210 | require(ilk.rate != 0, "Vat/ilk-not-init");
211 |
212 | urn.ink = _add(urn.ink, dink);
213 | urn.art = _add(urn.art, dart);
214 | ilk.Art = _add(ilk.Art, dart);
215 |
216 | int256 dtab = _mul(ilk.rate, dart);
217 | uint256 tab = _mul(ilk.rate, urn.art);
218 | debt = _add(debt, dtab);
219 |
220 | // either debt has decreased, or debt ceilings are not exceeded
221 | require(either(dart <= 0, both(_mul(ilk.Art, ilk.rate) <= ilk.line, debt <= Line)), "Vat/ceiling-exceeded");
222 | // urn is either less risky than before, or it is safe
223 | require(either(both(dart <= 0, dink >= 0), tab <= _mul(urn.ink, ilk.spot)), "Vat/not-safe");
224 |
225 | // urn is either more safe, or the owner consents
226 | require(either(both(dart <= 0, dink >= 0), wish(u, msg.sender)), "Vat/not-allowed-u");
227 | // collateral src consents
228 | require(either(dink <= 0, wish(v, msg.sender)), "Vat/not-allowed-v");
229 | // debt dst consents
230 | require(either(dart >= 0, wish(w, msg.sender)), "Vat/not-allowed-w");
231 |
232 | // urn has no debt, or a non-dusty amount
233 | require(either(urn.art == 0, tab >= ilk.dust), "Vat/dust");
234 |
235 | gem[i][v] = _sub(gem[i][v], dink);
236 | zar[w] = _add(zar[w], dtab);
237 |
238 | urns[i][u] = urn;
239 | ilks[i] = ilk;
240 | emit Frob(i, u, v, w, dink, dart);
241 | }
242 |
243 | // --- CDP Fungibility ---
244 | function fork(bytes32 ilk, address src, address dst, int256 dink, int256 dart) external {
245 | Urn storage u = urns[ilk][src];
246 | Urn storage v = urns[ilk][dst];
247 | Ilk storage i = ilks[ilk];
248 |
249 | u.ink = _sub(u.ink, dink);
250 | u.art = _sub(u.art, dart);
251 | v.ink = _add(v.ink, dink);
252 | v.art = _add(v.art, dart);
253 |
254 | uint256 utab = _mul(u.art, i.rate);
255 | uint256 vtab = _mul(v.art, i.rate);
256 |
257 | // both sides consent
258 | require(both(wish(src, msg.sender), wish(dst, msg.sender)), "Vat/not-allowed3");
259 |
260 | // both sides safe
261 | require(utab <= _mul(u.ink, i.spot), "Vat/not-safe-src");
262 | require(vtab <= _mul(v.ink, i.spot), "Vat/not-safe-dst");
263 |
264 | // both sides non-dusty
265 | require(either(utab >= i.dust, u.art == 0), "Vat/dust-src");
266 | require(either(vtab >= i.dust, v.art == 0), "Vat/dust-dst");
267 | emit Fork(ilk, src, dst, dink, dart);
268 | }
269 |
270 | // --- CDP Confiscation ---
271 | function grab(bytes32 i, address u, address v, address w, int256 dink, int256 dart) external auth {
272 | Urn storage urn = urns[i][u];
273 | Ilk storage ilk = ilks[i];
274 |
275 | urn.ink = _add(urn.ink, dink);
276 | urn.art = _add(urn.art, dart);
277 | ilk.Art = _add(ilk.Art, dart);
278 |
279 | int256 dtab = _mul(ilk.rate, dart);
280 |
281 | gem[i][v] = _sub(gem[i][v], dink);
282 | sin[w] = _sub(sin[w], dtab);
283 | vice = _sub(vice, dtab);
284 | emit Grab(i, u, v, w, dink, dart);
285 | }
286 |
287 | // --- Settlement ---
288 | function heal(uint256 rad) external {
289 | address u = msg.sender;
290 | sin[u] = sin[u] - rad;
291 | zar[u] = zar[u] - rad;
292 | vice = vice - rad;
293 | debt = debt - rad;
294 |
295 | emit Heal(rad);
296 | }
297 |
298 | function suck(address u, address v, uint256 rad) external auth {
299 | sin[u] = sin[u] + rad;
300 | zar[v] = zar[v] + rad;
301 | vice = vice + rad;
302 | debt = debt + rad;
303 |
304 | emit Suck(u, v, rad);
305 | }
306 |
307 | // --- Rates ---
308 | function fold(bytes32 i, address u, int256 rate) external auth {
309 | require(live == 1, "Vat/not-live");
310 | Ilk storage ilk = ilks[i];
311 | ilk.rate = _add(ilk.rate, rate);
312 | int256 rad = _mul(ilk.Art, rate);
313 | zar[u] = _add(zar[u], rad);
314 | debt = _add(debt, rad);
315 | emit Fold(i, u, rate);
316 | }
317 | }
318 |
--------------------------------------------------------------------------------
/contracts/system/end.sol:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: AGPL-3.0-or-later
2 | pragma solidity ^0.8.13;
3 | // This program is free software: you can redistribute it and/or modify
4 | // it under the terms of the GNU Affero General Public License as published by
5 | // the Free Software Foundation, either version 3 of the License, or
6 | // (at your option) any later version.
7 | //
8 | // This program is distributed in the hope that it will be useful,
9 | // but WITHOUT ANY WARRANTY; without even the implied warranty of
10 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 | // GNU Affero General Public License for more details.
12 | //
13 | // You should have received a copy of the GNU Affero General Public License
14 | // along with this program. If not, see .
15 |
16 |
17 | interface VatLike {
18 | function zar(address) external view returns (uint256);
19 |
20 | function ilks(bytes32 ilk)
21 | external
22 | returns (
23 | uint256 Art, // [wad]
24 | uint256 rate, // [ray]
25 | uint256 spot, // [ray]
26 | uint256 line, // [rad]
27 | uint256 dust
28 | ); // [rad]
29 |
30 | function urns(bytes32 ilk, address urn)
31 | external
32 | returns (
33 | uint256 ink, // [wad]
34 | uint256 art
35 | ); // [wad]
36 |
37 | function debt() external returns (uint256);
38 |
39 | function move(address src, address dst, uint256 rad) external;
40 |
41 | function hope(address) external;
42 |
43 | function flux(bytes32 ilk, address src, address dst, uint256 rad) external;
44 |
45 | function grab(bytes32 i, address u, address v, address w, int256 dink, int256 dart) external;
46 |
47 | function suck(address u, address v, uint256 rad) external;
48 |
49 | function cage() external;
50 | }
51 |
52 | interface DogLike {
53 | function ilks(bytes32) external returns (address clip, uint256 chop, uint256 hole, uint256 dirt);
54 |
55 | function cage() external;
56 | }
57 |
58 | interface VowLike {
59 | function cage() external;
60 | }
61 |
62 | interface ClipLike {
63 | function sales(uint256 id)
64 | external
65 | view
66 | returns (uint256 pos, uint256 tab, uint256 lot, address usr, uint96 tic, uint256 top);
67 |
68 | function yank(uint256 id) external;
69 | }
70 |
71 | interface PipLike {
72 | function read() external view returns (bytes32);
73 | }
74 |
75 | interface SpotLike {
76 | function par() external view returns (uint256);
77 |
78 | function ilks(bytes32) external view returns (PipLike pip, uint256 mat); // [ray]
79 |
80 | function cage() external;
81 | }
82 |
83 | /*
84 | This is the `End` and it coordinates Global Settlement. This is an
85 | involved, stateful process that takes place over nine steps.
86 |
87 | First we freeze the system and lock the prices for each ilk.
88 |
89 | 1. `cage()`:
90 | - freezes user entrypoints
91 | - cancels flop process
92 | - starts cooldown period
93 |
94 | 2. `cage(ilk)`:
95 | - set the cage price for each `ilk`, reading off the price feed
96 |
97 | We must process some system state before it is possible to calculate
98 | the final zar / collateral price. In particular, we need to determine
99 |
100 | a. `gap`, the collateral shortfall per collateral type by
101 | considering under-collateralised CDPs.
102 |
103 | b. `debt`, the outstanding zar supply after including system
104 | surplus / deficit
105 |
106 | We determine (a) by processing all under-collateralised CDPs with
107 | `skim`:
108 |
109 | 3. `skim(ilk, urn)`:
110 | - cancels CDP debt
111 | - any excess collateral remains
112 | - backing collateral taken
113 |
114 | We determine (b) by processing ongoing zar generating processes,
115 | i.e. auctions. We need to ensure that auctions will not generate any
116 | further zar income.
117 |
118 | In the case of the Dutch Auctions model (Clipper) they keep recovering
119 | debt during the whole lifetime and there isn't a max duration time
120 | guaranteed for the auction to end.
121 | So the way to ensure the protocol will not receive extra zar income is:
122 |
123 | 4b. i) `snip`: cancel all ongoing auctions and seize the collateral.
124 |
125 | `snip(ilk, id)`:
126 | - cancel individual running clip auctions
127 | - retrieves remaining collateral and debt (including penalty)
128 | to owner's CDP
129 |
130 | When a CDP has been processed and has no debt remaining, the
131 | remaining collateral can be removed.
132 |
133 | 5. `free(ilk)`:
134 | - remove collateral from the caller's CDP
135 | - owner can call as needed
136 |
137 | After the processing period has elapsed, we enable calculation of
138 | the final price for each collateral type.
139 |
140 | 6. `thaw()`:
141 | - only callable after processing time period elapsed
142 | - assumption that all under-collateralised CDPs are processed
143 | - fixes the total outstanding supply of zar
144 | - may also require extra CDP processing to cover vow surplus
145 |
146 | 7. `flow(ilk)`:
147 | - calculate the `fix`, the cash price for a given ilk
148 | - adjusts the `fix` in the case of deficit / surplus
149 |
150 | At this point we have computed the final price for each collateral
151 | type and zar holders can now turn their zar into collateral. Each
152 | unit zar can claim a fixed basket of collateral.
153 |
154 | zar holders must first `pack` some zar into a `bag`. Once packed,
155 | zar cannot be unpacked and is not transferrable. More zar can be
156 | added to a bag later.
157 |
158 | 8. `pack(wad)`:
159 | - put some zar into a bag in preparation for `cash`
160 |
161 | Finally, collateral can be obtained with `cash`. The bigger the bag,
162 | the more collateral can be released.
163 |
164 | 9. `cash(ilk, wad)`:
165 | - exchange some zar from your bag for gems from a specific ilk
166 | - the number of gems is limited by how big your bag is*/
167 |
168 | contract End {
169 | // --- Auth ---
170 | mapping(address => uint256) public wards;
171 |
172 | function rely(address usr) external auth {
173 | wards[usr] = 1;
174 | emit Rely(usr);
175 | }
176 |
177 | function deny(address usr) external auth {
178 | wards[usr] = 0;
179 | emit Deny(usr);
180 | }
181 |
182 | modifier auth() {
183 | require(wards[msg.sender] == 1, "End/not-authorized");
184 | _;
185 | }
186 |
187 | // --- Data ---
188 | VatLike public vat; // CDP Engine
189 | DogLike public dog;
190 | VowLike public vow; // Debt Engine
191 | SpotLike public spot;
192 |
193 | uint256 public live; // Active Flag
194 | uint256 public when; // Time of cage [unix epoch time]
195 | uint256 public wait; // Processing Cooldown Length [seconds]
196 | uint256 public debt; // Total outstanding zar following processing [rad]
197 |
198 | mapping(bytes32 => uint256) public tag; // Cage price [ray]
199 | mapping(bytes32 => uint256) public gap; // Collateral shortfall [wad]
200 | mapping(bytes32 => uint256) public Art; // Total debt per ilk [wad]
201 | mapping(bytes32 => uint256) public fix; // Final cash price [ray]
202 |
203 | mapping(address => uint256) public bag; // [wad]
204 | mapping(bytes32 => mapping(address => uint256)) public out; // [wad]
205 |
206 | // --- Events ---
207 | event Rely(address indexed usr);
208 | event Deny(address indexed usr);
209 |
210 | event File(bytes32 indexed what, uint256 data);
211 | event File(bytes32 indexed what, address data);
212 |
213 | event Cage();
214 | event Cage(bytes32 indexed ilk);
215 | event Snip(bytes32 indexed ilk, uint256 indexed id, address indexed usr, uint256 tab, uint256 lot, uint256 art);
216 | event Skim(bytes32 indexed ilk, address indexed urn, uint256 wad, uint256 art);
217 | event Free(bytes32 indexed ilk, address indexed usr, uint256 ink);
218 | event Thaw();
219 | event Flow(bytes32 indexed ilk);
220 | event Pack(address indexed usr, uint256 wad);
221 | event Cash(bytes32 indexed ilk, address indexed usr, uint256 wad);
222 |
223 | // --- Init ---
224 | constructor() public {
225 | wards[msg.sender] = 1;
226 | live = 1;
227 | emit Rely(msg.sender);
228 | }
229 |
230 | // --- Math ---
231 | uint256 constant WAD = 10 ** 18;
232 | uint256 constant RAY = 10 ** 27;
233 |
234 | function mul(uint256 x, uint256 y) internal pure returns (uint256 z) {
235 | require(y == 0 || (z = x * y) / y == x);
236 | }
237 |
238 | function min(uint256 x, uint256 y) internal pure returns (uint256 z) {
239 | return x <= y ? x : y;
240 | }
241 |
242 | function rmul(uint256 x, uint256 y) internal pure returns (uint256 z) {
243 | z = mul(x, y) / RAY;
244 | }
245 |
246 | function wdiv(uint256 x, uint256 y) internal pure returns (uint256 z) {
247 | z = mul(x, WAD) / y;
248 | }
249 |
250 | // --- Administration ---
251 | function file(bytes32 what, address data) external auth {
252 | require(live == 1, "End/not-live");
253 | if (what == "vat") vat = VatLike(data);
254 | else if (what == "dog") dog = DogLike(data);
255 | else if (what == "vow") vow = VowLike(data);
256 | else if (what == "spot") spot = SpotLike(data);
257 | else revert("End/file-unrecognized-param");
258 | emit File(what, data);
259 | }
260 |
261 | function file(bytes32 what, uint256 data) external auth {
262 | require(live == 1, "End/not-live");
263 | if (what == "wait") wait = data;
264 | else revert("End/file-unrecognized-param");
265 | emit File(what, data);
266 | }
267 |
268 | // --- Settlement ---
269 | function cage() external auth {
270 | require(live == 1, "End/not-live");
271 | live = 0;
272 | when = block.timestamp;
273 | vat.cage();
274 | dog.cage();
275 | vow.cage();
276 | spot.cage();
277 | emit Cage();
278 | }
279 |
280 | function cage(bytes32 ilk) external {
281 | require(live == 0, "End/still-live");
282 | require(tag[ilk] == 0, "End/tag-ilk-already-defined");
283 | (Art[ilk],,,,) = vat.ilks(ilk);
284 | (PipLike pip,) = spot.ilks(ilk);
285 | // par is a ray, pip returns a wad
286 | tag[ilk] = wdiv(spot.par(), uint256(pip.read()));
287 | emit Cage(ilk);
288 | }
289 |
290 | function snip(bytes32 ilk, uint256 id) external {
291 | require(tag[ilk] != 0, "End/tag-ilk-not-defined");
292 |
293 | (address _clip,,,) = dog.ilks(ilk);
294 | ClipLike clip = ClipLike(_clip);
295 | (, uint256 rate,,,) = vat.ilks(ilk);
296 | (, uint256 tab, uint256 lot, address usr,,) = clip.sales(id);
297 |
298 | vat.suck(address(vow), address(vow), tab);
299 | clip.yank(id);
300 |
301 | uint256 art = tab / rate;
302 | Art[ilk] = Art[ilk] + art;
303 | require(int256(lot) >= 0 && int256(art) >= 0, "End/overflow");
304 | vat.grab(ilk, usr, address(this), address(vow), int256(lot), int256(art));
305 | emit Snip(ilk, id, usr, tab, lot, art);
306 | }
307 |
308 | function skim(bytes32 ilk, address urn) external {
309 | require(tag[ilk] != 0, "End/tag-ilk-not-defined");
310 | (, uint256 rate,,,) = vat.ilks(ilk);
311 | (uint256 ink, uint256 art) = vat.urns(ilk, urn);
312 |
313 | uint256 owe = rmul(rmul(art, rate), tag[ilk]);
314 | uint256 wad = min(ink, owe);
315 | gap[ilk] = gap[ilk] + (owe - wad);
316 |
317 | require(wad <= 2 ** 255 && art <= 2 ** 255, "End/overflow");
318 | vat.grab(ilk, urn, address(this), address(vow), -int256(wad), -int256(art));
319 | emit Skim(ilk, urn, wad, art);
320 | }
321 |
322 | function free(bytes32 ilk) external {
323 | require(live == 0, "End/still-live");
324 | (uint256 ink, uint256 art) = vat.urns(ilk, msg.sender);
325 | require(art == 0, "End/art-not-zero");
326 | require(ink <= 2 ** 255, "End/overflow");
327 | vat.grab(ilk, msg.sender, msg.sender, address(vow), -int256(ink), 0);
328 | emit Free(ilk, msg.sender, ink);
329 | }
330 |
331 | function thaw() external {
332 | require(live == 0, "End/still-live");
333 | require(debt == 0, "End/debt-not-zero");
334 | require(vat.zar(address(vow)) == 0, "End/surplus-not-zero");
335 | require(block.timestamp >= when + wait, "End/wait-not-finished");
336 | debt = vat.debt();
337 | emit Thaw();
338 | }
339 |
340 | function flow(bytes32 ilk) external {
341 | require(debt != 0, "End/debt-zero");
342 | require(fix[ilk] == 0, "End/fix-ilk-already-defined");
343 |
344 | (, uint256 rate,,,) = vat.ilks(ilk);
345 | uint256 wad = rmul(rmul(Art[ilk], rate), tag[ilk]);
346 | fix[ilk] = mul(wad - gap[ilk], RAY) / (debt / RAY);
347 | emit Flow(ilk);
348 | }
349 |
350 | function pack(uint256 wad) external {
351 | require(debt != 0, "End/debt-zero");
352 | vat.move(msg.sender, address(vow), mul(wad, RAY));
353 | bag[msg.sender] = bag[msg.sender] + wad;
354 | emit Pack(msg.sender, wad);
355 | }
356 |
357 | function cash(bytes32 ilk, uint256 wad) external {
358 | require(fix[ilk] != 0, "End/fix-ilk-not-defined");
359 | vat.flux(ilk, address(this), msg.sender, rmul(wad, fix[ilk]));
360 | out[ilk][msg.sender] = out[ilk][msg.sender] + wad;
361 | require(out[ilk][msg.sender] <= bag[msg.sender], "End/insufficient-bag-balance");
362 | emit Cash(ilk, msg.sender, wad);
363 | }
364 | }
365 |
--------------------------------------------------------------------------------
/contracts/system/clip.sol:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: AGPL-3.0-or-later
2 | pragma solidity ^0.8.13;
3 | // This program is free software: you can redistribute it and/or modify
4 | // it under the terms of the GNU Affero General Public License as published
5 | // by the Free Software Foundation, either version 3 of the License, or
6 | // (at your option) any later version.
7 | //
8 | // This program is distributed in the hope that it will be useful,
9 | // but WITHOUT ANY WARRANTY; without even the implied warranty of
10 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 | // GNU Affero General Public License for more details.
12 | //
13 | // You should have received a copy of the GNU Affero General Public License
14 | // along with this program. If not, see .
15 |
16 | interface VatLike {
17 | function move(address, address, uint256) external;
18 |
19 | function flux(bytes32, address, address, uint256) external;
20 |
21 | function ilks(bytes32) external returns (uint256, uint256, uint256, uint256, uint256);
22 |
23 | function suck(address, address, uint256) external;
24 | }
25 |
26 | interface PipLike {
27 | function peek() external returns (bytes32, bool);
28 | }
29 |
30 | interface SpotterLike {
31 | function par() external returns (uint256);
32 |
33 | function ilks(bytes32) external returns (PipLike, uint256);
34 | }
35 |
36 | interface DogLike {
37 | function chop(bytes32) external returns (uint256);
38 |
39 | function digs(bytes32, uint256) external;
40 | }
41 |
42 | interface ClipperCallee {
43 | function clipperCall(address, uint256, uint256, bytes calldata) external;
44 | }
45 |
46 | interface AbacusLike {
47 | function price(uint256, uint256) external view returns (uint256);
48 | }
49 |
50 | contract Clipper {
51 | // --- Auth ---
52 | mapping(address => uint256) public wards;
53 |
54 | function rely(address usr) external auth {
55 | wards[usr] = 1;
56 | emit Rely(usr);
57 | }
58 |
59 | function deny(address usr) external auth {
60 | wards[usr] = 0;
61 | emit Deny(usr);
62 | }
63 |
64 | modifier auth() {
65 | require(wards[msg.sender] == 1, "Clipper/not-authorized");
66 | _;
67 | }
68 |
69 | // --- Data ---
70 | bytes32 public immutable ilk; // Collateral type of this Clipper
71 | VatLike public immutable vat; // Core CDP Engine
72 |
73 | DogLike public dog; // Liquidation module
74 | address public vow; // Recipient of ZAR raised in auctions
75 | SpotterLike public spotter; // Collateral price module
76 | AbacusLike public calc; // Current price calculator
77 |
78 | uint256 public buf; // Multiplicative factor to increase starting price [ray]
79 | uint256 public tail; // Time elapsed before auction reset [seconds]
80 | uint256 public cusp; // Percentage drop before auction reset [ray]
81 | uint64 public chip; // Percentage of tab to suck from vow to incentivize keepers [wad]
82 | uint192 public tip; // Flat fee to suck from vow to incentivize keepers [rad]
83 | uint256 public chost; // Cache the ilk dust times the ilk chop to prevent excessive SLOADs [rad]
84 |
85 | uint256 public kicks; // Total auctions
86 | uint256[] public active; // Array of active auction ids
87 |
88 | struct Sale {
89 | uint256 pos; // Index in active array
90 | uint256 tab; // ZAR to raise [rad]
91 | uint256 lot; // collateral to sell [wad]
92 | address usr; // Liquidated CDP
93 | uint96 tic; // Auction start time
94 | uint256 top; // Starting price [ray]
95 | }
96 |
97 | mapping(uint256 => Sale) public sales;
98 |
99 | uint256 internal locked;
100 |
101 | // Levels for circuit breaker
102 | // 0: no breaker
103 | // 1: no new kick()
104 | // 2: no new kick() or redo()
105 | // 3: no new kick(), redo(), or take()
106 | uint256 public stopped = 0;
107 |
108 | // --- Events ---
109 | event Rely(address indexed usr);
110 | event Deny(address indexed usr);
111 |
112 | event File(bytes32 indexed what, uint256 data);
113 | event File(bytes32 indexed what, address data);
114 |
115 | event Kick(
116 | uint256 indexed id,
117 | uint256 top,
118 | uint256 tab,
119 | uint256 lot,
120 | address indexed usr,
121 | address indexed kpr,
122 | uint256 coin
123 | );
124 | event Take(
125 | uint256 indexed id, uint256 max, uint256 price, uint256 owe, uint256 tab, uint256 lot, address indexed usr
126 | );
127 | event Redo(
128 | uint256 indexed id,
129 | uint256 top,
130 | uint256 tab,
131 | uint256 lot,
132 | address indexed usr,
133 | address indexed kpr,
134 | uint256 coin
135 | );
136 |
137 | event Yank(uint256 id);
138 |
139 | // --- Init ---
140 | constructor(address vat_, address spotter_, address dog_, bytes32 ilk_) public {
141 | vat = VatLike(vat_);
142 | spotter = SpotterLike(spotter_);
143 | dog = DogLike(dog_);
144 | ilk = ilk_;
145 | buf = RAY;
146 | wards[msg.sender] = 1;
147 | emit Rely(msg.sender);
148 | }
149 |
150 | // --- Synchronization ---
151 | modifier lock() {
152 | require(locked == 0, "Clipper/system-locked");
153 | locked = 1;
154 | _;
155 | locked = 0;
156 | }
157 |
158 | modifier isStopped(uint256 level) {
159 | require(stopped < level, "Clipper/stopped-incorrect");
160 | _;
161 | }
162 |
163 | // --- Administration ---
164 | function file(bytes32 what, uint256 data) external auth lock {
165 | if (what == "buf") {
166 | buf = data;
167 | } else if (what == "tail") {
168 | tail = data;
169 | } // Time elapsed before auction reset
170 | else if (what == "cusp") {
171 | cusp = data;
172 | } // Percentage drop before auction reset
173 | else if (what == "chip") {
174 | chip = uint64(data);
175 | } // Percentage of tab to incentivize (max: 2^64 - 1 => 18.xxx WAD = 18xx%)
176 | else if (what == "tip") {
177 | tip = uint192(data);
178 | } // Flat fee to incentivize keepers (max: 2^192 - 1 => 6.277T RAD)
179 | else if (what == "stopped") {
180 | stopped = data;
181 | } // Set breaker (0, 1, 2, or 3)
182 | else {
183 | revert("Clipper/file-unrecognized-param");
184 | }
185 | emit File(what, data);
186 | }
187 |
188 | function file(bytes32 what, address data) external auth lock {
189 | if (what == "spotter") spotter = SpotterLike(data);
190 | else if (what == "dog") dog = DogLike(data);
191 | else if (what == "vow") vow = data;
192 | else if (what == "calc") calc = AbacusLike(data);
193 | else revert("Clipper/file-unrecognized-param");
194 | emit File(what, data);
195 | }
196 |
197 | // --- Math ---
198 | uint256 constant BLN = 10 ** 9;
199 | uint256 constant WAD = 10 ** 18;
200 | uint256 constant RAY = 10 ** 27;
201 |
202 | function min(uint256 x, uint256 y) internal pure returns (uint256 z) {
203 | z = x <= y ? x : y;
204 | }
205 |
206 | function mul(uint256 x, uint256 y) internal pure returns (uint256 z) {
207 | require(y == 0 || (z = x * y) / y == x);
208 | }
209 |
210 | function wmul(uint256 x, uint256 y) internal pure returns (uint256 z) {
211 | z = mul(x, y) / WAD;
212 | }
213 |
214 | function rmul(uint256 x, uint256 y) internal pure returns (uint256 z) {
215 | z = mul(x, y) / RAY;
216 | }
217 |
218 | function rdiv(uint256 x, uint256 y) internal pure returns (uint256 z) {
219 | z = mul(x, RAY) / y;
220 | }
221 |
222 | // --- Auction ---
223 |
224 | // get the price directly from the OSM
225 | // Could get this from rmul(Vat.ilks(ilk).spot, Spotter.mat()) instead, but
226 | // if mat has changed since the last poke, the resulting value will be
227 | // incorrect.
228 | function getFeedPrice() internal returns (uint256 feedPrice) {
229 | (PipLike pip,) = spotter.ilks(ilk);
230 | (bytes32 val, bool has) = pip.peek();
231 | require(has, "Clipper/invalid-price");
232 | feedPrice = rdiv(mul(uint256(val), BLN), spotter.par());
233 | }
234 |
235 | // start an auction
236 | // note: trusts the caller to transfer collateral to the contract
237 | // The starting price `top` is obtained as follows:
238 | //
239 | // top = val * buf / par
240 | //
241 | // Where `val` is the collateral's unitary value in USD, `buf` is a
242 | // multiplicative factor to increase the starting price, and `par` is a
243 | // reference per ZAR.
244 | function kick(
245 | uint256 tab, // Debt [rad]
246 | uint256 lot, // Collateral [wad]
247 | address usr, // Address that will receive any leftover collateral
248 | address kpr // Address that will receive incentives
249 | ) external auth lock isStopped(1) returns (uint256 id) {
250 | // Input validation
251 | require(tab > 0, "Clipper/zero-tab");
252 | require(lot > 0, "Clipper/zero-lot");
253 | require(usr != address(0), "Clipper/zero-usr");
254 | id = ++kicks;
255 | require(id > 0, "Clipper/overflow");
256 |
257 | active.push(id);
258 |
259 | sales[id].pos = active.length - 1;
260 |
261 | sales[id].tab = tab;
262 | sales[id].lot = lot;
263 | sales[id].usr = usr;
264 | sales[id].tic = uint96(block.timestamp);
265 |
266 | uint256 top;
267 | top = rmul(getFeedPrice(), buf);
268 | require(top > 0, "Clipper/zero-top-price");
269 | sales[id].top = top;
270 |
271 | // incentive to kick auction
272 | uint256 _tip = tip;
273 | uint256 _chip = chip;
274 | uint256 coin;
275 | if (_tip > 0 || _chip > 0) {
276 | coin = _tip + wmul(tab, _chip);
277 | vat.suck(vow, kpr, coin);
278 | }
279 |
280 | emit Kick(id, top, tab, lot, usr, kpr, coin);
281 | }
282 |
283 | // Reset an auction
284 | // See `kick` above for an explanation of the computation of `top`.
285 | function redo(
286 | uint256 id, // id of the auction to reset
287 | address kpr // Address that will receive incentives
288 | ) external lock isStopped(2) {
289 | // Read auction data
290 | address usr = sales[id].usr;
291 | uint96 tic = sales[id].tic;
292 | uint256 top = sales[id].top;
293 |
294 | require(usr != address(0), "Clipper/not-running-auction");
295 |
296 | // Check that auction needs reset
297 | // and compute current price [ray]
298 | (bool done,) = status(tic, top);
299 | require(done, "Clipper/cannot-reset");
300 |
301 | uint256 tab = sales[id].tab;
302 | uint256 lot = sales[id].lot;
303 | sales[id].tic = uint96(block.timestamp);
304 |
305 | uint256 feedPrice = getFeedPrice();
306 | top = rmul(feedPrice, buf);
307 | require(top > 0, "Clipper/zero-top-price");
308 | sales[id].top = top;
309 |
310 | // incentive to redo auction
311 | uint256 _tip = tip;
312 | uint256 _chip = chip;
313 | uint256 coin;
314 | if (_tip > 0 || _chip > 0) {
315 | uint256 _chost = chost;
316 | if (tab >= _chost && mul(lot, feedPrice) >= _chost) {
317 | coin = _tip + wmul(tab, _chip);
318 | vat.suck(vow, kpr, coin);
319 | }
320 | }
321 |
322 | emit Redo(id, top, tab, lot, usr, kpr, coin);
323 | }
324 |
325 | // Buy up to `amt` of collateral from the auction indexed by `id`.
326 | //
327 | // Auctions will not collect more ZAR than their assigned ZAR target,`tab`;
328 | // thus, if `amt` would cost more ZAR than `tab` at the current price, the
329 | // amount of collateral purchased will instead be just enough to collect `tab` ZAR.
330 | //
331 | // To avoid partial purchases resulting in very small leftover auctions that will
332 | // never be cleared, any partial purchase must leave at least `Clipper.chost`
333 | // remaining ZAR target. `chost` is an asynchronously updated value equal to
334 | // (Vat.dust * Dog.chop(ilk) / WAD) where the values are understood to be determined
335 | // by whatever they were when Clipper.upchost() was last called. Purchase amounts
336 | // will be minimally decreased when necessary to respect this limit; i.e., if the
337 | // specified `amt` would leave `tab < chost` but `tab > 0`, the amount actually
338 | // purchased will be such that `tab == chost`.
339 | //
340 | // If `tab <= chost`, partial purchases are no longer possible; that is, the remaining
341 | // collateral can only be purchased entirely, or not at all.
342 | function take(
343 | uint256 id, // Auction id
344 | uint256 amt, // Upper limit on amount of collateral to buy [wad]
345 | uint256 max, // Maximum acceptable price (ZAR / collateral) [ray]
346 | address who, // Receiver of collateral and external call address
347 | bytes calldata data // Data to pass in external call; if length 0, no call is done
348 | ) external lock isStopped(3) {
349 | address usr = sales[id].usr;
350 | uint96 tic = sales[id].tic;
351 |
352 | require(usr != address(0), "Clipper/not-running-auction");
353 |
354 | uint256 price;
355 | {
356 | bool done;
357 | (done, price) = status(tic, sales[id].top);
358 |
359 | // Check that auction doesn't need reset
360 | require(!done, "Clipper/needs-reset");
361 | }
362 |
363 | // Ensure price is acceptable to buyer
364 | require(max >= price, "Clipper/too-expensive");
365 |
366 | uint256 lot = sales[id].lot;
367 | uint256 tab = sales[id].tab;
368 | uint256 owe;
369 |
370 | {
371 | // Purchase as much as possible, up to amt
372 | uint256 slice = min(lot, amt); // slice <= lot
373 |
374 | // ZAR needed to buy a slice of this sale
375 | owe = mul(slice, price);
376 |
377 | // Don't collect more than tab of ZAR
378 | if (owe > tab) {
379 | // Total debt will be paid
380 | owe = tab; // owe' <= owe
381 | // Adjust slice
382 | slice = owe / price; // slice' = owe' / price <= owe / price == slice <= lot
383 | } else if (owe < tab && slice < lot) {
384 | // If slice == lot => auction completed => dust doesn't matter
385 | uint256 _chost = chost;
386 | if (tab - owe < _chost) {
387 | // safe as owe < tab
388 | // If tab <= chost, buyers have to take the entire lot.
389 | require(tab > _chost, "Clipper/no-partial-purchase");
390 | // Adjust amount to pay
391 | owe = tab - _chost; // owe' <= owe
392 | // Adjust slice
393 | slice = owe / price; // slice' = owe' / price < owe / price == slice < lot
394 | }
395 | }
396 |
397 | // Calculate remaining tab after operation
398 | tab = tab - owe; // safe since owe <= tab
399 | // Calculate remaining lot after operation
400 | lot = lot - slice;
401 |
402 | // Send collateral to who
403 | vat.flux(ilk, address(this), who, slice);
404 |
405 | // Do external call (if data is defined) but to be
406 | // extremely careful we don't allow to do it to the two
407 | // contracts which the Clipper needs to be authorized
408 | DogLike dog_ = dog;
409 | if (data.length > 0 && who != address(vat) && who != address(dog_)) {
410 | ClipperCallee(who).clipperCall(msg.sender, owe, slice, data);
411 | }
412 |
413 | // Get ZAR from caller
414 | vat.move(msg.sender, vow, owe);
415 |
416 | // Removes ZAR out for liquidation from accumulator
417 | dog_.digs(ilk, lot == 0 ? tab + owe : owe);
418 | }
419 |
420 | if (lot == 0) {
421 | _remove(id);
422 | } else if (tab == 0) {
423 | vat.flux(ilk, address(this), usr, lot);
424 | _remove(id);
425 | } else {
426 | sales[id].tab = tab;
427 | sales[id].lot = lot;
428 | }
429 |
430 | emit Take(id, max, price, owe, tab, lot, usr);
431 | }
432 |
433 | function _remove(uint256 id) internal {
434 | uint256 _move = active[active.length - 1];
435 | if (id != _move) {
436 | uint256 _index = sales[id].pos;
437 | active[_index] = _move;
438 | sales[_move].pos = _index;
439 | }
440 | active.pop();
441 | delete sales[id];
442 | }
443 |
444 | // The number of active auctions
445 | function count() external view returns (uint256) {
446 | return active.length;
447 | }
448 |
449 | // Return the entire array of active auctions
450 | function list() external view returns (uint256[] memory) {
451 | return active;
452 | }
453 |
454 | // Externally returns boolean for if an auction needs a redo and also the current price
455 | function getStatus(uint256 id) external view returns (bool needsRedo, uint256 price, uint256 lot, uint256 tab) {
456 | // Read auction data
457 | address usr = sales[id].usr;
458 | uint96 tic = sales[id].tic;
459 |
460 | bool done;
461 | (done, price) = status(tic, sales[id].top);
462 |
463 | needsRedo = usr != address(0) && done;
464 | lot = sales[id].lot;
465 | tab = sales[id].tab;
466 | }
467 |
468 | // Internally returns boolean for if an auction needs a redo
469 | function status(uint96 tic, uint256 top) internal view returns (bool done, uint256 price) {
470 | price = calc.price(top, block.timestamp - tic);
471 | done = ((block.timestamp - tic) > tail || rdiv(price, top) < cusp);
472 | }
473 |
474 | // Public function to update the cached dust*chop value.
475 | function upchost() external {
476 | (,,,, uint256 _dust) = VatLike(vat).ilks(ilk);
477 | chost = wmul(_dust, dog.chop(ilk));
478 | }
479 |
480 | // Cancel an auction during ES or via governance action.
481 | function yank(uint256 id) external auth lock {
482 | require(sales[id].usr != address(0), "Clipper/not-running-auction");
483 | dog.digs(ilk, sales[id].tab);
484 | vat.flux(ilk, address(this), msg.sender, sales[id].lot);
485 | _remove(id);
486 | emit Yank(id);
487 | }
488 | }
489 |
--------------------------------------------------------------------------------