Search
Use custom logic to allow Keepers to determine when to execute your smart contract functions.
Learn how to make smart contracts Keepers-compatible with the KeeperCompatibleInterface
and its functions.
Topics
Keepers-compatible contracts must meet the following requirements:
KeeperCompatible.sol
. You can refer to the Chainlink Contracts on GitHub to find the latest version.KeeperCompatibleInterface
from the library to ensure your checkUpkeep
and performUpkeep
function definitions match the definitions expected by the Keepers Network.checkUpkeep
function that contains the logic that will be executed off-chain to see if performUpkeep
should be executed. checkUpkeep
can use on-chain data and a specified checkData
parameter to perform complex calculations off-chain and then send the result to performUpkeep
as performData
.performUpkeep
function that will be executed on-chain when checkUpkeep
returns true
. Because performUpkeep
is external, users are advised to revalidate conditions and performData.Use these elements to create a Keepers-compatible contract that will automatically increment a counter after every updateInterval
seconds. After you register the contract as an upkeep, the Keepers Network simulates our checkUpkeep
off-chain during every block to determine if the updateInterval
time has passed since the last increment (timestamp). When checkUpkeep
returns true, the Keepers Network calls performUpkeep
on-chain and increments the counter. This cycle repeats until the upkeep is cancelled or runs out of funding.
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.7;
// KeeperCompatible.sol imports the functions from both ./KeeperBase.sol and
// ./interfaces/KeeperCompatibleInterface.sol
import "@chainlink/contracts/src/v0.8/KeeperCompatible.sol";
/**
* THIS IS AN EXAMPLE CONTRACT THAT USES HARDCODED VALUES FOR CLARITY.
* THIS IS AN EXAMPLE CONTRACT THAT USES UN-AUDITED CODE.
* DO NOT USE THIS CODE IN PRODUCTION.
*/
contract Counter is KeeperCompatibleInterface {
/**
* Public counter variable
*/
uint public counter;
/**
* Use an interval in seconds and a timestamp to slow execution of Upkeep
*/
uint public immutable interval;
uint public lastTimeStamp;
constructor(uint updateInterval) {
interval = updateInterval;
lastTimeStamp = block.timestamp;
counter = 0;
}
function checkUpkeep(bytes calldata /* checkData */) external view override returns (bool upkeepNeeded, bytes memory /* performData */) {
upkeepNeeded = (block.timestamp - lastTimeStamp) > interval;
// We don't use the checkData in this example. The checkData is defined when the Upkeep was registered.
}
function performUpkeep(bytes calldata /* performData */) external override {
//We highly recommend revalidating the upkeep in the performUpkeep function
if ((block.timestamp - lastTimeStamp) > interval ) {
lastTimeStamp = block.timestamp;
counter = counter + 1;
}
// We don't use the performData in this example. The performData is generated by the Keeper's call to your checkUpkeep function
}
}
Compile and deploy your own Keepers Counter onto a supported Testnet.
Counter
smart contract in the injected web3
environment. When deploying the contract, specify the updateInterval
value. For this example, set a short interval of 60. This is the interval at which the performUpkeep
function will be called.To see more complex examples, go to the utility contracts page.
We will now look at each function in a Keepers-compatible contract in detail.
Function Name | Description |
---|---|
checkUpkeep | Runs off-chain at every block to determine if the performUpkeep function should be called on-chain. |
performUpkeep | Contains the logic that should be executed on-chain when checkUpkeep returns true. |
checkUpkeep
functionThis function contains the logic that runs off-chain during every block as an eth_call
(link) to determine if performUpkeep
should be executed on-chain. To reduce on-chain gas usage, attempt to do your gas intensive calculations off-chain in checkUpkeep
and pass the result to performUpkeep
on-chain.
Gas limits for checkUpkeep
The checkUpkeep
function is subject to the checkGasLimit
in the registry configuration.
Because checkUpkeep
is only off-chain in simulation it is best to treat this as a view
function and not modify any state. This might not always be possible if you want to use more advanced Solidity features like DelegateCall
(link). It is a best practice to import the KeeperCompatible.sol
(link) contract and use the cannotExecute
modifier to ensure that the method can be used only for simulation purposes.
function checkUpkeep(
bytes calldata checkData
)
external
view
override
returns (
bool upkeepNeeded,
bytes memory performData
);
Below are the parameters and return values of the checkUpkeep
function. Click each value to learn more about its design patterns and best practices:
Parameters:
checkData
: Fixed and specified at upkeep registration and used in every checkUpkeep
. Can be empty (0x).Return Values:
upkeepNeeded
: Boolean that when True will trigger the on-chain performUpkeep
call.performData
: Bytes that will be used as input parameter when calling performUpkeep
. If you would like to encode data to decode later, try abi.encode
.checkData
You can pass information into your checkUpkeep
function from your upkeep registration to execute different code paths. For example, to check the balance on an specific address, set the checkData
to abi encode of the address. To learn how to create flexible upkeeps with checkData, please see out flexible upkeeps page.
function checkUpkeep(bytes calldata checkData) public view returns(bool, bytes memory) {
address wallet = abi.decode(checkData, (address));
return (wallet.balance < 1 ether, bytes(""));
}
Tips on using checkData
:
Managing unbounded upkeeps: Limit the problem set of your on-chain execution by creating a range bound for your upkeep to check and perform. This allows you to keep within predefined gas limits, which creates a predictable upper bound gas cost on your transactions. Break apart your problem into multiple upkeep registrations to limit the scope of work.
Example: You could create an upkeep for each subset of addresses that you want to service. The ranges could be 0 to 49, 50 to 99, and 100 to 149.
Managing code paths: Pass in data to your checkUpkeep
to make your contract logic go down different code paths. This can be used in creative ways based on your use case needs.
Example: You could support multiple types of upkeep within a single contract and pass a function selector through the checkData
function.
performData
The response from checkUpkeep
is passed to the performUpkeep
function as performData
. This allows you to perform complex and gas intensive calculations as a simulation off-chain and only pass the needed data on-chain.
You can create a highly flexible off-chain computation infrastructure that can perform precise actions on-chain by using checkData
and performData
. Both of these computations are entirely programmable.
performUpkeep
functionWhen checkUpkeep
returns upkeepNeeded == true
, the Keeper node broadcasts a transaction to the blockchain to execute your performUpkeep
function on-chain with performData
as an input.
Gas limits for performUpkeep
During registration you have to specify the maximum gas limit that we should allow your contract to use. We simulate performUpkeep
during checkUpkeep
and if the gas exceeds this limit the function will not execute on-chain. One method to determine your upkeep's gas limit is to simulate the performUpkeep
function and add enough overhead to take into account increases that might happen due to changes in performData
or on-chain data. The gas limit you specify cannot exceed the callGasLimit
in the configuration of the registry.
Ensure that your performUpkeep
is idempotent. Your performUpkeep
function should change state such that checkUpkeep
will not return true
for the same subset of work once said work is complete. Otherwise the Upkeep will remain eligible and result in multiple performances by the Keeper Network on the exactly same subset of work. As a best practice, always revalidate conditions for your upkeep at the start of your performUpkeep
function.
function performUpkeep(
bytes calldata performData
) external override;
Parameters:
performData
: 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 always be validated against the contract's current state.performData
You can perform complex and broad off-chain computation, then execute on-chain state changes on a subset that meet your conditions. This can be done by passing the appropriate inputs within performData
based on the results from your checkUpkeep
. This pattern can greatly reduce your on-chain gas usage by narrowing the scope of work intelligently in your own Solidity code.
Identify a list of addresses that require work: You might have a number of addresses that you are validating for conditions before your contract takes an action. Doing this on-chain can be expensive. Filter the list of addresses by validating the necessary conditions within your checkUpkeep
function. Then, pass the addresses that meets the condition through the performData
function.
For example, if you have a "top up" contract that ensures several hundred account balances never decrease below a threshold, pass the list of accounts that meet the conditions so that the performUpkeep
function validates and tops up only a small subset of the accounts.
Identify the subset of states that must be updated: If your contract maintains complicated objects such as arrays and structs, or stores a lot of data, you should read through your storage objects within your checkUpkeep
and run your proprietary logic to determine if they require updates or maintenance. After that is complete, you can pass the known list of objects that require updates through the performData
function.
Note on arrays:
Make sure the checkdata array size is correct. Vyper does not support dynamic arrays.
You can find a KeepersConsumer
example here. Read the apeworx-starter-kit README to learn how to run the example.
performUpkeep
We recommend that you revalidate the conditions and data in performUpkeep
before work is performed. By default the performUpkeep
is external
and thus any party can call it, so revalidation is recommended. If you send data from your checkUpkeep
to your performUpkeep
and this data drives a critical function, please ensure you put adequate checks in place.
Some actions must be performed only when specific conditions are met. Check all of the preconditions within performUpkeep
to ensure that state change occurs only when necessary.
In this pattern, it is undesirable for the state change to occur until the next time the Upkeep is checked by the network and the conditions are met. It is a best practice to stop any state change or effects by performing the same checks or similar checks that you use in checkUpkeep
. These checks validate the conditions before doing the work.
For example, if you have a contract where you create a timer in checkUpkeep
that is designed to start a game at a specific time, validate the condition to ensure third-party calls to your performUpkeep
function do not start the game at a different time.
Some actions must be performed using data you intend to use. Revalidate that the performData
is allowed before execution.
For example, if you have a performUpkeep
that funds a wallet and the address of the wallet is received via the performData
parameter, ensure you have a list of permissable addresses to compare against to prevent third-party calling your function to send money to their address.
Sometimes actions must be performed when conditions are met, but performing actions when conditions are not met is still acceptable. Condition checks within performUpkeep
might not be required, but it can still be a good practice to short circuit expensive and unnecessary on-chain processing when it is not required.
It might be desirable to call performUpkeep
when the checkUpkeep
conditions haven't yet been tested by Chainlink Keepers, so any specific checks that you perform are entirely use case specific.
As with all smart contract testing, it is important to test the boundaries of your smart contract in order to ensure it operates as intended. Similarly, it is important to make sure your Keepers-compatible contract operates within the parameters of the KeeperRegistry
.
Test all of your mission-critical contracts, and stress-test the contract to confirm the performance and correct operation of your use case under load and adversarial conditions. The Chainlink Keeper Network will continue to operate under stress, but so should your contract. For a list of supported Testnet blockchains, please review the supported networks page.