-
Notifications
You must be signed in to change notification settings - Fork 1.7k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
b1e8230
commit 912f20a
Showing
27 changed files
with
333 additions
and
301 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
--- | ||
"chainlink": patch | ||
--- | ||
|
||
#internal Keystone - rename type -> id |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
--- | ||
"@chainlink/contracts": patch | ||
--- | ||
|
||
#internal Keystone - rename type to id |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -28,10 +28,10 @@ contract CapabilityRegistry is OwnerIsCreator, TypeAndVersionInterface { | |
bytes32 p2pId; | ||
/// @notice The signer address for application-layer message verification. | ||
address signer; | ||
/// @notice The list of capability IDs this node supports. This list is | ||
/// @notice The list of hashed capability IDs this node supports. This list is | ||
/// never empty and all capabilities are guaranteed to exist in the | ||
/// CapabilityRegistry. | ||
bytes32[] supportedCapabilityIds; | ||
bytes32[] supportedHashedCapabilityIds; | ||
} | ||
|
||
// CapabilityResponseType indicates whether remote response requires | ||
|
@@ -45,10 +45,16 @@ contract CapabilityRegistry is OwnerIsCreator, TypeAndVersionInterface { | |
} | ||
|
||
struct Capability { | ||
// Capability type, e.g. "data-streams-reports" | ||
// The `labelledName` is a partially qualified ID for the capability. | ||
// | ||
// Given the following capability ID: {name}:{label1_key}_{label1_value}:{label2_key}_{label2_value}@{version} | ||
// Then we denote the `labelledName` as the `{name}:{label1_key}_{label1_value}:{label2_key}_{label2_value}` portion of the ID. | ||
// | ||
// Ex. id = "data-streams-reports:chain:[email protected]" | ||
// labelledName = "data-streams-reports:chain:ethereum" | ||
// | ||
// bytes32(string); validation regex: ^[a-z0-9_\-:]{1,32}$ | ||
// Not "type" because that's a reserved keyword in Solidity. | ||
bytes32 capabilityType; | ||
bytes32 labelledName; | ||
// Semver, e.g., "1.2.3" | ||
// bytes32(string); must be valid Semver + max 32 characters. | ||
bytes32 version; | ||
|
@@ -92,8 +98,8 @@ contract CapabilityRegistry is OwnerIsCreator, TypeAndVersionInterface { | |
|
||
/// @notice This error is thrown when trying to add a node without | ||
/// capabilities or with capabilities that do not exist. | ||
/// @param capabilityIds The IDs of the capabilities that are being added. | ||
error InvalidNodeCapabilities(bytes32[] capabilityIds); | ||
/// @param hashedCapabilityIds The IDs of the capabilities that are being added. | ||
error InvalidNodeCapabilities(bytes32[] hashedCapabilityIds); | ||
|
||
/// @notice This event is emitted when a new node is added | ||
/// @param p2pId The P2P ID of the node | ||
|
@@ -104,15 +110,15 @@ contract CapabilityRegistry is OwnerIsCreator, TypeAndVersionInterface { | |
/// exists. | ||
error CapabilityAlreadyExists(); | ||
|
||
/// @notice This error is thrown when a capability with the provided ID is | ||
/// @notice This error is thrown when a capability with the provided hashed ID is | ||
/// not found. | ||
/// @param capabilityId The ID used for the lookup. | ||
error CapabilityDoesNotExist(bytes32 capabilityId); | ||
/// @param hashedCapabilityId The hashed ID used for the lookup. | ||
error CapabilityDoesNotExist(bytes32 hashedCapabilityId); | ||
|
||
/// @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); | ||
/// @param hashedCapabilityId The hashed ID of the capability that is already deprecated. | ||
error CapabilityAlreadyDeprecated(bytes32 hashedCapabilityId); | ||
|
||
/// @notice This error is thrown when trying to add a capability with a | ||
/// configuration contract that does not implement the required interface. | ||
|
@@ -138,16 +144,22 @@ contract CapabilityRegistry is OwnerIsCreator, TypeAndVersionInterface { | |
event NodeOperatorUpdated(uint256 nodeOperatorId, address indexed admin, string name); | ||
|
||
/// @notice This event is emitted when a new capability is added | ||
/// @param capabilityId The ID of the newly added capability | ||
event CapabilityAdded(bytes32 indexed capabilityId); | ||
/// @param hashedCapabilityId The hashed ID of the newly added capability | ||
event CapabilityAdded(bytes32 indexed hashedCapabilityId); | ||
|
||
/// @notice This event is emitted when a capability is deprecated | ||
/// @param capabilityId The ID of the deprecated capability | ||
event CapabilityDeprecated(bytes32 indexed capabilityId); | ||
/// @param hashedCapabilityId The hashed ID of the deprecated capability | ||
event CapabilityDeprecated(bytes32 indexed hashedCapabilityId); | ||
|
||
mapping(bytes32 => Capability) private s_capabilities; | ||
EnumerableSet.Bytes32Set private s_capabilityIds; | ||
EnumerableSet.Bytes32Set private s_deprecatedCapabilityIds; | ||
/// @notice Set of hashed capability IDs. | ||
/// A hashed ID is created by the function `getHashedCapabilityId`. | ||
EnumerableSet.Bytes32Set private s_hashedCapabilityIds; | ||
/// @notice Set of deprecated hashed capability IDs, | ||
/// A hashed ID is created by the function `getHashedCapabilityId`. | ||
/// | ||
/// Deprecated capabilities are skipped by the `getCapabilities` function. | ||
EnumerableSet.Bytes32Set private s_deprecatedHashedCapabilityIds; | ||
|
||
/// @notice Mapping of node operators | ||
mapping(uint256 nodeOperatorId => NodeOperator nodeOperator) private s_nodeOperators; | ||
|
@@ -227,14 +239,15 @@ contract CapabilityRegistry is OwnerIsCreator, TypeAndVersionInterface { | |
NodeOperator memory nodeOperator = s_nodeOperators[node.nodeOperatorId]; | ||
if (msg.sender != nodeOperator.admin) revert AccessForbidden(); | ||
|
||
bool nodeExists = s_nodes[node.p2pId].supportedCapabilityIds.length > 0; | ||
bool nodeExists = s_nodes[node.p2pId].supportedHashedCapabilityIds.length > 0; | ||
if (nodeExists || bytes32(node.p2pId) == bytes32("")) revert InvalidNodeP2PId(node.p2pId); | ||
|
||
if (node.supportedCapabilityIds.length == 0) revert InvalidNodeCapabilities(node.supportedCapabilityIds); | ||
if (node.supportedHashedCapabilityIds.length == 0) | ||
revert InvalidNodeCapabilities(node.supportedHashedCapabilityIds); | ||
|
||
for (uint256 j; j < node.supportedCapabilityIds.length; ++j) { | ||
if (!s_capabilityIds.contains(node.supportedCapabilityIds[j])) | ||
revert InvalidNodeCapabilities(node.supportedCapabilityIds); | ||
for (uint256 j; j < node.supportedHashedCapabilityIds.length; ++j) { | ||
if (!s_hashedCapabilityIds.contains(node.supportedHashedCapabilityIds[j])) | ||
revert InvalidNodeCapabilities(node.supportedHashedCapabilityIds); | ||
} | ||
|
||
s_nodes[node.p2pId] = node; | ||
|
@@ -250,9 +263,8 @@ contract CapabilityRegistry is OwnerIsCreator, TypeAndVersionInterface { | |
} | ||
|
||
function addCapability(Capability calldata capability) external onlyOwner { | ||
bytes32 capabilityId = getCapabilityID(capability.capabilityType, capability.version); | ||
|
||
if (s_capabilityIds.contains(capabilityId)) revert CapabilityAlreadyExists(); | ||
bytes32 hashedId = getHashedCapabilityId(capability.labelledName, capability.version); | ||
if (s_hashedCapabilityIds.contains(hashedId)) revert CapabilityAlreadyExists(); | ||
|
||
if (capability.configurationContract != address(0)) { | ||
if ( | ||
|
@@ -263,63 +275,67 @@ contract CapabilityRegistry is OwnerIsCreator, TypeAndVersionInterface { | |
) revert InvalidCapabilityConfigurationContractInterface(capability.configurationContract); | ||
} | ||
|
||
s_capabilityIds.add(capabilityId); | ||
s_capabilities[capabilityId] = capability; | ||
s_hashedCapabilityIds.add(hashedId); | ||
s_capabilities[hashedId] = capability; | ||
|
||
emit CapabilityAdded(capabilityId); | ||
emit CapabilityAdded(hashedId); | ||
} | ||
|
||
/// @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); | ||
/// @param hashedCapabilityId The ID of the capability to deprecate | ||
function deprecateCapability(bytes32 hashedCapabilityId) external onlyOwner { | ||
if (!s_hashedCapabilityIds.contains(hashedCapabilityId)) revert CapabilityDoesNotExist(hashedCapabilityId); | ||
if (s_deprecatedHashedCapabilityIds.contains(hashedCapabilityId)) | ||
revert CapabilityAlreadyDeprecated(hashedCapabilityId); | ||
|
||
s_deprecatedHashedCapabilityIds.add(hashedCapabilityId); | ||
emit CapabilityDeprecated(hashedCapabilityId); | ||
} | ||
|
||
function getCapability(bytes32 capabilityID) public view returns (Capability memory) { | ||
return s_capabilities[capabilityID]; | ||
/// @notice This function returns a Capability by its hashed ID. Use `getHashedCapabilityId` to get the hashed ID. | ||
function getCapability(bytes32 hashedId) public view returns (Capability memory) { | ||
return s_capabilities[hashedId]; | ||
} | ||
|
||
/// @notice Returns all capabilities. This operation will copy capabilities | ||
/// to memory, which can be quite expensive. This is designed to mostly be | ||
/// used by view accessors that are queried without any gas fees. | ||
/// @return Capability[] An array of capabilities | ||
function getCapabilities() external view returns (Capability[] memory) { | ||
bytes32[] memory capabilityIds = s_capabilityIds.values(); | ||
bytes32[] memory hashedCapabilityIds = s_hashedCapabilityIds.values(); | ||
|
||
// 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()); | ||
Capability[] memory capabilities = new Capability[]( | ||
hashedCapabilityIds.length - s_deprecatedHashedCapabilityIds.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]; | ||
for (uint256 i; i < hashedCapabilityIds.length; ++i) { | ||
bytes32 hashedCapabilityId = hashedCapabilityIds[i]; | ||
|
||
if (!s_deprecatedCapabilityIds.contains(capabilityId)) { | ||
capabilities[newIndex] = getCapability(capabilityId); | ||
if (!s_deprecatedHashedCapabilityIds.contains(hashedCapabilityId)) { | ||
capabilities[newIndex] = getCapability(hashedCapabilityId); | ||
newIndex++; | ||
} | ||
} | ||
|
||
return capabilities; | ||
} | ||
|
||
/// @notice This functions returns a Capability ID packed into a bytes32 for cheaper access | ||
/// @notice This functions returns a capability id that has been hashed to fit into a bytes32 for cheaper access | ||
/// @return bytes32 A unique identifier for the capability | ||
function getCapabilityID(bytes32 capabilityType, bytes32 version) public pure returns (bytes32) { | ||
return keccak256(abi.encodePacked(capabilityType, version)); | ||
function getHashedCapabilityId(bytes32 labelledName, bytes32 version) public pure returns (bytes32) { | ||
return keccak256(abi.encodePacked(labelledName, version)); | ||
} | ||
|
||
/// @notice Returns whether a capability is deprecated | ||
/// @param capabilityId The ID of the capability to check | ||
/// @param hashedCapabilityId The hashed 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); | ||
function isCapabilityDeprecated(bytes32 hashedCapabilityId) external view returns (bool) { | ||
return s_deprecatedHashedCapabilityIds.contains(hashedCapabilityId); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.