Skip to content

Commit

Permalink
add exit validator tests
Browse files Browse the repository at this point in the history
  • Loading branch information
mtabasco committed Jan 16, 2024
1 parent 49ba157 commit c80dc3b
Show file tree
Hide file tree
Showing 5 changed files with 279 additions and 94 deletions.
6 changes: 5 additions & 1 deletion contracts/libraries/ValidatorLib.sol
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,9 @@ library ValidatorLib {
uint64[] calldata operatorIds,
StorageData storage s
) internal view returns (bytes32[] memory hashedValidator) {
if (publicKeys.length == 0) {
revert ISSVNetworkCore.ValidatorDoesNotExist();
}
hashedValidator = new bytes32[](publicKeys.length);
bytes32 hashedOperatorIds = hashOperatorIds(operatorIds);

Expand All @@ -76,7 +79,8 @@ library ValidatorLib {
revert ISSVNetworkCore.ValidatorDoesNotExist();
}

if ((validatorData & ~bytes32(uint256(1))) != hashedOperatorIds) { // All bits set to 1 except LSB
if ((validatorData & ~bytes32(uint256(1))) != hashedOperatorIds) {
// All bits set to 1 except LSB
// Clear LSB of stored validator data and compare
revert ISSVNetworkCore.IncorrectValidatorState();
}
Expand Down
8 changes: 3 additions & 5 deletions contracts/modules/SSVClusters.sol
Original file line number Diff line number Diff line change
Expand Up @@ -85,11 +85,10 @@ contract SSVClusters is ISSVClusters {
) external override {
StorageData storage s = SSVStorage.load();

bytes32 hashedCluster = cluster.validateHashedCluster(msg.sender, operatorIds, s);
bytes32 hashedOperatorIds = ValidatorLib.hashOperatorIds(operatorIds);
bytes32 hashedValidator = ValidatorLib.validateState(publicKey, hashedOperatorIds, s);

bytes32 hashedCluster = cluster.validateHashedCluster(msg.sender, operatorIds, s);

if (cluster.active) {
StorageProtocol storage sp = SSVStorageProtocol.load();
(uint64 clusterIndex, ) = OperatorLib.updateClusterOperators(operatorIds, false, false, 1, s, sp);
Expand All @@ -116,7 +115,6 @@ contract SSVClusters is ISSVClusters {
StorageData storage s = SSVStorage.load();

bytes32 hashedCluster = cluster.validateHashedCluster(msg.sender, operatorIds, s);

bytes32[] memory hashedValidators = ValidatorLib.validateStates(publicKeys, operatorIds, s);

uint32 validatorsLength = uint32(publicKeys.length);
Expand All @@ -137,12 +135,12 @@ contract SSVClusters is ISSVClusters {
sp.updateDAO(false, validatorsLength);
}

cluster.validatorCount -= validatorsLength;

for (uint i; i < validatorsLength; ++i) {
delete s.validatorPKs[hashedValidators[i]];
}

cluster.validatorCount -= validatorsLength;

s.clusters[hashedCluster] = cluster.hashClusterData();

for (uint i; i < validatorsLength; ++i) {
Expand Down
51 changes: 44 additions & 7 deletions test/sanity/balances.ts
Original file line number Diff line number Diff line change
Expand Up @@ -430,13 +430,7 @@ describe('Balance Tests', () => {
10,
[5, 6, 7, 8],
minDepositAmount,
{
validatorCount: 0,
networkFeeIndex: 0,
index: 0,
balance: 0,
active: true,
},
helpers.DB.initialClusterState,
);

await utils.progressBlocks(2);
Expand Down Expand Up @@ -474,4 +468,47 @@ describe('Balance Tests', () => {
expect(await ssvViews.getOperatorEarnings(7)).to.equal((helpers.CONFIG.minimalOperatorFee * 10 * 3) + (helpers.CONFIG.minimalOperatorFee * 5 * 2));
expect(await ssvViews.getOperatorEarnings(8)).to.equal((helpers.CONFIG.minimalOperatorFee * 10 * 3) + (helpers.CONFIG.minimalOperatorFee * 5 * 2));
});

it('Remove validators from a liquidated cluster', async () => {
const clusterDeposit = minDepositAmount * 10;
// 3 operators cluster burnPerBlock
const newBurnPerBlock = helpers.CONFIG.minimalOperatorFee * 3 + networkFee;

// register 10 validators
const { args, pks } = await helpers.bulkRegisterValidators(
2,
10,
[5, 6, 7, 8],
minDepositAmount,
helpers.DB.initialClusterState,
);

await utils.progressBlocks(2);

// remove one operator
await ssvNetworkContract.connect(helpers.DB.owners[0]).removeOperator(8);

await utils.progressBlocks(2);

// bulk remove 10 validators
const result = await trackGas(ssvNetworkContract
.connect(helpers.DB.owners[2])
.bulkRemoveValidator(pks, args.operatorIds, args.cluster)
);
const removed = result.eventsByName.ValidatorRemoved[0].args;

await utils.progressBlocks(2);

// check operators' balances
expect(await ssvViews.getOperatorEarnings(5)).to.equal((helpers.CONFIG.minimalOperatorFee * 10 * 6));
expect(await ssvViews.getOperatorEarnings(6)).to.equal((helpers.CONFIG.minimalOperatorFee * 10 * 6));
expect(await ssvViews.getOperatorEarnings(7)).to.equal((helpers.CONFIG.minimalOperatorFee * 10 * 6));
expect(await ssvViews.getOperatorEarnings(8)).to.equal(0);

// check cluster balance
expect(
await ssvViews.getBalance(helpers.DB.owners[2].address, removed.operatorIds, removed.cluster),
).to.equal(clusterDeposit - (burnPerBlock * 10 * 3) - (newBurnPerBlock * 10 * 3));

});
});
139 changes: 103 additions & 36 deletions test/validators/others.ts → test/validators/exit.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import { trackGas, GasGroup } from '../helpers/gas-usage';
// Declare globals
let ssvNetworkContract: any, minDepositAmount: any, firstCluster: any;

describe('Other Validator Tests', () => {
describe('Exit Validator Tests', () => {
beforeEach(async () => {
// Initialize contract
const metadata = await helpers.initializeContract();
Expand Down Expand Up @@ -176,13 +176,7 @@ describe('Other Validator Tests', () => {
10,
helpers.DEFAULT_OPERATOR_IDS[4],
minDepositAmount,
{
validatorCount: 0,
networkFeeIndex: 0,
index: 0,
balance: 0,
active: true,
},
helpers.DB.initialClusterState,
);

await expect(
Expand All @@ -199,13 +193,7 @@ describe('Other Validator Tests', () => {
10,
helpers.DEFAULT_OPERATOR_IDS[4],
minDepositAmount,
{
validatorCount: 0,
networkFeeIndex: 0,
index: 0,
balance: 0,
active: true,
},
helpers.DB.initialClusterState,
);

await trackGas(
Expand All @@ -222,13 +210,7 @@ describe('Other Validator Tests', () => {
10,
helpers.DEFAULT_OPERATOR_IDS[7],
minDepositAmount,
{
validatorCount: 0,
networkFeeIndex: 0,
index: 0,
balance: 0,
active: true,
},
helpers.DB.initialClusterState,
);

await trackGas(
Expand All @@ -245,13 +227,7 @@ describe('Other Validator Tests', () => {
10,
helpers.DEFAULT_OPERATOR_IDS[10],
minDepositAmount,
{
validatorCount: 0,
networkFeeIndex: 0,
index: 0,
balance: 0,
active: true,
},
helpers.DB.initialClusterState,
);

await trackGas(
Expand All @@ -268,13 +244,7 @@ describe('Other Validator Tests', () => {
10,
helpers.DEFAULT_OPERATOR_IDS[13],
minDepositAmount,
{
validatorCount: 0,
networkFeeIndex: 0,
index: 0,
balance: 0,
active: true,
},
helpers.DB.initialClusterState,
);

await trackGas(
Expand All @@ -284,4 +254,101 @@ describe('Other Validator Tests', () => {
[GasGroup.BULK_EXIT_10_VALIDATOR_13],
);
});

it('Bulk exiting removed validators reverts "ValidatorDoesNotExist"', async () => {
const { args, pks } = await helpers.bulkRegisterValidators(
2,
10,
helpers.DEFAULT_OPERATOR_IDS[4],
minDepositAmount,
helpers.DB.initialClusterState,
);

await trackGas(ssvNetworkContract
.connect(helpers.DB.owners[2])
.bulkRemoveValidator(pks.slice(0, 5), args.operatorIds, args.cluster)
);

await expect(
ssvNetworkContract
.connect(helpers.DB.owners[2])
.bulkExitValidator(pks.slice(0, 5), args.operatorIds),
).to.be.revertedWithCustomError(ssvNetworkContract, 'ValidatorDoesNotExist');
});

it('Bulk exiting non-existing validators reverts "ValidatorDoesNotExist"', async () => {
const { args, pks } = await helpers.bulkRegisterValidators(
2,
10,
helpers.DEFAULT_OPERATOR_IDS[4],
minDepositAmount,
helpers.DB.initialClusterState,
);

pks[4] = "0xabcd1234";

await expect(
ssvNetworkContract
.connect(helpers.DB.owners[2])
.bulkExitValidator(pks, args.operatorIds),
).to.be.revertedWithCustomError(ssvNetworkContract, 'ValidatorDoesNotExist');
});

it('Bulk exiting validators with empty operator list reverts "IncorrectValidatorState"', async () => {
const { args, pks } = await helpers.bulkRegisterValidators(
2,
10,
helpers.DEFAULT_OPERATOR_IDS[4],
minDepositAmount,
helpers.DB.initialClusterState,
);

await expect(
ssvNetworkContract.connect(helpers.DB.owners[2]).bulkExitValidator(pks, []),
).to.be.revertedWithCustomError(ssvNetworkContract, 'IncorrectValidatorState');
});

it('Bulk exiting validators with empty public key reverts "ValidatorDoesNotExist', async () => {
const { args } = await helpers.bulkRegisterValidators(
2,
10,
helpers.DEFAULT_OPERATOR_IDS[4],
minDepositAmount,
helpers.DB.initialClusterState,
);

await expect(
ssvNetworkContract.connect(helpers.DB.owners[2]).bulkExitValidator([], args.operatorIds),
).to.be.revertedWithCustomError(ssvNetworkContract, 'ValidatorDoesNotExist');
});

it('Bulk exiting validators using the wrong account reverts "ValidatorDoesNotExist"', async () => {
const { args, pks } = await helpers.bulkRegisterValidators(
2,
10,
helpers.DEFAULT_OPERATOR_IDS[4],
minDepositAmount,
helpers.DB.initialClusterState,
);

await expect(
ssvNetworkContract
.connect(helpers.DB.owners[3])
.bulkExitValidator(pks, args.operatorIds),
).to.be.revertedWithCustomError(ssvNetworkContract, 'ValidatorDoesNotExist');
});

it('Bulk exiting validators with incorrect operators (unsorted list) reverts with "IncorrectValidatorState"', async () => {
const { args, pks } = await helpers.bulkRegisterValidators(
2,
10,
helpers.DEFAULT_OPERATOR_IDS[4],
minDepositAmount,
helpers.DB.initialClusterState,
);

await expect(
ssvNetworkContract.connect(helpers.DB.owners[1]).bulkExitValidator(pks, [4, 3, 2, 1]),
).to.be.revertedWithCustomError(ssvNetworkContract, 'IncorrectValidatorState');
});
});
Loading

0 comments on commit c80dc3b

Please sign in to comment.