From 14e4ba9ec61940f7245c803d869eb17eab663364 Mon Sep 17 00:00:00 2001 From: 0xwalid Date: Mon, 7 Oct 2024 12:21:19 -0700 Subject: [PATCH 01/22] initial alternative validator registration logic --- l1-contracts/script/SetupAccess.s.sol | 4 +- l1-contracts/src/UniFiAVSManager.sol | 184 +++++++++++++++- l1-contracts/src/UniFiAVSManagerStorage.sol | 4 + .../src/interfaces/IUniFiAVSManager.sol | 42 ++++ l1-contracts/src/structs/ValidatorData.sol | 39 +++- l1-contracts/test/UniFiAVSManager.t.sol | 204 +++++++++++++++++- 6 files changed, 463 insertions(+), 14 deletions(-) diff --git a/l1-contracts/script/SetupAccess.s.sol b/l1-contracts/script/SetupAccess.s.sol index d44ba64..03fa607 100644 --- a/l1-contracts/script/SetupAccess.s.sol +++ b/l1-contracts/script/SetupAccess.s.sol @@ -88,7 +88,7 @@ contract SetupAccess is BaseScript { ); bytes4[] memory publicSelectors = new bytes4[](0); - publicSelectors = new bytes4[](8); + publicSelectors = new bytes4[](10); publicSelectors[0] = UniFiAVSManager.registerOperator.selector; publicSelectors[1] = UniFiAVSManager.registerValidators.selector; publicSelectors[2] = UniFiAVSManager.startDeregisterOperator.selector; @@ -97,6 +97,8 @@ contract SetupAccess is BaseScript { publicSelectors[5] = UniFiAVSManager.setOperatorCommitment.selector; publicSelectors[6] = UniFiAVSManager.updateOperatorCommitment.selector; publicSelectors[7] = UniFiAVSManager.registerOperatorWithCommitment.selector; + publicSelectors[8] = UniFiAVSManager.registerValidatorsOptimistically.selector; + publicSelectors[9] = UniFiAVSManager.verifyValidatorSignatures.selector; calldatas[1] = abi.encodeWithSelector( AccessManager.setTargetFunctionRole.selector, diff --git a/l1-contracts/src/UniFiAVSManager.sol b/l1-contracts/src/UniFiAVSManager.sol index 6ddf9a6..47661a4 100644 --- a/l1-contracts/src/UniFiAVSManager.sol +++ b/l1-contracts/src/UniFiAVSManager.sol @@ -1,10 +1,14 @@ // SPDX-License-Identifier: GPL-3.0 pragma solidity >=0.8.0 <0.9.0; +// OpenZeppelin Imports import { UUPSUpgradeable } from "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol"; +import { EIP712 } from "@openzeppelin/contracts/utils/cryptography/EIP712.sol"; import { AccessManagedUpgradeable } from "@openzeppelin/contracts-upgradeable/access/manager/AccessManagedUpgradeable.sol"; import { EnumerableSet } from "@openzeppelin/contracts/utils/structs/EnumerableSet.sol"; + +// EigenLayer Imports import { ISignatureUtils } from "eigenlayer/interfaces/ISignatureUtils.sol"; import { IStrategy } from "eigenlayer/interfaces/IStrategy.sol"; import { IAVSDirectory } from "eigenlayer/interfaces/IAVSDirectory.sol"; @@ -12,15 +16,26 @@ import { IAVSDirectoryExtended } from "./interfaces/EigenLayer/IAVSDirectoryExte import { IDelegationManager } from "eigenlayer/interfaces/IDelegationManager.sol"; import { IEigenPodManager } from "eigenlayer/interfaces/IEigenPodManager.sol"; import { IEigenPod } from "eigenlayer/interfaces/IEigenPod.sol"; +import { BN254 } from "eigenlayer-middleware/libraries/BN254.sol"; +// Local Imports +import { BLSSignatureCheckerLib } from "./lib/BLSSignatureCheckerLib.sol"; import { IUniFiAVSManager } from "./interfaces/IUniFiAVSManager.sol"; import { UniFiAVSManagerStorage } from "./UniFiAVSManagerStorage.sol"; import "./structs/ValidatorData.sol"; import "./structs/OperatorData.sol"; -contract UniFiAVSManager is UniFiAVSManagerStorage, IUniFiAVSManager, UUPSUpgradeable, AccessManagedUpgradeable { +contract UniFiAVSManager is + UniFiAVSManagerStorage, + IUniFiAVSManager, + UUPSUpgradeable, + AccessManagedUpgradeable, + EIP712 +{ using EnumerableSet for EnumerableSet.AddressSet; address public constant BEACON_CHAIN_STRATEGY = 0xbeaC0eeEeeeeEEeEeEEEEeeEEeEeeeEeeEEBEaC0; + bytes32 public constant VALIDATOR_REGISTRATION_TYPEHASH = + keccak256("BN254ValidatorRegistration(address operator,bytes32 salt,uint256 expiry,uint64 index)"); /** * @notice The EigenPodManager @@ -76,7 +91,7 @@ contract UniFiAVSManager is UniFiAVSManagerStorage, IUniFiAVSManager, UUPSUpgrad IEigenPodManager eigenPodManagerAddress, IDelegationManager eigenDelegationManagerAddress, IAVSDirectory avsDirectoryAddress - ) { + ) EIP712("UniFiAVSManager", "1") { EIGEN_POD_MANAGER = eigenPodManagerAddress; EIGEN_DELEGATION_MANAGER = eigenDelegationManagerAddress; AVS_DIRECTORY = IAVSDirectoryExtended(address(avsDirectoryAddress)); @@ -212,6 +227,135 @@ contract UniFiAVSManager is UniFiAVSManagerStorage, IUniFiAVSManager, UUPSUpgrad $.operators[msg.sender].validatorCount -= uint128(validatorCount); } + /** + * @inheritdoc IUniFiAVSManager + * @dev Restricted in this context is like `whenNotPaused` modifier from Pausable.sol + */ + function registerValidatorsOptimistically(ValidatorRegistrationParams[] memory paramsArray) + external + registeredOperator(msg.sender) + restricted + { + UniFiAVSStorage storage $ = _getUniFiAVSManagerStorage(); + uint256 newValidatorCount = 0; + bytes memory delegateKey = $.operators[msg.sender].commitment.delegateKey; + + for (uint256 i = 0; i < paramsArray.length; i++) { + ValidatorRegistrationParams memory params = paramsArray[i]; + + // Derive the BLS public key hash from pubkeyG1 + bytes32 blsPubKeyHash = BN254.hashG1Point(params.pubkeyG1); + + ValidatorData storage existingValidator = $.validators[blsPubKeyHash]; + ValidatorRegistrationData storage validatorRegistrationData = $.validatorRegistrations[blsPubKeyHash]; + + // Check if the validator already exists with active status + if (existingValidator.index != 0 && existingValidator.registeredUntil == type(uint64).max) { + revert ValidatorAlreadyRegistered(); + } + + // Check if the registration salt has been used before + if (validatorRegistrationData.salt == params.salt) { + revert SaltAlreadyUsed(); + } + + // Check if the signature is expired + if (block.timestamp > params.expiry) { + revert SignatureExpired(); + } + + // Store the validator data + $.validators[blsPubKeyHash] = ValidatorData({ + eigenPod: address(0), // Not from EigenPod + index: params.index, // Store the provided index + operator: msg.sender, + registeredUntil: type(uint64).max + }); + + $.validatorRegistrations[blsPubKeyHash] = ValidatorRegistrationData({ + registrationSignature: params.registrationSignature, + pubkeyG1: params.pubkeyG1, + pubkeyG2: params.pubkeyG2, + salt: params.salt, + expiry: params.expiry + }); + + emit ValidatorRegistered({ + podOwner: address(0), + operator: msg.sender, + delegateKey: delegateKey, + blsPubKeyHash: blsPubKeyHash, + validatorIndex: params.index + }); + + newValidatorCount++; + } + + // Update the operator's validator count + OperatorData storage operator = $.operators[msg.sender]; + operator.validatorCount += uint128(newValidatorCount); + } + + /** + * @inheritdoc IUniFiAVSManager + * @dev Restricted in this context is like `whenNotPaused` modifier from Pausable.sol + */ + function verifyValidatorSignatures(bytes32[] calldata blsPubKeyHashes) external restricted { + UniFiAVSStorage storage $ = _getUniFiAVSManagerStorage(); + + for (uint256 i = 0; i < blsPubKeyHashes.length; i++) { + bytes32 blsPubKeyHash = blsPubKeyHashes[i]; + ValidatorData storage validator = $.validators[blsPubKeyHash]; + address operator = validator.operator; + ValidatorRegistrationData memory validatorRegistrationData = $.validatorRegistrations[blsPubKeyHash]; + + if (validator.index == 0) { + revert ValidatorNotFound(); + } + + // Check if the validator is already deregistered + if (validator.registeredUntil != type(uint64).max) { + revert ValidatorAlreadyDeregistered(); + } + + // Calculate the hash using EIP-712 + BN254.G1Point memory messageHash = blsMessageHash({ + typeHash: VALIDATOR_REGISTRATION_TYPEHASH, + operator: operator, + salt: validatorRegistrationData.salt, + expiry: validatorRegistrationData.expiry, + index: validator.index + }); + + // Use the stored signature for verification + bool isValid = BLSSignatureCheckerLib.isBlsSignatureValid( + validatorRegistrationData.pubkeyG1, + validatorRegistrationData.pubkeyG2, + validatorRegistrationData.registrationSignature, + messageHash + ); + + if (!isValid) { + // Slash the operator + $.slashedOperators[operator].push( + InvalidValidator({ slashingBeneficiary: msg.sender, blsPubKeyHash: blsPubKeyHash }) + ); + + // Update the registeredUntil field to deregister the validator + validator.registeredUntil = uint64(block.number) + $.deregistrationDelay; + + // Emit the ValidatorDeregistered event + emit ValidatorDeregistered({ operator: operator, blsPubKeyHash: blsPubKeyHash }); + + emit ValidatorSlashed(operator, blsPubKeyHash); + + // Decrement the operator's validator count + OperatorData storage operatorData = $.operators[operator]; + operatorData.validatorCount -= 1; + } + } + } + /** * @inheritdoc IUniFiAVSManager * @dev Restricted in this context is like `whenNotPaused` modifier from Pausable.sol @@ -508,6 +652,23 @@ contract UniFiAVSManager is UniFiAVSManagerStorage, IUniFiAVSManager, UUPSUpgrad return address(AVS_DIRECTORY); } + /** + * @notice Calculate the BLS message hash using EIP-712 + * @param typeHash The type hash for the registration + * @param operator The operator's address + * @param salt The unique salt + * @param expiry The expiry time + * @param index The index of the validator + * @return The BLS message hash as a G1 point + */ + function blsMessageHash(bytes32 typeHash, address operator, bytes32 salt, uint256 expiry, uint64 index) + public + view + returns (BN254.G1Point memory) + { + return BN254.hashToG1(_hashTypedDataV4(keccak256(abi.encodePacked(typeHash, operator, salt, expiry, index)))); + } + // INTERNAL FUNCTIONS function _getOperator(address operator) internal view returns (OperatorDataExtended memory) { @@ -533,10 +694,17 @@ contract UniFiAVSManager is UniFiAVSManagerStorage, IUniFiAVSManager, UUPSUpgrad ValidatorData memory validatorData = $.validators[blsPubKeyHash]; if (validatorData.index != 0) { - IEigenPod eigenPod = IEigenPod(validatorData.eigenPod); - IEigenPod.ValidatorInfo memory validatorInfo = eigenPod.validatorPubkeyHashToInfo(blsPubKeyHash); + IEigenPod.VALIDATOR_STATUS eigenPodStatus; + bool backedByEigenPodStake; - bool backedByStake = EIGEN_DELEGATION_MANAGER.delegatedTo(eigenPod.podOwner()) == validatorData.operator; + if (validatorData.eigenPod != address(0)) { + IEigenPod eigenPod = IEigenPod(validatorData.eigenPod); + IEigenPod.ValidatorInfo memory validatorInfo = eigenPod.validatorPubkeyHashToInfo(blsPubKeyHash); + + eigenPodStatus = validatorInfo.status; + backedByEigenPodStake = + EIGEN_DELEGATION_MANAGER.delegatedTo(eigenPod.podOwner()) == validatorData.operator; + } OperatorData storage operator = $.operators[validatorData.operator]; OperatorCommitment memory activeCommitment = _getActiveCommitment(operator); @@ -544,11 +712,11 @@ contract UniFiAVSManager is UniFiAVSManagerStorage, IUniFiAVSManager, UUPSUpgrad return ValidatorDataExtended({ operator: validatorData.operator, eigenPod: validatorData.eigenPod, - validatorIndex: validatorInfo.validatorIndex, - status: validatorInfo.status, + validatorIndex: validatorData.index, + eigenPodStatus: eigenPodStatus, delegateKey: activeCommitment.delegateKey, chainIDBitMap: activeCommitment.chainIDBitMap, - backedByStake: backedByStake, + backedByEigenPodStake: backedByEigenPodStake, registered: block.number < validatorData.registeredUntil }); } diff --git a/l1-contracts/src/UniFiAVSManagerStorage.sol b/l1-contracts/src/UniFiAVSManagerStorage.sol index fc1996a..afa6aba 100644 --- a/l1-contracts/src/UniFiAVSManagerStorage.sol +++ b/l1-contracts/src/UniFiAVSManagerStorage.sol @@ -22,6 +22,10 @@ abstract contract UniFiAVSManagerStorage { mapping(uint256 => uint8) chainIdToBitmapIndex; // Set of allowlisted restaking strategies EnumerableSet.AddressSet allowlistedRestakingStrategies; + // Mapping to store validator registration data + mapping(bytes32 => ValidatorRegistrationData) validatorRegistrations; + // Slashed operators mapping + mapping(address => InvalidValidator[]) slashedOperators; // operator => InvalidValidator[] } /** diff --git a/l1-contracts/src/interfaces/IUniFiAVSManager.sol b/l1-contracts/src/interfaces/IUniFiAVSManager.sol index 01b442a..92767b3 100644 --- a/l1-contracts/src/interfaces/IUniFiAVSManager.sol +++ b/l1-contracts/src/interfaces/IUniFiAVSManager.sol @@ -1,8 +1,11 @@ // SPDX-License-Identifier: GPL-3.0 pragma solidity >=0.8.0 <0.9.0; +// EigenLayer Imports import { IDelegationManager } from "eigenlayer/interfaces/IDelegationManager.sol"; import { ISignatureUtils } from "eigenlayer/interfaces/ISignatureUtils.sol"; +import { BN254 } from "eigenlayer-middleware/libraries/BN254.sol"; +// Local Imports import { IAVSDirectoryExtended } from "../interfaces/EigenLayer/IAVSDirectoryExtended.sol"; import "../structs/ValidatorData.sol"; import "../structs/OperatorData.sol"; @@ -70,6 +73,12 @@ interface IUniFiAVSManager { /// @notice Thrown when a restaking strategy allowlist update fails error RestakingStrategyAllowlistUpdateFailed(); + /// @notice Thrown when a salt is already used for a registration + error SaltAlreadyUsed(); + + /// @notice Thrown when a signature is expired + error SignatureExpired(); + /** * @notice Emitted when a new operator is registered in the UniFi AVS. * @param operator The address of the registered operator. @@ -152,6 +161,13 @@ interface IUniFiAVSManager { */ event RestakingStrategyAllowlistUpdated(address indexed strategy, bool allowed); + /** + * @notice Emitted when a validator is slashed. + * @param operator The address of the operator managing the validator. + * @param blsPubKeyHash The BLS public key hash of the slashed validator. + */ + event ValidatorSlashed(address indexed operator, bytes32 indexed blsPubKeyHash); + /** * @notice Returns the EigenPodManager contract. * @return IEigenPodManager The EigenPodManager contract. @@ -248,6 +264,18 @@ interface IUniFiAVSManager { */ function setAllowlistRestakingStrategy(address strategy, bool allowed) external; + /** + * @notice Registers validators optimistically. + * @param paramsArray The array of ValidatorRegistrationParams. + */ + function registerValidatorsOptimistically(ValidatorRegistrationParams[] calldata paramsArray) external; + + /** + * @notice Verifies the signatures of validators. + * @param blsPubKeyHashes The BLS public key hashes of the validators. + */ + function verifyValidatorSignatures(bytes32[] calldata blsPubKeyHashes) external; + /** * @notice Retrieves information about a specific operator. * @param operator The address of the operator. @@ -330,4 +358,18 @@ interface IUniFiAVSManager { /// @notice Returns the EigenLayer AVSDirectory contract. function avsDirectory() external view returns (address); + + /** + * @notice Returns the BLS message hash for a validator registration. + * @param typeHash The type hash for the message. + * @param operator The address of the operator. + * @param salt The salt for the message. + * @param expiry The expiry for the message. + * @param index The index for the message. + * @return BN254.G1Point The BLS message hash. + */ + function blsMessageHash(bytes32 typeHash, address operator, uint64 salt, uint64 expiry, uint256 index) + external + view + returns (BN254.G1Point memory); } diff --git a/l1-contracts/src/structs/ValidatorData.sol b/l1-contracts/src/structs/ValidatorData.sol index 8da2907..174e810 100644 --- a/l1-contracts/src/structs/ValidatorData.sol +++ b/l1-contracts/src/structs/ValidatorData.sol @@ -2,6 +2,7 @@ pragma solidity >=0.8.0 <0.9.0; import "eigenlayer/interfaces/IEigenPod.sol"; +import { BN254 } from "eigenlayer-middleware/libraries/BN254.sol"; /** * @title ValidatorData @@ -32,13 +33,47 @@ struct ValidatorDataExtended { /// @notice The index of the validator in the beacon chain. uint64 validatorIndex; /// @notice The current status of the validator in the EigenPod. - IEigenPod.VALIDATOR_STATUS status; + IEigenPod.VALIDATOR_STATUS eigenPodStatus; /// @notice The delegate key currently associated with the validator's operator. bytes delegateKey; /// @notice Bitmap of chain IDs the validator's operator is committed to. uint256 chainIDBitMap; /// @notice Indicates whether the validator's EigenPod is currently delegated to the operator. - bool backedByStake; + bool backedByEigenPodStake; /// @notice Indicates whether the validator is currently registered (current block < registeredUntil). bool registered; } + +/** + * @title ValidatorRegistrationData + * @notice Struct to store registration-related data for a validator. + */ +struct ValidatorRegistrationData { + BN254.G1Point registrationSignature; + BN254.G1Point pubkeyG1; + BN254.G2Point pubkeyG2; + bytes32 salt; + uint256 expiry; +} + +/** + * @title ValidatorRegistrationParams + * @notice Struct to store parameters for validator registration. + */ +struct ValidatorRegistrationParams { + BN254.G1Point registrationSignature; + BN254.G1Point pubkeyG1; + BN254.G2Point pubkeyG2; + uint64 index; + bytes32 salt; + uint256 expiry; +} + +/** + * @title InvalidValidator + * @notice Struct to store information about a slashed validator. + */ +struct InvalidValidator { + address slashingBeneficiary; + bytes32 blsPubKeyHash; +} diff --git a/l1-contracts/test/UniFiAVSManager.t.sol b/l1-contracts/test/UniFiAVSManager.t.sol index 29e67a8..ff40b1b 100644 --- a/l1-contracts/test/UniFiAVSManager.t.sol +++ b/l1-contracts/test/UniFiAVSManager.t.sol @@ -56,6 +56,36 @@ contract UniFiAVSManagerTest is UnitTestHelper { g2Point.Y[0] = abi.decode(res, (uint256)); } + function _getValidatorSignature( + uint256 validatorPrivateKey, + uint64 validatorIndex, + address operator, + bytes32 salt, + uint256 expiry + ) internal returns (BN254.G1Point memory registrationSignature, bytes32 pubkeyHash) { + // Generate BLS key pair + IBLSApkRegistry.PubkeyRegistrationParams memory blsKeyPair = _generateBlsPubkeyParams(validatorPrivateKey); + + // Create ValidatorRegistrationParams + ValidatorRegistrationParams memory params; + + params.pubkeyG1 = blsKeyPair.pubkeyG1; + params.pubkeyG2 = blsKeyPair.pubkeyG2; + params.salt = salt; + params.expiry = expiry; + params.index = validatorIndex; + + // Generate a valid signature + BN254.G1Point memory messagePoint = avsManager.blsMessageHash( + avsManager.VALIDATOR_REGISTRATION_TYPEHASH(), operator, params.salt, params.expiry, params.index + ); + + registrationSignature = messagePoint.scalar_mul(validatorPrivateKey); + pubkeyHash = BN254.hashG1Point(params.pubkeyG1); + + return (registrationSignature, pubkeyHash); + } + // With ECDSA key, he sign the hash confirming that the operator wants to be registered to a certain restaking service function _getOperatorSignature( uint256 _operatorPrivateKey, @@ -230,7 +260,7 @@ contract UniFiAVSManagerTest is UnitTestHelper { ValidatorDataExtended memory validatorData = avsManager.getValidator(blsPubKeyHashes[i]); assertEq(validatorData.eigenPod, address(mockEigenPodManager.getPod(podOwner))); assertEq(validatorData.operator, operator); - assertTrue(validatorData.backedByStake); + assertTrue(validatorData.backedByEigenPodStake); } } @@ -506,7 +536,7 @@ contract UniFiAVSManagerTest is UnitTestHelper { assertEq(avsManager.getDeregistrationDelay(), newDelay, "Deregistration delay should be updated"); } - function testGetValidator_BackedByStakeFalse() public { + function testGetValidator_backedByEigenPodStakeFalse() public { bytes32[] memory blsPubKeyHashes = new bytes32[](1); blsPubKeyHashes[0] = keccak256(abi.encodePacked("validator1")); @@ -524,7 +554,10 @@ contract UniFiAVSManagerTest is UnitTestHelper { ValidatorDataExtended memory validatorData = avsManager.getValidator(blsPubKeyHashes[0]); assertEq(validatorData.operator, operator); - assertFalse(validatorData.backedByStake, "backedByStake should be false when delegated to a different address"); + assertFalse( + validatorData.backedByEigenPodStake, + "backedByEigenPodStake should be false when delegated to a different address" + ); } function testSetOperatorCommitment() public { @@ -1022,4 +1055,169 @@ contract UniFiAVSManagerTest is UnitTestHelper { assertEq(restakedStrategies.length, 0, "Should return no restaked strategies for unregistered operator"); } + + function testRegisterValidatorsOptimistically() public { + _setupOperator(); + _registerOperator(); + + uint256 validatorPrivateKey1 = 123; + uint256 validatorPrivateKey2 = 456; + uint64 validatorIndex1 = 1; + uint64 validatorIndex2 = 2; + bytes32 salt1 = bytes32(uint256(1)); + bytes32 salt2 = bytes32(uint256(2)); + uint256 expiry = block.timestamp + 1 days; + + (BN254.G1Point memory signature1, bytes32 pubkeyHash1) = + _getValidatorSignature(validatorPrivateKey1, validatorIndex1, operator, salt1, expiry); + + (BN254.G1Point memory signature2, bytes32 pubkeyHash2) = + _getValidatorSignature(validatorPrivateKey2, validatorIndex2, operator, salt2, expiry); + + ValidatorRegistrationParams[] memory paramsArray = new ValidatorRegistrationParams[](2); + paramsArray[0] = ValidatorRegistrationParams({ + pubkeyG1: _generateBlsPubkeyParams(validatorPrivateKey1).pubkeyG1, + pubkeyG2: _generateBlsPubkeyParams(validatorPrivateKey1).pubkeyG2, + salt: salt1, + expiry: expiry, + index: validatorIndex1, + registrationSignature: signature1 + }); + paramsArray[1] = ValidatorRegistrationParams({ + pubkeyG1: _generateBlsPubkeyParams(validatorPrivateKey2).pubkeyG1, + pubkeyG2: _generateBlsPubkeyParams(validatorPrivateKey2).pubkeyG2, + salt: salt2, + expiry: expiry, + index: validatorIndex2, + registrationSignature: signature2 + }); + + vm.prank(operator); + avsManager.registerValidatorsOptimistically(paramsArray); + + OperatorDataExtended memory operatorData = avsManager.getOperator(operator); + assertEq(operatorData.validatorCount, 2, "Validator count should be 2"); + + ValidatorDataExtended memory validator1 = avsManager.getValidator(pubkeyHash1); + ValidatorDataExtended memory validator2 = avsManager.getValidator(pubkeyHash2); + + assertTrue(validator1.registered, "Validator 1 should be registered"); + assertTrue(validator2.registered, "Validator 2 should be registered"); + assertEq(validator1.operator, operator, "Validator 1 should be assigned to the correct operator"); + assertEq(validator2.operator, operator, "Validator 2 should be assigned to the correct operator"); + } + + function testRegisterValidatorsOptimistically_AlreadyRegistered() public { + _setupOperator(); + _registerOperator(); + + uint256 validatorPrivateKey = 123; + uint64 validatorIndex = 1; + bytes32 salt = bytes32(uint256(1)); + uint256 expiry = block.timestamp + 1 days; + + (BN254.G1Point memory signature, bytes32 pubkeyHash) = + _getValidatorSignature(validatorPrivateKey, validatorIndex, operator, salt, expiry); + + ValidatorRegistrationParams[] memory paramsArray = new ValidatorRegistrationParams[](1); + paramsArray[0] = ValidatorRegistrationParams({ + pubkeyG1: _generateBlsPubkeyParams(validatorPrivateKey).pubkeyG1, + pubkeyG2: _generateBlsPubkeyParams(validatorPrivateKey).pubkeyG2, + salt: salt, + expiry: expiry, + index: validatorIndex, + registrationSignature: signature + }); + + vm.prank(operator); + avsManager.registerValidatorsOptimistically(paramsArray); + + vm.prank(operator); + vm.expectRevert(IUniFiAVSManager.ValidatorAlreadyRegistered.selector); + avsManager.registerValidatorsOptimistically(paramsArray); + } + + function testVerifyValidatorSignatures() public { + _setupOperator(); + _registerOperator(); + + uint256 validatorPrivateKey = 123; + uint64 validatorIndex = 1; + bytes32 salt = bytes32(uint256(1)); + uint256 expiry = block.timestamp + 1 days; + + (BN254.G1Point memory signature, bytes32 pubkeyHash) = + _getValidatorSignature(validatorPrivateKey, validatorIndex, operator, salt, expiry); + + ValidatorRegistrationParams[] memory paramsArray = new ValidatorRegistrationParams[](1); + paramsArray[0] = ValidatorRegistrationParams({ + pubkeyG1: _generateBlsPubkeyParams(validatorPrivateKey).pubkeyG1, + pubkeyG2: _generateBlsPubkeyParams(validatorPrivateKey).pubkeyG2, + salt: salt, + expiry: expiry, + index: validatorIndex, + registrationSignature: signature + }); + + vm.prank(operator); + avsManager.registerValidatorsOptimistically(paramsArray); + + bytes32[] memory blsPubKeyHashes = new bytes32[](1); + blsPubKeyHashes[0] = pubkeyHash; + + avsManager.verifyValidatorSignatures(blsPubKeyHashes); + + ValidatorDataExtended memory validator = avsManager.getValidator(pubkeyHash); + assertTrue(validator.registered, "Validator should still be registered after verification"); + } + + function testVerifyValidatorSignatures_InvalidSignature() public { + _setupOperator(); + _registerOperator(); + + uint256 validatorPrivateKey = 123; + uint64 validatorIndex = 1; + bytes32 salt = bytes32(uint256(1)); + uint256 expiry = block.timestamp + 1 days; + + (BN254.G1Point memory validSignature, bytes32 pubkeyHash) = + _getValidatorSignature(validatorPrivateKey, validatorIndex, operator, salt, expiry); + + // Create an invalid signature by using a different private key + (BN254.G1Point memory invalidSignature,) = _getValidatorSignature( + 456, // Different private key + validatorIndex, + operator, + salt, + expiry + ); + + ValidatorRegistrationParams[] memory paramsArray = new ValidatorRegistrationParams[](1); + paramsArray[0] = ValidatorRegistrationParams({ + pubkeyG1: _generateBlsPubkeyParams(validatorPrivateKey).pubkeyG1, + pubkeyG2: _generateBlsPubkeyParams(validatorPrivateKey).pubkeyG2, + salt: salt, + expiry: expiry, + index: validatorIndex, + registrationSignature: invalidSignature // Use the invalid signature + }); + + vm.prank(operator); + avsManager.registerValidatorsOptimistically(paramsArray); + + bytes32[] memory blsPubKeyHashes = new bytes32[](1); + blsPubKeyHashes[0] = pubkeyHash; + + vm.expectEmit(true, true, false, false); + emit IUniFiAVSManager.ValidatorSlashed(operator, pubkeyHash); + + avsManager.verifyValidatorSignatures(blsPubKeyHashes); + + vm.roll(block.number + avsManager.getDeregistrationDelay() + 1); + ValidatorDataExtended memory validator = avsManager.getValidator(pubkeyHash); + assertFalse(validator.registered, "Validator should be deregistered after invalid signature verification"); + + OperatorDataExtended memory operatorData = avsManager.getOperator(operator); + assertEq(operatorData.validatorCount, 0, "Operator should have no validators after slashing"); + } } From c16672130ac4eb21a1e2dfa4cf5c7788d9fbec8b Mon Sep 17 00:00:00 2001 From: 0xwalid Date: Tue, 8 Oct 2024 02:34:24 -0700 Subject: [PATCH 02/22] adds verifyValidatorOnBeaconChain using EIP-4788 --- l1-contracts/src/UniFiAVSManager.sol | 111 +++++++++++++----- .../src/interfaces/IUniFiAVSManager.sol | 17 +++ l1-contracts/src/lib/BeaconChainHelperLib.sol | 110 ++++++++++++++--- 3 files changed, 193 insertions(+), 45 deletions(-) diff --git a/l1-contracts/src/UniFiAVSManager.sol b/l1-contracts/src/UniFiAVSManager.sol index 47661a4..de1e176 100644 --- a/l1-contracts/src/UniFiAVSManager.sol +++ b/l1-contracts/src/UniFiAVSManager.sol @@ -19,6 +19,8 @@ import { IEigenPod } from "eigenlayer/interfaces/IEigenPod.sol"; import { BN254 } from "eigenlayer-middleware/libraries/BN254.sol"; // Local Imports import { BLSSignatureCheckerLib } from "./lib/BLSSignatureCheckerLib.sol"; +import { BeaconChainHelperLib } from "./lib/BeaconChainHelperLib.sol"; + import { IUniFiAVSManager } from "./interfaces/IUniFiAVSManager.sol"; import { UniFiAVSManagerStorage } from "./UniFiAVSManagerStorage.sol"; import "./structs/ValidatorData.sol"; @@ -309,15 +311,6 @@ contract UniFiAVSManager is address operator = validator.operator; ValidatorRegistrationData memory validatorRegistrationData = $.validatorRegistrations[blsPubKeyHash]; - if (validator.index == 0) { - revert ValidatorNotFound(); - } - - // Check if the validator is already deregistered - if (validator.registeredUntil != type(uint64).max) { - revert ValidatorAlreadyDeregistered(); - } - // Calculate the hash using EIP-712 BN254.G1Point memory messageHash = blsMessageHash({ typeHash: VALIDATOR_REGISTRATION_TYPEHASH, @@ -336,26 +329,92 @@ contract UniFiAVSManager is ); if (!isValid) { - // Slash the operator - $.slashedOperators[operator].push( - InvalidValidator({ slashingBeneficiary: msg.sender, blsPubKeyHash: blsPubKeyHash }) - ); + _slashAndDeregisterValidator(blsPubKeyHash); + } + } + } + + /** + * @inheritdoc IUniFiAVSManager + * @dev Restricted in this context is like `whenNotPaused` modifier from Pausable.sol + */ + function verifyValidatorOnBeaconChain( + bytes32[] calldata blsPubKeyHashes, + BeaconChainHelperLib.InclusionProof[] calldata proofs + ) external restricted { + if (blsPubKeyHashes.length != proofs.length) { + revert InvalidArrayLengths(); + } + + UniFiAVSStorage storage $ = _getUniFiAVSManagerStorage(); + + for (uint256 i = 0; i < blsPubKeyHashes.length; i++) { + bytes32 blsPubKeyHash = blsPubKeyHashes[i]; + BeaconChainHelperLib.InclusionProof memory proof = proofs[i]; - // Update the registeredUntil field to deregister the validator - validator.registeredUntil = uint64(block.number) + $.deregistrationDelay; + (, bytes32 beaconBlockRoot) = BeaconChainHelperLib.getRootFromTimestamp(block.timestamp - 12); - // Emit the ValidatorDeregistered event - emit ValidatorDeregistered({ operator: operator, blsPubKeyHash: blsPubKeyHash }); + // Verify the validator using BeaconChainHelperLib + bool isValid = BeaconChainHelperLib.verifyValidator(blsPubKeyHash, beaconBlockRoot, proof); - emit ValidatorSlashed(operator, blsPubKeyHash); + if (isValid) { + ValidatorData storage validator = $.validators[blsPubKeyHash]; + uint64 validatorIndex = validator.index; - // Decrement the operator's validator count - OperatorData storage operatorData = $.operators[operator]; - operatorData.validatorCount -= 1; + // If verification succeeds, check if the stored index matches the proof + if (validatorIndex != 0 && validatorIndex != proof.validatorIndex) { + _slashAndDeregisterValidator(blsPubKeyHash); + delete $.validatorIndexes[proof.validatorIndex]; // free up the index + } + + if (validatorIndex == 0) { + bytes32 blsPubKeyHashFromIndex = $.validatorIndexes[proof.validatorIndex]; + validator = $.validators[blsPubKeyHashFromIndex]; + + if (validator.index != 0 && blsPubKeyHashFromIndex != blsPubKeyHash) { + _slashAndDeregisterValidator(blsPubKeyHashFromIndex); + delete $.validatorIndexes[proof.validatorIndex]; // free up the index + } else { + revert ValidatorNotFound(); + } + } + } else { + revert InvalidValidatorProof(); } } } + // Helper function to slash and deregister a validator + function _slashAndDeregisterValidator(bytes32 blsPubKeyHash) internal { + UniFiAVSStorage storage $ = _getUniFiAVSManagerStorage(); + ValidatorData storage validator = $.validators[blsPubKeyHash]; + address operator = validator.operator; + + if (validator.index == 0) { + revert ValidatorNotFound(); + } + + if (validator.registeredUntil <= block.number) { + revert ValidatorAlreadyDeregistered(); + } + + $.slashedOperators[operator].push( + InvalidValidator({ slashingBeneficiary: msg.sender, blsPubKeyHash: blsPubKeyHash }) + ); + + // Update the registeredUntil field to deregister the validator immediately + validator.registeredUntil = uint64(block.number); + + // Emit the ValidatorDeregistered event + emit ValidatorDeregistered({ operator: operator, blsPubKeyHash: blsPubKeyHash }); + + emit ValidatorSlashed(operator, blsPubKeyHash); + + // Decrement the operator's validator count + OperatorData storage operatorData = $.operators[operator]; + operatorData.validatorCount -= 1; + } + /** * @inheritdoc IUniFiAVSManager * @dev Restricted in this context is like `whenNotPaused` modifier from Pausable.sol @@ -653,15 +712,9 @@ contract UniFiAVSManager is } /** - * @notice Calculate the BLS message hash using EIP-712 - * @param typeHash The type hash for the registration - * @param operator The operator's address - * @param salt The unique salt - * @param expiry The expiry time - * @param index The index of the validator - * @return The BLS message hash as a G1 point + * @inheritdoc IUniFiAVSManager */ - function blsMessageHash(bytes32 typeHash, address operator, bytes32 salt, uint256 expiry, uint64 index) + function blsMessageHash(bytes32 typeHash, address operator, bytes32 salt, uint256 expiry, uint256 index) public view returns (BN254.G1Point memory) diff --git a/l1-contracts/src/interfaces/IUniFiAVSManager.sol b/l1-contracts/src/interfaces/IUniFiAVSManager.sol index 92767b3..40d2fd2 100644 --- a/l1-contracts/src/interfaces/IUniFiAVSManager.sol +++ b/l1-contracts/src/interfaces/IUniFiAVSManager.sol @@ -9,6 +9,7 @@ import { BN254 } from "eigenlayer-middleware/libraries/BN254.sol"; import { IAVSDirectoryExtended } from "../interfaces/EigenLayer/IAVSDirectoryExtended.sol"; import "../structs/ValidatorData.sol"; import "../structs/OperatorData.sol"; +import { BeaconChainHelperLib } from "../lib/BeaconChainHelperLib.sol"; /** * @title IUniFiAVSManager @@ -79,6 +80,12 @@ interface IUniFiAVSManager { /// @notice Thrown when a signature is expired error SignatureExpired(); + /// @notice Thrown when the lengths of the input arrays are not equal + error InvalidArrayLengths(); + + /// @notice Thrown when a validator proof is invalid + error InvalidValidatorProof(); + /** * @notice Emitted when a new operator is registered in the UniFi AVS. * @param operator The address of the registered operator. @@ -276,6 +283,16 @@ interface IUniFiAVSManager { */ function verifyValidatorSignatures(bytes32[] calldata blsPubKeyHashes) external; + /** + * @notice Verifies the validator's presence on the beacon chain. + * @param blsPubKeyHashes The BLS public key hashes of the validators. + * @param proofs The inclusion proofs for each validator. + */ + function verifyValidatorOnBeaconChain( + bytes32[] calldata blsPubKeyHashes, + BeaconChainHelperLib.InclusionProof[] calldata proofs + ) external; + /** * @notice Retrieves information about a specific operator. * @param operator The address of the operator. diff --git a/l1-contracts/src/lib/BeaconChainHelperLib.sol b/l1-contracts/src/lib/BeaconChainHelperLib.sol index 3cc765a..9739782 100644 --- a/l1-contracts/src/lib/BeaconChainHelperLib.sol +++ b/l1-contracts/src/lib/BeaconChainHelperLib.sol @@ -1,9 +1,101 @@ // SPDX-License-Identifier: GPL-3.0 pragma solidity >=0.8.0 <0.9.0; +import { MerkleUtils } from "./MerkleUtils.sol"; + library BeaconChainHelperLib { address internal constant _BEACON_ROOT_CONTRACT = 0x000F3df6D732807Ef1319fB7B8bB8522d0Beac02; + struct InclusionProof { + // `Chunks` of the SSZ encoded validator + bytes32[8] validator; + // Index of the validator in the beacon state validator list + uint256 validatorIndex; + // Proof of inclusion of validator in beacon state validator list + bytes32[] validatorProof; + // Root of the validator list in the beacon state + bytes32 validatorsRoot; + // Proof of inclusion of validator list in the beacon state + bytes32[] beaconStateProof; + // Root of the beacon state + bytes32 beaconStateRoot; + // Proof of inclusion of beacon state in the beacon block + bytes32[] beaconBlockProofForState; + // Proof of inclusion of the validator index in the beacon block + bytes32[] beaconBlockProofForProposerIndex; + } + + /// @dev The validator pub key failed verification against the pub key hash tree root in the validator chunks + error InvalidValidatorBLSPubKey(); + /// @dev The proof that the validator is a part of the validator list is invalid. + error ValidatorProofFailed(); + /// @dev The proof that the validator list is a part of the beacon state is invalid. + error BeaconStateProofFailed(); + /// @dev The proof that the beacon state is a part of the beacon block is invalid. + error BeaconBlockProofForStateFailed(); + /// @dev The proof that the actual validator index is a part of the beacon is invalid. + error BeaconBlockProofForProposerIndex(); + + function verifyValidator( + bytes32 validatorBLSPubKeyHash, + bytes32 beaconBlockRoot, + InclusionProof memory inclusionProof + ) internal pure returns (bool) { + // Validator's BLS public key is verified against the hash tree root within Validator chunks + if (validatorBLSPubKeyHash != inclusionProof.validator[0]) { + revert InvalidValidatorBLSPubKey(); + } + + // Validator is verified against the validator list in the beacon state + bytes32 validatorHashTreeRoot = MerkleUtils.merkleize(inclusionProof.validator); + if ( + !MerkleUtils.verifyProof( + inclusionProof.validatorProof, + inclusionProof.validatorsRoot, + validatorHashTreeRoot, + inclusionProof.validatorIndex + ) + ) { + // Revert if the proof that the expected validator is a part of the validator + // list in beacon state fails + return false; + } + + if ( + !MerkleUtils.verifyProof( + inclusionProof.beaconStateProof, inclusionProof.beaconStateRoot, inclusionProof.validatorsRoot, 11 + ) + ) { + // Revert if the proof that the validator list is a part of the beacon state fails + return false; + } + + // Beacon state is verified against the beacon block + if ( + !MerkleUtils.verifyProof( + inclusionProof.beaconBlockProofForState, beaconBlockRoot, inclusionProof.beaconStateRoot, 3 + ) + ) { + // Revert if the proof for the beacon state being a part of the beacon block fails + return false; + } + + // Validator index is verified against the beacon block + if ( + !MerkleUtils.verifyProof( + inclusionProof.beaconBlockProofForProposerIndex, + beaconBlockRoot, + MerkleUtils.toLittleEndian(inclusionProof.validatorIndex), + 1 + ) + ) { + // Revert if the proof that the proposer index is a part of the beacon block fails + return false; + } + + return true; + } + function verifyProposerAt(uint256 timestamp, uint256 proposerIndex, bytes32[2] memory proof) internal returns (bool) @@ -16,8 +108,8 @@ library BeaconChainHelperLib { bytes32 slotAndProposerIndexNode = sha256( abi.encodePacked( - abi.encodePacked(to_little_endian_64(uint64(slot)), bytes24(0)), - abi.encodePacked(to_little_endian_64(uint64(proposerIndex)), bytes24(0)) + abi.encodePacked(MerkleUtils.to_little_endian_64(uint64(slot)), bytes24(0)), + abi.encodePacked(MerkleUtils.to_little_endian_64(uint64(proposerIndex)), bytes24(0)) ) ); @@ -28,20 +120,6 @@ library BeaconChainHelperLib { return root == beaconRootFromChain; } - function to_little_endian_64(uint64 value) internal pure returns (bytes memory ret) { - ret = new bytes(8); - bytes8 bytesValue = bytes8(value); - // Byteswapping during copying to bytes. - ret[0] = bytesValue[7]; - ret[1] = bytesValue[6]; - ret[2] = bytesValue[5]; - ret[3] = bytesValue[4]; - ret[4] = bytesValue[3]; - ret[5] = bytesValue[2]; - ret[6] = bytesValue[1]; - ret[7] = bytesValue[0]; - } - function getRootFromTimestamp(uint256 timestamp) internal returns (bool, bytes32) { (bool ret, bytes memory data) = _BEACON_ROOT_CONTRACT.call(bytes.concat(bytes32(timestamp))); return (ret, bytes32(data)); From 9cb6930a738d2dd94738fd6ff7a059725a8e43da Mon Sep 17 00:00:00 2001 From: 0xwalid Date: Tue, 8 Oct 2024 02:36:23 -0700 Subject: [PATCH 03/22] adds missing vlaidator index mapping --- l1-contracts/src/UniFiAVSManager.sol | 2 ++ 1 file changed, 2 insertions(+) diff --git a/l1-contracts/src/UniFiAVSManager.sol b/l1-contracts/src/UniFiAVSManager.sol index de1e176..91f10dd 100644 --- a/l1-contracts/src/UniFiAVSManager.sol +++ b/l1-contracts/src/UniFiAVSManager.sol @@ -282,6 +282,8 @@ contract UniFiAVSManager is expiry: params.expiry }); + $.validatorIndexes[params.index] = blsPubKeyHash; + emit ValidatorRegistered({ podOwner: address(0), operator: msg.sender, From 78dd1debc7984a6dcd283c33ef81302db2864f7b Mon Sep 17 00:00:00 2001 From: 0xwalid Date: Tue, 8 Oct 2024 02:37:38 -0700 Subject: [PATCH 04/22] minor fix --- l1-contracts/src/UniFiAVSManager.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/l1-contracts/src/UniFiAVSManager.sol b/l1-contracts/src/UniFiAVSManager.sol index 91f10dd..caa785a 100644 --- a/l1-contracts/src/UniFiAVSManager.sol +++ b/l1-contracts/src/UniFiAVSManager.sol @@ -366,7 +366,7 @@ contract UniFiAVSManager is // If verification succeeds, check if the stored index matches the proof if (validatorIndex != 0 && validatorIndex != proof.validatorIndex) { _slashAndDeregisterValidator(blsPubKeyHash); - delete $.validatorIndexes[proof.validatorIndex]; // free up the index + delete $.validatorIndexes[validatorIndex]; // free up the index } if (validatorIndex == 0) { From 1a80ef7df36cd7894d8232e4d194b991bd9dcdcf Mon Sep 17 00:00:00 2001 From: 0xwalid Date: Tue, 8 Oct 2024 02:50:28 -0700 Subject: [PATCH 05/22] add merkle utils lib --- l1-contracts/src/lib/MerkleUtils.sol | 86 ++++++++++++++++++++++++++++ 1 file changed, 86 insertions(+) create mode 100644 l1-contracts/src/lib/MerkleUtils.sol diff --git a/l1-contracts/src/lib/MerkleUtils.sol b/l1-contracts/src/lib/MerkleUtils.sol new file mode 100644 index 0000000..defdbc4 --- /dev/null +++ b/l1-contracts/src/lib/MerkleUtils.sol @@ -0,0 +1,86 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity >=0.8.0 <0.9.0; + +library MerkleUtils { + uint256 internal constant CHUNKS_LENGTH = 8; + uint256 internal constant TMP_LENGTH = 4; + + function hash(bytes32 a, bytes32 b) internal pure returns (bytes32) { + return sha256(abi.encodePacked(a, b)); + } + + function merkleize(bytes32[CHUNKS_LENGTH] memory chunks) internal pure returns (bytes32) { + bytes32[] memory tmp = new bytes32[](TMP_LENGTH); + + for (uint256 i; i < CHUNKS_LENGTH; ++i) { + merge(tmp, i, chunks[i]); + } + + return tmp[TMP_LENGTH - 1]; + } + + function merge(bytes32[] memory tmp, uint256 index, bytes32 chunk) internal pure { + bytes32 h = chunk; + uint256 j = 0; + while (true) { + if (index & 1 << j == 0) { + break; + } else { + h = hash(tmp[j], h); + } + j += 1; + } + tmp[j] = h; + } + + function verifyProof(bytes32[] memory proof, bytes32 root, bytes32 leaf, uint256 leafIndex) + internal + pure + returns (bool) + { + bytes32 h = leaf; + uint256 index = leafIndex; + + for (uint256 i = 0; i < proof.length; i++) { + bytes32 proofElement = proof[i]; + + if (index % 2 == 0) { + h = sha256(bytes.concat(h, proofElement)); + } else { + h = sha256(bytes.concat(proofElement, h)); + } + + index = index / 2; + } + + return h == root; + } + + function toLittleEndian(uint256 n) internal pure returns (bytes32) { + uint256 v = n; + v = ((v & 0xFF00FF00FF00FF00FF00FF00FF00FF00FF00FF00FF00FF00FF00FF00FF00FF00) >> 8) + | ((v & 0x00FF00FF00FF00FF00FF00FF00FF00FF00FF00FF00FF00FF00FF00FF00FF00FF) << 8); + v = ((v & 0xFFFF0000FFFF0000FFFF0000FFFF0000FFFF0000FFFF0000FFFF0000FFFF0000) >> 16) + | ((v & 0x0000FFFF0000FFFF0000FFFF0000FFFF0000FFFF0000FFFF0000FFFF0000FFFF) << 16); + v = ((v & 0xFFFFFFFF00000000FFFFFFFF00000000FFFFFFFF00000000FFFFFFFF00000000) >> 32) + | ((v & 0x00000000FFFFFFFF00000000FFFFFFFF00000000FFFFFFFF00000000FFFFFFFF) << 32); + v = ((v & 0xFFFFFFFFFFFFFFFF0000000000000000FFFFFFFFFFFFFFFF0000000000000000) >> 64) + | ((v & 0x0000000000000000FFFFFFFFFFFFFFFF0000000000000000FFFFFFFFFFFFFFFF) << 64); + v = (v >> 128) | (v << 128); + return bytes32(v); + } + + function to_little_endian_64(uint64 value) internal pure returns (bytes memory ret) { + ret = new bytes(8); + bytes8 bytesValue = bytes8(value); + // Byteswapping during copying to bytes. + ret[0] = bytesValue[7]; + ret[1] = bytesValue[6]; + ret[2] = bytesValue[5]; + ret[3] = bytesValue[4]; + ret[4] = bytesValue[3]; + ret[5] = bytesValue[2]; + ret[6] = bytesValue[1]; + ret[7] = bytesValue[0]; + } +} From 37f7a26d51653ad13d48693bf7f09dab7caffb93 Mon Sep 17 00:00:00 2001 From: 0xwalid Date: Tue, 8 Oct 2024 13:18:45 -0700 Subject: [PATCH 06/22] fix bls message hash --- l1-contracts/src/interfaces/IUniFiAVSManager.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/l1-contracts/src/interfaces/IUniFiAVSManager.sol b/l1-contracts/src/interfaces/IUniFiAVSManager.sol index 40d2fd2..0e4f9a7 100644 --- a/l1-contracts/src/interfaces/IUniFiAVSManager.sol +++ b/l1-contracts/src/interfaces/IUniFiAVSManager.sol @@ -385,7 +385,7 @@ interface IUniFiAVSManager { * @param index The index for the message. * @return BN254.G1Point The BLS message hash. */ - function blsMessageHash(bytes32 typeHash, address operator, uint64 salt, uint64 expiry, uint256 index) + function blsMessageHash(bytes32 typeHash, address operator, bytes32 salt, uint256 expiry, uint256 index) external view returns (BN254.G1Point memory); From fc3315334d6b8ae89ac648b86a83f98b9be0c740 Mon Sep 17 00:00:00 2001 From: 0xwalid Date: Tue, 8 Oct 2024 23:58:53 -0700 Subject: [PATCH 07/22] adds beacon chain validator inclusion verification tests --- l1-contracts/script/SetupAccess.s.sol | 3 +- l1-contracts/src/UniFiAVSManager.sol | 11 +- .../src/lib/BLSSignatureCheckerLib.sol | 40 ++++ l1-contracts/src/structs/ValidatorData.sol | 1 + l1-contracts/test/UniFiAVSManager.t.sol | 220 +++++++++++++++++- l1-contracts/test/fixtures/BeaconProofs.sol | 131 +++++++++++ 6 files changed, 389 insertions(+), 17 deletions(-) create mode 100644 l1-contracts/test/fixtures/BeaconProofs.sol diff --git a/l1-contracts/script/SetupAccess.s.sol b/l1-contracts/script/SetupAccess.s.sol index 03fa607..1ebb9b4 100644 --- a/l1-contracts/script/SetupAccess.s.sol +++ b/l1-contracts/script/SetupAccess.s.sol @@ -88,7 +88,7 @@ contract SetupAccess is BaseScript { ); bytes4[] memory publicSelectors = new bytes4[](0); - publicSelectors = new bytes4[](10); + publicSelectors = new bytes4[](11); publicSelectors[0] = UniFiAVSManager.registerOperator.selector; publicSelectors[1] = UniFiAVSManager.registerValidators.selector; publicSelectors[2] = UniFiAVSManager.startDeregisterOperator.selector; @@ -99,6 +99,7 @@ contract SetupAccess is BaseScript { publicSelectors[7] = UniFiAVSManager.registerOperatorWithCommitment.selector; publicSelectors[8] = UniFiAVSManager.registerValidatorsOptimistically.selector; publicSelectors[9] = UniFiAVSManager.verifyValidatorSignatures.selector; + publicSelectors[10] = UniFiAVSManager.verifyValidatorOnBeaconChain.selector; calldatas[1] = abi.encodeWithSelector( AccessManager.setTargetFunctionRole.selector, diff --git a/l1-contracts/src/UniFiAVSManager.sol b/l1-contracts/src/UniFiAVSManager.sol index caa785a..35d7ca7 100644 --- a/l1-contracts/src/UniFiAVSManager.sol +++ b/l1-contracts/src/UniFiAVSManager.sol @@ -26,6 +26,8 @@ import { UniFiAVSManagerStorage } from "./UniFiAVSManagerStorage.sol"; import "./structs/ValidatorData.sol"; import "./structs/OperatorData.sol"; +import "forge-std/console.sol"; + contract UniFiAVSManager is UniFiAVSManagerStorage, IUniFiAVSManager, @@ -246,7 +248,7 @@ contract UniFiAVSManager is ValidatorRegistrationParams memory params = paramsArray[i]; // Derive the BLS public key hash from pubkeyG1 - bytes32 blsPubKeyHash = BN254.hashG1Point(params.pubkeyG1); + bytes32 blsPubKeyHash = params.blsPubKeyHash; ValidatorData storage existingValidator = $.validators[blsPubKeyHash]; ValidatorRegistrationData storage validatorRegistrationData = $.validatorRegistrations[blsPubKeyHash]; @@ -353,20 +355,17 @@ contract UniFiAVSManager is for (uint256 i = 0; i < blsPubKeyHashes.length; i++) { bytes32 blsPubKeyHash = blsPubKeyHashes[i]; BeaconChainHelperLib.InclusionProof memory proof = proofs[i]; - (, bytes32 beaconBlockRoot) = BeaconChainHelperLib.getRootFromTimestamp(block.timestamp - 12); - // Verify the validator using BeaconChainHelperLib bool isValid = BeaconChainHelperLib.verifyValidator(blsPubKeyHash, beaconBlockRoot, proof); if (isValid) { ValidatorData storage validator = $.validators[blsPubKeyHash]; uint64 validatorIndex = validator.index; - // If verification succeeds, check if the stored index matches the proof if (validatorIndex != 0 && validatorIndex != proof.validatorIndex) { _slashAndDeregisterValidator(blsPubKeyHash); - delete $.validatorIndexes[validatorIndex]; // free up the index + delete $.validatorIndexes[validatorIndex]; } if (validatorIndex == 0) { @@ -375,7 +374,7 @@ contract UniFiAVSManager is if (validator.index != 0 && blsPubKeyHashFromIndex != blsPubKeyHash) { _slashAndDeregisterValidator(blsPubKeyHashFromIndex); - delete $.validatorIndexes[proof.validatorIndex]; // free up the index + delete $.validatorIndexes[proof.validatorIndex]; } else { revert ValidatorNotFound(); } diff --git a/l1-contracts/src/lib/BLSSignatureCheckerLib.sol b/l1-contracts/src/lib/BLSSignatureCheckerLib.sol index 557d954..273f4f8 100644 --- a/l1-contracts/src/lib/BLSSignatureCheckerLib.sol +++ b/l1-contracts/src/lib/BLSSignatureCheckerLib.sol @@ -35,4 +35,44 @@ library BLSSignatureCheckerLib { pubkeyG2 ); } + + function bytesToG1Point(bytes memory pubkey) internal pure returns (BN254.G1Point memory) { + // require(pubkey.length == 48, "Invalid public key length"); + + // Extract X and Y coordinates + uint256 x; + uint256 y; + + assembly { + x := mload(add(pubkey, 32)) + y := mload(add(pubkey, 64)) + } + + // Ensure the most significant bit of y is 0 (positive y-coordinate) + y = y & ((1 << 255) - 1); + + return BN254.G1Point(x, y); + } + + function g1PointToBytes(BN254.G1Point memory point) internal pure returns (bytes memory) { + bytes memory result = new bytes(48); + + assembly { + // Store X coordinate + mstore(add(result, 32), mload(point)) + + // Store Y coordinate + // Set the most significant bit to 1 if Y is odd, 0 if Y is even + let y := mload(add(point, 32)) + let yMod2 := mod(y, 2) + y := or(and(y, not(shl(255, 1))), shl(255, yMod2)) + mstore(add(result, 64), y) + } + + return result; + } + + function hashG1Point(BN254.G1Point memory pk) internal pure returns (bytes32 hashedG1) { + return sha256(abi.encodePacked(g1PointToBytes(pk), bytes16(0))); + } } diff --git a/l1-contracts/src/structs/ValidatorData.sol b/l1-contracts/src/structs/ValidatorData.sol index 174e810..b8256e9 100644 --- a/l1-contracts/src/structs/ValidatorData.sol +++ b/l1-contracts/src/structs/ValidatorData.sol @@ -61,6 +61,7 @@ struct ValidatorRegistrationData { * @notice Struct to store parameters for validator registration. */ struct ValidatorRegistrationParams { + bytes32 blsPubKeyHash; BN254.G1Point registrationSignature; BN254.G1Point pubkeyG1; BN254.G2Point pubkeyG2; diff --git a/l1-contracts/test/UniFiAVSManager.t.sol b/l1-contracts/test/UniFiAVSManager.t.sol index ff40b1b..b368d54 100644 --- a/l1-contracts/test/UniFiAVSManager.t.sol +++ b/l1-contracts/test/UniFiAVSManager.t.sol @@ -14,6 +14,7 @@ import "eigenlayer-middleware/libraries/BN254.sol"; import "eigenlayer-middleware/interfaces/IBLSApkRegistry.sol"; import { Strings } from "@openzeppelin/contracts/utils/Strings.sol"; import { UnitTestHelper } from "../test/helpers/UnitTestHelper.sol"; +import { BeaconProofs } from "./fixtures/BeaconProofs.sol"; contract UniFiAVSManagerTest is UnitTestHelper { using BN254 for BN254.G1Point; @@ -1075,17 +1076,25 @@ contract UniFiAVSManagerTest is UnitTestHelper { _getValidatorSignature(validatorPrivateKey2, validatorIndex2, operator, salt2, expiry); ValidatorRegistrationParams[] memory paramsArray = new ValidatorRegistrationParams[](2); + + BN254.G1Point memory pubkeyG1_1 = _generateBlsPubkeyParams(validatorPrivateKey1).pubkeyG1; + BN254.G2Point memory pubkeyG2_1 = _generateBlsPubkeyParams(validatorPrivateKey1).pubkeyG2; paramsArray[0] = ValidatorRegistrationParams({ - pubkeyG1: _generateBlsPubkeyParams(validatorPrivateKey1).pubkeyG1, - pubkeyG2: _generateBlsPubkeyParams(validatorPrivateKey1).pubkeyG2, + blsPubKeyHash: pubkeyHash1, + pubkeyG1: pubkeyG1_1, + pubkeyG2: pubkeyG2_1, salt: salt1, expiry: expiry, index: validatorIndex1, registrationSignature: signature1 }); + + BN254.G1Point memory pubkeyG1_2 = _generateBlsPubkeyParams(validatorPrivateKey2).pubkeyG1; + BN254.G2Point memory pubkeyG2_2 = _generateBlsPubkeyParams(validatorPrivateKey2).pubkeyG2; paramsArray[1] = ValidatorRegistrationParams({ - pubkeyG1: _generateBlsPubkeyParams(validatorPrivateKey2).pubkeyG1, - pubkeyG2: _generateBlsPubkeyParams(validatorPrivateKey2).pubkeyG2, + blsPubKeyHash: pubkeyHash2, + pubkeyG1: pubkeyG1_2, + pubkeyG2: pubkeyG2_2, salt: salt2, expiry: expiry, index: validatorIndex2, @@ -1119,10 +1128,14 @@ contract UniFiAVSManagerTest is UnitTestHelper { (BN254.G1Point memory signature, bytes32 pubkeyHash) = _getValidatorSignature(validatorPrivateKey, validatorIndex, operator, salt, expiry); + BN254.G1Point memory pubkeyG1 = _generateBlsPubkeyParams(validatorPrivateKey).pubkeyG1; + BN254.G2Point memory pubkeyG2 = _generateBlsPubkeyParams(validatorPrivateKey).pubkeyG2; + ValidatorRegistrationParams[] memory paramsArray = new ValidatorRegistrationParams[](1); paramsArray[0] = ValidatorRegistrationParams({ - pubkeyG1: _generateBlsPubkeyParams(validatorPrivateKey).pubkeyG1, - pubkeyG2: _generateBlsPubkeyParams(validatorPrivateKey).pubkeyG2, + blsPubKeyHash: pubkeyHash, + pubkeyG1: pubkeyG1, + pubkeyG2: pubkeyG2, salt: salt, expiry: expiry, index: validatorIndex, @@ -1149,10 +1162,14 @@ contract UniFiAVSManagerTest is UnitTestHelper { (BN254.G1Point memory signature, bytes32 pubkeyHash) = _getValidatorSignature(validatorPrivateKey, validatorIndex, operator, salt, expiry); + BN254.G1Point memory pubkeyG1 = _generateBlsPubkeyParams(validatorPrivateKey).pubkeyG1; + BN254.G2Point memory pubkeyG2 = _generateBlsPubkeyParams(validatorPrivateKey).pubkeyG2; + ValidatorRegistrationParams[] memory paramsArray = new ValidatorRegistrationParams[](1); paramsArray[0] = ValidatorRegistrationParams({ - pubkeyG1: _generateBlsPubkeyParams(validatorPrivateKey).pubkeyG1, - pubkeyG2: _generateBlsPubkeyParams(validatorPrivateKey).pubkeyG2, + blsPubKeyHash: pubkeyHash, + pubkeyG1: pubkeyG1, + pubkeyG2: pubkeyG2, salt: salt, expiry: expiry, index: validatorIndex, @@ -1192,10 +1209,14 @@ contract UniFiAVSManagerTest is UnitTestHelper { expiry ); + BN254.G1Point memory pubkeyG1 = _generateBlsPubkeyParams(validatorPrivateKey).pubkeyG1; + BN254.G2Point memory pubkeyG2 = _generateBlsPubkeyParams(validatorPrivateKey).pubkeyG2; + ValidatorRegistrationParams[] memory paramsArray = new ValidatorRegistrationParams[](1); paramsArray[0] = ValidatorRegistrationParams({ - pubkeyG1: _generateBlsPubkeyParams(validatorPrivateKey).pubkeyG1, - pubkeyG2: _generateBlsPubkeyParams(validatorPrivateKey).pubkeyG2, + blsPubKeyHash: pubkeyHash, + pubkeyG1: pubkeyG1, + pubkeyG2: pubkeyG2, salt: salt, expiry: expiry, index: validatorIndex, @@ -1220,4 +1241,183 @@ contract UniFiAVSManagerTest is UnitTestHelper { OperatorDataExtended memory operatorData = avsManager.getOperator(operator); assertEq(operatorData.validatorCount, 0, "Operator should have no validators after slashing"); } + + function testVerifyValidatorOnBeaconChainValidProofAndValidator() public { + _setupOperator(); + _registerOperator(); + vm.warp(block.timestamp + 50); + // Register the validator optimistically + ValidatorRegistrationParams[] memory paramsArray = new ValidatorRegistrationParams[](1); + bytes32 pubkeyHash = sha256(abi.encodePacked(BeaconProofs.validator(), bytes16(0))); + + paramsArray[0] = ValidatorRegistrationParams({ + blsPubKeyHash: pubkeyHash, + pubkeyG1: _generateBlsPubkeyParams(123).pubkeyG1, + pubkeyG2: _generateBlsPubkeyParams(123).pubkeyG2, // Use a dummy value for pubkeyG2 + salt: bytes32(uint256(1)), + expiry: block.timestamp + 1 days, + index: uint64(BeaconProofs.validatorIndex()), + registrationSignature: _generateBlsPubkeyParams(123).pubkeyG1 // Use a dummy value for the signature + }); + + vm.prank(operator); + avsManager.registerValidatorsOptimistically(paramsArray); + + // Prepare the data for verification + bytes32[] memory blsPubKeyHashes = new bytes32[](1); + blsPubKeyHashes[0] = pubkeyHash; + + BeaconChainHelperLib.InclusionProof[] memory proofs = new BeaconChainHelperLib.InclusionProof[](1); + proofs[0] = BeaconProofs.eip4788ValidatorInclusionProof(); + // Mock the getRootFromTimestamp function to return the expected beacon block root + vm.mockCall( + address(0x000F3df6D732807Ef1319fB7B8bB8522d0Beac02), + hex"00000000", + abi.encode(BeaconProofs.beaconBlockRoot()) + ); + + // Verify the validator on the beacon chain + avsManager.verifyValidatorOnBeaconChain(blsPubKeyHashes, proofs); + + // Check that the validator is still registered + ValidatorDataExtended memory validator = avsManager.getValidator(blsPubKeyHashes[0]); + assertTrue(validator.registered, "Validator should still be registered after successful verification"); + } + + function testVerifyValidatorOnBeaconChainValidProofAndInvalidValidatorIndex() public { + _setupOperator(); + _registerOperator(); + vm.warp(block.timestamp + 50); + // Register the validator optimistically + ValidatorRegistrationParams[] memory paramsArray = new ValidatorRegistrationParams[](1); + bytes32 pubkeyHash = sha256(abi.encodePacked(BeaconProofs.validator(), bytes16(0))); + + paramsArray[0] = ValidatorRegistrationParams({ + blsPubKeyHash: pubkeyHash, + pubkeyG1: _generateBlsPubkeyParams(123).pubkeyG1, + pubkeyG2: _generateBlsPubkeyParams(123).pubkeyG2, // Use a dummy value for pubkeyG2 + salt: bytes32(uint256(1)), + expiry: block.timestamp + 1 days, + index: 1, // Use a different index + registrationSignature: _generateBlsPubkeyParams(123).pubkeyG1 // Use a dummy value for the signature + }); + + vm.prank(operator); + avsManager.registerValidatorsOptimistically(paramsArray); + + // Prepare the data for verification + bytes32[] memory blsPubKeyHashes = new bytes32[](1); + blsPubKeyHashes[0] = pubkeyHash; + + BeaconChainHelperLib.InclusionProof[] memory proofs = new BeaconChainHelperLib.InclusionProof[](1); + proofs[0] = BeaconProofs.eip4788ValidatorInclusionProof(); + // Mock the getRootFromTimestamp function to return the expected beacon block root + vm.mockCall( + address(0x000F3df6D732807Ef1319fB7B8bB8522d0Beac02), + hex"00000000", + abi.encode(BeaconProofs.beaconBlockRoot()) + ); + + // Verify the validator on the beacon chain + avsManager.verifyValidatorOnBeaconChain(blsPubKeyHashes, proofs); + + // Check that the validator is still registered + ValidatorDataExtended memory validator = avsManager.getValidator(blsPubKeyHashes[0]); + assertFalse(validator.registered, "Validator should be deregistered after slashing"); + + OperatorDataExtended memory operatorData = avsManager.getOperator(operator); + assertEq(operatorData.validatorCount, 0, "Operator should have no validators after slashing"); + } + + function testVerifyValidatorOnBeaconChainValidProofAndInvalidValidatorPubkey() public { + _setupOperator(); + _registerOperator(); + vm.warp(block.timestamp + 50); + // Register the validator optimistically + ValidatorRegistrationParams[] memory paramsArray = new ValidatorRegistrationParams[](1); + bytes32 pubkeyHash = sha256(abi.encodePacked(BeaconProofs.validator(), bytes16(0))); + + paramsArray[0] = ValidatorRegistrationParams({ + blsPubKeyHash: bytes32(uint256(1234)), + pubkeyG1: _generateBlsPubkeyParams(123).pubkeyG1, + pubkeyG2: _generateBlsPubkeyParams(123).pubkeyG2, // Use a dummy value for pubkeyG2 + salt: bytes32(uint256(1)), + expiry: block.timestamp + 1 days, + index: uint64(BeaconProofs.validatorIndex()), + registrationSignature: _generateBlsPubkeyParams(123).pubkeyG1 // Use a dummy value for the signature + }); + + vm.prank(operator); + avsManager.registerValidatorsOptimistically(paramsArray); + + // Prepare the data for verification + bytes32[] memory blsPubKeyHashes = new bytes32[](1); + blsPubKeyHashes[0] = pubkeyHash; + + BeaconChainHelperLib.InclusionProof[] memory proofs = new BeaconChainHelperLib.InclusionProof[](1); + proofs[0] = BeaconProofs.eip4788ValidatorInclusionProof(); + // Mock the getRootFromTimestamp function to return the expected beacon block root + vm.mockCall( + address(0x000F3df6D732807Ef1319fB7B8bB8522d0Beac02), + hex"00000000", + abi.encode(BeaconProofs.beaconBlockRoot()) + ); + + // Verify the validator on the beacon chain + avsManager.verifyValidatorOnBeaconChain(blsPubKeyHashes, proofs); + + // Check that the validator is still registered + ValidatorDataExtended memory validator = avsManager.getValidator(BeaconProofs.validatorIndex()); + assertFalse(validator.registered, "Validator should be deregistered after slashing"); + + OperatorDataExtended memory operatorData = avsManager.getOperator(operator); + assertEq(operatorData.validatorCount, 0, "Operator should have no validators after slashing"); + } + + function testVerifyValidatorOnBeaconChainInvalidProof() public { + _setupOperator(); + _registerOperator(); + vm.warp(block.timestamp + 50); + // Register the validator optimistically + ValidatorRegistrationParams[] memory paramsArray = new ValidatorRegistrationParams[](1); + bytes32 pubkeyHash = sha256(abi.encodePacked(BeaconProofs.validator(), bytes16(0))); + paramsArray[0] = ValidatorRegistrationParams({ + blsPubKeyHash: pubkeyHash, + pubkeyG1: _generateBlsPubkeyParams(123).pubkeyG1, + pubkeyG2: _generateBlsPubkeyParams(123).pubkeyG2, // Use a dummy value for pubkeyG2 + salt: bytes32(uint256(1)), + expiry: block.timestamp + 1 days, + index: uint64(BeaconProofs.validatorIndex()), + registrationSignature: _generateBlsPubkeyParams(123).pubkeyG1 // Use a dummy value for the signature + }); + + vm.prank(operator); + avsManager.registerValidatorsOptimistically(paramsArray); + + // Prepare the data for verification + bytes32[] memory blsPubKeyHashes = new bytes32[](1); + blsPubKeyHashes[0] = pubkeyHash; + + BeaconChainHelperLib.InclusionProof[] memory proofs = new BeaconChainHelperLib.InclusionProof[](1); + proofs[0] = BeaconProofs.eip4788ValidatorInclusionProof(); + + // Mock the getRootFromTimestamp function to return an incorrect beacon block root + vm.mockCall( + address(0x000F3df6D732807Ef1319fB7B8bB8522d0Beac02), + hex"00000000", + abi.encode(bytes32(uint256(1))) // Incorrect beacon block root + ); + + // Expect the verification to fail + vm.expectRevert(IUniFiAVSManager.InvalidValidatorProof.selector); + avsManager.verifyValidatorOnBeaconChain(blsPubKeyHashes, proofs); + + // Check that the validator is not slashed nor deregistered + vm.roll(block.number + avsManager.getDeregistrationDelay() + 1); + ValidatorDataExtended memory validator = avsManager.getValidator(blsPubKeyHashes[0]); + assertTrue(validator.registered, "Validator should be still registered after failed slashing"); + + OperatorDataExtended memory operatorData = avsManager.getOperator(operator); + assertEq(operatorData.validatorCount, 1, "Operator should have 1 validator after failed slashing"); + } } diff --git a/l1-contracts/test/fixtures/BeaconProofs.sol b/l1-contracts/test/fixtures/BeaconProofs.sol new file mode 100644 index 0000000..6d687ee --- /dev/null +++ b/l1-contracts/test/fixtures/BeaconProofs.sol @@ -0,0 +1,131 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity >=0.8.0 <0.9.0; + +import { BeaconChainHelperLib } from "../../src/lib/BeaconChainHelperLib.sol"; + +/// @dev Data has been taken from beacon block at slot 9000000 on Ethereum mainnet +library BeaconProofs { + // validator public bls key + function validator() internal pure returns (bytes memory) { + return hex"98fb8eacf684f80712faa9354535620f94a10687c2243c0cdae7280cf6220fb64c78e49efe8eef599406b33e5aac4dd0"; + } + + function validatorsRoot() internal pure returns (bytes32) { + return 0x0ccf56d8e76d16306c6e6e78ec20c07be5fa5ae89b18873b43cc823075a5df0b; + } + + function validatorIndex() internal pure returns (uint256) { + return 912203; + } + + function beaconStateRoot() internal pure returns (bytes32) { + return 0xcd918afbe365c6dcabab551e32fae5f3f9677433876049dc035e5135122a2e7e; + } + + function beaconBlockRoot() internal pure returns (bytes32) { + return 0xcc8a36da0d5112c8dd602530ac7c7b8364edfd92cdc6f0d62365de392e8e5bb6; + } + + function validatorChunks() internal pure returns (bytes32[8] memory) { + bytes32[8] memory chunks; + chunks[0] = 0x8d7c2b324f41a1d395fc265d42c6e1293b38c33a674244cae9ac67d68367036d; // bls key hash + chunks[1] = 0x0100000000000000000000006661be71769ff00c5e403f327869505caf0b7f70; + chunks[2] = 0x0040597307000000000000000000000000000000000000000000000000000000; + chunks[3] = 0x0000000000000000000000000000000000000000000000000000000000000000; + chunks[4] = 0xe271030000000000000000000000000000000000000000000000000000000000; + chunks[5] = 0x6084030000000000000000000000000000000000000000000000000000000000; + chunks[6] = 0xffffffffffffffff000000000000000000000000000000000000000000000000; + chunks[7] = 0xffffffffffffffff000000000000000000000000000000000000000000000000; + + return chunks; + } + + function validatorProof() internal pure returns (bytes32[] memory) { + bytes32[] memory proof = new bytes32[](41); + proof[0] = 0xf5ee350215176477a7fb48aa80292de237856ad3068f46728da26aedca8a3b2b; + proof[1] = 0xfbeca4cff4f86c2ff5f1ff6808f57b12e7a6f3365d59a35c90f19715995f8be8; + proof[2] = 0x06ee0000b0cf0c0531c2a4f3368eb8df6079216bb6cca127a76e459c62058615; + proof[3] = 0x5b8c291888e7936b46e36d7b71d36c846fbfc04d48cab6beb20e23642f64ee69; + proof[4] = 0xa748ed979e88b53c303ece0946d13d2def12e003b90b562474dac1768d1d0975; + proof[5] = 0xe667bf725f0e72f47409d089248b50a9a11d08591b83374f18ed338f5c3ff964; + proof[6] = 0xd86b77a649fad1d48e109b8bc98d2a2dbc88a4b9b86c5e06878e0b980ebda3b7; + proof[7] = 0xc2db7c18d080f2b21f2c981f65414d00b0cc8542fda38233fa1c1ee33df4bbe1; + proof[8] = 0xe72e80d2ce704957f507af587e19a61ceadad2411c9728315e1f294fadae23f1; + proof[9] = 0x32f30ee3311d96e0544e2e4b0f4e1e1863d06224636ea8004e49a27280a81a11; + proof[10] = 0x89d191926d7681be7545b42b9ef95d413fbe1d8c014400c5ece8141be300b238; + proof[11] = 0x0c924ac306b692750b3285f974edf991dd4f05fff0ab3dd114430499722ff93b; + proof[12] = 0x1eb9a358bbe044159a2bed16a0b69b5b988ba0c57f2c267cfd390b3fb86fde6a; + proof[13] = 0xda60132f38fc053c26ba06136e03a861fd5e59734dc3e6cc1b69c072b9ce600a; + proof[14] = 0xcee182aa676671046ccf49213a58ef8d35e227a3adfaa146f7b71dc47c7bdd73; + proof[15] = 0xf1d0df094ceceed165886daf4c52c467710ed19a53df98ab2607629dbf7036ba; + proof[16] = 0x81917306117277e02aa4174ae73a2ec414862aced0491ec933434d9bd2279e3f; + proof[17] = 0xc562f7ffddaec138272a84b043216c1c906f68198f752ad6b80171794fcba3b5; + proof[18] = 0xcdfeaaff006b40d110ff925b18bffc36cf55543a35c84d25da0b196ea81c6029; + proof[19] = 0x8bd5e9cadc78cd0b0e0abd32a63a39596ad24e14552926bce0f6c54e39c29b99; + proof[20] = 0x6187b4f2f4b3e572fe26a6c73567ab5b1695303b0ad9dd5c9ab9679266fba2e3; + proof[21] = 0x8a8d7fe3af8caa085a7639a832001457dfb9128a8061142ad0335629ff23ff9c; + proof[22] = 0xfeb3c337d7a51a6fbf00b9e34c52e1c9195c969bd4e7a0bfd51d5c5bed9c1167; + proof[23] = 0xe71f0aa83cc32edfbefa9f4d3e0174ca85182eec9f3a09f6a6c0df6377a510d7; + proof[24] = 0x31206fa80a50bb6abe29085058f16212212a60eec8f049fecb92d8c8e0a84bc0; + proof[25] = 0x21352bfecbeddde993839f614c3dac0a3ee37543f9b412b16199dc158e23b544; + proof[26] = 0x619e312724bb6d7c3153ed9de791d764a366b389af13c58bf8a8d90481a46765; + proof[27] = 0x7cdd2986268250628d0c10e385c58c6191e6fbe05191bcc04f133f2cea72c1c4; + proof[28] = 0x848930bd7ba8cac54661072113fb278869e07bb8587f91392933374d017bcbe1; + proof[29] = 0x8869ff2c22b28cc10510d9853292803328be4fb0e80495e8bb8d271f5b889636; + proof[30] = 0xb5fe28e79f1b850f8658246ce9b6a1e7b49fc06db7143e8fe0b4f2b0c5523a5c; + proof[31] = 0x985e929f70af28d0bdd1a90a808f977f597c7c778c489e98d3bd8910d31ac0f7; + proof[32] = 0xc6f67e02e6e4e1bdefb994c6098953f34636ba2b6ca20a4721d2b26a886722ff; + proof[33] = 0x1c9a7e5ff1cf48b4ad1582d3f4e4a1004f3b20d8c5a2b71387a4254ad933ebc5; + proof[34] = 0x2f075ae229646b6f6aed19a5e372cf295081401eb893ff599b3f9acc0c0d3e7d; + proof[35] = 0x328921deb59612076801e8cd61592107b5c67c79b846595cc6320c395b46362c; + proof[36] = 0xbfb909fdb236ad2411b4e4883810a074b840464689986c3f8a8091827e17c327; + proof[37] = 0x55d8fb3687ba3ba49f342c77f5a1f89bec83d811446e1a467139213d640b6a74; + proof[38] = 0xf7210d4f8e7e1039790e7bf4efa207555a10a6db1dd4b95da313aaa88b88fe76; + proof[39] = 0xad21b516cbc645ffe34ab5de1c8aef8cd4e7f8d2b51e8e1456adc7563cda206f; + proof[40] = 0x2821150000000000000000000000000000000000000000000000000000000000; + + return proof; + } + + function beaconStateProofForValidatorList() internal pure returns (bytes32[] memory) { + bytes32[] memory proof = new bytes32[](5); + proof[0] = 0x8c53160000000000000000000000000000000000000000000000000000000000; + proof[1] = 0xd9cb62ffd113d2a2b71b4539c54bf01587d8a2a5a7c81baa2c2ae89d245578d6; + proof[2] = 0xefbad4c97640101fc18122e8b818e8cc3c278a18e05dc601af4095d5519d834a; + proof[3] = 0x775d61d75ab0731115447847764383a42283b502eb4ed3ca7ba412ac67da5138; + proof[4] = 0xbb5cf5c0273b8d100f329ea0c78c471d0833f048c7fc264c285c3696d7aed412; + + return proof; + } + + function beaconBlockProofForBeaconState() internal pure returns (bytes32[] memory) { + bytes32[] memory proof = new bytes32[](3); + proof[0] = 0xf47de6dfa04049ce0586d989821321111d896f3cc37e40637fc226bee212e43d; + proof[1] = 0x7506bc99ed6f0e48ad0e1ded3e878dfcfe08ca4a89308910ba1941e912673258; + proof[2] = 0x00f48b46fd6aac7f8a72d8e1eed4f3b5bd244bf6242cb538ca94b44aed02857a; + + return proof; + } + + function beaconBlockProofForProposer() internal pure returns (bytes32[] memory) { + bytes32[] memory proof = new bytes32[](3); + proof[0] = 0x4054890000000000000000000000000000000000000000000000000000000000; + proof[1] = 0xd22083672621f940e26b3f1e627f8c311a3f5f0874c193b40974f244668e1372; + proof[2] = 0x00f48b46fd6aac7f8a72d8e1eed4f3b5bd244bf6242cb538ca94b44aed02857a; + + return proof; + } + + function eip4788ValidatorInclusionProof() internal pure returns (BeaconChainHelperLib.InclusionProof memory) { + return BeaconChainHelperLib.InclusionProof({ + validator: validatorChunks(), + validatorIndex: validatorIndex(), + validatorProof: validatorProof(), + validatorsRoot: validatorsRoot(), + beaconStateProof: beaconStateProofForValidatorList(), + beaconStateRoot: beaconStateRoot(), + beaconBlockProofForState: beaconBlockProofForBeaconState(), + beaconBlockProofForProposerIndex: beaconBlockProofForProposer() + }); + } +} From 01509c5eea454e1510167371bed3bbf318d9e431 Mon Sep 17 00:00:00 2001 From: 0xwalid Date: Wed, 9 Oct 2024 00:03:03 -0700 Subject: [PATCH 08/22] fix test --- l1-contracts/test/UniFiAVSManager.t.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/l1-contracts/test/UniFiAVSManager.t.sol b/l1-contracts/test/UniFiAVSManager.t.sol index b368d54..f29d283 100644 --- a/l1-contracts/test/UniFiAVSManager.t.sol +++ b/l1-contracts/test/UniFiAVSManager.t.sol @@ -1367,7 +1367,7 @@ contract UniFiAVSManagerTest is UnitTestHelper { avsManager.verifyValidatorOnBeaconChain(blsPubKeyHashes, proofs); // Check that the validator is still registered - ValidatorDataExtended memory validator = avsManager.getValidator(BeaconProofs.validatorIndex()); + ValidatorDataExtended memory validator = avsManager.getValidator(bytes32(uint256(1234))); assertFalse(validator.registered, "Validator should be deregistered after slashing"); OperatorDataExtended memory operatorData = avsManager.getOperator(operator); From ba95a8cc6db672991cbdb22b3af73326dd4d2725 Mon Sep 17 00:00:00 2001 From: 0xwalid Date: Wed, 9 Oct 2024 00:11:06 -0700 Subject: [PATCH 09/22] minor optimization --- l1-contracts/src/UniFiAVSManager.sol | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/l1-contracts/src/UniFiAVSManager.sol b/l1-contracts/src/UniFiAVSManager.sol index 35d7ca7..900066b 100644 --- a/l1-contracts/src/UniFiAVSManager.sol +++ b/l1-contracts/src/UniFiAVSManager.sol @@ -366,9 +366,7 @@ contract UniFiAVSManager is if (validatorIndex != 0 && validatorIndex != proof.validatorIndex) { _slashAndDeregisterValidator(blsPubKeyHash); delete $.validatorIndexes[validatorIndex]; - } - - if (validatorIndex == 0) { + } else if (validatorIndex == 0) { bytes32 blsPubKeyHashFromIndex = $.validatorIndexes[proof.validatorIndex]; validator = $.validators[blsPubKeyHashFromIndex]; From 181e7cd168da21b18ca59eba155b77ae65d6b07a Mon Sep 17 00:00:00 2001 From: 0xwalid Date: Thu, 10 Oct 2024 00:19:56 -0700 Subject: [PATCH 10/22] add registration delay --- l1-contracts/src/UniFiAVSManager.sol | 104 +++++++++++------- .../src/interfaces/IUniFiAVSManager.sol | 17 +++ .../{ => storage}/UniFiAVSManagerStorage.sol | 6 +- l1-contracts/src/structs/ValidatorData.sol | 2 + 4 files changed, 85 insertions(+), 44 deletions(-) rename l1-contracts/src/{ => storage}/UniFiAVSManagerStorage.sol (90%) diff --git a/l1-contracts/src/UniFiAVSManager.sol b/l1-contracts/src/UniFiAVSManager.sol index 900066b..ade1093 100644 --- a/l1-contracts/src/UniFiAVSManager.sol +++ b/l1-contracts/src/UniFiAVSManager.sol @@ -22,12 +22,10 @@ import { BLSSignatureCheckerLib } from "./lib/BLSSignatureCheckerLib.sol"; import { BeaconChainHelperLib } from "./lib/BeaconChainHelperLib.sol"; import { IUniFiAVSManager } from "./interfaces/IUniFiAVSManager.sol"; -import { UniFiAVSManagerStorage } from "./UniFiAVSManagerStorage.sol"; +import { UniFiAVSManagerStorage } from "./storage/UniFiAVSManagerStorage.sol"; import "./structs/ValidatorData.sol"; import "./structs/OperatorData.sol"; -import "forge-std/console.sol"; - contract UniFiAVSManager is UniFiAVSManagerStorage, IUniFiAVSManager, @@ -180,7 +178,8 @@ contract UniFiAVSManager is eigenPod: address(eigenPod), index: validatorInfo.validatorIndex, operator: msg.sender, - registeredUntil: type(uint64).max + registeredUntil: type(uint64).max, + registeredAfter: uint64(block.number) }); $.validatorIndexes[validatorInfo.validatorIndex] = blsPubKeyHash; @@ -258,6 +257,10 @@ contract UniFiAVSManager is revert ValidatorAlreadyRegistered(); } + if ($.validatorIndexes[params.index] != bytes32(0)) { + revert ValidatorIndexAlreadyUsed(); + } + // Check if the registration salt has been used before if (validatorRegistrationData.salt == params.salt) { revert SaltAlreadyUsed(); @@ -273,7 +276,8 @@ contract UniFiAVSManager is eigenPod: address(0), // Not from EigenPod index: params.index, // Store the provided index operator: msg.sender, - registeredUntil: type(uint64).max + registeredUntil: type(uint64).max, + registeredAfter: uint64(block.number) + $.registerationDelay }); $.validatorRegistrations[blsPubKeyHash] = ValidatorRegistrationData({ @@ -333,7 +337,7 @@ contract UniFiAVSManager is ); if (!isValid) { - _slashAndDeregisterValidator(blsPubKeyHash); + _slashAndDeregisterValidator(blsPubKeyHash, validator.index); } } } @@ -364,15 +368,13 @@ contract UniFiAVSManager is uint64 validatorIndex = validator.index; if (validatorIndex != 0 && validatorIndex != proof.validatorIndex) { - _slashAndDeregisterValidator(blsPubKeyHash); - delete $.validatorIndexes[validatorIndex]; + _slashAndDeregisterValidator(blsPubKeyHash, validatorIndex); } else if (validatorIndex == 0) { bytes32 blsPubKeyHashFromIndex = $.validatorIndexes[proof.validatorIndex]; validator = $.validators[blsPubKeyHashFromIndex]; if (validator.index != 0 && blsPubKeyHashFromIndex != blsPubKeyHash) { - _slashAndDeregisterValidator(blsPubKeyHashFromIndex); - delete $.validatorIndexes[proof.validatorIndex]; + _slashAndDeregisterValidator(blsPubKeyHashFromIndex, uint64(proof.validatorIndex)); } else { revert ValidatorNotFound(); } @@ -383,37 +385,6 @@ contract UniFiAVSManager is } } - // Helper function to slash and deregister a validator - function _slashAndDeregisterValidator(bytes32 blsPubKeyHash) internal { - UniFiAVSStorage storage $ = _getUniFiAVSManagerStorage(); - ValidatorData storage validator = $.validators[blsPubKeyHash]; - address operator = validator.operator; - - if (validator.index == 0) { - revert ValidatorNotFound(); - } - - if (validator.registeredUntil <= block.number) { - revert ValidatorAlreadyDeregistered(); - } - - $.slashedOperators[operator].push( - InvalidValidator({ slashingBeneficiary: msg.sender, blsPubKeyHash: blsPubKeyHash }) - ); - - // Update the registeredUntil field to deregister the validator immediately - validator.registeredUntil = uint64(block.number); - - // Emit the ValidatorDeregistered event - emit ValidatorDeregistered({ operator: operator, blsPubKeyHash: blsPubKeyHash }); - - emit ValidatorSlashed(operator, blsPubKeyHash); - - // Decrement the operator's validator count - OperatorData storage operatorData = $.operators[operator]; - operatorData.validatorCount -= 1; - } - /** * @inheritdoc IUniFiAVSManager * @dev Restricted in this context is like `whenNotPaused` modifier from Pausable.sol @@ -510,6 +481,18 @@ contract UniFiAVSManager is _setDeregistrationDelay(newDelay); } + /** + * @inheritdoc IUniFiAVSManager + * @dev Restricted to the DAO + */ + function setRegistrationDelay(uint64 newDelay) external restricted { + UniFiAVSStorage storage $ = _getUniFiAVSManagerStorage(); + uint64 oldDelay = $.registerationDelay; + $.registerationDelay = newDelay; + + emit RegistrationDelaySet(oldDelay, newDelay); + } + /** * @inheritdoc IUniFiAVSManager * @dev Restricted to the DAO @@ -769,7 +752,7 @@ contract UniFiAVSManager is delegateKey: activeCommitment.delegateKey, chainIDBitMap: activeCommitment.chainIDBitMap, backedByEigenPodStake: backedByEigenPodStake, - registered: block.number < validatorData.registeredUntil + registered: block.number < validatorData.registeredUntil && block.number > validatorData.registeredAfter }); } } @@ -797,5 +780,42 @@ contract UniFiAVSManager is emit DeregistrationDelaySet(oldDelay, newDelay); } + /** + * @dev Internal function to slash and deregister a validator + * @param blsPubKeyHash The BLS public key hash of the validator + * @param validatorIndex The index of the validator + */ + function _slashAndDeregisterValidator(bytes32 blsPubKeyHash, uint64 validatorIndex) internal { + UniFiAVSStorage storage $ = _getUniFiAVSManagerStorage(); + ValidatorData storage validator = $.validators[blsPubKeyHash]; + address operator = validator.operator; + + if (validator.index == 0) { + revert ValidatorNotFound(); + } + + if (validator.registeredUntil <= block.number) { + revert ValidatorAlreadyDeregistered(); + } + + $.slashedOperators[operator].push( + InvalidValidator({ slashingBeneficiary: msg.sender, blsPubKeyHash: blsPubKeyHash }) + ); + + // Update the registeredUntil field to deregister the validator immediately + validator.registeredUntil = uint64(block.number); + + delete $.validatorIndexes[validatorIndex]; + + // Emit the ValidatorDeregistered event + emit ValidatorDeregistered({ operator: operator, blsPubKeyHash: blsPubKeyHash }); + + emit ValidatorSlashed(operator, blsPubKeyHash); + + // Decrement the operator's validator count + OperatorData storage operatorData = $.operators[operator]; + operatorData.validatorCount -= 1; + } + function _authorizeUpgrade(address newImplementation) internal virtual override restricted { } } diff --git a/l1-contracts/src/interfaces/IUniFiAVSManager.sol b/l1-contracts/src/interfaces/IUniFiAVSManager.sol index 0e4f9a7..6db339e 100644 --- a/l1-contracts/src/interfaces/IUniFiAVSManager.sol +++ b/l1-contracts/src/interfaces/IUniFiAVSManager.sol @@ -86,6 +86,9 @@ interface IUniFiAVSManager { /// @notice Thrown when a validator proof is invalid error InvalidValidatorProof(); + /// @notice Thrown when a validator index is already used + error ValidatorIndexAlreadyUsed(); + /** * @notice Emitted when a new operator is registered in the UniFi AVS. * @param operator The address of the registered operator. @@ -175,6 +178,13 @@ interface IUniFiAVSManager { */ event ValidatorSlashed(address indexed operator, bytes32 indexed blsPubKeyHash); + /** + * @notice Emitted when the registration delay is set. + * @param oldDelay The previous registration delay value. + * @param newDelay The new registration delay value. + */ + event RegistrationDelaySet(uint64 oldDelay, uint64 newDelay); + /** * @notice Returns the EigenPodManager contract. * @return IEigenPodManager The EigenPodManager contract. @@ -255,6 +265,13 @@ interface IUniFiAVSManager { */ function setDeregistrationDelay(uint64 newDelay) external; + /** + * @notice Sets a new registration delay for validators. + * @param newDelay The new registration delay in seconds. + * @dev Restricted to the DAO + */ + function setRegistrationDelay(uint64 newDelay) external; + /** * @notice Sets the chain ID for a specific index in the bitmap. * @param index The index in the bitmap to set. diff --git a/l1-contracts/src/UniFiAVSManagerStorage.sol b/l1-contracts/src/storage/UniFiAVSManagerStorage.sol similarity index 90% rename from l1-contracts/src/UniFiAVSManagerStorage.sol rename to l1-contracts/src/storage/UniFiAVSManagerStorage.sol index afa6aba..5e9fb33 100644 --- a/l1-contracts/src/UniFiAVSManagerStorage.sol +++ b/l1-contracts/src/storage/UniFiAVSManagerStorage.sol @@ -2,8 +2,8 @@ pragma solidity >=0.8.0 <0.9.0; import { EnumerableSet } from "@openzeppelin/contracts/utils/structs/EnumerableSet.sol"; -import "./structs/ValidatorData.sol"; -import "./structs/OperatorData.sol"; +import "../structs/ValidatorData.sol"; +import "../structs/OperatorData.sol"; /** * @title UniFiAVSManagerStorage @@ -24,6 +24,8 @@ abstract contract UniFiAVSManagerStorage { EnumerableSet.AddressSet allowlistedRestakingStrategies; // Mapping to store validator registration data mapping(bytes32 => ValidatorRegistrationData) validatorRegistrations; + // Delay after which a validator can be considered registered + uint64 registerationDelay; // Slashed operators mapping mapping(address => InvalidValidator[]) slashedOperators; // operator => InvalidValidator[] } diff --git a/l1-contracts/src/structs/ValidatorData.sol b/l1-contracts/src/structs/ValidatorData.sol index b8256e9..d9b1568 100644 --- a/l1-contracts/src/structs/ValidatorData.sol +++ b/l1-contracts/src/structs/ValidatorData.sol @@ -18,6 +18,8 @@ struct ValidatorData { address operator; /// @notice The block number until which the validator is registered. uint64 registeredUntil; + /// @notice The block number after which the validator is registered. + uint64 registeredAfter; } /** From a792458bda9cab93743c8b3d9e3361ba8b327764 Mon Sep 17 00:00:00 2001 From: 0xwalid Date: Thu, 10 Oct 2024 01:00:56 -0700 Subject: [PATCH 11/22] fix tests after adding registration delay --- l1-contracts/src/UniFiAVSManager.sol | 12 ++++++++++++ l1-contracts/src/interfaces/IUniFiAVSManager.sol | 9 +++++++++ l1-contracts/test/UniFiAVSManager.t.sol | 5 +++++ 3 files changed, 26 insertions(+) diff --git a/l1-contracts/src/UniFiAVSManager.sol b/l1-contracts/src/UniFiAVSManager.sol index ade1093..fc81da0 100644 --- a/l1-contracts/src/UniFiAVSManager.sol +++ b/l1-contracts/src/UniFiAVSManager.sol @@ -402,6 +402,10 @@ contract UniFiAVSManager is revert DeregistrationAlreadyStarted(); } + if ($.slashedOperators[msg.sender].length > 0) { + revert OperatorSlashed(); + } + operator.startDeregisterOperatorBlock = uint64(block.number); emit OperatorDeregisterStarted(msg.sender); @@ -544,6 +548,14 @@ contract UniFiAVSManager is return $.deregistrationDelay; } + /** + * @inheritdoc IUniFiAVSManager + */ + function getRegistrationDelay() external view returns (uint64) { + UniFiAVSStorage storage $ = _getUniFiAVSManagerStorage(); + return $.registerationDelay; + } + /** * @inheritdoc IUniFiAVSManager */ diff --git a/l1-contracts/src/interfaces/IUniFiAVSManager.sol b/l1-contracts/src/interfaces/IUniFiAVSManager.sol index 6db339e..58a4982 100644 --- a/l1-contracts/src/interfaces/IUniFiAVSManager.sol +++ b/l1-contracts/src/interfaces/IUniFiAVSManager.sol @@ -89,6 +89,9 @@ interface IUniFiAVSManager { /// @notice Thrown when a validator index is already used error ValidatorIndexAlreadyUsed(); + /// @notice Thrown when an operator is slashed + error OperatorSlashed(); + /** * @notice Emitted when a new operator is registered in the UniFi AVS. * @param operator The address of the registered operator. @@ -352,6 +355,12 @@ interface IUniFiAVSManager { */ function getDeregistrationDelay() external view returns (uint64); + /** + * @notice Retrieves the current registration delay for validators. + * @return The current registration delay in seconds. + */ + function getRegistrationDelay() external view returns (uint64); + /** * @notice Converts a bitmap to an array of chain IDs. * @param bitmap The bitmap to convert. diff --git a/l1-contracts/test/UniFiAVSManager.t.sol b/l1-contracts/test/UniFiAVSManager.t.sol index f29d283..86a5581 100644 --- a/l1-contracts/test/UniFiAVSManager.t.sol +++ b/l1-contracts/test/UniFiAVSManager.t.sol @@ -348,6 +348,7 @@ contract UniFiAVSManagerTest is UnitTestHelper { operatorData = avsManager.getOperator(operator); assertEq(operatorData.validatorCount, 0, "all validators should be deregistered"); + vm.roll(initialBlockNumber + avsManager.getRegistrationDelay() + 1); for (uint256 i = 0; i < blsPubKeyHashes.length; i++) { ValidatorDataExtended memory validatorData = avsManager.getValidator(blsPubKeyHashes[i]); assertTrue(validatorData.registered, "Validator should be registered"); @@ -901,6 +902,7 @@ contract UniFiAVSManagerTest is UnitTestHelper { vm.prank(operator); avsManager.registerValidators(podOwner, blsPubKeyHashes); + vm.roll(block.number + avsManager.getRegistrationDelay() + 1); // Deregister the first validator bytes32[] memory deregisterFirst = new bytes32[](1); @@ -1106,6 +1108,7 @@ contract UniFiAVSManagerTest is UnitTestHelper { OperatorDataExtended memory operatorData = avsManager.getOperator(operator); assertEq(operatorData.validatorCount, 2, "Validator count should be 2"); + vm.roll(block.number + avsManager.getRegistrationDelay() + 1); ValidatorDataExtended memory validator1 = avsManager.getValidator(pubkeyHash1); ValidatorDataExtended memory validator2 = avsManager.getValidator(pubkeyHash2); @@ -1183,6 +1186,7 @@ contract UniFiAVSManagerTest is UnitTestHelper { blsPubKeyHashes[0] = pubkeyHash; avsManager.verifyValidatorSignatures(blsPubKeyHashes); + vm.roll(block.number + avsManager.getRegistrationDelay() + 1); ValidatorDataExtended memory validator = avsManager.getValidator(pubkeyHash); assertTrue(validator.registered, "Validator should still be registered after verification"); @@ -1278,6 +1282,7 @@ contract UniFiAVSManagerTest is UnitTestHelper { // Verify the validator on the beacon chain avsManager.verifyValidatorOnBeaconChain(blsPubKeyHashes, proofs); + vm.roll(block.number + avsManager.getRegistrationDelay() + 1); // Check that the validator is still registered ValidatorDataExtended memory validator = avsManager.getValidator(blsPubKeyHashes[0]); From 59e8980765ad092d7ce91d067e4ff9a73f7621d2 Mon Sep 17 00:00:00 2001 From: 0xwalid Date: Tue, 15 Oct 2024 23:51:54 -0700 Subject: [PATCH 12/22] simplify slashing on invalid beacon chain data --- l1-contracts/script/SetupAccess.s.sol | 5 +- l1-contracts/src/UniFiAVSManager.sol | 113 ++++++++++++------ .../src/interfaces/IUniFiAVSManager.sol | 18 +-- l1-contracts/src/lib/BeaconChainHelperLib.sol | 13 +- l1-contracts/src/structs/ValidatorData.sol | 5 +- l1-contracts/test/UniFiAVSManager.t.sol | 20 ++-- l1-contracts/test/fixtures/BeaconProofs.sol | 5 +- 7 files changed, 113 insertions(+), 66 deletions(-) diff --git a/l1-contracts/script/SetupAccess.s.sol b/l1-contracts/script/SetupAccess.s.sol index 1ebb9b4..1d3fafa 100644 --- a/l1-contracts/script/SetupAccess.s.sol +++ b/l1-contracts/script/SetupAccess.s.sol @@ -88,7 +88,7 @@ contract SetupAccess is BaseScript { ); bytes4[] memory publicSelectors = new bytes4[](0); - publicSelectors = new bytes4[](11); + publicSelectors = new bytes4[](12); publicSelectors[0] = UniFiAVSManager.registerOperator.selector; publicSelectors[1] = UniFiAVSManager.registerValidators.selector; publicSelectors[2] = UniFiAVSManager.startDeregisterOperator.selector; @@ -99,7 +99,8 @@ contract SetupAccess is BaseScript { publicSelectors[7] = UniFiAVSManager.registerOperatorWithCommitment.selector; publicSelectors[8] = UniFiAVSManager.registerValidatorsOptimistically.selector; publicSelectors[9] = UniFiAVSManager.verifyValidatorSignatures.selector; - publicSelectors[10] = UniFiAVSManager.verifyValidatorOnBeaconChain.selector; + publicSelectors[10] = UniFiAVSManager.slashValidatorsWithInvalidPubkey.selector; + publicSelectors[11] = UniFiAVSManager.slashValidatorsWithInvalidIndex.selector; calldatas[1] = abi.encodeWithSelector( AccessManager.setTargetFunctionRole.selector, diff --git a/l1-contracts/src/UniFiAVSManager.sol b/l1-contracts/src/UniFiAVSManager.sol index fc81da0..9111f1d 100644 --- a/l1-contracts/src/UniFiAVSManager.sol +++ b/l1-contracts/src/UniFiAVSManager.sol @@ -39,6 +39,8 @@ contract UniFiAVSManager is bytes32 public constant VALIDATOR_REGISTRATION_TYPEHASH = keccak256("BN254ValidatorRegistration(address operator,bytes32 salt,uint256 expiry,uint64 index)"); + uint256 public constant PROOF_TIMESTAMP_THRESHOLD = 12 hours; + /** * @notice The EigenPodManager * @custom:oz-upgrades-unsafe-allow state-variable-immutable @@ -285,7 +287,8 @@ contract UniFiAVSManager is pubkeyG1: params.pubkeyG1, pubkeyG2: params.pubkeyG2, salt: params.salt, - expiry: params.expiry + expiry: uint64(params.expiry), + registeredAt: uint64(block.number) }); $.validatorIndexes[params.index] = blsPubKeyHash; @@ -337,7 +340,7 @@ contract UniFiAVSManager is ); if (!isValid) { - _slashAndDeregisterValidator(blsPubKeyHash, validator.index); + _slashAndDeregisterValidator(blsPubKeyHash); } } } @@ -346,41 +349,76 @@ contract UniFiAVSManager is * @inheritdoc IUniFiAVSManager * @dev Restricted in this context is like `whenNotPaused` modifier from Pausable.sol */ - function verifyValidatorOnBeaconChain( - bytes32[] calldata blsPubKeyHashes, - BeaconChainHelperLib.InclusionProof[] calldata proofs - ) external restricted { - if (blsPubKeyHashes.length != proofs.length) { - revert InvalidArrayLengths(); + function slashValidatorsWithInvalidIndex(BeaconChainHelperLib.InclusionProof[] calldata proofs) + external + restricted + { + UniFiAVSStorage storage $ = _getUniFiAVSManagerStorage(); + + for (uint256 i = 0; i < proofs.length; i++) { + BeaconChainHelperLib.InclusionProof memory proof = proofs[i]; + bytes32 blsPubKeyHash = proof.validator[0]; + bool isValid = BeaconChainHelperLib.verifyValidator(proof); + + if (!isValid) { + revert InvalidValidatorProof(blsPubKeyHash); + } + + ValidatorData storage validator = $.validators[blsPubKeyHash]; + uint64 validatorIndex = validator.index; + + if (validator.eigenPod != address(0)) { + revert InvalidValidatorType(); + } + ValidatorRegistrationData storage validatorRegistrationData = $.validatorRegistrations[blsPubKeyHash]; + uint64 registeredAt = validatorRegistrationData.registeredAt; + + if (proof.timestamp < registeredAt || proof.timestamp > validator.registeredUntil) { + revert InvalidValidatorProof(blsPubKeyHash); + } + + if (validatorIndex != proof.validatorIndex) { + _slashAndDeregisterValidator(blsPubKeyHash); + } } + } + /** + * @inheritdoc IUniFiAVSManager + * @dev Restricted in this context is like `whenNotPaused` modifier from Pausable.sol + */ + function slashValidatorsWithInvalidPubkey(BeaconChainHelperLib.InclusionProof[] calldata proofs) + external + restricted + { UniFiAVSStorage storage $ = _getUniFiAVSManagerStorage(); - for (uint256 i = 0; i < blsPubKeyHashes.length; i++) { - bytes32 blsPubKeyHash = blsPubKeyHashes[i]; + for (uint256 i = 0; i < proofs.length; i++) { BeaconChainHelperLib.InclusionProof memory proof = proofs[i]; - (, bytes32 beaconBlockRoot) = BeaconChainHelperLib.getRootFromTimestamp(block.timestamp - 12); + uint256 index = proof.validatorIndex; - bool isValid = BeaconChainHelperLib.verifyValidator(blsPubKeyHash, beaconBlockRoot, proof); + bool isValid = BeaconChainHelperLib.verifyValidator(proof); - if (isValid) { - ValidatorData storage validator = $.validators[blsPubKeyHash]; - uint64 validatorIndex = validator.index; + if (!isValid) { + revert InvalidValidatorProof(proof.validator[0]); + } - if (validatorIndex != 0 && validatorIndex != proof.validatorIndex) { - _slashAndDeregisterValidator(blsPubKeyHash, validatorIndex); - } else if (validatorIndex == 0) { - bytes32 blsPubKeyHashFromIndex = $.validatorIndexes[proof.validatorIndex]; - validator = $.validators[blsPubKeyHashFromIndex]; + bytes32 blsPubKeyHashFromIndex = $.validatorIndexes[index]; + ValidatorData storage validator = $.validators[blsPubKeyHashFromIndex]; + if (validator.eigenPod != address(0)) { + revert InvalidValidatorType(); + } - if (validator.index != 0 && blsPubKeyHashFromIndex != blsPubKeyHash) { - _slashAndDeregisterValidator(blsPubKeyHashFromIndex, uint64(proof.validatorIndex)); - } else { - revert ValidatorNotFound(); - } - } - } else { - revert InvalidValidatorProof(); + ValidatorRegistrationData storage validatorRegistrationData = + $.validatorRegistrations[blsPubKeyHashFromIndex]; + uint64 registeredAt = validatorRegistrationData.registeredAt; + + if (proof.timestamp < registeredAt || proof.timestamp > validator.registeredUntil) { + revert InvalidValidatorProof(proof.validator[0]); + } + + if (blsPubKeyHashFromIndex != proof.validator[0]) { + _slashAndDeregisterValidator(blsPubKeyHashFromIndex); } } } @@ -795,21 +833,17 @@ contract UniFiAVSManager is /** * @dev Internal function to slash and deregister a validator * @param blsPubKeyHash The BLS public key hash of the validator - * @param validatorIndex The index of the validator */ - function _slashAndDeregisterValidator(bytes32 blsPubKeyHash, uint64 validatorIndex) internal { + function _slashAndDeregisterValidator(bytes32 blsPubKeyHash) internal { UniFiAVSStorage storage $ = _getUniFiAVSManagerStorage(); ValidatorData storage validator = $.validators[blsPubKeyHash]; address operator = validator.operator; - if (validator.index == 0) { + uint64 validatorIndex = validator.index; + if (validatorIndex == 0) { revert ValidatorNotFound(); } - if (validator.registeredUntil <= block.number) { - revert ValidatorAlreadyDeregistered(); - } - $.slashedOperators[operator].push( InvalidValidator({ slashingBeneficiary: msg.sender, blsPubKeyHash: blsPubKeyHash }) ); @@ -817,7 +851,14 @@ contract UniFiAVSManager is // Update the registeredUntil field to deregister the validator immediately validator.registeredUntil = uint64(block.number); - delete $.validatorIndexes[validatorIndex]; + delete $.validatorRegistrations[blsPubKeyHash]; + delete $.validators[blsPubKeyHash]; + + // if the index and is pointing to the same validator, delete the index. + bytes32 pubkeyFromIndex = $.validatorIndexes[validatorIndex]; + if (pubkeyFromIndex == blsPubKeyHash) { + delete $.validatorIndexes[validatorIndex]; + } // Emit the ValidatorDeregistered event emit ValidatorDeregistered({ operator: operator, blsPubKeyHash: blsPubKeyHash }); diff --git a/l1-contracts/src/interfaces/IUniFiAVSManager.sol b/l1-contracts/src/interfaces/IUniFiAVSManager.sol index 58a4982..b520b80 100644 --- a/l1-contracts/src/interfaces/IUniFiAVSManager.sol +++ b/l1-contracts/src/interfaces/IUniFiAVSManager.sol @@ -84,7 +84,7 @@ interface IUniFiAVSManager { error InvalidArrayLengths(); /// @notice Thrown when a validator proof is invalid - error InvalidValidatorProof(); + error InvalidValidatorProof(bytes32 blsPubKeyHash); /// @notice Thrown when a validator index is already used error ValidatorIndexAlreadyUsed(); @@ -92,6 +92,9 @@ interface IUniFiAVSManager { /// @notice Thrown when an operator is slashed error OperatorSlashed(); + /// @notice Thrown when a validator is not backed by an EigenPod + error InvalidValidatorType(); + /** * @notice Emitted when a new operator is registered in the UniFi AVS. * @param operator The address of the registered operator. @@ -304,15 +307,16 @@ interface IUniFiAVSManager { function verifyValidatorSignatures(bytes32[] calldata blsPubKeyHashes) external; /** - * @notice Verifies the validator's presence on the beacon chain. - * @param blsPubKeyHashes The BLS public key hashes of the validators. + * @notice Slashes validators with invalid pubkey. * @param proofs The inclusion proofs for each validator. */ - function verifyValidatorOnBeaconChain( - bytes32[] calldata blsPubKeyHashes, - BeaconChainHelperLib.InclusionProof[] calldata proofs - ) external; + function slashValidatorsWithInvalidPubkey(BeaconChainHelperLib.InclusionProof[] calldata proofs) external; + /** + * @notice Slashes validators with invalid index. + * @param proofs The inclusion proofs for each validator. + */ + function slashValidatorsWithInvalidIndex(BeaconChainHelperLib.InclusionProof[] calldata proofs) external; /** * @notice Retrieves information about a specific operator. * @param operator The address of the operator. diff --git a/l1-contracts/src/lib/BeaconChainHelperLib.sol b/l1-contracts/src/lib/BeaconChainHelperLib.sol index 9739782..556f100 100644 --- a/l1-contracts/src/lib/BeaconChainHelperLib.sol +++ b/l1-contracts/src/lib/BeaconChainHelperLib.sol @@ -23,6 +23,8 @@ library BeaconChainHelperLib { bytes32[] beaconBlockProofForState; // Proof of inclusion of the validator index in the beacon block bytes32[] beaconBlockProofForProposerIndex; + // Timestamp of the beacon block + uint256 timestamp; } /// @dev The validator pub key failed verification against the pub key hash tree root in the validator chunks @@ -36,15 +38,8 @@ library BeaconChainHelperLib { /// @dev The proof that the actual validator index is a part of the beacon is invalid. error BeaconBlockProofForProposerIndex(); - function verifyValidator( - bytes32 validatorBLSPubKeyHash, - bytes32 beaconBlockRoot, - InclusionProof memory inclusionProof - ) internal pure returns (bool) { - // Validator's BLS public key is verified against the hash tree root within Validator chunks - if (validatorBLSPubKeyHash != inclusionProof.validator[0]) { - revert InvalidValidatorBLSPubKey(); - } + function verifyValidator(InclusionProof memory inclusionProof) internal returns (bool) { + (, bytes32 beaconBlockRoot) = getRootFromTimestamp(inclusionProof.timestamp); // Validator is verified against the validator list in the beacon state bytes32 validatorHashTreeRoot = MerkleUtils.merkleize(inclusionProof.validator); diff --git a/l1-contracts/src/structs/ValidatorData.sol b/l1-contracts/src/structs/ValidatorData.sol index d9b1568..e2e20a0 100644 --- a/l1-contracts/src/structs/ValidatorData.sol +++ b/l1-contracts/src/structs/ValidatorData.sol @@ -55,7 +55,8 @@ struct ValidatorRegistrationData { BN254.G1Point pubkeyG1; BN254.G2Point pubkeyG2; bytes32 salt; - uint256 expiry; + uint64 registeredAt; + uint64 expiry; } /** @@ -68,8 +69,8 @@ struct ValidatorRegistrationParams { BN254.G1Point pubkeyG1; BN254.G2Point pubkeyG2; uint64 index; - bytes32 salt; uint256 expiry; + bytes32 salt; } /** diff --git a/l1-contracts/test/UniFiAVSManager.t.sol b/l1-contracts/test/UniFiAVSManager.t.sol index 86a5581..5deb538 100644 --- a/l1-contracts/test/UniFiAVSManager.t.sol +++ b/l1-contracts/test/UniFiAVSManager.t.sol @@ -1272,7 +1272,9 @@ contract UniFiAVSManagerTest is UnitTestHelper { blsPubKeyHashes[0] = pubkeyHash; BeaconChainHelperLib.InclusionProof[] memory proofs = new BeaconChainHelperLib.InclusionProof[](1); + proofs[0] = BeaconProofs.eip4788ValidatorInclusionProof(); + proofs[0].timestamp = block.number + avsManager.getRegistrationDelay() + 1; // Mock the getRootFromTimestamp function to return the expected beacon block root vm.mockCall( address(0x000F3df6D732807Ef1319fB7B8bB8522d0Beac02), @@ -1281,7 +1283,7 @@ contract UniFiAVSManagerTest is UnitTestHelper { ); // Verify the validator on the beacon chain - avsManager.verifyValidatorOnBeaconChain(blsPubKeyHashes, proofs); + avsManager.slashValidatorsWithInvalidIndex(proofs); vm.roll(block.number + avsManager.getRegistrationDelay() + 1); // Check that the validator is still registered @@ -1289,7 +1291,7 @@ contract UniFiAVSManagerTest is UnitTestHelper { assertTrue(validator.registered, "Validator should still be registered after successful verification"); } - function testVerifyValidatorOnBeaconChainValidProofAndInvalidValidatorIndex() public { + function testSlashValidatorsWithInvalidIndex() public { _setupOperator(); _registerOperator(); vm.warp(block.timestamp + 50); @@ -1316,6 +1318,7 @@ contract UniFiAVSManagerTest is UnitTestHelper { BeaconChainHelperLib.InclusionProof[] memory proofs = new BeaconChainHelperLib.InclusionProof[](1); proofs[0] = BeaconProofs.eip4788ValidatorInclusionProof(); + proofs[0].timestamp = block.number + avsManager.getRegistrationDelay() + 1; // Mock the getRootFromTimestamp function to return the expected beacon block root vm.mockCall( address(0x000F3df6D732807Ef1319fB7B8bB8522d0Beac02), @@ -1324,7 +1327,7 @@ contract UniFiAVSManagerTest is UnitTestHelper { ); // Verify the validator on the beacon chain - avsManager.verifyValidatorOnBeaconChain(blsPubKeyHashes, proofs); + avsManager.slashValidatorsWithInvalidIndex(proofs); // Check that the validator is still registered ValidatorDataExtended memory validator = avsManager.getValidator(blsPubKeyHashes[0]); @@ -1334,7 +1337,7 @@ contract UniFiAVSManagerTest is UnitTestHelper { assertEq(operatorData.validatorCount, 0, "Operator should have no validators after slashing"); } - function testVerifyValidatorOnBeaconChainValidProofAndInvalidValidatorPubkey() public { + function testSlashValidatorsWithInvalidPubkey() public { _setupOperator(); _registerOperator(); vm.warp(block.timestamp + 50); @@ -1361,6 +1364,7 @@ contract UniFiAVSManagerTest is UnitTestHelper { BeaconChainHelperLib.InclusionProof[] memory proofs = new BeaconChainHelperLib.InclusionProof[](1); proofs[0] = BeaconProofs.eip4788ValidatorInclusionProof(); + proofs[0].timestamp = block.number + avsManager.getRegistrationDelay() + 1; // Mock the getRootFromTimestamp function to return the expected beacon block root vm.mockCall( address(0x000F3df6D732807Ef1319fB7B8bB8522d0Beac02), @@ -1369,7 +1373,7 @@ contract UniFiAVSManagerTest is UnitTestHelper { ); // Verify the validator on the beacon chain - avsManager.verifyValidatorOnBeaconChain(blsPubKeyHashes, proofs); + avsManager.slashValidatorsWithInvalidPubkey(proofs); // Check that the validator is still registered ValidatorDataExtended memory validator = avsManager.getValidator(bytes32(uint256(1234))); @@ -1379,7 +1383,7 @@ contract UniFiAVSManagerTest is UnitTestHelper { assertEq(operatorData.validatorCount, 0, "Operator should have no validators after slashing"); } - function testVerifyValidatorOnBeaconChainInvalidProof() public { + function testSlashValidatorsWithInvalidProof() public { _setupOperator(); _registerOperator(); vm.warp(block.timestamp + 50); @@ -1414,8 +1418,8 @@ contract UniFiAVSManagerTest is UnitTestHelper { ); // Expect the verification to fail - vm.expectRevert(IUniFiAVSManager.InvalidValidatorProof.selector); - avsManager.verifyValidatorOnBeaconChain(blsPubKeyHashes, proofs); + vm.expectRevert(abi.encodeWithSelector(IUniFiAVSManager.InvalidValidatorProof.selector, pubkeyHash)); + avsManager.slashValidatorsWithInvalidPubkey(proofs); // Check that the validator is not slashed nor deregistered vm.roll(block.number + avsManager.getDeregistrationDelay() + 1); diff --git a/l1-contracts/test/fixtures/BeaconProofs.sol b/l1-contracts/test/fixtures/BeaconProofs.sol index 6d687ee..06d2b9a 100644 --- a/l1-contracts/test/fixtures/BeaconProofs.sol +++ b/l1-contracts/test/fixtures/BeaconProofs.sol @@ -116,7 +116,7 @@ library BeaconProofs { return proof; } - function eip4788ValidatorInclusionProof() internal pure returns (BeaconChainHelperLib.InclusionProof memory) { + function eip4788ValidatorInclusionProof() internal view returns (BeaconChainHelperLib.InclusionProof memory) { return BeaconChainHelperLib.InclusionProof({ validator: validatorChunks(), validatorIndex: validatorIndex(), @@ -125,7 +125,8 @@ library BeaconProofs { beaconStateProof: beaconStateProofForValidatorList(), beaconStateRoot: beaconStateRoot(), beaconBlockProofForState: beaconBlockProofForBeaconState(), - beaconBlockProofForProposerIndex: beaconBlockProofForProposer() + beaconBlockProofForProposerIndex: beaconBlockProofForProposer(), + timestamp: block.timestamp }); } } From 9f5b00d583e986a80abe5d23f6eb6f7b84206ac1 Mon Sep 17 00:00:00 2001 From: 0xwalid Date: Wed, 16 Oct 2024 19:32:37 -0700 Subject: [PATCH 13/22] reduce redundant logic and remove unused functions --- l1-contracts/src/UniFiAVSManager.sol | 83 +++++++------------ .../src/interfaces/IUniFiAVSManager.sol | 5 -- .../src/lib/BLSSignatureCheckerLib.sol | 40 --------- .../src/storage/UniFiAVSManagerStorage.sol | 2 +- 4 files changed, 31 insertions(+), 99 deletions(-) diff --git a/l1-contracts/src/UniFiAVSManager.sol b/l1-contracts/src/UniFiAVSManager.sol index 9111f1d..06fc69d 100644 --- a/l1-contracts/src/UniFiAVSManager.sol +++ b/l1-contracts/src/UniFiAVSManager.sol @@ -1,14 +1,11 @@ // SPDX-License-Identifier: GPL-3.0 pragma solidity >=0.8.0 <0.9.0; -// OpenZeppelin Imports import { UUPSUpgradeable } from "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol"; import { EIP712 } from "@openzeppelin/contracts/utils/cryptography/EIP712.sol"; import { AccessManagedUpgradeable } from "@openzeppelin/contracts-upgradeable/access/manager/AccessManagedUpgradeable.sol"; import { EnumerableSet } from "@openzeppelin/contracts/utils/structs/EnumerableSet.sol"; - -// EigenLayer Imports import { ISignatureUtils } from "eigenlayer/interfaces/ISignatureUtils.sol"; import { IStrategy } from "eigenlayer/interfaces/IStrategy.sol"; import { IAVSDirectory } from "eigenlayer/interfaces/IAVSDirectory.sol"; @@ -17,10 +14,8 @@ import { IDelegationManager } from "eigenlayer/interfaces/IDelegationManager.sol import { IEigenPodManager } from "eigenlayer/interfaces/IEigenPodManager.sol"; import { IEigenPod } from "eigenlayer/interfaces/IEigenPod.sol"; import { BN254 } from "eigenlayer-middleware/libraries/BN254.sol"; -// Local Imports import { BLSSignatureCheckerLib } from "./lib/BLSSignatureCheckerLib.sol"; import { BeaconChainHelperLib } from "./lib/BeaconChainHelperLib.sol"; - import { IUniFiAVSManager } from "./interfaces/IUniFiAVSManager.sol"; import { UniFiAVSManagerStorage } from "./storage/UniFiAVSManagerStorage.sol"; import "./structs/ValidatorData.sol"; @@ -39,8 +34,6 @@ contract UniFiAVSManager is bytes32 public constant VALIDATOR_REGISTRATION_TYPEHASH = keccak256("BN254ValidatorRegistration(address operator,bytes32 salt,uint256 expiry,uint64 index)"); - uint256 public constant PROOF_TIMESTAMP_THRESHOLD = 12 hours; - /** * @notice The EigenPodManager * @custom:oz-upgrades-unsafe-allow state-variable-immutable @@ -353,31 +346,12 @@ contract UniFiAVSManager is external restricted { - UniFiAVSStorage storage $ = _getUniFiAVSManagerStorage(); - for (uint256 i = 0; i < proofs.length; i++) { BeaconChainHelperLib.InclusionProof memory proof = proofs[i]; bytes32 blsPubKeyHash = proof.validator[0]; - bool isValid = BeaconChainHelperLib.verifyValidator(proof); - - if (!isValid) { - revert InvalidValidatorProof(blsPubKeyHash); - } - - ValidatorData storage validator = $.validators[blsPubKeyHash]; - uint64 validatorIndex = validator.index; + (uint64 index,) = _validateProofAndGetValidatorKeys(proof, false); - if (validator.eigenPod != address(0)) { - revert InvalidValidatorType(); - } - ValidatorRegistrationData storage validatorRegistrationData = $.validatorRegistrations[blsPubKeyHash]; - uint64 registeredAt = validatorRegistrationData.registeredAt; - - if (proof.timestamp < registeredAt || proof.timestamp > validator.registeredUntil) { - revert InvalidValidatorProof(blsPubKeyHash); - } - - if (validatorIndex != proof.validatorIndex) { + if (index != proof.validatorIndex) { _slashAndDeregisterValidator(blsPubKeyHash); } } @@ -391,34 +365,12 @@ contract UniFiAVSManager is external restricted { - UniFiAVSStorage storage $ = _getUniFiAVSManagerStorage(); - for (uint256 i = 0; i < proofs.length; i++) { BeaconChainHelperLib.InclusionProof memory proof = proofs[i]; - uint256 index = proof.validatorIndex; - - bool isValid = BeaconChainHelperLib.verifyValidator(proof); + (, bytes32 storedBlsPubKeyHash) = _validateProofAndGetValidatorKeys(proof, true); - if (!isValid) { - revert InvalidValidatorProof(proof.validator[0]); - } - - bytes32 blsPubKeyHashFromIndex = $.validatorIndexes[index]; - ValidatorData storage validator = $.validators[blsPubKeyHashFromIndex]; - if (validator.eigenPod != address(0)) { - revert InvalidValidatorType(); - } - - ValidatorRegistrationData storage validatorRegistrationData = - $.validatorRegistrations[blsPubKeyHashFromIndex]; - uint64 registeredAt = validatorRegistrationData.registeredAt; - - if (proof.timestamp < registeredAt || proof.timestamp > validator.registeredUntil) { - revert InvalidValidatorProof(proof.validator[0]); - } - - if (blsPubKeyHashFromIndex != proof.validator[0]) { - _slashAndDeregisterValidator(blsPubKeyHashFromIndex); + if (storedBlsPubKeyHash != proof.validator[0]) { + _slashAndDeregisterValidator(storedBlsPubKeyHash); } } } @@ -830,6 +782,31 @@ contract UniFiAVSManager is emit DeregistrationDelaySet(oldDelay, newDelay); } + function _validateProofAndGetValidatorKeys(BeaconChainHelperLib.InclusionProof memory proof, bool checkIndex) + internal + returns (uint64, bytes32) + { + UniFiAVSStorage storage $ = _getUniFiAVSManagerStorage(); + + if (!BeaconChainHelperLib.verifyValidator(proof)) { + revert InvalidValidatorProof(proof.validator[0]); + } + + bytes32 storedBlsPubKeyHash = checkIndex ? $.validatorIndexes[proof.validatorIndex] : proof.validator[0]; + ValidatorData storage validator = $.validators[storedBlsPubKeyHash]; + + if (validator.eigenPod != address(0)) { + revert InvalidValidatorType(); + } + + ValidatorRegistrationData storage validatorRegistrationData = $.validatorRegistrations[storedBlsPubKeyHash]; + if (proof.timestamp < validatorRegistrationData.registeredAt || proof.timestamp > validator.registeredUntil) { + revert InvalidValidatorProof(proof.validator[0]); + } + + return (validator.index, storedBlsPubKeyHash); + } + /** * @dev Internal function to slash and deregister a validator * @param blsPubKeyHash The BLS public key hash of the validator diff --git a/l1-contracts/src/interfaces/IUniFiAVSManager.sol b/l1-contracts/src/interfaces/IUniFiAVSManager.sol index b520b80..55462b1 100644 --- a/l1-contracts/src/interfaces/IUniFiAVSManager.sol +++ b/l1-contracts/src/interfaces/IUniFiAVSManager.sol @@ -1,11 +1,9 @@ // SPDX-License-Identifier: GPL-3.0 pragma solidity >=0.8.0 <0.9.0; -// EigenLayer Imports import { IDelegationManager } from "eigenlayer/interfaces/IDelegationManager.sol"; import { ISignatureUtils } from "eigenlayer/interfaces/ISignatureUtils.sol"; import { BN254 } from "eigenlayer-middleware/libraries/BN254.sol"; -// Local Imports import { IAVSDirectoryExtended } from "../interfaces/EigenLayer/IAVSDirectoryExtended.sol"; import "../structs/ValidatorData.sol"; import "../structs/OperatorData.sol"; @@ -80,9 +78,6 @@ interface IUniFiAVSManager { /// @notice Thrown when a signature is expired error SignatureExpired(); - /// @notice Thrown when the lengths of the input arrays are not equal - error InvalidArrayLengths(); - /// @notice Thrown when a validator proof is invalid error InvalidValidatorProof(bytes32 blsPubKeyHash); diff --git a/l1-contracts/src/lib/BLSSignatureCheckerLib.sol b/l1-contracts/src/lib/BLSSignatureCheckerLib.sol index 273f4f8..557d954 100644 --- a/l1-contracts/src/lib/BLSSignatureCheckerLib.sol +++ b/l1-contracts/src/lib/BLSSignatureCheckerLib.sol @@ -35,44 +35,4 @@ library BLSSignatureCheckerLib { pubkeyG2 ); } - - function bytesToG1Point(bytes memory pubkey) internal pure returns (BN254.G1Point memory) { - // require(pubkey.length == 48, "Invalid public key length"); - - // Extract X and Y coordinates - uint256 x; - uint256 y; - - assembly { - x := mload(add(pubkey, 32)) - y := mload(add(pubkey, 64)) - } - - // Ensure the most significant bit of y is 0 (positive y-coordinate) - y = y & ((1 << 255) - 1); - - return BN254.G1Point(x, y); - } - - function g1PointToBytes(BN254.G1Point memory point) internal pure returns (bytes memory) { - bytes memory result = new bytes(48); - - assembly { - // Store X coordinate - mstore(add(result, 32), mload(point)) - - // Store Y coordinate - // Set the most significant bit to 1 if Y is odd, 0 if Y is even - let y := mload(add(point, 32)) - let yMod2 := mod(y, 2) - y := or(and(y, not(shl(255, 1))), shl(255, yMod2)) - mstore(add(result, 64), y) - } - - return result; - } - - function hashG1Point(BN254.G1Point memory pk) internal pure returns (bytes32 hashedG1) { - return sha256(abi.encodePacked(g1PointToBytes(pk), bytes16(0))); - } } diff --git a/l1-contracts/src/storage/UniFiAVSManagerStorage.sol b/l1-contracts/src/storage/UniFiAVSManagerStorage.sol index 5e9fb33..e62ae16 100644 --- a/l1-contracts/src/storage/UniFiAVSManagerStorage.sol +++ b/l1-contracts/src/storage/UniFiAVSManagerStorage.sol @@ -27,7 +27,7 @@ abstract contract UniFiAVSManagerStorage { // Delay after which a validator can be considered registered uint64 registerationDelay; // Slashed operators mapping - mapping(address => InvalidValidator[]) slashedOperators; // operator => InvalidValidator[] + mapping(address operator => InvalidValidator[] invalidValidators) slashedOperators; } /** From f0c7b9366f5229e7e37ade3b3cd8183a8ed1a8e6 Mon Sep 17 00:00:00 2001 From: 0xwalid Date: Wed, 16 Oct 2024 20:13:29 -0700 Subject: [PATCH 14/22] remvoe proposer index verification --- l1-contracts/src/UniFiAVSManager.sol | 2 +- l1-contracts/src/lib/BeaconChainHelperLib.sol | 17 ++--------------- 2 files changed, 3 insertions(+), 16 deletions(-) diff --git a/l1-contracts/src/UniFiAVSManager.sol b/l1-contracts/src/UniFiAVSManager.sol index 06fc69d..f29ae5c 100644 --- a/l1-contracts/src/UniFiAVSManager.sol +++ b/l1-contracts/src/UniFiAVSManager.sol @@ -788,7 +788,7 @@ contract UniFiAVSManager is { UniFiAVSStorage storage $ = _getUniFiAVSManagerStorage(); - if (!BeaconChainHelperLib.verifyValidator(proof)) { + if (!BeaconChainHelperLib.verifyValidatorExistence(proof)) { revert InvalidValidatorProof(proof.validator[0]); } diff --git a/l1-contracts/src/lib/BeaconChainHelperLib.sol b/l1-contracts/src/lib/BeaconChainHelperLib.sol index 556f100..18ba7ff 100644 --- a/l1-contracts/src/lib/BeaconChainHelperLib.sol +++ b/l1-contracts/src/lib/BeaconChainHelperLib.sol @@ -21,7 +21,7 @@ library BeaconChainHelperLib { bytes32 beaconStateRoot; // Proof of inclusion of beacon state in the beacon block bytes32[] beaconBlockProofForState; - // Proof of inclusion of the validator index in the beacon block + // Proof of inclusion of the validator index in the beacon block. leave this empty if not needed. bytes32[] beaconBlockProofForProposerIndex; // Timestamp of the beacon block uint256 timestamp; @@ -38,7 +38,7 @@ library BeaconChainHelperLib { /// @dev The proof that the actual validator index is a part of the beacon is invalid. error BeaconBlockProofForProposerIndex(); - function verifyValidator(InclusionProof memory inclusionProof) internal returns (bool) { + function verifyValidatorExistence(InclusionProof memory inclusionProof) internal returns (bool) { (, bytes32 beaconBlockRoot) = getRootFromTimestamp(inclusionProof.timestamp); // Validator is verified against the validator list in the beacon state @@ -75,19 +75,6 @@ library BeaconChainHelperLib { return false; } - // Validator index is verified against the beacon block - if ( - !MerkleUtils.verifyProof( - inclusionProof.beaconBlockProofForProposerIndex, - beaconBlockRoot, - MerkleUtils.toLittleEndian(inclusionProof.validatorIndex), - 1 - ) - ) { - // Revert if the proof that the proposer index is a part of the beacon block fails - return false; - } - return true; } From 67423ee3ecdfa59b527eb8027c9915376151349d Mon Sep 17 00:00:00 2001 From: 0xwalid Date: Thu, 24 Oct 2024 19:49:25 -1000 Subject: [PATCH 15/22] address comments and add dispute manager contract --- l1-contracts/script/DeployEverything.s.sol | 25 ++++++- .../script/DeployUniFiAVSManager.s.sol | 10 ++- .../DeployUniFiAVSManagerWithMocks.s.sol | 19 ++++- l1-contracts/script/DeploymentStructs.sol | 2 + l1-contracts/script/Roles.sol | 3 + l1-contracts/script/SetupAccess.s.sol | 39 +++++++--- .../script/UpgradeMainnetUniFiAVS.s.sol | 19 ++++- l1-contracts/src/UniFiAVSDisputeManager.sol | 48 +++++++++++++ l1-contracts/src/UniFiAVSManager.sol | 71 +++++++++++-------- .../interfaces/IUniFiAVSDisputeManager.sol | 31 ++++++++ .../src/interfaces/IUniFiAVSManager.sol | 7 +- l1-contracts/src/lib/BeaconChainHelperLib.sol | 4 +- .../storage/UniFiAVSDisputeManagerStorage.sol | 36 ++++++++++ .../src/storage/UniFiAVSManagerStorage.sol | 9 ++- l1-contracts/src/structs/OperatorData.sol | 3 +- l1-contracts/test/UniFiAVSManager.t.sol | 29 ++++---- .../test/forks/UniFiAVSManagerForkTest.sol | 5 +- l1-contracts/test/helpers/UnitTestHelper.sol | 5 +- 18 files changed, 289 insertions(+), 76 deletions(-) create mode 100644 l1-contracts/src/UniFiAVSDisputeManager.sol create mode 100644 l1-contracts/src/interfaces/IUniFiAVSDisputeManager.sol create mode 100644 l1-contracts/src/storage/UniFiAVSDisputeManagerStorage.sol diff --git a/l1-contracts/script/DeployEverything.s.sol b/l1-contracts/script/DeployEverything.s.sol index 9684710..3e62b2d 100644 --- a/l1-contracts/script/DeployEverything.s.sol +++ b/l1-contracts/script/DeployEverything.s.sol @@ -6,8 +6,8 @@ import { DeployUniFiAVSManager } from "script/DeployUniFiAVSManager.s.sol"; import { SetupAccess } from "script/SetupAccess.s.sol"; import { AccessManager } from "@openzeppelin/contracts/access/manager/AccessManager.sol"; import { AVSDeployment } from "script/DeploymentStructs.sol"; -import { console } from "forge-std/console.sol"; - +import { UniFiAVSDisputeManager } from "../src/UniFiAVSDisputeManager.sol"; +import { ERC1967Proxy } from "@openzeppelin/contracts/proxy/ERC1967/ERC1967Proxy.sol"; /** * @title Deploy all protocol contracts * @author Puffer Finance @@ -15,6 +15,7 @@ import { console } from "forge-std/console.sol"; * @dev Example on how to run the script * forge script script/DeployEverything.s.sol:DeployEverything --rpc-url=$RPC_URL --sig 'run()' --broadcast */ + contract DeployEverything is BaseScript { address DAO; @@ -28,16 +29,32 @@ contract DeployEverything is BaseScript { vm.startBroadcast(_deployerPrivateKey); AccessManager accessManager = new AccessManager(_broadcaster); + + // Deploy DisputeManager + UniFiAVSDisputeManager disputeManagerImplementation = new UniFiAVSDisputeManager(); + address disputeManager = address( + new ERC1967Proxy{ salt: bytes32("UniFiAVSDisputeManager") }( + address(disputeManagerImplementation), + abi.encodeCall(UniFiAVSDisputeManager.initialize, (address(accessManager))) + ) + ); + vm.stopBroadcast(); // 1. Deploy AVSManager (address avsManagerImplementation, address avsManagerProxy) = new DeployUniFiAVSManager().run( - address(accessManager), eigenPodManager, eigenDelegationManager, avsDirectory, initialDeregistrationDelay + address(accessManager), + eigenPodManager, + eigenDelegationManager, + avsDirectory, + initialDeregistrationDelay, + disputeManager ); deployment.avsManagerImplementation = avsManagerImplementation; deployment.avsManagerProxy = avsManagerProxy; deployment.accessManager = address(accessManager); + deployment.disputeManagerProxy = disputeManager; // `anvil` in the terminal if (_localAnvil) { @@ -65,6 +82,8 @@ contract DeployEverything is BaseScript { vm.serializeAddress(obj, "avsManagerImplementation", deployment.avsManagerImplementation); vm.serializeAddress(obj, "avsManagerProxy", deployment.avsManagerProxy); vm.serializeAddress(obj, "accessManager", deployment.accessManager); + vm.serializeAddress(obj, "disputeManagerImplementation", deployment.disputeManagerImplementation); + vm.serializeAddress(obj, "disputeManagerProxy", deployment.disputeManagerProxy); vm.serializeAddress(obj, "dao", DAO); string memory finalJson = vm.serializeString(obj, "", ""); diff --git a/l1-contracts/script/DeployUniFiAVSManager.s.sol b/l1-contracts/script/DeployUniFiAVSManager.s.sol index 3a42b61..b9d2a5b 100644 --- a/l1-contracts/script/DeployUniFiAVSManager.s.sol +++ b/l1-contracts/script/DeployUniFiAVSManager.s.sol @@ -7,7 +7,7 @@ import { IEigenPodManager } from "eigenlayer/interfaces/IEigenPodManager.sol"; import { IDelegationManager } from "eigenlayer/interfaces/IDelegationManager.sol"; import { IAVSDirectory } from "eigenlayer/interfaces/IAVSDirectory.sol"; import { ERC1967Proxy } from "@openzeppelin/contracts/proxy/ERC1967/ERC1967Proxy.sol"; -import { console } from "forge-std/console.sol"; +import { IUniFiAVSDisputeManager } from "../src/interfaces/IUniFiAVSDisputeManager.sol"; contract DeployUniFiAVSManager is BaseScript { UniFiAVSManager public uniFiAVSManagerProxy; @@ -17,11 +17,15 @@ contract DeployUniFiAVSManager is BaseScript { address eigenPodManager, address eigenDelegationManager, address avsDirectory, - uint64 initialDeregistrationDelay + uint64 initialDeregistrationDelay, + address disputeManager ) public returns (address, address) { vm.startBroadcast(_deployerPrivateKey); UniFiAVSManager uniFiAVSManagerImplementation = new UniFiAVSManager( - IEigenPodManager(eigenPodManager), IDelegationManager(eigenDelegationManager), IAVSDirectory(avsDirectory) + IEigenPodManager(eigenPodManager), + IDelegationManager(eigenDelegationManager), + IAVSDirectory(avsDirectory), + IUniFiAVSDisputeManager(disputeManager) ); uniFiAVSManagerProxy = UniFiAVSManager( diff --git a/l1-contracts/script/DeployUniFiAVSManagerWithMocks.s.sol b/l1-contracts/script/DeployUniFiAVSManagerWithMocks.s.sol index 9f422db..57f6ba7 100644 --- a/l1-contracts/script/DeployUniFiAVSManagerWithMocks.s.sol +++ b/l1-contracts/script/DeployUniFiAVSManagerWithMocks.s.sol @@ -8,11 +8,12 @@ import { IDelegationManager } from "eigenlayer/interfaces/IDelegationManager.sol import { IAVSDirectory } from "eigenlayer/interfaces/IAVSDirectory.sol"; import { ERC1967Proxy } from "@openzeppelin/contracts/proxy/ERC1967/ERC1967Proxy.sol"; import { AccessManager } from "@openzeppelin/contracts/access/manager/AccessManager.sol"; -import "forge-std/console.sol"; - import "../test/mocks/MockEigenPodManager.sol"; import "../test/mocks/MockDelegationManager.sol"; import "../test/mocks/MockAVSDirectory.sol"; +import "../src/UniFiAVSDisputeManager.sol"; +import { IUniFiAVSDisputeManager } from "../src/interfaces/IUniFiAVSDisputeManager.sol"; +import { console } from "forge-std/console.sol"; contract DeployUniFiAVSManagerWithMocks is BaseScript { UniFiAVSManager public uniFiAVSManagerProxy; @@ -21,6 +22,7 @@ contract DeployUniFiAVSManagerWithMocks is BaseScript { address eigenDelegationManager; address avsDirectory; uint64 initialDeregistrationDelay = 0; + address disputeManager; function run() public broadcast returns (address, address) { eigenPodManager = address(new MockEigenPodManager()); @@ -28,9 +30,19 @@ contract DeployUniFiAVSManagerWithMocks is BaseScript { avsDirectory = address(new MockAVSDirectory()); accessManager = new AccessManager(_broadcaster); + UniFiAVSDisputeManager disputeManagerImplementation = new UniFiAVSDisputeManager(); + disputeManager = address( + new ERC1967Proxy{ salt: bytes32("UniFiAVSDisputeManager") }( + address(disputeManagerImplementation), + abi.encodeCall(UniFiAVSDisputeManager.initialize, (address(accessManager))) + ) + ); UniFiAVSManager uniFiAVSManagerImplementation = new UniFiAVSManager( - IEigenPodManager(eigenPodManager), IDelegationManager(eigenDelegationManager), IAVSDirectory(avsDirectory) + IEigenPodManager(eigenPodManager), + IDelegationManager(eigenDelegationManager), + IAVSDirectory(avsDirectory), + IUniFiAVSDisputeManager(disputeManager) ); uniFiAVSManagerProxy = UniFiAVSManager( @@ -49,6 +61,7 @@ contract DeployUniFiAVSManagerWithMocks is BaseScript { console.log("eigenPodManager mock:", address(eigenPodManager)); console.log("eigenDelegationManager mock:", address(eigenDelegationManager)); console.log("avsDirectory mock:", address(avsDirectory)); + console.log("disputeManager mock:", address(disputeManager)); return (address(uniFiAVSManagerImplementation), address(uniFiAVSManagerProxy)); } diff --git a/l1-contracts/script/DeploymentStructs.sol b/l1-contracts/script/DeploymentStructs.sol index bbb6594..d27c701 100644 --- a/l1-contracts/script/DeploymentStructs.sol +++ b/l1-contracts/script/DeploymentStructs.sol @@ -10,4 +10,6 @@ struct AVSDeployment { address accessManager; address timelock; address dao; + address disputeManagerProxy; + address disputeManagerImplementation; } diff --git a/l1-contracts/script/Roles.sol b/l1-contracts/script/Roles.sol index 617a1a3..cc54bd9 100644 --- a/l1-contracts/script/Roles.sol +++ b/l1-contracts/script/Roles.sol @@ -30,3 +30,6 @@ uint64 constant ROLE_ID_AVS_COORDINATOR_ALLOWLISTER = 5; // Lockbox role for ETH Mainnet uint64 constant ROLE_ID_LOCKBOX = 7; + +// Role for UniFiAVSManager +uint64 constant ROLE_ID_UNIFI_AVS_MANAGER = 30; diff --git a/l1-contracts/script/SetupAccess.s.sol b/l1-contracts/script/SetupAccess.s.sol index 1d3fafa..c70dc1b 100644 --- a/l1-contracts/script/SetupAccess.s.sol +++ b/l1-contracts/script/SetupAccess.s.sol @@ -8,14 +8,15 @@ import { AccessManager } from "@openzeppelin/contracts/access/manager/AccessMana import { Multicall } from "@openzeppelin/contracts/utils/Multicall.sol"; import { UniFiAVSManager } from "../src/UniFiAVSManager.sol"; import { AVSDeployment } from "script/DeploymentStructs.sol"; - +import { UniFiAVSDisputeManager } from "../src/UniFiAVSDisputeManager.sol"; import { ROLE_ID_OPERATIONS_MULTISIG, ROLE_ID_OPERATIONS_PAYMASTER, ROLE_ID_PUFFER_PROTOCOL, ROLE_ID_DAO, ROLE_ID_OPERATIONS_COORDINATOR, - ROLE_ID_VT_PRICER + ROLE_ID_VT_PRICER, + ROLE_ID_UNIFI_AVS_MANAGER } from "../script/Roles.sol"; contract SetupAccess is BaseScript { @@ -31,6 +32,7 @@ contract SetupAccess is BaseScript { bytes[] memory calldatas = _generateAccessCalldata({ rolesCalldatas: _grantRoles(dao), uniFiAVSManagerRoles: _setupUniFiAVSManagerRoles(), + uniFiAVSDisputeManagerRoles: _setupUniFiAVSDisputeManagerRoles(), roleLabels: _labelRoles() }); @@ -43,21 +45,26 @@ contract SetupAccess is BaseScript { function _generateAccessCalldata( bytes[] memory rolesCalldatas, bytes[] memory uniFiAVSManagerRoles, + bytes[] memory uniFiAVSDisputeManagerRoles, bytes[] memory roleLabels ) internal view returns (bytes[] memory calldatas) { - calldatas = new bytes[](4); + calldatas = new bytes[](6); calldatas[0] = rolesCalldatas[0]; + calldatas[1] = rolesCalldatas[1]; - calldatas[1] = uniFiAVSManagerRoles[0]; - calldatas[2] = uniFiAVSManagerRoles[1]; - - calldatas[3] = roleLabels[0]; + calldatas[2] = uniFiAVSManagerRoles[0]; + calldatas[3] = uniFiAVSManagerRoles[1]; + calldatas[4] = uniFiAVSDisputeManagerRoles[0]; + calldatas[5] = roleLabels[0]; } function _grantRoles(address dao) internal view returns (bytes[] memory) { - bytes[] memory calldatas = new bytes[](1); + bytes[] memory calldatas = new bytes[](2); calldatas[0] = abi.encodeWithSelector(AccessManager.grantRole.selector, ROLE_ID_DAO, dao, 0); + calldatas[1] = abi.encodeWithSelector( + AccessManager.grantRole.selector, ROLE_ID_UNIFI_AVS_MANAGER, avsDeployment.avsManagerProxy, 0 + ); return calldatas; } @@ -111,4 +118,20 @@ contract SetupAccess is BaseScript { return calldatas; } + + function _setupUniFiAVSDisputeManagerRoles() internal view returns (bytes[] memory) { + bytes[] memory calldatas = new bytes[](1); + + bytes4[] memory selectors = new bytes4[](1); + selectors[0] = UniFiAVSDisputeManager.slashOperator.selector; + + calldatas[0] = abi.encodeWithSelector( + AccessManager.setTargetFunctionRole.selector, + address(avsDeployment.disputeManagerProxy), + selectors, + ROLE_ID_UNIFI_AVS_MANAGER + ); + + return calldatas; + } } diff --git a/l1-contracts/script/UpgradeMainnetUniFiAVS.s.sol b/l1-contracts/script/UpgradeMainnetUniFiAVS.s.sol index 63f6b4b..ac7b908 100644 --- a/l1-contracts/script/UpgradeMainnetUniFiAVS.s.sol +++ b/l1-contracts/script/UpgradeMainnetUniFiAVS.s.sol @@ -12,6 +12,9 @@ import { IAVSDirectory } from "eigenlayer/interfaces/IAVSDirectory.sol"; import { console } from "forge-std/console.sol"; import { ROLE_ID_OPERATIONS_MULTISIG, ROLE_ID_DAO } from "./Roles.sol"; import { UUPSUpgradeable } from "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol"; +import { UniFiAVSDisputeManager } from "../src/UniFiAVSDisputeManager.sol"; +import { IUniFiAVSDisputeManager } from "../src/interfaces/IUniFiAVSDisputeManager.sol"; +import { ERC1967Proxy } from "@openzeppelin/contracts/proxy/ERC1967/ERC1967Proxy.sol"; contract UpgradeMainnetUniFiAVS is BaseScript { function run() public returns (AVSDeployment memory deployment) { @@ -25,8 +28,22 @@ contract UpgradeMainnetUniFiAVS is BaseScript { uint64 initialDeregistrationDelay = 0; AccessManager accessManager = AccessManager(accessManagerAddress); + // Deploy DisputeManager + UniFiAVSDisputeManager disputeManagerImplementation = new UniFiAVSDisputeManager(); + address disputeManager = address( + new ERC1967Proxy{ salt: bytes32("UniFiAVSDisputeManager") }( + address(disputeManagerImplementation), + abi.encodeCall(UniFiAVSDisputeManager.initialize, (address(accessManager))) + ) + ); + console.log("DisputeManager implementation:", address(disputeManagerImplementation)); + console.log("DisputeManager proxy:", disputeManager); + UniFiAVSManager uniFiAVSManagerImplementation = new UniFiAVSManager( - IEigenPodManager(eigenPodManager), IDelegationManager(eigenDelegationManager), IAVSDirectory(avsDirectory) + IEigenPodManager(eigenPodManager), + IDelegationManager(eigenDelegationManager), + IAVSDirectory(avsDirectory), + IUniFiAVSDisputeManager(disputeManager) ); console.log("UniFiAVSManager Implementation:", address(uniFiAVSManagerImplementation)); diff --git a/l1-contracts/src/UniFiAVSDisputeManager.sol b/l1-contracts/src/UniFiAVSDisputeManager.sol new file mode 100644 index 0000000..6978459 --- /dev/null +++ b/l1-contracts/src/UniFiAVSDisputeManager.sol @@ -0,0 +1,48 @@ +pragma solidity >=0.8.0 <0.9.0; + +import "./storage/UniFiAVSDisputeManagerStorage.sol"; +import "./structs/ValidatorData.sol"; +import { AccessManagedUpgradeable } from + "@openzeppelin/contracts-upgradeable/access/manager/AccessManagedUpgradeable.sol"; +import { IUniFiAVSDisputeManager } from "./interfaces/IUniFiAVSDisputeManager.sol"; +/** + * @title UniFiAVSDisputeManager + * @dev Manages disputes and slashing of operators. + */ + +contract UniFiAVSDisputeManager is IUniFiAVSDisputeManager, UniFiAVSDisputeManagerStorage, AccessManagedUpgradeable { + constructor() { + _disableInitializers(); + } + + function initialize(address accessManager) external initializer { + __AccessManaged_init(accessManager); + } + + /** + * @inheritdoc IUniFiAVSDisputeManager + * @dev restricted to the UniFiAVSManager + */ + function slashOperator(address operator, bytes32[] calldata validators, address slashingBeneficiary) + external + restricted + { + UniFiAVSDisputeStorage storage $ = _getUniFiAVSDisputeStorage(); + + for (uint256 i = 0; i < validators.length; i++) { + $.slashedOperators[operator].push( + InvalidValidator({ slashingBeneficiary: slashingBeneficiary, blsPubKeyHash: validators[i] }) + ); + } + + emit OperatorSlashed(operator, validators, slashingBeneficiary); + } + + /** + * @inheritdoc IUniFiAVSDisputeManager + */ + function isOperatorSlashed(address operator) external view returns (bool) { + UniFiAVSDisputeStorage storage $ = _getUniFiAVSDisputeStorage(); + return $.slashedOperators[operator].length > 0; + } +} diff --git a/l1-contracts/src/UniFiAVSManager.sol b/l1-contracts/src/UniFiAVSManager.sol index f29ae5c..21417eb 100644 --- a/l1-contracts/src/UniFiAVSManager.sol +++ b/l1-contracts/src/UniFiAVSManager.sol @@ -20,6 +20,7 @@ import { IUniFiAVSManager } from "./interfaces/IUniFiAVSManager.sol"; import { UniFiAVSManagerStorage } from "./storage/UniFiAVSManagerStorage.sol"; import "./structs/ValidatorData.sol"; import "./structs/OperatorData.sol"; +import { IUniFiAVSDisputeManager } from "./interfaces/IUniFiAVSDisputeManager.sol"; contract UniFiAVSManager is UniFiAVSManagerStorage, @@ -30,8 +31,8 @@ contract UniFiAVSManager is { using EnumerableSet for EnumerableSet.AddressSet; - address public constant BEACON_CHAIN_STRATEGY = 0xbeaC0eeEeeeeEEeEeEEEEeeEEeEeeeEeeEEBEaC0; - bytes32 public constant VALIDATOR_REGISTRATION_TYPEHASH = + address private constant BEACON_CHAIN_STRATEGY = 0xbeaC0eeEeeeeEEeEeEEEEeeEEeEeeeEeeEEBEaC0; + bytes32 private constant VALIDATOR_REGISTRATION_TYPEHASH = keccak256("BN254ValidatorRegistration(address operator,bytes32 salt,uint256 expiry,uint64 index)"); /** @@ -49,6 +50,11 @@ contract UniFiAVSManager is * @custom:oz-upgrades-unsafe-allow state-variable-immutable */ IAVSDirectoryExtended public immutable override AVS_DIRECTORY; + /** + * @notice The UniFiAVSDisputeManager contract + * @custom:oz-upgrades-unsafe-allow state-variable-immutable + */ + IUniFiAVSDisputeManager public immutable DISPUTE_MANAGER; /** * @dev Modifier to check if the pod is delegated to the msg.sender @@ -87,11 +93,13 @@ contract UniFiAVSManager is constructor( IEigenPodManager eigenPodManagerAddress, IDelegationManager eigenDelegationManagerAddress, - IAVSDirectory avsDirectoryAddress + IAVSDirectory avsDirectoryAddress, + IUniFiAVSDisputeManager disputeManagerAddress ) EIP712("UniFiAVSManager", "1") { EIGEN_POD_MANAGER = eigenPodManagerAddress; EIGEN_DELEGATION_MANAGER = eigenDelegationManagerAddress; AVS_DIRECTORY = IAVSDirectoryExtended(address(avsDirectoryAddress)); + DISPUTE_MANAGER = disputeManagerAddress; _disableInitializers(); } @@ -229,7 +237,7 @@ contract UniFiAVSManager is * @inheritdoc IUniFiAVSManager * @dev Restricted in this context is like `whenNotPaused` modifier from Pausable.sol */ - function registerValidatorsOptimistically(ValidatorRegistrationParams[] memory paramsArray) + function registerValidatorsOptimistically(ValidatorRegistrationParams[] calldata validators) external registeredOperator(msg.sender) restricted @@ -238,11 +246,9 @@ contract UniFiAVSManager is uint256 newValidatorCount = 0; bytes memory delegateKey = $.operators[msg.sender].commitment.delegateKey; - for (uint256 i = 0; i < paramsArray.length; i++) { - ValidatorRegistrationParams memory params = paramsArray[i]; - + for (uint256 i = 0; i < validators.length; i++) { // Derive the BLS public key hash from pubkeyG1 - bytes32 blsPubKeyHash = params.blsPubKeyHash; + bytes32 blsPubKeyHash = validators[i].blsPubKeyHash; ValidatorData storage existingValidator = $.validators[blsPubKeyHash]; ValidatorRegistrationData storage validatorRegistrationData = $.validatorRegistrations[blsPubKeyHash]; @@ -252,46 +258,46 @@ contract UniFiAVSManager is revert ValidatorAlreadyRegistered(); } - if ($.validatorIndexes[params.index] != bytes32(0)) { + if ($.validatorIndexes[validators[i].index] != bytes32(0)) { revert ValidatorIndexAlreadyUsed(); } // Check if the registration salt has been used before - if (validatorRegistrationData.salt == params.salt) { + if (validatorRegistrationData.salt == validators[i].salt) { revert SaltAlreadyUsed(); } // Check if the signature is expired - if (block.timestamp > params.expiry) { + if (block.timestamp > validators[i].expiry) { revert SignatureExpired(); } // Store the validator data $.validators[blsPubKeyHash] = ValidatorData({ eigenPod: address(0), // Not from EigenPod - index: params.index, // Store the provided index + index: validators[i].index, // Store the provided index operator: msg.sender, registeredUntil: type(uint64).max, registeredAfter: uint64(block.number) + $.registerationDelay }); $.validatorRegistrations[blsPubKeyHash] = ValidatorRegistrationData({ - registrationSignature: params.registrationSignature, - pubkeyG1: params.pubkeyG1, - pubkeyG2: params.pubkeyG2, - salt: params.salt, - expiry: uint64(params.expiry), + registrationSignature: validators[i].registrationSignature, + pubkeyG1: validators[i].pubkeyG1, + pubkeyG2: validators[i].pubkeyG2, + salt: validators[i].salt, + expiry: uint64(validators[i].expiry), registeredAt: uint64(block.number) }); - $.validatorIndexes[params.index] = blsPubKeyHash; + $.validatorIndexes[validators[i].index] = blsPubKeyHash; emit ValidatorRegistered({ podOwner: address(0), operator: msg.sender, delegateKey: delegateKey, blsPubKeyHash: blsPubKeyHash, - validatorIndex: params.index + validatorIndex: validators[i].index }); newValidatorCount++; @@ -317,7 +323,6 @@ contract UniFiAVSManager is // Calculate the hash using EIP-712 BN254.G1Point memory messageHash = blsMessageHash({ - typeHash: VALIDATOR_REGISTRATION_TYPEHASH, operator: operator, salt: validatorRegistrationData.salt, expiry: validatorRegistrationData.expiry, @@ -392,7 +397,7 @@ contract UniFiAVSManager is revert DeregistrationAlreadyStarted(); } - if ($.slashedOperators[msg.sender].length > 0) { + if (DISPUTE_MANAGER.isOperatorSlashed(msg.sender)) { revert OperatorSlashed(); } @@ -698,12 +703,16 @@ contract UniFiAVSManager is /** * @inheritdoc IUniFiAVSManager */ - function blsMessageHash(bytes32 typeHash, address operator, bytes32 salt, uint256 expiry, uint256 index) + function blsMessageHash(address operator, bytes32 salt, uint256 expiry, uint256 index) public view returns (BN254.G1Point memory) { - return BN254.hashToG1(_hashTypedDataV4(keccak256(abi.encodePacked(typeHash, operator, salt, expiry, index)))); + return BN254.hashToG1( + _hashTypedDataV4( + keccak256(abi.encodePacked(VALIDATOR_REGISTRATION_TYPEHASH, operator, salt, expiry, index)) + ) + ); } // INTERNAL FUNCTIONS @@ -721,7 +730,8 @@ contract UniFiAVSManager is startDeregisterOperatorBlock: operatorData.startDeregisterOperatorBlock, isRegistered: AVS_DIRECTORY.avsOperatorStatus(address(this), operator) == IAVSDirectory.OperatorAVSRegistrationStatus.REGISTERED, - commitmentValidAfter: operatorData.commitmentValidAfter + commitmentValidAfter: operatorData.commitmentValidAfter, + isSlashed: DISPUTE_MANAGER.isOperatorSlashed(operator) }); } @@ -782,7 +792,7 @@ contract UniFiAVSManager is emit DeregistrationDelaySet(oldDelay, newDelay); } - function _validateProofAndGetValidatorKeys(BeaconChainHelperLib.InclusionProof memory proof, bool checkIndex) + function _validateProofAndGetValidatorKeys(BeaconChainHelperLib.InclusionProof memory proof, bool queryByProofIndex) internal returns (uint64, bytes32) { @@ -792,7 +802,7 @@ contract UniFiAVSManager is revert InvalidValidatorProof(proof.validator[0]); } - bytes32 storedBlsPubKeyHash = checkIndex ? $.validatorIndexes[proof.validatorIndex] : proof.validator[0]; + bytes32 storedBlsPubKeyHash = queryByProofIndex ? $.validatorIndexes[proof.validatorIndex] : proof.validator[0]; ValidatorData storage validator = $.validators[storedBlsPubKeyHash]; if (validator.eigenPod != address(0)) { @@ -821,9 +831,10 @@ contract UniFiAVSManager is revert ValidatorNotFound(); } - $.slashedOperators[operator].push( - InvalidValidator({ slashingBeneficiary: msg.sender, blsPubKeyHash: blsPubKeyHash }) - ); + // Call the dispute manager to slash the operator + bytes32[] memory validators = new bytes32[](1); + validators[0] = blsPubKeyHash; + DISPUTE_MANAGER.slashOperator(operator, validators, msg.sender); // Update the registeredUntil field to deregister the validator immediately validator.registeredUntil = uint64(block.number); @@ -840,7 +851,7 @@ contract UniFiAVSManager is // Emit the ValidatorDeregistered event emit ValidatorDeregistered({ operator: operator, blsPubKeyHash: blsPubKeyHash }); - emit ValidatorSlashed(operator, blsPubKeyHash); + emit ValidatorSlashed({ operator: operator, blsPubKeyHash: blsPubKeyHash }); // Decrement the operator's validator count OperatorData storage operatorData = $.operators[operator]; diff --git a/l1-contracts/src/interfaces/IUniFiAVSDisputeManager.sol b/l1-contracts/src/interfaces/IUniFiAVSDisputeManager.sol new file mode 100644 index 0000000..d973399 --- /dev/null +++ b/l1-contracts/src/interfaces/IUniFiAVSDisputeManager.sol @@ -0,0 +1,31 @@ +// SPDX-License-Identifier: GPL-3.0 +pragma solidity >=0.8.0 <0.9.0; + +/** + * @title IUniFiAVSDisputeManager + * @dev Interface for the UniFiAVSDisputeManager contract. + */ +interface IUniFiAVSDisputeManager { + /** + * @dev Emitted when an operator is slashed. + * @param operator The address of the operator that was slashed. + * @param validatorIds The array of validator IDs associated with the operator. + * @param slashingBeneficiary The address that benefits from the slashing. + */ + event OperatorSlashed(address indexed operator, bytes32[] validatorIds, address slashingBeneficiary); + + /** + * @dev Slashes an operator by storing their invalid validators and specifying a beneficiary. + * @param operator The address of the operator to be slashed. + * @param validators An array of validators associated with the operator. + * @param slashingBeneficiary The address that will benefit from the slashing. + */ + function slashOperator(address operator, bytes32[] calldata validators, address slashingBeneficiary) external; + + /** + * @dev Checks if an operator has been slashed. + * @param operator The address of the operator to check. + * @return True if the operator has been slashed, false otherwise. + */ + function isOperatorSlashed(address operator) external view returns (bool); +} diff --git a/l1-contracts/src/interfaces/IUniFiAVSManager.sol b/l1-contracts/src/interfaces/IUniFiAVSManager.sol index 55462b1..a606711 100644 --- a/l1-contracts/src/interfaces/IUniFiAVSManager.sol +++ b/l1-contracts/src/interfaces/IUniFiAVSManager.sol @@ -261,14 +261,14 @@ interface IUniFiAVSManager { /** * @notice Sets a new deregistration delay for operators. - * @param newDelay The new deregistration delay in seconds. + * @param newDelay The new deregistration delay in blocks. * @dev Restricted to the DAO */ function setDeregistrationDelay(uint64 newDelay) external; /** * @notice Sets a new registration delay for validators. - * @param newDelay The new registration delay in seconds. + * @param newDelay The new registration delay in blocks. * @dev Restricted to the DAO */ function setRegistrationDelay(uint64 newDelay) external; @@ -403,14 +403,13 @@ interface IUniFiAVSManager { /** * @notice Returns the BLS message hash for a validator registration. - * @param typeHash The type hash for the message. * @param operator The address of the operator. * @param salt The salt for the message. * @param expiry The expiry for the message. * @param index The index for the message. * @return BN254.G1Point The BLS message hash. */ - function blsMessageHash(bytes32 typeHash, address operator, bytes32 salt, uint256 expiry, uint256 index) + function blsMessageHash(address operator, bytes32 salt, uint256 expiry, uint256 index) external view returns (BN254.G1Point memory); diff --git a/l1-contracts/src/lib/BeaconChainHelperLib.sol b/l1-contracts/src/lib/BeaconChainHelperLib.sol index 18ba7ff..745dd83 100644 --- a/l1-contracts/src/lib/BeaconChainHelperLib.sol +++ b/l1-contracts/src/lib/BeaconChainHelperLib.sol @@ -39,8 +39,6 @@ library BeaconChainHelperLib { error BeaconBlockProofForProposerIndex(); function verifyValidatorExistence(InclusionProof memory inclusionProof) internal returns (bool) { - (, bytes32 beaconBlockRoot) = getRootFromTimestamp(inclusionProof.timestamp); - // Validator is verified against the validator list in the beacon state bytes32 validatorHashTreeRoot = MerkleUtils.merkleize(inclusionProof.validator); if ( @@ -65,6 +63,8 @@ library BeaconChainHelperLib { return false; } + (, bytes32 beaconBlockRoot) = getRootFromTimestamp(inclusionProof.timestamp); + // Beacon state is verified against the beacon block if ( !MerkleUtils.verifyProof( diff --git a/l1-contracts/src/storage/UniFiAVSDisputeManagerStorage.sol b/l1-contracts/src/storage/UniFiAVSDisputeManagerStorage.sol new file mode 100644 index 0000000..11f3a14 --- /dev/null +++ b/l1-contracts/src/storage/UniFiAVSDisputeManagerStorage.sol @@ -0,0 +1,36 @@ +// SPDX-License-Identifier: GPL-3.0 +pragma solidity >=0.8.0 <0.9.0; + +import "../structs/ValidatorData.sol"; + +/** + * @title UniFiAVSDisputeManagerStorage + * @author Puffer Finance + * @custom:security-contact security@puffer.fi + */ +abstract contract UniFiAVSDisputeManagerStorage { + /** + * @dev +-----------------------------------------------------------+ + * | | + * | DO NOT CHANGE, REORDER, REMOVE EXISTING STORAGE VARIABLES | + * | | + * +-----------------------------------------------------------+ + */ + struct UniFiAVSDisputeStorage { + // Slashed operators mapping + mapping(address operator => InvalidValidator[] slashedValidators) slashedOperators; + } + + /** + * @dev Storage slot location for UniFiAVSDisputeManager + * @custom:storage-location erc7201:UniFiAVSDisputeManager.storage + */ + bytes32 private constant _STORAGE_LOCATION = 0x5637C918091F80BAF4C11B7735D9493160412E6224AE5AFB3BEEAD3699789342; + + function _getUniFiAVSDisputeStorage() internal pure returns (UniFiAVSDisputeStorage storage $) { + // solhint-disable-next-line no-inline-assembly + assembly { + $.slot := _STORAGE_LOCATION + } + } +} diff --git a/l1-contracts/src/storage/UniFiAVSManagerStorage.sol b/l1-contracts/src/storage/UniFiAVSManagerStorage.sol index e62ae16..2b87ca0 100644 --- a/l1-contracts/src/storage/UniFiAVSManagerStorage.sol +++ b/l1-contracts/src/storage/UniFiAVSManagerStorage.sol @@ -11,6 +11,13 @@ import "../structs/OperatorData.sol"; * @custom:security-contact security@puffer.fi */ abstract contract UniFiAVSManagerStorage { + /** + * @dev +-----------------------------------------------------------+ + * | | + * | DO NOT CHANGE, REORDER, REMOVE EXISTING STORAGE VARIABLES | + * | | + * +-----------------------------------------------------------+ + */ struct UniFiAVSStorage { mapping(bytes32 => ValidatorData) validators; mapping(uint256 => bytes32) validatorIndexes; @@ -24,7 +31,7 @@ abstract contract UniFiAVSManagerStorage { EnumerableSet.AddressSet allowlistedRestakingStrategies; // Mapping to store validator registration data mapping(bytes32 => ValidatorRegistrationData) validatorRegistrations; - // Delay after which a validator can be considered registered + // Delay (in blocks) after which a validator can be considered registered uint64 registerationDelay; // Slashed operators mapping mapping(address operator => InvalidValidator[] invalidValidators) slashedOperators; diff --git a/l1-contracts/src/structs/OperatorData.sol b/l1-contracts/src/structs/OperatorData.sol index 156fb73..9e59501 100644 --- a/l1-contracts/src/structs/OperatorData.sol +++ b/l1-contracts/src/structs/OperatorData.sol @@ -44,5 +44,6 @@ struct OperatorDataExtended { uint128 commitmentValidAfter; /// @notice Whether the operator is registered or not. bool isRegistered; + /// @notice Whether the operator is slashed or not. + bool isSlashed; } -// 7 bytes padding here (automatically added by the compiler) diff --git a/l1-contracts/test/UniFiAVSManager.t.sol b/l1-contracts/test/UniFiAVSManager.t.sol index 5deb538..4036fdc 100644 --- a/l1-contracts/test/UniFiAVSManager.t.sol +++ b/l1-contracts/test/UniFiAVSManager.t.sol @@ -20,6 +20,8 @@ contract UniFiAVSManagerTest is UnitTestHelper { using BN254 for BN254.G1Point; using Strings for uint256; + address constant BEACON_CHAIN_STRATEGY = 0xbeaC0eeEeeeeEEeEeEEEEeeEEeEeeeEeeEEBEaC0; + bytes delegatePubKey = abi.encodePacked(uint256(1337)); // TEST HELPERS @@ -77,9 +79,8 @@ contract UniFiAVSManagerTest is UnitTestHelper { params.index = validatorIndex; // Generate a valid signature - BN254.G1Point memory messagePoint = avsManager.blsMessageHash( - avsManager.VALIDATOR_REGISTRATION_TYPEHASH(), operator, params.salt, params.expiry, params.index - ); + BN254.G1Point memory messagePoint = + avsManager.blsMessageHash(operator, params.salt, params.expiry, params.index); registrationSignature = messagePoint.scalar_mul(validatorPrivateKey); pubkeyHash = BN254.hashG1Point(params.pubkeyG1); @@ -802,19 +803,19 @@ contract UniFiAVSManagerTest is UnitTestHelper { _registerOperator(); // Set shares for the operator - mockDelegationManager.setShares(operator, IStrategy(avsManager.BEACON_CHAIN_STRATEGY()), 100); + mockDelegationManager.setShares(operator, IStrategy(BEACON_CHAIN_STRATEGY), 100); address[] memory restakedStrategies = avsManager.getOperatorRestakedStrategies(operator); assertEq(restakedStrategies.length, 1, "Should return one restaked strategy"); - assertEq(restakedStrategies[0], avsManager.BEACON_CHAIN_STRATEGY(), "Should return BEACON_CHAIN_STRATEGY"); + assertEq(restakedStrategies[0], BEACON_CHAIN_STRATEGY, "Should return BEACON_CHAIN_STRATEGY"); } function testGetRestakeableStrategies() public { address[] memory restakeableStrategies = avsManager.getRestakeableStrategies(); assertEq(restakeableStrategies.length, 1, "Should return one restakeable strategy"); - assertEq(restakeableStrategies[0], avsManager.BEACON_CHAIN_STRATEGY(), "Should return BEACON_CHAIN_STRATEGY"); + assertEq(restakeableStrategies[0], BEACON_CHAIN_STRATEGY, "Should return BEACON_CHAIN_STRATEGY"); } function testIsValidatorInChainId_AfterCommitmentChange() public { @@ -949,7 +950,7 @@ contract UniFiAVSManagerTest is UnitTestHelper { // Initially, only BEACON_CHAIN_STRATEGY should be allowlisted address[] memory initialStrategies = avsManager.getRestakeableStrategies(); assertEq(initialStrategies.length, 1); - assertEq(initialStrategies[0], avsManager.BEACON_CHAIN_STRATEGY()); + assertEq(initialStrategies[0], BEACON_CHAIN_STRATEGY); // Add a new strategy vm.prank(DAO); @@ -960,10 +961,7 @@ contract UniFiAVSManagerTest is UnitTestHelper { // Check that the new strategy is added address[] memory updatedStrategies = avsManager.getRestakeableStrategies(); assertEq(updatedStrategies.length, 2); - assertTrue( - updatedStrategies[0] == avsManager.BEACON_CHAIN_STRATEGY() - || updatedStrategies[1] == avsManager.BEACON_CHAIN_STRATEGY() - ); + assertTrue(updatedStrategies[0] == BEACON_CHAIN_STRATEGY || updatedStrategies[1] == BEACON_CHAIN_STRATEGY); assertTrue(updatedStrategies[0] == newStrategy || updatedStrategies[1] == newStrategy); // Remove the new strategy @@ -975,7 +973,7 @@ contract UniFiAVSManagerTest is UnitTestHelper { // Check that the strategy is removed address[] memory finalStrategies = avsManager.getRestakeableStrategies(); assertEq(finalStrategies.length, 1); - assertEq(finalStrategies[0], avsManager.BEACON_CHAIN_STRATEGY()); + assertEq(finalStrategies[0], BEACON_CHAIN_STRATEGY); // Try to remove newStrategy (should fail) vm.prank(DAO); @@ -1005,7 +1003,7 @@ contract UniFiAVSManagerTest is UnitTestHelper { vm.stopPrank(); // Set shares for the operator - mockDelegationManager.setShares(operator, IStrategy(avsManager.BEACON_CHAIN_STRATEGY()), 100); + mockDelegationManager.setShares(operator, IStrategy(BEACON_CHAIN_STRATEGY), 100); mockDelegationManager.setShares(operator, IStrategy(newStrategy1), 200); // Note: We don't set shares for newStrategy2 @@ -1013,8 +1011,7 @@ contract UniFiAVSManagerTest is UnitTestHelper { assertEq(restakedStrategies.length, 2, "Should return two restaked strategies"); assertTrue( - restakedStrategies[0] == avsManager.BEACON_CHAIN_STRATEGY() - || restakedStrategies[1] == avsManager.BEACON_CHAIN_STRATEGY(), + restakedStrategies[0] == BEACON_CHAIN_STRATEGY || restakedStrategies[1] == BEACON_CHAIN_STRATEGY, "Should include BEACON_CHAIN_STRATEGY" ); assertTrue( @@ -1051,7 +1048,7 @@ contract UniFiAVSManagerTest is UnitTestHelper { avsManager.setAllowlistRestakingStrategy(newStrategy, true); // Set shares for the operator - mockDelegationManager.setShares(operator, IStrategy(avsManager.BEACON_CHAIN_STRATEGY()), 100); + mockDelegationManager.setShares(operator, IStrategy(BEACON_CHAIN_STRATEGY), 100); mockDelegationManager.setShares(operator, IStrategy(newStrategy), 200); address[] memory restakedStrategies = avsManager.getOperatorRestakedStrategies(operator); diff --git a/l1-contracts/test/forks/UniFiAVSManagerForkTest.sol b/l1-contracts/test/forks/UniFiAVSManagerForkTest.sol index c3c07c1..261fff5 100644 --- a/l1-contracts/test/forks/UniFiAVSManagerForkTest.sol +++ b/l1-contracts/test/forks/UniFiAVSManagerForkTest.sol @@ -55,6 +55,7 @@ contract UniFiAVSManagerForkTest is Test, BaseScript { uint256 public operatorPrivateKey; address public DAO = 0xC0896ab1A8cae8c2C1d27d011eb955Cca955580d; + address constant BEACON_CHAIN_STRATEGY = 0xbeaC0eeEeeeeEEeEeEEEEeeEEeEeeeEeeEEBEaC0; function setUp() public virtual { vm.createSelectFork(vm.rpcUrl("mainnet"), 20731077); // Replace with an appropriate block number @@ -362,7 +363,7 @@ contract UniFiAVSManagerForkTest is Test, BaseScript { // Assert assertEq(restakedStrategies.length, 1, "Should have one restaked strategy"); - assertEq(restakedStrategies[0], avsManager.BEACON_CHAIN_STRATEGY(), "Should be the Beacon Chain strategy"); + assertEq(restakedStrategies[0], BEACON_CHAIN_STRATEGY, "Should be the Beacon Chain strategy"); } function test_getRestakeableStrategies() public { @@ -371,7 +372,7 @@ contract UniFiAVSManagerForkTest is Test, BaseScript { // Assert assertEq(restakeableStrategies.length, 1, "Should have one restakeable strategy"); - assertEq(restakeableStrategies[0], avsManager.BEACON_CHAIN_STRATEGY(), "Should be the Beacon Chain strategy"); + assertEq(restakeableStrategies[0], BEACON_CHAIN_STRATEGY, "Should be the Beacon Chain strategy"); } function _registerOperator() internal { diff --git a/l1-contracts/test/helpers/UnitTestHelper.sol b/l1-contracts/test/helpers/UnitTestHelper.sol index d17791d..9a70135 100644 --- a/l1-contracts/test/helpers/UnitTestHelper.sol +++ b/l1-contracts/test/helpers/UnitTestHelper.sol @@ -11,7 +11,7 @@ import "../mocks/MockDelegationManager.sol"; import "../mocks/MockAVSDirectory.sol"; import { UpgradeableBeacon } from "@openzeppelin/contracts/proxy/beacon/UpgradeableBeacon.sol"; import { AccessManager } from "@openzeppelin/contracts/access/manager/AccessManager.sol"; -import "forge-std/console.sol"; +import { UniFiAVSDisputeManager } from "../../src/UniFiAVSDisputeManager.sol"; contract UnitTestHelper is Test, BaseScript { address public constant ADDRESS_ZERO = address(0); @@ -28,6 +28,7 @@ contract UnitTestHelper is Test, BaseScript { MockEigenPodManager public mockEigenPodManager; MockDelegationManager public mockDelegationManager; MockAVSDirectory public mockAVSDirectory; + UniFiAVSDisputeManager public disputeManager; address public DAO = makeAddr("DAO"); address public COMMUNITY_MULTISIG = makeAddr("communityMultisig"); @@ -79,7 +80,7 @@ contract UnitTestHelper is Test, BaseScript { address(mockAVSDirectory), DEREGISTRATION_DELAY ); - + disputeManager = UniFiAVSDisputeManager(avsDeployment.disputeManagerProxy); // accessManager = AccessManager(avsDeployment.accessManager); timelock = avsDeployment.timelock; avsManager = UniFiAVSManager(avsDeployment.avsManagerProxy); From 92c46a5b73f022d8703a84a52ca7f1fe81dd4769 Mon Sep 17 00:00:00 2001 From: 0xwalid Date: Mon, 28 Oct 2024 23:08:24 -1000 Subject: [PATCH 16/22] optimize the optimistic regisration path --- l1-contracts/src/UniFiAVSManager.sol | 125 ++++++++++------ .../src/interfaces/IUniFiAVSManager.sol | 29 +++- .../src/lib/BLSSignatureCheckerLib.sol | 22 +++ l1-contracts/src/structs/ValidatorData.sol | 23 +-- l1-contracts/test/UniFiAVSManager.t.sol | 135 ++++++------------ 5 files changed, 186 insertions(+), 148 deletions(-) diff --git a/l1-contracts/src/UniFiAVSManager.sol b/l1-contracts/src/UniFiAVSManager.sol index 21417eb..5ddff91 100644 --- a/l1-contracts/src/UniFiAVSManager.sol +++ b/l1-contracts/src/UniFiAVSManager.sol @@ -173,7 +173,7 @@ contract UniFiAVSManager is revert ValidatorNotActive(); } - if ($.validators[blsPubKeyHash].index != 0) { + if ($.validators[blsPubKeyHash].index != 0 && $.validators[blsPubKeyHash].registeredUntil > block.number) { revert ValidatorAlreadyRegistered(); } @@ -181,8 +181,7 @@ contract UniFiAVSManager is eigenPod: address(eigenPod), index: validatorInfo.validatorIndex, operator: msg.sender, - registeredUntil: type(uint64).max, - registeredAfter: uint64(block.number) + registeredUntil: type(uint64).max }); $.validatorIndexes[validatorInfo.validatorIndex] = blsPubKeyHash; @@ -248,17 +247,19 @@ contract UniFiAVSManager is for (uint256 i = 0; i < validators.length; i++) { // Derive the BLS public key hash from pubkeyG1 - bytes32 blsPubKeyHash = validators[i].blsPubKeyHash; - ValidatorData storage existingValidator = $.validators[blsPubKeyHash]; - ValidatorRegistrationData storage validatorRegistrationData = $.validatorRegistrations[blsPubKeyHash]; + ValidatorData storage existingValidator = $.validators[validators[i].blsPubKeyHash]; + ValidatorRegistrationData storage validatorRegistrationData = + $.validatorRegistrations[validators[i].blsPubKeyHash]; // Check if the validator already exists with active status - if (existingValidator.index != 0 && existingValidator.registeredUntil == type(uint64).max) { + if (existingValidator.index != 0 && existingValidator.registeredUntil > block.number) { revert ValidatorAlreadyRegistered(); } - if ($.validatorIndexes[validators[i].index] != bytes32(0)) { + // only allow re-registration if the index is not already used for a different validator + bytes32 storedBlsPubKeyHash = $.validatorIndexes[validators[i].index]; + if (storedBlsPubKeyHash != bytes32(0) && storedBlsPubKeyHash != validators[i].blsPubKeyHash) { revert ValidatorIndexAlreadyUsed(); } @@ -273,30 +274,36 @@ contract UniFiAVSManager is } // Store the validator data - $.validators[blsPubKeyHash] = ValidatorData({ + $.validators[validators[i].blsPubKeyHash] = ValidatorData({ eigenPod: address(0), // Not from EigenPod index: validators[i].index, // Store the provided index operator: msg.sender, - registeredUntil: type(uint64).max, - registeredAfter: uint64(block.number) + $.registerationDelay + registeredUntil: type(uint64).max }); - $.validatorRegistrations[blsPubKeyHash] = ValidatorRegistrationData({ - registrationSignature: validators[i].registrationSignature, - pubkeyG1: validators[i].pubkeyG1, - pubkeyG2: validators[i].pubkeyG2, + bytes32 registrationHash = keccak256( + abi.encodePacked( + validators[i].registrationSignature.X, + validators[i].registrationSignature.Y, + validators[i].expiry, + validators[i].salt + ) + ); + + $.validatorRegistrations[validators[i].blsPubKeyHash] = ValidatorRegistrationData({ + registrationHash: registrationHash, salt: validators[i].salt, - expiry: uint64(validators[i].expiry), - registeredAt: uint64(block.number) + registeredAt: uint64(block.number), + activeAfter: uint64(block.number) + $.registerationDelay }); - $.validatorIndexes[validators[i].index] = blsPubKeyHash; + $.validatorIndexes[validators[i].index] = validators[i].blsPubKeyHash; emit ValidatorRegistered({ podOwner: address(0), operator: msg.sender, delegateKey: delegateKey, - blsPubKeyHash: blsPubKeyHash, + blsPubKeyHash: validators[i].blsPubKeyHash, validatorIndex: validators[i].index }); @@ -312,29 +319,39 @@ contract UniFiAVSManager is * @inheritdoc IUniFiAVSManager * @dev Restricted in this context is like `whenNotPaused` modifier from Pausable.sol */ - function verifyValidatorSignatures(bytes32[] calldata blsPubKeyHashes) external restricted { + function verifyValidatorSignatures(ValidatorRegistrationSlashingParams[] calldata validators) external restricted { UniFiAVSStorage storage $ = _getUniFiAVSManagerStorage(); - for (uint256 i = 0; i < blsPubKeyHashes.length; i++) { - bytes32 blsPubKeyHash = blsPubKeyHashes[i]; + for (uint256 i = 0; i < validators.length; i++) { + bytes32 blsPubKeyHash = BLSSignatureCheckerLib.hashG1Point(validators[i].pubkeyG1); + ValidatorData storage validator = $.validators[blsPubKeyHash]; - address operator = validator.operator; ValidatorRegistrationData memory validatorRegistrationData = $.validatorRegistrations[blsPubKeyHash]; + bytes32 registrationHash = keccak256( + abi.encodePacked( + validators[i].registrationSignature.X, + validators[i].registrationSignature.Y, + validators[i].expiry, + validators[i].salt + ) + ); + + if (validatorRegistrationData.registrationHash != registrationHash) { + revert InvalidRegistrationSignature(); + } + // Calculate the hash using EIP-712 BN254.G1Point memory messageHash = blsMessageHash({ - operator: operator, - salt: validatorRegistrationData.salt, - expiry: validatorRegistrationData.expiry, - index: validator.index + operator: validator.operator, + salt: validators[i].salt, + expiry: validators[i].expiry, + index: validators[i].index }); // Use the stored signature for verification bool isValid = BLSSignatureCheckerLib.isBlsSignatureValid( - validatorRegistrationData.pubkeyG1, - validatorRegistrationData.pubkeyG2, - validatorRegistrationData.registrationSignature, - messageHash + validators[i].pubkeyG1, validators[i].pubkeyG2, validators[i].registrationSignature, messageHash ); if (!isValid) { @@ -352,11 +369,10 @@ contract UniFiAVSManager is restricted { for (uint256 i = 0; i < proofs.length; i++) { - BeaconChainHelperLib.InclusionProof memory proof = proofs[i]; - bytes32 blsPubKeyHash = proof.validator[0]; - (uint64 index,) = _validateProofAndGetValidatorKeys(proof, false); + bytes32 blsPubKeyHash = proofs[i].validator[0]; + (uint64 index,) = _validateProofAndGetValidatorKeys(proofs[i], false); - if (index != proof.validatorIndex) { + if (index != proofs[i].validatorIndex) { _slashAndDeregisterValidator(blsPubKeyHash); } } @@ -371,10 +387,9 @@ contract UniFiAVSManager is restricted { for (uint256 i = 0; i < proofs.length; i++) { - BeaconChainHelperLib.InclusionProof memory proof = proofs[i]; - (, bytes32 storedBlsPubKeyHash) = _validateProofAndGetValidatorKeys(proof, true); + (, bytes32 storedBlsPubKeyHash) = _validateProofAndGetValidatorKeys(proofs[i], true); - if (storedBlsPubKeyHash != proof.validator[0]) { + if (storedBlsPubKeyHash != proofs[i].validator[0]) { _slashAndDeregisterValidator(storedBlsPubKeyHash); } } @@ -703,7 +718,7 @@ contract UniFiAVSManager is /** * @inheritdoc IUniFiAVSManager */ - function blsMessageHash(address operator, bytes32 salt, uint256 expiry, uint256 index) + function blsMessageHash(address operator, uint256 salt, uint256 expiry, uint256 index) public view returns (BN254.G1Point memory) @@ -715,6 +730,25 @@ contract UniFiAVSManager is ); } + function getValidatorRegistrationData(bytes32 blsPubKeyHash) + external + view + returns (ValidatorRegistrationData memory) + { + UniFiAVSStorage storage $ = _getUniFiAVSManagerStorage(); + return $.validatorRegistrations[blsPubKeyHash]; + } + + function getValidatorRegistrationData(uint256 validatorIndex) + external + view + returns (ValidatorRegistrationData memory) + { + UniFiAVSStorage storage $ = _getUniFiAVSManagerStorage(); + bytes32 blsPubKeyHash = $.validatorIndexes[validatorIndex]; + return $.validatorRegistrations[blsPubKeyHash]; + } + // INTERNAL FUNCTIONS function _getOperator(address operator) internal view returns (OperatorDataExtended memory) { @@ -739,6 +773,7 @@ contract UniFiAVSManager is UniFiAVSStorage storage $ = _getUniFiAVSManagerStorage(); ValidatorData memory validatorData = $.validators[blsPubKeyHash]; + ValidatorRegistrationData memory validatorRegistrationData = $.validatorRegistrations[blsPubKeyHash]; if (validatorData.index != 0) { IEigenPod.VALIDATOR_STATUS eigenPodStatus; @@ -747,7 +782,6 @@ contract UniFiAVSManager is if (validatorData.eigenPod != address(0)) { IEigenPod eigenPod = IEigenPod(validatorData.eigenPod); IEigenPod.ValidatorInfo memory validatorInfo = eigenPod.validatorPubkeyHashToInfo(blsPubKeyHash); - eigenPodStatus = validatorInfo.status; backedByEigenPodStake = EIGEN_DELEGATION_MANAGER.delegatedTo(eigenPod.podOwner()) == validatorData.operator; @@ -764,7 +798,8 @@ contract UniFiAVSManager is delegateKey: activeCommitment.delegateKey, chainIDBitMap: activeCommitment.chainIDBitMap, backedByEigenPodStake: backedByEigenPodStake, - registered: block.number < validatorData.registeredUntil && block.number > validatorData.registeredAfter + registered: block.number < validatorData.registeredUntil + && block.number > validatorRegistrationData.activeAfter }); } } @@ -792,10 +827,10 @@ contract UniFiAVSManager is emit DeregistrationDelaySet(oldDelay, newDelay); } - function _validateProofAndGetValidatorKeys(BeaconChainHelperLib.InclusionProof memory proof, bool queryByProofIndex) - internal - returns (uint64, bytes32) - { + function _validateProofAndGetValidatorKeys( + BeaconChainHelperLib.InclusionProof calldata proof, + bool queryByProofIndex + ) internal returns (uint64, bytes32) { UniFiAVSStorage storage $ = _getUniFiAVSManagerStorage(); if (!BeaconChainHelperLib.verifyValidatorExistence(proof)) { diff --git a/l1-contracts/src/interfaces/IUniFiAVSManager.sol b/l1-contracts/src/interfaces/IUniFiAVSManager.sol index a606711..1d33185 100644 --- a/l1-contracts/src/interfaces/IUniFiAVSManager.sol +++ b/l1-contracts/src/interfaces/IUniFiAVSManager.sol @@ -90,6 +90,9 @@ interface IUniFiAVSManager { /// @notice Thrown when a validator is not backed by an EigenPod error InvalidValidatorType(); + /// @notice Thrown when a validator registration signature is invalid + error InvalidRegistrationSignature(); + /** * @notice Emitted when a new operator is registered in the UniFi AVS. * @param operator The address of the registered operator. @@ -297,9 +300,9 @@ interface IUniFiAVSManager { /** * @notice Verifies the signatures of validators. - * @param blsPubKeyHashes The BLS public key hashes of the validators. + * @param validators The array of ValidatorRegistrationSlashingParams. */ - function verifyValidatorSignatures(bytes32[] calldata blsPubKeyHashes) external; + function verifyValidatorSignatures(ValidatorRegistrationSlashingParams[] calldata validators) external; /** * @notice Slashes validators with invalid pubkey. @@ -409,8 +412,28 @@ interface IUniFiAVSManager { * @param index The index for the message. * @return BN254.G1Point The BLS message hash. */ - function blsMessageHash(address operator, bytes32 salt, uint256 expiry, uint256 index) + function blsMessageHash(address operator, uint256 salt, uint256 expiry, uint256 index) external view returns (BN254.G1Point memory); + + /** + * @notice Retrieves the validator registration data for a given BLS public key hash. + * @param blsPubKeyHash The BLS public key hash of the validator. + * @return ValidatorRegistrationData struct containing registration data for the validator. + */ + function getValidatorRegistrationData(bytes32 blsPubKeyHash) + external + view + returns (ValidatorRegistrationData memory); + + /** + * @notice Retrieves the validator registration data for a given validator index. + * @param validatorIndex The index of the validator. + * @return ValidatorRegistrationData struct containing registration data for the validator. + */ + function getValidatorRegistrationData(uint256 validatorIndex) + external + view + returns (ValidatorRegistrationData memory); } diff --git a/l1-contracts/src/lib/BLSSignatureCheckerLib.sol b/l1-contracts/src/lib/BLSSignatureCheckerLib.sol index 557d954..eb5b180 100644 --- a/l1-contracts/src/lib/BLSSignatureCheckerLib.sol +++ b/l1-contracts/src/lib/BLSSignatureCheckerLib.sol @@ -35,4 +35,26 @@ library BLSSignatureCheckerLib { pubkeyG2 ); } + + function g1PointToBytes(BN254.G1Point memory point) internal pure returns (bytes memory) { + bytes memory result = new bytes(48); + + assembly { + // Store X coordinate + mstore(add(result, 32), mload(point)) + + // Store Y coordinate + // Set the most significant bit to 1 if Y is odd, 0 if Y is even + let y := mload(add(point, 32)) + let yMod2 := mod(y, 2) + y := or(and(y, not(shl(255, 1))), shl(255, yMod2)) + mstore(add(result, 64), y) + } + + return result; + } + + function hashG1Point(BN254.G1Point memory pk) internal pure returns (bytes32 hashedG1) { + return sha256(abi.encodePacked(g1PointToBytes(pk), bytes16(0))); + } } diff --git a/l1-contracts/src/structs/ValidatorData.sol b/l1-contracts/src/structs/ValidatorData.sol index e2e20a0..842e17a 100644 --- a/l1-contracts/src/structs/ValidatorData.sol +++ b/l1-contracts/src/structs/ValidatorData.sol @@ -18,8 +18,6 @@ struct ValidatorData { address operator; /// @notice The block number until which the validator is registered. uint64 registeredUntil; - /// @notice The block number after which the validator is registered. - uint64 registeredAfter; } /** @@ -51,12 +49,10 @@ struct ValidatorDataExtended { * @notice Struct to store registration-related data for a validator. */ struct ValidatorRegistrationData { - BN254.G1Point registrationSignature; - BN254.G1Point pubkeyG1; - BN254.G2Point pubkeyG2; - bytes32 salt; + bytes32 registrationHash; + uint64 salt; uint64 registeredAt; - uint64 expiry; + uint64 activeAfter; } /** @@ -66,11 +62,22 @@ struct ValidatorRegistrationData { struct ValidatorRegistrationParams { bytes32 blsPubKeyHash; BN254.G1Point registrationSignature; + uint64 index; + uint256 expiry; + uint64 salt; +} + +/** + * @title ValidatorRegistrationSlashingParams + * @notice Struct to store parameters for validator registration slashing. + */ +struct ValidatorRegistrationSlashingParams { BN254.G1Point pubkeyG1; BN254.G2Point pubkeyG2; + BN254.G1Point registrationSignature; uint64 index; uint256 expiry; - bytes32 salt; + uint64 salt; } /** diff --git a/l1-contracts/test/UniFiAVSManager.t.sol b/l1-contracts/test/UniFiAVSManager.t.sol index 4036fdc..dac46ab 100644 --- a/l1-contracts/test/UniFiAVSManager.t.sol +++ b/l1-contracts/test/UniFiAVSManager.t.sol @@ -63,7 +63,7 @@ contract UniFiAVSManagerTest is UnitTestHelper { uint256 validatorPrivateKey, uint64 validatorIndex, address operator, - bytes32 salt, + uint256 salt, uint256 expiry ) internal returns (BN254.G1Point memory registrationSignature, bytes32 pubkeyHash) { // Generate BLS key pair @@ -72,9 +72,7 @@ contract UniFiAVSManagerTest is UnitTestHelper { // Create ValidatorRegistrationParams ValidatorRegistrationParams memory params; - params.pubkeyG1 = blsKeyPair.pubkeyG1; - params.pubkeyG2 = blsKeyPair.pubkeyG2; - params.salt = salt; + params.salt = uint64(salt); params.expiry = expiry; params.index = validatorIndex; @@ -83,7 +81,7 @@ contract UniFiAVSManagerTest is UnitTestHelper { avsManager.blsMessageHash(operator, params.salt, params.expiry, params.index); registrationSignature = messagePoint.scalar_mul(validatorPrivateKey); - pubkeyHash = BN254.hashG1Point(params.pubkeyG1); + pubkeyHash = BLSSignatureCheckerLib.hashG1Point(blsKeyPair.pubkeyG1); return (registrationSignature, pubkeyHash); } @@ -1064,8 +1062,8 @@ contract UniFiAVSManagerTest is UnitTestHelper { uint256 validatorPrivateKey2 = 456; uint64 validatorIndex1 = 1; uint64 validatorIndex2 = 2; - bytes32 salt1 = bytes32(uint256(1)); - bytes32 salt2 = bytes32(uint256(2)); + uint64 salt1 = 1; + uint64 salt2 = 2; uint256 expiry = block.timestamp + 1 days; (BN254.G1Point memory signature1, bytes32 pubkeyHash1) = @@ -1075,29 +1073,20 @@ contract UniFiAVSManagerTest is UnitTestHelper { _getValidatorSignature(validatorPrivateKey2, validatorIndex2, operator, salt2, expiry); ValidatorRegistrationParams[] memory paramsArray = new ValidatorRegistrationParams[](2); - - BN254.G1Point memory pubkeyG1_1 = _generateBlsPubkeyParams(validatorPrivateKey1).pubkeyG1; - BN254.G2Point memory pubkeyG2_1 = _generateBlsPubkeyParams(validatorPrivateKey1).pubkeyG2; paramsArray[0] = ValidatorRegistrationParams({ blsPubKeyHash: pubkeyHash1, - pubkeyG1: pubkeyG1_1, - pubkeyG2: pubkeyG2_1, - salt: salt1, - expiry: expiry, + registrationSignature: signature1, index: validatorIndex1, - registrationSignature: signature1 + expiry: expiry, + salt: salt1 }); - BN254.G1Point memory pubkeyG1_2 = _generateBlsPubkeyParams(validatorPrivateKey2).pubkeyG1; - BN254.G2Point memory pubkeyG2_2 = _generateBlsPubkeyParams(validatorPrivateKey2).pubkeyG2; paramsArray[1] = ValidatorRegistrationParams({ blsPubKeyHash: pubkeyHash2, - pubkeyG1: pubkeyG1_2, - pubkeyG2: pubkeyG2_2, - salt: salt2, - expiry: expiry, + registrationSignature: signature2, index: validatorIndex2, - registrationSignature: signature2 + expiry: expiry, + salt: salt2 }); vm.prank(operator); @@ -1116,73 +1105,41 @@ contract UniFiAVSManagerTest is UnitTestHelper { assertEq(validator2.operator, operator, "Validator 2 should be assigned to the correct operator"); } - function testRegisterValidatorsOptimistically_AlreadyRegistered() public { + function testVerifyValidatorSignatures() public { _setupOperator(); _registerOperator(); uint256 validatorPrivateKey = 123; uint64 validatorIndex = 1; - bytes32 salt = bytes32(uint256(1)); + uint64 salt = 1; uint256 expiry = block.timestamp + 1 days; (BN254.G1Point memory signature, bytes32 pubkeyHash) = _getValidatorSignature(validatorPrivateKey, validatorIndex, operator, salt, expiry); - BN254.G1Point memory pubkeyG1 = _generateBlsPubkeyParams(validatorPrivateKey).pubkeyG1; - BN254.G2Point memory pubkeyG2 = _generateBlsPubkeyParams(validatorPrivateKey).pubkeyG2; - ValidatorRegistrationParams[] memory paramsArray = new ValidatorRegistrationParams[](1); paramsArray[0] = ValidatorRegistrationParams({ blsPubKeyHash: pubkeyHash, - pubkeyG1: pubkeyG1, - pubkeyG2: pubkeyG2, - salt: salt, - expiry: expiry, + registrationSignature: signature, index: validatorIndex, - registrationSignature: signature + expiry: expiry, + salt: salt }); vm.prank(operator); avsManager.registerValidatorsOptimistically(paramsArray); - vm.prank(operator); - vm.expectRevert(IUniFiAVSManager.ValidatorAlreadyRegistered.selector); - avsManager.registerValidatorsOptimistically(paramsArray); - } - - function testVerifyValidatorSignatures() public { - _setupOperator(); - _registerOperator(); - - uint256 validatorPrivateKey = 123; - uint64 validatorIndex = 1; - bytes32 salt = bytes32(uint256(1)); - uint256 expiry = block.timestamp + 1 days; - - (BN254.G1Point memory signature, bytes32 pubkeyHash) = - _getValidatorSignature(validatorPrivateKey, validatorIndex, operator, salt, expiry); - - BN254.G1Point memory pubkeyG1 = _generateBlsPubkeyParams(validatorPrivateKey).pubkeyG1; - BN254.G2Point memory pubkeyG2 = _generateBlsPubkeyParams(validatorPrivateKey).pubkeyG2; - - ValidatorRegistrationParams[] memory paramsArray = new ValidatorRegistrationParams[](1); - paramsArray[0] = ValidatorRegistrationParams({ - blsPubKeyHash: pubkeyHash, - pubkeyG1: pubkeyG1, - pubkeyG2: pubkeyG2, - salt: salt, - expiry: expiry, + ValidatorRegistrationSlashingParams[] memory slashingParams = new ValidatorRegistrationSlashingParams[](1); + slashingParams[0] = ValidatorRegistrationSlashingParams({ + pubkeyG1: _generateBlsPubkeyParams(validatorPrivateKey).pubkeyG1, + pubkeyG2: _generateBlsPubkeyParams(validatorPrivateKey).pubkeyG2, + registrationSignature: signature, index: validatorIndex, - registrationSignature: signature + expiry: expiry, + salt: salt }); - vm.prank(operator); - avsManager.registerValidatorsOptimistically(paramsArray); - - bytes32[] memory blsPubKeyHashes = new bytes32[](1); - blsPubKeyHashes[0] = pubkeyHash; - - avsManager.verifyValidatorSignatures(blsPubKeyHashes); + avsManager.verifyValidatorSignatures(slashingParams); vm.roll(block.number + avsManager.getRegistrationDelay() + 1); ValidatorDataExtended memory validator = avsManager.getValidator(pubkeyHash); @@ -1195,7 +1152,7 @@ contract UniFiAVSManagerTest is UnitTestHelper { uint256 validatorPrivateKey = 123; uint64 validatorIndex = 1; - bytes32 salt = bytes32(uint256(1)); + uint64 salt = 1; uint256 expiry = block.timestamp + 1 days; (BN254.G1Point memory validSignature, bytes32 pubkeyHash) = @@ -1210,30 +1167,32 @@ contract UniFiAVSManagerTest is UnitTestHelper { expiry ); - BN254.G1Point memory pubkeyG1 = _generateBlsPubkeyParams(validatorPrivateKey).pubkeyG1; - BN254.G2Point memory pubkeyG2 = _generateBlsPubkeyParams(validatorPrivateKey).pubkeyG2; - ValidatorRegistrationParams[] memory paramsArray = new ValidatorRegistrationParams[](1); paramsArray[0] = ValidatorRegistrationParams({ blsPubKeyHash: pubkeyHash, - pubkeyG1: pubkeyG1, - pubkeyG2: pubkeyG2, - salt: salt, - expiry: expiry, + registrationSignature: invalidSignature, index: validatorIndex, - registrationSignature: invalidSignature // Use the invalid signature - }); + expiry: expiry, + salt: salt + }); vm.prank(operator); avsManager.registerValidatorsOptimistically(paramsArray); - bytes32[] memory blsPubKeyHashes = new bytes32[](1); - blsPubKeyHashes[0] = pubkeyHash; + ValidatorRegistrationSlashingParams[] memory slashingParams = new ValidatorRegistrationSlashingParams[](1); + slashingParams[0] = ValidatorRegistrationSlashingParams({ + pubkeyG1: _generateBlsPubkeyParams(validatorPrivateKey).pubkeyG1, + pubkeyG2: _generateBlsPubkeyParams(validatorPrivateKey).pubkeyG2, + registrationSignature: invalidSignature, + index: validatorIndex, + expiry: expiry, + salt: salt + }); vm.expectEmit(true, true, false, false); emit IUniFiAVSManager.ValidatorSlashed(operator, pubkeyHash); - avsManager.verifyValidatorSignatures(blsPubKeyHashes); + avsManager.verifyValidatorSignatures(slashingParams); vm.roll(block.number + avsManager.getDeregistrationDelay() + 1); ValidatorDataExtended memory validator = avsManager.getValidator(pubkeyHash); @@ -1253,9 +1212,7 @@ contract UniFiAVSManagerTest is UnitTestHelper { paramsArray[0] = ValidatorRegistrationParams({ blsPubKeyHash: pubkeyHash, - pubkeyG1: _generateBlsPubkeyParams(123).pubkeyG1, - pubkeyG2: _generateBlsPubkeyParams(123).pubkeyG2, // Use a dummy value for pubkeyG2 - salt: bytes32(uint256(1)), + salt: 1, expiry: block.timestamp + 1 days, index: uint64(BeaconProofs.validatorIndex()), registrationSignature: _generateBlsPubkeyParams(123).pubkeyG1 // Use a dummy value for the signature @@ -1298,9 +1255,7 @@ contract UniFiAVSManagerTest is UnitTestHelper { paramsArray[0] = ValidatorRegistrationParams({ blsPubKeyHash: pubkeyHash, - pubkeyG1: _generateBlsPubkeyParams(123).pubkeyG1, - pubkeyG2: _generateBlsPubkeyParams(123).pubkeyG2, // Use a dummy value for pubkeyG2 - salt: bytes32(uint256(1)), + salt: 1, expiry: block.timestamp + 1 days, index: 1, // Use a different index registrationSignature: _generateBlsPubkeyParams(123).pubkeyG1 // Use a dummy value for the signature @@ -1344,9 +1299,7 @@ contract UniFiAVSManagerTest is UnitTestHelper { paramsArray[0] = ValidatorRegistrationParams({ blsPubKeyHash: bytes32(uint256(1234)), - pubkeyG1: _generateBlsPubkeyParams(123).pubkeyG1, - pubkeyG2: _generateBlsPubkeyParams(123).pubkeyG2, // Use a dummy value for pubkeyG2 - salt: bytes32(uint256(1)), + salt: 1, expiry: block.timestamp + 1 days, index: uint64(BeaconProofs.validatorIndex()), registrationSignature: _generateBlsPubkeyParams(123).pubkeyG1 // Use a dummy value for the signature @@ -1389,9 +1342,7 @@ contract UniFiAVSManagerTest is UnitTestHelper { bytes32 pubkeyHash = sha256(abi.encodePacked(BeaconProofs.validator(), bytes16(0))); paramsArray[0] = ValidatorRegistrationParams({ blsPubKeyHash: pubkeyHash, - pubkeyG1: _generateBlsPubkeyParams(123).pubkeyG1, - pubkeyG2: _generateBlsPubkeyParams(123).pubkeyG2, // Use a dummy value for pubkeyG2 - salt: bytes32(uint256(1)), + salt: 1, expiry: block.timestamp + 1 days, index: uint64(BeaconProofs.validatorIndex()), registrationSignature: _generateBlsPubkeyParams(123).pubkeyG1 // Use a dummy value for the signature From 9ee3f2e4ede1b9c9d81a2fd93f3e30605f8e6aa2 Mon Sep 17 00:00:00 2001 From: 0xwalid Date: Mon, 28 Oct 2024 23:23:30 -1000 Subject: [PATCH 17/22] add comments --- l1-contracts/src/UniFiAVSManager.sol | 1 + 1 file changed, 1 insertion(+) diff --git a/l1-contracts/src/UniFiAVSManager.sol b/l1-contracts/src/UniFiAVSManager.sol index 5ddff91..54103cc 100644 --- a/l1-contracts/src/UniFiAVSManager.sol +++ b/l1-contracts/src/UniFiAVSManager.sol @@ -878,6 +878,7 @@ contract UniFiAVSManager is delete $.validators[blsPubKeyHash]; // if the index and is pointing to the same validator, delete the index. + // this check is in place in case an EigenPod validator is registered and has used the index. bytes32 pubkeyFromIndex = $.validatorIndexes[validatorIndex]; if (pubkeyFromIndex == blsPubKeyHash) { delete $.validatorIndexes[validatorIndex]; From a89bc28d567360980744bbb93b935c91db6bb411 Mon Sep 17 00:00:00 2001 From: 0xwalid Date: Tue, 29 Oct 2024 10:27:02 -1000 Subject: [PATCH 18/22] address comments --- l1-contracts/src/UniFiAVSManager.sol | 12 ++++++------ l1-contracts/src/storage/UniFiAVSManagerStorage.sol | 4 +--- 2 files changed, 7 insertions(+), 9 deletions(-) diff --git a/l1-contracts/src/UniFiAVSManager.sol b/l1-contracts/src/UniFiAVSManager.sol index 54103cc..bba1539 100644 --- a/l1-contracts/src/UniFiAVSManager.sol +++ b/l1-contracts/src/UniFiAVSManager.sol @@ -294,7 +294,7 @@ contract UniFiAVSManager is registrationHash: registrationHash, salt: validators[i].salt, registeredAt: uint64(block.number), - activeAfter: uint64(block.number) + $.registerationDelay + activeAfter: uint64(block.number) + $.registrationDelay }); $.validatorIndexes[validators[i].index] = validators[i].blsPubKeyHash; @@ -313,6 +313,7 @@ contract UniFiAVSManager is // Update the operator's validator count OperatorData storage operator = $.operators[msg.sender]; operator.validatorCount += uint128(newValidatorCount); + operator.startDeregisterOperatorBlock = 0; } /** @@ -326,7 +327,6 @@ contract UniFiAVSManager is bytes32 blsPubKeyHash = BLSSignatureCheckerLib.hashG1Point(validators[i].pubkeyG1); ValidatorData storage validator = $.validators[blsPubKeyHash]; - ValidatorRegistrationData memory validatorRegistrationData = $.validatorRegistrations[blsPubKeyHash]; bytes32 registrationHash = keccak256( abi.encodePacked( @@ -337,7 +337,7 @@ contract UniFiAVSManager is ) ); - if (validatorRegistrationData.registrationHash != registrationHash) { + if ($.validatorRegistrations[blsPubKeyHash].registrationHash != registrationHash) { revert InvalidRegistrationSignature(); } @@ -501,8 +501,8 @@ contract UniFiAVSManager is */ function setRegistrationDelay(uint64 newDelay) external restricted { UniFiAVSStorage storage $ = _getUniFiAVSManagerStorage(); - uint64 oldDelay = $.registerationDelay; - $.registerationDelay = newDelay; + uint64 oldDelay = $.registrationDelay; + $.registrationDelay = newDelay; emit RegistrationDelaySet(oldDelay, newDelay); } @@ -563,7 +563,7 @@ contract UniFiAVSManager is */ function getRegistrationDelay() external view returns (uint64) { UniFiAVSStorage storage $ = _getUniFiAVSManagerStorage(); - return $.registerationDelay; + return $.registrationDelay; } /** diff --git a/l1-contracts/src/storage/UniFiAVSManagerStorage.sol b/l1-contracts/src/storage/UniFiAVSManagerStorage.sol index 2b87ca0..62530f6 100644 --- a/l1-contracts/src/storage/UniFiAVSManagerStorage.sol +++ b/l1-contracts/src/storage/UniFiAVSManagerStorage.sol @@ -32,9 +32,7 @@ abstract contract UniFiAVSManagerStorage { // Mapping to store validator registration data mapping(bytes32 => ValidatorRegistrationData) validatorRegistrations; // Delay (in blocks) after which a validator can be considered registered - uint64 registerationDelay; - // Slashed operators mapping - mapping(address operator => InvalidValidator[] invalidValidators) slashedOperators; + uint64 registrationDelay; } /** From bca0c3577ef5e84e2451a7884677424ca3ec0e5d Mon Sep 17 00:00:00 2001 From: 0xwalid Date: Tue, 29 Oct 2024 11:02:36 -1000 Subject: [PATCH 19/22] add access control setup in the deployment script --- l1-contracts/script/SetupAccess.s.sol | 2 +- .../script/UpgradeMainnetUniFiAVS.s.sol | 37 +++++++++++++++---- l1-contracts/src/UniFiAVSManager.sol | 5 ++- .../src/interfaces/IUniFiAVSManager.sol | 4 +- l1-contracts/test/UniFiAVSManager.t.sol | 8 ++-- 5 files changed, 41 insertions(+), 15 deletions(-) diff --git a/l1-contracts/script/SetupAccess.s.sol b/l1-contracts/script/SetupAccess.s.sol index c70dc1b..54c3c85 100644 --- a/l1-contracts/script/SetupAccess.s.sol +++ b/l1-contracts/script/SetupAccess.s.sol @@ -105,7 +105,7 @@ contract SetupAccess is BaseScript { publicSelectors[6] = UniFiAVSManager.updateOperatorCommitment.selector; publicSelectors[7] = UniFiAVSManager.registerOperatorWithCommitment.selector; publicSelectors[8] = UniFiAVSManager.registerValidatorsOptimistically.selector; - publicSelectors[9] = UniFiAVSManager.verifyValidatorSignatures.selector; + publicSelectors[9] = UniFiAVSManager.slashValidatorsWithInvalidSignature.selector; publicSelectors[10] = UniFiAVSManager.slashValidatorsWithInvalidPubkey.selector; publicSelectors[11] = UniFiAVSManager.slashValidatorsWithInvalidIndex.selector; diff --git a/l1-contracts/script/UpgradeMainnetUniFiAVS.s.sol b/l1-contracts/script/UpgradeMainnetUniFiAVS.s.sol index ac7b908..466923e 100644 --- a/l1-contracts/script/UpgradeMainnetUniFiAVS.s.sol +++ b/l1-contracts/script/UpgradeMainnetUniFiAVS.s.sol @@ -10,11 +10,12 @@ import { IEigenPodManager } from "eigenlayer/interfaces/IEigenPodManager.sol"; import { IDelegationManager } from "eigenlayer/interfaces/IDelegationManager.sol"; import { IAVSDirectory } from "eigenlayer/interfaces/IAVSDirectory.sol"; import { console } from "forge-std/console.sol"; -import { ROLE_ID_OPERATIONS_MULTISIG, ROLE_ID_DAO } from "./Roles.sol"; +import { ROLE_ID_OPERATIONS_MULTISIG, ROLE_ID_DAO, ROLE_ID_UNIFI_AVS_MANAGER } from "./Roles.sol"; import { UUPSUpgradeable } from "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol"; import { UniFiAVSDisputeManager } from "../src/UniFiAVSDisputeManager.sol"; import { IUniFiAVSDisputeManager } from "../src/interfaces/IUniFiAVSDisputeManager.sol"; import { ERC1967Proxy } from "@openzeppelin/contracts/proxy/ERC1967/ERC1967Proxy.sol"; +import { Multicall } from "@openzeppelin/contracts/utils/Multicall.sol"; contract UpgradeMainnetUniFiAVS is BaseScript { function run() public returns (AVSDeployment memory deployment) { @@ -60,14 +61,36 @@ contract UpgradeMainnetUniFiAVS is BaseScript { console.log("Access control calldata:"); - bytes memory calldatas; - bytes4[] memory daoSelectors = new bytes4[](1); - daoSelectors[0] = UniFiAVSManager.setAllowlistRestakingStrategy.selector; + bytes[] memory calldatas = new bytes[](3); + bytes4[] memory uniFiAVSManagerSelectors = new bytes4[](4); + uniFiAVSManagerSelectors[0] = UniFiAVSManager.registerValidatorsOptimistically.selector; + uniFiAVSManagerSelectors[1] = UniFiAVSManager.slashValidatorsWithInvalidSignature.selector; + uniFiAVSManagerSelectors[2] = UniFiAVSManager.slashValidatorsWithInvalidPubkey.selector; + uniFiAVSManagerSelectors[3] = UniFiAVSManager.slashValidatorsWithInvalidIndex.selector; - calldatas = abi.encodeWithSelector( - AccessManager.setTargetFunctionRole.selector, address(uniFiAVSManagerProxy), daoSelectors, ROLE_ID_DAO + calldatas[0] = abi.encodeWithSelector( + AccessManager.setTargetFunctionRole.selector, + address(uniFiAVSManagerProxy), + uniFiAVSManagerSelectors, + accessManager.PUBLIC_ROLE() ); - console.logBytes(calldatas); + calldatas[1] = abi.encodeWithSelector( + AccessManager.grantRole.selector, ROLE_ID_UNIFI_AVS_MANAGER, address(uniFiAVSManagerProxy), 0 + ); + + bytes4[] memory disputeManagerSelectors = new bytes4[](1); + disputeManagerSelectors[0] = IUniFiAVSDisputeManager.slashOperator.selector; + + calldatas[2] = abi.encodeWithSelector( + AccessManager.setTargetFunctionRole.selector, + address(disputeManager), + disputeManagerSelectors, + ROLE_ID_UNIFI_AVS_MANAGER + ); + + bytes memory multicallData = abi.encodeCall(Multicall.multicall, (calldatas)); + + console.logBytes(multicallData); } } diff --git a/l1-contracts/src/UniFiAVSManager.sol b/l1-contracts/src/UniFiAVSManager.sol index bba1539..0545e17 100644 --- a/l1-contracts/src/UniFiAVSManager.sol +++ b/l1-contracts/src/UniFiAVSManager.sol @@ -320,7 +320,10 @@ contract UniFiAVSManager is * @inheritdoc IUniFiAVSManager * @dev Restricted in this context is like `whenNotPaused` modifier from Pausable.sol */ - function verifyValidatorSignatures(ValidatorRegistrationSlashingParams[] calldata validators) external restricted { + function slashValidatorsWithInvalidSignature(ValidatorRegistrationSlashingParams[] calldata validators) + external + restricted + { UniFiAVSStorage storage $ = _getUniFiAVSManagerStorage(); for (uint256 i = 0; i < validators.length; i++) { diff --git a/l1-contracts/src/interfaces/IUniFiAVSManager.sol b/l1-contracts/src/interfaces/IUniFiAVSManager.sol index 1d33185..71d9c74 100644 --- a/l1-contracts/src/interfaces/IUniFiAVSManager.sol +++ b/l1-contracts/src/interfaces/IUniFiAVSManager.sol @@ -299,10 +299,10 @@ interface IUniFiAVSManager { function registerValidatorsOptimistically(ValidatorRegistrationParams[] calldata paramsArray) external; /** - * @notice Verifies the signatures of validators. + * @notice Slashes validators with invalid signatures. * @param validators The array of ValidatorRegistrationSlashingParams. */ - function verifyValidatorSignatures(ValidatorRegistrationSlashingParams[] calldata validators) external; + function slashValidatorsWithInvalidSignature(ValidatorRegistrationSlashingParams[] calldata validators) external; /** * @notice Slashes validators with invalid pubkey. diff --git a/l1-contracts/test/UniFiAVSManager.t.sol b/l1-contracts/test/UniFiAVSManager.t.sol index dac46ab..75c89a4 100644 --- a/l1-contracts/test/UniFiAVSManager.t.sol +++ b/l1-contracts/test/UniFiAVSManager.t.sol @@ -1105,7 +1105,7 @@ contract UniFiAVSManagerTest is UnitTestHelper { assertEq(validator2.operator, operator, "Validator 2 should be assigned to the correct operator"); } - function testVerifyValidatorSignatures() public { + function testslashValidatorsWithInvalidSignature() public { _setupOperator(); _registerOperator(); @@ -1139,14 +1139,14 @@ contract UniFiAVSManagerTest is UnitTestHelper { salt: salt }); - avsManager.verifyValidatorSignatures(slashingParams); + avsManager.slashValidatorsWithInvalidSignature(slashingParams); vm.roll(block.number + avsManager.getRegistrationDelay() + 1); ValidatorDataExtended memory validator = avsManager.getValidator(pubkeyHash); assertTrue(validator.registered, "Validator should still be registered after verification"); } - function testVerifyValidatorSignatures_InvalidSignature() public { + function testslashValidatorsWithInvalidSignature_InvalidSignature() public { _setupOperator(); _registerOperator(); @@ -1192,7 +1192,7 @@ contract UniFiAVSManagerTest is UnitTestHelper { vm.expectEmit(true, true, false, false); emit IUniFiAVSManager.ValidatorSlashed(operator, pubkeyHash); - avsManager.verifyValidatorSignatures(slashingParams); + avsManager.slashValidatorsWithInvalidSignature(slashingParams); vm.roll(block.number + avsManager.getDeregistrationDelay() + 1); ValidatorDataExtended memory validator = avsManager.getValidator(pubkeyHash); From 30c7841119344da75c906105f7fa94462890bac6 Mon Sep 17 00:00:00 2001 From: 0xwalid Date: Wed, 30 Oct 2024 00:15:06 -1000 Subject: [PATCH 20/22] change optimistic registration event --- l1-contracts/src/UniFiAVSManager.sol | 12 +++++------- .../src/interfaces/IUniFiAVSManager.sol | 18 ++++++++++++++++++ 2 files changed, 23 insertions(+), 7 deletions(-) diff --git a/l1-contracts/src/UniFiAVSManager.sol b/l1-contracts/src/UniFiAVSManager.sol index 0545e17..867446e 100644 --- a/l1-contracts/src/UniFiAVSManager.sol +++ b/l1-contracts/src/UniFiAVSManager.sol @@ -243,7 +243,6 @@ contract UniFiAVSManager is { UniFiAVSStorage storage $ = _getUniFiAVSManagerStorage(); uint256 newValidatorCount = 0; - bytes memory delegateKey = $.operators[msg.sender].commitment.delegateKey; for (uint256 i = 0; i < validators.length; i++) { // Derive the BLS public key hash from pubkeyG1 @@ -299,12 +298,13 @@ contract UniFiAVSManager is $.validatorIndexes[validators[i].index] = validators[i].blsPubKeyHash; - emit ValidatorRegistered({ - podOwner: address(0), + emit ValidatorRegisteredOptimistically({ operator: msg.sender, - delegateKey: delegateKey, blsPubKeyHash: validators[i].blsPubKeyHash, - validatorIndex: validators[i].index + validatorIndex: validators[i].index, + salt: validators[i].salt, + expiry: validators[i].expiry, + signature: validators[i].registrationSignature }); newValidatorCount++; @@ -890,8 +890,6 @@ contract UniFiAVSManager is // Emit the ValidatorDeregistered event emit ValidatorDeregistered({ operator: operator, blsPubKeyHash: blsPubKeyHash }); - emit ValidatorSlashed({ operator: operator, blsPubKeyHash: blsPubKeyHash }); - // Decrement the operator's validator count OperatorData storage operatorData = $.operators[operator]; operatorData.validatorCount -= 1; diff --git a/l1-contracts/src/interfaces/IUniFiAVSManager.sol b/l1-contracts/src/interfaces/IUniFiAVSManager.sol index 71d9c74..75aa7f4 100644 --- a/l1-contracts/src/interfaces/IUniFiAVSManager.sol +++ b/l1-contracts/src/interfaces/IUniFiAVSManager.sol @@ -189,6 +189,24 @@ interface IUniFiAVSManager { */ event RegistrationDelaySet(uint64 oldDelay, uint64 newDelay); + /** + * @notice Emitted when a validator is registered optimistically. + * @param operator The address of the operator. + * @param blsPubKeyHash The BLS public key hash of the validator. + * @param validatorIndex The beacon chain validator index. + * @param salt The salt for the message. + * @param expiry The expiry for the message. + * @param signature The signature for the message. + */ + event ValidatorRegisteredOptimistically( + address indexed operator, + bytes32 indexed blsPubKeyHash, + uint256 validatorIndex, + uint256 salt, + uint256 expiry, + BN254.G1Point signature + ); + /** * @notice Returns the EigenPodManager contract. * @return IEigenPodManager The EigenPodManager contract. From cb4bb6c7d70da53ed1bfb67b2e21ec3c0a14b972 Mon Sep 17 00:00:00 2001 From: 0xwalid Date: Thu, 31 Oct 2024 19:20:19 -0700 Subject: [PATCH 21/22] add registration documentation --- l1-contracts/docs/registration.md | 56 +++++++++++++++++++++++++------ 1 file changed, 46 insertions(+), 10 deletions(-) diff --git a/l1-contracts/docs/registration.md b/l1-contracts/docs/registration.md index a262a3d..24d6342 100644 --- a/l1-contracts/docs/registration.md +++ b/l1-contracts/docs/registration.md @@ -152,22 +152,58 @@ sequenceDiagram ### Validator Registration Process Explanation -1. The `Operator` calls `registerValidators()` on the `UniFiAVSManager`, providing the `podOwner` address and an array of BLS public key hashes for the validators to be registered. +There are two primary paths for validator registration and incorporates robust slashing mechanisms to maintain the integrity of the network. This document explores these paths and mechanisms in detail, highlighting key nuances such as registration delays and slashing conditions. -2. The `UniFiAVSManager` checks if the operator is registered with the AVS using the `AVSDirectory`. +## Validator Registration Path 1: Registering Validators with EigenPod -3. The `UniFiAVSManager` verifies that the operator has set an OperatorCommitment. +The `registerValidators` function is used for validators associated with an EigenPod. This method ensures that validators are actively managed and verified through the EigenLayers ecosystem. -4. For each BLS public key hash in provided: - - The `UniFiAVSManager` retrieves the validator information from the `EigenPod`. - - It checks if the validator is active in the EigenPod. - - It verifies that the validator is not already registered in the UniFi AVS. - - If all checks pass, it registers the validator, associating it with the operator and storing relevant information. +```solidity +function registerValidators(address podOwner, bytes32[] calldata blsPubKeyHashes) external; +``` +Parameters: +- `podOwner`: The address of the pod owner. +- `blsPubKeyHashes`: An array of BLS public key hashes for the validators to be registered. + +Example: + +```solidity + bytes32[] memory pubKeyHashes = [0x1234..., 0x5678...]; + uniFiAVSManager.registerValidators(podOwner, pubKeyHashes); +``` +Process: +The function requires the caller to be the operator delegated to the pod owner, ensuring proper authorization. It also verifies that the operator is registered in the AVS (Active Validator Set) and checks that the validators are active and not already registered. Once these conditions are met, validators indefinitely. + +## Validator Registration Path 2: Optimistic Registration for Independent Validators + +This function is designed for independent validators, allowing for an optimistic registration process that prioritizes gas efficiency. -5. The `UniFiAVSManager` updates the operator's validator count and resets their deregistration state if they had previously queued to deregister their operator. +```solidity +function registerValidatorsOptimistically(ValidatorRegistrationParams[] calldata validators) external; +``` + +Parameters: +- `validators`: An array of `ValidatorRegistrationParams` structs, each containing the necessary data for registration. + +Example: + +```solidity + ValidatorRegistrationParams[] memory validators = new ValidatorRegistrationParams[](1); + validators[0] = ValidatorRegistrationParams({ + blsPubKeyHash: 0x1234..., + index: 1, + salt: 123456, + expiry: block.timestamp + 1 days, + registrationSignature: BN254.G1Point({X: 0x5678..., Y: 0x9abc...}) + }); + + uniFiAVSManager.registerValidatorsOptimistically(validators); +``` -These checks will ensure that a validator can only be registered exactly once in the AVS, and that it can only be to the Operator whom the validator's podOwner is delegated to. +Process: +Validators are registered without immediate validation checks, assuming validity until proven otherwise. A key feature of this method is the registration delay, which introduces a window before validators become active. This delay allows time for potential slashing of invalid registrations, enhancing network security. +A critical feature of the optimistic registration process is the registration delay. This delay serves as a safeguard, allowing time for the network to identify and slash invalid validators before they become active. During this period, slashers can verify the validity of the registration and take action if necessary. This mechanism significantly enhances network security by preventing invalid validators from participating in the network. # Deregistering from UniFi AVS From b4eb50dcdbd8c1cd3b1b10238daefa8f9e53ed40 Mon Sep 17 00:00:00 2001 From: 0xwalid Date: Fri, 1 Nov 2024 09:57:54 -0700 Subject: [PATCH 22/22] adds slashing documentation --- l1-contracts/docs/registration.md | 15 ++++++ l1-contracts/docs/slashing.md | 88 ++++++++++++++++++++++++++++--- 2 files changed, 97 insertions(+), 6 deletions(-) diff --git a/l1-contracts/docs/registration.md b/l1-contracts/docs/registration.md index 24d6342..eb8c9b3 100644 --- a/l1-contracts/docs/registration.md +++ b/l1-contracts/docs/registration.md @@ -205,6 +205,21 @@ Validators are registered without immediate validation checks, assuming validity A critical feature of the optimistic registration process is the registration delay. This delay serves as a safeguard, allowing time for the network to identify and slash invalid validators before they become active. During this period, slashers can verify the validity of the registration and take action if necessary. This mechanism significantly enhances network security by preventing invalid validators from participating in the network. +To generate the `registrationSignature`, the validator needs to sign the BLS message hash using their BLS key containing the `operator`, `index`, `salt`, and `expiry`. + +The mssage hash that needs to be signed can be fetched using the following view function: + +```solidity +function blsMessageHash(address operator, uint256 salt, uint256 expiry, uint256 index) + public + view + returns (BN254.G1Point memory) +``` +If you want to generate the hash yourself, you can use the following typehash: + +`VALIDATOR_REGISTRATION_TYPEHASH = + keccak256("BN254ValidatorRegistration(address operator,bytes32 salt,uint256 expiry,uint64 index)");` + # Deregistering from UniFi AVS ## Deregistering Validators diff --git a/l1-contracts/docs/slashing.md b/l1-contracts/docs/slashing.md index 9544688..01d5c9c 100644 --- a/l1-contracts/docs/slashing.md +++ b/l1-contracts/docs/slashing.md @@ -1,11 +1,87 @@ # Slashing Mechanism -The slashing mechanism in UniFi AVS is designed to ensure the integrity of the pre-confirmation process. It consists of two main cases: +The slashing mechanism in UniFi AVS is designed to ensure the integrity of the pre-confirmation process. It consists of three main cases: -1. Safety Faults (Breaking Pre-confirmation Promises) -2. Liveness Faults (Missed Block Slashing) +1. Invalid Validator Registration +2. Safety Faults (Breaking Pre-confirmation Promises) +3. Liveness Faults (Missed Block Slashing) -## Safety Faults +## Invalid Validator Registration +To maintain the integrity of the network, the UniFiAVSManager contract includes several mechanisms to slash operators who register invalid validators. Slashing acts as a deterrent against fraudulent or incorrect registrations. + +### Slashing Validators with Invalid Registration Signatures + +```solidity +function slashValidatorsWithInvalidSignature(ValidatorRegistrationSlashingParams[] calldata validators) external; +``` +Parameters: +- `validators`: An array of `ValidatorRegistrationSlashingParams` structs, each containing the necessary data for slashing. + +Example: +```solidity + ValidatorRegistrationSlashingParams[] memory validators = new ValidatorRegistrationSlashingParams[](1); + validators[0] = ValidatorRegistrationSlashingParams({ + pubkeyG1: BN254.G1Point({X: 0x1234..., Y: 0x5678...}), + pubkeyG2: BN254.G2Point({X: [0x9abc..., 0xdef0...], Y: [0x1234..., 0x5678...]}), + registrationSignature: BN254.G1Point({X: 0x9abc..., Y: 0xdef0...}), + expiry: block.timestamp + 1 days, + salt: 123456, + index: 1 // index of the validator + }); + + uniFiAVSManager.slashValidatorsWithInvalidSignature(validators); +``` + +Mechanism: +It checks the validity of the registration signature using BLS signature verification. If the signature is found to be invalid, the validator is slashed, and the operator is penalized. This mechanism maintains the authenticity of registrations, ensuring that only legitimate validators are part of the network. + +### Slashing Validators with Invalid Index + +```solidity +function slashValidatorsWithInvalidIndex(BeaconChainHelperLib.InclusionProof[] calldata proofs) external; +``` +Parameters: +- `proofs`: An array of `BeaconChainHelperLib.InclusionProof` structs, each containing the necessary data for slashing. + +Example: + +```solidity + BeaconChainHelperLib.InclusionProof[] memory proofs = new BeaconChainHelperLib.InclusionProof[](1); + proofs[0] = BeaconChainHelperLib.InclusionProof({ + validator: [0x1234...], + validatorIndex: 1, + // Additional proof data... + }); + + uniFiAVSManager.slashValidatorsWithInvalidIndex(proofs); +``` + +Mechanism: +This function verifies the validator's index against the provided proof. If the index does not match, the validator is slashed. This mechanism prevents the misuse of validator indices, ensuring that each index is unique and correctly assigned. + +### Slashing Validators with Invalid Public Key + +```solidity +function slashValidatorsWithInvalidPubkey(BeaconChainHelperLib.InclusionProof[] calldata proofs) external; +``` +Parameters: +- `proofs`: An array of `BeaconChainHelperLib.InclusionProof` structs, each containing the necessary data for slashing. + +Example: +```solidity + BeaconChainHelperLib.InclusionProof[] memory proofs = new BeaconChainHelperLib.InclusionProof[](1); + proofs[0] = BeaconChainHelperLib.InclusionProof({ + validator: [0x1234...], + // Additional proof data... + }); + + uniFiAVSManager.slashValidatorsWithInvalidPubkey(proofs); +``` + +Mechanism: +Similar to the previous mechanisms, this function verifies the validator's public key against the provided proof. If the public key is found to be invalid, the validator is slashed. This mechanism ensures the integrity of the validator's public key, preventing unauthorized or incorrect registrations. + +## Safety Faults (Not Implemented) Safety faults occur when a validator breaks their pre-conf promise. This category encompasses a larger design space compared to Liveness faults, including: @@ -21,7 +97,7 @@ b) Execution Pre-conf Violations: The larger design space for Safety faults allows for more complex and nuanced slashing conditions, which can be expanded and refined as the pre-confirmation ecosystem evolves. -## Liveness Faults +## Liveness Faults (Not Implemented) Liveness faults occur when: @@ -31,7 +107,7 @@ Liveness faults occur when: This mechanism ensures that validators cannot abuse the pre-confirmation system by making promises they don't intend to keep due to inactivity. -## Slashing Process +## Slashing Process for Liveness Faults and Safety Faults The slashing process involves two key components: