Copy of Slashing
Overview
One prominent issue any attempt at decentralised execution automation must tackle is that of incentivising task execution in a timely and reliable manner for conditionals of arbitrary logical complexity. This is achieved by means of combining rewards for execution with punishments for failure thereof. Do refer to Copy of Task Reward and Gas Compensation for details on computation and payment of rewards; hereafter punishment is given ample consideration.
Since the protocol needs to stay decentralised, and we wish not to offload the role of an overseer to any external party, it is necessary to devise a method and an incentive for keepers to not only execute tasks, but also ensure execution thereof by others. In PowerAgent, this is done by introducing the concept of a Slasher and the corresponding process of slashing.
Slashing is defined as confiscation of a portion of a Keeper's stake by an entity acting as a Slasher. Slashing may be brought about by failure to execute tasks or other theoretically arbitrarily configurable conditions. The role of a Slaher may be filled by either the Agent owner (as is the case for the Flashbots realisation) or some other entity (e.g. other Keepers, as is the case for the RanDAO realisation). Naturally, a process must be introduced to make sure wrongful slashes do not happen (e.g. a Keeper is not slashed for failing to execute a task which reverts at attempts to do so). Hence a natural way to incentivise Keeper Slashers arises: if they are rewarded for slashing by the amount confiscated or a portion thereof, they would naturally seek to maximise their slashing volume and thereby exert effective control on the Keepers, automatically providing efficient economic disincentive for malicious conduct.
Moreover, if the Slasher is also granted the opportunity to execute the contract instead of the failed Keeper, a natural way to generalise the slashing process to also provide additional execution security to Job owners arises, and the incentive to perform the important duty of a Slasher is accordingly increased, since now the slashing reward is augmented by the execution reward.
Specification
The details of slashing mechanism implementation depend on the realization of the PowerAgent.
Flashbots
Flashbots realization doesnât need an imbedded decentralised slashing mechanism: the auction format of the (Flashbots mempool) Gas Wars 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 suffering revertion. In other words, there is theoretically no need to safeguard against task non-execution due to non-exclusivity of possible executors and therefore a glut thereof.
This realization has a fallback slashing method which can be called only by the PowerAgent contract owner, aka manual slashing.
For the sake of completeness, we briefly outline the slashing algorithm elucidated beforehand in Keeper staking, withdrawal, and deactivation. The values of currentAmount_
and pendingAmount_
are subtracted from the current stake of the keeper and his stake pending withdrawal, respectively. The latter value is simply subtracted, while the former is afterwards added to the mapping slashedStakeOf
at an entry corresponding to this keeper's ID. In effect, both types of the slashed token no longer belong to the Keeper in question, since their totality is transferred to the specified address immediately after decrements are performed, but the totality slashed from the current amount is left accessible as a variable so that derivative implementations could use knowledge on the accrued slashed stake. A means of zeroing the value of slashedStakeOf
is provided by the stake withdrawal function, where one needs to compensate all the slashed stake before withdrawing any actual tokens. In the core Flashbots realisation contract, this is a mere formality, only requiring addition of a number to the specified withdrawal amount without actually losing the Keeper any extra tokens, but derivative implementations could well define additional restrictions and impose extra punishment via the overridable _beforeInitiateRedeem
hook invoked in the redemption initiation functions.
RanDAO
RanDAO realization uses a random number generator (outsourced to RanDAO, whence the name of the realisation is derived, and used in the form of the block difficulty
parameter, which since the transition from PoW to PoS corresponds to the block's RanDAO reveal) to pre-assign both keepers and slashers for the next execution of a given task. Then we can obtain the following algorithms of slashing:
Note that since the slashers necessarily execute the job the keeper failed to (as a precaution against nonexecution), they must satisfy the job's demands regarding minimal stake to be able to do so. Assertions to such effect are in place.
Mind that although it is ensured that the assigned keeper for each job has sufficient stake, the same does not necessarily hold for the assigned slasher. For this reason, it is perfectly possible for a slasher to lack the ability to actually slash, which may cause delays a few blocks long (as the keeper stake obedience check is in the non-overridable function execute_44g58pv
).
The sole distinction in these algorithms is the presence of slashing initiation in the Resolver case. The cause for this is the fact that the timestamp at which execution is made possible is known exactly a priori for interval jobs by definition, but for resolver jobs is is defined by essentially arbitrary logic. Slashing is initiated whenever a corresponding function is called by the assigned slasher who can execute the underlying function being automated at the timestamp of initiation. This timestamp is taken to be the first one at which job execution is made possible for the purposes of computing grace and admissibility periods. This estimate is rather tight at discrete time and rational slashers (i.e. slashers acting as optimiser agents).
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 the following expression with projection onto a finite ring (not necessarily a field since the keeper amount is quite arbitrary) , where is the cardinality of the active keeper set, by means of modular arithmetic:
The keeper from the active list chosen by this index becomes a slasher for this particular job (since keys are unique, the mapping from slashers to jobs is injective, though the converse is generally false due to difference in set cardinalities).
So, for each block a single unique (with some caveats) slasher has a chance to slash a negligent keeper.
Resolver job slashing initiation
To initiate slashing, a current slasher invokes the function initiateSlashing
once he is able to execute the task (checked off-chain by means of the supplied resolver). The initiation is handled by a set of hooks, see link, that ensure executability of the job and onset of a number of conditions, such as the slasher being indeed the slasher assigned at this time. Superior presentation of logic entailed is available at the Hooks page.
Initiation is needed for resolver type jobs, where the exact timestamp of execution is not known a priori. Interval jobs are instead slashed directly.
The idea behind initiation is as follows: 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, the keeper tries to execute the function, and the slasher tries to initiate slashing. If the slasher initiates before the keeper is able to execute, the slashing is initiated. This means that a preconfigured time interval (grace period defined by rdConfig.period1
) is assigned during which a keeper can execute the task, but cannot be slashed. If they fail to do this during this period, the slashing may commence, with the job being executed by the slasher instead. If slashing is not commenced, it may be re-initiated after a different period (admissibility period given by rdConfig.period2
) elapses.
Slash initiation event
Slash
If the keeper could not properly execute the task in time (i.e. the current time is not in excess of nextExecutionTimeoutAt = _lastExecutionAt + intervalSeconds + rdConfig.period1
for interval jobs or jobSlashingPossibleAfter[jobKey_] = initiation_timestamp+rdConfig.period1
for resolver jobs) , the slasher is allowed to execute the task instead of the failed keeper. Refer to the hooks page Hooks and helper functions to obtain superior clarity regarding the checks performed. After successful execution, in the _afterSuccessfulExecution
hook the keeperâs CVP stake is reduced and the slasher CVP stake is increased by the same amount.
By this process, execution is secured, and unreliable behaviour is punished.
Re-initiation
Should the slasher fail to execute the job as well, either due to negligence or because his stake is not sufficient to do so, another slasher may re-initiate the slashing once the admissibility period expires.
Slashing amount
Flashbots
Since Flashbots slashing is manual, slashing amounts are fully determined by the Agent owner and are only bounded by the totality of the keeper's stake (due to reverts at setting negative values for uint variables).
RanDAO
In the RanDAO realisation, slashing consists of two components: fixed and dynamic, where fixed component is independent of the keeper stake, and the dynamic component comprises a pre-defined portion thereof. Both the fixed slashing fee and the dynamic slashing fee in basis points of the stake are Agent-wide parameters controlled by setting rdConfig
. The formulae are as follows:
Since the fixed CVP slashing fee is capped at one half of the minimal CVP stake admissible by the Agent, and the dynamic CVP slashing fee is capped at 50% (5000 basis points), no keeper at no point in time can be slashed for more than 100% of his stake.
Last updated