Skip to content

Commit

Permalink
Merge pull request #27 from makerdao/dev
Browse files Browse the repository at this point in the history
Add Splitter + FlapperMom => SplitterMom + DAI => USDS
  • Loading branch information
sunbreak1211 authored Sep 26, 2024
2 parents b02f177 + 582ffbd commit e136bb1
Show file tree
Hide file tree
Showing 28 changed files with 1,731 additions and 788 deletions.
30 changes: 18 additions & 12 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,39 +1,45 @@
# Dss Flappers

Implementations of MakerDao surplus auctions, triggered on `vow.flap`. The current featured Flapper is `FlapperUniV2`.
Implementations of MakerDAO surplus auctions, triggered on `vow.flap`.

### FlapperUniV2
### Splitter

Exposes a `kick` operation to be triggered periodically. Its logic withdraws `DAI` from the `vow` and buys `gem` tokens on Uniswap v2. The acquired tokens, along with a proportional amount of additional `DAI` withdrawn from the `vow`, are deposited back into the liquidity pool. Finally, the minted LP tokens are sent to a predefined `receiver` address.
Exposes a `kick` operation to be triggered periodically. Its logic withdraws `USDS` from the `vow` and splits it in two parts. The first part (`burn`) is sent to the underlying `flapper` contract to be processed by the burn engine. The second part (`WAD - burn`) is distributed as reward to a `farm` contract. The `kick` cadence is determined by the `hop` value.

Configurable Parameters:
* `burn` - The percentage of the `vow.bump` to be moved to the underlying `flapper`. For example, a value of 0.70 \* `WAD` corresponds to funneling 70% of the `USDS` to the burn engine.
* `hop` - Minimum seconds interval between kicks.
* `flapper` - The underlying burner strategy (e.g. the address of `FlapperUniV2SwapOnly`).
* `farm` - The staking rewards contract receiving the rewards.

### FlapperUniV2

Exposes an `exec` operation to be triggered periodically by the `Splitter` (at a cadence determined by `Splitter.hop()`). Its logic withdraws `USDS` from the `Splitter` and buys `gem` tokens on Uniswap v2. The acquired tokens, along with a proportional amount of `USDS` (saved from the initial withdraw) are deposited back into the liquidity pool. Finally, the minted LP tokens are sent to a predefined `receiver` address.

Configurable Parameters:
* `pip` - A reference price oracle, used for bounding the exchange rate of the swap.
* `want` - Relative multiplier of the reference price to insist on in the swap. For example, a value of 0.98 * `WAD` allows for a 2% worse price than the reference.

#### Notes:
#### Note:

* As a `kick` operation also withdraws `DAI` for depositing in the pool (and not only for swapping), it can in practice reduce the Surplus Buffer to below `vow.bump`.

* Although the Flapper interface is conformant with the Emergency Shutdown procedure and will stop operating when it is triggered, LP tokens already sent to the receiver do not have special redeeming handling. Therefore, in case the Pause Proxy is the receiver and governance does not control it, the LP tokens can be lost or seized by a governance attack.
* Although the Flapper interface is conformant with the Emergency Shutdown procedure and will stop operating when it is triggered, LP tokens already sent to the `receiver` do not have special redeeming handling. Therefore, in case the Pause Proxy is the `receiver` and governance does not control it, the LP tokens can be lost or seized by a governance attack.

### FlapperUniV2SwapOnly

Exposes a `kick` operation to be triggered periodically. Its logic withdraws `DAI` from the `vow` and buys `gem` tokens on Uniswap v2. The acquired tokens are sent to a predefined `receiver` address.
Exposes an `exec` operation to be triggered periodically by the `Splitter` (at a cadence determined by `Splitter.hop()`). Its logic withdraws `USDS` from the `Splitter` and buys `gem` tokens on Uniswap v2. The acquired tokens are sent to a predefined `receiver` address.

Configurable Parameters:
* `hop` - Minimum seconds interval between kicks.
* `pip` - A reference price oracle, used for bounding the exchange rate of the swap.
* `want` - Relative multiplier of the reference price to insist on in the swap. For example, a value of 0.98 * `WAD` allows for a 2% worse price than the reference.

### FlapperMom
### SplitterMom

