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