Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

79 switching the validator choice without interrupting the staking process #26

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 22 additions & 1 deletion contracts/RewardPool/IRewardPool.sol
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,20 @@ interface IRewardPool {
uint256 currentEpochId
) external returns (uint256 penalty, uint256 fullReward);

/**
* @notice Swap a vesting postion from one validator to another
* @param oldValidator The address of the validator to swap from
* @param newValidator The address of the delegator to swap to
* @param delegator The address of the delegator
* @return amount The swapped amount
*/
function onSwapPosition(
address oldValidator,
address newValidator,
address delegator,
uint256 currentEpochId
) external returns (uint256 amount);

/**
* @notice Claims delegator rewards for sender.
* @param validator Validator to claim from
Expand Down Expand Up @@ -252,9 +266,16 @@ interface IRewardPool {
function totalDelegationOf(address validator) external view returns (uint256);

/**
* @dev Should be called only by the Governance.
* @notice Changes the minDelegationAmount
* @dev Should be called only by the Governance.
* @param newMinDelegation New minimum delegation amount
*/
function changeMinDelegation(uint256 newMinDelegation) external;

/**
* @notice Modifies the balance changes threshold for vested positions
* @dev Should be called only by the Governance.
* @param newBalanceChangeThreshold The number of allowed changes of the balance
*/
function changeBalanceChangeThreshold(uint256 newBalanceChangeThreshold) external;
}
7 changes: 5 additions & 2 deletions contracts/RewardPool/libs/DelegationPoolLib.sol
Original file line number Diff line number Diff line change
Expand Up @@ -131,8 +131,11 @@ library DelegationPoolLib {
uint256 balance,
int256 correction
) internal view returns (uint256) {
if (pool.claimedRewards[account] >= rewardsEarned(rps, balance, correction)) return 0;
return rewardsEarned(rps, balance, correction) - pool.claimedRewards[account];
uint256 _rewardsEarned = rewardsEarned(rps, balance, correction);
uint256 claimedRewards = pool.claimedRewards[account];
if (claimedRewards >= _rewardsEarned) return 0;

return _rewardsEarned - claimedRewards;
}

function claimRewards(
Expand Down
154 changes: 124 additions & 30 deletions contracts/RewardPool/modules/DelegationRewards.sol
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ import "./RewardsWithdrawal.sol";
import "./../RewardPoolBase.sol";
import "./../libs/DelegationPoolLib.sol";
import "./../libs/VestingPositionLib.sol";

import "./../../common/Errors.sol";

abstract contract DelegationRewards is RewardPoolBase, Vesting, RewardsWithdrawal {
Expand All @@ -32,6 +31,7 @@ abstract contract DelegationRewards is RewardPoolBase, Vesting, RewardsWithdrawa

function __DelegationRewards_init_unchained(uint256 newMinDelegation) internal onlyInitializing {
_changeMinDelegation(newMinDelegation);
balanceChangeThreshold = 32;
}

// _______________ External functions _______________
Expand Down Expand Up @@ -226,7 +226,7 @@ abstract contract DelegationRewards is RewardPoolBase, Vesting, RewardsWithdrawa
});

