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

Track uptimes for delegation period #520

Merged
merged 8 commits into from
Sep 5, 2024
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

Large diffs are not rendered by default.

Large diffs are not rendered by default.

75 changes: 44 additions & 31 deletions contracts/staking/PoSValidatorManager.sol
Original file line number Diff line number Diff line change
Expand Up @@ -36,8 +36,6 @@
/// @notice Maps the validationID to a mapping of delegator address to pending end delegator messages.
mapping(bytes32 validationID => mapping(address delegator => bytes))
_pendingEndDelegatorMessages;
/// @notice Maps the validationID to the uptime of the validator.
mapping(bytes32 validationID => uint64) _validatorUptimes;
}
// solhint-enable private-vars-leading-underscore

Expand Down Expand Up @@ -79,11 +77,11 @@
uint64 minimumStakeDuration,
IRewardCalculator rewardCalculator
) internal onlyInitializing {
PoSValidatorManagerStorage storage s = _getPoSValidatorManagerStorage();
s._minimumStakeAmount = minimumStakeAmount;
s._maximumStakeAmount = maximumStakeAmount;
s._minimumStakeDuration = minimumStakeDuration;
s._rewardCalculator = rewardCalculator;
PoSValidatorManagerStorage storage $ = _getPoSValidatorManagerStorage();
$._minimumStakeAmount = minimumStakeAmount;
$._maximumStakeAmount = maximumStakeAmount;
$._minimumStakeDuration = minimumStakeDuration;
$._rewardCalculator = rewardCalculator;
}

function initializeEndValidation(
Expand All @@ -92,34 +90,36 @@
uint32 messageIndex
) external {
if (includeUptimeProof) {
PoSValidatorManagerStorage storage $ = _getPoSValidatorManagerStorage();
(WarpMessage memory warpMessage, bool valid) =
WARP_MESSENGER.getVerifiedWarpMessage(messageIndex);
require(valid, "PoSValidatorManager: invalid warp message");

require(
warpMessage.sourceChainID == WARP_MESSENGER.getBlockchainID(),
"PoSValidatorManager: invalid source chain ID"
);
require(
warpMessage.originSenderAddress == address(0),
"PoSValidatorManager: invalid origin sender address"
);

(bytes32 uptimeValidationID, uint64 uptime) =
ValidatorMessages.unpackValidationUptimeMessage(warpMessage.payload);
require(
validationID == uptimeValidationID,
"PoSValidatorManager: invalid uptime validation ID"
);

$._validatorUptimes[validationID] = uptime;
emit ValidationUptimeUpdated(validationID, uptime);
_getUptime(validationID, messageIndex);
}
// TODO: Calculate the reward for the validator, but do not unlock it

_initializeEndValidation(validationID);
}

function _getUptime(bytes32 validationID, uint32 messageIndex) internal view returns (uint64) {
(WarpMessage memory warpMessage, bool valid) =
WARP_MESSENGER.getVerifiedWarpMessage(messageIndex);
require(valid, "PoSValidatorManager: invalid warp message");

require(
warpMessage.sourceChainID == WARP_MESSENGER.getBlockchainID(),
"PoSValidatorManager: invalid source chain ID"
);
require(
warpMessage.originSenderAddress == address(0),
"PoSValidatorManager: invalid origin sender address"
);

(bytes32 uptimeValidationID, uint64 uptime) =
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just for my own understanding, does uptime return the absolute amount of time that the validator was up, or a percent of time that it was up during the staking period? I'm just thinking of whether other information is necessary to calculate rewards.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It returns the absolute number of seconds the validator has been up

ValidatorMessages.unpackValidationUptimeMessage(warpMessage.payload);
require(
validationID == uptimeValidationID, "PoSValidatorManager: invalid uptime validation ID"
);

return uptime;
}

