Skip to content

Commit 34d6a5f

Browse files
committed
set operators whitelists
1 parent 9a348fc commit 34d6a5f

File tree

9 files changed

+250
-18
lines changed

9 files changed

+250
-18
lines changed

contracts/SSVNetwork.sol

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -129,6 +129,27 @@ contract SSVNetwork is
129129
_delegate(SSVStorage.load().ssvContracts[SSVModules.SSV_OPERATORS]);
130130
}
131131

132+
function setOperatorMultipleWhitelists(
133+
uint64[] calldata operatorIds,
134+
address[] calldata whitelistAddresses
135+
) external override {
136+
_delegate(SSVStorage.load().ssvContracts[SSVModules.SSV_OPERATORS]);
137+
}
138+
139+
function setOperatorsWhitelistingContract(
140+
uint64[] calldata operatorIds,
141+
address whitelistingContract
142+
) external override {
143+
_delegate(SSVStorage.load().ssvContracts[SSVModules.SSV_OPERATORS]);
144+
}
145+
146+
function removeOperatorMultipleWhitelists(
147+
uint64[] calldata operatorIds,
148+
address[] calldata whitelistAddresses
149+
) external override {
150+
_delegate(SSVStorage.load().ssvContracts[SSVModules.SSV_OPERATORS]);
151+
}
152+
132153
function declareOperatorFee(uint64 operatorId, uint256 fee) external override {
133154
_delegate(SSVStorage.load().ssvContracts[SSVModules.SSV_OPERATORS]);
134155
}

