Slashing
Overview
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.
Specification
The details of slashing mechanism implementation depend on the realization of the PowerAgent.
Flashbots
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.
RanDAO
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.
Assign a slasher
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.
Initiate slashing
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.
Slash
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 _afterSuccessfulExecution
hook the keeperâs CVP stake is reduced and the slasher CVP stake is increased by the same amount.
Last updated