Keeper staking, withdrawal, and deactivation

Deactivation, or removal, of keepers is not described in the core contract and is left to inheritors to implement by using the provided keeper data structure (which conveniently lists activity)

Overview

Alongside with the initial stake, provided during keeper registration, the keeper can top up or redeem their stake and compensation at any time, provided certain conditions are fulfilled.

Keeper may also pause and continue their participation in the PowerAgent.

Specification

Staking

Increasing CVP stake may be needed for partaking in execution of those Jobs that require higher minimal CVP stake than default.

Staking is performed by calling stake.

The specified amount of tokens is transferred from the caller to the Agent contract, and the keeper data stored in the mapping keepers is updated (the stored cvpStake value is incremented by the added amount).

On successful staking an event is emitted:

event Stake(
    //id of the keeper whose stake was incremented
    uint256 indexed keeperId,
    
    //amount of stake increment
    uint256 amount,
    
    //address of the user who staked
    address staker
)

Native token compensation withdrawal

Since the native chain tokens (in which the keepers are rewarded by default) are not staked, no time delay is necessary to withdraw them. To withdraw the rewards, call withdrawCompensation.

The amount of available compensation for this keeper, which is stored at its index in the mapping compensations, is decremented by the withdrawn amount, and this amount of the native token is transferred to the specified address.

On successful withdrawal, an event is emitted:

event WithdrawCompensation (
    
    //id of the keeper to withdraw compensation from
    uint256 indexed keeperId,
    
    //address to withdraw to
    address indexed to,
    
    //amount to withdraw
    uint256 amount
)

CVP stake redemption

CVP stake of the keeper is used as an insurance against their malicious behavior. That is why the process of stake redemption is not instant and has a cooldown period, during which the stake is considered to be withdrawn, but is actually held on the Agent contract. By the end of the period the withdrawn CVP stake is transferred to the withdrawer.

The CVP withdrawal process is divided into two successive parts: initiation and finalization.

Initiation

Implementation is dependent on realization of the PowerAgent by way of the overridable virtual function _beforeInitiateRedeem.

When a keeper admin decides to withdraw some of their CVP stake, they call the function initiateRedeem. This function checks that the keeper is not assigned to any oncoming job.

Flashbots

Firstly, it is important to understand the difference in slashing between Flashbots and RanDAO.

In Flashbots, the slashed amount is not simply deducted from the keeper's stake; instead, it is transferred to be stored in the slashedStakeOf mapping, becoming inactive for the purposes of stake comparison and essentially representing the keeper's liability that must be burned before they can redeem any of their CVP, and a corresponding amount of tokens is sent to an Agent owner-specified address. The deduction is therefore finalised when the keeper wants to withdraw their stake (it is also possible to directly slash the pending withdrawals, in which case their value is decremented directly, and a corresponding amount of CVP is sent to the specified address).

If the amount declared for withdrawal is not in excess of the slashed stake totality, the redemption initiation reverts. Otherwise, the accrued slashed stake is zeroed (i.e. burned, since all tokens corresponding to this stake have been transferred to an Agent owner-specified address when slashing commenced), and the remaining amount is subtracted from the keeper's stake and added to the mapping pendingWithdrawalAmounts. The timestamp of withdrawal finalization is stored in the mapping pendingWithdrawalEndsAt.

Note that calling initiateRedeem while the previous redeem was initiated will reset the period, and the redeemer will have to wait for full period again. The amount, however, will be the sum of both redeems.

Note also that if the keeper wishes to simply zero out their slashed stake, they need to call the initiateRedeem function with exactly the totality of the slashed amount passed as the corresponding parameter. The slashed stake will be burned, finalising the accrued punishment, but the keeper will once more be made able to withdraw their stake.

Simple example of stake slashing in Flashbots

