Skip to content

Commit

Permalink
set operators whitelists
Browse files Browse the repository at this point in the history
  • Loading branch information
mtabasco committed Apr 17, 2024
1 parent 9a348fc commit 34d6a5f
Show file tree
Hide file tree
Showing 9 changed files with 250 additions and 18 deletions.
21 changes: 21 additions & 0 deletions contracts/SSVNetwork.sol
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,27 @@ contract SSVNetwork is
_delegate(SSVStorage.load().ssvContracts[SSVModules.SSV_OPERATORS]);
}

function setOperatorMultipleWhitelists(
uint64[] calldata operatorIds,
address[] calldata whitelistAddresses
) external override {
_delegate(SSVStorage.load().ssvContracts[SSVModules.SSV_OPERATORS]);
}

function setOperatorsWhitelistingContract(
uint64[] calldata operatorIds,
address whitelistingContract
) external override {
_delegate(SSVStorage.load().ssvContracts[SSVModules.SSV_OPERATORS]);
}

function removeOperatorMultipleWhitelists(
uint64[] calldata operatorIds,
address[] calldata whitelistAddresses
) external override {
_delegate(SSVStorage.load().ssvContracts[SSVModules.SSV_OPERATORS]);
}

function declareOperatorFee(uint64 operatorId, uint256 fee) external override {
_delegate(SSVStorage.load().ssvContracts[SSVModules.SSV_OPERATORS]);
}
Expand Down
3 changes: 3 additions & 0 deletions contracts/interfaces/ISSVNetworkCore.sol
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,9 @@ interface ISSVNetworkCore {
error IncorrectValidatorStateWithData(bytes publicKey); // 0x89307938
error ValidatorAlreadyExistsWithData(bytes publicKey); // 0x388e7999
error EmptyPublicKeysList(); // df83e679
error InvalidContractAddress(); // 0xa710429d
error AddressIsContract(); // 0x9166b12d
error InvalidWhitelistAddressesLength(); // 0xcbb362dc

// legacy errors
error ValidatorAlreadyExists(); // 0x8d09a73e
Expand Down
33 changes: 31 additions & 2 deletions contracts/interfaces/ISSVOperators.sol
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,29 @@ interface ISSVOperators is ISSVNetworkCore {

/// @notice Sets the whitelist for an operator
/// @param operatorId The ID of the operator
/// @param whitelisted The address to be whitelisted
function setOperatorWhitelist(uint64 operatorId, address whitelisted) external;
/// @param whitelistAddress The address to be whitelisted
function setOperatorWhitelist(uint64 operatorId, address whitelistAddress) external;

/// @notice Sets a list of whitelisted addresses (EOAs or generic contracts) for a list of operators
/// @param operatorIds The operator IDs to set the whitelists for
/// @param whitelistAddresses The list of addresses to be whitelisted
function setOperatorMultipleWhitelists(
uint64[] calldata operatorIds,
address[] calldata whitelistAddresses
) external;

/// @notice Removes a list of whitelisted addresses (EOAs or generic contracts) for a list of operators
/// @param operatorIds Operator IDs for which whitelists are removed
/// @param whitelistAddresses List of addresses to be removed from the whitelist
function removeOperatorMultipleWhitelists(
uint64[] calldata operatorIds,
address[] calldata whitelistAddresses
) external;

/// @notice Sets a whitelisting contract for a list of operators
/// @param operatorIds The operator IDs to set the whitelisting contract for
/// @param whitelistingContract The address of a whitelisting contract
function setOperatorsWhitelistingContract(uint64[] calldata operatorIds, address whitelistingContract) external;

/// @notice Declares the operator's fee
/// @param operatorId The ID of the operator
Expand Down Expand Up @@ -66,6 +87,14 @@ interface ISSVOperators is ISSVNetworkCore {
* @param whitelisted operator's new whitelisted address.
*/
event OperatorWhitelistUpdated(uint64 indexed operatorId, address whitelisted);

/**
* @dev Emitted when the whitelisting contract of an operator is updated.
* @param operatorIds operators' IDs.
* @param whitelistingContract operators' new whitelisting contract address.
*/
event OperatorWhitelistingContractUpdated(uint64[] indexed operatorIds, address whitelistingContract);

event OperatorFeeDeclared(address indexed owner, uint64 indexed operatorId, uint256 blockNumber, uint256 fee);

event OperatorFeeDeclarationCancelled(address indexed owner, uint64 indexed operatorId);
Expand Down
97 changes: 97 additions & 0 deletions contracts/libraries/OperatorLib.sol
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,17 @@
pragma solidity 0.8.18;

import "../interfaces/ISSVNetworkCore.sol";
import "./CoreLib.sol";
import "./SSVStorage.sol";
import "./SSVStorageProtocol.sol";
import "./Types.sol";

import "@openzeppelin/contracts/utils/introspection/ERC165Checker.sol";

library OperatorLib {
// isWhitelisted(address,uint64)
bytes4 private constant IS_WHITELISTED_INTERFACE = 0xbe7a1ee7;

using Types64 for uint64;

function updateSnapshot(ISSVNetworkCore.Operator memory operator) internal view {
Expand Down Expand Up @@ -93,4 +99,95 @@ library OperatorLib {
}
}
}

function getBitmapIndexes(uint64 operatorId) internal pure returns (uint256 blockIndex, uint256 bitPosition) {
blockIndex = operatorId >> 8; // Equivalent to operatorId / 256
bitPosition = operatorId & 0xFF; // Equivalent to operatorId % 256
}

function updateMultipleWhitelists(
address[] calldata whitelistAddresses,
uint64[] calldata operatorIds,
bool addAddresses,
StorageData storage s
) internal {
uint256 addressesLength = whitelistAddresses.length;
uint256 operatorsLength = operatorIds.length;

if (addressesLength == 0) revert ISSVNetworkCore.InvalidWhitelistAddressesLength();
if (operatorsLength == 0) revert ISSVNetworkCore.InvalidOperatorIdsLength();

uint64 currentOperatorId;
uint64 nextOperatorId;
uint256 blockIndex;
uint256 bitPosition;

// create the max number of masks that will be updated
uint256[] memory masks = new uint256[]((operatorIds[operatorsLength - 1] >> 8) + 1);

for (uint256 i = 0; i < operatorsLength - 1; ++i) {
currentOperatorId = operatorIds[i];

checkOwner(s.operators[currentOperatorId]);

if (i + 1 < operatorsLength) {
nextOperatorId = operatorIds[i + 1];
if (currentOperatorId >= nextOperatorId) {
if (currentOperatorId == nextOperatorId) {
revert ISSVNetworkCore.OperatorsListNotUnique();
}
revert ISSVNetworkCore.UnsortedOperatorsList();
}
}
(blockIndex, bitPosition) = getBitmapIndexes(currentOperatorId);

masks[blockIndex] |= (1 << bitPosition);

if (!s.operators[currentOperatorId].whitelisted) s.operators[currentOperatorId].whitelisted = true;
}

for (uint256 i = 0; i < addressesLength; ++i) {
address addr = whitelistAddresses[i];

if (isWhitelistingContract(addr)) revert ISSVNetworkCore.AddressIsContract();

for (blockIndex = 0; blockIndex < masks.length; ++blockIndex) {
// only update storage for updated masks
if (masks[blockIndex] != 0) {
if (addAddresses) {
s.addressWhitelistedForOperators[addr][blockIndex] |= masks[blockIndex];
} else {
s.addressWhitelistedForOperators[addr][blockIndex] &= ~masks[blockIndex];
}
}
}
}
}

function updateWhitelistingContract(
uint64 operatorId,
address whitelistingContract,
StorageData storage s
) internal {
checkOwner(s.operators[operatorId]);

if (!isWhitelistingContract(whitelistingContract)) revert ISSVNetworkCore.InvalidContractAddress();

address currentWhitelisted = s.operatorsWhitelist[operatorId];

// operator already whitelisted? EOA or generic contract
if (currentWhitelisted != address(0)) {
(uint256 blockIndex, uint256 bitPosition) = OperatorLib.getBitmapIndexes(operatorId);
delete s.operatorsWhitelist[operatorId];
s.addressWhitelistedForOperators[currentWhitelisted][blockIndex] |= (1 << bitPosition);
}

s.operatorsWhitelist[operatorId] = whitelistingContract;
if (!s.operators[operatorId].whitelisted) s.operators[operatorId].whitelisted = true;
}

function isWhitelistingContract(address whitelistingContract) internal view returns (bool) {
// TODO create type for whitelisting contracts
return ERC165Checker.supportsInterface(whitelistingContract, IS_WHITELISTED_INTERFACE);
}
}
6 changes: 3 additions & 3 deletions contracts/libraries/SSVStorage.sol
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ struct StorageData {
mapping(bytes32 => uint64) operatorsPKs;
/// @notice Maps each SSVModules' module to its corresponding contract address
mapping(SSVModules => address) ssvContracts;
/// @notice Operators' whitelist: Maps each operator's ID to its corresponding whitelisted Ethereum address
/// @notice Operators' whitelist: Maps each operator's ID to a whitelisting contract
mapping(uint64 => address) operatorsWhitelist;
/// @notice Maps each operator's ID to its corresponding operator fee change request data
mapping(uint64 => ISSVNetworkCore.OperatorFeeChangeRequest) operatorFeeChangeRequests;
Expand All @@ -33,14 +33,14 @@ struct StorageData {
IERC20 token;
/// @notice Counter keeping track of the last Operator ID issued
Counters.Counter lastOperatorId;
/// @notice Operators' whitelist: Maps each whitelisted address to a list of operators
/// @notice Operators' whitelist: Maps each whitelisted address to a list of operators
/// @notice that are whitelisted for that address using bitmaps
/// @dev The nested mapping's key represents a uint256 slot to handle more than 256 operators per address
mapping(address => mapping(uint256 => uint256)) addressWhitelistedForOperators;
}

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

function load() internal pure returns (StorageData storage sd) {
uint256 position = SSV_STORAGE_POSITION;
Expand Down
45 changes: 36 additions & 9 deletions contracts/modules/SSVOperators.sol
Original file line number Diff line number Diff line change
Expand Up @@ -73,18 +73,45 @@ contract SSVOperators is ISSVOperators {
emit OperatorRemoved(operatorId);
}

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

if (whitelisted == address(0)) {
s.operators[operatorId].whitelisted = false;
} else {
s.operators[operatorId].whitelisted = true;
}
if (OperatorLib.isWhitelistingContract(whitelistAddress)) revert AddressIsContract();

// Set the bit at bitPosition for the operatorId in the corresponding uint256 blockIndex
(uint256 blockIndex, uint256 bitPosition) = OperatorLib.getBitmapIndexes(operatorId);

s.addressWhitelistedForOperators[whitelistAddress][blockIndex] |= (1 << bitPosition);
if (!s.operators[operatorId].whitelisted) s.operators[operatorId].whitelisted = true;

emit OperatorWhitelistUpdated(operatorId, whitelistAddress);
}

function setOperatorMultipleWhitelists(
uint64[] calldata operatorIds,
address[] calldata whitelistAddresses
) external override {
OperatorLib.updateMultipleWhitelists(whitelistAddresses, operatorIds, true, SSVStorage.load());
}

s.operatorsWhitelist[operatorId] = whitelisted;
emit OperatorWhitelistUpdated(operatorId, whitelisted);
function removeOperatorMultipleWhitelists(
uint64[] calldata operatorIds,
address[] calldata whitelistAddresses
) external override {
OperatorLib.updateMultipleWhitelists(whitelistAddresses, operatorIds, false, SSVStorage.load());
}

function setOperatorsWhitelistingContract(uint64[] calldata operatorIds, address whitelistingContract) external {
uint256 operatorsLength = operatorIds.length;
if (operatorsLength == 0) revert InvalidOperatorIdsLength();

StorageData storage s = SSVStorage.load();

for (uint256 i = 0; i < operatorsLength; ++i) {
OperatorLib.updateWhitelistingContract(operatorIds[i], whitelistingContract, s);
}
emit OperatorWhitelistingContractUpdated(operatorIds, whitelistingContract);
}

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

delete s.operatorFeeChangeRequests[operatorId];

emit OperatorFeeExecuted(msg.sender, operatorId, block.number, fee);
}

Expand Down
42 changes: 42 additions & 0 deletions contracts/test/SSVNetworkUpgrade.sol
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,48 @@ contract SSVNetworkUpgrade is
);
}

