// SPDX-License-Identifier: BUSL-1.1 pragma solidity 0.8.16; import {IAutomationForwarder} from "../interfaces/IAutomationForwarder.sol"; import {IAutomationV21PlusCommon} from "../interfaces/IAutomationV21PlusCommon.sol"; import {UpkeepFormat} from "../interfaces/UpkeepTranscoderInterface.sol"; import {KeeperRegistryBase2_1} from "./KeeperRegistryBase2_1.sol"; import {Address} from "@openzeppelin/contracts@4.9.6/utils/Address.sol"; import {EnumerableSet} from "@openzeppelin/contracts@4.9.6/utils/structs/EnumerableSet.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 (IAutomationV21PlusCommon.UpkeepInfoLegacy memory upkeepInfo) { Upkeep memory reg = s_upkeep[id]; address target = address(reg.forwarder) == address(0) ? address(0) : reg.forwarder.getTarget(); upkeepInfo = IAutomationV21PlusCommon.UpkeepInfoLegacy({ 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 ( IAutomationV21PlusCommon.StateLegacy memory state, IAutomationV21PlusCommon.OnchainConfigLegacy memory config, address[] memory signers, address[] memory transmitters, uint8 f ) { state = IAutomationV21PlusCommon.StateLegacy({ 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 = IAutomationV21PlusCommon.OnchainConfigLegacy({ 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]; } }