diff --git a/contracts-abi/abi/RocketMinipoolRegistry.abi b/contracts-abi/abi/RocketMinipoolRegistry.abi index f7f515a09..5dbc3f96d 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", @@ -279,15 +345,10 @@ }, { "type": "function", - "name": "isOperatorValidForKey", + "name": "isValidatorOptedIn", "inputs": [ { - "name": "operator", - "type": "address", - "internalType": "address" - }, - { - "name": "validatorPubkey", + "name": "valPubKey", "type": "bytes", "internalType": "bytes" } @@ -303,10 +364,10 @@ }, { "type": "function", - "name": "isValidatorOptedIn", + "name": "isValidatorRegistered", "inputs": [ { - "name": "valPubKey", + "name": "validatorPubkey", "type": "bytes", "internalType": "bytes" } @@ -322,19 +383,19 @@ }, { "type": "function", - "name": "isValidatorRegistered", + "name": "nonces", "inputs": [ { - "name": "validatorPubkey", - "type": "bytes", - "internalType": "bytes" + "name": "", + "type": "address", + "internalType": "address" } ], "outputs": [ { "name": "", - "type": "bool", - "internalType": "bool" + "type": "uint256", + "internalType": "uint256" } ], "stateMutability": "view" @@ -411,6 +472,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 +504,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 +547,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 +754,12 @@ ], "stateMutability": "view" }, + { + "type": "event", + "name": "EIP712DomainChanged", + "inputs": [], + "anonymous": false + }, { "type": "event", "name": "Initialized", @@ -871,6 +1003,11 @@ "name": "ExpectedPause", "inputs": [] }, + { + "type": "error", + "name": "ExpiredSignature", + "inputs": [] + }, { "type": "error", "name": "FailedInnerCall", @@ -918,6 +1055,11 @@ "name": "InvalidReceive", "inputs": [] }, + { + "type": "error", + "name": "InvalidSignature", + "inputs": [] + }, { "type": "error", "name": "MinipoolNotActive", @@ -931,7 +1073,7 @@ }, { "type": "error", - "name": "NoMinipoolForKey", + "name": "MixedNodeBatch", "inputs": [ { "name": "validatorPubkey", @@ -940,6 +1082,16 @@ } ] }, + { + "type": "error", + "name": "NoKeysProvided", + "inputs": [] + }, + { + "type": "error", + "name": "NoMinipoolForKey", + "inputs": [] + }, { "type": "error", "name": "NotInitializing", @@ -956,6 +1108,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..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\":\"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\":\"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. @@ -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) @@ -498,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) @@ -591,6 +630,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 +785,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 +1001,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 +1127,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 +1190,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 +1442,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..0df1759de 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; @@ -46,9 +45,18 @@ 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); + error NoMinipoolForKey(); error DeregRequestDoesNotExist(bytes validatorPubkey); @@ -75,6 +83,21 @@ 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; @@ -99,9 +122,7 @@ 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 e61e2063e..533cb8d11 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"; @@ -16,8 +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 { +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)"); modifier onlyFreezeOracle() { require(msg.sender == freezeOracle, IRocketMinipoolRegistry.OnlyFreezeOracle()); @@ -27,8 +44,11 @@ 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)); + require( + valPubKeys[i].length == 48, IRocketMinipoolRegistry.InvalidBLSPubKeyLength(48, valPubKeys[i].length) + ); } _; } @@ -48,12 +68,20 @@ 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(); __UUPSUpgradeable_init(); + __EIP712_init("RocketMinipoolRegistry", "1"); _setFreezeOracle(freezeOracle); _setUnfreezeReceiver(unfreezeReceiver); _setUnfreezeFee(unfreezeFee); @@ -61,31 +89,147 @@ 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); } } - function requestValidatorDeregistration(bytes[] calldata valPubKeys) external onlyValidBLSPubKeys(valPubKeys) whenNotPaused { + /// @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 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))); + + // 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 = 0; i < len; ++i) { + 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 = 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 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))); + + 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 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))); + + 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() { + function freeze(bytes[] calldata valPubKeys) external whenNotPaused onlyFreezeOracle { uint256 len = valPubKeys.length; for (uint256 i = 0; i < len; ++i) { _freeze(valPubKeys[i]); @@ -100,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()); } } @@ -114,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(); } @@ -124,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); } @@ -175,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))); } @@ -191,54 +335,60 @@ contract RocketMinipoolRegistry is IRocketMinipoolRegistry, RocketMinipoolRegist 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) public 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; } /// @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) { + 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 { @@ -250,7 +400,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); } @@ -283,6 +433,16 @@ contract RocketMinipoolRegistry is IRocketMinipoolRegistry, RocketMinipoolRegist // 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)); + 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 +453,17 @@ 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..00257390a 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)); @@ -288,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 { @@ -300,7 +289,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 +298,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 {} +}