function setOperatorMultipleWhitelists(
uint64[] calldata operatorIds,
address[] calldata whitelistAddresses
) external override {
_delegateCall(
SSVStorage.load().ssvContracts[SSVModules.SSV_OPERATORS],
abi.encodeWithSignature(
"setOperatorMultipleWhitelists(address[],uint64[])",
whitelistAddresses,
operatorIds
)
);
}

function removeOperatorMultipleWhitelists(
uint64[] calldata operatorIds,
address[] calldata whitelistAddresses
) external override {
_delegateCall(
SSVStorage.load().ssvContracts[SSVModules.SSV_OPERATORS],
abi.encodeWithSignature(
"removeOperatorMultipleWhitelists(address[],uint64[])",
whitelistAddresses,
operatorIds
)
);
}

function setOperatorsWhitelistingContract(
uint64[] calldata operatorIds,
address whitelistingContract
) external override {
_delegateCall(
SSVStorage.load().ssvContracts[SSVModules.SSV_OPERATORS],
abi.encodeWithSignature(
"setOperatorsWhitelistingContract(uint64[],address)",
operatorIds,
whitelistingContract
)
);
}

function declareOperatorFee(uint64 operatorId, uint256 fee) external override {
_delegateCall(
SSVStorage.load().ssvContracts[SSVModules.SSV_OPERATORS],
Expand Down
19 changes: 16 additions & 3 deletions contracts/test/modules/SSVOperatorsUpdate.sol
Original file line number Diff line number Diff line change
Expand Up @@ -86,9 +86,22 @@ contract SSVOperatorsUpdate is ISSVOperators {
emit OperatorWhitelistUpdated(operatorId, whitelisted);
}

function declareOperatorFee(uint64 operatorId, uint256 fee) external override {
if (operatorId == 0 && fee == 0) revert NoFeeDeclared();
}
function setOperatorMultipleWhitelists(
uint64[] calldata operatorIds,
address[] calldata whitelistAddresses
) external override {}

function removeOperatorMultipleWhitelists(
uint64[] calldata operatorIds,
address[] calldata whitelistAddresses
) external override {}

function setOperatorsWhitelistingContract(
uint64[] calldata operatorIds,
address whitelistingContract
) external override {}

function declareOperatorFee(uint64 operatorId, uint256 fee) external override {}

function executeOperatorFee(uint64 operatorId) external override {
StorageData storage s = SSVStorage.load();
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "ssv-network",
"version": "1.1.1",
"version": "1.2.0",
"description": "Solidity smart contracts for the SSV Network",
"author": "SSV.Network",
"repository": {
Expand Down

0 comments on commit 34d6a5f

Please sign in to comment.