Skip to content

Commit

Permalink
Add functions to add a node operator (#12906)
Browse files Browse the repository at this point in the history
* add functions to add node operator

* update gethwrapper
  • Loading branch information
cds95 authored Apr 25, 2024
1 parent 274a988 commit 365c38b
Show file tree
Hide file tree
Showing 10 changed files with 361 additions and 47 deletions.
5 changes: 5 additions & 0 deletions .changeset/two-countries-lay.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"chainlink": patch
---

update keystone gethwrapper #internal
5 changes: 5 additions & 0 deletions contracts/.changeset/tender-jokes-do.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@chainlink/contracts": patch
---

Implement function to add node operators to the capability registry'
72 changes: 60 additions & 12 deletions contracts/src/v0.8/keystone/CapabilityRegistry.sol
Original file line number Diff line number Diff line change
Expand Up @@ -4,25 +4,73 @@ pragma solidity ^0.8.0;
import {TypeAndVersionInterface} from "../interfaces/TypeAndVersionInterface.sol";
import {OwnerIsCreator} from "../shared/access/OwnerIsCreator.sol";

struct Capability {
// Capability type, e.g. "data-streams-reports"
// bytes32(string); validation regex: ^[a-z0-9_\-:]{1,32}$
// Not "type" because that's a reserved keyword in Solidity.
bytes32 capabilityType;
// Semver, e.g., "1.2.3"
// bytes32(string); must be valid Semver + max 32 characters.
bytes32 version;
}

contract CapabilityRegistry is OwnerIsCreator, TypeAndVersionInterface {
mapping(bytes32 => Capability) private s_capabilities;
struct NodeOperator {
/// @notice The address of the admin that can manage a node
/// operator
address admin;
/// @notice Human readable name of a Node Operator managing the node
string name;
}

struct Capability {
// Capability type, e.g. "data-streams-reports"
// bytes32(string); validation regex: ^[a-z0-9_\-:]{1,32}$
// Not "type" because that's a reserved keyword in Solidity.
bytes32 capabilityType;
// Semver, e.g., "1.2.3"
// bytes32(string); must be valid Semver + max 32 characters.
bytes32 version;
}

/// @notice This error is thrown when trying to set a node operator's
/// admin address to the zero address
error InvalidNodeOperatorAdmin();

/// @notice This event is emitted when a new node operator is added
/// @param nodeOperatorId The ID of the newly added node operator
/// @param admin The address of the admin that can manage the node
/// operator
/// @param name The human readable name of the node operator
event NodeOperatorAdded(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);

mapping(bytes32 => Capability) private s_capabilities;

/// @notice Mapping of node operators
mapping(uint256 nodeOperatorId => NodeOperator) private s_nodeOperators;

/// @notice The latest node operator ID
/// @dev No getter for this as this is an implementation detail
uint256 private s_nodeOperatorId;

function typeAndVersion() external pure override returns (string memory) {
return "CapabilityRegistry 1.0.0";
}

/// @notice Adds a list of node operators
/// @param nodeOperators List of node operators to add
function addNodeOperators(NodeOperator[] calldata nodeOperators) external onlyOwner {
for (uint256 i; i < nodeOperators.length; ++i) {
NodeOperator memory nodeOperator = nodeOperators[i];
if (nodeOperator.admin == address(0)) revert InvalidNodeOperatorAdmin();
uint256 nodeOperatorId = s_nodeOperatorId;
s_nodeOperators[nodeOperatorId] = NodeOperator({admin: nodeOperator.admin, name: nodeOperator.name});
++s_nodeOperatorId;
emit NodeOperatorAdded(nodeOperatorId, nodeOperator.admin, nodeOperator.name);
}
}

/// @notice Gets a node operator's data
/// @param nodeOperatorId The ID of the node operator to query for
/// @return NodeOperator The node operator data
function getNodeOperator(uint256 nodeOperatorId) external view returns (NodeOperator memory) {
return s_nodeOperators[nodeOperatorId];
}

function addCapability(Capability calldata capability) external onlyOwner {
bytes32 capabilityId = getCapabilityID(capability.capabilityType, capability.version);
s_capabilities[capabilityId] = capability;
Expand All @@ -34,7 +82,7 @@ contract CapabilityRegistry is OwnerIsCreator, TypeAndVersionInterface {
}

/// @notice This functions returns a Capability ID packed into a bytes32 for cheaper access
/// @return A unique identifier for the capability
/// @return bytes32 A unique identifier for the capability
function getCapabilityID(bytes32 capabilityType, bytes32 version) public pure returns (bytes32) {
return keccak256(abi.encodePacked(capabilityType, version));
}
Expand Down
22 changes: 22 additions & 0 deletions contracts/src/v0.8/keystone/test/BaseTest.t.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.19;

import {Test} from "forge-std/Test.sol";
import {Constants} from "./Constants.t.sol";
import {CapabilityRegistry} from "../CapabilityRegistry.sol";

contract BaseTest is Test, Constants {
CapabilityRegistry internal s_capabilityRegistry;

function setUp() public virtual {
vm.startPrank(ADMIN);
s_capabilityRegistry = new CapabilityRegistry();
}

function _getNodeOperators() internal view returns (CapabilityRegistry.NodeOperator[] memory) {
CapabilityRegistry.NodeOperator[] memory nodeOperators = new CapabilityRegistry.NodeOperator[](2);
nodeOperators[0] = CapabilityRegistry.NodeOperator({admin: NODE_OPERATOR_ONE_ADMIN, name: NODE_OPERATOR_ONE_NAME});
nodeOperators[1] = CapabilityRegistry.NodeOperator({admin: NODE_OPERATOR_TWO_ADMIN, name: NODE_OPERATOR_TWO_NAME});
return nodeOperators;
}
}
21 changes: 0 additions & 21 deletions contracts/src/v0.8/keystone/test/CapabilityRegistry.t.sol

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
// 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 {
function test_AddCapability() public {
s_capabilityRegistry.addCapability(CapabilityRegistry.Capability("data-streams-reports", "1.0.0"));

bytes32 capabilityId = s_capabilityRegistry.getCapabilityID(bytes32("data-streams-reports"), bytes32("1.0.0"));
CapabilityRegistry.Capability memory capability = s_capabilityRegistry.getCapability(capabilityId);

assertEq(capability.capabilityType, "data-streams-reports");
assertEq(capability.version, "1.0.0");
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.19;

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

contract CapabilityRegistry_AddNodeOperatorsTest is BaseTest {
event NodeOperatorAdded(uint256 nodeOperatorId, address indexed admin, string name);

function test_RevertWhen_CalledByNonAdmin() public {
changePrank(STRANGER);
vm.expectRevert("Only callable by owner");
s_capabilityRegistry.addNodeOperators(_getNodeOperators());
}

function test_RevertWhen_NodeOperatorAdminAddressZero() public {
changePrank(ADMIN);
CapabilityRegistry.NodeOperator[] memory nodeOperators = _getNodeOperators();
nodeOperators[0].admin = address(0);
vm.expectRevert(CapabilityRegistry.InvalidNodeOperatorAdmin.selector);
s_capabilityRegistry.addNodeOperators(nodeOperators);
}

function test_AddNodeOperators() public {
changePrank(ADMIN);

vm.expectEmit(true, true, true, true, address(s_capabilityRegistry));
emit NodeOperatorAdded(0, NODE_OPERATOR_ONE_ADMIN, NODE_OPERATOR_ONE_NAME);
vm.expectEmit(true, true, true, true, address(s_capabilityRegistry));
emit NodeOperatorAdded(1, NODE_OPERATOR_TWO_ADMIN, NODE_OPERATOR_TWO_NAME);
s_capabilityRegistry.addNodeOperators(_getNodeOperators());

CapabilityRegistry.NodeOperator memory nodeOperatorOne = s_capabilityRegistry.getNodeOperator(0);
assertEq(nodeOperatorOne.admin, NODE_OPERATOR_ONE_ADMIN);
assertEq(nodeOperatorOne.name, NODE_OPERATOR_ONE_NAME);

CapabilityRegistry.NodeOperator memory nodeOperatorTwo = s_capabilityRegistry.getNodeOperator(1);
assertEq(nodeOperatorTwo.admin, NODE_OPERATOR_TWO_ADMIN);
assertEq(nodeOperatorTwo.name, NODE_OPERATOR_TWO_NAME);
}
}
13 changes: 13 additions & 0 deletions contracts/src/v0.8/keystone/test/Constants.t.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.19;

import {Test} from "forge-std/Test.sol";

contract Constants {
address internal ADMIN = address(1);
address internal STRANGER = address(2);
address internal NODE_OPERATOR_ONE_ADMIN = address(3);
string internal NODE_OPERATOR_ONE_NAME = "node-operator-one";
address internal NODE_OPERATOR_TWO_ADMIN = address(4);
string internal NODE_OPERATOR_TWO_NAME = "node-operator-two";
}

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 fbaf8eceb929494bdfe0028921a0742da525cb4ec1b6d57a1382eda46fa32c64
keystone_capability_registry: ../../../contracts/solc/v0.8.19/CapabilityRegistry/CapabilityRegistry.abi ../../../contracts/solc/v0.8.19/CapabilityRegistry/CapabilityRegistry.bin 579334dfcb59da4221823735dc815116745b488ddde327199367e04457aac808
ocr3_capability: ../../../contracts/solc/v0.8.19/OCR3Capability/OCR3Capability.abi ../../../contracts/solc/v0.8.19/OCR3Capability/OCR3Capability.bin 9dcbdf55bd5729ba266148da3f17733eb592c871c2108ccca546618628fd9ad2

0 comments on commit 365c38b

Please sign in to comment.