diff --git a/.changeset/metal-horses-count.md b/.changeset/metal-horses-count.md new file mode 100644 index 00000000000..5cb5e8331a9 --- /dev/null +++ b/.changeset/metal-horses-count.md @@ -0,0 +1,5 @@ +--- +"chainlink": patch +--- + +#internal implement remove DONs in capability registry diff --git a/contracts/.changeset/long-beans-turn.md b/contracts/.changeset/long-beans-turn.md new file mode 100644 index 00000000000..3af5e4a2171 --- /dev/null +++ b/contracts/.changeset/long-beans-turn.md @@ -0,0 +1,5 @@ +--- +"@chainlink/contracts": patch +--- + +implement remove DONs in capability registry diff --git a/contracts/src/v0.8/keystone/CapabilityRegistry.sol b/contracts/src/v0.8/keystone/CapabilityRegistry.sol index 21f57831051..e17676bf7c7 100644 --- a/contracts/src/v0.8/keystone/CapabilityRegistry.sol +++ b/contracts/src/v0.8/keystone/CapabilityRegistry.sol @@ -182,6 +182,14 @@ contract CapabilityRegistry is OwnerIsCreator, TypeAndVersionInterface { /// @param isPublic True if the newly created DON is public event DONAdded(uint256 donId, bool isPublic); + /// @notice This event is emitted when a DON is removed + /// @param donId The ID of the removed DON + event DONRemoved(uint32 donId); + + /// @notice This error is emitted when a DON does not exist + /// @param donId The ID of the nonexistent DON + error DONDoesNotExist(uint32 donId); + /// @notice This error is thrown when trying to set the node's /// signer address to zero or if the signer address has already /// been used by another node @@ -583,6 +591,20 @@ contract CapabilityRegistry is OwnerIsCreator, TypeAndVersionInterface { emit DONAdded(id, isPublic); } + /// @notice Removes DONs from the Capability Registry + /// @param donIds The IDs of the DON to be removed + function removeDONs(uint32[] calldata donIds) external onlyOwner { + for (uint256 i; i < donIds.length; ++i) { + uint32 donId = donIds[i]; + DON storage don = s_dons[donId]; + + // DON config count starts at index 1 + if (don.configCount == 0) revert DONDoesNotExist(donId); + delete s_dons[donId]; + emit DONRemoved(donId); + } + } + /// @notice Gets DON's data /// @param donId The DON ID /// @return uint32 The DON ID diff --git a/contracts/src/v0.8/keystone/test/CapabilityRegistry_RemoveDONsTest.t.sol b/contracts/src/v0.8/keystone/test/CapabilityRegistry_RemoveDONsTest.t.sol new file mode 100644 index 00000000000..36d29b0a107 --- /dev/null +++ b/contracts/src/v0.8/keystone/test/CapabilityRegistry_RemoveDONsTest.t.sol @@ -0,0 +1,99 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.19; + +import {BaseTest} from "./BaseTest.t.sol"; + +import {CapabilityRegistry} from "../CapabilityRegistry.sol"; + +contract CapabilityRegistry_RemoveDONsTest is BaseTest { + event DONRemoved(uint32 donId); + + uint32 private constant DON_ID = 1; + uint32 private constant TEST_NODE_OPERATOR_ONE_ID = 0; + uint256 private constant TEST_NODE_OPERATOR_TWO_ID = 1; + bytes32 private constant INVALID_P2P_ID = bytes32("fake-p2p"); + bytes private constant CONFIG = bytes("onchain-config"); + + function setUp() public override { + BaseTest.setUp(); + + s_capabilityRegistry.addNodeOperators(_getNodeOperators()); + s_capabilityRegistry.addCapability(s_basicCapability); + s_capabilityRegistry.addCapability(s_capabilityWithConfigurationContract); + + CapabilityRegistry.NodeParams[] memory nodes = new CapabilityRegistry.NodeParams[](2); + bytes32[] memory capabilityIds = new bytes32[](2); + capabilityIds[0] = s_basicHashedCapabilityId; + capabilityIds[1] = s_capabilityWithConfigurationContractId; + + nodes[0] = CapabilityRegistry.NodeParams({ + nodeOperatorId: TEST_NODE_OPERATOR_ONE_ID, + p2pId: P2P_ID, + signer: NODE_OPERATOR_ONE_SIGNER_ADDRESS, + hashedCapabilityIds: capabilityIds + }); + + bytes32[] memory nodeTwoCapabilityIds = new bytes32[](1); + nodeTwoCapabilityIds[0] = s_basicHashedCapabilityId; + + nodes[1] = CapabilityRegistry.NodeParams({ + nodeOperatorId: TEST_NODE_OPERATOR_ONE_ID, + p2pId: P2P_ID_TWO, + signer: NODE_OPERATOR_TWO_SIGNER_ADDRESS, + hashedCapabilityIds: nodeTwoCapabilityIds + }); + + changePrank(NODE_OPERATOR_ONE_ADMIN); + s_capabilityRegistry.addNodes(nodes); + + CapabilityRegistry.CapabilityConfiguration[] + memory capabilityConfigs = new CapabilityRegistry.CapabilityConfiguration[](1); + capabilityConfigs[0] = CapabilityRegistry.CapabilityConfiguration({ + capabilityId: s_basicHashedCapabilityId, + config: CONFIG + }); + + bytes32[] memory nodeIds = new bytes32[](2); + nodeIds[0] = P2P_ID; + nodeIds[1] = P2P_ID_TWO; + + changePrank(ADMIN); + s_capabilityRegistry.addDON(nodeIds, capabilityConfigs, true); + } + + function test_RevertWhen_CalledByNonAdmin() public { + uint32[] memory donIDs = new uint32[](1); + donIDs[0] = 1; + changePrank(STRANGER); + vm.expectRevert("Only callable by owner"); + s_capabilityRegistry.removeDONs(donIDs); + } + + function test_RevertWhen_DONDoesNotExist() public { + uint32 invalidDONId = 10; + uint32[] memory donIDs = new uint32[](1); + donIDs[0] = invalidDONId; + vm.expectRevert(abi.encodeWithSelector(CapabilityRegistry.DONDoesNotExist.selector, invalidDONId)); + s_capabilityRegistry.removeDONs(donIDs); + } + + function test_RemovesDON() public { + uint32[] memory donIDs = new uint32[](1); + donIDs[0] = DON_ID; + vm.expectEmit(true, true, true, true, address(s_capabilityRegistry)); + emit DONRemoved(DON_ID); + s_capabilityRegistry.removeDONs(donIDs); + + ( + uint32 id, + bool isPublic, + bytes32[] memory donNodes, + CapabilityRegistry.CapabilityConfiguration[] memory donCapabilityConfigs + ) = s_capabilityRegistry.getDON(DON_ID); + assertEq(id, 0); + assertEq(isPublic, false); + assertEq(donCapabilityConfigs.length, 0); + assertEq(s_capabilityRegistry.getDONCapabilityConfig(DON_ID, s_basicHashedCapabilityId), bytes("")); + assertEq(donNodes.length, 0); + } +}