// SPDX-License-Identifier: MIT pragma solidity 0.8.6; import "../../vendor/openzeppelin-solidity/v4.7.3/contracts/utils/structs/EnumerableSet.sol"; import "../../vendor/openzeppelin-solidity/v4.7.3/contracts/utils/Address.sol"; import "./KeeperRegistryBase2_0.sol"; import {UpkeepFailureReason} from "../interfaces/v2_0/AutomationRegistryInterface2_0.sol"; import "../interfaces/MigratableKeeperRegistryInterfaceV2.sol"; import "../interfaces/UpkeepTranscoderInterfaceV2.sol"; /** * @notice Logic contract, works in tandem with KeeperRegistry as a proxy */ contract KeeperRegistryLogic2_0 is KeeperRegistryBase2_0 { using Address for address; using EnumerableSet for EnumerableSet.UintSet; /** * @param mode one of Default, Arbitrum, 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 ) KeeperRegistryBase2_0(mode, link, linkNativeFeed, fastGasFeed) {} function checkUpkeep( uint256 id ) external cannotExecute returns ( bool upkeepNeeded, bytes memory performData, UpkeepFailureReason upkeepFailureReason, uint256 gasUsed, uint256 fastGasWei, uint256 linkNative ) { HotVars memory hotVars = s_hotVars; Upkeep memory upkeep = s_upkeep[id]; if (upkeep.maxValidBlocknumber != UINT32_MAX) return (false, bytes(""), UpkeepFailureReason.UPKEEP_CANCELLED, gasUsed, 0, 0); if (upkeep.paused) return (false, bytes(""), UpkeepFailureReason.UPKEEP_PAUSED, gasUsed, 0, 0); (fastGasWei, linkNative) = _getFeedData(hotVars); uint96 maxLinkPayment = _getMaxLinkPayment( hotVars, upkeep.executeGas, s_storage.maxPerformDataSize, fastGasWei, linkNative, false ); if (upkeep.balance < maxLinkPayment) return (false, bytes(""), UpkeepFailureReason.INSUFFICIENT_BALANCE, gasUsed, fastGasWei, linkNative); gasUsed = gasleft(); bytes memory callData = abi.encodeWithSelector(CHECK_SELECTOR, s_checkData[id]); (bool success, bytes memory result) = upkeep.target.call{gas: s_storage.checkGasLimit}(callData); gasUsed = gasUsed - gasleft(); if (!success) { upkeepFailureReason = UpkeepFailureReason.TARGET_CHECK_REVERTED; } else { (upkeepNeeded, result) = abi.decode(result, (bool, bytes)); if (!upkeepNeeded) return (false, bytes(""), UpkeepFailureReason.UPKEEP_NOT_NEEDED, gasUsed, fastGasWei, linkNative); if (result.length > s_storage.maxPerformDataSize) return (false, bytes(""), UpkeepFailureReason.PERFORM_DATA_EXCEEDS_LIMIT, gasUsed, fastGasWei, linkNative); } performData = abi.encode( PerformDataWrapper({ checkBlockNumber: uint32(_blockNum() - 1), checkBlockhash: _blockHash(_blockNum() - 1), performData: result }) ); return (success, performData, upkeepFailureReason, gasUsed, fastGasWei, linkNative); } /** * @dev Called through KeeperRegistry main contract */ 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); } /** * @dev Called through KeeperRegistry main contract */ function recoverFunds() external onlyOwner { uint256 total = i_link.balanceOf(address(this)); i_link.transfer(msg.sender, total - s_expectedLinkBalance); } /** * @dev Called through KeeperRegistry main contract */ 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); } /** * @dev Called through KeeperRegistry main contract */ function pause() external onlyOwner { s_hotVars.paused = true; emit Paused(msg.sender); } /** * @dev Called through KeeperRegistry main contract */ function unpause() external onlyOwner { s_hotVars.paused = false; emit Unpaused(msg.sender); } /** * @dev Called through KeeperRegistry main contract */ function setPeerRegistryMigrationPermission(address peer, MigrationPermission permission) external onlyOwner { s_peerRegistryMigrationPermission[peer] = permission; } /** * @dev Called through KeeperRegistry main contract */ function registerUpkeep( address target, uint32 gasLimit, address admin, bytes calldata checkData, bytes calldata offchainConfig ) external returns (uint256 id) { if (msg.sender != owner() && msg.sender != s_storage.registrar) revert OnlyCallableByOwnerOrRegistrar(); id = uint256(keccak256(abi.encode(_blockHash(_blockNum() - 1), address(this), s_storage.nonce))); _createUpkeep(id, target, gasLimit, admin, 0, checkData, false); s_storage.nonce++; s_upkeepOffchainConfig[id] = offchainConfig; emit UpkeepRegistered(id, gasLimit, admin); return id; } /** * @dev Called through KeeperRegistry main contract */ 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)); } /** * @dev Called through KeeperRegistry main contract */ 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); } /** * @dev Called through KeeperRegistry main contract */ 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); } /** * @dev Called through KeeperRegistry main contract */ function setUpkeepGasLimit(uint256 id, uint32 gasLimit) external { if (gasLimit < PERFORM_GAS_MIN || gasLimit > s_storage.maxPerformGas) revert GasLimitOutsideRange(); _requireAdminAndNotCancelled(id); s_upkeep[id].executeGas = gasLimit; emit UpkeepGasLimitSet(id, gasLimit); } /** * @dev Called through KeeperRegistry main contract */ function setUpkeepOffchainConfig(uint256 id, bytes calldata config) external { _requireAdminAndNotCancelled(id); s_upkeepOffchainConfig[id] = config; emit UpkeepOffchainConfigSet(id, config); } /** * @dev Called through KeeperRegistry main contract */ 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); } /** * @dev Called through KeeperRegistry main contract */ 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); } } /** * @dev Called through KeeperRegistry main contract */ 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); } /** * @dev Called through KeeperRegistry main contract */ function transferUpkeepAdmin(uint256 id, address proposed) external { _requireAdminAndNotCancelled(id); if (proposed == msg.sender) revert ValueNotChanged(); if (proposed == ZERO_ADDRESS) revert InvalidRecipient(); if (s_proposedAdmin[id] != proposed) { s_proposedAdmin[id] = proposed; emit UpkeepAdminTransferRequested(id, msg.sender, proposed); } } /** * @dev Called through KeeperRegistry main contract */ 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); } /** * @dev Called through KeeperRegistry main contract */ 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); } /** * @dev Called through KeeperRegistry main contract */ 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); } /** * @dev Called through KeeperRegistry main contract */ function updateCheckData(uint256 id, bytes calldata newCheckData) external { _requireAdminAndNotCancelled(id); if (newCheckData.length > s_storage.maxCheckDataSize) revert CheckDataExceedsLimit(); s_checkData[id] = newCheckData; emit UpkeepCheckDataUpdated(id, newCheckData); } /** * @dev Called through KeeperRegistry main contract */ 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; bytes[] memory checkDatas = new bytes[](ids.length); address[] memory admins = new address[](ids.length); Upkeep[] memory upkeeps = new Upkeep[](ids.length); for (uint256 idx = 0; idx < ids.length; idx++) { id = ids[idx]; upkeep = s_upkeep[id]; _requireAdminAndNotCancelled(id); upkeeps[idx] = upkeep; checkDatas[idx] = s_checkData[id]; admins[idx] = s_upkeepAdmin[id]; totalBalanceRemaining = totalBalanceRemaining + upkeep.balance; delete s_upkeep[id]; delete s_checkData[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, checkDatas, admins); MigratableKeeperRegistryInterfaceV2(destination).receiveUpkeeps( UpkeepTranscoderInterfaceV2(s_storage.transcoder).transcodeUpkeeps( UPKEEP_VERSION_BASE, MigratableKeeperRegistryInterfaceV2(destination).upkeepVersion(), encodedUpkeeps ) ); i_link.transfer(destination, totalBalanceRemaining); } /** * @dev Called through KeeperRegistry main contract */ 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, bytes[] memory checkDatas, address[] memory upkeepAdmins) = abi .decode(encodedUpkeeps, (uint256[], Upkeep[], bytes[], address[])); for (uint256 idx = 0; idx < ids.length; idx++) { _createUpkeep( ids[idx], upkeeps[idx].target, upkeeps[idx].executeGas, upkeepAdmins[idx], upkeeps[idx].balance, checkDatas[idx], upkeeps[idx].paused ); emit UpkeepReceived(ids[idx], upkeeps[idx].balance, msg.sender); } } /** * @notice creates a new upkeep with the given fields * @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 checkData data passed to the contract when checking for upkeep * @param paused if this upkeep is paused */ function _createUpkeep( uint256 id, address target, uint32 gasLimit, address admin, uint96 balance, bytes memory checkData, bool paused ) internal { if (s_hotVars.paused) revert RegistryPaused(); if (!target.isContract()) revert NotAContract(); if (checkData.length > s_storage.maxCheckDataSize) revert CheckDataExceedsLimit(); if (gasLimit < PERFORM_GAS_MIN || gasLimit > s_storage.maxPerformGas) revert GasLimitOutsideRange(); if (s_upkeep[id].target != address(0)) revert UpkeepAlreadyExists(); s_upkeep[id] = Upkeep({ target: target, executeGas: gasLimit, balance: balance, maxValidBlocknumber: UINT32_MAX, lastPerformBlockNumber: 0, amountSpent: 0, paused: paused }); s_upkeepAdmin[id] = admin; s_expectedLinkBalance = s_expectedLinkBalance + balance; s_checkData[id] = checkData; s_upkeepIDs.add(id); } /** * @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(); } }