Skip to content
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -109,13 +109,13 @@ npx hardhat run scripts/emissions/<version>/register_log_upkeeps.ts --network <n
1. Deploy and configure `TokenUpkeepManager` contract by running:

```bash
npx hardhat run scripts/prices/deploy_upkeep_manager.ts --network <network>
npx hardhat run scripts/prices/<version>/deploy_upkeep_manager.ts --network <network>
```

2. Register log trigger upkeep for the deployed `TokenUpkeepManager` contract and set the trusted forwarder:

```bash
npx hardhat run scripts/prices/register_log_upkeep.ts --network <network>
npx hardhat run scripts/prices/<version>/register_log_upkeep.ts --network <network>
```

**Note:** Make sure the account running the script has enough LINK to pay for the initial upkeep registration determined by the `LOG_UPKEEP_FUND_AMOUNT` and `LOG_UPKEEP_GAS_LIMIT` environment variables.
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
// SPDX-License-Identifier: MIT
pragma solidity 0.8.6;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: Can also move the TokenUpkeep contract into the common folder


import {ITokenUpkeep} from "./interfaces/ITokenUpkeep.sol";
import {ITokenUpkeepManager} from "./interfaces/ITokenUpkeepManager.sol";
import {ITokenUpkeep} from "../interfaces/ITokenUpkeep.sol";
import {ITokenUpkeepManager} from "../interfaces/common/ITokenUpkeepManager.sol";

