From 34f3abc3e45a265de5ecdd882e3d891fa6880f04 Mon Sep 17 00:00:00 2001 From: dean65 Date: Thu, 24 Feb 2022 12:42:04 +0800 Subject: [PATCH 1/8] implement bep-131 --- contracts/BSCValidatorSet.sol | 61 ++++++++++++++++++++++- contracts/BSCValidatorSet.template | 61 ++++++++++++++++++++++- test/BSCValidatorSet.js | 80 ++++++++++++++++++++++++++++++ 3 files changed, 200 insertions(+), 2 deletions(-) diff --git a/contracts/BSCValidatorSet.sol b/contracts/BSCValidatorSet.sol index 958f3917..448a4fff 100644 --- a/contracts/BSCValidatorSet.sol +++ b/contracts/BSCValidatorSet.sol @@ -39,6 +39,8 @@ contract BSCValidatorSet is IBSCValidatorSet, System, IParamSubscriber, IApplica uint32 public constant ERROR_LEN_OF_VAL_MISMATCH = 103; uint32 public constant ERROR_RELAYFEE_TOO_LARGE = 104; + uint256 public constant INIT_NUM_OF_CABINETS = 21; + uint256 public constant EPOCH = 200; /*********************** state of the contract **************************/ Validator[] public currentValidatorSet; @@ -67,6 +69,10 @@ contract BSCValidatorSet is IBSCValidatorSet, System, IParamSubscriber, IApplica // Corresponds strictly to currentValidatorSet // validatorExtraSet[index] = the `ValidatorExtra` info of currentValidatorSet[index] ValidatorExtra[] public validatorExtraSet; + // BEP-131 candidate validator + uint256 public numOfCabinets; + uint256 public maxNumOfCandidates; + uint256 public maxNumOfWorkingCandidates; struct Validator{ address consensusAddress; @@ -354,7 +360,44 @@ contract BSCValidatorSet is IBSCValidatorSet, System, IParamSubscriber, IApplica return CODE_OK; } - function getValidators() public view returns (address[] memory) { + function shuffle(address[] memory validators, uint256 epochNumber, uint startIdx, uint offset, uint limit, uint modNumber) internal pure { + for (uint i = 0; i 0, "the maintainSlashScale must be greater than 0"); maintainSlashScale = newMaintainSlashScale; + } else if (Memory.compareStrings(key, "maxNumOfWorkingCandidates")) { + require(value.length == 32, "length of maxNumOfWorkingCandidates mismatch"); + uint256 newMaxNumOfWorkingCandidates = BytesToTypes.bytesToUint256(32, value); + require(newMaxNumOfWorkingCandidates >= 0, "the maxNumOfWorkingCandidates must be not less than 0"); + require(newMaxNumOfWorkingCandidates <= maxNumOfCandidates, "the maxNumOfWorkingCandidates must be not greater than maxNumOfCandidates"); + maxNumOfWorkingCandidates = newMaxNumOfWorkingCandidates; + } else if (Memory.compareStrings(key, "maxNumOfCandidates")) { + require(value.length == 32, "length of maxNumOfCandidates mismatch"); + uint256 newMaxNumOfCandidates = BytesToTypes.bytesToUint256(32, value); + require(newMaxNumOfCandidates >= 0, "the maxNumOfCandidates must be not less than 0"); + maxNumOfCandidates = newMaxNumOfCandidates; + } else if (Memory.compareStrings(key, "numOfCabinets")) { + require(value.length == 32, "length of numOfCabinets mismatch"); + uint256 newNumOfCabinets = BytesToTypes.bytesToUint256(32, value); + require(newNumOfCabinets > 0, "the numOfCabinets must be greater than 0"); + numOfCabinets = newNumOfCabinets; } else { require(false, "unknown param"); } diff --git a/contracts/BSCValidatorSet.template b/contracts/BSCValidatorSet.template index 237c9fa6..066a50e8 100644 --- a/contracts/BSCValidatorSet.template +++ b/contracts/BSCValidatorSet.template @@ -39,6 +39,8 @@ contract BSCValidatorSet is IBSCValidatorSet, System, IParamSubscriber, IApplica uint32 public constant ERROR_LEN_OF_VAL_MISMATCH = 103; uint32 public constant ERROR_RELAYFEE_TOO_LARGE = 104; + uint256 public constant INIT_NUM_OF_CABINETS = 21; + uint256 public constant EPOCH = 200; /*********************** state of the contract **************************/ Validator[] public currentValidatorSet; @@ -67,6 +69,10 @@ contract BSCValidatorSet is IBSCValidatorSet, System, IParamSubscriber, IApplica // Corresponds strictly to currentValidatorSet // validatorExtraSet[index] = the `ValidatorExtra` info of currentValidatorSet[index] ValidatorExtra[] public validatorExtraSet; + // BEP-131 candidate validator + uint256 public numOfCabinets; + uint256 public maxNumOfCandidates; + uint256 public maxNumOfWorkingCandidates; struct Validator{ address consensusAddress; @@ -354,7 +360,44 @@ contract BSCValidatorSet is IBSCValidatorSet, System, IParamSubscriber, IApplica return CODE_OK; } - function getValidators() public view returns (address[] memory) { + function shuffle(address[] memory validators, uint256 epochNumber, uint startIdx, uint offset, uint limit, uint modNumber) internal pure { + for (uint i = 0; i 0, "the maintainSlashScale must be greater than 0"); maintainSlashScale = newMaintainSlashScale; + } else if (Memory.compareStrings(key, "maxNumOfWorkingCandidates")) { + require(value.length == 32, "length of maxNumOfWorkingCandidates mismatch"); + uint256 newMaxNumOfWorkingCandidates = BytesToTypes.bytesToUint256(32, value); + require(newMaxNumOfWorkingCandidates >= 0, "the maxNumOfWorkingCandidates must be not less than 0"); + require(newMaxNumOfWorkingCandidates <= maxNumOfCandidates, "the maxNumOfWorkingCandidates must be not greater than maxNumOfCandidates"); + maxNumOfWorkingCandidates = newMaxNumOfWorkingCandidates; + } else if (Memory.compareStrings(key, "maxNumOfCandidates")) { + require(value.length == 32, "length of maxNumOfCandidates mismatch"); + uint256 newMaxNumOfCandidates = BytesToTypes.bytesToUint256(32, value); + require(newMaxNumOfCandidates >= 0, "the maxNumOfCandidates must be not less than 0"); + maxNumOfCandidates = newMaxNumOfCandidates; + } else if (Memory.compareStrings(key, "numOfCabinets")) { + require(value.length == 32, "length of numOfCabinets mismatch"); + uint256 newNumOfCabinets = BytesToTypes.bytesToUint256(32, value); + require(newNumOfCabinets > 0, "the numOfCabinets must be greater than 0"); + numOfCabinets = newNumOfCabinets; } else { require(false, "unknown param"); } diff --git a/test/BSCValidatorSet.js b/test/BSCValidatorSet.js index aed19f04..c307cd26 100644 --- a/test/BSCValidatorSet.js +++ b/test/BSCValidatorSet.js @@ -600,6 +600,86 @@ contract('BSCValidatorSet', (accounts) => { }); }); +contract('BSCValidatorSet', (accounts) => { + it('test set maxNumOfWorkingCandidates greater than maxNumOfCandidates', async () => { + const validatorSetInstance = await BSCValidatorSet.deployed(); + const relayer = accounts[2]; + const relayerInstance = await RelayerHub.deployed(); + await relayerInstance.register({from: relayer, value: 1e20}); + const crossChain = await CrossChain.deployed(); + const govHub = await GovHub.deployed(); + await govHub.updateContractAddr(BSCValidatorSet.address, SlashIndicator.address, SystemReward.address, LightClient.address, MockTokenHub.address, RelayerIncentivize.address, RelayerHub.address, GovHub.address, TokenManager.address, crossChain.address); + + // should fail + govChannelSeq = await crossChain.channelReceiveSequenceMap(GOV_CHANNEL_ID) + govValue = "0x0000000000000000000000000000000000000000000000000000000000000002";// 2; + govPackageBytes = serializeGovPack("maxNumOfWorkingCandidates", govValue, validatorSetInstance.address); + await crossChain.handlePackage(Buffer.concat([buildSyncPackagePrefix(2e16), (govPackageBytes)]), proof, merkleHeight, govChannelSeq, GOV_CHANNEL_ID, {from: relayer}); + except = await validatorSetInstance.maxNumOfWorkingCandidates.call(); + assert.equal(web3.utils.toBN(except).eq(web3.utils.toBN(0)), true, "wrong maxNumOfWorkingCandidates"); + }); +}); + +contract('BSCValidatorSet', (accounts) => { + it('test getMiningValidators with 41 validators', async () => { + const validatorSetInstance = await BSCValidatorSet.deployed(); + const relayer = accounts[2]; + const relayerInstance = await RelayerHub.deployed(); + await relayerInstance.register({from: relayer, value: 1e20}); + const crossChain = await CrossChain.deployed(); + const govHub = await GovHub.deployed(); + await govHub.updateContractAddr(BSCValidatorSet.address, SlashIndicator.address, SystemReward.address, LightClient.address, MockTokenHub.address, RelayerIncentivize.address, RelayerHub.address, GovHub.address, TokenManager.address, crossChain.address); + + let relayerAccount = accounts[8]; + let newValidators = []; + for (let i = 0; i < 41; i++) { + newValidators.push(web3.eth.accounts.create().address) + } + let packageBytes = validatorUpdateRlpEncode(newValidators, + newValidators, newValidators); + await validatorSetInstance.handleSynPackage(STAKE_CHANNEL_ID, packageBytes, {from: relayerAccount}); + + // set numOfCabinets to 21 + let govChannelSeq = await crossChain.channelReceiveSequenceMap(GOV_CHANNEL_ID); + let govValue = "0x0000000000000000000000000000000000000000000000000000000000000015";// 21; + let govPackageBytes = serializeGovPack("numOfCabinets", govValue, validatorSetInstance.address); + await crossChain.handlePackage(Buffer.concat([buildSyncPackagePrefix(2e16), (govPackageBytes)]), proof, merkleHeight, govChannelSeq, GOV_CHANNEL_ID, {from: relayer}); + + let except = await validatorSetInstance.numOfCabinets.call(); + assert.equal(web3.utils.toBN(except).eq(web3.utils.toBN(21)), true, "wrong numOfCabinets"); + + // set maxNumOfCandidates to 20 + govChannelSeq = await crossChain.channelReceiveSequenceMap(GOV_CHANNEL_ID) + govValue = "0x0000000000000000000000000000000000000000000000000000000000000014";// 20; + govPackageBytes = serializeGovPack("maxNumOfCandidates", govValue, validatorSetInstance.address); + await crossChain.handlePackage(Buffer.concat([buildSyncPackagePrefix(2e16), (govPackageBytes)]), proof, merkleHeight, govChannelSeq, GOV_CHANNEL_ID, {from: relayer}); + + // set maxNumOfWorkingCandidates to 2 + govChannelSeq = await crossChain.channelReceiveSequenceMap(GOV_CHANNEL_ID) + govValue = "0x0000000000000000000000000000000000000000000000000000000000000002";// 2; + govPackageBytes = serializeGovPack("maxNumOfWorkingCandidates", govValue, validatorSetInstance.address); + await crossChain.handlePackage(Buffer.concat([buildSyncPackagePrefix(2e16), (govPackageBytes)]), proof, merkleHeight, govChannelSeq, GOV_CHANNEL_ID, {from: relayer}); + + except = await validatorSetInstance.maxNumOfWorkingCandidates.call(); + assert.equal(web3.utils.toBN(except).eq(web3.utils.toBN(2)), true, "wrong maxNumOfWorkingCandidates"); + + let maxNumOfWorkingCandidates = 2; + let validators = await validatorSetInstance.getValidators.call(); + let numOfCabinets = 21; + + if ((validators.length - numOfCabinets) < maxNumOfWorkingCandidates){ + maxNumOfWorkingCandidates = validators.length - numOfCabinets; + } + + let miningValidators = await validatorSetInstance.getMiningValidators.call(); + let exceptValues = validators.slice(0,numOfCabinets); + for (var j=0;j Date: Thu, 24 Feb 2022 15:12:02 +0800 Subject: [PATCH 2/8] update ut --- test/BSCValidatorSet.js | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/test/BSCValidatorSet.js b/test/BSCValidatorSet.js index c307cd26..2d381508 100644 --- a/test/BSCValidatorSet.js +++ b/test/BSCValidatorSet.js @@ -648,12 +648,22 @@ contract('BSCValidatorSet', (accounts) => { let except = await validatorSetInstance.numOfCabinets.call(); assert.equal(web3.utils.toBN(except).eq(web3.utils.toBN(21)), true, "wrong numOfCabinets"); + // without candidate validators + let maxNumOfWorkingCandidates = 2; + let numOfCabinets = 21; + let validators = await validatorSetInstance.getValidators.call(); + let miningValidators = await validatorSetInstance.getMiningValidators.call(); + assert.deepEqual(validators.slice(0,numOfCabinets), miningValidators, "wrong validators"); + // set maxNumOfCandidates to 20 govChannelSeq = await crossChain.channelReceiveSequenceMap(GOV_CHANNEL_ID) govValue = "0x0000000000000000000000000000000000000000000000000000000000000014";// 20; govPackageBytes = serializeGovPack("maxNumOfCandidates", govValue, validatorSetInstance.address); await crossChain.handlePackage(Buffer.concat([buildSyncPackagePrefix(2e16), (govPackageBytes)]), proof, merkleHeight, govChannelSeq, GOV_CHANNEL_ID, {from: relayer}); + except = await validatorSetInstance.maxNumOfCandidates.call(); + assert.equal(web3.utils.toBN(except).eq(web3.utils.toBN(20)), true, "wrong maxNumOfCandidates"); + // set maxNumOfWorkingCandidates to 2 govChannelSeq = await crossChain.channelReceiveSequenceMap(GOV_CHANNEL_ID) govValue = "0x0000000000000000000000000000000000000000000000000000000000000002";// 2; @@ -663,18 +673,14 @@ contract('BSCValidatorSet', (accounts) => { except = await validatorSetInstance.maxNumOfWorkingCandidates.call(); assert.equal(web3.utils.toBN(except).eq(web3.utils.toBN(2)), true, "wrong maxNumOfWorkingCandidates"); - let maxNumOfWorkingCandidates = 2; - let validators = await validatorSetInstance.getValidators.call(); - let numOfCabinets = 21; - if ((validators.length - numOfCabinets) < maxNumOfWorkingCandidates){ maxNumOfWorkingCandidates = validators.length - numOfCabinets; } - let miningValidators = await validatorSetInstance.getMiningValidators.call(); + miningValidators = await validatorSetInstance.getMiningValidators.call(); let exceptValues = validators.slice(0,numOfCabinets); for (var j=0;j Date: Thu, 24 Feb 2022 15:43:35 +0800 Subject: [PATCH 3/8] update logic of felony function --- contracts/BSCValidatorSet.sol | 12 +++---- contracts/BSCValidatorSet.template | 11 +++--- test/Slash.js | 54 +++++++++++++++--------------- 3 files changed, 38 insertions(+), 39 deletions(-) diff --git a/contracts/BSCValidatorSet.sol b/contracts/BSCValidatorSet.sol index 448a4fff..463af4aa 100644 --- a/contracts/BSCValidatorSet.sol +++ b/contracts/BSCValidatorSet.sol @@ -679,13 +679,13 @@ contract BSCValidatorSet is IBSCValidatorSet, System, IParamSubscriber, IApplica // remove the validator from currentValidatorSet delete currentValidatorSetMap[validator]; - // It is ok that the validatorSet is not in order. - if (index != currentValidatorSet.length - 1) { - currentValidatorSet[index] = currentValidatorSet[currentValidatorSet.length - 1]; - validatorExtraSet[index] = validatorExtraSet[currentValidatorSet.length - 1]; - - currentValidatorSetMap[currentValidatorSet[index].consensusAddress] = index + 1; + // remove felony validator + for (uint i = index;i < (currentValidatorSet.length-1);i++) { + currentValidatorSet[i] = currentValidatorSet[i+1]; + validatorExtraSet[i] = validatorExtraSet[i+1]; + currentValidatorSetMap[currentValidatorSet[i].consensusAddress] = i+1; } + currentValidatorSet.pop(); validatorExtraSet.pop(); diff --git a/contracts/BSCValidatorSet.template b/contracts/BSCValidatorSet.template index 066a50e8..363c84c8 100644 --- a/contracts/BSCValidatorSet.template +++ b/contracts/BSCValidatorSet.template @@ -679,12 +679,11 @@ contract BSCValidatorSet is IBSCValidatorSet, System, IParamSubscriber, IApplica // remove the validator from currentValidatorSet delete currentValidatorSetMap[validator]; - // It is ok that the validatorSet is not in order. - if (index != currentValidatorSet.length - 1) { - currentValidatorSet[index] = currentValidatorSet[currentValidatorSet.length - 1]; - validatorExtraSet[index] = validatorExtraSet[currentValidatorSet.length - 1]; - - currentValidatorSetMap[currentValidatorSet[index].consensusAddress] = index + 1; + // remove felony validator + for (uint i = index;i < (currentValidatorSet.length-1);i++) { + currentValidatorSet[i] = currentValidatorSet[i+1]; + validatorExtraSet[i] = validatorExtraSet[i+1]; + currentValidatorSetMap[currentValidatorSet[i].consensusAddress] = i+1; } currentValidatorSet.pop(); validatorExtraSet.pop(); diff --git a/test/Slash.js b/test/Slash.js index 340009fe..9aaf2ac1 100644 --- a/test/Slash.js +++ b/test/Slash.js @@ -163,23 +163,23 @@ contract('felony SlashIndicator', (accounts) => { await validatorSetInstance.deposit(validator, {from: systemAccount, value: 2e18 }); amount = await validatorSetInstance.getIncoming.call(validator); - assert.equal(amount.toString(),web3.utils.toBN(2e18).toString()) + assert.equal(amount.toString(),web3.utils.toBN(2e18).toString(), "case1: incoming of account1 is wrong") for (let i =1; i<=150; i++){ await slashInstance.slash(validator, { from: systemAccount }); } let res= (await slashInstance.getSlashIndicator.call(validator)); - assert.equal(res[1].toNumber(),0); + assert.equal(res[1].toNumber(),0, "case1: slash indicator of account1 is wrong"); amount = await validatorSetInstance.getIncoming.call(validator); - assert.equal(amount.toNumber(),0); + assert.equal(amount.toNumber(),0, "case1: incoming of account1 is wrong"); amount = await validatorSetInstance.getIncoming.call(secondValidator); - assert.equal(amount.toString(),web3.utils.toBN(1e18).toString()); + assert.equal(amount.toString(),web3.utils.toBN(1e18).toString(), "case1: incoming of account2 is wrong"); amount = await validatorSetInstance.getIncoming.call(thirdValidator); - assert.equal(amount.toString(),web3.utils.toBN(1e18).toString()); - let consensusAddres = await validatorSetInstance.getValidators.call(); - assert.equal(consensusAddres.length,2); - assert.equal(consensusAddres[0],thirdValidator); - assert.equal(consensusAddres[1],secondValidator); + assert.equal(amount.toString(),web3.utils.toBN(1e18).toString(), "case1: incoming of account3 is wrong"); + let consensusAddress = await validatorSetInstance.getValidators.call(); + assert.equal(consensusAddress.length,2, "case1: length of validators should be 2"); + assert.equal(consensusAddress[0],secondValidator, "case1: index 0 of validators should be account2"); + assert.equal(consensusAddress[1],thirdValidator, "case1: index 1 of validators should be account3"); packageBytes = validatorUpdateRlpEncode([validator,secondValidator,thirdValidator], [validator,secondValidator,thirdValidator],[validator,secondValidator,thirdValidator]); @@ -187,23 +187,23 @@ contract('felony SlashIndicator', (accounts) => { await validatorSetInstance.deposit(secondValidator, {from: systemAccount, value: 2e18 }); amount = await validatorSetInstance.getIncoming.call(secondValidator); - assert.equal(amount.toString(),web3.utils.toBN(2e18).toString()) + assert.equal(amount.toString(),web3.utils.toBN(2e18).toString(), "case2: incoming of account2 is wrong") for (let i =1; i<=150; i++){ await slashInstance.slash(secondValidator, { from: systemAccount }); } res= (await slashInstance.getSlashIndicator.call(secondValidator)); - assert.equal(res[1].toNumber(),0); + assert.equal(res[1].toNumber(),0, "case2: slash indicator of account2 is wrong"); amount = await validatorSetInstance.getIncoming.call(secondValidator); - assert.equal(amount.toNumber(),0); + assert.equal(amount.toNumber(),0, "case2: incoming of account2 is wrong"); amount = await validatorSetInstance.getIncoming.call(validator); - assert.equal(amount.toString(),web3.utils.toBN(1e18).toString()); + assert.equal(amount.toString(),web3.utils.toBN(1e18).toString(), "case2: incoming of account1 is wrong"); amount = await validatorSetInstance.getIncoming.call(thirdValidator); - assert.equal(amount.toString(),web3.utils.toBN(1e18).toString()); - consensusAddres = await validatorSetInstance.getValidators.call(); - assert.equal(consensusAddres.length,2); - assert.equal(consensusAddres[0],validator); - assert.equal(consensusAddres[1],thirdValidator); + assert.equal(amount.toString(),web3.utils.toBN(1e18).toString(), "case2: incoming of account3 is wrong"); + consensusAddress = await validatorSetInstance.getValidators.call(); + assert.equal(consensusAddress.length,2, "case2: length of validators should be 2"); + assert.equal(consensusAddress[0],validator, "case2: index 0 of validators should be account1"); + assert.equal(consensusAddress[1],thirdValidator, "case2: index 1 of validators should be account3"); packageBytes = validatorUpdateRlpEncode([validator,secondValidator,thirdValidator], [validator,secondValidator,thirdValidator],[validator,secondValidator,thirdValidator]); @@ -211,23 +211,23 @@ contract('felony SlashIndicator', (accounts) => { await validatorSetInstance.deposit(thirdValidator, {from: systemAccount, value: 2e18 }); amount = await validatorSetInstance.getIncoming.call(thirdValidator); - assert.equal(amount.toString(),web3.utils.toBN(2e18).toString()) + assert.equal(amount.toString(),web3.utils.toBN(2e18).toString(), "case3: incoming of account3 is wrong") for (let i =1; i<=150; i++){ await slashInstance.slash(thirdValidator, { from: systemAccount }); } res= (await slashInstance.getSlashIndicator.call(thirdValidator)); - assert.equal(res[1].toNumber(),0); + assert.equal(res[1].toNumber(),0, "case3: slash indicator of account3 is wrong"); amount = await validatorSetInstance.getIncoming.call(thirdValidator); - assert.equal(amount.toNumber(),0); + assert.equal(amount.toNumber(),0, "case3: incoming of account3 is wrong"); amount = await validatorSetInstance.getIncoming.call(validator); - assert.equal(amount.toString(),web3.utils.toBN(1e18).toString()); + assert.equal(amount.toString(),web3.utils.toBN(1e18).toString(), "case3: incoming of account1 is wrong"); amount = await validatorSetInstance.getIncoming.call(secondValidator); - assert.equal(amount.toString(),web3.utils.toBN(1e18).toString()); - consensusAddres = await validatorSetInstance.getValidators.call(); - assert.equal(consensusAddres.length,2); - assert.equal(consensusAddres[0],validator); - assert.equal(consensusAddres[1],secondValidator); + assert.equal(amount.toString(),web3.utils.toBN(1e18).toString(), "case3: incoming of account2 is wrong"); + consensusAddress = await validatorSetInstance.getValidators.call(); + assert.equal(consensusAddress.length,2, "case3: length of validators should be 2"); + assert.equal(consensusAddress[0],validator, "case3: index 0 of validators should be account1"); + assert.equal(consensusAddress[1],secondValidator, "case3: index 0 of validators should be account2"); }); }); From bc746fd8a4819aef737c8141056ba77807bc0b00 Mon Sep 17 00:00:00 2001 From: dean65 Date: Thu, 24 Feb 2022 16:00:55 +0800 Subject: [PATCH 4/8] update the getMiningValidators function --- contracts/BSCValidatorSet.sol | 11 +++++++---- contracts/BSCValidatorSet.template | 10 ++++++---- 2 files changed, 13 insertions(+), 8 deletions(-) diff --git a/contracts/BSCValidatorSet.sol b/contracts/BSCValidatorSet.sol index 463af4aa..a2b0af66 100644 --- a/contracts/BSCValidatorSet.sol +++ b/contracts/BSCValidatorSet.sol @@ -386,10 +386,13 @@ contract BSCValidatorSet is IBSCValidatorSet, System, IParamSubscriber, IApplica if ((validators.length - _numOfCabinets) < _maxNumOfWorkingCandidates){ _maxNumOfWorkingCandidates = validators.length - _numOfCabinets; } - uint256 epochNumber = block.number & EPOCH; - shuffle(validators, epochNumber, _numOfCabinets-_maxNumOfWorkingCandidates, 0, _maxNumOfWorkingCandidates, _numOfCabinets); - shuffle(validators, epochNumber, _numOfCabinets-_maxNumOfWorkingCandidates, _numOfCabinets-_maxNumOfWorkingCandidates, - _maxNumOfWorkingCandidates, validators.length-_numOfCabinets+_maxNumOfWorkingCandidates); + if (_maxNumOfWorkingCandidates > 0) { + uint256 epochNumber = block.number & EPOCH; + shuffle(validators, epochNumber, _numOfCabinets-_maxNumOfWorkingCandidates, 0, _maxNumOfWorkingCandidates, _numOfCabinets); + shuffle(validators, epochNumber, _numOfCabinets-_maxNumOfWorkingCandidates, _numOfCabinets-_maxNumOfWorkingCandidates, + _maxNumOfWorkingCandidates, validators.length-_numOfCabinets+_maxNumOfWorkingCandidates); + } + address[] memory miningValidators = new address[](_numOfCabinets); for (uint i=0;i<_numOfCabinets;i++) { miningValidators[i] = validators[i]; diff --git a/contracts/BSCValidatorSet.template b/contracts/BSCValidatorSet.template index 363c84c8..5c7dd0d7 100644 --- a/contracts/BSCValidatorSet.template +++ b/contracts/BSCValidatorSet.template @@ -386,10 +386,12 @@ contract BSCValidatorSet is IBSCValidatorSet, System, IParamSubscriber, IApplica if ((validators.length - _numOfCabinets) < _maxNumOfWorkingCandidates){ _maxNumOfWorkingCandidates = validators.length - _numOfCabinets; } - uint256 epochNumber = block.number & EPOCH; - shuffle(validators, epochNumber, _numOfCabinets-_maxNumOfWorkingCandidates, 0, _maxNumOfWorkingCandidates, _numOfCabinets); - shuffle(validators, epochNumber, _numOfCabinets-_maxNumOfWorkingCandidates, _numOfCabinets-_maxNumOfWorkingCandidates, - _maxNumOfWorkingCandidates, validators.length-_numOfCabinets+_maxNumOfWorkingCandidates); + if (_maxNumOfWorkingCandidates > 0) { + uint256 epochNumber = block.number & EPOCH; + shuffle(validators, epochNumber, _numOfCabinets-_maxNumOfWorkingCandidates, 0, _maxNumOfWorkingCandidates, _numOfCabinets); + shuffle(validators, epochNumber, _numOfCabinets-_maxNumOfWorkingCandidates, _numOfCabinets-_maxNumOfWorkingCandidates, + _maxNumOfWorkingCandidates, validators.length-_numOfCabinets+_maxNumOfWorkingCandidates); + } address[] memory miningValidators = new address[](_numOfCabinets); for (uint i=0;i<_numOfCabinets;i++) { miningValidators[i] = validators[i]; From d53bcea9976eb4c91f830e30847962f8f6038a36 Mon Sep 17 00:00:00 2001 From: dean65 Date: Thu, 24 Feb 2022 16:31:48 +0800 Subject: [PATCH 5/8] update logic of the update maxNumOfCandidates function --- contracts/BSCValidatorSet.sol | 4 ++- contracts/BSCValidatorSet.template | 3 +++ test/BSCValidatorSet.js | 41 ++++++++++++++++++++++++++++++ 3 files changed, 47 insertions(+), 1 deletion(-) diff --git a/contracts/BSCValidatorSet.sol b/contracts/BSCValidatorSet.sol index a2b0af66..005e45e8 100644 --- a/contracts/BSCValidatorSet.sol +++ b/contracts/BSCValidatorSet.sol @@ -392,7 +392,6 @@ contract BSCValidatorSet is IBSCValidatorSet, System, IParamSubscriber, IApplica shuffle(validators, epochNumber, _numOfCabinets-_maxNumOfWorkingCandidates, _numOfCabinets-_maxNumOfWorkingCandidates, _maxNumOfWorkingCandidates, validators.length-_numOfCabinets+_maxNumOfWorkingCandidates); } - address[] memory miningValidators = new address[](_numOfCabinets); for (uint i=0;i<_numOfCabinets;i++) { miningValidators[i] = validators[i]; @@ -557,6 +556,9 @@ contract BSCValidatorSet is IBSCValidatorSet, System, IParamSubscriber, IApplica uint256 newMaxNumOfCandidates = BytesToTypes.bytesToUint256(32, value); require(newMaxNumOfCandidates >= 0, "the maxNumOfCandidates must be not less than 0"); maxNumOfCandidates = newMaxNumOfCandidates; + if (maxNumOfWorkingCandidates>maxNumOfCandidates) { + maxNumOfWorkingCandidates = maxNumOfCandidates; + } } else if (Memory.compareStrings(key, "numOfCabinets")) { require(value.length == 32, "length of numOfCabinets mismatch"); uint256 newNumOfCabinets = BytesToTypes.bytesToUint256(32, value); diff --git a/contracts/BSCValidatorSet.template b/contracts/BSCValidatorSet.template index 5c7dd0d7..f03b4536 100644 --- a/contracts/BSCValidatorSet.template +++ b/contracts/BSCValidatorSet.template @@ -556,6 +556,9 @@ contract BSCValidatorSet is IBSCValidatorSet, System, IParamSubscriber, IApplica uint256 newMaxNumOfCandidates = BytesToTypes.bytesToUint256(32, value); require(newMaxNumOfCandidates >= 0, "the maxNumOfCandidates must be not less than 0"); maxNumOfCandidates = newMaxNumOfCandidates; + if (maxNumOfWorkingCandidates > maxNumOfCandidates) { + maxNumOfWorkingCandidates = maxNumOfCandidates; + } } else if (Memory.compareStrings(key, "numOfCabinets")) { require(value.length == 32, "length of numOfCabinets mismatch"); uint256 newNumOfCabinets = BytesToTypes.bytesToUint256(32, value); diff --git a/test/BSCValidatorSet.js b/test/BSCValidatorSet.js index 2d381508..ff2c1d61 100644 --- a/test/BSCValidatorSet.js +++ b/test/BSCValidatorSet.js @@ -620,6 +620,47 @@ contract('BSCValidatorSet', (accounts) => { }); }); +contract('BSCValidatorSet', (accounts) => { + it('test set maxNumOfCandidates less than maxNumOfWorkingCandidates', async () => { + const validatorSetInstance = await BSCValidatorSet.deployed(); + const relayer = accounts[2]; + const relayerInstance = await RelayerHub.deployed(); + await relayerInstance.register({from: relayer, value: 1e20}); + const crossChain = await CrossChain.deployed(); + const govHub = await GovHub.deployed(); + await govHub.updateContractAddr(BSCValidatorSet.address, SlashIndicator.address, SystemReward.address, LightClient.address, MockTokenHub.address, RelayerIncentivize.address, RelayerHub.address, GovHub.address, TokenManager.address, crossChain.address); + + // set maxNumOfCandidates to 20 + govChannelSeq = await crossChain.channelReceiveSequenceMap(GOV_CHANNEL_ID) + govValue = "0x0000000000000000000000000000000000000000000000000000000000000014";// 20; + govPackageBytes = serializeGovPack("maxNumOfCandidates", govValue, validatorSetInstance.address); + await crossChain.handlePackage(Buffer.concat([buildSyncPackagePrefix(2e16), (govPackageBytes)]), proof, merkleHeight, govChannelSeq, GOV_CHANNEL_ID, {from: relayer}); + + except = await validatorSetInstance.maxNumOfCandidates.call(); + assert.equal(web3.utils.toBN(except).eq(web3.utils.toBN(20)), true, "wrong maxNumOfCandidates"); + + // set maxNumOfWorkingCandidates to 10 + govChannelSeq = await crossChain.channelReceiveSequenceMap(GOV_CHANNEL_ID) + govValue = "0x000000000000000000000000000000000000000000000000000000000000000A";// 10; + govPackageBytes = serializeGovPack("maxNumOfWorkingCandidates", govValue, validatorSetInstance.address); + await crossChain.handlePackage(Buffer.concat([buildSyncPackagePrefix(2e16), (govPackageBytes)]), proof, merkleHeight, govChannelSeq, GOV_CHANNEL_ID, {from: relayer}); + + except = await validatorSetInstance.maxNumOfWorkingCandidates.call(); + assert.equal(web3.utils.toBN(except).eq(web3.utils.toBN(10)), true, "wrong maxNumOfWorkingCandidates"); + + // set maxNumOfCandidates to 5 + govChannelSeq = await crossChain.channelReceiveSequenceMap(GOV_CHANNEL_ID) + govValue = "0x0000000000000000000000000000000000000000000000000000000000000005";// 5; + govPackageBytes = serializeGovPack("maxNumOfCandidates", govValue, validatorSetInstance.address); + await crossChain.handlePackage(Buffer.concat([buildSyncPackagePrefix(2e16), (govPackageBytes)]), proof, merkleHeight, govChannelSeq, GOV_CHANNEL_ID, {from: relayer}); + + except = await validatorSetInstance.maxNumOfCandidates.call(); + assert.equal(web3.utils.toBN(except).eq(web3.utils.toBN(5)), true, "wrong maxNumOfCandidates"); + except = await validatorSetInstance.maxNumOfWorkingCandidates.call(); + assert.equal(web3.utils.toBN(except).eq(web3.utils.toBN(5)), true, "wrong maxNumOfWorkingCandidates"); + }); +}); + contract('BSCValidatorSet', (accounts) => { it('test getMiningValidators with 41 validators', async () => { const validatorSetInstance = await BSCValidatorSet.deployed(); From 430151c17cadd7a2321b6dd6afc551513096b189 Mon Sep 17 00:00:00 2001 From: dean65 Date: Fri, 25 Feb 2022 10:06:19 +0800 Subject: [PATCH 6/8] fix calculating epoch number --- contracts/BSCValidatorSet.sol | 4 ++-- contracts/BSCValidatorSet.template | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/contracts/BSCValidatorSet.sol b/contracts/BSCValidatorSet.sol index 005e45e8..8b249d5e 100644 --- a/contracts/BSCValidatorSet.sol +++ b/contracts/BSCValidatorSet.sol @@ -387,7 +387,7 @@ contract BSCValidatorSet is IBSCValidatorSet, System, IParamSubscriber, IApplica _maxNumOfWorkingCandidates = validators.length - _numOfCabinets; } if (_maxNumOfWorkingCandidates > 0) { - uint256 epochNumber = block.number & EPOCH; + uint256 epochNumber = block.number / EPOCH; shuffle(validators, epochNumber, _numOfCabinets-_maxNumOfWorkingCandidates, 0, _maxNumOfWorkingCandidates, _numOfCabinets); shuffle(validators, epochNumber, _numOfCabinets-_maxNumOfWorkingCandidates, _numOfCabinets-_maxNumOfWorkingCandidates, _maxNumOfWorkingCandidates, validators.length-_numOfCabinets+_maxNumOfWorkingCandidates); @@ -556,7 +556,7 @@ contract BSCValidatorSet is IBSCValidatorSet, System, IParamSubscriber, IApplica uint256 newMaxNumOfCandidates = BytesToTypes.bytesToUint256(32, value); require(newMaxNumOfCandidates >= 0, "the maxNumOfCandidates must be not less than 0"); maxNumOfCandidates = newMaxNumOfCandidates; - if (maxNumOfWorkingCandidates>maxNumOfCandidates) { + if (maxNumOfWorkingCandidates > maxNumOfCandidates) { maxNumOfWorkingCandidates = maxNumOfCandidates; } } else if (Memory.compareStrings(key, "numOfCabinets")) { diff --git a/contracts/BSCValidatorSet.template b/contracts/BSCValidatorSet.template index f03b4536..e21518e6 100644 --- a/contracts/BSCValidatorSet.template +++ b/contracts/BSCValidatorSet.template @@ -387,7 +387,7 @@ contract BSCValidatorSet is IBSCValidatorSet, System, IParamSubscriber, IApplica _maxNumOfWorkingCandidates = validators.length - _numOfCabinets; } if (_maxNumOfWorkingCandidates > 0) { - uint256 epochNumber = block.number & EPOCH; + uint256 epochNumber = block.number / EPOCH; shuffle(validators, epochNumber, _numOfCabinets-_maxNumOfWorkingCandidates, 0, _maxNumOfWorkingCandidates, _numOfCabinets); shuffle(validators, epochNumber, _numOfCabinets-_maxNumOfWorkingCandidates, _numOfCabinets-_maxNumOfWorkingCandidates, _maxNumOfWorkingCandidates, validators.length-_numOfCabinets+_maxNumOfWorkingCandidates); From 0b6fba04cda8108e6a0ccd4e01c7e8df85071cc8 Mon Sep 17 00:00:00 2001 From: dean65 Date: Mon, 28 Feb 2022 13:46:00 +0800 Subject: [PATCH 7/8] update _exitMaintenance --- contracts/BSCValidatorSet.sol | 2 +- contracts/BSCValidatorSet.template | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/contracts/BSCValidatorSet.sol b/contracts/BSCValidatorSet.sol index 8b249d5e..6076d0d4 100644 --- a/contracts/BSCValidatorSet.sol +++ b/contracts/BSCValidatorSet.sol @@ -769,7 +769,7 @@ contract BSCValidatorSet is IBSCValidatorSet, System, IParamSubscriber, IApplica uint256 slashCount = block.number .sub(validatorExtraSet[index].enterMaintenanceHeight) - .div(workingValidatorCount) + .div(numOfCabinets) .div(maintainSlashScale); // step 2: clear maintaining info of the validator diff --git a/contracts/BSCValidatorSet.template b/contracts/BSCValidatorSet.template index e21518e6..ec250b7b 100644 --- a/contracts/BSCValidatorSet.template +++ b/contracts/BSCValidatorSet.template @@ -768,7 +768,7 @@ contract BSCValidatorSet is IBSCValidatorSet, System, IParamSubscriber, IApplica uint256 slashCount = block.number .sub(validatorExtraSet[index].enterMaintenanceHeight) - .div(workingValidatorCount) + .div(numOfCabinets) .div(maintainSlashScale); // step 2: clear maintaining info of the validator From c98f24c0d5c182892a43bd5ed6e370cdc0b111a1 Mon Sep 17 00:00:00 2001 From: dean65 Date: Tue, 1 Mar 2022 19:14:07 +0800 Subject: [PATCH 8/8] fix comments --- contracts/BSCValidatorSet.sol | 8 +++++--- contracts/BSCValidatorSet.template | 8 +++++--- test/BSCValidatorSet.js | 8 ++++++++ 3 files changed, 18 insertions(+), 6 deletions(-) diff --git a/contracts/BSCValidatorSet.sol b/contracts/BSCValidatorSet.sol index 6076d0d4..c84fc420 100644 --- a/contracts/BSCValidatorSet.sol +++ b/contracts/BSCValidatorSet.sol @@ -548,13 +548,11 @@ contract BSCValidatorSet is IBSCValidatorSet, System, IParamSubscriber, IApplica } else if (Memory.compareStrings(key, "maxNumOfWorkingCandidates")) { require(value.length == 32, "length of maxNumOfWorkingCandidates mismatch"); uint256 newMaxNumOfWorkingCandidates = BytesToTypes.bytesToUint256(32, value); - require(newMaxNumOfWorkingCandidates >= 0, "the maxNumOfWorkingCandidates must be not less than 0"); require(newMaxNumOfWorkingCandidates <= maxNumOfCandidates, "the maxNumOfWorkingCandidates must be not greater than maxNumOfCandidates"); maxNumOfWorkingCandidates = newMaxNumOfWorkingCandidates; } else if (Memory.compareStrings(key, "maxNumOfCandidates")) { require(value.length == 32, "length of maxNumOfCandidates mismatch"); uint256 newMaxNumOfCandidates = BytesToTypes.bytesToUint256(32, value); - require(newMaxNumOfCandidates >= 0, "the maxNumOfCandidates must be not less than 0"); maxNumOfCandidates = newMaxNumOfCandidates; if (maxNumOfWorkingCandidates > maxNumOfCandidates) { maxNumOfWorkingCandidates = maxNumOfCandidates; @@ -563,6 +561,7 @@ contract BSCValidatorSet is IBSCValidatorSet, System, IParamSubscriber, IApplica require(value.length == 32, "length of numOfCabinets mismatch"); uint256 newNumOfCabinets = BytesToTypes.bytesToUint256(32, value); require(newNumOfCabinets > 0, "the numOfCabinets must be greater than 0"); + require(newNumOfCabinets <= MAX_NUM_OF_VALIDATORS, "the numOfCabinets must be less than MAX_NUM_OF_VALIDATORS"); numOfCabinets = newNumOfCabinets; } else { require(false, "unknown param"); @@ -757,6 +756,9 @@ contract BSCValidatorSet is IBSCValidatorSet, System, IParamSubscriber, IApplica function _exitMaintenance(address validator, uint index) private returns (bool isFelony){ uint256 workingValidatorCount = getValidators().length; + if (workingValidatorCount>numOfCabinets) { + workingValidatorCount = numOfCabinets; + } if (maintainSlashScale == 0 || workingValidatorCount == 0 || numOfMaintaining == 0) { // should not happen, still protect return false; @@ -769,7 +771,7 @@ contract BSCValidatorSet is IBSCValidatorSet, System, IParamSubscriber, IApplica uint256 slashCount = block.number .sub(validatorExtraSet[index].enterMaintenanceHeight) - .div(numOfCabinets) + .div(workingValidatorCount) .div(maintainSlashScale); // step 2: clear maintaining info of the validator diff --git a/contracts/BSCValidatorSet.template b/contracts/BSCValidatorSet.template index ec250b7b..4bf64317 100644 --- a/contracts/BSCValidatorSet.template +++ b/contracts/BSCValidatorSet.template @@ -548,13 +548,11 @@ contract BSCValidatorSet is IBSCValidatorSet, System, IParamSubscriber, IApplica } else if (Memory.compareStrings(key, "maxNumOfWorkingCandidates")) { require(value.length == 32, "length of maxNumOfWorkingCandidates mismatch"); uint256 newMaxNumOfWorkingCandidates = BytesToTypes.bytesToUint256(32, value); - require(newMaxNumOfWorkingCandidates >= 0, "the maxNumOfWorkingCandidates must be not less than 0"); require(newMaxNumOfWorkingCandidates <= maxNumOfCandidates, "the maxNumOfWorkingCandidates must be not greater than maxNumOfCandidates"); maxNumOfWorkingCandidates = newMaxNumOfWorkingCandidates; } else if (Memory.compareStrings(key, "maxNumOfCandidates")) { require(value.length == 32, "length of maxNumOfCandidates mismatch"); uint256 newMaxNumOfCandidates = BytesToTypes.bytesToUint256(32, value); - require(newMaxNumOfCandidates >= 0, "the maxNumOfCandidates must be not less than 0"); maxNumOfCandidates = newMaxNumOfCandidates; if (maxNumOfWorkingCandidates > maxNumOfCandidates) { maxNumOfWorkingCandidates = maxNumOfCandidates; @@ -563,6 +561,7 @@ contract BSCValidatorSet is IBSCValidatorSet, System, IParamSubscriber, IApplica require(value.length == 32, "length of numOfCabinets mismatch"); uint256 newNumOfCabinets = BytesToTypes.bytesToUint256(32, value); require(newNumOfCabinets > 0, "the numOfCabinets must be greater than 0"); + require(newNumOfCabinets <= MAX_NUM_OF_VALIDATORS, "the numOfCabinets must be less than MAX_NUM_OF_VALIDATORS"); numOfCabinets = newNumOfCabinets; } else { require(false, "unknown param"); @@ -756,6 +755,9 @@ contract BSCValidatorSet is IBSCValidatorSet, System, IParamSubscriber, IApplica function _exitMaintenance(address validator, uint index) private returns (bool isFelony){ uint256 workingValidatorCount = getValidators().length; + if (workingValidatorCount > numOfCabinets) { + workingValidatorCount = numOfCabinets; + } if (maintainSlashScale == 0 || workingValidatorCount == 0 || numOfMaintaining == 0) { // should not happen, still protect return false; @@ -768,7 +770,7 @@ contract BSCValidatorSet is IBSCValidatorSet, System, IParamSubscriber, IApplica uint256 slashCount = block.number .sub(validatorExtraSet[index].enterMaintenanceHeight) - .div(numOfCabinets) + .div(workingValidatorCount) .div(maintainSlashScale); // step 2: clear maintaining info of the validator diff --git a/test/BSCValidatorSet.js b/test/BSCValidatorSet.js index ff2c1d61..f51c425e 100644 --- a/test/BSCValidatorSet.js +++ b/test/BSCValidatorSet.js @@ -720,8 +720,16 @@ contract('BSCValidatorSet', (accounts) => { miningValidators = await validatorSetInstance.getMiningValidators.call(); let exceptValues = validators.slice(0,numOfCabinets); + let outValidator = miningValidators.filter((addr)=>{ + return !exceptValues.includes(addr); + }); for (var j=0;j