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

Prorated rewards v2 #631

Draft
wants to merge 18 commits into
base: main
Choose a base branch
from
Draft
Changes from 1 commit
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
Prev Previous commit
Next Next commit
Merge branch 'main' into prorated-rewards
cam-schultz committed Oct 29, 2024
commit ee8ac29967dd82419a8324723026de6ded8a3371

Large diffs are not rendered by default.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Large diffs are not rendered by default.

6 changes: 3 additions & 3 deletions contracts/validator-manager/ExampleRewardCalculator.sol
Original file line number Diff line number Diff line change
@@ -10,6 +10,8 @@ import {IRewardCalculator} from "./interfaces/IRewardCalculator.sol";
contract ExampleRewardCalculator is IRewardCalculator {
uint256 public constant SECONDS_IN_YEAR = 31536000;

uint16 public constant BIPS_CONVERSION_FACTOR = 10000;

uint64 public immutable rewardBasisPoints;

constructor(uint64 rewardBasisPoints_) {
@@ -25,9 +27,7 @@ contract ExampleRewardCalculator is IRewardCalculator {
uint64, // validatorStartTime
uint64 stakingStartTime,
uint64 stakingEndTime,
uint64, // uptimeSeconds
uint256, // initialSupply
uint256 // endSupply
uint64 // uptimeSeconds
) external view returns (uint256) {
return (stakeAmount * rewardBasisPoints * (stakingEndTime - stakingStartTime))
/ SECONDS_IN_YEAR / BIPS_CONVERSION_FACTOR;
21 changes: 13 additions & 8 deletions contracts/validator-manager/PoSValidatorManager.sol
Original file line number Diff line number Diff line change
@@ -77,6 +77,7 @@
uint16 public constant MAXIMUM_DELEGATION_FEE_BIPS = 10000;

uint8 public constant UPTIME_REWARDS_THRESHOLD_PERCENTAGE = 80;
uint16 public constant BIPS_CONVERSION_FACTOR = 10000;

error InvalidDelegationFee(uint16 delegationFeeBips);
error InvalidDelegationID(bytes32 delegationID);
@@ -277,9 +278,7 @@
validatorStartTime: lastClaimTime,
stakingStartTime: lastClaimTime,
stakingEndTime: validator.endedAt,
uptimeSeconds: uptime,
initialSupply: 0,
endSupply: 0
uptimeSeconds: uptime
});
$._redeemableValidatorRewards[validationID] += reward;
}
@@ -320,86 +319,89 @@
*
* @dev See {IPoSValidatorManager-claimValidationRewards}.
*/
function claimValidationRewards(
bytes32 validationID,
uint32 messageIndex
) external nonReentrant {
PoSValidatorManagerStorage storage $ = _getPoSValidatorManagerStorage();

Validator memory validator = getValidator(validationID);
if (validator.status != ValidatorStatus.Active) {
revert InvalidValidatorStatus(validator.status);
}

// Non-PoS validators are required to boostrap the network, but are not eligible for rewards.
if (!_isPoSValidator(validationID)) {
revert ValidatorNotPoS(validationID);
}

// Rewards can only be claimed by the validator owner.
if ($._posValidatorInfo[validationID].owner != _msgSender()) {
revert UnauthorizedOwner(_msgSender());
}

// Check that minimum stake duration has passed.
uint64 claimTime = uint64(block.timestamp);
if (claimTime < validator.startedAt + $._posValidatorInfo[validationID].minStakeDuration) {
revert MinStakeDurationNotPassed(claimTime);
}

// The claim's uptime is the difference between the total uptime and the minimum possible uptime from the last claim.
// We use the minimum uptime to get a lower bound on the required uptime for this claim
uint64 totalUptime = _updateUptime(validationID, messageIndex);
uint64 uptime = totalUptime - $._posValidatorInfo[validationID].lastClaimMinUptime;

// If no rewards have yet been claimed, use the validator's start time
uint64 lastClaimTime = $._posValidatorInfo[validationID].lastClaimTime;
if (lastClaimTime == 0) {
lastClaimTime = validator.startedAt;
}
// Validate the uptime for this claim. Given that all previous claims have been similarly validated,
// this is equivalent to validating the uptime of the entire validation period up to this point, due
// to the linearity of the uptime threshold calculation.
if (!_validateSufficientUptime(uptime, lastClaimTime, claimTime)) {
revert ValidatorIneligibleForRewards(validationID);
}

uint256 reward = $._rewardCalculator.calculateReward({
stakeAmount: weightToValue(validator.startingWeight),
validatorStartTime: lastClaimTime,
stakingStartTime: lastClaimTime,
stakingEndTime: claimTime,
uptimeSeconds: uptime,
initialSupply: 0,
endSupply: 0
uptimeSeconds: uptime
});

$._posValidatorInfo[validationID].lastClaimMinUptime =
(claimTime - validator.startedAt) * UPTIME_REWARDS_THRESHOLD_PERCENTAGE / 100;
$._posValidatorInfo[validationID].lastClaimTime = claimTime;
_reward($._posValidatorInfo[validationID].owner, reward);

emit ValidationRewardsClaimed(validationID, reward);
}

