Contract Name:
KeeperRegistry2_1
Contract Source Code:
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import "../utils/Context.sol";
/**
* @dev Contract module which provides a basic access control mechanism, where
* there is an account (an owner) that can be granted exclusive access to
* specific functions.
*
* By default, the owner account will be the one that deploys the contract. This
* can later be changed with {transferOwnership}.
*
* This module is used through inheritance. It will make available the modifier
* `onlyOwner`, which can be applied to your functions to restrict their use to
* the owner.
*/
abstract contract Ownable is Context {
address private _owner;
event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);
/**
* @dev Initializes the contract setting the deployer as the initial owner.
*/
constructor() {
_setOwner(_msgSender());
}
/**
* @dev Returns the address of the current owner.
*/
function owner() public view virtual returns (address) {
return _owner;
}
/**
* @dev Throws if called by any account other than the owner.
*/
modifier onlyOwner() {
require(owner() == _msgSender(), "Ownable: caller is not the owner");
_;
}
/**
* @dev Leaves the contract without owner. It will not be possible to call
* `onlyOwner` functions anymore. Can only be called by the current owner.
*
* NOTE: Renouncing ownership will leave the contract without an owner,
* thereby removing any functionality that is only available to the owner.
*/
function renounceOwnership() public virtual onlyOwner {
_setOwner(address(0));
}
/**
* @dev Transfers ownership of the contract to a new account (`newOwner`).
* Can only be called by the current owner.
*/
function transferOwnership(address newOwner) public virtual onlyOwner {
require(newOwner != address(0), "Ownable: new owner is the zero address");
_setOwner(newOwner);
}
function _setOwner(address newOwner) private {
address oldOwner = _owner;
_owner = newOwner;
emit OwnershipTransferred(oldOwner, newOwner);
}
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
/**
* @dev Provides information about the current execution context, including the
* sender of the transaction and its data. While these are generally available
* via msg.sender and msg.data, they should not be accessed in such a direct
* manner, since when dealing with meta-transactions the account sending and
* paying for execution may not be the actual sender (as far as an application
* is concerned).
*
* This contract is only required for intermediate, library-like contracts.
*/
abstract contract Context {
function _msgSender() internal view virtual returns (address) {
return msg.sender;
}
function _msgData() internal view virtual returns (bytes calldata) {
return msg.data;
}
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
abstract contract ExecutionPrevention {
error OnlySimulatedBackend();
/**
* @notice method that allows it to be simulated via eth_call by checking that
* the sender is the zero address.
*/
function preventExecution() internal view {
if (tx.origin != address(0)) {
revert OnlySimulatedBackend();
}
}
/**
* @notice modifier that allows it to be simulated via eth_call by checking
* that the sender is the zero address.
*/
modifier cannotExecute() {
preventExecution();
_;
}
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
/**
* @notice OnchainConfig of the registry
* @dev only used in params and return values
* @member paymentPremiumPPB payment premium rate oracles receive on top of
* being reimbursed for gas, measured in parts per billion
* @member flatFeeMicroLink flat fee paid to oracles for performing upkeeps,
* priced in MicroLink; can be used in conjunction with or independently of
* paymentPremiumPPB
* @member checkGasLimit gas limit when checking for upkeep
* @member stalenessSeconds number of seconds that is allowed for feed data to
* be stale before switching to the fallback pricing
* @member gasCeilingMultiplier multiplier to apply to the fast gas feed price
* when calculating the payment ceiling for keepers
* @member minUpkeepSpend minimum LINK that an upkeep must spend before cancelling
* @member maxPerformGas max executeGas allowed for an upkeep on this registry
* @member fallbackGasPrice gas price used if the gas price feed is stale
* @member fallbackLinkPrice LINK price used if the LINK price feed is stale
* @member transcoder address of the transcoder contract
* @member registrar address of the registrar contract
*/
struct OnchainConfig {
uint32 paymentPremiumPPB;
uint32 flatFeeMicroLink; // min 0.000001 LINK, max 4294 LINK
uint32 checkGasLimit;
uint24 stalenessSeconds;
uint16 gasCeilingMultiplier;
uint96 minUpkeepSpend;
uint32 maxPerformGas;
uint32 maxCheckDataSize;
uint32 maxPerformDataSize;
uint256 fallbackGasPrice;
uint256 fallbackLinkPrice;
address transcoder;
address registrar;
}
/**
* @notice state of the registry
* @dev only used in params and return values
* @member nonce used for ID generation
* @member ownerLinkBalance withdrawable balance of LINK by contract owner
* @member expectedLinkBalance the expected balance of LINK of the registry
* @member totalPremium the total premium collected on registry so far
* @member numUpkeeps total number of upkeeps on the registry
* @member configCount ordinal number of current config, out of all configs applied to this contract so far
* @member latestConfigBlockNumber last block at which this config was set
* @member latestConfigDigest domain-separation tag for current config
* @member latestEpoch for which a report was transmitted
* @member paused freeze on execution scoped to the entire registry
*/
struct State {
uint32 nonce;
uint96 ownerLinkBalance;
uint256 expectedLinkBalance;
uint96 totalPremium;
uint256 numUpkeeps;
uint32 configCount;
uint32 latestConfigBlockNumber;
bytes32 latestConfigDigest;
uint32 latestEpoch;
bool paused;
}
/**
* @notice all information about an upkeep
* @dev only used in return values
* @member target the contract which needs to be serviced
* @member executeGas the gas limit of upkeep execution
* @member checkData the checkData bytes for this upkeep
* @member balance the balance of this upkeep
* @member admin for this upkeep
* @member maxValidBlocknumber until which block this upkeep is valid
* @member lastPerformBlockNumber the last block number when this upkeep was performed
* @member amountSpent the amount this upkeep has spent
* @member paused if this upkeep has been paused
* @member skipSigVerification skip signature verification in transmit for a low security low cost model
*/
struct UpkeepInfo {
address target;
uint32 executeGas;
bytes checkData;
uint96 balance;
address admin;
uint64 maxValidBlocknumber;
uint32 lastPerformBlockNumber;
uint96 amountSpent;
bool paused;
bytes offchainConfig;
}
enum UpkeepFailureReason {
NONE,
UPKEEP_CANCELLED,
UPKEEP_PAUSED,
TARGET_CHECK_REVERTED,
UPKEEP_NOT_NEEDED,
PERFORM_DATA_EXCEEDS_LIMIT,
INSUFFICIENT_BALANCE
}
interface AutomationRegistryBaseInterface {
function registerUpkeep(
address target,
uint32 gasLimit,
address admin,
bytes calldata checkData,
bytes calldata offchainConfig
) external returns (uint256 id);
function cancelUpkeep(uint256 id) external;
function pauseUpkeep(uint256 id) external;
function unpauseUpkeep(uint256 id) external;
function transferUpkeepAdmin(uint256 id, address proposed) external;
function acceptUpkeepAdmin(uint256 id) external;
function updateCheckData(uint256 id, bytes calldata newCheckData) external;
function addFunds(uint256 id, uint96 amount) external;
function setUpkeepGasLimit(uint256 id, uint32 gasLimit) external;
function setUpkeepOffchainConfig(uint256 id, bytes calldata config) external;
function getUpkeep(uint256 id) external view returns (UpkeepInfo memory upkeepInfo);
function getActiveUpkeepIDs(uint256 startIndex, uint256 maxCount) external view returns (uint256[] memory);
function getTransmitterInfo(
address query
) external view returns (bool active, uint8 index, uint96 balance, uint96 lastCollected, address payee);
function getState()
external
view
returns (
State memory state,
OnchainConfig memory config,
address[] memory signers,
address[] memory transmitters,
uint8 f
);
}
/**
* @dev The view methods are not actually marked as view in the implementation
* but we want them to be easily queried off-chain. Solidity will not compile
* if we actually inherit from this interface, so we document it here.
*/
interface AutomationRegistryInterface is AutomationRegistryBaseInterface {
function checkUpkeep(
uint256 upkeepId
)
external
view
returns (
bool upkeepNeeded,
bytes memory performData,
UpkeepFailureReason upkeepFailureReason,
uint256 gasUsed,
uint256 fastGasWei,
uint256 linkNative
);
}
interface AutomationRegistryExecutableInterface is AutomationRegistryBaseInterface {
function checkUpkeep(
uint256 upkeepId
)
external
returns (
bool upkeepNeeded,
bytes memory performData,
UpkeepFailureReason upkeepFailureReason,
uint256 gasUsed,
uint256 fastGasWei,
uint256 linkNative
);
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
interface AutomationCompatibleInterface {
/**
* @notice method that is simulated by the keepers to see if any work actually
* needs to be performed. This method does does not actually need to be
* executable, and since it is only ever simulated it can consume lots of gas.
* @dev To ensure that it is never called, you may want to add the
* cannotExecute modifier from KeeperBase to your implementation of this
* method.
* @param checkData specified in the upkeep registration so it is always the
* same for a registered upkeep. This can easily be broken down into specific
* arguments using `abi.decode`, so multiple upkeeps can be registered on the
* same contract and easily differentiated by the contract.
* @return upkeepNeeded boolean to indicate whether the keeper should call
* performUpkeep or not.
* @return performData bytes that the keeper should call performUpkeep with, if
* upkeep is needed. If you would like to encode data to decode later, try
* `abi.encode`.
*/
function checkUpkeep(bytes calldata checkData) external returns (bool upkeepNeeded, bytes memory performData);
/**
* @notice method that is actually executed by the keepers, via the registry.
* The data returned by the checkUpkeep simulation will be passed into
* this method to actually be executed.
* @dev The input to this method should not be trusted, and the caller of the
* method should not even be restricted to any single registry. Anyone should
* be able call it, and the input should be validated, there is no guarantee
* that the data passed in is the performData returned from checkUpkeep. This
* could happen due to malicious keepers, racing keepers, or simply a state
* change while the performUpkeep transaction is waiting for confirmation.
* Always validate the data passed in.
* @param performData is the data which was passed back from the checkData
* simulation. If it is encoded, it can easily be decoded into other types by
* calling `abi.decode`. This data should not be trusted, and should be
* validated against the contract's current state.
*/
function performUpkeep(bytes calldata performData) external;
}
// SPDX-License-Identifier: MIT
/**
* @notice This is a deprecated interface. Please use AutomationCompatibleInterface directly.
*/
pragma solidity ^0.8.0;
import {AutomationCompatibleInterface as KeeperCompatibleInterface} from "./AutomationCompatibleInterface.sol";
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import "../UpkeepFormat.sol";
interface MigratableKeeperRegistryInterfaceV2 {
/**
* @notice Migrates upkeeps from one registry to another, including LINK and upkeep params.
* Only callable by the upkeep admin. All upkeeps must have the same admin. Can only migrate active upkeeps.
* @param upkeepIDs ids of upkeeps to migrate
* @param destination the address of the registry to migrate to
*/
function migrateUpkeeps(uint256[] calldata upkeepIDs, address destination) external;
/**
* @notice Called by other registries when migrating upkeeps. Only callable by other registries.
* @param encodedUpkeeps abi encoding of upkeeps to import - decoded by the transcoder
*/
function receiveUpkeeps(bytes calldata encodedUpkeeps) external;
/**
* @notice Specifies the version of upkeep data that this registry requires in order to import
*/
function upkeepVersion() external view returns (uint8 version);
}
// SPDX-License-Identifier: MIT
import "../UpkeepFormat.sol";
pragma solidity ^0.8.0;
interface UpkeepTranscoderInterface {
function transcodeUpkeeps(
UpkeepFormat fromVersion,
UpkeepFormat toVersion,
bytes calldata encodedUpkeeps
) external view returns (bytes memory);
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
interface UpkeepTranscoderInterfaceV2 {
function transcodeUpkeeps(
uint8 fromVersion,
uint8 toVersion,
bytes calldata encodedUpkeeps
) external view returns (bytes memory);
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
/**
* @dev this struct is only maintained for backwards compatibility with MigratableKeeperRegistryInterface
* it should be deprecated in the future in favor of MigratableKeeperRegistryInterfaceV2
*/
enum UpkeepFormat {
V1,
V2,
V3
}
// SPDX-License-Identifier: MIT
pragma solidity 0.8.16;
import {IAutomationRegistryConsumer} from "./interfaces/IAutomationRegistryConsumer.sol";
uint256 constant PERFORM_GAS_CUSHION = 5_000;
/**
* @title AutomationForwarder is a relayer that sits between the registry and the customer's target contract
* @dev The purpose of the forwarder is to give customers a consistent address to authorize against,
* which stays consistent between migrations. The Forwarder also exposes the registry address, so that users who
* want to programatically interact with the registry (ie top up funds) can do so.
*/
contract AutomationForwarder {
/// @notice the user's target contract address
address private immutable i_target;
/// @notice the shared logic address
address private immutable i_logic;
IAutomationRegistryConsumer private s_registry;
constructor(address target, address registry, address logic) {
s_registry = IAutomationRegistryConsumer(registry);
i_target = target;
i_logic = logic;
}
/**
* @notice forward is called by the registry and forwards the call to the target
* @param gasAmount is the amount of gas to use in the call
* @param data is the 4 bytes function selector + arbitrary function data
* @return success indicating whether the target call succeeded or failed
*/
function forward(uint256 gasAmount, bytes memory data) external returns (bool success, uint256 gasUsed) {
if (msg.sender != address(s_registry)) revert();
address target = i_target;
gasUsed = gasleft();
assembly {
let g := gas()
// Compute g -= PERFORM_GAS_CUSHION and check for underflow
if lt(g, PERFORM_GAS_CUSHION) {
revert(0, 0)
}
g := sub(g, PERFORM_GAS_CUSHION)
// if g - g//64 <= gasAmount, revert
// (we subtract g//64 because of EIP-150)
if iszero(gt(sub(g, div(g, 64)), gasAmount)) {
revert(0, 0)
}
// solidity calls check that a contract actually exists at the destination, so we do the same
if iszero(extcodesize(target)) {
revert(0, 0)
}
// call with exact gas
success := call(gasAmount, target, 0, add(data, 0x20), mload(data), 0, 0)
}
gasUsed = gasUsed - gasleft();
return (success, gasUsed);
}
function getTarget() external view returns (address) {
return i_target;
}
fallback() external {
// copy to memory for assembly access
address logic = i_logic;
// copied directly from OZ's Proxy contract
assembly {
// Copy msg.data. We take full control of memory in this inline assembly
// block because it will not return to Solidity code. We overwrite the
// Solidity scratch pad at memory position 0.
calldatacopy(0, 0, calldatasize())
// out and outsize are 0 because we don't know the size yet.
let result := delegatecall(gas(), logic, 0, calldatasize(), 0, 0)
// Copy the returned data.
returndatacopy(0, 0, returndatasize())
switch result
// delegatecall returns 0 on error.
case 0 {
revert(0, returndatasize())
}
default {
return(0, returndatasize())
}
}
}
}
// SPDX-License-Identifier: MIT
pragma solidity 0.8.16;
import {IAutomationRegistryConsumer} from "./interfaces/IAutomationRegistryConsumer.sol";
import {ITypeAndVersion} from "../../../shared/interfaces/ITypeAndVersion.sol";
contract AutomationForwarderLogic is ITypeAndVersion {
IAutomationRegistryConsumer private s_registry;
string public constant typeAndVersion = "AutomationForwarder 1.0.0";
/**
* @notice updateRegistry is called by the registry during migrations
* @param newRegistry is the registry that this forwarder is being migrated to
*/
function updateRegistry(address newRegistry) external {
if (msg.sender != address(s_registry)) revert();
s_registry = IAutomationRegistryConsumer(newRegistry);
}
function getRegistry() external view returns (IAutomationRegistryConsumer) {
return s_registry;
}
}
// SPDX-License-Identifier: MIT
pragma solidity 0.8.16;
import {LinkTokenInterface} from "../../../shared/interfaces/LinkTokenInterface.sol";
import {IKeeperRegistryMaster} from "./interfaces/IKeeperRegistryMaster.sol";
import {TypeAndVersionInterface} from "../../../interfaces/TypeAndVersionInterface.sol";
import {ConfirmedOwner} from "../../../shared/access/ConfirmedOwner.sol";
import {IERC677Receiver} from "../../../shared/interfaces/IERC677Receiver.sol";
/**
* @notice Contract to accept requests for upkeep registrations
* @dev There are 2 registration workflows in this contract
* Flow 1. auto approve OFF / manual registration - UI calls `register` function on this contract, this contract owner at a later time then manually
* calls `approve` to register upkeep and emit events to inform UI and others interested.
* Flow 2. auto approve ON / real time registration - UI calls `register` function as before, which calls the `registerUpkeep` function directly on
* keeper registry and then emits approved event to finish the flow automatically without manual intervention.
* The idea is to have same interface(functions,events) for UI or anyone using this contract irrespective of auto approve being enabled or not.
* they can just listen to `RegistrationRequested` & `RegistrationApproved` events and know the status on registrations.
*/
contract AutomationRegistrar2_1 is TypeAndVersionInterface, ConfirmedOwner, IERC677Receiver {
/**
* DISABLED: No auto approvals, all new upkeeps should be approved manually.
* ENABLED_SENDER_ALLOWLIST: Auto approvals for allowed senders subject to max allowed. Manual for rest.
* ENABLED_ALL: Auto approvals for all new upkeeps subject to max allowed.
*/
enum AutoApproveType {
DISABLED,
ENABLED_SENDER_ALLOWLIST,
ENABLED_ALL
}
bytes4 private constant REGISTER_REQUEST_SELECTOR = this.register.selector;
mapping(bytes32 => PendingRequest) private s_pendingRequests;
mapping(uint8 => TriggerRegistrationStorage) private s_triggerRegistrations;
LinkTokenInterface public immutable LINK;
/**
* @notice versions:
* - KeeperRegistrar 2.1.0: Update for compatability with registry 2.1.0
* Add auto approval levels by type
* - KeeperRegistrar 2.0.0: Remove source from register
* Breaks our example of "Register an Upkeep using your own deployed contract"
* - KeeperRegistrar 1.1.0: Add functionality for sender allowlist in auto approve
* : Remove rate limit and add max allowed for auto approve
* - KeeperRegistrar 1.0.0: initial release
*/
string public constant override typeAndVersion = "AutomationRegistrar 2.1.0";
/**
* @notice TriggerRegistrationStorage stores the auto-approval levels for upkeeps by type
* @member autoApproveType the auto approval setting (see enum)
* @member autoApproveMaxAllowed the max number of upkeeps that can be auto approved of this type
* @member approvedCount the count of upkeeps auto approved of this type
*/
struct TriggerRegistrationStorage {
AutoApproveType autoApproveType;
uint32 autoApproveMaxAllowed;
uint32 approvedCount;
}
/**
* @notice InitialTriggerConfig configures the auto-approval levels for upkeeps by trigger type
* @dev this struct is only used in the constructor to set the initial values for various trigger configs
* @member triggerType the upkeep type to configure
* @member autoApproveType the auto approval setting (see enum)
* @member autoApproveMaxAllowed the max number of upkeeps that can be auto approved of this type
*/
struct InitialTriggerConfig {
uint8 triggerType;
AutoApproveType autoApproveType;
uint32 autoApproveMaxAllowed;
}
struct RegistrarConfig {
IKeeperRegistryMaster keeperRegistry;
uint96 minLINKJuels;
}
struct PendingRequest {
address admin;
uint96 balance;
}
struct RegistrationParams {
string name;
bytes encryptedEmail;
address upkeepContract;
uint32 gasLimit;
address adminAddress;
uint8 triggerType;
bytes checkData;
bytes triggerConfig;
bytes offchainConfig;
uint96 amount;
}
RegistrarConfig private s_config;
// Only applicable if s_config.configType is ENABLED_SENDER_ALLOWLIST
mapping(address => bool) private s_autoApproveAllowedSenders;
event RegistrationRequested(
bytes32 indexed hash,
string name,
bytes encryptedEmail,
address indexed upkeepContract,
uint32 gasLimit,
address adminAddress,
uint8 triggerType,
bytes triggerConfig,
bytes offchainConfig,
bytes checkData,
uint96 amount
);
event RegistrationApproved(bytes32 indexed hash, string displayName, uint256 indexed upkeepId);
event RegistrationRejected(bytes32 indexed hash);
event AutoApproveAllowedSenderSet(address indexed senderAddress, bool allowed);
event ConfigChanged(address keeperRegistry, uint96 minLINKJuels);
event TriggerConfigSet(uint8 triggerType, AutoApproveType autoApproveType, uint32 autoApproveMaxAllowed);
error InvalidAdminAddress();
error RequestNotFound();
error HashMismatch();
error OnlyAdminOrOwner();
error InsufficientPayment();
error RegistrationRequestFailed();
error OnlyLink();
error AmountMismatch();
error SenderMismatch();
error FunctionNotPermitted();
error LinkTransferFailed(address to);
error InvalidDataLength();
/**
* @param LINKAddress Address of Link token
* @param keeperRegistry keeper registry address
* @param minLINKJuels minimum LINK that new registrations should fund their upkeep with
* @param triggerConfigs the initial config for individual triggers
*/
constructor(
address LINKAddress,
address keeperRegistry,
uint96 minLINKJuels,
InitialTriggerConfig[] memory triggerConfigs
) ConfirmedOwner(msg.sender) {
LINK = LinkTokenInterface(LINKAddress);
setConfig(keeperRegistry, minLINKJuels);
for (uint256 idx = 0; idx < triggerConfigs.length; idx++) {
setTriggerConfig(
triggerConfigs[idx].triggerType,
triggerConfigs[idx].autoApproveType,
triggerConfigs[idx].autoApproveMaxAllowed
);
}
}
//EXTERNAL
/**
* @notice register can only be called through transferAndCall on LINK contract
* @param name string of the upkeep to be registered
* @param encryptedEmail email address of upkeep contact
* @param upkeepContract address to perform upkeep on
* @param gasLimit amount of gas to provide the target contract when performing upkeep
* @param adminAddress address to cancel upkeep and withdraw remaining funds
* @param triggerType the type of trigger for the upkeep
* @param checkData data passed to the contract when checking for upkeep
* @param triggerConfig the config for the trigger
* @param offchainConfig offchainConfig for upkeep in bytes
* @param amount quantity of LINK upkeep is funded with (specified in Juels)
* @param sender address of the sender making the request
*/
function register(
string memory name,
bytes calldata encryptedEmail,
address upkeepContract,
uint32 gasLimit,
address adminAddress,
uint8 triggerType,
bytes memory checkData,
bytes memory triggerConfig,
bytes memory offchainConfig,
uint96 amount,
address sender
) external onlyLINK {
_register(
RegistrationParams({
name: name,
encryptedEmail: encryptedEmail,
upkeepContract: upkeepContract,
gasLimit: gasLimit,
adminAddress: adminAddress,
triggerType: triggerType,
checkData: checkData,
triggerConfig: triggerConfig,
offchainConfig: offchainConfig,
amount: amount
}),
sender
);
}
/**
* @notice Allows external users to register upkeeps; assumes amount is approved for transfer by the contract
* @param requestParams struct of all possible registration parameters
*/
function registerUpkeep(RegistrationParams calldata requestParams) external returns (uint256) {
if (requestParams.amount < s_config.minLINKJuels) {
revert InsufficientPayment();
}
LINK.transferFrom(msg.sender, address(this), requestParams.amount);
return _register(requestParams, msg.sender);
}
/**
* @dev register upkeep on KeeperRegistry contract and emit RegistrationApproved event
*/
function approve(
string memory name,
address upkeepContract,
uint32 gasLimit,
address adminAddress,
uint8 triggerType,
bytes calldata checkData,
bytes memory triggerConfig,
bytes calldata offchainConfig,
bytes32 hash
) external onlyOwner {
PendingRequest memory request = s_pendingRequests[hash];
if (request.admin == address(0)) {
revert RequestNotFound();
}
bytes32 expectedHash = keccak256(
abi.encode(upkeepContract, gasLimit, adminAddress, triggerType, checkData, triggerConfig, offchainConfig)
);
if (hash != expectedHash) {
revert HashMismatch();
}
delete s_pendingRequests[hash];
_approve(
RegistrationParams({
name: name,
encryptedEmail: "",
upkeepContract: upkeepContract,
gasLimit: gasLimit,
adminAddress: adminAddress,
triggerType: triggerType,
checkData: checkData,
triggerConfig: triggerConfig,
offchainConfig: offchainConfig,
amount: request.balance
}),
expectedHash
);
}
/**
* @notice cancel will remove a registration request and return the refunds to the request.admin
* @param hash the request hash
*/
function cancel(bytes32 hash) external {
PendingRequest memory request = s_pendingRequests[hash];
if (!(msg.sender == request.admin || msg.sender == owner())) {
revert OnlyAdminOrOwner();
}
if (request.admin == address(0)) {
revert RequestNotFound();
}
delete s_pendingRequests[hash];
bool success = LINK.transfer(request.admin, request.balance);
if (!success) {
revert LinkTransferFailed(request.admin);
}
emit RegistrationRejected(hash);
}
/**
* @notice owner calls this function to set contract config
* @param keeperRegistry new keeper registry address
* @param minLINKJuels minimum LINK that new registrations should fund their upkeep with
*/
function setConfig(address keeperRegistry, uint96 minLINKJuels) public onlyOwner {
s_config = RegistrarConfig({minLINKJuels: minLINKJuels, keeperRegistry: IKeeperRegistryMaster(keeperRegistry)});
emit ConfigChanged(keeperRegistry, minLINKJuels);
}
/**
* @notice owner calls to set the config for this upkeep type
* @param triggerType the upkeep type to configure
* @param autoApproveType the auto approval setting (see enum)
* @param autoApproveMaxAllowed the max number of upkeeps that can be auto approved of this type
*/
function setTriggerConfig(
uint8 triggerType,
AutoApproveType autoApproveType,
uint32 autoApproveMaxAllowed
) public onlyOwner {
s_triggerRegistrations[triggerType].autoApproveType = autoApproveType;
s_triggerRegistrations[triggerType].autoApproveMaxAllowed = autoApproveMaxAllowed;
emit TriggerConfigSet(triggerType, autoApproveType, autoApproveMaxAllowed);
}
/**
* @notice owner calls this function to set allowlist status for senderAddress
* @param senderAddress senderAddress to set the allowlist status for
* @param allowed true if senderAddress needs to be added to allowlist, false if needs to be removed
*/
function setAutoApproveAllowedSender(address senderAddress, bool allowed) external onlyOwner {
s_autoApproveAllowedSenders[senderAddress] = allowed;
emit AutoApproveAllowedSenderSet(senderAddress, allowed);
}
/**
* @notice read the allowlist status of senderAddress
* @param senderAddress address to read the allowlist status for
*/
function getAutoApproveAllowedSender(address senderAddress) external view returns (bool) {
return s_autoApproveAllowedSenders[senderAddress];
}
/**
* @notice read the current registration configuration
*/
function getConfig() external view returns (address keeperRegistry, uint256 minLINKJuels) {
RegistrarConfig memory config = s_config;
return (address(config.keeperRegistry), config.minLINKJuels);
}
/**
* @notice read the config for this upkeep type
* @param triggerType upkeep type to read config for
*/
function getTriggerRegistrationDetails(uint8 triggerType) external view returns (TriggerRegistrationStorage memory) {
return s_triggerRegistrations[triggerType];
}
/**
* @notice gets the admin address and the current balance of a registration request
*/
function getPendingRequest(bytes32 hash) external view returns (address, uint96) {
PendingRequest memory request = s_pendingRequests[hash];
return (request.admin, request.balance);
}
/**
* @notice Called when LINK is sent to the contract via `transferAndCall`
* @param sender Address of the sender transfering LINK
* @param amount Amount of LINK sent (specified in Juels)
* @param data Payload of the transaction
*/
function onTokenTransfer(
address sender,
uint256 amount,
bytes calldata data
)
external
override
onlyLINK
permittedFunctionsForLINK(data)
isActualAmount(amount, data)
isActualSender(sender, data)
{
if (amount < s_config.minLINKJuels) {
revert InsufficientPayment();
}
(bool success, ) = address(this).delegatecall(data);
// calls register
if (!success) {
revert RegistrationRequestFailed();
}
}
// ================================================================
// | PRIVATE |
// ================================================================
/**
* @dev verify registration request and emit RegistrationRequested event
*/
function _register(RegistrationParams memory params, address sender) private returns (uint256) {
if (params.adminAddress == address(0)) {
revert InvalidAdminAddress();
}
bytes32 hash = keccak256(
abi.encode(
params.upkeepContract,
params.gasLimit,
params.adminAddress,
params.triggerType,
params.checkData,
params.triggerConfig,
params.offchainConfig
)
);
emit RegistrationRequested(
hash,
params.name,
params.encryptedEmail,
params.upkeepContract,
params.gasLimit,
params.adminAddress,
params.triggerType,
params.triggerConfig,
params.offchainConfig,
params.checkData,
params.amount
);
uint256 upkeepId;
if (_shouldAutoApprove(s_triggerRegistrations[params.triggerType], sender)) {
s_triggerRegistrations[params.triggerType].approvedCount++;
upkeepId = _approve(params, hash);
} else {
uint96 newBalance = s_pendingRequests[hash].balance + params.amount;
s_pendingRequests[hash] = PendingRequest({admin: params.adminAddress, balance: newBalance});
}
return upkeepId;
}
/**
* @dev register upkeep on KeeperRegistry contract and emit RegistrationApproved event
*/
function _approve(RegistrationParams memory params, bytes32 hash) private returns (uint256) {
IKeeperRegistryMaster keeperRegistry = s_config.keeperRegistry;
uint256 upkeepId = keeperRegistry.registerUpkeep(
params.upkeepContract,
params.gasLimit,
params.adminAddress,
params.triggerType,
params.checkData,
params.triggerConfig,
params.offchainConfig
);
bool success = LINK.transferAndCall(address(keeperRegistry), params.amount, abi.encode(upkeepId));
if (!success) {
revert LinkTransferFailed(address(keeperRegistry));
}
emit RegistrationApproved(hash, params.name, upkeepId);
return upkeepId;
}
/**
* @dev verify sender allowlist if needed and check max limit
*/
function _shouldAutoApprove(TriggerRegistrationStorage memory config, address sender) private view returns (bool) {
if (config.autoApproveType == AutoApproveType.DISABLED) {
return false;
}
if (config.autoApproveType == AutoApproveType.ENABLED_SENDER_ALLOWLIST && (!s_autoApproveAllowedSenders[sender])) {
return false;
}
if (config.approvedCount < config.autoApproveMaxAllowed) {
return true;
}
return false;
}
// ================================================================
// | MODIFIERS |
// ================================================================
/**
* @dev Reverts if not sent from the LINK token
*/
modifier onlyLINK() {
if (msg.sender != address(LINK)) {
revert OnlyLink();
}
_;
}
/**
* @dev Reverts if the given data does not begin with the `register` function selector
* @param _data The data payload of the request
*/
modifier permittedFunctionsForLINK(bytes memory _data) {
bytes4 funcSelector;
assembly {
// solhint-disable-next-line avoid-low-level-calls
funcSelector := mload(add(_data, 32)) // First 32 bytes contain length of data
}
if (funcSelector != REGISTER_REQUEST_SELECTOR) {
revert FunctionNotPermitted();
}
_;
}
/**
* @dev Reverts if the actual amount passed does not match the expected amount
* @param expected amount that should match the actual amount
* @param data bytes
*/
modifier isActualAmount(uint256 expected, bytes calldata data) {
// decode register function arguments to get actual amount
(, , , , , , , , , uint96 amount, ) = abi.decode(
data[4:],
(string, bytes, address, uint32, address, uint8, bytes, bytes, bytes, uint96, address)
);
if (expected != amount) {
revert AmountMismatch();
}
_;
}
/**
* @dev Reverts if the actual sender address does not match the expected sender address
* @param expected address that should match the actual sender address
* @param data bytes
*/
modifier isActualSender(address expected, bytes calldata data) {
// decode register function arguments to get actual sender
(, , , , , , , , , , address sender) = abi.decode(
data[4:],
(string, bytes, address, uint32, address, uint8, bytes, bytes, bytes, uint96, address)
);
if (expected != sender) {
revert SenderMismatch();
}
_;
}
}
// SPDX-License-Identifier: MIT
pragma solidity 0.8.16;
import {KeeperRegistryBase2_1} from "./KeeperRegistryBase2_1.sol";
import {ILogAutomation, Log} from "./interfaces/ILogAutomation.sol";
/**
* @notice this file exposes structs that are otherwise internal to the automation registry
* doing this allows those structs to be encoded and decoded with type safety in offchain code
* and tests because generated wrappers are made available
*/
/**
* @notice structure of trigger for log triggers
*/
struct LogTriggerConfig {
address contractAddress;
uint8 filterSelector; // denotes which topics apply to filter ex 000, 101, 111...only last 3 bits apply
bytes32 topic0;
bytes32 topic1;
bytes32 topic2;
bytes32 topic3;
}
contract AutomationUtils2_1 {
/**
* @dev this can be removed as OnchainConfig is now exposed directly from the registry
*/
function _onChainConfig(KeeperRegistryBase2_1.OnchainConfig memory) external {} // 0x2ff92a81
function _report(KeeperRegistryBase2_1.Report memory) external {} // 0xe65d6546
function _logTriggerConfig(LogTriggerConfig memory) external {} // 0x21f373d7
function _logTrigger(KeeperRegistryBase2_1.LogTrigger memory) external {} // 0x1c8d8260
function _conditionalTrigger(KeeperRegistryBase2_1.ConditionalTrigger memory) external {} // 0x4b6df294
function _log(Log memory) external {} // 0xe9720a49
}
// SPDX-License-Identifier: MIT
pragma solidity 0.8.16;
/**
* @title Chainable - the contract size limit nullifier
* @notice Chainable is designed to link together a "chain" of contracts through fallback functions
* and delegatecalls. All code is executed in the context of the head of the chain, the "master" contract.
*/
contract Chainable {
/**
* @dev addresses of the next contract in the chain **have to be immutable/constant** or the system won't work
*/
address private immutable FALLBACK_ADDRESS;
/**
* @param fallbackAddress the address of the next contract in the chain
*/
constructor(address fallbackAddress) {
FALLBACK_ADDRESS = fallbackAddress;
}
/**
* @notice returns the address of the next contract in the chain
*/
function fallbackTo() external view returns (address) {
return FALLBACK_ADDRESS;
}
/**
* @notice the fallback function routes the call to the next contract in the chain
* @dev most of the implementation is copied directly from OZ's Proxy contract
*/
fallback() external {
// copy to memory for assembly access
address next = FALLBACK_ADDRESS;
// copied directly from OZ's Proxy contract
assembly {
// Copy msg.data. We take full control of memory in this inline assembly
// block because it will not return to Solidity code. We overwrite the
// Solidity scratch pad at memory position 0.
calldatacopy(0, 0, calldatasize())
// Call the next contract.
// out and outsize are 0 because we don't know the size yet.
let result := delegatecall(gas(), next, 0, calldatasize(), 0, 0)
// Copy the returned data.
returndatacopy(0, 0, returndatasize())
switch result
// delegatecall returns 0 on error.
case 0 {
revert(0, returndatasize())
}
default {
return(0, returndatasize())
}
}
}
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import {ITypeAndVersion} from "../../../../shared/interfaces/ITypeAndVersion.sol";
import {IAutomationRegistryConsumer} from "./IAutomationRegistryConsumer.sol";
interface IAutomationForwarder is ITypeAndVersion {
function forward(uint256 gasAmount, bytes memory data) external returns (bool success, uint256 gasUsed);
function updateRegistry(address newRegistry) external;
function getRegistry() external view returns (IAutomationRegistryConsumer);
function getTarget() external view returns (address);
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.4;
/**
* @notice IAutomationRegistryConsumer defines the LTS user-facing interface that we intend to maintain for
* across upgrades. As long as users use functions from within this interface, their upkeeps will retain
* backwards compatability across migrations.
* @dev Functions can be added to this interface, but not removed.
*/
interface IAutomationRegistryConsumer {
function getBalance(uint256 id) external view returns (uint96 balance);
function getMinBalance(uint256 id) external view returns (uint96 minBalance);
function cancelUpkeep(uint256 id) external;
function pauseUpkeep(uint256 id) external;
function unpauseUpkeep(uint256 id) external;
function addFunds(uint256 id, uint96 amount) external;
function withdrawFunds(uint256 id, address to) external;
}
// abi-checksum: 0x0ed34e4b36bd7b4a5447152c2d61491e6ba7ed944b11e4dfef4fea184708975e
// SPDX-License-Identifier: MIT
// !! THIS FILE WAS AUTOGENERATED BY abi-to-sol v0.6.6. SEE SOURCE BELOW. !!
pragma solidity ^0.8.4;
interface IKeeperRegistryMaster {
error ArrayHasNoEntries();
error CannotCancel();
error CheckDataExceedsLimit();
error ConfigDigestMismatch();
error DuplicateEntry();
error DuplicateSigners();
error GasLimitCanOnlyIncrease();
error GasLimitOutsideRange();
error IncorrectNumberOfFaultyOracles();
error IncorrectNumberOfSignatures();
error IncorrectNumberOfSigners();
error IndexOutOfRange();
error InsufficientFunds();
error InvalidDataLength();
error InvalidPayee();
error InvalidRecipient();
error InvalidReport();
error InvalidSigner();
error InvalidTransmitter();
error InvalidTrigger();
error InvalidTriggerType();
error MaxCheckDataSizeCanOnlyIncrease();
error MaxPerformDataSizeCanOnlyIncrease();
error MigrationNotPermitted();
error NotAContract();
error OnlyActiveSigners();
error OnlyActiveTransmitters();
error OnlyCallableByAdmin();
error OnlyCallableByLINKToken();
error OnlyCallableByOwnerOrAdmin();
error OnlyCallableByOwnerOrRegistrar();
error OnlyCallableByPayee();
error OnlyCallableByProposedAdmin();
error OnlyCallableByProposedPayee();
error OnlyCallableByUpkeepPrivilegeManager();
error OnlyPausedUpkeep();
error OnlySimulatedBackend();
error OnlyUnpausedUpkeep();
error ParameterLengthError();
error PaymentGreaterThanAllLINK();
error ReentrantCall();
error RegistryPaused();
error RepeatedSigner();
error RepeatedTransmitter();
error TargetCheckReverted(bytes reason);
error TooManyOracles();
error TranscoderNotSet();
error UpkeepAlreadyExists();
error UpkeepCancelled();
error UpkeepNotCanceled();
error UpkeepNotNeeded();
error ValueNotChanged();
event AdminPrivilegeConfigSet(address indexed admin, bytes privilegeConfig);
event CancelledUpkeepReport(uint256 indexed id, bytes trigger);
event ConfigSet(
uint32 previousConfigBlockNumber,
bytes32 configDigest,
uint64 configCount,
address[] signers,
address[] transmitters,
uint8 f,
bytes onchainConfig,
uint64 offchainConfigVersion,
bytes offchainConfig
);
event DedupKeyAdded(bytes32 indexed dedupKey);
event FundsAdded(uint256 indexed id, address indexed from, uint96 amount);
event FundsWithdrawn(uint256 indexed id, uint256 amount, address to);
event InsufficientFundsUpkeepReport(uint256 indexed id, bytes trigger);
event OwnerFundsWithdrawn(uint96 amount);
event OwnershipTransferRequested(address indexed from, address indexed to);
event OwnershipTransferred(address indexed from, address indexed to);
event Paused(address account);
event PayeesUpdated(address[] transmitters, address[] payees);
event PayeeshipTransferRequested(address indexed transmitter, address indexed from, address indexed to);
event PayeeshipTransferred(address indexed transmitter, address indexed from, address indexed to);
event PaymentWithdrawn(address indexed transmitter, uint256 indexed amount, address indexed to, address payee);
event ReorgedUpkeepReport(uint256 indexed id, bytes trigger);
event StaleUpkeepReport(uint256 indexed id, bytes trigger);
event Transmitted(bytes32 configDigest, uint32 epoch);
event Unpaused(address account);
event UpkeepAdminTransferRequested(uint256 indexed id, address indexed from, address indexed to);
event UpkeepAdminTransferred(uint256 indexed id, address indexed from, address indexed to);
event UpkeepCanceled(uint256 indexed id, uint64 indexed atBlockHeight);
event UpkeepCheckDataSet(uint256 indexed id, bytes newCheckData);
event UpkeepGasLimitSet(uint256 indexed id, uint96 gasLimit);
event UpkeepMigrated(uint256 indexed id, uint256 remainingBalance, address destination);
event UpkeepOffchainConfigSet(uint256 indexed id, bytes offchainConfig);
event UpkeepPaused(uint256 indexed id);
event UpkeepPerformed(
uint256 indexed id,
bool indexed success,
uint96 totalPayment,
uint256 gasUsed,
uint256 gasOverhead,
bytes trigger
);
event UpkeepPrivilegeConfigSet(uint256 indexed id, bytes privilegeConfig);
event UpkeepReceived(uint256 indexed id, uint256 startingBalance, address importedFrom);
event UpkeepRegistered(uint256 indexed id, uint32 performGas, address admin);
event UpkeepTriggerConfigSet(uint256 indexed id, bytes triggerConfig);
event UpkeepUnpaused(uint256 indexed id);
fallback() external;
function acceptOwnership() external;
function fallbackTo() external view returns (address);
function latestConfigDetails() external view returns (uint32 configCount, uint32 blockNumber, bytes32 configDigest);
function latestConfigDigestAndEpoch() external view returns (bool scanLogs, bytes32 configDigest, uint32 epoch);
function onTokenTransfer(address sender, uint256 amount, bytes memory data) external;
function owner() external view returns (address);
function setConfig(
address[] memory signers,
address[] memory transmitters,
uint8 f,
bytes memory onchainConfigBytes,
uint64 offchainConfigVersion,
bytes memory offchainConfig
) external;
function setConfigTypeSafe(
address[] memory signers,
address[] memory transmitters,
uint8 f,
KeeperRegistryBase2_1.OnchainConfig memory onchainConfig,
uint64 offchainConfigVersion,
bytes memory offchainConfig
) external;
function simulatePerformUpkeep(uint256 id, bytes memory performData) external returns (bool success, uint256 gasUsed);
function transferOwnership(address to) external;
function transmit(
bytes32[3] memory reportContext,
bytes memory rawReport,
bytes32[] memory rs,
bytes32[] memory ss,
bytes32 rawVs
) external;
function typeAndVersion() external view returns (string memory);
function addFunds(uint256 id, uint96 amount) external;
function cancelUpkeep(uint256 id) external;
function checkCallback(
uint256 id,
bytes[] memory values,
bytes memory extraData
) external returns (bool upkeepNeeded, bytes memory performData, uint8 upkeepFailureReason, uint256 gasUsed);
function checkUpkeep(
uint256 id,
bytes memory triggerData
)
external
returns (
bool upkeepNeeded,
bytes memory performData,
uint8 upkeepFailureReason,
uint256 gasUsed,
uint256 gasLimit,
uint256 fastGasWei,
uint256 linkNative
);
function checkUpkeep(
uint256 id
)
external
returns (
bool upkeepNeeded,
bytes memory performData,
uint8 upkeepFailureReason,
uint256 gasUsed,
uint256 gasLimit,
uint256 fastGasWei,
uint256 linkNative
);
function executeCallback(
uint256 id,
bytes memory payload
) external returns (bool upkeepNeeded, bytes memory performData, uint8 upkeepFailureReason, uint256 gasUsed);
function migrateUpkeeps(uint256[] memory ids, address destination) external;
function receiveUpkeeps(bytes memory encodedUpkeeps) external;
function registerUpkeep(
address target,
uint32 gasLimit,
address admin,
uint8 triggerType,
bytes memory checkData,
bytes memory triggerConfig,
bytes memory offchainConfig
) external returns (uint256 id);
function registerUpkeep(
address target,
uint32 gasLimit,
address admin,
bytes memory checkData,
bytes memory offchainConfig
) external returns (uint256 id);
function setUpkeepTriggerConfig(uint256 id, bytes memory triggerConfig) external;
function acceptPayeeship(address transmitter) external;
function acceptUpkeepAdmin(uint256 id) external;
function getActiveUpkeepIDs(uint256 startIndex, uint256 maxCount) external view returns (uint256[] memory);
function getAdminPrivilegeConfig(address admin) external view returns (bytes memory);
function getAutomationForwarderLogic() external view returns (address);
function getBalance(uint256 id) external view returns (uint96 balance);
function getCancellationDelay() external pure returns (uint256);
function getConditionalGasOverhead() external pure returns (uint256);
function getFastGasFeedAddress() external view returns (address);
function getForwarder(uint256 upkeepID) external view returns (address);
function getLinkAddress() external view returns (address);
function getLinkNativeFeedAddress() external view returns (address);
function getLogGasOverhead() external pure returns (uint256);
function getMaxPaymentForGas(uint8 triggerType, uint32 gasLimit) external view returns (uint96 maxPayment);
function getMinBalance(uint256 id) external view returns (uint96);
function getMinBalanceForUpkeep(uint256 id) external view returns (uint96 minBalance);
function getMode() external view returns (uint8);
function getPeerRegistryMigrationPermission(address peer) external view returns (uint8);
function getPerPerformByteGasOverhead() external pure returns (uint256);
function getPerSignerGasOverhead() external pure returns (uint256);
function getSignerInfo(address query) external view returns (bool active, uint8 index);
function getState()
external
view
returns (
KeeperRegistryBase2_1.State memory state,
KeeperRegistryBase2_1.OnchainConfig memory config,
address[] memory signers,
address[] memory transmitters,
uint8 f
);
function getTransmitterInfo(
address query
) external view returns (bool active, uint8 index, uint96 balance, uint96 lastCollected, address payee);
function getTriggerType(uint256 upkeepId) external pure returns (uint8);
function getUpkeep(uint256 id) external view returns (KeeperRegistryBase2_1.UpkeepInfo memory upkeepInfo);
function getUpkeepPrivilegeConfig(uint256 upkeepId) external view returns (bytes memory);
function getUpkeepTriggerConfig(uint256 upkeepId) external view returns (bytes memory);
function hasDedupKey(bytes32 dedupKey) external view returns (bool);
function pause() external;
function pauseUpkeep(uint256 id) external;
function recoverFunds() external;
function setAdminPrivilegeConfig(address admin, bytes memory newPrivilegeConfig) external;
function setPayees(address[] memory payees) external;
function setPeerRegistryMigrationPermission(address peer, uint8 permission) external;
function setUpkeepCheckData(uint256 id, bytes memory newCheckData) external;
function setUpkeepGasLimit(uint256 id, uint32 gasLimit) external;
function setUpkeepOffchainConfig(uint256 id, bytes memory config) external;
function setUpkeepPrivilegeConfig(uint256 upkeepId, bytes memory newPrivilegeConfig) external;
function transferPayeeship(address transmitter, address proposed) external;
function transferUpkeepAdmin(uint256 id, address proposed) external;
function unpause() external;
function unpauseUpkeep(uint256 id) external;
function upkeepTranscoderVersion() external pure returns (uint8);
function upkeepVersion() external pure returns (uint8);
function withdrawFunds(uint256 id, address to) external;
function withdrawOwnerFunds() external;
function withdrawPayment(address from, address to) external;
}
interface KeeperRegistryBase2_1 {
struct OnchainConfig {
uint32 paymentPremiumPPB;
uint32 flatFeeMicroLink;
uint32 checkGasLimit;
uint24 stalenessSeconds;
uint16 gasCeilingMultiplier;
uint96 minUpkeepSpend;
uint32 maxPerformGas;
uint32 maxCheckDataSize;
uint32 maxPerformDataSize;
uint32 maxRevertDataSize;
uint256 fallbackGasPrice;
uint256 fallbackLinkPrice;
address transcoder;
address[] registrars;
address upkeepPrivilegeManager;
}
struct State {
uint32 nonce;
uint96 ownerLinkBalance;
uint256 expectedLinkBalance;
uint96 totalPremium;
uint256 numUpkeeps;
uint32 configCount;
uint32 latestConfigBlockNumber;
bytes32 latestConfigDigest;
uint32 latestEpoch;
bool paused;
}
struct UpkeepInfo {
address target;
uint32 performGas;
bytes checkData;
uint96 balance;
address admin;
uint64 maxValidBlocknumber;
uint32 lastPerformedBlockNumber;
uint96 amountSpent;
bool paused;
bytes offchainConfig;
}
}
// THIS FILE WAS AUTOGENERATED FROM THE FOLLOWING ABI JSON:
/*
[{"inputs":[{"internalType":"contract KeeperRegistryLogicB2_1","name":"logicA","type":"address"}],"stateMutability":"nonpayable","type":"constructor"},{"inputs":[],"name":"ArrayHasNoEntries","type":"error"},{"inputs":[],"name":"CannotCancel","type":"error"},{"inputs":[],"name":"CheckDataExceedsLimit","type":"error"},{"inputs":[],"name":"ConfigDigestMismatch","type":"error"},{"inputs":[],"name":"DuplicateEntry","type":"error"},{"inputs":[],"name":"DuplicateSigners","type":"error"},{"inputs":[],"name":"GasLimitCanOnlyIncrease","type":"error"},{"inputs":[],"name":"GasLimitOutsideRange","type":"error"},{"inputs":[],"name":"IncorrectNumberOfFaultyOracles","type":"error"},{"inputs":[],"name":"IncorrectNumberOfSignatures","type":"error"},{"inputs":[],"name":"IncorrectNumberOfSigners","type":"error"},{"inputs":[],"name":"IndexOutOfRange","type":"error"},{"inputs":[],"name":"InsufficientFunds","type":"error"},{"inputs":[],"name":"InvalidDataLength","type":"error"},{"inputs":[],"name":"InvalidPayee","type":"error"},{"inputs":[],"name":"InvalidRecipient","type":"error"},{"inputs":[],"name":"InvalidReport","type":"error"},{"inputs":[],"name":"InvalidSigner","type":"error"},{"inputs":[],"name":"InvalidTransmitter","type":"error"},{"inputs":[],"name":"InvalidTrigger","type":"error"},{"inputs":[],"name":"InvalidTriggerType","type":"error"},{"inputs":[],"name":"MaxCheckDataSizeCanOnlyIncrease","type":"error"},{"inputs":[],"name":"MaxPerformDataSizeCanOnlyIncrease","type":"error"},{"inputs":[],"name":"MigrationNotPermitted","type":"error"},{"inputs":[],"name":"NotAContract","type":"error"},{"inputs":[],"name":"OnlyActiveSigners","type":"error"},{"inputs":[],"name":"OnlyActiveTransmitters","type":"error"},{"inputs":[],"name":"OnlyCallableByAdmin","type":"error"},{"inputs":[],"name":"OnlyCallableByLINKToken","type":"error"},{"inputs":[],"name":"OnlyCallableByOwnerOrAdmin","type":"error"},{"inputs":[],"name":"OnlyCallableByOwnerOrRegistrar","type":"error"},{"inputs":[],"name":"OnlyCallableByPayee","type":"error"},{"inputs":[],"name":"OnlyCallableByProposedAdmin","type":"error"},{"inputs":[],"name":"OnlyCallableByProposedPayee","type":"error"},{"inputs":[],"name":"OnlyCallableByUpkeepPrivilegeManager","type":"error"},{"inputs":[],"name":"OnlyPausedUpkeep","type":"error"},{"inputs":[],"name":"OnlySimulatedBackend","type":"error"},{"inputs":[],"name":"OnlyUnpausedUpkeep","type":"error"},{"inputs":[],"name":"ParameterLengthError","type":"error"},{"inputs":[],"name":"PaymentGreaterThanAllLINK","type":"error"},{"inputs":[],"name":"ReentrantCall","type":"error"},{"inputs":[],"name":"RegistryPaused","type":"error"},{"inputs":[],"name":"RepeatedSigner","type":"error"},{"inputs":[],"name":"RepeatedTransmitter","type":"error"},{"inputs":[{"internalType":"bytes","name":"reason","type":"bytes"}],"name":"TargetCheckReverted","type":"error"},{"inputs":[],"name":"TooManyOracles","type":"error"},{"inputs":[],"name":"TranscoderNotSet","type":"error"},{"inputs":[],"name":"UpkeepAlreadyExists","type":"error"},{"inputs":[],"name":"UpkeepCancelled","type":"error"},{"inputs":[],"name":"UpkeepNotCanceled","type":"error"},{"inputs":[],"name":"UpkeepNotNeeded","type":"error"},{"inputs":[],"name":"ValueNotChanged","type":"error"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"admin","type":"address"},{"indexed":false,"internalType":"bytes","name":"privilegeConfig","type":"bytes"}],"name":"AdminPrivilegeConfigSet","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint256","name":"id","type":"uint256"},{"indexed":false,"internalType":"bytes","name":"trigger","type":"bytes"}],"name":"CancelledUpkeepReport","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint32","name":"previousConfigBlockNumber","type":"uint32"},{"indexed":false,"internalType":"bytes32","name":"configDigest","type":"bytes32"},{"indexed":false,"internalType":"uint64","name":"configCount","type":"uint64"},{"indexed":false,"internalType":"address[]","name":"signers","type":"address[]"},{"indexed":false,"internalType":"address[]","name":"transmitters","type":"address[]"},{"indexed":false,"internalType":"uint8","name":"f","type":"uint8"},{"indexed":false,"internalType":"bytes","name":"onchainConfig","type":"bytes"},{"indexed":false,"internalType":"uint64","name":"offchainConfigVersion","type":"uint64"},{"indexed":false,"internalType":"bytes","name":"offchainConfig","type":"bytes"}],"name":"ConfigSet","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"bytes32","name":"dedupKey","type":"bytes32"}],"name":"DedupKeyAdded","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint256","name":"id","type":"uint256"},{"indexed":true,"internalType":"address","name":"from","type":"address"},{"indexed":false,"internalType":"uint96","name":"amount","type":"uint96"}],"name":"FundsAdded","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint256","name":"id","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"},{"indexed":false,"internalType":"address","name":"to","type":"address"}],"name":"FundsWithdrawn","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint256","name":"id","type":"uint256"},{"indexed":false,"internalType":"bytes","name":"trigger","type":"bytes"}],"name":"InsufficientFundsUpkeepReport","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint96","name":"amount","type":"uint96"}],"name":"OwnerFundsWithdrawn","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"from","type":"address"},{"indexed":true,"internalType":"address","name":"to","type":"address"}],"name":"OwnershipTransferRequested","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"from","type":"address"},{"indexed":true,"internalType":"address","name":"to","type":"address"}],"name":"OwnershipTransferred","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"account","type":"address"}],"name":"Paused","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address[]","name":"transmitters","type":"address[]"},{"indexed":false,"internalType":"address[]","name":"payees","type":"address[]"}],"name":"PayeesUpdated","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"transmitter","type":"address"},{"indexed":true,"internalType":"address","name":"from","type":"address"},{"indexed":true,"internalType":"address","name":"to","type":"address"}],"name":"PayeeshipTransferRequested","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"transmitter","type":"address"},{"indexed":true,"internalType":"address","name":"from","type":"address"},{"indexed":true,"internalType":"address","name":"to","type":"address"}],"name":"PayeeshipTransferred","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"transmitter","type":"address"},{"indexed":true,"internalType":"uint256","name":"amount","type":"uint256"},{"indexed":true,"internalType":"address","name":"to","type":"address"},{"indexed":false,"internalType":"address","name":"payee","type":"address"}],"name":"PaymentWithdrawn","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint256","name":"id","type":"uint256"},{"indexed":false,"internalType":"bytes","name":"trigger","type":"bytes"}],"name":"ReorgedUpkeepReport","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint256","name":"id","type":"uint256"},{"indexed":false,"internalType":"bytes","name":"trigger","type":"bytes"}],"name":"StaleUpkeepReport","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"bytes32","name":"configDigest","type":"bytes32"},{"indexed":false,"internalType":"uint32","name":"epoch","type":"uint32"}],"name":"Transmitted","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"account","type":"address"}],"name":"Unpaused","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint256","name":"id","type":"uint256"},{"indexed":true,"internalType":"address","name":"from","type":"address"},{"indexed":true,"internalType":"address","name":"to","type":"address"}],"name":"UpkeepAdminTransferRequested","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint256","name":"id","type":"uint256"},{"indexed":true,"internalType":"address","name":"from","type":"address"},{"indexed":true,"internalType":"address","name":"to","type":"address"}],"name":"UpkeepAdminTransferred","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint256","name":"id","type":"uint256"},{"indexed":true,"internalType":"uint64","name":"atBlockHeight","type":"uint64"}],"name":"UpkeepCanceled","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint256","name":"id","type":"uint256"},{"indexed":false,"internalType":"bytes","name":"newCheckData","type":"bytes"}],"name":"UpkeepCheckDataSet","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint256","name":"id","type":"uint256"},{"indexed":false,"internalType":"uint96","name":"gasLimit","type":"uint96"}],"name":"UpkeepGasLimitSet","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint256","name":"id","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"remainingBalance","type":"uint256"},{"indexed":false,"internalType":"address","name":"destination","type":"address"}],"name":"UpkeepMigrated","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint256","name":"id","type":"uint256"},{"indexed":false,"internalType":"bytes","name":"offchainConfig","type":"bytes"}],"name":"UpkeepOffchainConfigSet","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint256","name":"id","type":"uint256"}],"name":"UpkeepPaused","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint256","name":"id","type":"uint256"},{"indexed":true,"internalType":"bool","name":"success","type":"bool"},{"indexed":false,"internalType":"uint96","name":"totalPayment","type":"uint96"},{"indexed":false,"internalType":"uint256","name":"gasUsed","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"gasOverhead","type":"uint256"},{"indexed":false,"internalType":"bytes","name":"trigger","type":"bytes"}],"name":"UpkeepPerformed","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint256","name":"id","type":"uint256"},{"indexed":false,"internalType":"bytes","name":"privilegeConfig","type":"bytes"}],"name":"UpkeepPrivilegeConfigSet","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint256","name":"id","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"startingBalance","type":"uint256"},{"indexed":false,"internalType":"address","name":"importedFrom","type":"address"}],"name":"UpkeepReceived","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint256","name":"id","type":"uint256"},{"indexed":false,"internalType":"uint32","name":"performGas","type":"uint32"},{"indexed":false,"internalType":"address","name":"admin","type":"address"}],"name":"UpkeepRegistered","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint256","name":"id","type":"uint256"},{"indexed":false,"internalType":"bytes","name":"triggerConfig","type":"bytes"}],"name":"UpkeepTriggerConfigSet","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint256","name":"id","type":"uint256"}],"name":"UpkeepUnpaused","type":"event"},{"stateMutability":"nonpayable","type":"fallback"},{"inputs":[],"name":"acceptOwnership","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"fallbackTo","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"latestConfigDetails","outputs":[{"internalType":"uint32","name":"configCount","type":"uint32"},{"internalType":"uint32","name":"blockNumber","type":"uint32"},{"internalType":"bytes32","name":"configDigest","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"latestConfigDigestAndEpoch","outputs":[{"internalType":"bool","name":"scanLogs","type":"bool"},{"internalType":"bytes32","name":"configDigest","type":"bytes32"},{"internalType":"uint32","name":"epoch","type":"uint32"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"sender","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"},{"internalType":"bytes","name":"data","type":"bytes"}],"name":"onTokenTransfer","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"owner","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address[]","name":"signers","type":"address[]"},{"internalType":"address[]","name":"transmitters","type":"address[]"},{"internalType":"uint8","name":"f","type":"uint8"},{"internalType":"bytes","name":"onchainConfigBytes","type":"bytes"},{"internalType":"uint64","name":"offchainConfigVersion","type":"uint64"},{"internalType":"bytes","name":"offchainConfig","type":"bytes"}],"name":"setConfig","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address[]","name":"signers","type":"address[]"},{"internalType":"address[]","name":"transmitters","type":"address[]"},{"internalType":"uint8","name":"f","type":"uint8"},{"components":[{"internalType":"uint32","name":"paymentPremiumPPB","type":"uint32"},{"internalType":"uint32","name":"flatFeeMicroLink","type":"uint32"},{"internalType":"uint32","name":"checkGasLimit","type":"uint32"},{"internalType":"uint24","name":"stalenessSeconds","type":"uint24"},{"internalType":"uint16","name":"gasCeilingMultiplier","type":"uint16"},{"internalType":"uint96","name":"minUpkeepSpend","type":"uint96"},{"internalType":"uint32","name":"maxPerformGas","type":"uint32"},{"internalType":"uint32","name":"maxCheckDataSize","type":"uint32"},{"internalType":"uint32","name":"maxPerformDataSize","type":"uint32"},{"internalType":"uint32","name":"maxRevertDataSize","type":"uint32"},{"internalType":"uint256","name":"fallbackGasPrice","type":"uint256"},{"internalType":"uint256","name":"fallbackLinkPrice","type":"uint256"},{"internalType":"address","name":"transcoder","type":"address"},{"internalType":"address[]","name":"registrars","type":"address[]"},{"internalType":"address","name":"upkeepPrivilegeManager","type":"address"}],"internalType":"struct KeeperRegistryBase2_1.OnchainConfig","name":"onchainConfig","type":"tuple"},{"internalType":"uint64","name":"offchainConfigVersion","type":"uint64"},{"internalType":"bytes","name":"offchainConfig","type":"bytes"}],"name":"setConfigTypeSafe","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"id","type":"uint256"},{"internalType":"bytes","name":"performData","type":"bytes"}],"name":"simulatePerformUpkeep","outputs":[{"internalType":"bool","name":"success","type":"bool"},{"internalType":"uint256","name":"gasUsed","type":"uint256"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"to","type":"address"}],"name":"transferOwnership","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes32[3]","name":"reportContext","type":"bytes32[3]"},{"internalType":"bytes","name":"rawReport","type":"bytes"},{"internalType":"bytes32[]","name":"rs","type":"bytes32[]"},{"internalType":"bytes32[]","name":"ss","type":"bytes32[]"},{"internalType":"bytes32","name":"rawVs","type":"bytes32"}],"name":"transmit","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"typeAndVersion","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"contract KeeperRegistryLogicB2_1","name":"logicB","type":"address"}],"stateMutability":"nonpayable","type":"constructor"},{"inputs":[{"internalType":"uint256","name":"id","type":"uint256"},{"internalType":"uint96","name":"amount","type":"uint96"}],"name":"addFunds","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"id","type":"uint256"}],"name":"cancelUpkeep","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"id","type":"uint256"},{"internalType":"bytes[]","name":"values","type":"bytes[]"},{"internalType":"bytes","name":"extraData","type":"bytes"}],"name":"checkCallback","outputs":[{"internalType":"bool","name":"upkeepNeeded","type":"bool"},{"internalType":"bytes","name":"performData","type":"bytes"},{"internalType":"enum KeeperRegistryBase2_1.UpkeepFailureReason","name":"upkeepFailureReason","type":"uint8"},{"internalType":"uint256","name":"gasUsed","type":"uint256"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"id","type":"uint256"},{"internalType":"bytes","name":"triggerData","type":"bytes"}],"name":"checkUpkeep","outputs":[{"internalType":"bool","name":"upkeepNeeded","type":"bool"},{"internalType":"bytes","name":"performData","type":"bytes"},{"internalType":"enum KeeperRegistryBase2_1.UpkeepFailureReason","name":"upkeepFailureReason","type":"uint8"},{"internalType":"uint256","name":"gasUsed","type":"uint256"},{"internalType":"uint256","name":"gasLimit","type":"uint256"},{"internalType":"uint256","name":"fastGasWei","type":"uint256"},{"internalType":"uint256","name":"linkNative","type":"uint256"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"id","type":"uint256"}],"name":"checkUpkeep","outputs":[{"internalType":"bool","name":"upkeepNeeded","type":"bool"},{"internalType":"bytes","name":"performData","type":"bytes"},{"internalType":"enum KeeperRegistryBase2_1.UpkeepFailureReason","name":"upkeepFailureReason","type":"uint8"},{"internalType":"uint256","name":"gasUsed","type":"uint256"},{"internalType":"uint256","name":"gasLimit","type":"uint256"},{"internalType":"uint256","name":"fastGasWei","type":"uint256"},{"internalType":"uint256","name":"linkNative","type":"uint256"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"id","type":"uint256"},{"internalType":"bytes","name":"payload","type":"bytes"}],"name":"executeCallback","outputs":[{"internalType":"bool","name":"upkeepNeeded","type":"bool"},{"internalType":"bytes","name":"performData","type":"bytes"},{"internalType":"enum KeeperRegistryBase2_1.UpkeepFailureReason","name":"upkeepFailureReason","type":"uint8"},{"internalType":"uint256","name":"gasUsed","type":"uint256"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256[]","name":"ids","type":"uint256[]"},{"internalType":"address","name":"destination","type":"address"}],"name":"migrateUpkeeps","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes","name":"encodedUpkeeps","type":"bytes"}],"name":"receiveUpkeeps","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"target","type":"address"},{"internalType":"uint32","name":"gasLimit","type":"uint32"},{"internalType":"address","name":"admin","type":"address"},{"internalType":"enum KeeperRegistryBase2_1.Trigger","name":"triggerType","type":"uint8"},{"internalType":"bytes","name":"checkData","type":"bytes"},{"internalType":"bytes","name":"triggerConfig","type":"bytes"},{"internalType":"bytes","name":"offchainConfig","type":"bytes"}],"name":"registerUpkeep","outputs":[{"internalType":"uint256","name":"id","type":"uint256"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"target","type":"address"},{"internalType":"uint32","name":"gasLimit","type":"uint32"},{"internalType":"address","name":"admin","type":"address"},{"internalType":"bytes","name":"checkData","type":"bytes"},{"internalType":"bytes","name":"offchainConfig","type":"bytes"}],"name":"registerUpkeep","outputs":[{"internalType":"uint256","name":"id","type":"uint256"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"id","type":"uint256"},{"internalType":"bytes","name":"triggerConfig","type":"bytes"}],"name":"setUpkeepTriggerConfig","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"enum KeeperRegistryBase2_1.Mode","name":"mode","type":"uint8"},{"internalType":"address","name":"link","type":"address"},{"internalType":"address","name":"linkNativeFeed","type":"address"},{"internalType":"address","name":"fastGasFeed","type":"address"},{"internalType":"address","name":"automationForwarderLogic","type":"address"}],"stateMutability":"nonpayable","type":"constructor"},{"inputs":[{"internalType":"address","name":"transmitter","type":"address"}],"name":"acceptPayeeship","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"id","type":"uint256"}],"name":"acceptUpkeepAdmin","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"startIndex","type":"uint256"},{"internalType":"uint256","name":"maxCount","type":"uint256"}],"name":"getActiveUpkeepIDs","outputs":[{"internalType":"uint256[]","name":"","type":"uint256[]"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"admin","type":"address"}],"name":"getAdminPrivilegeConfig","outputs":[{"internalType":"bytes","name":"","type":"bytes"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getAutomationForwarderLogic","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"id","type":"uint256"}],"name":"getBalance","outputs":[{"internalType":"uint96","name":"balance","type":"uint96"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getCancellationDelay","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"pure","type":"function"},{"inputs":[],"name":"getConditionalGasOverhead","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"pure","type":"function"},{"inputs":[],"name":"getFastGasFeedAddress","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"upkeepID","type":"uint256"}],"name":"getForwarder","outputs":[{"internalType":"contract IAutomationForwarder","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getLinkAddress","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getLinkNativeFeedAddress","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getLogGasOverhead","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"enum KeeperRegistryBase2_1.Trigger","name":"triggerType","type":"uint8"},{"internalType":"uint32","name":"gasLimit","type":"uint32"}],"name":"getMaxPaymentForGas","outputs":[{"internalType":"uint96","name":"maxPayment","type":"uint96"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"id","type":"uint256"}],"name":"getMinBalance","outputs":[{"internalType":"uint96","name":"","type":"uint96"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"id","type":"uint256"}],"name":"getMinBalanceForUpkeep","outputs":[{"internalType":"uint96","name":"minBalance","type":"uint96"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getMode","outputs":[{"internalType":"enum KeeperRegistryBase2_1.Mode","name":"","type":"uint8"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"peer","type":"address"}],"name":"getPeerRegistryMigrationPermission","outputs":[{"internalType":"enum KeeperRegistryBase2_1.MigrationPermission","name":"","type":"uint8"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getPerPerformByteGasOverhead","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"pure","type":"function"},{"inputs":[],"name":"getPerSignerGasOverhead","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"query","type":"address"}],"name":"getSignerInfo","outputs":[{"internalType":"bool","name":"active","type":"bool"},{"internalType":"uint8","name":"index","type":"uint8"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getState","outputs":[{"components":[{"internalType":"uint32","name":"nonce","type":"uint32"},{"internalType":"uint96","name":"ownerLinkBalance","type":"uint96"},{"internalType":"uint256","name":"expectedLinkBalance","type":"uint256"},{"internalType":"uint96","name":"totalPremium","type":"uint96"},{"internalType":"uint256","name":"numUpkeeps","type":"uint256"},{"internalType":"uint32","name":"configCount","type":"uint32"},{"internalType":"uint32","name":"latestConfigBlockNumber","type":"uint32"},{"internalType":"bytes32","name":"latestConfigDigest","type":"bytes32"},{"internalType":"uint32","name":"latestEpoch","type":"uint32"},{"internalType":"bool","name":"paused","type":"bool"}],"internalType":"struct KeeperRegistryBase2_1.State","name":"state","type":"tuple"},{"components":[{"internalType":"uint32","name":"paymentPremiumPPB","type":"uint32"},{"internalType":"uint32","name":"flatFeeMicroLink","type":"uint32"},{"internalType":"uint32","name":"checkGasLimit","type":"uint32"},{"internalType":"uint24","name":"stalenessSeconds","type":"uint24"},{"internalType":"uint16","name":"gasCeilingMultiplier","type":"uint16"},{"internalType":"uint96","name":"minUpkeepSpend","type":"uint96"},{"internalType":"uint32","name":"maxPerformGas","type":"uint32"},{"internalType":"uint32","name":"maxCheckDataSize","type":"uint32"},{"internalType":"uint32","name":"maxPerformDataSize","type":"uint32"},{"internalType":"uint32","name":"maxRevertDataSize","type":"uint32"},{"internalType":"uint256","name":"fallbackGasPrice","type":"uint256"},{"internalType":"uint256","name":"fallbackLinkPrice","type":"uint256"},{"internalType":"address","name":"transcoder","type":"address"},{"internalType":"address[]","name":"registrars","type":"address[]"},{"internalType":"address","name":"upkeepPrivilegeManager","type":"address"}],"internalType":"struct KeeperRegistryBase2_1.OnchainConfig","name":"config","type":"tuple"},{"internalType":"address[]","name":"signers","type":"address[]"},{"internalType":"address[]","name":"transmitters","type":"address[]"},{"internalType":"uint8","name":"f","type":"uint8"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"query","type":"address"}],"name":"getTransmitterInfo","outputs":[{"internalType":"bool","name":"active","type":"bool"},{"internalType":"uint8","name":"index","type":"uint8"},{"internalType":"uint96","name":"balance","type":"uint96"},{"internalType":"uint96","name":"lastCollected","type":"uint96"},{"internalType":"address","name":"payee","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"upkeepId","type":"uint256"}],"name":"getTriggerType","outputs":[{"internalType":"enum KeeperRegistryBase2_1.Trigger","name":"","type":"uint8"}],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"id","type":"uint256"}],"name":"getUpkeep","outputs":[{"components":[{"internalType":"address","name":"target","type":"address"},{"internalType":"uint32","name":"performGas","type":"uint32"},{"internalType":"bytes","name":"checkData","type":"bytes"},{"internalType":"uint96","name":"balance","type":"uint96"},{"internalType":"address","name":"admin","type":"address"},{"internalType":"uint64","name":"maxValidBlocknumber","type":"uint64"},{"internalType":"uint32","name":"lastPerformedBlockNumber","type":"uint32"},{"internalType":"uint96","name":"amountSpent","type":"uint96"},{"internalType":"bool","name":"paused","type":"bool"},{"internalType":"bytes","name":"offchainConfig","type":"bytes"}],"internalType":"struct KeeperRegistryBase2_1.UpkeepInfo","name":"upkeepInfo","type":"tuple"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"upkeepId","type":"uint256"}],"name":"getUpkeepPrivilegeConfig","outputs":[{"internalType":"bytes","name":"","type":"bytes"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"upkeepId","type":"uint256"}],"name":"getUpkeepTriggerConfig","outputs":[{"internalType":"bytes","name":"","type":"bytes"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"dedupKey","type":"bytes32"}],"name":"hasDedupKey","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"pause","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"id","type":"uint256"}],"name":"pauseUpkeep","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"recoverFunds","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"admin","type":"address"},{"internalType":"bytes","name":"newPrivilegeConfig","type":"bytes"}],"name":"setAdminPrivilegeConfig","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address[]","name":"payees","type":"address[]"}],"name":"setPayees","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"peer","type":"address"},{"internalType":"enum KeeperRegistryBase2_1.MigrationPermission","name":"permission","type":"uint8"}],"name":"setPeerRegistryMigrationPermission","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"id","type":"uint256"},{"internalType":"bytes","name":"newCheckData","type":"bytes"}],"name":"setUpkeepCheckData","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"id","type":"uint256"},{"internalType":"uint32","name":"gasLimit","type":"uint32"}],"name":"setUpkeepGasLimit","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"id","type":"uint256"},{"internalType":"bytes","name":"config","type":"bytes"}],"name":"setUpkeepOffchainConfig","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"upkeepId","type":"uint256"},{"internalType":"bytes","name":"newPrivilegeConfig","type":"bytes"}],"name":"setUpkeepPrivilegeConfig","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"transmitter","type":"address"},{"internalType":"address","name":"proposed","type":"address"}],"name":"transferPayeeship","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"id","type":"uint256"},{"internalType":"address","name":"proposed","type":"address"}],"name":"transferUpkeepAdmin","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"unpause","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"id","type":"uint256"}],"name":"unpauseUpkeep","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"upkeepTranscoderVersion","outputs":[{"internalType":"enum UpkeepFormat","name":"","type":"uint8"}],"stateMutability":"pure","type":"function"},{"inputs":[],"name":"upkeepVersion","outputs":[{"internalType":"uint8","name":"","type":"uint8"}],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"id","type":"uint256"},{"internalType":"address","name":"to","type":"address"}],"name":"withdrawFunds","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"withdrawOwnerFunds","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"from","type":"address"},{"internalType":"address","name":"to","type":"address"}],"name":"withdrawPayment","outputs":[],"stateMutability":"nonpayable","type":"function"}]
*/
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
struct Log {
uint256 index;
uint256 txIndex;
bytes32 txHash;
uint256 blockNumber;
bytes32 blockHash;
address source;
bytes32[] topics;
bytes data;
}
interface ILogAutomation {
/**
* @notice method that is simulated by the keepers to see if any work actually
* needs to be performed. This method does does not actually need to be
* executable, and since it is only ever simulated it can consume lots of gas.
* @dev To ensure that it is never called, you may want to add the
* cannotExecute modifier from KeeperBase to your implementation of this
* method.
* @param log the raw log data matching the filter that this contract has
* registered as a trigger
* @param checkData user-specified extra data to provide context to this upkeep
* @return upkeepNeeded boolean to indicate whether the keeper should call
* performUpkeep or not.
* @return performData bytes that the keeper should call performUpkeep with, if
* upkeep is needed. If you would like to encode data to decode later, try
* `abi.encode`.
*/
function checkLog(
Log calldata log,
bytes memory checkData
) external returns (bool upkeepNeeded, bytes memory performData);
/**
* @notice method that is actually executed by the keepers, via the registry.
* The data returned by the checkUpkeep simulation will be passed into
* this method to actually be executed.
* @dev The input to this method should not be trusted, and the caller of the
* method should not even be restricted to any single registry. Anyone should
* be able call it, and the input should be validated, there is no guarantee
* that the data passed in is the performData returned from checkUpkeep. This
* could happen due to malicious keepers, racing keepers, or simply a state
* change while the performUpkeep transaction is waiting for confirmation.
* Always validate the data passed in.
* @param performData is the data which was passed back from the checkData
* simulation. If it is encoded, it can easily be decoded into other types by
* calling `abi.decode`. This data should not be trusted, and should be
* validated against the contract's current state.
*/
function performUpkeep(bytes calldata performData) external;
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
interface StreamsLookupCompatibleInterface {
error StreamsLookup(string feedParamKey, string[] feeds, string timeParamKey, uint256 time, bytes extraData);
/**
* @notice any contract which wants to utilize StreamsLookup feature needs to
* implement this interface as well as the automation compatible interface.
* @param values an array of bytes returned from data streams endpoint.
* @param extraData context data from streams lookup process.
* @return upkeepNeeded boolean to indicate whether the keeper should call performUpkeep or not.
* @return performData bytes that the keeper should call performUpkeep with, if
* upkeep is needed. If you would like to encode data to decode later, try `abi.encode`.
*/
function checkCallback(
bytes[] memory values,
bytes memory extraData
) external view returns (bool upkeepNeeded, bytes memory performData);
}
// SPDX-License-Identifier: MIT
pragma solidity 0.8.16;
import {EnumerableSet} from "../../../vendor/openzeppelin-solidity/v4.7.3/contracts/utils/structs/EnumerableSet.sol";
import {Address} from "../../../vendor/openzeppelin-solidity/v4.7.3/contracts/utils/Address.sol";
import {Proxy} from "../../../vendor/openzeppelin-solidity/v4.7.3/contracts/proxy/Proxy.sol";
import {KeeperRegistryBase2_1} from "./KeeperRegistryBase2_1.sol";
import {KeeperRegistryLogicB2_1} from "./KeeperRegistryLogicB2_1.sol";
import {Chainable} from "./Chainable.sol";
import {IERC677Receiver} from "../../../shared/interfaces/IERC677Receiver.sol";
import {OCR2Abstract} from "../../../shared/ocr2/OCR2Abstract.sol";
/**
* @notice Registry for adding work for Chainlink Keepers to perform on client
* contracts. Clients must support the Upkeep interface.
*/
contract KeeperRegistry2_1 is KeeperRegistryBase2_1, OCR2Abstract, Chainable, IERC677Receiver {
using Address for address;
using EnumerableSet for EnumerableSet.UintSet;
using EnumerableSet for EnumerableSet.AddressSet;
/**
* @notice versions:
* - KeeperRegistry 2.1.0: introduces support for log, cron, and ready triggers
: removes the need for "wrapped perform data"
* - KeeperRegistry 2.0.2: pass revert bytes as performData when target contract reverts
* : fixes issue with arbitrum block number
* : does an early return in case of stale report instead of revert
* - KeeperRegistry 2.0.1: implements workaround for buggy migrate function in 1.X
* - KeeperRegistry 2.0.0: implement OCR interface
* - KeeperRegistry 1.3.0: split contract into Proxy and Logic
* : account for Arbitrum and Optimism L1 gas fee
* : allow users to configure upkeeps
* - KeeperRegistry 1.2.0: allow funding within performUpkeep
* : allow configurable registry maxPerformGas
* : add function to let admin change upkeep gas limit
* : add minUpkeepSpend requirement
* : upgrade to solidity v0.8
* - KeeperRegistry 1.1.0: added flatFeeMicroLink
* - KeeperRegistry 1.0.0: initial release
*/
string public constant override typeAndVersion = "KeeperRegistry 2.1.0";
/**
* @param logicA the address of the first logic contract, but cast as logicB in order to call logicB functions
*/
constructor(
KeeperRegistryLogicB2_1 logicA
)
KeeperRegistryBase2_1(
logicA.getMode(),
logicA.getLinkAddress(),
logicA.getLinkNativeFeedAddress(),
logicA.getFastGasFeedAddress(),
logicA.getAutomationForwarderLogic()
)
Chainable(address(logicA))
{}
// ================================================================
// | ACTIONS |
// ================================================================
/**
* @inheritdoc OCR2Abstract
*/
function transmit(
bytes32[3] calldata reportContext,
bytes calldata rawReport,
bytes32[] calldata rs,
bytes32[] calldata ss,
bytes32 rawVs
) external override {
uint256 gasOverhead = gasleft();
HotVars memory hotVars = s_hotVars;
if (hotVars.paused) revert RegistryPaused();
if (!s_transmitters[msg.sender].active) revert OnlyActiveTransmitters();
// Verify signatures
if (s_latestConfigDigest != reportContext[0]) revert ConfigDigestMismatch();
if (rs.length != hotVars.f + 1 || rs.length != ss.length) revert IncorrectNumberOfSignatures();
_verifyReportSignature(reportContext, rawReport, rs, ss, rawVs);
Report memory report = _decodeReport(rawReport);
UpkeepTransmitInfo[] memory upkeepTransmitInfo = new UpkeepTransmitInfo[](report.upkeepIds.length);
uint16 numUpkeepsPassedChecks;
for (uint256 i = 0; i < report.upkeepIds.length; i++) {
upkeepTransmitInfo[i].upkeep = s_upkeep[report.upkeepIds[i]];
upkeepTransmitInfo[i].triggerType = _getTriggerType(report.upkeepIds[i]);
upkeepTransmitInfo[i].maxLinkPayment = _getMaxLinkPayment(
hotVars,
upkeepTransmitInfo[i].triggerType,
uint32(report.gasLimits[i]),
uint32(report.performDatas[i].length),
report.fastGasWei,
report.linkNative,
true
);
(upkeepTransmitInfo[i].earlyChecksPassed, upkeepTransmitInfo[i].dedupID) = _prePerformChecks(
report.upkeepIds[i],
report.triggers[i],
upkeepTransmitInfo[i]
);
if (upkeepTransmitInfo[i].earlyChecksPassed) {
numUpkeepsPassedChecks += 1;
} else {
continue;
}
// Actually perform the target upkeep
(upkeepTransmitInfo[i].performSuccess, upkeepTransmitInfo[i].gasUsed) = _performUpkeep(
upkeepTransmitInfo[i].upkeep.forwarder,
report.gasLimits[i],
report.performDatas[i]
);
// Deduct that gasUsed by upkeep from our running counter
gasOverhead -= upkeepTransmitInfo[i].gasUsed;
// Store last perform block number / deduping key for upkeep
_updateTriggerMarker(report.upkeepIds[i], upkeepTransmitInfo[i]);
}
// No upkeeps to be performed in this report
if (numUpkeepsPassedChecks == 0) {
return;
}
// This is the overall gas overhead that will be split across performed upkeeps
// Take upper bound of 16 gas per callData bytes, which is approximated to be reportLength
// Rest of msg.data is accounted for in accounting overheads
gasOverhead =
(gasOverhead - gasleft() + 16 * rawReport.length) +
ACCOUNTING_FIXED_GAS_OVERHEAD +
(ACCOUNTING_PER_SIGNER_GAS_OVERHEAD * (hotVars.f + 1));
gasOverhead = gasOverhead / numUpkeepsPassedChecks + ACCOUNTING_PER_UPKEEP_GAS_OVERHEAD;
uint96 totalReimbursement;
uint96 totalPremium;
{
uint96 reimbursement;
uint96 premium;
for (uint256 i = 0; i < report.upkeepIds.length; i++) {
if (upkeepTransmitInfo[i].earlyChecksPassed) {
upkeepTransmitInfo[i].gasOverhead = _getCappedGasOverhead(
gasOverhead,
upkeepTransmitInfo[i].triggerType,
uint32(report.performDatas[i].length),
hotVars.f
);
(reimbursement, premium) = _postPerformPayment(
hotVars,
report.upkeepIds[i],
upkeepTransmitInfo[i],
report.fastGasWei,
report.linkNative,
numUpkeepsPassedChecks
);
totalPremium += premium;
totalReimbursement += reimbursement;
emit UpkeepPerformed(
report.upkeepIds[i],
upkeepTransmitInfo[i].performSuccess,
reimbursement + premium,
upkeepTransmitInfo[i].gasUsed,
upkeepTransmitInfo[i].gasOverhead,
report.triggers[i]
);
}
}
}
// record payments
s_transmitters[msg.sender].balance += totalReimbursement;
s_hotVars.totalPremium += totalPremium;
uint40 epochAndRound = uint40(uint256(reportContext[1]));
uint32 epoch = uint32(epochAndRound >> 8);
if (epoch > hotVars.latestEpoch) {
s_hotVars.latestEpoch = epoch;
}
}
/**
* @notice simulates the upkeep with the perform data returned from checkUpkeep
* @param id identifier of the upkeep to execute the data with.
* @param performData calldata parameter to be passed to the target upkeep.
* @return success whether the call reverted or not
* @return gasUsed the amount of gas the target contract consumed
*/
function simulatePerformUpkeep(
uint256 id,
bytes calldata performData
) external cannotExecute returns (bool success, uint256 gasUsed) {
if (s_hotVars.paused) revert RegistryPaused();
Upkeep memory upkeep = s_upkeep[id];
(success, gasUsed) = _performUpkeep(upkeep.forwarder, upkeep.performGas, performData);
return (success, gasUsed);
}
/**
* @notice uses LINK's transferAndCall to LINK and add funding to an upkeep
* @dev safe to cast uint256 to uint96 as total LINK supply is under UINT96MAX
* @param sender the account which transferred the funds
* @param amount number of LINK transfer
*/
function onTokenTransfer(address sender, uint256 amount, bytes calldata data) external override {
if (msg.sender != address(i_link)) revert OnlyCallableByLINKToken();
if (data.length != 32) revert InvalidDataLength();
uint256 id = abi.decode(data, (uint256));
if (s_upkeep[id].maxValidBlocknumber != UINT32_MAX) revert UpkeepCancelled();
s_upkeep[id].balance = s_upkeep[id].balance + uint96(amount);
s_expectedLinkBalance = s_expectedLinkBalance + amount;
emit FundsAdded(id, sender, uint96(amount));
}
// ================================================================
// | SETTERS |
// ================================================================
/**
* @inheritdoc OCR2Abstract
* @dev prefer the type-safe version of setConfig (below) whenever possible
*/
function setConfig(
address[] memory signers,
address[] memory transmitters,
uint8 f,
bytes memory onchainConfigBytes,
uint64 offchainConfigVersion,
bytes memory offchainConfig
) external override {
setConfigTypeSafe(
signers,
transmitters,
f,
abi.decode(onchainConfigBytes, (OnchainConfig)),
offchainConfigVersion,
offchainConfig
);
}
function setConfigTypeSafe(
address[] memory signers,
address[] memory transmitters,
uint8 f,
OnchainConfig memory onchainConfig,
uint64 offchainConfigVersion,
bytes memory offchainConfig
) public onlyOwner {
if (signers.length > maxNumOracles) revert TooManyOracles();
if (f == 0) revert IncorrectNumberOfFaultyOracles();
if (signers.length != transmitters.length || signers.length <= 3 * f) revert IncorrectNumberOfSigners();
// move all pooled payments out of the pool to each transmitter's balance
uint96 totalPremium = s_hotVars.totalPremium;
uint96 oldLength = uint96(s_transmittersList.length);
for (uint256 i = 0; i < oldLength; i++) {
_updateTransmitterBalanceFromPool(s_transmittersList[i], totalPremium, oldLength);
}
// remove any old signer/transmitter addresses
address signerAddress;
address transmitterAddress;
for (uint256 i = 0; i < oldLength; i++) {
signerAddress = s_signersList[i];
transmitterAddress = s_transmittersList[i];
delete s_signers[signerAddress];
// Do not delete the whole transmitter struct as it has balance information stored
s_transmitters[transmitterAddress].active = false;
}
delete s_signersList;
delete s_transmittersList;
// add new signer/transmitter addresses
{
Transmitter memory transmitter;
address temp;
for (uint256 i = 0; i < signers.length; i++) {
if (s_signers[signers[i]].active) revert RepeatedSigner();
if (signers[i] == ZERO_ADDRESS) revert InvalidSigner();
s_signers[signers[i]] = Signer({active: true, index: uint8(i)});
temp = transmitters[i];
if (temp == ZERO_ADDRESS) revert InvalidTransmitter();
transmitter = s_transmitters[temp];
if (transmitter.active) revert RepeatedTransmitter();
transmitter.active = true;
transmitter.index = uint8(i);
// new transmitters start afresh from current totalPremium
// some spare change of premium from previous pool will be forfeited
transmitter.lastCollected = totalPremium;
s_transmitters[temp] = transmitter;
}
}
s_signersList = signers;
s_transmittersList = transmitters;
s_hotVars = HotVars({
f: f,
paymentPremiumPPB: onchainConfig.paymentPremiumPPB,
flatFeeMicroLink: onchainConfig.flatFeeMicroLink,
stalenessSeconds: onchainConfig.stalenessSeconds,
gasCeilingMultiplier: onchainConfig.gasCeilingMultiplier,
paused: s_hotVars.paused,
reentrancyGuard: s_hotVars.reentrancyGuard,
totalPremium: totalPremium,
latestEpoch: 0 // DON restarts epoch
});
s_storage = Storage({
checkGasLimit: onchainConfig.checkGasLimit,
minUpkeepSpend: onchainConfig.minUpkeepSpend,
maxPerformGas: onchainConfig.maxPerformGas,
transcoder: onchainConfig.transcoder,
maxCheckDataSize: onchainConfig.maxCheckDataSize,
maxPerformDataSize: onchainConfig.maxPerformDataSize,
maxRevertDataSize: onchainConfig.maxRevertDataSize,
upkeepPrivilegeManager: onchainConfig.upkeepPrivilegeManager,
nonce: s_storage.nonce,
configCount: s_storage.configCount,
latestConfigBlockNumber: s_storage.latestConfigBlockNumber,
ownerLinkBalance: s_storage.ownerLinkBalance
});
s_fallbackGasPrice = onchainConfig.fallbackGasPrice;
s_fallbackLinkPrice = onchainConfig.fallbackLinkPrice;
uint32 previousConfigBlockNumber = s_storage.latestConfigBlockNumber;
s_storage.latestConfigBlockNumber = uint32(_blockNum());
s_storage.configCount += 1;
bytes memory onchainConfigBytes = abi.encode(onchainConfig);
s_latestConfigDigest = _configDigestFromConfigData(
block.chainid,
address(this),
s_storage.configCount,
signers,
transmitters,
f,
onchainConfigBytes,
offchainConfigVersion,
offchainConfig
);
for (uint256 idx = 0; idx < s_registrars.length(); idx++) {
s_registrars.remove(s_registrars.at(idx));
}
for (uint256 idx = 0; idx < onchainConfig.registrars.length; idx++) {
s_registrars.add(onchainConfig.registrars[idx]);
}
emit ConfigSet(
previousConfigBlockNumber,
s_latestConfigDigest,
s_storage.configCount,
signers,
transmitters,
f,
onchainConfigBytes,
offchainConfigVersion,
offchainConfig
);
}
// ================================================================
// | GETTERS |
// ================================================================
/**
* @inheritdoc OCR2Abstract
*/
function latestConfigDetails()
external
view
override
returns (uint32 configCount, uint32 blockNumber, bytes32 configDigest)
{
return (s_storage.configCount, s_storage.latestConfigBlockNumber, s_latestConfigDigest);
}
/**
* @inheritdoc OCR2Abstract
*/
function latestConfigDigestAndEpoch()
external
view
override
returns (bool scanLogs, bytes32 configDigest, uint32 epoch)
{
return (false, s_latestConfigDigest, s_hotVars.latestEpoch);
}
}
// SPDX-License-Identifier: MIT
pragma solidity 0.8.16;
import {EnumerableSet} from "../../../vendor/openzeppelin-solidity/v4.7.3/contracts/utils/structs/EnumerableSet.sol";
import {Address} from "../../../vendor/openzeppelin-solidity/v4.7.3/contracts/utils/Address.sol";
import {ArbGasInfo} from "../../../vendor/@arbitrum/nitro-contracts/src/precompiles/ArbGasInfo.sol";
import {OVM_GasPriceOracle} from "../../../vendor/@eth-optimism/contracts/0.8.9/contracts/L2/predeploys/OVM_GasPriceOracle.sol";
import {ExecutionPrevention} from "../../../automation/ExecutionPrevention.sol";
import {ArbSys} from "../../../vendor/@arbitrum/nitro-contracts/src/precompiles/ArbSys.sol";
import {StreamsLookupCompatibleInterface} from "./interfaces/StreamsLookupCompatibleInterface.sol";
import {ILogAutomation, Log} from "./interfaces/ILogAutomation.sol";
import {IAutomationForwarder} from "./interfaces/IAutomationForwarder.sol";
import {ConfirmedOwner} from "../../../shared/access/ConfirmedOwner.sol";
import {AggregatorV3Interface} from "../../../interfaces/AggregatorV3Interface.sol";
import {LinkTokenInterface} from "../../../shared/interfaces/LinkTokenInterface.sol";
import {KeeperCompatibleInterface} from "../../../automation/interfaces/KeeperCompatibleInterface.sol";
import {UpkeepTranscoderInterface, UpkeepFormat} from "../../../automation/interfaces/UpkeepTranscoderInterface.sol";
/**
* @notice Base Keeper Registry contract, contains shared logic between
* KeeperRegistry and KeeperRegistryLogic
* @dev all errors, events, and internal functions should live here
*/
abstract contract KeeperRegistryBase2_1 is ConfirmedOwner, ExecutionPrevention {
using Address for address;
using EnumerableSet for EnumerableSet.UintSet;
using EnumerableSet for EnumerableSet.AddressSet;
address internal constant ZERO_ADDRESS = address(0);
address internal constant IGNORE_ADDRESS = 0xFFfFfFffFFfffFFfFFfFFFFFffFFFffffFfFFFfF;
bytes4 internal constant CHECK_SELECTOR = KeeperCompatibleInterface.checkUpkeep.selector;
bytes4 internal constant PERFORM_SELECTOR = KeeperCompatibleInterface.performUpkeep.selector;
bytes4 internal constant CHECK_CALLBACK_SELECTOR = StreamsLookupCompatibleInterface.checkCallback.selector;
bytes4 internal constant CHECK_LOG_SELECTOR = ILogAutomation.checkLog.selector;
uint256 internal constant PERFORM_GAS_MIN = 2_300;
uint256 internal constant CANCELLATION_DELAY = 50;
uint256 internal constant PERFORM_GAS_CUSHION = 5_000;
uint256 internal constant PPB_BASE = 1_000_000_000;
uint32 internal constant UINT32_MAX = type(uint32).max;
uint96 internal constant LINK_TOTAL_SUPPLY = 1e27;
// The first byte of the mask can be 0, because we only ever have 31 oracles
uint256 internal constant ORACLE_MASK = 0x0001010101010101010101010101010101010101010101010101010101010101;
/**
* @dev UPKEEP_TRANSCODER_VERSION_BASE is temporary necessity for backwards compatibility with
* MigratableKeeperRegistryInterfaceV1 - it should be removed in future versions in favor of
* UPKEEP_VERSION_BASE and MigratableKeeperRegistryInterfaceV2
*/
UpkeepFormat internal constant UPKEEP_TRANSCODER_VERSION_BASE = UpkeepFormat.V1;
uint8 internal constant UPKEEP_VERSION_BASE = 3;
// L1_FEE_DATA_PADDING includes 35 bytes for L1 data padding for Optimism
bytes internal constant L1_FEE_DATA_PADDING =
"0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff";
uint256 internal constant REGISTRY_CONDITIONAL_OVERHEAD = 90_000; // Used in maxPayment estimation, and in capping overheads during actual payment
uint256 internal constant REGISTRY_LOG_OVERHEAD = 110_000; // Used only in maxPayment estimation, and in capping overheads during actual payment.
uint256 internal constant REGISTRY_PER_PERFORM_BYTE_GAS_OVERHEAD = 20; // Used only in maxPayment estimation, and in capping overheads during actual payment. Value scales with performData length.
uint256 internal constant REGISTRY_PER_SIGNER_GAS_OVERHEAD = 7_500; // Used only in maxPayment estimation, and in capping overheads during actual payment. Value scales with f.
uint256 internal constant ACCOUNTING_FIXED_GAS_OVERHEAD = 27_500; // Used in actual payment. Fixed overhead per tx
uint256 internal constant ACCOUNTING_PER_SIGNER_GAS_OVERHEAD = 1_100; // Used in actual payment. overhead per signer
uint256 internal constant ACCOUNTING_PER_UPKEEP_GAS_OVERHEAD = 7_000; // Used in actual payment. overhead per upkeep performed
OVM_GasPriceOracle internal constant OPTIMISM_ORACLE = OVM_GasPriceOracle(0x420000000000000000000000000000000000000F);
ArbGasInfo internal constant ARB_NITRO_ORACLE = ArbGasInfo(0x000000000000000000000000000000000000006C);
ArbSys internal constant ARB_SYS = ArbSys(0x0000000000000000000000000000000000000064);
LinkTokenInterface internal immutable i_link;
AggregatorV3Interface internal immutable i_linkNativeFeed;
AggregatorV3Interface internal immutable i_fastGasFeed;
Mode internal immutable i_mode;
address internal immutable i_automationForwarderLogic;
/**
* @dev - The storage is gas optimised for one and only one function - transmit. All the storage accessed in transmit
* is stored compactly. Rest of the storage layout is not of much concern as transmit is the only hot path
*/
// Upkeep storage
EnumerableSet.UintSet internal s_upkeepIDs;
mapping(uint256 => Upkeep) internal s_upkeep; // accessed during transmit
mapping(uint256 => address) internal s_upkeepAdmin;
mapping(uint256 => address) internal s_proposedAdmin;
mapping(uint256 => bytes) internal s_checkData;
mapping(bytes32 => bool) internal s_dedupKeys;
// Registry config and state
EnumerableSet.AddressSet internal s_registrars;
mapping(address => Transmitter) internal s_transmitters;
mapping(address => Signer) internal s_signers;
address[] internal s_signersList; // s_signersList contains the signing address of each oracle
address[] internal s_transmittersList; // s_transmittersList contains the transmission address of each oracle
mapping(address => address) internal s_transmitterPayees; // s_payees contains the mapping from transmitter to payee.
mapping(address => address) internal s_proposedPayee; // proposed payee for a transmitter
bytes32 internal s_latestConfigDigest; // Read on transmit path in case of signature verification
HotVars internal s_hotVars; // Mixture of config and state, used in transmit
Storage internal s_storage; // Mixture of config and state, not used in transmit
uint256 internal s_fallbackGasPrice;
uint256 internal s_fallbackLinkPrice;
uint256 internal s_expectedLinkBalance; // Used in case of erroneous LINK transfers to contract
mapping(address => MigrationPermission) internal s_peerRegistryMigrationPermission; // Permissions for migration to and fro
mapping(uint256 => bytes) internal s_upkeepTriggerConfig; // upkeep triggers
mapping(uint256 => bytes) internal s_upkeepOffchainConfig; // general config set by users for each upkeep
mapping(uint256 => bytes) internal s_upkeepPrivilegeConfig; // general config set by an administrative role for an upkeep
mapping(address => bytes) internal s_adminPrivilegeConfig; // general config set by an administrative role for an admin
error ArrayHasNoEntries();
error CannotCancel();
error CheckDataExceedsLimit();
error ConfigDigestMismatch();
error DuplicateEntry();
error DuplicateSigners();
error GasLimitCanOnlyIncrease();
error GasLimitOutsideRange();
error IncorrectNumberOfFaultyOracles();
error IncorrectNumberOfSignatures();
error IncorrectNumberOfSigners();
error IndexOutOfRange();
error InsufficientFunds();
error InvalidDataLength();
error InvalidTrigger();
error InvalidPayee();
error InvalidRecipient();
error InvalidReport();
error InvalidSigner();
error InvalidTransmitter();
error InvalidTriggerType();
error MaxCheckDataSizeCanOnlyIncrease();
error MaxPerformDataSizeCanOnlyIncrease();
error MigrationNotPermitted();
error NotAContract();
error OnlyActiveSigners();
error OnlyActiveTransmitters();
error OnlyCallableByAdmin();
error OnlyCallableByLINKToken();
error OnlyCallableByOwnerOrAdmin();
error OnlyCallableByOwnerOrRegistrar();
error OnlyCallableByPayee();
error OnlyCallableByProposedAdmin();
error OnlyCallableByProposedPayee();
error OnlyCallableByUpkeepPrivilegeManager();
error OnlyPausedUpkeep();
error OnlyUnpausedUpkeep();
error ParameterLengthError();
error PaymentGreaterThanAllLINK();
error ReentrantCall();
error RegistryPaused();
error RepeatedSigner();
error RepeatedTransmitter();
error TargetCheckReverted(bytes reason);
error TooManyOracles();
error TranscoderNotSet();
error UpkeepAlreadyExists();
error UpkeepCancelled();
error UpkeepNotCanceled();
error UpkeepNotNeeded();
error ValueNotChanged();
enum MigrationPermission {
NONE,
OUTGOING,
INCOMING,
BIDIRECTIONAL
}
enum Mode {
DEFAULT,
ARBITRUM,
OPTIMISM
}
enum Trigger {
CONDITION,
LOG
}
enum UpkeepFailureReason {
NONE,
UPKEEP_CANCELLED,
UPKEEP_PAUSED,
TARGET_CHECK_REVERTED,
UPKEEP_NOT_NEEDED,
PERFORM_DATA_EXCEEDS_LIMIT,
INSUFFICIENT_BALANCE,
CALLBACK_REVERTED,
REVERT_DATA_EXCEEDS_LIMIT,
REGISTRY_PAUSED
}
/**
* @notice OnchainConfig of the registry
* @dev only used in params and return values
* @member paymentPremiumPPB payment premium rate oracles receive on top of
* being reimbursed for gas, measured in parts per billion
* @member flatFeeMicroLink flat fee paid to oracles for performing upkeeps,
* priced in MicroLink; can be used in conjunction with or independently of
* paymentPremiumPPB
* @member checkGasLimit gas limit when checking for upkeep
* @member stalenessSeconds number of seconds that is allowed for feed data to
* be stale before switching to the fallback pricing
* @member gasCeilingMultiplier multiplier to apply to the fast gas feed price
* when calculating the payment ceiling for keepers
* @member minUpkeepSpend minimum LINK that an upkeep must spend before cancelling
* @member maxPerformGas max performGas allowed for an upkeep on this registry
* @member maxCheckDataSize max length of checkData bytes
* @member maxPerformDataSize max length of performData bytes
* @member maxRevertDataSize max length of revertData bytes
* @member fallbackGasPrice gas price used if the gas price feed is stale
* @member fallbackLinkPrice LINK price used if the LINK price feed is stale
* @member transcoder address of the transcoder contract
* @member registrars addresses of the registrar contracts
* @member upkeepPrivilegeManager address which can set privilege for upkeeps
*/
struct OnchainConfig {
uint32 paymentPremiumPPB;
uint32 flatFeeMicroLink; // min 0.000001 LINK, max 4294 LINK
uint32 checkGasLimit;
uint24 stalenessSeconds;
uint16 gasCeilingMultiplier;
uint96 minUpkeepSpend;
uint32 maxPerformGas;
uint32 maxCheckDataSize;
uint32 maxPerformDataSize;
uint32 maxRevertDataSize;
uint256 fallbackGasPrice;
uint256 fallbackLinkPrice;
address transcoder;
address[] registrars;
address upkeepPrivilegeManager;
}
/**
* @notice state of the registry
* @dev only used in params and return values
* @dev this will likely be deprecated in a future version of the registry in favor of individual getters
* @member nonce used for ID generation
* @member ownerLinkBalance withdrawable balance of LINK by contract owner
* @member expectedLinkBalance the expected balance of LINK of the registry
* @member totalPremium the total premium collected on registry so far
* @member numUpkeeps total number of upkeeps on the registry
* @member configCount ordinal number of current config, out of all configs applied to this contract so far
* @member latestConfigBlockNumber last block at which this config was set
* @member latestConfigDigest domain-separation tag for current config
* @member latestEpoch for which a report was transmitted
* @member paused freeze on execution scoped to the entire registry
*/
struct State {
uint32 nonce;
uint96 ownerLinkBalance;
uint256 expectedLinkBalance;
uint96 totalPremium;
uint256 numUpkeeps;
uint32 configCount;
uint32 latestConfigBlockNumber;
bytes32 latestConfigDigest;
uint32 latestEpoch;
bool paused;
}
/**
* @notice relevant state of an upkeep which is used in transmit function
* @member paused if this upkeep has been paused
* @member performGas the gas limit of upkeep execution
* @member maxValidBlocknumber until which block this upkeep is valid
* @member forwarder the forwarder contract to use for this upkeep
* @member amountSpent the amount this upkeep has spent
* @member balance the balance of this upkeep
* @member lastPerformedBlockNumber the last block number when this upkeep was performed
*/
struct Upkeep {
bool paused;
uint32 performGas;
uint32 maxValidBlocknumber;
IAutomationForwarder forwarder;
// 0 bytes left in 1st EVM word - not written to in transmit
uint96 amountSpent;
uint96 balance;
uint32 lastPerformedBlockNumber;
// 2 bytes left in 2nd EVM word - written in transmit path
}
/**
* @notice all information about an upkeep
* @dev only used in return values
* @dev this will likely be deprecated in a future version of the registry
* @member target the contract which needs to be serviced
* @member performGas the gas limit of upkeep execution
* @member checkData the checkData bytes for this upkeep
* @member balance the balance of this upkeep
* @member admin for this upkeep
* @member maxValidBlocknumber until which block this upkeep is valid
* @member lastPerformedBlockNumber the last block number when this upkeep was performed
* @member amountSpent the amount this upkeep has spent
* @member paused if this upkeep has been paused
* @member offchainConfig the off-chain config of this upkeep
*/
struct UpkeepInfo {
address target;
uint32 performGas;
bytes checkData;
uint96 balance;
address admin;
uint64 maxValidBlocknumber;
uint32 lastPerformedBlockNumber;
uint96 amountSpent;
bool paused;
bytes offchainConfig;
}
/// @dev Config + State storage struct which is on hot transmit path
struct HotVars {
uint8 f; // maximum number of faulty oracles
uint32 paymentPremiumPPB; // premium percentage charged to user over tx cost
uint32 flatFeeMicroLink; // flat fee charged to user for every perform
uint24 stalenessSeconds; // Staleness tolerance for feeds
uint16 gasCeilingMultiplier; // multiplier on top of fast gas feed for upper bound
bool paused; // pause switch for all upkeeps in the registry
bool reentrancyGuard; // guard against reentrancy
uint96 totalPremium; // total historical payment to oracles for premium
uint32 latestEpoch; // latest epoch for which a report was transmitted
// 1 EVM word full
}
/// @dev Config + State storage struct which is not on hot transmit path
struct Storage {
uint96 minUpkeepSpend; // Minimum amount an upkeep must spend
address transcoder; // Address of transcoder contract used in migrations
// 1 EVM word full
uint96 ownerLinkBalance; // Balance of owner, accumulates minUpkeepSpend in case it is not spent
uint32 checkGasLimit; // Gas limit allowed in checkUpkeep
uint32 maxPerformGas; // Max gas an upkeep can use on this registry
uint32 nonce; // Nonce for each upkeep created
uint32 configCount; // incremented each time a new config is posted, The count
// is incorporated into the config digest to prevent replay attacks.
uint32 latestConfigBlockNumber; // makes it easier for offchain systems to extract config from logs
// 2 EVM word full
uint32 maxCheckDataSize; // max length of checkData bytes
uint32 maxPerformDataSize; // max length of performData bytes
uint32 maxRevertDataSize; // max length of revertData bytes
address upkeepPrivilegeManager; // address which can set privilege for upkeeps
// 3 EVM word full
}
/// @dev Report transmitted by OCR to transmit function
struct Report {
uint256 fastGasWei;
uint256 linkNative;
uint256[] upkeepIds;
uint256[] gasLimits;
bytes[] triggers;
bytes[] performDatas;
}
/**
* @dev This struct is used to maintain run time information about an upkeep in transmit function
* @member upkeep the upkeep struct
* @member earlyChecksPassed whether the upkeep passed early checks before perform
* @member maxLinkPayment the max amount this upkeep could pay for work
* @member performSuccess whether the perform was successful
* @member triggerType the type of trigger
* @member gasUsed gasUsed by this upkeep in perform
* @member gasOverhead gasOverhead for this upkeep
* @member dedupID unique ID used to dedup an upkeep/trigger combo
*/
struct UpkeepTransmitInfo {
Upkeep upkeep;
bool earlyChecksPassed;
uint96 maxLinkPayment;
bool performSuccess;
Trigger triggerType;
uint256 gasUsed;
uint256 gasOverhead;
bytes32 dedupID;
}
struct Transmitter {
bool active;
uint8 index; // Index of oracle in s_signersList/s_transmittersList
uint96 balance;
uint96 lastCollected;
}
struct Signer {
bool active;
// Index of oracle in s_signersList/s_transmittersList
uint8 index;
}
/**
* @notice the trigger structure conditional trigger type
*/
struct ConditionalTrigger {
uint32 blockNum;
bytes32 blockHash;
}
/**
* @notice the trigger structure of log upkeeps
* @dev NOTE that blockNum / blockHash describe the block used for the callback,
* not necessarily the block number that the log was emitted in!!!!
*/
struct LogTrigger {
bytes32 logBlockHash;
bytes32 txHash;
uint32 logIndex;
uint32 blockNum;
bytes32 blockHash;
}
event AdminPrivilegeConfigSet(address indexed admin, bytes privilegeConfig);
event CancelledUpkeepReport(uint256 indexed id, bytes trigger);
event DedupKeyAdded(bytes32 indexed dedupKey);
event FundsAdded(uint256 indexed id, address indexed from, uint96 amount);
event FundsWithdrawn(uint256 indexed id, uint256 amount, address to);
event InsufficientFundsUpkeepReport(uint256 indexed id, bytes trigger);
event OwnerFundsWithdrawn(uint96 amount);
event Paused(address account);
event PayeesUpdated(address[] transmitters, address[] payees);
event PayeeshipTransferRequested(address indexed transmitter, address indexed from, address indexed to);
event PayeeshipTransferred(address indexed transmitter, address indexed from, address indexed to);
event PaymentWithdrawn(address indexed transmitter, uint256 indexed amount, address indexed to, address payee);
event ReorgedUpkeepReport(uint256 indexed id, bytes trigger);
event StaleUpkeepReport(uint256 indexed id, bytes trigger);
event UpkeepAdminTransferred(uint256 indexed id, address indexed from, address indexed to);
event UpkeepAdminTransferRequested(uint256 indexed id, address indexed from, address indexed to);
event UpkeepCanceled(uint256 indexed id, uint64 indexed atBlockHeight);
event UpkeepCheckDataSet(uint256 indexed id, bytes newCheckData);
event UpkeepGasLimitSet(uint256 indexed id, uint96 gasLimit);
event UpkeepMigrated(uint256 indexed id, uint256 remainingBalance, address destination);
event UpkeepOffchainConfigSet(uint256 indexed id, bytes offchainConfig);
event UpkeepPaused(uint256 indexed id);
event UpkeepPerformed(
uint256 indexed id,
bool indexed success,
uint96 totalPayment,
uint256 gasUsed,
uint256 gasOverhead,
bytes trigger
);
event UpkeepPrivilegeConfigSet(uint256 indexed id, bytes privilegeConfig);
event UpkeepReceived(uint256 indexed id, uint256 startingBalance, address importedFrom);
event UpkeepRegistered(uint256 indexed id, uint32 performGas, address admin);
event UpkeepTriggerConfigSet(uint256 indexed id, bytes triggerConfig);
event UpkeepUnpaused(uint256 indexed id);
event Unpaused(address account);
/**
* @param mode the contract mode of default, Arbitrum, or Optimism
* @param link address of the LINK Token
* @param linkNativeFeed address of the LINK/Native price feed
* @param fastGasFeed address of the Fast Gas price feed
*/
constructor(
Mode mode,
address link,
address linkNativeFeed,
address fastGasFeed,
address automationForwarderLogic
) ConfirmedOwner(msg.sender) {
i_mode = mode;
i_link = LinkTokenInterface(link);
i_linkNativeFeed = AggregatorV3Interface(linkNativeFeed);
i_fastGasFeed = AggregatorV3Interface(fastGasFeed);
i_automationForwarderLogic = automationForwarderLogic;
}
// ================================================================
// | INTERNAL FUNCTIONS ONLY |
// ================================================================
/**
* @dev creates a new upkeep with the given fields
* @param id the id of the upkeep
* @param upkeep the upkeep to create
* @param admin address to cancel upkeep and withdraw remaining funds
* @param checkData data which is passed to user's checkUpkeep
* @param triggerConfig the trigger config for this upkeep
* @param offchainConfig the off-chain config of this upkeep
*/
function _createUpkeep(
uint256 id,
Upkeep memory upkeep,
address admin,
bytes memory checkData,
bytes memory triggerConfig,
bytes memory offchainConfig
) internal {
if (s_hotVars.paused) revert RegistryPaused();
if (checkData.length > s_storage.maxCheckDataSize) revert CheckDataExceedsLimit();
if (upkeep.performGas < PERFORM_GAS_MIN || upkeep.performGas > s_storage.maxPerformGas)
revert GasLimitOutsideRange();
if (address(s_upkeep[id].forwarder) != address(0)) revert UpkeepAlreadyExists();
s_upkeep[id] = upkeep;
s_upkeepAdmin[id] = admin;
s_checkData[id] = checkData;
s_expectedLinkBalance = s_expectedLinkBalance + upkeep.balance;
s_upkeepTriggerConfig[id] = triggerConfig;
s_upkeepOffchainConfig[id] = offchainConfig;
s_upkeepIDs.add(id);
}
/**
* @dev creates an ID for the upkeep based on the upkeep's type
* @dev the format of the ID looks like this:
* ****00000000000X****************
* 4 bytes of entropy
* 11 bytes of zeros
* 1 identifying byte for the trigger type
* 16 bytes of entropy
* @dev this maintains the same level of entropy as eth addresses, so IDs will still be unique
* @dev we add the "identifying" part in the middle so that it is mostly hidden from users who usually only
* see the first 4 and last 4 hex values ex 0x1234...ABCD
*/
function _createID(Trigger triggerType) internal view returns (uint256) {
bytes1 empty;
bytes memory idBytes = abi.encodePacked(
keccak256(abi.encode(_blockHash(_blockNum() - 1), address(this), s_storage.nonce))
);
for (uint256 idx = 4; idx < 15; idx++) {
idBytes[idx] = empty;
}
idBytes[15] = bytes1(uint8(triggerType));
return uint256(bytes32(idBytes));
}
/**
* @dev retrieves feed data for fast gas/native and link/native prices. if the feed
* data is stale it uses the configured fallback price. Once a price is picked
* for gas it takes the min of gas price in the transaction or the fast gas
* price in order to reduce costs for the upkeep clients.
*/
function _getFeedData(HotVars memory hotVars) internal view returns (uint256 gasWei, uint256 linkNative) {
uint32 stalenessSeconds = hotVars.stalenessSeconds;
bool staleFallback = stalenessSeconds > 0;
uint256 timestamp;
int256 feedValue;
(, feedValue, , timestamp, ) = i_fastGasFeed.latestRoundData();
if (
feedValue <= 0 || block.timestamp < timestamp || (staleFallback && stalenessSeconds < block.timestamp - timestamp)
) {
gasWei = s_fallbackGasPrice;
} else {
gasWei = uint256(feedValue);
}
(, feedValue, , timestamp, ) = i_linkNativeFeed.latestRoundData();
if (
feedValue <= 0 || block.timestamp < timestamp || (staleFallback && stalenessSeconds < block.timestamp - timestamp)
) {
linkNative = s_fallbackLinkPrice;
} else {
linkNative = uint256(feedValue);
}
return (gasWei, linkNative);
}
/**
* @dev calculates LINK paid for gas spent plus a configure premium percentage
* @param gasLimit the amount of gas used
* @param gasOverhead the amount of gas overhead
* @param fastGasWei the fast gas price
* @param linkNative the exchange ratio between LINK and Native token
* @param numBatchedUpkeeps the number of upkeeps in this batch. Used to divide the L1 cost
* @param isExecution if this is triggered by a perform upkeep function
*/
function _calculatePaymentAmount(
HotVars memory hotVars,
uint256 gasLimit,
uint256 gasOverhead,
uint256 fastGasWei,
uint256 linkNative,
uint16 numBatchedUpkeeps,
bool isExecution
) internal view returns (uint96, uint96) {
uint256 gasWei = fastGasWei * hotVars.gasCeilingMultiplier;
// in case it's actual execution use actual gas price, capped by fastGasWei * gasCeilingMultiplier
if (isExecution && tx.gasprice < gasWei) {
gasWei = tx.gasprice;
}
uint256 l1CostWei = 0;
if (i_mode == Mode.OPTIMISM) {
bytes memory txCallData = new bytes(0);
if (isExecution) {
txCallData = bytes.concat(msg.data, L1_FEE_DATA_PADDING);
} else {
// fee is 4 per 0 byte, 16 per non-zero byte. Worst case we can have
// s_storage.maxPerformDataSize non zero-bytes. Instead of setting bytes to non-zero
// we initialize 'new bytes' of length 4*maxPerformDataSize to cover for zero bytes.
txCallData = new bytes(4 * s_storage.maxPerformDataSize);
}
l1CostWei = OPTIMISM_ORACLE.getL1Fee(txCallData);
} else if (i_mode == Mode.ARBITRUM) {
if (isExecution) {
l1CostWei = ARB_NITRO_ORACLE.getCurrentTxL1GasFees();
} else {
// fee is 4 per 0 byte, 16 per non-zero byte - we assume all non-zero and
// max data size to calculate max payment
(, uint256 perL1CalldataUnit, , , , ) = ARB_NITRO_ORACLE.getPricesInWei();
l1CostWei = perL1CalldataUnit * s_storage.maxPerformDataSize * 16;
}
}
// if it's not performing upkeeps, use gas ceiling multiplier to estimate the upper bound
if (!isExecution) {
l1CostWei = hotVars.gasCeilingMultiplier * l1CostWei;
}
// Divide l1CostWei among all batched upkeeps. Spare change from division is not charged
l1CostWei = l1CostWei / numBatchedUpkeeps;
uint256 gasPayment = ((gasWei * (gasLimit + gasOverhead) + l1CostWei) * 1e18) / linkNative;
uint256 premium = (((gasWei * gasLimit) + l1CostWei) * 1e9 * hotVars.paymentPremiumPPB) /
linkNative +
uint256(hotVars.flatFeeMicroLink) *
1e12;
// LINK_TOTAL_SUPPLY < UINT96_MAX
if (gasPayment + premium > LINK_TOTAL_SUPPLY) revert PaymentGreaterThanAllLINK();
return (uint96(gasPayment), uint96(premium));
}
/**
* @dev calculates the max LINK payment for an upkeep
*/
function _getMaxLinkPayment(
HotVars memory hotVars,
Trigger triggerType,
uint32 performGas,
uint32 performDataLength,
uint256 fastGasWei,
uint256 linkNative,
bool isExecution // Whether this is an actual perform execution or just a simulation
) internal view returns (uint96) {
uint256 gasOverhead = _getMaxGasOverhead(triggerType, performDataLength, hotVars.f);
(uint96 reimbursement, uint96 premium) = _calculatePaymentAmount(
hotVars,
performGas,
gasOverhead,
fastGasWei,
linkNative,
1, // Consider only 1 upkeep in batch to get maxPayment
isExecution
);
return reimbursement + premium;
}
/**
* @dev returns the max gas overhead that can be charged for an upkeep
*/
function _getMaxGasOverhead(Trigger triggerType, uint32 performDataLength, uint8 f) internal pure returns (uint256) {
// performData causes additional overhead in report length and memory operations
uint256 baseOverhead;
if (triggerType == Trigger.CONDITION) {
baseOverhead = REGISTRY_CONDITIONAL_OVERHEAD;
} else if (triggerType == Trigger.LOG) {
baseOverhead = REGISTRY_LOG_OVERHEAD;
} else {
revert InvalidTriggerType();
}
return
baseOverhead +
(REGISTRY_PER_SIGNER_GAS_OVERHEAD * (f + 1)) +
(REGISTRY_PER_PERFORM_BYTE_GAS_OVERHEAD * performDataLength);
}
/**
* @dev move a transmitter's balance from total pool to withdrawable balance
*/
function _updateTransmitterBalanceFromPool(
address transmitterAddress,
uint96 totalPremium,
uint96 payeeCount
) internal returns (uint96) {
Transmitter memory transmitter = s_transmitters[transmitterAddress];
if (transmitter.active) {
uint96 uncollected = totalPremium - transmitter.lastCollected;
uint96 due = uncollected / payeeCount;
transmitter.balance += due;
transmitter.lastCollected += due * payeeCount;
s_transmitters[transmitterAddress] = transmitter;
}
return transmitter.balance;
}
/**
* @dev gets the trigger type from an upkeepID (trigger type is encoded in the middle of the ID)
*/
function _getTriggerType(uint256 upkeepId) internal pure returns (Trigger) {
bytes32 rawID = bytes32(upkeepId);
bytes1 empty = bytes1(0);
for (uint256 idx = 4; idx < 15; idx++) {
if (rawID[idx] != empty) {
// old IDs that were created before this standard and migrated to this registry
return Trigger.CONDITION;
}
}
return Trigger(uint8(rawID[15]));
}
function _checkPayload(
uint256 upkeepId,
Trigger triggerType,
bytes memory triggerData
) internal view returns (bytes memory) {
if (triggerType == Trigger.CONDITION) {
return abi.encodeWithSelector(CHECK_SELECTOR, s_checkData[upkeepId]);
} else if (triggerType == Trigger.LOG) {
Log memory log = abi.decode(triggerData, (Log));
return abi.encodeWithSelector(CHECK_LOG_SELECTOR, log, s_checkData[upkeepId]);
}
revert InvalidTriggerType();
}
/**
* @dev _decodeReport decodes a serialized report into a Report struct
*/
function _decodeReport(bytes calldata rawReport) internal pure returns (Report memory) {
Report memory report = abi.decode(rawReport, (Report));
uint256 expectedLength = report.upkeepIds.length;
if (
report.gasLimits.length != expectedLength ||
report.triggers.length != expectedLength ||
report.performDatas.length != expectedLength
) {
revert InvalidReport();
}
return report;
}
/**
* @dev Does some early sanity checks before actually performing an upkeep
* @return bool whether the upkeep should be performed
* @return bytes32 dedupID for preventing duplicate performances of this trigger
*/
function _prePerformChecks(
uint256 upkeepId,
bytes memory rawTrigger,
UpkeepTransmitInfo memory transmitInfo
) internal returns (bool, bytes32) {
bytes32 dedupID;
if (transmitInfo.triggerType == Trigger.CONDITION) {
if (!_validateConditionalTrigger(upkeepId, rawTrigger, transmitInfo)) return (false, dedupID);
} else if (transmitInfo.triggerType == Trigger.LOG) {
bool valid;
(valid, dedupID) = _validateLogTrigger(upkeepId, rawTrigger, transmitInfo);
if (!valid) return (false, dedupID);
} else {
revert InvalidTriggerType();
}
if (transmitInfo.upkeep.maxValidBlocknumber <= _blockNum()) {
// Can happen when an upkeep got cancelled after report was generated.
// However we have a CANCELLATION_DELAY of 50 blocks so shouldn't happen in practice
emit CancelledUpkeepReport(upkeepId, rawTrigger);
return (false, dedupID);
}
if (transmitInfo.upkeep.balance < transmitInfo.maxLinkPayment) {
// Can happen due to fluctuations in gas / link prices
emit InsufficientFundsUpkeepReport(upkeepId, rawTrigger);
return (false, dedupID);
}
return (true, dedupID);
}
/**
* @dev Does some early sanity checks before actually performing an upkeep
*/
function _validateConditionalTrigger(
uint256 upkeepId,
bytes memory rawTrigger,
UpkeepTransmitInfo memory transmitInfo
) internal returns (bool) {
ConditionalTrigger memory trigger = abi.decode(rawTrigger, (ConditionalTrigger));
if (trigger.blockNum < transmitInfo.upkeep.lastPerformedBlockNumber) {
// Can happen when another report performed this upkeep after this report was generated
emit StaleUpkeepReport(upkeepId, rawTrigger);
return false;
}
if (
(trigger.blockHash != bytes32("") && _blockHash(trigger.blockNum) != trigger.blockHash) ||
trigger.blockNum >= _blockNum()
) {
// There are two cases of reorged report
// 1. trigger block number is in future: this is an edge case during extreme deep reorgs of chain
// which is always protected against
// 2. blockHash at trigger block number was same as trigger time. This is an optional check which is
// applied if DON sends non empty trigger.blockHash. Note: It only works for last 256 blocks on chain
// when it is sent
emit ReorgedUpkeepReport(upkeepId, rawTrigger);
return false;
}
return true;
}
function _validateLogTrigger(
uint256 upkeepId,
bytes memory rawTrigger,
UpkeepTransmitInfo memory transmitInfo
) internal returns (bool, bytes32) {
LogTrigger memory trigger = abi.decode(rawTrigger, (LogTrigger));
bytes32 dedupID = keccak256(abi.encodePacked(upkeepId, trigger.logBlockHash, trigger.txHash, trigger.logIndex));
if (
(trigger.blockHash != bytes32("") && _blockHash(trigger.blockNum) != trigger.blockHash) ||
trigger.blockNum >= _blockNum()
) {
// Reorg protection is same as conditional trigger upkeeps
emit ReorgedUpkeepReport(upkeepId, rawTrigger);
return (false, dedupID);
}
if (s_dedupKeys[dedupID]) {
emit StaleUpkeepReport(upkeepId, rawTrigger);
return (false, dedupID);
}
return (true, dedupID);
}
/**
* @dev Verify signatures attached to report
*/
function _verifyReportSignature(
bytes32[3] calldata reportContext,
bytes calldata report,
bytes32[] calldata rs,
bytes32[] calldata ss,
bytes32 rawVs
) internal view {
bytes32 h = keccak256(abi.encode(keccak256(report), reportContext));
// i-th byte counts number of sigs made by i-th signer
uint256 signedCount = 0;
Signer memory signer;
address signerAddress;
for (uint256 i = 0; i < rs.length; i++) {
signerAddress = ecrecover(h, uint8(rawVs[i]) + 27, rs[i], ss[i]);
signer = s_signers[signerAddress];
if (!signer.active) revert OnlyActiveSigners();
unchecked {
signedCount += 1 << (8 * signer.index);
}
}
if (signedCount & ORACLE_MASK != signedCount) revert DuplicateSigners();
}
/**
* @dev updates a storage marker for this upkeep to prevent duplicate and out of order performances
* @dev for conditional triggers we set the latest block number, for log triggers we store a dedupID
*/
function _updateTriggerMarker(uint256 upkeepID, UpkeepTransmitInfo memory upkeepTransmitInfo) internal {
if (upkeepTransmitInfo.triggerType == Trigger.CONDITION) {
s_upkeep[upkeepID].lastPerformedBlockNumber = uint32(_blockNum());
} else if (upkeepTransmitInfo.triggerType == Trigger.LOG) {
s_dedupKeys[upkeepTransmitInfo.dedupID] = true;
emit DedupKeyAdded(upkeepTransmitInfo.dedupID);
}
}
/**
* @dev calls the Upkeep target with the performData param passed in by the
* transmitter and the exact gas required by the Upkeep
*/
function _performUpkeep(
IAutomationForwarder forwarder,
uint256 performGas,
bytes memory performData
) internal nonReentrant returns (bool success, uint256 gasUsed) {
performData = abi.encodeWithSelector(PERFORM_SELECTOR, performData);
return forwarder.forward(performGas, performData);
}
/**
* @dev does postPerform payment processing for an upkeep. Deducts upkeep's balance and increases
* amount spent.
*/
function _postPerformPayment(
HotVars memory hotVars,
uint256 upkeepId,
UpkeepTransmitInfo memory upkeepTransmitInfo,
uint256 fastGasWei,
uint256 linkNative,
uint16 numBatchedUpkeeps
) internal returns (uint96 gasReimbursement, uint96 premium) {
(gasReimbursement, premium) = _calculatePaymentAmount(
hotVars,
upkeepTransmitInfo.gasUsed,
upkeepTransmitInfo.gasOverhead,
fastGasWei,
linkNative,
numBatchedUpkeeps,
true
);
uint96 payment = gasReimbursement + premium;
s_upkeep[upkeepId].balance -= payment;
s_upkeep[upkeepId].amountSpent += payment;
return (gasReimbursement, premium);
}
/**
* @dev Caps the gas overhead by the constant overhead used within initial payment checks in order to
* prevent a revert in payment processing.
*/
function _getCappedGasOverhead(
uint256 calculatedGasOverhead,
Trigger triggerType,
uint32 performDataLength,
uint8 f
) internal pure returns (uint256 cappedGasOverhead) {
cappedGasOverhead = _getMaxGasOverhead(triggerType, performDataLength, f);
if (calculatedGasOverhead < cappedGasOverhead) {
return calculatedGasOverhead;
}
return cappedGasOverhead;
}
/**
* @dev ensures the upkeep is not cancelled and the caller is the upkeep admin
*/
function _requireAdminAndNotCancelled(uint256 upkeepId) internal view {
if (msg.sender != s_upkeepAdmin[upkeepId]) revert OnlyCallableByAdmin();
if (s_upkeep[upkeepId].maxValidBlocknumber != UINT32_MAX) revert UpkeepCancelled();
}
/**
* @dev returns the current block number in a chain agnostic manner
*/
function _blockNum() internal view returns (uint256) {
if (i_mode == Mode.ARBITRUM) {
return ARB_SYS.arbBlockNumber();
} else {
return block.number;
}
}
/**
* @dev returns the blockhash of the provided block number in a chain agnostic manner
* @param n the blocknumber to retrieve the blockhash for
* @return blockhash the blockhash of block number n, or 0 if n is out queryable of range
*/
function _blockHash(uint256 n) internal view returns (bytes32) {
if (i_mode == Mode.ARBITRUM) {
uint256 blockNum = ARB_SYS.arbBlockNumber();
if (n >= blockNum || blockNum - n > 256) {
return "";
}
return ARB_SYS.arbBlockHash(n);
} else {
return blockhash(n);
}
}
/**
* @dev replicates Open Zeppelin's ReentrancyGuard but optimized to fit our storage
*/
modifier nonReentrant() {
if (s_hotVars.reentrancyGuard) revert ReentrantCall();
s_hotVars.reentrancyGuard = true;
_;
s_hotVars.reentrancyGuard = false;
}
}
// SPDX-License-Identifier: MIT
pragma solidity 0.8.16;
import {EnumerableSet} from "../../../vendor/openzeppelin-solidity/v4.7.3/contracts/utils/structs/EnumerableSet.sol";
import {Address} from "../../../vendor/openzeppelin-solidity/v4.7.3/contracts/utils/Address.sol";
import {KeeperRegistryBase2_1} from "./KeeperRegistryBase2_1.sol";
import {KeeperRegistryLogicB2_1} from "./KeeperRegistryLogicB2_1.sol";
import {Chainable} from "./Chainable.sol";
import {AutomationForwarder} from "./AutomationForwarder.sol";
import {IAutomationForwarder} from "./interfaces/IAutomationForwarder.sol";
import {UpkeepTranscoderInterfaceV2} from "../../../automation/interfaces/UpkeepTranscoderInterfaceV2.sol";
import {MigratableKeeperRegistryInterfaceV2} from "../../../automation/interfaces/MigratableKeeperRegistryInterfaceV2.sol";
/**
* @notice Logic contract, works in tandem with KeeperRegistry as a proxy
*/
contract KeeperRegistryLogicA2_1 is KeeperRegistryBase2_1, Chainable {
using Address for address;
using EnumerableSet for EnumerableSet.UintSet;
using EnumerableSet for EnumerableSet.AddressSet;
/**
* @param logicB the address of the second logic contract
*/
constructor(
KeeperRegistryLogicB2_1 logicB
)
KeeperRegistryBase2_1(
logicB.getMode(),
logicB.getLinkAddress(),
logicB.getLinkNativeFeedAddress(),
logicB.getFastGasFeedAddress(),
logicB.getAutomationForwarderLogic()
)
Chainable(address(logicB))
{}
/**
* @notice called by the automation DON to check if work is needed
* @param id the upkeep ID to check for work needed
* @param triggerData extra contextual data about the trigger (not used in all code paths)
* @dev this one of the core functions called in the hot path
* @dev there is a 2nd checkUpkeep function (below) that is being maintained for backwards compatibility
* @dev there is an incongruency on what gets returned during failure modes
* ex sometimes we include price data, sometimes we omit it depending on the failure
*/
function checkUpkeep(
uint256 id,
bytes memory triggerData
)
public
cannotExecute
returns (
bool upkeepNeeded,
bytes memory performData,
UpkeepFailureReason upkeepFailureReason,
uint256 gasUsed,
uint256 gasLimit,
uint256 fastGasWei,
uint256 linkNative
)
{
Trigger triggerType = _getTriggerType(id);
HotVars memory hotVars = s_hotVars;
Upkeep memory upkeep = s_upkeep[id];
if (hotVars.paused) return (false, bytes(""), UpkeepFailureReason.REGISTRY_PAUSED, 0, upkeep.performGas, 0, 0);
if (upkeep.maxValidBlocknumber != UINT32_MAX)
return (false, bytes(""), UpkeepFailureReason.UPKEEP_CANCELLED, 0, upkeep.performGas, 0, 0);
if (upkeep.paused) return (false, bytes(""), UpkeepFailureReason.UPKEEP_PAUSED, 0, upkeep.performGas, 0, 0);
(fastGasWei, linkNative) = _getFeedData(hotVars);
uint96 maxLinkPayment = _getMaxLinkPayment(
hotVars,
triggerType,
upkeep.performGas,
s_storage.maxPerformDataSize,
fastGasWei,
linkNative,
false
);
if (upkeep.balance < maxLinkPayment) {
return (false, bytes(""), UpkeepFailureReason.INSUFFICIENT_BALANCE, 0, upkeep.performGas, 0, 0);
}
bytes memory callData = _checkPayload(id, triggerType, triggerData);
gasUsed = gasleft();
(bool success, bytes memory result) = upkeep.forwarder.getTarget().call{gas: s_storage.checkGasLimit}(callData);
gasUsed = gasUsed - gasleft();
if (!success) {
// User's target check reverted. We capture the revert data here and pass it within performData
if (result.length > s_storage.maxRevertDataSize) {
return (
false,
bytes(""),
UpkeepFailureReason.REVERT_DATA_EXCEEDS_LIMIT,
gasUsed,
upkeep.performGas,
fastGasWei,
linkNative
);
}
return (
upkeepNeeded,
result,
UpkeepFailureReason.TARGET_CHECK_REVERTED,
gasUsed,
upkeep.performGas,
fastGasWei,
linkNative
);
}
(upkeepNeeded, performData) = abi.decode(result, (bool, bytes));
if (!upkeepNeeded)
return (
false,
bytes(""),
UpkeepFailureReason.UPKEEP_NOT_NEEDED,
gasUsed,
upkeep.performGas,
fastGasWei,
linkNative
);
if (performData.length > s_storage.maxPerformDataSize)
return (
false,
bytes(""),
UpkeepFailureReason.PERFORM_DATA_EXCEEDS_LIMIT,
gasUsed,
upkeep.performGas,
fastGasWei,
linkNative
);
return (upkeepNeeded, performData, upkeepFailureReason, gasUsed, upkeep.performGas, fastGasWei, linkNative);
}
/**
* @notice see other checkUpkeep function for description
* @dev this function may be deprecated in a future version of chainlink automation
*/
function checkUpkeep(
uint256 id
)
external
returns (
bool upkeepNeeded,
bytes memory performData,
UpkeepFailureReason upkeepFailureReason,
uint256 gasUsed,
uint256 gasLimit,
uint256 fastGasWei,
uint256 linkNative
)
{
return checkUpkeep(id, bytes(""));
}
/**
* @dev checkCallback is used specifically for automation data streams lookups (see StreamsLookupCompatibleInterface.sol)
* @param id the upkeepID to execute a callback for
* @param values the values returned from the data streams lookup
* @param extraData the user-provided extra context data
*/
function checkCallback(
uint256 id,
bytes[] memory values,
bytes calldata extraData
)
external
cannotExecute
returns (bool upkeepNeeded, bytes memory performData, UpkeepFailureReason upkeepFailureReason, uint256 gasUsed)
{
bytes memory payload = abi.encodeWithSelector(CHECK_CALLBACK_SELECTOR, values, extraData);
return executeCallback(id, payload);
}
/**
* @notice this is a generic callback executor that forwards a call to a user's contract with the configured
* gas limit
* @param id the upkeepID to execute a callback for
* @param payload the data (including function selector) to call on the upkeep target contract
*/
function executeCallback(
uint256 id,
bytes memory payload
)
public
cannotExecute
returns (bool upkeepNeeded, bytes memory performData, UpkeepFailureReason upkeepFailureReason, uint256 gasUsed)
{
Upkeep memory upkeep = s_upkeep[id];
gasUsed = gasleft();
(bool success, bytes memory result) = upkeep.forwarder.getTarget().call{gas: s_storage.checkGasLimit}(payload);
gasUsed = gasUsed - gasleft();
if (!success) {
return (false, bytes(""), UpkeepFailureReason.CALLBACK_REVERTED, gasUsed);
}
(upkeepNeeded, performData) = abi.decode(result, (bool, bytes));
if (!upkeepNeeded) {
return (false, bytes(""), UpkeepFailureReason.UPKEEP_NOT_NEEDED, gasUsed);
}
if (performData.length > s_storage.maxPerformDataSize) {
return (false, bytes(""), UpkeepFailureReason.PERFORM_DATA_EXCEEDS_LIMIT, gasUsed);
}
return (upkeepNeeded, performData, upkeepFailureReason, gasUsed);
}
/**
* @notice adds a new upkeep
* @param target address to perform upkeep on
* @param gasLimit amount of gas to provide the target contract when
* performing upkeep
* @param admin address to cancel upkeep and withdraw remaining funds
* @param triggerType the trigger for the upkeep
* @param checkData data passed to the contract when checking for upkeep
* @param triggerConfig the config for the trigger
* @param offchainConfig arbitrary offchain config for the upkeep
*/
function registerUpkeep(
address target,
uint32 gasLimit,
address admin,
Trigger triggerType,
bytes calldata checkData,
bytes memory triggerConfig,
bytes memory offchainConfig
) public returns (uint256 id) {
if (msg.sender != owner() && !s_registrars.contains(msg.sender)) revert OnlyCallableByOwnerOrRegistrar();
if (!target.isContract()) revert NotAContract();
id = _createID(triggerType);
IAutomationForwarder forwarder = IAutomationForwarder(
address(new AutomationForwarder(target, address(this), i_automationForwarderLogic))
);
_createUpkeep(
id,
Upkeep({
performGas: gasLimit,
balance: 0,
maxValidBlocknumber: UINT32_MAX,
lastPerformedBlockNumber: 0,
amountSpent: 0,
paused: false,
forwarder: forwarder
}),
admin,
checkData,
triggerConfig,
offchainConfig
);
s_storage.nonce++;
emit UpkeepRegistered(id, gasLimit, admin);
emit UpkeepCheckDataSet(id, checkData);
emit UpkeepTriggerConfigSet(id, triggerConfig);
emit UpkeepOffchainConfigSet(id, offchainConfig);
return (id);
}
/**
* @notice this function registers a conditional upkeep, using a backwards compatible function signature
* @dev this function is backwards compatible with versions <=2.0, but may be removed in a future version
*/
function registerUpkeep(
address target,
uint32 gasLimit,
address admin,
bytes calldata checkData,
bytes calldata offchainConfig
) external returns (uint256 id) {
return registerUpkeep(target, gasLimit, admin, Trigger.CONDITION, checkData, bytes(""), offchainConfig);
}
/**
* @notice cancels an upkeep
* @param id the upkeepID to cancel
* @dev if a user cancels an upkeep, their funds are locked for CANCELLATION_DELAY blocks to
* allow any pending performUpkeep txs time to get confirmed
*/
function cancelUpkeep(uint256 id) external {
Upkeep memory upkeep = s_upkeep[id];
bool canceled = upkeep.maxValidBlocknumber != UINT32_MAX;
bool isOwner = msg.sender == owner();
if (canceled && !(isOwner && upkeep.maxValidBlocknumber > _blockNum())) revert CannotCancel();
if (!isOwner && msg.sender != s_upkeepAdmin[id]) revert OnlyCallableByOwnerOrAdmin();
uint256 height = _blockNum();
if (!isOwner) {
height = height + CANCELLATION_DELAY;
}
s_upkeep[id].maxValidBlocknumber = uint32(height);
s_upkeepIDs.remove(id);
// charge the cancellation fee if the minUpkeepSpend is not met
uint96 minUpkeepSpend = s_storage.minUpkeepSpend;
uint96 cancellationFee = 0;
// cancellationFee is supposed to be min(max(minUpkeepSpend - amountSpent,0), amountLeft)
if (upkeep.amountSpent < minUpkeepSpend) {
cancellationFee = minUpkeepSpend - upkeep.amountSpent;
if (cancellationFee > upkeep.balance) {
cancellationFee = upkeep.balance;
}
}
s_upkeep[id].balance = upkeep.balance - cancellationFee;
s_storage.ownerLinkBalance = s_storage.ownerLinkBalance + cancellationFee;
emit UpkeepCanceled(id, uint64(height));
}
/**
* @notice adds fund to an upkeep
* @param id the upkeepID
* @param amount the amount of LINK to fund, in jules (jules = "wei" of LINK)
*/
function addFunds(uint256 id, uint96 amount) external {
Upkeep memory upkeep = s_upkeep[id];
if (upkeep.maxValidBlocknumber != UINT32_MAX) revert UpkeepCancelled();
s_upkeep[id].balance = upkeep.balance + amount;
s_expectedLinkBalance = s_expectedLinkBalance + amount;
i_link.transferFrom(msg.sender, address(this), amount);
emit FundsAdded(id, msg.sender, amount);
}
/**
* @notice migrates upkeeps from one registry to another
* @param ids the upkeepIDs to migrate
* @param destination the destination registry address
* @dev a transcoder must be set in order to enable migration
* @dev migration permissions must be set on *both* sending and receiving registries
* @dev only an upkeep admin can migrate their upkeeps
*/
function migrateUpkeeps(uint256[] calldata ids, address destination) external {
if (
s_peerRegistryMigrationPermission[destination] != MigrationPermission.OUTGOING &&
s_peerRegistryMigrationPermission[destination] != MigrationPermission.BIDIRECTIONAL
) revert MigrationNotPermitted();
if (s_storage.transcoder == ZERO_ADDRESS) revert TranscoderNotSet();
if (ids.length == 0) revert ArrayHasNoEntries();
uint256 id;
Upkeep memory upkeep;
uint256 totalBalanceRemaining;
address[] memory admins = new address[](ids.length);
Upkeep[] memory upkeeps = new Upkeep[](ids.length);
bytes[] memory checkDatas = new bytes[](ids.length);
bytes[] memory triggerConfigs = new bytes[](ids.length);
bytes[] memory offchainConfigs = new bytes[](ids.length);
for (uint256 idx = 0; idx < ids.length; idx++) {
id = ids[idx];
upkeep = s_upkeep[id];
_requireAdminAndNotCancelled(id);
upkeep.forwarder.updateRegistry(destination);
upkeeps[idx] = upkeep;
admins[idx] = s_upkeepAdmin[id];
checkDatas[idx] = s_checkData[id];
triggerConfigs[idx] = s_upkeepTriggerConfig[id];
offchainConfigs[idx] = s_upkeepOffchainConfig[id];
totalBalanceRemaining = totalBalanceRemaining + upkeep.balance;
delete s_upkeep[id];
delete s_checkData[id];
delete s_upkeepTriggerConfig[id];
delete s_upkeepOffchainConfig[id];
// nullify existing proposed admin change if an upkeep is being migrated
delete s_proposedAdmin[id];
s_upkeepIDs.remove(id);
emit UpkeepMigrated(id, upkeep.balance, destination);
}
s_expectedLinkBalance = s_expectedLinkBalance - totalBalanceRemaining;
bytes memory encodedUpkeeps = abi.encode(
ids,
upkeeps,
new address[](ids.length),
admins,
checkDatas,
triggerConfigs,
offchainConfigs
);
MigratableKeeperRegistryInterfaceV2(destination).receiveUpkeeps(
UpkeepTranscoderInterfaceV2(s_storage.transcoder).transcodeUpkeeps(
UPKEEP_VERSION_BASE,
MigratableKeeperRegistryInterfaceV2(destination).upkeepVersion(),
encodedUpkeeps
)
);
i_link.transfer(destination, totalBalanceRemaining);
}
/**
* @notice received upkeeps migrated from another registry
* @param encodedUpkeeps the raw upkeep data to import
* @dev this function is never called directly, it is only called by another registry's migrate function
*/
function receiveUpkeeps(bytes calldata encodedUpkeeps) external {
if (
s_peerRegistryMigrationPermission[msg.sender] != MigrationPermission.INCOMING &&
s_peerRegistryMigrationPermission[msg.sender] != MigrationPermission.BIDIRECTIONAL
) revert MigrationNotPermitted();
(
uint256[] memory ids,
Upkeep[] memory upkeeps,
address[] memory targets,
address[] memory upkeepAdmins,
bytes[] memory checkDatas,
bytes[] memory triggerConfigs,
bytes[] memory offchainConfigs
) = abi.decode(encodedUpkeeps, (uint256[], Upkeep[], address[], address[], bytes[], bytes[], bytes[]));
for (uint256 idx = 0; idx < ids.length; idx++) {
if (address(upkeeps[idx].forwarder) == ZERO_ADDRESS) {
upkeeps[idx].forwarder = IAutomationForwarder(
address(new AutomationForwarder(targets[idx], address(this), i_automationForwarderLogic))
);
}
_createUpkeep(
ids[idx],
upkeeps[idx],
upkeepAdmins[idx],
checkDatas[idx],
triggerConfigs[idx],
offchainConfigs[idx]
);
emit UpkeepReceived(ids[idx], upkeeps[idx].balance, msg.sender);
}
}
/**
* @notice sets the upkeep trigger config
* @param id the upkeepID to change the trigger for
* @param triggerConfig the new trigger config
*/
function setUpkeepTriggerConfig(uint256 id, bytes calldata triggerConfig) external {
_requireAdminAndNotCancelled(id);
s_upkeepTriggerConfig[id] = triggerConfig;
emit UpkeepTriggerConfigSet(id, triggerConfig);
}
}
// SPDX-License-Identifier: MIT
pragma solidity 0.8.16;
import {KeeperRegistryBase2_1} from "./KeeperRegistryBase2_1.sol";
import {EnumerableSet} from "../../../vendor/openzeppelin-solidity/v4.7.3/contracts/utils/structs/EnumerableSet.sol";
import {Address} from "../../../vendor/openzeppelin-solidity/v4.7.3/contracts/utils/Address.sol";
import {UpkeepFormat} from "../../../automation/interfaces/UpkeepTranscoderInterface.sol";
import {IAutomationForwarder} from "./interfaces/IAutomationForwarder.sol";
contract KeeperRegistryLogicB2_1 is KeeperRegistryBase2_1 {
using Address for address;
using EnumerableSet for EnumerableSet.UintSet;
using EnumerableSet for EnumerableSet.AddressSet;
/**
* @dev see KeeperRegistry master contract for constructor description
*/
constructor(
Mode mode,
address link,
address linkNativeFeed,
address fastGasFeed,
address automationForwarderLogic
) KeeperRegistryBase2_1(mode, link, linkNativeFeed, fastGasFeed, automationForwarderLogic) {}
// ================================================================
// | UPKEEP MANAGEMENT |
// ================================================================
/**
* @notice transfers the address of an admin for an upkeep
*/
function transferUpkeepAdmin(uint256 id, address proposed) external {
_requireAdminAndNotCancelled(id);
if (proposed == msg.sender) revert ValueNotChanged();
if (s_proposedAdmin[id] != proposed) {
s_proposedAdmin[id] = proposed;
emit UpkeepAdminTransferRequested(id, msg.sender, proposed);
}
}
/**
* @notice accepts the transfer of an upkeep admin
*/
function acceptUpkeepAdmin(uint256 id) external {
Upkeep memory upkeep = s_upkeep[id];
if (upkeep.maxValidBlocknumber != UINT32_MAX) revert UpkeepCancelled();
if (s_proposedAdmin[id] != msg.sender) revert OnlyCallableByProposedAdmin();
address past = s_upkeepAdmin[id];
s_upkeepAdmin[id] = msg.sender;
s_proposedAdmin[id] = ZERO_ADDRESS;
emit UpkeepAdminTransferred(id, past, msg.sender);
}
/**
* @notice pauses an upkeep - an upkeep will be neither checked nor performed while paused
*/
function pauseUpkeep(uint256 id) external {
_requireAdminAndNotCancelled(id);
Upkeep memory upkeep = s_upkeep[id];
if (upkeep.paused) revert OnlyUnpausedUpkeep();
s_upkeep[id].paused = true;
s_upkeepIDs.remove(id);
emit UpkeepPaused(id);
}
/**
* @notice unpauses an upkeep
*/
function unpauseUpkeep(uint256 id) external {
_requireAdminAndNotCancelled(id);
Upkeep memory upkeep = s_upkeep[id];
if (!upkeep.paused) revert OnlyPausedUpkeep();
s_upkeep[id].paused = false;
s_upkeepIDs.add(id);
emit UpkeepUnpaused(id);
}
/**
* @notice updates the checkData for an upkeep
*/
function setUpkeepCheckData(uint256 id, bytes calldata newCheckData) external {
_requireAdminAndNotCancelled(id);
if (newCheckData.length > s_storage.maxCheckDataSize) revert CheckDataExceedsLimit();
s_checkData[id] = newCheckData;
emit UpkeepCheckDataSet(id, newCheckData);
}
/**
* @notice updates the gas limit for an upkeep
*/
function setUpkeepGasLimit(uint256 id, uint32 gasLimit) external {
if (gasLimit < PERFORM_GAS_MIN || gasLimit > s_storage.maxPerformGas) revert GasLimitOutsideRange();
_requireAdminAndNotCancelled(id);
s_upkeep[id].performGas = gasLimit;
emit UpkeepGasLimitSet(id, gasLimit);
}
/**
* @notice updates the offchain config for an upkeep
*/
function setUpkeepOffchainConfig(uint256 id, bytes calldata config) external {
_requireAdminAndNotCancelled(id);
s_upkeepOffchainConfig[id] = config;
emit UpkeepOffchainConfigSet(id, config);
}
/**
* @notice withdraws LINK funds from an upkeep
* @dev note that an upkeep must be cancelled first!!
*/
function withdrawFunds(uint256 id, address to) external nonReentrant {
if (to == ZERO_ADDRESS) revert InvalidRecipient();
Upkeep memory upkeep = s_upkeep[id];
if (s_upkeepAdmin[id] != msg.sender) revert OnlyCallableByAdmin();
if (upkeep.maxValidBlocknumber > _blockNum()) revert UpkeepNotCanceled();
uint96 amountToWithdraw = s_upkeep[id].balance;
s_expectedLinkBalance = s_expectedLinkBalance - amountToWithdraw;
s_upkeep[id].balance = 0;
i_link.transfer(to, amountToWithdraw);
emit FundsWithdrawn(id, amountToWithdraw, to);
}
// ================================================================
// | NODE MANAGEMENT |
// ================================================================
/**
* @notice transfers the address of payee for a transmitter
*/
function transferPayeeship(address transmitter, address proposed) external {
if (s_transmitterPayees[transmitter] != msg.sender) revert OnlyCallableByPayee();
if (proposed == msg.sender) revert ValueNotChanged();
if (s_proposedPayee[transmitter] != proposed) {
s_proposedPayee[transmitter] = proposed;
emit PayeeshipTransferRequested(transmitter, msg.sender, proposed);
}
}
/**
* @notice accepts the transfer of the payee
*/
function acceptPayeeship(address transmitter) external {
if (s_proposedPayee[transmitter] != msg.sender) revert OnlyCallableByProposedPayee();
address past = s_transmitterPayees[transmitter];
s_transmitterPayees[transmitter] = msg.sender;
s_proposedPayee[transmitter] = ZERO_ADDRESS;
emit PayeeshipTransferred(transmitter, past, msg.sender);
}
/**
* @notice withdraws LINK received as payment for work performed
*/
function withdrawPayment(address from, address to) external {
if (to == ZERO_ADDRESS) revert InvalidRecipient();
if (s_transmitterPayees[from] != msg.sender) revert OnlyCallableByPayee();
uint96 balance = _updateTransmitterBalanceFromPool(from, s_hotVars.totalPremium, uint96(s_transmittersList.length));
s_transmitters[from].balance = 0;
s_expectedLinkBalance = s_expectedLinkBalance - balance;
i_link.transfer(to, balance);
emit PaymentWithdrawn(from, balance, to, msg.sender);
}
// ================================================================
// | OWNER / MANAGER ACTIONS |
// ================================================================
/**
* @notice sets the privilege config for an upkeep
*/
function setUpkeepPrivilegeConfig(uint256 upkeepId, bytes calldata newPrivilegeConfig) external {
if (msg.sender != s_storage.upkeepPrivilegeManager) {
revert OnlyCallableByUpkeepPrivilegeManager();
}
s_upkeepPrivilegeConfig[upkeepId] = newPrivilegeConfig;
emit UpkeepPrivilegeConfigSet(upkeepId, newPrivilegeConfig);
}
/**
* @notice withdraws the owner's LINK balance
*/
function withdrawOwnerFunds() external onlyOwner {
uint96 amount = s_storage.ownerLinkBalance;
s_expectedLinkBalance = s_expectedLinkBalance - amount;
s_storage.ownerLinkBalance = 0;
emit OwnerFundsWithdrawn(amount);
i_link.transfer(msg.sender, amount);
}
/**
* @notice allows the owner to withdraw any LINK accidentally sent to the contract
*/
function recoverFunds() external onlyOwner {
uint256 total = i_link.balanceOf(address(this));
i_link.transfer(msg.sender, total - s_expectedLinkBalance);
}
/**
* @notice sets the payees for the transmitters
*/
function setPayees(address[] calldata payees) external onlyOwner {
if (s_transmittersList.length != payees.length) revert ParameterLengthError();
for (uint256 i = 0; i < s_transmittersList.length; i++) {
address transmitter = s_transmittersList[i];
address oldPayee = s_transmitterPayees[transmitter];
address newPayee = payees[i];
if (
(newPayee == ZERO_ADDRESS) || (oldPayee != ZERO_ADDRESS && oldPayee != newPayee && newPayee != IGNORE_ADDRESS)
) revert InvalidPayee();
if (newPayee != IGNORE_ADDRESS) {
s_transmitterPayees[transmitter] = newPayee;
}
}
emit PayeesUpdated(s_transmittersList, payees);
}
/**
* @notice sets the migration permission for a peer registry
* @dev this must be done before upkeeps can be migrated to/from another registry
*/
function setPeerRegistryMigrationPermission(address peer, MigrationPermission permission) external onlyOwner {
s_peerRegistryMigrationPermission[peer] = permission;
}
/**
* @notice pauses the entire registry
*/
function pause() external onlyOwner {
s_hotVars.paused = true;
emit Paused(msg.sender);
}
/**
* @notice unpauses the entire registry
*/
function unpause() external onlyOwner {
s_hotVars.paused = false;
emit Unpaused(msg.sender);
}
/**
* @notice sets a generic bytes field used to indicate the privilege that this admin address had
* @param admin the address to set privilege for
* @param newPrivilegeConfig the privileges that this admin has
*/
function setAdminPrivilegeConfig(address admin, bytes calldata newPrivilegeConfig) external {
if (msg.sender != s_storage.upkeepPrivilegeManager) {
revert OnlyCallableByUpkeepPrivilegeManager();
}
s_adminPrivilegeConfig[admin] = newPrivilegeConfig;
emit AdminPrivilegeConfigSet(admin, newPrivilegeConfig);
}
// ================================================================
// | GETTERS |
// ================================================================
function getConditionalGasOverhead() external pure returns (uint256) {
return REGISTRY_CONDITIONAL_OVERHEAD;
}
function getLogGasOverhead() external pure returns (uint256) {
return REGISTRY_LOG_OVERHEAD;
}
function getPerPerformByteGasOverhead() external pure returns (uint256) {
return REGISTRY_PER_PERFORM_BYTE_GAS_OVERHEAD;
}
function getPerSignerGasOverhead() external pure returns (uint256) {
return REGISTRY_PER_SIGNER_GAS_OVERHEAD;
}
function getCancellationDelay() external pure returns (uint256) {
return CANCELLATION_DELAY;
}
function getMode() external view returns (Mode) {
return i_mode;
}
function getLinkAddress() external view returns (address) {
return address(i_link);
}
function getLinkNativeFeedAddress() external view returns (address) {
return address(i_linkNativeFeed);
}
function getFastGasFeedAddress() external view returns (address) {
return address(i_fastGasFeed);
}
function getAutomationForwarderLogic() external view returns (address) {
return i_automationForwarderLogic;
}
function upkeepTranscoderVersion() public pure returns (UpkeepFormat) {
return UPKEEP_TRANSCODER_VERSION_BASE;
}
function upkeepVersion() public pure returns (uint8) {
return UPKEEP_VERSION_BASE;
}
/**
* @notice read all of the details about an upkeep
* @dev this function may be deprecated in a future version of automation in favor of individual
* getters for each field
*/
function getUpkeep(uint256 id) external view returns (UpkeepInfo memory upkeepInfo) {
Upkeep memory reg = s_upkeep[id];
address target = address(reg.forwarder) == address(0) ? address(0) : reg.forwarder.getTarget();
upkeepInfo = UpkeepInfo({
target: target,
performGas: reg.performGas,
checkData: s_checkData[id],
balance: reg.balance,
admin: s_upkeepAdmin[id],
maxValidBlocknumber: reg.maxValidBlocknumber,
lastPerformedBlockNumber: reg.lastPerformedBlockNumber,
amountSpent: reg.amountSpent,
paused: reg.paused,
offchainConfig: s_upkeepOffchainConfig[id]
});
return upkeepInfo;
}
/**
* @notice retrieve active upkeep IDs. Active upkeep is defined as an upkeep which is not paused and not canceled.
* @param startIndex starting index in list
* @param maxCount max count to retrieve (0 = unlimited)
* @dev the order of IDs in the list is **not guaranteed**, therefore, if making successive calls, one
* should consider keeping the blockheight constant to ensure a holistic picture of the contract state
*/
function getActiveUpkeepIDs(uint256 startIndex, uint256 maxCount) external view returns (uint256[] memory) {
uint256 numUpkeeps = s_upkeepIDs.length();
if (startIndex >= numUpkeeps) revert IndexOutOfRange();
uint256 endIndex = startIndex + maxCount;
endIndex = endIndex > numUpkeeps || maxCount == 0 ? numUpkeeps : endIndex;
uint256[] memory ids = new uint256[](endIndex - startIndex);
for (uint256 idx = 0; idx < ids.length; idx++) {
ids[idx] = s_upkeepIDs.at(idx + startIndex);
}
return ids;
}
/**
* @notice returns the upkeep's trigger type
*/
function getTriggerType(uint256 upkeepId) external pure returns (Trigger) {
return _getTriggerType(upkeepId);
}
/**
* @notice returns the trigger config for an upkeeep
*/
function getUpkeepTriggerConfig(uint256 upkeepId) public view returns (bytes memory) {
return s_upkeepTriggerConfig[upkeepId];
}
/**
* @notice read the current info about any transmitter address
*/
function getTransmitterInfo(
address query
) external view returns (bool active, uint8 index, uint96 balance, uint96 lastCollected, address payee) {
Transmitter memory transmitter = s_transmitters[query];
uint96 pooledShare = 0;
if (transmitter.active) {
uint96 totalDifference = s_hotVars.totalPremium - transmitter.lastCollected;
pooledShare = totalDifference / uint96(s_transmittersList.length);
}
return (
transmitter.active,
transmitter.index,
(transmitter.balance + pooledShare),
transmitter.lastCollected,
s_transmitterPayees[query]
);
}
/**
* @notice read the current info about any signer address
*/
function getSignerInfo(address query) external view returns (bool active, uint8 index) {
Signer memory signer = s_signers[query];
return (signer.active, signer.index);
}
/**
* @notice read the current state of the registry
*/
function getState()
external
view
returns (
State memory state,
OnchainConfig memory config,
address[] memory signers,
address[] memory transmitters,
uint8 f
)
{
state = State({
nonce: s_storage.nonce,
ownerLinkBalance: s_storage.ownerLinkBalance,
expectedLinkBalance: s_expectedLinkBalance,
totalPremium: s_hotVars.totalPremium,
numUpkeeps: s_upkeepIDs.length(),
configCount: s_storage.configCount,
latestConfigBlockNumber: s_storage.latestConfigBlockNumber,
latestConfigDigest: s_latestConfigDigest,
latestEpoch: s_hotVars.latestEpoch,
paused: s_hotVars.paused
});
config = OnchainConfig({
paymentPremiumPPB: s_hotVars.paymentPremiumPPB,
flatFeeMicroLink: s_hotVars.flatFeeMicroLink,
checkGasLimit: s_storage.checkGasLimit,
stalenessSeconds: s_hotVars.stalenessSeconds,
gasCeilingMultiplier: s_hotVars.gasCeilingMultiplier,
minUpkeepSpend: s_storage.minUpkeepSpend,
maxPerformGas: s_storage.maxPerformGas,
maxCheckDataSize: s_storage.maxCheckDataSize,
maxPerformDataSize: s_storage.maxPerformDataSize,
maxRevertDataSize: s_storage.maxRevertDataSize,
fallbackGasPrice: s_fallbackGasPrice,
fallbackLinkPrice: s_fallbackLinkPrice,
transcoder: s_storage.transcoder,
registrars: s_registrars.values(),
upkeepPrivilegeManager: s_storage.upkeepPrivilegeManager
});
return (state, config, s_signersList, s_transmittersList, s_hotVars.f);
}
/**
* @notice calculates the minimum balance required for an upkeep to remain eligible
* @param id the upkeep id to calculate minimum balance for
*/
function getBalance(uint256 id) external view returns (uint96 balance) {
return s_upkeep[id].balance;
}
/**
* @notice calculates the minimum balance required for an upkeep to remain eligible
* @param id the upkeep id to calculate minimum balance for
*/
function getMinBalance(uint256 id) external view returns (uint96) {
return getMinBalanceForUpkeep(id);
}
/**
* @notice calculates the minimum balance required for an upkeep to remain eligible
* @param id the upkeep id to calculate minimum balance for
* @dev this will be deprecated in a future version in favor of getMinBalance
*/
function getMinBalanceForUpkeep(uint256 id) public view returns (uint96 minBalance) {
return getMaxPaymentForGas(_getTriggerType(id), s_upkeep[id].performGas);
}
/**
* @notice calculates the maximum payment for a given gas limit
* @param gasLimit the gas to calculate payment for
*/
function getMaxPaymentForGas(Trigger triggerType, uint32 gasLimit) public view returns (uint96 maxPayment) {
HotVars memory hotVars = s_hotVars;
(uint256 fastGasWei, uint256 linkNative) = _getFeedData(hotVars);
return
_getMaxLinkPayment(hotVars, triggerType, gasLimit, s_storage.maxPerformDataSize, fastGasWei, linkNative, false);
}
/**
* @notice retrieves the migration permission for a peer registry
*/
function getPeerRegistryMigrationPermission(address peer) external view returns (MigrationPermission) {
return s_peerRegistryMigrationPermission[peer];
}
/**
* @notice returns the upkeep privilege config
*/
function getUpkeepPrivilegeConfig(uint256 upkeepId) external view returns (bytes memory) {
return s_upkeepPrivilegeConfig[upkeepId];
}
/**
* @notice returns the upkeep privilege config
*/
function getAdminPrivilegeConfig(address admin) external view returns (bytes memory) {
return s_adminPrivilegeConfig[admin];
}
/**
* @notice returns the upkeep's forwarder contract
*/
function getForwarder(uint256 upkeepID) external view returns (IAutomationForwarder) {
return s_upkeep[upkeepID].forwarder;
}
/**
* @notice returns the upkeep's forwarder contract
*/
function hasDedupKey(bytes32 dedupKey) external view returns (bool) {
return s_dedupKeys[dedupKey];
}
}
// SPDX-License-Identifier: MIT
pragma solidity 0.8.16;
import "../interfaces/IAutomationRegistryConsumer.sol";
contract MockKeeperRegistry2_1 is IAutomationRegistryConsumer {
uint96 balance;
uint96 minBalance;
constructor() {}
function getBalance(uint256 id) external view override returns (uint96) {
return balance;
}
function getMinBalance(uint256 id) external view override returns (uint96) {
return minBalance;
}
function cancelUpkeep(uint256 id) external override {}
function pauseUpkeep(uint256 id) external override {}
function unpauseUpkeep(uint256 id) external override {}
function updateCheckData(uint256 id, bytes calldata newCheckData) external {}
function addFunds(uint256 id, uint96 amount) external override {}
function withdrawFunds(uint256 id, address to) external override {}
}
// SPDX-License-Identifier: MIT
pragma solidity 0.8.16;
contract UpkeepCounter {
event PerformingUpkeep(
address indexed from,
uint256 initialBlock,
uint256 lastBlock,
uint256 previousBlock,
uint256 counter
);
uint256 public testRange;
uint256 public interval;
uint256 public lastBlock;
uint256 public previousPerformBlock;
uint256 public initialBlock;
uint256 public counter;
constructor(uint256 _testRange, uint256 _interval) {
testRange = _testRange;
interval = _interval;
previousPerformBlock = 0;
lastBlock = block.number;
initialBlock = 0;
counter = 0;
}
function checkUpkeep(bytes calldata data) external view returns (bool, bytes memory) {
return (eligible(), data);
}
function performUpkeep(bytes calldata performData) external {
if (initialBlock == 0) {
initialBlock = block.number;
}
lastBlock = block.number;
counter = counter + 1;
performData;
emit PerformingUpkeep(tx.origin, initialBlock, lastBlock, previousPerformBlock, counter);
previousPerformBlock = lastBlock;
}
function eligible() public view returns (bool) {
if (initialBlock == 0) {
return true;
}
return (block.number - initialBlock) < testRange && (block.number - lastBlock) >= interval;
}
function setSpread(uint256 _testRange, uint256 _interval) external {
testRange = _testRange;
interval = _interval;
initialBlock = 0;
counter = 0;
}
}
// SPDX-License-Identifier: MIT
pragma solidity 0.8.16;
contract StructFactory {
address internal OWNER;
address internal constant STRANGER = address(999);
}
// SPDX-License-Identifier: MIT
pragma solidity 0.8.16;
import {UpkeepTranscoderInterfaceV2} from "../../../automation/interfaces/UpkeepTranscoderInterfaceV2.sol";
import {TypeAndVersionInterface} from "../../../interfaces/TypeAndVersionInterface.sol";
import {KeeperRegistryBase2_1 as R21} from "./KeeperRegistryBase2_1.sol";
import {IAutomationForwarder} from "./interfaces/IAutomationForwarder.sol";
import {AutomationRegistryBaseInterface, UpkeepInfo} from "../../../automation/interfaces/2_0/AutomationRegistryInterface2_0.sol";
enum RegistryVersion {
V12,
V13,
V20,
V21
}
/**
* @dev structs copied directly from source (can't import without changing the contract version)
*/
struct UpkeepV12 {
uint96 balance;
address lastKeeper;
uint32 executeGas;
uint64 maxValidBlocknumber;
address target;
uint96 amountSpent;
address admin;
}
struct UpkeepV13 {
uint96 balance;
address lastKeeper;
uint96 amountSpent;
address admin;
uint32 executeGas;
uint32 maxValidBlocknumber;
address target;
bool paused;
}
struct UpkeepV20 {
uint32 executeGas;
uint32 maxValidBlocknumber;
bool paused;
address target;
uint96 amountSpent;
uint96 balance;
uint32 lastPerformedBlockNumber;
}
/**
* @notice UpkeepTranscoder allows converting upkeep data from previous keeper registry versions 1.2, 1.3, and
* 2.0 to registry 2.1
*/
contract UpkeepTranscoder4_0 is UpkeepTranscoderInterfaceV2, TypeAndVersionInterface {
error InvalidTranscoding();
/**
* @notice versions:
* - UpkeepTranscoder 4.0.0: adds support for registry 2.1; adds support for offchainConfigs
* - UpkeepTranscoder 3.0.0: works with registry 2.0; adds temporary workaround for UpkeepFormat enum bug
*/
string public constant override typeAndVersion = "UpkeepTranscoder 4.0.0";
uint32 internal constant UINT32_MAX = type(uint32).max;
IAutomationForwarder internal constant ZERO_FORWARDER = IAutomationForwarder(address(0));
/**
* @notice transcodeUpkeeps transforms upkeep data from the format expected by
* one registry to the format expected by another. It future-proofs migrations
* by allowing keepers team to customize migration paths and set sensible defaults
* when new fields are added
* @param fromVersion struct version the upkeep is migrating from
* @param encodedUpkeeps encoded upkeep data
* @dev this transcoder should ONLY be use for V1/V2 --> V3 migrations
* @dev this transcoder **ignores** the toVersion param, as it assumes all migrations are
* for the V3 version. Therefore, it is the responsibility of the deployer of this contract
* to ensure it is not used in any other migration paths.
*/
function transcodeUpkeeps(
uint8 fromVersion,
uint8,
bytes calldata encodedUpkeeps
) external view override returns (bytes memory) {
// v1.2 => v2.1
if (fromVersion == uint8(RegistryVersion.V12)) {
(uint256[] memory ids, UpkeepV12[] memory upkeepsV12, bytes[] memory checkDatas) = abi.decode(
encodedUpkeeps,
(uint256[], UpkeepV12[], bytes[])
);
if (ids.length != upkeepsV12.length || ids.length != checkDatas.length) {
revert InvalidTranscoding();
}
address[] memory targets = new address[](ids.length);
address[] memory admins = new address[](ids.length);
R21.Upkeep[] memory newUpkeeps = new R21.Upkeep[](ids.length);
UpkeepV12 memory upkeepV12;
for (uint256 idx = 0; idx < ids.length; idx++) {
upkeepV12 = upkeepsV12[idx];
newUpkeeps[idx] = R21.Upkeep({
performGas: upkeepV12.executeGas,
maxValidBlocknumber: UINT32_MAX, // maxValidBlocknumber is uint64 in V1, hence a new default value is provided
paused: false, // migrated upkeeps are not paused by default
forwarder: ZERO_FORWARDER,
amountSpent: upkeepV12.amountSpent,
balance: upkeepV12.balance,
lastPerformedBlockNumber: 0
});
targets[idx] = upkeepV12.target;
admins[idx] = upkeepV12.admin;
}
return abi.encode(ids, newUpkeeps, targets, admins, checkDatas, new bytes[](ids.length), new bytes[](ids.length));
}
// v1.3 => v2.1
if (fromVersion == uint8(RegistryVersion.V13)) {
(uint256[] memory ids, UpkeepV13[] memory upkeepsV13, bytes[] memory checkDatas) = abi.decode(
encodedUpkeeps,
(uint256[], UpkeepV13[], bytes[])
);
if (ids.length != upkeepsV13.length || ids.length != checkDatas.length) {
revert InvalidTranscoding();
}
address[] memory targets = new address[](ids.length);
address[] memory admins = new address[](ids.length);
R21.Upkeep[] memory newUpkeeps = new R21.Upkeep[](ids.length);
UpkeepV13 memory upkeepV13;
for (uint256 idx = 0; idx < ids.length; idx++) {
upkeepV13 = upkeepsV13[idx];
newUpkeeps[idx] = R21.Upkeep({
performGas: upkeepV13.executeGas,
maxValidBlocknumber: upkeepV13.maxValidBlocknumber,
paused: upkeepV13.paused,
forwarder: ZERO_FORWARDER,
amountSpent: upkeepV13.amountSpent,
balance: upkeepV13.balance,
lastPerformedBlockNumber: 0
});
targets[idx] = upkeepV13.target;
admins[idx] = upkeepV13.admin;
}
return abi.encode(ids, newUpkeeps, targets, admins, checkDatas, new bytes[](ids.length), new bytes[](ids.length));
}
// v2.0 => v2.1
if (fromVersion == uint8(RegistryVersion.V20)) {
(uint256[] memory ids, UpkeepV20[] memory upkeepsV20, bytes[] memory checkDatas, address[] memory admins) = abi
.decode(encodedUpkeeps, (uint256[], UpkeepV20[], bytes[], address[]));
if (ids.length != upkeepsV20.length || ids.length != checkDatas.length) {
revert InvalidTranscoding();
}
// bit of a hack - transcodeUpkeeps should be a pure function
R21.Upkeep[] memory newUpkeeps = new R21.Upkeep[](ids.length);
bytes[] memory emptyBytes = new bytes[](ids.length);
address[] memory targets = new address[](ids.length);
UpkeepV20 memory upkeepV20;
for (uint256 idx = 0; idx < ids.length; idx++) {
upkeepV20 = upkeepsV20[idx];
newUpkeeps[idx] = R21.Upkeep({
performGas: upkeepV20.executeGas,
maxValidBlocknumber: upkeepV20.maxValidBlocknumber,
paused: upkeepV20.paused,
forwarder: ZERO_FORWARDER,
amountSpent: upkeepV20.amountSpent,
balance: upkeepV20.balance,
lastPerformedBlockNumber: 0
});
targets[idx] = upkeepV20.target;
}
return abi.encode(ids, newUpkeeps, targets, admins, checkDatas, emptyBytes, emptyBytes);
}
// v2.1 => v2.1
if (fromVersion == uint8(RegistryVersion.V21)) {
return encodedUpkeeps;
}
revert InvalidTranscoding();
}
}
// SPDX-License-Identifier: MIT
pragma solidity 0.8.16;
// this struct is the same as LogTriggerConfig defined in KeeperRegistryLogicA2_1 contract
struct LogTriggerConfig {
address contractAddress;
uint8 filterSelector; // denotes which topics apply to filter ex 000, 101, 111...only last 3 bits apply
bytes32 topic0;
bytes32 topic1;
bytes32 topic2;
bytes32 topic3;
}
contract DummyProtocol {
event LimitOrderSent(uint256 indexed amount, uint256 indexed price, address indexed to); // keccak256(LimitOrderSent(uint256,uint256,address)) => 0x3e9c37b3143f2eb7e9a2a0f8091b6de097b62efcfe48e1f68847a832e521750a
event LimitOrderWithdrawn(uint256 indexed amount, uint256 indexed price, address indexed from); // keccak256(LimitOrderWithdrawn(uint256,uint256,address)) => 0x0a71b8ed921ff64d49e4d39449f8a21094f38a0aeae489c3051aedd63f2c229f
event LimitOrderExecuted(uint256 indexed orderId, uint256 indexed amount, address indexed exchange); // keccak(LimitOrderExecuted(uint256,uint256,address)) => 0xd1ffe9e45581c11d7d9f2ed5f75217cd4be9f8b7eee6af0f6d03f46de53956cd
function sendLimitedOrder(uint256 amount, uint256 price, address to) public {
// send an order to an exchange
emit LimitOrderSent(amount, price, to);
}
function withdrawLimit(uint256 amount, uint256 price, address from) public {
// withdraw an order from an exchange
emit LimitOrderSent(amount, price, from);
}
function executeLimitOrder(uint256 orderId, uint256 amount, address exchange) public {
// execute a limit order
emit LimitOrderExecuted(orderId, amount, exchange);
}
/**
* @notice this function generates bytes for a basic log trigger config with no filter selector.
* @param targetContract the address of contract where events will be emitted from
* @param t0 the signature of the event to listen to
*/
function getBasicLogTriggerConfig(
address targetContract,
bytes32 t0
) external view returns (bytes memory logTrigger) {
LogTriggerConfig memory cfg = LogTriggerConfig({
contractAddress: targetContract,
filterSelector: 0,
topic0: t0,
topic1: 0x000000000000000000000000000000000000000000000000000000000000000,
topic2: 0x000000000000000000000000000000000000000000000000000000000000000,
topic3: 0x000000000000000000000000000000000000000000000000000000000000000
});
return abi.encode(cfg);
}
/**
* @notice this function generates bytes for a customizable log trigger config.
* @param targetContract the address of contract where events will be emitted from
* @param selector the filter selector. this denotes which topics apply to filter ex 000, 101, 111....only last 3 bits apply
* if 0, it won't filter based on topic 1, 2, 3.
* if 1, it will filter based on topic 1,
* if 2, it will filter based on topic 2,
* if 3, it will filter based on topic 1 and topic 2,
* if 4, it will filter based on topic 3,
* if 5, it will filter based on topic 1 and topic 3....
* @param t0 the signature of the event to listen to.
* @param t1 the topic 1 of the event.
* @param t2 the topic 2 of the event.
* @param t3 the topic 2 of the event.
*/
function getAdvancedLogTriggerConfig(
address targetContract,
uint8 selector,
bytes32 t0,
bytes32 t1,
bytes32 t2,
bytes32 t3
) external view returns (bytes memory logTrigger) {
LogTriggerConfig memory cfg = LogTriggerConfig({
contractAddress: targetContract,
filterSelector: selector,
topic0: t0,
topic1: t1,
topic2: t2,
topic3: t3
});
return abi.encode(cfg);
}
}
// SPDX-License-Identifier: MIT
pragma solidity 0.8.16;
import {ILogAutomation, Log} from "../2_1/interfaces/ILogAutomation.sol";
import "../2_1/interfaces/StreamsLookupCompatibleInterface.sol";
import {ArbSys} from "../../../vendor/@arbitrum/nitro-contracts/src/precompiles/ArbSys.sol";
interface IVerifierProxy {
/**
* @notice Verifies that the data encoded has been signed
* correctly by routing to the correct verifier.
* @param signedReport The encoded data to be verified.
* @return verifierResponse The encoded response from the verifier.
*/
function verify(bytes memory signedReport) external returns (bytes memory verifierResponse);
}
contract LogTriggeredStreamsLookup is ILogAutomation, StreamsLookupCompatibleInterface {
event PerformingLogTriggerUpkeep(
address indexed from,
uint256 orderId,
uint256 amount,
address exchange,
uint256 blockNumber,
bytes blob,
bytes verified
);
ArbSys internal constant ARB_SYS = ArbSys(0x0000000000000000000000000000000000000064);
IVerifierProxy internal constant VERIFIER = IVerifierProxy(0x09DFf56A4fF44e0f4436260A04F5CFa65636A481);
// for log trigger
bytes32 constant sentSig = 0x3e9c37b3143f2eb7e9a2a0f8091b6de097b62efcfe48e1f68847a832e521750a;
bytes32 constant withdrawnSig = 0x0a71b8ed921ff64d49e4d39449f8a21094f38a0aeae489c3051aedd63f2c229f;
bytes32 constant executedSig = 0xd1ffe9e45581c11d7d9f2ed5f75217cd4be9f8b7eee6af0f6d03f46de53956cd;
// for mercury config
bool public useArbitrumBlockNum;
bool public verify;
string[] public feedsHex = ["0x4554482d5553442d415242495452554d2d544553544e45540000000000000000"];
string public feedParamKey = "feedIdHex";
string public timeParamKey = "blockNumber";
constructor(bool _useArbitrumBlockNum, bool _verify) {
useArbitrumBlockNum = _useArbitrumBlockNum;
verify = _verify;
}
function setTimeParamKey(string memory timeParam) external {
timeParamKey = timeParam;
}
function setFeedParamKey(string memory feedParam) external {
feedParamKey = feedParam;
}
function setFeedsHex(string[] memory newFeeds) external {
feedsHex = newFeeds;
}
function checkLog(
Log calldata log,
bytes memory
) external override returns (bool upkeepNeeded, bytes memory performData) {
uint256 blockNum = getBlockNumber();
// filter by event signature
if (log.topics[0] == executedSig) {
// filter by indexed parameters
bytes memory t1 = abi.encodePacked(log.topics[1]); // bytes32 to bytes
uint256 orderId = abi.decode(t1, (uint256));
bytes memory t2 = abi.encodePacked(log.topics[2]);
uint256 amount = abi.decode(t2, (uint256));
bytes memory t3 = abi.encodePacked(log.topics[3]);
address exchange = abi.decode(t3, (address));
revert StreamsLookup(feedParamKey, feedsHex, timeParamKey, blockNum, abi.encode(orderId, amount, exchange));
}
revert("could not find matching event sig");
}
function performUpkeep(bytes calldata performData) external override {
(bytes[] memory values, bytes memory extraData) = abi.decode(performData, (bytes[], bytes));
(uint256 orderId, uint256 amount, address exchange) = abi.decode(extraData, (uint256, uint256, address));
bytes memory verifiedResponse = "";
if (verify) {
verifiedResponse = VERIFIER.verify(values[0]);
}
emit PerformingLogTriggerUpkeep(
tx.origin,
orderId,
amount,
exchange,
getBlockNumber(),
values[0],
verifiedResponse
);
}
function checkCallback(
bytes[] memory values,
bytes memory extraData
) external view override returns (bool, bytes memory) {
// do sth about the chainlinkBlob data in values and extraData
bytes memory performData = abi.encode(values, extraData);
return (true, performData);
}
function getBlockNumber() internal view returns (uint256) {
if (useArbitrumBlockNum) {
return ARB_SYS.arbBlockNumber();
} else {
return block.number;
}
}
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
interface AggregatorV3Interface {
function decimals() external view returns (uint8);
function description() external view returns (string memory);
function version() external view returns (uint256);
function getRoundData(
uint80 _roundId
) external view returns (uint80 roundId, int256 answer, uint256 startedAt, uint256 updatedAt, uint80 answeredInRound);
function latestRoundData()
external
view
returns (uint80 roundId, int256 answer, uint256 startedAt, uint256 updatedAt, uint80 answeredInRound);
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
abstract contract TypeAndVersionInterface {
function typeAndVersion() external pure virtual returns (string memory);
}
// SPDX-License-Identifier: MIT
pragma solidity 0.8.16;
/*
* @title ByteUtil
* @author Michael Fletcher
* @notice Byte utility functions for efficiently parsing and manipulating packed byte data
*/
library ByteUtil {
// Error message when an offset is out of bounds
error MalformedData();
/**
* @dev Reads a uint256 from a position within a byte array.
* @param data Byte array to read from.
* @param offset Position to start reading from.
* @return result The uint256 read from the byte array.
*/
function readUint256(bytes memory data, uint256 offset) internal pure returns (uint256 result) {
//bounds check
if (offset + 32 > data.length) revert MalformedData();
assembly {
//load 32 byte word accounting for 32 bit length and offset
result := mload(add(add(data, 32), offset))
}
}
/**
* @dev Reads a uint192 from a position within a byte array.
* @param data Byte array to read from.
* @param offset Position to start reading from.
* @return result The uint192 read from the byte array.
*/
function readUint192(bytes memory data, uint256 offset) internal pure returns (uint256 result) {
//bounds check
if (offset + 24 > data.length) revert MalformedData();
assembly {
//load 32 byte word accounting for 32 bit length and offset
result := mload(add(add(data, 32), offset))
//shift the result right 64 bits
result := shr(64, result)
}
}
/**
* @dev Reads a uint32 from a position within a byte array.
* @param data Byte array to read from.
* @param offset Position to start reading from.
* @return result The uint32 read from the byte array.
*/
function readUint32(bytes memory data, uint256 offset) internal pure returns (uint256 result) {
//bounds check
if (offset + 4 > data.length) revert MalformedData();
assembly {
//load 32 byte word accounting for 32 bit length and offset
result := mload(add(add(data, 32), offset))
//shift the result right 224 bits
result := shr(224, result)
}
}
/**
* @dev Reads an address from a position within a byte array.
* @param data Byte array to read from.
* @param offset Position to start reading from.
* @return result The uint32 read from the byte array.
*/
function readAddress(bytes memory data, uint256 offset) internal pure returns (address result) {
//bounds check
if (offset + 20 > data.length) revert MalformedData();
assembly {
//load 32 byte word accounting for 32 bit length and offset
let word := mload(add(add(data, 32), offset))
//address is the last 20 bytes of the word, so shift right
result := shr(96, word)
}
}
}
// SPDX-License-Identifier: MIT
pragma solidity 0.8.16;
/*
* @title Common
* @author Michael Fletcher
* @notice Common functions and structs
*/
library Common {
// @notice The asset struct to hold the address of an asset and amount
struct Asset {
address assetAddress;
uint256 amount;
}
// @notice Struct to hold the address and its associated weight
struct AddressAndWeight {
address addr;
uint64 weight;
}
/**
* @notice Checks if an array of AddressAndWeight has duplicate addresses
* @param recipients The array of AddressAndWeight to check
* @return bool True if there are duplicates, false otherwise
*/
function hasDuplicateAddresses(Common.AddressAndWeight[] memory recipients) internal pure returns (bool) {
for (uint256 i = 0; i < recipients.length; ) {
for (uint256 j = i + 1; j < recipients.length; ) {
if (recipients[i].addr == recipients[j].addr) {
return true;
}
unchecked {
++j;
}
}
unchecked {
++i;
}
}
return false;
}
}
// SPDX-License-Identifier: MIT
pragma solidity 0.8.16;
import {ConfirmedOwner} from "../../shared/access/ConfirmedOwner.sol";
import {IFeeManager} from "./interfaces/IFeeManager.sol";
import {TypeAndVersionInterface} from "../../interfaces/TypeAndVersionInterface.sol";
import {IERC165} from "../../vendor/openzeppelin-solidity/v4.8.0/contracts/interfaces/IERC165.sol";
import {Common} from "../../libraries/Common.sol";
import {IRewardManager} from "./interfaces/IRewardManager.sol";
import {IWERC20} from "../../shared/interfaces/IWERC20.sol";
import {IERC20} from "../../vendor/openzeppelin-solidity/v4.8.0/contracts/interfaces/IERC20.sol";
import {Math} from "../../vendor/openzeppelin-solidity/v4.8.0/contracts/utils/math/Math.sol";
import {SafeERC20} from "../../vendor/openzeppelin-solidity/v4.8.0/contracts/token/ERC20/utils/SafeERC20.sol";
import {IVerifierFeeManager} from "./interfaces/IVerifierFeeManager.sol";
/**
* @title FeeManager
* @author Michael Fletcher
* @author Austin Born
* @notice This contract is used for the handling of fees required for users verifying reports.
*/
contract FeeManager is IFeeManager, ConfirmedOwner, TypeAndVersionInterface {
using SafeERC20 for IERC20;
/// @notice list of subscribers and their discounts subscriberDiscounts[subscriber][feedId][token]
mapping(address => mapping(bytes32 => mapping(address => uint256))) public s_subscriberDiscounts;
/// @notice keep track of any subsidised link that is owed to the reward manager.
mapping(bytes32 => uint256) public s_linkDeficit;
/// @notice the total discount that can be applied to a fee, 1e18 = 100% discount
uint64 private constant PERCENTAGE_SCALAR = 1e18;
/// @notice the LINK token address
address public immutable i_linkAddress;
/// @notice the native token address
address public immutable i_nativeAddress;
/// @notice the proxy address
address public immutable i_proxyAddress;
/// @notice the reward manager address
IRewardManager public immutable i_rewardManager;
// @notice the mask to apply to get the report version
bytes32 private constant REPORT_VERSION_MASK = 0xffff000000000000000000000000000000000000000000000000000000000000;
// @notice the different report versions
bytes32 private constant REPORT_V1 = 0x0001000000000000000000000000000000000000000000000000000000000000;
/// @notice the surcharge fee to be paid if paying in native
uint256 public s_nativeSurcharge;
/// @notice the error thrown if the discount or surcharge is invalid
error InvalidSurcharge();
/// @notice the error thrown if the discount is invalid
error InvalidDiscount();
/// @notice the error thrown if the address is invalid
error InvalidAddress();
/// @notice thrown if msg.value is supplied with a bad quote
error InvalidDeposit();
/// @notice thrown if a report has expired
error ExpiredReport();
/// @notice thrown if a report has no quote
error InvalidQuote();
// @notice thrown when the caller is not authorized
error Unauthorized();
// @notice thrown when trying to clear a zero deficit
error ZeroDeficit();
/// @notice thrown when trying to pay an address that cannot except funds
error InvalidReceivingAddress();
/// @notice Emitted whenever a subscriber's discount is updated
/// @param subscriber address of the subscriber to update discounts for
/// @param feedId Feed ID for the discount
/// @param token Token address for the discount
/// @param discount Discount to apply, in relation to the PERCENTAGE_SCALAR
event SubscriberDiscountUpdated(address indexed subscriber, bytes32 indexed feedId, address token, uint64 discount);
/// @notice Emitted when updating the native surcharge
/// @param newSurcharge Surcharge amount to apply relative to PERCENTAGE_SCALAR
event NativeSurchargeUpdated(uint64 newSurcharge);
/// @notice Emits when this contract does not have enough LINK to send to the reward manager when paying in native
/// @param rewards Config digest and link fees which could not be subsidised
event InsufficientLink(IRewardManager.FeePayment[] rewards);
/// @notice Emitted when funds are withdrawn
/// @param adminAddress Address of the admin
/// @param recipient Address of the recipient
/// @param assetAddress Address of the asset withdrawn
/// @param quantity Amount of the asset withdrawn
event Withdraw(address adminAddress, address recipient, address assetAddress, uint192 quantity);
/// @notice Emits when a deficit has been cleared for a particular config digest
/// @param configDigest Config digest of the deficit cleared
/// @param linkQuantity Amount of LINK required to pay the deficit
event LinkDeficitCleared(bytes32 indexed configDigest, uint256 linkQuantity);
/// @notice Emits when a fee has been processed
/// @param configDigest Config digest of the fee processed
/// @param subscriber Address of the subscriber who paid the fee
/// @param fee Fee paid
/// @param reward Reward paid
/// @param appliedDiscount Discount applied to the fee
event DiscountApplied(
bytes32 indexed configDigest,
address indexed subscriber,
Common.Asset fee,
Common.Asset reward,
uint256 appliedDiscount
);
/**
* @notice Construct the FeeManager contract
* @param _linkAddress The address of the LINK token
* @param _nativeAddress The address of the wrapped ERC-20 version of the native token (represents fee in native or wrapped)
* @param _proxyAddress The address of the proxy contract
* @param _rewardManagerAddress The address of the reward manager contract
*/
constructor(
address _linkAddress,
address _nativeAddress,
address _proxyAddress,
address _rewardManagerAddress
) ConfirmedOwner(msg.sender) {
if (
_linkAddress == address(0) ||
_nativeAddress == address(0) ||
_proxyAddress == address(0) ||
_rewardManagerAddress == address(0)
) revert InvalidAddress();
i_linkAddress = _linkAddress;
i_nativeAddress = _nativeAddress;
i_proxyAddress = _proxyAddress;
i_rewardManager = IRewardManager(_rewardManagerAddress);
IERC20(i_linkAddress).approve(address(i_rewardManager), type(uint256).max);
}
modifier onlyOwnerOrProxy() {
if (msg.sender != i_proxyAddress && msg.sender != owner()) revert Unauthorized();
_;
}
modifier onlyProxy() {
if (msg.sender != i_proxyAddress) revert Unauthorized();
_;
}
/// @inheritdoc TypeAndVersionInterface
function typeAndVersion() external pure override returns (string memory) {
return "FeeManager 1.0.0";
}
/// @inheritdoc IERC165
function supportsInterface(bytes4 interfaceId) external pure override returns (bool) {
return interfaceId == this.processFee.selector || interfaceId == this.processFeeBulk.selector;
}
/// @inheritdoc IVerifierFeeManager
function processFee(bytes calldata payload, address subscriber) external payable override onlyProxy {
(Common.Asset memory fee, Common.Asset memory reward, uint256 appliedDiscount) = _processFee(payload, subscriber);
if (fee.amount == 0) {
_tryReturnChange(subscriber, msg.value);
return;
}
IFeeManager.FeeAndReward[] memory feeAndReward = new IFeeManager.FeeAndReward[](1);
feeAndReward[0] = IFeeManager.FeeAndReward(bytes32(payload), fee, reward, appliedDiscount);
if (fee.assetAddress == i_linkAddress) {
_handleFeesAndRewards(subscriber, feeAndReward, 1, 0);
} else {
_handleFeesAndRewards(subscriber, feeAndReward, 0, 1);
}
}
/// @inheritdoc IVerifierFeeManager
function processFeeBulk(bytes[] calldata payloads, address subscriber) external payable override onlyProxy {
FeeAndReward[] memory feesAndRewards = new IFeeManager.FeeAndReward[](payloads.length);
//keep track of the number of fees to prevent over initialising the FeePayment array within _convertToLinkAndNativeFees
uint256 numberOfLinkFees;
uint256 numberOfNativeFees;
uint256 feesAndRewardsIndex;
for (uint256 i; i < payloads.length; ++i) {
(Common.Asset memory fee, Common.Asset memory reward, uint256 appliedDiscount) = _processFee(
payloads[i],
subscriber
);
if (fee.amount != 0) {
feesAndRewards[feesAndRewardsIndex++] = IFeeManager.FeeAndReward(
bytes32(payloads[i]),
fee,
reward,
appliedDiscount
);
unchecked {
//keep track of some tallys to make downstream calculations more efficient
if (fee.assetAddress == i_linkAddress) {
++numberOfLinkFees;
} else {
++numberOfNativeFees;
}
}
}
}
if (numberOfLinkFees != 0 || numberOfNativeFees != 0) {
_handleFeesAndRewards(subscriber, feesAndRewards, numberOfLinkFees, numberOfNativeFees);
} else {
_tryReturnChange(subscriber, msg.value);
}
}
/// @inheritdoc IFeeManager
function getFeeAndReward(
address subscriber,
bytes memory report,
Quote memory quote
) public view returns (Common.Asset memory, Common.Asset memory, uint256) {
Common.Asset memory fee;
Common.Asset memory reward;
//get the feedId from the report
bytes32 feedId = bytes32(report);
//the report needs to be a support version
bytes32 reportVersion = _getReportVersion(feedId);
//version 1 of the reports don't require quotes, so the fee will be 0
if (reportVersion == REPORT_V1) {
fee.assetAddress = i_nativeAddress;
reward.assetAddress = i_linkAddress;
return (fee, reward, 0);
}
//verify the quote payload is a supported token
if (quote.quoteAddress != i_nativeAddress && quote.quoteAddress != i_linkAddress) {
revert InvalidQuote();
}
//decode the report depending on the version
uint256 linkQuantity;
uint256 nativeQuantity;
uint256 expiresAt;
(, , , nativeQuantity, linkQuantity, expiresAt) = abi.decode(
report,
(bytes32, uint32, uint32, uint192, uint192, uint32)
);
//read the timestamp bytes from the report data and verify it has not expired
if (expiresAt < block.timestamp) {
revert ExpiredReport();
}
//get the discount being applied
uint256 discount = s_subscriberDiscounts[subscriber][feedId][quote.quoteAddress];
//the reward is always set in LINK
reward.assetAddress = i_linkAddress;
reward.amount = Math.ceilDiv(linkQuantity * (PERCENTAGE_SCALAR - discount), PERCENTAGE_SCALAR);
//calculate either the LINK fee or native fee if it's within the report
if (quote.quoteAddress == i_linkAddress) {
fee.assetAddress = i_linkAddress;
fee.amount = reward.amount;
} else {
uint256 surchargedFee = Math.ceilDiv(nativeQuantity * (PERCENTAGE_SCALAR + s_nativeSurcharge), PERCENTAGE_SCALAR);
fee.assetAddress = i_nativeAddress;
fee.amount = Math.ceilDiv(surchargedFee * (PERCENTAGE_SCALAR - discount), PERCENTAGE_SCALAR);
}
//return the fee
return (fee, reward, discount);
}
/// @inheritdoc IVerifierFeeManager
function setFeeRecipients(
bytes32 configDigest,
Common.AddressAndWeight[] calldata rewardRecipientAndWeights
) external onlyOwnerOrProxy {
i_rewardManager.setRewardRecipients(configDigest, rewardRecipientAndWeights);
}
/// @inheritdoc IFeeManager
function setNativeSurcharge(uint64 surcharge) external onlyOwner {
if (surcharge > PERCENTAGE_SCALAR) revert InvalidSurcharge();
s_nativeSurcharge = surcharge;
emit NativeSurchargeUpdated(surcharge);
}
/// @inheritdoc IFeeManager
function updateSubscriberDiscount(
address subscriber,
bytes32 feedId,
address token,
uint64 discount
) external onlyOwner {
//make sure the discount is not greater than the total discount that can be applied
if (discount > PERCENTAGE_SCALAR) revert InvalidDiscount();
//make sure the token is either LINK or native
if (token != i_linkAddress && token != i_nativeAddress) revert InvalidAddress();
s_subscriberDiscounts[subscriber][feedId][token] = discount;
emit SubscriberDiscountUpdated(subscriber, feedId, token, discount);
}
/// @inheritdoc IFeeManager
function withdraw(address assetAddress, address recipient, uint192 quantity) external onlyOwner {
//address 0 is used to withdraw native in the context of withdrawing
if (assetAddress == address(0)) {
(bool success, ) = payable(recipient).call{value: quantity}("");
if (!success) revert InvalidReceivingAddress();
return;
}
//withdraw the requested asset
IERC20(assetAddress).safeTransfer(recipient, quantity);
//emit event when funds are withdrawn
emit Withdraw(msg.sender, recipient, assetAddress, uint192(quantity));
}
/// @inheritdoc IFeeManager
function linkAvailableForPayment() external view returns (uint256) {
//return the amount of LINK this contact has available to pay rewards
return IERC20(i_linkAddress).balanceOf(address(this));
}
/**
* @notice Gets the current version of the report that is encoded as the last two bytes of the feed
* @param feedId feed id to get the report version for
*/
function _getReportVersion(bytes32 feedId) internal pure returns (bytes32) {
return REPORT_VERSION_MASK & feedId;
}
function _processFee(
bytes calldata payload,
address subscriber
) internal view returns (Common.Asset memory, Common.Asset memory, uint256) {
if (subscriber == address(this)) revert InvalidAddress();
//decode the report from the payload
(, bytes memory report) = abi.decode(payload, (bytes32[3], bytes));
//get the feedId from the report
bytes32 feedId = bytes32(report);
//v1 doesn't need a quote payload, so skip the decoding
Quote memory quote;
if (_getReportVersion(feedId) != REPORT_V1) {
//all reports greater than v1 should have a quote payload
(, , , , , bytes memory quoteBytes) = abi.decode(
payload,
// reportContext, report, rs, ss, raw, quote
(bytes32[3], bytes, bytes32[], bytes32[], bytes32, bytes)
);
//decode the quote from the bytes
(quote) = abi.decode(quoteBytes, (Quote));
}
//decode the fee, it will always be native or LINK
return getFeeAndReward(subscriber, report, quote);
}
function _handleFeesAndRewards(
address subscriber,
FeeAndReward[] memory feesAndRewards,
uint256 numberOfLinkFees,
uint256 numberOfNativeFees
) internal {
IRewardManager.FeePayment[] memory linkRewards = new IRewardManager.FeePayment[](numberOfLinkFees);
IRewardManager.FeePayment[] memory nativeFeeLinkRewards = new IRewardManager.FeePayment[](numberOfNativeFees);
uint256 totalNativeFee;
uint256 totalNativeFeeLinkValue;
uint256 linkRewardsIndex;
uint256 nativeFeeLinkRewardsIndex;
uint256 totalNumberOfFees = numberOfLinkFees + numberOfNativeFees;
for (uint256 i; i < totalNumberOfFees; ++i) {
if (feesAndRewards[i].fee.assetAddress == i_linkAddress) {
linkRewards[linkRewardsIndex++] = IRewardManager.FeePayment(
feesAndRewards[i].configDigest,
uint192(feesAndRewards[i].reward.amount)
);
} else {
nativeFeeLinkRewards[nativeFeeLinkRewardsIndex++] = IRewardManager.FeePayment(
feesAndRewards[i].configDigest,
uint192(feesAndRewards[i].reward.amount)
);
totalNativeFee += feesAndRewards[i].fee.amount;
totalNativeFeeLinkValue += feesAndRewards[i].reward.amount;
}
if (feesAndRewards[i].appliedDiscount != 0) {
emit DiscountApplied(
feesAndRewards[i].configDigest,
subscriber,
feesAndRewards[i].fee,
feesAndRewards[i].reward,
feesAndRewards[i].appliedDiscount
);
}
}
//keep track of change in case of any over payment
uint256 change;
if (msg.value != 0) {
//there must be enough to cover the fee
if (totalNativeFee > msg.value) revert InvalidDeposit();
//wrap the amount required to pay the fee & approve as the subscriber paid in wrapped native
IWERC20(i_nativeAddress).deposit{value: totalNativeFee}();
unchecked {
//msg.value is always >= to fee.amount
change = msg.value - totalNativeFee;
}
} else {
if (totalNativeFee != 0) {
//subscriber has paid in wrapped native, so transfer the native to this contract
IERC20(i_nativeAddress).safeTransferFrom(subscriber, address(this), totalNativeFee);
}
}
if (linkRewards.length != 0) {
i_rewardManager.onFeePaid(linkRewards, subscriber);
}
if (nativeFeeLinkRewards.length != 0) {
//distribute subsidised fees paid in Native
if (totalNativeFeeLinkValue > IERC20(i_linkAddress).balanceOf(address(this))) {
// If not enough LINK on this contract to forward for rewards, tally the deficit to be paid by out-of-band LINK
for (uint256 i; i < nativeFeeLinkRewards.length; ++i) {
unchecked {
//we have previously tallied the fees, any overflows would have already reverted
s_linkDeficit[nativeFeeLinkRewards[i].poolId] += nativeFeeLinkRewards[i].amount;
}
}
emit InsufficientLink(nativeFeeLinkRewards);
} else {
//distribute the fees
i_rewardManager.onFeePaid(nativeFeeLinkRewards, address(this));
}
}
// a refund may be needed if the payee has paid in excess of the fee
_tryReturnChange(subscriber, change);
}
function _tryReturnChange(address subscriber, uint256 quantity) internal {
if (quantity != 0) {
payable(subscriber).transfer(quantity);
}
}
/// @inheritdoc IFeeManager
function payLinkDeficit(bytes32 configDigest) external onlyOwner {
uint256 deficit = s_linkDeficit[configDigest];
if (deficit == 0) revert ZeroDeficit();
delete s_linkDeficit[configDigest];
IRewardManager.FeePayment[] memory deficitFeePayment = new IRewardManager.FeePayment[](1);
deficitFeePayment[0] = IRewardManager.FeePayment(configDigest, uint192(deficit));
i_rewardManager.onFeePaid(deficitFeePayment, address(this));
emit LinkDeficitCleared(configDigest, deficit);
}
}
// SPDX-License-Identifier: MIT
pragma solidity 0.8.16;
import {IERC165} from "../../../vendor/openzeppelin-solidity/v4.8.0/contracts/interfaces/IERC165.sol";
import {Common} from "../../../libraries/Common.sol";
import {IVerifierFeeManager} from "./IVerifierFeeManager.sol";
interface IFeeManager is IERC165, IVerifierFeeManager {
/**
* @notice Calculate the applied fee and the reward from a report. If the sender is a subscriber, they will receive a discount.
* @param subscriber address trying to verify
* @param report report to calculate the fee for
* @param quote any metadata required to fetch the fee
* @return (fee, reward, totalDiscount) fee and the reward data with the discount applied
*/
function getFeeAndReward(
address subscriber,
bytes memory report,
Quote memory quote
) external returns (Common.Asset memory, Common.Asset memory, uint256);
/**
* @notice Sets the native surcharge
* @param surcharge surcharge to be paid if paying in native
*/
function setNativeSurcharge(uint64 surcharge) external;
/**
* @notice Adds a subscriber to the fee manager
* @param subscriber address of the subscriber
* @param feedId feed id to apply the discount to
* @param token token to apply the discount to
* @param discount discount to be applied to the fee
*/
function updateSubscriberDiscount(address subscriber, bytes32 feedId, address token, uint64 discount) external;
/**
* @notice Withdraws any native or LINK rewards to the owner address
* @param assetAddress address of the asset to withdraw
* @param recipientAddress address to withdraw to
* @param quantity quantity to withdraw
*/
function withdraw(address assetAddress, address recipientAddress, uint192 quantity) external;
/**
* @notice Returns the link balance of the fee manager
* @return link balance of the fee manager
*/
function linkAvailableForPayment() external returns (uint256);
/**
* @notice Admin function to pay the LINK deficit for a given config digest
* @param configDigest the config digest to pay the deficit for
*/
function payLinkDeficit(bytes32 configDigest) external;
/**
* @notice The structure to hold a fee and reward to verify a report
* @param digest the digest linked to the fee and reward
* @param fee the fee paid to verify the report
* @param reward the reward paid upon verification
& @param appliedDiscount the discount applied to the reward
*/
struct FeeAndReward {
bytes32 configDigest;
Common.Asset fee;
Common.Asset reward;
uint256 appliedDiscount;
}
/**
* @notice The structure to hold quote metadata
* @param quoteAddress the address of the quote
*/
struct Quote {
address quoteAddress;
}
}
// SPDX-License-Identifier: MIT
pragma solidity 0.8.16;
import {IERC165} from "../../../vendor/openzeppelin-solidity/v4.8.0/contracts/interfaces/IERC165.sol";
import {Common} from "../../../libraries/Common.sol";
interface IRewardManager is IERC165 {
/**
* @notice Record the fee received for a particular pool
* @param payments array of structs containing pool id and amount
* @param payee the user the funds should be retrieved from
*/
function onFeePaid(FeePayment[] calldata payments, address payee) external;
/**
* @notice Claims the rewards in a specific pool
* @param poolIds array of poolIds to claim rewards for
*/
function claimRewards(bytes32[] calldata poolIds) external;
/**
* @notice Set the RewardRecipients and weights for a specific pool. This should only be called once per pool Id. Else updateRewardRecipients should be used.
* @param poolId poolId to set RewardRecipients and weights for
* @param rewardRecipientAndWeights array of each RewardRecipient and associated weight
*/
function setRewardRecipients(bytes32 poolId, Common.AddressAndWeight[] calldata rewardRecipientAndWeights) external;
/**
* @notice Updates a subset the reward recipients for a specific poolId. The collective weight of the recipients should add up to the recipients existing weights. Any recipients with a weight of 0 will be removed.
* @param poolId the poolId to update
* @param newRewardRecipients array of new reward recipients
*/
function updateRewardRecipients(bytes32 poolId, Common.AddressAndWeight[] calldata newRewardRecipients) external;
/**
* @notice Pays all the recipients for each of the pool ids
* @param poolId the pool id to pay recipients for
* @param recipients array of recipients to pay within the pool
*/
function payRecipients(bytes32 poolId, address[] calldata recipients) external;
/**
* @notice Sets the fee manager. This needs to be done post construction to prevent a circular dependency.
* @param newFeeManager address of the new verifier proxy
*/
function setFeeManager(address newFeeManager) external;
/**
* @notice Gets a list of pool ids which have reward for a specific recipient.
* @param recipient address of the recipient to get pool ids for
* @param startIndex the index to start from
* @param endIndex the index to stop at
*/
function getAvailableRewardPoolIds(
address recipient,
uint256 startIndex,
uint256 endIndex
) external view returns (bytes32[] memory);
/**
* @notice The structure to hold a fee payment notice
* @param poolId the poolId receiving the payment
* @param amount the amount being paid
*/
struct FeePayment {
bytes32 poolId;
uint192 amount;
}
}
// SPDX-License-Identifier: MIT
pragma solidity 0.8.16;
import {IERC165} from "../../../vendor/openzeppelin-solidity/v4.8.0/contracts/interfaces/IERC165.sol";
import {Common} from "../../../libraries/Common.sol";
interface IVerifierFeeManager is IERC165 {
/**
* @notice Handles fees for a report from the subscriber and manages rewards
* @param payload report and quote to process the fee for
* @param subscriber address of the fee will be applied
*/
function processFee(bytes calldata payload, address subscriber) external payable;
/**
* @notice Processes the fees for each report in the payload, billing the subscriber and paying the reward manager
* @param payloads reports and quotes to process
* @param subscriber address of the user to process fee for
*/
function processFeeBulk(bytes[] calldata payloads, address subscriber) external payable;
/**
* @notice Sets the fee recipients according to the fee manager
* @param configDigest digest of the configuration
* @param rewardRecipientAndWeights the address and weights of all the recipients to receive rewards
*/
function setFeeRecipients(
bytes32 configDigest,
Common.AddressAndWeight[] calldata rewardRecipientAndWeights
) external;
}
// SPDX-License-Identifier: MIT
pragma solidity 0.8.16;
import {ConfirmedOwner} from "../../shared/access/ConfirmedOwner.sol";
import {IRewardManager} from "./interfaces/IRewardManager.sol";
import {IERC20} from "../../vendor/openzeppelin-solidity/v4.8.0/contracts/interfaces/IERC20.sol";
import {TypeAndVersionInterface} from "../../interfaces/TypeAndVersionInterface.sol";
import {IERC165} from "../../vendor/openzeppelin-solidity/v4.8.0/contracts/interfaces/IERC165.sol";
import {Common} from "../../libraries/Common.sol";
import {SafeERC20} from "../../vendor/openzeppelin-solidity/v4.8.0/contracts/token/ERC20/utils/SafeERC20.sol";
/**
* @title RewardManager
* @author Michael Fletcher
* @author Austin Born
* @notice This contract will be used to reward any configured recipients within a pool. Recipients will receive a share of their pool relative to their configured weight.
*/
contract RewardManager is IRewardManager, ConfirmedOwner, TypeAndVersionInterface {
using SafeERC20 for IERC20;
// @dev The mapping of total fees collected for a particular pot: s_totalRewardRecipientFees[poolId]
mapping(bytes32 => uint256) public s_totalRewardRecipientFees;
// @dev The mapping of fee balances for each pot last time the recipient claimed: s_totalRewardRecipientFeesLastClaimedAmounts[poolId][recipient]
mapping(bytes32 => mapping(address => uint256)) public s_totalRewardRecipientFeesLastClaimedAmounts;
// @dev The mapping of RewardRecipient weights for a particular poolId: s_rewardRecipientWeights[poolId][rewardRecipient].
mapping(bytes32 => mapping(address => uint256)) public s_rewardRecipientWeights;
// @dev Keep track of the reward recipient weights that have been set to prevent duplicates
mapping(bytes32 => bool) public s_rewardRecipientWeightsSet;
// @dev Store a list of pool ids that have been registered, to make off chain lookups easier
bytes32[] public s_registeredPoolIds;
// @dev The address for the LINK contract
address public immutable i_linkAddress;
// The total weight of all RewardRecipients. 1e18 = 100% of the pool fees
uint64 private constant PERCENTAGE_SCALAR = 1e18;
// The fee manager address
address public s_feeManagerAddress;
// @notice Thrown whenever the RewardRecipient weights are invalid
error InvalidWeights();
// @notice Thrown when any given address is invalid
error InvalidAddress();
// @notice Thrown when the pool id is invalid
error InvalidPoolId();
// @notice Thrown when the calling contract is not within the authorized contracts
error Unauthorized();
// @notice Thrown when getAvailableRewardPoolIds parameters are incorrectly set
error InvalidPoolLength();
// Events emitted upon state change
event RewardRecipientsUpdated(bytes32 indexed poolId, Common.AddressAndWeight[] newRewardRecipients);
event RewardsClaimed(bytes32 indexed poolId, address indexed recipient, uint192 quantity);
event FeeManagerUpdated(address newFeeManagerAddress);
event FeePaid(FeePayment[] payments, address payer);
/**
* @notice Constructor
* @param linkAddress address of the wrapped LINK token
*/
constructor(address linkAddress) ConfirmedOwner(msg.sender) {
//ensure that the address ia not zero
if (linkAddress == address(0)) revert InvalidAddress();
i_linkAddress = linkAddress;
}
// @inheritdoc TypeAndVersionInterface
function typeAndVersion() external pure override returns (string memory) {
return "RewardManager 1.0.0";
}
// @inheritdoc IERC165
function supportsInterface(bytes4 interfaceId) external pure override returns (bool) {
return interfaceId == this.onFeePaid.selector;
}
modifier onlyOwnerOrFeeManager() {
if (msg.sender != owner() && msg.sender != s_feeManagerAddress) revert Unauthorized();
_;
}
modifier onlyOwnerOrRecipientInPool(bytes32 poolId) {
if (msg.sender != owner() && s_rewardRecipientWeights[poolId][msg.sender] == 0) revert Unauthorized();
_;
}
modifier onlyFeeManager() {
if (msg.sender != s_feeManagerAddress) revert Unauthorized();
_;
}
/// @inheritdoc IRewardManager
function onFeePaid(FeePayment[] calldata payments, address payer) external override onlyFeeManager {
uint256 totalFeeAmount;
for (uint256 i; i < payments.length; ++i) {
unchecked {
//the total amount for any ERC20 asset cannot exceed 2^256 - 1
s_totalRewardRecipientFees[payments[i].poolId] += payments[i].amount;
//tally the total payable fees
totalFeeAmount += payments[i].amount;
}
}
//transfer the fees to this contract
IERC20(i_linkAddress).safeTransferFrom(payer, address(this), totalFeeAmount);
emit FeePaid(payments, payer);
}
/// @inheritdoc IRewardManager
function claimRewards(bytes32[] memory poolIds) external override {
_claimRewards(msg.sender, poolIds);
}
// wrapper impl for claimRewards
function _claimRewards(address recipient, bytes32[] memory poolIds) internal returns (uint256) {
//get the total amount claimable for this recipient
uint256 claimAmount;
//loop and claim all the rewards in the poolId pot
for (uint256 i; i < poolIds.length; ++i) {
//get the poolId to be claimed
bytes32 poolId = poolIds[i];
//get the total fees for the pot
uint256 totalFeesInPot = s_totalRewardRecipientFees[poolId];
unchecked {
//avoid unnecessary storage reads if there's no fees in the pot
if (totalFeesInPot == 0) continue;
//get the claimable amount for this recipient, this calculation will never exceed the amount in the pot
uint256 claimableAmount = totalFeesInPot - s_totalRewardRecipientFeesLastClaimedAmounts[poolId][recipient];
//calculate the recipients share of the fees, which is their weighted share of the difference between the last amount they claimed and the current amount in the pot. This can never be more than the total amount in existence
uint256 recipientShare = (claimableAmount * s_rewardRecipientWeights[poolId][recipient]) / PERCENTAGE_SCALAR;
//if there's no fees to claim, continue as there's nothing to update
if (recipientShare == 0) continue;
//keep track of the total amount claimable, this can never be more than the total amount in existence
claimAmount += recipientShare;
//set the current total amount of fees in the pot as it's used to calculate future claims
s_totalRewardRecipientFeesLastClaimedAmounts[poolId][recipient] = totalFeesInPot;
//emit event if the recipient has rewards to claim
emit RewardsClaimed(poolIds[i], recipient, uint192(recipientShare));
}
}
//check if there's any rewards to claim in the given poolId
if (claimAmount != 0) {
//transfer the reward to the recipient
IERC20(i_linkAddress).safeTransfer(recipient, claimAmount);
}
return claimAmount;
}
/// @inheritdoc IRewardManager
function setRewardRecipients(
bytes32 poolId,
Common.AddressAndWeight[] calldata rewardRecipientAndWeights
) external override onlyOwnerOrFeeManager {
//revert if there are no recipients to set
if (rewardRecipientAndWeights.length == 0) revert InvalidAddress();
//check that the weights have not been previously set
if (s_rewardRecipientWeightsSet[poolId]) revert InvalidPoolId();
//keep track of the registered poolIds to make off chain lookups easier
s_registeredPoolIds.push(poolId);
//keep track of which pools have had their reward recipients set
s_rewardRecipientWeightsSet[poolId] = true;
//set the reward recipients, this will only be called once and contain the full set of RewardRecipients with a total weight of 100%
_setRewardRecipientWeights(poolId, rewardRecipientAndWeights, PERCENTAGE_SCALAR);
emit RewardRecipientsUpdated(poolId, rewardRecipientAndWeights);
}
function _setRewardRecipientWeights(
bytes32 poolId,
Common.AddressAndWeight[] calldata rewardRecipientAndWeights,
uint256 expectedWeight
) internal {
//we can't update the weights if it contains duplicates
if (Common.hasDuplicateAddresses(rewardRecipientAndWeights)) revert InvalidAddress();
//loop all the reward recipients and validate the weight and address
uint256 totalWeight;
for (uint256 i; i < rewardRecipientAndWeights.length; ++i) {
//get the weight
uint256 recipientWeight = rewardRecipientAndWeights[i].weight;
//get the address
address recipientAddress = rewardRecipientAndWeights[i].addr;
//ensure the reward recipient address is not zero
if (recipientAddress == address(0)) revert InvalidAddress();
//ensure the weight is not zero
if (recipientWeight == 0) revert InvalidWeights();
//save/overwrite the weight for the reward recipient
s_rewardRecipientWeights[poolId][recipientAddress] = recipientWeight;
unchecked {
//keep track of the cumulative weight, this cannot overflow as the total weight is restricted at 1e18
totalWeight += recipientWeight;
}
}
//if total weight is not met, the fees will either be under or over distributed
if (totalWeight != expectedWeight) revert InvalidWeights();
}
/// @inheritdoc IRewardManager
function updateRewardRecipients(
bytes32 poolId,
Common.AddressAndWeight[] calldata newRewardRecipients
) external override onlyOwner {
//create an array of poolIds to pass to _claimRewards if required
bytes32[] memory poolIds = new bytes32[](1);
poolIds[0] = poolId;
//loop all the reward recipients and claim their rewards before updating their weights
uint256 existingTotalWeight;
for (uint256 i; i < newRewardRecipients.length; ++i) {
//get the address
address recipientAddress = newRewardRecipients[i].addr;
//get the existing weight
uint256 existingWeight = s_rewardRecipientWeights[poolId][recipientAddress];
//if the existing weight is 0, the recipient isn't part of this configuration
if (existingWeight == 0) revert InvalidAddress();
//if a recipient is updated, the rewards must be claimed first as they can't claim previous fees at the new weight
_claimRewards(newRewardRecipients[i].addr, poolIds);
unchecked {
//keep tally of the weights so that the expected collective weight is known
existingTotalWeight += existingWeight;
}
}
//update the reward recipients, if the new collective weight isn't equal to the previous collective weight, the fees will either be under or over distributed
_setRewardRecipientWeights(poolId, newRewardRecipients, existingTotalWeight);
//emit event
emit RewardRecipientsUpdated(poolId, newRewardRecipients);
}
/// @inheritdoc IRewardManager
function payRecipients(bytes32 poolId, address[] calldata recipients) external onlyOwnerOrRecipientInPool(poolId) {
//convert poolIds to an array to match the interface of _claimRewards
bytes32[] memory poolIdsArray = new bytes32[](1);
poolIdsArray[0] = poolId;
//loop each recipient and claim the rewards for each of the pools and assets
for (uint256 i; i < recipients.length; ++i) {
_claimRewards(recipients[i], poolIdsArray);
}
}
/// @inheritdoc IRewardManager
function setFeeManager(address newFeeManagerAddress) external onlyOwner {
if (newFeeManagerAddress == address(0)) revert InvalidAddress();
s_feeManagerAddress = newFeeManagerAddress;
emit FeeManagerUpdated(newFeeManagerAddress);
}
/// @inheritdoc IRewardManager
function getAvailableRewardPoolIds(
address recipient,
uint256 startIndex,
uint256 endIndex
) external view returns (bytes32[] memory) {
//get the length of the pool ids which we will loop through and potentially return
uint256 registeredPoolIdsLength = s_registeredPoolIds.length;
uint256 lastIndex = endIndex > registeredPoolIdsLength ? registeredPoolIdsLength : endIndex;
if (startIndex > lastIndex) revert InvalidPoolLength();
//create a new array with the maximum amount of potential pool ids
bytes32[] memory claimablePoolIds = new bytes32[](lastIndex - startIndex);
//we want the pools which a recipient has funds for to be sequential, so we need to keep track of the index
uint256 poolIdArrayIndex;
//loop all the pool ids, and check if the recipient has a registered weight and a claimable amount
for (uint256 i = startIndex; i < lastIndex; ++i) {
//get the poolId
bytes32 poolId = s_registeredPoolIds[i];
//if the recipient has a weight, they are a recipient of this poolId
if (s_rewardRecipientWeights[poolId][recipient] != 0) {
//get the total in this pool
uint256 totalPoolAmount = s_totalRewardRecipientFees[poolId];
//if the recipient has any LINK, then add the poolId to the array
unchecked {
//s_totalRewardRecipientFeesLastClaimedAmounts can never exceed total pool amount, and the number of pools can't exceed the max array length
if (totalPoolAmount - s_totalRewardRecipientFeesLastClaimedAmounts[poolId][recipient] != 0) {
claimablePoolIds[poolIdArrayIndex++] = poolId;
}
}
}
}
return claimablePoolIds;
}
}
// SPDX-License-Identifier: MIT
pragma solidity 0.8.16;
import {IERC165} from "../../vendor/openzeppelin-solidity/v4.8.0/contracts/interfaces/IERC165.sol";
import {Common} from "../../libraries/Common.sol";
interface IVerifier is IERC165 {
/**
* @notice Verifies that the data encoded has been signed
* correctly by routing to the correct verifier.
* @param signedReport The encoded data to be verified.
* @param sender The address that requested to verify the contract.
* This is only used for logging purposes.
* @dev Verification is typically only done through the proxy contract so
* we can't just use msg.sender to log the requester as the msg.sender
* contract will always be the proxy.
* @return verifierResponse The encoded verified response.
*/
function verify(bytes calldata signedReport, address sender) external returns (bytes memory verifierResponse);
/**
* @notice sets offchain reporting protocol configuration incl. participating oracles
* @param feedId Feed ID to set config for
* @param signers addresses with which oracles sign the reports
* @param offchainTransmitters CSA key for the ith Oracle
* @param f number of faulty oracles the system can tolerate
* @param onchainConfig serialized configuration used by the contract (and possibly oracles)
* @param offchainConfigVersion version number for offchainEncoding schema
* @param offchainConfig serialized configuration used by the oracles exclusively and only passed through the contract
* @param recipientAddressesAndWeights the addresses and weights of all the recipients to receive rewards
*/
function setConfig(
bytes32 feedId,
address[] memory signers,
bytes32[] memory offchainTransmitters,
uint8 f,
bytes memory onchainConfig,
uint64 offchainConfigVersion,
bytes memory offchainConfig,
Common.AddressAndWeight[] memory recipientAddressesAndWeights
) external;
/**
* @notice identical to `setConfig` except with args for sourceChainId and sourceAddress
* @param feedId Feed ID to set config for
* @param sourceChainId Chain ID of source config
* @param sourceAddress Address of source config Verifier
* @param signers addresses with which oracles sign the reports
* @param offchainTransmitters CSA key for the ith Oracle
* @param f number of faulty oracles the system can tolerate
* @param onchainConfig serialized configuration used by the contract (and possibly oracles)
* @param offchainConfigVersion version number for offchainEncoding schema
* @param offchainConfig serialized configuration used by the oracles exclusively and only passed through the contract
* @param recipientAddressesAndWeights the addresses and weights of all the recipients to receive rewards
*/
function setConfigFromSource(
bytes32 feedId,
uint256 sourceChainId,
address sourceAddress,
address[] memory signers,
bytes32[] memory offchainTransmitters,
uint8 f,
bytes memory onchainConfig,
uint64 offchainConfigVersion,
bytes memory offchainConfig,
Common.AddressAndWeight[] memory recipientAddressesAndWeights
) external;
/**
* @notice Activates the configuration for a config digest
* @param feedId Feed ID to activate config for
* @param configDigest The config digest to activate
* @dev This function can be called by the contract admin to activate a configuration.
*/
function activateConfig(bytes32 feedId, bytes32 configDigest) external;
/**
* @notice Deactivates the configuration for a config digest
* @param feedId Feed ID to deactivate config for
* @param configDigest The config digest to deactivate
* @dev This function can be called by the contract admin to deactivate an incorrect configuration.
*/
function deactivateConfig(bytes32 feedId, bytes32 configDigest) external;
/**
* @notice Activates the given feed
* @param feedId Feed ID to activated
* @dev This function can be called by the contract admin to activate a feed
*/
function activateFeed(bytes32 feedId) external;
/**
* @notice Deactivates the given feed
* @param feedId Feed ID to deactivated
* @dev This function can be called by the contract admin to deactivate a feed
*/
function deactivateFeed(bytes32 feedId) external;
/**
* @notice returns the latest config digest and epoch for a feed
* @param feedId Feed ID to fetch data for
* @return scanLogs indicates whether to rely on the configDigest and epoch
* returned or whether to scan logs for the Transmitted event instead.
* @return configDigest
* @return epoch
*/
function latestConfigDigestAndEpoch(
bytes32 feedId
) external view returns (bool scanLogs, bytes32 configDigest, uint32 epoch);
/**
* @notice information about current offchain reporting protocol configuration
* @param feedId Feed ID to fetch data for
* @return configCount ordinal number of current config, out of all configs applied to this contract so far
* @return blockNumber block at which this config was set
* @return configDigest domain-separation tag for current config
*/
function latestConfigDetails(
bytes32 feedId
) external view returns (uint32 configCount, uint32 blockNumber, bytes32 configDigest);
}
// SPDX-License-Identifier: MIT
pragma solidity 0.8.16;
import {Common} from "../../libraries/Common.sol";
import {AccessControllerInterface} from "../../shared/interfaces/AccessControllerInterface.sol";
import {IVerifierFeeManager} from "./../dev/interfaces/IVerifierFeeManager.sol";
interface IVerifierProxy {
/**
* @notice Verifies that the data encoded has been signed
* correctly by routing to the correct verifier, and bills the user if applicable.
* @param payload The encoded data to be verified, including the signed
* report and any metadata for billing.
* @return verifierResponse The encoded report from the verifier.
*/
function verify(bytes calldata payload) external payable returns (bytes memory verifierResponse);
/**
* @notice Bulk verifies that the data encoded has been signed
* correctly by routing to the correct verifier, and bills the user if applicable.
* @param payloads The encoded payloads to be verified, including the signed
* report and any metadata for billing.
* @return verifiedReports The encoded reports from the verifier.
*/
function verifyBulk(bytes[] calldata payloads) external payable returns (bytes[] memory verifiedReports);
/**
* @notice Sets the verifier address initially, allowing `setVerifier` to be set by this Verifier in the future
* @param verifierAddress The address of the verifier contract to initialize
*/
function initializeVerifier(address verifierAddress) external;
/**
* @notice Sets a new verifier for a config digest
* @param currentConfigDigest The current config digest
* @param newConfigDigest The config digest to set
* @param addressesAndWeights The addresses and weights of reward recipients
* reports for a given config digest.
*/
function setVerifier(
bytes32 currentConfigDigest,
bytes32 newConfigDigest,
Common.AddressAndWeight[] memory addressesAndWeights
) external;
/**
* @notice Removes a verifier for a given config digest
* @param configDigest The config digest of the verifier to remove
*/
function unsetVerifier(bytes32 configDigest) external;
/**
* @notice Retrieves the verifier address that verifies reports
* for a config digest.
* @param configDigest The config digest to query for
* @return verifierAddress The address of the verifier contract that verifies
* reports for a given config digest.
*/
function getVerifier(bytes32 configDigest) external view returns (address verifierAddress);
/**
* @notice Called by the admin to set an access controller contract
* @param accessController The new access controller to set
*/
function setAccessController(AccessControllerInterface accessController) external;
/**
* @notice Updates the fee manager
* @param feeManager The new fee manager
*/
function setFeeManager(IVerifierFeeManager feeManager) external;
}
// SPDX-License-Identifier: UNLICENSED
pragma solidity 0.8.16;
import {IVerifier} from "../../interfaces/IVerifier.sol";
import {Common} from "../../../libraries/Common.sol";
contract ErroredVerifier is IVerifier {
function supportsInterface(bytes4 interfaceId) public pure override returns (bool) {
return interfaceId == this.verify.selector;
}
function verify(
bytes memory,
/**
* signedReport*
*/
address
)
external
pure
override
returns (
/**
* sender*
*/
bytes memory
)
{
revert("Failed to verify");
}
function setConfig(
bytes32,
address[] memory,
bytes32[] memory,
uint8,
bytes memory,
uint64,
bytes memory,
Common.AddressAndWeight[] memory
) external pure override {
revert("Failed to set config");
}
function setConfigFromSource(
bytes32,
uint256,
address,
address[] memory,
bytes32[] memory,
uint8,
bytes memory,
uint64,
bytes memory,
Common.AddressAndWeight[] memory
) external pure override {
revert("Failed to set config");
}
function activateConfig(bytes32, bytes32) external pure {
revert("Failed to activate config");
}
function deactivateConfig(bytes32, bytes32) external pure {
revert("Failed to deactivate config");
}
function activateFeed(bytes32) external pure {
revert("Failed to activate feed");
}
function deactivateFeed(bytes32) external pure {
revert("Failed to deactivate feed");
}
function latestConfigDigestAndEpoch(bytes32) external pure override returns (bool, bytes32, uint32) {
revert("Failed to get latest config digest and epoch");
}
function latestConfigDetails(bytes32) external pure override returns (uint32, uint32, bytes32) {
revert("Failed to get latest config details");
}
}
// SPDX-License-Identifier: MIT
pragma solidity 0.8.16;
// ExposedVerifier exposes certain internal Verifier
// methods/structures so that golang code can access them, and we get
// reliable type checking on their usage
contract ExposedVerifier {
constructor() {}
function _configDigestFromConfigData(
bytes32 feedId,
uint256 chainId,
address contractAddress,
uint64 configCount,
address[] memory signers,
bytes32[] memory offchainTransmitters,
uint8 f,
bytes memory onchainConfig,
uint64 offchainConfigVersion,
bytes memory offchainConfig
) internal pure returns (bytes32) {
uint256 h = uint256(
keccak256(
abi.encode(
feedId,
chainId,
contractAddress,
configCount,
signers,
offchainTransmitters,
f,
onchainConfig,
offchainConfigVersion,
offchainConfig
)
)
);
uint256 prefixMask = type(uint256).max << (256 - 16); // 0xFFFF00..00
uint256 prefix = 0x0006 << (256 - 16); // 0x000600..00
return bytes32((prefix & prefixMask) | (h & ~prefixMask));
}
function exposedConfigDigestFromConfigData(
bytes32 _feedId,
uint256 _chainId,
address _contractAddress,
uint64 _configCount,
address[] memory _signers,
bytes32[] memory _offchainTransmitters,
uint8 _f,
bytes calldata _onchainConfig,
uint64 _encodedConfigVersion,
bytes memory _encodedConfig
) public pure returns (bytes32) {
return
_configDigestFromConfigData(
_feedId,
_chainId,
_contractAddress,
_configCount,
_signers,
_offchainTransmitters,
_f,
_onchainConfig,
_encodedConfigVersion,
_encodedConfig
);
}
}
// SPDX-License-Identifier: MIT
pragma solidity 0.8.16;
import "../../dev/interfaces/IFeeManager.sol";
contract FeeManagerProxy {
IFeeManager internal i_feeManager;
function processFee(bytes calldata payload) public payable {
i_feeManager.processFee{value: msg.value}(payload, msg.sender);
}
function processFeeBulk(bytes[] calldata payloads) public payable {
i_feeManager.processFeeBulk{value: msg.value}(payloads, msg.sender);
}
function setFeeManager(IFeeManager feeManager) public {
i_feeManager = feeManager;
}
}
// SPDX-License-Identifier: MIT
pragma solidity 0.8.16;
import {ConfirmedOwner} from "../shared/access/ConfirmedOwner.sol";
import {IVerifier} from "./interfaces/IVerifier.sol";
import {IVerifierProxy} from "./interfaces/IVerifierProxy.sol";
import {TypeAndVersionInterface} from "../interfaces/TypeAndVersionInterface.sol";
import {IERC165} from "../vendor/openzeppelin-solidity/v4.8.0/contracts/interfaces/IERC165.sol";
import {Common} from "../libraries/Common.sol";
// OCR2 standard
uint256 constant MAX_NUM_ORACLES = 31;
/*
* The verifier contract is used to verify offchain reports signed
* by DONs. A report consists of a price, block number and feed Id. It
* represents the observed price of an asset at a specified block number for
* a feed. The verifier contract is used to verify that such reports have
* been signed by the correct signers.
**/
contract Verifier is IVerifier, ConfirmedOwner, TypeAndVersionInterface {
// The first byte of the mask can be 0, because we only ever have 31 oracles
uint256 internal constant ORACLE_MASK = 0x0001010101010101010101010101010101010101010101010101010101010101;
enum Role {
// Default role for an oracle address. This means that the oracle address
// is not a signer
Unset,
// Role given to an oracle address that is allowed to sign feed data
Signer
}
struct Signer {
// Index of oracle in a configuration
uint8 index;
// The oracle's role
Role role;
}
struct Config {
// Fault tolerance
uint8 f;
// Marks whether or not a configuration is active
bool isActive;
// Map of signer addresses to oracles
mapping(address => Signer) oracles;
}
struct VerifierState {
// The number of times a new configuration
/// has been set
uint32 configCount;
// The block number of the block the last time
/// the configuration was updated.
uint32 latestConfigBlockNumber;
// The latest epoch a report was verified for
uint32 latestEpoch;
// Whether or not the verifier for this feed has been deactivated
bool isDeactivated;
/// The latest config digest set
bytes32 latestConfigDigest;
/// The historical record of all previously set configs by feedId
mapping(bytes32 => Config) s_verificationDataConfigs;
}
/// @notice This event is emitted when a new report is verified.
/// It is used to keep a historical record of verified reports.
event ReportVerified(bytes32 indexed feedId, address requester);
/// @notice This event is emitted whenever a new configuration is set for a feed. It triggers a new run of the offchain reporting protocol.
event ConfigSet(
bytes32 indexed feedId,
uint32 previousConfigBlockNumber,
bytes32 configDigest,
uint64 configCount,
address[] signers,
bytes32[] offchainTransmitters,
uint8 f,
bytes onchainConfig,
uint64 offchainConfigVersion,
bytes offchainConfig
);
/// @notice This event is emitted whenever a configuration is deactivated
event ConfigDeactivated(bytes32 indexed feedId, bytes32 configDigest);
/// @notice This event is emitted whenever a configuration is activated
event ConfigActivated(bytes32 indexed feedId, bytes32 configDigest);
/// @notice This event is emitted whenever a feed is activated
event FeedActivated(bytes32 indexed feedId);
/// @notice This event is emitted whenever a feed is deactivated
event FeedDeactivated(bytes32 indexed feedId);
/// @notice This error is thrown whenever an address tries
/// to exeecute a transaction that it is not authorized to do so
error AccessForbidden();
/// @notice This error is thrown whenever a zero address is passed
error ZeroAddress();
/// @notice This error is thrown whenever the feed ID passed in
/// a signed report is empty
error FeedIdEmpty();
/// @notice This error is thrown whenever the config digest
/// is empty
error DigestEmpty();
/// @notice This error is thrown whenever the config digest
/// passed in has not been set in this verifier
/// @param feedId The feed ID in the signed report
/// @param configDigest The config digest that has not been set
error DigestNotSet(bytes32 feedId, bytes32 configDigest);
/// @notice This error is thrown whenever the config digest
/// has been deactivated
/// @param feedId The feed ID in the signed report
/// @param configDigest The config digest that is inactive
error DigestInactive(bytes32 feedId, bytes32 configDigest);
/// @notice This error is thrown whenever trying to set a config
/// with a fault tolerance of 0
error FaultToleranceMustBePositive();
/// @notice This error is thrown whenever a report is signed
/// with more than the max number of signers
/// @param numSigners The number of signers who have signed the report
/// @param maxSigners The maximum number of signers that can sign a report
error ExcessSigners(uint256 numSigners, uint256 maxSigners);
/// @notice This error is thrown whenever a report is signed
/// with less than the minimum number of signers
/// @param numSigners The number of signers who have signed the report
/// @param minSigners The minimum number of signers that need to sign a report
error InsufficientSigners(uint256 numSigners, uint256 minSigners);
/// @notice This error is thrown whenever a report is signed
/// with an incorrect number of signers
/// @param numSigners The number of signers who have signed the report
/// @param expectedNumSigners The expected number of signers that need to sign
/// a report
error IncorrectSignatureCount(uint256 numSigners, uint256 expectedNumSigners);
/// @notice This error is thrown whenever the R and S signer components
/// have different lengths
/// @param rsLength The number of r signature components
/// @param ssLength The number of s signature components
error MismatchedSignatures(uint256 rsLength, uint256 ssLength);
/// @notice This error is thrown whenever setting a config with duplicate signatures
error NonUniqueSignatures();
/// @notice This error is thrown whenever a report fails to verify due to bad or duplicate signatures
error BadVerification();
/// @notice This error is thrown whenever the admin tries to deactivate
/// the latest config digest
/// @param feedId The feed ID in the signed report
/// @param configDigest The latest config digest
error CannotDeactivateLatestConfig(bytes32 feedId, bytes32 configDigest);
/// @notice This error is thrown whenever the feed ID passed in is deactivated
/// @param feedId The feed ID
error InactiveFeed(bytes32 feedId);
/// @notice This error is thrown whenever the feed ID passed in is not found
/// @param feedId The feed ID
error InvalidFeed(bytes32 feedId);
/// @notice The address of the verifier proxy
address private immutable i_verifierProxyAddr;
/// @notice Verifier states keyed on Feed ID
mapping(bytes32 => VerifierState) s_feedVerifierStates;
/// @param verifierProxyAddr The address of the VerifierProxy contract
constructor(address verifierProxyAddr) ConfirmedOwner(msg.sender) {
if (verifierProxyAddr == address(0)) revert ZeroAddress();
i_verifierProxyAddr = verifierProxyAddr;
}
modifier checkConfigValid(uint256 numSigners, uint256 f) {
if (f == 0) revert FaultToleranceMustBePositive();
if (numSigners > MAX_NUM_ORACLES) revert ExcessSigners(numSigners, MAX_NUM_ORACLES);
if (numSigners <= 3 * f) revert InsufficientSigners(numSigners, 3 * f + 1);
_;
}
/// @inheritdoc IERC165
function supportsInterface(bytes4 interfaceId) external pure override returns (bool isVerifier) {
return interfaceId == this.verify.selector;
}
/// @inheritdoc TypeAndVersionInterface
function typeAndVersion() external pure override returns (string memory) {
return "Verifier 1.1.0";
}
/// @inheritdoc IVerifier
function verify(
bytes calldata signedReport,
address sender
) external override returns (bytes memory verifierResponse) {
if (msg.sender != i_verifierProxyAddr) revert AccessForbidden();
(
bytes32[3] memory reportContext,
bytes memory reportData,
bytes32[] memory rs,
bytes32[] memory ss,
bytes32 rawVs
) = abi.decode(signedReport, (bytes32[3], bytes, bytes32[], bytes32[], bytes32));
// The feed ID is the first 32 bytes of the report data.
bytes32 feedId = bytes32(reportData);
VerifierState storage feedVerifierState = s_feedVerifierStates[feedId];
// If the feed has been deactivated, do not verify the report
if (feedVerifierState.isDeactivated) {
revert InactiveFeed(feedId);
}
// reportContext consists of:
// reportContext[0]: ConfigDigest
// reportContext[1]: 27 byte padding, 4-byte epoch and 1-byte round
// reportContext[2]: ExtraHash
bytes32 configDigest = reportContext[0];
Config storage s_config = feedVerifierState.s_verificationDataConfigs[configDigest];
_validateReport(feedId, configDigest, rs, ss, s_config);
_updateEpoch(reportContext, feedVerifierState);
bytes32 hashedReport = keccak256(reportData);
_verifySignatures(hashedReport, reportContext, rs, ss, rawVs, s_config);
emit ReportVerified(feedId, sender);
return reportData;
}
/// @notice Validates parameters of the report
/// @param feedId Feed ID from the report
/// @param configDigest Config digest from the report
/// @param rs R components from the report
/// @param ss S components from the report
/// @param config Config for the given feed ID keyed on the config digest
function _validateReport(
bytes32 feedId,
bytes32 configDigest,
bytes32[] memory rs,
bytes32[] memory ss,
Config storage config
) private view {
uint8 expectedNumSignatures = config.f + 1;
if (!config.isActive) revert DigestInactive(feedId, configDigest);
if (rs.length != expectedNumSignatures) revert IncorrectSignatureCount(rs.length, expectedNumSignatures);
if (rs.length != ss.length) revert MismatchedSignatures(rs.length, ss.length);
}
/**
* @notice Conditionally update the epoch for a feed
* @param reportContext Report context containing the epoch and round
* @param feedVerifierState Feed verifier state to conditionally update
*/
function _updateEpoch(bytes32[3] memory reportContext, VerifierState storage feedVerifierState) private {
uint40 epochAndRound = uint40(uint256(reportContext[1]));
uint32 epoch = uint32(epochAndRound >> 8);
if (epoch > feedVerifierState.latestEpoch) {
feedVerifierState.latestEpoch = epoch;
}
}
/// @notice Verifies that a report has been signed by the correct
/// signers and that enough signers have signed the reports.
/// @param hashedReport The keccak256 hash of the raw report's bytes
/// @param reportContext The context the report was signed in
/// @param rs ith element is the R components of the ith signature on report. Must have at most MAX_NUM_ORACLES entries
/// @param ss ith element is the S components of the ith signature on report. Must have at most MAX_NUM_ORACLES entries
/// @param rawVs ith element is the the V component of the ith signature
/// @param s_config The config digest the report was signed for
function _verifySignatures(
bytes32 hashedReport,
bytes32[3] memory reportContext,
bytes32[] memory rs,
bytes32[] memory ss,
bytes32 rawVs,
Config storage s_config
) private view {
bytes32 h = keccak256(abi.encodePacked(hashedReport, reportContext));
// i-th byte counts number of sigs made by i-th signer
uint256 signedCount;
Signer memory o;
address signerAddress;
uint256 numSigners = rs.length;
for (uint256 i; i < numSigners; ++i) {
signerAddress = ecrecover(h, uint8(rawVs[i]) + 27, rs[i], ss[i]);
o = s_config.oracles[signerAddress];
if (o.role != Role.Signer) revert BadVerification();
unchecked {
signedCount += 1 << (8 * o.index);
}
}
if (signedCount & ORACLE_MASK != signedCount) revert BadVerification();
}
/// @inheritdoc IVerifier
function setConfig(
bytes32 feedId,
address[] memory signers,
bytes32[] memory offchainTransmitters,
uint8 f,
bytes memory onchainConfig,
uint64 offchainConfigVersion,
bytes memory offchainConfig,
Common.AddressAndWeight[] memory recipientAddressesAndWeights
) external override checkConfigValid(signers.length, f) onlyOwner {
_setConfig(
feedId,
block.chainid,
address(this),
signers,
offchainTransmitters,
f,
onchainConfig,
offchainConfigVersion,
offchainConfig,
recipientAddressesAndWeights
);
}
/// @inheritdoc IVerifier
function setConfigFromSource(
bytes32 feedId,
uint256 sourceChainId,
address sourceAddress,
address[] memory signers,
bytes32[] memory offchainTransmitters,
uint8 f,
bytes memory onchainConfig,
uint64 offchainConfigVersion,
bytes memory offchainConfig,
Common.AddressAndWeight[] memory recipientAddressesAndWeights
) external override checkConfigValid(signers.length, f) onlyOwner {
_setConfig(
feedId,
sourceChainId,
sourceAddress,
signers,
offchainTransmitters,
f,
onchainConfig,
offchainConfigVersion,
offchainConfig,
recipientAddressesAndWeights
);
}
/// @notice Sets config based on the given arguments
/// @param feedId Feed ID to set config for
/// @param sourceChainId Chain ID of source config
/// @param sourceAddress Address of source config Verifier
/// @param signers addresses with which oracles sign the reports
/// @param offchainTransmitters CSA key for the ith Oracle
/// @param f number of faulty oracles the system can tolerate
/// @param onchainConfig serialized configuration used by the contract (and possibly oracles)
/// @param offchainConfigVersion version number for offchainEncoding schema
/// @param offchainConfig serialized configuration used by the oracles exclusively and only passed through the contract
/// @param recipientAddressesAndWeights the addresses and weights of all the recipients to receive rewards
function _setConfig(
bytes32 feedId,
uint256 sourceChainId,
address sourceAddress,
address[] memory signers,
bytes32[] memory offchainTransmitters,
uint8 f,
bytes memory onchainConfig,
uint64 offchainConfigVersion,
bytes memory offchainConfig,
Common.AddressAndWeight[] memory recipientAddressesAndWeights
) internal {
VerifierState storage feedVerifierState = s_feedVerifierStates[feedId];
// Increment the number of times a config has been set first
feedVerifierState.configCount++;
bytes32 configDigest = _configDigestFromConfigData(
feedId,
sourceChainId,
sourceAddress,
feedVerifierState.configCount,
signers,
offchainTransmitters,
f,
onchainConfig,
offchainConfigVersion,
offchainConfig
);
feedVerifierState.s_verificationDataConfigs[configDigest].f = f;
feedVerifierState.s_verificationDataConfigs[configDigest].isActive = true;
for (uint8 i; i < signers.length; ++i) {
address signerAddr = signers[i];
if (signerAddr == address(0)) revert ZeroAddress();
// All signer roles are unset by default for a new config digest.
// Here the contract checks to see if a signer's address has already
// been set to ensure that the group of signer addresses that will
// sign reports with the config digest are unique.
bool isSignerAlreadySet = feedVerifierState.s_verificationDataConfigs[configDigest].oracles[signerAddr].role !=
Role.Unset;
if (isSignerAlreadySet) revert NonUniqueSignatures();
feedVerifierState.s_verificationDataConfigs[configDigest].oracles[signerAddr] = Signer({
role: Role.Signer,
index: i
});
}
IVerifierProxy(i_verifierProxyAddr).setVerifier(
feedVerifierState.latestConfigDigest,
configDigest,
recipientAddressesAndWeights
);
emit ConfigSet(
feedId,
feedVerifierState.latestConfigBlockNumber,
configDigest,
feedVerifierState.configCount,
signers,
offchainTransmitters,
f,
onchainConfig,
offchainConfigVersion,
offchainConfig
);
feedVerifierState.latestEpoch = 0;
feedVerifierState.latestConfigBlockNumber = uint32(block.number);
feedVerifierState.latestConfigDigest = configDigest;
}
/// @notice Generates the config digest from config data
/// @param feedId Feed ID to set config for
/// @param sourceChainId Chain ID of source config
/// @param sourceAddress Address of source config Verifier
/// @param configCount ordinal number of this config setting among all config settings over the life of this contract
/// @param signers ith element is address ith oracle uses to sign a report
/// @param offchainTransmitters ith element is address ith oracle used to transmit reports (in this case used for flexible additional field, such as CSA pub keys)
/// @param f maximum number of faulty/dishonest oracles the protocol can tolerate while still working correctly
/// @param onchainConfig serialized configuration used by the contract (and possibly oracles)
/// @param offchainConfigVersion version of the serialization format used for "offchainConfig" parameter
/// @param offchainConfig serialized configuration used by the oracles exclusively and only passed through the contract
/// @dev This function is a modified version of the method from OCR2Abstract
function _configDigestFromConfigData(
bytes32 feedId,
uint256 sourceChainId,
address sourceAddress,
uint64 configCount,
address[] memory signers,
bytes32[] memory offchainTransmitters,
uint8 f,
bytes memory onchainConfig,
uint64 offchainConfigVersion,
bytes memory offchainConfig
) internal pure returns (bytes32) {
uint256 h = uint256(
keccak256(
abi.encode(
feedId,
sourceChainId,
sourceAddress,
configCount,
signers,
offchainTransmitters,
f,
onchainConfig,
offchainConfigVersion,
offchainConfig
)
)
);
uint256 prefixMask = type(uint256).max << (256 - 16); // 0xFFFF00..00
// 0x0006 corresponds to ConfigDigestPrefixMercuryV02 in libocr
uint256 prefix = 0x0006 << (256 - 16); // 0x000600..00
return bytes32((prefix & prefixMask) | (h & ~prefixMask));
}
/// @inheritdoc IVerifier
function activateConfig(bytes32 feedId, bytes32 configDigest) external onlyOwner {
VerifierState storage feedVerifierState = s_feedVerifierStates[feedId];
if (configDigest == bytes32("")) revert DigestEmpty();
if (feedVerifierState.s_verificationDataConfigs[configDigest].f == 0) revert DigestNotSet(feedId, configDigest);
feedVerifierState.s_verificationDataConfigs[configDigest].isActive = true;
emit ConfigActivated(feedId, configDigest);
}
/// @inheritdoc IVerifier
function deactivateConfig(bytes32 feedId, bytes32 configDigest) external onlyOwner {
VerifierState storage feedVerifierState = s_feedVerifierStates[feedId];
if (configDigest == bytes32("")) revert DigestEmpty();
if (feedVerifierState.s_verificationDataConfigs[configDigest].f == 0) revert DigestNotSet(feedId, configDigest);
if (configDigest == feedVerifierState.latestConfigDigest) {
revert CannotDeactivateLatestConfig(feedId, configDigest);
}
feedVerifierState.s_verificationDataConfigs[configDigest].isActive = false;
emit ConfigDeactivated(feedId, configDigest);
}
/// @inheritdoc IVerifier
function activateFeed(bytes32 feedId) external onlyOwner {
VerifierState storage feedVerifierState = s_feedVerifierStates[feedId];
if (feedVerifierState.configCount == 0) revert InvalidFeed(feedId);
feedVerifierState.isDeactivated = false;
emit FeedActivated(feedId);
}
/// @inheritdoc IVerifier
function deactivateFeed(bytes32 feedId) external onlyOwner {
VerifierState storage feedVerifierState = s_feedVerifierStates[feedId];
if (feedVerifierState.configCount == 0) revert InvalidFeed(feedId);
feedVerifierState.isDeactivated = true;
emit FeedDeactivated(feedId);
}
/// @inheritdoc IVerifier
function latestConfigDigestAndEpoch(
bytes32 feedId
) external view override returns (bool scanLogs, bytes32 configDigest, uint32 epoch) {
VerifierState storage feedVerifierState = s_feedVerifierStates[feedId];
return (false, feedVerifierState.latestConfigDigest, feedVerifierState.latestEpoch);
}
/// @inheritdoc IVerifier
function latestConfigDetails(
bytes32 feedId
) external view override returns (uint32 configCount, uint32 blockNumber, bytes32 configDigest) {
VerifierState storage feedVerifierState = s_feedVerifierStates[feedId];
return (
feedVerifierState.configCount,
feedVerifierState.latestConfigBlockNumber,
feedVerifierState.latestConfigDigest
);
}
}
// SPDX-License-Identifier: MIT
pragma solidity 0.8.16;
import {ConfirmedOwner} from "../shared/access/ConfirmedOwner.sol";
import {IVerifierProxy} from "./interfaces/IVerifierProxy.sol";
import {IVerifier} from "./interfaces/IVerifier.sol";
import {TypeAndVersionInterface} from "../interfaces/TypeAndVersionInterface.sol";
import {AccessControllerInterface} from "../shared/interfaces/AccessControllerInterface.sol";
import {IERC165} from "../vendor/openzeppelin-solidity/v4.8.0/contracts/interfaces/IERC165.sol";
import {IVerifierFeeManager} from "./dev/interfaces/IVerifierFeeManager.sol";
import {Common} from "../libraries/Common.sol";
/**
* The verifier proxy contract is the gateway for all report verification requests
* on a chain. It is responsible for taking in a verification request and routing
* it to the correct verifier contract.
*/
contract VerifierProxy is IVerifierProxy, ConfirmedOwner, TypeAndVersionInterface {
/// @notice This event is emitted whenever a new verifier contract is set
/// @param oldConfigDigest The config digest that was previously the latest config
/// digest of the verifier contract at the verifier address.
/// @param oldConfigDigest The latest config digest of the verifier contract
/// at the verifier address.
/// @param verifierAddress The address of the verifier contract that verifies reports for
/// a given digest
event VerifierSet(bytes32 oldConfigDigest, bytes32 newConfigDigest, address verifierAddress);
/// @notice This event is emitted whenever a new verifier contract is initialized
/// @param verifierAddress The address of the verifier contract that verifies reports
event VerifierInitialized(address verifierAddress);
/// @notice This event is emitted whenever a verifier is unset
/// @param configDigest The config digest that was unset
/// @param verifierAddress The Verifier contract address unset
event VerifierUnset(bytes32 configDigest, address verifierAddress);
/// @notice This event is emitted when a new access controller is set
/// @param oldAccessController The old access controller address
/// @param newAccessController The new access controller address
event AccessControllerSet(address oldAccessController, address newAccessController);
/// @notice This event is emitted when a new fee manager is set
/// @param oldFeeManager The old fee manager address
/// @param newFeeManager The new fee manager address
event FeeManagerSet(address oldFeeManager, address newFeeManager);
/// @notice This error is thrown whenever an address tries
/// to exeecute a transaction that it is not authorized to do so
error AccessForbidden();
/// @notice This error is thrown whenever a zero address is passed
error ZeroAddress();
/// @notice This error is thrown when trying to set a verifier address
/// for a digest that has already been initialized
/// @param configDigest The digest for the verifier that has
/// already been set
/// @param verifier The address of the verifier the digest was set for
error ConfigDigestAlreadySet(bytes32 configDigest, address verifier);
/// @notice This error is thrown when trying to set a verifier address that has already been initialized
error VerifierAlreadyInitialized(address verifier);
/// @notice This error is thrown when the verifier at an address does
/// not conform to the verifier interface
error VerifierInvalid();
/// @notice This error is thrown when the fee manager at an address does
/// not conform to the fee manager interface
error FeeManagerInvalid();
/// @notice This error is thrown whenever a verifier is not found
/// @param configDigest The digest for which a verifier is not found
error VerifierNotFound(bytes32 configDigest);
/// @notice This error is thrown whenever billing fails.
error BadVerification();
/// @notice Mapping of authorized verifiers
mapping(address => bool) private s_initializedVerifiers;
/// @notice Mapping between config digests and verifiers
mapping(bytes32 => address) private s_verifiersByConfig;
/// @notice The contract to control addresses that are allowed to verify reports
AccessControllerInterface public s_accessController;
/// @notice The contract to control fees for report verification
IVerifierFeeManager public s_feeManager;
constructor(AccessControllerInterface accessController) ConfirmedOwner(msg.sender) {
s_accessController = accessController;
}
modifier checkAccess() {
AccessControllerInterface ac = s_accessController;
if (address(ac) != address(0) && !ac.hasAccess(msg.sender, msg.data)) revert AccessForbidden();
_;
}
modifier onlyInitializedVerifier() {
if (!s_initializedVerifiers[msg.sender]) revert AccessForbidden();
_;
}
modifier onlyValidVerifier(address verifierAddress) {
if (verifierAddress == address(0)) revert ZeroAddress();
if (!IERC165(verifierAddress).supportsInterface(IVerifier.verify.selector)) revert VerifierInvalid();
_;
}
modifier onlyUnsetConfigDigest(bytes32 configDigest) {
address configDigestVerifier = s_verifiersByConfig[configDigest];
if (configDigestVerifier != address(0)) revert ConfigDigestAlreadySet(configDigest, configDigestVerifier);
_;
}
/// @inheritdoc TypeAndVersionInterface
function typeAndVersion() external pure override returns (string memory) {
return "VerifierProxy 1.1.0";
}
/// @inheritdoc IVerifierProxy
function verify(bytes calldata payload) external payable checkAccess returns (bytes memory) {
IVerifierFeeManager feeManager = s_feeManager;
// Bill the verifier
if (address(feeManager) != address(0)) {
feeManager.processFee{value: msg.value}(payload, msg.sender);
}
return _verify(payload);
}
/// @inheritdoc IVerifierProxy
function verifyBulk(bytes[] calldata payloads) external payable checkAccess returns (bytes[] memory verifiedReports) {
IVerifierFeeManager feeManager = s_feeManager;
// Bill the verifier
if (address(feeManager) != address(0)) {
feeManager.processFeeBulk{value: msg.value}(payloads, msg.sender);
}
//verify the reports
verifiedReports = new bytes[](payloads.length);
for (uint256 i; i < payloads.length; ++i) {
verifiedReports[i] = _verify(payloads[i]);
}
return verifiedReports;
}
function _verify(bytes calldata payload) internal returns (bytes memory verifiedReport) {
// First 32 bytes of the signed report is the config digest
bytes32 configDigest = bytes32(payload);
address verifierAddress = s_verifiersByConfig[configDigest];
if (verifierAddress == address(0)) revert VerifierNotFound(configDigest);
return IVerifier(verifierAddress).verify(payload, msg.sender);
}
/// @inher