Skip to content

Commit

Permalink
Merge branch 'require-uptime' into delegator-refactor
Browse files Browse the repository at this point in the history
  • Loading branch information
cam-schultz committed Sep 27, 2024
2 parents 0de3381 + c4a34bc commit 1420c70
Show file tree
Hide file tree
Showing 6 changed files with 200 additions and 32 deletions.

Large diffs are not rendered by default.

Large diffs are not rendered by default.

32 changes: 30 additions & 2 deletions contracts/validator-manager/PoSValidatorManager.sol
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,7 @@ abstract contract PoSValidatorManager is
error InvalidStakeAmount();
error InvalidStakeMultiplier();
error ValidatorIneligibleForRewards();
error DelegatorIneligibleForRewards();

// solhint-disable ordering
function _getPoSValidatorManagerStorage()
Expand Down Expand Up @@ -450,6 +451,28 @@ abstract contract PoSValidatorManager is
bool includeUptimeProof,
uint32 messageIndex
) external {
if (!_initializeEndDelegation(delegationID, includeUptimeProof, messageIndex)) {
revert DelegatorIneligibleForRewards();
}
}

function forceInitializeEndDelegation(
bytes32 delegationID,
bool includeUptimeProof,
uint32 messageIndex
) external {
// Ignore the return value here to force end delegation, regardless of possible missed rewards
_initializeEndDelegation(delegationID, includeUptimeProof, messageIndex);
}

