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

refactor: val reg v1 now uses explicit "exist" fields #172

Merged
merged 3 commits into from
Jun 26, 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
46 changes: 40 additions & 6 deletions contracts-abi/abi/ValidatorRegistryV1.abi
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,11 @@
"type": "tuple",
"internalType": "struct IValidatorRegistryV1.StakedValidator",
"components": [
{
"name": "exists",
"type": "bool",
"internalType": "bool"
},
{
"name": "balance",
"type": "uint256",
Expand All @@ -121,9 +126,21 @@
"internalType": "address"
},
{
"name": "unstakeBlockNum",
"type": "uint256",
"internalType": "uint256"
"name": "unstakeHeight",
"type": "tuple",
"internalType": "struct EventHeightLib.EventHeight",
"components": [
{
"name": "exists",
"type": "bool",
"internalType": "bool"
},
{
"name": "blockHeight",
"type": "uint256",
"internalType": "uint256"
}
]
}
]
}
Expand Down Expand Up @@ -413,6 +430,11 @@
}
],
"outputs": [
{
"name": "exists",
"type": "bool",
"internalType": "bool"
},
{
"name": "balance",
"type": "uint256",
Expand All @@ -424,9 +446,21 @@
"internalType": "address"
},
{
"name": "unstakeBlockNum",
"type": "uint256",
"internalType": "uint256"
"name": "unstakeHeight",
"type": "tuple",
"internalType": "struct EventHeightLib.EventHeight",
"components": [
{
"name": "exists",
"type": "bool",
"internalType": "bool"
},
{
"name": "blockHeight",
"type": "uint256",
"internalType": "uint256"
}
]
}
],
"stateMutability": "view"
Expand Down
42 changes: 27 additions & 15 deletions contracts-abi/clients/ValidatorRegistryV1/ValidatorRegistryV1.go

Large diffs are not rendered by default.

5 changes: 4 additions & 1 deletion contracts/contracts/interfaces/IValidatorRegistryV1.sol
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
// SPDX-License-Identifier: BSL 1.1
pragma solidity ^0.8.20;

import { EventHeightLib } from "../utils/EventHeight.sol";

