// SPDX-License-Identifier: MIT// OpenZeppelin Contracts (last updated v4.5.0) (utils/Address.sol)pragmasolidity ^0.8.1;/**
* @dev Collection of functions related to the address type
*/libraryAddress{
/**
* @dev Returns true if `account` is a contract.
*
* [IMPORTANT]
* ====
* It is unsafe to assume that an address for which this function returns
* false is an externally-owned account (EOA) and not a contract.
*
* Among others, `isContract` will return false for the following
* types of addresses:
*
* - an externally-owned account
* - a contract in construction
* - an address where a contract will be created
* - an address where a contract lived, but was destroyed
* ====
*
* [IMPORTANT]
* ====
* You shouldn't rely on `isContract` to protect against flash loan attacks!
*
* Preventing calls from contracts is highly discouraged. It breaks composability, breaks support for smart wallets
* like Gnosis Safe, and does not provide security since it can be circumvented by calling from a contract
* constructor.
* ====
*/functionisContract(address account) internalviewreturns (bool) {
// This method relies on extcodesize/address.code.length, which returns 0// for contracts in construction, since the code is only stored at the end// of the constructor execution.return account.code.length>0;
}
/**
* @dev Replacement for Solidity's `transfer`: sends `amount` wei to
* `recipient`, forwarding all available gas and reverting on errors.
*
* https://eips.ethereum.org/EIPS/eip-1884[EIP1884] increases the gas cost
* of certain opcodes, possibly making contracts go over the 2300 gas limit
* imposed by `transfer`, making them unable to receive funds via
* `transfer`. {sendValue} removes this limitation.
*
* https://diligence.consensys.net/posts/2019/09/stop-using-soliditys-transfer-now/[Learn more].
*
* IMPORTANT: because control is transferred to `recipient`, care must be
* taken to not create reentrancy vulnerabilities. Consider using
* {ReentrancyGuard} or the
* https://solidity.readthedocs.io/en/v0.5.11/security-considerations.html#use-the-checks-effects-interactions-pattern[checks-effects-interactions pattern].
*/functionsendValue(addresspayable recipient, uint256 amount) internal{
require(address(this).balance>= amount, "Address: insufficient balance");
(bool success, ) = recipient.call{value: amount}("");
require(success, "Address: unable to send value, recipient may have reverted");
}
/**
* @dev Performs a Solidity function call using a low level `call`. A
* plain `call` is an unsafe replacement for a function call: use this
* function instead.
*
* If `target` reverts with a revert reason, it is bubbled up by this
* function (like regular Solidity function calls).
*
* Returns the raw returned data. To convert to the expected return value,
* use https://solidity.readthedocs.io/en/latest/units-and-global-variables.html?highlight=abi.decode#abi-encoding-and-decoding-functions[`abi.decode`].
*
* Requirements:
*
* - `target` must be a contract.
* - calling `target` with `data` must not revert.
*
* _Available since v3.1._
*/functionfunctionCall(address target, bytesmemory data) internalreturns (bytesmemory) {
return functionCall(target, data, "Address: low-level call failed");
}
/**
* @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`], but with
* `errorMessage` as a fallback revert reason when `target` reverts.
*
* _Available since v3.1._
*/functionfunctionCall(address target,
bytesmemory data,
stringmemory errorMessage
) internalreturns (bytesmemory) {
return functionCallWithValue(target, data, 0, errorMessage);
}
/**
* @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
* but also transferring `value` wei to `target`.
*
* Requirements:
*
* - the calling contract must have an ETH balance of at least `value`.
* - the called Solidity function must be `payable`.
*
* _Available since v3.1._
*/functionfunctionCallWithValue(address target,
bytesmemory data,
uint256 value
) internalreturns (bytesmemory) {
return functionCallWithValue(target, data, value, "Address: low-level call with value failed");
}
/**
* @dev Same as {xref-Address-functionCallWithValue-address-bytes-uint256-}[`functionCallWithValue`], but
* with `errorMessage` as a fallback revert reason when `target` reverts.
*
* _Available since v3.1._
*/functionfunctionCallWithValue(address target,
bytesmemory data,
uint256 value,
stringmemory errorMessage
) internalreturns (bytesmemory) {
require(address(this).balance>= value, "Address: insufficient balance for call");
require(isContract(target), "Address: call to non-contract");
(bool success, bytesmemory returndata) = target.call{value: value}(data);
return verifyCallResult(success, returndata, errorMessage);
}
/**
* @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
* but performing a static call.
*
* _Available since v3.3._
*/functionfunctionStaticCall(address target, bytesmemory data) internalviewreturns (bytesmemory) {
return functionStaticCall(target, data, "Address: low-level static call failed");
}
/**
* @dev Same as {xref-Address-functionCall-address-bytes-string-}[`functionCall`],
* but performing a static call.
*
* _Available since v3.3._
*/functionfunctionStaticCall(address target,
bytesmemory data,
stringmemory errorMessage
) internalviewreturns (bytesmemory) {
require(isContract(target), "Address: static call to non-contract");
(bool success, bytesmemory returndata) = target.staticcall(data);
return verifyCallResult(success, returndata, errorMessage);
}
/**
* @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
* but performing a delegate call.
*
* _Available since v3.4._
*/functionfunctionDelegateCall(address target, bytesmemory data) internalreturns (bytesmemory) {
return functionDelegateCall(target, data, "Address: low-level delegate call failed");
}
/**
* @dev Same as {xref-Address-functionCall-address-bytes-string-}[`functionCall`],
* but performing a delegate call.
*
* _Available since v3.4._
*/functionfunctionDelegateCall(address target,
bytesmemory data,
stringmemory errorMessage
) internalreturns (bytesmemory) {
require(isContract(target), "Address: delegate call to non-contract");
(bool success, bytesmemory returndata) = target.delegatecall(data);
return verifyCallResult(success, returndata, errorMessage);
}
/**
* @dev Tool to verifies that a low level call was successful, and revert if it wasn't, either by bubbling the
* revert reason using the provided one.
*
* _Available since v4.3._
*/functionverifyCallResult(bool success,
bytesmemory returndata,
stringmemory errorMessage
) internalpurereturns (bytesmemory) {
if (success) {
return returndata;
} else {
// Look for revert reason and bubble it up if presentif (returndata.length>0) {
// The easiest way to bubble the revert reason is using memory via assemblyassembly {
let returndata_size :=mload(returndata)
revert(add(32, returndata), returndata_size)
}
} else {
revert(errorMessage);
}
}
}
}
Contract Source Code
File 2 of 19: AggregatorV3Interface.sol
// SPDX-License-Identifier: MITpragmasolidity ^0.8.0;interfaceAggregatorV3Interface{
functiondecimals() externalviewreturns (uint8);
functiondescription() externalviewreturns (stringmemory);
functionversion() externalviewreturns (uint256);
// getRoundData and latestRoundData should both raise "No data present"// if they do not have data to report, instead of returning unset values// which could be misinterpreted as actual reported values.functiongetRoundData(uint80 _roundId)
externalviewreturns (uint80 roundId,
int256 answer,
uint256 startedAt,
uint256 updatedAt,
uint80 answeredInRound
);
functionlatestRoundData()
externalviewreturns (uint80 roundId,
int256 answer,
uint256 startedAt,
uint256 updatedAt,
uint80 answeredInRound
);
}
Contract Source Code
File 3 of 19: ConfirmedOwner.sol
// SPDX-License-Identifier: MITpragmasolidity ^0.8.0;import"./ConfirmedOwnerWithProposal.sol";
/**
* @title The ConfirmedOwner contract
* @notice A contract with helpers for basic contract ownership.
*/contractConfirmedOwnerisConfirmedOwnerWithProposal{
constructor(address newOwner) ConfirmedOwnerWithProposal(newOwner, address(0)) {}
}
Contract Source Code
File 4 of 19: ConfirmedOwnerWithProposal.sol
// SPDX-License-Identifier: MITpragmasolidity ^0.8.0;import"./interfaces/OwnableInterface.sol";
/**
* @title The ConfirmedOwner contract
* @notice A contract with helpers for basic contract ownership.
*/contractConfirmedOwnerWithProposalisOwnableInterface{
addressprivate s_owner;
addressprivate s_pendingOwner;
eventOwnershipTransferRequested(addressindexedfrom, addressindexed to);
eventOwnershipTransferred(addressindexedfrom, addressindexed to);
constructor(address newOwner, address pendingOwner) {
require(newOwner !=address(0), "Cannot set owner to zero");
s_owner = newOwner;
if (pendingOwner !=address(0)) {
_transferOwnership(pendingOwner);
}
}
/**
* @notice Allows an owner to begin transferring ownership to a new address,
* pending.
*/functiontransferOwnership(address to) publicoverrideonlyOwner{
_transferOwnership(to);
}
/**
* @notice Allows an ownership transfer to be completed by the recipient.
*/functionacceptOwnership() externaloverride{
require(msg.sender== s_pendingOwner, "Must be proposed owner");
address oldOwner = s_owner;
s_owner =msg.sender;
s_pendingOwner =address(0);
emit OwnershipTransferred(oldOwner, msg.sender);
}
/**
* @notice Get the current owner
*/functionowner() publicviewoverridereturns (address) {
return s_owner;
}
/**
* @notice validate, transfer ownership, and emit relevant events
*/function_transferOwnership(address to) private{
require(to !=msg.sender, "Cannot transfer to self");
s_pendingOwner = to;
emit OwnershipTransferRequested(s_owner, to);
}
/**
* @notice validate access
*/function_validateOwnership() internalview{
require(msg.sender== s_owner, "Only callable by owner");
}
/**
* @notice Reverts if called by anyone other than the contract owner.
*/modifieronlyOwner() {
_validateOwnership();
_;
}
}
Contract Source Code
File 5 of 19: Context.sol
// SPDX-License-Identifier: MIT// OpenZeppelin Contracts v4.4.1 (utils/Context.sol)pragmasolidity ^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.
*/abstractcontractContext{
function_msgSender() internalviewvirtualreturns (address) {
returnmsg.sender;
}
function_msgData() internalviewvirtualreturns (bytescalldata) {
returnmsg.data;
}
}
// SPDX-License-Identifier: MIT// OpenZeppelin Contracts v4.4.1 (utils/structs/EnumerableSet.sol)pragmasolidity ^0.8.0;/**
* @dev Library for managing
* https://en.wikipedia.org/wiki/Set_(abstract_data_type)[sets] of primitive
* types.
*
* Sets have the following properties:
*
* - Elements are added, removed, and checked for existence in constant time
* (O(1)).
* - Elements are enumerated in O(n). No guarantees are made on the ordering.
*
* ```
* contract Example {
* // Add the library methods
* using EnumerableSet for EnumerableSet.AddressSet;
*
* // Declare a set state variable
* EnumerableSet.AddressSet private mySet;
* }
* ```
*
* As of v3.3.0, sets of type `bytes32` (`Bytes32Set`), `address` (`AddressSet`)
* and `uint256` (`UintSet`) are supported.
*/libraryEnumerableSet{
// To implement this library for multiple types with as little code// repetition as possible, we write it in terms of a generic Set type with// bytes32 values.// The Set implementation uses private functions, and user-facing// implementations (such as AddressSet) are just wrappers around the// underlying Set.// This means that we can only create new EnumerableSets for types that fit// in bytes32.structSet {
// Storage of set valuesbytes32[] _values;
// Position of the value in the `values` array, plus 1 because index 0// means a value is not in the set.mapping(bytes32=>uint256) _indexes;
}
/**
* @dev Add a value to a set. O(1).
*
* Returns true if the value was added to the set, that is if it was not
* already present.
*/function_add(Set storage set, bytes32 value) privatereturns (bool) {
if (!_contains(set, value)) {
set._values.push(value);
// The value is stored at length-1, but we add 1 to all indexes// and use 0 as a sentinel value
set._indexes[value] = set._values.length;
returntrue;
} else {
returnfalse;
}
}
/**
* @dev Removes a value from a set. O(1).
*
* Returns true if the value was removed from the set, that is if it was
* present.
*/function_remove(Set storage set, bytes32 value) privatereturns (bool) {
// We read and store the value's index to prevent multiple reads from the same storage slotuint256 valueIndex = set._indexes[value];
if (valueIndex !=0) {
// Equivalent to contains(set, value)// To delete an element from the _values array in O(1), we swap the element to delete with the last one in// the array, and then remove the last element (sometimes called as 'swap and pop').// This modifies the order of the array, as noted in {at}.uint256 toDeleteIndex = valueIndex -1;
uint256 lastIndex = set._values.length-1;
if (lastIndex != toDeleteIndex) {
bytes32 lastvalue = set._values[lastIndex];
// Move the last value to the index where the value to delete is
set._values[toDeleteIndex] = lastvalue;
// Update the index for the moved value
set._indexes[lastvalue] = valueIndex; // Replace lastvalue's index to valueIndex
}
// Delete the slot where the moved value was stored
set._values.pop();
// Delete the index for the deleted slotdelete set._indexes[value];
returntrue;
} else {
returnfalse;
}
}
/**
* @dev Returns true if the value is in the set. O(1).
*/function_contains(Set storage set, bytes32 value) privateviewreturns (bool) {
return set._indexes[value] !=0;
}
/**
* @dev Returns the number of values on the set. O(1).
*/function_length(Set storage set) privateviewreturns (uint256) {
return set._values.length;
}
/**
* @dev Returns the value stored at position `index` in the set. O(1).
*
* Note that there are no guarantees on the ordering of values inside the
* array, and it may change when more values are added or removed.
*
* Requirements:
*
* - `index` must be strictly less than {length}.
*/function_at(Set storage set, uint256 index) privateviewreturns (bytes32) {
return set._values[index];
}
/**
* @dev Return the entire set in an array
*
* WARNING: This operation will copy the entire storage to memory, which can be quite expensive. This is designed
* to mostly be used by view accessors that are queried without any gas fees. Developers should keep in mind that
* this function has an unbounded cost, and using it as part of a state-changing function may render the function
* uncallable if the set grows to a point where copying to memory consumes too much gas to fit in a block.
*/function_values(Set storage set) privateviewreturns (bytes32[] memory) {
return set._values;
}
// Bytes32SetstructBytes32Set {
Set _inner;
}
/**
* @dev Add a value to a set. O(1).
*
* Returns true if the value was added to the set, that is if it was not
* already present.
*/functionadd(Bytes32Set storage set, bytes32 value) internalreturns (bool) {
return _add(set._inner, value);
}
/**
* @dev Removes a value from a set. O(1).
*
* Returns true if the value was removed from the set, that is if it was
* present.
*/functionremove(Bytes32Set storage set, bytes32 value) internalreturns (bool) {
return _remove(set._inner, value);
}
/**
* @dev Returns true if the value is in the set. O(1).
*/functioncontains(Bytes32Set storage set, bytes32 value) internalviewreturns (bool) {
return _contains(set._inner, value);
}
/**
* @dev Returns the number of values in the set. O(1).
*/functionlength(Bytes32Set storage set) internalviewreturns (uint256) {
return _length(set._inner);
}
/**
* @dev Returns the value stored at position `index` in the set. O(1).
*
* Note that there are no guarantees on the ordering of values inside the
* array, and it may change when more values are added or removed.
*
* Requirements:
*
* - `index` must be strictly less than {length}.
*/functionat(Bytes32Set storage set, uint256 index) internalviewreturns (bytes32) {
return _at(set._inner, index);
}
/**
* @dev Return the entire set in an array
*
* WARNING: This operation will copy the entire storage to memory, which can be quite expensive. This is designed
* to mostly be used by view accessors that are queried without any gas fees. Developers should keep in mind that
* this function has an unbounded cost, and using it as part of a state-changing function may render the function
* uncallable if the set grows to a point where copying to memory consumes too much gas to fit in a block.
*/functionvalues(Bytes32Set storage set) internalviewreturns (bytes32[] memory) {
return _values(set._inner);
}
// AddressSetstructAddressSet {
Set _inner;
}
/**
* @dev Add a value to a set. O(1).
*
* Returns true if the value was added to the set, that is if it was not
* already present.
*/functionadd(AddressSet storage set, address value) internalreturns (bool) {
return _add(set._inner, bytes32(uint256(uint160(value))));
}
/**
* @dev Removes a value from a set. O(1).
*
* Returns true if the value was removed from the set, that is if it was
* present.
*/functionremove(AddressSet storage set, address value) internalreturns (bool) {
return _remove(set._inner, bytes32(uint256(uint160(value))));
}
/**
* @dev Returns true if the value is in the set. O(1).
*/functioncontains(AddressSet storage set, address value) internalviewreturns (bool) {
return _contains(set._inner, bytes32(uint256(uint160(value))));
}
/**
* @dev Returns the number of values in the set. O(1).
*/functionlength(AddressSet storage set) internalviewreturns (uint256) {
return _length(set._inner);
}
/**
* @dev Returns the value stored at position `index` in the set. O(1).
*
* Note that there are no guarantees on the ordering of values inside the
* array, and it may change when more values are added or removed.
*
* Requirements:
*
* - `index` must be strictly less than {length}.
*/functionat(AddressSet storage set, uint256 index) internalviewreturns (address) {
returnaddress(uint160(uint256(_at(set._inner, index))));
}
/**
* @dev Return the entire set in an array
*
* WARNING: This operation will copy the entire storage to memory, which can be quite expensive. This is designed
* to mostly be used by view accessors that are queried without any gas fees. Developers should keep in mind that
* this function has an unbounded cost, and using it as part of a state-changing function may render the function
* uncallable if the set grows to a point where copying to memory consumes too much gas to fit in a block.
*/functionvalues(AddressSet storage set) internalviewreturns (address[] memory) {
bytes32[] memory store = _values(set._inner);
address[] memory result;
assembly {
result := store
}
return result;
}
// UintSetstructUintSet {
Set _inner;
}
/**
* @dev Add a value to a set. O(1).
*
* Returns true if the value was added to the set, that is if it was not
* already present.
*/functionadd(UintSet storage set, uint256 value) internalreturns (bool) {
return _add(set._inner, bytes32(value));
}
/**
* @dev Removes a value from a set. O(1).
*
* Returns true if the value was removed from the set, that is if it was
* present.
*/functionremove(UintSet storage set, uint256 value) internalreturns (bool) {
return _remove(set._inner, bytes32(value));
}
/**
* @dev Returns true if the value is in the set. O(1).
*/functioncontains(UintSet storage set, uint256 value) internalviewreturns (bool) {
return _contains(set._inner, bytes32(value));
}
/**
* @dev Returns the number of values on the set. O(1).
*/functionlength(UintSet storage set) internalviewreturns (uint256) {
return _length(set._inner);
}
/**
* @dev Returns the value stored at position `index` in the set. O(1).
*
* Note that there are no guarantees on the ordering of values inside the
* array, and it may change when more values are added or removed.
*
* Requirements:
*
* - `index` must be strictly less than {length}.
*/functionat(UintSet storage set, uint256 index) internalviewreturns (uint256) {
returnuint256(_at(set._inner, index));
}
/**
* @dev Return the entire set in an array
*
* WARNING: This operation will copy the entire storage to memory, which can be quite expensive. This is designed
* to mostly be used by view accessors that are queried without any gas fees. Developers should keep in mind that
* this function has an unbounded cost, and using it as part of a state-changing function may render the function
* uncallable if the set grows to a point where copying to memory consumes too much gas to fit in a block.
*/functionvalues(UintSet storage set) internalviewreturns (uint256[] memory) {
bytes32[] memory store = _values(set._inner);
uint256[] memory result;
assembly {
result := store
}
return result;
}
}
Contract Source Code
File 8 of 19: KeeperBase.sol
// SPDX-License-Identifier: MITpragmasolidity ^0.8.0;contractKeeperBase{
errorOnlySimulatedBackend();
/**
* @notice method that allows it to be simulated via eth_call by checking that
* the sender is the zero address.
*/functionpreventExecution() internalview{
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.
*/modifiercannotExecute() {
preventExecution();
_;
}
}
Contract Source Code
File 9 of 19: KeeperCompatibleInterface.sol
// SPDX-License-Identifier: MITpragmasolidity ^0.8.0;interfaceKeeperCompatibleInterface{
/**
* @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`.
*/functioncheckUpkeep(bytescalldata checkData) externalreturns (bool upkeepNeeded, bytesmemory 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.
*/functionperformUpkeep(bytescalldata performData) external;
}
Contract Source Code
File 10 of 19: KeeperRegistry.sol
// SPDX-License-Identifier: MITpragmasolidity 0.8.13;import"@openzeppelin/contracts/utils/structs/EnumerableSet.sol";
import"@openzeppelin/contracts/utils/Address.sol";
import"@openzeppelin/contracts/security/Pausable.sol";
import"@openzeppelin/contracts/security/ReentrancyGuard.sol";
import"./KeeperBase.sol";
import"./ConfirmedOwner.sol";
import"./interfaces/TypeAndVersionInterface.sol";
import"./interfaces/AggregatorV3Interface.sol";
import"./interfaces/LinkTokenInterface.sol";
import"./interfaces/KeeperCompatibleInterface.sol";
import"./interfaces/KeeperRegistryInterface.sol";
import"./interfaces/MigratableKeeperRegistryInterface.sol";
import"./interfaces/UpkeepTranscoderInterface.sol";
import"./interfaces/ERC677ReceiverInterface.sol";
/**
* @notice Registry for adding work for Chainlink Keepers to perform on client
* contracts. Clients must support the Upkeep interface.
*/contractKeeperRegistryisTypeAndVersionInterface,
ConfirmedOwner,
KeeperBase,
ReentrancyGuard,
Pausable,
KeeperRegistryExecutableInterface,
MigratableKeeperRegistryInterface,
ERC677ReceiverInterface{
usingAddressforaddress;
usingEnumerableSetforEnumerableSet.UintSet;
addressprivateconstant ZERO_ADDRESS =address(0);
addressprivateconstant IGNORE_ADDRESS =0xFFfFfFffFFfffFFfFFfFFFFFffFFFffffFfFFFfF;
bytes4privateconstant CHECK_SELECTOR = KeeperCompatibleInterface.checkUpkeep.selector;
bytes4privateconstant PERFORM_SELECTOR = KeeperCompatibleInterface.performUpkeep.selector;
uint256privateconstant PERFORM_GAS_MIN =2_300;
uint256privateconstant CANCELATION_DELAY =50;
uint256privateconstant PERFORM_GAS_CUSHION =5_000;
uint256privateconstant REGISTRY_GAS_OVERHEAD =80_000;
uint256privateconstant PPB_BASE =1_000_000_000;
uint64privateconstant UINT64_MAX =2**64-1;
uint96privateconstant LINK_TOTAL_SUPPLY =1e27;
address[] private s_keeperList;
EnumerableSet.UintSet private s_upkeepIDs;
mapping(uint256=> Upkeep) private s_upkeep;
mapping(address=> KeeperInfo) private s_keeperInfo;
mapping(address=>address) private s_proposedPayee;
mapping(uint256=>bytes) private s_checkData;
mapping(address=> MigrationPermission) private s_peerRegistryMigrationPermission;
Storage private s_storage;
uint256private s_fallbackGasPrice; // not in config object for gas savingsuint256private s_fallbackLinkPrice; // not in config object for gas savingsuint96private s_ownerLinkBalance;
uint256private s_expectedLinkBalance;
addressprivate s_transcoder;
addressprivate s_registrar;
LinkTokenInterface publicimmutable LINK;
AggregatorV3Interface publicimmutable LINK_ETH_FEED;
AggregatorV3Interface publicimmutable FAST_GAS_FEED;
/**
* @notice versions:
* - 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
*/stringpublicconstantoverride typeAndVersion ="KeeperRegistry 1.2.0";
errorCannotCancel();
errorUpkeepNotActive();
errorMigrationNotPermitted();
errorUpkeepNotCanceled();
errorUpkeepNotNeeded();
errorNotAContract();
errorPaymentGreaterThanAllLINK();
errorOnlyActiveKeepers();
errorInsufficientFunds();
errorKeepersMustTakeTurns();
errorParameterLengthError();
errorOnlyCallableByOwnerOrAdmin();
errorOnlyCallableByLINKToken();
errorInvalidPayee();
errorDuplicateEntry();
errorValueNotChanged();
errorIndexOutOfRange();
errorTranscoderNotSet();
errorArrayHasNoEntries();
errorGasLimitOutsideRange();
errorOnlyCallableByPayee();
errorOnlyCallableByProposedPayee();
errorGasLimitCanOnlyIncrease();
errorOnlyCallableByAdmin();
errorOnlyCallableByOwnerOrRegistrar();
errorInvalidRecipient();
errorInvalidDataLength();
errorTargetCheckReverted(bytes reason);
enumMigrationPermission {
NONE,
OUTGOING,
INCOMING,
BIDIRECTIONAL
}
/**
* @notice storage of the registry, contains a mix of config and state data
*/structStorage {
uint32 paymentPremiumPPB;
uint32 flatFeeMicroLink;
uint24 blockCountPerTurn;
uint32 checkGasLimit;
uint24 stalenessSeconds;
uint16 gasCeilingMultiplier;
uint96 minUpkeepSpend; // 1 evm worduint32 maxPerformGas;
uint32 nonce; // 2 evm words
}
structUpkeep {
uint96 balance;
address lastKeeper; // 1 storage slot fulluint32 executeGas;
uint64 maxValidBlocknumber;
address target; // 2 storage slots fulluint96 amountSpent;
address admin; // 3 storage slots full
}
structKeeperInfo {
address payee;
uint96 balance;
bool active;
}
structPerformParams {
addressfrom;
uint256 id;
bytes performData;
uint256 maxLinkPayment;
uint256 gasLimit;
uint256 adjustedGasWei;
uint256 linkEth;
}
eventUpkeepRegistered(uint256indexed id, uint32 executeGas, address admin);
eventUpkeepPerformed(uint256indexed id,
boolindexed success,
addressindexedfrom,
uint96 payment,
bytes performData
);
eventUpkeepCanceled(uint256indexed id, uint64indexed atBlockHeight);
eventFundsAdded(uint256indexed id, addressindexedfrom, uint96 amount);
eventFundsWithdrawn(uint256indexed id, uint256 amount, address to);
eventOwnerFundsWithdrawn(uint96 amount);
eventUpkeepMigrated(uint256indexed id, uint256 remainingBalance, address destination);
eventUpkeepReceived(uint256indexed id, uint256 startingBalance, address importedFrom);
eventConfigSet(Config config);
eventKeepersUpdated(address[] keepers, address[] payees);
eventPaymentWithdrawn(addressindexed keeper, uint256indexed amount, addressindexed to, address payee);
eventPayeeshipTransferRequested(addressindexed keeper, addressindexedfrom, addressindexed to);
eventPayeeshipTransferred(addressindexed keeper, addressindexedfrom, addressindexed to);
eventUpkeepGasLimitSet(uint256indexed id, uint96 gasLimit);
/**
* @param link address of the LINK Token
* @param linkEthFeed address of the LINK/ETH price feed
* @param fastGasFeed address of the Fast Gas price feed
* @param config registry config settings
*/constructor(address link,
address linkEthFeed,
address fastGasFeed,
Config memory config
) ConfirmedOwner(msg.sender) {
LINK = LinkTokenInterface(link);
LINK_ETH_FEED = AggregatorV3Interface(linkEthFeed);
FAST_GAS_FEED = AggregatorV3Interface(fastGasFeed);
setConfig(config);
}
// ACTIONS/**
* @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 checkData data passed to the contract when checking for upkeep
*/functionregisterUpkeep(address target,
uint32 gasLimit,
address admin,
bytescalldata checkData
) externaloverrideonlyOwnerOrRegistrarreturns (uint256 id) {
id =uint256(keccak256(abi.encodePacked(blockhash(block.number-1), address(this), s_storage.nonce)));
_createUpkeep(id, target, gasLimit, admin, 0, checkData);
s_storage.nonce++;
emit UpkeepRegistered(id, gasLimit, admin);
return id;
}
/**
* @notice simulated by keepers via eth_call to see if the upkeep needs to be
* performed. If upkeep is needed, the call then simulates performUpkeep
* to make sure it succeeds. Finally, it returns the success status along with
* payment information and the perform data payload.
* @param id identifier of the upkeep to check
* @param from the address to simulate performing the upkeep from
*/functioncheckUpkeep(uint256 id, addressfrom)
externaloverridecannotExecutereturns (bytesmemory performData,
uint256 maxLinkPayment,
uint256 gasLimit,
uint256 adjustedGasWei,
uint256 linkEth
)
{
Upkeep memory upkeep = s_upkeep[id];
bytesmemory callData =abi.encodeWithSelector(CHECK_SELECTOR, s_checkData[id]);
(bool success, bytesmemory result) = upkeep.target.call{gas: s_storage.checkGasLimit}(callData);
if (!success) revert TargetCheckReverted(result);
(success, performData) =abi.decode(result, (bool, bytes));
if (!success) revert UpkeepNotNeeded();
PerformParams memory params = _generatePerformParams(from, id, performData, false);
_prePerformUpkeep(upkeep, params.from, params.maxLinkPayment);
return (performData, params.maxLinkPayment, params.gasLimit, params.adjustedGasWei, params.linkEth);
}
/**
* @notice executes the upkeep with the perform data returned from
* checkUpkeep, validates the keeper's permissions, and pays the keeper.
* @param id identifier of the upkeep to execute the data with.
* @param performData calldata parameter to be passed to the target upkeep.
*/functionperformUpkeep(uint256 id, bytescalldata performData)
externaloverridewhenNotPausedreturns (bool success)
{
return _performUpkeepWithParams(_generatePerformParams(msg.sender, id, performData, true));
}
/**
* @notice prevent an upkeep from being performed in the future
* @param id upkeep to be canceled
*/functioncancelUpkeep(uint256 id) externaloverride{
uint64 maxValid = s_upkeep[id].maxValidBlocknumber;
bool canceled = maxValid != UINT64_MAX;
bool isOwner =msg.sender== owner();
if (canceled &&!(isOwner && maxValid >block.number)) revert CannotCancel();
if (!isOwner &&msg.sender!= s_upkeep[id].admin) revert OnlyCallableByOwnerOrAdmin();
uint256 height =block.number;
if (!isOwner) {
height = height + CANCELATION_DELAY;
}
s_upkeep[id].maxValidBlocknumber =uint64(height);
s_upkeepIDs.remove(id);
emit UpkeepCanceled(id, uint64(height));
}
/**
* @notice adds LINK funding for an upkeep by transferring from the sender's
* LINK balance
* @param id upkeep to fund
* @param amount number of LINK to transfer
*/functionaddFunds(uint256 id, uint96 amount) externaloverrideonlyActiveUpkeep(id) {
s_upkeep[id].balance= s_upkeep[id].balance+ amount;
s_expectedLinkBalance = s_expectedLinkBalance + amount;
LINK.transferFrom(msg.sender, address(this), amount);
emit FundsAdded(id, msg.sender, amount);
}
/**
* @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
*/functiononTokenTransfer(address sender,
uint256 amount,
bytescalldata data
) external{
if (msg.sender!=address(LINK)) revert OnlyCallableByLINKToken();
if (data.length!=32) revert InvalidDataLength();
uint256 id =abi.decode(data, (uint256));
if (s_upkeep[id].maxValidBlocknumber != UINT64_MAX) revert UpkeepNotActive();
s_upkeep[id].balance= s_upkeep[id].balance+uint96(amount);
s_expectedLinkBalance = s_expectedLinkBalance + amount;
emit FundsAdded(id, sender, uint96(amount));
}
/**
* @notice removes funding from a canceled upkeep
* @param id upkeep to withdraw funds from
* @param to destination address for sending remaining funds
*/functionwithdrawFunds(uint256 id, address to) externalvalidRecipient(to) onlyUpkeepAdmin(id) {
if (s_upkeep[id].maxValidBlocknumber >block.number) revert UpkeepNotCanceled();
uint96 minUpkeepSpend = s_storage.minUpkeepSpend;
uint96 amountLeft = s_upkeep[id].balance;
uint96 amountSpent = s_upkeep[id].amountSpent;
uint96 cancellationFee =0;
// cancellationFee is supposed to be min(max(minUpkeepSpend - amountSpent,0), amountLeft)if (amountSpent < minUpkeepSpend) {
cancellationFee = minUpkeepSpend - amountSpent;
if (cancellationFee > amountLeft) {
cancellationFee = amountLeft;
}
}
uint96 amountToWithdraw = amountLeft - cancellationFee;
s_upkeep[id].balance=0;
s_ownerLinkBalance = s_ownerLinkBalance + cancellationFee;
s_expectedLinkBalance = s_expectedLinkBalance - amountToWithdraw;
emit FundsWithdrawn(id, amountToWithdraw, to);
LINK.transfer(to, amountToWithdraw);
}
/**
* @notice withdraws LINK funds collected through cancellation fees
*/functionwithdrawOwnerFunds() externalonlyOwner{
uint96 amount = s_ownerLinkBalance;
s_expectedLinkBalance = s_expectedLinkBalance - amount;
s_ownerLinkBalance =0;
emit OwnerFundsWithdrawn(amount);
LINK.transfer(msg.sender, amount);
}
/**
* @notice allows the admin of an upkeep to modify gas limit
* @param id upkeep to be change the gas limit for
* @param gasLimit new gas limit for the upkeep
*/functionsetUpkeepGasLimit(uint256 id, uint32 gasLimit) externaloverrideonlyActiveUpkeep(id) onlyUpkeepAdmin(id) {
if (gasLimit < PERFORM_GAS_MIN || gasLimit > s_storage.maxPerformGas) revert GasLimitOutsideRange();
s_upkeep[id].executeGas = gasLimit;
emit UpkeepGasLimitSet(id, gasLimit);
}
/**
* @notice recovers LINK funds improperly transferred to the registry
* @dev In principle this function’s execution cost could exceed block
* gas limit. However, in our anticipated deployment, the number of upkeeps and
* keepers will be low enough to avoid this problem.
*/functionrecoverFunds() externalonlyOwner{
uint256 total = LINK.balanceOf(address(this));
LINK.transfer(msg.sender, total - s_expectedLinkBalance);
}
/**
* @notice withdraws a keeper's payment, callable only by the keeper's payee
* @param from keeper address
* @param to address to send the payment to
*/functionwithdrawPayment(addressfrom, address to) externalvalidRecipient(to) {
KeeperInfo memory keeper = s_keeperInfo[from];
if (keeper.payee !=msg.sender) revert OnlyCallableByPayee();
s_keeperInfo[from].balance=0;
s_expectedLinkBalance = s_expectedLinkBalance - keeper.balance;
emit PaymentWithdrawn(from, keeper.balance, to, msg.sender);
LINK.transfer(to, keeper.balance);
}
/**
* @notice proposes the safe transfer of a keeper's payee to another address
* @param keeper address of the keeper to transfer payee role
* @param proposed address to nominate for next payeeship
*/functiontransferPayeeship(address keeper, address proposed) external{
if (s_keeperInfo[keeper].payee !=msg.sender) revert OnlyCallableByPayee();
if (proposed ==msg.sender) revert ValueNotChanged();
if (s_proposedPayee[keeper] != proposed) {
s_proposedPayee[keeper] = proposed;
emit PayeeshipTransferRequested(keeper, msg.sender, proposed);
}
}
/**
* @notice accepts the safe transfer of payee role for a keeper
* @param keeper address to accept the payee role for
*/functionacceptPayeeship(address keeper) external{
if (s_proposedPayee[keeper] !=msg.sender) revert OnlyCallableByProposedPayee();
address past = s_keeperInfo[keeper].payee;
s_keeperInfo[keeper].payee =msg.sender;
s_proposedPayee[keeper] = ZERO_ADDRESS;
emit PayeeshipTransferred(keeper, past, msg.sender);
}
/**
* @notice signals to keepers that they should not perform upkeeps until the
* contract has been unpaused
*/functionpause() externalonlyOwner{
_pause();
}
/**
* @notice signals to keepers that they can perform upkeeps once again after
* having been paused
*/functionunpause() externalonlyOwner{
_unpause();
}
// SETTERS/**
* @notice updates the configuration of the registry
* @param config registry config fields
*/functionsetConfig(Config memory config) publiconlyOwner{
if (config.maxPerformGas < s_storage.maxPerformGas) revert GasLimitCanOnlyIncrease();
s_storage = Storage({
paymentPremiumPPB: config.paymentPremiumPPB,
flatFeeMicroLink: config.flatFeeMicroLink,
blockCountPerTurn: config.blockCountPerTurn,
checkGasLimit: config.checkGasLimit,
stalenessSeconds: config.stalenessSeconds,
gasCeilingMultiplier: config.gasCeilingMultiplier,
minUpkeepSpend: config.minUpkeepSpend,
maxPerformGas: config.maxPerformGas,
nonce: s_storage.nonce
});
s_fallbackGasPrice = config.fallbackGasPrice;
s_fallbackLinkPrice = config.fallbackLinkPrice;
s_transcoder = config.transcoder;
s_registrar = config.registrar;
emit ConfigSet(config);
}
/**
* @notice update the list of keepers allowed to perform upkeep
* @param keepers list of addresses allowed to perform upkeep
* @param payees addresses corresponding to keepers who are allowed to
* move payments which have been accrued
*/functionsetKeepers(address[] calldata keepers, address[] calldata payees) externalonlyOwner{
if (keepers.length!= payees.length|| keepers.length<2) revert ParameterLengthError();
for (uint256 i =0; i < s_keeperList.length; i++) {
address keeper = s_keeperList[i];
s_keeperInfo[keeper].active =false;
}
for (uint256 i =0; i < keepers.length; i++) {
address keeper = keepers[i];
KeeperInfo storage s_keeper = s_keeperInfo[keeper];
address oldPayee = s_keeper.payee;
address newPayee = payees[i];
if (
(newPayee == ZERO_ADDRESS) || (oldPayee != ZERO_ADDRESS && oldPayee != newPayee && newPayee != IGNORE_ADDRESS)
) revert InvalidPayee();
if (s_keeper.active) revert DuplicateEntry();
s_keeper.active =true;
if (newPayee != IGNORE_ADDRESS) {
s_keeper.payee = newPayee;
}
}
s_keeperList = keepers;
emit KeepersUpdated(keepers, payees);
}
// GETTERS/**
* @notice read all of the details about an upkeep
*/functiongetUpkeep(uint256 id)
externalviewoverridereturns (address target,
uint32 executeGas,
bytesmemory checkData,
uint96 balance,
address lastKeeper,
address admin,
uint64 maxValidBlocknumber,
uint96 amountSpent
)
{
Upkeep memory reg = s_upkeep[id];
return (
reg.target,
reg.executeGas,
s_checkData[id],
reg.balance,
reg.lastKeeper,
reg.admin,
reg.maxValidBlocknumber,
reg.amountSpent
);
}
/**
* @notice retrieve active upkeep IDs
* @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 wholistic picture of the contract state
*/functiongetActiveUpkeepIDs(uint256 startIndex, uint256 maxCount) externalviewoverridereturns (uint256[] memory) {
uint256 maxIdx = s_upkeepIDs.length();
if (startIndex >= maxIdx) revert IndexOutOfRange();
if (maxCount ==0) {
maxCount = maxIdx - startIndex;
}
uint256[] memory ids =newuint256[](maxCount);
for (uint256 idx =0; idx < maxCount; idx++) {
ids[idx] = s_upkeepIDs.at(startIndex + idx);
}
return ids;
}
/**
* @notice read the current info about any keeper address
*/functiongetKeeperInfo(address query)
externalviewoverridereturns (address payee,
bool active,
uint96 balance
)
{
KeeperInfo memory keeper = s_keeperInfo[query];
return (keeper.payee, keeper.active, keeper.balance);
}
/**
* @notice read the current state of the registry
*/functiongetState()
externalviewoverridereturns (
State memory state,
Config memory config,
address[] memory keepers
)
{
Storage memory store = s_storage;
state.nonce = store.nonce;
state.ownerLinkBalance = s_ownerLinkBalance;
state.expectedLinkBalance = s_expectedLinkBalance;
state.numUpkeeps = s_upkeepIDs.length();
config.paymentPremiumPPB = store.paymentPremiumPPB;
config.flatFeeMicroLink = store.flatFeeMicroLink;
config.blockCountPerTurn = store.blockCountPerTurn;
config.checkGasLimit = store.checkGasLimit;
config.stalenessSeconds = store.stalenessSeconds;
config.gasCeilingMultiplier = store.gasCeilingMultiplier;
config.minUpkeepSpend = store.minUpkeepSpend;
config.maxPerformGas = store.maxPerformGas;
config.fallbackGasPrice = s_fallbackGasPrice;
config.fallbackLinkPrice = s_fallbackLinkPrice;
config.transcoder = s_transcoder;
config.registrar = s_registrar;
return (state, config, s_keeperList);
}
/**
* @notice calculates the minimum balance required for an upkeep to remain eligible
* @param id the upkeep id to calculate minimum balance for
*/functiongetMinBalanceForUpkeep(uint256 id) externalviewreturns (uint96 minBalance) {
return getMaxPaymentForGas(s_upkeep[id].executeGas);
}
/**
* @notice calculates the maximum payment for a given gas limit
* @param gasLimit the gas to calculate payment for
*/functiongetMaxPaymentForGas(uint256 gasLimit) publicviewreturns (uint96 maxPayment) {
(uint256 gasWei, uint256 linkEth) = _getFeedData();
uint256 adjustedGasWei = _adjustGasPrice(gasWei, false);
return _calculatePaymentAmount(gasLimit, adjustedGasWei, linkEth);
}
/**
* @notice retrieves the migration permission for a peer registry
*/functiongetPeerRegistryMigrationPermission(address peer) externalviewreturns (MigrationPermission) {
return s_peerRegistryMigrationPermission[peer];
}
/**
* @notice sets the peer registry migration permission
*/functionsetPeerRegistryMigrationPermission(address peer, MigrationPermission permission) externalonlyOwner{
s_peerRegistryMigrationPermission[peer] = permission;
}
/**
* @inheritdoc MigratableKeeperRegistryInterface
*/functionmigrateUpkeeps(uint256[] calldata ids, address destination) externaloverride{
if (
s_peerRegistryMigrationPermission[destination] != MigrationPermission.OUTGOING &&
s_peerRegistryMigrationPermission[destination] != MigrationPermission.BIDIRECTIONAL
) revert MigrationNotPermitted();
if (s_transcoder == ZERO_ADDRESS) revert TranscoderNotSet();
if (ids.length==0) revert ArrayHasNoEntries();
uint256 id;
Upkeep memory upkeep;
uint256 totalBalanceRemaining;
bytes[] memory checkDatas =newbytes[](ids.length);
Upkeep[] memory upkeeps =new Upkeep[](ids.length);
for (uint256 idx =0; idx < ids.length; idx++) {
id = ids[idx];
upkeep = s_upkeep[id];
if (upkeep.admin !=msg.sender) revert OnlyCallableByAdmin();
if (upkeep.maxValidBlocknumber != UINT64_MAX) revert UpkeepNotActive();
upkeeps[idx] = upkeep;
checkDatas[idx] = s_checkData[id];
totalBalanceRemaining = totalBalanceRemaining + upkeep.balance;
delete s_upkeep[id];
delete s_checkData[id];
s_upkeepIDs.remove(id);
emit UpkeepMigrated(id, upkeep.balance, destination);
}
s_expectedLinkBalance = s_expectedLinkBalance - totalBalanceRemaining;
bytesmemory encodedUpkeeps =abi.encode(ids, upkeeps, checkDatas);
MigratableKeeperRegistryInterface(destination).receiveUpkeeps(
UpkeepTranscoderInterface(s_transcoder).transcodeUpkeeps(
UpkeepFormat.V1,
MigratableKeeperRegistryInterface(destination).upkeepTranscoderVersion(),
encodedUpkeeps
)
);
LINK.transfer(destination, totalBalanceRemaining);
}
/**
* @inheritdoc MigratableKeeperRegistryInterface
*/
UpkeepFormat publicconstant upkeepTranscoderVersion = UpkeepFormat.V1;
/**
* @inheritdoc MigratableKeeperRegistryInterface
*/functionreceiveUpkeeps(bytescalldata encodedUpkeeps) externaloverride{
if (
s_peerRegistryMigrationPermission[msg.sender] != MigrationPermission.INCOMING &&
s_peerRegistryMigrationPermission[msg.sender] != MigrationPermission.BIDIRECTIONAL
) revert MigrationNotPermitted();
(uint256[] memory ids, Upkeep[] memory upkeeps, bytes[] memory checkDatas) =abi.decode(
encodedUpkeeps,
(uint256[], Upkeep[], bytes[])
);
for (uint256 idx =0; idx < ids.length; idx++) {
_createUpkeep(
ids[idx],
upkeeps[idx].target,
upkeeps[idx].executeGas,
upkeeps[idx].admin,
upkeeps[idx].balance,
checkDatas[idx]
);
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
*/function_createUpkeep(uint256 id,
address target,
uint32 gasLimit,
address admin,
uint96 balance,
bytesmemory checkData
) internalwhenNotPaused{
if (!target.isContract()) revert NotAContract();
if (gasLimit < PERFORM_GAS_MIN || gasLimit > s_storage.maxPerformGas) revert GasLimitOutsideRange();
s_upkeep[id] = Upkeep({
target: target,
executeGas: gasLimit,
balance: balance,
admin: admin,
maxValidBlocknumber: UINT64_MAX,
lastKeeper: ZERO_ADDRESS,
amountSpent: 0
});
s_expectedLinkBalance = s_expectedLinkBalance + balance;
s_checkData[id] = checkData;
s_upkeepIDs.add(id);
}
/**
* @dev retrieves feed data for fast gas/eth and link/eth 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() privateviewreturns (uint256 gasWei, uint256 linkEth) {
uint32 stalenessSeconds = s_storage.stalenessSeconds;
bool staleFallback = stalenessSeconds >0;
uint256 timestamp;
int256 feedValue;
(, feedValue, , timestamp, ) = FAST_GAS_FEED.latestRoundData();
if ((staleFallback && stalenessSeconds <block.timestamp- timestamp) || feedValue <=0) {
gasWei = s_fallbackGasPrice;
} else {
gasWei =uint256(feedValue);
}
(, feedValue, , timestamp, ) = LINK_ETH_FEED.latestRoundData();
if ((staleFallback && stalenessSeconds <block.timestamp- timestamp) || feedValue <=0) {
linkEth = s_fallbackLinkPrice;
} else {
linkEth =uint256(feedValue);
}
return (gasWei, linkEth);
}
/**
* @dev calculates LINK paid for gas spent plus a configure premium percentage
*/function_calculatePaymentAmount(uint256 gasLimit,
uint256 gasWei,
uint256 linkEth
) privateviewreturns (uint96 payment) {
uint256 weiForGas = gasWei * (gasLimit + REGISTRY_GAS_OVERHEAD);
uint256 premium = PPB_BASE + s_storage.paymentPremiumPPB;
uint256 total = ((weiForGas * (1e9) * (premium)) / (linkEth)) + (uint256(s_storage.flatFeeMicroLink) * (1e12));
if (total > LINK_TOTAL_SUPPLY) revert PaymentGreaterThanAllLINK();
returnuint96(total); // LINK_TOTAL_SUPPLY < UINT96_MAX
}
/**
* @dev calls target address with exactly gasAmount gas and data as calldata
* or reverts if at least gasAmount gas is not available
*/function_callWithExactGas(uint256 gasAmount,
address target,
bytesmemory data
) privatereturns (bool success) {
assembly {
let g :=gas()
// Compute g -= PERFORM_GAS_CUSHION and check for underflowiflt(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)ifiszero(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 sameifiszero(extcodesize(target)) {
revert(0, 0)
}
// call and return whether we succeeded. ignore return data
success :=call(gasAmount, target, 0, add(data, 0x20), mload(data), 0, 0)
}
return success;
}
/**
* @dev calls the Upkeep target with the performData param passed in by the
* keeper and the exact gas required by the Upkeep
*/function_performUpkeepWithParams(PerformParams memory params)
privatenonReentrantvalidUpkeep(params.id)
returns (bool success)
{
Upkeep memory upkeep = s_upkeep[params.id];
_prePerformUpkeep(upkeep, params.from, params.maxLinkPayment);
uint256 gasUsed =gasleft();
bytesmemory callData =abi.encodeWithSelector(PERFORM_SELECTOR, params.performData);
success = _callWithExactGas(params.gasLimit, upkeep.target, callData);
gasUsed = gasUsed -gasleft();
uint96 payment = _calculatePaymentAmount(gasUsed, params.adjustedGasWei, params.linkEth);
s_upkeep[params.id].balance= s_upkeep[params.id].balance- payment;
s_upkeep[params.id].amountSpent = s_upkeep[params.id].amountSpent + payment;
s_upkeep[params.id].lastKeeper = params.from;
s_keeperInfo[params.from].balance= s_keeperInfo[params.from].balance+ payment;
emit UpkeepPerformed(params.id, success, params.from, payment, params.performData);
return success;
}
/**
* @dev ensures all required checks are passed before an upkeep is performed
*/function_prePerformUpkeep(
Upkeep memory upkeep,
addressfrom,
uint256 maxLinkPayment
) privateview{
if (!s_keeperInfo[from].active) revert OnlyActiveKeepers();
if (upkeep.balance< maxLinkPayment) revert InsufficientFunds();
if (upkeep.lastKeeper ==from) revert KeepersMustTakeTurns();
}
/**
* @dev adjusts the gas price to min(ceiling, tx.gasprice) or just uses the ceiling if tx.gasprice is disabled
*/function_adjustGasPrice(uint256 gasWei, bool useTxGasPrice) privateviewreturns (uint256 adjustedPrice) {
adjustedPrice = gasWei * s_storage.gasCeilingMultiplier;
if (useTxGasPrice &&tx.gasprice< adjustedPrice) {
adjustedPrice =tx.gasprice;
}
}
/**
* @dev generates a PerformParams struct for use in _performUpkeepWithParams()
*/function_generatePerformParams(addressfrom,
uint256 id,
bytesmemory performData,
bool useTxGasPrice
) privateviewreturns (PerformParams memory) {
uint256 gasLimit = s_upkeep[id].executeGas;
(uint256 gasWei, uint256 linkEth) = _getFeedData();
uint256 adjustedGasWei = _adjustGasPrice(gasWei, useTxGasPrice);
uint96 maxLinkPayment = _calculatePaymentAmount(gasLimit, adjustedGasWei, linkEth);
return
PerformParams({
from: from,
id: id,
performData: performData,
maxLinkPayment: maxLinkPayment,
gasLimit: gasLimit,
adjustedGasWei: adjustedGasWei,
linkEth: linkEth
});
}
// MODIFIERS/**
* @dev ensures a upkeep is valid
*/modifiervalidUpkeep(uint256 id) {
if (s_upkeep[id].maxValidBlocknumber <=block.number) revert UpkeepNotActive();
_;
}
/**
* @dev Reverts if called by anyone other than the admin of upkeep #id
*/modifieronlyUpkeepAdmin(uint256 id) {
if (msg.sender!= s_upkeep[id].admin) revert OnlyCallableByAdmin();
_;
}
/**
* @dev Reverts if called on a cancelled upkeep
*/modifieronlyActiveUpkeep(uint256 id) {
if (s_upkeep[id].maxValidBlocknumber != UINT64_MAX) revert UpkeepNotActive();
_;
}
/**
* @dev ensures that burns don't accidentally happen by sending to the zero
* address
*/modifiervalidRecipient(address to) {
if (to == ZERO_ADDRESS) revert InvalidRecipient();
_;
}
/**
* @dev Reverts if called by anyone other than the contract owner or registrar.
*/modifieronlyOwnerOrRegistrar() {
if (msg.sender!= owner() &&msg.sender!= s_registrar) revert OnlyCallableByOwnerOrRegistrar();
_;
}
}
Contract Source Code
File 11 of 19: KeeperRegistryInterface.sol
// SPDX-License-Identifier: MITpragmasolidity ^0.8.0;/**
* @notice config 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 blockCountPerTurn number of blocks each oracle has during their turn to
* perform upkeep before it will be the next keeper's turn to submit
* @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
*/structConfig {
uint32 paymentPremiumPPB;
uint32 flatFeeMicroLink; // min 0.000001 LINK, max 4294 LINKuint24 blockCountPerTurn;
uint32 checkGasLimit;
uint24 stalenessSeconds;
uint16 gasCeilingMultiplier;
uint96 minUpkeepSpend;
uint32 maxPerformGas;
uint256 fallbackGasPrice;
uint256 fallbackLinkPrice;
address transcoder;
address registrar;
}
/**
* @notice config of the registry
* @dev only used in params and return values
* @member nonce used for ID generation
* @ownerLinkBalance withdrawable balance of LINK by contract owner
* @numUpkeeps total number of upkeeps on the registry
*/structState {
uint32 nonce;
uint96 ownerLinkBalance;
uint256 expectedLinkBalance;
uint256 numUpkeeps;
}
interfaceKeeperRegistryBaseInterface{
functionregisterUpkeep(address target,
uint32 gasLimit,
address admin,
bytescalldata checkData
) externalreturns (uint256 id);
functionperformUpkeep(uint256 id, bytescalldata performData) externalreturns (bool success);
functioncancelUpkeep(uint256 id) external;
functionaddFunds(uint256 id, uint96 amount) external;
functionsetUpkeepGasLimit(uint256 id, uint32 gasLimit) external;
functiongetUpkeep(uint256 id)
externalviewreturns (address target,
uint32 executeGas,
bytesmemory checkData,
uint96 balance,
address lastKeeper,
address admin,
uint64 maxValidBlocknumber,
uint96 amountSpent
);
functiongetActiveUpkeepIDs(uint256 startIndex, uint256 maxCount) externalviewreturns (uint256[] memory);
functiongetKeeperInfo(address query)
externalviewreturns (address payee,
bool active,
uint96 balance
);
functiongetState()
externalviewreturns (
State memory,
Config memory,
address[] memory);
}
/**
* @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.
*/interfaceKeeperRegistryInterfaceisKeeperRegistryBaseInterface{
functioncheckUpkeep(uint256 upkeepId, addressfrom)
externalviewreturns (bytesmemory performData,
uint256 maxLinkPayment,
uint256 gasLimit,
int256 gasWei,
int256 linkEth
);
}
interfaceKeeperRegistryExecutableInterfaceisKeeperRegistryBaseInterface{
functioncheckUpkeep(uint256 upkeepId, addressfrom)
externalreturns (bytesmemory performData,
uint256 maxLinkPayment,
uint256 gasLimit,
uint256 adjustedGasWei,
uint256 linkEth
);
}
File 13 of 19: MigratableKeeperRegistryInterface.sol
// SPDX-License-Identifier: MITpragmasolidity ^0.8.0;import"../UpkeepFormat.sol";
interfaceMigratableKeeperRegistryInterface{
/**
* @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
*/functionmigrateUpkeeps(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
*/functionreceiveUpkeeps(bytescalldata encodedUpkeeps) external;
/**
* @notice Specifies the version of upkeep data that this registry requires in order to import
*/functionupkeepTranscoderVersion() externalreturns (UpkeepFormat version);
}
// SPDX-License-Identifier: MIT// OpenZeppelin Contracts v4.4.1 (security/Pausable.sol)pragmasolidity ^0.8.0;import"../utils/Context.sol";
/**
* @dev Contract module which allows children to implement an emergency stop
* mechanism that can be triggered by an authorized account.
*
* This module is used through inheritance. It will make available the
* modifiers `whenNotPaused` and `whenPaused`, which can be applied to
* the functions of your contract. Note that they will not be pausable by
* simply including this module, only once the modifiers are put in place.
*/abstractcontractPausableisContext{
/**
* @dev Emitted when the pause is triggered by `account`.
*/eventPaused(address account);
/**
* @dev Emitted when the pause is lifted by `account`.
*/eventUnpaused(address account);
boolprivate _paused;
/**
* @dev Initializes the contract in unpaused state.
*/constructor() {
_paused =false;
}
/**
* @dev Returns true if the contract is paused, and false otherwise.
*/functionpaused() publicviewvirtualreturns (bool) {
return _paused;
}
/**
* @dev Modifier to make a function callable only when the contract is not paused.
*
* Requirements:
*
* - The contract must not be paused.
*/modifierwhenNotPaused() {
require(!paused(), "Pausable: paused");
_;
}
/**
* @dev Modifier to make a function callable only when the contract is paused.
*
* Requirements:
*
* - The contract must be paused.
*/modifierwhenPaused() {
require(paused(), "Pausable: not paused");
_;
}
/**
* @dev Triggers stopped state.
*
* Requirements:
*
* - The contract must not be paused.
*/function_pause() internalvirtualwhenNotPaused{
_paused =true;
emit Paused(_msgSender());
}
/**
* @dev Returns to normal state.
*
* Requirements:
*
* - The contract must be paused.
*/function_unpause() internalvirtualwhenPaused{
_paused =false;
emit Unpaused(_msgSender());
}
}
Contract Source Code
File 16 of 19: ReentrancyGuard.sol
// SPDX-License-Identifier: MIT// OpenZeppelin Contracts v4.4.1 (security/ReentrancyGuard.sol)pragmasolidity ^0.8.0;/**
* @dev Contract module that helps prevent reentrant calls to a function.
*
* Inheriting from `ReentrancyGuard` will make the {nonReentrant} modifier
* available, which can be applied to functions to make sure there are no nested
* (reentrant) calls to them.
*
* Note that because there is a single `nonReentrant` guard, functions marked as
* `nonReentrant` may not call one another. This can be worked around by making
* those functions `private`, and then adding `external` `nonReentrant` entry
* points to them.
*
* TIP: If you would like to learn more about reentrancy and alternative ways
* to protect against it, check out our blog post
* https://blog.openzeppelin.com/reentrancy-after-istanbul/[Reentrancy After Istanbul].
*/abstractcontractReentrancyGuard{
// Booleans are more expensive than uint256 or any type that takes up a full// word because each write operation emits an extra SLOAD to first read the// slot's contents, replace the bits taken up by the boolean, and then write// back. This is the compiler's defense against contract upgrades and// pointer aliasing, and it cannot be disabled.// The values being non-zero value makes deployment a bit more expensive,// but in exchange the refund on every call to nonReentrant will be lower in// amount. Since refunds are capped to a percentage of the total// transaction's gas, it is best to keep them low in cases like this one, to// increase the likelihood of the full refund coming into effect.uint256privateconstant _NOT_ENTERED =1;
uint256privateconstant _ENTERED =2;
uint256private _status;
constructor() {
_status = _NOT_ENTERED;
}
/**
* @dev Prevents a contract from calling itself, directly or indirectly.
* Calling a `nonReentrant` function from another `nonReentrant`
* function is not supported. It is possible to prevent this from happening
* by making the `nonReentrant` function external, and making it call a
* `private` function that does the actual work.
*/modifiernonReentrant() {
// On the first call to nonReentrant, _notEntered will be truerequire(_status != _ENTERED, "ReentrancyGuard: reentrant call");
// Any calls to nonReentrant after this point will fail
_status = _ENTERED;
_;
// By storing the original value once again, a refund is triggered (see// https://eips.ethereum.org/EIPS/eip-2200)
_status = _NOT_ENTERED;
}
}