Skip to content

Commit e136bb1

Browse files
authored
Merge pull request #27 from makerdao/dev
Add Splitter + FlapperMom => SplitterMom + DAI => USDS
2 parents b02f177 + 582ffbd commit e136bb1

28 files changed

+1731
-788
lines changed

README.md

Lines changed: 18 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,39 +1,45 @@
11
# Dss Flappers
22

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

5-
### FlapperUniV2
5+
### Splitter
66

7-
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.
7+
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.
88

99
Configurable Parameters:
10+
* `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.
1011
* `hop` - Minimum seconds interval between kicks.
12+
* `flapper` - The underlying burner strategy (e.g. the address of `FlapperUniV2SwapOnly`).
13+
* `farm` - The staking rewards contract receiving the rewards.
14+
15+
### FlapperUniV2
16+
17+
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.
18+
19+
Configurable Parameters:
1120
* `pip` - A reference price oracle, used for bounding the exchange rate of the swap.
1221
* `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.
1322

14-
#### Notes:
23+
#### Note:
1524

16-
* 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`.
17-
18-
* 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.
25+
* 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.
1926

2027
### FlapperUniV2SwapOnly
2128

22-
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.
29+
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.
2330

2431
Configurable Parameters:
25-
* `hop` - Minimum seconds interval between kicks.
2632
* `pip` - A reference price oracle, used for bounding the exchange rate of the swap.
2733
* `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.
2834

29-
### FlapperMom
35+
### SplitterMom
3036

31-
This contract allows bypassing the governance delay when disabling the Flapper in an emergency.
37+
This contract allows bypassing the governance delay when disabling the Splitter in an emergency.
3238

3339
### OracleWrapper
3440

3541
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.
3642

3743
### General Note:
3844

39-
* 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`).
45+
* 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`).
483 KB
Binary file not shown.
581 KB
Binary file not shown.
310 KB
Binary file not shown.

deploy/FlapperDeploy.sol

Lines changed: 25 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -14,41 +14,36 @@
1414
// You should have received a copy of the GNU Affero General Public License
1515
// along with this program. If not, see <https://www.gnu.org/licenses/>.
1616

17-
pragma solidity ^0.8.16;
17+
pragma solidity ^0.8.21;
1818

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

22-
import { FlapperInstance } from "./FlapperInstance.sol";
22+
import { SplitterInstance } from "./SplitterInstance.sol";
2323
import { FlapperUniV2 } from "src/FlapperUniV2.sol";
2424
import { FlapperUniV2SwapOnly } from "src/FlapperUniV2SwapOnly.sol";
25-
import { FlapperMom } from "src/FlapperMom.sol";
25+
import { SplitterMom } from "src/SplitterMom.sol";
2626
import { OracleWrapper } from "src/OracleWrapper.sol";
27+
import { Splitter } from "src/Splitter.sol";
2728

