Smart Contract Resolver
Here is how a simple smart contract resolver looks like. This resolver is responsible for checking the
lastExecuted
state of a simple counter in another smart contract. And if 3 minutes have passed since the last execution, this resolver will prompt Gelato to execute.CounterResolver.sol
contract CounterResolver {
ICounter public immutable counter;
constructor(ICounter _counter) {
counter = _counter;
}
function checker()
external
view
returns (bool canExec, bytes memory execPayload)
{
uint256 lastExecuted = counter.lastExecuted();
canExec = (block.timestamp - lastExecuted) > 180;
execPayload = abi.encodeCall(ICounter.increaseCount, (1));
}
}
A resolver should always return 2 things:
- 1.bool
canExec
: whether Gelato should execute the task. - 2.bytes
execData
: data that executors should use for the execution.
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:
function checker(address _counter)
external
view
returns (bool canExec, bytes memory execPayload)
{
uint256 lastExecuted = ICounter(_counter).lastExecuted();
canExec = (block.timestamp - lastExecuted) > 180;
execPayload = abi.encodeCall(ICounter.increaseCount, (1));
}
Instead of a hardcoded
COUNTER
address, you can pass counterAddress
as an argument. 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.
function checker()
external
view
returns (bool canExec, bytes memory execPayload)
{
uint256 delay = harvester.delay();
for (uint256 i = 0; i < vaults.length(); i++) {
IVault vault = IVault(getVault(i));
canExec = block.timestamp >= vault.lastDistribution().add(delay);
execPayload = abi.encodeWithSelector(
IHarvester.harvestVault.selector,
address(vault)
);
if (canExec) return(true, execPayload);
return(false, bytes("No vaults to harvest");
}
}
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.
The Gelato Automate UI has a feature which provides logs which show what the Gelato Executors are seeing in real time.
Using custom return messages, you can always check where in your smart contract Resolver the logic is currently "stuck", i.e. why isn't the Resolver returning
true
. function checker()
external
view
returns (bool canExec, bytes memory execPayload)
{
uint256 lastExecuted = counter.lastExecuted();
if(block.timestamp - lastExecuted < 180) return(false, bytes("Time not elapsed");
execPayload = abi.encodeCall(ICounter.increaseCount, (1));
return(true, execPayload);
}
On networks such as Ethereum, gas will get expensive at certain times. If what you are automating is not time-sensitive and don't mind having your transaction mined at a later point, you can limit the gas price used in your execution in your resolver.
function checker()
external
view
returns (bool canExec, bytes memory execPayload)
{
// condition here
if(tx.gasprice > 80 gwei) return (false, bytes("Gas price too high"));
}
This way, Gelato will not execute your transaction if the gas price is higher than 80 GWEI.
Last modified 3mo ago