A big headache of any automated execution protocol is to ensure that all tasks are executed timely and reliably - either for interval tasks (that should be executed every once in a predefined time interval) or for resolver tasks (that are executed based on some complex external logic, e.g., a certain amount of tokens is accrued as a reward and should be claimed and restaked).
The goal here is to make partaking in automatic execution economically interesting (provide reasonable rewards for each correctly executed task) and, simultaneously, punish keepers which provide unreliable results.
Moreover, it is a puzzle to make the process fully decentralized and economically incentivized for those participants who observe and supervise the behavior of keepers.
PowerAgent solves both problems by introducing a slasher role and a mechanism of slashing.
Slashing is a process of taking away a certain part of a keeper’s stake once they misbehave in any way: either not execute the task on time or fail during execution. Before slashing algorithm ensures that the failure occurred on the keeper’s side, and not on the side of the contract being executed.
Slasher, accordingly, is a keeper which is assigned the role of overseer. Their task is to constantly challenge keepers and try to catch them on low-quality execution (or non-execution) of a task. To incentivize the slasher, they receive all the penalty collected from the punished keeper.
The details of slashing mechanism implementation depend on the realization of the PowerAgent.
Flashbots realization naturally doesn’t need the slashing as a decentralized mechanism: the rally of keepers trying to overbid each other in a Gas War implies that there always will be enough actors ready to execute the task, and one of them will be successful and included by the Flashbots, others reverted.
This realization has a fallback slashing method which can be called only by the PowerAgent contract owner, aka manual slashing.
//id of the keeper to be slashed
//address to which to transfer the slashed stake to
//Amount of CVP to slash from the current keeper's stake
//Amount of CVP to slash from the pending withdrawal keeper balance
RanDAO realization uses a random number generator to pre-assign both keepers and slashers for the next execution of a given task. The slashing then is divided into several logical steps: assign a slasher, initiate slashing, slash.
A slasher for a given task is assigned from a list of active keepers on a per-block basis by the function
getSlasherIdByBlock()using modulo remainder from the following expression:
The keeper from the active list chosen by this index becomes a slasher for this particular job.
So, for each block a single unique slasher has a chance to slash a negligent keeper.
//block number to get slasher id for
//job key of the job to get slasher for
) public view returns (uint256)
//total number of all active keepers
uint256 totalActiveKeepers = activeKeepers.length();
//index of the keeper to choose as a slasher
uint256 index = ((blockNumber_ / rdConfig.slashingEpochBlocks + uint256(jobKey_)) % totalActiveKeepers);
To initiate slashing, a current slasher tries to perform execution of a given task. The initiation is handled by a set of hooks, see link.
Initiation is needed for resolver type jobs, where the exact timestamp of execution is not known a priori.
For a resolver type job, both the keeper and the slasher constantly virtually execute the resolver contract on their nodes, waiting for the resolver to return
canExecute=True(meaning that the execution is made possible and should be done).
As soon as this happens, both try to execute the target function. If the slasher is the one to do it first, the slashing is initiated. This means, a preconfigured time interval is given for a keeper, during which the keeper can execute the task and not be punished. If they fail to do this during this period, the slashing may commence. If slashing is not commenced, it may be re-initiated after a different period elapses.
//address of the job to initiate slashing for
//job id of the job to initiate slashing for
//id of the keeper who initiates slashing
//whether the job is resolver or not
//job execution calldata
bytes memory jobCalldata_
If the keeper could not properly execute the task, the slasher is allowed to execute the task instead of the failed keeper. After the execution in the
_afterSuccessfulExecutionhook the keeper’s CVP stake is reduced and the slasher CVP stake is increased by the same amount.