This contract allows bypassing the governance delay when disabling the Flapper in an emergency.
This contract allows bypassing the governance delay when disabling the Splitter in an emergency.

### OracleWrapper

Allows for scaling down an oracle price by a certain value. This can be useful when the `gem` is a redenominated version of an existing token, which already has a reliable oracle.

### General Note:

* Availability and accounting of the withdrawn `DAI` is the responsibility of the `vow`. At the time of a `kick`, the `vow` is expected to hold at least the swapped amount (`vow.bump`) over the configured flapping threshold (`vow.hump`).
* Availability and accounting of the withdrawn `USDS` is the responsibility of the `vow`. At the time of a `kick`, the `vow` is expected to hold at least the drawn amount (`vow.bump`) over the configured flapping threshold (`vow.hump`).
Binary file added audit/20240703-cantina-report-maker-flappers.pdf
Binary file not shown.
Binary file not shown.
Binary file not shown.
40 changes: 25 additions & 15 deletions deploy/FlapperDeploy.sol
Original file line number Diff line number Diff line change
Expand Up @@ -14,41 +14,36 @@
// 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.16;
pragma solidity ^0.8.21;

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

import { FlapperInstance } from "./FlapperInstance.sol";
import { SplitterInstance } from "./SplitterInstance.sol";
import { FlapperUniV2 } from "src/FlapperUniV2.sol";
import { FlapperUniV2SwapOnly } from "src/FlapperUniV2SwapOnly.sol";
import { FlapperMom } from "src/FlapperMom.sol";
import { SplitterMom } from "src/SplitterMom.sol";
import { OracleWrapper } from "src/OracleWrapper.sol";
import { Splitter } from "src/Splitter.sol";