function _processStake(uint256 stakeAmount) internal virtual returns (uint64) {
PoSValidatorManagerStorage storage $ = _getPoSValidatorManagerStorage();
// Lock the stake in the contract.
Expand Down Expand Up @@ -241,41 +241,52 @@
});
}

function initializeEndDelegation(bytes32 validationID) external {
function initializeEndDelegation(
bytes32 validationID,
bool includeUptimeProof,
uint32 messageIndex
) external {
uint64 uptime;
Fixed Show fixed Hide fixed
if (includeUptimeProof) {
uptime = _getUptime(validationID, messageIndex);
}

// TODO: Calculate the delegator's reward, but do not unlock it

PoSValidatorManagerStorage storage $ = _getPoSValidatorManagerStorage();

// Ensure the delegator is active
Delegator memory delegator = $._delegatorStakes[validationID][_msgSender()];
require(
delegator.status == DelegatorStatus.Active, "PoSValidatorManager: delegator not active"
);
uint64 nonce = _getAndIncrementNonce(validationID);
delegator.status = DelegatorStatus.PendingRemoved;
delegator.endedAt = uint64(block.timestamp);
delegator.endingNonce = nonce;

$._delegatorStakes[validationID][_msgSender()] = delegator;

Validator memory validator = _getValidator(validationID);
require(validator.weight > delegator.weight, "PoSValidatorManager: Invalid weight");
validator.weight -= delegator.weight;
_setValidator(validationID, validator);

// Submit the message to the Warp precompile.
bytes memory setValidatorWeightPayload = ValidatorMessages
.packSetSubnetValidatorWeightMessage(validationID, nonce, validator.weight);
$._pendingEndDelegatorMessages[validationID][_msgSender()] = setValidatorWeightPayload;
bytes32 messageID = WARP_MESSENGER.sendWarpMessage(setValidatorWeightPayload);

emit DelegatorRemovalInitialized({
validationID: validationID,
setWeightMessageID: messageID,
delegator: _msgSender(),
validatorWeight: validator.weight,
nonce: nonce,
endTime: block.timestamp
});
}

function resendEndDelegation(bytes32 validationID, address delegator) external {
_checkPendingEndDelegatorMessage(validationID, delegator);
Expand Down Expand Up @@ -310,6 +321,8 @@
// Update the delegator status
$._delegatorStakes[validationID][delegator].status = DelegatorStatus.Completed;

// TODO: Unlock the delegator's stake and their reward

emit DelegationEnded(validationID, delegator, nonce);
}

Expand Down
4 changes: 1 addition & 3 deletions contracts/staking/ValidatorManager.sol
Original file line number Diff line number Diff line change
Expand Up @@ -304,9 +304,7 @@ abstract contract ValidatorManager is
validator.status = endStatus;
$._validationPeriods[validationID] = validator;

// Unlock the stake.

// Calculate the reward for the validator.
// TODO: Unlock the stake.

// Emit event.
emit ValidationPeriodEnded(validationID, validator.status);
Expand Down
17 changes: 9 additions & 8 deletions contracts/staking/interfaces/IPoSValidatorManager.sol
Original file line number Diff line number Diff line change
Expand Up @@ -34,13 +34,6 @@ struct Delegator {
}

interface IPoSValidatorManager is IValidatorManager {
/**
* @notice Event emitted when a validator's uptime is updated.
* @param validationID The ID of the validation period
* @param uptime The new uptime of the validator
*/
event ValidationUptimeUpdated(bytes32 indexed validationID, uint64 uptime);

/**
* @notice Event emitted when a delegator registration is initiated
* @param validationID The ID of the validation period
Expand Down Expand Up @@ -139,8 +132,16 @@ interface IPoSValidatorManager is IValidatorManager {
* @notice Begins the process of removing a delegator from a validation period. The delegator must have been previously
* registered with the given validationID.
* @param validationID The ID of the validation period being removed.
* @param includeUptimeProof Whether or not an uptime proof is provided for the validation period.
* If no uptime proof is provided, the validation uptime for the delegation period will be assumed to be 0.
* @param messageIndex If {includeUptimeProof} is true, the index of the Warp message to be received providing the
* uptime proof.
*/
function initializeEndDelegation(bytes32 validationID) external;
function initializeEndDelegation(
bytes32 validationID,
bool includeUptimeProof,
uint32 messageIndex
) external;

/**
* @notice Resubmits a delegator end message to be sent to the P-Chain.
Expand Down
36 changes: 1 addition & 35 deletions contracts/staking/tests/PoSValidatorManagerTests.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,6 @@ abstract contract PoSValidatorManagerTest is ValidatorManagerTest {

PoSValidatorManager public posValidatorManager;

event ValidationUptimeUpdated(bytes32 indexed validationID, uint64 uptime);

event DelegatorAdded(
bytes32 indexed validationID,
bytes32 indexed setWeightMessageID,
Expand Down Expand Up @@ -55,38 +53,6 @@ abstract contract PoSValidatorManagerTest is ValidatorManagerTest {
bytes32 indexed validationID, address indexed delegator, uint64 indexed nonce
);

function testInitializeEndValidationWithUptimeProof() public {
bytes32 validationID = _setUpCompleteValidatorRegistration({
nodeID: DEFAULT_NODE_ID,
subnetID: DEFAULT_SUBNET_ID,
weight: DEFAULT_WEIGHT,
registrationExpiry: DEFAULT_EXPIRY,
blsPublicKey: DEFAULT_BLS_PUBLIC_KEY,
registrationTimestamp: DEFAULT_REGISTRATION_TIMESTAMP
});

_mockGetBlockchainID();
vm.mockCall(
WARP_PRECOMPILE_ADDRESS,
abi.encodeWithSelector(IWarpMessenger.getVerifiedWarpMessage.selector, uint32(0)),
abi.encode(
WarpMessage({
sourceChainID: DEFAULT_SOURCE_BLOCKCHAIN_ID,
originSenderAddress: address(0),
payload: ValidatorMessages.packValidationUptimeMessage(validationID, DEFAULT_UPTIME)
}),
true
)
);
vm.expectCall(
WARP_PRECOMPILE_ADDRESS, abi.encodeCall(IWarpMessenger.getVerifiedWarpMessage, 0)
);

vm.expectEmit(true, true, true, true, address(posValidatorManager));
emit ValidationUptimeUpdated(validationID, DEFAULT_UPTIME);
posValidatorManager.initializeEndValidation(validationID, true, 0);
}

function testInvalidUptimeWarpMessage() public {
bytes32 validationID = _setUpCompleteValidatorRegistration({
nodeID: DEFAULT_NODE_ID,
Expand Down Expand Up @@ -683,7 +649,7 @@ abstract contract PoSValidatorManagerTest is ValidatorManagerTest {
endTime: endDelegationTimestamp
});
vm.prank(delegator);
posValidatorManager.initializeEndDelegation(validationID);
posValidatorManager.initializeEndDelegation(validationID, false, 0);
return validationID;
}

Expand Down
4 changes: 4 additions & 0 deletions tests/utils/staking.go
Original file line number Diff line number Diff line change
Expand Up @@ -745,6 +745,8 @@ func InitializeEndERC20Delegation(
tx, err := stakingManager.InitializeEndDelegation(
opts,
validationID,
false,
0,
)
Expect(err).Should(BeNil())
return WaitForTransactionSuccess(context.Background(), subnet, tx.Hash())
Expand Down Expand Up @@ -826,6 +828,8 @@ func InitializeEndNativeDelegation(
tx, err := stakingManager.InitializeEndDelegation(
opts,
validationID,
false,
0,
)
Expect(err).Should(BeNil())
return WaitForTransactionSuccess(context.Background(), subnet, tx.Hash())
Expand Down