/**
* @dev Helper function that checks if the submitted uptime is sufficient for rewards.
*/
function _validateSufficientUptime(
uint64 uptimeSeconds,
uint64 periodStartTime,
uint64 periodEndTime
) internal pure returns (bool) {
// Equivalent to uptimeSeconds/(periodEndTime - periodStartTime) < UPTIME_REWARDS_THRESHOLD_PERCENTAGE/100
// Rearranged to prevent integer division truncation.
if (
uptimeSeconds * 100
< (periodEndTime - periodStartTime) * UPTIME_REWARDS_THRESHOLD_PERCENTAGE
) {
return false;
}
return true;
}

// Helper function that extracts the uptime from a ValidationUptimeMessage Warp message
// If the uptime is greater than the stored uptime, update the stored uptime
/**
* @dev Helper function that extracts the uptime from a ValidationUptimeMessage Warp message
* If the uptime is greater than the stored uptime, update the stored uptime.
*/
function _updateUptime(bytes32 validationID, uint32 messageIndex) internal returns (uint64) {
(WarpMessage memory warpMessage, bool valid) =
WARP_MESSENGER.getVerifiedWarpMessage(messageIndex);
@@ -558,52 +560,55 @@
return delegationID;
}

/**
* @notice See {IPoSValidatorManager-completeDelegatorRegistration}.
*/
function completeDelegatorRegistration(bytes32 delegationID, uint32 messageIndex) external {
PoSValidatorManagerStorage storage $ = _getPoSValidatorManagerStorage();

Delegator memory delegator = $._delegatorStakes[delegationID];
bytes32 validationID = delegator.validationID;
Validator memory validator = getValidator(validationID);

// Ensure the delegator is pending added. Since anybody can call this function once
// delegator registration has been initialized, we need to make sure that this function is only
// callable after that has been done.
if (delegator.status != DelegatorStatus.PendingAdded) {
revert InvalidDelegatorStatus(delegator.status);
}

// In the case where the validator has completed its validation period, we can no
// longer stake and should move our status directly to completed and return the stake.
if (validator.status == ValidatorStatus.Completed) {
return _completeEndDelegation(delegationID);
}

// Unpack the Warp message
(bytes32 messageValidationID, uint64 nonce,) = ValidatorMessages
.unpackSubnetValidatorWeightMessage(_getPChainWarpMessage(messageIndex).payload);

if (validationID != messageValidationID) {
revert InvalidValidationID(delegator.validationID);
}

// The received nonce should be no greater than the highest sent nonce, and at least as high as
// the delegation's starting nonce. This allows a weight update using a higher nonce
// (which implicitly includes the delegation's weight update) to be used to complete delisting
// for an earlier delegation. This is necessary because the P-Chain is only willing to sign the latest weight update.
if (validator.messageNonce < nonce || delegator.startingNonce > nonce) {
revert InvalidNonce(nonce);
}

// Update the delegation status
$._delegatorStakes[delegationID].status = DelegatorStatus.Active;
$._delegatorStakes[delegationID].startedAt = uint64(block.timestamp);

emit DelegatorRegistered({
delegationID: delegationID,
validationID: validationID,
startTime: uint64(block.timestamp)
});
}

/**
* @notice See {IPoSValidatorManager-initializeEndDelegation}.
Original file line number Diff line number Diff line change
@@ -171,7 +171,7 @@ interface IPoSValidatorManager is IValidatorManager {
function claimValidationRewards(bytes32 validationID, uint32 messageIndex) external;

/**
* @notice Completes the delegator registration process by returning an acknowledgement of the registration of a
* @notice Completes the delegator registration process by submitting an acknowledgement of the registration of a
* validationID from the P-Chain. After this function is called, the validator's weight is updated in the contract state.
* Any P-Chain acknowledgement with a nonce greater than or equal to the nonce used to initialize registration of the
* delegator is valid, as long as that nonce has been sent by the contract. For the purposes of computing delegation rewards,
Original file line number Diff line number Diff line change
@@ -27,9 +27,7 @@ contract ExampleRewardCalculatorTest is Test {
validatorStartTime: 0,
stakingStartTime: DEFAULT_START_TIME,
stakingEndTime: DEFAULT_END_TIME,
uptimeSeconds: 0,
initialSupply: 0,
endSupply: 0
uptimeSeconds: 0
});
assertEq(output, 42e8);
}
Loading
You are viewing a condensed version of this merge commit. You can view the full changes here.