From 876058899695faa2b386c1ee8e57d7ec0c543697 Mon Sep 17 00:00:00 2001 From: Shawn <44221603+shaspitz@users.noreply.github.com> Date: Wed, 26 Jun 2024 12:03:04 -0700 Subject: [PATCH] refactor: val reg v1 now uses explicit "exist" fields (#172) * refactor: val reg now uses explicit "exist" fields * fix: example script * fix: bindings + abi --- contracts-abi/abi/ValidatorRegistryV1.abi | 46 ++++++++++++-- .../ValidatorRegistryV1.go | 42 ++++++++----- .../interfaces/IValidatorRegistryV1.sol | 5 +- contracts/contracts/utils/EventHeight.sol | 29 +++++++++ .../ValidatorRegistryV1.sol | 62 ++++++++++--------- .../ValidatorExampleScript.s.sol | 2 +- .../ValidatorRegistryV1Test.sol | 36 +++++------ 7 files changed, 151 insertions(+), 71 deletions(-) create mode 100644 contracts/contracts/utils/EventHeight.sol diff --git a/contracts-abi/abi/ValidatorRegistryV1.abi b/contracts-abi/abi/ValidatorRegistryV1.abi index 7b60d48b5..11e7841d5 100644 --- a/contracts-abi/abi/ValidatorRegistryV1.abi +++ b/contracts-abi/abi/ValidatorRegistryV1.abi @@ -110,6 +110,11 @@ "type": "tuple", "internalType": "struct IValidatorRegistryV1.StakedValidator", "components": [ + { + "name": "exists", + "type": "bool", + "internalType": "bool" + }, { "name": "balance", "type": "uint256", @@ -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" + } + ] } ] } @@ -413,6 +430,11 @@ } ], "outputs": [ + { + "name": "exists", + "type": "bool", + "internalType": "bool" + }, { "name": "balance", "type": "uint256", @@ -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" diff --git a/contracts-abi/clients/ValidatorRegistryV1/ValidatorRegistryV1.go b/contracts-abi/clients/ValidatorRegistryV1/ValidatorRegistryV1.go index c37280f2d..658f9a64d 100644 --- a/contracts-abi/clients/ValidatorRegistryV1/ValidatorRegistryV1.go +++ b/contracts-abi/clients/ValidatorRegistryV1/ValidatorRegistryV1.go @@ -29,16 +29,23 @@ var ( _ = abi.ConvertType ) +// EventHeightLibEventHeight is an auto generated low-level Go binding around an user-defined struct. +type EventHeightLibEventHeight struct { + Exists bool + BlockHeight *big.Int +} + // IValidatorRegistryV1StakedValidator is an auto generated low-level Go binding around an user-defined struct. type IValidatorRegistryV1StakedValidator struct { + Exists bool Balance *big.Int WithdrawalAddress common.Address - UnstakeBlockNum *big.Int + UnstakeHeight EventHeightLibEventHeight } // Validatorregistryv1MetaData contains all meta data concerning the Validatorregistryv1 contract. var Validatorregistryv1MetaData = &bind.MetaData{ - ABI: "[{\"type\":\"constructor\",\"inputs\":[],\"stateMutability\":\"nonpayable\"},{\"type\":\"fallback\",\"stateMutability\":\"payable\"},{\"type\":\"receive\",\"stateMutability\":\"payable\"},{\"type\":\"function\",\"name\":\"UPGRADE_INTERFACE_VERSION\",\"inputs\":[],\"outputs\":[{\"name\":\"\",\"type\":\"string\",\"internalType\":\"string\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"addStake\",\"inputs\":[{\"name\":\"blsPubKeys\",\"type\":\"bytes[]\",\"internalType\":\"bytes[]\"}],\"outputs\":[],\"stateMutability\":\"payable\"},{\"type\":\"function\",\"name\":\"delegateStake\",\"inputs\":[{\"name\":\"blsPubKeys\",\"type\":\"bytes[]\",\"internalType\":\"bytes[]\"},{\"name\":\"withdrawalAddress\",\"type\":\"address\",\"internalType\":\"address\"}],\"outputs\":[],\"stateMutability\":\"payable\"},{\"type\":\"function\",\"name\":\"getBlocksTillWithdrawAllowed\",\"inputs\":[{\"name\":\"valBLSPubKey\",\"type\":\"bytes\",\"internalType\":\"bytes\"}],\"outputs\":[{\"name\":\"\",\"type\":\"uint256\",\"internalType\":\"uint256\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"getStakedAmount\",\"inputs\":[{\"name\":\"valBLSPubKey\",\"type\":\"bytes\",\"internalType\":\"bytes\"}],\"outputs\":[{\"name\":\"\",\"type\":\"uint256\",\"internalType\":\"uint256\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"getStakedValidator\",\"inputs\":[{\"name\":\"valBLSPubKey\",\"type\":\"bytes\",\"internalType\":\"bytes\"}],\"outputs\":[{\"name\":\"\",\"type\":\"tuple\",\"internalType\":\"structIValidatorRegistryV1.StakedValidator\",\"components\":[{\"name\":\"balance\",\"type\":\"uint256\",\"internalType\":\"uint256\"},{\"name\":\"withdrawalAddress\",\"type\":\"address\",\"internalType\":\"address\"},{\"name\":\"unstakeBlockNum\",\"type\":\"uint256\",\"internalType\":\"uint256\"}]}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"initialize\",\"inputs\":[{\"name\":\"_minStake\",\"type\":\"uint256\",\"internalType\":\"uint256\"},{\"name\":\"_slashAmount\",\"type\":\"uint256\",\"internalType\":\"uint256\"},{\"name\":\"_slashOracle\",\"type\":\"address\",\"internalType\":\"address\"},{\"name\":\"_slashReceiver\",\"type\":\"address\",\"internalType\":\"address\"},{\"name\":\"_unstakePeriodBlocks\",\"type\":\"uint256\",\"internalType\":\"uint256\"},{\"name\":\"_owner\",\"type\":\"address\",\"internalType\":\"address\"}],\"outputs\":[],\"stateMutability\":\"nonpayable\"},{\"type\":\"function\",\"name\":\"isUnstaking\",\"inputs\":[{\"name\":\"valBLSPubKey\",\"type\":\"bytes\",\"internalType\":\"bytes\"}],\"outputs\":[{\"name\":\"\",\"type\":\"bool\",\"internalType\":\"bool\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"isValidatorOptedIn\",\"inputs\":[{\"name\":\"valBLSPubKey\",\"type\":\"bytes\",\"internalType\":\"bytes\"}],\"outputs\":[{\"name\":\"\",\"type\":\"bool\",\"internalType\":\"bool\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"minStake\",\"inputs\":[],\"outputs\":[{\"name\":\"\",\"type\":\"uint256\",\"internalType\":\"uint256\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"owner\",\"inputs\":[],\"outputs\":[{\"name\":\"\",\"type\":\"address\",\"internalType\":\"address\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"pause\",\"inputs\":[],\"outputs\":[],\"stateMutability\":\"nonpayable\"},{\"type\":\"function\",\"name\":\"paused\",\"inputs\":[],\"outputs\":[{\"name\":\"\",\"type\":\"bool\",\"internalType\":\"bool\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"proxiableUUID\",\"inputs\":[],\"outputs\":[{\"name\":\"\",\"type\":\"bytes32\",\"internalType\":\"bytes32\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"renounceOwnership\",\"inputs\":[],\"outputs\":[],\"stateMutability\":\"nonpayable\"},{\"type\":\"function\",\"name\":\"setMinStake\",\"inputs\":[{\"name\":\"newMinStake\",\"type\":\"uint256\",\"internalType\":\"uint256\"}],\"outputs\":[],\"stateMutability\":\"nonpayable\"},{\"type\":\"function\",\"name\":\"setSlashAmount\",\"inputs\":[{\"name\":\"newSlashAmount\",\"type\":\"uint256\",\"internalType\":\"uint256\"}],\"outputs\":[],\"stateMutability\":\"nonpayable\"},{\"type\":\"function\",\"name\":\"setSlashOracle\",\"inputs\":[{\"name\":\"newSlashOracle\",\"type\":\"address\",\"internalType\":\"address\"}],\"outputs\":[],\"stateMutability\":\"nonpayable\"},{\"type\":\"function\",\"name\":\"setSlashReceiver\",\"inputs\":[{\"name\":\"newSlashReceiver\",\"type\":\"address\",\"internalType\":\"address\"}],\"outputs\":[],\"stateMutability\":\"nonpayable\"},{\"type\":\"function\",\"name\":\"setUnstakePeriodBlocks\",\"inputs\":[{\"name\":\"newUnstakePeriodBlocks\",\"type\":\"uint256\",\"internalType\":\"uint256\"}],\"outputs\":[],\"stateMutability\":\"nonpayable\"},{\"type\":\"function\",\"name\":\"slash\",\"inputs\":[{\"name\":\"blsPubKeys\",\"type\":\"bytes[]\",\"internalType\":\"bytes[]\"}],\"outputs\":[],\"stateMutability\":\"nonpayable\"},{\"type\":\"function\",\"name\":\"slashAmount\",\"inputs\":[],\"outputs\":[{\"name\":\"\",\"type\":\"uint256\",\"internalType\":\"uint256\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"slashOracle\",\"inputs\":[],\"outputs\":[{\"name\":\"\",\"type\":\"address\",\"internalType\":\"address\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"slashReceiver\",\"inputs\":[],\"outputs\":[{\"name\":\"\",\"type\":\"address\",\"internalType\":\"address\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"stake\",\"inputs\":[{\"name\":\"blsPubKeys\",\"type\":\"bytes[]\",\"internalType\":\"bytes[]\"}],\"outputs\":[],\"stateMutability\":\"payable\"},{\"type\":\"function\",\"name\":\"stakedValidators\",\"inputs\":[{\"name\":\"\",\"type\":\"bytes\",\"internalType\":\"bytes\"}],\"outputs\":[{\"name\":\"balance\",\"type\":\"uint256\",\"internalType\":\"uint256\"},{\"name\":\"withdrawalAddress\",\"type\":\"address\",\"internalType\":\"address\"},{\"name\":\"unstakeBlockNum\",\"type\":\"uint256\",\"internalType\":\"uint256\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"transferOwnership\",\"inputs\":[{\"name\":\"newOwner\",\"type\":\"address\",\"internalType\":\"address\"}],\"outputs\":[],\"stateMutability\":\"nonpayable\"},{\"type\":\"function\",\"name\":\"unpause\",\"inputs\":[],\"outputs\":[],\"stateMutability\":\"nonpayable\"},{\"type\":\"function\",\"name\":\"unstake\",\"inputs\":[{\"name\":\"blsPubKeys\",\"type\":\"bytes[]\",\"internalType\":\"bytes[]\"}],\"outputs\":[],\"stateMutability\":\"nonpayable\"},{\"type\":\"function\",\"name\":\"unstakePeriodBlocks\",\"inputs\":[],\"outputs\":[{\"name\":\"\",\"type\":\"uint256\",\"internalType\":\"uint256\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"upgradeToAndCall\",\"inputs\":[{\"name\":\"newImplementation\",\"type\":\"address\",\"internalType\":\"address\"},{\"name\":\"data\",\"type\":\"bytes\",\"internalType\":\"bytes\"}],\"outputs\":[],\"stateMutability\":\"payable\"},{\"type\":\"function\",\"name\":\"withdraw\",\"inputs\":[{\"name\":\"blsPubKeys\",\"type\":\"bytes[]\",\"internalType\":\"bytes[]\"}],\"outputs\":[],\"stateMutability\":\"nonpayable\"},{\"type\":\"event\",\"name\":\"Initialized\",\"inputs\":[{\"name\":\"version\",\"type\":\"uint64\",\"indexed\":false,\"internalType\":\"uint64\"}],\"anonymous\":false},{\"type\":\"event\",\"name\":\"MinStakeSet\",\"inputs\":[{\"name\":\"msgSender\",\"type\":\"address\",\"indexed\":true,\"internalType\":\"address\"},{\"name\":\"newMinStake\",\"type\":\"uint256\",\"indexed\":false,\"internalType\":\"uint256\"}],\"anonymous\":false},{\"type\":\"event\",\"name\":\"OwnershipTransferred\",\"inputs\":[{\"name\":\"previousOwner\",\"type\":\"address\",\"indexed\":true,\"internalType\":\"address\"},{\"name\":\"newOwner\",\"type\":\"address\",\"indexed\":true,\"internalType\":\"address\"}],\"anonymous\":false},{\"type\":\"event\",\"name\":\"Paused\",\"inputs\":[{\"name\":\"account\",\"type\":\"address\",\"indexed\":false,\"internalType\":\"address\"}],\"anonymous\":false},{\"type\":\"event\",\"name\":\"SlashAmountSet\",\"inputs\":[{\"name\":\"msgSender\",\"type\":\"address\",\"indexed\":true,\"internalType\":\"address\"},{\"name\":\"newSlashAmount\",\"type\":\"uint256\",\"indexed\":false,\"internalType\":\"uint256\"}],\"anonymous\":false},{\"type\":\"event\",\"name\":\"SlashOracleSet\",\"inputs\":[{\"name\":\"msgSender\",\"type\":\"address\",\"indexed\":true,\"internalType\":\"address\"},{\"name\":\"newSlashOracle\",\"type\":\"address\",\"indexed\":false,\"internalType\":\"address\"}],\"anonymous\":false},{\"type\":\"event\",\"name\":\"SlashReceiverSet\",\"inputs\":[{\"name\":\"msgSender\",\"type\":\"address\",\"indexed\":true,\"internalType\":\"address\"},{\"name\":\"newSlashReceiver\",\"type\":\"address\",\"indexed\":false,\"internalType\":\"address\"}],\"anonymous\":false},{\"type\":\"event\",\"name\":\"Slashed\",\"inputs\":[{\"name\":\"msgSender\",\"type\":\"address\",\"indexed\":true,\"internalType\":\"address\"},{\"name\":\"slashReceiver\",\"type\":\"address\",\"indexed\":true,\"internalType\":\"address\"},{\"name\":\"withdrawalAddress\",\"type\":\"address\",\"indexed\":true,\"internalType\":\"address\"},{\"name\":\"valBLSPubKey\",\"type\":\"bytes\",\"indexed\":false,\"internalType\":\"bytes\"},{\"name\":\"amount\",\"type\":\"uint256\",\"indexed\":false,\"internalType\":\"uint256\"}],\"anonymous\":false},{\"type\":\"event\",\"name\":\"StakeAdded\",\"inputs\":[{\"name\":\"msgSender\",\"type\":\"address\",\"indexed\":true,\"internalType\":\"address\"},{\"name\":\"withdrawalAddress\",\"type\":\"address\",\"indexed\":true,\"internalType\":\"address\"},{\"name\":\"valBLSPubKey\",\"type\":\"bytes\",\"indexed\":false,\"internalType\":\"bytes\"},{\"name\":\"amount\",\"type\":\"uint256\",\"indexed\":false,\"internalType\":\"uint256\"},{\"name\":\"newBalance\",\"type\":\"uint256\",\"indexed\":false,\"internalType\":\"uint256\"}],\"anonymous\":false},{\"type\":\"event\",\"name\":\"StakeWithdrawn\",\"inputs\":[{\"name\":\"msgSender\",\"type\":\"address\",\"indexed\":true,\"internalType\":\"address\"},{\"name\":\"withdrawalAddress\",\"type\":\"address\",\"indexed\":true,\"internalType\":\"address\"},{\"name\":\"valBLSPubKey\",\"type\":\"bytes\",\"indexed\":false,\"internalType\":\"bytes\"},{\"name\":\"amount\",\"type\":\"uint256\",\"indexed\":false,\"internalType\":\"uint256\"}],\"anonymous\":false},{\"type\":\"event\",\"name\":\"Staked\",\"inputs\":[{\"name\":\"msgSender\",\"type\":\"address\",\"indexed\":true,\"internalType\":\"address\"},{\"name\":\"withdrawalAddress\",\"type\":\"address\",\"indexed\":true,\"internalType\":\"address\"},{\"name\":\"valBLSPubKey\",\"type\":\"bytes\",\"indexed\":false,\"internalType\":\"bytes\"},{\"name\":\"amount\",\"type\":\"uint256\",\"indexed\":false,\"internalType\":\"uint256\"}],\"anonymous\":false},{\"type\":\"event\",\"name\":\"Unpaused\",\"inputs\":[{\"name\":\"account\",\"type\":\"address\",\"indexed\":false,\"internalType\":\"address\"}],\"anonymous\":false},{\"type\":\"event\",\"name\":\"UnstakePeriodBlocksSet\",\"inputs\":[{\"name\":\"msgSender\",\"type\":\"address\",\"indexed\":true,\"internalType\":\"address\"},{\"name\":\"newUnstakePeriodBlocks\",\"type\":\"uint256\",\"indexed\":false,\"internalType\":\"uint256\"}],\"anonymous\":false},{\"type\":\"event\",\"name\":\"Unstaked\",\"inputs\":[{\"name\":\"msgSender\",\"type\":\"address\",\"indexed\":true,\"internalType\":\"address\"},{\"name\":\"withdrawalAddress\",\"type\":\"address\",\"indexed\":true,\"internalType\":\"address\"},{\"name\":\"valBLSPubKey\",\"type\":\"bytes\",\"indexed\":false,\"internalType\":\"bytes\"},{\"name\":\"amount\",\"type\":\"uint256\",\"indexed\":false,\"internalType\":\"uint256\"}],\"anonymous\":false},{\"type\":\"event\",\"name\":\"Upgraded\",\"inputs\":[{\"name\":\"implementation\",\"type\":\"address\",\"indexed\":true,\"internalType\":\"address\"}],\"anonymous\":false},{\"type\":\"error\",\"name\":\"AddressEmptyCode\",\"inputs\":[{\"name\":\"target\",\"type\":\"address\",\"internalType\":\"address\"}]},{\"type\":\"error\",\"name\":\"ERC1967InvalidImplementation\",\"inputs\":[{\"name\":\"implementation\",\"type\":\"address\",\"internalType\":\"address\"}]},{\"type\":\"error\",\"name\":\"ERC1967NonPayable\",\"inputs\":[]},{\"type\":\"error\",\"name\":\"EnforcedPause\",\"inputs\":[]},{\"type\":\"error\",\"name\":\"ExpectedPause\",\"inputs\":[]},{\"type\":\"error\",\"name\":\"FailedInnerCall\",\"inputs\":[]},{\"type\":\"error\",\"name\":\"InvalidInitialization\",\"inputs\":[]},{\"type\":\"error\",\"name\":\"NotInitializing\",\"inputs\":[]},{\"type\":\"error\",\"name\":\"OwnableInvalidOwner\",\"inputs\":[{\"name\":\"owner\",\"type\":\"address\",\"internalType\":\"address\"}]},{\"type\":\"error\",\"name\":\"OwnableUnauthorizedAccount\",\"inputs\":[{\"name\":\"account\",\"type\":\"address\",\"internalType\":\"address\"}]},{\"type\":\"error\",\"name\":\"UUPSUnauthorizedCallContext\",\"inputs\":[]},{\"type\":\"error\",\"name\":\"UUPSUnsupportedProxiableUUID\",\"inputs\":[{\"name\":\"slot\",\"type\":\"bytes32\",\"internalType\":\"bytes32\"}]}]", + ABI: "[{\"type\":\"constructor\",\"inputs\":[],\"stateMutability\":\"nonpayable\"},{\"type\":\"fallback\",\"stateMutability\":\"payable\"},{\"type\":\"receive\",\"stateMutability\":\"payable\"},{\"type\":\"function\",\"name\":\"UPGRADE_INTERFACE_VERSION\",\"inputs\":[],\"outputs\":[{\"name\":\"\",\"type\":\"string\",\"internalType\":\"string\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"addStake\",\"inputs\":[{\"name\":\"blsPubKeys\",\"type\":\"bytes[]\",\"internalType\":\"bytes[]\"}],\"outputs\":[],\"stateMutability\":\"payable\"},{\"type\":\"function\",\"name\":\"delegateStake\",\"inputs\":[{\"name\":\"blsPubKeys\",\"type\":\"bytes[]\",\"internalType\":\"bytes[]\"},{\"name\":\"withdrawalAddress\",\"type\":\"address\",\"internalType\":\"address\"}],\"outputs\":[],\"stateMutability\":\"payable\"},{\"type\":\"function\",\"name\":\"getBlocksTillWithdrawAllowed\",\"inputs\":[{\"name\":\"valBLSPubKey\",\"type\":\"bytes\",\"internalType\":\"bytes\"}],\"outputs\":[{\"name\":\"\",\"type\":\"uint256\",\"internalType\":\"uint256\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"getStakedAmount\",\"inputs\":[{\"name\":\"valBLSPubKey\",\"type\":\"bytes\",\"internalType\":\"bytes\"}],\"outputs\":[{\"name\":\"\",\"type\":\"uint256\",\"internalType\":\"uint256\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"getStakedValidator\",\"inputs\":[{\"name\":\"valBLSPubKey\",\"type\":\"bytes\",\"internalType\":\"bytes\"}],\"outputs\":[{\"name\":\"\",\"type\":\"tuple\",\"internalType\":\"structIValidatorRegistryV1.StakedValidator\",\"components\":[{\"name\":\"exists\",\"type\":\"bool\",\"internalType\":\"bool\"},{\"name\":\"balance\",\"type\":\"uint256\",\"internalType\":\"uint256\"},{\"name\":\"withdrawalAddress\",\"type\":\"address\",\"internalType\":\"address\"},{\"name\":\"unstakeHeight\",\"type\":\"tuple\",\"internalType\":\"structEventHeightLib.EventHeight\",\"components\":[{\"name\":\"exists\",\"type\":\"bool\",\"internalType\":\"bool\"},{\"name\":\"blockHeight\",\"type\":\"uint256\",\"internalType\":\"uint256\"}]}]}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"initialize\",\"inputs\":[{\"name\":\"_minStake\",\"type\":\"uint256\",\"internalType\":\"uint256\"},{\"name\":\"_slashAmount\",\"type\":\"uint256\",\"internalType\":\"uint256\"},{\"name\":\"_slashOracle\",\"type\":\"address\",\"internalType\":\"address\"},{\"name\":\"_slashReceiver\",\"type\":\"address\",\"internalType\":\"address\"},{\"name\":\"_unstakePeriodBlocks\",\"type\":\"uint256\",\"internalType\":\"uint256\"},{\"name\":\"_owner\",\"type\":\"address\",\"internalType\":\"address\"}],\"outputs\":[],\"stateMutability\":\"nonpayable\"},{\"type\":\"function\",\"name\":\"isUnstaking\",\"inputs\":[{\"name\":\"valBLSPubKey\",\"type\":\"bytes\",\"internalType\":\"bytes\"}],\"outputs\":[{\"name\":\"\",\"type\":\"bool\",\"internalType\":\"bool\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"isValidatorOptedIn\",\"inputs\":[{\"name\":\"valBLSPubKey\",\"type\":\"bytes\",\"internalType\":\"bytes\"}],\"outputs\":[{\"name\":\"\",\"type\":\"bool\",\"internalType\":\"bool\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"minStake\",\"inputs\":[],\"outputs\":[{\"name\":\"\",\"type\":\"uint256\",\"internalType\":\"uint256\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"owner\",\"inputs\":[],\"outputs\":[{\"name\":\"\",\"type\":\"address\",\"internalType\":\"address\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"pause\",\"inputs\":[],\"outputs\":[],\"stateMutability\":\"nonpayable\"},{\"type\":\"function\",\"name\":\"paused\",\"inputs\":[],\"outputs\":[{\"name\":\"\",\"type\":\"bool\",\"internalType\":\"bool\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"proxiableUUID\",\"inputs\":[],\"outputs\":[{\"name\":\"\",\"type\":\"bytes32\",\"internalType\":\"bytes32\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"renounceOwnership\",\"inputs\":[],\"outputs\":[],\"stateMutability\":\"nonpayable\"},{\"type\":\"function\",\"name\":\"setMinStake\",\"inputs\":[{\"name\":\"newMinStake\",\"type\":\"uint256\",\"internalType\":\"uint256\"}],\"outputs\":[],\"stateMutability\":\"nonpayable\"},{\"type\":\"function\",\"name\":\"setSlashAmount\",\"inputs\":[{\"name\":\"newSlashAmount\",\"type\":\"uint256\",\"internalType\":\"uint256\"}],\"outputs\":[],\"stateMutability\":\"nonpayable\"},{\"type\":\"function\",\"name\":\"setSlashOracle\",\"inputs\":[{\"name\":\"newSlashOracle\",\"type\":\"address\",\"internalType\":\"address\"}],\"outputs\":[],\"stateMutability\":\"nonpayable\"},{\"type\":\"function\",\"name\":\"setSlashReceiver\",\"inputs\":[{\"name\":\"newSlashReceiver\",\"type\":\"address\",\"internalType\":\"address\"}],\"outputs\":[],\"stateMutability\":\"nonpayable\"},{\"type\":\"function\",\"name\":\"setUnstakePeriodBlocks\",\"inputs\":[{\"name\":\"newUnstakePeriodBlocks\",\"type\":\"uint256\",\"internalType\":\"uint256\"}],\"outputs\":[],\"stateMutability\":\"nonpayable\"},{\"type\":\"function\",\"name\":\"slash\",\"inputs\":[{\"name\":\"blsPubKeys\",\"type\":\"bytes[]\",\"internalType\":\"bytes[]\"}],\"outputs\":[],\"stateMutability\":\"nonpayable\"},{\"type\":\"function\",\"name\":\"slashAmount\",\"inputs\":[],\"outputs\":[{\"name\":\"\",\"type\":\"uint256\",\"internalType\":\"uint256\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"slashOracle\",\"inputs\":[],\"outputs\":[{\"name\":\"\",\"type\":\"address\",\"internalType\":\"address\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"slashReceiver\",\"inputs\":[],\"outputs\":[{\"name\":\"\",\"type\":\"address\",\"internalType\":\"address\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"stake\",\"inputs\":[{\"name\":\"blsPubKeys\",\"type\":\"bytes[]\",\"internalType\":\"bytes[]\"}],\"outputs\":[],\"stateMutability\":\"payable\"},{\"type\":\"function\",\"name\":\"stakedValidators\",\"inputs\":[{\"name\":\"\",\"type\":\"bytes\",\"internalType\":\"bytes\"}],\"outputs\":[{\"name\":\"exists\",\"type\":\"bool\",\"internalType\":\"bool\"},{\"name\":\"balance\",\"type\":\"uint256\",\"internalType\":\"uint256\"},{\"name\":\"withdrawalAddress\",\"type\":\"address\",\"internalType\":\"address\"},{\"name\":\"unstakeHeight\",\"type\":\"tuple\",\"internalType\":\"structEventHeightLib.EventHeight\",\"components\":[{\"name\":\"exists\",\"type\":\"bool\",\"internalType\":\"bool\"},{\"name\":\"blockHeight\",\"type\":\"uint256\",\"internalType\":\"uint256\"}]}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"transferOwnership\",\"inputs\":[{\"name\":\"newOwner\",\"type\":\"address\",\"internalType\":\"address\"}],\"outputs\":[],\"stateMutability\":\"nonpayable\"},{\"type\":\"function\",\"name\":\"unpause\",\"inputs\":[],\"outputs\":[],\"stateMutability\":\"nonpayable\"},{\"type\":\"function\",\"name\":\"unstake\",\"inputs\":[{\"name\":\"blsPubKeys\",\"type\":\"bytes[]\",\"internalType\":\"bytes[]\"}],\"outputs\":[],\"stateMutability\":\"nonpayable\"},{\"type\":\"function\",\"name\":\"unstakePeriodBlocks\",\"inputs\":[],\"outputs\":[{\"name\":\"\",\"type\":\"uint256\",\"internalType\":\"uint256\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"upgradeToAndCall\",\"inputs\":[{\"name\":\"newImplementation\",\"type\":\"address\",\"internalType\":\"address\"},{\"name\":\"data\",\"type\":\"bytes\",\"internalType\":\"bytes\"}],\"outputs\":[],\"stateMutability\":\"payable\"},{\"type\":\"function\",\"name\":\"withdraw\",\"inputs\":[{\"name\":\"blsPubKeys\",\"type\":\"bytes[]\",\"internalType\":\"bytes[]\"}],\"outputs\":[],\"stateMutability\":\"nonpayable\"},{\"type\":\"event\",\"name\":\"Initialized\",\"inputs\":[{\"name\":\"version\",\"type\":\"uint64\",\"indexed\":false,\"internalType\":\"uint64\"}],\"anonymous\":false},{\"type\":\"event\",\"name\":\"MinStakeSet\",\"inputs\":[{\"name\":\"msgSender\",\"type\":\"address\",\"indexed\":true,\"internalType\":\"address\"},{\"name\":\"newMinStake\",\"type\":\"uint256\",\"indexed\":false,\"internalType\":\"uint256\"}],\"anonymous\":false},{\"type\":\"event\",\"name\":\"OwnershipTransferred\",\"inputs\":[{\"name\":\"previousOwner\",\"type\":\"address\",\"indexed\":true,\"internalType\":\"address\"},{\"name\":\"newOwner\",\"type\":\"address\",\"indexed\":true,\"internalType\":\"address\"}],\"anonymous\":false},{\"type\":\"event\",\"name\":\"Paused\",\"inputs\":[{\"name\":\"account\",\"type\":\"address\",\"indexed\":false,\"internalType\":\"address\"}],\"anonymous\":false},{\"type\":\"event\",\"name\":\"SlashAmountSet\",\"inputs\":[{\"name\":\"msgSender\",\"type\":\"address\",\"indexed\":true,\"internalType\":\"address\"},{\"name\":\"newSlashAmount\",\"type\":\"uint256\",\"indexed\":false,\"internalType\":\"uint256\"}],\"anonymous\":false},{\"type\":\"event\",\"name\":\"SlashOracleSet\",\"inputs\":[{\"name\":\"msgSender\",\"type\":\"address\",\"indexed\":true,\"internalType\":\"address\"},{\"name\":\"newSlashOracle\",\"type\":\"address\",\"indexed\":false,\"internalType\":\"address\"}],\"anonymous\":false},{\"type\":\"event\",\"name\":\"SlashReceiverSet\",\"inputs\":[{\"name\":\"msgSender\",\"type\":\"address\",\"indexed\":true,\"internalType\":\"address\"},{\"name\":\"newSlashReceiver\",\"type\":\"address\",\"indexed\":false,\"internalType\":\"address\"}],\"anonymous\":false},{\"type\":\"event\",\"name\":\"Slashed\",\"inputs\":[{\"name\":\"msgSender\",\"type\":\"address\",\"indexed\":true,\"internalType\":\"address\"},{\"name\":\"slashReceiver\",\"type\":\"address\",\"indexed\":true,\"internalType\":\"address\"},{\"name\":\"withdrawalAddress\",\"type\":\"address\",\"indexed\":true,\"internalType\":\"address\"},{\"name\":\"valBLSPubKey\",\"type\":\"bytes\",\"indexed\":false,\"internalType\":\"bytes\"},{\"name\":\"amount\",\"type\":\"uint256\",\"indexed\":false,\"internalType\":\"uint256\"}],\"anonymous\":false},{\"type\":\"event\",\"name\":\"StakeAdded\",\"inputs\":[{\"name\":\"msgSender\",\"type\":\"address\",\"indexed\":true,\"internalType\":\"address\"},{\"name\":\"withdrawalAddress\",\"type\":\"address\",\"indexed\":true,\"internalType\":\"address\"},{\"name\":\"valBLSPubKey\",\"type\":\"bytes\",\"indexed\":false,\"internalType\":\"bytes\"},{\"name\":\"amount\",\"type\":\"uint256\",\"indexed\":false,\"internalType\":\"uint256\"},{\"name\":\"newBalance\",\"type\":\"uint256\",\"indexed\":false,\"internalType\":\"uint256\"}],\"anonymous\":false},{\"type\":\"event\",\"name\":\"StakeWithdrawn\",\"inputs\":[{\"name\":\"msgSender\",\"type\":\"address\",\"indexed\":true,\"internalType\":\"address\"},{\"name\":\"withdrawalAddress\",\"type\":\"address\",\"indexed\":true,\"internalType\":\"address\"},{\"name\":\"valBLSPubKey\",\"type\":\"bytes\",\"indexed\":false,\"internalType\":\"bytes\"},{\"name\":\"amount\",\"type\":\"uint256\",\"indexed\":false,\"internalType\":\"uint256\"}],\"anonymous\":false},{\"type\":\"event\",\"name\":\"Staked\",\"inputs\":[{\"name\":\"msgSender\",\"type\":\"address\",\"indexed\":true,\"internalType\":\"address\"},{\"name\":\"withdrawalAddress\",\"type\":\"address\",\"indexed\":true,\"internalType\":\"address\"},{\"name\":\"valBLSPubKey\",\"type\":\"bytes\",\"indexed\":false,\"internalType\":\"bytes\"},{\"name\":\"amount\",\"type\":\"uint256\",\"indexed\":false,\"internalType\":\"uint256\"}],\"anonymous\":false},{\"type\":\"event\",\"name\":\"Unpaused\",\"inputs\":[{\"name\":\"account\",\"type\":\"address\",\"indexed\":false,\"internalType\":\"address\"}],\"anonymous\":false},{\"type\":\"event\",\"name\":\"UnstakePeriodBlocksSet\",\"inputs\":[{\"name\":\"msgSender\",\"type\":\"address\",\"indexed\":true,\"internalType\":\"address\"},{\"name\":\"newUnstakePeriodBlocks\",\"type\":\"uint256\",\"indexed\":false,\"internalType\":\"uint256\"}],\"anonymous\":false},{\"type\":\"event\",\"name\":\"Unstaked\",\"inputs\":[{\"name\":\"msgSender\",\"type\":\"address\",\"indexed\":true,\"internalType\":\"address\"},{\"name\":\"withdrawalAddress\",\"type\":\"address\",\"indexed\":true,\"internalType\":\"address\"},{\"name\":\"valBLSPubKey\",\"type\":\"bytes\",\"indexed\":false,\"internalType\":\"bytes\"},{\"name\":\"amount\",\"type\":\"uint256\",\"indexed\":false,\"internalType\":\"uint256\"}],\"anonymous\":false},{\"type\":\"event\",\"name\":\"Upgraded\",\"inputs\":[{\"name\":\"implementation\",\"type\":\"address\",\"indexed\":true,\"internalType\":\"address\"}],\"anonymous\":false},{\"type\":\"error\",\"name\":\"AddressEmptyCode\",\"inputs\":[{\"name\":\"target\",\"type\":\"address\",\"internalType\":\"address\"}]},{\"type\":\"error\",\"name\":\"ERC1967InvalidImplementation\",\"inputs\":[{\"name\":\"implementation\",\"type\":\"address\",\"internalType\":\"address\"}]},{\"type\":\"error\",\"name\":\"ERC1967NonPayable\",\"inputs\":[]},{\"type\":\"error\",\"name\":\"EnforcedPause\",\"inputs\":[]},{\"type\":\"error\",\"name\":\"ExpectedPause\",\"inputs\":[]},{\"type\":\"error\",\"name\":\"FailedInnerCall\",\"inputs\":[]},{\"type\":\"error\",\"name\":\"InvalidInitialization\",\"inputs\":[]},{\"type\":\"error\",\"name\":\"NotInitializing\",\"inputs\":[]},{\"type\":\"error\",\"name\":\"OwnableInvalidOwner\",\"inputs\":[{\"name\":\"owner\",\"type\":\"address\",\"internalType\":\"address\"}]},{\"type\":\"error\",\"name\":\"OwnableUnauthorizedAccount\",\"inputs\":[{\"name\":\"account\",\"type\":\"address\",\"internalType\":\"address\"}]},{\"type\":\"error\",\"name\":\"UUPSUnauthorizedCallContext\",\"inputs\":[]},{\"type\":\"error\",\"name\":\"UUPSUnsupportedProxiableUUID\",\"inputs\":[{\"name\":\"slot\",\"type\":\"bytes32\",\"internalType\":\"bytes32\"}]}]", } // Validatorregistryv1ABI is the input ABI used to generate the binding from. @@ -282,7 +289,7 @@ func (_Validatorregistryv1 *Validatorregistryv1CallerSession) GetStakedAmount(va // GetStakedValidator is a free data retrieval call binding the contract method 0x1fc7c7c8. // -// Solidity: function getStakedValidator(bytes valBLSPubKey) view returns((uint256,address,uint256)) +// Solidity: function getStakedValidator(bytes valBLSPubKey) view returns((bool,uint256,address,(bool,uint256))) func (_Validatorregistryv1 *Validatorregistryv1Caller) GetStakedValidator(opts *bind.CallOpts, valBLSPubKey []byte) (IValidatorRegistryV1StakedValidator, error) { var out []interface{} err := _Validatorregistryv1.contract.Call(opts, &out, "getStakedValidator", valBLSPubKey) @@ -299,14 +306,14 @@ func (_Validatorregistryv1 *Validatorregistryv1Caller) GetStakedValidator(opts * // GetStakedValidator is a free data retrieval call binding the contract method 0x1fc7c7c8. // -// Solidity: function getStakedValidator(bytes valBLSPubKey) view returns((uint256,address,uint256)) +// Solidity: function getStakedValidator(bytes valBLSPubKey) view returns((bool,uint256,address,(bool,uint256))) func (_Validatorregistryv1 *Validatorregistryv1Session) GetStakedValidator(valBLSPubKey []byte) (IValidatorRegistryV1StakedValidator, error) { return _Validatorregistryv1.Contract.GetStakedValidator(&_Validatorregistryv1.CallOpts, valBLSPubKey) } // GetStakedValidator is a free data retrieval call binding the contract method 0x1fc7c7c8. // -// Solidity: function getStakedValidator(bytes valBLSPubKey) view returns((uint256,address,uint256)) +// Solidity: function getStakedValidator(bytes valBLSPubKey) view returns((bool,uint256,address,(bool,uint256))) func (_Validatorregistryv1 *Validatorregistryv1CallerSession) GetStakedValidator(valBLSPubKey []byte) (IValidatorRegistryV1StakedValidator, error) { return _Validatorregistryv1.Contract.GetStakedValidator(&_Validatorregistryv1.CallOpts, valBLSPubKey) } @@ -592,27 +599,30 @@ func (_Validatorregistryv1 *Validatorregistryv1CallerSession) SlashReceiver() (c // StakedValidators is a free data retrieval call binding the contract method 0xfced6425. // -// Solidity: function stakedValidators(bytes ) view returns(uint256 balance, address withdrawalAddress, uint256 unstakeBlockNum) +// Solidity: function stakedValidators(bytes ) view returns(bool exists, uint256 balance, address withdrawalAddress, (bool,uint256) unstakeHeight) func (_Validatorregistryv1 *Validatorregistryv1Caller) StakedValidators(opts *bind.CallOpts, arg0 []byte) (struct { + Exists bool Balance *big.Int WithdrawalAddress common.Address - UnstakeBlockNum *big.Int + UnstakeHeight EventHeightLibEventHeight }, error) { var out []interface{} err := _Validatorregistryv1.contract.Call(opts, &out, "stakedValidators", arg0) outstruct := new(struct { + Exists bool Balance *big.Int WithdrawalAddress common.Address - UnstakeBlockNum *big.Int + UnstakeHeight EventHeightLibEventHeight }) if err != nil { return *outstruct, err } - outstruct.Balance = *abi.ConvertType(out[0], new(*big.Int)).(**big.Int) - outstruct.WithdrawalAddress = *abi.ConvertType(out[1], new(common.Address)).(*common.Address) - outstruct.UnstakeBlockNum = *abi.ConvertType(out[2], new(*big.Int)).(**big.Int) + outstruct.Exists = *abi.ConvertType(out[0], new(bool)).(*bool) + outstruct.Balance = *abi.ConvertType(out[1], new(*big.Int)).(**big.Int) + outstruct.WithdrawalAddress = *abi.ConvertType(out[2], new(common.Address)).(*common.Address) + outstruct.UnstakeHeight = *abi.ConvertType(out[3], new(EventHeightLibEventHeight)).(*EventHeightLibEventHeight) return *outstruct, err @@ -620,22 +630,24 @@ func (_Validatorregistryv1 *Validatorregistryv1Caller) StakedValidators(opts *bi // StakedValidators is a free data retrieval call binding the contract method 0xfced6425. // -// Solidity: function stakedValidators(bytes ) view returns(uint256 balance, address withdrawalAddress, uint256 unstakeBlockNum) +// Solidity: function stakedValidators(bytes ) view returns(bool exists, uint256 balance, address withdrawalAddress, (bool,uint256) unstakeHeight) func (_Validatorregistryv1 *Validatorregistryv1Session) StakedValidators(arg0 []byte) (struct { + Exists bool Balance *big.Int WithdrawalAddress common.Address - UnstakeBlockNum *big.Int + UnstakeHeight EventHeightLibEventHeight }, error) { return _Validatorregistryv1.Contract.StakedValidators(&_Validatorregistryv1.CallOpts, arg0) } // StakedValidators is a free data retrieval call binding the contract method 0xfced6425. // -// Solidity: function stakedValidators(bytes ) view returns(uint256 balance, address withdrawalAddress, uint256 unstakeBlockNum) +// Solidity: function stakedValidators(bytes ) view returns(bool exists, uint256 balance, address withdrawalAddress, (bool,uint256) unstakeHeight) func (_Validatorregistryv1 *Validatorregistryv1CallerSession) StakedValidators(arg0 []byte) (struct { + Exists bool Balance *big.Int WithdrawalAddress common.Address - UnstakeBlockNum *big.Int + UnstakeHeight EventHeightLibEventHeight }, error) { return _Validatorregistryv1.Contract.StakedValidators(&_Validatorregistryv1.CallOpts, arg0) } diff --git a/contracts/contracts/interfaces/IValidatorRegistryV1.sol b/contracts/contracts/interfaces/IValidatorRegistryV1.sol index eb83f94e0..694bdd5f1 100644 --- a/contracts/contracts/interfaces/IValidatorRegistryV1.sol +++ b/contracts/contracts/interfaces/IValidatorRegistryV1.sol @@ -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 { @@ -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; } } diff --git a/contracts/contracts/utils/EventHeight.sol b/contracts/contracts/utils/EventHeight.sol new file mode 100644 index 000000000..f8904588d --- /dev/null +++ b/contracts/contracts/utils/EventHeight.sol @@ -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); + } +} diff --git a/contracts/contracts/validator-registry/ValidatorRegistryV1.sol b/contracts/contracts/validator-registry/ValidatorRegistryV1.sol index d6eec0807..5e7184d2c 100644 --- a/contracts/contracts/validator-registry/ValidatorRegistryV1.sol +++ b/contracts/contracts/validator-registry/ValidatorRegistryV1.sol @@ -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"; @@ -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++) { @@ -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); } @@ -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); } @@ -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); } @@ -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); } @@ -117,7 +126,7 @@ 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); } @@ -125,7 +134,8 @@ contract ValidatorRegistryV1 is IValidatorRegistryV1, ValidatorRegistryV1Storage * @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); } @@ -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); } @@ -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); @@ -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); } @@ -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; @@ -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); } @@ -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; } @@ -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. diff --git a/contracts/scripts/validator-registry/ValidatorExampleScript.s.sol b/contracts/scripts/validator-registry/ValidatorExampleScript.s.sol index a7ce6a35d..008ea696a 100644 --- a/contracts/scripts/validator-registry/ValidatorExampleScript.s.sol +++ b/contracts/scripts/validator-registry/ValidatorExampleScript.s.sol @@ -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 { diff --git a/contracts/test/validator-registry/ValidatorRegistryV1Test.sol b/contracts/test/validator-registry/ValidatorRegistryV1Test.sol index 3460eb1a8..47f73fcdb 100644 --- a/contracts/test/validator-registry/ValidatorRegistryV1Test.sol +++ b/contracts/test/validator-registry/ValidatorRegistryV1Test.sol @@ -158,7 +158,7 @@ contract ValidatorRegistryV1Test is Test { assertEq(validatorRegistry.getStakedAmount(user2BLSKey), 0); vm.startPrank(user2); - vm.expectRevert("Validator must have staked balance"); + vm.expectRevert("Validator record must exist"); validatorRegistry.unstake(validators); vm.stopPrank(); assertEq(validatorRegistry.getStakedAmount(user2BLSKey), 0); @@ -200,14 +200,14 @@ contract ValidatorRegistryV1Test is Test { vm.stopPrank(); vm.startPrank(user1); - vm.expectRevert("Unstake already initiated for validator"); + vm.expectRevert("Unstake must NOT be initiated for validator"); validatorRegistry.unstake(validators); vm.stopPrank(); vm.roll(500); vm.startPrank(user1); - vm.expectRevert("Unstake already initiated for validator"); + vm.expectRevert("Unstake must NOT be initiated for validator"); validatorRegistry.unstake(validators); vm.stopPrank(); } @@ -225,24 +225,24 @@ contract ValidatorRegistryV1Test is Test { vm.stopPrank(); assertFalse(validatorRegistry.isValidatorOptedIn(user1BLSKey)); - assertEq(validatorRegistry.getStakedValidator(user1BLSKey).unstakeBlockNum, block.number); + assertEq(validatorRegistry.getStakedValidator(user1BLSKey).unstakeHeight.blockHeight, block.number); assertEq(validatorRegistry.getStakedAmount(user1BLSKey), MIN_STAKE); vm.startPrank(user1); - vm.expectRevert("Validator staking record must be empty"); + vm.expectRevert("Validator record must NOT exist"); validatorRegistry.stake{value: MIN_STAKE}(validators); vm.stopPrank(); vm.roll(500); vm.startPrank(user1); - vm.expectRevert("Validator staking record must be empty"); + vm.expectRevert("Validator record must NOT exist"); validatorRegistry.stake{value: MIN_STAKE}(validators); vm.stopPrank(); vm.deal(user2, 10 ether); vm.startPrank(user2); - vm.expectRevert("Validator staking record must be empty"); + vm.expectRevert("Validator record must NOT exist"); validatorRegistry.stake{value: MIN_STAKE}(validators); vm.stopPrank(); @@ -279,7 +279,7 @@ contract ValidatorRegistryV1Test is Test { assertEq(validatorRegistry.getStakedValidator(user1BLSKey).balance, MIN_STAKE); assertFalse(validatorRegistry.isValidatorOptedIn(user1BLSKey)); - assertEq(validatorRegistry.getStakedValidator(user1BLSKey).unstakeBlockNum, block.number); + assertEq(validatorRegistry.getStakedValidator(user1BLSKey).unstakeHeight.blockHeight, block.number); assertEq(address(user1).balance, 8 ether); uint256 blockWaitPeriod = 11; @@ -296,11 +296,11 @@ contract ValidatorRegistryV1Test is Test { assertEq(validatorRegistry.getStakedValidator(user1BLSKey).balance, 0, "User1s staked balance should be 0 after withdrawal"); assertEq(validatorRegistry.getStakedValidator(user1BLSKey).withdrawalAddress, address(0), "User1s withdrawal address should be reset after withdrawal"); - assertEq(validatorRegistry.getStakedValidator(user1BLSKey).unstakeBlockNum, 0, "User1s unstake block number should be reset after withdrawal"); + assertEq(validatorRegistry.getStakedValidator(user1BLSKey).unstakeHeight.blockHeight, 0, "User1s unstake block number should be reset after withdrawal"); } function testSlashWithoutEnoughStake() public { - vm.expectRevert("Validator balance must be greater than or equal to slash amount"); + vm.expectRevert("Validator record must exist"); bytes[] memory validators = new bytes[](1); validators[0] = user1BLSKey; vm.prank(SLASH_ORACLE); @@ -341,7 +341,7 @@ contract ValidatorRegistryV1Test is Test { assertEq(address(SLASH_RECEIVER).balance, 0); assertEq(validatorRegistry.getStakedValidator(user1BLSKey).balance, 1 ether); assertEq(validatorRegistry.getStakedValidator(user1BLSKey).withdrawalAddress, user1); - assertEq(validatorRegistry.getStakedValidator(user1BLSKey).unstakeBlockNum, 0); + assertEq(validatorRegistry.getStakedValidator(user1BLSKey).unstakeHeight.blockHeight, 0); assertTrue(validatorRegistry.isValidatorOptedIn(user1BLSKey)); vm.roll(11); @@ -360,7 +360,7 @@ contract ValidatorRegistryV1Test is Test { assertEq(address(SLASH_RECEIVER).balance, 0.1 ether); assertEq(validatorRegistry.getStakedValidator(user1BLSKey).balance, 0.9 ether); assertEq(validatorRegistry.getStakedValidator(user1BLSKey).withdrawalAddress, user1); - assertEq(validatorRegistry.getStakedValidator(user1BLSKey).unstakeBlockNum, 11); + assertEq(validatorRegistry.getStakedValidator(user1BLSKey).unstakeHeight.blockHeight, 11); assertFalse(validatorRegistry.isValidatorOptedIn(user1BLSKey)); } @@ -380,7 +380,7 @@ contract ValidatorRegistryV1Test is Test { assertEq(address(SLASH_RECEIVER).balance, 0); assertEq(validatorRegistry.getStakedValidator(user1BLSKey).balance, 1 ether); assertEq(validatorRegistry.getStakedValidator(user1BLSKey).withdrawalAddress, user1); - assertEq(validatorRegistry.getStakedValidator(user1BLSKey).unstakeBlockNum, 11); + assertEq(validatorRegistry.getStakedValidator(user1BLSKey).unstakeHeight.blockHeight, 11); assertFalse(validatorRegistry.isValidatorOptedIn(user1BLSKey)); vm.roll(22); @@ -399,7 +399,7 @@ contract ValidatorRegistryV1Test is Test { assertEq(address(SLASH_RECEIVER).balance, 0.1 ether); assertEq(validatorRegistry.getStakedValidator(user1BLSKey).balance, 0.9 ether); assertEq(validatorRegistry.getStakedValidator(user1BLSKey).withdrawalAddress, user1); - assertEq(validatorRegistry.getStakedValidator(user1BLSKey).unstakeBlockNum, 22); + assertEq(validatorRegistry.getStakedValidator(user1BLSKey).unstakeHeight.blockHeight, 22); assertFalse(validatorRegistry.isValidatorOptedIn(user1BLSKey)); } @@ -420,12 +420,12 @@ contract ValidatorRegistryV1Test is Test { assertEq(validatorRegistry.getStakedValidator(user1BLSKey).balance, 1 ether); assertEq(validatorRegistry.getStakedValidator(user1BLSKey).withdrawalAddress, user1); - assertEq(validatorRegistry.getStakedValidator(user1BLSKey).unstakeBlockNum, 14); + assertEq(validatorRegistry.getStakedValidator(user1BLSKey).unstakeHeight.blockHeight, 14); assertFalse(validatorRegistry.isValidatorOptedIn(user1BLSKey)); assertEq(validatorRegistry.getStakedValidator(user2BLSKey).balance, 1 ether); assertEq(validatorRegistry.getStakedValidator(user2BLSKey).withdrawalAddress, user1); - assertEq(validatorRegistry.getStakedValidator(user2BLSKey).unstakeBlockNum, 0); + assertEq(validatorRegistry.getStakedValidator(user2BLSKey).unstakeHeight.blockHeight, 0); assertTrue(validatorRegistry.isValidatorOptedIn(user2BLSKey)); vm.roll(78); @@ -444,12 +444,12 @@ contract ValidatorRegistryV1Test is Test { assertEq(validatorRegistry.getStakedValidator(user1BLSKey).balance, 0.9 ether); assertEq(validatorRegistry.getStakedValidator(user1BLSKey).withdrawalAddress, user1); - assertEq(validatorRegistry.getStakedValidator(user1BLSKey).unstakeBlockNum, 78); + assertEq(validatorRegistry.getStakedValidator(user1BLSKey).unstakeHeight.blockHeight, 78); assertFalse(validatorRegistry.isValidatorOptedIn(user1BLSKey)); assertEq(validatorRegistry.getStakedValidator(user2BLSKey).balance, 0.9 ether); assertEq(validatorRegistry.getStakedValidator(user2BLSKey).withdrawalAddress, user1); - assertEq(validatorRegistry.getStakedValidator(user2BLSKey).unstakeBlockNum, 78); + assertEq(validatorRegistry.getStakedValidator(user2BLSKey).unstakeHeight.blockHeight, 78); assertFalse(validatorRegistry.isValidatorOptedIn(user2BLSKey)); }