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 @@ + + + + + + + +
+| + | + | + | + |
+
+
+
+ |
+
|
+ 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);
+ }
+ _;
+ }
+}
+
+
+ |
+
|
+ 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);
+ }
+ _;
+ }
+}
+
+
+ |
+