// SPDX-License-Identifier: GPL-2.0-or-later pragma solidity >=0.8.0; import '@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol'; import '@openzeppelin/contracts-upgradeable/utils/cryptography/ECDSAUpgradeable.sol'; import '@openzeppelin/contracts/utils/Strings.sol'; import '../interfaces/ISlasher.sol'; /// @dev Slasher contract /// @author Alexandas contract Slasher is ISlasher, OwnableUpgradeable { /// @dev governance address address public governance; /// @dev return `Stake` contract address IStake public override stake; /// @dev return `MajorCandidates` contract address IMajorCandidates public override majorCandidates; /// @dev return default slash amount uint256 public override defaultSlashAmount; /// @dev return public notice period uint256 public override publicNoticePeriod; /// @dev return current slash nonce uint256 public override nonce; /// @dev return max coefficient uint64 public override MAXCOEF; /// @dev return drafter slash reward coefficient uint64 public override drafterCoef; /// @dev return validator slash reward coefficient uint64 public override validatorCoef; /// @dev return executor slash reward coefficient uint64 public override executorCoef; mapping(uint256 => Types.SlashInfo) internal slashes; mapping(address => uint256) internal nonces; modifier onlyGovernance() { require(msg.sender == governance, 'Slasher: caller must be the Governace'); _; } /// @dev proxy initialize function /// @param newOwner contract owner /// @param _governance governance address /// @param _stake `Stake` contract /// @param _majorCandidates `MajorCandidates` contract /// @param _defaultSlashAmount default slash amount /// @param _publicNoticePeriod public notice period /// @param maxCoef max coefficient /// @param _drafterCoef drafter slash reward coefficient /// @param _validatorCoef validator slash reward coefficient /// @param _executorCoef executor slash reward coefficient function initialize( address newOwner, address _governance, IStake _stake, IMajorCandidates _majorCandidates, uint256 _defaultSlashAmount, uint256 _publicNoticePeriod, uint64 maxCoef, uint64 _drafterCoef, uint64 _validatorCoef, uint64 _executorCoef ) external initializer { _transferOwnership(newOwner); _setGovernance(_governance); _setStake(_stake); _setMajorCandidates(_majorCandidates); _setDefaultSlashAmount(_defaultSlashAmount); _setPublicNoticePeriod(_publicNoticePeriod); _setMaxCoef(maxCoef); _setDrafterCoef(_drafterCoef); _setValidatorCoef(_validatorCoef); _setExecutorCoef(_executorCoef); nonce = 1; } /// @dev draft a slash /// @param slashBlock slashed block in posc /// @param manifest node manifest /// @param accuracy posc accuracy /// @param signatures major candidates signatures function draft( uint64 slashBlock, string memory manifest, uint64 accuracy, bytes[] memory signatures ) external override onlyGovernance { address candidate = stake.manifestMap(manifest); require(candidate != address(0), 'Slasher: nonexistent candidate'); require(!slashExists(candidate), 'Slasher: candidate is in slash'); bytes memory raw = abi.encode(nonce, slashBlock, manifest, accuracy); bytes memory messsage = abi.encodePacked(bytes('\x19Ethereum Signed Message:\n'), bytes(Strings.toString(raw.length)), raw); address[] memory validators = checkNSignatures(keccak256(messsage), signatures); uint256 amount = stake.draftSlash(candidate, defaultSlashAmount); if (amount > 0) { slashes[nonce] = Types.SlashInfo({ candidate: candidate, drafter: msg.sender, validators: validators, amount: amount, timestamp: block.timestamp, status: Types.SlashStatus.Drafted }); nonces[candidate] = nonce; emit DraftSlash(nonce, candidate, slashBlock, manifest, accuracy); nonce++; } } /// @dev reject a slash /// @param manifest node manifest function reject(string memory manifest) external override onlyGovernance { address candidate = stake.manifestMap(manifest); require(candidate != address(0), 'Slasher: nonexistent candidate'); uint256 _nonce = nonces[candidate]; require(slashes[_nonce].timestamp > 0, 'Slasher: nonexistent slash'); require(slashes[_nonce].status == Types.SlashStatus.Drafted, 'Slasher: slash status is not drafted'); require(block.timestamp <= slashes[_nonce].timestamp + publicNoticePeriod, 'Slasher: slash is not in public notice period'); slashes[_nonce].status = Types.SlashStatus.Rejected; stake.rejectSlash(candidate, slashes[_nonce].amount); delete nonces[candidate]; emit RejectSlash(_nonce); } /// @dev execute a slash /// @param manifest node manifest function execute(string memory manifest) external override onlyGovernance { address candidate = stake.manifestMap(manifest); require(candidate != address(0), 'Slasher: nonexistent candidate'); uint256 _nonce = nonceOf(candidate); require(slashes[_nonce].timestamp > 0, 'Slasher: nonexistent slash'); require(slashes[_nonce].status == Types.SlashStatus.Drafted, 'Slasher: slash status is not drafted'); require(block.timestamp > slashes[_nonce].timestamp + publicNoticePeriod, 'Slasher: slash is in public notice period'); slashes[nonce].status = Types.SlashStatus.Executed; uint256 amount = slashes[_nonce].amount; address[] memory validators = slashes[_nonce].validators; uint256 validatorLen = validators.length; address[] memory beneficiaries = new address[](validatorLen + 2); uint256[] memory amounts = new uint256[](validatorLen + 2); uint256 validatorAmount = (amount * validatorCoef) / MAXCOEF; uint256 drafterAmount = (amount * drafterCoef) / MAXCOEF; uint256 executorAmount = (amount * executorCoef) / MAXCOEF; uint256 burned = amount - drafterAmount - validatorAmount - executorAmount; uint256 averageValidatorAmount = validatorAmount / validatorLen; for (uint256 i = 0; i < validators.length; i++) { beneficiaries[i] = validators[i]; amounts[i] = averageValidatorAmount; } beneficiaries[validatorLen] = slashes[_nonce].drafter; amounts[validatorLen] = drafterAmount; beneficiaries[validatorLen + 1] = msg.sender; amounts[validatorLen + 1] = executorAmount; stake.executeSlash(candidate, amount, beneficiaries, amounts, burned); delete nonces[candidate]; emit ExecuteSlash(_nonce, msg.sender); } /// @dev return whether candidate is in slashing /// @param candidate candidate address /// @return whether candidate is in slashing function slashExists(address candidate) public view override returns (bool) { return nonces[candidate] > 0; } /// @dev check whether signatures is valid /// @param hash message hash /// @param signatures signatures for major candidates /// @param signers major candidate signers function checkNSignatures(bytes32 hash, bytes[] memory signatures) public view override returns (address[] memory signers) { require(signatures.length >= majorCandidates.MAX_MAJOR_CANDIDATES() * 2 / 3, 'Slasher: signature length is not enough'); signers = new address[](signatures.length); for (uint8 i = 0; i < signatures.length; i++) { (address recovered, ECDSAUpgradeable.RecoverError err) = ECDSAUpgradeable.tryRecover(hash, signatures[i]); require(err == ECDSAUpgradeable.RecoverError.NoError, 'Slasher: invalid signature'); if (i > 0) { require(recovered > signers[i-1], 'Slasher: incorrect signature order'); } require(majorCandidates.isMajor(recovered), 'Slasher: signer is not the major candidate'); signers[i] = recovered; } } function getSlashAt(uint256 _nonce) public view override returns (Types.SlashInfo memory) { return slashes[_nonce]; } function nonceOf(address candidate) public view override returns (uint256) { require(slashExists(candidate), 'Slasher: nonexistent slash'); return nonces[candidate]; } function endOf(uint256 _nonce) public view returns (uint256) { require(_nonce > 0 && _nonce < nonce, 'Slasher: invalid nonce'); return slashes[_nonce].timestamp + publicNoticePeriod; } /// @dev set drafter slash reward coefficient /// @param _drafterCoef drafter slash reward coefficient function setDrafterCoef(uint64 _drafterCoef) external override onlyOwner { _setDrafterCoef(_drafterCoef); } /// @dev set validator slash reward coefficient /// @param _validatorCoef validator slash reward coefficient function setValidatorCoef(uint64 _validatorCoef) external override onlyOwner { _setValidatorCoef(_validatorCoef); } /// @dev set executor slash reward coefficient /// @param _executorCoef executor slash reward coefficient function setExecutorCoef(uint64 _executorCoef) external override onlyOwner { _setExecutorCoef(_executorCoef); } function _setMaxCoef(uint64 maxCoef) internal { MAXCOEF = maxCoef; emit MaxCoefUpdated(maxCoef); } function _setDrafterCoef(uint64 _drafterCoef) internal { require(_drafterCoef + validatorCoef + executorCoef <= MAXCOEF, 'Slasher: invalid coefficient'); drafterCoef = _drafterCoef; emit DrafterCoefUpdated(_drafterCoef); } function _setValidatorCoef(uint64 _validatorCoef) internal { require(drafterCoef + _validatorCoef + executorCoef <= MAXCOEF, 'Slasher: invalid coefficient'); validatorCoef = _validatorCoef; emit ValidatorCoefUpdated(_validatorCoef); } function _setExecutorCoef(uint64 _executorCoef) internal { require(drafterCoef + validatorCoef + _executorCoef <= MAXCOEF, 'Slasher: invalid coefficient'); executorCoef = _executorCoef; emit ExecutorCoefUpdated(_executorCoef); } function _setGovernance(address _governance) internal { governance = _governance; emit GovernanceUpdated(_governance); } function _setStake(IStake _stake) internal { stake = _stake; emit StakeUpdated(_stake); } function _setMajorCandidates(IMajorCandidates _majorCandidates) internal { majorCandidates = _majorCandidates; emit MajorCandidatesUpdated(_majorCandidates); } function _setDefaultSlashAmount(uint256 amount) internal { defaultSlashAmount = amount; emit DefaultSlashAmountUpdated(amount); } function _setPublicNoticePeriod(uint256 period) internal { publicNoticePeriod = period; emit PublicNoticePeriodUpdated(period); } function changeGovernance(address newGovernance) external onlyGovernance { _setGovernance(newGovernance); } }