diff --git a/contracts/BSCValidatorSet.sol b/contracts/BSCValidatorSet.sol index 4b9507a3..8002e52e 100644 --- a/contracts/BSCValidatorSet.sol +++ b/contracts/BSCValidatorSet.sol @@ -364,189 +364,56 @@ contract BSCValidatorSet is IBSCValidatorSet, System, IParamSubscriber, IApplica } } - function updateValidatorSet(Validator[] memory validatorSet, bytes[] memory voteAddrs) internal returns (uint32) { - { - // do verify. - if (validatorSet.length > MAX_NUM_OF_VALIDATORS) { - emit failReasonWithStr("the number of validators exceed the limit"); - return ERROR_FAIL_CHECK_VALIDATORS; - } - for (uint i; i= DUSTY_INCOMING) { - ++ crossSize; - } else if (currentValidatorSet[i].incoming != 0) { - ++ directSize; - } - } - - //cross transfer - address[] memory crossAddrs = new address[](crossSize); - uint256[] memory crossAmounts = new uint256[](crossSize); - uint256[] memory crossIndexes = new uint256[](crossSize); - address[] memory crossRefundAddrs = new address[](crossSize); - uint256 crossTotal; - // direct transfer - address payable[] memory directAddrs = new address payable[](directSize); - uint256[] memory directAmounts = new uint256[](directSize); - crossSize = 0; - directSize = 0; - uint256 relayFee = ITokenHub(TOKEN_HUB_ADDR).getMiniRelayFee(); - if (relayFee > DUSTY_INCOMING) { - emit failReasonWithStr("fee is larger than DUSTY_INCOMING"); - return ERROR_RELAYFEE_TOO_LARGE; - } - for (uint i; i < validatorsNum; ++i) { - if (isMigrated[i] == 1) { - if (currentValidatorSet[i].incoming != 0) { - directAddrs[directSize] = payable(currentValidatorSet[i].consensusAddress); - directAmounts[directSize] = currentValidatorSet[i].incoming; - isMigrated[directSize] = 1; // directSize must be less than i. so we can use directSize as index - ++directSize; - } - } else if (currentValidatorSet[i].incoming >= DUSTY_INCOMING) { - crossAddrs[crossSize] = currentValidatorSet[i].BBCFeeAddress; - uint256 value = currentValidatorSet[i].incoming - currentValidatorSet[i].incoming % PRECISION; - crossAmounts[crossSize] = value.sub(relayFee); - crossRefundAddrs[crossSize] = currentValidatorSet[i].feeAddress; - crossIndexes[crossSize] = i; - crossTotal = crossTotal.add(value); - ++crossSize; - } else if (currentValidatorSet[i].incoming != 0) { - directAddrs[directSize] = currentValidatorSet[i].feeAddress; - directAmounts[directSize] = currentValidatorSet[i].incoming; - isMigrated[directSize] = 0; - ++directSize; - } - } - - //step 2: do cross chain transfer - bool failCross = false; - if (crossTotal > 0) { - try ITokenHub(TOKEN_HUB_ADDR).batchTransferOutBNB{value : crossTotal}(crossAddrs, crossAmounts, crossRefundAddrs, uint64(block.timestamp + expireTimeSecondGap)) returns (bool success) { - if (success) { - emit batchTransfer(crossTotal); - } else { - emit batchTransferFailed(crossTotal, "batch transfer return false"); - } - }catch Error(string memory reason) { - failCross = true; - emit batchTransferFailed(crossTotal, reason); - }catch (bytes memory lowLevelData) { - failCross = true; - emit batchTransferLowerFailed(crossTotal, lowLevelData); - } - } - - if (failCross) { - for (uint i; i 0) { - for (uint i; i < directAddrs.length; ++i) { - if (isMigrated[i] == 1) { - IStakeHub(STAKE_HUB_ADDR).distributeReward{value : directAmounts[i]}(directAddrs[i]); - } else { - bool success = directAddrs[i].send(directAmounts[i]); - if (success) { - emit directTransfer(directAddrs[i], directAmounts[i]); - } else { - emit directTransferFail(directAddrs[i], directAmounts[i]); - } - } - } - } + function distributeFinalityReward(address[] calldata valAddrs, uint256[] calldata weights) external onlyCoinbase oncePerBlock onlyZeroGasPrice onlyInit { + uint256 totalValue; + uint256 balanceOfSystemReward = address(SYSTEM_REWARD_ADDR).balance; + if (balanceOfSystemReward > MAX_SYSTEM_REWARD_BALANCE) { + // when a slash happens, theres will no rewards in some epochs, + // it's tolerated because slash happens rarely + totalValue = balanceOfSystemReward.sub(MAX_SYSTEM_REWARD_BALANCE); + } else { + return; } - for (uint i; i < currentValidatorSet.length; ++i) { - if (currentValidatorSet[i].incoming != 0) { - currentValidatorSet[i].incoming = 0; - } + totalValue = ISystemReward(SYSTEM_REWARD_ADDR).claimRewards(payable(address(this)), totalValue); + if (totalValue == 0) { + return; } - // step 4: do dusk transfer - if (address(this).balance>0) { - emit systemTransfer(address(this).balance); - address(uint160(SYSTEM_REWARD_ADDR)).transfer(address(this).balance); + uint256 totalWeight; + for (uint256 i; i 0) { - doUpdateState(validatorSetTemp, voteAddrsTemp); + if (totalWeight == 0) { + return; } - // step 6: clean slash contract - ISlashIndicator(SLASH_CONTRACT_ADDR).clean(); - emit validatorSetUpdated(); - return CODE_OK; - } + uint256 value; + address valAddr; + uint256 index; - /** - * @dev With each epoch, there will be a partial rotation between cabinets and candidates. Rotation is determined by this function - */ - function shuffle(address[] memory validators, bytes[] memory voteAddrs, uint256 epochNumber, uint startIdx, uint offset, uint limit, uint modNumber) internal pure { - for (uint i; i 0) { + Validator storage validator = currentValidatorSet[index - 1]; + if (validator.jailed) { + emit deprecatedFinalityRewardDeposit(valAddr, value); + } else { + totalInComing = totalInComing.add(value); + validator.incoming = validator.incoming.add(value); + emit finalityRewardDeposit(valAddr, value); + } + } else { + // get incoming from deprecated validator; + emit deprecatedFinalityRewardDeposit(valAddr, value); } } + } + /*********************** View Functions **************************/ /** * @notice Return the vote address and consensus address of the validators in `currentValidatorSet` that are not jailed */ @@ -636,8 +503,20 @@ contract BSCValidatorSet is IBSCValidatorSet, System, IParamSubscriber, IApplica return consensusAddrs; } + /** + * @notice Return the current incoming of the validator + */ + function getIncoming(address validator) external view returns(uint256) { + uint256 index = currentValidatorSetMap[validator]; + if (index<=0) { + return 0; + } + return currentValidatorSet[index-1].incoming; + } + /** * @notice Return whether the validator is a working validator(not jailed or maintaining) by index + * Will return false if the validator is not in `currentValidatorSet` * * @param index The index of the validator in `currentValidatorSet`(from 0 to `currentValidatorSet.length-1`) */ @@ -654,17 +533,6 @@ contract BSCValidatorSet is IBSCValidatorSet, System, IParamSubscriber, IApplica return !currentValidatorSet[index].jailed && !validatorExtraSet[index].isMaintaining; } - /** - * @notice Return the current incoming of the validator - */ - function getIncoming(address validator)external view returns(uint256) { - uint256 index = currentValidatorSetMap[validator]; - if (index<=0) { - return 0; - } - return currentValidatorSet[index-1].incoming; - } - /** * @notice Return whether the validator is a working validator(not jailed or maintaining) by consensus address * Will return false if the validator is not in `currentValidatorSet` @@ -680,63 +548,25 @@ contract BSCValidatorSet is IBSCValidatorSet, System, IParamSubscriber, IApplica return isWorkingValidator(index); } - function distributeFinalityReward(address[] calldata valAddrs, uint256[] calldata weights) external onlyCoinbase oncePerBlock onlyZeroGasPrice onlyInit { - uint256 totalValue; - uint256 balanceOfSystemReward = address(SYSTEM_REWARD_ADDR).balance; - if (balanceOfSystemReward > MAX_SYSTEM_REWARD_BALANCE) { - // when a slash happens, theres will no rewards in some epochs, - // it's tolerated because slash happens rarely - totalValue = balanceOfSystemReward.sub(MAX_SYSTEM_REWARD_BALANCE); - } else { - return; - } - - totalValue = ISystemReward(SYSTEM_REWARD_ADDR).claimRewards(payable(address(this)), totalValue); - if (totalValue == 0) { - return; - } - - uint256 totalWeight; - for (uint256 i; i 0) { - Validator storage validator = currentValidatorSet[index - 1]; - if (validator.jailed) { - emit deprecatedFinalityRewardDeposit(valAddr, value); - } else { - totalInComing = totalInComing.add(value); - validator.incoming = validator.incoming.add(value); - emit finalityRewardDeposit(valAddr, value); - } - } else { - // get incoming from deprecated validator; - emit deprecatedFinalityRewardDeposit(valAddr, value); - } - } + /** + * @notice Return the index of the validator in `currentValidatorSet`(from 0 to `currentValidatorSet.length-1`) + */ + function getCurrentValidatorIndex(address validator) public view returns (uint256) { + uint256 index = currentValidatorSetMap[validator]; + require(index > 0, "only current validators"); + // the actual index + return index - 1; } - function getWorkingValidatorCount() public view returns(uint256 workingValidatorCount) { - workingValidatorCount = getValidators().length; + function getMiningValidatorCount() public view returns(uint256 miningValidatorCount) { + miningValidatorCount = getValidators().length; uint256 _numOfCabinets = numOfCabinets > 0 ? numOfCabinets : INIT_NUM_OF_CABINETS; - if (workingValidatorCount > _numOfCabinets) { - workingValidatorCount = _numOfCabinets; + if (miningValidatorCount > _numOfCabinets) { + miningValidatorCount = _numOfCabinets; } - if (workingValidatorCount == 0) { - workingValidatorCount = 1; + if (miningValidatorCount == 0) { + miningValidatorCount = 1; } } @@ -774,17 +604,6 @@ contract BSCValidatorSet is IBSCValidatorSet, System, IParamSubscriber, IApplica } /*********************** For Temporary Maintenance **************************/ - /** - * @notice Return the index of the validator in `currentValidatorSet`(from 0 to `currentValidatorSet.length-1`) - */ - function getCurrentValidatorIndex(address validator) public view returns (uint256) { - uint256 index = currentValidatorSetMap[validator]; - require(index > 0, "only current validators"); - - // the actual index - return index - 1; - } - /** * @notice Return whether the validator at index could enter maintenance */ @@ -833,8 +652,8 @@ contract BSCValidatorSet is IBSCValidatorSet, System, IParamSubscriber, IApplica // jailed validators are allowed to exit maintenance require(validatorExtraSet[index].isMaintaining, "not in maintenance"); - uint256 workingValidatorCount = getWorkingValidatorCount(); - _exitMaintenance(msg.sender, index, workingValidatorCount); + uint256 miningValidatorCount = getMiningValidatorCount(); + _exitMaintenance(msg.sender, index, miningValidatorCount); } /*********************** Param update ********************************/ @@ -893,6 +712,172 @@ contract BSCValidatorSet is IBSCValidatorSet, System, IParamSubscriber, IApplica } /*********************** Internal Functions **************************/ + function updateValidatorSet(Validator[] memory validatorSet, bytes[] memory voteAddrs) internal returns (uint32) { + { + // do verify. + if (validatorSet.length > MAX_NUM_OF_VALIDATORS) { + emit failReasonWithStr("the number of validators exceed the limit"); + return ERROR_FAIL_CHECK_VALIDATORS; + } + for (uint i; i= DUSTY_INCOMING) { + ++ crossSize; + } else if (currentValidatorSet[i].incoming != 0) { + ++ directSize; + } + } + + //cross transfer + address[] memory crossAddrs = new address[](crossSize); + uint256[] memory crossAmounts = new uint256[](crossSize); + uint256[] memory crossIndexes = new uint256[](crossSize); + address[] memory crossRefundAddrs = new address[](crossSize); + uint256 crossTotal; + // direct transfer + address payable[] memory directAddrs = new address payable[](directSize); + uint256[] memory directAmounts = new uint256[](directSize); + crossSize = 0; + directSize = 0; + uint256 relayFee = ITokenHub(TOKEN_HUB_ADDR).getMiniRelayFee(); + if (relayFee > DUSTY_INCOMING) { + emit failReasonWithStr("fee is larger than DUSTY_INCOMING"); + return ERROR_RELAYFEE_TOO_LARGE; + } + for (uint i; i < validatorsNum; ++i) { + if (isMigrated[i] == 1) { + if (currentValidatorSet[i].incoming != 0) { + directAddrs[directSize] = payable(currentValidatorSet[i].consensusAddress); + directAmounts[directSize] = currentValidatorSet[i].incoming; + isMigrated[directSize] = 1; // directSize must be less than i. so we can use directSize as index + ++directSize; + } + } else if (currentValidatorSet[i].incoming >= DUSTY_INCOMING) { + crossAddrs[crossSize] = currentValidatorSet[i].BBCFeeAddress; + uint256 value = currentValidatorSet[i].incoming - currentValidatorSet[i].incoming % PRECISION; + crossAmounts[crossSize] = value.sub(relayFee); + crossRefundAddrs[crossSize] = currentValidatorSet[i].feeAddress; + crossIndexes[crossSize] = i; + crossTotal = crossTotal.add(value); + ++crossSize; + } else if (currentValidatorSet[i].incoming != 0) { + directAddrs[directSize] = currentValidatorSet[i].feeAddress; + directAmounts[directSize] = currentValidatorSet[i].incoming; + isMigrated[directSize] = 0; + ++directSize; + } + } + + //step 2: do cross chain transfer + bool failCross = false; + if (crossTotal > 0) { + try ITokenHub(TOKEN_HUB_ADDR).batchTransferOutBNB{value : crossTotal}(crossAddrs, crossAmounts, crossRefundAddrs, uint64(block.timestamp + expireTimeSecondGap)) returns (bool success) { + if (success) { + emit batchTransfer(crossTotal); + } else { + emit batchTransferFailed(crossTotal, "batch transfer return false"); + } + }catch Error(string memory reason) { + failCross = true; + emit batchTransferFailed(crossTotal, reason); + }catch (bytes memory lowLevelData) { + failCross = true; + emit batchTransferLowerFailed(crossTotal, lowLevelData); + } + } + + if (failCross) { + for (uint i; i 0) { + for (uint i; i < directAddrs.length; ++i) { + if (isMigrated[i] == 1) { + IStakeHub(STAKE_HUB_ADDR).distributeReward{value : directAmounts[i]}(directAddrs[i]); + } else { + bool success = directAddrs[i].send(directAmounts[i]); + if (success) { + emit directTransfer(directAddrs[i], directAmounts[i]); + } else { + emit directTransferFail(directAddrs[i], directAmounts[i]); + } + } + } + } + } + + for (uint i; i < currentValidatorSet.length; ++i) { + if (currentValidatorSet[i].incoming != 0) { + currentValidatorSet[i].incoming = 0; + } + } + + // step 4: do dusk transfer + if (address(this).balance>0) { + emit systemTransfer(address(this).balance); + address(uint160(SYSTEM_REWARD_ADDR)).transfer(address(this).balance); + } + + // step 5: do update validator set state + totalInComing = 0; + numOfJailed = 0; + if (validatorSetTemp.length > 0) { + doUpdateState(validatorSetTemp, voteAddrsTemp); + } + + // step 6: clean slash contract + ISlashIndicator(SLASH_CONTRACT_ADDR).clean(); + emit validatorSetUpdated(); + return CODE_OK; + } + function doUpdateState(Validator[] memory newValidatorSet, bytes[] memory newVoteAddrs) private { uint n = currentValidatorSet.length; uint m = newValidatorSet.length; @@ -962,6 +947,23 @@ contract BSCValidatorSet is IBSCValidatorSet, System, IParamSubscriber, IApplica } } + /** + * @dev With each epoch, there will be a partial rotation between cabinets and candidates. Rotation is determined by this function + */ + function shuffle(address[] memory validators, bytes[] memory voteAddrs, uint256 epochNumber, uint startIdx, uint offset, uint limit, uint modNumber) internal pure { + for (uint i; i 0; --index) { @@ -1139,7 +1141,7 @@ contract BSCValidatorSet is IBSCValidatorSet, System, IParamSubscriber, IApplica validator = currentValidatorSet[i].consensusAddress; // exit maintenance - isFelony = _exitMaintenance(validator, i, workingValidatorCount); + isFelony = _exitMaintenance(validator, i, miningValidatorCount); if (!isFelony || numOfFelony >= _validatorSet.length - 1) { continue; } @@ -1176,8 +1178,8 @@ contract BSCValidatorSet is IBSCValidatorSet, System, IParamSubscriber, IApplica emit validatorEnterMaintenance(validator); } - function _exitMaintenance(address validator, uint index, uint256 workingValidatorCount) private returns (bool isFelony){ - if (maintainSlashScale == 0 || workingValidatorCount == 0 || numOfMaintaining == 0) { + function _exitMaintenance(address validator, uint index, uint256 miningValidatorCount) private returns (bool isFelony){ + if (maintainSlashScale == 0 || miningValidatorCount == 0 || numOfMaintaining == 0) { // should not happen, still protect return false; } @@ -1189,7 +1191,7 @@ contract BSCValidatorSet is IBSCValidatorSet, System, IParamSubscriber, IApplica uint256 slashCount = block.number .sub(validatorExtraSet[index].enterMaintenanceHeight) - .div(workingValidatorCount) + .div(miningValidatorCount) .div(maintainSlashScale); // step 2: clear maintaining info of the validator