From 6608e957e7aaf3de77daba4bd402fd692a2cb0ea Mon Sep 17 00:00:00 2001 From: owen-eth Date: Sun, 9 Nov 2025 13:30:25 -0500 Subject: [PATCH 1/4] refactored + added sig verification to rocketpool registry --- contracts-abi/abi/RocketMinipoolRegistry.abi | 193 +++++++++ .../RocketMinipoolRegistry.go | 330 +++++++++++++++- .../interfaces/IRocketMinipoolRegistry.sol | 18 + .../rocketpool/RocketMinipoolRegistry.sol | 197 +++++++-- .../RocketMinipoolRegistryStorage.sol | 2 + .../rocketpool/RocketMinipoolRegistryTest.sol | 42 +- .../rocketpool/RocketMinipoolSigTest.sol | 374 ++++++++++++++++++ 7 files changed, 1108 insertions(+), 48 deletions(-) create mode 100644 contracts/test/validator-registry/rocketpool/RocketMinipoolSigTest.sol diff --git a/contracts-abi/abi/RocketMinipoolRegistry.abi b/contracts-abi/abi/RocketMinipoolRegistry.abi index f7f515a09..4e4379917 100644 --- a/contracts-abi/abi/RocketMinipoolRegistry.abi +++ b/contracts-abi/abi/RocketMinipoolRegistry.abi @@ -45,6 +45,29 @@ "outputs": [], "stateMutability": "nonpayable" }, + { + "type": "function", + "name": "deregisterValidatorsWithSig", + "inputs": [ + { + "name": "valPubKeys", + "type": "bytes[]", + "internalType": "bytes[]" + }, + { + "name": "signature", + "type": "bytes", + "internalType": "bytes" + }, + { + "name": "deadline", + "type": "uint256", + "internalType": "uint256" + } + ], + "outputs": [], + "stateMutability": "nonpayable" + }, { "type": "function", "name": "deregistrationPeriod", @@ -58,6 +81,49 @@ ], "stateMutability": "view" }, + { + "type": "function", + "name": "eip712Domain", + "inputs": [], + "outputs": [ + { + "name": "fields", + "type": "bytes1", + "internalType": "bytes1" + }, + { + "name": "name", + "type": "string", + "internalType": "string" + }, + { + "name": "version", + "type": "string", + "internalType": "string" + }, + { + "name": "chainId", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "verifyingContract", + "type": "address", + "internalType": "address" + }, + { + "name": "salt", + "type": "bytes32", + "internalType": "bytes32" + }, + { + "name": "extensions", + "type": "uint256[]", + "internalType": "uint256[]" + } + ], + "stateMutability": "view" + }, { "type": "function", "name": "freeze", @@ -339,6 +405,25 @@ ], "stateMutability": "view" }, + { + "type": "function", + "name": "nonces", + "inputs": [ + { + "name": "", + "type": "address", + "internalType": "address" + } + ], + "outputs": [ + { + "name": "", + "type": "uint256", + "internalType": "uint256" + } + ], + "stateMutability": "view" + }, { "type": "function", "name": "owner", @@ -411,6 +496,25 @@ ], "stateMutability": "view" }, + { + "type": "function", + "name": "pubkeysHash", + "inputs": [ + { + "name": "pubkeys", + "type": "bytes[]", + "internalType": "bytes[]" + } + ], + "outputs": [ + { + "name": "", + "type": "bytes32", + "internalType": "bytes32" + } + ], + "stateMutability": "pure" + }, { "type": "function", "name": "registerValidators", @@ -424,6 +528,29 @@ "outputs": [], "stateMutability": "nonpayable" }, + { + "type": "function", + "name": "registerValidatorsWithSig", + "inputs": [ + { + "name": "valPubKeys", + "type": "bytes[]", + "internalType": "bytes[]" + }, + { + "name": "signature", + "type": "bytes", + "internalType": "bytes" + }, + { + "name": "deadline", + "type": "uint256", + "internalType": "uint256" + } + ], + "outputs": [], + "stateMutability": "nonpayable" + }, { "type": "function", "name": "renounceOwnership", @@ -444,6 +571,29 @@ "outputs": [], "stateMutability": "nonpayable" }, + { + "type": "function", + "name": "requestValidatorDeregistrationWithSig", + "inputs": [ + { + "name": "valPubKeys", + "type": "bytes[]", + "internalType": "bytes[]" + }, + { + "name": "signature", + "type": "bytes", + "internalType": "bytes" + }, + { + "name": "deadline", + "type": "uint256", + "internalType": "uint256" + } + ], + "outputs": [], + "stateMutability": "nonpayable" + }, { "type": "function", "name": "rocketStorage", @@ -628,6 +778,12 @@ ], "stateMutability": "view" }, + { + "type": "event", + "name": "EIP712DomainChanged", + "inputs": [], + "anonymous": false + }, { "type": "event", "name": "Initialized", @@ -871,6 +1027,11 @@ "name": "ExpectedPause", "inputs": [] }, + { + "type": "error", + "name": "ExpiredSignature", + "inputs": [] + }, { "type": "error", "name": "FailedInnerCall", @@ -918,6 +1079,11 @@ "name": "InvalidReceive", "inputs": [] }, + { + "type": "error", + "name": "InvalidSignature", + "inputs": [] + }, { "type": "error", "name": "MinipoolNotActive", @@ -929,6 +1095,22 @@ } ] }, + { + "type": "error", + "name": "MixedNodeBatch", + "inputs": [ + { + "name": "validatorPubkey", + "type": "bytes", + "internalType": "bytes" + } + ] + }, + { + "type": "error", + "name": "NoKeysProvided", + "inputs": [] + }, { "type": "error", "name": "NoMinipoolForKey", @@ -956,6 +1138,17 @@ } ] }, + { + "type": "error", + "name": "NotWithdrawalAddress", + "inputs": [ + { + "name": "withdrawalAddress", + "type": "address", + "internalType": "address" + } + ] + }, { "type": "error", "name": "OnlyFreezeOracle", diff --git a/contracts-abi/clients/RocketMinipoolRegistry/RocketMinipoolRegistry.go b/contracts-abi/clients/RocketMinipoolRegistry/RocketMinipoolRegistry.go index 573cbd821..17cc76650 100644 --- a/contracts-abi/clients/RocketMinipoolRegistry/RocketMinipoolRegistry.go +++ b/contracts-abi/clients/RocketMinipoolRegistry/RocketMinipoolRegistry.go @@ -38,7 +38,7 @@ type IRocketMinipoolRegistryValidatorRegistration struct { // RocketminipoolregistryMetaData contains all meta data concerning the Rocketminipoolregistry contract. var RocketminipoolregistryMetaData = &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\":\"acceptOwnership\",\"inputs\":[],\"outputs\":[],\"stateMutability\":\"nonpayable\"},{\"type\":\"function\",\"name\":\"deregisterValidators\",\"inputs\":[{\"name\":\"valPubKeys\",\"type\":\"bytes[]\",\"internalType\":\"bytes[]\"}],\"outputs\":[],\"stateMutability\":\"nonpayable\"},{\"type\":\"function\",\"name\":\"deregistrationPeriod\",\"inputs\":[],\"outputs\":[{\"name\":\"\",\"type\":\"uint64\",\"internalType\":\"uint64\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"freeze\",\"inputs\":[{\"name\":\"valPubKeys\",\"type\":\"bytes[]\",\"internalType\":\"bytes[]\"}],\"outputs\":[],\"stateMutability\":\"nonpayable\"},{\"type\":\"function\",\"name\":\"freezeOracle\",\"inputs\":[],\"outputs\":[{\"name\":\"\",\"type\":\"address\",\"internalType\":\"address\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"getEligibleTimeForDeregistration\",\"inputs\":[{\"name\":\"validatorPubkey\",\"type\":\"bytes\",\"internalType\":\"bytes\"}],\"outputs\":[{\"name\":\"\",\"type\":\"uint64\",\"internalType\":\"uint64\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"getMinipoolFromPubkey\",\"inputs\":[{\"name\":\"validatorPubkey\",\"type\":\"bytes\",\"internalType\":\"bytes\"}],\"outputs\":[{\"name\":\"\",\"type\":\"address\",\"internalType\":\"address\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"getNodeAddressFromMinipool\",\"inputs\":[{\"name\":\"minipool\",\"type\":\"address\",\"internalType\":\"address\"}],\"outputs\":[{\"name\":\"\",\"type\":\"address\",\"internalType\":\"address\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"getNodeAddressFromPubkey\",\"inputs\":[{\"name\":\"validatorPubkey\",\"type\":\"bytes\",\"internalType\":\"bytes\"}],\"outputs\":[{\"name\":\"\",\"type\":\"address\",\"internalType\":\"address\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"getValidOperatorsForKey\",\"inputs\":[{\"name\":\"validatorPubkey\",\"type\":\"bytes\",\"internalType\":\"bytes\"}],\"outputs\":[{\"name\":\"\",\"type\":\"address\",\"internalType\":\"address\"},{\"name\":\"\",\"type\":\"address\",\"internalType\":\"address\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"getValidatorRegInfo\",\"inputs\":[{\"name\":\"valPubKey\",\"type\":\"bytes\",\"internalType\":\"bytes\"}],\"outputs\":[{\"name\":\"\",\"type\":\"tuple\",\"internalType\":\"structIRocketMinipoolRegistry.ValidatorRegistration\",\"components\":[{\"name\":\"exists\",\"type\":\"bool\",\"internalType\":\"bool\"},{\"name\":\"deregTimestamp\",\"type\":\"uint64\",\"internalType\":\"uint64\"},{\"name\":\"freezeTimestamp\",\"type\":\"uint64\",\"internalType\":\"uint64\"}]}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"initialize\",\"inputs\":[{\"name\":\"owner\",\"type\":\"address\",\"internalType\":\"address\"},{\"name\":\"freezeOracle\",\"type\":\"address\",\"internalType\":\"address\"},{\"name\":\"unfreezeReceiver\",\"type\":\"address\",\"internalType\":\"address\"},{\"name\":\"rocketStorage\",\"type\":\"address\",\"internalType\":\"address\"},{\"name\":\"unfreezeFee\",\"type\":\"uint256\",\"internalType\":\"uint256\"},{\"name\":\"deregistrationPeriod\",\"type\":\"uint64\",\"internalType\":\"uint64\"}],\"outputs\":[],\"stateMutability\":\"nonpayable\"},{\"type\":\"function\",\"name\":\"isMinipoolActive\",\"inputs\":[{\"name\":\"minipool\",\"type\":\"address\",\"internalType\":\"address\"}],\"outputs\":[{\"name\":\"\",\"type\":\"bool\",\"internalType\":\"bool\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"isOperatorValidForKey\",\"inputs\":[{\"name\":\"operator\",\"type\":\"address\",\"internalType\":\"address\"},{\"name\":\"validatorPubkey\",\"type\":\"bytes\",\"internalType\":\"bytes\"}],\"outputs\":[{\"name\":\"\",\"type\":\"bool\",\"internalType\":\"bool\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"isValidatorOptedIn\",\"inputs\":[{\"name\":\"valPubKey\",\"type\":\"bytes\",\"internalType\":\"bytes\"}],\"outputs\":[{\"name\":\"\",\"type\":\"bool\",\"internalType\":\"bool\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"isValidatorRegistered\",\"inputs\":[{\"name\":\"validatorPubkey\",\"type\":\"bytes\",\"internalType\":\"bytes\"}],\"outputs\":[{\"name\":\"\",\"type\":\"bool\",\"internalType\":\"bool\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"owner\",\"inputs\":[],\"outputs\":[{\"name\":\"\",\"type\":\"address\",\"internalType\":\"address\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"ownerUnfreeze\",\"inputs\":[{\"name\":\"valPubKeys\",\"type\":\"bytes[]\",\"internalType\":\"bytes[]\"}],\"outputs\":[],\"stateMutability\":\"nonpayable\"},{\"type\":\"function\",\"name\":\"pause\",\"inputs\":[],\"outputs\":[],\"stateMutability\":\"nonpayable\"},{\"type\":\"function\",\"name\":\"paused\",\"inputs\":[],\"outputs\":[{\"name\":\"\",\"type\":\"bool\",\"internalType\":\"bool\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"pendingOwner\",\"inputs\":[],\"outputs\":[{\"name\":\"\",\"type\":\"address\",\"internalType\":\"address\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"proxiableUUID\",\"inputs\":[],\"outputs\":[{\"name\":\"\",\"type\":\"bytes32\",\"internalType\":\"bytes32\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"registerValidators\",\"inputs\":[{\"name\":\"valPubKeys\",\"type\":\"bytes[]\",\"internalType\":\"bytes[]\"}],\"outputs\":[],\"stateMutability\":\"nonpayable\"},{\"type\":\"function\",\"name\":\"renounceOwnership\",\"inputs\":[],\"outputs\":[],\"stateMutability\":\"nonpayable\"},{\"type\":\"function\",\"name\":\"requestValidatorDeregistration\",\"inputs\":[{\"name\":\"valPubKeys\",\"type\":\"bytes[]\",\"internalType\":\"bytes[]\"}],\"outputs\":[],\"stateMutability\":\"nonpayable\"},{\"type\":\"function\",\"name\":\"rocketStorage\",\"inputs\":[],\"outputs\":[{\"name\":\"\",\"type\":\"address\",\"internalType\":\"contractRocketStorageInterface\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"setDeregistrationPeriod\",\"inputs\":[{\"name\":\"newDeregistrationPeriod\",\"type\":\"uint64\",\"internalType\":\"uint64\"}],\"outputs\":[],\"stateMutability\":\"nonpayable\"},{\"type\":\"function\",\"name\":\"setFreezeOracle\",\"inputs\":[{\"name\":\"newFreezeOracle\",\"type\":\"address\",\"internalType\":\"address\"}],\"outputs\":[],\"stateMutability\":\"nonpayable\"},{\"type\":\"function\",\"name\":\"setRocketStorage\",\"inputs\":[{\"name\":\"newRocketStorage\",\"type\":\"address\",\"internalType\":\"address\"}],\"outputs\":[],\"stateMutability\":\"nonpayable\"},{\"type\":\"function\",\"name\":\"setUnfreezeFee\",\"inputs\":[{\"name\":\"newUnfreezeFee\",\"type\":\"uint256\",\"internalType\":\"uint256\"}],\"outputs\":[],\"stateMutability\":\"nonpayable\"},{\"type\":\"function\",\"name\":\"setUnfreezeReceiver\",\"inputs\":[{\"name\":\"newUnfreezeReceiver\",\"type\":\"address\",\"internalType\":\"address\"}],\"outputs\":[],\"stateMutability\":\"nonpayable\"},{\"type\":\"function\",\"name\":\"transferOwnership\",\"inputs\":[{\"name\":\"newOwner\",\"type\":\"address\",\"internalType\":\"address\"}],\"outputs\":[],\"stateMutability\":\"nonpayable\"},{\"type\":\"function\",\"name\":\"unfreeze\",\"inputs\":[{\"name\":\"valPubKeys\",\"type\":\"bytes[]\",\"internalType\":\"bytes[]\"}],\"outputs\":[],\"stateMutability\":\"payable\"},{\"type\":\"function\",\"name\":\"unfreezeFee\",\"inputs\":[],\"outputs\":[{\"name\":\"\",\"type\":\"uint256\",\"internalType\":\"uint256\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"unfreezeReceiver\",\"inputs\":[],\"outputs\":[{\"name\":\"\",\"type\":\"address\",\"internalType\":\"address\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"unpause\",\"inputs\":[],\"outputs\":[],\"stateMutability\":\"nonpayable\"},{\"type\":\"function\",\"name\":\"upgradeToAndCall\",\"inputs\":[{\"name\":\"newImplementation\",\"type\":\"address\",\"internalType\":\"address\"},{\"name\":\"data\",\"type\":\"bytes\",\"internalType\":\"bytes\"}],\"outputs\":[],\"stateMutability\":\"payable\"},{\"type\":\"function\",\"name\":\"validatorRegistrations\",\"inputs\":[{\"name\":\"\",\"type\":\"bytes\",\"internalType\":\"bytes\"}],\"outputs\":[{\"name\":\"exists\",\"type\":\"bool\",\"internalType\":\"bool\"},{\"name\":\"deregTimestamp\",\"type\":\"uint64\",\"internalType\":\"uint64\"},{\"name\":\"freezeTimestamp\",\"type\":\"uint64\",\"internalType\":\"uint64\"}],\"stateMutability\":\"view\"},{\"type\":\"event\",\"name\":\"Initialized\",\"inputs\":[{\"name\":\"version\",\"type\":\"uint64\",\"indexed\":false,\"internalType\":\"uint64\"}],\"anonymous\":false},{\"type\":\"event\",\"name\":\"OwnershipTransferStarted\",\"inputs\":[{\"name\":\"previousOwner\",\"type\":\"address\",\"indexed\":true,\"internalType\":\"address\"},{\"name\":\"newOwner\",\"type\":\"address\",\"indexed\":true,\"internalType\":\"address\"}],\"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\":\"Unpaused\",\"inputs\":[{\"name\":\"account\",\"type\":\"address\",\"indexed\":false,\"internalType\":\"address\"}],\"anonymous\":false},{\"type\":\"event\",\"name\":\"Upgraded\",\"inputs\":[{\"name\":\"implementation\",\"type\":\"address\",\"indexed\":true,\"internalType\":\"address\"}],\"anonymous\":false},{\"type\":\"event\",\"name\":\"ValidatorDeregistered\",\"inputs\":[{\"name\":\"validatorPubKey\",\"type\":\"bytes\",\"indexed\":true,\"internalType\":\"bytes\"},{\"name\":\"nodeAddress\",\"type\":\"address\",\"indexed\":true,\"internalType\":\"address\"}],\"anonymous\":false},{\"type\":\"event\",\"name\":\"ValidatorDeregistrationRequested\",\"inputs\":[{\"name\":\"validatorPubKey\",\"type\":\"bytes\",\"indexed\":true,\"internalType\":\"bytes\"},{\"name\":\"nodeAddress\",\"type\":\"address\",\"indexed\":true,\"internalType\":\"address\"}],\"anonymous\":false},{\"type\":\"event\",\"name\":\"ValidatorFrozen\",\"inputs\":[{\"name\":\"validatorPubKey\",\"type\":\"bytes\",\"indexed\":true,\"internalType\":\"bytes\"}],\"anonymous\":false},{\"type\":\"event\",\"name\":\"ValidatorRegistered\",\"inputs\":[{\"name\":\"validatorPubKey\",\"type\":\"bytes\",\"indexed\":true,\"internalType\":\"bytes\"},{\"name\":\"nodeAddress\",\"type\":\"address\",\"indexed\":true,\"internalType\":\"address\"}],\"anonymous\":false},{\"type\":\"event\",\"name\":\"ValidatorUnfrozen\",\"inputs\":[{\"name\":\"validatorPubKey\",\"type\":\"bytes\",\"indexed\":true,\"internalType\":\"bytes\"}],\"anonymous\":false},{\"type\":\"error\",\"name\":\"AddressEmptyCode\",\"inputs\":[{\"name\":\"target\",\"type\":\"address\",\"internalType\":\"address\"}]},{\"type\":\"error\",\"name\":\"DeregRequestAlreadyExists\",\"inputs\":[{\"name\":\"validatorPubkey\",\"type\":\"bytes\",\"internalType\":\"bytes\"}]},{\"type\":\"error\",\"name\":\"DeregRequestDoesNotExist\",\"inputs\":[{\"name\":\"validatorPubkey\",\"type\":\"bytes\",\"internalType\":\"bytes\"}]},{\"type\":\"error\",\"name\":\"DeregistrationTooSoon\",\"inputs\":[{\"name\":\"validatorPubkey\",\"type\":\"bytes\",\"internalType\":\"bytes\"}]},{\"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\":\"FrozenValidatorCannotDeregister\",\"inputs\":[{\"name\":\"validatorPubkey\",\"type\":\"bytes\",\"internalType\":\"bytes\"}]},{\"type\":\"error\",\"name\":\"InvalidBLSPubKeyLength\",\"inputs\":[{\"name\":\"expectedLength\",\"type\":\"uint256\",\"internalType\":\"uint256\"},{\"name\":\"actualLength\",\"type\":\"uint256\",\"internalType\":\"uint256\"}]},{\"type\":\"error\",\"name\":\"InvalidFallback\",\"inputs\":[]},{\"type\":\"error\",\"name\":\"InvalidInitialization\",\"inputs\":[]},{\"type\":\"error\",\"name\":\"InvalidReceive\",\"inputs\":[]},{\"type\":\"error\",\"name\":\"MinipoolNotActive\",\"inputs\":[{\"name\":\"validatorPubkey\",\"type\":\"bytes\",\"internalType\":\"bytes\"}]},{\"type\":\"error\",\"name\":\"NoMinipoolForKey\",\"inputs\":[{\"name\":\"validatorPubkey\",\"type\":\"bytes\",\"internalType\":\"bytes\"}]},{\"type\":\"error\",\"name\":\"NotInitializing\",\"inputs\":[]},{\"type\":\"error\",\"name\":\"NotMinipoolOperator\",\"inputs\":[{\"name\":\"validatorPubkey\",\"type\":\"bytes\",\"internalType\":\"bytes\"}]},{\"type\":\"error\",\"name\":\"OnlyFreezeOracle\",\"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\":\"ReentrancyGuardReentrantCall\",\"inputs\":[]},{\"type\":\"error\",\"name\":\"RefundFailed\",\"inputs\":[]},{\"type\":\"error\",\"name\":\"UUPSUnauthorizedCallContext\",\"inputs\":[]},{\"type\":\"error\",\"name\":\"UUPSUnsupportedProxiableUUID\",\"inputs\":[{\"name\":\"slot\",\"type\":\"bytes32\",\"internalType\":\"bytes32\"}]},{\"type\":\"error\",\"name\":\"UnfreezeFeeRequired\",\"inputs\":[{\"name\":\"requiredFee\",\"type\":\"uint256\",\"internalType\":\"uint256\"}]},{\"type\":\"error\",\"name\":\"UnfreezeTransferFailed\",\"inputs\":[]},{\"type\":\"error\",\"name\":\"ValidatorAlreadyFrozen\",\"inputs\":[{\"name\":\"validatorPubkey\",\"type\":\"bytes\",\"internalType\":\"bytes\"}]},{\"type\":\"error\",\"name\":\"ValidatorAlreadyRegistered\",\"inputs\":[{\"name\":\"validatorPubkey\",\"type\":\"bytes\",\"internalType\":\"bytes\"}]},{\"type\":\"error\",\"name\":\"ValidatorDeregistrationNotExpired\",\"inputs\":[{\"name\":\"validatorPubkey\",\"type\":\"bytes\",\"internalType\":\"bytes\"}]},{\"type\":\"error\",\"name\":\"ValidatorNotFrozen\",\"inputs\":[{\"name\":\"validatorPubkey\",\"type\":\"bytes\",\"internalType\":\"bytes\"}]},{\"type\":\"error\",\"name\":\"ValidatorNotRegistered\",\"inputs\":[{\"name\":\"validatorPubkey\",\"type\":\"bytes\",\"internalType\":\"bytes\"}]},{\"type\":\"error\",\"name\":\"ZeroParam\",\"inputs\":[]}]", + 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\":\"acceptOwnership\",\"inputs\":[],\"outputs\":[],\"stateMutability\":\"nonpayable\"},{\"type\":\"function\",\"name\":\"deregisterValidators\",\"inputs\":[{\"name\":\"valPubKeys\",\"type\":\"bytes[]\",\"internalType\":\"bytes[]\"}],\"outputs\":[],\"stateMutability\":\"nonpayable\"},{\"type\":\"function\",\"name\":\"deregisterValidatorsWithSig\",\"inputs\":[{\"name\":\"valPubKeys\",\"type\":\"bytes[]\",\"internalType\":\"bytes[]\"},{\"name\":\"signature\",\"type\":\"bytes\",\"internalType\":\"bytes\"},{\"name\":\"deadline\",\"type\":\"uint256\",\"internalType\":\"uint256\"}],\"outputs\":[],\"stateMutability\":\"nonpayable\"},{\"type\":\"function\",\"name\":\"deregistrationPeriod\",\"inputs\":[],\"outputs\":[{\"name\":\"\",\"type\":\"uint64\",\"internalType\":\"uint64\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"eip712Domain\",\"inputs\":[],\"outputs\":[{\"name\":\"fields\",\"type\":\"bytes1\",\"internalType\":\"bytes1\"},{\"name\":\"name\",\"type\":\"string\",\"internalType\":\"string\"},{\"name\":\"version\",\"type\":\"string\",\"internalType\":\"string\"},{\"name\":\"chainId\",\"type\":\"uint256\",\"internalType\":\"uint256\"},{\"name\":\"verifyingContract\",\"type\":\"address\",\"internalType\":\"address\"},{\"name\":\"salt\",\"type\":\"bytes32\",\"internalType\":\"bytes32\"},{\"name\":\"extensions\",\"type\":\"uint256[]\",\"internalType\":\"uint256[]\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"freeze\",\"inputs\":[{\"name\":\"valPubKeys\",\"type\":\"bytes[]\",\"internalType\":\"bytes[]\"}],\"outputs\":[],\"stateMutability\":\"nonpayable\"},{\"type\":\"function\",\"name\":\"freezeOracle\",\"inputs\":[],\"outputs\":[{\"name\":\"\",\"type\":\"address\",\"internalType\":\"address\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"getEligibleTimeForDeregistration\",\"inputs\":[{\"name\":\"validatorPubkey\",\"type\":\"bytes\",\"internalType\":\"bytes\"}],\"outputs\":[{\"name\":\"\",\"type\":\"uint64\",\"internalType\":\"uint64\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"getMinipoolFromPubkey\",\"inputs\":[{\"name\":\"validatorPubkey\",\"type\":\"bytes\",\"internalType\":\"bytes\"}],\"outputs\":[{\"name\":\"\",\"type\":\"address\",\"internalType\":\"address\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"getNodeAddressFromMinipool\",\"inputs\":[{\"name\":\"minipool\",\"type\":\"address\",\"internalType\":\"address\"}],\"outputs\":[{\"name\":\"\",\"type\":\"address\",\"internalType\":\"address\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"getNodeAddressFromPubkey\",\"inputs\":[{\"name\":\"validatorPubkey\",\"type\":\"bytes\",\"internalType\":\"bytes\"}],\"outputs\":[{\"name\":\"\",\"type\":\"address\",\"internalType\":\"address\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"getValidOperatorsForKey\",\"inputs\":[{\"name\":\"validatorPubkey\",\"type\":\"bytes\",\"internalType\":\"bytes\"}],\"outputs\":[{\"name\":\"\",\"type\":\"address\",\"internalType\":\"address\"},{\"name\":\"\",\"type\":\"address\",\"internalType\":\"address\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"getValidatorRegInfo\",\"inputs\":[{\"name\":\"valPubKey\",\"type\":\"bytes\",\"internalType\":\"bytes\"}],\"outputs\":[{\"name\":\"\",\"type\":\"tuple\",\"internalType\":\"structIRocketMinipoolRegistry.ValidatorRegistration\",\"components\":[{\"name\":\"exists\",\"type\":\"bool\",\"internalType\":\"bool\"},{\"name\":\"deregTimestamp\",\"type\":\"uint64\",\"internalType\":\"uint64\"},{\"name\":\"freezeTimestamp\",\"type\":\"uint64\",\"internalType\":\"uint64\"}]}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"initialize\",\"inputs\":[{\"name\":\"owner\",\"type\":\"address\",\"internalType\":\"address\"},{\"name\":\"freezeOracle\",\"type\":\"address\",\"internalType\":\"address\"},{\"name\":\"unfreezeReceiver\",\"type\":\"address\",\"internalType\":\"address\"},{\"name\":\"rocketStorage\",\"type\":\"address\",\"internalType\":\"address\"},{\"name\":\"unfreezeFee\",\"type\":\"uint256\",\"internalType\":\"uint256\"},{\"name\":\"deregistrationPeriod\",\"type\":\"uint64\",\"internalType\":\"uint64\"}],\"outputs\":[],\"stateMutability\":\"nonpayable\"},{\"type\":\"function\",\"name\":\"isMinipoolActive\",\"inputs\":[{\"name\":\"minipool\",\"type\":\"address\",\"internalType\":\"address\"}],\"outputs\":[{\"name\":\"\",\"type\":\"bool\",\"internalType\":\"bool\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"isOperatorValidForKey\",\"inputs\":[{\"name\":\"operator\",\"type\":\"address\",\"internalType\":\"address\"},{\"name\":\"validatorPubkey\",\"type\":\"bytes\",\"internalType\":\"bytes\"}],\"outputs\":[{\"name\":\"\",\"type\":\"bool\",\"internalType\":\"bool\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"isValidatorOptedIn\",\"inputs\":[{\"name\":\"valPubKey\",\"type\":\"bytes\",\"internalType\":\"bytes\"}],\"outputs\":[{\"name\":\"\",\"type\":\"bool\",\"internalType\":\"bool\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"isValidatorRegistered\",\"inputs\":[{\"name\":\"validatorPubkey\",\"type\":\"bytes\",\"internalType\":\"bytes\"}],\"outputs\":[{\"name\":\"\",\"type\":\"bool\",\"internalType\":\"bool\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"nonces\",\"inputs\":[{\"name\":\"\",\"type\":\"address\",\"internalType\":\"address\"}],\"outputs\":[{\"name\":\"\",\"type\":\"uint256\",\"internalType\":\"uint256\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"owner\",\"inputs\":[],\"outputs\":[{\"name\":\"\",\"type\":\"address\",\"internalType\":\"address\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"ownerUnfreeze\",\"inputs\":[{\"name\":\"valPubKeys\",\"type\":\"bytes[]\",\"internalType\":\"bytes[]\"}],\"outputs\":[],\"stateMutability\":\"nonpayable\"},{\"type\":\"function\",\"name\":\"pause\",\"inputs\":[],\"outputs\":[],\"stateMutability\":\"nonpayable\"},{\"type\":\"function\",\"name\":\"paused\",\"inputs\":[],\"outputs\":[{\"name\":\"\",\"type\":\"bool\",\"internalType\":\"bool\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"pendingOwner\",\"inputs\":[],\"outputs\":[{\"name\":\"\",\"type\":\"address\",\"internalType\":\"address\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"proxiableUUID\",\"inputs\":[],\"outputs\":[{\"name\":\"\",\"type\":\"bytes32\",\"internalType\":\"bytes32\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"pubkeysHash\",\"inputs\":[{\"name\":\"pubkeys\",\"type\":\"bytes[]\",\"internalType\":\"bytes[]\"}],\"outputs\":[{\"name\":\"\",\"type\":\"bytes32\",\"internalType\":\"bytes32\"}],\"stateMutability\":\"pure\"},{\"type\":\"function\",\"name\":\"registerValidators\",\"inputs\":[{\"name\":\"valPubKeys\",\"type\":\"bytes[]\",\"internalType\":\"bytes[]\"}],\"outputs\":[],\"stateMutability\":\"nonpayable\"},{\"type\":\"function\",\"name\":\"registerValidatorsWithSig\",\"inputs\":[{\"name\":\"valPubKeys\",\"type\":\"bytes[]\",\"internalType\":\"bytes[]\"},{\"name\":\"signature\",\"type\":\"bytes\",\"internalType\":\"bytes\"},{\"name\":\"deadline\",\"type\":\"uint256\",\"internalType\":\"uint256\"}],\"outputs\":[],\"stateMutability\":\"nonpayable\"},{\"type\":\"function\",\"name\":\"renounceOwnership\",\"inputs\":[],\"outputs\":[],\"stateMutability\":\"nonpayable\"},{\"type\":\"function\",\"name\":\"requestValidatorDeregistration\",\"inputs\":[{\"name\":\"valPubKeys\",\"type\":\"bytes[]\",\"internalType\":\"bytes[]\"}],\"outputs\":[],\"stateMutability\":\"nonpayable\"},{\"type\":\"function\",\"name\":\"requestValidatorDeregistrationWithSig\",\"inputs\":[{\"name\":\"valPubKeys\",\"type\":\"bytes[]\",\"internalType\":\"bytes[]\"},{\"name\":\"signature\",\"type\":\"bytes\",\"internalType\":\"bytes\"},{\"name\":\"deadline\",\"type\":\"uint256\",\"internalType\":\"uint256\"}],\"outputs\":[],\"stateMutability\":\"nonpayable\"},{\"type\":\"function\",\"name\":\"rocketStorage\",\"inputs\":[],\"outputs\":[{\"name\":\"\",\"type\":\"address\",\"internalType\":\"contractRocketStorageInterface\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"setDeregistrationPeriod\",\"inputs\":[{\"name\":\"newDeregistrationPeriod\",\"type\":\"uint64\",\"internalType\":\"uint64\"}],\"outputs\":[],\"stateMutability\":\"nonpayable\"},{\"type\":\"function\",\"name\":\"setFreezeOracle\",\"inputs\":[{\"name\":\"newFreezeOracle\",\"type\":\"address\",\"internalType\":\"address\"}],\"outputs\":[],\"stateMutability\":\"nonpayable\"},{\"type\":\"function\",\"name\":\"setRocketStorage\",\"inputs\":[{\"name\":\"newRocketStorage\",\"type\":\"address\",\"internalType\":\"address\"}],\"outputs\":[],\"stateMutability\":\"nonpayable\"},{\"type\":\"function\",\"name\":\"setUnfreezeFee\",\"inputs\":[{\"name\":\"newUnfreezeFee\",\"type\":\"uint256\",\"internalType\":\"uint256\"}],\"outputs\":[],\"stateMutability\":\"nonpayable\"},{\"type\":\"function\",\"name\":\"setUnfreezeReceiver\",\"inputs\":[{\"name\":\"newUnfreezeReceiver\",\"type\":\"address\",\"internalType\":\"address\"}],\"outputs\":[],\"stateMutability\":\"nonpayable\"},{\"type\":\"function\",\"name\":\"transferOwnership\",\"inputs\":[{\"name\":\"newOwner\",\"type\":\"address\",\"internalType\":\"address\"}],\"outputs\":[],\"stateMutability\":\"nonpayable\"},{\"type\":\"function\",\"name\":\"unfreeze\",\"inputs\":[{\"name\":\"valPubKeys\",\"type\":\"bytes[]\",\"internalType\":\"bytes[]\"}],\"outputs\":[],\"stateMutability\":\"payable\"},{\"type\":\"function\",\"name\":\"unfreezeFee\",\"inputs\":[],\"outputs\":[{\"name\":\"\",\"type\":\"uint256\",\"internalType\":\"uint256\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"unfreezeReceiver\",\"inputs\":[],\"outputs\":[{\"name\":\"\",\"type\":\"address\",\"internalType\":\"address\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"unpause\",\"inputs\":[],\"outputs\":[],\"stateMutability\":\"nonpayable\"},{\"type\":\"function\",\"name\":\"upgradeToAndCall\",\"inputs\":[{\"name\":\"newImplementation\",\"type\":\"address\",\"internalType\":\"address\"},{\"name\":\"data\",\"type\":\"bytes\",\"internalType\":\"bytes\"}],\"outputs\":[],\"stateMutability\":\"payable\"},{\"type\":\"function\",\"name\":\"validatorRegistrations\",\"inputs\":[{\"name\":\"\",\"type\":\"bytes\",\"internalType\":\"bytes\"}],\"outputs\":[{\"name\":\"exists\",\"type\":\"bool\",\"internalType\":\"bool\"},{\"name\":\"deregTimestamp\",\"type\":\"uint64\",\"internalType\":\"uint64\"},{\"name\":\"freezeTimestamp\",\"type\":\"uint64\",\"internalType\":\"uint64\"}],\"stateMutability\":\"view\"},{\"type\":\"event\",\"name\":\"EIP712DomainChanged\",\"inputs\":[],\"anonymous\":false},{\"type\":\"event\",\"name\":\"Initialized\",\"inputs\":[{\"name\":\"version\",\"type\":\"uint64\",\"indexed\":false,\"internalType\":\"uint64\"}],\"anonymous\":false},{\"type\":\"event\",\"name\":\"OwnershipTransferStarted\",\"inputs\":[{\"name\":\"previousOwner\",\"type\":\"address\",\"indexed\":true,\"internalType\":\"address\"},{\"name\":\"newOwner\",\"type\":\"address\",\"indexed\":true,\"internalType\":\"address\"}],\"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\":\"Unpaused\",\"inputs\":[{\"name\":\"account\",\"type\":\"address\",\"indexed\":false,\"internalType\":\"address\"}],\"anonymous\":false},{\"type\":\"event\",\"name\":\"Upgraded\",\"inputs\":[{\"name\":\"implementation\",\"type\":\"address\",\"indexed\":true,\"internalType\":\"address\"}],\"anonymous\":false},{\"type\":\"event\",\"name\":\"ValidatorDeregistered\",\"inputs\":[{\"name\":\"validatorPubKey\",\"type\":\"bytes\",\"indexed\":true,\"internalType\":\"bytes\"},{\"name\":\"nodeAddress\",\"type\":\"address\",\"indexed\":true,\"internalType\":\"address\"}],\"anonymous\":false},{\"type\":\"event\",\"name\":\"ValidatorDeregistrationRequested\",\"inputs\":[{\"name\":\"validatorPubKey\",\"type\":\"bytes\",\"indexed\":true,\"internalType\":\"bytes\"},{\"name\":\"nodeAddress\",\"type\":\"address\",\"indexed\":true,\"internalType\":\"address\"}],\"anonymous\":false},{\"type\":\"event\",\"name\":\"ValidatorFrozen\",\"inputs\":[{\"name\":\"validatorPubKey\",\"type\":\"bytes\",\"indexed\":true,\"internalType\":\"bytes\"}],\"anonymous\":false},{\"type\":\"event\",\"name\":\"ValidatorRegistered\",\"inputs\":[{\"name\":\"validatorPubKey\",\"type\":\"bytes\",\"indexed\":true,\"internalType\":\"bytes\"},{\"name\":\"nodeAddress\",\"type\":\"address\",\"indexed\":true,\"internalType\":\"address\"}],\"anonymous\":false},{\"type\":\"event\",\"name\":\"ValidatorUnfrozen\",\"inputs\":[{\"name\":\"validatorPubKey\",\"type\":\"bytes\",\"indexed\":true,\"internalType\":\"bytes\"}],\"anonymous\":false},{\"type\":\"error\",\"name\":\"AddressEmptyCode\",\"inputs\":[{\"name\":\"target\",\"type\":\"address\",\"internalType\":\"address\"}]},{\"type\":\"error\",\"name\":\"DeregRequestAlreadyExists\",\"inputs\":[{\"name\":\"validatorPubkey\",\"type\":\"bytes\",\"internalType\":\"bytes\"}]},{\"type\":\"error\",\"name\":\"DeregRequestDoesNotExist\",\"inputs\":[{\"name\":\"validatorPubkey\",\"type\":\"bytes\",\"internalType\":\"bytes\"}]},{\"type\":\"error\",\"name\":\"DeregistrationTooSoon\",\"inputs\":[{\"name\":\"validatorPubkey\",\"type\":\"bytes\",\"internalType\":\"bytes\"}]},{\"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\":\"ExpiredSignature\",\"inputs\":[]},{\"type\":\"error\",\"name\":\"FailedInnerCall\",\"inputs\":[]},{\"type\":\"error\",\"name\":\"FrozenValidatorCannotDeregister\",\"inputs\":[{\"name\":\"validatorPubkey\",\"type\":\"bytes\",\"internalType\":\"bytes\"}]},{\"type\":\"error\",\"name\":\"InvalidBLSPubKeyLength\",\"inputs\":[{\"name\":\"expectedLength\",\"type\":\"uint256\",\"internalType\":\"uint256\"},{\"name\":\"actualLength\",\"type\":\"uint256\",\"internalType\":\"uint256\"}]},{\"type\":\"error\",\"name\":\"InvalidFallback\",\"inputs\":[]},{\"type\":\"error\",\"name\":\"InvalidInitialization\",\"inputs\":[]},{\"type\":\"error\",\"name\":\"InvalidReceive\",\"inputs\":[]},{\"type\":\"error\",\"name\":\"InvalidSignature\",\"inputs\":[]},{\"type\":\"error\",\"name\":\"MinipoolNotActive\",\"inputs\":[{\"name\":\"validatorPubkey\",\"type\":\"bytes\",\"internalType\":\"bytes\"}]},{\"type\":\"error\",\"name\":\"MixedNodeBatch\",\"inputs\":[{\"name\":\"validatorPubkey\",\"type\":\"bytes\",\"internalType\":\"bytes\"}]},{\"type\":\"error\",\"name\":\"NoKeysProvided\",\"inputs\":[]},{\"type\":\"error\",\"name\":\"NoMinipoolForKey\",\"inputs\":[{\"name\":\"validatorPubkey\",\"type\":\"bytes\",\"internalType\":\"bytes\"}]},{\"type\":\"error\",\"name\":\"NotInitializing\",\"inputs\":[]},{\"type\":\"error\",\"name\":\"NotMinipoolOperator\",\"inputs\":[{\"name\":\"validatorPubkey\",\"type\":\"bytes\",\"internalType\":\"bytes\"}]},{\"type\":\"error\",\"name\":\"NotWithdrawalAddress\",\"inputs\":[{\"name\":\"withdrawalAddress\",\"type\":\"address\",\"internalType\":\"address\"}]},{\"type\":\"error\",\"name\":\"OnlyFreezeOracle\",\"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\":\"ReentrancyGuardReentrantCall\",\"inputs\":[]},{\"type\":\"error\",\"name\":\"RefundFailed\",\"inputs\":[]},{\"type\":\"error\",\"name\":\"UUPSUnauthorizedCallContext\",\"inputs\":[]},{\"type\":\"error\",\"name\":\"UUPSUnsupportedProxiableUUID\",\"inputs\":[{\"name\":\"slot\",\"type\":\"bytes32\",\"internalType\":\"bytes32\"}]},{\"type\":\"error\",\"name\":\"UnfreezeFeeRequired\",\"inputs\":[{\"name\":\"requiredFee\",\"type\":\"uint256\",\"internalType\":\"uint256\"}]},{\"type\":\"error\",\"name\":\"UnfreezeTransferFailed\",\"inputs\":[]},{\"type\":\"error\",\"name\":\"ValidatorAlreadyFrozen\",\"inputs\":[{\"name\":\"validatorPubkey\",\"type\":\"bytes\",\"internalType\":\"bytes\"}]},{\"type\":\"error\",\"name\":\"ValidatorAlreadyRegistered\",\"inputs\":[{\"name\":\"validatorPubkey\",\"type\":\"bytes\",\"internalType\":\"bytes\"}]},{\"type\":\"error\",\"name\":\"ValidatorDeregistrationNotExpired\",\"inputs\":[{\"name\":\"validatorPubkey\",\"type\":\"bytes\",\"internalType\":\"bytes\"}]},{\"type\":\"error\",\"name\":\"ValidatorNotFrozen\",\"inputs\":[{\"name\":\"validatorPubkey\",\"type\":\"bytes\",\"internalType\":\"bytes\"}]},{\"type\":\"error\",\"name\":\"ValidatorNotRegistered\",\"inputs\":[{\"name\":\"validatorPubkey\",\"type\":\"bytes\",\"internalType\":\"bytes\"}]},{\"type\":\"error\",\"name\":\"ZeroParam\",\"inputs\":[]}]", } // RocketminipoolregistryABI is the input ABI used to generate the binding from. @@ -249,6 +249,76 @@ func (_Rocketminipoolregistry *RocketminipoolregistryCallerSession) Deregistrati return _Rocketminipoolregistry.Contract.DeregistrationPeriod(&_Rocketminipoolregistry.CallOpts) } +// Eip712Domain is a free data retrieval call binding the contract method 0x84b0196e. +// +// Solidity: function eip712Domain() view returns(bytes1 fields, string name, string version, uint256 chainId, address verifyingContract, bytes32 salt, uint256[] extensions) +func (_Rocketminipoolregistry *RocketminipoolregistryCaller) Eip712Domain(opts *bind.CallOpts) (struct { + Fields [1]byte + Name string + Version string + ChainId *big.Int + VerifyingContract common.Address + Salt [32]byte + Extensions []*big.Int +}, error) { + var out []interface{} + err := _Rocketminipoolregistry.contract.Call(opts, &out, "eip712Domain") + + outstruct := new(struct { + Fields [1]byte + Name string + Version string + ChainId *big.Int + VerifyingContract common.Address + Salt [32]byte + Extensions []*big.Int + }) + if err != nil { + return *outstruct, err + } + + outstruct.Fields = *abi.ConvertType(out[0], new([1]byte)).(*[1]byte) + outstruct.Name = *abi.ConvertType(out[1], new(string)).(*string) + outstruct.Version = *abi.ConvertType(out[2], new(string)).(*string) + outstruct.ChainId = *abi.ConvertType(out[3], new(*big.Int)).(**big.Int) + outstruct.VerifyingContract = *abi.ConvertType(out[4], new(common.Address)).(*common.Address) + outstruct.Salt = *abi.ConvertType(out[5], new([32]byte)).(*[32]byte) + outstruct.Extensions = *abi.ConvertType(out[6], new([]*big.Int)).(*[]*big.Int) + + return *outstruct, err + +} + +// Eip712Domain is a free data retrieval call binding the contract method 0x84b0196e. +// +// Solidity: function eip712Domain() view returns(bytes1 fields, string name, string version, uint256 chainId, address verifyingContract, bytes32 salt, uint256[] extensions) +func (_Rocketminipoolregistry *RocketminipoolregistrySession) Eip712Domain() (struct { + Fields [1]byte + Name string + Version string + ChainId *big.Int + VerifyingContract common.Address + Salt [32]byte + Extensions []*big.Int +}, error) { + return _Rocketminipoolregistry.Contract.Eip712Domain(&_Rocketminipoolregistry.CallOpts) +} + +// Eip712Domain is a free data retrieval call binding the contract method 0x84b0196e. +// +// Solidity: function eip712Domain() view returns(bytes1 fields, string name, string version, uint256 chainId, address verifyingContract, bytes32 salt, uint256[] extensions) +func (_Rocketminipoolregistry *RocketminipoolregistryCallerSession) Eip712Domain() (struct { + Fields [1]byte + Name string + Version string + ChainId *big.Int + VerifyingContract common.Address + Salt [32]byte + Extensions []*big.Int +}, error) { + return _Rocketminipoolregistry.Contract.Eip712Domain(&_Rocketminipoolregistry.CallOpts) +} + // FreezeOracle is a free data retrieval call binding the contract method 0xaf91e0bf. // // Solidity: function freezeOracle() view returns(address) @@ -591,6 +661,37 @@ func (_Rocketminipoolregistry *RocketminipoolregistryCallerSession) IsValidatorR return _Rocketminipoolregistry.Contract.IsValidatorRegistered(&_Rocketminipoolregistry.CallOpts, validatorPubkey) } +// Nonces is a free data retrieval call binding the contract method 0x7ecebe00. +// +// Solidity: function nonces(address ) view returns(uint256) +func (_Rocketminipoolregistry *RocketminipoolregistryCaller) Nonces(opts *bind.CallOpts, arg0 common.Address) (*big.Int, error) { + var out []interface{} + err := _Rocketminipoolregistry.contract.Call(opts, &out, "nonces", arg0) + + if err != nil { + return *new(*big.Int), err + } + + out0 := *abi.ConvertType(out[0], new(*big.Int)).(**big.Int) + + return out0, err + +} + +// Nonces is a free data retrieval call binding the contract method 0x7ecebe00. +// +// Solidity: function nonces(address ) view returns(uint256) +func (_Rocketminipoolregistry *RocketminipoolregistrySession) Nonces(arg0 common.Address) (*big.Int, error) { + return _Rocketminipoolregistry.Contract.Nonces(&_Rocketminipoolregistry.CallOpts, arg0) +} + +// Nonces is a free data retrieval call binding the contract method 0x7ecebe00. +// +// Solidity: function nonces(address ) view returns(uint256) +func (_Rocketminipoolregistry *RocketminipoolregistryCallerSession) Nonces(arg0 common.Address) (*big.Int, error) { + return _Rocketminipoolregistry.Contract.Nonces(&_Rocketminipoolregistry.CallOpts, arg0) +} + // Owner is a free data retrieval call binding the contract method 0x8da5cb5b. // // Solidity: function owner() view returns(address) @@ -715,6 +816,37 @@ func (_Rocketminipoolregistry *RocketminipoolregistryCallerSession) ProxiableUUI return _Rocketminipoolregistry.Contract.ProxiableUUID(&_Rocketminipoolregistry.CallOpts) } +// PubkeysHash is a free data retrieval call binding the contract method 0xcea82feb. +// +// Solidity: function pubkeysHash(bytes[] pubkeys) pure returns(bytes32) +func (_Rocketminipoolregistry *RocketminipoolregistryCaller) PubkeysHash(opts *bind.CallOpts, pubkeys [][]byte) ([32]byte, error) { + var out []interface{} + err := _Rocketminipoolregistry.contract.Call(opts, &out, "pubkeysHash", pubkeys) + + if err != nil { + return *new([32]byte), err + } + + out0 := *abi.ConvertType(out[0], new([32]byte)).(*[32]byte) + + return out0, err + +} + +// PubkeysHash is a free data retrieval call binding the contract method 0xcea82feb. +// +// Solidity: function pubkeysHash(bytes[] pubkeys) pure returns(bytes32) +func (_Rocketminipoolregistry *RocketminipoolregistrySession) PubkeysHash(pubkeys [][]byte) ([32]byte, error) { + return _Rocketminipoolregistry.Contract.PubkeysHash(&_Rocketminipoolregistry.CallOpts, pubkeys) +} + +// PubkeysHash is a free data retrieval call binding the contract method 0xcea82feb. +// +// Solidity: function pubkeysHash(bytes[] pubkeys) pure returns(bytes32) +func (_Rocketminipoolregistry *RocketminipoolregistryCallerSession) PubkeysHash(pubkeys [][]byte) ([32]byte, error) { + return _Rocketminipoolregistry.Contract.PubkeysHash(&_Rocketminipoolregistry.CallOpts, pubkeys) +} + // RocketStorage is a free data retrieval call binding the contract method 0x67601a8e. // // Solidity: function rocketStorage() view returns(address) @@ -900,6 +1032,27 @@ func (_Rocketminipoolregistry *RocketminipoolregistryTransactorSession) Deregist return _Rocketminipoolregistry.Contract.DeregisterValidators(&_Rocketminipoolregistry.TransactOpts, valPubKeys) } +// DeregisterValidatorsWithSig is a paid mutator transaction binding the contract method 0xe8b2a244. +// +// Solidity: function deregisterValidatorsWithSig(bytes[] valPubKeys, bytes signature, uint256 deadline) returns() +func (_Rocketminipoolregistry *RocketminipoolregistryTransactor) DeregisterValidatorsWithSig(opts *bind.TransactOpts, valPubKeys [][]byte, signature []byte, deadline *big.Int) (*types.Transaction, error) { + return _Rocketminipoolregistry.contract.Transact(opts, "deregisterValidatorsWithSig", valPubKeys, signature, deadline) +} + +// DeregisterValidatorsWithSig is a paid mutator transaction binding the contract method 0xe8b2a244. +// +// Solidity: function deregisterValidatorsWithSig(bytes[] valPubKeys, bytes signature, uint256 deadline) returns() +func (_Rocketminipoolregistry *RocketminipoolregistrySession) DeregisterValidatorsWithSig(valPubKeys [][]byte, signature []byte, deadline *big.Int) (*types.Transaction, error) { + return _Rocketminipoolregistry.Contract.DeregisterValidatorsWithSig(&_Rocketminipoolregistry.TransactOpts, valPubKeys, signature, deadline) +} + +// DeregisterValidatorsWithSig is a paid mutator transaction binding the contract method 0xe8b2a244. +// +// Solidity: function deregisterValidatorsWithSig(bytes[] valPubKeys, bytes signature, uint256 deadline) returns() +func (_Rocketminipoolregistry *RocketminipoolregistryTransactorSession) DeregisterValidatorsWithSig(valPubKeys [][]byte, signature []byte, deadline *big.Int) (*types.Transaction, error) { + return _Rocketminipoolregistry.Contract.DeregisterValidatorsWithSig(&_Rocketminipoolregistry.TransactOpts, valPubKeys, signature, deadline) +} + // Freeze is a paid mutator transaction binding the contract method 0xa694d33f. // // Solidity: function freeze(bytes[] valPubKeys) returns() @@ -1005,6 +1158,27 @@ func (_Rocketminipoolregistry *RocketminipoolregistryTransactorSession) Register return _Rocketminipoolregistry.Contract.RegisterValidators(&_Rocketminipoolregistry.TransactOpts, valPubKeys) } +// RegisterValidatorsWithSig is a paid mutator transaction binding the contract method 0xab6e1fae. +// +// Solidity: function registerValidatorsWithSig(bytes[] valPubKeys, bytes signature, uint256 deadline) returns() +func (_Rocketminipoolregistry *RocketminipoolregistryTransactor) RegisterValidatorsWithSig(opts *bind.TransactOpts, valPubKeys [][]byte, signature []byte, deadline *big.Int) (*types.Transaction, error) { + return _Rocketminipoolregistry.contract.Transact(opts, "registerValidatorsWithSig", valPubKeys, signature, deadline) +} + +// RegisterValidatorsWithSig is a paid mutator transaction binding the contract method 0xab6e1fae. +// +// Solidity: function registerValidatorsWithSig(bytes[] valPubKeys, bytes signature, uint256 deadline) returns() +func (_Rocketminipoolregistry *RocketminipoolregistrySession) RegisterValidatorsWithSig(valPubKeys [][]byte, signature []byte, deadline *big.Int) (*types.Transaction, error) { + return _Rocketminipoolregistry.Contract.RegisterValidatorsWithSig(&_Rocketminipoolregistry.TransactOpts, valPubKeys, signature, deadline) +} + +// RegisterValidatorsWithSig is a paid mutator transaction binding the contract method 0xab6e1fae. +// +// Solidity: function registerValidatorsWithSig(bytes[] valPubKeys, bytes signature, uint256 deadline) returns() +func (_Rocketminipoolregistry *RocketminipoolregistryTransactorSession) RegisterValidatorsWithSig(valPubKeys [][]byte, signature []byte, deadline *big.Int) (*types.Transaction, error) { + return _Rocketminipoolregistry.Contract.RegisterValidatorsWithSig(&_Rocketminipoolregistry.TransactOpts, valPubKeys, signature, deadline) +} + // RenounceOwnership is a paid mutator transaction binding the contract method 0x715018a6. // // Solidity: function renounceOwnership() returns() @@ -1047,6 +1221,27 @@ func (_Rocketminipoolregistry *RocketminipoolregistryTransactorSession) RequestV return _Rocketminipoolregistry.Contract.RequestValidatorDeregistration(&_Rocketminipoolregistry.TransactOpts, valPubKeys) } +// RequestValidatorDeregistrationWithSig is a paid mutator transaction binding the contract method 0x11a14bd1. +// +// Solidity: function requestValidatorDeregistrationWithSig(bytes[] valPubKeys, bytes signature, uint256 deadline) returns() +func (_Rocketminipoolregistry *RocketminipoolregistryTransactor) RequestValidatorDeregistrationWithSig(opts *bind.TransactOpts, valPubKeys [][]byte, signature []byte, deadline *big.Int) (*types.Transaction, error) { + return _Rocketminipoolregistry.contract.Transact(opts, "requestValidatorDeregistrationWithSig", valPubKeys, signature, deadline) +} + +// RequestValidatorDeregistrationWithSig is a paid mutator transaction binding the contract method 0x11a14bd1. +// +// Solidity: function requestValidatorDeregistrationWithSig(bytes[] valPubKeys, bytes signature, uint256 deadline) returns() +func (_Rocketminipoolregistry *RocketminipoolregistrySession) RequestValidatorDeregistrationWithSig(valPubKeys [][]byte, signature []byte, deadline *big.Int) (*types.Transaction, error) { + return _Rocketminipoolregistry.Contract.RequestValidatorDeregistrationWithSig(&_Rocketminipoolregistry.TransactOpts, valPubKeys, signature, deadline) +} + +// RequestValidatorDeregistrationWithSig is a paid mutator transaction binding the contract method 0x11a14bd1. +// +// Solidity: function requestValidatorDeregistrationWithSig(bytes[] valPubKeys, bytes signature, uint256 deadline) returns() +func (_Rocketminipoolregistry *RocketminipoolregistryTransactorSession) RequestValidatorDeregistrationWithSig(valPubKeys [][]byte, signature []byte, deadline *big.Int) (*types.Transaction, error) { + return _Rocketminipoolregistry.Contract.RequestValidatorDeregistrationWithSig(&_Rocketminipoolregistry.TransactOpts, valPubKeys, signature, deadline) +} + // SetDeregistrationPeriod is a paid mutator transaction binding the contract method 0xaaa47b39. // // Solidity: function setDeregistrationPeriod(uint64 newDeregistrationPeriod) returns() @@ -1278,6 +1473,139 @@ func (_Rocketminipoolregistry *RocketminipoolregistryTransactorSession) Receive( return _Rocketminipoolregistry.Contract.Receive(&_Rocketminipoolregistry.TransactOpts) } +// RocketminipoolregistryEIP712DomainChangedIterator is returned from FilterEIP712DomainChanged and is used to iterate over the raw logs and unpacked data for EIP712DomainChanged events raised by the Rocketminipoolregistry contract. +type RocketminipoolregistryEIP712DomainChangedIterator struct { + Event *RocketminipoolregistryEIP712DomainChanged // Event containing the contract specifics and raw log + + contract *bind.BoundContract // Generic contract to use for unpacking event data + event string // Event name to use for unpacking event data + + logs chan types.Log // Log channel receiving the found contract events + sub ethereum.Subscription // Subscription for errors, completion and termination + done bool // Whether the subscription completed delivering logs + fail error // Occurred error to stop iteration +} + +// Next advances the iterator to the subsequent event, returning whether there +// are any more events found. In case of a retrieval or parsing error, false is +// returned and Error() can be queried for the exact failure. +func (it *RocketminipoolregistryEIP712DomainChangedIterator) Next() bool { + // If the iterator failed, stop iterating + if it.fail != nil { + return false + } + // If the iterator completed, deliver directly whatever's available + if it.done { + select { + case log := <-it.logs: + it.Event = new(RocketminipoolregistryEIP712DomainChanged) + if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil { + it.fail = err + return false + } + it.Event.Raw = log + return true + + default: + return false + } + } + // Iterator still in progress, wait for either a data or an error event + select { + case log := <-it.logs: + it.Event = new(RocketminipoolregistryEIP712DomainChanged) + if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil { + it.fail = err + return false + } + it.Event.Raw = log + return true + + case err := <-it.sub.Err(): + it.done = true + it.fail = err + return it.Next() + } +} + +// Error returns any retrieval or parsing error occurred during filtering. +func (it *RocketminipoolregistryEIP712DomainChangedIterator) Error() error { + return it.fail +} + +// Close terminates the iteration process, releasing any pending underlying +// resources. +func (it *RocketminipoolregistryEIP712DomainChangedIterator) Close() error { + it.sub.Unsubscribe() + return nil +} + +// RocketminipoolregistryEIP712DomainChanged represents a EIP712DomainChanged event raised by the Rocketminipoolregistry contract. +type RocketminipoolregistryEIP712DomainChanged struct { + Raw types.Log // Blockchain specific contextual infos +} + +// FilterEIP712DomainChanged is a free log retrieval operation binding the contract event 0x0a6387c9ea3628b88a633bb4f3b151770f70085117a15f9bf3787cda53f13d31. +// +// Solidity: event EIP712DomainChanged() +func (_Rocketminipoolregistry *RocketminipoolregistryFilterer) FilterEIP712DomainChanged(opts *bind.FilterOpts) (*RocketminipoolregistryEIP712DomainChangedIterator, error) { + + logs, sub, err := _Rocketminipoolregistry.contract.FilterLogs(opts, "EIP712DomainChanged") + if err != nil { + return nil, err + } + return &RocketminipoolregistryEIP712DomainChangedIterator{contract: _Rocketminipoolregistry.contract, event: "EIP712DomainChanged", logs: logs, sub: sub}, nil +} + +// WatchEIP712DomainChanged is a free log subscription operation binding the contract event 0x0a6387c9ea3628b88a633bb4f3b151770f70085117a15f9bf3787cda53f13d31. +// +// Solidity: event EIP712DomainChanged() +func (_Rocketminipoolregistry *RocketminipoolregistryFilterer) WatchEIP712DomainChanged(opts *bind.WatchOpts, sink chan<- *RocketminipoolregistryEIP712DomainChanged) (event.Subscription, error) { + + logs, sub, err := _Rocketminipoolregistry.contract.WatchLogs(opts, "EIP712DomainChanged") + if err != nil { + return nil, err + } + return event.NewSubscription(func(quit <-chan struct{}) error { + defer sub.Unsubscribe() + for { + select { + case log := <-logs: + // New log arrived, parse the event and forward to the user + event := new(RocketminipoolregistryEIP712DomainChanged) + if err := _Rocketminipoolregistry.contract.UnpackLog(event, "EIP712DomainChanged", log); err != nil { + return err + } + event.Raw = log + + select { + case sink <- event: + case err := <-sub.Err(): + return err + case <-quit: + return nil + } + case err := <-sub.Err(): + return err + case <-quit: + return nil + } + } + }), nil +} + +// ParseEIP712DomainChanged is a log parse operation binding the contract event 0x0a6387c9ea3628b88a633bb4f3b151770f70085117a15f9bf3787cda53f13d31. +// +// Solidity: event EIP712DomainChanged() +func (_Rocketminipoolregistry *RocketminipoolregistryFilterer) ParseEIP712DomainChanged(log types.Log) (*RocketminipoolregistryEIP712DomainChanged, error) { + event := new(RocketminipoolregistryEIP712DomainChanged) + if err := _Rocketminipoolregistry.contract.UnpackLog(event, "EIP712DomainChanged", log); err != nil { + return nil, err + } + event.Raw = log + return event, nil +} + // RocketminipoolregistryInitializedIterator is returned from FilterInitialized and is used to iterate over the raw logs and unpacked data for Initialized events raised by the Rocketminipoolregistry contract. type RocketminipoolregistryInitializedIterator struct { Event *RocketminipoolregistryInitialized // Event containing the contract specifics and raw log diff --git a/contracts/contracts/interfaces/IRocketMinipoolRegistry.sol b/contracts/contracts/interfaces/IRocketMinipoolRegistry.sol index f96fe7246..cb05c93d9 100644 --- a/contracts/contracts/interfaces/IRocketMinipoolRegistry.sol +++ b/contracts/contracts/interfaces/IRocketMinipoolRegistry.sol @@ -46,6 +46,15 @@ interface IRocketMinipoolRegistry { error NotMinipoolOperator(bytes validatorPubkey); + error ExpiredSignature(); + error InvalidSignature(); + + error NotWithdrawalAddress(address withdrawalAddress); + + error NoKeysProvided(); + + error MixedNodeBatch(bytes validatorPubkey); + error MinipoolNotActive(bytes validatorPubkey); error NoMinipoolForKey(bytes validatorPubkey); @@ -75,6 +84,15 @@ interface IRocketMinipoolRegistry { /// @notice Deregisters validators. function deregisterValidators(bytes[] calldata validatorPubkeys) external; + /// @notice Registers validators with a signature from the node address. + function registerValidatorsWithSig(bytes[] calldata valPubKeys, bytes calldata signature, uint256 deadline) external; + + /// @notice Requests deregistration for validators with a signature from the node address. + function requestValidatorDeregistrationWithSig(bytes[] calldata valPubKeys, bytes calldata signature, uint256 deadline) external; + + /// @notice Deregisters validators with a signature from the node address. + function deregisterValidatorsWithSig(bytes[] calldata valPubKeys, bytes calldata signature, uint256 deadline) external; + /// @notice Freezes validators. function freeze(bytes[] calldata validatorPubkeys) external; diff --git a/contracts/contracts/validator-registry/rocketpool/RocketMinipoolRegistry.sol b/contracts/contracts/validator-registry/rocketpool/RocketMinipoolRegistry.sol index e61e2063e..77352499a 100644 --- a/contracts/contracts/validator-registry/rocketpool/RocketMinipoolRegistry.sol +++ b/contracts/contracts/validator-registry/rocketpool/RocketMinipoolRegistry.sol @@ -5,6 +5,9 @@ import {Ownable2StepUpgradeable} from "@openzeppelin/contracts-upgradeable/acces import {PausableUpgradeable} from "@openzeppelin/contracts-upgradeable/utils/PausableUpgradeable.sol"; import {UUPSUpgradeable} from "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol"; import {ReentrancyGuardUpgradeable} from "@openzeppelin/contracts-upgradeable/utils/ReentrancyGuardUpgradeable.sol"; +import {EIP712Upgradeable} from "@openzeppelin/contracts-upgradeable/utils/cryptography/EIP712Upgradeable.sol"; +import {SignatureChecker} from "@openzeppelin/contracts/utils/cryptography/SignatureChecker.sol"; + import {RocketMinipoolInterface} from "rocketpool/contracts/interface/minipool/RocketMinipoolInterface.sol"; import {MinipoolStatus} from "rocketpool/contracts/types/MinipoolStatus.sol"; import {RocketStorageInterface} from "rocketpool/contracts/interface/RocketStorageInterface.sol"; @@ -17,7 +20,12 @@ import {RocketMinipoolRegistryStorage} from "./RocketMinipoolRegistryStorage.sol /// @notice This contract serves as the entrypoint for operators to register with /// the mev-commit protocol via Rocketpool minipools. contract RocketMinipoolRegistry is IRocketMinipoolRegistry, RocketMinipoolRegistryStorage, - Ownable2StepUpgradeable, PausableUpgradeable, ReentrancyGuardUpgradeable, UUPSUpgradeable { + Ownable2StepUpgradeable, PausableUpgradeable, ReentrancyGuardUpgradeable, UUPSUpgradeable, EIP712Upgradeable { + + // We keep payloads compact by hashing the pubkeys array to a bytes32. + bytes32 private constant _REGISTER_TYPEHASH = keccak256("Register(bytes32 pubkeysHash,address executor,uint256 nonce,uint256 deadline)"); + bytes32 private constant _DEREG_REQ_TYPEHASH = keccak256("DeregRequest(bytes32 pubkeysHash,address executor,uint256 nonce,uint256 deadline)"); + bytes32 private constant _DEREG_TYPEHASH = keccak256("Deregister(bytes32 pubkeysHash,address executor,uint256 nonce,uint256 deadline)"); modifier onlyFreezeOracle() { require(msg.sender == freezeOracle, IRocketMinipoolRegistry.OnlyFreezeOracle()); @@ -27,6 +35,7 @@ contract RocketMinipoolRegistry is IRocketMinipoolRegistry, RocketMinipoolRegist /// @dev Modifier to confirm all provided BLS pubkeys are valid length. modifier onlyValidBLSPubKeys(bytes[] calldata valPubKeys) { uint256 len = valPubKeys.length; + require(len > 0, NoKeysProvided()); for (uint256 i = 0; i < len; ++i) { require(valPubKeys[i].length == 48, IRocketMinipoolRegistry.InvalidBLSPubKeyLength(48, valPubKeys[i].length)); } @@ -54,6 +63,7 @@ contract RocketMinipoolRegistry is IRocketMinipoolRegistry, RocketMinipoolRegist __Pausable_init(); __ReentrancyGuard_init(); __UUPSUpgradeable_init(); + __EIP712_init("RocketMinipoolRegistry", "1"); _setFreezeOracle(freezeOracle); _setUnfreezeReceiver(unfreezeReceiver); _setUnfreezeFee(unfreezeFee); @@ -61,29 +71,146 @@ contract RocketMinipoolRegistry is IRocketMinipoolRegistry, RocketMinipoolRegist _setDeregistrationPeriod(deregistrationPeriod); } + /// @notice Allows Minipool's withdrawal address to register validators. function registerValidators(bytes[] calldata valPubKeys) external onlyValidBLSPubKeys(valPubKeys) whenNotPaused { + address nodeAddress0 = _getFirstNodeAddrAndValidate(valPubKeys[0]); + _registerValidator(valPubKeys[0]); + emit ValidatorRegistered(valPubKeys[0], nodeAddress0); + uint256 len = valPubKeys.length; - for (uint256 i = 0; i < len; ++i) { + for (uint256 i = 1; i < len; ++i) { + require(getNodeAddressFromPubkey(valPubKeys[i]) == nodeAddress0, MixedNodeBatch(valPubKeys[i])); _registerValidator(valPubKeys[i]); - + emit ValidatorRegistered(valPubKeys[i], nodeAddress0); } } + /// @notice Allows a user to register using a signature. + function registerValidatorsWithSig(bytes[] calldata valPubKeys, bytes calldata signature, uint256 deadline) external onlyValidBLSPubKeys(valPubKeys) whenNotPaused { + bytes32 pkHash = pubkeysHash(valPubKeys); + address minipool0 = getMinipoolFromPubkey(valPubKeys[0]); + address nodeAddress0 = getNodeAddressFromMinipool(minipool0); + require(nodeAddress0 != address(0), NoMinipoolForKey(valPubKeys[0])); + + // 2) Pull nonce & check deadline + uint256 nonce = nonces[nodeAddress0]; + require(block.timestamp <= deadline, ExpiredSignature()); + + bytes32 digest = _hashTypedDataV4(keccak256(abi.encode( + _REGISTER_TYPEHASH, + pkHash, + msg.sender, + nonce, + deadline + ))); + + // 6) Verify signature came from the node + require(SignatureChecker.isValidSignatureNow(nodeAddress0, digest, signature), InvalidSignature()); + unchecked { nonces[nodeAddress0] = nonce + 1; } + + _registerValidator(valPubKeys[0]); + emit ValidatorRegistered(valPubKeys[0], nodeAddress0); + + uint256 len = valPubKeys.length; + for (uint256 i = 1; i < len; ++i) { + //still need to check that the node address is the same for all validators in the batch + require(getNodeAddressFromPubkey(valPubKeys[i]) == nodeAddress0, MixedNodeBatch(valPubKeys[i])); + _registerValidator(valPubKeys[i]); + emit ValidatorRegistered(valPubKeys[i], nodeAddress0); + } + } + + /// @notice Allows Minipool's withdrawal address to request deregistration for validators. function requestValidatorDeregistration(bytes[] calldata valPubKeys) external onlyValidBLSPubKeys(valPubKeys) whenNotPaused { + address nodeAddress0 = _getFirstNodeAddrAndValidate(valPubKeys[0]); + _requestValidatorDeregistration(valPubKeys[0]); + emit ValidatorDeregistrationRequested(valPubKeys[0], nodeAddress0); + uint256 len = valPubKeys.length; - for (uint256 i = 0; i < len; ++i) { + for (uint256 i = 1; i < len; ++i) { + require(getNodeAddressFromPubkey(valPubKeys[i]) == nodeAddress0, MixedNodeBatch(valPubKeys[i])); _requestValidatorDeregistration(valPubKeys[i]); + emit ValidatorDeregistrationRequested(valPubKeys[i], nodeAddress0); } } - /// @dev Deregister validators. Can only be called once the deregistration period has passed from time of request. + function requestValidatorDeregistrationWithSig(bytes[] calldata valPubKeys, bytes calldata signature, uint256 deadline) external onlyValidBLSPubKeys(valPubKeys) whenNotPaused { + bytes32 pkHash = pubkeysHash(valPubKeys); + address minipool0 = getMinipoolFromPubkey(valPubKeys[0]); + address nodeAddress0 = getNodeAddressFromMinipool(minipool0); + require(nodeAddress0 != address(0), NoMinipoolForKey(valPubKeys[0])); + + uint256 nonce = nonces[nodeAddress0]; + require(block.timestamp <= deadline, ExpiredSignature()); + + bytes32 digest = _hashTypedDataV4(keccak256(abi.encode( + _DEREG_REQ_TYPEHASH, + pkHash, + msg.sender, + nonce, + deadline + ))); + + require(SignatureChecker.isValidSignatureNow(nodeAddress0, digest, signature), InvalidSignature()); + unchecked { nonces[nodeAddress0] = nonce + 1; } + + _requestValidatorDeregistration(valPubKeys[0]); + emit ValidatorDeregistrationRequested(valPubKeys[0], nodeAddress0); + + uint256 len = valPubKeys.length; + for (uint256 i = 1; i < len; ++i) { + require(getNodeAddressFromPubkey(valPubKeys[i]) == nodeAddress0, MixedNodeBatch(valPubKeys[i])); + _requestValidatorDeregistration(valPubKeys[i]); + emit ValidatorDeregistrationRequested(valPubKeys[i], nodeAddress0); + } + } + + /// @notice Allows Minipool's withdrawal address to eregister validators. Can only be called once the deregistration period has passed from time of request. function deregisterValidators(bytes[] calldata valPubKeys) external onlyValidBLSPubKeys(valPubKeys) whenNotPaused { + address nodeAddress0 = _getFirstNodeAddrAndValidate(valPubKeys[0]); + _deregisterValidator(valPubKeys[0]); + emit ValidatorDeregistered(valPubKeys[0], nodeAddress0); + uint256 len = valPubKeys.length; - for (uint256 i = 0; i < len; ++i) { + for (uint256 i = 1; i < len; ++i) { + require(getNodeAddressFromPubkey(valPubKeys[i]) == nodeAddress0, MixedNodeBatch(valPubKeys[i])); + _deregisterValidator(valPubKeys[i]); + emit ValidatorDeregistered(valPubKeys[i], nodeAddress0); + } + } + + function deregisterValidatorsWithSig(bytes[] calldata valPubKeys, bytes calldata signature, uint256 deadline) external onlyValidBLSPubKeys(valPubKeys) whenNotPaused { + bytes32 pkHash = pubkeysHash(valPubKeys); + address minipool0 = getMinipoolFromPubkey(valPubKeys[0]); + address nodeAddress0 = getNodeAddressFromMinipool(minipool0); + require(nodeAddress0 != address(0), NoMinipoolForKey(valPubKeys[0])); + + uint256 nonce = nonces[nodeAddress0]; + require(block.timestamp <= deadline, ExpiredSignature()); + + bytes32 digest = _hashTypedDataV4(keccak256(abi.encode( + _DEREG_TYPEHASH, + pkHash, + msg.sender, + nonce, + deadline + ))); + + require(SignatureChecker.isValidSignatureNow(nodeAddress0, digest, signature), InvalidSignature()); + unchecked { nonces[nodeAddress0] = nonce + 1; } + + _deregisterValidator(valPubKeys[0]); + emit ValidatorDeregistered(valPubKeys[0], nodeAddress0); + + uint256 len = valPubKeys.length; + for (uint256 i = 1; i < len; ++i) { + require(getNodeAddressFromPubkey(valPubKeys[i]) == nodeAddress0, MixedNodeBatch(valPubKeys[i])); _deregisterValidator(valPubKeys[i]); + emit ValidatorDeregistered(valPubKeys[i], nodeAddress0); } } + /// @dev Allows the freeze oracle account to freeze validators which disobey the mev-commit protocol. function freeze(bytes[] calldata valPubKeys) external whenNotPaused onlyFreezeOracle() { uint256 len = valPubKeys.length; @@ -207,38 +334,47 @@ contract RocketMinipoolRegistry is IRocketMinipoolRegistry, RocketMinipoolRegist return RocketMinipoolInterface(minipool).getNodeAddress(); } + function pubkeysHash(bytes[] calldata pubkeys) public pure returns (bytes32) { + uint256 count = pubkeys.length; + + // Contiguous buffer to hold all pubkeys back-to-back + bytes memory concatenated = new bytes(48 * count); + + // Pointer to the next write position in `concatenated` + uint256 writePtr; + assembly { writePtr := add(concatenated, 32) } + + for (uint256 i = 0; i < count; ++i) { + bytes calldata pubkey = pubkeys[i]; + assembly { + let len := 48 + calldatacopy(writePtr, pubkey.offset, len) + writePtr := add(writePtr, len) + } + } + return keccak256(concatenated); + } + /// @dev Registers a validator. function _registerValidator(bytes calldata valPubKey) internal { - address minipool = getMinipoolFromPubkey(valPubKey); - require(isMinipoolActive(minipool), MinipoolNotActive(valPubKey)); require(!isValidatorRegistered(valPubKey), ValidatorAlreadyRegistered(valPubKey)); - address nodeAddress = getNodeAddressFromMinipool(minipool); - require(_isOperatorValid(nodeAddress), NotMinipoolOperator(valPubKey)); - IRocketMinipoolRegistry.ValidatorRegistration storage reg = validatorRegistrations[valPubKey]; - reg.exists = true; - emit ValidatorRegistered(valPubKey, nodeAddress); + validatorRegistrations[valPubKey].exists = true; } /// @dev Requests deregistration for a validator. function _requestValidatorDeregistration(bytes calldata valPubKey) internal { - address nodeAddress = getNodeAddressFromPubkey(valPubKey); - require(_isOperatorValid(nodeAddress), NotMinipoolOperator(valPubKey)); require(isValidatorRegistered(valPubKey), ValidatorNotRegistered(valPubKey)); IRocketMinipoolRegistry.ValidatorRegistration storage reg = validatorRegistrations[valPubKey]; require(reg.deregTimestamp == 0, DeregRequestAlreadyExists(valPubKey)); reg.deregTimestamp = uint64(block.timestamp); - emit ValidatorDeregistrationRequested(valPubKey, nodeAddress); } function _deregisterValidator(bytes calldata valPubKey) internal { - address nodeAddress = getNodeAddressFromPubkey(valPubKey); - require(_isOperatorValid(nodeAddress), NotMinipoolOperator(valPubKey)); IRocketMinipoolRegistry.ValidatorRegistration storage reg = validatorRegistrations[valPubKey]; + require(reg.freezeTimestamp == 0, FrozenValidatorCannotDeregister(valPubKey)); require(reg.deregTimestamp != 0, DeregRequestDoesNotExist(valPubKey)); require(uint64(block.timestamp) > reg.deregTimestamp + deregistrationPeriod, DeregistrationTooSoon(valPubKey)); - require(reg.freezeTimestamp == 0, FrozenValidatorCannotDeregister(valPubKey)); delete validatorRegistrations[valPubKey]; - emit ValidatorDeregistered(valPubKey, nodeAddress); } function _freeze(bytes calldata valPubKey) internal { @@ -279,10 +415,19 @@ contract RocketMinipoolRegistry is IRocketMinipoolRegistry, RocketMinipoolRegist deregistrationPeriod = newDeregistrationPeriod; } + /// @dev Authorizes contract upgrades, restricted to contract owner. // solhint-disable-next-line no-empty-blocks function _authorizeUpgrade(address) internal override onlyOwner {} + + function _getFirstNodeAddrAndValidate(bytes calldata firstPubKey) internal view returns (address firstNodeAddress) { + firstNodeAddress = getNodeAddressFromMinipool(getMinipoolFromPubkey(firstPubKey)); + require(firstNodeAddress != address(0), NoMinipoolForKey(firstPubKey)); + address withdrawalAddress = rocketStorage.getNodeWithdrawalAddress(firstNodeAddress); + require(withdrawalAddress == msg.sender, NotWithdrawalAddress(withdrawalAddress)); + } + function _isValidatorOptedIn(bytes calldata valPubKey) internal view returns (bool) { if (!isValidatorRegistered(valPubKey)) return false; if (validatorRegistrations[valPubKey].freezeTimestamp != 0) return false; @@ -293,8 +438,14 @@ contract RocketMinipoolRegistry is IRocketMinipoolRegistry, RocketMinipoolRegist return true; } - /// @dev Returns true if caller is either the minipool's node address or node'swithdrawal address. - function _isOperatorValid(address operator) internal view returns (bool) { - return (operator == msg.sender || rocketStorage.getNodeWithdrawalAddress(operator) == msg.sender); + /// @dev Deterministically hash a list of pubkeys to avoid large EIP-712 arrays. + function _hashPubkeysAndFunction(bytes memory functionSelector, bytes[] calldata pubkeys) internal pure returns (bytes32) { + bytes32[] memory leaves = new bytes32[](pubkeys.length); + uint256 len = pubkeys.length; + for (uint256 i = 0; i < len; ++i) { + leaves[i] = keccak256(pubkeys[i]); + } + return keccak256(abi.encodePacked(functionSelector, leaves)); } + } \ No newline at end of file diff --git a/contracts/contracts/validator-registry/rocketpool/RocketMinipoolRegistryStorage.sol b/contracts/contracts/validator-registry/rocketpool/RocketMinipoolRegistryStorage.sol index b61a8d31f..33c557878 100644 --- a/contracts/contracts/validator-registry/rocketpool/RocketMinipoolRegistryStorage.sol +++ b/contracts/contracts/validator-registry/rocketpool/RocketMinipoolRegistryStorage.sol @@ -16,5 +16,7 @@ abstract contract RocketMinipoolRegistryStorage { mapping(bytes => IRocketMinipoolRegistry.ValidatorRegistration) public validatorRegistrations; + mapping(address => uint256) public nonces; // nonce per node + uint256[44] private __gap; } \ No newline at end of file diff --git a/contracts/test/validator-registry/rocketpool/RocketMinipoolRegistryTest.sol b/contracts/test/validator-registry/rocketpool/RocketMinipoolRegistryTest.sol index 229abc730..6b50657c6 100644 --- a/contracts/test/validator-registry/rocketpool/RocketMinipoolRegistryTest.sol +++ b/contracts/test/validator-registry/rocketpool/RocketMinipoolRegistryTest.sol @@ -130,14 +130,14 @@ contract RocketMinipoolRegistryTest is Test { reg.registerValidators(_one(pk1)); vm.prank(owner); reg.unpause(); - vm.prank(node); reg.registerValidators(_one(pk1)); // ok + vm.prank(withdrawal); reg.registerValidators(_one(pk1)); // ok } // ---------- register ---------- function test_Register_ByNode_Succeeds() public { vm.expectEmit(false, true, false, true); emit IRocketMinipoolRegistry.ValidatorRegistered(pk1, node); - vm.prank(node); + vm.prank(withdrawal); reg.registerValidators(_one(pk1)); // ✅ read struct, then fields @@ -166,23 +166,17 @@ contract RocketMinipoolRegistryTest is Test { reg.registerValidators(bad); } - function test_Register_MinipoolNotActive_Reverts() public { - mp1.setStatus(MinipoolStatus.Withdrawable); // anything != Staking - vm.prank(node); - vm.expectRevert(abi.encodeWithSelector(IRocketMinipoolRegistry.MinipoolNotActive.selector, pk1)); - reg.registerValidators(_one(pk1)); - } function test_Register_Twice_Reverts() public { - vm.prank(node); reg.registerValidators(_one(pk1)); - vm.prank(node); + vm.prank(withdrawal); reg.registerValidators(_one(pk1)); + vm.prank(withdrawal); vm.expectRevert(abi.encodeWithSelector(IRocketMinipoolRegistry.ValidatorAlreadyRegistered.selector, pk1)); reg.registerValidators(_one(pk1)); } // ---------- freeze / unfreeze ---------- function test_Freeze_OnlyOracle() public { - vm.prank(node); reg.registerValidators(_one(pk1)); + vm.prank(withdrawal); reg.registerValidators(_one(pk1)); vm.expectRevert(IRocketMinipoolRegistry.OnlyFreezeOracle.selector); reg.freeze(_one(pk1)); // caller = this @@ -199,7 +193,7 @@ contract RocketMinipoolRegistryTest is Test { function test_Unfreeze_RequiresFee_And_Refunds() public { // setup: register & freeze - vm.prank(node); reg.registerValidators(_one(pk1)); + vm.prank(withdrawal); reg.registerValidators(_one(pk1)); vm.prank(oracle); reg.freeze(_one(pk1)); // underpay -> revert with required fee @@ -233,7 +227,7 @@ contract RocketMinipoolRegistryTest is Test { } function test_OwnerUnfreeze_NoFee() public { - vm.prank(node); reg.registerValidators(_one(pk1)); + vm.prank(withdrawal); reg.registerValidators(_one(pk1)); vm.prank(oracle); reg.freeze(_one(pk1)); vm.prank(owner); @@ -244,30 +238,30 @@ contract RocketMinipoolRegistryTest is Test { // ---------- deregistration flow ---------- function test_RequestDereg_Then_Finalize() public { - vm.prank(node); reg.registerValidators(_one(pk1)); + vm.prank(withdrawal); reg.registerValidators(_one(pk1)); vm.expectEmit(false, true, false, true); emit IRocketMinipoolRegistry.ValidatorDeregistrationRequested(pk1, node); - vm.prank(node); + vm.prank(withdrawal); reg.requestValidatorDeregistration(_one(pk1)); - vm.prank(node); + vm.prank(withdrawal); vm.expectRevert(abi.encodeWithSelector(IRocketMinipoolRegistry.DeregRequestAlreadyExists.selector, pk1)); reg.requestValidatorDeregistration(_one(pk1)); - vm.prank(node); + vm.prank(withdrawal); vm.expectRevert(abi.encodeWithSelector(IRocketMinipoolRegistry.DeregistrationTooSoon.selector, pk1)); reg.deregisterValidators(_one(pk1)); vm.prank(oracle); reg.freeze(_one(pk1)); vm.warp(block.timestamp + period + 1); - vm.prank(node); + vm.prank(withdrawal); vm.expectRevert(abi.encodeWithSelector(IRocketMinipoolRegistry.FrozenValidatorCannotDeregister.selector, pk1)); reg.deregisterValidators(_one(pk1)); reg.unfreeze{value: fee}(_one(pk1)); - vm.prank(node); + vm.prank(withdrawal); reg.deregisterValidators(_one(pk1)); assertFalse(reg.isValidatorRegistered(pk1)); @@ -279,7 +273,7 @@ contract RocketMinipoolRegistryTest is Test { // ---------- getters / helpers ---------- function test_Getters_Work() public { - vm.prank(node); reg.registerValidators(_one(pk1)); + vm.prank(withdrawal); reg.registerValidators(_one(pk1)); assertEq(reg.getNodeAddressFromPubkey(pk1), node); assertEq(reg.getMinipoolFromPubkey(pk1), address(mp1)); @@ -300,7 +294,7 @@ contract RocketMinipoolRegistryTest is Test { assertFalse(reg.isValidatorOptedIn(pk1)); // registered + active - vm.prank(node); reg.registerValidators(_one(pk1)); + vm.prank(withdrawal); reg.registerValidators(_one(pk1)); assertTrue(reg.isValidatorOptedIn(pk1)); // frozen -> false @@ -309,16 +303,16 @@ contract RocketMinipoolRegistryTest is Test { reg.unfreeze{value: fee}(_one(pk1)); // dereg requested -> false - vm.prank(node); reg.requestValidatorDeregistration(_one(pk1)); + vm.prank(withdrawal); reg.requestValidatorDeregistration(_one(pk1)); assertFalse(reg.isValidatorOptedIn(pk1)); // complete dereg clears vm.warp(block.timestamp + period + 1); - vm.prank(node); reg.deregisterValidators(_one(pk1)); + vm.prank(withdrawal); reg.deregisterValidators(_one(pk1)); assertFalse(reg.isValidatorOptedIn(pk1)); // inactive minipool -> false - vm.prank(node); reg.registerValidators(_one(pk1)); + vm.prank(withdrawal); reg.registerValidators(_one(pk1)); MinipoolMock(address(mp1)).setStatus(MinipoolStatus.Withdrawable); assertFalse(reg.isValidatorOptedIn(pk1)); } diff --git a/contracts/test/validator-registry/rocketpool/RocketMinipoolSigTest.sol b/contracts/test/validator-registry/rocketpool/RocketMinipoolSigTest.sol new file mode 100644 index 000000000..f92c2b21c --- /dev/null +++ b/contracts/test/validator-registry/rocketpool/RocketMinipoolSigTest.sol @@ -0,0 +1,374 @@ +// SPDX-License-Identifier: BSL 1.1 +pragma solidity 0.8.26; + +import "forge-std/Test.sol"; +import "@openzeppelin/contracts-upgradeable/utils/PausableUpgradeable.sol"; +import {Upgrades} from "openzeppelin-foundry-upgrades/Upgrades.sol"; +import {Options} from "openzeppelin-foundry-upgrades/Options.sol"; + +import {RocketMinipoolRegistry} from "../../../contracts/validator-registry/rocketpool/RocketMinipoolRegistry.sol"; +import {IRocketMinipoolRegistry} from "../../../contracts/interfaces/IRocketMinipoolRegistry.sol"; +import {MinipoolStatus} from "rocketpool/contracts/types/MinipoolStatus.sol"; + +// ---------- Mocks ---------- + +contract RocketStorageMock { + mapping(bytes32 => address) internal _addr; + mapping(address => address) internal _withdrawal; + + function setMinipoolForPubkey(bytes calldata pk, address mp) external { + _addr[keccak256(abi.encodePacked("validator.minipool", pk))] = mp; + } + + function setNodeWithdrawalAddress(address node, address withdrawal) external { + _withdrawal[node] = withdrawal; + } + + // registry reads these + function getAddress(bytes32 key) external view returns (address) { + return _addr[key]; + } + + function getNodeWithdrawalAddress(address node) external view returns (address) { + return _withdrawal[node]; + } +} + +contract MinipoolMock { + address public node; + MinipoolStatus public status; + + constructor(address _node) { + node = _node; + status = MinipoolStatus.Staking; // default to active + } + + function getNodeAddress() external view returns (address) { + return node; + } + + function getStatus() external view returns (MinipoolStatus) { + return status; + } + + function setStatus(MinipoolStatus s) external { + status = s; + } +} + +// ---------- Tests ---------- + +contract RocketMinipoolRegistrySigTest is Test { + // actors + address internal owner; + address internal oracle; + address internal node; // derived from nodeSk + address internal withdrawal; + address internal receiver; + address internal stranger; + + // signer key + uint256 internal nodeSk; + + // system + RocketStorageMock internal storageMock; + MinipoolMock internal mp1; + RocketMinipoolRegistry internal reg; + + // params + uint256 internal fee = 0.01 ether; + uint64 internal period = 3 days; + + // sample 48-byte pubkeys (96 hex chars) + bytes internal pk1 = hex"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"; + + // --- EIP-712 constants (must match contract) --- + bytes32 internal constant EIP712_DOMAIN_TYPEHASH = + keccak256("EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)"); + bytes32 internal constant NAME_HASH = keccak256(bytes("RocketMinipoolRegistry")); + bytes32 internal constant VERSION_HASH = keccak256(bytes("1")); + + bytes32 internal constant REGISTER_TYPEHASH = + keccak256("Register(bytes32 pubkeysHash,address executor,uint256 nonce,uint256 deadline)"); + bytes32 internal constant DEREG_REQ_TYPEHASH = + keccak256("DeregRequest(bytes32 pubkeysHash,address executor,uint256 nonce,uint256 deadline)"); + bytes32 internal constant DEREG_TYPEHASH = + keccak256("Deregister(bytes32 pubkeysHash,address executor,uint256 nonce,uint256 deadline)"); + + // helpers + function _one(bytes memory pk) internal pure returns (bytes[] memory a) { + a = new bytes[](1); + a[0] = pk; + } + + function _domainSeparator() internal view returns (bytes32) { + return keccak256(abi.encode( + EIP712_DOMAIN_TYPEHASH, + NAME_HASH, + VERSION_HASH, + block.chainid, + address(reg) + )); + } + + function _pubkeysHash(bytes[] memory pks) internal pure returns (bytes32) { + // Concatenate 48-byte pubkeys and hash (matches contract intent) + bytes memory c; + for (uint256 i = 0; i < pks.length; ++i) { + // tests rely on the production modifier enforcing 48-byte length; keep it simple here + c = bytes.concat(c, pks[i]); + } + return keccak256(c); + } + + function _sign(bytes32 structHash) internal view returns (bytes memory) { + bytes32 digest = keccak256(abi.encodePacked("\x19\x01", _domainSeparator(), structHash)); + (uint8 v, bytes32 r, bytes32 s) = vm.sign(nodeSk, digest); + return abi.encodePacked(r, s, v); + } + + function setUp() public { + deal(address(this), 100 ether); // for unfreeze tests + + // choose a real private key for node so we can sign + nodeSk = 0xA11CE; + node = vm.addr(nodeSk); + + // other actors + owner = makeAddr("owner"); + oracle = makeAddr("freezeOracle"); + withdrawal = makeAddr("withdrawal"); + receiver = makeAddr("receiver"); + stranger = makeAddr("stranger"); + + // mocks + storageMock = new RocketStorageMock(); + mp1 = new MinipoolMock(node); + + // wire lookups + storageMock.setMinipoolForPubkey(pk1, address(mp1)); + storageMock.setNodeWithdrawalAddress(node, withdrawal); + + Options memory opts; + opts.unsafeSkipAllChecks = true; + + address proxy = Upgrades.deployUUPSProxy( + "RocketMinipoolRegistry.sol", + abi.encodeCall( + RocketMinipoolRegistry.initialize, + (owner, oracle, receiver, address(storageMock), fee, period) + ), + opts + ); + reg = RocketMinipoolRegistry(payable(proxy)); + } + + // ---------- access: only withdrawal for non-sig paths ---------- + function test_Register_ByWithdrawal_Succeeds_NodeReverts() public { + // node should NOT be allowed anymore + vm.prank(node); + vm.expectRevert(abi.encodeWithSelector(IRocketMinipoolRegistry.NotWithdrawalAddress.selector, withdrawal)); + reg.registerValidators(_one(pk1)); + + // withdrawal can call + vm.prank(withdrawal); + reg.registerValidators(_one(pk1)); + assertTrue(reg.isValidatorRegistered(pk1)); + } + + function test_RequestDereg_ByWithdrawal_Succeeds_NodeReverts() public { + vm.prank(withdrawal); + reg.registerValidators(_one(pk1)); + + vm.prank(node); + vm.expectRevert(abi.encodeWithSelector(IRocketMinipoolRegistry.NotWithdrawalAddress.selector, withdrawal)); + reg.requestValidatorDeregistration(_one(pk1)); + + vm.prank(withdrawal); + reg.requestValidatorDeregistration(_one(pk1)); + } + + function test_Deregister_ByWithdrawal_Succeeds_NodeReverts() public { + vm.prank(withdrawal); + reg.registerValidators(_one(pk1)); + + vm.prank(withdrawal); + reg.requestValidatorDeregistration(_one(pk1)); + + vm.warp(block.timestamp + period + 1); + + vm.prank(node); + vm.expectRevert(abi.encodeWithSelector(IRocketMinipoolRegistry.NotWithdrawalAddress.selector, withdrawal)); + reg.deregisterValidators(_one(pk1)); + + vm.prank(withdrawal); + reg.deregisterValidators(_one(pk1)); + assertFalse(reg.isValidatorRegistered(pk1)); + } + + // ---------- WithSig happy paths ---------- + function test_Register_WithSig_ByIntendedExecutor_Works() public { + // build struct hash with executor bound to withdrawal (who will call) + uint256 nonce = 0; // fresh contract: per-node nonce starts at 0 + uint256 deadline = block.timestamp + 1 days; + bytes32 pkHash = _pubkeysHash(_one(pk1)); + + bytes32 structHash = keccak256(abi.encode( + REGISTER_TYPEHASH, + pkHash, + withdrawal, + nonce, + deadline + )); + + bytes memory sig = _sign(structHash); + + vm.prank(withdrawal); + reg.registerValidatorsWithSig(_one(pk1), sig, deadline); + assertTrue(reg.isValidatorRegistered(pk1)); + } + + function test_RequestDereg_WithSig_Works() public { + // first register via withdrawal (non-sig path) to set state + vm.prank(withdrawal); + reg.registerValidators(_one(pk1)); + + uint256 nonce = 0; // first signature use for this node + uint256 deadline = block.timestamp + 1 days; + bytes32 pkHash = _pubkeysHash(_one(pk1)); + + bytes32 structHash = keccak256(abi.encode( + DEREG_REQ_TYPEHASH, + pkHash, + withdrawal, + nonce, + deadline + )); + bytes memory sig = _sign(structHash); + + vm.prank(withdrawal); + reg.requestValidatorDeregistrationWithSig(_one(pk1), sig, deadline); + } + + function test_Deregister_WithSig_Works() public { + // setup: registered, requested, period elapsed + vm.prank(withdrawal); + reg.registerValidators(_one(pk1)); + + // Dereg request first (non-sig or sig). Use non-sig via withdrawal: + vm.prank(withdrawal); + reg.requestValidatorDeregistration(_one(pk1)); + vm.warp(block.timestamp + period + 1); + + uint256 nonce = 0; // still 0: requestDereg non-sig path doesn't consume node nonce + uint256 deadline = block.timestamp + 1 days; + bytes32 pkHash = _pubkeysHash(_one(pk1)); + + bytes32 structHash = keccak256(abi.encode( + DEREG_TYPEHASH, + pkHash, + withdrawal, + nonce, + deadline + )); + bytes memory sig = _sign(structHash); + + vm.prank(withdrawal); + reg.deregisterValidatorsWithSig(_one(pk1), sig, deadline); + assertFalse(reg.isValidatorRegistered(pk1)); + } + + // ---------- WithSig failures ---------- + function test_WithSig_InvalidSignature_Reverts() public { + // correct message, but signed by the wrong key + uint256 deadline = block.timestamp + 1 days; + bytes32 pkHash = _pubkeysHash(_one(pk1)); + bytes32 structHash = keccak256(abi.encode( + REGISTER_TYPEHASH, + pkHash, + withdrawal, + 0, + deadline + )); + + // sign with a different key + uint256 badSk = 0xBEEF; + bytes32 digest = keccak256(abi.encodePacked("\x19\x01", _domainSeparator(), structHash)); + (uint8 v, bytes32 r, bytes32 s) = vm.sign(badSk, digest); + bytes memory badSig = abi.encodePacked(r, s, v); + + vm.prank(withdrawal); + vm.expectRevert(IRocketMinipoolRegistry.InvalidSignature.selector); + reg.registerValidatorsWithSig(_one(pk1), badSig, deadline); + } + + function test_WithSig_ExecutorMismatch_Reverts() public { + // node signs for executor = withdrawal + uint256 nonce = 0; + uint256 deadline = block.timestamp + 1 days; + bytes32 pkHash = _pubkeysHash(_one(pk1)); + bytes32 structHash = keccak256(abi.encode( + REGISTER_TYPEHASH, + pkHash, + withdrawal, + nonce, + deadline + )); + bytes memory sig = _sign(structHash); + + // call from a different address -> digest won't match (bound to msg.sender) + vm.prank(makeAddr("not-withdrawal")); + vm.expectRevert(IRocketMinipoolRegistry.InvalidSignature.selector); + reg.registerValidatorsWithSig(_one(pk1), sig, deadline); + } + + function test_WithSig_ExpiredDeadline_Reverts() public { + uint256 nonce = 0; + uint256 deadline = block.timestamp - 1; // already expired + bytes32 pkHash = _pubkeysHash(_one(pk1)); + bytes32 structHash = keccak256(abi.encode( + REGISTER_TYPEHASH, + pkHash, + withdrawal, + nonce, + deadline + )); + bytes memory sig = _sign(structHash); + + vm.prank(withdrawal); + vm.expectRevert(IRocketMinipoolRegistry.ExpiredSignature.selector); + reg.registerValidatorsWithSig(_one(pk1), sig, deadline); + } + + // ---------- Behavior parity checks ---------- + function test_IsValidatorOptedIn_ReflectsState() public { + // not registered + assertFalse(reg.isValidatorOptedIn(pk1)); + + // registered + active + vm.prank(withdrawal); reg.registerValidators(_one(pk1)); + assertTrue(reg.isValidatorOptedIn(pk1)); + + // frozen -> false + vm.prank(oracle); reg.freeze(_one(pk1)); + assertFalse(reg.isValidatorOptedIn(pk1)); + reg.unfreeze{value: fee}(_one(pk1)); + + // dereg requested -> false + vm.prank(withdrawal); reg.requestValidatorDeregistration(_one(pk1)); + assertFalse(reg.isValidatorOptedIn(pk1)); + + // complete dereg clears + vm.warp(block.timestamp + period + 1); + vm.prank(withdrawal); reg.deregisterValidators(_one(pk1)); + assertFalse(reg.isValidatorOptedIn(pk1)); + + // inactive minipool -> false + vm.prank(withdrawal); reg.registerValidators(_one(pk1)); + MinipoolMock(address(mp1)).setStatus(MinipoolStatus.Withdrawable); + assertFalse(reg.isValidatorOptedIn(pk1)); + } + + // receive refunds + receive() external payable {} +} From 4cf652e588315b37e5d3cd270576cdd40d57fed9 Mon Sep 17 00:00:00 2001 From: owen-eth Date: Tue, 11 Nov 2025 15:59:08 -0500 Subject: [PATCH 2/4] cleanup and fmt formatting --- contracts-abi/abi/RocketMinipoolRegistry.abi | 8 +- .../RocketMinipoolRegistry.go | 2 +- .../interfaces/IRocketMinipoolRegistry.sol | 25 +-- .../rocketpool/RocketMinipoolRegistry.sol | 165 ++++++++++-------- 4 files changed, 112 insertions(+), 88 deletions(-) diff --git a/contracts-abi/abi/RocketMinipoolRegistry.abi b/contracts-abi/abi/RocketMinipoolRegistry.abi index 4e4379917..1937f53bc 100644 --- a/contracts-abi/abi/RocketMinipoolRegistry.abi +++ b/contracts-abi/abi/RocketMinipoolRegistry.abi @@ -1114,13 +1114,7 @@ { "type": "error", "name": "NoMinipoolForKey", - "inputs": [ - { - "name": "validatorPubkey", - "type": "bytes", - "internalType": "bytes" - } - ] + "inputs": [] }, { "type": "error", diff --git a/contracts-abi/clients/RocketMinipoolRegistry/RocketMinipoolRegistry.go b/contracts-abi/clients/RocketMinipoolRegistry/RocketMinipoolRegistry.go index 17cc76650..87f715b3a 100644 --- a/contracts-abi/clients/RocketMinipoolRegistry/RocketMinipoolRegistry.go +++ b/contracts-abi/clients/RocketMinipoolRegistry/RocketMinipoolRegistry.go @@ -38,7 +38,7 @@ type IRocketMinipoolRegistryValidatorRegistration struct { // RocketminipoolregistryMetaData contains all meta data concerning the Rocketminipoolregistry contract. var RocketminipoolregistryMetaData = &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\":\"acceptOwnership\",\"inputs\":[],\"outputs\":[],\"stateMutability\":\"nonpayable\"},{\"type\":\"function\",\"name\":\"deregisterValidators\",\"inputs\":[{\"name\":\"valPubKeys\",\"type\":\"bytes[]\",\"internalType\":\"bytes[]\"}],\"outputs\":[],\"stateMutability\":\"nonpayable\"},{\"type\":\"function\",\"name\":\"deregisterValidatorsWithSig\",\"inputs\":[{\"name\":\"valPubKeys\",\"type\":\"bytes[]\",\"internalType\":\"bytes[]\"},{\"name\":\"signature\",\"type\":\"bytes\",\"internalType\":\"bytes\"},{\"name\":\"deadline\",\"type\":\"uint256\",\"internalType\":\"uint256\"}],\"outputs\":[],\"stateMutability\":\"nonpayable\"},{\"type\":\"function\",\"name\":\"deregistrationPeriod\",\"inputs\":[],\"outputs\":[{\"name\":\"\",\"type\":\"uint64\",\"internalType\":\"uint64\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"eip712Domain\",\"inputs\":[],\"outputs\":[{\"name\":\"fields\",\"type\":\"bytes1\",\"internalType\":\"bytes1\"},{\"name\":\"name\",\"type\":\"string\",\"internalType\":\"string\"},{\"name\":\"version\",\"type\":\"string\",\"internalType\":\"string\"},{\"name\":\"chainId\",\"type\":\"uint256\",\"internalType\":\"uint256\"},{\"name\":\"verifyingContract\",\"type\":\"address\",\"internalType\":\"address\"},{\"name\":\"salt\",\"type\":\"bytes32\",\"internalType\":\"bytes32\"},{\"name\":\"extensions\",\"type\":\"uint256[]\",\"internalType\":\"uint256[]\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"freeze\",\"inputs\":[{\"name\":\"valPubKeys\",\"type\":\"bytes[]\",\"internalType\":\"bytes[]\"}],\"outputs\":[],\"stateMutability\":\"nonpayable\"},{\"type\":\"function\",\"name\":\"freezeOracle\",\"inputs\":[],\"outputs\":[{\"name\":\"\",\"type\":\"address\",\"internalType\":\"address\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"getEligibleTimeForDeregistration\",\"inputs\":[{\"name\":\"validatorPubkey\",\"type\":\"bytes\",\"internalType\":\"bytes\"}],\"outputs\":[{\"name\":\"\",\"type\":\"uint64\",\"internalType\":\"uint64\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"getMinipoolFromPubkey\",\"inputs\":[{\"name\":\"validatorPubkey\",\"type\":\"bytes\",\"internalType\":\"bytes\"}],\"outputs\":[{\"name\":\"\",\"type\":\"address\",\"internalType\":\"address\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"getNodeAddressFromMinipool\",\"inputs\":[{\"name\":\"minipool\",\"type\":\"address\",\"internalType\":\"address\"}],\"outputs\":[{\"name\":\"\",\"type\":\"address\",\"internalType\":\"address\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"getNodeAddressFromPubkey\",\"inputs\":[{\"name\":\"validatorPubkey\",\"type\":\"bytes\",\"internalType\":\"bytes\"}],\"outputs\":[{\"name\":\"\",\"type\":\"address\",\"internalType\":\"address\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"getValidOperatorsForKey\",\"inputs\":[{\"name\":\"validatorPubkey\",\"type\":\"bytes\",\"internalType\":\"bytes\"}],\"outputs\":[{\"name\":\"\",\"type\":\"address\",\"internalType\":\"address\"},{\"name\":\"\",\"type\":\"address\",\"internalType\":\"address\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"getValidatorRegInfo\",\"inputs\":[{\"name\":\"valPubKey\",\"type\":\"bytes\",\"internalType\":\"bytes\"}],\"outputs\":[{\"name\":\"\",\"type\":\"tuple\",\"internalType\":\"structIRocketMinipoolRegistry.ValidatorRegistration\",\"components\":[{\"name\":\"exists\",\"type\":\"bool\",\"internalType\":\"bool\"},{\"name\":\"deregTimestamp\",\"type\":\"uint64\",\"internalType\":\"uint64\"},{\"name\":\"freezeTimestamp\",\"type\":\"uint64\",\"internalType\":\"uint64\"}]}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"initialize\",\"inputs\":[{\"name\":\"owner\",\"type\":\"address\",\"internalType\":\"address\"},{\"name\":\"freezeOracle\",\"type\":\"address\",\"internalType\":\"address\"},{\"name\":\"unfreezeReceiver\",\"type\":\"address\",\"internalType\":\"address\"},{\"name\":\"rocketStorage\",\"type\":\"address\",\"internalType\":\"address\"},{\"name\":\"unfreezeFee\",\"type\":\"uint256\",\"internalType\":\"uint256\"},{\"name\":\"deregistrationPeriod\",\"type\":\"uint64\",\"internalType\":\"uint64\"}],\"outputs\":[],\"stateMutability\":\"nonpayable\"},{\"type\":\"function\",\"name\":\"isMinipoolActive\",\"inputs\":[{\"name\":\"minipool\",\"type\":\"address\",\"internalType\":\"address\"}],\"outputs\":[{\"name\":\"\",\"type\":\"bool\",\"internalType\":\"bool\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"isOperatorValidForKey\",\"inputs\":[{\"name\":\"operator\",\"type\":\"address\",\"internalType\":\"address\"},{\"name\":\"validatorPubkey\",\"type\":\"bytes\",\"internalType\":\"bytes\"}],\"outputs\":[{\"name\":\"\",\"type\":\"bool\",\"internalType\":\"bool\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"isValidatorOptedIn\",\"inputs\":[{\"name\":\"valPubKey\",\"type\":\"bytes\",\"internalType\":\"bytes\"}],\"outputs\":[{\"name\":\"\",\"type\":\"bool\",\"internalType\":\"bool\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"isValidatorRegistered\",\"inputs\":[{\"name\":\"validatorPubkey\",\"type\":\"bytes\",\"internalType\":\"bytes\"}],\"outputs\":[{\"name\":\"\",\"type\":\"bool\",\"internalType\":\"bool\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"nonces\",\"inputs\":[{\"name\":\"\",\"type\":\"address\",\"internalType\":\"address\"}],\"outputs\":[{\"name\":\"\",\"type\":\"uint256\",\"internalType\":\"uint256\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"owner\",\"inputs\":[],\"outputs\":[{\"name\":\"\",\"type\":\"address\",\"internalType\":\"address\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"ownerUnfreeze\",\"inputs\":[{\"name\":\"valPubKeys\",\"type\":\"bytes[]\",\"internalType\":\"bytes[]\"}],\"outputs\":[],\"stateMutability\":\"nonpayable\"},{\"type\":\"function\",\"name\":\"pause\",\"inputs\":[],\"outputs\":[],\"stateMutability\":\"nonpayable\"},{\"type\":\"function\",\"name\":\"paused\",\"inputs\":[],\"outputs\":[{\"name\":\"\",\"type\":\"bool\",\"internalType\":\"bool\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"pendingOwner\",\"inputs\":[],\"outputs\":[{\"name\":\"\",\"type\":\"address\",\"internalType\":\"address\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"proxiableUUID\",\"inputs\":[],\"outputs\":[{\"name\":\"\",\"type\":\"bytes32\",\"internalType\":\"bytes32\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"pubkeysHash\",\"inputs\":[{\"name\":\"pubkeys\",\"type\":\"bytes[]\",\"internalType\":\"bytes[]\"}],\"outputs\":[{\"name\":\"\",\"type\":\"bytes32\",\"internalType\":\"bytes32\"}],\"stateMutability\":\"pure\"},{\"type\":\"function\",\"name\":\"registerValidators\",\"inputs\":[{\"name\":\"valPubKeys\",\"type\":\"bytes[]\",\"internalType\":\"bytes[]\"}],\"outputs\":[],\"stateMutability\":\"nonpayable\"},{\"type\":\"function\",\"name\":\"registerValidatorsWithSig\",\"inputs\":[{\"name\":\"valPubKeys\",\"type\":\"bytes[]\",\"internalType\":\"bytes[]\"},{\"name\":\"signature\",\"type\":\"bytes\",\"internalType\":\"bytes\"},{\"name\":\"deadline\",\"type\":\"uint256\",\"internalType\":\"uint256\"}],\"outputs\":[],\"stateMutability\":\"nonpayable\"},{\"type\":\"function\",\"name\":\"renounceOwnership\",\"inputs\":[],\"outputs\":[],\"stateMutability\":\"nonpayable\"},{\"type\":\"function\",\"name\":\"requestValidatorDeregistration\",\"inputs\":[{\"name\":\"valPubKeys\",\"type\":\"bytes[]\",\"internalType\":\"bytes[]\"}],\"outputs\":[],\"stateMutability\":\"nonpayable\"},{\"type\":\"function\",\"name\":\"requestValidatorDeregistrationWithSig\",\"inputs\":[{\"name\":\"valPubKeys\",\"type\":\"bytes[]\",\"internalType\":\"bytes[]\"},{\"name\":\"signature\",\"type\":\"bytes\",\"internalType\":\"bytes\"},{\"name\":\"deadline\",\"type\":\"uint256\",\"internalType\":\"uint256\"}],\"outputs\":[],\"stateMutability\":\"nonpayable\"},{\"type\":\"function\",\"name\":\"rocketStorage\",\"inputs\":[],\"outputs\":[{\"name\":\"\",\"type\":\"address\",\"internalType\":\"contractRocketStorageInterface\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"setDeregistrationPeriod\",\"inputs\":[{\"name\":\"newDeregistrationPeriod\",\"type\":\"uint64\",\"internalType\":\"uint64\"}],\"outputs\":[],\"stateMutability\":\"nonpayable\"},{\"type\":\"function\",\"name\":\"setFreezeOracle\",\"inputs\":[{\"name\":\"newFreezeOracle\",\"type\":\"address\",\"internalType\":\"address\"}],\"outputs\":[],\"stateMutability\":\"nonpayable\"},{\"type\":\"function\",\"name\":\"setRocketStorage\",\"inputs\":[{\"name\":\"newRocketStorage\",\"type\":\"address\",\"internalType\":\"address\"}],\"outputs\":[],\"stateMutability\":\"nonpayable\"},{\"type\":\"function\",\"name\":\"setUnfreezeFee\",\"inputs\":[{\"name\":\"newUnfreezeFee\",\"type\":\"uint256\",\"internalType\":\"uint256\"}],\"outputs\":[],\"stateMutability\":\"nonpayable\"},{\"type\":\"function\",\"name\":\"setUnfreezeReceiver\",\"inputs\":[{\"name\":\"newUnfreezeReceiver\",\"type\":\"address\",\"internalType\":\"address\"}],\"outputs\":[],\"stateMutability\":\"nonpayable\"},{\"type\":\"function\",\"name\":\"transferOwnership\",\"inputs\":[{\"name\":\"newOwner\",\"type\":\"address\",\"internalType\":\"address\"}],\"outputs\":[],\"stateMutability\":\"nonpayable\"},{\"type\":\"function\",\"name\":\"unfreeze\",\"inputs\":[{\"name\":\"valPubKeys\",\"type\":\"bytes[]\",\"internalType\":\"bytes[]\"}],\"outputs\":[],\"stateMutability\":\"payable\"},{\"type\":\"function\",\"name\":\"unfreezeFee\",\"inputs\":[],\"outputs\":[{\"name\":\"\",\"type\":\"uint256\",\"internalType\":\"uint256\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"unfreezeReceiver\",\"inputs\":[],\"outputs\":[{\"name\":\"\",\"type\":\"address\",\"internalType\":\"address\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"unpause\",\"inputs\":[],\"outputs\":[],\"stateMutability\":\"nonpayable\"},{\"type\":\"function\",\"name\":\"upgradeToAndCall\",\"inputs\":[{\"name\":\"newImplementation\",\"type\":\"address\",\"internalType\":\"address\"},{\"name\":\"data\",\"type\":\"bytes\",\"internalType\":\"bytes\"}],\"outputs\":[],\"stateMutability\":\"payable\"},{\"type\":\"function\",\"name\":\"validatorRegistrations\",\"inputs\":[{\"name\":\"\",\"type\":\"bytes\",\"internalType\":\"bytes\"}],\"outputs\":[{\"name\":\"exists\",\"type\":\"bool\",\"internalType\":\"bool\"},{\"name\":\"deregTimestamp\",\"type\":\"uint64\",\"internalType\":\"uint64\"},{\"name\":\"freezeTimestamp\",\"type\":\"uint64\",\"internalType\":\"uint64\"}],\"stateMutability\":\"view\"},{\"type\":\"event\",\"name\":\"EIP712DomainChanged\",\"inputs\":[],\"anonymous\":false},{\"type\":\"event\",\"name\":\"Initialized\",\"inputs\":[{\"name\":\"version\",\"type\":\"uint64\",\"indexed\":false,\"internalType\":\"uint64\"}],\"anonymous\":false},{\"type\":\"event\",\"name\":\"OwnershipTransferStarted\",\"inputs\":[{\"name\":\"previousOwner\",\"type\":\"address\",\"indexed\":true,\"internalType\":\"address\"},{\"name\":\"newOwner\",\"type\":\"address\",\"indexed\":true,\"internalType\":\"address\"}],\"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\":\"Unpaused\",\"inputs\":[{\"name\":\"account\",\"type\":\"address\",\"indexed\":false,\"internalType\":\"address\"}],\"anonymous\":false},{\"type\":\"event\",\"name\":\"Upgraded\",\"inputs\":[{\"name\":\"implementation\",\"type\":\"address\",\"indexed\":true,\"internalType\":\"address\"}],\"anonymous\":false},{\"type\":\"event\",\"name\":\"ValidatorDeregistered\",\"inputs\":[{\"name\":\"validatorPubKey\",\"type\":\"bytes\",\"indexed\":true,\"internalType\":\"bytes\"},{\"name\":\"nodeAddress\",\"type\":\"address\",\"indexed\":true,\"internalType\":\"address\"}],\"anonymous\":false},{\"type\":\"event\",\"name\":\"ValidatorDeregistrationRequested\",\"inputs\":[{\"name\":\"validatorPubKey\",\"type\":\"bytes\",\"indexed\":true,\"internalType\":\"bytes\"},{\"name\":\"nodeAddress\",\"type\":\"address\",\"indexed\":true,\"internalType\":\"address\"}],\"anonymous\":false},{\"type\":\"event\",\"name\":\"ValidatorFrozen\",\"inputs\":[{\"name\":\"validatorPubKey\",\"type\":\"bytes\",\"indexed\":true,\"internalType\":\"bytes\"}],\"anonymous\":false},{\"type\":\"event\",\"name\":\"ValidatorRegistered\",\"inputs\":[{\"name\":\"validatorPubKey\",\"type\":\"bytes\",\"indexed\":true,\"internalType\":\"bytes\"},{\"name\":\"nodeAddress\",\"type\":\"address\",\"indexed\":true,\"internalType\":\"address\"}],\"anonymous\":false},{\"type\":\"event\",\"name\":\"ValidatorUnfrozen\",\"inputs\":[{\"name\":\"validatorPubKey\",\"type\":\"bytes\",\"indexed\":true,\"internalType\":\"bytes\"}],\"anonymous\":false},{\"type\":\"error\",\"name\":\"AddressEmptyCode\",\"inputs\":[{\"name\":\"target\",\"type\":\"address\",\"internalType\":\"address\"}]},{\"type\":\"error\",\"name\":\"DeregRequestAlreadyExists\",\"inputs\":[{\"name\":\"validatorPubkey\",\"type\":\"bytes\",\"internalType\":\"bytes\"}]},{\"type\":\"error\",\"name\":\"DeregRequestDoesNotExist\",\"inputs\":[{\"name\":\"validatorPubkey\",\"type\":\"bytes\",\"internalType\":\"bytes\"}]},{\"type\":\"error\",\"name\":\"DeregistrationTooSoon\",\"inputs\":[{\"name\":\"validatorPubkey\",\"type\":\"bytes\",\"internalType\":\"bytes\"}]},{\"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\":\"ExpiredSignature\",\"inputs\":[]},{\"type\":\"error\",\"name\":\"FailedInnerCall\",\"inputs\":[]},{\"type\":\"error\",\"name\":\"FrozenValidatorCannotDeregister\",\"inputs\":[{\"name\":\"validatorPubkey\",\"type\":\"bytes\",\"internalType\":\"bytes\"}]},{\"type\":\"error\",\"name\":\"InvalidBLSPubKeyLength\",\"inputs\":[{\"name\":\"expectedLength\",\"type\":\"uint256\",\"internalType\":\"uint256\"},{\"name\":\"actualLength\",\"type\":\"uint256\",\"internalType\":\"uint256\"}]},{\"type\":\"error\",\"name\":\"InvalidFallback\",\"inputs\":[]},{\"type\":\"error\",\"name\":\"InvalidInitialization\",\"inputs\":[]},{\"type\":\"error\",\"name\":\"InvalidReceive\",\"inputs\":[]},{\"type\":\"error\",\"name\":\"InvalidSignature\",\"inputs\":[]},{\"type\":\"error\",\"name\":\"MinipoolNotActive\",\"inputs\":[{\"name\":\"validatorPubkey\",\"type\":\"bytes\",\"internalType\":\"bytes\"}]},{\"type\":\"error\",\"name\":\"MixedNodeBatch\",\"inputs\":[{\"name\":\"validatorPubkey\",\"type\":\"bytes\",\"internalType\":\"bytes\"}]},{\"type\":\"error\",\"name\":\"NoKeysProvided\",\"inputs\":[]},{\"type\":\"error\",\"name\":\"NoMinipoolForKey\",\"inputs\":[{\"name\":\"validatorPubkey\",\"type\":\"bytes\",\"internalType\":\"bytes\"}]},{\"type\":\"error\",\"name\":\"NotInitializing\",\"inputs\":[]},{\"type\":\"error\",\"name\":\"NotMinipoolOperator\",\"inputs\":[{\"name\":\"validatorPubkey\",\"type\":\"bytes\",\"internalType\":\"bytes\"}]},{\"type\":\"error\",\"name\":\"NotWithdrawalAddress\",\"inputs\":[{\"name\":\"withdrawalAddress\",\"type\":\"address\",\"internalType\":\"address\"}]},{\"type\":\"error\",\"name\":\"OnlyFreezeOracle\",\"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\":\"ReentrancyGuardReentrantCall\",\"inputs\":[]},{\"type\":\"error\",\"name\":\"RefundFailed\",\"inputs\":[]},{\"type\":\"error\",\"name\":\"UUPSUnauthorizedCallContext\",\"inputs\":[]},{\"type\":\"error\",\"name\":\"UUPSUnsupportedProxiableUUID\",\"inputs\":[{\"name\":\"slot\",\"type\":\"bytes32\",\"internalType\":\"bytes32\"}]},{\"type\":\"error\",\"name\":\"UnfreezeFeeRequired\",\"inputs\":[{\"name\":\"requiredFee\",\"type\":\"uint256\",\"internalType\":\"uint256\"}]},{\"type\":\"error\",\"name\":\"UnfreezeTransferFailed\",\"inputs\":[]},{\"type\":\"error\",\"name\":\"ValidatorAlreadyFrozen\",\"inputs\":[{\"name\":\"validatorPubkey\",\"type\":\"bytes\",\"internalType\":\"bytes\"}]},{\"type\":\"error\",\"name\":\"ValidatorAlreadyRegistered\",\"inputs\":[{\"name\":\"validatorPubkey\",\"type\":\"bytes\",\"internalType\":\"bytes\"}]},{\"type\":\"error\",\"name\":\"ValidatorDeregistrationNotExpired\",\"inputs\":[{\"name\":\"validatorPubkey\",\"type\":\"bytes\",\"internalType\":\"bytes\"}]},{\"type\":\"error\",\"name\":\"ValidatorNotFrozen\",\"inputs\":[{\"name\":\"validatorPubkey\",\"type\":\"bytes\",\"internalType\":\"bytes\"}]},{\"type\":\"error\",\"name\":\"ValidatorNotRegistered\",\"inputs\":[{\"name\":\"validatorPubkey\",\"type\":\"bytes\",\"internalType\":\"bytes\"}]},{\"type\":\"error\",\"name\":\"ZeroParam\",\"inputs\":[]}]", + 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\":\"acceptOwnership\",\"inputs\":[],\"outputs\":[],\"stateMutability\":\"nonpayable\"},{\"type\":\"function\",\"name\":\"deregisterValidators\",\"inputs\":[{\"name\":\"valPubKeys\",\"type\":\"bytes[]\",\"internalType\":\"bytes[]\"}],\"outputs\":[],\"stateMutability\":\"nonpayable\"},{\"type\":\"function\",\"name\":\"deregisterValidatorsWithSig\",\"inputs\":[{\"name\":\"valPubKeys\",\"type\":\"bytes[]\",\"internalType\":\"bytes[]\"},{\"name\":\"signature\",\"type\":\"bytes\",\"internalType\":\"bytes\"},{\"name\":\"deadline\",\"type\":\"uint256\",\"internalType\":\"uint256\"}],\"outputs\":[],\"stateMutability\":\"nonpayable\"},{\"type\":\"function\",\"name\":\"deregistrationPeriod\",\"inputs\":[],\"outputs\":[{\"name\":\"\",\"type\":\"uint64\",\"internalType\":\"uint64\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"eip712Domain\",\"inputs\":[],\"outputs\":[{\"name\":\"fields\",\"type\":\"bytes1\",\"internalType\":\"bytes1\"},{\"name\":\"name\",\"type\":\"string\",\"internalType\":\"string\"},{\"name\":\"version\",\"type\":\"string\",\"internalType\":\"string\"},{\"name\":\"chainId\",\"type\":\"uint256\",\"internalType\":\"uint256\"},{\"name\":\"verifyingContract\",\"type\":\"address\",\"internalType\":\"address\"},{\"name\":\"salt\",\"type\":\"bytes32\",\"internalType\":\"bytes32\"},{\"name\":\"extensions\",\"type\":\"uint256[]\",\"internalType\":\"uint256[]\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"freeze\",\"inputs\":[{\"name\":\"valPubKeys\",\"type\":\"bytes[]\",\"internalType\":\"bytes[]\"}],\"outputs\":[],\"stateMutability\":\"nonpayable\"},{\"type\":\"function\",\"name\":\"freezeOracle\",\"inputs\":[],\"outputs\":[{\"name\":\"\",\"type\":\"address\",\"internalType\":\"address\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"getEligibleTimeForDeregistration\",\"inputs\":[{\"name\":\"validatorPubkey\",\"type\":\"bytes\",\"internalType\":\"bytes\"}],\"outputs\":[{\"name\":\"\",\"type\":\"uint64\",\"internalType\":\"uint64\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"getMinipoolFromPubkey\",\"inputs\":[{\"name\":\"validatorPubkey\",\"type\":\"bytes\",\"internalType\":\"bytes\"}],\"outputs\":[{\"name\":\"\",\"type\":\"address\",\"internalType\":\"address\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"getNodeAddressFromMinipool\",\"inputs\":[{\"name\":\"minipool\",\"type\":\"address\",\"internalType\":\"address\"}],\"outputs\":[{\"name\":\"\",\"type\":\"address\",\"internalType\":\"address\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"getNodeAddressFromPubkey\",\"inputs\":[{\"name\":\"validatorPubkey\",\"type\":\"bytes\",\"internalType\":\"bytes\"}],\"outputs\":[{\"name\":\"\",\"type\":\"address\",\"internalType\":\"address\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"getValidOperatorsForKey\",\"inputs\":[{\"name\":\"validatorPubkey\",\"type\":\"bytes\",\"internalType\":\"bytes\"}],\"outputs\":[{\"name\":\"\",\"type\":\"address\",\"internalType\":\"address\"},{\"name\":\"\",\"type\":\"address\",\"internalType\":\"address\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"getValidatorRegInfo\",\"inputs\":[{\"name\":\"valPubKey\",\"type\":\"bytes\",\"internalType\":\"bytes\"}],\"outputs\":[{\"name\":\"\",\"type\":\"tuple\",\"internalType\":\"structIRocketMinipoolRegistry.ValidatorRegistration\",\"components\":[{\"name\":\"exists\",\"type\":\"bool\",\"internalType\":\"bool\"},{\"name\":\"deregTimestamp\",\"type\":\"uint64\",\"internalType\":\"uint64\"},{\"name\":\"freezeTimestamp\",\"type\":\"uint64\",\"internalType\":\"uint64\"}]}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"initialize\",\"inputs\":[{\"name\":\"owner\",\"type\":\"address\",\"internalType\":\"address\"},{\"name\":\"freezeOracle\",\"type\":\"address\",\"internalType\":\"address\"},{\"name\":\"unfreezeReceiver\",\"type\":\"address\",\"internalType\":\"address\"},{\"name\":\"rocketStorage\",\"type\":\"address\",\"internalType\":\"address\"},{\"name\":\"unfreezeFee\",\"type\":\"uint256\",\"internalType\":\"uint256\"},{\"name\":\"deregistrationPeriod\",\"type\":\"uint64\",\"internalType\":\"uint64\"}],\"outputs\":[],\"stateMutability\":\"nonpayable\"},{\"type\":\"function\",\"name\":\"isMinipoolActive\",\"inputs\":[{\"name\":\"minipool\",\"type\":\"address\",\"internalType\":\"address\"}],\"outputs\":[{\"name\":\"\",\"type\":\"bool\",\"internalType\":\"bool\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"isOperatorValidForKey\",\"inputs\":[{\"name\":\"operator\",\"type\":\"address\",\"internalType\":\"address\"},{\"name\":\"validatorPubkey\",\"type\":\"bytes\",\"internalType\":\"bytes\"}],\"outputs\":[{\"name\":\"\",\"type\":\"bool\",\"internalType\":\"bool\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"isValidatorOptedIn\",\"inputs\":[{\"name\":\"valPubKey\",\"type\":\"bytes\",\"internalType\":\"bytes\"}],\"outputs\":[{\"name\":\"\",\"type\":\"bool\",\"internalType\":\"bool\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"isValidatorRegistered\",\"inputs\":[{\"name\":\"validatorPubkey\",\"type\":\"bytes\",\"internalType\":\"bytes\"}],\"outputs\":[{\"name\":\"\",\"type\":\"bool\",\"internalType\":\"bool\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"nonces\",\"inputs\":[{\"name\":\"\",\"type\":\"address\",\"internalType\":\"address\"}],\"outputs\":[{\"name\":\"\",\"type\":\"uint256\",\"internalType\":\"uint256\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"owner\",\"inputs\":[],\"outputs\":[{\"name\":\"\",\"type\":\"address\",\"internalType\":\"address\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"ownerUnfreeze\",\"inputs\":[{\"name\":\"valPubKeys\",\"type\":\"bytes[]\",\"internalType\":\"bytes[]\"}],\"outputs\":[],\"stateMutability\":\"nonpayable\"},{\"type\":\"function\",\"name\":\"pause\",\"inputs\":[],\"outputs\":[],\"stateMutability\":\"nonpayable\"},{\"type\":\"function\",\"name\":\"paused\",\"inputs\":[],\"outputs\":[{\"name\":\"\",\"type\":\"bool\",\"internalType\":\"bool\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"pendingOwner\",\"inputs\":[],\"outputs\":[{\"name\":\"\",\"type\":\"address\",\"internalType\":\"address\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"proxiableUUID\",\"inputs\":[],\"outputs\":[{\"name\":\"\",\"type\":\"bytes32\",\"internalType\":\"bytes32\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"pubkeysHash\",\"inputs\":[{\"name\":\"pubkeys\",\"type\":\"bytes[]\",\"internalType\":\"bytes[]\"}],\"outputs\":[{\"name\":\"\",\"type\":\"bytes32\",\"internalType\":\"bytes32\"}],\"stateMutability\":\"pure\"},{\"type\":\"function\",\"name\":\"registerValidators\",\"inputs\":[{\"name\":\"valPubKeys\",\"type\":\"bytes[]\",\"internalType\":\"bytes[]\"}],\"outputs\":[],\"stateMutability\":\"nonpayable\"},{\"type\":\"function\",\"name\":\"registerValidatorsWithSig\",\"inputs\":[{\"name\":\"valPubKeys\",\"type\":\"bytes[]\",\"internalType\":\"bytes[]\"},{\"name\":\"signature\",\"type\":\"bytes\",\"internalType\":\"bytes\"},{\"name\":\"deadline\",\"type\":\"uint256\",\"internalType\":\"uint256\"}],\"outputs\":[],\"stateMutability\":\"nonpayable\"},{\"type\":\"function\",\"name\":\"renounceOwnership\",\"inputs\":[],\"outputs\":[],\"stateMutability\":\"nonpayable\"},{\"type\":\"function\",\"name\":\"requestValidatorDeregistration\",\"inputs\":[{\"name\":\"valPubKeys\",\"type\":\"bytes[]\",\"internalType\":\"bytes[]\"}],\"outputs\":[],\"stateMutability\":\"nonpayable\"},{\"type\":\"function\",\"name\":\"requestValidatorDeregistrationWithSig\",\"inputs\":[{\"name\":\"valPubKeys\",\"type\":\"bytes[]\",\"internalType\":\"bytes[]\"},{\"name\":\"signature\",\"type\":\"bytes\",\"internalType\":\"bytes\"},{\"name\":\"deadline\",\"type\":\"uint256\",\"internalType\":\"uint256\"}],\"outputs\":[],\"stateMutability\":\"nonpayable\"},{\"type\":\"function\",\"name\":\"rocketStorage\",\"inputs\":[],\"outputs\":[{\"name\":\"\",\"type\":\"address\",\"internalType\":\"contractRocketStorageInterface\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"setDeregistrationPeriod\",\"inputs\":[{\"name\":\"newDeregistrationPeriod\",\"type\":\"uint64\",\"internalType\":\"uint64\"}],\"outputs\":[],\"stateMutability\":\"nonpayable\"},{\"type\":\"function\",\"name\":\"setFreezeOracle\",\"inputs\":[{\"name\":\"newFreezeOracle\",\"type\":\"address\",\"internalType\":\"address\"}],\"outputs\":[],\"stateMutability\":\"nonpayable\"},{\"type\":\"function\",\"name\":\"setRocketStorage\",\"inputs\":[{\"name\":\"newRocketStorage\",\"type\":\"address\",\"internalType\":\"address\"}],\"outputs\":[],\"stateMutability\":\"nonpayable\"},{\"type\":\"function\",\"name\":\"setUnfreezeFee\",\"inputs\":[{\"name\":\"newUnfreezeFee\",\"type\":\"uint256\",\"internalType\":\"uint256\"}],\"outputs\":[],\"stateMutability\":\"nonpayable\"},{\"type\":\"function\",\"name\":\"setUnfreezeReceiver\",\"inputs\":[{\"name\":\"newUnfreezeReceiver\",\"type\":\"address\",\"internalType\":\"address\"}],\"outputs\":[],\"stateMutability\":\"nonpayable\"},{\"type\":\"function\",\"name\":\"transferOwnership\",\"inputs\":[{\"name\":\"newOwner\",\"type\":\"address\",\"internalType\":\"address\"}],\"outputs\":[],\"stateMutability\":\"nonpayable\"},{\"type\":\"function\",\"name\":\"unfreeze\",\"inputs\":[{\"name\":\"valPubKeys\",\"type\":\"bytes[]\",\"internalType\":\"bytes[]\"}],\"outputs\":[],\"stateMutability\":\"payable\"},{\"type\":\"function\",\"name\":\"unfreezeFee\",\"inputs\":[],\"outputs\":[{\"name\":\"\",\"type\":\"uint256\",\"internalType\":\"uint256\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"unfreezeReceiver\",\"inputs\":[],\"outputs\":[{\"name\":\"\",\"type\":\"address\",\"internalType\":\"address\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"unpause\",\"inputs\":[],\"outputs\":[],\"stateMutability\":\"nonpayable\"},{\"type\":\"function\",\"name\":\"upgradeToAndCall\",\"inputs\":[{\"name\":\"newImplementation\",\"type\":\"address\",\"internalType\":\"address\"},{\"name\":\"data\",\"type\":\"bytes\",\"internalType\":\"bytes\"}],\"outputs\":[],\"stateMutability\":\"payable\"},{\"type\":\"function\",\"name\":\"validatorRegistrations\",\"inputs\":[{\"name\":\"\",\"type\":\"bytes\",\"internalType\":\"bytes\"}],\"outputs\":[{\"name\":\"exists\",\"type\":\"bool\",\"internalType\":\"bool\"},{\"name\":\"deregTimestamp\",\"type\":\"uint64\",\"internalType\":\"uint64\"},{\"name\":\"freezeTimestamp\",\"type\":\"uint64\",\"internalType\":\"uint64\"}],\"stateMutability\":\"view\"},{\"type\":\"event\",\"name\":\"EIP712DomainChanged\",\"inputs\":[],\"anonymous\":false},{\"type\":\"event\",\"name\":\"Initialized\",\"inputs\":[{\"name\":\"version\",\"type\":\"uint64\",\"indexed\":false,\"internalType\":\"uint64\"}],\"anonymous\":false},{\"type\":\"event\",\"name\":\"OwnershipTransferStarted\",\"inputs\":[{\"name\":\"previousOwner\",\"type\":\"address\",\"indexed\":true,\"internalType\":\"address\"},{\"name\":\"newOwner\",\"type\":\"address\",\"indexed\":true,\"internalType\":\"address\"}],\"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\":\"Unpaused\",\"inputs\":[{\"name\":\"account\",\"type\":\"address\",\"indexed\":false,\"internalType\":\"address\"}],\"anonymous\":false},{\"type\":\"event\",\"name\":\"Upgraded\",\"inputs\":[{\"name\":\"implementation\",\"type\":\"address\",\"indexed\":true,\"internalType\":\"address\"}],\"anonymous\":false},{\"type\":\"event\",\"name\":\"ValidatorDeregistered\",\"inputs\":[{\"name\":\"validatorPubKey\",\"type\":\"bytes\",\"indexed\":true,\"internalType\":\"bytes\"},{\"name\":\"nodeAddress\",\"type\":\"address\",\"indexed\":true,\"internalType\":\"address\"}],\"anonymous\":false},{\"type\":\"event\",\"name\":\"ValidatorDeregistrationRequested\",\"inputs\":[{\"name\":\"validatorPubKey\",\"type\":\"bytes\",\"indexed\":true,\"internalType\":\"bytes\"},{\"name\":\"nodeAddress\",\"type\":\"address\",\"indexed\":true,\"internalType\":\"address\"}],\"anonymous\":false},{\"type\":\"event\",\"name\":\"ValidatorFrozen\",\"inputs\":[{\"name\":\"validatorPubKey\",\"type\":\"bytes\",\"indexed\":true,\"internalType\":\"bytes\"}],\"anonymous\":false},{\"type\":\"event\",\"name\":\"ValidatorRegistered\",\"inputs\":[{\"name\":\"validatorPubKey\",\"type\":\"bytes\",\"indexed\":true,\"internalType\":\"bytes\"},{\"name\":\"nodeAddress\",\"type\":\"address\",\"indexed\":true,\"internalType\":\"address\"}],\"anonymous\":false},{\"type\":\"event\",\"name\":\"ValidatorUnfrozen\",\"inputs\":[{\"name\":\"validatorPubKey\",\"type\":\"bytes\",\"indexed\":true,\"internalType\":\"bytes\"}],\"anonymous\":false},{\"type\":\"error\",\"name\":\"AddressEmptyCode\",\"inputs\":[{\"name\":\"target\",\"type\":\"address\",\"internalType\":\"address\"}]},{\"type\":\"error\",\"name\":\"DeregRequestAlreadyExists\",\"inputs\":[{\"name\":\"validatorPubkey\",\"type\":\"bytes\",\"internalType\":\"bytes\"}]},{\"type\":\"error\",\"name\":\"DeregRequestDoesNotExist\",\"inputs\":[{\"name\":\"validatorPubkey\",\"type\":\"bytes\",\"internalType\":\"bytes\"}]},{\"type\":\"error\",\"name\":\"DeregistrationTooSoon\",\"inputs\":[{\"name\":\"validatorPubkey\",\"type\":\"bytes\",\"internalType\":\"bytes\"}]},{\"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\":\"ExpiredSignature\",\"inputs\":[]},{\"type\":\"error\",\"name\":\"FailedInnerCall\",\"inputs\":[]},{\"type\":\"error\",\"name\":\"FrozenValidatorCannotDeregister\",\"inputs\":[{\"name\":\"validatorPubkey\",\"type\":\"bytes\",\"internalType\":\"bytes\"}]},{\"type\":\"error\",\"name\":\"InvalidBLSPubKeyLength\",\"inputs\":[{\"name\":\"expectedLength\",\"type\":\"uint256\",\"internalType\":\"uint256\"},{\"name\":\"actualLength\",\"type\":\"uint256\",\"internalType\":\"uint256\"}]},{\"type\":\"error\",\"name\":\"InvalidFallback\",\"inputs\":[]},{\"type\":\"error\",\"name\":\"InvalidInitialization\",\"inputs\":[]},{\"type\":\"error\",\"name\":\"InvalidReceive\",\"inputs\":[]},{\"type\":\"error\",\"name\":\"InvalidSignature\",\"inputs\":[]},{\"type\":\"error\",\"name\":\"MinipoolNotActive\",\"inputs\":[{\"name\":\"validatorPubkey\",\"type\":\"bytes\",\"internalType\":\"bytes\"}]},{\"type\":\"error\",\"name\":\"MixedNodeBatch\",\"inputs\":[{\"name\":\"validatorPubkey\",\"type\":\"bytes\",\"internalType\":\"bytes\"}]},{\"type\":\"error\",\"name\":\"NoKeysProvided\",\"inputs\":[]},{\"type\":\"error\",\"name\":\"NoMinipoolForKey\",\"inputs\":[]},{\"type\":\"error\",\"name\":\"NotInitializing\",\"inputs\":[]},{\"type\":\"error\",\"name\":\"NotMinipoolOperator\",\"inputs\":[{\"name\":\"validatorPubkey\",\"type\":\"bytes\",\"internalType\":\"bytes\"}]},{\"type\":\"error\",\"name\":\"NotWithdrawalAddress\",\"inputs\":[{\"name\":\"withdrawalAddress\",\"type\":\"address\",\"internalType\":\"address\"}]},{\"type\":\"error\",\"name\":\"OnlyFreezeOracle\",\"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\":\"ReentrancyGuardReentrantCall\",\"inputs\":[]},{\"type\":\"error\",\"name\":\"RefundFailed\",\"inputs\":[]},{\"type\":\"error\",\"name\":\"UUPSUnauthorizedCallContext\",\"inputs\":[]},{\"type\":\"error\",\"name\":\"UUPSUnsupportedProxiableUUID\",\"inputs\":[{\"name\":\"slot\",\"type\":\"bytes32\",\"internalType\":\"bytes32\"}]},{\"type\":\"error\",\"name\":\"UnfreezeFeeRequired\",\"inputs\":[{\"name\":\"requiredFee\",\"type\":\"uint256\",\"internalType\":\"uint256\"}]},{\"type\":\"error\",\"name\":\"UnfreezeTransferFailed\",\"inputs\":[]},{\"type\":\"error\",\"name\":\"ValidatorAlreadyFrozen\",\"inputs\":[{\"name\":\"validatorPubkey\",\"type\":\"bytes\",\"internalType\":\"bytes\"}]},{\"type\":\"error\",\"name\":\"ValidatorAlreadyRegistered\",\"inputs\":[{\"name\":\"validatorPubkey\",\"type\":\"bytes\",\"internalType\":\"bytes\"}]},{\"type\":\"error\",\"name\":\"ValidatorDeregistrationNotExpired\",\"inputs\":[{\"name\":\"validatorPubkey\",\"type\":\"bytes\",\"internalType\":\"bytes\"}]},{\"type\":\"error\",\"name\":\"ValidatorNotFrozen\",\"inputs\":[{\"name\":\"validatorPubkey\",\"type\":\"bytes\",\"internalType\":\"bytes\"}]},{\"type\":\"error\",\"name\":\"ValidatorNotRegistered\",\"inputs\":[{\"name\":\"validatorPubkey\",\"type\":\"bytes\",\"internalType\":\"bytes\"}]},{\"type\":\"error\",\"name\":\"ZeroParam\",\"inputs\":[]}]", } // RocketminipoolregistryABI is the input ABI used to generate the binding from. diff --git a/contracts/contracts/interfaces/IRocketMinipoolRegistry.sol b/contracts/contracts/interfaces/IRocketMinipoolRegistry.sol index cb05c93d9..b2fe9f219 100644 --- a/contracts/contracts/interfaces/IRocketMinipoolRegistry.sol +++ b/contracts/contracts/interfaces/IRocketMinipoolRegistry.sol @@ -2,7 +2,6 @@ pragma solidity 0.8.26; interface IRocketMinipoolRegistry { - struct ValidatorRegistration { bool exists; uint64 deregTimestamp; @@ -48,7 +47,7 @@ interface IRocketMinipoolRegistry { error ExpiredSignature(); error InvalidSignature(); - + error NotWithdrawalAddress(address withdrawalAddress); error NoKeysProvided(); @@ -57,7 +56,7 @@ interface IRocketMinipoolRegistry { error MinipoolNotActive(bytes validatorPubkey); - error NoMinipoolForKey(bytes validatorPubkey); + error NoMinipoolForKey(); error DeregRequestDoesNotExist(bytes validatorPubkey); @@ -85,13 +84,19 @@ interface IRocketMinipoolRegistry { function deregisterValidators(bytes[] calldata validatorPubkeys) external; /// @notice Registers validators with a signature from the node address. - function registerValidatorsWithSig(bytes[] calldata valPubKeys, bytes calldata signature, uint256 deadline) external; - + function registerValidatorsWithSig(bytes[] calldata valPubKeys, bytes calldata signature, uint256 deadline) + external; + /// @notice Requests deregistration for validators with a signature from the node address. - function requestValidatorDeregistrationWithSig(bytes[] calldata valPubKeys, bytes calldata signature, uint256 deadline) external; - + function requestValidatorDeregistrationWithSig( + bytes[] calldata valPubKeys, + bytes calldata signature, + uint256 deadline + ) external; + /// @notice Deregisters validators with a signature from the node address. - function deregisterValidatorsWithSig(bytes[] calldata valPubKeys, bytes calldata signature, uint256 deadline) external; + function deregisterValidatorsWithSig(bytes[] calldata valPubKeys, bytes calldata signature, uint256 deadline) + external; /// @notice Freezes validators. function freeze(bytes[] calldata validatorPubkeys) external; @@ -117,9 +122,9 @@ interface IRocketMinipoolRegistry { /// @notice Returns the validator registration info. function getValidatorRegInfo(bytes calldata validatorPubkey) external view returns (ValidatorRegistration memory); - /// @notice Checks if a validator is opted-in. + /// @notice Checks if a validator is opted-in. function isValidatorOptedIn(bytes calldata validatorPubkey) external view returns (bool); /// @notice Checks if an operator is valid to interact on behalf of a validator. function isOperatorValidForKey(address operator, bytes calldata validatorPubkey) external view returns (bool); -} \ No newline at end of file +} diff --git a/contracts/contracts/validator-registry/rocketpool/RocketMinipoolRegistry.sol b/contracts/contracts/validator-registry/rocketpool/RocketMinipoolRegistry.sol index 77352499a..c4cc3525a 100644 --- a/contracts/contracts/validator-registry/rocketpool/RocketMinipoolRegistry.sol +++ b/contracts/contracts/validator-registry/rocketpool/RocketMinipoolRegistry.sol @@ -19,13 +19,22 @@ import {RocketMinipoolRegistryStorage} from "./RocketMinipoolRegistryStorage.sol /// @title RocketMinipoolRegistry /// @notice This contract serves as the entrypoint for operators to register with /// the mev-commit protocol via Rocketpool minipools. -contract RocketMinipoolRegistry is IRocketMinipoolRegistry, RocketMinipoolRegistryStorage, - Ownable2StepUpgradeable, PausableUpgradeable, ReentrancyGuardUpgradeable, UUPSUpgradeable, EIP712Upgradeable { - +contract RocketMinipoolRegistry is + IRocketMinipoolRegistry, + RocketMinipoolRegistryStorage, + Ownable2StepUpgradeable, + PausableUpgradeable, + ReentrancyGuardUpgradeable, + UUPSUpgradeable, + EIP712Upgradeable +{ // We keep payloads compact by hashing the pubkeys array to a bytes32. - bytes32 private constant _REGISTER_TYPEHASH = keccak256("Register(bytes32 pubkeysHash,address executor,uint256 nonce,uint256 deadline)"); - bytes32 private constant _DEREG_REQ_TYPEHASH = keccak256("DeregRequest(bytes32 pubkeysHash,address executor,uint256 nonce,uint256 deadline)"); - bytes32 private constant _DEREG_TYPEHASH = keccak256("Deregister(bytes32 pubkeysHash,address executor,uint256 nonce,uint256 deadline)"); + bytes32 private constant _REGISTER_TYPEHASH = + keccak256("Register(bytes32 pubkeysHash,address executor,uint256 nonce,uint256 deadline)"); + bytes32 private constant _DEREG_REQ_TYPEHASH = + keccak256("DeregRequest(bytes32 pubkeysHash,address executor,uint256 nonce,uint256 deadline)"); + bytes32 private constant _DEREG_TYPEHASH = + keccak256("Deregister(bytes32 pubkeysHash,address executor,uint256 nonce,uint256 deadline)"); modifier onlyFreezeOracle() { require(msg.sender == freezeOracle, IRocketMinipoolRegistry.OnlyFreezeOracle()); @@ -37,7 +46,9 @@ contract RocketMinipoolRegistry is IRocketMinipoolRegistry, RocketMinipoolRegist uint256 len = valPubKeys.length; require(len > 0, NoKeysProvided()); for (uint256 i = 0; i < len; ++i) { - require(valPubKeys[i].length == 48, IRocketMinipoolRegistry.InvalidBLSPubKeyLength(48, valPubKeys[i].length)); + require( + valPubKeys[i].length == 48, IRocketMinipoolRegistry.InvalidBLSPubKeyLength(48, valPubKeys[i].length) + ); } _; } @@ -57,8 +68,15 @@ contract RocketMinipoolRegistry is IRocketMinipoolRegistry, RocketMinipoolRegist fallback() external payable { revert Errors.InvalidFallback(); } - - function initialize(address owner, address freezeOracle, address unfreezeReceiver, address rocketStorage, uint256 unfreezeFee, uint64 deregistrationPeriod) external initializer { + + function initialize( + address owner, + address freezeOracle, + address unfreezeReceiver, + address rocketStorage, + uint256 unfreezeFee, + uint64 deregistrationPeriod + ) external initializer { __Ownable_init(owner); __Pausable_init(); __ReentrancyGuard_init(); @@ -76,7 +94,7 @@ contract RocketMinipoolRegistry is IRocketMinipoolRegistry, RocketMinipoolRegist address nodeAddress0 = _getFirstNodeAddrAndValidate(valPubKeys[0]); _registerValidator(valPubKeys[0]); emit ValidatorRegistered(valPubKeys[0], nodeAddress0); - + uint256 len = valPubKeys.length; for (uint256 i = 1; i < len; ++i) { require(getNodeAddressFromPubkey(valPubKeys[i]) == nodeAddress0, MixedNodeBatch(valPubKeys[i])); @@ -86,31 +104,30 @@ contract RocketMinipoolRegistry is IRocketMinipoolRegistry, RocketMinipoolRegist } /// @notice Allows a user to register using a signature. - function registerValidatorsWithSig(bytes[] calldata valPubKeys, bytes calldata signature, uint256 deadline) external onlyValidBLSPubKeys(valPubKeys) whenNotPaused { + function registerValidatorsWithSig(bytes[] calldata valPubKeys, bytes calldata signature, uint256 deadline) + external + onlyValidBLSPubKeys(valPubKeys) + whenNotPaused + { bytes32 pkHash = pubkeysHash(valPubKeys); - address minipool0 = getMinipoolFromPubkey(valPubKeys[0]); - address nodeAddress0 = getNodeAddressFromMinipool(minipool0); - require(nodeAddress0 != address(0), NoMinipoolForKey(valPubKeys[0])); + address nodeAddress0 = getNodeAddressFromPubkey(valPubKeys[0]); // 2) Pull nonce & check deadline uint256 nonce = nonces[nodeAddress0]; require(block.timestamp <= deadline, ExpiredSignature()); - - bytes32 digest = _hashTypedDataV4(keccak256(abi.encode( - _REGISTER_TYPEHASH, - pkHash, - msg.sender, - nonce, - deadline - ))); + + bytes32 digest = + _hashTypedDataV4(keccak256(abi.encode(_REGISTER_TYPEHASH, pkHash, msg.sender, nonce, deadline))); // 6) Verify signature came from the node require(SignatureChecker.isValidSignatureNow(nodeAddress0, digest, signature), InvalidSignature()); - unchecked { nonces[nodeAddress0] = nonce + 1; } + unchecked { + nonces[nodeAddress0] = nonce + 1; + } _registerValidator(valPubKeys[0]); emit ValidatorRegistered(valPubKeys[0], nodeAddress0); - + uint256 len = valPubKeys.length; for (uint256 i = 1; i < len; ++i) { //still need to check that the node address is the same for all validators in the batch @@ -121,7 +138,11 @@ contract RocketMinipoolRegistry is IRocketMinipoolRegistry, RocketMinipoolRegist } /// @notice Allows Minipool's withdrawal address to request deregistration for validators. - function requestValidatorDeregistration(bytes[] calldata valPubKeys) external onlyValidBLSPubKeys(valPubKeys) whenNotPaused { + function requestValidatorDeregistration(bytes[] calldata valPubKeys) + external + onlyValidBLSPubKeys(valPubKeys) + whenNotPaused + { address nodeAddress0 = _getFirstNodeAddrAndValidate(valPubKeys[0]); _requestValidatorDeregistration(valPubKeys[0]); emit ValidatorDeregistrationRequested(valPubKeys[0], nodeAddress0); @@ -134,25 +155,24 @@ contract RocketMinipoolRegistry is IRocketMinipoolRegistry, RocketMinipoolRegist } } - function requestValidatorDeregistrationWithSig(bytes[] calldata valPubKeys, bytes calldata signature, uint256 deadline) external onlyValidBLSPubKeys(valPubKeys) whenNotPaused { + function requestValidatorDeregistrationWithSig( + bytes[] calldata valPubKeys, + bytes calldata signature, + uint256 deadline + ) external onlyValidBLSPubKeys(valPubKeys) whenNotPaused { bytes32 pkHash = pubkeysHash(valPubKeys); - address minipool0 = getMinipoolFromPubkey(valPubKeys[0]); - address nodeAddress0 = getNodeAddressFromMinipool(minipool0); - require(nodeAddress0 != address(0), NoMinipoolForKey(valPubKeys[0])); + address nodeAddress0 = getNodeAddressFromPubkey(valPubKeys[0]); uint256 nonce = nonces[nodeAddress0]; require(block.timestamp <= deadline, ExpiredSignature()); - bytes32 digest = _hashTypedDataV4(keccak256(abi.encode( - _DEREG_REQ_TYPEHASH, - pkHash, - msg.sender, - nonce, - deadline - ))); + bytes32 digest = + _hashTypedDataV4(keccak256(abi.encode(_DEREG_REQ_TYPEHASH, pkHash, msg.sender, nonce, deadline))); require(SignatureChecker.isValidSignatureNow(nodeAddress0, digest, signature), InvalidSignature()); - unchecked { nonces[nodeAddress0] = nonce + 1; } + unchecked { + nonces[nodeAddress0] = nonce + 1; + } _requestValidatorDeregistration(valPubKeys[0]); emit ValidatorDeregistrationRequested(valPubKeys[0], nodeAddress0); @@ -179,25 +199,23 @@ contract RocketMinipoolRegistry is IRocketMinipoolRegistry, RocketMinipoolRegist } } - function deregisterValidatorsWithSig(bytes[] calldata valPubKeys, bytes calldata signature, uint256 deadline) external onlyValidBLSPubKeys(valPubKeys) whenNotPaused { + function deregisterValidatorsWithSig(bytes[] calldata valPubKeys, bytes calldata signature, uint256 deadline) + external + onlyValidBLSPubKeys(valPubKeys) + whenNotPaused + { bytes32 pkHash = pubkeysHash(valPubKeys); - address minipool0 = getMinipoolFromPubkey(valPubKeys[0]); - address nodeAddress0 = getNodeAddressFromMinipool(minipool0); - require(nodeAddress0 != address(0), NoMinipoolForKey(valPubKeys[0])); + address nodeAddress0 = getNodeAddressFromPubkey(valPubKeys[0]); uint256 nonce = nonces[nodeAddress0]; require(block.timestamp <= deadline, ExpiredSignature()); - - bytes32 digest = _hashTypedDataV4(keccak256(abi.encode( - _DEREG_TYPEHASH, - pkHash, - msg.sender, - nonce, - deadline - ))); + + bytes32 digest = _hashTypedDataV4(keccak256(abi.encode(_DEREG_TYPEHASH, pkHash, msg.sender, nonce, deadline))); require(SignatureChecker.isValidSignatureNow(nodeAddress0, digest, signature), InvalidSignature()); - unchecked { nonces[nodeAddress0] = nonce + 1; } + unchecked { + nonces[nodeAddress0] = nonce + 1; + } _deregisterValidator(valPubKeys[0]); emit ValidatorDeregistered(valPubKeys[0], nodeAddress0); @@ -210,9 +228,8 @@ contract RocketMinipoolRegistry is IRocketMinipoolRegistry, RocketMinipoolRegist } } - /// @dev Allows the freeze oracle account to freeze validators which disobey the mev-commit protocol. - function freeze(bytes[] calldata valPubKeys) external whenNotPaused onlyFreezeOracle() { + function freeze(bytes[] calldata valPubKeys) external whenNotPaused onlyFreezeOracle { uint256 len = valPubKeys.length; for (uint256 i = 0; i < len; ++i) { _freeze(valPubKeys[i]); @@ -227,11 +244,11 @@ contract RocketMinipoolRegistry is IRocketMinipoolRegistry, RocketMinipoolRegist for (uint256 i = 0; i < len; ++i) { _unfreeze(valPubKeys[i]); } - (bool success, ) = unfreezeReceiver.call{value: requiredFee}(""); + (bool success,) = unfreezeReceiver.call{value: requiredFee}(""); require(success, UnfreezeTransferFailed()); uint256 excessFee = msg.value - requiredFee; if (excessFee != 0) { - (bool successRefund, ) = msg.sender.call{value: excessFee}(""); + (bool successRefund,) = msg.sender.call{value: excessFee}(""); require(successRefund, RefundFailed()); } } @@ -241,7 +258,7 @@ contract RocketMinipoolRegistry is IRocketMinipoolRegistry, RocketMinipoolRegist _pause(); } - /// @dev Unpauses the contract, restricted to contract owner. + /// @dev Unpauses the contract, restricted to contract owner. function unpause() external onlyOwner { _unpause(); } @@ -251,7 +268,7 @@ contract RocketMinipoolRegistry is IRocketMinipoolRegistry, RocketMinipoolRegist _setDeregistrationPeriod(newDeregistrationPeriod); } - /// @dev Sets the rocket storage, restricted to contract owner. + /// @dev Sets the rocket storage, restricted to contract owner. function setRocketStorage(address newRocketStorage) external onlyOwner { _setRocketStorage(newRocketStorage); } @@ -302,7 +319,7 @@ contract RocketMinipoolRegistry is IRocketMinipoolRegistry, RocketMinipoolRegist return _isValidatorOptedIn(valPubKey); } - /// @dev Returns the minipool for a validator. + /// @dev Returns the minipool for a validator. function getMinipoolFromPubkey(bytes calldata validatorPubkey) public view returns (address) { return rocketStorage.getAddress(keccak256(abi.encodePacked("validator.minipool", validatorPubkey))); } @@ -319,7 +336,7 @@ contract RocketMinipoolRegistry is IRocketMinipoolRegistry, RocketMinipoolRegist } /// @dev Fetches the minipool from a validator's pubkey and returns true if caller is either the minipool's node address or node's withdrawal address. - function isOperatorValidForKey(address operator, bytes calldata validatorPubkey) public view returns (bool) { + function isOperatorValidForKey(address operator, bytes calldata validatorPubkey) external view returns (bool) { address minipool = getMinipoolFromPubkey(validatorPubkey); address nodeAddress = getNodeAddressFromMinipool(minipool); return (nodeAddress == operator || rocketStorage.getNodeWithdrawalAddress(nodeAddress) == operator); @@ -331,7 +348,9 @@ contract RocketMinipoolRegistry is IRocketMinipoolRegistry, RocketMinipoolRegist /// @dev Returns the node address of a minipool. function getNodeAddressFromMinipool(address minipool) public view returns (address) { - return RocketMinipoolInterface(minipool).getNodeAddress(); + address nodeAddress = RocketMinipoolInterface(minipool).getNodeAddress(); + require(nodeAddress != address(0), NoMinipoolForKey()); + return nodeAddress; } function pubkeysHash(bytes[] calldata pubkeys) public pure returns (bytes32) { @@ -342,7 +361,9 @@ contract RocketMinipoolRegistry is IRocketMinipoolRegistry, RocketMinipoolRegist // Pointer to the next write position in `concatenated` uint256 writePtr; - assembly { writePtr := add(concatenated, 32) } + assembly { + writePtr := add(concatenated, 32) + } for (uint256 i = 0; i < count; ++i) { bytes calldata pubkey = pubkeys[i]; @@ -368,7 +389,7 @@ contract RocketMinipoolRegistry is IRocketMinipoolRegistry, RocketMinipoolRegist require(reg.deregTimestamp == 0, DeregRequestAlreadyExists(valPubKey)); reg.deregTimestamp = uint64(block.timestamp); } - + function _deregisterValidator(bytes calldata valPubKey) internal { IRocketMinipoolRegistry.ValidatorRegistration storage reg = validatorRegistrations[valPubKey]; require(reg.freezeTimestamp == 0, FrozenValidatorCannotDeregister(valPubKey)); @@ -386,7 +407,7 @@ contract RocketMinipoolRegistry is IRocketMinipoolRegistry, RocketMinipoolRegist function _unfreeze(bytes calldata valPubKey) internal { IRocketMinipoolRegistry.ValidatorRegistration storage regInfo = validatorRegistrations[valPubKey]; - require(regInfo.freezeTimestamp != 0, ValidatorNotFrozen(valPubKey)); + require(regInfo.freezeTimestamp != 0, ValidatorNotFrozen(valPubKey)); regInfo.freezeTimestamp = 0; emit ValidatorUnfrozen(valPubKey); } @@ -415,15 +436,16 @@ contract RocketMinipoolRegistry is IRocketMinipoolRegistry, RocketMinipoolRegist deregistrationPeriod = newDeregistrationPeriod; } - /// @dev Authorizes contract upgrades, restricted to contract owner. // solhint-disable-next-line no-empty-blocks function _authorizeUpgrade(address) internal override onlyOwner {} - - function _getFirstNodeAddrAndValidate(bytes calldata firstPubKey) internal view returns (address firstNodeAddress) { + function _getFirstNodeAddrAndValidate(bytes calldata firstPubKey) + internal + view + returns (address firstNodeAddress) + { firstNodeAddress = getNodeAddressFromMinipool(getMinipoolFromPubkey(firstPubKey)); - require(firstNodeAddress != address(0), NoMinipoolForKey(firstPubKey)); address withdrawalAddress = rocketStorage.getNodeWithdrawalAddress(firstNodeAddress); require(withdrawalAddress == msg.sender, NotWithdrawalAddress(withdrawalAddress)); } @@ -438,8 +460,12 @@ contract RocketMinipoolRegistry is IRocketMinipoolRegistry, RocketMinipoolRegist return true; } - /// @dev Deterministically hash a list of pubkeys to avoid large EIP-712 arrays. - function _hashPubkeysAndFunction(bytes memory functionSelector, bytes[] calldata pubkeys) internal pure returns (bytes32) { + /// @dev Deterministically hash a list of pubkeys to avoid large EIP-712 arrays. + function _hashPubkeysAndFunction(bytes memory functionSelector, bytes[] calldata pubkeys) + internal + pure + returns (bytes32) + { bytes32[] memory leaves = new bytes32[](pubkeys.length); uint256 len = pubkeys.length; for (uint256 i = 0; i < len; ++i) { @@ -447,5 +473,4 @@ contract RocketMinipoolRegistry is IRocketMinipoolRegistry, RocketMinipoolRegist } return keccak256(abi.encodePacked(functionSelector, leaves)); } - -} \ No newline at end of file +} From 2a25ff81ec75d08f3d83d0026e31590ab27f352b Mon Sep 17 00:00:00 2001 From: owen-eth Date: Tue, 11 Nov 2025 16:38:55 -0500 Subject: [PATCH 3/4] removed unused function --- contracts-abi/abi/RocketMinipoolRegistry.abi | 24 ---- .../RocketMinipoolRegistry.go | 33 +---- .../interfaces/IRocketMinipoolRegistry.sol | 2 - .../rocketpool/RocketMinipoolRegistry.sol | 7 - .../rocketpool/ForkRegisterWithSig.sol | 126 ++++++++++++++++++ .../rocketpool/RocketMinipoolRegistryTest.sol | 5 - 6 files changed, 127 insertions(+), 70 deletions(-) create mode 100644 contracts/test/validator-registry/rocketpool/ForkRegisterWithSig.sol diff --git a/contracts-abi/abi/RocketMinipoolRegistry.abi b/contracts-abi/abi/RocketMinipoolRegistry.abi index 1937f53bc..5dbc3f96d 100644 --- a/contracts-abi/abi/RocketMinipoolRegistry.abi +++ b/contracts-abi/abi/RocketMinipoolRegistry.abi @@ -343,30 +343,6 @@ ], "stateMutability": "view" }, - { - "type": "function", - "name": "isOperatorValidForKey", - "inputs": [ - { - "name": "operator", - "type": "address", - "internalType": "address" - }, - { - "name": "validatorPubkey", - "type": "bytes", - "internalType": "bytes" - } - ], - "outputs": [ - { - "name": "", - "type": "bool", - "internalType": "bool" - } - ], - "stateMutability": "view" - }, { "type": "function", "name": "isValidatorOptedIn", diff --git a/contracts-abi/clients/RocketMinipoolRegistry/RocketMinipoolRegistry.go b/contracts-abi/clients/RocketMinipoolRegistry/RocketMinipoolRegistry.go index 87f715b3a..7dc2364ba 100644 --- a/contracts-abi/clients/RocketMinipoolRegistry/RocketMinipoolRegistry.go +++ b/contracts-abi/clients/RocketMinipoolRegistry/RocketMinipoolRegistry.go @@ -38,7 +38,7 @@ type IRocketMinipoolRegistryValidatorRegistration struct { // RocketminipoolregistryMetaData contains all meta data concerning the Rocketminipoolregistry contract. var RocketminipoolregistryMetaData = &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\":\"acceptOwnership\",\"inputs\":[],\"outputs\":[],\"stateMutability\":\"nonpayable\"},{\"type\":\"function\",\"name\":\"deregisterValidators\",\"inputs\":[{\"name\":\"valPubKeys\",\"type\":\"bytes[]\",\"internalType\":\"bytes[]\"}],\"outputs\":[],\"stateMutability\":\"nonpayable\"},{\"type\":\"function\",\"name\":\"deregisterValidatorsWithSig\",\"inputs\":[{\"name\":\"valPubKeys\",\"type\":\"bytes[]\",\"internalType\":\"bytes[]\"},{\"name\":\"signature\",\"type\":\"bytes\",\"internalType\":\"bytes\"},{\"name\":\"deadline\",\"type\":\"uint256\",\"internalType\":\"uint256\"}],\"outputs\":[],\"stateMutability\":\"nonpayable\"},{\"type\":\"function\",\"name\":\"deregistrationPeriod\",\"inputs\":[],\"outputs\":[{\"name\":\"\",\"type\":\"uint64\",\"internalType\":\"uint64\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"eip712Domain\",\"inputs\":[],\"outputs\":[{\"name\":\"fields\",\"type\":\"bytes1\",\"internalType\":\"bytes1\"},{\"name\":\"name\",\"type\":\"string\",\"internalType\":\"string\"},{\"name\":\"version\",\"type\":\"string\",\"internalType\":\"string\"},{\"name\":\"chainId\",\"type\":\"uint256\",\"internalType\":\"uint256\"},{\"name\":\"verifyingContract\",\"type\":\"address\",\"internalType\":\"address\"},{\"name\":\"salt\",\"type\":\"bytes32\",\"internalType\":\"bytes32\"},{\"name\":\"extensions\",\"type\":\"uint256[]\",\"internalType\":\"uint256[]\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"freeze\",\"inputs\":[{\"name\":\"valPubKeys\",\"type\":\"bytes[]\",\"internalType\":\"bytes[]\"}],\"outputs\":[],\"stateMutability\":\"nonpayable\"},{\"type\":\"function\",\"name\":\"freezeOracle\",\"inputs\":[],\"outputs\":[{\"name\":\"\",\"type\":\"address\",\"internalType\":\"address\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"getEligibleTimeForDeregistration\",\"inputs\":[{\"name\":\"validatorPubkey\",\"type\":\"bytes\",\"internalType\":\"bytes\"}],\"outputs\":[{\"name\":\"\",\"type\":\"uint64\",\"internalType\":\"uint64\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"getMinipoolFromPubkey\",\"inputs\":[{\"name\":\"validatorPubkey\",\"type\":\"bytes\",\"internalType\":\"bytes\"}],\"outputs\":[{\"name\":\"\",\"type\":\"address\",\"internalType\":\"address\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"getNodeAddressFromMinipool\",\"inputs\":[{\"name\":\"minipool\",\"type\":\"address\",\"internalType\":\"address\"}],\"outputs\":[{\"name\":\"\",\"type\":\"address\",\"internalType\":\"address\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"getNodeAddressFromPubkey\",\"inputs\":[{\"name\":\"validatorPubkey\",\"type\":\"bytes\",\"internalType\":\"bytes\"}],\"outputs\":[{\"name\":\"\",\"type\":\"address\",\"internalType\":\"address\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"getValidOperatorsForKey\",\"inputs\":[{\"name\":\"validatorPubkey\",\"type\":\"bytes\",\"internalType\":\"bytes\"}],\"outputs\":[{\"name\":\"\",\"type\":\"address\",\"internalType\":\"address\"},{\"name\":\"\",\"type\":\"address\",\"internalType\":\"address\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"getValidatorRegInfo\",\"inputs\":[{\"name\":\"valPubKey\",\"type\":\"bytes\",\"internalType\":\"bytes\"}],\"outputs\":[{\"name\":\"\",\"type\":\"tuple\",\"internalType\":\"structIRocketMinipoolRegistry.ValidatorRegistration\",\"components\":[{\"name\":\"exists\",\"type\":\"bool\",\"internalType\":\"bool\"},{\"name\":\"deregTimestamp\",\"type\":\"uint64\",\"internalType\":\"uint64\"},{\"name\":\"freezeTimestamp\",\"type\":\"uint64\",\"internalType\":\"uint64\"}]}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"initialize\",\"inputs\":[{\"name\":\"owner\",\"type\":\"address\",\"internalType\":\"address\"},{\"name\":\"freezeOracle\",\"type\":\"address\",\"internalType\":\"address\"},{\"name\":\"unfreezeReceiver\",\"type\":\"address\",\"internalType\":\"address\"},{\"name\":\"rocketStorage\",\"type\":\"address\",\"internalType\":\"address\"},{\"name\":\"unfreezeFee\",\"type\":\"uint256\",\"internalType\":\"uint256\"},{\"name\":\"deregistrationPeriod\",\"type\":\"uint64\",\"internalType\":\"uint64\"}],\"outputs\":[],\"stateMutability\":\"nonpayable\"},{\"type\":\"function\",\"name\":\"isMinipoolActive\",\"inputs\":[{\"name\":\"minipool\",\"type\":\"address\",\"internalType\":\"address\"}],\"outputs\":[{\"name\":\"\",\"type\":\"bool\",\"internalType\":\"bool\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"isOperatorValidForKey\",\"inputs\":[{\"name\":\"operator\",\"type\":\"address\",\"internalType\":\"address\"},{\"name\":\"validatorPubkey\",\"type\":\"bytes\",\"internalType\":\"bytes\"}],\"outputs\":[{\"name\":\"\",\"type\":\"bool\",\"internalType\":\"bool\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"isValidatorOptedIn\",\"inputs\":[{\"name\":\"valPubKey\",\"type\":\"bytes\",\"internalType\":\"bytes\"}],\"outputs\":[{\"name\":\"\",\"type\":\"bool\",\"internalType\":\"bool\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"isValidatorRegistered\",\"inputs\":[{\"name\":\"validatorPubkey\",\"type\":\"bytes\",\"internalType\":\"bytes\"}],\"outputs\":[{\"name\":\"\",\"type\":\"bool\",\"internalType\":\"bool\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"nonces\",\"inputs\":[{\"name\":\"\",\"type\":\"address\",\"internalType\":\"address\"}],\"outputs\":[{\"name\":\"\",\"type\":\"uint256\",\"internalType\":\"uint256\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"owner\",\"inputs\":[],\"outputs\":[{\"name\":\"\",\"type\":\"address\",\"internalType\":\"address\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"ownerUnfreeze\",\"inputs\":[{\"name\":\"valPubKeys\",\"type\":\"bytes[]\",\"internalType\":\"bytes[]\"}],\"outputs\":[],\"stateMutability\":\"nonpayable\"},{\"type\":\"function\",\"name\":\"pause\",\"inputs\":[],\"outputs\":[],\"stateMutability\":\"nonpayable\"},{\"type\":\"function\",\"name\":\"paused\",\"inputs\":[],\"outputs\":[{\"name\":\"\",\"type\":\"bool\",\"internalType\":\"bool\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"pendingOwner\",\"inputs\":[],\"outputs\":[{\"name\":\"\",\"type\":\"address\",\"internalType\":\"address\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"proxiableUUID\",\"inputs\":[],\"outputs\":[{\"name\":\"\",\"type\":\"bytes32\",\"internalType\":\"bytes32\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"pubkeysHash\",\"inputs\":[{\"name\":\"pubkeys\",\"type\":\"bytes[]\",\"internalType\":\"bytes[]\"}],\"outputs\":[{\"name\":\"\",\"type\":\"bytes32\",\"internalType\":\"bytes32\"}],\"stateMutability\":\"pure\"},{\"type\":\"function\",\"name\":\"registerValidators\",\"inputs\":[{\"name\":\"valPubKeys\",\"type\":\"bytes[]\",\"internalType\":\"bytes[]\"}],\"outputs\":[],\"stateMutability\":\"nonpayable\"},{\"type\":\"function\",\"name\":\"registerValidatorsWithSig\",\"inputs\":[{\"name\":\"valPubKeys\",\"type\":\"bytes[]\",\"internalType\":\"bytes[]\"},{\"name\":\"signature\",\"type\":\"bytes\",\"internalType\":\"bytes\"},{\"name\":\"deadline\",\"type\":\"uint256\",\"internalType\":\"uint256\"}],\"outputs\":[],\"stateMutability\":\"nonpayable\"},{\"type\":\"function\",\"name\":\"renounceOwnership\",\"inputs\":[],\"outputs\":[],\"stateMutability\":\"nonpayable\"},{\"type\":\"function\",\"name\":\"requestValidatorDeregistration\",\"inputs\":[{\"name\":\"valPubKeys\",\"type\":\"bytes[]\",\"internalType\":\"bytes[]\"}],\"outputs\":[],\"stateMutability\":\"nonpayable\"},{\"type\":\"function\",\"name\":\"requestValidatorDeregistrationWithSig\",\"inputs\":[{\"name\":\"valPubKeys\",\"type\":\"bytes[]\",\"internalType\":\"bytes[]\"},{\"name\":\"signature\",\"type\":\"bytes\",\"internalType\":\"bytes\"},{\"name\":\"deadline\",\"type\":\"uint256\",\"internalType\":\"uint256\"}],\"outputs\":[],\"stateMutability\":\"nonpayable\"},{\"type\":\"function\",\"name\":\"rocketStorage\",\"inputs\":[],\"outputs\":[{\"name\":\"\",\"type\":\"address\",\"internalType\":\"contractRocketStorageInterface\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"setDeregistrationPeriod\",\"inputs\":[{\"name\":\"newDeregistrationPeriod\",\"type\":\"uint64\",\"internalType\":\"uint64\"}],\"outputs\":[],\"stateMutability\":\"nonpayable\"},{\"type\":\"function\",\"name\":\"setFreezeOracle\",\"inputs\":[{\"name\":\"newFreezeOracle\",\"type\":\"address\",\"internalType\":\"address\"}],\"outputs\":[],\"stateMutability\":\"nonpayable\"},{\"type\":\"function\",\"name\":\"setRocketStorage\",\"inputs\":[{\"name\":\"newRocketStorage\",\"type\":\"address\",\"internalType\":\"address\"}],\"outputs\":[],\"stateMutability\":\"nonpayable\"},{\"type\":\"function\",\"name\":\"setUnfreezeFee\",\"inputs\":[{\"name\":\"newUnfreezeFee\",\"type\":\"uint256\",\"internalType\":\"uint256\"}],\"outputs\":[],\"stateMutability\":\"nonpayable\"},{\"type\":\"function\",\"name\":\"setUnfreezeReceiver\",\"inputs\":[{\"name\":\"newUnfreezeReceiver\",\"type\":\"address\",\"internalType\":\"address\"}],\"outputs\":[],\"stateMutability\":\"nonpayable\"},{\"type\":\"function\",\"name\":\"transferOwnership\",\"inputs\":[{\"name\":\"newOwner\",\"type\":\"address\",\"internalType\":\"address\"}],\"outputs\":[],\"stateMutability\":\"nonpayable\"},{\"type\":\"function\",\"name\":\"unfreeze\",\"inputs\":[{\"name\":\"valPubKeys\",\"type\":\"bytes[]\",\"internalType\":\"bytes[]\"}],\"outputs\":[],\"stateMutability\":\"payable\"},{\"type\":\"function\",\"name\":\"unfreezeFee\",\"inputs\":[],\"outputs\":[{\"name\":\"\",\"type\":\"uint256\",\"internalType\":\"uint256\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"unfreezeReceiver\",\"inputs\":[],\"outputs\":[{\"name\":\"\",\"type\":\"address\",\"internalType\":\"address\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"unpause\",\"inputs\":[],\"outputs\":[],\"stateMutability\":\"nonpayable\"},{\"type\":\"function\",\"name\":\"upgradeToAndCall\",\"inputs\":[{\"name\":\"newImplementation\",\"type\":\"address\",\"internalType\":\"address\"},{\"name\":\"data\",\"type\":\"bytes\",\"internalType\":\"bytes\"}],\"outputs\":[],\"stateMutability\":\"payable\"},{\"type\":\"function\",\"name\":\"validatorRegistrations\",\"inputs\":[{\"name\":\"\",\"type\":\"bytes\",\"internalType\":\"bytes\"}],\"outputs\":[{\"name\":\"exists\",\"type\":\"bool\",\"internalType\":\"bool\"},{\"name\":\"deregTimestamp\",\"type\":\"uint64\",\"internalType\":\"uint64\"},{\"name\":\"freezeTimestamp\",\"type\":\"uint64\",\"internalType\":\"uint64\"}],\"stateMutability\":\"view\"},{\"type\":\"event\",\"name\":\"EIP712DomainChanged\",\"inputs\":[],\"anonymous\":false},{\"type\":\"event\",\"name\":\"Initialized\",\"inputs\":[{\"name\":\"version\",\"type\":\"uint64\",\"indexed\":false,\"internalType\":\"uint64\"}],\"anonymous\":false},{\"type\":\"event\",\"name\":\"OwnershipTransferStarted\",\"inputs\":[{\"name\":\"previousOwner\",\"type\":\"address\",\"indexed\":true,\"internalType\":\"address\"},{\"name\":\"newOwner\",\"type\":\"address\",\"indexed\":true,\"internalType\":\"address\"}],\"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\":\"Unpaused\",\"inputs\":[{\"name\":\"account\",\"type\":\"address\",\"indexed\":false,\"internalType\":\"address\"}],\"anonymous\":false},{\"type\":\"event\",\"name\":\"Upgraded\",\"inputs\":[{\"name\":\"implementation\",\"type\":\"address\",\"indexed\":true,\"internalType\":\"address\"}],\"anonymous\":false},{\"type\":\"event\",\"name\":\"ValidatorDeregistered\",\"inputs\":[{\"name\":\"validatorPubKey\",\"type\":\"bytes\",\"indexed\":true,\"internalType\":\"bytes\"},{\"name\":\"nodeAddress\",\"type\":\"address\",\"indexed\":true,\"internalType\":\"address\"}],\"anonymous\":false},{\"type\":\"event\",\"name\":\"ValidatorDeregistrationRequested\",\"inputs\":[{\"name\":\"validatorPubKey\",\"type\":\"bytes\",\"indexed\":true,\"internalType\":\"bytes\"},{\"name\":\"nodeAddress\",\"type\":\"address\",\"indexed\":true,\"internalType\":\"address\"}],\"anonymous\":false},{\"type\":\"event\",\"name\":\"ValidatorFrozen\",\"inputs\":[{\"name\":\"validatorPubKey\",\"type\":\"bytes\",\"indexed\":true,\"internalType\":\"bytes\"}],\"anonymous\":false},{\"type\":\"event\",\"name\":\"ValidatorRegistered\",\"inputs\":[{\"name\":\"validatorPubKey\",\"type\":\"bytes\",\"indexed\":true,\"internalType\":\"bytes\"},{\"name\":\"nodeAddress\",\"type\":\"address\",\"indexed\":true,\"internalType\":\"address\"}],\"anonymous\":false},{\"type\":\"event\",\"name\":\"ValidatorUnfrozen\",\"inputs\":[{\"name\":\"validatorPubKey\",\"type\":\"bytes\",\"indexed\":true,\"internalType\":\"bytes\"}],\"anonymous\":false},{\"type\":\"error\",\"name\":\"AddressEmptyCode\",\"inputs\":[{\"name\":\"target\",\"type\":\"address\",\"internalType\":\"address\"}]},{\"type\":\"error\",\"name\":\"DeregRequestAlreadyExists\",\"inputs\":[{\"name\":\"validatorPubkey\",\"type\":\"bytes\",\"internalType\":\"bytes\"}]},{\"type\":\"error\",\"name\":\"DeregRequestDoesNotExist\",\"inputs\":[{\"name\":\"validatorPubkey\",\"type\":\"bytes\",\"internalType\":\"bytes\"}]},{\"type\":\"error\",\"name\":\"DeregistrationTooSoon\",\"inputs\":[{\"name\":\"validatorPubkey\",\"type\":\"bytes\",\"internalType\":\"bytes\"}]},{\"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\":\"ExpiredSignature\",\"inputs\":[]},{\"type\":\"error\",\"name\":\"FailedInnerCall\",\"inputs\":[]},{\"type\":\"error\",\"name\":\"FrozenValidatorCannotDeregister\",\"inputs\":[{\"name\":\"validatorPubkey\",\"type\":\"bytes\",\"internalType\":\"bytes\"}]},{\"type\":\"error\",\"name\":\"InvalidBLSPubKeyLength\",\"inputs\":[{\"name\":\"expectedLength\",\"type\":\"uint256\",\"internalType\":\"uint256\"},{\"name\":\"actualLength\",\"type\":\"uint256\",\"internalType\":\"uint256\"}]},{\"type\":\"error\",\"name\":\"InvalidFallback\",\"inputs\":[]},{\"type\":\"error\",\"name\":\"InvalidInitialization\",\"inputs\":[]},{\"type\":\"error\",\"name\":\"InvalidReceive\",\"inputs\":[]},{\"type\":\"error\",\"name\":\"InvalidSignature\",\"inputs\":[]},{\"type\":\"error\",\"name\":\"MinipoolNotActive\",\"inputs\":[{\"name\":\"validatorPubkey\",\"type\":\"bytes\",\"internalType\":\"bytes\"}]},{\"type\":\"error\",\"name\":\"MixedNodeBatch\",\"inputs\":[{\"name\":\"validatorPubkey\",\"type\":\"bytes\",\"internalType\":\"bytes\"}]},{\"type\":\"error\",\"name\":\"NoKeysProvided\",\"inputs\":[]},{\"type\":\"error\",\"name\":\"NoMinipoolForKey\",\"inputs\":[]},{\"type\":\"error\",\"name\":\"NotInitializing\",\"inputs\":[]},{\"type\":\"error\",\"name\":\"NotMinipoolOperator\",\"inputs\":[{\"name\":\"validatorPubkey\",\"type\":\"bytes\",\"internalType\":\"bytes\"}]},{\"type\":\"error\",\"name\":\"NotWithdrawalAddress\",\"inputs\":[{\"name\":\"withdrawalAddress\",\"type\":\"address\",\"internalType\":\"address\"}]},{\"type\":\"error\",\"name\":\"OnlyFreezeOracle\",\"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\":\"ReentrancyGuardReentrantCall\",\"inputs\":[]},{\"type\":\"error\",\"name\":\"RefundFailed\",\"inputs\":[]},{\"type\":\"error\",\"name\":\"UUPSUnauthorizedCallContext\",\"inputs\":[]},{\"type\":\"error\",\"name\":\"UUPSUnsupportedProxiableUUID\",\"inputs\":[{\"name\":\"slot\",\"type\":\"bytes32\",\"internalType\":\"bytes32\"}]},{\"type\":\"error\",\"name\":\"UnfreezeFeeRequired\",\"inputs\":[{\"name\":\"requiredFee\",\"type\":\"uint256\",\"internalType\":\"uint256\"}]},{\"type\":\"error\",\"name\":\"UnfreezeTransferFailed\",\"inputs\":[]},{\"type\":\"error\",\"name\":\"ValidatorAlreadyFrozen\",\"inputs\":[{\"name\":\"validatorPubkey\",\"type\":\"bytes\",\"internalType\":\"bytes\"}]},{\"type\":\"error\",\"name\":\"ValidatorAlreadyRegistered\",\"inputs\":[{\"name\":\"validatorPubkey\",\"type\":\"bytes\",\"internalType\":\"bytes\"}]},{\"type\":\"error\",\"name\":\"ValidatorDeregistrationNotExpired\",\"inputs\":[{\"name\":\"validatorPubkey\",\"type\":\"bytes\",\"internalType\":\"bytes\"}]},{\"type\":\"error\",\"name\":\"ValidatorNotFrozen\",\"inputs\":[{\"name\":\"validatorPubkey\",\"type\":\"bytes\",\"internalType\":\"bytes\"}]},{\"type\":\"error\",\"name\":\"ValidatorNotRegistered\",\"inputs\":[{\"name\":\"validatorPubkey\",\"type\":\"bytes\",\"internalType\":\"bytes\"}]},{\"type\":\"error\",\"name\":\"ZeroParam\",\"inputs\":[]}]", + 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\":\"acceptOwnership\",\"inputs\":[],\"outputs\":[],\"stateMutability\":\"nonpayable\"},{\"type\":\"function\",\"name\":\"deregisterValidators\",\"inputs\":[{\"name\":\"valPubKeys\",\"type\":\"bytes[]\",\"internalType\":\"bytes[]\"}],\"outputs\":[],\"stateMutability\":\"nonpayable\"},{\"type\":\"function\",\"name\":\"deregisterValidatorsWithSig\",\"inputs\":[{\"name\":\"valPubKeys\",\"type\":\"bytes[]\",\"internalType\":\"bytes[]\"},{\"name\":\"signature\",\"type\":\"bytes\",\"internalType\":\"bytes\"},{\"name\":\"deadline\",\"type\":\"uint256\",\"internalType\":\"uint256\"}],\"outputs\":[],\"stateMutability\":\"nonpayable\"},{\"type\":\"function\",\"name\":\"deregistrationPeriod\",\"inputs\":[],\"outputs\":[{\"name\":\"\",\"type\":\"uint64\",\"internalType\":\"uint64\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"eip712Domain\",\"inputs\":[],\"outputs\":[{\"name\":\"fields\",\"type\":\"bytes1\",\"internalType\":\"bytes1\"},{\"name\":\"name\",\"type\":\"string\",\"internalType\":\"string\"},{\"name\":\"version\",\"type\":\"string\",\"internalType\":\"string\"},{\"name\":\"chainId\",\"type\":\"uint256\",\"internalType\":\"uint256\"},{\"name\":\"verifyingContract\",\"type\":\"address\",\"internalType\":\"address\"},{\"name\":\"salt\",\"type\":\"bytes32\",\"internalType\":\"bytes32\"},{\"name\":\"extensions\",\"type\":\"uint256[]\",\"internalType\":\"uint256[]\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"freeze\",\"inputs\":[{\"name\":\"valPubKeys\",\"type\":\"bytes[]\",\"internalType\":\"bytes[]\"}],\"outputs\":[],\"stateMutability\":\"nonpayable\"},{\"type\":\"function\",\"name\":\"freezeOracle\",\"inputs\":[],\"outputs\":[{\"name\":\"\",\"type\":\"address\",\"internalType\":\"address\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"getEligibleTimeForDeregistration\",\"inputs\":[{\"name\":\"validatorPubkey\",\"type\":\"bytes\",\"internalType\":\"bytes\"}],\"outputs\":[{\"name\":\"\",\"type\":\"uint64\",\"internalType\":\"uint64\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"getMinipoolFromPubkey\",\"inputs\":[{\"name\":\"validatorPubkey\",\"type\":\"bytes\",\"internalType\":\"bytes\"}],\"outputs\":[{\"name\":\"\",\"type\":\"address\",\"internalType\":\"address\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"getNodeAddressFromMinipool\",\"inputs\":[{\"name\":\"minipool\",\"type\":\"address\",\"internalType\":\"address\"}],\"outputs\":[{\"name\":\"\",\"type\":\"address\",\"internalType\":\"address\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"getNodeAddressFromPubkey\",\"inputs\":[{\"name\":\"validatorPubkey\",\"type\":\"bytes\",\"internalType\":\"bytes\"}],\"outputs\":[{\"name\":\"\",\"type\":\"address\",\"internalType\":\"address\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"getValidOperatorsForKey\",\"inputs\":[{\"name\":\"validatorPubkey\",\"type\":\"bytes\",\"internalType\":\"bytes\"}],\"outputs\":[{\"name\":\"\",\"type\":\"address\",\"internalType\":\"address\"},{\"name\":\"\",\"type\":\"address\",\"internalType\":\"address\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"getValidatorRegInfo\",\"inputs\":[{\"name\":\"valPubKey\",\"type\":\"bytes\",\"internalType\":\"bytes\"}],\"outputs\":[{\"name\":\"\",\"type\":\"tuple\",\"internalType\":\"structIRocketMinipoolRegistry.ValidatorRegistration\",\"components\":[{\"name\":\"exists\",\"type\":\"bool\",\"internalType\":\"bool\"},{\"name\":\"deregTimestamp\",\"type\":\"uint64\",\"internalType\":\"uint64\"},{\"name\":\"freezeTimestamp\",\"type\":\"uint64\",\"internalType\":\"uint64\"}]}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"initialize\",\"inputs\":[{\"name\":\"owner\",\"type\":\"address\",\"internalType\":\"address\"},{\"name\":\"freezeOracle\",\"type\":\"address\",\"internalType\":\"address\"},{\"name\":\"unfreezeReceiver\",\"type\":\"address\",\"internalType\":\"address\"},{\"name\":\"rocketStorage\",\"type\":\"address\",\"internalType\":\"address\"},{\"name\":\"unfreezeFee\",\"type\":\"uint256\",\"internalType\":\"uint256\"},{\"name\":\"deregistrationPeriod\",\"type\":\"uint64\",\"internalType\":\"uint64\"}],\"outputs\":[],\"stateMutability\":\"nonpayable\"},{\"type\":\"function\",\"name\":\"isMinipoolActive\",\"inputs\":[{\"name\":\"minipool\",\"type\":\"address\",\"internalType\":\"address\"}],\"outputs\":[{\"name\":\"\",\"type\":\"bool\",\"internalType\":\"bool\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"isValidatorOptedIn\",\"inputs\":[{\"name\":\"valPubKey\",\"type\":\"bytes\",\"internalType\":\"bytes\"}],\"outputs\":[{\"name\":\"\",\"type\":\"bool\",\"internalType\":\"bool\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"isValidatorRegistered\",\"inputs\":[{\"name\":\"validatorPubkey\",\"type\":\"bytes\",\"internalType\":\"bytes\"}],\"outputs\":[{\"name\":\"\",\"type\":\"bool\",\"internalType\":\"bool\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"nonces\",\"inputs\":[{\"name\":\"\",\"type\":\"address\",\"internalType\":\"address\"}],\"outputs\":[{\"name\":\"\",\"type\":\"uint256\",\"internalType\":\"uint256\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"owner\",\"inputs\":[],\"outputs\":[{\"name\":\"\",\"type\":\"address\",\"internalType\":\"address\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"ownerUnfreeze\",\"inputs\":[{\"name\":\"valPubKeys\",\"type\":\"bytes[]\",\"internalType\":\"bytes[]\"}],\"outputs\":[],\"stateMutability\":\"nonpayable\"},{\"type\":\"function\",\"name\":\"pause\",\"inputs\":[],\"outputs\":[],\"stateMutability\":\"nonpayable\"},{\"type\":\"function\",\"name\":\"paused\",\"inputs\":[],\"outputs\":[{\"name\":\"\",\"type\":\"bool\",\"internalType\":\"bool\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"pendingOwner\",\"inputs\":[],\"outputs\":[{\"name\":\"\",\"type\":\"address\",\"internalType\":\"address\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"proxiableUUID\",\"inputs\":[],\"outputs\":[{\"name\":\"\",\"type\":\"bytes32\",\"internalType\":\"bytes32\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"pubkeysHash\",\"inputs\":[{\"name\":\"pubkeys\",\"type\":\"bytes[]\",\"internalType\":\"bytes[]\"}],\"outputs\":[{\"name\":\"\",\"type\":\"bytes32\",\"internalType\":\"bytes32\"}],\"stateMutability\":\"pure\"},{\"type\":\"function\",\"name\":\"registerValidators\",\"inputs\":[{\"name\":\"valPubKeys\",\"type\":\"bytes[]\",\"internalType\":\"bytes[]\"}],\"outputs\":[],\"stateMutability\":\"nonpayable\"},{\"type\":\"function\",\"name\":\"registerValidatorsWithSig\",\"inputs\":[{\"name\":\"valPubKeys\",\"type\":\"bytes[]\",\"internalType\":\"bytes[]\"},{\"name\":\"signature\",\"type\":\"bytes\",\"internalType\":\"bytes\"},{\"name\":\"deadline\",\"type\":\"uint256\",\"internalType\":\"uint256\"}],\"outputs\":[],\"stateMutability\":\"nonpayable\"},{\"type\":\"function\",\"name\":\"renounceOwnership\",\"inputs\":[],\"outputs\":[],\"stateMutability\":\"nonpayable\"},{\"type\":\"function\",\"name\":\"requestValidatorDeregistration\",\"inputs\":[{\"name\":\"valPubKeys\",\"type\":\"bytes[]\",\"internalType\":\"bytes[]\"}],\"outputs\":[],\"stateMutability\":\"nonpayable\"},{\"type\":\"function\",\"name\":\"requestValidatorDeregistrationWithSig\",\"inputs\":[{\"name\":\"valPubKeys\",\"type\":\"bytes[]\",\"internalType\":\"bytes[]\"},{\"name\":\"signature\",\"type\":\"bytes\",\"internalType\":\"bytes\"},{\"name\":\"deadline\",\"type\":\"uint256\",\"internalType\":\"uint256\"}],\"outputs\":[],\"stateMutability\":\"nonpayable\"},{\"type\":\"function\",\"name\":\"rocketStorage\",\"inputs\":[],\"outputs\":[{\"name\":\"\",\"type\":\"address\",\"internalType\":\"contractRocketStorageInterface\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"setDeregistrationPeriod\",\"inputs\":[{\"name\":\"newDeregistrationPeriod\",\"type\":\"uint64\",\"internalType\":\"uint64\"}],\"outputs\":[],\"stateMutability\":\"nonpayable\"},{\"type\":\"function\",\"name\":\"setFreezeOracle\",\"inputs\":[{\"name\":\"newFreezeOracle\",\"type\":\"address\",\"internalType\":\"address\"}],\"outputs\":[],\"stateMutability\":\"nonpayable\"},{\"type\":\"function\",\"name\":\"setRocketStorage\",\"inputs\":[{\"name\":\"newRocketStorage\",\"type\":\"address\",\"internalType\":\"address\"}],\"outputs\":[],\"stateMutability\":\"nonpayable\"},{\"type\":\"function\",\"name\":\"setUnfreezeFee\",\"inputs\":[{\"name\":\"newUnfreezeFee\",\"type\":\"uint256\",\"internalType\":\"uint256\"}],\"outputs\":[],\"stateMutability\":\"nonpayable\"},{\"type\":\"function\",\"name\":\"setUnfreezeReceiver\",\"inputs\":[{\"name\":\"newUnfreezeReceiver\",\"type\":\"address\",\"internalType\":\"address\"}],\"outputs\":[],\"stateMutability\":\"nonpayable\"},{\"type\":\"function\",\"name\":\"transferOwnership\",\"inputs\":[{\"name\":\"newOwner\",\"type\":\"address\",\"internalType\":\"address\"}],\"outputs\":[],\"stateMutability\":\"nonpayable\"},{\"type\":\"function\",\"name\":\"unfreeze\",\"inputs\":[{\"name\":\"valPubKeys\",\"type\":\"bytes[]\",\"internalType\":\"bytes[]\"}],\"outputs\":[],\"stateMutability\":\"payable\"},{\"type\":\"function\",\"name\":\"unfreezeFee\",\"inputs\":[],\"outputs\":[{\"name\":\"\",\"type\":\"uint256\",\"internalType\":\"uint256\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"unfreezeReceiver\",\"inputs\":[],\"outputs\":[{\"name\":\"\",\"type\":\"address\",\"internalType\":\"address\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"unpause\",\"inputs\":[],\"outputs\":[],\"stateMutability\":\"nonpayable\"},{\"type\":\"function\",\"name\":\"upgradeToAndCall\",\"inputs\":[{\"name\":\"newImplementation\",\"type\":\"address\",\"internalType\":\"address\"},{\"name\":\"data\",\"type\":\"bytes\",\"internalType\":\"bytes\"}],\"outputs\":[],\"stateMutability\":\"payable\"},{\"type\":\"function\",\"name\":\"validatorRegistrations\",\"inputs\":[{\"name\":\"\",\"type\":\"bytes\",\"internalType\":\"bytes\"}],\"outputs\":[{\"name\":\"exists\",\"type\":\"bool\",\"internalType\":\"bool\"},{\"name\":\"deregTimestamp\",\"type\":\"uint64\",\"internalType\":\"uint64\"},{\"name\":\"freezeTimestamp\",\"type\":\"uint64\",\"internalType\":\"uint64\"}],\"stateMutability\":\"view\"},{\"type\":\"event\",\"name\":\"EIP712DomainChanged\",\"inputs\":[],\"anonymous\":false},{\"type\":\"event\",\"name\":\"Initialized\",\"inputs\":[{\"name\":\"version\",\"type\":\"uint64\",\"indexed\":false,\"internalType\":\"uint64\"}],\"anonymous\":false},{\"type\":\"event\",\"name\":\"OwnershipTransferStarted\",\"inputs\":[{\"name\":\"previousOwner\",\"type\":\"address\",\"indexed\":true,\"internalType\":\"address\"},{\"name\":\"newOwner\",\"type\":\"address\",\"indexed\":true,\"internalType\":\"address\"}],\"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\":\"Unpaused\",\"inputs\":[{\"name\":\"account\",\"type\":\"address\",\"indexed\":false,\"internalType\":\"address\"}],\"anonymous\":false},{\"type\":\"event\",\"name\":\"Upgraded\",\"inputs\":[{\"name\":\"implementation\",\"type\":\"address\",\"indexed\":true,\"internalType\":\"address\"}],\"anonymous\":false},{\"type\":\"event\",\"name\":\"ValidatorDeregistered\",\"inputs\":[{\"name\":\"validatorPubKey\",\"type\":\"bytes\",\"indexed\":true,\"internalType\":\"bytes\"},{\"name\":\"nodeAddress\",\"type\":\"address\",\"indexed\":true,\"internalType\":\"address\"}],\"anonymous\":false},{\"type\":\"event\",\"name\":\"ValidatorDeregistrationRequested\",\"inputs\":[{\"name\":\"validatorPubKey\",\"type\":\"bytes\",\"indexed\":true,\"internalType\":\"bytes\"},{\"name\":\"nodeAddress\",\"type\":\"address\",\"indexed\":true,\"internalType\":\"address\"}],\"anonymous\":false},{\"type\":\"event\",\"name\":\"ValidatorFrozen\",\"inputs\":[{\"name\":\"validatorPubKey\",\"type\":\"bytes\",\"indexed\":true,\"internalType\":\"bytes\"}],\"anonymous\":false},{\"type\":\"event\",\"name\":\"ValidatorRegistered\",\"inputs\":[{\"name\":\"validatorPubKey\",\"type\":\"bytes\",\"indexed\":true,\"internalType\":\"bytes\"},{\"name\":\"nodeAddress\",\"type\":\"address\",\"indexed\":true,\"internalType\":\"address\"}],\"anonymous\":false},{\"type\":\"event\",\"name\":\"ValidatorUnfrozen\",\"inputs\":[{\"name\":\"validatorPubKey\",\"type\":\"bytes\",\"indexed\":true,\"internalType\":\"bytes\"}],\"anonymous\":false},{\"type\":\"error\",\"name\":\"AddressEmptyCode\",\"inputs\":[{\"name\":\"target\",\"type\":\"address\",\"internalType\":\"address\"}]},{\"type\":\"error\",\"name\":\"DeregRequestAlreadyExists\",\"inputs\":[{\"name\":\"validatorPubkey\",\"type\":\"bytes\",\"internalType\":\"bytes\"}]},{\"type\":\"error\",\"name\":\"DeregRequestDoesNotExist\",\"inputs\":[{\"name\":\"validatorPubkey\",\"type\":\"bytes\",\"internalType\":\"bytes\"}]},{\"type\":\"error\",\"name\":\"DeregistrationTooSoon\",\"inputs\":[{\"name\":\"validatorPubkey\",\"type\":\"bytes\",\"internalType\":\"bytes\"}]},{\"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\":\"ExpiredSignature\",\"inputs\":[]},{\"type\":\"error\",\"name\":\"FailedInnerCall\",\"inputs\":[]},{\"type\":\"error\",\"name\":\"FrozenValidatorCannotDeregister\",\"inputs\":[{\"name\":\"validatorPubkey\",\"type\":\"bytes\",\"internalType\":\"bytes\"}]},{\"type\":\"error\",\"name\":\"InvalidBLSPubKeyLength\",\"inputs\":[{\"name\":\"expectedLength\",\"type\":\"uint256\",\"internalType\":\"uint256\"},{\"name\":\"actualLength\",\"type\":\"uint256\",\"internalType\":\"uint256\"}]},{\"type\":\"error\",\"name\":\"InvalidFallback\",\"inputs\":[]},{\"type\":\"error\",\"name\":\"InvalidInitialization\",\"inputs\":[]},{\"type\":\"error\",\"name\":\"InvalidReceive\",\"inputs\":[]},{\"type\":\"error\",\"name\":\"InvalidSignature\",\"inputs\":[]},{\"type\":\"error\",\"name\":\"MinipoolNotActive\",\"inputs\":[{\"name\":\"validatorPubkey\",\"type\":\"bytes\",\"internalType\":\"bytes\"}]},{\"type\":\"error\",\"name\":\"MixedNodeBatch\",\"inputs\":[{\"name\":\"validatorPubkey\",\"type\":\"bytes\",\"internalType\":\"bytes\"}]},{\"type\":\"error\",\"name\":\"NoKeysProvided\",\"inputs\":[]},{\"type\":\"error\",\"name\":\"NoMinipoolForKey\",\"inputs\":[]},{\"type\":\"error\",\"name\":\"NotInitializing\",\"inputs\":[]},{\"type\":\"error\",\"name\":\"NotMinipoolOperator\",\"inputs\":[{\"name\":\"validatorPubkey\",\"type\":\"bytes\",\"internalType\":\"bytes\"}]},{\"type\":\"error\",\"name\":\"NotWithdrawalAddress\",\"inputs\":[{\"name\":\"withdrawalAddress\",\"type\":\"address\",\"internalType\":\"address\"}]},{\"type\":\"error\",\"name\":\"OnlyFreezeOracle\",\"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\":\"ReentrancyGuardReentrantCall\",\"inputs\":[]},{\"type\":\"error\",\"name\":\"RefundFailed\",\"inputs\":[]},{\"type\":\"error\",\"name\":\"UUPSUnauthorizedCallContext\",\"inputs\":[]},{\"type\":\"error\",\"name\":\"UUPSUnsupportedProxiableUUID\",\"inputs\":[{\"name\":\"slot\",\"type\":\"bytes32\",\"internalType\":\"bytes32\"}]},{\"type\":\"error\",\"name\":\"UnfreezeFeeRequired\",\"inputs\":[{\"name\":\"requiredFee\",\"type\":\"uint256\",\"internalType\":\"uint256\"}]},{\"type\":\"error\",\"name\":\"UnfreezeTransferFailed\",\"inputs\":[]},{\"type\":\"error\",\"name\":\"ValidatorAlreadyFrozen\",\"inputs\":[{\"name\":\"validatorPubkey\",\"type\":\"bytes\",\"internalType\":\"bytes\"}]},{\"type\":\"error\",\"name\":\"ValidatorAlreadyRegistered\",\"inputs\":[{\"name\":\"validatorPubkey\",\"type\":\"bytes\",\"internalType\":\"bytes\"}]},{\"type\":\"error\",\"name\":\"ValidatorDeregistrationNotExpired\",\"inputs\":[{\"name\":\"validatorPubkey\",\"type\":\"bytes\",\"internalType\":\"bytes\"}]},{\"type\":\"error\",\"name\":\"ValidatorNotFrozen\",\"inputs\":[{\"name\":\"validatorPubkey\",\"type\":\"bytes\",\"internalType\":\"bytes\"}]},{\"type\":\"error\",\"name\":\"ValidatorNotRegistered\",\"inputs\":[{\"name\":\"validatorPubkey\",\"type\":\"bytes\",\"internalType\":\"bytes\"}]},{\"type\":\"error\",\"name\":\"ZeroParam\",\"inputs\":[]}]", } // RocketminipoolregistryABI is the input ABI used to generate the binding from. @@ -568,37 +568,6 @@ func (_Rocketminipoolregistry *RocketminipoolregistryCallerSession) IsMinipoolAc return _Rocketminipoolregistry.Contract.IsMinipoolActive(&_Rocketminipoolregistry.CallOpts, minipool) } -// IsOperatorValidForKey is a free data retrieval call binding the contract method 0x5ac0191f. -// -// Solidity: function isOperatorValidForKey(address operator, bytes validatorPubkey) view returns(bool) -func (_Rocketminipoolregistry *RocketminipoolregistryCaller) IsOperatorValidForKey(opts *bind.CallOpts, operator common.Address, validatorPubkey []byte) (bool, error) { - var out []interface{} - err := _Rocketminipoolregistry.contract.Call(opts, &out, "isOperatorValidForKey", operator, validatorPubkey) - - if err != nil { - return *new(bool), err - } - - out0 := *abi.ConvertType(out[0], new(bool)).(*bool) - - return out0, err - -} - -// IsOperatorValidForKey is a free data retrieval call binding the contract method 0x5ac0191f. -// -// Solidity: function isOperatorValidForKey(address operator, bytes validatorPubkey) view returns(bool) -func (_Rocketminipoolregistry *RocketminipoolregistrySession) IsOperatorValidForKey(operator common.Address, validatorPubkey []byte) (bool, error) { - return _Rocketminipoolregistry.Contract.IsOperatorValidForKey(&_Rocketminipoolregistry.CallOpts, operator, validatorPubkey) -} - -// IsOperatorValidForKey is a free data retrieval call binding the contract method 0x5ac0191f. -// -// Solidity: function isOperatorValidForKey(address operator, bytes validatorPubkey) view returns(bool) -func (_Rocketminipoolregistry *RocketminipoolregistryCallerSession) IsOperatorValidForKey(operator common.Address, validatorPubkey []byte) (bool, error) { - return _Rocketminipoolregistry.Contract.IsOperatorValidForKey(&_Rocketminipoolregistry.CallOpts, operator, validatorPubkey) -} - // IsValidatorOptedIn is a free data retrieval call binding the contract method 0x470b690f. // // Solidity: function isValidatorOptedIn(bytes valPubKey) view returns(bool) diff --git a/contracts/contracts/interfaces/IRocketMinipoolRegistry.sol b/contracts/contracts/interfaces/IRocketMinipoolRegistry.sol index b2fe9f219..0df1759de 100644 --- a/contracts/contracts/interfaces/IRocketMinipoolRegistry.sol +++ b/contracts/contracts/interfaces/IRocketMinipoolRegistry.sol @@ -125,6 +125,4 @@ interface IRocketMinipoolRegistry { /// @notice Checks if a validator is opted-in. function isValidatorOptedIn(bytes calldata validatorPubkey) external view returns (bool); - /// @notice Checks if an operator is valid to interact on behalf of a validator. - function isOperatorValidForKey(address operator, bytes calldata validatorPubkey) external view returns (bool); } diff --git a/contracts/contracts/validator-registry/rocketpool/RocketMinipoolRegistry.sol b/contracts/contracts/validator-registry/rocketpool/RocketMinipoolRegistry.sol index c4cc3525a..533cb8d11 100644 --- a/contracts/contracts/validator-registry/rocketpool/RocketMinipoolRegistry.sol +++ b/contracts/contracts/validator-registry/rocketpool/RocketMinipoolRegistry.sol @@ -335,13 +335,6 @@ contract RocketMinipoolRegistry is return RocketMinipoolInterface(minipool).getStatus() == MinipoolStatus.Staking; } - /// @dev Fetches the minipool from a validator's pubkey and returns true if caller is either the minipool's node address or node's withdrawal address. - function isOperatorValidForKey(address operator, bytes calldata validatorPubkey) external view returns (bool) { - address minipool = getMinipoolFromPubkey(validatorPubkey); - address nodeAddress = getNodeAddressFromMinipool(minipool); - return (nodeAddress == operator || rocketStorage.getNodeWithdrawalAddress(nodeAddress) == operator); - } - function isValidatorRegistered(bytes calldata validatorPubkey) public view returns (bool) { return validatorRegistrations[validatorPubkey].exists; } diff --git a/contracts/test/validator-registry/rocketpool/ForkRegisterWithSig.sol b/contracts/test/validator-registry/rocketpool/ForkRegisterWithSig.sol new file mode 100644 index 000000000..f9dd113fd --- /dev/null +++ b/contracts/test/validator-registry/rocketpool/ForkRegisterWithSig.sol @@ -0,0 +1,126 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.26; + +import "forge-std/Test.sol"; +import "forge-std/console.sol"; + +import {RocketMinipoolRegistry} from "../../../contracts/validator-registry/rocketpool/RocketMinipoolRegistry.sol"; + +contract MinipoolShim { + address public immutable nodeAddr; + constructor(address _node) { + nodeAddr = _node; + } + + function getNodeAddress() external view returns (address) { + return nodeAddr; + } + + // Return Staking (2) to behave as active if caller checks status + function getStatus() external pure returns (uint8) { + return 2; + } +} + +// contract ExecutorProxy { +// function execRegister(address registry, bytes[] calldata pks, bytes calldata sig, uint256 deadline) external { +// RocketMinipoolRegistry(registry).registerValidatorsWithSig(pks, sig, deadline); +// } +// } + +contract ForkRegisterWithSig is Test { + // Replace these constants with your values (or set as environment variables) + address constant HOODI_REGISTRY = 0x0694bFD12dcBC9165e91A8C2843bc1fe9d3f3DD0; + address constant TARGET_MINIPOOL = 0x862e2909Dafb8dc16417B48dBBe688714737bBf5; + address constant NODE_ADDR = 0x1623fE21185c92BB43bD83741E226288B516134a; + // The 48-byte validator pubkey (as bytes). Provide exact bytes. + bytes constant VAL_PUBKEY = hex"b0d7f0d83e3bbaefacd7b05596f0e8f4c520d96f494b46180b9bcbb139a866bb89a63c92c86365658b1411d22ada016c"; + + // EIP-712 typehash (must match on-chain contract) + bytes32 internal constant REGISTER_TYPEHASH = + keccak256("Register(bytes32 pubkeysHash,address executor,uint256 nonce,uint256 deadline)"); + + string internal constant NAME = "RocketMinipoolRegistry"; + string internal constant VERSION = "1"; + bytes32 internal constant EIP712_DOMAIN_TYPEHASH = + keccak256("EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)"); + + function test_fork_register_with_sig() public { + // This test is intended to run on a mainnet (hoodi) fork. + // Make sure you run: forge test -vvvv --fork-url -m test_fork_register_with_sig + + // 1) Etch the minipool shim at the target minipool address so getNodeAddress() returns NODE_ADDR + MinipoolShim shim = new MinipoolShim(NODE_ADDR); + bytes memory shimCode = address(shim).code; + vm.etch(TARGET_MINIPOOL, shimCode); + console.log("Etched shim at minipool:", TARGET_MINIPOOL); + + // 2) Point to deployed registry on the fork + RocketMinipoolRegistry reg = RocketMinipoolRegistry(payable(HOODI_REGISTRY)); + + // 3) Compute pubkeysHash exactly as the contract expects (concatenate 48-byte pubkeys) + bytes[] memory pks = new bytes[](1); + pks[0] = VAL_PUBKEY; + bytes32 pkHash = reg.pubkeysHash(pks); + console.logBytes32(pkHash); + + // 4) Determine executor (the address that will call the tx). We'll use address(this) by default. + address executor = address(this); + + // Optionally override executor with an env var "EXECUTOR" + string memory execMaybe = vm.envOr("EXECUTOR", string("")); + if (bytes(execMaybe).length != 0) { + executor = vm.envAddress("EXECUTOR"); + } + console.log("Executor:", executor); + + + // 5) Get nonce for the node from the on-chain registry + uint256 nonce = reg.nonces(NODE_ADDR); + console.logUint(nonce); + + // 6) Prepare deadline + uint256 deadline = block.timestamp + 1 days; + + // 7) Build struct hash and domain separator, then the digest + bytes32 structHash = keccak256(abi.encode( + REGISTER_TYPEHASH, + pkHash, + executor, + nonce, + deadline + )); + + bytes32 domainSeparator = keccak256(abi.encode( + EIP712_DOMAIN_TYPEHASH, + keccak256(bytes(NAME)), + keccak256(bytes(VERSION)), + block.chainid, + address(reg) + )); + + bytes32 digest = keccak256(abi.encodePacked("\x19\x01", domainSeparator, structHash)); + console.logBytes32(digest); + + // 8) Try to sign the digest using an env-provided private key (NODE_PK). If present, we'll sign and call. + string memory skHex = vm.envOr("NODE_PK", string("")); + if (bytes(skHex).length == 0) { + // Not provided: print instructions for offline signing. + console.log("No NODE_PK env var provided. To complete flow:"); + console.log(" - Sign the digest printed above with the private key controlling NODE_ADDR."); + console.log(" - Provide the r||s||v as bytes in the call below, or set NODE_PK to the hex private key."); + return; + } + + // If NODE_PK provided as hex (with or without 0x), read as uint + uint256 nodeSk = vm.envUint("NODE_PK"); + (uint8 v, bytes32 r, bytes32 s) = vm.sign(nodeSk, digest); + bytes memory sig = abi.encodePacked(r, s, v); + console.logBytes(sig); + + // 9) Call from the intended executor; ensure the account exists locally to avoid fork lookups + reg.registerValidatorsWithSig(pks, sig, deadline); + + console.log("Called registerValidatorsWithSig on registry:", HOODI_REGISTRY); + } +} diff --git a/contracts/test/validator-registry/rocketpool/RocketMinipoolRegistryTest.sol b/contracts/test/validator-registry/rocketpool/RocketMinipoolRegistryTest.sol index 6b50657c6..00257390a 100644 --- a/contracts/test/validator-registry/rocketpool/RocketMinipoolRegistryTest.sol +++ b/contracts/test/validator-registry/rocketpool/RocketMinipoolRegistryTest.sol @@ -282,11 +282,6 @@ contract RocketMinipoolRegistryTest is Test { assertEq(w, withdrawal); assertTrue(reg.isMinipoolActive(address(mp1))); - - // if your isOperatorValidForKey uses msg.sender, these pass; if it takes (addr, key), swap accordingly. - vm.prank(node); assertTrue(reg.isOperatorValidForKey(node, pk1)); - vm.prank(withdrawal);assertTrue(reg.isOperatorValidForKey(withdrawal,pk1)); - vm.prank(stranger); assertFalse(reg.isOperatorValidForKey(stranger,pk1)); } function test_IsValidatorOptedIn_TruthTable() public { From 799f312d11e65f782d612db8d44d7b7eb3d0ee39 Mon Sep 17 00:00:00 2001 From: owen-eth Date: Tue, 11 Nov 2025 16:50:15 -0500 Subject: [PATCH 4/4] removed hoodi fork sig test --- .../rocketpool/ForkRegisterWithSig.sol | 126 ------------------ 1 file changed, 126 deletions(-) delete mode 100644 contracts/test/validator-registry/rocketpool/ForkRegisterWithSig.sol diff --git a/contracts/test/validator-registry/rocketpool/ForkRegisterWithSig.sol b/contracts/test/validator-registry/rocketpool/ForkRegisterWithSig.sol deleted file mode 100644 index f9dd113fd..000000000 --- a/contracts/test/validator-registry/rocketpool/ForkRegisterWithSig.sol +++ /dev/null @@ -1,126 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity 0.8.26; - -import "forge-std/Test.sol"; -import "forge-std/console.sol"; - -import {RocketMinipoolRegistry} from "../../../contracts/validator-registry/rocketpool/RocketMinipoolRegistry.sol"; - -contract MinipoolShim { - address public immutable nodeAddr; - constructor(address _node) { - nodeAddr = _node; - } - - function getNodeAddress() external view returns (address) { - return nodeAddr; - } - - // Return Staking (2) to behave as active if caller checks status - function getStatus() external pure returns (uint8) { - return 2; - } -} - -// contract ExecutorProxy { -// function execRegister(address registry, bytes[] calldata pks, bytes calldata sig, uint256 deadline) external { -// RocketMinipoolRegistry(registry).registerValidatorsWithSig(pks, sig, deadline); -// } -// } - -contract ForkRegisterWithSig is Test { - // Replace these constants with your values (or set as environment variables) - address constant HOODI_REGISTRY = 0x0694bFD12dcBC9165e91A8C2843bc1fe9d3f3DD0; - address constant TARGET_MINIPOOL = 0x862e2909Dafb8dc16417B48dBBe688714737bBf5; - address constant NODE_ADDR = 0x1623fE21185c92BB43bD83741E226288B516134a; - // The 48-byte validator pubkey (as bytes). Provide exact bytes. - bytes constant VAL_PUBKEY = hex"b0d7f0d83e3bbaefacd7b05596f0e8f4c520d96f494b46180b9bcbb139a866bb89a63c92c86365658b1411d22ada016c"; - - // EIP-712 typehash (must match on-chain contract) - bytes32 internal constant REGISTER_TYPEHASH = - keccak256("Register(bytes32 pubkeysHash,address executor,uint256 nonce,uint256 deadline)"); - - string internal constant NAME = "RocketMinipoolRegistry"; - string internal constant VERSION = "1"; - bytes32 internal constant EIP712_DOMAIN_TYPEHASH = - keccak256("EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)"); - - function test_fork_register_with_sig() public { - // This test is intended to run on a mainnet (hoodi) fork. - // Make sure you run: forge test -vvvv --fork-url -m test_fork_register_with_sig - - // 1) Etch the minipool shim at the target minipool address so getNodeAddress() returns NODE_ADDR - MinipoolShim shim = new MinipoolShim(NODE_ADDR); - bytes memory shimCode = address(shim).code; - vm.etch(TARGET_MINIPOOL, shimCode); - console.log("Etched shim at minipool:", TARGET_MINIPOOL); - - // 2) Point to deployed registry on the fork - RocketMinipoolRegistry reg = RocketMinipoolRegistry(payable(HOODI_REGISTRY)); - - // 3) Compute pubkeysHash exactly as the contract expects (concatenate 48-byte pubkeys) - bytes[] memory pks = new bytes[](1); - pks[0] = VAL_PUBKEY; - bytes32 pkHash = reg.pubkeysHash(pks); - console.logBytes32(pkHash); - - // 4) Determine executor (the address that will call the tx). We'll use address(this) by default. - address executor = address(this); - - // Optionally override executor with an env var "EXECUTOR" - string memory execMaybe = vm.envOr("EXECUTOR", string("")); - if (bytes(execMaybe).length != 0) { - executor = vm.envAddress("EXECUTOR"); - } - console.log("Executor:", executor); - - - // 5) Get nonce for the node from the on-chain registry - uint256 nonce = reg.nonces(NODE_ADDR); - console.logUint(nonce); - - // 6) Prepare deadline - uint256 deadline = block.timestamp + 1 days; - - // 7) Build struct hash and domain separator, then the digest - bytes32 structHash = keccak256(abi.encode( - REGISTER_TYPEHASH, - pkHash, - executor, - nonce, - deadline - )); - - bytes32 domainSeparator = keccak256(abi.encode( - EIP712_DOMAIN_TYPEHASH, - keccak256(bytes(NAME)), - keccak256(bytes(VERSION)), - block.chainid, - address(reg) - )); - - bytes32 digest = keccak256(abi.encodePacked("\x19\x01", domainSeparator, structHash)); - console.logBytes32(digest); - - // 8) Try to sign the digest using an env-provided private key (NODE_PK). If present, we'll sign and call. - string memory skHex = vm.envOr("NODE_PK", string("")); - if (bytes(skHex).length == 0) { - // Not provided: print instructions for offline signing. - console.log("No NODE_PK env var provided. To complete flow:"); - console.log(" - Sign the digest printed above with the private key controlling NODE_ADDR."); - console.log(" - Provide the r||s||v as bytes in the call below, or set NODE_PK to the hex private key."); - return; - } - - // If NODE_PK provided as hex (with or without 0x), read as uint - uint256 nodeSk = vm.envUint("NODE_PK"); - (uint8 v, bytes32 r, bytes32 s) = vm.sign(nodeSk, digest); - bytes memory sig = abi.encodePacked(r, s, v); - console.logBytes(sig); - - // 9) Call from the intended executor; ensure the account exists locally to avoid fork lookups - reg.registerValidatorsWithSig(pks, sig, deadline); - - console.log("Called registerValidatorsWithSig on registry:", HOODI_REGISTRY); - } -}