Skip to content

Commit

Permalink
Add deprecateCapability (#13036)
Browse files Browse the repository at this point in the history
* Add getCapabilities

* Fix comments

* Add deprecateCapability

* Wrappers + changesets

* Name fixes
  • Loading branch information
DeividasK authored Apr 30, 2024
1 parent 04b42f1 commit 77efb61
Show file tree
Hide file tree
Showing 7 changed files with 356 additions and 5 deletions.
5 changes: 5 additions & 0 deletions .changeset/beige-papayas-develop.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"chainlink": patch
---

#internal
5 changes: 5 additions & 0 deletions contracts/.changeset/plenty-ligers-joke.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@chainlink/contracts": patch
---

#internal
47 changes: 45 additions & 2 deletions contracts/src/v0.8/keystone/CapabilityRegistry.sol
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,16 @@ contract CapabilityRegistry is OwnerIsCreator, TypeAndVersionInterface {
/// exists.
error CapabilityAlreadyExists();

/// @notice This error is thrown when a capability with the provided ID is
/// not found.
/// @param capabilityId The ID used for the lookup.
error CapabilityDoesNotExist(bytes32 capabilityId);

/// @notice This error is thrown when trying to deprecate a capability that
/// is already deprecated.
/// @param capabilityId The ID of the capability that is already deprecated.
error CapabilityAlreadyDeprecated(bytes32 capabilityId);

/// @notice This error is thrown when trying to add a capability with a
/// configuration contract that does not implement the required interface.
/// @param proposedConfigurationContract The address of the proposed
Expand Down Expand Up @@ -101,8 +111,13 @@ contract CapabilityRegistry is OwnerIsCreator, TypeAndVersionInterface {
/// @param capabilityId The ID of the newly added capability
event CapabilityAdded(bytes32 indexed capabilityId);

/// @notice This event is emitted when a capability is deprecated
/// @param capabilityId The ID of the deprecated capability
event CapabilityDeprecated(bytes32 indexed capabilityId);

mapping(bytes32 => Capability) private s_capabilities;
EnumerableSet.Bytes32Set private s_capabilityIds;
EnumerableSet.Bytes32Set private s_deprecatedCapabilityIds;

/// @notice Mapping of node operators
mapping(uint256 nodeOperatorId => NodeOperator) private s_nodeOperators;
Expand Down Expand Up @@ -189,6 +204,16 @@ contract CapabilityRegistry is OwnerIsCreator, TypeAndVersionInterface {
emit CapabilityAdded(capabilityId);
}

/// @notice Deprecates a capability by adding it to the deprecated list
/// @param capabilityId The ID of the capability to deprecate
function deprecateCapability(bytes32 capabilityId) external onlyOwner {
if (!s_capabilityIds.contains(capabilityId)) revert CapabilityDoesNotExist(capabilityId);
if (s_deprecatedCapabilityIds.contains(capabilityId)) revert CapabilityAlreadyDeprecated(capabilityId);

s_deprecatedCapabilityIds.add(capabilityId);
emit CapabilityDeprecated(capabilityId);
}

function getCapability(bytes32 capabilityID) public view returns (Capability memory) {
return s_capabilities[capabilityID];
}
Expand All @@ -199,11 +224,22 @@ contract CapabilityRegistry is OwnerIsCreator, TypeAndVersionInterface {
/// @return Capability[] An array of capabilities
function getCapabilities() external view returns (Capability[] memory) {
bytes32[] memory capabilityIds = s_capabilityIds.values();
Capability[] memory capabilities = new Capability[](capabilityIds.length);

// Solidity does not support dynamic arrays in memory, so we create a
// fixed-size array and copy the capabilities into it.
Capability[] memory capabilities = new Capability[](capabilityIds.length - s_deprecatedCapabilityIds.length());

// We need to keep track of the new index because we are skipping
// deprecated capabilities.
uint256 newIndex;

for (uint256 i; i < capabilityIds.length; ++i) {
bytes32 capabilityId = capabilityIds[i];
capabilities[i] = getCapability(capabilityId);

if (!s_deprecatedCapabilityIds.contains(capabilityId)) {
capabilities[newIndex] = getCapability(capabilityId);
newIndex++;
}
}

return capabilities;
Expand All @@ -214,4 +250,11 @@ contract CapabilityRegistry is OwnerIsCreator, TypeAndVersionInterface {
function getCapabilityID(bytes32 capabilityType, bytes32 version) public pure returns (bytes32) {
return keccak256(abi.encodePacked(capabilityType, version));
}

/// @notice Returns whether a capability is deprecated
/// @param capabilityId The ID of the capability to check
/// @return bool True if the capability is deprecated, false otherwise
function isCapabilityDeprecated(bytes32 capabilityId) external view returns (bool) {
return s_deprecatedCapabilityIds.contains(capabilityId);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.19;

import {BaseTest} from "./BaseTest.t.sol";

import {CapabilityRegistry} from "../CapabilityRegistry.sol";

contract CapabilityRegistry_AddCapabilityTest is BaseTest {
event CapabilityDeprecated(bytes32 indexed capabilityId);

function setUp() public override {
BaseTest.setUp();

s_capabilityRegistry.addCapability(s_basicCapability);
s_capabilityRegistry.addCapability(s_capabilityWithConfigurationContract);
}

function test_RevertWhen_CalledByNonAdmin() public {
changePrank(STRANGER);
bytes32 capabilityId = s_capabilityRegistry.getCapabilityID(
s_basicCapability.capabilityType,
s_basicCapability.version
);

vm.expectRevert("Only callable by owner");
s_capabilityRegistry.deprecateCapability(capabilityId);
}

function test_RevertWhen_CapabilityDoesNotExist() public {
bytes32 capabilityId = s_capabilityRegistry.getCapabilityID("non-existent-capability", "1.0.0");

vm.expectRevert(abi.encodeWithSelector(CapabilityRegistry.CapabilityDoesNotExist.selector, capabilityId));
s_capabilityRegistry.deprecateCapability(capabilityId);
}

function test_RevertWhen_CapabilityAlreadyDeprecated() public {
bytes32 capabilityId = s_capabilityRegistry.getCapabilityID(
s_basicCapability.capabilityType,
s_basicCapability.version
);

s_capabilityRegistry.deprecateCapability(capabilityId);

vm.expectRevert(abi.encodeWithSelector(CapabilityRegistry.CapabilityAlreadyDeprecated.selector, capabilityId));
s_capabilityRegistry.deprecateCapability(capabilityId);
}

function test_DeprecatesCapability() public {
bytes32 capabilityId = s_capabilityRegistry.getCapabilityID(
s_basicCapability.capabilityType,
s_basicCapability.version
);

s_capabilityRegistry.deprecateCapability(capabilityId);

assertEq(s_capabilityRegistry.isCapabilityDeprecated(capabilityId), true);
}

function test_EmitsEvent() public {
bytes32 capabilityId = s_capabilityRegistry.getCapabilityID(
s_basicCapability.capabilityType,
s_basicCapability.version
);

vm.expectEmit(address(s_capabilityRegistry));
emit CapabilityDeprecated(capabilityId);
s_capabilityRegistry.deprecateCapability(capabilityId);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.19;

import {BaseTest} from "./BaseTest.t.sol";
import {CapabilityRegistry} from "../CapabilityRegistry.sol";

contract CapabilityRegistry_GetCapabilitiesTest is BaseTest {
function setUp() public override {
BaseTest.setUp();

s_capabilityRegistry.addCapability(s_basicCapability);
s_capabilityRegistry.addCapability(s_capabilityWithConfigurationContract);
}

function test_ReturnsCapabilities() public view {
CapabilityRegistry.Capability[] memory capabilities = s_capabilityRegistry.getCapabilities();

assertEq(capabilities.length, 2);

assertEq(capabilities[0].capabilityType, "data-streams-reports");
assertEq(capabilities[0].version, "1.0.0");
assertEq(uint256(capabilities[0].responseType), uint256(CapabilityRegistry.CapabilityResponseType.REPORT));
assertEq(capabilities[0].configurationContract, address(0));

assertEq(capabilities[1].capabilityType, "read-ethereum-mainnet-gas-price");
assertEq(capabilities[1].version, "1.0.2");
assertEq(
uint256(capabilities[1].responseType),
uint256(CapabilityRegistry.CapabilityResponseType.OBSERVATION_IDENTICAL)
);
assertEq(capabilities[1].configurationContract, address(s_capabilityConfigurationContract));
}

function test_ExcludesDeprecatedCapabilities() public {
bytes32 capabilityId = s_capabilityRegistry.getCapabilityID(
s_basicCapability.capabilityType,
s_basicCapability.version
);
s_capabilityRegistry.deprecateCapability(capabilityId);

CapabilityRegistry.Capability[] memory capabilities = s_capabilityRegistry.getCapabilities();
assertEq(capabilities.length, 1);

assertEq(capabilities[0].capabilityType, "read-ethereum-mainnet-gas-price");
assertEq(capabilities[0].version, "1.0.2");
assertEq(
uint256(capabilities[0].responseType),
uint256(CapabilityRegistry.CapabilityResponseType.OBSERVATION_IDENTICAL)
);
assertEq(capabilities[0].configurationContract, address(s_capabilityConfigurationContract));
}
}

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
GETH_VERSION: 1.13.8
forwarder: ../../../contracts/solc/v0.8.19/KeystoneForwarder/KeystoneForwarder.abi ../../../contracts/solc/v0.8.19/KeystoneForwarder/KeystoneForwarder.bin b4c900aae9e022f01abbac7993d41f93912247613ac6270b0c4da4ef6f2016e3
keystone_capability_registry: ../../../contracts/solc/v0.8.19/CapabilityRegistry/CapabilityRegistry.abi ../../../contracts/solc/v0.8.19/CapabilityRegistry/CapabilityRegistry.bin ae9e077854854fa066746eaa354324c7dac2a13bc38b81a66c856fddfc3b79bf
keystone_capability_registry: ../../../contracts/solc/v0.8.19/CapabilityRegistry/CapabilityRegistry.abi ../../../contracts/solc/v0.8.19/CapabilityRegistry/CapabilityRegistry.bin 28fb0e1c437b97271d999f3b028f7bd44558352ec83f89501aabf501fcc915f1
ocr3_capability: ../../../contracts/solc/v0.8.19/OCR3Capability/OCR3Capability.abi ../../../contracts/solc/v0.8.19/OCR3Capability/OCR3Capability.bin 9dcbdf55bd5729ba266148da3f17733eb592c871c2108ccca546618628fd9ad2

0 comments on commit 77efb61

Please sign in to comment.