Skip to content

Commit ed90ec2

Browse files
sunbreak1211telome
andauthored
Add Kicker (#32)
* Add Kicker * Minor changes and changes for oracle wrapper test to not fail * Fix init script + tests changes * Minor changes to tests * Update src/Kicker.sol Co-authored-by: telome <[email protected]> * Minor changes * Add README * Minor changes * Add note to REAME * Fix typos Co-authored-by: telome <[email protected]> * Add audit reports * Enforce evmVersion in foundry.toml --------- Co-authored-by: telome <[email protected]>
1 parent e136bb1 commit ed90ec2

10 files changed

+583
-8
lines changed

README.md

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,22 @@
11
# Dss Flappers
22

3-
Implementations of MakerDAO surplus auctions, triggered on `vow.flap`.
3+
Implementations of MakerDAO surplus auctions, triggered on `vow.flap` or via new `kicker.flap`.
4+
5+
### Kicker
6+
7+
Implements a Splitter/Flapper calling function that replaces `Vow.flap` and can be called even when `Vat.dai(Vow) < Vat.sin(Vow)`.
8+
The triggering threshold is assumed to be carefully set up and controlled by governance, ensuring there is enough surplus secured (in the Vow based surplus buffer or elsewhere).
9+
10+
Configurable Parameters:
11+
* `kbump` - Fixed lot size (`RAD` precision)
12+
* `khump` - Flap threshold (`RAD` precision, signed integer value).
13+
14+
Note: It is assumed that the `Flop` auctions mechanism is disabled and remains in that state. As otherwise it could collide with the above mechanism.
15+
Currently this is done through the configuration of `Vow.sump` as max uint256 (aka infinity).
16+
17+
Note 2: Rate limiting is ensured via the `Splitter`.
18+
19+
Note 3: Stop functionality is implemented via the `Splitter.cage` function (reason to leave the `Splitter` as `flapper` reference in the `Vow` and the `wards` still set). However, even if `Vow.cage` remains functional, in order to execute `End.cage` it is still necessary a deep analysis and prior actions in different modules to be executed successfully.
420

521
### Splitter
622

-581 KB
Binary file not shown.
1.17 MB
Binary file not shown.
406 KB
Binary file not shown.

deploy/FlapperDeploy.sol

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
pragma solidity ^0.8.21;
1818

1919
import "dss-interfaces/Interfaces.sol";
20+
import { MCD, DssInstance } from "dss-test/MCD.sol";
2021
import { ScriptTools } from "dss-test/ScriptTools.sol";
2122

2223
import { SplitterInstance } from "./SplitterInstance.sol";
@@ -25,9 +26,12 @@ import { FlapperUniV2SwapOnly } from "src/FlapperUniV2SwapOnly.sol";
2526
import { SplitterMom } from "src/SplitterMom.sol";
2627
import { OracleWrapper } from "src/OracleWrapper.sol";
2728
import { Splitter } from "src/Splitter.sol";
29+
import { Kicker } from "src/Kicker.sol";
2830

2931
library FlapperDeploy {
3032

33+
address constant LOG = 0xdA0Ab1e0017DEbCd72Be8599041a2aa3bA7e740F;
34+
3135
function deployFlapperUniV2(
3236
address deployer,
3337
address owner,
@@ -68,4 +72,17 @@ library FlapperDeploy {
6872
splitterInstance.splitter = splitter;
6973
splitterInstance.mom = mom;
7074
}
75+
76+
function deployKicker(
77+
address deployer,
78+
address owner
79+
) internal returns (address kicker) {
80+
DssInstance memory dss = MCD.loadFromChainlog(LOG);
81+
82+
kicker = address(new Kicker(
83+
dss.chainlog.getAddress("MCD_VOW"),
84+
dss.chainlog.getAddress("MCD_SPLIT")));
85+
86+
ScriptTools.switchOwner(kicker, deployer, owner);
87+
}
7188
}

deploy/FlapperInit.sol

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,14 @@ interface FarmLike {
7070
function setRewardsDuration(uint256) external;
7171
}
7272

73+
interface KickerLike {
74+
function vat() external view returns (address);
75+
function vow() external view returns (address);
76+
function splitter() external view returns (address);
77+
function file(bytes32, uint256) external;
78+
function file(bytes32, int256) external;
79+
}
80+
7381
struct FlapperUniV2Config {
7482
uint256 want;
7583
address pip;
@@ -99,6 +107,12 @@ struct SplitterConfig {
99107
bytes32 momChainlogKey;
100108
}
101109

110+
struct KickerConfig {
111+
int256 khump;
112+
uint256 kbump;
113+
bytes32 chainlogKey;
114+
}
115+
102116
library FlapperInit {
103117
uint256 constant WAD = 10 ** 18;
104118
uint256 constant RAY = 10 ** 27;
@@ -208,4 +222,29 @@ library FlapperInit {
208222
if (cfg.prevMomChainlogKey != bytes32(0)) dss.chainlog.removeAddress(cfg.prevMomChainlogKey);
209223
dss.chainlog.setAddress(cfg.momChainlogKey, address(mom));
210224
}
225+
226+
function initKicker(
227+
DssInstance memory dss,
228+
address kicker,
229+
KickerConfig memory cfg
230+
) internal {
231+
require(cfg.kbump % RAY == 0, "kbump not multiple of RAY");
232+
233+
address splitter = dss.chainlog.getAddress("MCD_SPLIT");
234+
require(KickerLike(kicker).vow() == address(dss.vow), "vow mismatch");
235+
require(KickerLike(kicker).splitter() == splitter, "splitter mismatch");
236+
237+
dss.vow.file("bump", 0);
238+
dss.vow.file("hump", type(uint256).max);
239+
dss.vow.file("dump", 0);
240+
dss.vow.file("sump", type(uint256).max);
241+
242+
KickerLike(kicker).file("khump", cfg.khump);
243+
KickerLike(kicker).file("kbump", cfg.kbump);
244+
245+
dss.vat.rely(kicker);
246+
SplitterLike(splitter).rely(kicker);
247+
248+
dss.chainlog.setAddress(cfg.chainlogKey, kicker);
249+
}
211250
}

foundry.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ libs = ["lib"]
55
solc = "0.8.21"
66
optimizer = true
77
optimizer_runs = 200
8+
evmVersion = "shanghai"
89
verbosity = 3
910

1011
# See more config options https://github.com/foundry-rs/foundry/tree/master/config

src/Kicker.sol

Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
// SPDX-FileCopyrightText: © 2025 Dai Foundation <www.daifoundation.org>
2+
// SPDX-License-Identifier: AGPL-3.0-or-later
3+
//
4+
// This program is free software: you can redistribute it and/or modify
5+
// it under the terms of the GNU Affero General Public License as published by
6+
// the Free Software Foundation, either version 3 of the License, or
7+
// (at your option) any later version.
8+
//
9+
// This program is distributed in the hope that it will be useful,
10+
// but WITHOUT ANY WARRANTY; without even the implied warranty of
11+
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12+
// GNU Affero General Public License for more details.
13+
//
14+
// You should have received a copy of the GNU Affero General Public License
15+
// along with this program. If not, see <https://www.gnu.org/licenses/>.
16+
17+
pragma solidity ^0.8.21;
18+
19+
interface VatLike {
20+
function dai(address) external view returns (uint256);
21+
function sin(address) external view returns (uint256);
22+
function hope(address) external;
23+
function suck(address, address, uint256) external;
24+
}
25+
26+
interface SplitterLike {
27+
function vat() external view returns (VatLike);
28+
function kick(uint256, uint256) external returns (uint256);
29+
}
30+
31+
contract Kicker {
32+
// --- storage variables ---
33+
34+
mapping(address usr => uint256 allowed) public wards;
35+
uint256 public kbump; // Fixed lot size [rad]
36+
int256 public khump; // Flap threshold [rad]
37+
38+
// --- immutables ---
39+
40+
VatLike public immutable vat;
41+
address public immutable vow;
42+
SplitterLike public immutable splitter;
43+
44+
// --- constructor ---
45+
46+
constructor(address vow_, address splitter_) {
47+
vow = vow_;
48+
splitter = SplitterLike(splitter_);
49+
vat = splitter.vat();
50+
vat.hope(splitter_);
51+
52+
wards[msg.sender] = 1;
53+
emit Rely(msg.sender);
54+
}
55+
56+
// --- events ---
57+
58+
event Rely(address indexed usr);
59+
event Deny(address indexed usr);
60+
event File(bytes32 indexed what, uint256 data);
61+
event File(bytes32 indexed what, int256 data);
62+
63+
// --- modifiers ---
64+
65+
modifier auth {
66+
require(wards[msg.sender] == 1, "Kicker/not-authorized");
67+
_;
68+
}
69+
70+
// --- internals ---
71+
72+
function _toInt256(uint256 x) internal pure returns (int256 y) {
73+
require(x <= uint256(type(int256).max), "Kicker/overflow");
74+
y = int256(x);
75+
}
76+
77+
// --- administration ---
78+
79+
function rely(address usr) external auth {
80+
wards[usr] = 1;
81+
emit Rely(usr);
82+
}
83+
84+
function deny(address usr) external auth {
85+
wards[usr] = 0;
86+
emit Deny(usr);
87+
}
88+
89+
function file(bytes32 what, uint256 data) external auth {
90+
if (what == "kbump") {
91+
kbump = data;
92+
} else revert("Kicker/file-unrecognized-param");
93+
emit File(what, data);
94+
}
95+
96+
function file(bytes32 what, int256 data) external auth {
97+
if (what == "khump") {
98+
khump = data;
99+
} else revert("Kicker/file-unrecognized-param");
100+
emit File(what, data);
101+
}
102+
103+
// --- execution ---
104+
105+
function flap() external returns (uint256 id) {
106+
require(_toInt256(vat.dai(vow)) >= _toInt256(vat.sin(vow)) + _toInt256(kbump) + khump, "Kicker/flap-threshold-not-reached");
107+
vat.suck(vow, address(this), kbump);
108+
id = splitter.kick(kbump, 0);
109+
}
110+
}

0 commit comments

Comments
 (0)