From 1d8846094aab83ed5ca4164d5bc6cf004296ad75 Mon Sep 17 00:00:00 2001 From: Roshan Date: Wed, 13 Dec 2023 11:16:23 +0800 Subject: [PATCH] fix: update slash logic to avoid malicious slash --- abi/slashindicator.abi | 28 ++-- abi/stakehub.abi | 143 ++++++++++++++------ contracts/BC_fusion/StakeCredit.sol | 4 +- contracts/BC_fusion/StakeHub.sol | 108 ++++++++------- contracts/BC_fusion/interface/IStakeHub.sol | 2 +- contracts/BSCValidatorSet.sol | 4 +- contracts/SlashIndicator.sol | 30 ++-- contracts/interface/IStakeHub.sol | 4 +- scripts/generate.py | 8 +- test/SlashIndicator.t.sol | 2 +- test/utils/interface/ISlashIndicator.sol | 4 +- test/utils/interface/IStakeHub.sol | 15 +- 12 files changed, 214 insertions(+), 138 deletions(-) diff --git a/abi/slashindicator.abi b/abi/slashindicator.abi index 56b7f700..5cde6e51 100644 --- a/abi/slashindicator.abi +++ b/abi/slashindicator.abi @@ -170,7 +170,7 @@ }, { "type": "function", - "name": "INIT_MALICIOUS_VOTE_SLASH_SCOPE", + "name": "INIT_FELONY_SLASH_SCOPE", "inputs": [], "outputs": [ { @@ -479,6 +479,19 @@ ], "stateMutability": "view" }, + { + "type": "function", + "name": "felonySlashScope", + "inputs": [], + "outputs": [ + { + "name": "", + "type": "uint256", + "internalType": "uint256" + } + ], + "stateMutability": "view" + }, { "type": "function", "name": "felonyThreshold", @@ -630,19 +643,6 @@ "outputs": [], "stateMutability": "nonpayable" }, - { - "type": "function", - "name": "maliciousVoteSlashScope", - "inputs": [], - "outputs": [ - { - "name": "", - "type": "uint256", - "internalType": "uint256" - } - ], - "stateMutability": "view" - }, { "type": "function", "name": "misdemeanorThreshold", diff --git a/abi/stakehub.abi b/abi/stakehub.abi index 6afbac61..4bad86d5 100644 --- a/abi/stakehub.abi +++ b/abi/stakehub.abi @@ -5,7 +5,7 @@ }, { "type": "function", - "name": "BREATH_BLOCK_INTERVAL", + "name": "BREATHE_BLOCK_INTERVAL", "inputs": [], "outputs": [ { @@ -136,6 +136,44 @@ "outputs": [], "stateMutability": "nonpayable" }, + { + "type": "function", + "name": "consensusExpiration", + "inputs": [ + { + "name": "", + "type": "address", + "internalType": "address" + } + ], + "outputs": [ + { + "name": "", + "type": "uint256", + "internalType": "uint256" + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "consensusToOperator", + "inputs": [ + { + "name": "", + "type": "address", + "internalType": "address" + } + ], + "outputs": [ + { + "name": "", + "type": "address", + "internalType": "address" + } + ], + "stateMutability": "view" + }, { "type": "function", "name": "createValidator", @@ -396,44 +434,6 @@ ], "stateMutability": "view" }, - { - "type": "function", - "name": "getOperatorAddressByConsensusAddress", - "inputs": [ - { - "name": "consensusAddress", - "type": "address", - "internalType": "address" - } - ], - "outputs": [ - { - "name": "", - "type": "address", - "internalType": "address" - } - ], - "stateMutability": "view" - }, - { - "type": "function", - "name": "getOperatorAddressByVoteAddress", - "inputs": [ - { - "name": "voteAddress", - "type": "bytes", - "internalType": "bytes" - } - ], - "outputs": [ - { - "name": "", - "type": "address", - "internalType": "address" - } - ], - "stateMutability": "view" - }, { "type": "function", "name": "getValidatorBasicInfo", @@ -667,7 +667,7 @@ "name": "maliciousVoteSlash", "inputs": [ { - "name": "_voteAddr", + "name": "voteAddress", "type": "bytes", "internalType": "bytes" } @@ -688,6 +688,19 @@ ], "stateMutability": "view" }, + { + "type": "function", + "name": "maxFelonyBetweenBreatheBlock", + "inputs": [], + "outputs": [ + { + "name": "", + "type": "uint256", + "internalType": "uint256" + } + ], + "stateMutability": "view" + }, { "type": "function", "name": "minDelegationBNBChange", @@ -888,6 +901,44 @@ "outputs": [], "stateMutability": "nonpayable" }, + { + "type": "function", + "name": "voteExpiration", + "inputs": [ + { + "name": "", + "type": "bytes", + "internalType": "bytes" + } + ], + "outputs": [ + { + "name": "", + "type": "uint256", + "internalType": "uint256" + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "voteToOperator", + "inputs": [ + { + "name": "", + "type": "bytes", + "internalType": "bytes" + } + ], + "outputs": [ + { + "name": "", + "type": "address", + "internalType": "address" + } + ], + "stateMutability": "view" + }, { "type": "event", "name": "Claimed", @@ -1276,6 +1327,11 @@ "name": "AlreadySlashed", "inputs": [] }, + { + "type": "error", + "name": "ConsensusAddressExpired", + "inputs": [] + }, { "type": "error", "name": "DelegationAmountTooSmall", @@ -1344,7 +1400,7 @@ }, { "type": "error", - "name": "NoMoreFelonyToday", + "name": "NoMoreFelonyAllowed", "inputs": [] }, { @@ -1434,6 +1490,11 @@ "name": "ValidatorNotJailed", "inputs": [] }, + { + "type": "error", + "name": "VoteAddressExpired", + "inputs": [] + }, { "type": "error", "name": "ZeroShares", diff --git a/contracts/BC_fusion/StakeCredit.sol b/contracts/BC_fusion/StakeCredit.sol index 1fba276b..ae2c77ac 100644 --- a/contracts/BC_fusion/StakeCredit.sol +++ b/contracts/BC_fusion/StakeCredit.sol @@ -69,7 +69,7 @@ contract StakeCredit is System, Initializable, ReentrancyGuardUpgradeable, ERC20 * @notice only accept BNB from `StakeHub` */ receive() external payable onlyStakeHub { - uint256 index = block.timestamp / IStakeHub(STAKE_HUB_ADDR).BREATH_BLOCK_INTERVAL(); + uint256 index = block.timestamp / IStakeHub(STAKE_HUB_ADDR).BREATHE_BLOCK_INTERVAL(); totalPooledBNBRecord[index] = totalPooledBNB; rewardRecord[index] += msg.value; totalPooledBNB += msg.value; @@ -182,7 +182,7 @@ contract StakeCredit is System, Initializable, ReentrancyGuardUpgradeable, ERC20 uint256 _commission = (bnbAmount * uint256(commissionRate)) / COMMISSION_RATE_BASE; uint256 _reward = bnbAmount - _commission; - uint256 index = block.timestamp / IStakeHub(STAKE_HUB_ADDR).BREATH_BLOCK_INTERVAL(); + uint256 index = block.timestamp / IStakeHub(STAKE_HUB_ADDR).BREATHE_BLOCK_INTERVAL(); totalPooledBNBRecord[index] = totalPooledBNB; rewardRecord[index] += _reward; totalPooledBNB += _reward; diff --git a/contracts/BC_fusion/StakeHub.sol b/contracts/BC_fusion/StakeHub.sol index 88cb00c8..7a1e24cb 100644 --- a/contracts/BC_fusion/StakeHub.sol +++ b/contracts/BC_fusion/StakeHub.sol @@ -24,7 +24,7 @@ contract StakeHub is System, Initializable { uint256 public constant LOCK_AMOUNT = 1 ether; uint256 public constant REDELEGATE_FEE_RATE_BASE = 10000; // 100% - uint256 public constant BREATH_BLOCK_INTERVAL = 1 days; + uint256 public constant BREATHE_BLOCK_INTERVAL = 1 days; //TODO bytes private constant INIT_BC_CONSENSUS_ADDRESSES = @@ -71,14 +71,18 @@ contract StakeHub is System, Initializable { error ZeroShares(); // @notice signature: 0xf0e3e629 error SameValidator(); - // @notice signature: 0x413361db - error NoMoreFelonyToday(); + // @notice signature: 0xbd52fcdb + error NoMoreFelonyAllowed(); // @notice signature: 0x37233762 error AlreadySlashed(); // @notice signature: 0x90b8ec18 error TransferFailed(); // @notice signature: 0x41abc801 error InvalidRequest(); + // @notice signature: 0x1898eb6b + error VoteAddressExpired(); + // @notice signature: 0xc2aee074 + error ConsensusAddressExpired(); /*----------------- storage -----------------*/ bool private _paused; @@ -102,12 +106,14 @@ contract StakeHub is System, Initializable { EnumerableSet.AddressSet private _validatorSet; // validator operator address => validator info mapping(address => Validator) private _validators; - // validator vote address => validator operator address - mapping(bytes => address) private _voteToOperator; // validator consensus address => validator operator address - mapping(address => address) private _consensusToOperator; - // slash key => slash jail time - mapping(bytes32 => uint256) private _felonyRecords; + mapping(address => address) public consensusToOperator; + // validator consensus address => expiry date + mapping(address => uint256) public consensusExpiration; + // validator vote address => validator operator address + mapping(bytes => address) public voteToOperator; + // validator vote address => expiry date + mapping(bytes => uint256) public voteExpiration; // legacy addresses of BC mapping(address => bool) private _legacyConsensusAddress; @@ -115,10 +121,12 @@ contract StakeHub is System, Initializable { // total number of current jailed validators uint256 public numOfJailed; - // max number of jailed validators per day(only for malicious vote and double sign) - uint256 private felonyPerDay; - // index(timestamp / 1 days) => number of malicious vote and double sign slash + // max number of jailed validators between breathe block(only for malicious vote and double sign) + uint256 public maxFelonyBetweenBreatheBlock; + // index(timestamp / breatheBlockInterval) => number of malicious vote and double sign slash mapping(uint256 => uint256) private _felonyMap; + // slash key => slash jail time + mapping(bytes32 => uint256) private _felonyRecords; address public assetProtector; mapping(address => bool) public blackList; @@ -229,7 +237,7 @@ contract StakeHub is System, Initializable { felonySlashAmount = 200 ether; downtimeJailTime = 2 days; felonyJailTime = 30 days; - felonyPerDay = 2; + maxFelonyBetweenBreatheBlock = 2; address[] memory bcConsensusAddress; bytes[] memory bcVoteAddress; @@ -263,10 +271,10 @@ contract StakeHub is System, Initializable { // basic check address operatorAddress = msg.sender; if (_validatorSet.contains(operatorAddress)) revert ValidatorExisted(); - if (_consensusToOperator[consensusAddress] != address(0) || _legacyConsensusAddress[consensusAddress]) { + if (consensusToOperator[consensusAddress] != address(0) || _legacyConsensusAddress[consensusAddress]) { revert DuplicateConsensusAddress(); } - if (_voteToOperator[voteAddress] != address(0) || _legacyVoteAddress[voteAddress]) { + if (voteToOperator[voteAddress] != address(0) || _legacyVoteAddress[voteAddress]) { revert DuplicateVoteAddress(); } @@ -295,8 +303,8 @@ contract StakeHub is System, Initializable { valInfo.description = description; valInfo.commission = commission; valInfo.updateTime = block.timestamp; - _consensusToOperator[consensusAddress] = operatorAddress; - _voteToOperator[voteAddress] = operatorAddress; + consensusToOperator[consensusAddress] = operatorAddress; + voteToOperator[voteAddress] = operatorAddress; emit ValidatorCreated(consensusAddress, operatorAddress, creditContract, voteAddress); @@ -313,17 +321,18 @@ contract StakeHub is System, Initializable { validatorExist(msg.sender) { if (newConsensusAddress == address(0)) revert InvalidConsensusAddress(); - if (_consensusToOperator[newConsensusAddress] != address(0) || _legacyConsensusAddress[newConsensusAddress]) { + if (consensusToOperator[newConsensusAddress] != address(0) || _legacyConsensusAddress[newConsensusAddress]) { revert DuplicateConsensusAddress(); } address operatorAddress = msg.sender; Validator storage valInfo = _validators[operatorAddress]; - if (valInfo.updateTime + BREATH_BLOCK_INTERVAL > block.timestamp) revert UpdateTooFrequently(); + if (valInfo.updateTime + BREATHE_BLOCK_INTERVAL > block.timestamp) revert UpdateTooFrequently(); + consensusExpiration[valInfo.consensusAddress] = block.timestamp; valInfo.consensusAddress = newConsensusAddress; valInfo.updateTime = block.timestamp; - _consensusToOperator[newConsensusAddress] = operatorAddress; + consensusToOperator[newConsensusAddress] = operatorAddress; emit ConsensusAddressEdited(operatorAddress, newConsensusAddress); } @@ -339,7 +348,7 @@ contract StakeHub is System, Initializable { { address operatorAddress = msg.sender; Validator storage valInfo = _validators[operatorAddress]; - if (valInfo.updateTime + BREATH_BLOCK_INTERVAL > block.timestamp) revert UpdateTooFrequently(); + if (valInfo.updateTime + BREATHE_BLOCK_INTERVAL > block.timestamp) revert UpdateTooFrequently(); if (commissionRate > valInfo.commission.maxRate) revert InvalidCommission(); uint256 changeRate = commissionRate >= valInfo.commission.rate @@ -365,7 +374,7 @@ contract StakeHub is System, Initializable { { address operatorAddress = msg.sender; Validator storage valInfo = _validators[operatorAddress]; - if (valInfo.updateTime + BREATH_BLOCK_INTERVAL > block.timestamp) revert UpdateTooFrequently(); + if (valInfo.updateTime + BREATHE_BLOCK_INTERVAL > block.timestamp) revert UpdateTooFrequently(); description.moniker = valInfo.description.moniker; valInfo.description = description; @@ -384,17 +393,18 @@ contract StakeHub is System, Initializable { ) external whenNotPaused notInBlackList validatorExist(msg.sender) { // proof-of-possession verify if (!_checkVoteAddress(newVoteAddress, blsProof)) revert InvalidVoteAddress(); - if (_voteToOperator[newVoteAddress] != address(0) || _legacyVoteAddress[newVoteAddress]) { + if (voteToOperator[newVoteAddress] != address(0) || _legacyVoteAddress[newVoteAddress]) { revert DuplicateVoteAddress(); } address operatorAddress = msg.sender; Validator storage valInfo = _validators[operatorAddress]; - if (valInfo.updateTime + BREATH_BLOCK_INTERVAL > block.timestamp) revert UpdateTooFrequently(); + if (valInfo.updateTime + BREATHE_BLOCK_INTERVAL > block.timestamp) revert UpdateTooFrequently(); + voteExpiration[valInfo.voteAddress] = block.timestamp; valInfo.voteAddress = newVoteAddress; valInfo.updateTime = block.timestamp; - _voteToOperator[newVoteAddress] = operatorAddress; + voteToOperator[newVoteAddress] = operatorAddress; emit VoteAddressEdited(operatorAddress, newVoteAddress); } @@ -563,7 +573,7 @@ contract StakeHub is System, Initializable { * @dev This function will be called by consensus engine. So it should never revert. */ function distributeReward(address consensusAddress) external payable onlyValidatorContract { - address operatorAddress = _consensusToOperator[consensusAddress]; + address operatorAddress = consensusToOperator[consensusAddress]; Validator memory valInfo = _validators[operatorAddress]; if (valInfo.creditContract == address(0) || valInfo.jailed) { emit RewardDistributeFailed(operatorAddress, "INVALID_VALIDATOR"); @@ -578,7 +588,7 @@ contract StakeHub is System, Initializable { * @dev Downtime slash. Only the `SlashIndicator` contract can call this function. */ function downtimeSlash(address consensusAddress) external onlySlash { - address operatorAddress = _consensusToOperator[consensusAddress]; + address operatorAddress = consensusToOperator[consensusAddress]; if (!_validatorSet.contains(operatorAddress)) revert ValidatorNotExist(); // should never happen Validator storage valInfo = _validators[operatorAddress]; @@ -595,16 +605,22 @@ contract StakeHub is System, Initializable { /** * @dev Malicious vote slash. Only the `SlashIndicator` contract can call this function. */ - function maliciousVoteSlash(bytes calldata _voteAddr) external onlySlash { - address operatorAddress = _voteToOperator[_voteAddr]; + function maliciousVoteSlash(bytes calldata voteAddress) external onlySlash { + address operatorAddress = voteToOperator[voteAddress]; if (!_validatorSet.contains(operatorAddress)) revert ValidatorNotExist(); // should never happen Validator storage valInfo = _validators[operatorAddress]; - uint256 index = block.timestamp / BREATH_BLOCK_INTERVAL; + uint256 index = block.timestamp / BREATHE_BLOCK_INTERVAL; // This is to prevent many honest validators being slashed at the same time because of implementation bugs - if (_felonyMap[index] >= felonyPerDay) revert NoMoreFelonyToday(); + if (_felonyMap[index] >= maxFelonyBetweenBreatheBlock) revert NoMoreFelonyAllowed(); _felonyMap[index] += 1; + // check if the voteAddress has already expired + if (voteExpiration[voteAddress] != 0 && voteExpiration[voteAddress] + BREATHE_BLOCK_INTERVAL < block.timestamp) + { + revert VoteAddressExpired(); + } + // slash (bool canSlash, uint256 jailUntil) = _checkFelonyRecord(operatorAddress, SlashType.MaliciousVote); if (!canSlash) revert AlreadySlashed(); @@ -620,15 +636,23 @@ contract StakeHub is System, Initializable { * @dev Double sign slash. Only the `SlashIndicator` contract can call this function. */ function doubleSignSlash(address consensusAddress) external onlySlash { - address operatorAddress = _consensusToOperator[consensusAddress]; + address operatorAddress = consensusToOperator[consensusAddress]; if (!_validatorSet.contains(operatorAddress)) revert ValidatorNotExist(); // should never happen Validator storage valInfo = _validators[operatorAddress]; - uint256 index = block.timestamp / BREATH_BLOCK_INTERVAL; + uint256 index = block.timestamp / BREATHE_BLOCK_INTERVAL; // This is to prevent many honest validators being slashed at the same time because of implementation bugs - if (_felonyMap[index] >= felonyPerDay) revert NoMoreFelonyToday(); + if (_felonyMap[index] >= maxFelonyBetweenBreatheBlock) revert NoMoreFelonyAllowed(); _felonyMap[index] += 1; + // check if the consensusAddress has already expired + if ( + consensusExpiration[consensusAddress] != 0 + && consensusExpiration[consensusAddress] + BREATHE_BLOCK_INTERVAL < block.timestamp + ) { + revert ConsensusAddressExpired(); + } + // slash (bool canSlash, uint256 jailUntil) = _checkFelonyRecord(operatorAddress, SlashType.DoubleSign); if (!canSlash) revert AlreadySlashed(); @@ -731,11 +755,11 @@ contract StakeHub is System, Initializable { uint256 newFelonyJailTime = value.bytesToUint256(32); if (newFelonyJailTime < 10 days || newFelonyJailTime <= downtimeJailTime) revert InvalidValue(key, value); felonyJailTime = newFelonyJailTime; - } else if (key.compareStrings("felonyPerDay")) { + } else if (key.compareStrings("maxFelonyBetweenBreatheBlock")) { if (value.length != 32) revert InvalidValue(key, value); uint256 newJailedPerDay = value.bytesToUint256(32); if (newJailedPerDay == 0) revert InvalidValue(key, value); - felonyPerDay = newJailedPerDay; + maxFelonyBetweenBreatheBlock = newJailedPerDay; } else if (key.compareStrings("assetProtector")) { if (value.length != 20) revert InvalidValue(key, value); address newAssetProtector = value.bytesToAddress(20); @@ -860,20 +884,6 @@ contract StakeHub is System, Initializable { } } - /** - * @return the operator address that ever used the vote address - */ - function getOperatorAddressByVoteAddress(bytes calldata voteAddress) external view returns (address) { - return _voteToOperator[voteAddress]; - } - - /** - * @return the operator address that ever used the consensus address - */ - function getOperatorAddressByConsensusAddress(address consensusAddress) external view returns (address) { - return _consensusToOperator[consensusAddress]; - } - /*----------------- internal functions -----------------*/ function _checkMoniker(string memory moniker) internal pure returns (bool) { bytes memory bz = bytes(moniker); diff --git a/contracts/BC_fusion/interface/IStakeHub.sol b/contracts/BC_fusion/interface/IStakeHub.sol index 96e25807..27b03a97 100644 --- a/contracts/BC_fusion/interface/IStakeHub.sol +++ b/contracts/BC_fusion/interface/IStakeHub.sol @@ -4,7 +4,7 @@ pragma solidity 0.8.17; interface IStakeHub { function DEAD_ADDRESS() external view returns (address); function LOCK_AMOUNT() external view returns (uint256); - function BREATH_BLOCK_INTERVAL() external view returns (uint256); + function BREATHE_BLOCK_INTERVAL() external view returns (uint256); function unbondPeriod() external view returns (uint256); function transferGasLimit() external view returns (uint256); } diff --git a/contracts/BSCValidatorSet.sol b/contracts/BSCValidatorSet.sol index a34e21ab..21abd0ee 100644 --- a/contracts/BSCValidatorSet.sol +++ b/contracts/BSCValidatorSet.sol @@ -426,7 +426,7 @@ contract BSCValidatorSet is IBSCValidatorSet, System, IParamSubscriber, IApplica uint validatorsNum = currentValidatorSet.length; uint8[] memory isMigrated = new uint8[](validatorsNum); for (uint i; i= felonyThreshold) { _felony(validator, index); - if (IStakeHub(STAKE_HUB_ADDR).getOperatorAddressByConsensusAddress(validator) != address(0)) { + if (IStakeHub(STAKE_HUB_ADDR).consensusToOperator(validator) != address(0)) { ISlashIndicator(SLASH_CONTRACT_ADDR).downtimeSlash(validator, slashCount); } else { ISlashIndicator(SLASH_CONTRACT_ADDR).sendFelonyPackage(validator); diff --git a/contracts/SlashIndicator.sol b/contracts/SlashIndicator.sol index 2e89169a..3e7b63f6 100644 --- a/contracts/SlashIndicator.sol +++ b/contracts/SlashIndicator.sol @@ -44,9 +44,9 @@ contract SlashIndicator is ISlashIndicator,System,IParamSubscriber, IApplication uint256 public felonySlashRewardRatio; bool public enableMaliciousVoteSlash; - uint256 public constant INIT_MALICIOUS_VOTE_SLASH_SCOPE = 86400; // 3 days + uint256 public constant INIT_FELONY_SLASH_SCOPE = 86400; // 3 days - uint256 public maliciousVoteSlashScope; + uint256 public felonySlashScope; event validatorSlashed(address indexed validator); event maliciousVoteSlashed(bytes32 indexed voteAddrSlice); @@ -135,7 +135,7 @@ contract SlashIndicator is ISlashIndicator,System,IParamSubscriber, IApplication if (indicator.count % felonyThreshold == 0) { indicator.count = 0; IBSCValidatorSet(VALIDATOR_CONTRACT_ADDR).felony(validator); - if (IStakeHub(STAKE_HUB_ADDR).getOperatorAddressByConsensusAddress(validator) != address(0)) { + if (IStakeHub(STAKE_HUB_ADDR).consensusToOperator(validator) != address(0)) { _downtimeSlash(validator, indicator.count); } else { // send slash msg to bc if validator is not migrated @@ -222,13 +222,13 @@ contract SlashIndicator is ISlashIndicator,System,IParamSubscriber, IApplication if (felonySlashRewardRatio == 0) { felonySlashRewardRatio = INIT_FELONY_SLASH_REWARD_RATIO; } - if (maliciousVoteSlashScope == 0) { - maliciousVoteSlashScope = INIT_MALICIOUS_VOTE_SLASH_SCOPE; + if (felonySlashScope == 0) { + felonySlashScope = INIT_FELONY_SLASH_SCOPE; } // Basic check - require(_evidence.voteA.tarNum+maliciousVoteSlashScope > block.number && - _evidence.voteB.tarNum+maliciousVoteSlashScope > block.number, "target block too old"); + require(_evidence.voteA.tarNum+felonySlashScope > block.number && + _evidence.voteB.tarNum+felonySlashScope > block.number, "target block too old"); require(!(_evidence.voteA.srcHash == _evidence.voteB.srcHash && _evidence.voteA.tarHash == _evidence.voteB.tarHash), "two identical votes"); require(_evidence.voteA.srcNum < _evidence.voteA.tarNum && @@ -257,7 +257,7 @@ contract SlashIndicator is ISlashIndicator,System,IParamSubscriber, IApplication } } - if (IStakeHub(STAKE_HUB_ADDR).getOperatorAddressByVoteAddress(_evidence.voteAddr) != address(0)) { + if (IStakeHub(STAKE_HUB_ADDR).voteToOperator(_evidence.voteAddr) != address(0)) { IStakeHub(STAKE_HUB_ADDR).maliciousVoteSlash(_evidence.voteAddr); } else { // send slash msg to bc if the validator not migrated @@ -293,13 +293,13 @@ contract SlashIndicator is ISlashIndicator,System,IParamSubscriber, IApplication } address signer; - uint256 evidenceTime; + uint256 evidenceHeight; assembly { signer := mload(add(output, 0x14)) - evidenceTime := mload(add(output, 0x34)) + evidenceHeight := mload(add(output, 0x34)) } - require(IStakeHub(STAKE_HUB_ADDR).getOperatorAddressByConsensusAddress(signer) != address(0), "validator not migrated"); - require(evidenceTime + 21 days >= block.timestamp, "evidence too old"); + require(IStakeHub(STAKE_HUB_ADDR).consensusToOperator(signer) != address(0), "validator not migrated"); + require(evidenceHeight + felonySlashScope >= block.number, "evidence too old"); // reward sender and felony validator if validator found (address[] memory vals, ) = IBSCValidatorSet(VALIDATOR_CONTRACT_ADDR).getLivingValidators(); @@ -383,11 +383,11 @@ contract SlashIndicator is ISlashIndicator,System,IParamSubscriber, IApplication } else if (Memory.compareStrings(key, "enableMaliciousVoteSlash")) { require(value.length == 32, "length of enableMaliciousVoteSlash mismatch"); enableMaliciousVoteSlash = BytesToTypes.bytesToBool(32, value); - } else if (Memory.compareStrings(key, "maliciousVoteSlashScope")) { - require(value.length == 32, "length of maliciousVoteSlashScope mismatch"); + } else if (Memory.compareStrings(key, "felonySlashScope")) { + require(value.length == 32, "length of felonySlashScope mismatch"); uint256 newMaliciousVoteSlashScope = BytesToTypes.bytesToUint256(32, value); require(newMaliciousVoteSlashScope >= 28800*1 && newMaliciousVoteSlashScope < 28800*30, "the malicious vote slash scope out of range"); - maliciousVoteSlashScope = newMaliciousVoteSlashScope; + felonySlashScope = newMaliciousVoteSlashScope; } else { require(false, "unknown param"); } diff --git a/contracts/interface/IStakeHub.sol b/contracts/interface/IStakeHub.sol index c928e74e..babee9ca 100644 --- a/contracts/interface/IStakeHub.sol +++ b/contracts/interface/IStakeHub.sol @@ -4,8 +4,8 @@ interface IStakeHub { function downtimeSlash(address validator) external; function maliciousVoteSlash(bytes calldata voteAddress) external; function doubleSignSlash(address validator) external; - function getOperatorAddressByVoteAddress(bytes calldata voteAddress) external view returns (address); - function getOperatorAddressByConsensusAddress(address validator) external view returns (address); + function voteToOperator(bytes calldata voteAddress) external view returns (address); + function consensusToOperator(address validator) external view returns (address); function maxElectedValidators() external view returns (uint256); function distributeReward(address validator) external payable; } diff --git a/scripts/generate.py b/scripts/generate.py index 15515270..74d0ce2a 100644 --- a/scripts/generate.py +++ b/scripts/generate.py @@ -127,7 +127,7 @@ def generate_slash_indicator(): def generate_stake_hub( - breath_block_interval, init_bc_consensus_addresses, init_bc_vote_addresses, unbond_period, downtime_jail_time, + breathe_block_interval, init_bc_consensus_addresses, init_bc_vote_addresses, unbond_period, downtime_jail_time, felony_jail_time, asset_protector ): contract = "BC_fusion/StakeHub.sol" @@ -135,7 +135,7 @@ def generate_stake_hub( os.path.join(work_dir, "contracts", contract), os.path.join(work_dir, "contracts", contract[:-4] + ".bak") ) - replace_parameter(contract, "uint256 public constant BREATH_BLOCK_INTERVAL", f"{breath_block_interval}") + replace_parameter(contract, "uint256 public constant BREATHE_BLOCK_INTERVAL", f"{breathe_block_interval}") replace_parameter(contract, "bytes private constant INIT_BC_CONSENSUS_ADDRESSES", f"{init_bc_consensus_addresses}") replace_parameter(contract, "bytes private constant INIT_BC_VOTE_ADDRESSES", f"{init_bc_vote_addresses}") @@ -348,7 +348,7 @@ def dev( str, typer.Option(help="whitelist relayer2's address")] = "0x316b2Fa7C8a2ab7E21110a4B3f58771C01A71344", source_chain_id: Annotated[ str, typer.Option(help="source chain id of the token recover portal")] = "Binance-Chain-Ganges", - breath_block_interval: Annotated[str, typer.Option(help="breath block interval of Parlia")] = "1 days", + breathe_block_interval: Annotated[str, typer.Option(help="breath block interval of Parlia")] = "1 days", block_interval: Annotated[str, typer.Option(help="block interval of Parlia")] = "3 seconds", init_bc_consensus_addresses: str = 'hex"00000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000000"', @@ -396,7 +396,7 @@ def dev( generate_validator_set(init_burn_ratio, init_validatorset_bytes) generate_token_recover_portal(source_chain_id) generate_stake_hub( - breath_block_interval, init_bc_consensus_addresses, init_bc_vote_addresses, unbond_period, downtime_jail_time, + breathe_block_interval, init_bc_consensus_addresses, init_bc_vote_addresses, unbond_period, downtime_jail_time, felony_jail_time, asset_protector ) generate_governor( diff --git a/test/SlashIndicator.t.sol b/test/SlashIndicator.t.sol index e149c90b..3f696b00 100644 --- a/test/SlashIndicator.t.sol +++ b/test/SlashIndicator.t.sol @@ -300,7 +300,7 @@ contract SlashIndicatorTest is Deployer { ); vm.mockCall( address(stakeHub), - abi.encodeCall(stakeHub.getOperatorAddressByConsensusAddress, (0x23618e81E3f5cdF7f54C3d65f7FBc0aBf5B21E8f)), + abi.encodeCall(stakeHub.consensusToOperator, (0x23618e81E3f5cdF7f54C3d65f7FBc0aBf5B21E8f)), hex"00000000000000000000000023618e81e3f5cdf7f54c3d65f7fbc0abf5b21e8f" ); vm.prank(relayer); diff --git a/test/utils/interface/ISlashIndicator.sol b/test/utils/interface/ISlashIndicator.sol index ecd6ea2b..5f6f256f 100644 --- a/test/utils/interface/ISlashIndicator.sol +++ b/test/utils/interface/ISlashIndicator.sol @@ -39,7 +39,7 @@ interface SlashIndicator { function GOV_TOKEN_ADDR() external view returns (address); function INCENTIVIZE_ADDR() external view returns (address); function INIT_FELONY_SLASH_REWARD_RATIO() external view returns (uint256); - function INIT_MALICIOUS_VOTE_SLASH_SCOPE() external view returns (uint256); + function INIT_FELONY_SLASH_SCOPE() external view returns (uint256); function LIGHT_CLIENT_ADDR() external view returns (address); function MISDEMEANOR_THRESHOLD() external view returns (uint256); function RELAYERHUB_CONTRACT_ADDR() external view returns (address); @@ -63,6 +63,7 @@ interface SlashIndicator { function downtimeSlash(address validator, uint256 count) external; function enableMaliciousVoteSlash() external view returns (bool); function felonySlashRewardRatio() external view returns (uint256); + function felonySlashScope() external view returns (uint256); function felonyThreshold() external view returns (uint256); function getSlashIndicator(address validator) external view returns (uint256, uint256); function getSlashThresholds() external view returns (uint256, uint256); @@ -71,7 +72,6 @@ interface SlashIndicator { function handleSynPackage(uint8, bytes memory) external returns (bytes memory); function indicators(address) external view returns (uint256 height, uint256 count, bool exist); function init() external; - function maliciousVoteSlashScope() external view returns (uint256); function misdemeanorThreshold() external view returns (uint256); function previousHeight() external view returns (uint256); function sendFelonyPackage(address validator) external; diff --git a/test/utils/interface/IStakeHub.sol b/test/utils/interface/IStakeHub.sol index 723f28e2..a3c09bce 100644 --- a/test/utils/interface/IStakeHub.sol +++ b/test/utils/interface/IStakeHub.sol @@ -18,6 +18,7 @@ interface StakeHub { } error AlreadySlashed(); + error ConsensusAddressExpired(); error DelegationAmountTooSmall(); error DuplicateConsensusAddress(); error DuplicateVoteAddress(); @@ -29,7 +30,7 @@ interface StakeHub { error InvalidValue(string key, bytes value); error InvalidVoteAddress(); error JailTimeNotExpired(); - error NoMoreFelonyToday(); + error NoMoreFelonyAllowed(); error OnlyAssetProtector(); error OnlyCoinbase(); error OnlySelfDelegation(); @@ -44,6 +45,7 @@ interface StakeHub { error ValidatorExisted(); error ValidatorNotExist(); error ValidatorNotJailed(); + error VoteAddressExpired(); error ZeroShares(); event Claimed(address indexed operatorAddress, address indexed delegator, uint256 bnbAmount); @@ -82,7 +84,7 @@ interface StakeHub { receive() external payable; - function BREATH_BLOCK_INTERVAL() external view returns (uint256); + function BREATHE_BLOCK_INTERVAL() external view returns (uint256); function DEAD_ADDRESS() external view returns (address); function LOCK_AMOUNT() external view returns (uint256); function REDELEGATE_FEE_RATE_BASE() external view returns (uint256); @@ -91,6 +93,8 @@ interface StakeHub { function blackList(address) external view returns (bool); function claim(address operatorAddress, uint256 requestNumber) external; function claimBatch(address[] memory operatorAddresses, uint256[] memory requestNumbers) external; + function consensusExpiration(address) external view returns (uint256); + function consensusToOperator(address) external view returns (address); function createValidator( address consensusAddress, bytes memory voteAddress, @@ -110,8 +114,6 @@ interface StakeHub { function editVoteAddress(bytes memory newVoteAddress, bytes memory blsProof) external; function felonyJailTime() external view returns (uint256); function felonySlashAmount() external view returns (uint256); - function getOperatorAddressByConsensusAddress(address consensusAddress) external view returns (address); - function getOperatorAddressByVoteAddress(bytes memory voteAddress) external view returns (address); function getValidatorBasicInfo(address operatorAddress) external view @@ -138,8 +140,9 @@ interface StakeHub { function getValidatorTotalPooledBNBRecord(address operatorAddress, uint256 index) external view returns (uint256); function initialize() external; function isPaused() external view returns (bool); - function maliciousVoteSlash(bytes memory _voteAddr) external; + function maliciousVoteSlash(bytes memory voteAddress) external; function maxElectedValidators() external view returns (uint256); + function maxFelonyBetweenBreatheBlock() external view returns (uint256); function minDelegationBNBChange() external view returns (uint256); function minSelfDelegationBNB() external view returns (uint256); function numOfJailed() external view returns (uint256); @@ -154,4 +157,6 @@ interface StakeHub { function undelegate(address operatorAddress, uint256 shares) external; function unjail(address operatorAddress) external; function updateParam(string memory key, bytes memory value) external; + function voteExpiration(bytes memory) external view returns (uint256); + function voteToOperator(bytes memory) external view returns (address); }