Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 17 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,22 @@
# Dss Flappers

Implementations of MakerDAO surplus auctions, triggered on `vow.flap`.
Implementations of MakerDAO surplus auctions, triggered on `vow.flap` or via new `kicker.flap`.

### Kicker

Implements a Splitter/Flapper calling function that replaces `Vow.flap` and can be called even when `Vat.dai(Vow) < Vat.sin(Vow)`.
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).

Configurable Parameters:
* `kbump` - Fixed lot size (`RAD` precision)
* `khump` - Flap threshold (`RAD` precision, signed integer value).

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.
Currently this is done through the configuration of `Vow.sump` as max uint256 (aka infinity).

Note 2: Rate limiting is ensured via the `Splitter`.

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.

### Splitter

Expand Down
Binary file not shown.
Binary file not shown.
Binary file added audit/20251023-cantina-report-sky-flappers.pdf
Binary file not shown.
17 changes: 17 additions & 0 deletions deploy/FlapperDeploy.sol
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
pragma solidity ^0.8.21;

import "dss-interfaces/Interfaces.sol";
import { MCD, DssInstance } from "dss-test/MCD.sol";
import { ScriptTools } from "dss-test/ScriptTools.sol";

import { SplitterInstance } from "./SplitterInstance.sol";
Expand All @@ -25,9 +26,12 @@ import { FlapperUniV2SwapOnly } from "src/FlapperUniV2SwapOnly.sol";
import { SplitterMom } from "src/SplitterMom.sol";
import { OracleWrapper } from "src/OracleWrapper.sol";
import { Splitter } from "src/Splitter.sol";
import { Kicker } from "src/Kicker.sol";

library FlapperDeploy {

address constant LOG = 0xdA0Ab1e0017DEbCd72Be8599041a2aa3bA7e740F;

function deployFlapperUniV2(
address deployer,
address owner,
Expand Down Expand Up @@ -68,4 +72,17 @@ library FlapperDeploy {
splitterInstance.splitter = splitter;
splitterInstance.mom = mom;
}

function deployKicker(
address deployer,
address owner
) internal returns (address kicker) {
DssInstance memory dss = MCD.loadFromChainlog(LOG);

kicker = address(new Kicker(
dss.chainlog.getAddress("MCD_VOW"),
dss.chainlog.getAddress("MCD_SPLIT")));

ScriptTools.switchOwner(kicker, deployer, owner);
}
}
39 changes: 39 additions & 0 deletions deploy/FlapperInit.sol
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,14 @@ interface FarmLike {
function setRewardsDuration(uint256) external;
}

interface KickerLike {
function vat() external view returns (address);
function vow() external view returns (address);
function splitter() external view returns (address);
function file(bytes32, uint256) external;
function file(bytes32, int256) external;
}

struct FlapperUniV2Config {
uint256 want;
address pip;
Expand Down Expand Up @@ -99,6 +107,12 @@ struct SplitterConfig {
bytes32 momChainlogKey;
}

struct KickerConfig {
int256 khump;
uint256 kbump;
bytes32 chainlogKey;
}

library FlapperInit {
uint256 constant WAD = 10 ** 18;
uint256 constant RAY = 10 ** 27;
Expand Down Expand Up @@ -208,4 +222,29 @@ library FlapperInit {
if (cfg.prevMomChainlogKey != bytes32(0)) dss.chainlog.removeAddress(cfg.prevMomChainlogKey);
dss.chainlog.setAddress(cfg.momChainlogKey, address(mom));
}

function initKicker(
DssInstance memory dss,
address kicker,
KickerConfig memory cfg
) internal {
require(cfg.kbump % RAY == 0, "kbump not multiple of RAY");

address splitter = dss.chainlog.getAddress("MCD_SPLIT");
require(KickerLike(kicker).vow() == address(dss.vow), "vow mismatch");
require(KickerLike(kicker).splitter() == splitter, "splitter mismatch");

dss.vow.file("bump", 0);
dss.vow.file("hump", type(uint256).max);
dss.vow.file("dump", 0);
dss.vow.file("sump", type(uint256).max);

KickerLike(kicker).file("khump", cfg.khump);
KickerLike(kicker).file("kbump", cfg.kbump);

dss.vat.rely(kicker);
SplitterLike(splitter).rely(kicker);

dss.chainlog.setAddress(cfg.chainlogKey, kicker);
}
}
1 change: 1 addition & 0 deletions foundry.toml
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ libs = ["lib"]
solc = "0.8.21"
optimizer = true
optimizer_runs = 200
evmVersion = "shanghai"
verbosity = 3

# See more config options https://github.com/foundry-rs/foundry/tree/master/config
110 changes: 110 additions & 0 deletions src/Kicker.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
// SPDX-FileCopyrightText: © 2025 Dai Foundation <www.daifoundation.org>
// SPDX-License-Identifier: AGPL-3.0-or-later
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.

pragma solidity ^0.8.21;

interface VatLike {
function dai(address) external view returns (uint256);
function sin(address) external view returns (uint256);
function hope(address) external;
function suck(address, address, uint256) external;
}

interface SplitterLike {
function vat() external view returns (VatLike);
function kick(uint256, uint256) external returns (uint256);
}

contract Kicker {
// --- storage variables ---

mapping(address usr => uint256 allowed) public wards;
uint256 public kbump; // Fixed lot size [rad]
int256 public khump; // Flap threshold [rad]

// --- immutables ---

VatLike public immutable vat;
address public immutable vow;
SplitterLike public immutable splitter;

// --- constructor ---

constructor(address vow_, address splitter_) {
vow = vow_;
splitter = SplitterLike(splitter_);
vat = splitter.vat();
vat.hope(splitter_);

wards[msg.sender] = 1;
emit Rely(msg.sender);
}

// --- events ---

event Rely(address indexed usr);
event Deny(address indexed usr);
event File(bytes32 indexed what, uint256 data);
event File(bytes32 indexed what, int256 data);

// --- modifiers ---

modifier auth {
require(wards[msg.sender] == 1, "Kicker/not-authorized");
_;
}

// --- internals ---

function _toInt256(uint256 x) internal pure returns (int256 y) {
require(x <= uint256(type(int256).max), "Kicker/overflow");
y = int256(x);
}

// --- administration ---

function rely(address usr) external auth {
wards[usr] = 1;
emit Rely(usr);
}

function deny(address usr) external auth {
wards[usr] = 0;
emit Deny(usr);
}

function file(bytes32 what, uint256 data) external auth {
if (what == "kbump") {
kbump = data;
} else revert("Kicker/file-unrecognized-param");
emit File(what, data);
}

function file(bytes32 what, int256 data) external auth {
if (what == "khump") {
khump = data;
} else revert("Kicker/file-unrecognized-param");
emit File(what, data);
}

// --- execution ---

function flap() external returns (uint256 id) {
require(_toInt256(vat.dai(vow)) >= _toInt256(vat.sin(vow)) + _toInt256(kbump) + khump, "Kicker/flap-threshold-not-reached");
vat.suck(vow, address(this), kbump);
id = splitter.kick(kbump, 0);
}
}
Loading