28-
// Deploy a Flapper instance
2929
library FlapperDeploy {
3030

3131
function deployFlapperUniV2(
3232
address deployer,
3333
address owner,
34-
address daiJoin,
3534
address spotter,
35+
address usds,
3636
address gem,
3737
address pair,
3838
address receiver,
3939
bool swapOnly
40-
) internal returns (FlapperInstance memory flapperInstance) {
41-
address _flapper =
42-
swapOnly ? address(new FlapperUniV2SwapOnly(daiJoin, spotter, gem, pair, receiver))
43-
: address(new FlapperUniV2(daiJoin, spotter, gem, pair, receiver))
40+
) internal returns (address flapper) {
41+
flapper =
42+
swapOnly ? address(new FlapperUniV2SwapOnly(spotter, usds, gem, pair, receiver))
43+
: address(new FlapperUniV2(spotter, usds, gem, pair, receiver))
4444
;
45-
address _mom = address(new FlapperMom(_flapper));
4645

47-
ScriptTools.switchOwner(_flapper, deployer, owner);
48-
DSAuthAbstract(_mom).setOwner(owner);
49-
50-
flapperInstance.flapper = _flapper;
51-
flapperInstance.mom = _mom;
46+
ScriptTools.switchOwner(flapper, deployer, owner);
5247
}
5348

5449
function deployOracleWrapper(
@@ -58,4 +53,19 @@ library FlapperDeploy {
5853
) internal returns (address wrapper) {
5954
wrapper = address(new OracleWrapper(pip, flapper, divisor));
6055
}
56+
57+
function deploySplitter(
58+
address deployer,
59+
address owner,
60+
address usdsJoin
61+
) internal returns (SplitterInstance memory splitterInstance) {
62+
address splitter = address(new Splitter(usdsJoin));
63+
address mom = address(new SplitterMom(splitter));
64+
65+
ScriptTools.switchOwner(splitter, deployer, owner);
66+
DSAuthAbstract(mom).setOwner(owner);
67+
68+
splitterInstance.splitter = splitter;
69+
splitterInstance.mom = mom;
70+
}
6171
}

deploy/FlapperInit.sol

Lines changed: 132 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -17,26 +17,28 @@
1717
pragma solidity >=0.8.0;
1818

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

2222
interface FlapperUniV2Like {
23-
function vat() external view returns (address);
24-
function daiJoin() external view returns (address);
25-
function spotter() external view returns (address);
2623
function pip() external view returns (address);
27-
function pair() external view returns (address);
24+
function spotter() external view returns (address);
25+
function usds() external view returns (address);
2826
function gem() external view returns (address);
27+
function receiver() external view returns (address);
28+
function pair() external view returns (address);
2929
function rely(address) external;
3030
function file(bytes32, uint256) external;
3131
function file(bytes32, address) external;
3232
}
3333

34-
interface FlapperMomLike {
34+
interface SplitterMomLike {
35+
function splitter() external view returns (address);
3536
function setAuthority(address) external;
3637
}
3738

3839
interface OracleWrapperLike {
3940
function pip() external view returns (address);
41+
function divisor() external view returns (uint256);
4042
}
4143

4244
interface PipLike {
@@ -48,68 +50,162 @@ interface PairLike {
4850
function token1() external view returns (address);
4951
}
5052

51-
interface DaiJoinLike {
52-
function dai() external view returns (address);
53+
interface UsdsJoinLike {
54+
function usds() external view returns (address);
55+
}
56+
57+
interface SplitterLike {
58+
function live() external view returns (uint256);
59+
function vat() external view returns (address);
60+
function usdsJoin() external view returns (address);
61+
function hop() external view returns (uint256);
62+
function rely(address) external;
63+
function file(bytes32, uint256) external;
64+
function file(bytes32, address) external;
65+
}
66+
67+
interface FarmLike {
68+
function rewardsToken() external view returns (address);
69+
function setRewardsDistribution(address) external;
70+
function setRewardsDuration(uint256) external;
5371
}
5472

5573
struct FlapperUniV2Config {
56-
uint256 hop;
5774
uint256 want;
5875
address pip;
76+
address pair;
77+
address usds;
78+
address splitter;
79+
bytes32 prevChainlogKey;
80+
bytes32 chainlogKey;
81+
}
82+
83+
struct FarmConfig {
84+
address splitter;
85+
address usdsJoin;
86+
uint256 hop;
87+
bytes32 prevChainlogKey;
88+
bytes32 chainlogKey;
89+
}
90+
91+
struct SplitterConfig {
5992
uint256 hump;
6093
uint256 bump;
61-
address daiJoin;
94+
uint256 hop;
95+
uint256 burn;
96+
address usdsJoin;
97+
bytes32 splitterChainlogKey;
98+
bytes32 prevMomChainlogKey;
99+
bytes32 momChainlogKey;
62100
}
63101

64102
library FlapperInit {
65103
uint256 constant WAD = 10 ** 18;
104+
uint256 constant RAY = 10 ** 27;
66105

67106
function initFlapperUniV2(
68107
DssInstance memory dss,
69-
FlapperInstance memory flapperInstance,
108+
address flapper_,
70109
FlapperUniV2Config memory cfg
71110
) internal {
72-
FlapperUniV2Like flapper = FlapperUniV2Like(flapperInstance.flapper);
73-
FlapperMomLike mom = FlapperMomLike(flapperInstance.mom);
111+
FlapperUniV2Like flapper = FlapperUniV2Like(flapper_);
74112

75113
// Sanity checks
76-
require(flapper.vat() == address(dss.vat), "Flapper vat mismatch");
77-
require(flapper.daiJoin() == cfg.daiJoin, "Flapper daiJoin mismatch");
78-
require(flapper.spotter() == address(dss.spotter), "Flapper spotter mismatch");
114+
require(flapper.spotter() == address(dss.spotter), "Flapper spotter mismatch");
115+
require(flapper.usds() == cfg.usds, "Flapper usds mismatch");
116+
require(flapper.pair() == cfg.pair, "Flapper pair mismatch");
117+
require(flapper.receiver() == dss.chainlog.getAddress("MCD_PAUSE_PROXY"), "Flapper receiver mismatch");
79118

80119
PairLike pair = PairLike(flapper.pair());
81-
address dai = DaiJoinLike(cfg.daiJoin).dai();
82-
(address pairDai, address pairGem) = pair.token0() == dai ? (pair.token0(), pair.token1())
83-
: (pair.token1(), pair.token0());
84-
require(pairDai == dai, "Dai mismatch");
85-
require(pairGem == flapper.gem(), "Gem mismatch");
120+
(address pairUsds, address pairGem) = pair.token0() == cfg.usds ? (pair.token0(), pair.token1())
121+
: (pair.token1(), pair.token0());
122+
require(pairUsds == cfg.usds, "Usds mismatch");
123+
require(pairGem == flapper.gem(), "Gem mismatch");
86124

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

91-
flapper.file("hop", cfg.hop);
92127
flapper.file("want", cfg.want);
93128
flapper.file("pip", cfg.pip);
94-
flapper.rely(address(dss.vow));
95-
flapper.rely(address(mom));
129+
flapper.rely(cfg.splitter);
96130

97-
dss.vow.file("flapper", address(flapper));
98-
dss.vow.file("hump", cfg.hump);
99-
dss.vow.file("bump", cfg.bump);
100-
101-
mom.setAuthority(dss.chainlog.getAddress("MCD_ADM"));
131+
SplitterLike(cfg.splitter).file("flapper", flapper_);
102132

103-
dss.chainlog.setAddress("MCD_FLAP", address(flapper));
104-
dss.chainlog.setAddress("FLAPPER_MOM", address(mom));
133+
if (cfg.prevChainlogKey != bytes32(0)) dss.chainlog.removeAddress(cfg.prevChainlogKey);
134+
dss.chainlog.setAddress(cfg.chainlogKey, flapper_);
105135
}
106136

107137
function initDirectOracle(address flapper) internal {
108138
PipLike(FlapperUniV2Like(flapper).pip()).kiss(flapper);
109139
}
110140

111-
function initOracleWrapper(DssInstance memory dss, address wrapper, bytes32 clKey) internal {
112-
PipLike(OracleWrapperLike(wrapper).pip()).kiss(wrapper);
113-
dss.chainlog.setAddress(clKey, wrapper);
141+
function initOracleWrapper(
142+
DssInstance memory dss,
143+
address wrapper_,
144+
uint256 divisor,
145+
bytes32 clKey
146+
) internal {
147+
OracleWrapperLike wrapper = OracleWrapperLike(wrapper_);
148+
require(wrapper.divisor() == divisor, "Wrapper divisor mismatch"); // Sanity check
149+
PipLike(wrapper.pip()).kiss(wrapper_);
150+
dss.chainlog.setAddress(clKey, wrapper_);
151+
}
152+
153+
function setFarm(
154+
DssInstance memory dss,
155+
address farm_,
156+
FarmConfig memory cfg
157+
) internal {
158+
FarmLike farm = FarmLike(farm_);
159+
SplitterLike splitter = SplitterLike(cfg.splitter);
160+
161+
require(farm.rewardsToken() == UsdsJoinLike(cfg.usdsJoin).usds(), "Farm rewards not usds");
162+
// Staking token is checked in the Lockstake script
163+
164+
// The following two checks enforce the initSplitter function has to be called first
165+
require(cfg.hop >= 5 minutes, "hop too low");
166+
require(cfg.hop == splitter.hop(), "hop mismatch");
167+
168+
splitter.file("farm", farm_);
169+
170+
farm.setRewardsDistribution(cfg.splitter);
171+
farm.setRewardsDuration(cfg.hop);
172+
173+
if (cfg.prevChainlogKey != bytes32(0)) dss.chainlog.removeAddress(cfg.prevChainlogKey);
174+
dss.chainlog.setAddress(cfg.chainlogKey, farm_);
175+
}
176+
177+
function initSplitter(
178+
DssInstance memory dss,
179+
SplitterInstance memory splitterInstance,
180+
SplitterConfig memory cfg
181+
) internal {
182+
SplitterLike splitter = SplitterLike(splitterInstance.splitter);
183+
SplitterMomLike mom = SplitterMomLike(splitterInstance.mom);
184+
185+
// Sanity checks
186+
require(splitter.live() == 1, "Splitter not live");
187+
require(splitter.vat() == address(dss.vat), "Splitter vat mismatch");
188+
require(splitter.usdsJoin() == cfg.usdsJoin, "Splitter usdsJoin mismatch");
189+
require(mom.splitter() == splitterInstance.splitter, "Mom splitter mismatch");
190+
191+
require(cfg.hump > 0, "hump too low");
192+
require(cfg.bump % RAY == 0, "bump not multiple of RAY");
193+
require(cfg.hop >= 5 minutes, "hop too low");
194+
require(cfg.burn <= WAD, "burn too high");
195+
196+
splitter.file("hop", cfg.hop);
197+
splitter.file("burn", cfg.burn);
198+
splitter.rely(address(mom));
199+
splitter.rely(address(dss.vow));
200+
201+
dss.vow.file("flapper", splitterInstance.splitter);
202+
dss.vow.file("hump", cfg.hump);
203+
dss.vow.file("bump", cfg.bump);
204+
205+
mom.setAuthority(dss.chainlog.getAddress("MCD_ADM"));
206+
207+
dss.chainlog.setAddress(cfg.splitterChainlogKey, splitterInstance.splitter);
208+
if (cfg.prevMomChainlogKey != bytes32(0)) dss.chainlog.removeAddress(cfg.prevMomChainlogKey);
209+
dss.chainlog.setAddress(cfg.momChainlogKey, address(mom));
114210
}
115211
}

deploy/FlapperInstance.sol renamed to deploy/SplitterInstance.sol

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@
1616

1717
pragma solidity >=0.8.0;
1818

19-
struct FlapperInstance {
20-
address flapper;
19+
struct SplitterInstance {
20+
address splitter;
2121
address mom;
2222
}

foundry.toml

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
[profile.default]
2-
src = 'src'
3-
out = 'out'
4-
libs = ['lib']
5-
solc = "0.8.16"
2+
src = "src"
3+
out = "out"
4+
libs = ["lib"]
5+
solc = "0.8.21"
66
optimizer = true
77
optimizer_runs = 200
88
verbosity = 3

0 commit comments

Comments
 (0)