Skip to content

Commit

Permalink
getWhitelistedOperators update
Browse files Browse the repository at this point in the history
  • Loading branch information
mtabasco committed Jun 7, 2024
1 parent ed84408 commit e1009fa
Show file tree
Hide file tree
Showing 3 changed files with 247 additions and 9 deletions.
49 changes: 41 additions & 8 deletions contracts/modules/SSVViews.sol
Original file line number Diff line number Diff line change
Expand Up @@ -84,15 +84,15 @@ contract SSVViews is ISSVViews {

StorageData storage s = SSVStorage.load();

// create the max number of masks that will be updated
uint256[] memory masks = OperatorLib.generateBlockMasks(operatorIds, false, s);

uint256 count;
whitelistedOperatorIds = new uint64[](operatorsLength);
uint256 internalCount;

// Check whitelisting address for each operator using the internal SSV whitelisting module
uint256 whitelistedMask;
uint256 matchedMask;

uint256[] memory masks = OperatorLib.generateBlockMasks(operatorIds, false, s);
uint64[] memory internalWhitelistedOperatorIds = new uint64[](operatorsLength);

// Check whitelisting status for each mask
for (uint256 blockIndex; blockIndex < masks.length; ++blockIndex) {
// Only check blocks that have operator IDs
Expand All @@ -106,15 +106,48 @@ contract SSVViews is ISSVViews {
uint256 blockPointer = blockIndex << 8;
for (uint256 bit; bit < 256; ++bit) {
if (matchedMask & (1 << bit) != 0) {
whitelistedOperatorIds[count++] = uint64(blockPointer + bit);
if (count == operatorsLength) {
return whitelistedOperatorIds; // Early termination
internalWhitelistedOperatorIds[internalCount++] = uint64(blockPointer + bit);
if (internalCount == operatorsLength) {
return internalWhitelistedOperatorIds; // Early termination
}
}
}
}
}

// Resize internalWhitelistedOperatorIds to the actual number of whitelisted operators
assembly {
mstore(internalWhitelistedOperatorIds, internalCount)
}

// Check if pending operators use an external whitelisting contract and check whitelistedAddress using it
whitelistedOperatorIds = new uint64[](operatorsLength);
uint256 operatorIndex;
uint256 internalWhitelistIndex;
uint256 count;

while (operatorIndex < operatorsLength) {
uint64 operatorId = operatorIds[operatorIndex];

// Check if operatorId is already in internalWhitelistedOperatorIds
if (internalWhitelistIndex < internalCount && operatorId == internalWhitelistedOperatorIds[internalWhitelistIndex]) {
whitelistedOperatorIds[count++] = operatorId;
++internalWhitelistIndex;
} else {
// Check whitelisting contract
address whitelistingContract = s.operatorsWhitelist[operatorId];
if (whitelistingContract != address(0)) {
if (
OperatorLib.isWhitelistingContract(whitelistingContract) &&
ISSVWhitelistingContract(whitelistingContract).isWhitelisted(whitelistedAddress, operatorId)
) {
whitelistedOperatorIds[count++] = operatorId;
}
}
}
++operatorIndex;
}

// Resize whitelistedOperatorIds to the actual number of whitelisted operators
assembly {
mstore(whitelistedOperatorIds, count)
Expand Down
1 change: 1 addition & 0 deletions hardhat.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ const config: HardhatUserConfig = {
enabled: true,
runs: 10000,
},
evmVersion: 'cancun',
},
},
],
Expand Down
206 changes: 205 additions & 1 deletion test/operators/whitelist.ts
Original file line number Diff line number Diff line change
Expand Up @@ -524,7 +524,7 @@ describe('Whitelisting Operator Tests', () => {
expect(await ssvViews.read.getWhitelistedOperators([[1, 2], ethers.ZeroAddress])).to.be.deep.equal([]);
});

