Try   HackMD

[WIP] Forced Target Limit

TL;DR

As mentioned in VEBO 2.0 ADR, in the current implementation of Lido protocol, the process of validator withdrawal is strictly governed by the withdrawal queue, limiting the system's flexibility. This constraint means that validators cannot be withdrawn independently of the queue, even in urgent situations where quick adjustments are necessary. To overcome this limitation and enhance the system's responsiveness, a new mechanism is proposed. This mechanism would enable the forceful and immediate withdrawal of validators, regardless of the withdrowal queue, allowing for more agile and efficient protocol management, especially in emergency scenarios.

Motivation

To complement the existing target limit feature, a new concept, the "Forced Target Limit," is being introduced. This innovation is designed to augment the current system by providing an additional layer of control, enabling node operators to forcibly adjust the number of active validators in critical situations. While the traditional target limit is bound by the withdrawal queue, the Forced Target Limit will empower operators to make necessary adjustments promptly.

It is suggested that the ability to set the forced application of the active validators limit at the operator should be available to DAOs and the module itself. Managing this limit of the number of active validators within the module is not mandatory but can be necessary in certain scenarios, such as in the CSM module for forcibly removing operators with poor performance or insufficient collateral/bond.

Proposed solution

Based on the current implementation of StakingRouter and modules, the following approach is proposed to introduce the new functionality with maximum backward compatibility:

  1. Replace the internal interpretation of the bool isTargetLimitActive parameter with uint targetLimitMode, which will allow multiple values for this flag in bitwise represenation:

    • 1st bit - target limit activity flag
    • 2nd bit - forced target limit activity flag

    i.e.:

    • b00 - means target limit is disabled
    • bx1 - target limit is enabled in normal mode (see link_TBA)
    • b1x - forced target limit is enabled, regardless of whether target limnit is enabled, i.e. the value of the first bit is ignored. (see link_TBA)
  2. Additionally, introduce the forcedTargetLimitCount internal variable, which will be changed by a separate method with its own access rights. A new method for reading the parameter will also be added. This will allow turning on/off the forced limit mode without changing the rest of the current protocol logic, and will provide additional flexibility in access rights to this mechanism.

Specification

  1. Change the module interface flag type isTargetLimitActive from bool to uint (keep the parameter name the same for compatibility), while the limit returned in targetValidatorsCount will indicate the required number of active validators to be reached, but considering the logic described below.
function getNodeOperatorSummary(uint256 _nodeOperatorId) external view returns (
	uint256 isTargetLimitActive,
	uint256 targetValidatorsCount,
	uint256 stuckValidatorsCount,
	uint256 refundedValidatorsCount,
	uint256 stuckPenaltyEndTimestamp,
	uint256 totalExitedValidators,
	uint256 totalDepositedValidators,
	uint256 depositableValidatorsCount
);

  1. For values returned by the getNodeOperatorSummary getter method, the following logic will be applied in cases where the operator has no set target limit but has a forced target limit set, or the target limit values differ (changes in the VEBO code will be required to follow the new logic):
/// @notice assuming the existence of forcedTargetLimitCount and targetLimitCount vars
/// @notice the code is not optimized and is presented for ease of understanding

isTargetLimitActive = targetLimitMode; // since now the return parameter type is uint

if (targetLimitMode > 2) { // i.e. =3 (b11), both target limit and forced target limit are enabled
    targetValidatorsCount = min(forcedTargetLimitCount, targetLimitCount);
} else if (targetLimitMode > 1) { // i.e. =2 (b10), only forced target limit is enabled
    targetValidatorsCount = forcedTargetLimitCount;
} else if (targetLimitMode > 0) { // i.e. =1 (b01), only target limit is enabled
    targetValidatorsCount = targetLimitCount;
} else { // no limits are enabled
    targetValidatorsCount = 0; 
}

/// @notice it is possible to require that when some limit is disabled, the correspondign values of targetLimitCount and forcedTargetLimitCount are also set to 0, then the logic can be simplified
  1. Additionally, introduce methods for reading and changing targetLimitMode and forcedTargetLimitCount values, with separate access rights (similarly to updateTargetValidatorsLimits method).
function updateForcedTargetValidatorsLimits(
	uint256 _nodeOperatorId,
	bool _isForcedTargetLimitActive,
	uint256 _forcedTargetLimit
) external;

function getForcedTargetValidatorsLimits(
	uint256 _nodeOperatorId
) external returns(
	bool isForcedTargetLimitActive,
	uint256 forcedTargetValidatorsCount
);

/// @notice due to the getNodeOperatorSummary method now returning only the aggregate value, this method is added to directly read target limit values
function getTargetValidatorsLimits(
	uint256 _nodeOperatorId
) external returns(
	bool isTargetLimitActive,
	uint256 targetValidatorsCount
);

Conclusions

This proposed approach will maximize backward compatibility of contract methods and enable the implementation of a mechanism in VEBO for prioritizing the exit of validators assigned to a specific operator. Having a separate method for setting forced limits allows DAO participants to manage this parameter in emergency cases, as well as the module itself to implement internal mechanisms.

Moreover, the proposed approach will allow avoiding additional operational load when disabling the forced target limit and restoring the target limit values if they were set before, and the forced target limit was enabled for some time.