forked from hackbg/chainlink-makerdao-automation
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathDssVestTopUp.sol
320 lines (281 loc) · 11.4 KB
/
DssVestTopUp.sol
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.9;
import "@openzeppelin/contracts/access/Ownable.sol";
import "@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol";
import "@uniswap/v3-periphery/contracts/libraries/TransferHelper.sol";
import "@uniswap/v3-periphery/contracts/interfaces/ISwapRouter.sol";
import "@chainlink/contracts/src/v0.8/interfaces/AggregatorV3Interface.sol";
import "./interfaces/IUpkeepRefunder.sol";
interface DssVestLike {
function vest(uint256 _id) external;
function unpaid(uint256 _id) external view returns (uint256 amt);
}
interface DaiJoinLike {
function join(address usr, uint256 wad) external;
}
interface KeeperRegistryLike {
struct UpkeepInfo {
address target;
uint32 executeGas;
bytes checkData;
uint96 balance;
address admin;
uint64 maxValidBlocknumber;
uint32 lastPerformBlockNumber;
uint96 amountSpent;
bool paused;
bytes offchainConfig;
}
function getUpkeep(uint256)
external
view
returns (UpkeepInfo memory upkeepInfo);
function addFunds(uint256 id, uint96 amount) external;
}
/**
* @title DssVestTopUp
* @notice Replenishes upkeep balance on demand
* @dev Withdraws vested tokens or uses transferred tokens from Maker protocol and
* funds an upkeep after swapping the payment tokens for LINK
*/
contract DssVestTopUp is IUpkeepRefunder, Ownable {
DaiJoinLike public immutable daiJoin;
ISwapRouter public immutable swapRouter;
address public immutable vow;
address public immutable paymentToken;
address public immutable linkToken;
address public immutable paymentUsdPriceFeed;
address public immutable linkUsdPriceFeed;
DssVestLike public dssVest;
KeeperRegistryLike public keeperRegistry;
uint24 public uniswapPoolFee = 3000;
uint24 public uniswapSlippageTolerancePercent = 2;
uint256 public vestId;
uint256 public upkeepId;
uint256 public minWithdrawAmt;
uint256 public maxDepositAmt;
uint256 public threshold;
event VestIdSet(uint256 newVestId);
event UpkeepIdSet(uint256 newUpkeepId);
event MinWithdrawAmtSet(uint256 newMinWithdrawAmt);
event MaxDepositAmtSet(uint256 newMaxDepositAmt);
event ThresholdSet(uint256 newThreshold);
event UniswapPoolFeeSet(uint24 poolFee);
event UniswapSlippageToleranceSet(uint24 slippageTolerancePercent);
event DssVestSet(address dssVest);
event KeeperRegistrySet(address keeperRegistry);
event VestedTokensWithdrawn(uint256 amount);
event ExcessPaymentReturned(uint256 amount);
event SwappedPaymentTokenForLink(uint256 amountIn, uint256 amountOut);
event UpkeepRefunded(uint256 amount);
event FundsRecovered(address token, uint256 amount);
constructor(
address _dssVest,
address _daiJoin,
address _vow,
address _paymentToken,
address _keeperRegistry,
address _swapRouter,
address _linkToken,
address _paymentUsdPriceFeed,
address _linkUsdPriceFeed,
uint256 _minWithdrawAmt,
uint256 _maxDepositAmt,
uint256 _threshold
) {
require(_daiJoin != address(0), "invalid daiJoin address");
require(_vow != address(0), "invalid vow address");
require(_paymentToken != address(0), "invalid paymentToken address");
require(_swapRouter != address(0), "invalid swapRouter address");
require(_linkToken != address(0), "invalid linkToken address");
require(_paymentUsdPriceFeed != address(0), "invalid paymentUsdPriceFeed address");
require(_linkUsdPriceFeed != address(0), "invalid linkUsdPriceFeed address");
daiJoin = DaiJoinLike(_daiJoin);
vow = _vow;
paymentToken = _paymentToken;
swapRouter = ISwapRouter(_swapRouter);
linkToken = _linkToken;
paymentUsdPriceFeed = _paymentUsdPriceFeed;
linkUsdPriceFeed = _linkUsdPriceFeed;
setDssVest(_dssVest);
setKeeperRegistry(_keeperRegistry);
setMinWithdrawAmt(_minWithdrawAmt);
setMaxDepositAmt(_maxDepositAmt);
setThreshold(_threshold);
}
modifier initialized() {
require(vestId > 0, "vestId not set");
require(upkeepId > 0, "upkeepId not set");
_;
}
// ACTIONS
/**
* @notice Top up upkeep balance with LINK
* @dev Called by the DssCronKeeper contract when check returns true
*/
function refundUpkeep() public initialized {
require(shouldRefundUpkeep(), "refund not needed");
uint256 amt;
uint256 preBalance = getPaymentBalance();
if (preBalance >= minWithdrawAmt) {
// Emergency topup
amt = preBalance;
} else {
// Withdraw vested tokens
dssVest.vest(vestId);
amt = getPaymentBalance();
emit VestedTokensWithdrawn(amt);
if (amt > maxDepositAmt) {
// Return excess amount to surplus buffer
uint256 excessAmt = amt - maxDepositAmt;
TransferHelper.safeApprove(paymentToken, address(daiJoin), excessAmt);
daiJoin.join(vow, excessAmt);
amt = maxDepositAmt;
emit ExcessPaymentReturned(excessAmt);
}
}
uint256 amtOut = _swapPaymentToLink(amt);
_fundUpkeep(amtOut);
}
/**
* @notice Check whether top up is needed
* @dev Called by the keeper
* @return Result indicating if topping up the upkeep balance is needed and
* if there's enough unpaid vested tokens or tokens in the contract balance
*/
function shouldRefundUpkeep() public view initialized returns (bool) {
uint96 balance = keeperRegistry.getUpkeep(upkeepId).balance;
if (
threshold < balance ||
(dssVest.unpaid(vestId) < minWithdrawAmt &&
getPaymentBalance() < minWithdrawAmt)
) {
return false;
}
return true;
}
// HELPERS
function _swapPaymentToLink(uint256 _amount)
internal
returns (uint256 amountOut)
{
TransferHelper.safeApprove(paymentToken, address(swapRouter), _amount);
ISwapRouter.ExactInputSingleParams memory params = ISwapRouter
.ExactInputSingleParams({
tokenIn: paymentToken,
tokenOut: linkToken,
fee: uniswapPoolFee,
recipient: address(this),
deadline: block.timestamp,
amountIn: _amount,
amountOutMinimum: _getPaymentLinkSwapOutMin(_amount),
sqrtPriceLimitX96: 0
});
amountOut = swapRouter.exactInputSingle(params);
emit SwappedPaymentTokenForLink(_amount, amountOut);
}
function _fundUpkeep(uint256 _amount) internal {
TransferHelper.safeApprove(linkToken, address(keeperRegistry), _amount);
keeperRegistry.addFunds(upkeepId, uint96(_amount));
emit UpkeepRefunded(_amount);
}
function _getPaymentLinkSwapOutMin(uint256 _amountIn) internal view returns (uint256) {
uint256 linkDecimals = IERC20Metadata(linkToken).decimals();
uint256 paymentLinkPrice = uint256(_getDerivedPrice(paymentUsdPriceFeed, linkUsdPriceFeed, uint8(linkDecimals)));
uint256 paymentDecimals = IERC20Metadata(paymentToken).decimals();
uint256 paymentAmt = uint256(_scalePrice(int256(_amountIn), uint8(paymentDecimals), uint8(linkDecimals)));
uint256 linkAmt = (paymentAmt * paymentLinkPrice) / 10 ** linkDecimals;
uint256 slippageTolerance = (linkAmt * uniswapSlippageTolerancePercent) / 100;
return linkAmt - slippageTolerance;
}
function _getDerivedPrice(address _base, address _quote, uint8 _decimals)
internal
view
returns (int256)
{
require(_decimals > uint8(0) && _decimals <= uint8(18), "invalid decimals");
int256 decimals = int256(10 ** uint256(_decimals));
( , int256 basePrice, , , ) = AggregatorV3Interface(_base).latestRoundData();
uint8 baseDecimals = AggregatorV3Interface(_base).decimals();
basePrice = _scalePrice(basePrice, baseDecimals, _decimals);
( , int256 quotePrice, , , ) = AggregatorV3Interface(_quote).latestRoundData();
uint8 quoteDecimals = AggregatorV3Interface(_quote).decimals();
quotePrice = _scalePrice(quotePrice, quoteDecimals, _decimals);
return basePrice * decimals / quotePrice;
}
function _scalePrice(int256 _price, uint8 _priceDecimals, uint8 _decimals)
internal
pure
returns (int256)
{
if (_priceDecimals < _decimals) {
return _price * int256(10 ** uint256(_decimals - _priceDecimals));
} else if (_priceDecimals > _decimals) {
return _price / int256(10 ** uint256(_priceDecimals - _decimals));
}
return _price;
}
/**
* @dev Rescues random funds stuck
* @param _token address of the token to rescue
*/
function recoverFunds(IERC20 _token) external onlyOwner {
uint256 tokenBalance = _token.balanceOf(address(this));
_token.transfer(msg.sender, tokenBalance);
emit FundsRecovered(address(_token), tokenBalance);
}
// GETTERS
/**
* @notice Retrieve the payment token balance of this contract
* @return balance
*/
function getPaymentBalance() public view returns (uint256) {
return IERC20(paymentToken).balanceOf(address(this));
}
// SETTERS
function setVestId(uint256 _vestId) external onlyOwner {
require(_vestId > 0, "invalid vestId");
vestId = _vestId;
emit VestIdSet(_vestId);
}
function setUpkeepId(uint256 _upkeepId) external onlyOwner {
require(_upkeepId > 0, "invalid upkeepId");
upkeepId = _upkeepId;
emit UpkeepIdSet(_upkeepId);
}
function setMinWithdrawAmt(uint256 _minWithdrawAmt) public onlyOwner {
require(_minWithdrawAmt > 0, "invalid minWithdrawAmt");
minWithdrawAmt = _minWithdrawAmt;
emit MinWithdrawAmtSet(_minWithdrawAmt);
}
function setMaxDepositAmt(uint256 _maxDepositAmt) public onlyOwner {
require(_maxDepositAmt > 0, "invalid maxDepositAmt");
maxDepositAmt = _maxDepositAmt;
emit MaxDepositAmtSet(_maxDepositAmt);
}
function setThreshold(uint256 _threshold) public onlyOwner {
require(_threshold > 0, "invalid threshold");
threshold = _threshold;
emit ThresholdSet(_threshold);
}
function setDssVest(address _dssVest) public onlyOwner {
require(_dssVest != address(0), "invalid dssVest address");
dssVest = DssVestLike(_dssVest);
emit DssVestSet(_dssVest);
}
function setKeeperRegistry(address _keeperRegistry) public onlyOwner {
require(_keeperRegistry != address(0), "invalid keeperRegistry address");
keeperRegistry = KeeperRegistryLike(_keeperRegistry);
emit KeeperRegistrySet(_keeperRegistry);
}
function setUniswapPoolFee(uint24 _uniswapPoolFee) external onlyOwner {
uniswapPoolFee = _uniswapPoolFee;
emit UniswapPoolFeeSet(_uniswapPoolFee);
}
function setSlippageTolerancePercent(
uint24 _uniswapSlippageTolerancePercent
) external onlyOwner {
uniswapSlippageTolerancePercent = _uniswapSlippageTolerancePercent;
emit UniswapSlippageToleranceSet(_uniswapSlippageTolerancePercent);
}
}