// SPDX-License-Identifier: MIT pragma solidity 0.8.6; import "../../vendor/openzeppelin-solidity/v4.7.3/contracts/utils/structs/EnumerableSet.sol"; import "../../vendor/@arbitrum/nitro-contracts/src/precompiles/ArbGasInfo.sol"; import "../../vendor/@eth-optimism/contracts/v0.8.6/contracts/L2/predeploys/OVM_GasPriceOracle.sol"; import {ArbSys} from "../../vendor/@arbitrum/nitro-contracts/src/precompiles/ArbSys.sol"; import "../ExecutionPrevention.sol"; import "../../shared/access/ConfirmedOwner.sol"; import "../../shared/interfaces/AggregatorV3Interface.sol"; import "../../shared/interfaces/LinkTokenInterface.sol"; import "../interfaces/KeeperCompatibleInterface.sol"; import "../interfaces/UpkeepTranscoderInterface.sol"; /** * @notice relevant state of an upkeep which is used in transmit function * @member executeGas the gas limit of upkeep execution * @member maxValidBlocknumber until which block this upkeep is valid * @member paused if this upkeep has been paused * @member target the contract which needs to be serviced * @member amountSpent the amount this upkeep has spent * @member balance the balance of this upkeep * @member lastPerformBlockNumber the last block number when this upkeep was performed */ struct Upkeep { uint32 executeGas; uint32 maxValidBlocknumber; bool paused; address target; // 3 bytes left in 1st EVM word - not written to in transmit uint96 amountSpent; uint96 balance; uint32 lastPerformBlockNumber; // 4 bytes left in 2nd EVM word - written in transmit path } /** * @notice Base Keeper Registry contract, contains shared logic between * KeeperRegistry and KeeperRegistryLogic */ abstract contract KeeperRegistryBase2_0 is ConfirmedOwner, ExecutionPrevention { 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; 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 = uint8(UpkeepFormat.V3); // 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_GAS_OVERHEAD = 70_000; // Used only in maxPayment estimation, not in actual payment uint256 internal constant REGISTRY_PER_PERFORM_BYTE_GAS_OVERHEAD = 20; // Used only in maxPayment estimation, not in actual payment. Value scales with performData length. uint256 internal constant REGISTRY_PER_SIGNER_GAS_OVERHEAD = 7_500; // Used only in maxPayment estimation, not in actual payment. Value scales with f. uint256 internal constant ACCOUNTING_FIXED_GAS_OVERHEAD = 26_900; // 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 = 5_800; // 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; // @dev - The storage is gas optimised for one and only 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; // Registry config and state 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_upkeepOffchainConfig; // general configuration preferences error ArrayHasNoEntries(); error CannotCancel(); error DuplicateEntry(); error GasLimitCanOnlyIncrease(); error GasLimitOutsideRange(); error IndexOutOfRange(); error InsufficientFunds(); error InvalidDataLength(); error InvalidPayee(); error InvalidRecipient(); error MigrationNotPermitted(); error NotAContract(); error OnlyActiveTransmitters(); error OnlyCallableByAdmin(); error OnlyCallableByLINKToken(); error OnlyCallableByOwnerOrAdmin(); error OnlyCallableByOwnerOrRegistrar(); error OnlyCallableByPayee(); error OnlyCallableByProposedAdmin(); error OnlyCallableByProposedPayee(); error OnlyPausedUpkeep(); error OnlyUnpausedUpkeep(); error ParameterLengthError(); error PaymentGreaterThanAllLINK(); error TargetCheckReverted(bytes reason); error TranscoderNotSet(); error UpkeepCancelled(); error UpkeepNotCanceled(); error UpkeepNotNeeded(); error ValueNotChanged(); error ConfigDigestMismatch(); error IncorrectNumberOfSignatures(); error OnlyActiveSigners(); error DuplicateSigners(); error TooManyOracles(); error IncorrectNumberOfSigners(); error IncorrectNumberOfFaultyOracles(); error RepeatedSigner(); error RepeatedTransmitter(); error OnchainConfigNonEmpty(); error CheckDataExceedsLimit(); error MaxCheckDataSizeCanOnlyIncrease(); error MaxPerformDataSizeCanOnlyIncrease(); error InvalidReport(); error RegistryPaused(); error ReentrantCall(); error UpkeepAlreadyExists(); enum MigrationPermission { NONE, OUTGOING, INCOMING, BIDIRECTIONAL } enum Mode { DEFAULT, ARBITRUM, OPTIMISM } // 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 } // 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 address registrar; // Address of registrar used to register upkeeps // 2 EVM word full 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 uint32 maxCheckDataSize; // max length of checkData bytes uint32 maxPerformDataSize; // max length of performData bytes // 4 bytes to 3rd EVM word } 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; } // This struct is used to pack information about the user's check function struct PerformDataWrapper { uint32 checkBlockNumber; // Block number-1 on which check was simulated bytes32 checkBlockhash; // blockhash of checkBlockNumber. Used for reorg protection bytes performData; // actual performData that user's check returned } // Report transmitted by OCR to transmit function struct Report { uint256 fastGasWei; uint256 linkNative; uint256[] upkeepIds; // Ids of upkeeps PerformDataWrapper[] wrappedPerformDatas; // Contains checkInfo and performData for the corresponding upkeeps } event FundsAdded(uint256 indexed id, address indexed from, uint96 amount); event FundsWithdrawn(uint256 indexed id, uint256 amount, address to); event OwnerFundsWithdrawn(uint96 amount); 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 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 UpkeepCheckDataUpdated(uint256 indexed id, bytes newCheckData); event UpkeepGasLimitSet(uint256 indexed id, uint96 gasLimit); event UpkeepOffchainConfigSet(uint256 indexed id, bytes offchainConfig); event UpkeepMigrated(uint256 indexed id, uint256 remainingBalance, address destination); event UpkeepPaused(uint256 indexed id); event UpkeepPerformed( uint256 indexed id, bool indexed success, uint32 checkBlockNumber, uint256 gasUsed, uint256 gasOverhead, uint96 totalPayment ); event UpkeepReceived(uint256 indexed id, uint256 startingBalance, address importedFrom); event UpkeepUnpaused(uint256 indexed id); event UpkeepRegistered(uint256 indexed id, uint32 executeGas, address admin); event StaleUpkeepReport(uint256 indexed id); event ReorgedUpkeepReport(uint256 indexed id); event InsufficientFundsUpkeepReport(uint256 indexed id); event CancelledUpkeepReport(uint256 indexed id); event Paused(address account); 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) ConfirmedOwner(msg.sender) { i_mode = mode; i_link = LinkTokenInterface(link); i_linkNativeFeed = AggregatorV3Interface(linkNativeFeed); i_fastGasFeed = AggregatorV3Interface(fastGasFeed); } //////// // GETTERS //////// 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); } //////// // INTERNAL //////// /** * @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 { // @dev 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) { l1CostWei = ARB_NITRO_ORACLE.getCurrentTxL1GasFees(); } // 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 generates the max link payment for an upkeep */ function _getMaxLinkPayment( HotVars memory hotVars, uint32 executeGas, 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(performDataLength, hotVars.f); (uint96 reimbursement, uint96 premium) = _calculatePaymentAmount( hotVars, executeGas, 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(uint32 performDataLength, uint8 f) internal pure returns (uint256) { // performData causes additional overhead in report length and memory operations return REGISTRY_GAS_OVERHEAD + (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]; uint96 uncollected = totalPremium - transmitter.lastCollected; uint96 due = uncollected / payeeCount; transmitter.balance += due; transmitter.lastCollected = totalPremium; // Transfer spare change to owner s_storage.ownerLinkBalance += (uncollected - due * payeeCount); s_transmitters[transmitterAddress] = transmitter; return transmitter.balance; } /** * @notice 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; } } /** * @notice 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); } } /** * @notice 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; } }