/// @title IValidatorRegistryV1
/// @notice Interface for the ValidatorRegistryV1 contract.
contract IValidatorRegistryV1 {
Expand Down Expand Up @@ -37,8 +39,9 @@ contract IValidatorRegistryV1 {

/// @dev Struct representing a validator staked with the registry.
struct StakedValidator {
bool exists;
uint256 balance;
address withdrawalAddress;
uint256 unstakeBlockNum;
EventHeightLib.EventHeight unstakeHeight;
}
}
29 changes: 29 additions & 0 deletions contracts/contracts/utils/EventHeight.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
// SPDX-License-Identifier: BSL 1.1
pragma solidity ^0.8.20;

library EventHeightLib {
/// @title EventHeight
/// @notice A struct to store the block height of an event, where the uint256 height value
/// is only relevant when the struct has been explicitly set.
struct EventHeight {
bool exists;
uint256 blockHeight;
}

/// @notice Sets the block height of an event
function set(EventHeight storage self, uint256 height) internal {
self.exists = true;
self.blockHeight = height;
}

/// @notice Deletes the event struct
function del(EventHeight storage self) internal {
self.exists = false;
self.blockHeight = 0;
}

/// @notice Gets the existance and possible block height of an event
function get(EventHeight storage self) internal view returns (bool, uint256) {
return (self.exists, self.blockHeight);
}
}
62 changes: 32 additions & 30 deletions contracts/contracts/validator-registry/ValidatorRegistryV1.sol
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ pragma solidity ^0.8.20;

import {IValidatorRegistryV1} from "../interfaces/IValidatorRegistryV1.sol";
import {ValidatorRegistryV1Storage} from "./ValidatorRegistryV1Storage.sol";
import {EventHeightLib} from "../utils/EventHeight.sol";
import {OwnableUpgradeable} from "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol";
import {PausableUpgradeable} from "@openzeppelin/contracts-upgradeable/utils/PausableUpgradeable.sol";
import {UUPSUpgradeable} from "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol";
Expand All @@ -13,14 +14,22 @@ import {UUPSUpgradeable} from "@openzeppelin/contracts-upgradeable/proxy/utils/U
contract ValidatorRegistryV1 is IValidatorRegistryV1, ValidatorRegistryV1Storage,
OwnableUpgradeable, PausableUpgradeable, UUPSUpgradeable {

/// @dev Modifier to confirm all BLS pubkeys have a staked balance.
modifier onlyHasStakingBalance(bytes[] calldata blsPubKeys) {
/// @dev Modifier to confirm a validator record exists for all provided BLS pubkeys.
modifier onlyExistentValidatorRecords(bytes[] calldata blsPubKeys) {
for (uint256 i = 0; i < blsPubKeys.length; i++) {
require(stakedValidators[blsPubKeys[i]].balance > 0, "Validator must have staked balance");
require(stakedValidators[blsPubKeys[i]].exists, "Validator record must exist");
}
_;
}


/// @dev Modifier to confirm a validator record does not exist for all provided BLS pubkeys.
modifier onlyNonExistentValidatorRecords(bytes[] calldata blsPubKeys) {
for (uint256 i = 0; i < blsPubKeys.length; i++) {
require(!stakedValidators[blsPubKeys[i]].exists, "Validator record must NOT exist");
}
_;
}

/// @dev Modifier to confirm the sender is the withdrawal address for all provided BLS pubkeys.
modifier onlyWithdrawalAddress(bytes[] calldata blsPubKeys) {
for (uint256 i = 0; i < blsPubKeys.length; i++) {
Expand Down Expand Up @@ -76,8 +85,8 @@ contract ValidatorRegistryV1 is IValidatorRegistryV1, ValidatorRegistryV1Storage
* @dev Stakes ETH on behalf of one or multiple validators via their BLS pubkey.
* @param blsPubKeys The validator BLS public keys to stake.
*/
function stake(bytes[] calldata blsPubKeys)
external payable onlyValidBLSPubKeys(blsPubKeys) whenNotPaused() {
function stake(bytes[] calldata blsPubKeys) external payable
onlyNonExistentValidatorRecords(blsPubKeys) onlyValidBLSPubKeys(blsPubKeys) whenNotPaused() {
_stake(blsPubKeys, msg.sender);
}

Expand All @@ -87,8 +96,8 @@ contract ValidatorRegistryV1 is IValidatorRegistryV1, ValidatorRegistryV1Storage
* @param blsPubKeys The validator BLS public keys to stake.
* @param withdrawalAddress The address to receive the staked ETH.
*/
function delegateStake(bytes[] calldata blsPubKeys, address withdrawalAddress)
external payable onlyValidBLSPubKeys(blsPubKeys) onlyOwner {
function delegateStake(bytes[] calldata blsPubKeys, address withdrawalAddress) external payable
onlyNonExistentValidatorRecords(blsPubKeys) onlyValidBLSPubKeys(blsPubKeys) onlyOwner {
require(withdrawalAddress != address(0), "Withdrawal address must be set");
_stake(blsPubKeys, withdrawalAddress);
}
Expand All @@ -98,8 +107,8 @@ contract ValidatorRegistryV1 is IValidatorRegistryV1, ValidatorRegistryV1Storage
* @dev A staking entry must already exist for each provided BLS pubkey.
* @param blsPubKeys The BLS public keys to add stake to.
*/
function addStake(bytes[] calldata blsPubKeys)
external payable whenNotPaused() {
function addStake(bytes[] calldata blsPubKeys) external payable
onlyExistentValidatorRecords(blsPubKeys) whenNotPaused() {
_addStake(blsPubKeys);
}

Expand All @@ -108,7 +117,7 @@ contract ValidatorRegistryV1 is IValidatorRegistryV1, ValidatorRegistryV1Storage
* @param blsPubKeys The BLS public keys to unstake.
*/
function unstake(bytes[] calldata blsPubKeys) external
onlyHasStakingBalance(blsPubKeys) onlyWithdrawalAddress(blsPubKeys) whenNotPaused() {
onlyExistentValidatorRecords(blsPubKeys) onlyWithdrawalAddress(blsPubKeys) whenNotPaused() {
_unstake(blsPubKeys);
}

Expand All @@ -117,15 +126,16 @@ contract ValidatorRegistryV1 is IValidatorRegistryV1, ValidatorRegistryV1Storage
* @param blsPubKeys The BLS public keys to withdraw.
*/
function withdraw(bytes[] calldata blsPubKeys) external
onlyHasStakingBalance(blsPubKeys) onlyWithdrawalAddress(blsPubKeys) whenNotPaused() {
onlyExistentValidatorRecords(blsPubKeys) onlyWithdrawalAddress(blsPubKeys) whenNotPaused() {
_withdraw(blsPubKeys);
}

/*
* @dev Allows oracle to slash some portion of stake for one or multiple validators via their BLS pubkey.
* @param blsPubKeys The BLS public keys to slash.
*/
function slash(bytes[] calldata blsPubKeys) external onlySlashOracle whenNotPaused() {
function slash(bytes[] calldata blsPubKeys) external
onlyExistentValidatorRecords(blsPubKeys) onlySlashOracle whenNotPaused() {
_slash(blsPubKeys);
}

Expand Down Expand Up @@ -174,16 +184,11 @@ contract ValidatorRegistryV1 is IValidatorRegistryV1, ValidatorRegistryV1Storage
uint256 splitAmount = msg.value / blsPubKeys.length;
for (uint256 i = 0; i < blsPubKeys.length; i++) {
bytes calldata pubKey = blsPubKeys[i];
require(
stakedValidators[pubKey].balance == 0 &&
stakedValidators[pubKey].withdrawalAddress == address(0) &&
stakedValidators[pubKey].unstakeBlockNum == 0,
"Validator staking record must be empty"
);
stakedValidators[pubKey] = StakedValidator({
exists: true,
balance: splitAmount,
withdrawalAddress: withdrawalAddress,
unstakeBlockNum: 0
unstakeHeight: EventHeightLib.EventHeight({ exists: false, blockHeight: 0 })
});
emit Staked(msg.sender, withdrawalAddress, pubKey, splitAmount);
}
Expand All @@ -198,8 +203,6 @@ contract ValidatorRegistryV1 is IValidatorRegistryV1, ValidatorRegistryV1Storage
uint256 splitAmount = msg.value / blsPubKeys.length;
for (uint256 i = 0; i < blsPubKeys.length; i++) {
bytes calldata pubKey = blsPubKeys[i];
require(stakedValidators[pubKey].withdrawalAddress != address(0),
"Validator staking record must exist. Call stake first");
stakedValidators[pubKey].balance += splitAmount;
emit StakeAdded(msg.sender, stakedValidators[pubKey].withdrawalAddress,
pubKey, splitAmount, stakedValidators[pubKey].balance);
Expand All @@ -222,9 +225,8 @@ contract ValidatorRegistryV1 is IValidatorRegistryV1, ValidatorRegistryV1Storage
* @param pubKey The single BLS public key to unstake.
*/
function _unstakeSingle(bytes calldata pubKey) internal {
require(stakedValidators[pubKey].unstakeBlockNum == 0,
"Unstake already initiated for validator");
stakedValidators[pubKey].unstakeBlockNum = block.number;
require(!_isUnstaking(pubKey), "Unstake must NOT be initiated for validator");
EventHeightLib.set(stakedValidators[pubKey].unstakeHeight, block.number);
emit Unstaked(msg.sender, stakedValidators[pubKey].withdrawalAddress,
pubKey, stakedValidators[pubKey].balance);
}
Expand All @@ -236,8 +238,8 @@ contract ValidatorRegistryV1 is IValidatorRegistryV1, ValidatorRegistryV1Storage
function _withdraw(bytes[] calldata blsPubKeys) internal {
for (uint256 i = 0; i < blsPubKeys.length; i++) {
bytes calldata pubKey = blsPubKeys[i];
require(stakedValidators[pubKey].unstakeBlockNum > 0, "Unstake must be initiated before withdrawal");
require(block.number >= stakedValidators[pubKey].unstakeBlockNum + unstakePeriodBlocks,
require(_isUnstaking(pubKey), "Unstake must be initiated before withdrawal");
require(block.number >= stakedValidators[pubKey].unstakeHeight.blockHeight + unstakePeriodBlocks,
"withdrawal not allowed yet. Blocks requirement not met.");
uint256 balance = stakedValidators[pubKey].balance;
address withdrawalAddress = stakedValidators[pubKey].withdrawalAddress;
Expand All @@ -260,7 +262,7 @@ contract ValidatorRegistryV1 is IValidatorRegistryV1, ValidatorRegistryV1Storage
payable(slashReceiver).transfer(slashAmount);
if (_isUnstaking(pubKey)) {
// If validator is already unstaking, reset their unstake block number
stakedValidators[pubKey].unstakeBlockNum = block.number;
EventHeightLib.set(stakedValidators[pubKey].unstakeHeight, block.number);
} else {
_unstakeSingle(pubKey);
}
Expand Down Expand Up @@ -328,7 +330,7 @@ contract ValidatorRegistryV1 is IValidatorRegistryV1, ValidatorRegistryV1Storage
/// @dev Returns the number of blocks remaining until an unstaking validator can withdraw their staked ETH.
function getBlocksTillWithdrawAllowed(bytes calldata valBLSPubKey) external view returns (uint256) {
require(_isUnstaking(valBLSPubKey), "Unstake must be initiated to check withdrawal eligibility");
uint256 blocksSinceUnstakeInitiated = block.number - stakedValidators[valBLSPubKey].unstakeBlockNum;
uint256 blocksSinceUnstakeInitiated = block.number - stakedValidators[valBLSPubKey].unstakeHeight.blockHeight;
return blocksSinceUnstakeInitiated > unstakePeriodBlocks ? 0 : unstakePeriodBlocks - blocksSinceUnstakeInitiated;
}

Expand All @@ -339,7 +341,7 @@ contract ValidatorRegistryV1 is IValidatorRegistryV1, ValidatorRegistryV1Storage

/// @dev Internal function to check if a validator is currently unstaking.
function _isUnstaking(bytes calldata valBLSPubKey) internal view returns (bool) {
return stakedValidators[valBLSPubKey].unstakeBlockNum > 0;
return stakedValidators[valBLSPubKey].unstakeHeight.exists;
}

/// @dev Fallback function to revert all calls, ensuring no unintended interactions.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ abstract contract ExampleScript is Script {
IValidatorRegistryV1.StakedValidator memory stakedValidator = _validatorRegistry.getStakedValidator(blsKeys[i]);
console.log("Staked Validator balance: %s", stakedValidator.balance);
console.log("Staked Validator withdrawalAddress: %s", stakedValidator.withdrawalAddress);
console.log("Staked Validator unstakeBlockNum: %s", stakedValidator.unstakeBlockNum);
console.log("Staked Validator unstakeBlockNum: %s", stakedValidator.unstakeHeight.blockHeight);
}
}
function checkWithdrawal(bytes[] memory blsKeys) public view {
Expand Down
Loading
Loading