contracts/interfaces/ISSVNetworkCore.sol

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,9 @@ interface ISSVNetworkCore {
9090
error IncorrectValidatorStateWithData(bytes publicKey); // 0x89307938
9191
error ValidatorAlreadyExistsWithData(bytes publicKey); // 0x388e7999
9292
error EmptyPublicKeysList(); // df83e679
93+
error InvalidContractAddress(); // 0xa710429d
94+
error AddressIsContract(); // 0x9166b12d
95+
error InvalidWhitelistAddressesLength(); // 0xcbb362dc
9396

9497
// legacy errors
9598
error ValidatorAlreadyExists(); // 0x8d09a73e

contracts/interfaces/ISSVOperators.sol

Lines changed: 31 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,29 @@ interface ISSVOperators is ISSVNetworkCore {
1515

1616
/// @notice Sets the whitelist for an operator
1717
/// @param operatorId The ID of the operator
18-
/// @param whitelisted The address to be whitelisted
19-
function setOperatorWhitelist(uint64 operatorId, address whitelisted) external;
18+
/// @param whitelistAddress The address to be whitelisted
19+
function setOperatorWhitelist(uint64 operatorId, address whitelistAddress) external;
20+
21+
/// @notice Sets a list of whitelisted addresses (EOAs or generic contracts) for a list of operators
22+
/// @param operatorIds The operator IDs to set the whitelists for
23+
/// @param whitelistAddresses The list of addresses to be whitelisted
24+
function setOperatorMultipleWhitelists(
25+
uint64[] calldata operatorIds,
26+
address[] calldata whitelistAddresses
27+
) external;
28+
29+
/// @notice Removes a list of whitelisted addresses (EOAs or generic contracts) for a list of operators
30+
/// @param operatorIds Operator IDs for which whitelists are removed
31+
/// @param whitelistAddresses List of addresses to be removed from the whitelist
32+
function removeOperatorMultipleWhitelists(
33+
uint64[] calldata operatorIds,
34+
address[] calldata whitelistAddresses
35+
) external;
36+
37+
/// @notice Sets a whitelisting contract for a list of operators
38+
/// @param operatorIds The operator IDs to set the whitelisting contract for
39+
/// @param whitelistingContract The address of a whitelisting contract
40+
function setOperatorsWhitelistingContract(uint64[] calldata operatorIds, address whitelistingContract) external;
2041

2142
/// @notice Declares the operator's fee
2243
/// @param operatorId The ID of the operator
@@ -66,6 +87,14 @@ interface ISSVOperators is ISSVNetworkCore {
6687
* @param whitelisted operator's new whitelisted address.
6788
*/
6889
event OperatorWhitelistUpdated(uint64 indexed operatorId, address whitelisted);
90+
91+
/**
92+
* @dev Emitted when the whitelisting contract of an operator is updated.
93+
* @param operatorIds operators' IDs.
94+
* @param whitelistingContract operators' new whitelisting contract address.
95+
*/
96+
event OperatorWhitelistingContractUpdated(uint64[] indexed operatorIds, address whitelistingContract);
97+
6998
event OperatorFeeDeclared(address indexed owner, uint64 indexed operatorId, uint256 blockNumber, uint256 fee);
7099

71100
event OperatorFeeDeclarationCancelled(address indexed owner, uint64 indexed operatorId);

contracts/libraries/OperatorLib.sol

Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,17 @@
22
pragma solidity 0.8.18;
33

44
import "../interfaces/ISSVNetworkCore.sol";
5+
import "./CoreLib.sol";
56
import "./SSVStorage.sol";
67
import "./SSVStorageProtocol.sol";
78
import "./Types.sol";
89

10+
import "@openzeppelin/contracts/utils/introspection/ERC165Checker.sol";
11+
912
library OperatorLib {
13+
// isWhitelisted(address,uint64)
14+
bytes4 private constant IS_WHITELISTED_INTERFACE = 0xbe7a1ee7;
15+
1016
using Types64 for uint64;
1117

1218
function updateSnapshot(ISSVNetworkCore.Operator memory operator) internal view {
@@ -93,4 +99,95 @@ library OperatorLib {
9399
}
94100
}
95101
}
102+
103+
function getBitmapIndexes(uint64 operatorId) internal pure returns (uint256 blockIndex, uint256 bitPosition) {
104+
blockIndex = operatorId >> 8; // Equivalent to operatorId / 256
105+
bitPosition = operatorId & 0xFF; // Equivalent to operatorId % 256
106+
}
107+
108+
function updateMultipleWhitelists(
109+
address[] calldata whitelistAddresses,
110+
uint64[] calldata operatorIds,
111+
bool addAddresses,
112+
StorageData storage s
113+
) internal {
114+
uint256 addressesLength = whitelistAddresses.length;
115+
uint256 operatorsLength = operatorIds.length;
116+
117+
if (addressesLength == 0) revert ISSVNetworkCore.InvalidWhitelistAddressesLength();
118+
if (operatorsLength == 0) revert ISSVNetworkCore.InvalidOperatorIdsLength();
119+
120+
uint64 currentOperatorId;
121+
uint64 nextOperatorId;
122+
uint256 blockIndex;
123+
uint256 bitPosition;
124+
125+
// create the max number of masks that will be updated
126+
uint256[] memory masks = new uint256[]((operatorIds[operatorsLength - 1] >> 8) + 1);
127+
128+
for (uint256 i = 0; i < operatorsLength - 1; ++i) {
129+
currentOperatorId = operatorIds[i];
130+
131+
checkOwner(s.operators[currentOperatorId]);
132+
133+
if (i + 1 < operatorsLength) {
134+
nextOperatorId = operatorIds[i + 1];
135+
if (currentOperatorId >= nextOperatorId) {
136+
if (currentOperatorId == nextOperatorId) {
137+
revert ISSVNetworkCore.OperatorsListNotUnique();
138+
}
139+
revert ISSVNetworkCore.UnsortedOperatorsList();
140+
}
141+
}
142+
(blockIndex, bitPosition) = getBitmapIndexes(currentOperatorId);
143+
144+
masks[blockIndex] |= (1 << bitPosition);
145+
146+
if (!s.operators[currentOperatorId].whitelisted) s.operators[currentOperatorId].whitelisted = true;
147+
}
148+
149+
for (uint256 i = 0; i < addressesLength; ++i) {
150+
address addr = whitelistAddresses[i];
151+
152+
if (isWhitelistingContract(addr)) revert ISSVNetworkCore.AddressIsContract();
153+
154+
for (blockIndex = 0; blockIndex < masks.length; ++blockIndex) {
155+
// only update storage for updated masks
156+
if (masks[blockIndex] != 0) {
157+
if (addAddresses) {
158+
s.addressWhitelistedForOperators[addr][blockIndex] |= masks[blockIndex];
159+
} else {
160+
s.addressWhitelistedForOperators[addr][blockIndex] &= ~masks[blockIndex];
161+
}
162+
}
163+
}
164+
}
165+
}
166+
167+
function updateWhitelistingContract(
168+
uint64 operatorId,
169+
address whitelistingContract,
170+
StorageData storage s
171+
) internal {
172+
checkOwner(s.operators[operatorId]);
173+
174+
if (!isWhitelistingContract(whitelistingContract)) revert ISSVNetworkCore.InvalidContractAddress();
175+
176+
address currentWhitelisted = s.operatorsWhitelist[operatorId];
177+
178+
// operator already whitelisted? EOA or generic contract
179+
if (currentWhitelisted != address(0)) {
180+
(uint256 blockIndex, uint256 bitPosition) = OperatorLib.getBitmapIndexes(operatorId);
181+
delete s.operatorsWhitelist[operatorId];
182+
s.addressWhitelistedForOperators[currentWhitelisted][blockIndex] |= (1 << bitPosition);
183+
}
184+
185+
s.operatorsWhitelist[operatorId] = whitelistingContract;
186+
if (!s.operators[operatorId].whitelisted) s.operators[operatorId].whitelisted = true;
187+
}
188+
189+
function isWhitelistingContract(address whitelistingContract) internal view returns (bool) {
190+
// TODO create type for whitelisting contracts
191+
return ERC165Checker.supportsInterface(whitelistingContract, IS_WHITELISTED_INTERFACE);
192+
}
96193
}

contracts/libraries/SSVStorage.sol

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ struct StorageData {
2323
mapping(bytes32 => uint64) operatorsPKs;
2424
/// @notice Maps each SSVModules' module to its corresponding contract address
2525
mapping(SSVModules => address) ssvContracts;
26-
/// @notice Operators' whitelist: Maps each operator's ID to its corresponding whitelisted Ethereum address
26+
/// @notice Operators' whitelist: Maps each operator's ID to a whitelisting contract
2727
mapping(uint64 => address) operatorsWhitelist;
2828
/// @notice Maps each operator's ID to its corresponding operator fee change request data
2929
mapping(uint64 => ISSVNetworkCore.OperatorFeeChangeRequest) operatorFeeChangeRequests;
@@ -33,14 +33,14 @@ struct StorageData {
3333
IERC20 token;
3434
/// @notice Counter keeping track of the last Operator ID issued
3535
Counters.Counter lastOperatorId;
36-
/// @notice Operators' whitelist: Maps each whitelisted address to a list of operators
36+
/// @notice Operators' whitelist: Maps each whitelisted address to a list of operators
3737
/// @notice that are whitelisted for that address using bitmaps
3838
/// @dev The nested mapping's key represents a uint256 slot to handle more than 256 operators per address
3939
mapping(address => mapping(uint256 => uint256)) addressWhitelistedForOperators;
4040
}
4141

4242
library SSVStorage {
43-
uint256 constant private SSV_STORAGE_POSITION = uint256(keccak256("ssv.network.storage.main")) - 1;
43+
uint256 private constant SSV_STORAGE_POSITION = uint256(keccak256("ssv.network.storage.main")) - 1;
4444

4545
function load() internal pure returns (StorageData storage sd) {
4646
uint256 position = SSV_STORAGE_POSITION;

contracts/modules/SSVOperators.sol

Lines changed: 36 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -73,18 +73,45 @@ contract SSVOperators is ISSVOperators {
7373
emit OperatorRemoved(operatorId);
7474
}
7575

76-
function setOperatorWhitelist(uint64 operatorId, address whitelisted) external {
76+
function setOperatorWhitelist(uint64 operatorId, address whitelistAddress) external override {
7777
StorageData storage s = SSVStorage.load();
7878
s.operators[operatorId].checkOwner();
7979

80-
if (whitelisted == address(0)) {
81-
s.operators[operatorId].whitelisted = false;
82-
} else {
83-
s.operators[operatorId].whitelisted = true;
84-
}
80+
if (OperatorLib.isWhitelistingContract(whitelistAddress)) revert AddressIsContract();
81+
82+
// Set the bit at bitPosition for the operatorId in the corresponding uint256 blockIndex
83+
(uint256 blockIndex, uint256 bitPosition) = OperatorLib.getBitmapIndexes(operatorId);
84+
85+
s.addressWhitelistedForOperators[whitelistAddress][blockIndex] |= (1 << bitPosition);
86+
if (!s.operators[operatorId].whitelisted) s.operators[operatorId].whitelisted = true;
87+
88+
emit OperatorWhitelistUpdated(operatorId, whitelistAddress);
89+
}
90+
91+
function setOperatorMultipleWhitelists(
92+
uint64[] calldata operatorIds,
93+
address[] calldata whitelistAddresses
94+
) external override {
95+
OperatorLib.updateMultipleWhitelists(whitelistAddresses, operatorIds, true, SSVStorage.load());
96+
}
8597

86-
s.operatorsWhitelist[operatorId] = whitelisted;
87-
emit OperatorWhitelistUpdated(operatorId, whitelisted);
98+
function removeOperatorMultipleWhitelists(
99+
uint64[] calldata operatorIds,
100+
address[] calldata whitelistAddresses
101+
) external override {
102+
OperatorLib.updateMultipleWhitelists(whitelistAddresses, operatorIds, false, SSVStorage.load());
103+
}
104+
105+
function setOperatorsWhitelistingContract(uint64[] calldata operatorIds, address whitelistingContract) external {
106+
uint256 operatorsLength = operatorIds.length;
107+
if (operatorsLength == 0) revert InvalidOperatorIdsLength();
108+
109+
StorageData storage s = SSVStorage.load();
110+
111+
for (uint256 i = 0; i < operatorsLength; ++i) {
112+
OperatorLib.updateWhitelistingContract(operatorIds[i], whitelistingContract, s);
113+
}
114+
emit OperatorWhitelistingContractUpdated(operatorIds, whitelistingContract);
88115
}
89116

90117
function declareOperatorFee(uint64 operatorId, uint256 fee) external override {
@@ -168,7 +195,7 @@ contract SSVOperators is ISSVOperators {
168195
s.operators[operatorId] = operator;
169196

170197
delete s.operatorFeeChangeRequests[operatorId];
171-
198+
172199
emit OperatorFeeExecuted(msg.sender, operatorId, block.number, fee);
173200
}
174201

contracts/test/SSVNetworkUpgrade.sol

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -139,6 +139,48 @@ contract SSVNetworkUpgrade is
139139
);
140140
}
141141

142+
function setOperatorMultipleWhitelists(
143+
uint64[] calldata operatorIds,
144+
address[] calldata whitelistAddresses
145+
) external override {
146+
_delegateCall(
147+
SSVStorage.load().ssvContracts[SSVModules.SSV_OPERATORS],
148+
abi.encodeWithSignature(
149+
"setOperatorMultipleWhitelists(address[],uint64[])",
150+
whitelistAddresses,
151+
operatorIds
152+
)
153+
);
154+
}
155+
156+
function removeOperatorMultipleWhitelists(
157+
uint64[] calldata operatorIds,
158+
address[] calldata whitelistAddresses
159+
) external override {
160+
_delegateCall(
161+
SSVStorage.load().ssvContracts[SSVModules.SSV_OPERATORS],
162+
abi.encodeWithSignature(
163+
"removeOperatorMultipleWhitelists(address[],uint64[])",
164+
whitelistAddresses,
165+
operatorIds
166+
)
167+
);
168+
}
169+
170+
function setOperatorsWhitelistingContract(
171+
uint64[] calldata operatorIds,
172+
address whitelistingContract
173+
) external override {
174+
_delegateCall(
175+
SSVStorage.load().ssvContracts[SSVModules.SSV_OPERATORS],
176+
abi.encodeWithSignature(
177+
"setOperatorsWhitelistingContract(uint64[],address)",
178+
operatorIds,
179+
whitelistingContract
180+
)
181+
);
182+
}
183+
142184
function declareOperatorFee(uint64 operatorId, uint256 fee) external override {
143185
_delegateCall(
144186
SSVStorage.load().ssvContracts[SSVModules.SSV_OPERATORS],

contracts/test/modules/SSVOperatorsUpdate.sol

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -86,9 +86,22 @@ contract SSVOperatorsUpdate is ISSVOperators {
8686
emit OperatorWhitelistUpdated(operatorId, whitelisted);
8787
}
8888

89-
function declareOperatorFee(uint64 operatorId, uint256 fee) external override {
90-
if (operatorId == 0 && fee == 0) revert NoFeeDeclared();
91-
}
89+
function setOperatorMultipleWhitelists(
90+
uint64[] calldata operatorIds,
91+
address[] calldata whitelistAddresses
92+
) external override {}
93+
94+
function removeOperatorMultipleWhitelists(
95+
uint64[] calldata operatorIds,
96+
address[] calldata whitelistAddresses
97+
) external override {}
98+
99+
function setOperatorsWhitelistingContract(
100+
uint64[] calldata operatorIds,
101+
address whitelistingContract
102+
) external override {}
103+
104+
function declareOperatorFee(uint64 operatorId, uint256 fee) external override {}
92105

93106
function executeOperatorFee(uint64 operatorId) external override {
94107
StorageData storage s = SSVStorage.load();

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "ssv-network",
3-
"version": "1.1.1",
3+
"version": "1.2.0",
44
"description": "Solidity smart contracts for the SSV Network",
55
"author": "SSV.Network",
66
"repository": {

0 commit comments

Comments
 (0)