Skip to content

Commit

Permalink
set operator public/private, add tests
Browse files Browse the repository at this point in the history
  • Loading branch information
mtabasco committed Apr 29, 2024
1 parent 5841234 commit 65347ee
Show file tree
Hide file tree
Showing 14 changed files with 698 additions and 238 deletions.
8 changes: 8 additions & 0 deletions contracts/SSVNetwork.sol
Original file line number Diff line number Diff line change
Expand Up @@ -154,6 +154,14 @@ contract SSVNetwork is
_delegate(SSVStorage.load().ssvContracts[SSVModules.SSV_OPERATORS_WHITELIST]);
}

function setOperatorsPrivateUnchecked(uint64[] calldata operatorIds) external override {
_delegate(SSVStorage.load().ssvContracts[SSVModules.SSV_OPERATORS_WHITELIST]);
}

function setOperatorsPublicUnchecked(uint64[] calldata operatorIds) external {
_delegate(SSVStorage.load().ssvContracts[SSVModules.SSV_OPERATORS_WHITELIST]);
}

function removeOperatorsWhitelistingContract(uint64[] calldata operatorIds) external override {
_delegate(SSVStorage.load().ssvContracts[SSVModules.SSV_OPERATORS_WHITELIST]);
}
Expand Down
18 changes: 18 additions & 0 deletions contracts/interfaces/ISSVOperatorsWhitelist.sol
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,17 @@ interface ISSVOperatorsWhitelist is ISSVNetworkCore {
/// @param operatorIds The operator IDs to remove the whitelisting contract for
function removeOperatorsWhitelistingContract(uint64[] calldata operatorIds) external;

/// @notice Set the list of operators as private without checking for any whitelisting address
/// @notice The operators are considered private when registering validators
/// @param operatorIds The operator IDs to set as private
function setOperatorsPrivateUnchecked(uint64[] calldata operatorIds) external;

/// @notice Set the list of operators as public without removing any whitelisting address
/// @notice The operators still keep its adresses whitelisted (external contract or EOAs/generic contracts)
/// @notice The operators are considered public when registering validators
/// @param operatorIds The operator IDs to set as public
function setOperatorsPublicUnchecked(uint64[] calldata operatorIds) external;

/**
* @dev Emitted when the whitelist of an operator is updated.
* @param operatorId operator's ID.
Expand Down Expand Up @@ -65,4 +76,11 @@ interface ISSVOperatorsWhitelist is ISSVNetworkCore {
* @param whitelistingContract operators' new whitelisting contract address.
*/
event OperatorWhitelistingContractUpdated(uint64[] operatorIds, address whitelistingContract);

/**
* @dev Emitted when the operators changed its privacy status
* @param operatorIds operators' IDs.
* @param toPrivate Flag that indicates if the operators are being set to private (true) or public (false).
*/
event OperatorPrivacyStatusUpdated(uint64[] operatorIds, bool toPrivate);
}
44 changes: 20 additions & 24 deletions contracts/libraries/OperatorLib.sol
Original file line number Diff line number Diff line change
Expand Up @@ -146,10 +146,9 @@ library OperatorLib {
StorageData storage s
) internal {
uint256 addressesLength = whitelistAddresses.length;
uint256 operatorsLength = operatorIds.length;

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

uint256 operatorsLength = getOperatorsLength(operatorIds);

ISSVNetworkCore.Operator storage operator;
for (uint256 i; i < operatorsLength; ++i) {
Expand Down Expand Up @@ -187,27 +186,6 @@ library OperatorLib {
}
}

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

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);
} else {
s.operators[operatorId].whitelisted = true;
}

s.operatorsWhitelist[operatorId] = address(whitelistingContract);
}

function generateBlockMasks(uint64[] calldata operatorIds) internal pure returns (uint256[] memory masks) {
uint256 blockIndex;
uint256 bitPosition;
Expand All @@ -234,6 +212,19 @@ library OperatorLib {
}
}

function updatePrivacyStatus(uint64[] calldata operatorIds, bool setPrivate, StorageData storage s) internal {
uint256 operatorsLength = getOperatorsLength(operatorIds);

ISSVNetworkCore.Operator storage operator;
for (uint256 i; i < operatorsLength; ++i) {
uint64 operatorId = operatorIds[i];
operator = s.operators[operatorId];
checkOwner(operator);

operator.whitelisted = setPrivate;
}
}

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
Expand All @@ -243,6 +234,11 @@ library OperatorLib {
if (whitelistAddress == address(0)) revert ISSVNetworkCore.ZeroAddressNotAllowed();
}

