diff --git a/contracts/BSCValidatorSet.sol b/contracts/BSCValidatorSet.sol index 3e7800ac..e625c318 100644 --- a/contracts/BSCValidatorSet.sol +++ b/contracts/BSCValidatorSet.sol @@ -452,6 +452,16 @@ contract BSCValidatorSet is IBSCValidatorSet, System, IParamSubscriber, IApplica return isWorkingValidator(index); } + function getWorkingValidatorCount() public view returns(uint256 workingValidatorCount) { + workingValidatorCount = getValidators().length; + uint256 _numOfCabinets = numOfCabinets > 0 ? numOfCabinets : INIT_NUM_OF_CABINETS; + if (workingValidatorCount > _numOfCabinets) { + workingValidatorCount = _numOfCabinets; + } + if (workingValidatorCount == 0) { + workingValidatorCount = 1; + } + } /*********************** For slash **************************/ function misdemeanor(address validator) external onlySlash initValidatorExtraSet override { uint256 validatorIndex = _misdemeanor(validator); @@ -523,7 +533,8 @@ contract BSCValidatorSet is IBSCValidatorSet, System, IParamSubscriber, IApplica // jailed validators are allowed to exit maintenance require(validatorExtraSet[index].isMaintaining, "not in maintenance"); - _exitMaintenance(msg.sender, index); + uint256 workingValidatorCount = getWorkingValidatorCount(); + _exitMaintenance(msg.sender, index, workingValidatorCount); } /*********************** Param update ********************************/ @@ -719,6 +730,9 @@ contract BSCValidatorSet is IBSCValidatorSet, System, IParamSubscriber, IApplica // 1. validators exit maintenance uint256 i; + // caution: it must calculate workingValidatorCount before _exitMaintenance loop + // because the workingValidatorCount will be changed in _exitMaintenance + uint256 workingValidatorCount = getWorkingValidatorCount(); // caution: it must loop from the endIndex to startIndex in currentValidatorSet // because the validators order in currentValidatorSet may be changed by _felony(validator) for (uint index = currentValidatorSet.length; index > 0; --index) { @@ -731,7 +745,7 @@ contract BSCValidatorSet is IBSCValidatorSet, System, IParamSubscriber, IApplica validator = currentValidatorSet[i].consensusAddress; // exit maintenance - isFelony = _exitMaintenance(validator, i); + isFelony = _exitMaintenance(validator, i, workingValidatorCount); if (!isFelony || numOfFelony >= _validatorSet.length - 1) { continue; } @@ -766,13 +780,7 @@ contract BSCValidatorSet is IBSCValidatorSet, System, IParamSubscriber, IApplica emit validatorEnterMaintenance(validator); } - function _exitMaintenance(address validator, uint index) private returns (bool isFelony){ - uint256 workingValidatorCount = getValidators().length; - - uint256 _numOfCabinets = numOfCabinets > 0 ? numOfCabinets : INIT_NUM_OF_CABINETS; - if (workingValidatorCount > _numOfCabinets) { - workingValidatorCount = _numOfCabinets; - } + function _exitMaintenance(address validator, uint index, uint256 workingValidatorCount) private returns (bool isFelony){ if (maintainSlashScale == 0 || workingValidatorCount == 0 || numOfMaintaining == 0) { // should not happen, still protect return false; diff --git a/contracts/BSCValidatorSet.template b/contracts/BSCValidatorSet.template index 82f15eab..b0c36308 100644 --- a/contracts/BSCValidatorSet.template +++ b/contracts/BSCValidatorSet.template @@ -452,6 +452,16 @@ contract BSCValidatorSet is IBSCValidatorSet, System, IParamSubscriber, IApplica return isWorkingValidator(index); } + function getWorkingValidatorCount() public view returns(uint256 workingValidatorCount) { + workingValidatorCount = getValidators().length; + uint256 _numOfCabinets = numOfCabinets > 0 ? numOfCabinets : INIT_NUM_OF_CABINETS; + if (workingValidatorCount > _numOfCabinets) { + workingValidatorCount = _numOfCabinets; + } + if (workingValidatorCount == 0) { + workingValidatorCount = 1; + } + } /*********************** For slash **************************/ function misdemeanor(address validator) external onlySlash initValidatorExtraSet override { uint256 validatorIndex = _misdemeanor(validator); @@ -523,7 +533,8 @@ contract BSCValidatorSet is IBSCValidatorSet, System, IParamSubscriber, IApplica // jailed validators are allowed to exit maintenance require(validatorExtraSet[index].isMaintaining, "not in maintenance"); - _exitMaintenance(msg.sender, index); + uint256 workingValidatorCount = getWorkingValidatorCount(); + _exitMaintenance(msg.sender, index, workingValidatorCount); } /*********************** Param update ********************************/ @@ -719,6 +730,9 @@ contract BSCValidatorSet is IBSCValidatorSet, System, IParamSubscriber, IApplica // 1. validators exit maintenance uint256 i; + // caution: it must calculate workingValidatorCount before _exitMaintenance loop + // because the workingValidatorCount will be changed in _exitMaintenance + uint256 workingValidatorCount = getWorkingValidatorCount(); // caution: it must loop from the endIndex to startIndex in currentValidatorSet // because the validators order in currentValidatorSet may be changed by _felony(validator) for (uint index = currentValidatorSet.length; index > 0; --index) { @@ -731,7 +745,7 @@ contract BSCValidatorSet is IBSCValidatorSet, System, IParamSubscriber, IApplica validator = currentValidatorSet[i].consensusAddress; // exit maintenance - isFelony = _exitMaintenance(validator, i); + isFelony = _exitMaintenance(validator, i, workingValidatorCount); if (!isFelony || numOfFelony >= _validatorSet.length - 1) { continue; } @@ -766,13 +780,7 @@ contract BSCValidatorSet is IBSCValidatorSet, System, IParamSubscriber, IApplica emit validatorEnterMaintenance(validator); } - function _exitMaintenance(address validator, uint index) private returns (bool isFelony){ - uint256 workingValidatorCount = getValidators().length; - - uint256 _numOfCabinets = numOfCabinets > 0 ? numOfCabinets : INIT_NUM_OF_CABINETS; - if (workingValidatorCount > _numOfCabinets) { - workingValidatorCount = _numOfCabinets; - } + function _exitMaintenance(address validator, uint index, uint256 workingValidatorCount) private returns (bool isFelony){ if (maintainSlashScale == 0 || workingValidatorCount == 0 || numOfMaintaining == 0) { // should not happen, still protect return false; diff --git a/hardhat.config.ts b/hardhat.config.ts index 303e14ba..8133d6ce 100644 --- a/hardhat.config.ts +++ b/hardhat.config.ts @@ -59,5 +59,8 @@ export default { cache: "./cache", artifacts: "./artifacts" }, + gasReporter: { + enabled: (process.env.REPORT_GAS) ? true : false + } }; diff --git a/package.json b/package.json index 8f515d42..b729e4e9 100644 --- a/package.json +++ b/package.json @@ -18,7 +18,8 @@ "flatten": "truffle-flattener contracts/BSCValidatorSet.sol > contracts/flattened/BSCValidatorSet.sol && truffle-flattener contracts/GovHub.sol > contracts/flattened/GovHub.sol && truffle-flattener contracts/RelayerHub.sol > contracts/flattened/RelayerHub.sol && truffle-flattener contracts/RelayerIncentivize.sol > contracts/flattened/RelayerIncentivize.sol && truffle-flattener contracts/SlashIndicator.sol > contracts/flattened/SlashIndicator.sol && truffle-flattener contracts/SystemReward.sol > contracts/flattened/SystemReward.sol && truffle-flattener contracts/TendermintLightClient.sol > contracts/flattened/TendermintLightClient.sol && truffle-flattener contracts/TokenHub.sol > contracts/flattened/TokenHub.sol && truffle-flattener contracts/CrossChain.sol > contracts/flattened/CrossChain.sol && truffle-flattener contracts/TokenManager.sol > contracts/flattened/TokenManager.sol", "generate-test": "node generate-system.js --mock true && node generate-systemReward.js --mock true && node generate-validatorset.js --mock true && node generate-system.js --mock true && node generate-crosschain.js --mock true && node generate-tokenhub.js --mock true && node generate-tendermintlightclient.js --mock true && node generate-relayerincentivizecontract.js --roundSize 30 --maximumWeight 3 --mock true && node generate-slash.js --mock true", "generate-mainnet": "node generate-genesis.js --chainid 56 --bscChainId 0038 --initBurnRatio 1000 --initConsensusStateBytes 42696e616e63652d436861696e2d5469677269730000000000000000000000000000000006915167cedaf7bbf7df47d932fdda630527ee648562cf3e52c5e5f46156a3a971a4ceb443c53a50d8653ef8cf1e5716da68120fb51b636dc6d111ec3277b098ecd42d49d3769d8a1f78b4c17a965f7a30d4181fabbd1f969f46d3c8e83b5ad4845421d8000000e8d4a510002ba4e81542f437b7ae1f8a35ddb233c789a8dc22734377d9b6d63af1ca403b61000000e8d4a51000df8da8c5abfdb38595391308bb71e5a1e0aabdc1d0cf38315d50d6be939b2606000000e8d4a51000b6619edca4143484800281d698b70c935e9152ad57b31d85c05f2f79f64b39f3000000e8d4a510009446d14ad86c8d2d74780b0847110001a1c2e252eedfea4753ebbbfce3a22f52000000e8d4a510000353c639f80cc8015944436dab1032245d44f912edc31ef668ff9f4a45cd0599000000e8d4a51000e81d3797e0544c3a718e1f05f0fb782212e248e784c1a851be87e77ae0db230e000000e8d4a510005e3fcda30bd19d45c4b73688da35e7da1fce7c6859b2c1f20ed5202d24144e3e000000e8d4a51000b06a59a2d75bf5d014fce7c999b5e71e7a960870f725847d4ba3235baeaa08ef000000e8d4a510000c910e2fe650e4e01406b3310b489fb60a84bc3ff5c5bee3a56d5898b6a8af32000000e8d4a5100071f2d7b8ec1c8b99a653429b0118cd201f794f409d0fea4d65b1b662f2b00063000000e8d4a51000 --initValidatorSetBytes f905ec80f905e8f846942a7cdd959bfe8d9487b2a43b33565295a698f7e294b6a7edd747c0554875d3fc531d19ba1497992c5e941ff80f3f7f110ffd8920a3ac38fdef318fe94a3f86048c27395000f846946488aa4d1955ee33403f8ccb1d4de5fb97c7ade294220f003d8bdfaadf52aa1e55ae4cc485e6794875941a87e90e440a39c99aa9cb5cea0ad6a3f0b2407b86048c27395000f846949ef9f4360c606c7ab4db26b016007d3ad0ab86a0946103af86a874b705854033438383c82575f25bc29418e2db06cbff3e3c5f856410a1838649e760175786048c27395000f84694ee01c3b1283aa067c58eab4709f85e99d46de5fe94ee4b9bfb1871c64e2bcabb1dc382dc8b7c4218a29415904ab26ab0e99d70b51c220ccdcccabee6e29786048c27395000f84694685b1ded8013785d6623cc18d214320b6bb6475994a20ef4e5e4e7e36258dbf51f4d905114cb1b34bc9413e39085dc88704f4394d35209a02b1a9520320c86048c27395000f8469478f3adfc719c99674c072166708589033e2d9afe9448a30d5eaa7b64492a160f139e2da2800ec3834e94055838358c29edf4dcc1ba1985ad58aedbb6be2b86048c27395000f84694c2be4ec20253b8642161bc3f444f53679c1f3d479466f50c616d737e60d7ca6311ff0d9c434197898a94d1d678a2506eeaa365056fe565df8bc8659f28b086048c27395000f846942f7be8361c80a4c1e7e9aaf001d0877f1cfde218945f93992ac37f3e61db2ef8a587a436a161fd210b94ecbc4fb1a97861344dad0867ca3cba2b860411f086048c27395000f84694ce2fd7544e0b2cc94692d4a704debef7bcb613289444abc67b4b2fba283c582387f54c9cba7c34bafa948acc2ab395ded08bb75ce85bf0f95ad2abc51ad586048c27395000f84694b8f7166496996a7da21cf1f1b04d9b3e26a3d077946770572763289aac606e4f327c2f6cc1aa3b3e3b94882d745ed97d4422ca8da1c22ec49d880c4c097286048c27395000f846942d4c407bbe49438ed859fe965b140dcf1aab71a9943ad0939e120f33518fbba04631afe7a3ed6327b194b2bbb170ca4e499a2b0f3cc85ebfa6e8c4dfcbea86048c27395000f846946bbad7cf34b5fa511d8e963dbba288b1960e75d694853b0f6c324d1f4e76c8266942337ac1b0af1a229442498946a51ca5924552ead6fc2af08b94fcba648601d1a94a2000f846944430b3230294d12c6ab2aac5c2cd68e80b16b581947b107f4976a252a6939b771202c28e64e03f52d694795811a7f214084116949fc4f53cedbf189eeab28601d1a94a2000f84694ea0a6e3c511bbd10f4519ece37dc24887e11b55d946811ca77acfb221a49393c193f3a22db829fcc8e9464feb7c04830dd9ace164fc5c52b3f5a29e5018a8601d1a94a2000f846947ae2f5b9e386cd1b50a4550696d957cb4900f03a94e83bcc5077e6b873995c24bac871b5ad856047e19464e48d4057a90b233e026c1041e6012ada897fe88601d1a94a2000f8469482012708dafc9e1b880fd083b32182b869be8e09948e5adc73a2d233a1b496ed3115464dd6c7b887509428b383d324bc9a37f4e276190796ba5a8947f5ed8601d1a94a2000f8469422b81f8e175ffde54d797fe11eb03f9e3bf75f1d94a1c3ef7ca38d8ba80cce3bfc53ebd2903ed21658942767f7447f7b9b70313d4147b795414aecea54718601d1a94a2000f8469468bf0b8b6fb4e317a0f9d6f03eaf8ce6675bc60d94675cfe570b7902623f47e7f59c9664b5f5065dcf94d84f0d2e50bcf00f2fc476e1c57f5ca2d57f625b8601d1a94a2000f846948c4d90829ce8f72d0163c1d5cf348a862d5506309485c42a7b34309bee2ed6a235f86d16f059deec5894cc2cedc53f0fa6d376336efb67e43d167169f3b78601d1a94a2000f8469435e7a025f4da968de7e4d7e4004197917f4070f194b1182abaeeb3b4d8eba7e6a4162eac7ace23d57394c4fd0d870da52e73de2dd8ded19fe3d26f43a1138601d1a94a2000f84694d6caa02bbebaebb5d7e581e4b66559e635f805ff94c07335cf083c1c46a487f0325769d88e163b653694efaff03b42e41f953a925fc43720e45fb61a19938601d1a94a2000", - "generate-testnet": "node generate-genesis.js --chainid 97 --bscChainId 0061 --initBurnRatio 1000 --initConsensusStateBytes 42696e616e63652d436861696e2d47616e67657300000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000000aea1ac326886b992a991d21a6eb155f41b77867cbf659e78f31d89d8205122a84d1be64f0e9a466c2e66a53433928192783e29f8fa21beb2133499b5ef770f60000000e8d4a5100099308aa365c40554bc89982af505d85da95251445d5dd4a9bb37dd2584fd92d3000000e8d4a5100001776920ff0b0f38d78cf95c033c21adf7045785114e392a7544179652e0a612000000e8d4a51000 --initValidatorSetBytes f901a880f901a4f844941284214b9b9c85549ab3d2b972df0deef66ac2c9946ddf42a51534fc98d0c0a3b42c963cace8441ddf946ddf42a51534fc98d0c0a3b42c963cace8441ddf8410000000f84494a2959d3f95eae5dc7d70144ce1b73b403b7eb6e0948081ef03f1d9e0bb4a5bf38f16285c879299f07f948081ef03f1d9e0bb4a5bf38f16285c879299f07f8410000000f8449435552c16704d214347f29fa77f77da6d75d7c75294dc4973e838e3949c77aced16ac2315dc2d7ab11194dc4973e838e3949c77aced16ac2315dc2d7ab1118410000000f84494980a75ecd1309ea12fa2ed87a8744fbfc9b863d594cc6ac05c95a99c1f7b5f88de0e3486c82293b27094cc6ac05c95a99c1f7b5f88de0e3486c82293b2708410000000f84494f474cf03cceff28abc65c9cbae594f725c80e12d94e61a183325a18a173319dd8e19c8d069459e217594e61a183325a18a173319dd8e19c8d069459e21758410000000f84494b71b214cb885500844365e95cd9942c7276e7fd894d22ca3ba2141d23adab65ce4940eb7665ea2b6a794d22ca3ba2141d23adab65ce4940eb7665ea2b6a78410000000" + "generate-testnet": "node generate-genesis.js --chainid 97 --bscChainId 0061 --initBurnRatio 1000 --initConsensusStateBytes 42696e616e63652d436861696e2d47616e67657300000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000000aea1ac326886b992a991d21a6eb155f41b77867cbf659e78f31d89d8205122a84d1be64f0e9a466c2e66a53433928192783e29f8fa21beb2133499b5ef770f60000000e8d4a5100099308aa365c40554bc89982af505d85da95251445d5dd4a9bb37dd2584fd92d3000000e8d4a5100001776920ff0b0f38d78cf95c033c21adf7045785114e392a7544179652e0a612000000e8d4a51000 --initValidatorSetBytes f901a880f901a4f844941284214b9b9c85549ab3d2b972df0deef66ac2c9946ddf42a51534fc98d0c0a3b42c963cace8441ddf946ddf42a51534fc98d0c0a3b42c963cace8441ddf8410000000f84494a2959d3f95eae5dc7d70144ce1b73b403b7eb6e0948081ef03f1d9e0bb4a5bf38f16285c879299f07f948081ef03f1d9e0bb4a5bf38f16285c879299f07f8410000000f8449435552c16704d214347f29fa77f77da6d75d7c75294dc4973e838e3949c77aced16ac2315dc2d7ab11194dc4973e838e3949c77aced16ac2315dc2d7ab1118410000000f84494980a75ecd1309ea12fa2ed87a8744fbfc9b863d594cc6ac05c95a99c1f7b5f88de0e3486c82293b27094cc6ac05c95a99c1f7b5f88de0e3486c82293b2708410000000f84494f474cf03cceff28abc65c9cbae594f725c80e12d94e61a183325a18a173319dd8e19c8d069459e217594e61a183325a18a173319dd8e19c8d069459e21758410000000f84494b71b214cb885500844365e95cd9942c7276e7fd894d22ca3ba2141d23adab65ce4940eb7665ea2b6a794d22ca3ba2141d23adab65ce4940eb7665ea2b6a78410000000", + "generate-QA": "node generate-genesis.js --chainid 714 --bscChainId 02ca --initBurnRatio 1000 --initConsensusStateBytes 42696e616e63652d436861696e2d47616e67657300000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000000aea1ac326886b992a991d21a6eb155f41b77867cbf659e78f31d89d8205122a84d1be64f0e9a466c2e66a53433928192783e29f8fa21beb2133499b5ef770f60000000e8d4a5100099308aa365c40554bc89982af505d85da95251445d5dd4a9bb37dd2584fd92d3000000e8d4a5100001776920ff0b0f38d78cf95c033c21adf7045785114e392a7544179652e0a612000000e8d4a51000 --initValidatorSetBytes f901a880f901a4f844941284214b9b9c85549ab3d2b972df0deef66ac2c9946ddf42a51534fc98d0c0a3b42c963cace8441ddf946ddf42a51534fc98d0c0a3b42c963cace8441ddf8410000000f84494a2959d3f95eae5dc7d70144ce1b73b403b7eb6e0948081ef03f1d9e0bb4a5bf38f16285c879299f07f948081ef03f1d9e0bb4a5bf38f16285c879299f07f8410000000f8449435552c16704d214347f29fa77f77da6d75d7c75294dc4973e838e3949c77aced16ac2315dc2d7ab11194dc4973e838e3949c77aced16ac2315dc2d7ab1118410000000f84494980a75ecd1309ea12fa2ed87a8744fbfc9b863d594cc6ac05c95a99c1f7b5f88de0e3486c82293b27094cc6ac05c95a99c1f7b5f88de0e3486c82293b2708410000000f84494f474cf03cceff28abc65c9cbae594f725c80e12d94e61a183325a18a173319dd8e19c8d069459e217594e61a183325a18a173319dd8e19c8d069459e21758410000000f84494b71b214cb885500844365e95cd9942c7276e7fd894d22ca3ba2141d23adab65ce4940eb7665ea2b6a794d22ca3ba2141d23adab65ce4940eb7665ea2b6a78410000000" }, "author": "", "license": "MIT", diff --git a/test/test-maintenance/BSCValidatorSet.spec.ts b/test/test-maintenance/BSCValidatorSet.spec.ts index 0d70e488..0c9fc134 100644 --- a/test/test-maintenance/BSCValidatorSet.spec.ts +++ b/test/test-maintenance/BSCValidatorSet.spec.ts @@ -644,4 +644,141 @@ describe('BSCValidatorSet', () => { expect(await validatorSet.getMaintainingValidators()).to.deep.eq([]); }); + + + it('common case 2-1 update params', async () => { + await waitTx( + govHub.updateContractAddr( + instances[10].address, + instances[8].address, + instances[3].address, + instances[4].address, + instances[5].address, + instances[0].address, + instances[7].address, + instances[9].address, + instances[6].address, + instances[2].address + ) + ); + + // set maxNumOfMaintaining to 5 + let govChannelSeq = await crosschain.channelReceiveSequenceMap(GOV_CHANNEL_ID); + maxNumOfMaintaining = 18; + let govValue = '0x0000000000000000000000000000000000000000000000000000000000000012'; // 18 + let govPackageBytes = serializeGovPack('maxNumOfMaintaining', govValue, validatorSet.address); + await crosschain + .connect(operator) + .handlePackage( + Buffer.concat([buildSyncPackagePrefix(2e16), govPackageBytes]), + proof, + merkleHeight, + govChannelSeq, + GOV_CHANNEL_ID + ); + expect(await validatorSet.maxNumOfMaintaining()).to.be.eq(BigNumber.from(govValue)); + + // set maintainSlashScale to 2 + govChannelSeq = await crosschain.channelReceiveSequenceMap(GOV_CHANNEL_ID); + maintainSlashScale = 1; + govValue = '0x0000000000000000000000000000000000000000000000000000000000000001'; // 1 + govPackageBytes = serializeGovPack('maintainSlashScale', govValue, validatorSet.address); + await crosschain + .connect(operator) + .handlePackage( + Buffer.concat([buildSyncPackagePrefix(2e16), govPackageBytes]), + proof, + merkleHeight, + govChannelSeq, + GOV_CHANNEL_ID + ); + expect(await validatorSet.maintainSlashScale()).to.be.eq(BigNumber.from(govValue)); + expect(await validatorSet.numOfMaintaining()).to.be.eq(0); + }); + + + it('common case 2-2: validator 7 ~ 10 enterMaintenance', async () => { + await setSlashIndicator(slashIndicator.address, validatorSet, instances); + + for (let i = 7; i < 10; i++) { + await waitTx(validatorSet.connect(signers[i]).enterMaintenance()); + } + + const expectedMaintainingValidators = [] + + for (let i = 7; i < 10; i++) { + const index = await validatorSet.getCurrentValidatorIndex(validators[i]); + const validatorExtra = await validatorSet.validatorExtraSet(index); + expect(validatorExtra.isMaintaining).to.be.eq(true); + expect(validatorExtra.enterMaintenanceHeight.toNumber() > 0).to.be.eq(true); + expectedMaintainingValidators.push(validators[i]); + } + + + expect(await validatorSet.getMaintainingValidators()).to.deep.eq(expectedMaintainingValidators); + expect(await validatorSet.numOfMaintaining()).to.be.eq(3); + + const felonyThreshold = (await slashIndicator.felonyThreshold()).toNumber(); + await mineBlocks( 4 * felonyThreshold * maintainSlashScale / 2); + }); + + + it('common case 2-3: validator 10 ~ 21 enterMaintenance', async () => { + await setSlashIndicator(slashIndicator.address, validatorSet, instances); + + for (let i = 10; i < 22; i++) { + await waitTx(validatorSet.connect(signers[i]).enterMaintenance()); + } + + const expectedMaintainingValidators = [] + for (let i = 7; i < 10; i++) { + expectedMaintainingValidators.push(validators[i]); + } + + for (let i = 10; i < 22; i++) { + const index = await validatorSet.getCurrentValidatorIndex(validators[i]); + const validatorExtra = await validatorSet.validatorExtraSet(index); + expect(validatorExtra.isMaintaining).to.be.eq(true); + expect(validatorExtra.enterMaintenanceHeight.toNumber() > 0).to.be.eq(true); + expectedMaintainingValidators.push(validators[i]); + } + + expect(await validatorSet.getMaintainingValidators()).to.deep.eq(expectedMaintainingValidators); + expect(await validatorSet.numOfMaintaining()).to.be.eq(15); + + const felonyThreshold = (await slashIndicator.felonyThreshold()).toNumber(); + await mineBlocks( 4 * felonyThreshold * maintainSlashScale / 2 + 1); + }); + + it('common case 2-4: update validator set', async () => { + await waitTx( + validatorSet.updateContractAddr( + instances[10].address, + instances[8].address, + instances[3].address, + instances[4].address, + instances[5].address, + instances[0].address, + instances[7].address, + instances[9].address, + instances[6].address, + operator.address + ) + ); + + // do update validators + let packageBytes = validatorUpdateRlpEncode( + validators.slice(5, 26), + validators.slice(5, 26), + validators.slice(5, 26), + ); + await waitTx(validatorSet.connect(operator).handleSynPackage(STAKE_CHANNEL_ID, packageBytes)); + + // validator 7 ~ 9 will be felony, their slashCount = 4 * felonyThreshold * maintainSlashScale / workingValidatorCount(4) + const expectedValidators: string[] = [validators[5], validators[6]].concat(validators.slice(10, 26)); + + expect(await validatorSet.getValidators()).to.deep.eq(expectedValidators); + expect(await validatorSet.numOfMaintaining()).to.be.eq(0); + }); + });