// Deploy a Flapper instance
library FlapperDeploy {

function deployFlapperUniV2(
address deployer,
address owner,
address daiJoin,
address spotter,
address usds,
address gem,
address pair,
address receiver,
bool swapOnly
) internal returns (FlapperInstance memory flapperInstance) {
address _flapper =
swapOnly ? address(new FlapperUniV2SwapOnly(daiJoin, spotter, gem, pair, receiver))
: address(new FlapperUniV2(daiJoin, spotter, gem, pair, receiver))
) internal returns (address flapper) {
flapper =
swapOnly ? address(new FlapperUniV2SwapOnly(spotter, usds, gem, pair, receiver))
: address(new FlapperUniV2(spotter, usds, gem, pair, receiver))
;
address _mom = address(new FlapperMom(_flapper));

ScriptTools.switchOwner(_flapper, deployer, owner);
DSAuthAbstract(_mom).setOwner(owner);

flapperInstance.flapper = _flapper;
flapperInstance.mom = _mom;
ScriptTools.switchOwner(flapper, deployer, owner);
}

function deployOracleWrapper(
Expand All @@ -58,4 +53,19 @@ library FlapperDeploy {
) internal returns (address wrapper) {
wrapper = address(new OracleWrapper(pip, flapper, divisor));
}

function deploySplitter(
address deployer,
address owner,
address usdsJoin
) internal returns (SplitterInstance memory splitterInstance) {
address splitter = address(new Splitter(usdsJoin));
address mom = address(new SplitterMom(splitter));

ScriptTools.switchOwner(splitter, deployer, owner);
DSAuthAbstract(mom).setOwner(owner);

splitterInstance.splitter = splitter;
splitterInstance.mom = mom;
}
}
168 changes: 132 additions & 36 deletions deploy/FlapperInit.sol
Original file line number Diff line number Diff line change
Expand Up @@ -17,26 +17,28 @@
pragma solidity >=0.8.0;

import { DssInstance } from "dss-test/MCD.sol";
import { FlapperInstance } from "./FlapperInstance.sol";
import { SplitterInstance } from "./SplitterInstance.sol";

interface FlapperUniV2Like {
function vat() external view returns (address);
function daiJoin() external view returns (address);
function spotter() external view returns (address);
function pip() external view returns (address);
function pair() external view returns (address);
function spotter() external view returns (address);
function usds() external view returns (address);
function gem() external view returns (address);
function receiver() external view returns (address);
function pair() external view returns (address);
function rely(address) external;
function file(bytes32, uint256) external;
function file(bytes32, address) external;
}

interface FlapperMomLike {
interface SplitterMomLike {
function splitter() external view returns (address);
function setAuthority(address) external;
}

interface OracleWrapperLike {
function pip() external view returns (address);
function divisor() external view returns (uint256);
}

interface PipLike {
Expand All @@ -48,68 +50,162 @@ interface PairLike {
function token1() external view returns (address);
}

interface DaiJoinLike {
function dai() external view returns (address);
interface UsdsJoinLike {
function usds() external view returns (address);
}

interface SplitterLike {
function live() external view returns (uint256);
function vat() external view returns (address);
function usdsJoin() external view returns (address);
function hop() external view returns (uint256);
function rely(address) external;
function file(bytes32, uint256) external;
function file(bytes32, address) external;
}

interface FarmLike {
function rewardsToken() external view returns (address);
function setRewardsDistribution(address) external;
function setRewardsDuration(uint256) external;
}

struct FlapperUniV2Config {
uint256 hop;
uint256 want;
address pip;
address pair;
address usds;
address splitter;
bytes32 prevChainlogKey;
bytes32 chainlogKey;
}

struct FarmConfig {
address splitter;
address usdsJoin;
uint256 hop;
bytes32 prevChainlogKey;
bytes32 chainlogKey;
}

struct SplitterConfig {
uint256 hump;
uint256 bump;
address daiJoin;
uint256 hop;
uint256 burn;
address usdsJoin;
bytes32 splitterChainlogKey;
bytes32 prevMomChainlogKey;
bytes32 momChainlogKey;
}

library FlapperInit {
uint256 constant WAD = 10 ** 18;
uint256 constant RAY = 10 ** 27;

function initFlapperUniV2(
DssInstance memory dss,
FlapperInstance memory flapperInstance,
address flapper_,
FlapperUniV2Config memory cfg
) internal {
FlapperUniV2Like flapper = FlapperUniV2Like(flapperInstance.flapper);
FlapperMomLike mom = FlapperMomLike(flapperInstance.mom);
FlapperUniV2Like flapper = FlapperUniV2Like(flapper_);

// Sanity checks
require(flapper.vat() == address(dss.vat), "Flapper vat mismatch");
require(flapper.daiJoin() == cfg.daiJoin, "Flapper daiJoin mismatch");
require(flapper.spotter() == address(dss.spotter), "Flapper spotter mismatch");
require(flapper.spotter() == address(dss.spotter), "Flapper spotter mismatch");
require(flapper.usds() == cfg.usds, "Flapper usds mismatch");
require(flapper.pair() == cfg.pair, "Flapper pair mismatch");
require(flapper.receiver() == dss.chainlog.getAddress("MCD_PAUSE_PROXY"), "Flapper receiver mismatch");

PairLike pair = PairLike(flapper.pair());
address dai = DaiJoinLike(cfg.daiJoin).dai();
(address pairDai, address pairGem) = pair.token0() == dai ? (pair.token0(), pair.token1())
: (pair.token1(), pair.token0());
require(pairDai == dai, "Dai mismatch");
require(pairGem == flapper.gem(), "Gem mismatch");
(address pairUsds, address pairGem) = pair.token0() == cfg.usds ? (pair.token0(), pair.token1())
: (pair.token1(), pair.token0());
require(pairUsds == cfg.usds, "Usds mismatch");
require(pairGem == flapper.gem(), "Gem mismatch");

require(cfg.hop >= 5 minutes, "hop too low");
require(cfg.want >= WAD * 90 / 100, "want too low");
require(cfg.hump > 0, "hump too low");

flapper.file("hop", cfg.hop);
flapper.file("want", cfg.want);
flapper.file("pip", cfg.pip);
flapper.rely(address(dss.vow));
flapper.rely(address(mom));
flapper.rely(cfg.splitter);

dss.vow.file("flapper", address(flapper));
dss.vow.file("hump", cfg.hump);
dss.vow.file("bump", cfg.bump);

mom.setAuthority(dss.chainlog.getAddress("MCD_ADM"));
SplitterLike(cfg.splitter).file("flapper", flapper_);

dss.chainlog.setAddress("MCD_FLAP", address(flapper));
dss.chainlog.setAddress("FLAPPER_MOM", address(mom));
if (cfg.prevChainlogKey != bytes32(0)) dss.chainlog.removeAddress(cfg.prevChainlogKey);
dss.chainlog.setAddress(cfg.chainlogKey, flapper_);
}

function initDirectOracle(address flapper) internal {
PipLike(FlapperUniV2Like(flapper).pip()).kiss(flapper);
}

function initOracleWrapper(DssInstance memory dss, address wrapper, bytes32 clKey) internal {
PipLike(OracleWrapperLike(wrapper).pip()).kiss(wrapper);
dss.chainlog.setAddress(clKey, wrapper);
function initOracleWrapper(
DssInstance memory dss,
address wrapper_,
uint256 divisor,
bytes32 clKey
) internal {
OracleWrapperLike wrapper = OracleWrapperLike(wrapper_);
require(wrapper.divisor() == divisor, "Wrapper divisor mismatch"); // Sanity check
PipLike(wrapper.pip()).kiss(wrapper_);
dss.chainlog.setAddress(clKey, wrapper_);
}

function setFarm(
DssInstance memory dss,
address farm_,
FarmConfig memory cfg
) internal {
FarmLike farm = FarmLike(farm_);
SplitterLike splitter = SplitterLike(cfg.splitter);

require(farm.rewardsToken() == UsdsJoinLike(cfg.usdsJoin).usds(), "Farm rewards not usds");
// Staking token is checked in the Lockstake script

// The following two checks enforce the initSplitter function has to be called first
require(cfg.hop >= 5 minutes, "hop too low");
require(cfg.hop == splitter.hop(), "hop mismatch");

splitter.file("farm", farm_);

farm.setRewardsDistribution(cfg.splitter);
farm.setRewardsDuration(cfg.hop);

if (cfg.prevChainlogKey != bytes32(0)) dss.chainlog.removeAddress(cfg.prevChainlogKey);
dss.chainlog.setAddress(cfg.chainlogKey, farm_);
}

function initSplitter(
DssInstance memory dss,
SplitterInstance memory splitterInstance,
SplitterConfig memory cfg
) internal {
SplitterLike splitter = SplitterLike(splitterInstance.splitter);
SplitterMomLike mom = SplitterMomLike(splitterInstance.mom);

// Sanity checks
require(splitter.live() == 1, "Splitter not live");
require(splitter.vat() == address(dss.vat), "Splitter vat mismatch");
require(splitter.usdsJoin() == cfg.usdsJoin, "Splitter usdsJoin mismatch");
require(mom.splitter() == splitterInstance.splitter, "Mom splitter mismatch");

require(cfg.hump > 0, "hump too low");
require(cfg.bump % RAY == 0, "bump not multiple of RAY");
require(cfg.hop >= 5 minutes, "hop too low");
require(cfg.burn <= WAD, "burn too high");

splitter.file("hop", cfg.hop);
splitter.file("burn", cfg.burn);
splitter.rely(address(mom));
splitter.rely(address(dss.vow));

dss.vow.file("flapper", splitterInstance.splitter);
dss.vow.file("hump", cfg.hump);
dss.vow.file("bump", cfg.bump);

mom.setAuthority(dss.chainlog.getAddress("MCD_ADM"));

dss.chainlog.setAddress(cfg.splitterChainlogKey, splitterInstance.splitter);
if (cfg.prevMomChainlogKey != bytes32(0)) dss.chainlog.removeAddress(cfg.prevMomChainlogKey);
dss.chainlog.setAddress(cfg.momChainlogKey, address(mom));
}
}
4 changes: 2 additions & 2 deletions deploy/FlapperInstance.sol → deploy/SplitterInstance.sol
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@

pragma solidity >=0.8.0;

struct FlapperInstance {
address flapper;
struct SplitterInstance {
address splitter;
address mom;
}
8 changes: 4 additions & 4 deletions foundry.toml
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
[profile.default]
src = 'src'
out = 'out'
libs = ['lib']
solc = "0.8.16"
src = "src"
out = "out"
libs = ["lib"]
solc = "0.8.21"
optimizer = true
optimizer_runs = 200
verbosity = 3
Expand Down
Loading

0 comments on commit e136bb1

Please sign in to comment.