// keep the change in the delegation pool params per account
_addNewDelegationPoolParam(
_saveAccountParamsChange(
validator,
delegator,
DelegationPoolParams({
Expand Down Expand Up @@ -291,9 +291,15 @@ abstract contract DelegationRewards is RewardPoolBase, Vesting, RewardsWithdrawa
delete delegationPoolParamsHistory[validator][delegator];
} else {
// keep the change in the account pool params
uint256 balance = delegation.balanceOf(delegator);
int256 correction = delegation.correctionOf(delegator);
_onAccountParamsChange(validator, delegator, balance, correction, currentEpochId);
_saveAccountParamsChange(
validator,
delegator,
DelegationPoolParams({
balance: delegation.balanceOf(delegator),
correction: delegation.correctionOf(delegator),
epochNum: currentEpochId
})
);
}
}

Expand All @@ -305,8 +311,59 @@ abstract contract DelegationRewards is RewardPoolBase, Vesting, RewardsWithdrawa
/**
* @inheritdoc IRewardPool
*/
function claimDelegatorReward(address validator) public {
_claimDelegatorReward(validator, msg.sender);
function onSwapPosition(
address oldValidator,
address newValidator,
address delegator,
uint256 currentEpochId
) external onlyValidatorSet returns (uint256 amount) {
VestingPosition memory oldPosition = delegationPositions[oldValidator][delegator];
// ensure that the old position is active in order to continue the swap
if (!oldPosition.isActive()) {
revert DelegateRequirement({src: "vesting", msg: "OLD_POSITION_INACTIVE"});
}

// ensure that the new position is available
if (!isPositionAvailable(newValidator, delegator)) {
revert DelegateRequirement({src: "vesting", msg: "NEW_POSITION_UNAVAILABLE"});
}

// update the old delegation position
DelegationPool storage oldDelegation = delegationPools[oldValidator];
amount = oldDelegation.balanceOf(delegator);
oldDelegation.withdraw(delegator, amount);

int256 correction = oldDelegation.correctionOf(delegator);
_saveAccountParamsChange(
oldValidator,
delegator,
DelegationPoolParams({balance: 0, correction: correction, epochNum: currentEpochId})
);

DelegationPool storage newDelegation = delegationPools[newValidator];
// deposit the old amount to the new position
newDelegation.deposit(delegator, amount);

// transfer the old position parameters to the new one
delegationPositions[newValidator][delegator] = VestingPosition({
duration: oldPosition.duration,
start: oldPosition.start,
end: oldPosition.end,
base: oldPosition.base,
vestBonus: oldPosition.vestBonus,
rsiBonus: oldPosition.rsiBonus
});

// keep the change in the new delegation pool params
_saveAccountParamsChange(
newValidator,
delegator,
DelegationPoolParams({
balance: amount,
correction: newDelegation.correctionOf(delegator),
epochNum: currentEpochId
})
);
}

/**
Expand Down Expand Up @@ -378,7 +435,17 @@ abstract contract DelegationRewards is RewardPoolBase, Vesting, RewardsWithdrawa
_changeMinDelegation(newMinDelegation);
}

function changeBalanceChangeThreshold(uint256 newBalanceChangeThreshold) external onlyRole(DEFAULT_ADMIN_ROLE) {
balanceChangeThreshold = newBalanceChangeThreshold;
}

// _______________ Public functions _______________
/**
* @inheritdoc IRewardPool
*/
function claimDelegatorReward(address validator) public {
_claimDelegatorReward(validator, msg.sender);
}

/**
* @inheritdoc IRewardPool
Expand All @@ -392,6 +459,8 @@ abstract contract DelegationRewards is RewardPoolBase, Vesting, RewardsWithdrawa
/**
* @notice Checks if balance change was already made in the current epoch
* @param validator Validator to delegate to
* @param delegator Delegator that has delegated
* @param currentEpochNum Current epoch number
*/
function isBalanceChangeMade(
address validator,
Expand All @@ -411,6 +480,37 @@ abstract contract DelegationRewards is RewardPoolBase, Vesting, RewardsWithdrawa
return false;
}

// TODO: Consider deleting it as we shouldn't be getting into that case
/**
* @notice Checks if the balance changes exceeds the threshold
* @param validator Validator to delegate to
* @param delegator Delegator that has delegated
*/
function isBalanceChangeThresholdExceeded(address validator, address delegator) public view returns (bool) {
return delegationPoolParamsHistory[validator][delegator].length > balanceChangeThreshold;
}

/**
* @notice Check if the new position that the user wants to swap to is available for the swap
* @dev Available positions one that is not active, not maturing and doesn't have any left balance or rewards
* @param newValidator The address of the new validator
* @param delegator The address of the delegator
*/
function isPositionAvailable(address newValidator, address delegator) public view returns (bool) {
VestingPosition memory newPosition = delegationPositions[newValidator][delegator];
if (newPosition.isActive() || newPosition.isMaturing()) {
return false;
}

DelegationPool storage newDelegation = delegationPools[newValidator];
uint256 balance = newDelegation.balanceOf(delegator);
if (balance != 0 || getRawDelegatorReward(newValidator, delegator) > 0) {
return false;
}

return true;
}

// _______________ Private functions _______________

function _changeMinDelegation(uint256 newMinDelegation) private {
Expand Down Expand Up @@ -487,9 +587,15 @@ abstract contract DelegationRewards is RewardPoolBase, Vesting, RewardsWithdrawa

// keep the change in the account pool params
uint256 balance = delegation.balanceOf(delegator);
int256 correction = delegation.correctionOf(delegator);

_onAccountParamsChange(validator, delegator, balance, correction, currentEpochId);
_saveAccountParamsChange(
validator,
delegator,
DelegationPoolParams({
balance: balance,
correction: delegation.correctionOf(delegator),
epochNum: currentEpochId
})
);
// Modify end period of position, decrease RSI bonus
// balance / old balance = increase coefficient
// apply increase coefficient to the vesting period to find the increase in the period
Expand Down Expand Up @@ -518,14 +624,19 @@ abstract contract DelegationRewards is RewardPoolBase, Vesting, RewardsWithdrawa
_withdrawRewards(delegator, reward);
}

function _addNewDelegationPoolParam(
function _saveAccountParamsChange(
address validator,
address delegator,
DelegationPoolParams memory params
) private {
if (isBalanceChangeMade(validator, delegator, params.epochNum)) {
// Top up can be made only once per epoch
revert StakeRequirement({src: "_addNewDelegationPoolParam", msg: "BALANCE_CHANGE_ALREADY_MADE"});
// balance can be changed only once per epoch
revert DelegateRequirement({src: "_saveAccountParamsChange", msg: "BALANCE_CHANGE_ALREADY_MADE"});
}

if (isBalanceChangeThresholdExceeded(validator, delegator)) {
// maximum amount of balance changes exceeded
revert DelegateRequirement({src: "_saveAccountParamsChange", msg: "BALANCE_CHANGES_EXCEEDED"});
}

delegationPoolParamsHistory[validator][delegator].push(params);
Expand All @@ -546,23 +657,6 @@ abstract contract DelegationRewards is RewardPoolBase, Vesting, RewardsWithdrawa
});
}

function _onAccountParamsChange(
address validator,
address delegator,
uint256 balance,
int256 correction,
uint256 currentEpochId
) private {
if (isBalanceChangeMade(validator, delegator, currentEpochId)) {
// Top up can be made only once on epoch
revert DelegateRequirement({src: "_onAccountParamsChange", msg: "BALANCE_CHANGE_ALREADY_MADE"});
}

delegationPoolParamsHistory[validator][delegator].push(
DelegationPoolParams({balance: balance, correction: correction, epochNum: currentEpochId})
);
}

function _getAccountParams(
address validator,
address manager,
Expand Down
6 changes: 6 additions & 0 deletions contracts/RewardPool/modules/Vesting.sol
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,12 @@ abstract contract Vesting is APR {
*/
mapping(address => mapping(address => RewardParams)) public beforeTopUpParams;

/**
* @notice The threshold for the maximum number of allowed balance changes
* @dev We are using this to restrict unlimited changes of the balance (delegationPoolParamsHistory)
*/
uint256 public balanceChangeThreshold;

// _______________ External functions _______________

function isActivePosition(address staker) external view returns (bool) {
Expand Down
19 changes: 16 additions & 3 deletions contracts/ValidatorSet/modules/Delegation/Delegation.sol
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ abstract contract Delegation is
function delegate(address validator) public payable {
if (msg.value == 0) revert DelegateRequirement({src: "delegate", msg: "DELEGATING_AMOUNT_ZERO"});
_delegate(validator, msg.sender, msg.value);
LiquidStaking._distributeTokens(msg.sender, msg.value);
rewardPool.onDelegate(validator, msg.sender, msg.value);
}

Expand All @@ -42,6 +43,7 @@ abstract contract Delegation is
function undelegate(address validator, uint256 amount) external {
rewardPool.onUndelegate(validator, msg.sender, amount);
_undelegate(validator, msg.sender, amount);
LiquidStaking._collectDelegatorTokens(msg.sender, amount);
_registerWithdrawal(msg.sender, amount);
}

Expand All @@ -50,6 +52,7 @@ abstract contract Delegation is
*/
function delegateWithVesting(address validator, uint256 durationWeeks) external payable onlyManager {
_delegate(validator, msg.sender, msg.value);
LiquidStaking._distributeTokens(msg.sender, msg.value);
rewardPool.onNewDelegatePosition(validator, msg.sender, durationWeeks, currentEpochId, msg.value);

emit PositionOpened(msg.sender, validator, durationWeeks, msg.value);
Expand All @@ -60,7 +63,7 @@ abstract contract Delegation is
*/
function topUpDelegatePosition(address validator) external payable onlyManager {
_delegate(validator, msg.sender, msg.value);

LiquidStaking._distributeTokens(msg.sender, msg.value);
rewardPool.onTopUpDelegatePosition(validator, msg.sender, currentEpochId, msg.value);

emit PositionTopUp(msg.sender, validator, msg.value);
Expand All @@ -72,27 +75,37 @@ abstract contract Delegation is
function undelegateWithVesting(address validator, uint256 amount) external onlyManager {
(uint256 penalty, ) = rewardPool.onCutPosition(validator, msg.sender, amount, currentEpochId);
_undelegate(validator, msg.sender, amount);
LiquidStaking._collectDelegatorTokens(msg.sender, amount);
uint256 amountAfterPenalty = amount - penalty;
_burnAmount(penalty);
_registerWithdrawal(msg.sender, amountAfterPenalty);

emit PositionCut(msg.sender, validator, amountAfterPenalty);
}

/**
* @inheritdoc IDelegation
*/
function swapVestedPositionValidator(address oldValidator, address newValidator) external onlyManager {
uint256 amount = rewardPool.onSwapPosition(oldValidator, newValidator, msg.sender, currentEpochId);
_undelegate(oldValidator, msg.sender, amount);
_delegate(newValidator, msg.sender, amount);

emit PositionSwapped(msg.sender, oldValidator, newValidator, amount);
}

// _______________ Private functions _______________

function _delegate(address validator, address delegator, uint256 amount) private onlyActiveValidator(validator) {
_increaseAccountBalance(validator, amount); // increase validator power
StateSyncer._syncStake(validator, balanceOf(validator));
LiquidStaking._distributeTokens(delegator, amount);

emit Delegated(validator, delegator, amount);
}

function _undelegate(address validator, address delegator, uint256 amount) private {
_decreaseAccountBalance(validator, amount); // decrease validator power
StateSyncer._syncStake(validator, balanceOf(validator));
LiquidStaking._collectDelegatorTokens(delegator, amount);

emit Undelegated(validator, delegator, amount);
}
Expand Down
8 changes: 8 additions & 0 deletions contracts/ValidatorSet/modules/Delegation/IDelegation.sol
Original file line number Diff line number Diff line change
Expand Up @@ -43,4 +43,12 @@ interface IDelegation {
* @param amount Amount to be undelegated
*/
function undelegateWithVesting(address validator, uint256 amount) external;

/**
* @notice Move a vested position to another validator.
* Can be called by vesting positions' managers only.
* @param oldValidator Validator to swap from
* @param newValidator Validator to swap to
*/
function swapVestedPositionValidator(address oldValidator, address newValidator) external;
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ interface IVestedDelegation {
);
event PositionTopUp(address indexed manager, address indexed validator, uint256 amount);
event PositionCut(address indexed manager, address indexed validator, uint256 amount);
event PositionSwapped(address indexed manager, address indexed oldValidator, address newValidator, uint256 amount);

/// @notice Gets the vesting managers per user address for fast off-chain lookup.
function getUserVestManagers(address user) external view returns (address[] memory);
Expand Down
Loading
Loading