// SPDX-License-Identifier: BUSL-1.1 pragma solidity 0.8.16; import {ConfirmedOwner} from "../../shared/access/ConfirmedOwner.sol"; import {AggregatorV3Interface} from "../../shared/interfaces/AggregatorV3Interface.sol"; import {LinkTokenInterface} from "../../shared/interfaces/LinkTokenInterface.sol"; import {ArbGasInfo} from "../../vendor/@arbitrum/nitro-contracts/src/precompiles/ArbGasInfo.sol"; import {ArbSys} from "../../vendor/@arbitrum/nitro-contracts/src/precompiles/ArbSys.sol"; import {OVM_GasPriceOracle} from "../../vendor/@eth-optimism/contracts/v0.8.9/contracts/L2/predeploys/OVM_GasPriceOracle.sol"; import {ExecutionPrevention} from "../ExecutionPrevention.sol"; import {IAutomationForwarder} from "../interfaces/IAutomationForwarder.sol"; import {ILogAutomation, Log} from "../interfaces/ILogAutomation.sol"; import {KeeperCompatibleInterface} from "../interfaces/KeeperCompatibleInterface.sol"; import {StreamsLookupCompatibleInterface} from "../interfaces/StreamsLookupCompatibleInterface.sol"; import {UpkeepFormat} from "../interfaces/UpkeepTranscoderInterface.sol"; import {Address} from "@openzeppelin/contracts@4.9.6/utils/Address.sol"; import {EnumerableSet} from "@openzeppelin/contracts@4.9.6/utils/structs/EnumerableSet.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 = 2300; uint256 internal constant CANCELLATION_DELAY = 50; uint256 internal constant PERFORM_GAS_CUSHION = 5000; 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 = 7500; // 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 = 1100; // Used in actual payment. overhead per signer uint256 internal constant ACCOUNTING_PER_UPKEEP_GAS_OVERHEAD = 7000; // 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 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 /// @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; } }