From 5ab18f866a1c8cb8db15209285da9091501ecfca Mon Sep 17 00:00:00 2001 From: Tritium <11885129+Tritium-VLK@users.noreply.github.com> Date: Tue, 6 Aug 2024 20:38:40 +0200 Subject: [PATCH] Add diff with original ChildChainInjector v1 --- diff_with_ChildChainInjector_v1.html | 3087 ++++++++++++++++++++++++++ 1 file changed, 3087 insertions(+) create mode 100644 diff_with_ChildChainInjector_v1.html diff --git a/diff_with_ChildChainInjector_v1.html b/diff_with_ChildChainInjector_v1.html new file mode 100644 index 0000000..aeb0d05 --- /dev/null +++ b/diff_with_ChildChainInjector_v1.html @@ -0,0 +1,3087 @@ + + + + + + + + + ExamDiff Pro Diff Report + + + + + + + + + + + +
+ + + + + + + + + + + + +
+
+
https://raw.githubusercontent.com/BalancerMaxis/MainChainGaugeInjector/main/src/MainChainGaugeInjector.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
+
321
+
322
+
323
+
324
+
325
+
326
+
327
+
328
+
329
+
330
+
331
+
332
+
333
+
334
+
335
+
336
+
337
+
338
+
339
+
340
+
341
+
342
+
343
+
344
+
345
+
346
+
347
+
348
+
349
+
350
+
351
+
352
+
353
+
354
+
355
+
356
+
357
+
358
+
359
+
360
+
361
+
362
+
363
+
364
+
365
+
366
+
367
+
368
+
369
+
370
+
371
+
372
+
373
+
374
+
375
+
376
+
377
+
378
+
379
+
380
+
381
+
382
+
383
+
384
+
385
+
386
+
387
+
388
+
389
+
390
+
391
+
392
+
393
+
394
+
395
+
396
+
397
+
398
+
399
+
400
+
401
+
402
+
403
+
404
+
405
+
406
+
407
+
408
+
409
+
410
+
411
+
412
+
413
+
414
+
415
+
416
+
417
+
418
+
419
+
420
+
421
+
422
+
423
+
424
+
425
+
426
+
427
+
428
+
429
+
430
+
431
+
432
+
433
+
434
+
435
+
436
+
437
+
438
+
439
+
440
+
441
+
442
+
443
+
444
+
445
+
446
+
447
+
448
+
449
+
450
+
451
+
452
+
453
+
454
+
455
+
456
+
457
+
458
+
459
+
460
+
461
+
462
+
463
+
464
+
465
+
466
+
467
+
468
+
469
+
470
+
471
+
472
+
473
+
474
+
475
+
476
+
477
+ +
+
// SPDX-License-Identifier: MIT
+
+
pragma solidity ^0.8.18;
+
+
import "@chainlink/contracts/src/v0.8/ConfirmedOwner.sol";
+
import "@chainlink/contracts/src/v0.8/interfaces/KeeperCompatibleInterface.sol";
+
import "@openzeppelin/contracts/security/Pausable.sol";
+
import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
+
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
+
import "./interfaces/IMainChainGauge.sol";
+
+
+
/**
+
* @title The MainChainGaugeInjector Contract
+
* @author 0xtritium.eth + master coder Mike B
+
* @notice This contract is a chainlink automation compatible interface to automate regular payment of non-BAL tokens to a child chain gauge.
+
* @notice This contract is meant to run/manage a single token. This is almost always the case for a DAO trying to use such a thing.
+
* @notice The configuration is rewritten each time it is loaded.
+
* @notice This contract will only function if it is configured as the distributor for a token/gauge it is operating on.
+
* @notice The contract is meant to hold token balances, and works on a schedule set using setRecipientList. The schedule defines an amount per round and number of rounds per gauge.
+
* @notice This contract is Ownable and has lots of sweep functionality to allow the owner to work with the contract or get tokens out should there be a problem.
+
* @dev This release only works on mainnet, due to differences in the interface of mainchain/childchain gauges!!!
+
* see https://docs.chain.link/chainlink-automation/utility-contracts/
+
*/
+
contract MainChainGaugeInjector is
+
ConfirmedOwner,
+
Pausable,
+
KeeperCompatibleInterface
+
{
+
event GasTokenWithdrawn(uint256 amountWithdrawn, address recipient);
+
event KeeperRegistryAddressUpdated(address oldAddress, address newAddress);
+
event MinWaitPeriodUpdated(
+
uint256 oldMinWaitPeriod,
+
uint256 newMinWaitPeriod
+
);
+
event ERC20Swept(address indexed token, address recipient, uint256 amount);
+
event EmissionsInjection(address gauge, address token, uint256 amount);
+
event SetHandlingToken(address token);
+
event PerformedUpkeep(address[] needsFunding);
+
+
error ListLengthMismatch();
+
error OnlyKeeperRegistry(address sender);
+
error DuplicateAddress(address duplicate);
+
error PeriodNotFinished(uint256 periodNumber, uint256 maxPeriods);
+
error ZeroAddress();
+
error ZeroAmount();
+
error BalancesMismatch();
+
error RewardTokenError();
+
+
struct Target {
+
uint256 amountPerPeriod;
+
bool isActive;
+
uint8 maxPeriods;
+
uint8 periodNumber;
+
uint56 lastInjectionTimeStamp; // enough space for 2 trillion years
+
}
+
+
+
address private s_keeperRegistryAddress;
+
uint256 private s_minWaitPeriodSeconds;
+
address[] private s_gaugeList;
+
mapping(address => Target) internal s_targets;
+
address private s_injectTokenAddress;
+
+
/**
+
* @param keeperRegistryAddress The address of the keeper registry contract
+
* @param minWaitPeriodSeconds The minimum wait period for address between funding (for security)
+
* @param injectTokenAddress The ERC20 token this contract should mange
+
*/
+
constructor(
+
address keeperRegistryAddress,
+
uint256 minWaitPeriodSeconds,
+
address injectTokenAddress
+
) ConfirmedOwner(msg.sender) {
+
setKeeperRegistryAddress(keeperRegistryAddress);
+
setMinWaitPeriodSeconds(minWaitPeriodSeconds);
+
setInjectTokenAddress(injectTokenAddress);
+
}
+
+
/**
+
* @notice Sets the list of addresses to watch and their funding parameters
+
* @param gaugeAddresses the list of addresses to watch
+
* @param amountsPerPeriod the minimum balances for each address
+
* @param maxPeriods the amount to top up each address
+
*/
+
function setRecipientList(
+
address[] calldata gaugeAddresses,
+
uint256[] calldata amountsPerPeriod,
+
uint8[] calldata maxPeriods
+
) public onlyOwner {
+
if (
+
gaugeAddresses.length != amountsPerPeriod.length ||
+
gaugeAddresses.length != maxPeriods.length
+
) {
+
revert ListLengthMismatch();
+
}
+
revertOnDuplicate(gaugeAddresses);
+
address[] memory oldGaugeList = s_gaugeList;
+
for (uint256 idx = 0; idx < oldGaugeList.length; idx++) {
+
s_targets[oldGaugeList[idx]].isActive = false;
+
}
+
for (uint256 idx = 0; idx < gaugeAddresses.length; idx++) {
+
+
if (gaugeAddresses[idx] == address(0)) {
+
revert ZeroAddress();
+
}
+
if (amountsPerPeriod[idx] == 0) {
+
revert ZeroAmount();
+
}
+
s_targets[gaugeAddresses[idx]] = Target({
+
isActive: true,
+
amountPerPeriod: amountsPerPeriod[idx],
+
maxPeriods: maxPeriods[idx],
+
lastInjectionTimeStamp: 0,
+
periodNumber: 0
+
});
+
}
+
s_gaugeList = gaugeAddresses;
+
}
+
+
/**
+
* @notice Validate that all periods are finished, and that the supplied schedule has enough tokens to fully execute
+
* @notice If everything checks out, update recipient list, otherwise, throw revert
+
* @notice you can use setRecipientList to set a list without validation
+
* @param gaugeAddresses : list of gauge addresses
+
* @param amountsPerPeriod : list of amount of token in wei to be injected each week
+
*/
+
function setValidatedRecipientList(
+
address[] calldata gaugeAddresses,
+
uint256[] calldata amountsPerPeriod,
+
uint8[] calldata maxPeriods
+
) external onlyOwner {
+
address[] memory gaugeList = s_gaugeList;
+
// validate all periods are finished
+
for (uint256 idx = 0; idx < gaugeList.length; idx++) {
+
Target memory target = s_targets[gaugeList[idx]];
+
if (target.periodNumber < target.maxPeriods) {
+
revert PeriodNotFinished(target.periodNumber, target.maxPeriods);
+
}
+
}
+
setRecipientList(gaugeAddresses, amountsPerPeriod, maxPeriods);
+
+
if (!checkSufficientBalances()) {
+
revert BalancesMismatch();
+
}
+
}
+
+
/**
+
* @notice Validate that the contract holds enough tokens to fulfill the current schedule
+
* @return bool true if balance of contract matches scheduled periods
+
*/
+
function checkSufficientBalances() public view returns (bool) {
+
// iterates through all gauges to make sure there are enough tokens in the contract to fulfill all scheduled tasks
+
// (maxperiods - periodnumber) * amountPerPeriod == token.balanceOf(address(this))
+
+
address[] memory gaugeList = s_gaugeList;
+
uint256 totalDue;
+
for (uint256 idx = 0; idx < gaugeList.length; idx++) {
+
Target memory target = s_targets[gaugeList[idx]];
+
totalDue +=
+
(target.maxPeriods - target.periodNumber) *
+
target.amountPerPeriod;
+
}
+
return totalDue <= IERC20(s_injectTokenAddress).balanceOf(address(this));
+
}
+
+
/**
+
* @notice Gets a list of addresses that are ready to inject
+
* @notice This is done by checking if the current period has ended, and should inject new funds directly after the end of each period.
+
* @return list of addresses that are ready to inject
+
*/
+
function getReadyGauges() public view returns (address[] memory) {
+
address[] memory gaugeList = s_gaugeList;
+
address[] memory ready = new address[](gaugeList.length);
+
address tokenAddress = s_injectTokenAddress;
+
uint256 count = 0;
+
uint256 minWaitPeriod = s_minWaitPeriodSeconds;
+
uint256 balance = IERC20(tokenAddress).balanceOf(address(this));
+
Target memory target;
+
for (uint256 idx = 0; idx < gaugeList.length; idx++) {
+
target = s_targets[gaugeList[idx]];
+
IMainChainGauge gauge = IMainChainGauge(gaugeList[idx]);
+
+
uint256 period_finish = gauge.reward_data(tokenAddress).period_finish;
+
+
if (
+
target.lastInjectionTimeStamp + minWaitPeriod <= block.timestamp &&
+
(period_finish <= block.timestamp) &&
+
balance >= target.amountPerPeriod &&
+
target.periodNumber < target.maxPeriods &&
+
gauge.reward_data(tokenAddress).distributor == address(this)
+
) {
+
ready[count] = gaugeList[idx];
+
count++;
+
balance -= target.amountPerPeriod;
+
}
+
}
+
if (count != gaugeList.length) {
+
// ready is a list large enough to hold all possible gauges
+
// count is the number of ready gauges that were inserted into ready
+
// this assembly shrinks ready to length count such that it removes empty elements
+
assembly {
+
mstore(ready, count)
+
}
+
}
+
return ready;
+
}
+
+
/**
+
* @notice Injects funds into the gauges provided
+
* @param ready the list of gauges to fund (addresses must be pre-approved)
+
*/
+
function _injectFunds(address[] memory ready) internal whenNotPaused {
+
+
address tokenAddress = s_injectTokenAddress;
+
IERC20 token = IERC20(tokenAddress);
+
uint256 balance = token.balanceOf(address(this));
+
Target memory target;
+
+
for (uint256 idx = 0; idx < ready.length; idx++) {
+
target = s_targets[ready[idx]];
+
IMainChainGauge gauge = IMainChainGauge(ready[idx]);
+
uint256 period_finish = gauge.reward_data(tokenAddress).period_finish;
+
+
if (
+
target.lastInjectionTimeStamp + s_minWaitPeriodSeconds <=
+
block.timestamp &&
+
period_finish <= block.timestamp &&
+
balance >= target.amountPerPeriod &&
+
target.periodNumber < target.maxPeriods &&
+
target.isActive == true
+
) {
+
+
SafeERC20.safeApprove(token, ready[idx], target.amountPerPeriod);
+
+
try
+
gauge.deposit_reward_token(
+
tokenAddress,
+
uint256(target.amountPerPeriod)
+
)
+
{
+
s_targets[ready[idx]].lastInjectionTimeStamp = uint56(
+
block.timestamp
+
);
+
s_targets[ready[idx]].periodNumber++;
+
emit EmissionsInjection(
+
ready[idx],
+
tokenAddress,
+
target.amountPerPeriod
+
);
+
} catch {
+
revert RewardTokenError();
+
}
+
}
+
}
+
}
+
+
/**
+
* * @notice This is to allow the owner to manually trigger an injection of funds in place of the keeper
+
* @notice without abi encoding the gauge list
+
* @param gauges array of gauges to inject tokens to
+
*/
+
function injectFunds(address[] memory gauges) external onlyOwner {
+
_injectFunds(gauges);
+
}
+
+
/**
+
* @notice Get list of addresses that are ready for new token injections and return keeper-compatible payload
+
* @notice calldata required by the chainlink interface but not used in this case, use 0x
+
* @return upkeepNeeded signals if upkeep is needed
+
* @return performData is an abi encoded list of addresses that need funds
+
*/
+
function checkUpkeep(
+
bytes calldata
+
)
+
external
+
view
+
override
+
whenNotPaused
+
returns (bool upkeepNeeded, bytes memory performData)
+
{
+
address[] memory ready = getReadyGauges();
+
upkeepNeeded = ready.length > 0;
+
performData = abi.encode(ready);
+
return (upkeepNeeded, performData);
+
}
+
+
/**
+
* @notice Called by keeper to send funds to underfunded addresses
+
* @param performData The abi encoded list of addresses to fund
+
*/
+
function performUpkeep(
+
bytes calldata performData
+
) external override onlyKeeperRegistry whenNotPaused {
+
address[] memory needsFunding = abi.decode(performData, (address[]));
+
_injectFunds(needsFunding);
+
emit PerformedUpkeep(needsFunding);
+
}
+
+
/**
+
* @notice Withdraws the contract balance
+
*/
+
function withdrawGasToken() external onlyOwner {
+
address payable recipient = payable(owner());
+
if (recipient == address(0)) {
+
revert ZeroAddress();
+
}
+
uint256 amount = address(this).balance;
+
recipient.transfer(amount);
+
emit GasTokenWithdrawn(amount, recipient);
+
}
+
+
/**
+
* @notice Sweep the full contract's balance for a given ERC-20 token
+
* @param token The ERC-20 token which needs to be swept
+
*/
+
function sweep(address token) external onlyOwner {
+
uint256 balance = IERC20(token).balanceOf(address(this));
+
SafeERC20.safeTransfer(IERC20(token), owner(), balance);
+
emit ERC20Swept(token, owner(), balance);
+
}
+
+
+
+
/**
+
* @notice Set distributor from the injector back to the owner.
+
* @notice You will have to call set_reward_distributor back to the injector FROM the current distributor if you wish to continue using the injector
+
* @notice be aware that the only addresses able to call set_reward_distributor are the current distributor and balancer governance authorized accounts (the LM multisig)
+
* @param gauge The Gauge to set distributor for
+
* @param reward_token Token you are setting the distributor for
+
*/
+
function setDistributorToOwner(
+
address gauge,
+
address reward_token
+
) external onlyOwner {
+
IMainChainGauge(gauge).set_reward_distributor(reward_token, msg.sender);
+
}
+
+
/**
+
* @notice Manually deposit an amount of tokens to the gauge
+
* @param gauge The Gauge to set distributor to injector owner
+
* @param reward_token Reward token you are seeding
+
* @param amount Amount to deposit
+
*/
+
function manualDeposit(
+
address gauge,
+
address reward_token,
+
uint256 amount
+
) external onlyOwner {
+
IMainChainGauge gaugeContract = IMainChainGauge(gauge);
+
IERC20 token = IERC20(reward_token);
+
SafeERC20.safeApprove(token, gauge, amount);
+
gaugeContract.deposit_reward_token(reward_token, amount);
+
emit EmissionsInjection(gauge, reward_token, amount);
+
}
+
+
/**
+
* @notice Sets the keeper registry address
+
*/
+
function setKeeperRegistryAddress(
+
address keeperRegistryAddress
+
) public onlyOwner {
+
s_keeperRegistryAddress = keeperRegistryAddress;
+
emit KeeperRegistryAddressUpdated(
+
s_keeperRegistryAddress,
+
keeperRegistryAddress
+
);
+
}
+
+
/**
+
* @notice Sets the minimum wait period (in seconds) for addresses between injections
+
*/
+
function setMinWaitPeriodSeconds(uint256 period) public onlyOwner {
+
s_minWaitPeriodSeconds = period;
+
emit MinWaitPeriodUpdated(s_minWaitPeriodSeconds, period);
+
}
+
+
/**
+
* @notice Gets the keeper registry address
+
*/
+
function getKeeperRegistryAddress()
+
external
+
view
+
returns (address keeperRegistryAddress)
+
{
+
return s_keeperRegistryAddress;
+
}
+
+
/**
+
* @notice Gets the minimum wait period
+
*/
+
function getMinWaitPeriodSeconds() external view returns (uint256) {
+
return s_minWaitPeriodSeconds;
+
}
+
+
/**
+
* @notice Gets the list of addresses on the in the current configuration.
+
*/
+
function getWatchList() external view returns (address[] memory) {
+
return s_gaugeList;
+
}
+
+
/**
+
* @notice Sets the address of the ERC20 token this contract should handle
+
*/
+
function setInjectTokenAddress(address ERC20token) public onlyOwner {
+
s_injectTokenAddress = ERC20token;
+
emit SetHandlingToken(ERC20token);
+
}
+
/**
+
* @notice Gets the token this injector is operating on
+
*/
+
+
function getInjectTokenAddress() external view returns (address) {
+
return s_injectTokenAddress;
+
}
+
/**
+
* @notice Gets configuration information for an address on the gaugelist
+
* @param targetAddress return Target struct for a given gauge according to the current scheduled distributions
+
*/
+
+
function getAccountInfo(
+
address targetAddress
+
)
+
external
+
view
+
returns (
+
uint256 amountPerPeriod,
+
bool isActive,
+
uint8 maxPeriods,
+
uint8 periodNumber,
+
uint56 lastInjectionTimeStamp
+
)
+
{
+
Target memory target = s_targets[targetAddress];
+
return (
+
target.amountPerPeriod,
+
target.isActive,
+
target.maxPeriods,
+
target.periodNumber,
+
target.lastInjectionTimeStamp
+
);
+
}
+
+
/**
+
* @notice Pauses the contract, which prevents executing performUpkeep
+
*/
+
function pause() external onlyOwner {
+
_pause();
+
}
+
+
/**
+
* @notice Unpauses the contract
+
*/
+
function unpause() external onlyOwner {
+
_unpause();
+
}
+
+
/**
+
* @notice takes in a list of addresses and reverts if there is a duplicate
+
*/
+
function revertOnDuplicate(address[] memory list) internal pure {
+
uint256 length = list.length;
+
if (length == 0) {
+
return;
+
}
+
for (uint256 i = 0; i < length - 1; i++) {
+
for (uint256 j = i + 1; j < length; j++) {
+
if (list[i] == list[j]) {
+
revert DuplicateAddress(list[i]);
+
}
+
}
+
}
+
// No duplicates found
+
}
+
+
modifier onlyKeeperRegistry() {
+
if (msg.sender != s_keeperRegistryAddress) {
+
revert OnlyKeeperRegistry(msg.sender);
+
}
+
_;
+
}
+
}
+
+ +
+
+
+
+
https://raw.githubusercontent.com/BalancerMaxis/ChildChainGaugeInjectoooor/main/contracts/ChildChainGaugeInjectoooor.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
+
321
+
322
+
+
+
323
+
324
+
+
+
+
325
+
326
+
327
+
328
+
329
+
330
+
331
+
332
+
333
+
334
+
335
+
336
+
337
+
338
+
+
+
+
+
339
+
340
+
341
+
342
+
343
+
344
+
345
+
346
+
347
+
348
+
349
+
350
+
351
+
352
+
353
+
354
+
355
+
356
+
357
+
358
+
359
+
360
+
361
+
362
+
363
+
364
+
365
+
+
366
+
367
+
368
+
369
+
370
+
371
+
372
+
+
373
+
+
+
374
+
375
+
376
+
377
+
378
+
379
+
380
+
381
+
382
+
383
+
384
+
385
+
+
+
+
+
+
+
386
+
387
+
388
+
389
+
390
+
391
+
392
+
393
+
394
+
395
+
396
+
397
+
398
+
399
+
400
+
401
+
402
+
403
+
404
+
405
+
406
+
407
+
408
+
409
+
410
+
411
+
412
+
413
+
414
+
415
+
416
+
417
+
418
+
419
+
420
+
421
+
422
+
423
+
424
+
425
+
426
+
427
+ +
+
// SPDX-License-Identifier: MIT
+
+
pragma solidity ^0.8.18;
+
+
import "@chainlink/contracts/src/v0.8/ConfirmedOwner.sol";
+
import "@chainlink/contracts/src/v0.8/interfaces/KeeperCompatibleInterface.sol";
+
import "@openzeppelin/contracts/security/Pausable.sol";
+
import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
+
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
+
import "interfaces/balancer/IChildChainGauge.sol";
+
+
+
/**
+
* @title The ChildChainGaugeInjector Contract
+
* @author 0xtritium.eth + master coder Mike B
+
* @notice This contract is a chainlink automation compatible interface to automate regular payment of non-BAL tokens to a child chain gauge.
+
* @notice This contract is meant to run/manage a single token. This is almost always the case for a DAO trying to use such a thing.
+
* @notice The configuration is rewritten each time it is loaded.
+
* @notice This contract will only function if it is configured as the distributor for a token/gauge it is operating on.
+
* @notice The contract is meant to hold token balances, and works on a schedule set using setRecipientList. The schedule defines an amount per round and number of rounds per gauge.
+
* @notice This contract is Ownable and has lots of sweep functionality to allow the owner to work with the contract or get tokens out should there be a problem.
+
+
* see https://docs.chain.link/chainlink-automation/utility-contracts/
+
*/
+
contract ChildChainGaugeInjector is ConfirmedOwner, Pausable, KeeperCompatibleInterface {
+
+
+
+
+
event GasTokenWithdrawn(uint256 amountWithdrawn, address recipient);
+
event KeeperRegistryAddressUpdated(address oldAddress, address newAddress);
+
event MinWaitPeriodUpdated(uint256 oldMinWaitPeriod, uint256 newMinWaitPeriod);
+
+
+
+
event ERC20Swept(address indexed token, address recipient, uint256 amount);
+
event EmissionsInjection(address gauge, address token, uint256 amount);
+
event SetHandlingToken(address token);
+
event PerformedUpkeep(address[] needsFunding);
+
+
error ListLengthMismatch();
+
error OnlyKeeperRegistry(address sender);
+
error DuplicateAddress(address duplicate);
+
error PeriodNotFinished(uint256 periodNumber, uint256 maxPeriods);
+
error ZeroAddress();
+
error ZeroAmount();
+
error BalancesMismatch();
+
error RewardTokenError();
+
+
struct Target {
+
uint256 amountPerPeriod;
+
bool isActive;
+
uint8 maxPeriods;
+
uint8 periodNumber;
+
uint56 lastInjectionTimeStamp; // enough space for 2 trillion years
+
}
+
+
+
address private s_keeperRegistryAddress;
+
uint256 private s_minWaitPeriodSeconds;
+
address[] private s_gaugeList;
+
mapping(address => Target) internal s_targets;
+
address private s_injectTokenAddress;
+
+
/**
+
* @param keeperRegistryAddress The address of the keeper registry contract
+
* @param minWaitPeriodSeconds The minimum wait period for address between funding (for security)
+
* @param injectTokenAddress The ERC20 token this contract should mange
+
*/
+
constructor(address keeperRegistryAddress, uint256 minWaitPeriodSeconds, address injectTokenAddress)
+
ConfirmedOwner(msg.sender)
+
{
+
+
+
setKeeperRegistryAddress(keeperRegistryAddress);
+
setMinWaitPeriodSeconds(minWaitPeriodSeconds);
+
setInjectTokenAddress(injectTokenAddress);
+
}
+
+
/**
+
* @notice Sets the list of addresses to watch and their funding parameters
+
* @param gaugeAddresses the list of addresses to watch
+
* @param amountsPerPeriod the minimum balances for each address
+
* @param maxPeriods the amount to top up each address
+
*/
+
function setRecipientList(
+
address[] calldata gaugeAddresses,
+
uint256[] calldata amountsPerPeriod,
+
uint8[] calldata maxPeriods
+
) public onlyOwner {
+
if (gaugeAddresses.length != amountsPerPeriod.length || gaugeAddresses.length != maxPeriods.length) {
+
+
+
+
revert ListLengthMismatch();
+
}
+
revertOnDuplicate(gaugeAddresses);
+
address[] memory oldGaugeList = s_gaugeList;
+
for (uint256 idx = 0; idx < oldGaugeList.length; idx++) {
+
s_targets[oldGaugeList[idx]].isActive = false;
+
}
+
for (uint256 idx = 0; idx < gaugeAddresses.length; idx++) {
+
+
if (gaugeAddresses[idx] == address(0)) {
+
revert ZeroAddress();
+
}
+
if (amountsPerPeriod[idx] == 0) {
+
revert ZeroAmount();
+
}
+
s_targets[gaugeAddresses[idx]] = Target({
+
isActive: true,
+
amountPerPeriod: amountsPerPeriod[idx],
+
maxPeriods: maxPeriods[idx],
+
lastInjectionTimeStamp: 0,
+
periodNumber: 0
+
});
+
}
+
s_gaugeList = gaugeAddresses;
+
}
+
+
/**
+
* @notice Validate that all periods are finished, and that the supplied schedule has enough tokens to fully execute
+
* @notice If everything checks out, update recipient list, otherwise, throw revert
+
* @notice you can use setRecipientList to set a list without validation
+
* @param gaugeAddresses : list of gauge addresses
+
* @param amountsPerPeriod : list of amount of token in wei to be injected each week
+
*/
+
function setValidatedRecipientList(
+
address[] calldata gaugeAddresses,
+
uint256[] calldata amountsPerPeriod,
+
uint8[] calldata maxPeriods
+
) external onlyOwner {
+
address[] memory gaugeList = s_gaugeList;
+
// validate all periods are finished
+
for (uint256 idx = 0; idx < gaugeList.length; idx++) {
+
Target memory target = s_targets[gaugeList[idx]];
+
if (target.periodNumber < target.maxPeriods) {
+
revert PeriodNotFinished(target.periodNumber, target.maxPeriods);
+
}
+
}
+
setRecipientList(gaugeAddresses, amountsPerPeriod, maxPeriods);
+
+
if (!checkSufficientBalances()) {
+
revert BalancesMismatch();
+
}
+
}
+
+
/**
+
* @notice Validate that the contract holds enough tokens to fulfill the current schedule
+
* @return bool true if balance of contract matches scheduled periods
+
*/
+
function checkSufficientBalances() public view returns (bool){
+
// iterates through all gauges to make sure there are enough tokens in the contract to fulfill all scheduled tasks
+
// (maxperiods - periodnumber) * amountPerPeriod == token.balanceOf(address(this))
+
+
address[] memory gaugeList = s_gaugeList;
+
uint256 totalDue;
+
for (uint256 idx = 0; idx < gaugeList.length; idx++) {
+
Target memory target = s_targets[gaugeList[idx]];
+
totalDue += (target.maxPeriods - target.periodNumber) * target.amountPerPeriod;
+
+
+
}
+
return totalDue <= IERC20(s_injectTokenAddress).balanceOf(address(this));
+
}
+
+
/**
+
* @notice Gets a list of addresses that are ready to inject
+
* @notice This is done by checking if the current period has ended, and should inject new funds directly after the end of each period.
+
* @return list of addresses that are ready to inject
+
*/
+
function getReadyGauges() public view returns (address[] memory) {
+
address[] memory gaugeList = s_gaugeList;
+
address[] memory ready = new address[](gaugeList.length);
+
address tokenAddress = s_injectTokenAddress;
+
uint256 count = 0;
+
uint256 minWaitPeriod = s_minWaitPeriodSeconds;
+
uint256 balance = IERC20(tokenAddress).balanceOf(address(this));
+
Target memory target;
+
for (uint256 idx = 0; idx < gaugeList.length; idx++) {
+
target = s_targets[gaugeList[idx]];
+
IChildChainGauge gauge = IChildChainGauge(gaugeList[idx]);
+
+
uint256 period_finish = gauge.reward_data(tokenAddress).period_finish;
+
+
if (
+
target.lastInjectionTimeStamp + minWaitPeriod <= block.timestamp &&
+
(period_finish <= block.timestamp) &&
+
balance >= target.amountPerPeriod &&
+
target.periodNumber < target.maxPeriods &&
+
gauge.reward_data(tokenAddress).distributor == address(this)
+
) {
+
ready[count] = gaugeList[idx];
+
count++;
+
balance -= target.amountPerPeriod;
+
}
+
}
+
if (count != gaugeList.length) {
+
// ready is a list large enough to hold all possible gauges
+
// count is the number of ready gauges that were inserted into ready
+
// this assembly shrinks ready to length count such that it removes empty elements
+
assembly {
+
mstore(ready, count)
+
}
+
}
+
return ready;
+
}
+
+
/**
+
* @notice Injects funds into the gauges provided
+
* @param ready the list of gauges to fund (addresses must be pre-approved)
+
*/
+
function _injectFunds(address[] memory ready) internal whenNotPaused {
+
uint256 minWaitPeriodSeconds = s_minWaitPeriodSeconds;
+
address tokenAddress = s_injectTokenAddress;
+
IERC20 token = IERC20(tokenAddress);
+
uint256 balance = token.balanceOf(address(this));
+
Target memory target;
+
+
for (uint256 idx = 0; idx < ready.length; idx++) {
+
target = s_targets[ready[idx]];
+
IChildChainGauge gauge = IChildChainGauge(ready[idx]);
+
uint256 period_finish = gauge.reward_data(tokenAddress).period_finish;
+
+
if (
+
target.lastInjectionTimeStamp + s_minWaitPeriodSeconds <= block.timestamp &&
+
+
period_finish <= block.timestamp &&
+
balance >= target.amountPerPeriod &&
+
target.periodNumber < target.maxPeriods &&
+
target.isActive == true
+
) {
+
+
SafeERC20.safeApprove(token, ready[idx], target.amountPerPeriod);
+
+
try gauge.deposit_reward_token(tokenAddress, uint256(target.amountPerPeriod)) {
+
+
+
+
+
+
s_targets[ready[idx]].lastInjectionTimeStamp = uint56(block.timestamp);
+
+
+
s_targets[ready[idx]].periodNumber++;
+
emit EmissionsInjection(ready[idx], tokenAddress, target.amountPerPeriod);
+
+
+
+
+
} catch {
+
revert RewardTokenError();
+
}
+
}
+
}
+
}
+
+
/**
+
* * @notice This is to allow the owner to manually trigger an injection of funds in place of the keeper
+
* @notice without abi encoding the gauge list
+
* @param gauges array of gauges to inject tokens to
+
*/
+
function injectFunds(address[] memory gauges) external onlyOwner {
+
_injectFunds(gauges);
+
}
+
+
/**
+
* @notice Get list of addresses that are ready for new token injections and return keeper-compatible payload
+
* @notice calldata required by the chainlink interface but not used in this case, use 0x
+
* @return upkeepNeeded signals if upkeep is needed
+
* @return performData is an abi encoded list of addresses that need funds
+
*/
+
function checkUpkeep(bytes calldata)
+
+
+
external
+
view
+
override
+
whenNotPaused
+
returns (bool upkeepNeeded, bytes memory performData)
+
{
+
address[] memory ready = getReadyGauges();
+
upkeepNeeded = ready.length > 0;
+
performData = abi.encode(ready);
+
return (upkeepNeeded, performData);
+
}
+
+
/**
+
* @notice Called by keeper to send funds to underfunded addresses
+
* @param performData The abi encoded list of addresses to fund
+
*/
+
function performUpkeep(bytes calldata performData) external override onlyKeeperRegistry whenNotPaused {
+
+
+
address[] memory needsFunding = abi.decode(performData, (address[]));
+
_injectFunds(needsFunding);
+
emit PerformedUpkeep(needsFunding);
+
}
+
+
/**
+
* @notice Withdraws the contract balance
+
*/
+
function withdrawGasToken() external onlyOwner {
+
address payable recipient = payable(owner());
+
if (recipient == address(0)) {
+
revert ZeroAddress();
+
}
+
uint256 amount = address(this).balance;
+
recipient.transfer(amount);
+
emit GasTokenWithdrawn(amount, recipient);
+
}
+
+
/**
+
* @notice Sweep the full contract's balance for a given ERC-20 token
+
* @param token The ERC-20 token which needs to be swept
+
*/
+
function sweep(address token) external onlyOwner {
+
uint256 balance = IERC20(token).balanceOf(address(this));
+
SafeERC20.safeTransfer(IERC20(token), owner(), balance);
+
emit ERC20Swept(token, owner(), balance);
+
}
+
+
+
+
/**
+
* @notice Set distributor from the injector back to the owner.
+
* @notice You will have to call set_reward_distributor back to the injector FROM the current distributor if you wish to continue using the injector
+
* @notice be aware that the only addresses able to call set_reward_distributor are the current distributor and balancer governance authorized accounts (the LM multisig)
+
* @param gauge The Gauge to set distributor for
+
* @param reward_token Token you are setting the distributor for
+
*/
+
function setDistributorToOwner(address gauge, address reward_token) external onlyOwner {
+
+
+
+
IChildChainGauge(gauge).set_reward_distributor(reward_token, msg.sender);
+
}
+
+
/**
+
* @notice Manually deposit an amount of tokens to the gauge
+
* @param gauge The Gauge to set distributor to injector owner
+
* @param reward_token Reward token you are seeding
+
* @param amount Amount to deposit
+
*/
+
function manualDeposit(address gauge, address reward_token, uint256 amount) external onlyOwner {
+
+
+
+
+
IChildChainGauge gaugeContract = IChildChainGauge(gauge);
+
IERC20 token = IERC20(reward_token);
+
SafeERC20.safeApprove(token, gauge, amount);
+
gaugeContract.deposit_reward_token(reward_token, amount);
+
emit EmissionsInjection(gauge, reward_token, amount);
+
}
+
+
/**
+
* @notice Sets the keeper registry address
+
*/
+
function setKeeperRegistryAddress(address keeperRegistryAddress) public onlyOwner {
+
+
+
s_keeperRegistryAddress = keeperRegistryAddress;
+
emit KeeperRegistryAddressUpdated(s_keeperRegistryAddress, keeperRegistryAddress);
+
+
+
+
}
+
+
/**
+
* @notice Sets the minimum wait period (in seconds) for addresses between injections
+
*/
+
function setMinWaitPeriodSeconds(uint256 period) public onlyOwner {
+
s_minWaitPeriodSeconds = period;
+
emit MinWaitPeriodUpdated(s_minWaitPeriodSeconds, period);
+
}
+
+
/**
+
* @notice Gets the keeper registry address
+
*/
+
function getKeeperRegistryAddress() external view returns (address keeperRegistryAddress) {
+
+
+
+
+
return s_keeperRegistryAddress;
+
}
+
+
/**
+
* @notice Gets the minimum wait period
+
*/
+
function getMinWaitPeriodSeconds() external view returns (uint256) {
+
return s_minWaitPeriodSeconds;
+
}
+
+
/**
+
* @notice Gets the list of addresses on the in the current configuration.
+
*/
+
function getWatchList() external view returns (address[] memory) {
+
return s_gaugeList;
+
}
+
+
/**
+
* @notice Sets the address of the ERC20 token this contract should handle
+
*/
+
function setInjectTokenAddress(address ERC20token) public onlyOwner {
+
s_injectTokenAddress = ERC20token;
+
emit SetHandlingToken(ERC20token);
+
}
+
/**
+
* @notice Gets the token this injector is operating on
+
*/
+
+
function getInjectTokenAddress() external view returns (address){
+
return s_injectTokenAddress;
+
}
+
/**
+
* @notice Gets configuration information for an address on the gaugelist
+
* @param targetAddress return Target struct for a given gauge according to the current scheduled distributions
+
*/
+
+
function getAccountInfo(address targetAddress)
+
+
+
external
+
view
+
returns (
+
uint256 amountPerPeriod,
+
bool isActive,
+
uint8 maxPeriods,
+
uint8 periodNumber,
+
uint56 lastInjectionTimeStamp
+
)
+
{
+
Target memory target = s_targets[targetAddress];
+
return (target.amountPerPeriod, target.isActive, target.maxPeriods, target.periodNumber, target.lastInjectionTimeStamp);
+
+
+
+
+
+
+
}
+
+
/**
+
* @notice Pauses the contract, which prevents executing performUpkeep
+
*/
+
function pause() external onlyOwner {
+
_pause();
+
}
+
+
/**
+
* @notice Unpauses the contract
+
*/
+
function unpause() external onlyOwner {
+
_unpause();
+
}
+
+
/**
+
* @notice takes in a list of addresses and reverts if there is a duplicate
+
*/
+
function revertOnDuplicate(address[] memory list) internal pure {
+
uint256 length = list.length;
+
if (length == 0) {
+
return;
+
}
+
for (uint256 i = 0; i < length - 1; i++) {
+
for (uint256 j = i + 1; j < length; j++) {
+
if (list[i] == list[j]) {
+
revert DuplicateAddress(list[i]);
+
}
+
}
+
}
+
// No duplicates found
+
}
+
+
modifier onlyKeeperRegistry() {
+
if (msg.sender != s_keeperRegistryAddress) {
+
revert OnlyKeeperRegistry(msg.sender);
+
}
+
_;
+
}
+
}
+
+ +
+
+
+
+ + + +
+
23 differences: 82 lines, 86 inline differences in 80 changed lines
Added(1,19)
Deleted(1,55)
Changed(80)
Changed in changed(12)
Ignored
+
+ +
+ Generated on August 6, 2024, 6:35 PM (UTC) by ExamDiff Pro 15.0.1.2. +
+
+ + +