function getOperatorsLength(uint64[] calldata operatorIds) internal pure returns (uint256 operatorsLength) {
operatorsLength = operatorIds.length;
if (operatorsLength == 0) revert ISSVNetworkCore.InvalidOperatorIdsLength();
}

function isWhitelistingContract(address whitelistingContract) internal view returns (bool) {
return ERC165Checker.supportsInterface(whitelistingContract, type(ISSVWhitelistingContract).interfaceId);
}
Expand Down
16 changes: 12 additions & 4 deletions contracts/modules/SSVOperatorsWhitelist.sol
Original file line number Diff line number Diff line change
Expand Up @@ -58,8 +58,7 @@ contract SSVOperatorsWhitelist is ISSVOperatorsWhitelist {
// Reverts also when whitelistingContract == address(0)
if (!OperatorLib.isWhitelistingContract(address(whitelistingContract))) revert InvalidWhitelistingContract();

uint256 operatorsLength = operatorIds.length;
if (operatorsLength == 0) revert InvalidOperatorIdsLength();
uint256 operatorsLength = OperatorLib.getOperatorsLength(operatorIds);

StorageData storage s = SSVStorage.load();
Operator storage operator;
Expand Down Expand Up @@ -88,8 +87,7 @@ contract SSVOperatorsWhitelist is ISSVOperatorsWhitelist {
}

function removeOperatorsWhitelistingContract(uint64[] calldata operatorIds) external {
uint256 operatorsLength = operatorIds.length;
if (operatorsLength == 0) revert InvalidOperatorIdsLength();
uint256 operatorsLength = OperatorLib.getOperatorsLength(operatorIds);

StorageData storage s = SSVStorage.load();
Operator storage operator;
Expand All @@ -105,4 +103,14 @@ contract SSVOperatorsWhitelist is ISSVOperatorsWhitelist {

emit OperatorWhitelistingContractUpdated(operatorIds, address(0));
}

function setOperatorsPrivateUnchecked(uint64[] calldata operatorIds) external override {
OperatorLib.updatePrivacyStatus(operatorIds, true, SSVStorage.load());
emit OperatorPrivacyStatusUpdated(operatorIds, true);
}

function setOperatorsPublicUnchecked(uint64[] calldata operatorIds) external override {
OperatorLib.updatePrivacyStatus(operatorIds, false, SSVStorage.load());
emit OperatorPrivacyStatusUpdated(operatorIds, false);
}
}
5 changes: 5 additions & 0 deletions hardhat.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,11 @@ const config: HardhatUserConfig = {
ssvToken: process.env.SSVTOKEN_ADDRESS, // if empty, deploy SSV mock token
} as SSVNetworkConfig,
hardhat: {
forking: {
enabled: process.env.FORK_TESTING_ENABLED ? true : false,
url: "https://mainnet.infura.io/v3/1810bc4fb927499990638f8451a455e4",
blockNumber: 19621100,
},
allowUnlimitedContractSize: true,
},
},
Expand Down
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
"scripts": {
"build": "npx hardhat compile",
"test": "npx hardhat test",
"fork-test": "FORK_TESTING_ENABLED=true npx hardhat test",
"lint": "eslint . --ext .ts",
"lint:fix": "eslint --fix . --ext .ts",
"solidity-coverage": "SOLIDITY_COVERAGE=true NO_GAS_ENFORCE=1 npx hardhat coverage",
Expand Down
10 changes: 10 additions & 0 deletions test/helpers/contract-helpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -307,6 +307,16 @@ export const reactivate = async function (ownerId: number, operatorIds: number[]
return reactivatedCluster.eventsByName.ClusterReactivated[0].args;
};

export const getTransactionReceipt = async function (tx: Promise<any>) {
const hash = await tx;

const receipt = await publicClient.waitForTransactionReceipt({
hash,
});

return receipt;
};

async function initialize() {
publicClient = await hre.viem.getPublicClient();
}
Expand Down
35 changes: 27 additions & 8 deletions test/helpers/gas-usage.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import hre from 'hardhat';
import { parseEventLogs } from 'viem';
import { expect } from 'chai';
import { publicClient, ssvNetwork } from '../helpers/contract-helpers';
import { ssvNetwork, getTransactionReceipt } from '../helpers/contract-helpers';

export enum GasGroup {
REGISTER_OPERATOR,
Expand All @@ -13,6 +12,9 @@ export enum GasGroup {
REMOVE_OPERATOR_WHITELISTING_CONTRACT_10,
SET_MULTIPLE_OPERATOR_WHITELIST_10_10,
REMOVE_MULTIPLE_OPERATOR_WHITELIST_10_10,
SET_OPERATORS_PRIVATE_10,
SET_OPERATORS_PUBLIC_10,


DECLARE_OPERATOR_FEE,
CANCEL_OPERATOR_FEE,
Expand All @@ -23,8 +25,17 @@ export enum GasGroup {
REGISTER_VALIDATOR_NEW_STATE,
REGISTER_VALIDATOR_WITHOUT_DEPOSIT,

REGISTER_VALIDATOR_NEW_STATE_1_WHITELISTED_4,
REGISTER_VALIDATOR_NEW_STATE_4_WHITELISTED_4,
REGISTER_VALIDATOR_EXISTING_CLUSTER_4_WHITELISTED_4,

REGISTER_VALIDATOR_NEW_STATE_1_WHITELISTING_CONTRACT_4,
REGISTER_VALIDATOR_NEW_STATE_4_WHITELISTING_CONTRACT_4,

BULK_REGISTER_10_VALIDATOR_NEW_STATE_4,
BULK_REGISTER_10_VALIDATOR_EXISTING_CLUSTER_4,
BULK_REGISTER_10_VALIDATOR_1_WHITELISTING_CONTRACT_EXISTING_CLUSTER_4,


REGISTER_VALIDATOR_EXISTING_CLUSTER_7,
REGISTER_VALIDATOR_NEW_STATE_7,
Expand Down Expand Up @@ -85,13 +96,15 @@ const MAX_GAS_PER_GROUP: any = {
/* REAL GAS LIMITS */
[GasGroup.REGISTER_OPERATOR]: 134500,
[GasGroup.REMOVE_OPERATOR]: 70500,
[GasGroup.REMOVE_OPERATOR_WITH_WITHDRAW]: 70300,
[GasGroup.REMOVE_OPERATOR_WITH_WITHDRAW]: 70500,
[GasGroup.SET_OPERATOR_WHITELIST]: 87000,
[GasGroup.SET_OPERATOR_WHITELISTING_CONTRACT]: 90500,
[GasGroup.SET_OPERATOR_WHITELISTING_CONTRACT_10]: 543000,
[GasGroup.REMOVE_OPERATOR_WHITELISTING_CONTRACT_10]: 130000,
[GasGroup.SET_MULTIPLE_OPERATOR_WHITELIST_10_10]: 590000,
[GasGroup.REMOVE_MULTIPLE_OPERATOR_WHITELIST_10_10]: 173000,
[GasGroup.SET_OPERATORS_PRIVATE_10]: 313000,
[GasGroup.SET_OPERATORS_PUBLIC_10]: 114000,

[GasGroup.DECLARE_OPERATOR_FEE]: 70000,
[GasGroup.CANCEL_OPERATOR_FEE]: 41900,
Expand All @@ -102,8 +115,15 @@ const MAX_GAS_PER_GROUP: any = {
[GasGroup.REGISTER_VALIDATOR_NEW_STATE]: 236000,
[GasGroup.REGISTER_VALIDATOR_WITHOUT_DEPOSIT]: 180600,

[GasGroup.REGISTER_VALIDATOR_NEW_STATE_1_WHITELISTED_4]: 240500,
[GasGroup.REGISTER_VALIDATOR_NEW_STATE_4_WHITELISTED_4]: 247000,
[GasGroup.REGISTER_VALIDATOR_EXISTING_CLUSTER_4_WHITELISTED_4]: 213500,

[GasGroup.REGISTER_VALIDATOR_NEW_STATE_1_WHITELISTING_CONTRACT_4]: 246000,

[GasGroup.BULK_REGISTER_10_VALIDATOR_NEW_STATE_4]: 835500,
[GasGroup.BULK_REGISTER_10_VALIDATOR_EXISTING_CLUSTER_4]: 818700,
[GasGroup.BULK_REGISTER_10_VALIDATOR_1_WHITELISTING_CONTRACT_EXISTING_CLUSTER_4]: 829000,

[GasGroup.REGISTER_VALIDATOR_EXISTING_CLUSTER_7]: 272500,
[GasGroup.REGISTER_VALIDATOR_NEW_STATE_7]: 289000,
Expand Down Expand Up @@ -189,12 +209,11 @@ for (const group in MAX_GAS_PER_GROUP) {
}

export const trackGas = async function (tx: Promise<any>, groups?: Array<GasGroup>): Promise<any> {
const hash = await tx;

const receipt = await publicClient.waitForTransactionReceipt({
hash,
});
const receipt = await getTransactionReceipt(tx);
return await trackGasFromReceipt(receipt, groups);
};

export const trackGasFromReceipt = async function (receipt: any, groups?: Array<GasGroup>): Promise<any> {
const logs = parseEventLogs({
abi: ssvNetwork.abi,
logs: receipt.logs,
Expand Down
21 changes: 21 additions & 0 deletions test/helpers/utils/test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,3 +35,24 @@ export async function assertEvent(tx: Promise<any>, eventAssertions: EventAssert
}
}
}

export async function assertPostTxEvent(eventAssertions: EventAssertion[], unemittedEvent?: Event) {
if (unemittedEvent) {
const events = await unemittedEvent.contract.getEvents[unemittedEvent.eventName]();
expect(events.length).to.equal(0);
}
for (const assertion of eventAssertions) {
const events = await assertion.contract.getEvents[assertion.eventName]();
if (assertion.eventLength) {
expect(events.length).to.equal(assertion.eventLength);
}

if (assertion.argNames && assertion.argValuesList) {
for (let i = 0; i < events.length; i++) {
for (let j = 0; j < assertion.argNames.length; j++) {
expect(events[i].args[assertion.argNames[j]]).to.deep.equal(assertion.argValuesList[i][j]);
}
}
}
}
}
23 changes: 8 additions & 15 deletions test/operators/register.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,5 @@
// Declare imports
import {
owners,
initializeContract,
DataGenerator,
CONFIG,
} from '../helpers/contract-helpers';
import { owners, initializeContract, DataGenerator, CONFIG } from '../helpers/contract-helpers';
import { assertEvent } from '../helpers/utils/test';
import { trackGas, GasGroup } from '../helpers/gas-usage';

Expand Down Expand Up @@ -58,7 +53,7 @@ describe('Register Operator Tests', () => {
owners[1].account.address, // owner
CONFIG.minimalOperatorFee, // fee
0, // validatorCount
ethers.ZeroAddress, // whitelisted
ethers.ZeroAddress, // whitelisting contract address
false, // isPrivate
true, // active
]);
Expand All @@ -73,7 +68,7 @@ describe('Register Operator Tests', () => {
ethers.ZeroAddress, // owner
0, // fee
0, // validatorCount
ethers.ZeroAddress, // whitelisted
ethers.ZeroAddress, // whitelisting contract address
false, // isPrivate
false, // active
]);
Expand All @@ -82,7 +77,7 @@ describe('Register Operator Tests', () => {
it('Get operator removed by id', async () => {
await ssvNetwork.write.registerOperator([DataGenerator.publicKey(0), CONFIG.minimalOperatorFee], {
account: owners[1].account,
});
});
await ssvNetwork.write.removeOperator([1], {
account: owners[1].account,
});
Expand All @@ -91,16 +86,14 @@ describe('Register Operator Tests', () => {
owners[1].account.address, // owner
0, // fee
0, // validatorCount
ethers.ZeroAddress, // whitelisted
ethers.ZeroAddress, // whitelisting contract address
false, // isPrivate
false, // active
]);
});

it('Register an operator with a fee thats too low reverts "FeeTooLow"', async () => {
await expect(ssvNetwork.write.registerOperator([DataGenerator.publicKey(0), '10'])).to.be.rejectedWith(
'FeeTooLow',
);
await expect(ssvNetwork.write.registerOperator([DataGenerator.publicKey(0), '10'])).to.be.rejectedWith('FeeTooLow');
});

it('Register an operator with a fee thats too high reverts "FeeTooHigh"', async () => {
Expand All @@ -113,12 +106,12 @@ describe('Register Operator Tests', () => {
const publicKey = DataGenerator.publicKey(1);
await ssvNetwork.write.registerOperator([publicKey, CONFIG.minimalOperatorFee], {
account: owners[1].account,
});
});

await expect(
ssvNetwork.write.registerOperator([publicKey, CONFIG.minimalOperatorFee], {
account: owners[1].account,
})
}),
).to.be.rejectedWith('OperatorAlreadyExists');
});
});
Loading

0 comments on commit 65347ee

Please sign in to comment.