Smart contract resolver
Here is how a simple smart contract resolver looks like. This resolver is responsible for checking the lastExecutedstate of a simple counter in another smart contract. And if 5 minutes have passed since the last execution, this resolver will prompt Gelato to execute.
CounterResolver.sol
1
contract CounterResolver is IResolver {
2
3
address public immutable COUNTER;
4
5
constructor(address _counter) {
6
COUNTER = _counter;
7
}
8
9
function checker()
10
external
11
view
12
override
13
returns (bool canExec, bytes memory execPayload)
14
{
15
uint256 lastExecuted = ICounter(COUNTER).lastExecuted();
16
17
// solhint-disable not-rely-on-time
18
canExec = (block.timestamp - lastExecuted) > 300;
19
20
execPayload = abi.encodeWithSelector(
21
ICounter.increaseCount.selector,
22
uint256(100)
23
);
24
}
25
}
Copied!
A resolver should always return 2 things:
  1. 1.
    bool canExec : wether Gelato should execute the task.
  2. 2.
    bytes execPayload : data that executors should use for the execution.
You can find the code for Counter and CounterResolver here.
Resolvers can also accept arguments. This is useful as you can potentially "re-use" your resolver when creating multiple tasks.
Using the same example as above:
1
function checker(address counterAddress)
2
external
3
view
4
override
5
returns (bool canExec, bytes memory execPayload)
6
{
7
uint256 lastExecuted = ICounter(counterAddress).lastExecuted();
8
9
// solhint-disable not-rely-on-time
10
canExec = (block.timestamp - lastExecuted) > 300;
11
12
execPayload = abi.encodeWithSelector(
13
ICounter.increaseCount.selector,
14
uint256(100)
15
);
16
}
Copied!
Instead of a hardcoded COUNTER address, you can pass counterAddress as an argument.

Common patterns and best practices

PokeMeReady

PokeMeReady is purely for security purposes which restricts the function call to only Gelato. This can prevent flash loan attacks. The Gelato contract already does a check if executors are EOA.
1
modifier onlyPokeMe() {
2
require(msg.sender == pokeMe, "Only PokeMe");
3
_;
4
}
Copied!
Note: This step is optional.

Checking over an array

Let's say you want to automate your yield compounding for multiple pools. To avoid creating multiple tasks and having different resolvers, you could keep a list of the pools and iterate through each of the pools to see if they should be compounded. Take a look at this example.
1
function checker()
2
external
3
view
4
returns (bool canExec, bytes memory execPayload)
5
{
6
uint256 delay = harvester.delay();
7
8
for (uint256 i = 0; i < vaults.length(); i++) {
9
IVault vault = IVault(getVault(i));
10
11
canExec = block.timestamp >= vault.lastDistribution().add(delay);
12
13
execPayload = abi.encodeWithSelector(
14
IHarvester.harvestVault.selector,
15
address(vault)
16
);
17
18
if (canExec) break;
19
}
20
}
Copied!
This resolver will return true when a certain time has elapsed since the last distribution of a pool, together with the payload to compound that specific pool.

Limit the Gas Price of your execution

On Ethereum Mainnet, gas will get expensive at certain times. If what you are automating is not time-sensitive and don't mind having your transaction mined later, you can limit the gas price used in your execution in your resolver.
1
function checker()
2
external
3
view
4
returns (bool canExec, bytes memory execPayload)
5
{
6
// condition here
7
8
if(tx.gasprice > 80 gwei) canExec = false;
9
}
10
Copied!
This way, Gelato will not execute your transaction if the gas price is higher than 80 GWEI.
Last modified 29d ago