it('Get whitelisted address for operators returns only the whitelisted operators', async () => {
it('Get whitelisted address for operators returns the whitelisted operators (only SSV whitelisting module)', async () => {
const whitelistAddress = owners[4].account.address;

// Register 1000 operators to have 4 bitmap blocks
Expand All @@ -546,6 +546,210 @@ describe('Whitelisting Operator Tests', () => {
).to.be.deep.equal([]);
});

it('Get whitelisted address for operators returns the whitelisted operators (only externally whitelisted)', async () => {
const whitelistAddress = owners[4].account.address;

// Register 1000 operators to have 4 bitmap blocks
await registerOperators(1, 1000, CONFIG.minimalOperatorFee);

await ssvNetwork.write.setOperatorsWhitelistingContract(
[[100, 200, 300, 400, 500, 600, 700, 800], mockWhitelistingContractAddress],
{
account: owners[1].account,
},
);

await mockWhitelistingContract.write.setWhitelistedAddress([whitelistAddress]);

expect(await ssvViews.read.getWhitelistedOperators([[200, 400, 600, 800], whitelistAddress])).to.be.deep.equal([
200, 400, 600, 800,
]);
expect(
await ssvViews.read.getWhitelistedOperators([[1, 60, 150, 200, 320, 400, 512, 715, 800, 905], whitelistAddress]),
).to.be.deep.equal([200, 400, 800]);
expect(
await ssvViews.read.getWhitelistedOperators([[1, 60, 150, 320, 512, 715, 905], whitelistAddress]),
).to.be.deep.equal([]);
});

it('Get whitelisted address for operators returns the whitelisted operators (internally and externally whitelisted)', async () => {
const whitelistAddress = owners[4].account.address;

// Register 1000 operators to have 4 bitmap blocks
await registerOperators(1, 1000, CONFIG.minimalOperatorFee);

// Whitelist using external whitelisting contract
await ssvNetwork.write.setOperatorsWhitelistingContract([[100, 400, 700, 800], mockWhitelistingContractAddress], {
account: owners[1].account,
});

await mockWhitelistingContract.write.setWhitelistedAddress([whitelistAddress]);

// Whitelist using SSV whitelisting module
await ssvNetwork.write.setOperatosWhitelists([[200, 300, 500, 600], [whitelistAddress]], {
account: owners[1].account,
});

expect(await ssvViews.read.getWhitelistedOperators([[200, 400, 600, 800], whitelistAddress])).to.be.deep.equal([
200, 400, 600, 800,
]);
expect(
await ssvViews.read.getWhitelistedOperators([[1, 60, 150, 200, 320, 400, 512, 715, 800, 905], whitelistAddress]),
).to.be.deep.equal([200, 400, 800]);
expect(
await ssvViews.read.getWhitelistedOperators([[1, 60, 150, 320, 512, 715, 905], whitelistAddress]),
).to.be.deep.equal([]);
});

it('Get whitelisted address for a single operator whitelisted both internally and externally', async () => {
const whitelistAddress = owners[4].account.address;

// Register operators
await registerOperators(1, 10, CONFIG.minimalOperatorFee);

// Whitelist using external whitelisting contract
await ssvNetwork.write.setOperatorsWhitelistingContract([[1], mockWhitelistingContractAddress], {
account: owners[1].account,
});

await mockWhitelistingContract.write.setWhitelistedAddress([whitelistAddress]);

// Whitelist using SSV whitelisting module
await ssvNetwork.write.setOperatosWhitelists([[1], [whitelistAddress]], {
account: owners[1].account,
});

expect(await ssvViews.read.getWhitelistedOperators([[1], whitelistAddress])).to.be.deep.equal([1]);
});

it('Get whitelisted address for overlapping internal and external whitelisting', async () => {
const whitelistAddress = owners[4].account.address;

// Register operators
await registerOperators(1, 10, CONFIG.minimalOperatorFee);

// Whitelist using external whitelisting contract
await ssvNetwork.write.setOperatorsWhitelistingContract([[1, 2, 3], mockWhitelistingContractAddress], {
account: owners[1].account,
});

await mockWhitelistingContract.write.setWhitelistedAddress([whitelistAddress]);

// Whitelist using SSV whitelisting module
await ssvNetwork.write.setOperatosWhitelists([[2, 3, 4], [whitelistAddress]], {
account: owners[1].account,
});

expect(await ssvViews.read.getWhitelistedOperators([[1, 2, 3, 4], whitelistAddress])).to.be.deep.equal([
1, 2, 3, 4,
]);
});

it('Get whitelisted address for a list containing non-whitelisted operators', async () => {
const whitelistAddress = owners[4].account.address;

// Register operators
await registerOperators(1, 10, CONFIG.minimalOperatorFee);

// Whitelist using SSV whitelisting module
await ssvNetwork.write.setOperatosWhitelists([[2, 4, 6], [whitelistAddress]], {
account: owners[1].account,
});

expect(await ssvViews.read.getWhitelistedOperators([[1, 2, 3, 4, 5, 6, 7, 8], whitelistAddress])).to.be.deep.equal([
2, 4, 6,
]);
});

it('Get whitelisted address for non-existent operator IDs', async () => {
const whitelistAddress = owners[4].account.address;

// Register operators
await registerOperators(1, 10, CONFIG.minimalOperatorFee);

// Whitelist using SSV whitelisting module
await ssvNetwork.write.setOperatosWhitelists([[2, 4, 6], [whitelistAddress]], {
account: owners[1].account,
});

expect(await ssvViews.read.getWhitelistedOperators([[11, 12, 13], whitelistAddress])).to.be.deep.equal([]);
});

it('Get whitelisted address for mixed whitelisted and non-whitelisted addresses', async () => {
const whitelistAddress1 = owners[4].account.address;
const whitelistAddress2 = owners[5].account.address;

// Register operators
await registerOperators(1, 10, CONFIG.minimalOperatorFee);

// Whitelist using SSV whitelisting module
await ssvNetwork.write.setOperatosWhitelists([[2, 4, 6], [whitelistAddress1]], {
account: owners[1].account,
});

await ssvNetwork.write.setOperatosWhitelists([[3, 5, 7], [whitelistAddress2]], {
account: owners[1].account,
});

expect(await ssvViews.read.getWhitelistedOperators([[1, 2, 3, 4, 5, 6, 7, 8], whitelistAddress1])).to.be.deep.equal(
[2, 4, 6],
);
expect(await ssvViews.read.getWhitelistedOperators([[1, 2, 3, 4, 5, 6, 7, 8], whitelistAddress2])).to.be.deep.equal(
[3, 5, 7],
);
});

it('Get whitelisted address for unsorted operators', async () => {
const whitelistAddress = owners[4].account.address;

// Register operators
await registerOperators(1, 10, CONFIG.minimalOperatorFee);

// Whitelist using SSV whitelisting module
await ssvNetwork.write.setOperatosWhitelists([[2, 4, 6], [whitelistAddress]], {
account: owners[1].account,
});

await expect(ssvViews.read.getWhitelistedOperators([[6, 2, 4], whitelistAddress])).to.be.rejectedWith(
'UnsortedOperatorsList',
);
});

it('Get whitelisted address for duplicate operator IDs', async () => {
const whitelistAddress = owners[4].account.address;

// Register operators
await registerOperators(1, 10, CONFIG.minimalOperatorFee);

// Whitelist using SSV whitelisting module
await ssvNetwork.write.setOperatosWhitelists([[2, 4, 6], [whitelistAddress]], {
account: owners[1].account,
});

await expect(ssvViews.read.getWhitelistedOperators([[2, 2, 4, 6, 6], whitelistAddress])).to.be.rejectedWith(
'OperatorsListNotUnique',
);
});

it('Get whitelisted address for a large number of operator IDs', async () => {
const whitelistAddress = owners[4].account.address;

// Register a large number of operators
const largeNumber = 3000;
await registerOperators(1, largeNumber, CONFIG.minimalOperatorFee);

let operatorIds = [];
for (let i = 1; i <= largeNumber; i++) {
operatorIds.push(i);
}

await ssvNetwork.write.setOperatosWhitelists([operatorIds, [whitelistAddress]], {
account: owners[1].account,
});

expect(await ssvViews.read.getWhitelistedOperators([operatorIds, whitelistAddress])).to.be.deep.equal(operatorIds);
});

it('Get private operator by id', async () => {
await ssvNetwork.write.registerOperator([DataGenerator.publicKey(0), CONFIG.minimalOperatorFee, false], {
account: owners[1].account,
Expand Down

0 comments on commit e1009fa

Please sign in to comment.