Keeper A initially has his CVP stake at a value 100 and is in the process of withdrawing 50 CVP. Keeper A is slashed with values (uint256 currentAmount_, uint256 pendingAmount_) = (30, 20). The pending CVP is decremented by 20, A's cvpStake value is set at 70, 20+30 = 50 tokens in total are transferred to the address specified by the Agent owner at call to slash, and A's slashedStakeOf value is set at 30. For the purposes of all stake amount checks, he now has 70 tokens for his stake. Though he is still pending withdrawal of 30 CVP (and can finalize it as usual!), if he desires to withdraw 10 more and calls initiateRedeem with the amount of 10, the function will revert because his slashed stake has a value of 30, and 10<30. Instead, to add 10 to his pending withdrawal, A must specify the value of 10+30 = 40. If he does so, his slashed stake will be zeroed out (effectively burned, but remember - the corresponding CVP tokens have not disappeared; they have been long since transferred to the address specified by the Agent owner in his call to slash), and 40-30 = 10 will be added to his pending value.

RanDAO

Shares the implementation with the exception that in the _beforeInitiateRedeem hook, it is checked whether the keeper has jobs pending. A keeper who has jobs pending is prohibited from initiating redemption as a measure of preventing malicious behaviour (e.g. withdrawing the totality of stake whilst assigned to a job and both failing execution and dodging punishment).

Emits

event InitiateRedeem (
    
    //id of the keeper which stake is being redeemed
    uint256 indexed keeperId,
    
    //amount to redeem
    uint256 redeemAmount,
    
    //current keeper stake
    uint256 stakeAmount,
    
    //keeper's slashed stake
    uint256 slashedStakeAmount
)

Finalization

Doesn't depend on PowerAgent realization.

Once the timeout period elapses, the keeper admin may call finalizeRedeem to finalise the CVP withdrawal.

The entire pending withdrawal amount is transferred to the supplied address, and the value in the corresponding mapping pendingWithdrawalEndsAt is zeroed.

Emits

event FinalizeRedeem (

    //id of the keeper which stake has been redeemed
    uint256 indexed keeperId,
    
    //address of the redeemed stake recepient
    address indexed beneficiary,
    
    //aomunt redeemed
    uint256 amount
)

Activation and deactivation

Activation and deactivation of keepers means adding or removing keepers to/from the pool of active keepers. To be eligible for execution and slashing duties, a keeper has to be in this pool. The keeper might want to suspend the participation: for this the methods of activation and deactivation are implemented.

Flashbots

Not implemented; to suspend activity, simply stop the off-chain keeper software. To continue, run it again.

RanDAO

Activation

Much like the CVP token withdrawal, the keeper activation is a process staggered in time. This is done to curb the possibility of the active keeper pool oscillating violently.

Initiation

When the admin of an inactive keeper deems it fit to reactivate the keeper once again, he invokes the function initiateKeeperActivation. If the keeepr is not already active, a timeout is imposed, the duration of which is set by the Agent parameter keeperActivationTimeoutHours (max 255 hours), and the time after which finalisation will be made possible is stored in the mapping keeperActivationCanBeFinalizedAt with the keeper ID as key.

Event

On initiating activation emits

event InitiateKeeperActivation(
//ID of the keeper being activated
uint256 keeperId, 

//timestamp at which activation may be finalised
uint256 canBeFinalizedAt
);

Finalisation

Finalisation of activation is done by the keeper admin invoking finalizeKeeperActivation. If the function is called at a timestamp not earlier than the one specified at keeperActivationCanBeFinalizedAt, the ID of the keeper being activated is added back to the set of active keepers, and the activity status of a corresponding entry in the mapping of IDs to keepers is set to True.

Event

On finalising activation emits

event FinalizeKeeperActivation(
//ID of the keeper now active
uint256 keeperId
);

Deactivation

Deactivation does not require any complicated multi-step procedure; a simple call to disableKeeper by the keeper admin suffices. If the keeper in question is not already inactive and can be released (i.e. has no jobs pending), they are removed from the active keeper set, and the corresponding entry in the mapping of IDs to keepers has its activity status set to False.

Event

On keeper deactivation emits

event DisableKeeper(
//the ID of the deactivated keeper
uint256 keeperId
);

Last updated