contract TokenUpkeep is ITokenUpkeep {
/// @inheritdoc ITokenUpkeep
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,26 +5,20 @@ import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol";
import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
import {EnumerableSet} from "@openzeppelin/contracts/utils/structs/EnumerableSet.sol";
import {StableEnumerableSet} from "./libraries/StableEnumerableSet.sol";
import {Log} from "@chainlink/contracts/src/v0.8/automation/interfaces/ILogAutomation.sol";

@airtoonricardo airtoonricardo May 27, 2025

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: Can we move the Log import above the StableEnumerableSet import? We usually try to separate external imports from internal ones.

import {IVoter} from "../../vendor/velodrome-contracts/contracts/interfaces/IVoter.sol";
import {IKeeperRegistryMaster} from "@chainlink/contracts/src/v0.8/automation/interfaces/v2_1/IKeeperRegistryMaster.sol";
import {IAutomationRegistrarV2_1} from "../interfaces/v2_1/IAutomationRegistrarV2_1.sol";
import {IUpkeepBalanceMonitor} from "../interfaces/IUpkeepBalanceMonitor.sol";
import {IPrices} from "./interfaces/IPrices.sol";
import {TokenUpkeep} from "./TokenUpkeep.sol";
import {ITokenUpkeepManager} from "./interfaces/ITokenUpkeepManager.sol";

contract TokenUpkeepManager is ITokenUpkeepManager, Ownable {
import {StableEnumerableSet} from "../libraries/StableEnumerableSet.sol";
import {IVoter} from "../../../vendor/velodrome-contracts/contracts/interfaces/IVoter.sol";
import {IPrices} from "../interfaces/IPrices.sol";
import {ITokenUpkeepManager} from "../interfaces/common/ITokenUpkeepManager.sol";

abstract contract TokenUpkeepManager is ITokenUpkeepManager, Ownable {
using SafeERC20 for IERC20;
using StableEnumerableSet for StableEnumerableSet.AddressSet;
using EnumerableSet for EnumerableSet.UintSet;

/// @inheritdoc ITokenUpkeepManager
address public immutable override linkToken;
/// @inheritdoc ITokenUpkeepManager
address public immutable override keeperRegistry;
/// @inheritdoc ITokenUpkeepManager
address public immutable override automationRegistrar;
/// @inheritdoc ITokenUpkeepManager
address public immutable override voter;
Expand All @@ -50,18 +44,17 @@ contract TokenUpkeepManager is ITokenUpkeepManager, Ownable {
uint256[] public override upkeepIds;

StableEnumerableSet.AddressSet internal _tokenList;
EnumerableSet.UintSet private _cancelledUpkeepIds;
EnumerableSet.UintSet internal _cancelledUpkeepIds;

uint256 private constant TOKENS_PER_UPKEEP = 100;
uint256 internal constant TOKENS_PER_UPKEEP = 100;
uint8 internal constant CONDITIONAL_TRIGGER_TYPE = 0;
string internal constant UPKEEP_NAME = "Token upkeep";
uint256 private constant UPKEEP_CANCEL_BUFFER = 20;
uint8 private constant CONDITIONAL_TRIGGER_TYPE = 0;
string private constant UPKEEP_NAME = "Token upkeep";

bytes32 private constant WHITELIST_TOKEN_EVENT = 0x44948130cf88523dbc150908a47dd6332c33a01a3869d7f2fa78e51d5a5f9c57;

constructor(
address _linkToken,
address _keeperRegistry,
address _automationRegistrar,
address _voter,
address _pricesOracle,
Expand All @@ -70,7 +63,6 @@ contract TokenUpkeepManager is ITokenUpkeepManager, Ownable {
uint32 _newUpkeepGasLimit
) {
linkToken = _linkToken;
keeperRegistry = _keeperRegistry;
automationRegistrar = _automationRegistrar;
voter = _voter;
pricesOracle = _pricesOracle;
Expand Down Expand Up @@ -337,51 +329,11 @@ contract TokenUpkeepManager is ITokenUpkeepManager, Ownable {
emit TokenDeregistered(_token);
}

function _registerTokenUpkeep() internal {
uint256 startIndex = _getNextUpkeepStartIndex(upkeepIds.length);
uint256 endIndex = startIndex + TOKENS_PER_UPKEEP;
address _tokenUpkeep = address(new TokenUpkeep(startIndex, endIndex));
isTokenUpkeep[_tokenUpkeep] = true;
IAutomationRegistrarV2_1.RegistrationParams memory params = IAutomationRegistrarV2_1.RegistrationParams({
name: UPKEEP_NAME,
encryptedEmail: "",
upkeepContract: _tokenUpkeep,
gasLimit: newUpkeepGasLimit,
adminAddress: address(this),
triggerType: CONDITIONAL_TRIGGER_TYPE,
checkData: "",
triggerConfig: "",
offchainConfig: "",
amount: newUpkeepFundAmount
});
uint256 upkeepId = _registerUpkeep(params);
upkeepIds.push(upkeepId);
tokenUpkeep[upkeepId] = _tokenUpkeep;
address forwarder = IKeeperRegistryMaster(keeperRegistry).getForwarder(upkeepId);
TokenUpkeep(_tokenUpkeep).setTrustedForwarder(forwarder);
IUpkeepBalanceMonitor(upkeepBalanceMonitor).addToWatchList(upkeepId);
emit TokenUpkeepRegistered(_tokenUpkeep, upkeepId, startIndex, endIndex);
}
function _registerTokenUpkeep() internal virtual;

function _registerUpkeep(IAutomationRegistrarV2_1.RegistrationParams memory _params) internal returns (uint256) {
IERC20(linkToken).safeIncreaseAllowance(automationRegistrar, _params.amount);
uint256 upkeepID = IAutomationRegistrarV2_1(automationRegistrar).registerUpkeep(_params);
if (upkeepID != 0) {
return upkeepID;
} else {
revert AutoApproveDisabled();
}
}
function _cancelTokenUpkeep(uint256 _upkeepId) internal virtual;

function _cancelTokenUpkeep(uint256 _upkeepId) internal {
upkeepIds.pop();
delete isTokenUpkeep[tokenUpkeep[_upkeepId]];
delete tokenUpkeep[_upkeepId];
_cancelledUpkeepIds.add(_upkeepId);
IUpkeepBalanceMonitor(upkeepBalanceMonitor).removeFromWatchList(_upkeepId);
IKeeperRegistryMaster(keeperRegistry).cancelUpkeep(_upkeepId);
emit TokenUpkeepCancelled(_upkeepId);
}
function _withdrawUpkeep(uint256 _upkeepId) internal virtual;

function _finishUpkeepAndCleanup(uint256 _lastRun) internal {
finishedUpkeeps[_lastRun]++;
Expand All @@ -395,12 +347,6 @@ contract TokenUpkeepManager is ITokenUpkeepManager, Ownable {
emit TokenListCleaned();
}

function _withdrawUpkeep(uint256 _upkeepId) internal {
_cancelledUpkeepIds.remove(_upkeepId);
IKeeperRegistryMaster(keeperRegistry).withdrawFunds(_upkeepId, address(this));
emit TokenUpkeepWithdrawn(_upkeepId);
}

function _getNextUpkeepStartIndex(uint256 _upkeepCount) internal pure returns (uint256) {
return _upkeepCount * TOKENS_PER_UPKEEP;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,9 +36,6 @@ interface ITokenUpkeepManager {
/// @notice LINK token address
function linkToken() external view returns (address);

/// @notice Keeper registry address
function keeperRegistry() external view returns (address);

/// @notice Automation registrar address
function automationRegistrar() external view returns (address);

Expand Down
9 changes: 9 additions & 0 deletions contracts/prices/interfaces/v2_1/ITokenUpkeepManagerV2_1.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
// SPDX-License-Identifier: MIT
pragma solidity 0.8.6;

import {ITokenUpkeepManager} from "../common/ITokenUpkeepManager.sol";

interface ITokenUpkeepManagerV2_1 is ITokenUpkeepManager {
/// @notice Keeper registry address
function keeperRegistry() external view returns (address);
}
9 changes: 9 additions & 0 deletions contracts/prices/interfaces/v2_3/ITokenUpkeepManagerV2_3.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
// SPDX-License-Identifier: MIT
pragma solidity 0.8.6;

import {ITokenUpkeepManager} from "../common/ITokenUpkeepManager.sol";

interface ITokenUpkeepManagerV2_3 is ITokenUpkeepManager {
/// @notice Keeper registry address
function keeperRegistry() external view returns (address payable);
}
95 changes: 95 additions & 0 deletions contracts/prices/v2_1/TokenUpkeepManagerV2_1.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
// SPDX-License-Identifier: MIT
pragma solidity 0.8.6;

import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
import {EnumerableSet} from "@openzeppelin/contracts/utils/structs/EnumerableSet.sol";
import {IKeeperRegistryMaster} from "@chainlink/contracts/src/v0.8/automation/interfaces/v2_1/IKeeperRegistryMaster.sol";
import {IAutomationRegistrarV2_1} from "../../interfaces/v2_1/IAutomationRegistrarV2_1.sol";
import {ITokenUpkeepManagerV2_1} from "../interfaces/v2_1/ITokenUpkeepManagerV2_1.sol";
import {IUpkeepBalanceMonitor} from "../../interfaces/IUpkeepBalanceMonitor.sol";
import {TokenUpkeepManager} from "../common/TokenUpkeepManager.sol";
import {TokenUpkeep} from "../common/TokenUpkeep.sol";

contract TokenUpkeepManagerV2_1 is TokenUpkeepManager, ITokenUpkeepManagerV2_1 {
using SafeERC20 for IERC20;
using EnumerableSet for EnumerableSet.UintSet;

/// @inheritdoc ITokenUpkeepManagerV2_1
address public immutable override keeperRegistry;

constructor(
address _linkToken,
address _keeperRegistry,
address _automationRegistrar,
address _voter,
address _pricesOracle,
address _upkeepBalanceMonitor,
uint96 _newUpkeepFundAmount,
uint32 _newUpkeepGasLimit
)
TokenUpkeepManager(
_linkToken,
_automationRegistrar,
_voter,
_pricesOracle,
_upkeepBalanceMonitor,
_newUpkeepFundAmount,
_newUpkeepGasLimit
)
{
keeperRegistry = _keeperRegistry;
}

function _registerTokenUpkeep() internal override {
uint256 startIndex = _getNextUpkeepStartIndex(upkeepIds.length);
uint256 endIndex = startIndex + TOKENS_PER_UPKEEP;
address _tokenUpkeep = address(new TokenUpkeep(startIndex, endIndex));
isTokenUpkeep[_tokenUpkeep] = true;
IAutomationRegistrarV2_1.RegistrationParams memory params = IAutomationRegistrarV2_1.RegistrationParams({
name: UPKEEP_NAME,
encryptedEmail: "",
upkeepContract: _tokenUpkeep,
gasLimit: newUpkeepGasLimit,
adminAddress: address(this),
triggerType: CONDITIONAL_TRIGGER_TYPE,
checkData: "",
triggerConfig: "",
offchainConfig: "",
amount: newUpkeepFundAmount
});
uint256 upkeepId = _registerUpkeep(params);
upkeepIds.push(upkeepId);
tokenUpkeep[upkeepId] = _tokenUpkeep;
address forwarder = IKeeperRegistryMaster(keeperRegistry).getForwarder(upkeepId);
TokenUpkeep(_tokenUpkeep).setTrustedForwarder(forwarder);
IUpkeepBalanceMonitor(upkeepBalanceMonitor).addToWatchList(upkeepId);
emit TokenUpkeepRegistered(_tokenUpkeep, upkeepId, startIndex, endIndex);
}

function _registerUpkeep(IAutomationRegistrarV2_1.RegistrationParams memory _params) internal returns (uint256) {
IERC20(linkToken).safeIncreaseAllowance(automationRegistrar, _params.amount);
uint256 upkeepID = IAutomationRegistrarV2_1(automationRegistrar).registerUpkeep(_params);
if (upkeepID != 0) {
return upkeepID;
} else {
revert AutoApproveDisabled();
}
}

function _cancelTokenUpkeep(uint256 _upkeepId) internal override {
upkeepIds.pop();
delete isTokenUpkeep[tokenUpkeep[_upkeepId]];
delete tokenUpkeep[_upkeepId];
_cancelledUpkeepIds.add(_upkeepId);
IUpkeepBalanceMonitor(upkeepBalanceMonitor).removeFromWatchList(_upkeepId);
IKeeperRegistryMaster(keeperRegistry).cancelUpkeep(_upkeepId);
emit TokenUpkeepCancelled(_upkeepId);
}

function _withdrawUpkeep(uint256 _upkeepId) internal override {
_cancelledUpkeepIds.remove(_upkeepId);
IKeeperRegistryMaster(keeperRegistry).withdrawFunds(_upkeepId, address(this));
emit TokenUpkeepWithdrawn(_upkeepId);
}
}
96 changes: 96 additions & 0 deletions contracts/prices/v2_3/TokenUpkeepManagerV2_3.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
// SPDX-License-Identifier: MIT
pragma solidity 0.8.6;

import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
import {EnumerableSet} from "@openzeppelin/contracts/utils/structs/EnumerableSet.sol";
import {IAutomationRegistryMaster2_3} from "@chainlink/contracts/src/v0.8/automation/interfaces/v2_3/IAutomationRegistryMaster2_3.sol";
import {IAutomationRegistrarV2_3} from "../../interfaces/v2_3/IAutomationRegistrarV2_3.sol";
import {ITokenUpkeepManagerV2_3} from "../interfaces/v2_3/ITokenUpkeepManagerV2_3.sol";
import {IUpkeepBalanceMonitor} from "../../interfaces/IUpkeepBalanceMonitor.sol";
import {TokenUpkeepManager} from "../common/TokenUpkeepManager.sol";
import {TokenUpkeep} from "../common/TokenUpkeep.sol";

contract TokenUpkeepManagerV2_3 is TokenUpkeepManager, ITokenUpkeepManagerV2_3 {
using SafeERC20 for IERC20;
using EnumerableSet for EnumerableSet.UintSet;

/// @inheritdoc ITokenUpkeepManagerV2_3
address payable public immutable override keeperRegistry;

constructor(
address _linkToken,
address payable _keeperRegistry,
address _automationRegistrar,
address _voter,
address _pricesOracle,
address _upkeepBalanceMonitor,
uint96 _newUpkeepFundAmount,
uint32 _newUpkeepGasLimit
)
TokenUpkeepManager(
_linkToken,
_automationRegistrar,
_voter,
_pricesOracle,
_upkeepBalanceMonitor,
_newUpkeepFundAmount,
_newUpkeepGasLimit
)
{
keeperRegistry = _keeperRegistry;
}

function _registerTokenUpkeep() internal override {
uint256 startIndex = _getNextUpkeepStartIndex(upkeepIds.length);
uint256 endIndex = startIndex + TOKENS_PER_UPKEEP;
address _tokenUpkeep = address(new TokenUpkeep(startIndex, endIndex));
isTokenUpkeep[_tokenUpkeep] = true;
IAutomationRegistrarV2_3.RegistrationParams memory params = IAutomationRegistrarV2_3.RegistrationParams({
name: UPKEEP_NAME,
encryptedEmail: "",
upkeepContract: _tokenUpkeep,
gasLimit: newUpkeepGasLimit,
adminAddress: address(this),
triggerType: CONDITIONAL_TRIGGER_TYPE,
checkData: "",
triggerConfig: "",
offchainConfig: "",
amount: newUpkeepFundAmount,
billingToken: IERC20(linkToken)
});
uint256 upkeepId = _registerUpkeep(params);
upkeepIds.push(upkeepId);
tokenUpkeep[upkeepId] = _tokenUpkeep;
address forwarder = IAutomationRegistryMaster2_3(keeperRegistry).getForwarder(upkeepId);
TokenUpkeep(_tokenUpkeep).setTrustedForwarder(forwarder);
IUpkeepBalanceMonitor(upkeepBalanceMonitor).addToWatchList(upkeepId);
emit TokenUpkeepRegistered(_tokenUpkeep, upkeepId, startIndex, endIndex);
}

function _registerUpkeep(IAutomationRegistrarV2_3.RegistrationParams memory _params) internal returns (uint256) {
IERC20(linkToken).safeIncreaseAllowance(automationRegistrar, _params.amount);
uint256 upkeepID = IAutomationRegistrarV2_3(automationRegistrar).registerUpkeep(_params);
if (upkeepID != 0) {
return upkeepID;
} else {
revert AutoApproveDisabled();
}
}

function _cancelTokenUpkeep(uint256 _upkeepId) internal override {
upkeepIds.pop();
delete isTokenUpkeep[tokenUpkeep[_upkeepId]];
delete tokenUpkeep[_upkeepId];
_cancelledUpkeepIds.add(_upkeepId);
IUpkeepBalanceMonitor(upkeepBalanceMonitor).removeFromWatchList(_upkeepId);
IAutomationRegistryMaster2_3(keeperRegistry).cancelUpkeep(_upkeepId);
emit TokenUpkeepCancelled(_upkeepId);
}

function _withdrawUpkeep(uint256 _upkeepId) internal override {
_cancelledUpkeepIds.remove(_upkeepId);
IAutomationRegistryMaster2_3(keeperRegistry).withdrawFunds(_upkeepId, address(this));
emit TokenUpkeepWithdrawn(_upkeepId);
}
}
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,8 @@
"test:unit": "npx hardhat test test/unit/*.test.ts test/**/unit/*.test.ts test/**/unit/**/*.test.ts",
"test:fork": "FORK_ENABLED=true FORK_CHAIN=optimism BLOCK_NUMBER=128856225 npx hardhat test test/emissions/fork/v2_1/GaugeUpkeepManagerV2_1.test.ts",
"test:fork:v2_3": "FORK_ENABLED=true FORK_CHAIN=base BLOCK_NUMBER=27557816 npx hardhat test test/emissions/fork/v2_3/GaugeUpkeepManagerV2_3.test.ts",
"test:fork:prices": "FORK_ENABLED=true FORK_CHAIN=optimism BLOCK_NUMBER=135890825 npx hardhat test test/prices/fork/TokenUpkeepManager.test.ts",
"test:fork:prices": "FORK_ENABLED=true FORK_CHAIN=optimism BLOCK_NUMBER=135890825 npx hardhat test test/prices/fork/v2_1/TokenUpkeepManagerV2_1.test.ts",
"test:fork:prices:v2_3": "FORK_ENABLED=true FORK_CHAIN=base BLOCK_NUMBER=30517684 npx hardhat test test/prices/fork/v2_3/TokenUpkeepManagerV2_3.test.ts",

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this will likely have some conflicts with the changes in #55, so once that's merged this file should be updated accordingly (i.e.: test:fork should run all fork tests, etc)

"prettier:check": "prettier '**/*' --check",
"prettier:write": "prettier '**/*' --write"
},
Expand Down
Loading