Skip to content

Commit

Permalink
add SSVViews.isAddressWhitelistedInWhitelistingContract
Browse files Browse the repository at this point in the history
  • Loading branch information
mtabasco committed May 14, 2024
1 parent 736ca09 commit fb0d810
Show file tree
Hide file tree
Showing 6 changed files with 106 additions and 23 deletions.
11 changes: 10 additions & 1 deletion contracts/SSVNetworkViews.sol
Original file line number Diff line number Diff line change
Expand Up @@ -67,10 +67,19 @@ contract SSVNetworkViews is UUPSUpgradeable, Ownable2StepUpgradeable, ISSVViews
) external view override returns (uint64[] memory whitelistedOperatorIds) {
return ssvNetwork.getWhitelistedOperators(operatorIds, whitelistedAddress);
}
function isWhitelistingContract(address contractAddress) external view returns (bool) {

function isWhitelistingContract(address contractAddress) external view override returns (bool) {
return ssvNetwork.isWhitelistingContract(contractAddress);
}

function isAddressWhitelistedInWhitelistingContract(
address addressToCheck,
uint256 operatorId,
address whitelistingContract
) external view override returns (bool isWhitelisted) {
return ssvNetwork.isAddressWhitelistedInWhitelistingContract(addressToCheck, operatorId, whitelistingContract);
}

/***********************************/
/* Cluster External View Functions */
/***********************************/
Expand Down
12 changes: 12 additions & 0 deletions contracts/interfaces/ISSVViews.sol
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,18 @@ interface ISSVViews is ISSVNetworkCore {
/// @return isWhitelistingContract A boolean indicating if the address is a whitelisting contract
function isWhitelistingContract(address contractAddress) external view returns (bool isWhitelistingContract);

/// @notice Checks if the given address is whitelisted in a specific whitelisting contract.
/// @notice It's up to the whitelisting contract implementation to use the operatorId parameter or not.
/// @param addressToCheck The address to check
/// @param operatorId The operator ID to check in combination with addressToCheck
/// @param whitelistingContract The whitelisting contract address
/// @return isWhitelisted A boolean indicating if the address is whitelisted in the given whitelisting contract for the given operator
function isAddressWhitelistedInWhitelistingContract(
address addressToCheck,
uint256 operatorId,
address whitelistingContract
) external view returns (bool isWhitelisted);

/// @notice Checks if the cluster can be liquidated
/// @param owner The owner address of the cluster
/// @param operatorIds The IDs of the operators in the cluster
Expand Down
10 changes: 10 additions & 0 deletions contracts/modules/SSVViews.sol
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
pragma solidity 0.8.24;

import {ISSVViews} from "../interfaces/ISSVViews.sol";
import {ISSVWhitelistingContract} from "../interfaces/external/ISSVWhitelistingContract.sol";
import {Types64} from "../libraries/Types.sol";
import "../libraries/ClusterLib.sol";
import "../libraries/OperatorLib.sol";
Expand Down Expand Up @@ -124,6 +125,15 @@ contract SSVViews is ISSVViews {
return OperatorLib.isWhitelistingContract(contractAddress);
}

function isAddressWhitelistedInWhitelistingContract(
address addressToCheck,
uint256 operatorId,
address whitelistingContract
) external view override returns (bool) {
if (!OperatorLib.isWhitelistingContract(whitelistingContract) || addressToCheck == address(0)) return false;
return ISSVWhitelistingContract(whitelistingContract).isWhitelisted(addressToCheck, operatorId);
}

/***********************************/
/* Cluster External View Functions */
/***********************************/
Expand Down
4 changes: 4 additions & 0 deletions contracts/test/mocks/MockWhitelistingContract.sol
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,10 @@ contract MockWhitelistingContract is ISSVWhitelistingContract, ERC165 {
}
}

function setWhitelistedAddress(address account) external {
whitelisted[account] = true;
}

function isWhitelisted(address account, uint256) external view override returns (bool) {
return whitelisted[account];
}
Expand Down
42 changes: 22 additions & 20 deletions docs/operators.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,11 @@ After the operator is registered, the caller becomes the `owner`, the `fee` is s
The `whitelisted` flag of the operator indicates if the operator is private (when set to `true`) or public (`false`),

## Whitelisted operators
An operator owner can restrict the usage of it to specific EOAs, generic contracts and whitelisting contracts. After doing it, the operator becomes private.
An operator owner can restrict the usage of it to specific EOAs, generic contracts and whitelisting contracts.
A whitelisting contract is the one that implements the [ISSVWhitelistingContract](../contracts/interfaces/external/ISSVWhitelistingContract.sol) interface.

The restriction is only effective when the operator owner sets the privacy status of the operator to *private*.

To manage the whitelisted addresses, these 2 data structures are used:

`mapping(uint64 => address) operatorsWhitelist`: Keeps the relation between an operator and a whitelisting contract.
Expand All @@ -23,30 +25,30 @@ To manage the whitelisted addresses, these 2 data structures are used:
### What is a Whitelisting Contract?
The operators can choose to whitelist an external contract with custom logic to manage authorized addresses externally. To be used in SSV contracts, it needs to implement the [ISSVWhitelistingContract](../contracts/interfaces/external/ISSVWhitelistingContract.sol) interface, that requires to implement the `isWhitelisted(address account, uint256 operatorId)` function. This function is called in the register validator process, that must return `true/false` to indicate if the caller (`msg.sender`) is whitelisted for the operator.

It's up to the implementation of the whitelisting contract to use the `operatorId` parameter in the `isWhitelisted` function.

To check if a contact is a valid whitelisting contract, use the function `SSVNetworkViews.isWhitelistingContract(address contractAddress)`.

To check if an account is whitelisted in a whitelisting contract, use the function `SSVNetworkViews.isAddressWhitelistedInWhitelistingContract(address account, uint256 operatorId, address whitelistingContract)`.

### Legacy whitelisted addresses transition process
Up until v1.1.1, operators use the `operatorsWhitelist` mapping to save EOAs and generic contracts. Now in v1.2.0, those type of addresses are stored in `addressWhitelistedForOperators`, leaving `operatorsWhitelist` to save only whitelisting contracts.
When whitelisting a new whitelisting contract, the current address stored in `operatorsWhitelist` will be moved to `addressWhitelistedForOperators`, and the new address stored in `operatorsWhitelist`.
When whitelising a new EOA/generic contract, it will be saved in `addressWhitelistedForOperators`, leaving the previous address in `operatorsWhitelist` intact.

### Operator whitelist states
The following table shows all possible combinations of whitelisted addresses for a given operator.
| | Use legacy EOA/generic contract | Use whitelising contract | Use EOAs/generic contracts | Operator private |
|---|---|---|---|---|
| 1 | Y | | | Y |
| 2 | Y | | Y | Y |
| 3 | | Y | | Y |
| 4 | | | Y | Y |
| 5 | | Y | Y | Y |

The operarator status changes to private (`Operator.whitelisted == true`), so only the whitelisted addresses can use the operator's services when:
- The operator owner sets a whitelisting contract.
- The operator owner whitelist EOAs or generic contracts.
- The operator owner explicitly sets the private status calling `SSVNetwork.setOperatorsPrivateUnchecked()`, no matter if it has whitelisted addresses.

The operarator status changes to public (`Operator.whitelisted == false`), so anyone can use the operator`s services when:
- The operator owner explicitly sets the public status calling `SSVNetwork.setOperatorsPublicUnchecked()`, no matter if it still has whitelisted addresses.
| Use legacy EOA/generic contract | Use whitelising contract | Use EOAs/generic contracts |
|---|---|---|
| Y | | |
| Y | | Y |
| | Y | |
| | | Y |
| | Y | Y |

The operarator status changes to private (`Operator.whitelisted == true`), so only the whitelisted addresses can use the operator's services when the operator owner explicitly sets the *private* status calling `SSVNetwork.setOperatorsPrivateUnchecked()`, no matter if it has whitelisted addresses.

The operarator status changes to public (`Operator.whitelisted == false`), so anyone can use the operator's services when the operator owner explicitly sets the public status calling `SSVNetwork.setOperatorsPublicUnchecked()`, no matter if it still has whitelisted addresses.

### Registering whitelist addresses
Functions related to whitelisting contracts:
Expand All @@ -60,10 +62,10 @@ Functions related to EOAs/generic contracts:

### Registering validators using whitelisted operators
When registering validators using `SSVNetwork.registerValidator` or `SSVNetwork.registerValidator`, the flow to check if the caller is authorized to use a whitelisted operator is the following:
1. Check if the operator has a whitelisted address in `operatorsWhitelist`.
1.1. If the address is a whitelisting contract, call its `isWhitelisted()` function.
1.2. If it's not a whitelisting contract, check if the caller is the whitelisted address. In this step we keep the whitelisting system backward compatible with previous whitelisted EOAs/generic contracts.
2. Check if the operator is whitelisted via the SSV whitelisting module, using `addressWhitelistedForOperators`.
1. Check if the operator is whitelisted via the SSV whitelisting module, using `addressWhitelistedForOperators`.
2. Check if the operator has a whitelisted address in `operatorsWhitelist`.
1. Check if the caller is the whitelisted address. In this step we keep the whitelisting system backward compatible with previous whitelisted EOAs/generic contracts.
2. Check if the address is a whitelisting contract. Then call its `isWhitelisted()` function.

If the caller is not authorized for any of the whitelisted operators, the transaction will revert with the `CallerNotWhitelisted()` error.

Expand Down
50 changes: 48 additions & 2 deletions test/operators/whitelist.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import { ethers } from 'hardhat';
import { expect } from 'chai';

// Declare globals
let ssvNetwork: any, ssvViews: any, mockWhitelistingContractAddress: any;
let ssvNetwork: any, ssvViews: any, mockWhitelistingContract: any, mockWhitelistingContractAddress: any;
const OPERATOR_IDS_10 = Array.from({ length: 10 }, (_, i) => i + 1);

describe('Whitelisting Operator Tests', () => {
Expand All @@ -19,7 +19,7 @@ describe('Whitelisting Operator Tests', () => {
ssvNetwork = metadata.ssvNetwork;
ssvViews = metadata.ssvNetworkViews;

const mockWhitelistingContract = await hre.viem.deployContract('MockWhitelistingContract', [[]], {
mockWhitelistingContract = await hre.viem.deployContract('MockWhitelistingContract', [[]], {
client: owners[0].client,
});
mockWhitelistingContractAddress = await mockWhitelistingContract.address;
Expand Down Expand Up @@ -659,4 +659,50 @@ describe('Whitelisting Operator Tests', () => {
expect(operatorData[4]).to.be.false;
}
});

it('Check account is whitelisted in a whitelisting contract', async () => {
await mockWhitelistingContract.write.setWhitelistedAddress([owners[4].account.address]);

expect(
await ssvViews.read.isAddressWhitelistedInWhitelistingContract([
owners[4].account.address,
0,
mockWhitelistingContractAddress,
]),
).to.be.true;
});

it('Check account is not whitelisted in a whitelisting contract', async () => {
await mockWhitelistingContract.write.setWhitelistedAddress([owners[4].account.address]);

expect(
await ssvViews.read.isAddressWhitelistedInWhitelistingContract([
owners[2].account.address,
0,
mockWhitelistingContractAddress,
]),
).to.be.false;
});

it('Check address(0) account in a whitelisting contract', async () => {
await mockWhitelistingContract.write.setWhitelistedAddress([owners[4].account.address]);

expect(
await ssvViews.read.isAddressWhitelistedInWhitelistingContract([
ethers.ZeroAddress,
0,
mockWhitelistingContractAddress,
]),
).to.be.false;
});

it('Check account in an address(0) contract', async () => {
expect(
await ssvViews.read.isAddressWhitelistedInWhitelistingContract([
owners[2].account.address,
0,
ethers.ZeroAddress,
]),
).to.be.false;
});
});

0 comments on commit fb0d810

Please sign in to comment.