mirror of
https://github.com/datahaven-xyz/datahaven
synced 2026-05-24 09:50:01 +00:00
## Summary This PR makes `removeValidatorFromAllowlist` immediately revoke active validator membership instead of only preventing future registrations. Previously, removing a validator from the allowlist only blocked future registration attempts, but did not remove validators that were already active in the operator set. This change closes that gap so allowlist removal immediately takes effect for active validator membership as well. ### What changed - Updated `DataHavenServiceManager.removeValidatorFromAllowlist` to force-deregister validators from the `VALIDATORS_SET_ID` when they are currently registered. - Extracted the deregistration flow into an internal helper so the same deregistration logic is reused consistently. ### Tests Added regression tests covering: - removing an allowlisted validator that is actively registered - removing an allowlisted validator that was never registered - ensuring removed validators are excluded from validator-set message generation
287 lines
10 KiB
Solidity
287 lines
10 KiB
Solidity
// SPDX-License-Identifier: UNLICENSED
|
|
pragma solidity ^0.8.27;
|
|
|
|
import {AVSDeployer} from "./utils/AVSDeployer.sol";
|
|
import {DataHavenServiceManager} from "../src/DataHavenServiceManager.sol";
|
|
import {
|
|
IAllocationManagerTypes
|
|
} from "eigenlayer-contracts/src/contracts/interfaces/IAllocationManager.sol";
|
|
import {OperatorSet} from "eigenlayer-contracts/src/contracts/libraries/OperatorSetLib.sol";
|
|
import {Test} from "forge-std/Test.sol";
|
|
|
|
contract OperatorAddressMappingsTest is AVSDeployer {
|
|
address public snowbridgeAgent = address(uint160(uint256(keccak256("snowbridgeAgent"))));
|
|
|
|
address internal operator1 = address(uint160(uint256(keccak256("operator1"))));
|
|
address internal operator2 = address(uint160(uint256(keccak256("operator2"))));
|
|
|
|
function setUp() public virtual {
|
|
_deployMockEigenLayerAndAVS();
|
|
|
|
// Configure the rewards initiator (not strictly needed for these tests,
|
|
// but keeps setup consistent with other suites).
|
|
vm.prank(avsOwner);
|
|
serviceManager.setSnowbridgeInitiator(snowbridgeAgent);
|
|
}
|
|
|
|
function _registerOperator(
|
|
address ethOperator,
|
|
address solochainOperator
|
|
) internal {
|
|
vm.prank(avsOwner);
|
|
serviceManager.addValidatorToAllowlist(ethOperator);
|
|
|
|
vm.prank(ethOperator);
|
|
delegationManager.registerAsOperator(address(0), 0, "");
|
|
|
|
uint32[] memory operatorSetIds = new uint32[](1);
|
|
operatorSetIds[0] = serviceManager.VALIDATORS_SET_ID();
|
|
IAllocationManagerTypes.RegisterParams memory registerParams =
|
|
IAllocationManagerTypes.RegisterParams({
|
|
avs: address(serviceManager),
|
|
operatorSetIds: operatorSetIds,
|
|
data: abi.encodePacked(solochainOperator)
|
|
});
|
|
|
|
vm.prank(ethOperator);
|
|
allocationManager.registerForOperatorSets(ethOperator, registerParams);
|
|
}
|
|
|
|
function test_registerOperator_revertsIfSolochainAlreadyAssignedToDifferentOperator() public {
|
|
address sharedSolochain = address(0xBEEF);
|
|
|
|
_registerOperator(operator1, sharedSolochain);
|
|
|
|
// operator2 cannot claim the same solochain address
|
|
vm.prank(avsOwner);
|
|
serviceManager.addValidatorToAllowlist(operator2);
|
|
vm.prank(operator2);
|
|
delegationManager.registerAsOperator(address(0), 0, "");
|
|
|
|
uint32[] memory operatorSetIds = new uint32[](1);
|
|
operatorSetIds[0] = serviceManager.VALIDATORS_SET_ID();
|
|
IAllocationManagerTypes.RegisterParams memory registerParams =
|
|
IAllocationManagerTypes.RegisterParams({
|
|
avs: address(serviceManager),
|
|
operatorSetIds: operatorSetIds,
|
|
data: abi.encodePacked(sharedSolochain)
|
|
});
|
|
|
|
vm.prank(operator2);
|
|
vm.expectRevert(abi.encodeWithSignature("SolochainAddressAlreadyAssigned()"));
|
|
allocationManager.registerForOperatorSets(operator2, registerParams);
|
|
}
|
|
|
|
function test_updateSolochainAddressForValidator_revertsIfAlreadyAssignedToDifferentOperator()
|
|
public
|
|
{
|
|
address solo1 = address(0xBEEF);
|
|
address solo2 = address(0xCAFE);
|
|
|
|
_registerOperator(operator1, solo1);
|
|
_registerOperator(operator2, solo2);
|
|
|
|
// operator2 cannot update to operator1's solochain address
|
|
vm.prank(operator2);
|
|
vm.expectRevert(abi.encodeWithSignature("SolochainAddressAlreadyAssigned()"));
|
|
serviceManager.updateSolochainAddressForValidator(solo1);
|
|
}
|
|
|
|
function test_updateSolochainAddressForValidator_clearsOldReverseMapping() public {
|
|
address soloOld = address(0xBEEF);
|
|
address soloNew = address(0xCAFE);
|
|
|
|
_registerOperator(operator1, soloOld);
|
|
|
|
assertEq(
|
|
serviceManager.validatorEthAddressToSolochainAddress(operator1),
|
|
soloOld,
|
|
"forward mapping should be set"
|
|
);
|
|
assertEq(
|
|
serviceManager.validatorSolochainAddressToEthAddress(soloOld),
|
|
operator1,
|
|
"reverse mapping should be set"
|
|
);
|
|
|
|
vm.prank(operator1);
|
|
serviceManager.updateSolochainAddressForValidator(soloNew);
|
|
|
|
assertEq(
|
|
serviceManager.validatorEthAddressToSolochainAddress(operator1),
|
|
soloNew,
|
|
"forward mapping should update"
|
|
);
|
|
assertEq(
|
|
serviceManager.validatorSolochainAddressToEthAddress(soloNew),
|
|
operator1,
|
|
"reverse mapping should update"
|
|
);
|
|
assertEq(
|
|
serviceManager.validatorSolochainAddressToEthAddress(soloOld),
|
|
address(0),
|
|
"old reverse mapping should be cleared"
|
|
);
|
|
}
|
|
|
|
function test_registerOperator_revertsIfAlreadyRegistered() public {
|
|
address solo1 = address(0xBEEF);
|
|
address solo2 = address(0xCAFE);
|
|
|
|
_registerOperator(operator1, solo1);
|
|
|
|
// operator1 cannot register again, even with a different solochain address
|
|
uint32[] memory operatorSetIds = new uint32[](1);
|
|
operatorSetIds[0] = serviceManager.VALIDATORS_SET_ID();
|
|
|
|
vm.prank(address(allocationManager));
|
|
vm.expectRevert(abi.encodeWithSignature("OperatorAlreadyRegistered()"));
|
|
serviceManager.registerOperator(
|
|
operator1, address(serviceManager), operatorSetIds, abi.encodePacked(solo2)
|
|
);
|
|
}
|
|
|
|
function test_deregisterOperator_clearsBothMappings() public {
|
|
address solo1 = address(0xBEEF);
|
|
|
|
_registerOperator(operator1, solo1);
|
|
|
|
uint32[] memory operatorSetIds = new uint32[](1);
|
|
operatorSetIds[0] = serviceManager.VALIDATORS_SET_ID();
|
|
|
|
vm.prank(address(allocationManager));
|
|
serviceManager.deregisterOperator(operator1, address(serviceManager), operatorSetIds);
|
|
|
|
assertEq(
|
|
serviceManager.validatorEthAddressToSolochainAddress(operator1),
|
|
address(0),
|
|
"forward mapping should be cleared"
|
|
);
|
|
assertEq(
|
|
serviceManager.validatorSolochainAddressToEthAddress(solo1),
|
|
operator1,
|
|
"reverse mapping should remain slashable until deallocation delay passes"
|
|
);
|
|
}
|
|
|
|
function test_deregisterOperator_revertsIfNotRegistered() public {
|
|
uint32[] memory operatorSetIds = new uint32[](1);
|
|
operatorSetIds[0] = serviceManager.VALIDATORS_SET_ID();
|
|
|
|
vm.prank(address(allocationManager));
|
|
vm.expectRevert(abi.encodeWithSignature("OperatorNotRegistered()"));
|
|
serviceManager.deregisterOperator(operator1, address(serviceManager), operatorSetIds);
|
|
}
|
|
|
|
function test_removeValidatorFromAllowlist_deregistersRegisteredOperator() public {
|
|
address solo1 = address(0xBEEF);
|
|
_registerOperator(operator1, solo1);
|
|
|
|
assertEq(
|
|
serviceManager.validatorEthAddressToSolochainAddress(operator1),
|
|
solo1,
|
|
"forward mapping should be set before removal"
|
|
);
|
|
assertTrue(
|
|
serviceManager.validatorsAllowlist(operator1), "operator should start allowlisted"
|
|
);
|
|
assertTrue(
|
|
allocationManager.isMemberOfOperatorSet(
|
|
operator1,
|
|
OperatorSet({avs: address(serviceManager), id: serviceManager.VALIDATORS_SET_ID()})
|
|
),
|
|
"operator should start in validator set"
|
|
);
|
|
|
|
vm.prank(avsOwner);
|
|
serviceManager.removeValidatorFromAllowlist(operator1);
|
|
|
|
assertFalse(
|
|
serviceManager.validatorsAllowlist(operator1),
|
|
"operator should be removed from allowlist"
|
|
);
|
|
assertFalse(
|
|
allocationManager.isMemberOfOperatorSet(
|
|
operator1,
|
|
OperatorSet({avs: address(serviceManager), id: serviceManager.VALIDATORS_SET_ID()})
|
|
),
|
|
"operator should be removed from validator set"
|
|
);
|
|
assertEq(
|
|
serviceManager.validatorEthAddressToSolochainAddress(operator1),
|
|
address(0),
|
|
"forward mapping should be cleared"
|
|
);
|
|
assertEq(
|
|
serviceManager.validatorSolochainAddressToEthAddress(solo1),
|
|
operator1,
|
|
"reverse mapping should remain slashable until deallocation delay passes"
|
|
);
|
|
}
|
|
|
|
function test_removeValidatorFromAllowlist_succeedsForUnregisteredValidator() public {
|
|
vm.prank(avsOwner);
|
|
serviceManager.addValidatorToAllowlist(operator1);
|
|
|
|
assertTrue(
|
|
serviceManager.validatorsAllowlist(operator1), "operator should start allowlisted"
|
|
);
|
|
|
|
vm.prank(avsOwner);
|
|
serviceManager.removeValidatorFromAllowlist(operator1);
|
|
|
|
assertFalse(
|
|
serviceManager.validatorsAllowlist(operator1),
|
|
"operator should be removed from allowlist"
|
|
);
|
|
assertEq(
|
|
serviceManager.validatorEthAddressToSolochainAddress(operator1),
|
|
address(0),
|
|
"forward mapping should remain empty"
|
|
);
|
|
}
|
|
|
|
function test_registerOperator_reclaimsExpiredSolochainMapping() public {
|
|
address solo1 = address(0xBEEF);
|
|
_registerOperator(operator1, solo1);
|
|
|
|
vm.prank(avsOwner);
|
|
serviceManager.removeValidatorFromAllowlist(operator1);
|
|
|
|
vm.roll(block.number + uint32(7 days) + 1);
|
|
|
|
vm.prank(avsOwner);
|
|
serviceManager.addValidatorToAllowlist(operator2);
|
|
vm.prank(operator2);
|
|
delegationManager.registerAsOperator(address(0), 0, "");
|
|
|
|
uint32[] memory operatorSetIds = new uint32[](1);
|
|
operatorSetIds[0] = serviceManager.VALIDATORS_SET_ID();
|
|
IAllocationManagerTypes.RegisterParams memory registerParams =
|
|
IAllocationManagerTypes.RegisterParams({
|
|
avs: address(serviceManager),
|
|
operatorSetIds: operatorSetIds,
|
|
data: abi.encodePacked(solo1)
|
|
});
|
|
|
|
vm.prank(operator2);
|
|
allocationManager.registerForOperatorSets(operator2, registerParams);
|
|
|
|
assertEq(
|
|
serviceManager.validatorSolochainAddressToEthAddress(solo1),
|
|
operator2,
|
|
"expired reverse mapping should be reclaimed by the new operator"
|
|
);
|
|
}
|
|
|
|
function test_updateSolochainAddressForValidator_revertsIfSameAddress() public {
|
|
address solo1 = address(0xBEEF);
|
|
|
|
_registerOperator(operator1, solo1);
|
|
|
|
vm.prank(operator1);
|
|
vm.expectRevert(abi.encodeWithSignature("SolochainAddressAlreadyAssigned()"));
|
|
serviceManager.updateSolochainAddressForValidator(solo1);
|
|
}
|
|
}
|
|
|