// Helper function that initializes the end of a PoS delegation period.
// Returns false if it is possible for the delegator to claim rewards, but it is not eligible.
// Returns true otherwise.
function _initializeEndDelegation(
bytes32 delegationID,
bool includeUptimeProof,
uint32 messageIndex
) internal returns (bool) {
PoSValidatorManagerStorage storage $ = _getPoSValidatorManagerStorage();

Delegator memory delegator = $._delegatorStakes[delegationID];
Expand Down Expand Up @@ -479,16 +502,21 @@ abstract contract PoSValidatorManager is
($._delegatorStakes[delegationID].endingNonce,) =
_setValidatorWeight(validationID, validator.weight - delegator.weight);

$._redeemableDelegatorRewards[delegationID] = _calculateDelegationReward(delegator);
uint256 reward = _calculateDelegationReward(delegator);
$._redeemableDelegatorRewards[delegationID] = reward;

emit DelegatorRemovalInitialized({
delegationID: delegationID,
validationID: validationID
});
return (reward > 0);
} else if (validator.status == ValidatorStatus.Completed) {
$._redeemableDelegatorRewards[delegationID] = _calculateDelegationReward(delegator);

return _completeEndDelegation(delegationID);
_completeEndDelegation(delegationID);
// If the validator has completed, then no further uptimes may be submitted, so we always
// end the delegation
return true;
} else {
revert InvalidValidatorStatus();
}
Expand Down
29 changes: 25 additions & 4 deletions contracts/validator-manager/interfaces/IPoSValidatorManager.sol
Original file line number Diff line number Diff line change
Expand Up @@ -154,15 +154,16 @@ interface IPoSValidatorManager is IValidatorManager {
function completeDelegatorRegistration(uint32 messageIndex, bytes32 delegationID) external;

/**
* @notice Begins the process of removing a delegator from a validation period. The delegator must have been previously
* registered with the given validationID. For the purposes of computing delegation rewards, the delegation period is
* considered ended when this function is called. In order to be eligible for rewards, an uptime proof must be provided.
* @notice Begins the process of removing a delegator from a validation period, and reverts if the delegation is not eligible for rewards.
* The delegator must have been previously registered with the given validationID. For the purposes of computing delegation rewards,
* the delegation period is considered ended when this function is called. Uses the supplied uptime proof to calculate rewards.
* If none is provided in the call, the latest known uptime will be used. Reverts if the uptime is not eligible for rewards.
* Note that this function can only be called by the address that registered the delegation.
* @param delegationID The ID of the delegation being removed.
* @param includeUptimeProof Whether or not an uptime proof is provided for the validation period.
* If the validator has completed its validation period, it has already provided an uptime proof, so {includeUptimeProof}
* will be ignored and can be set to false. If the validator has not completed its validation period and no uptime proof
* is provided, the validation uptime for the delegation period will be assumed to be 0.
* is provided, the latest known uptime will be used.
* @param messageIndex If {includeUptimeProof} is true, the index of the Warp message to be received providing the
* uptime proof.
*/
Expand All @@ -172,6 +173,26 @@ interface IPoSValidatorManager is IValidatorManager {
uint32 messageIndex
) external;

/**
* @notice Begins the process of removing a delegator from a validation period, but does not revert if the delegation is not eligible for rewards.
* The delegator must have been previously registered with the given validationID. For the purposes of computing delegation rewards,
* the delegation period is considered ended when this function is called. Uses the supplied uptime proof to calculate rewards.
* If none is provided in the call, the latest known uptime will be used. Reverts if the uptime is not eligible for rewards.
* Note that this function can only be called by the address that registered the delegation.
* @param delegationID The ID of the delegation being removed.
* @param includeUptimeProof Whether or not an uptime proof is provided for the validation period.
* If the validator has completed its validation period, it has already provided an uptime proof, so {includeUptimeProof}
* will be ignored and can be set to false. If the validator has not completed its validation period and no uptime proof
* is provided, the latest known uptime will be used.
* @param messageIndex If {includeUptimeProof} is true, the index of the Warp message to be received providing the
* uptime proof.
*/
function forceInitializeEndDelegation(
bytes32 delegationID,
bool includeUptimeProof,
uint32 messageIndex
) external;

/**
* @notice Resubmits a delegator registration or delegator end message to be sent to the P-Chain.
* Only necessary if the original message can't be delivered due to validator churn.
Expand Down
113 changes: 95 additions & 18 deletions contracts/validator-manager/tests/PoSValidatorManagerTests.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -350,7 +350,53 @@ abstract contract PoSValidatorManagerTest is ValidatorManagerTest {
startDelegationTimestamp: DEFAULT_DELEGATOR_INIT_REGISTRATION_TIMESTAMP,
endDelegationTimestamp: DEFAULT_DELEGATOR_END_DELEGATION_TIMESTAMP,
expectedValidatorWeight: DEFAULT_WEIGHT,
expectedNonce: 2
expectedNonce: 2,
includeUptime: true,
force: false
});
}

function testInitializeEndDelegationInsufficientUptime() public {
bytes32 validationID = _registerDefaultValidator();
bytes32 delegationID = _registerDefaultDelegator(validationID);

vm.expectRevert(PoSValidatorManager.DelegatorIneligibleForRewards.selector);
vm.warp(DEFAULT_DELEGATOR_END_DELEGATION_TIMESTAMP);
vm.prank(DEFAULT_DELEGATOR_ADDRESS);
posValidatorManager.initializeEndDelegation(delegationID, false, 0);
}

function testForceInitializeEndDelegation() public {
bytes32 validationID = _registerDefaultValidator();
bytes32 delegationID = _registerDefaultDelegator(validationID);

_initializeEndDelegationValidatorActiveWithChecks({
validationID: validationID,
delegatorAddress: DEFAULT_DELEGATOR_ADDRESS,
delegationID: delegationID,
startDelegationTimestamp: DEFAULT_DELEGATOR_INIT_REGISTRATION_TIMESTAMP,
endDelegationTimestamp: DEFAULT_DELEGATOR_END_DELEGATION_TIMESTAMP,
expectedValidatorWeight: DEFAULT_WEIGHT,
expectedNonce: 2,
includeUptime: true,
force: true
});
}

function testForceInitializeEndDelegationInsufficientUptime() public {
bytes32 validationID = _registerDefaultValidator();
bytes32 delegationID = _registerDefaultDelegator(validationID);

_initializeEndDelegationValidatorActiveWithChecks({
validationID: validationID,
delegatorAddress: DEFAULT_DELEGATOR_ADDRESS,
delegationID: delegationID,
startDelegationTimestamp: DEFAULT_DELEGATOR_INIT_REGISTRATION_TIMESTAMP,
endDelegationTimestamp: DEFAULT_DELEGATOR_END_DELEGATION_TIMESTAMP,
expectedValidatorWeight: DEFAULT_WEIGHT,
expectedNonce: 2,
includeUptime: false,
force: true
});
}

Expand All @@ -365,7 +411,9 @@ abstract contract PoSValidatorManagerTest is ValidatorManagerTest {
startDelegationTimestamp: DEFAULT_DELEGATOR_INIT_REGISTRATION_TIMESTAMP,
endDelegationTimestamp: DEFAULT_DELEGATOR_END_DELEGATION_TIMESTAMP,
expectedValidatorWeight: DEFAULT_WEIGHT,
expectedNonce: 2
expectedNonce: 2,
includeUptime: true,
force: false
});
bytes memory setValidatorWeightPayload =
ValidatorMessages.packSetSubnetValidatorWeightMessage(validationID, 2, DEFAULT_WEIGHT);
Expand Down Expand Up @@ -401,7 +449,9 @@ abstract contract PoSValidatorManagerTest is ValidatorManagerTest {
startDelegationTimestamp: DEFAULT_DELEGATOR_INIT_REGISTRATION_TIMESTAMP,
endDelegationTimestamp: DEFAULT_DELEGATOR_END_DELEGATION_TIMESTAMP,
expectedValidatorWeight: DEFAULT_WEIGHT,
expectedNonce: 2
expectedNonce: 2,
includeUptime: true,
force: false
});

uint256 expectedTotalReward = rewardCalculator.calculateReward({
Expand Down Expand Up @@ -513,7 +563,9 @@ abstract contract PoSValidatorManagerTest is ValidatorManagerTest {
startDelegationTimestamp: DEFAULT_DELEGATOR_INIT_REGISTRATION_TIMESTAMP,
endDelegationTimestamp: DEFAULT_DELEGATOR_END_DELEGATION_TIMESTAMP,
expectedValidatorWeight: DEFAULT_WEIGHT,
expectedNonce: 2
expectedNonce: 2,
includeUptime: true,
force: false
});

uint64 validationEndTime = DEFAULT_DELEGATOR_END_DELEGATION_TIMESTAMP + 1;
Expand Down Expand Up @@ -640,7 +692,9 @@ abstract contract PoSValidatorManagerTest is ValidatorManagerTest {
startDelegationTimestamp: DEFAULT_DELEGATOR_INIT_REGISTRATION_TIMESTAMP,
endDelegationTimestamp: DEFAULT_DELEGATOR_END_DELEGATION_TIMESTAMP,
expectedValidatorWeight: DEFAULT_WEIGHT,
expectedNonce: 2
expectedNonce: 2,
includeUptime: true,
force: false
});

_endDefaultValidator(validationID, 3);
Expand Down Expand Up @@ -708,7 +762,9 @@ abstract contract PoSValidatorManagerTest is ValidatorManagerTest {
startDelegationTimestamp: DEFAULT_DELEGATOR_INIT_REGISTRATION_TIMESTAMP,
endDelegationTimestamp: DEFAULT_DELEGATOR_END_DELEGATION_TIMESTAMP,
expectedValidatorWeight: DEFAULT_DELEGATOR_WEIGHT + DEFAULT_WEIGHT,
expectedNonce: 3
expectedNonce: 3,
includeUptime: true,
force: false
});
_initializeEndDelegationValidatorActiveWithChecks({
validationID: validationID,
Expand All @@ -717,7 +773,9 @@ abstract contract PoSValidatorManagerTest is ValidatorManagerTest {
startDelegationTimestamp: DEFAULT_DELEGATOR_INIT_REGISTRATION_TIMESTAMP,
endDelegationTimestamp: DEFAULT_DELEGATOR_END_DELEGATION_TIMESTAMP + 1,
expectedValidatorWeight: DEFAULT_WEIGHT,
expectedNonce: 4
expectedNonce: 4,
includeUptime: true,
force: false
});

// Complete ending delegator2 with delegator1's nonce
Expand Down Expand Up @@ -766,7 +824,9 @@ abstract contract PoSValidatorManagerTest is ValidatorManagerTest {
startDelegationTimestamp: DEFAULT_DELEGATOR_COMPLETE_REGISTRATION_TIMESTAMP,
endDelegationTimestamp: DEFAULT_DELEGATOR_END_DELEGATION_TIMESTAMP,
expectedValidatorWeight: DEFAULT_DELEGATOR_WEIGHT + DEFAULT_WEIGHT,
expectedNonce: 3
expectedNonce: 3,
includeUptime: true,
force: false
});
_initializeEndDelegationValidatorActiveWithChecks({
validationID: validationID,
Expand All @@ -775,7 +835,9 @@ abstract contract PoSValidatorManagerTest is ValidatorManagerTest {
startDelegationTimestamp: DEFAULT_DELEGATOR_COMPLETE_REGISTRATION_TIMESTAMP,
endDelegationTimestamp: DEFAULT_DELEGATOR_END_DELEGATION_TIMESTAMP + 1,
expectedValidatorWeight: DEFAULT_WEIGHT,
expectedNonce: 4
expectedNonce: 4,
includeUptime: true,
force: false
});

uint256 expectedTotalReward = rewardCalculator.calculateReward({
Expand Down Expand Up @@ -1148,7 +1210,9 @@ abstract contract PoSValidatorManagerTest is ValidatorManagerTest {
uint64 startDelegationTimestamp,
uint64 endDelegationTimestamp,
uint64 expectedValidatorWeight,
uint64 expectedNonce
uint64 expectedNonce,
bool includeUptime,
bool force
) internal {
vm.expectEmit(true, true, true, true, address(posValidatorManager));
emit ValidatorWeightUpdate({
Expand All @@ -1168,7 +1232,9 @@ abstract contract PoSValidatorManagerTest is ValidatorManagerTest {
startDelegationTimestamp: startDelegationTimestamp,
endDelegationTimestamp: endDelegationTimestamp,
expectedValidatorWeight: expectedValidatorWeight,
expectedNonce: expectedNonce
expectedNonce: expectedNonce,
includeUptime: includeUptime,
force: force
});
}

Expand All @@ -1179,28 +1245,39 @@ abstract contract PoSValidatorManagerTest is ValidatorManagerTest {
uint64 startDelegationTimestamp,
uint64 endDelegationTimestamp,
uint64 expectedValidatorWeight,
uint64 expectedNonce
uint64 expectedNonce,
bool includeUptime,
bool force
) internal {
bytes memory setValidatorWeightPayload = ValidatorMessages
.packSetSubnetValidatorWeightMessage(validationID, expectedNonce, expectedValidatorWeight);
_mockSendWarpMessage(setValidatorWeightPayload, bytes32(0));
bytes memory uptimeMsg = ValidatorMessages.packValidationUptimeMessage(
validationID, endDelegationTimestamp - startDelegationTimestamp
);
_mockGetUptimeWarpMessage(uptimeMsg, true);
_mockGetBlockchainID();

_initializeEndDelegation(delegatorAddress, delegationID, endDelegationTimestamp);
if (includeUptime) {
_mockGetUptimeWarpMessage(uptimeMsg, true);
_mockGetBlockchainID();
}
_initializeEndDelegation(
delegatorAddress, delegationID, endDelegationTimestamp, includeUptime, force
);
}

function _initializeEndDelegation(
address delegatorAddress,
bytes32 delegationID,
uint64 endDelegationTimestamp
uint64 endDelegationTimestamp,
bool includeUptime,
bool force
) internal {
vm.warp(endDelegationTimestamp);
vm.prank(delegatorAddress);
posValidatorManager.initializeEndDelegation(delegationID, true, 0);
if (force) {
posValidatorManager.forceInitializeEndDelegation(delegationID, includeUptime, 0);
} else {
posValidatorManager.initializeEndDelegation(delegationID, includeUptime, 0);
}
}

function _endDefaultValidator(bytes32 validationID, uint64 expectedNonce) internal {
Expand Down
8 changes: 4 additions & 4 deletions tests/utils/validator_manager.go
Original file line number Diff line number Diff line change
Expand Up @@ -945,7 +945,7 @@ func ForceInitializeEndNativeValidation(
0,
)
Expect(err).Should(BeNil())
return WaitForTransactionSuccess(context.Background(), subnet, tx.Hash())
return WaitForTransactionSuccess(ctx, subnet, tx.Hash())
}

func InitializeEndERC20Validation(
Expand All @@ -965,7 +965,7 @@ func InitializeEndERC20Validation(
0,
)
Expect(err).Should(BeNil())
return WaitForTransactionSuccess(context.Background(), subnet, tx.Hash())
return WaitForTransactionSuccess(ctx, subnet, tx.Hash())
}

func ForceInitializeEndERC20Validation(
Expand Down Expand Up @@ -1136,7 +1136,7 @@ func InitializeEndERC20Delegation(
WaitMinStakeDuration(ctx, subnet, senderKey)
opts, err := bind.NewKeyedTransactorWithChainID(senderKey, subnet.EVMChainID)
Expect(err).Should(BeNil())
tx, err := stakingManager.InitializeEndDelegation(
tx, err := stakingManager.ForceInitializeEndDelegation(
opts,
delegationID,
false,
Expand Down Expand Up @@ -1227,7 +1227,7 @@ func InitializeEndNativeDelegation(
WaitMinStakeDuration(ctx, subnet, senderKey)
opts, err := bind.NewKeyedTransactorWithChainID(senderKey, subnet.EVMChainID)
Expect(err).Should(BeNil())
tx, err := stakingManager.InitializeEndDelegation(
tx, err := stakingManager.ForceInitializeEndDelegation(
opts,
delegationID,
false,
Expand Down

0 comments on commit 1420c70

Please sign in to comment.