diff --git a/abi/bscvalidatorset.abi b/abi/bscvalidatorset.abi index 9c9c265e6..88cf1f33d 100644 --- a/abi/bscvalidatorset.abi +++ b/abi/bscvalidatorset.abi @@ -912,6 +912,19 @@ ], "stateMutability": "view" }, + { + "type": "function", + "name": "getTurnTerm", + "inputs": [], + "outputs": [ + { + "name": "", + "type": "uint256", + "internalType": "uint256" + } + ], + "stateMutability": "view" + }, { "type": "function", "name": "getValidators", @@ -1239,7 +1252,20 @@ }, { "type": "function", - "name": "systemRewardRatio", + "name": "systemRewardAntiMEVRatio", + "inputs": [], + "outputs": [ + { + "name": "", + "type": "uint256", + "internalType": "uint256" + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "systemRewardBaseRatio", "inputs": [], "outputs": [ { @@ -1263,6 +1289,19 @@ ], "stateMutability": "view" }, + { + "type": "function", + "name": "turnTerm", + "inputs": [], + "outputs": [ + { + "name": "", + "type": "uint256", + "internalType": "uint256" + } + ], + "stateMutability": "view" + }, { "type": "function", "name": "updateParam", @@ -1684,4 +1723,4 @@ "inputs": [], "anonymous": false } -] \ No newline at end of file +] diff --git a/contracts/BSCValidatorSet.sol b/contracts/BSCValidatorSet.sol index 91cf59ba6..27ce1ebbf 100644 --- a/contracts/BSCValidatorSet.sol +++ b/contracts/BSCValidatorSet.sol @@ -84,7 +84,7 @@ contract BSCValidatorSet is IBSCValidatorSet, System, IParamSubscriber, IApplica uint256 public constant INIT_SYSTEM_REWARD_RATIO = 625; // 625/10000 is 1/16 uint256 public constant MAX_SYSTEM_REWARD_BALANCE = 100 ether; - uint256 public systemRewardRatio; + uint256 public systemRewardBaseRatio; uint256 public previousHeight; uint256 public previousBalanceOfSystemReward; // deprecated bytes[] public previousVoteAddrFullSet; @@ -95,6 +95,10 @@ contract BSCValidatorSet is IBSCValidatorSet, System, IParamSubscriber, IApplica Validator[] private _tmpMigratedValidatorSet; bytes[] private _tmpMigratedVoteAddrs; + // BEP-341 Validators can produce consecutive blocks + uint256 public turnTerm; + uint256 public systemRewardAntiMEVRatio; + struct Validator { address consensusAddress; address payable feeAddress; @@ -331,11 +335,16 @@ contract BSCValidatorSet is IBSCValidatorSet, System, IParamSubscriber, IApplica uint256 index = currentValidatorSetMap[valAddr]; if (isSystemRewardIncluded == false) { - systemRewardRatio = INIT_SYSTEM_REWARD_RATIO; + systemRewardBaseRatio = INIT_SYSTEM_REWARD_RATIO; burnRatio = INIT_BURN_RATIO; isSystemRewardIncluded = true; } + uint256 systemRewardRatio = systemRewardBaseRatio; + if (turnTerm > 1 && systemRewardAntiMEVRatio > 0) { + systemRewardRatio += systemRewardAntiMEVRatio * (block.number % turnTerm) / (turnTerm - 1); + } + if (value > 0 && systemRewardRatio > 0) { uint256 toSystemReward = msg.value.mul(systemRewardRatio).div(BLOCK_FEES_RATIO_SCALE); if (toSystemReward > 0) { @@ -697,8 +706,8 @@ contract BSCValidatorSet is IBSCValidatorSet, System, IParamSubscriber, IApplica require(value.length == 32, "length of burnRatio mismatch"); uint256 newBurnRatio = BytesToTypes.bytesToUint256(32, value); require( - newBurnRatio.add(systemRewardRatio) <= BLOCK_FEES_RATIO_SCALE, - "the burnRatio plus systemRewardRatio must be no greater than 10000" + newBurnRatio.add(systemRewardBaseRatio).add(systemRewardAntiMEVRatio) <= BLOCK_FEES_RATIO_SCALE, + "the burnRatio plus systemRewardBaseRatio and systemRewardAntiMEVRatio must be no greater than 10000" ); burnRatio = newBurnRatio; } else if (Memory.compareStrings(key, "maxNumOfMaintaining")) { @@ -741,14 +750,30 @@ contract BSCValidatorSet is IBSCValidatorSet, System, IParamSubscriber, IApplica newNumOfCabinets <= MAX_NUM_OF_VALIDATORS, "the numOfCabinets must be less than MAX_NUM_OF_VALIDATORS" ); numOfCabinets = newNumOfCabinets; - } else if (Memory.compareStrings(key, "systemRewardRatio")) { - require(value.length == 32, "length of systemRewardRatio mismatch"); - uint256 newSystemRewardRatio = BytesToTypes.bytesToUint256(32, value); + } else if (Memory.compareStrings(key, "systemRewardBaseRatio")) { + require(value.length == 32, "length of systemRewardBaseRatio mismatch"); + uint256 newSystemRewardBaseRatio = BytesToTypes.bytesToUint256(32, value); + require( + newSystemRewardBaseRatio.add(burnRatio).add(systemRewardAntiMEVRatio) <= BLOCK_FEES_RATIO_SCALE, + "the systemRewardBaseRatio plus burnRatio and systemRewardAntiMEVRatio must be no greater than 10000" + ); + systemRewardBaseRatio = newSystemRewardBaseRatio; + } else if (Memory.compareStrings(key, "systemRewardAntiMEVRatio")) { + require(value.length == 32, "length of systemRewardAntiMEVRatio mismatch"); + uint256 newSystemRewardAntiMEVRatio = BytesToTypes.bytesToUint256(32, value); require( - newSystemRewardRatio.add(burnRatio) <= BLOCK_FEES_RATIO_SCALE, - "the systemRewardRatio plus burnRatio must be no greater than 10000" + newSystemRewardAntiMEVRatio.add(burnRatio).add(systemRewardBaseRatio) <= BLOCK_FEES_RATIO_SCALE, + "the systemRewardAntiMEVRatio plus burnRatio and systemRewardBaseRatio must be no greater than 10000" ); - systemRewardRatio = newSystemRewardRatio; + systemRewardAntiMEVRatio = newSystemRewardAntiMEVRatio; + } else if (Memory.compareStrings(key, "turnTerm")) { + require(value.length == 32, "length of turnTerm mismatch"); + uint256 newTurnTerm = BytesToTypes.bytesToUint256(32, value); + require( + newTurnTerm >= 3 && newTurnTerm <= 9 || newTurnTerm == 1, + "the turnTerm should be in [3,9] or equal to 1" + ); + turnTerm = newTurnTerm; } else { require(false, "unknown param"); } @@ -1047,6 +1072,13 @@ contract BSCValidatorSet is IBSCValidatorSet, System, IParamSubscriber, IApplica return voteAddrs; } + function getTurnTerm() external view returns (uint256) { + if (turnTerm == 0) { + return 1; + } + return turnTerm; + } + function setPreviousVoteAddrFullSet() private { uint256 n = previousVoteAddrFullSet.length; uint256 m = currentVoteAddrFullSet.length; diff --git a/test/SlashIndicator.t.sol b/test/SlashIndicator.t.sol index 278a34bd8..56f2f6c9f 100644 --- a/test/SlashIndicator.t.sol +++ b/test/SlashIndicator.t.sol @@ -8,7 +8,7 @@ contract SlashIndicatorTest is Deployer { uint256 public burnRatio; uint256 public burnRatioScale; - uint256 public systemRewardRatio; + uint256 public systemRewardBaseRatio; uint256 public systemRewardRatioScale; address public coinbase; @@ -19,8 +19,8 @@ contract SlashIndicatorTest is Deployer { bscValidatorSet.isSystemRewardIncluded() ? bscValidatorSet.burnRatio() : bscValidatorSet.INIT_BURN_RATIO(); burnRatioScale = bscValidatorSet.BLOCK_FEES_RATIO_SCALE(); - systemRewardRatio = bscValidatorSet.isSystemRewardIncluded() - ? bscValidatorSet.systemRewardRatio() + systemRewardBaseRatio = bscValidatorSet.isSystemRewardIncluded() + ? bscValidatorSet.systemRewardBaseRatio() : bscValidatorSet.INIT_SYSTEM_REWARD_RATIO(); systemRewardRatioScale = bscValidatorSet.BLOCK_FEES_RATIO_SCALE(); @@ -356,7 +356,13 @@ contract SlashIndicatorTest is Deployer { } function _calcIncoming(uint256 value) internal view returns (uint256 incoming) { - uint256 toSystemReward = (value * systemRewardRatio) / systemRewardRatioScale; + uint256 turnTerm = bscValidatorSet.getTurnTerm(); + uint256 systemRewardAntiMEVRatio = bscValidatorSet.systemRewardAntiMEVRatio(); + uint256 systemRewardRatio = systemRewardBaseRatio; + if (turnTerm > 1 && systemRewardAntiMEVRatio > 0) { + systemRewardRatio += systemRewardAntiMEVRatio * (block.number % turnTerm) / (turnTerm - 1); + } + uint256 toSystemReward = (value * systemRewardBaseRatio) / systemRewardRatioScale; uint256 toBurn = (value * burnRatio) / burnRatioScale; incoming = value - toSystemReward - toBurn; } diff --git a/test/ValidatorSet.t.sol b/test/ValidatorSet.t.sol index b101a1141..a9f9a23d0 100644 --- a/test/ValidatorSet.t.sol +++ b/test/ValidatorSet.t.sol @@ -23,7 +23,7 @@ contract ValidatorSetTest is Deployer { uint256 public burnRatioScale; uint256 public maxNumOfWorkingCandidates; uint256 public numOfCabinets; - uint256 public systemRewardRatio; + uint256 public systemRewardBaseRatio; uint256 public systemRewardRatioScale; address public coinbase; @@ -42,8 +42,8 @@ contract ValidatorSetTest is Deployer { burnRatio = bscValidatorSet.isSystemRewardIncluded() ? bscValidatorSet.burnRatio() : bscValidatorSet.INIT_BURN_RATIO(); burnRatioScale = bscValidatorSet.BLOCK_FEES_RATIO_SCALE(); - systemRewardRatio = bscValidatorSet.isSystemRewardIncluded() - ? bscValidatorSet.systemRewardRatio() + systemRewardBaseRatio = bscValidatorSet.isSystemRewardIncluded() + ? bscValidatorSet.systemRewardBaseRatio() : bscValidatorSet.INIT_SYSTEM_REWARD_RATIO(); systemRewardRatioScale = bscValidatorSet.BLOCK_FEES_RATIO_SCALE(); totalInComing = bscValidatorSet.totalInComing(); @@ -71,17 +71,35 @@ contract ValidatorSetTest is Deployer { vm.expectRevert("deposit value is zero"); bscValidatorSet.deposit(validator0); - uint256 realAmount = _calcIncoming(amount); + uint256 realAmount0 = _calcIncoming(amount); vm.expectEmit(true, false, false, true, address(bscValidatorSet)); - emit validatorDeposit(validator0, realAmount); + emit validatorDeposit(validator0, realAmount0); + bscValidatorSet.deposit{ value: amount }(validator0); + + vm.stopPrank(); + assertEq(bscValidatorSet.getTurnTerm(), 1); + bytes memory key = "turnTerm"; + bytes memory value = bytes(hex"0000000000000000000000000000000000000000000000000000000000000005"); // 5 + _updateParamByGovHub(key, value, address(bscValidatorSet)); + assertEq(bscValidatorSet.getTurnTerm(), 5); + + key = "systemRewardAntiMEVRatio"; + value = bytes(hex"0000000000000000000000000000000000000000000000000000000000000200"); // 512 + _updateParamByGovHub(key, value, address(bscValidatorSet)); + assertEq(bscValidatorSet.systemRewardAntiMEVRatio(), 512); + vm.startPrank(coinbase); + + uint256 realAmount1 = _calcIncoming(amount); + vm.expectEmit(true, false, false, true, address(bscValidatorSet)); + emit validatorDeposit(validator0, realAmount1); bscValidatorSet.deposit{ value: amount }(validator0); address newAccount = _getNextUserAddress(); vm.expectEmit(true, false, false, true, address(bscValidatorSet)); - emit deprecatedDeposit(newAccount, realAmount); + emit deprecatedDeposit(newAccount, realAmount1); bscValidatorSet.deposit{ value: amount }(newAccount); - assertEq(bscValidatorSet.totalInComing(), totalInComing + realAmount); + assertEq(bscValidatorSet.totalInComing(), totalInComing + realAmount0 + realAmount1); vm.stopPrank(); } @@ -102,6 +120,11 @@ contract ValidatorSetTest is Deployer { _updateParamByGovHub(key, value, address(bscValidatorSet)); assertEq(bscValidatorSet.maxNumOfCandidates(), 5); assertEq(bscValidatorSet.maxNumOfWorkingCandidates(), 5); + + key = "systemRewardBaseRatio"; + value = bytes(hex"0000000000000000000000000000000000000000000000000000000000000400"); // 1024 + _updateParamByGovHub(key, value, address(bscValidatorSet)); + assertEq(bscValidatorSet.systemRewardBaseRatio(), 1024); } function testGetMiningValidatorsWith41Vals() public { @@ -615,6 +638,12 @@ contract ValidatorSetTest is Deployer { } function _calcIncoming(uint256 value) internal view returns (uint256 incoming) { + uint256 turnTerm = bscValidatorSet.getTurnTerm(); + uint256 systemRewardAntiMEVRatio = bscValidatorSet.systemRewardAntiMEVRatio(); + uint256 systemRewardRatio = systemRewardBaseRatio; + if (turnTerm > 1 && systemRewardAntiMEVRatio > 0) { + systemRewardRatio += systemRewardAntiMEVRatio * (block.number % turnTerm) / (turnTerm - 1); + } uint256 toSystemReward = (value * systemRewardRatio) / systemRewardRatioScale; uint256 toBurn = (value * burnRatio) / burnRatioScale; incoming = value - toSystemReward - toBurn; diff --git a/test/utils/interface/IBSCValidatorSet.sol b/test/utils/interface/IBSCValidatorSet.sol index 960be0788..669ddc63b 100644 --- a/test/utils/interface/IBSCValidatorSet.sol +++ b/test/utils/interface/IBSCValidatorSet.sol @@ -75,6 +75,7 @@ interface BSCValidatorSet { function VALIDATORS_UPDATE_MESSAGE_TYPE() external view returns (uint8); function VALIDATOR_CONTRACT_ADDR() external view returns (address); function alreadyInit() external view returns (bool); + function systemRewardAntiMEVRatio() external view returns (uint256); function bscChainID() external view returns (uint16); function burnRatio() external view returns (uint256); function burnRatioInitialized() external view returns (bool); @@ -102,12 +103,14 @@ interface BSCValidatorSet { function getIncoming(address validator) external view returns (uint256); function getLivingValidators() external view returns (address[] memory, bytes[] memory); function getMiningValidators() external view returns (address[] memory, bytes[] memory); + function getTurnTerm() external view returns (uint256); function getValidators() external view returns (address[] memory); function getWorkingValidatorCount() external view returns (uint256 workingValidatorCount); function handleAckPackage(uint8 channelId, bytes memory msgBytes) external; function handleFailAckPackage(uint8 channelId, bytes memory msgBytes) external; function handleSynPackage(uint8, bytes memory msgBytes) external returns (bytes memory responsePayload); function init() external; + function systemRewardBaseRatio() external view returns (uint256); function isCurrentValidator(address validator) external view returns (bool); function isMonitoredForMaliciousVote(bytes memory voteAddr) external view returns (bool); function isSystemRewardIncluded() external view returns (bool); @@ -124,8 +127,8 @@ interface BSCValidatorSet { function previousHeight() external view returns (uint256); function previousVoteAddrFullSet(uint256) external view returns (bytes memory); function removeTmpMigratedValidator(address validator) external; - function systemRewardRatio() external view returns (uint256); function totalInComing() external view returns (uint256); + function turnTerm() external view returns (uint256); function updateParam(string memory key, bytes memory value) external; function updateValidatorSetV2( address[] memory _consensusAddrs,