-
Notifications
You must be signed in to change notification settings - Fork 9
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #273 from axel-muller/I266-node-operator-configura…
…tion Node operator support implementation
- Loading branch information
Showing
7 changed files
with
730 additions
and
132 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -141,11 +141,23 @@ contract StakingHbbft is Initializable, OwnableUpgradeable, ReentrancyGuardUpgra | |
|
||
IBonusScoreSystem public bonusScoreContract; | ||
|
||
/// @dev Address of node operator for specified pool. | ||
mapping(address => address) public poolNodeOperator; | ||
|
||
/// @dev Node operator share percent of total pool rewards. | ||
mapping(address => uint256) public poolNodeOperatorShare; | ||
|
||
/// @dev The epoch number in which the operator's address can be changed. | ||
mapping(address => uint256) internal _poolNodeOperatorLastChangeEpoch; | ||
|
||
// ============================================== Constants ======================================================= | ||
|
||
/// @dev The max number of candidates (including validators). This limit was determined through stress testing. | ||
uint256 public constant MAX_CANDIDATES = 3000; | ||
|
||
uint256 public constant MAX_NODE_OPERATOR_SHARE_PERCENT = 2000; | ||
uint256 public constant PERCENT_DENOMINATOR = 10000; | ||
|
||
// ================================================ Events ======================================================== | ||
|
||
/// @dev Emitted by the `claimOrderedWithdraw` function to signal the staker withdrew the specified | ||
|
@@ -232,6 +244,16 @@ contract StakingHbbft is Initializable, OwnableUpgradeable, ReentrancyGuardUpgra | |
uint256 delegatorsReward | ||
); | ||
|
||
/// @dev Emitted by the `_setNodeOperator` function. | ||
/// @param poolStakingAddress The pool for which node operator was configured. | ||
/// @param nodeOperatorAddress Address of node operator address related to `poolStakingAddress`. | ||
/// @param operatorShare Node operator share percent. | ||
event SetNodeOperator( | ||
address indexed poolStakingAddress, | ||
address indexed nodeOperatorAddress, | ||
uint256 operatorShare | ||
); | ||
|
||
/** | ||
* @dev Emitted when the minimum stake for a delegator is updated. | ||
* @param minStake The new minimum stake value. | ||
|
@@ -246,6 +268,7 @@ contract StakingHbbft is Initializable, OwnableUpgradeable, ReentrancyGuardUpgra | |
|
||
// ============================================== Errors ======================================================= | ||
error CannotClaimWithdrawOrderYet(address pool, address staker); | ||
error OnlyOncePerEpoch(uint256 _epoch); | ||
error MaxPoolsCountExceeded(); | ||
error MaxAllowedWithdrawExceeded(uint256 allowed, uint256 desired); | ||
error NoStakesToRecover(); | ||
|
@@ -268,6 +291,8 @@ contract StakingHbbft is Initializable, OwnableUpgradeable, ReentrancyGuardUpgra | |
error InvalidStakingFixedEpochDuration(); | ||
error InvalidTransitionTimeFrame(); | ||
error InvalidWithdrawAmount(address pool, address delegator, uint256 amount); | ||
error InvalidNodeOperatorConfiguration(address _operator, uint256 _share); | ||
error InvalidNodeOperatorShare(uint256 _share); | ||
error WithdrawNotAllowed(); | ||
error ZeroWidthrawAmount(); | ||
error ZeroWidthrawDisallowPeriod(); | ||
|
@@ -505,15 +530,27 @@ contract StakingHbbft is Initializable, OwnableUpgradeable, ReentrancyGuardUpgra | |
/// they want to create a pool. This is a wrapper for the `stake` function. | ||
/// @param _miningAddress The mining address of the candidate. The mining address is bound to the staking address | ||
/// (msg.sender). This address cannot be equal to `msg.sender`. | ||
function addPool(address _miningAddress, bytes calldata _publicKey, bytes16 _ip) external payable gasPriceIsValid { | ||
/// @param _nodeOperatorAddress Address of node operator, will receive `_operatorShare` of epoch rewards. | ||
/// @param _operatorShare Percent of epoch rewards to send to `_nodeOperatorAddress`. | ||
/// Integer value with 2 decimal places, e.g. 1% = 100, 10.25% = 1025. | ||
function addPool( | ||
address _miningAddress, | ||
address _nodeOperatorAddress, | ||
uint256 _operatorShare, | ||
bytes calldata _publicKey, | ||
bytes16 _ip | ||
) external payable gasPriceIsValid { | ||
address stakingAddress = msg.sender; | ||
uint256 amount = msg.value; | ||
validatorSetContract.setStakingAddress(_miningAddress, stakingAddress); | ||
// The staking address and the staker are the same. | ||
_stake(stakingAddress, stakingAddress, amount); | ||
poolInfo[stakingAddress].publicKey = _publicKey; | ||
poolInfo[stakingAddress].internetAddress = _ip; | ||
|
||
_setNodeOperator(stakingAddress, _nodeOperatorAddress, _operatorShare); | ||
|
||
_stake(stakingAddress, stakingAddress, amount); | ||
|
||
emit PlacedStake(stakingAddress, stakingAddress, stakingEpoch, amount); | ||
} | ||
Check notice Code scanning / Slither Reentrancy vulnerabilities Low
Reentrancy in StakingHbbft.addPool(address,address,uint256,bytes,bytes16):
External calls: - validatorSetContract.setStakingAddress(_miningAddress,stakingAddress) State variables written after the call(s): - _stake(stakingAddress,stakingAddress,amount) - _delegatorStakeSnapshot[_stakingAddress][_delegator][stakingEpoch] = stakeAmount[_stakingAddress][_delegator] - _setNodeOperator(stakingAddress,_nodeOperatorAddress,_operatorShare) - _poolNodeOperatorLastChangeEpoch[_stakingAddress] = stakingEpoch - _stake(stakingAddress,stakingAddress,amount) - _poolsLikelihood.push(0) - _poolsLikelihood[index] = newValue - _stake(stakingAddress,stakingAddress,amount) - _poolsLikelihoodSum = _poolsLikelihoodSum - oldValue + newValue - _stake(stakingAddress,stakingAddress,amount) - _poolsToBeElected.push(_stakingAddress) - _stake(stakingAddress,stakingAddress,amount) - _stakeAmountByEpoch[_poolStakingAddress][_staker][stakingEpoch] += _amount - _stake(stakingAddress,stakingAddress,amount) - _stakeSnapshotLastEpoch[_stakingAddress][_delegator] = stakingEpoch - poolInfo[stakingAddress].publicKey = _publicKey - poolInfo[stakingAddress].internetAddress = _ip - _setNodeOperator(stakingAddress,_nodeOperatorAddress,_operatorShare) - poolNodeOperator[_stakingAddress] = _operatorAddress - _setNodeOperator(stakingAddress,_nodeOperatorAddress,_operatorShare) - poolNodeOperatorShare[_stakingAddress] = _operatorSharePercent - _stake(stakingAddress,stakingAddress,amount) - poolToBeElectedIndex[_stakingAddress] = length - _stake(stakingAddress,stakingAddress,amount) - stakeAmount[_poolStakingAddress][_staker] = newStakeAmount - _stake(stakingAddress,stakingAddress,amount) - stakeAmountTotal[_poolStakingAddress] += _amount - _stake(stakingAddress,stakingAddress,amount) - totalStakedAmount += _amount Check notice Code scanning / Slither Reentrancy vulnerabilities Low
Reentrancy in StakingHbbft.addPool(address,address,uint256,bytes,bytes16):
External calls: - validatorSetContract.setStakingAddress(_miningAddress,stakingAddress) Event emitted after the call(s): - PlacedStake(stakingAddress,stakingAddress,stakingEpoch,amount) - SetNodeOperator(_stakingAddress,_operatorAddress,_operatorSharePercent) - _setNodeOperator(stakingAddress,_nodeOperatorAddress,_operatorShare) |
||
|
||
|
@@ -547,6 +584,17 @@ contract StakingHbbft is Initializable, OwnableUpgradeable, ReentrancyGuardUpgra | |
poolInfo[msg.sender].port = _port; | ||
} | ||
|
||
/// @dev Set's the pool node operator configuration for a specific ethereum address. | ||
/// @param _operatorAddress Node operator address. | ||
/// @param _operatorShare Node operator reward share percent. | ||
function setNodeOperator(address _operatorAddress, uint256 _operatorShare) external { | ||
if (validatorSetContract.miningByStakingAddress(msg.sender) == address(0)) { | ||
revert PoolNotExist(msg.sender); | ||
} | ||
|
||
_setNodeOperator(msg.sender, _operatorAddress, _operatorShare); | ||
} | ||
|
||
/// @dev Removes a specified pool from the `pools` array (a list of active pools which can be retrieved by the | ||
/// `getPools` getter). Called by the `ValidatorSetHbbft._removeMaliciousValidator` internal function, | ||
/// and the `ValidatorSetHbbft.handleFailedKeyGeneration` function | ||
|
@@ -623,38 +671,35 @@ contract StakingHbbft is Initializable, OwnableUpgradeable, ReentrancyGuardUpgra | |
|
||
uint256 poolReward = msg.value; | ||
uint256 totalStake = snapshotPoolTotalStakeAmount[stakingEpoch][_poolStakingAddress]; | ||
uint256 validatorStake = snapshotPoolValidatorStakeAmount[stakingEpoch][_poolStakingAddress]; | ||
|
||
uint256 validatorReward = 0; | ||
PoolRewardShares memory shares = _splitPoolReward(_poolStakingAddress, poolReward, _validatorMinRewardPercent); | ||
|
||
if (totalStake > validatorStake) { | ||
address[] memory delegators = poolDelegators(_poolStakingAddress); | ||
address[] memory delegators = poolDelegators(_poolStakingAddress); | ||
for (uint256 i = 0; i < delegators.length; ++i) { | ||
uint256 delegatorReward = (shares.delegatorsShare * | ||
_getDelegatorStake(stakingEpoch, _poolStakingAddress, delegators[i])) / totalStake; | ||
|
||
uint256 validatorFixedReward = (poolReward * _validatorMinRewardPercent) / 100; | ||
uint256 rewardsToDisribute = poolReward - validatorFixedReward; | ||
|
||
validatorReward = validatorFixedReward + (rewardsToDisribute * validatorStake) / totalStake; | ||
|
||
for (uint256 i = 0; i < delegators.length; ++i) { | ||
uint256 delegatorReward = (rewardsToDisribute * | ||
_getDelegatorStake(stakingEpoch, _poolStakingAddress, delegators[i])) / totalStake; | ||
stakeAmount[_poolStakingAddress][delegators[i]] += delegatorReward; | ||
_stakeAmountByEpoch[_poolStakingAddress][delegators[i]][stakingEpoch] += delegatorReward; | ||
} | ||
|
||
stakeAmount[_poolStakingAddress][delegators[i]] += delegatorReward; | ||
_stakeAmountByEpoch[_poolStakingAddress][delegators[i]][stakingEpoch] += delegatorReward; | ||
} | ||
} else { | ||
// Whole pool stake belongs to the pool owner | ||
// and he received all the rewards. | ||
validatorReward = poolReward; | ||
if (shares.nodeOperatorShare != 0) { | ||
_rewardNodeOperator(_poolStakingAddress, shares.nodeOperatorShare); | ||
} | ||
|
||
stakeAmount[_poolStakingAddress][_poolStakingAddress] += validatorReward; | ||
stakeAmount[_poolStakingAddress][_poolStakingAddress] += shares.validatorShare; | ||
|
||
stakeAmountTotal[_poolStakingAddress] += poolReward; | ||
totalStakedAmount += poolReward; | ||
|
||
_setLikelihood(_poolStakingAddress); | ||
|
||
emit RestakeReward(_poolStakingAddress, stakingEpoch, validatorReward, poolReward - validatorReward); | ||
emit RestakeReward( | ||
_poolStakingAddress, | ||
stakingEpoch, | ||
shares.validatorShare, | ||
poolReward - shares.validatorShare | ||
); | ||
} | ||
|
||
/// @dev Orders coins withdrawal from the staking address of the specified pool to the | ||
|
@@ -1433,6 +1478,43 @@ contract StakingHbbft is Initializable, OwnableUpgradeable, ReentrancyGuardUpgra | |
} | ||
} | ||
|
||
function _setNodeOperator( | ||
address _stakingAddress, | ||
address _operatorAddress, | ||
uint256 _operatorSharePercent | ||
) private { | ||
if (_operatorSharePercent > MAX_NODE_OPERATOR_SHARE_PERCENT) { | ||
revert InvalidNodeOperatorShare(_operatorSharePercent); | ||
} | ||
|
||
if (_operatorAddress == address(0) && _operatorSharePercent != 0) { | ||
revert InvalidNodeOperatorConfiguration(_operatorAddress, _operatorSharePercent); | ||
} | ||
|
||
uint256 lastChangeEpoch = _poolNodeOperatorLastChangeEpoch[_stakingAddress]; | ||
if (lastChangeEpoch != 0 && lastChangeEpoch == stakingEpoch) { | ||
revert OnlyOncePerEpoch(stakingEpoch); | ||
} | ||
|
||
poolNodeOperator[_stakingAddress] = _operatorAddress; | ||
poolNodeOperatorShare[_stakingAddress] = _operatorSharePercent; | ||
|
||
_poolNodeOperatorLastChangeEpoch[_stakingAddress] = stakingEpoch; | ||
|
||
emit SetNodeOperator(_stakingAddress, _operatorAddress, _operatorSharePercent); | ||
} | ||
|
||
function _rewardNodeOperator(address _stakingAddress, uint256 _operatorShare) private { | ||
address nodeOperator = poolNodeOperator[_stakingAddress]; | ||
|
||
if (!_poolDelegators[_stakingAddress].contains(nodeOperator)) { | ||
_addPoolDelegator(_stakingAddress, nodeOperator); | ||
} | ||
|
||
stakeAmount[_stakingAddress][nodeOperator] += _operatorShare; | ||
_stakeAmountByEpoch[_stakingAddress][nodeOperator][stakingEpoch] += _operatorShare; | ||
} | ||
|
||
function _getDelegatorStake( | ||
uint256 _stakingEpoch, | ||
address _stakingAddress, | ||
|
@@ -1469,6 +1551,30 @@ contract StakingHbbft is Initializable, OwnableUpgradeable, ReentrancyGuardUpgra | |
} | ||
return (false, 0); | ||
} | ||
|
||
function _splitPoolReward( | ||
address _poolAddress, | ||
uint256 _poolReward, | ||
uint256 _validatorMinRewardPercent | ||
) private view returns (PoolRewardShares memory shares) { | ||
uint256 totalStake = snapshotPoolTotalStakeAmount[stakingEpoch][_poolAddress]; | ||
uint256 validatorStake = snapshotPoolValidatorStakeAmount[stakingEpoch][_poolAddress]; | ||
|
||
uint256 validatorFixedReward = (_poolReward * _validatorMinRewardPercent) / 100; | ||
|
||
shares.delegatorsShare = _poolReward - validatorFixedReward; | ||
|
||
uint256 operatorSharePercent = poolNodeOperatorShare[_poolAddress]; | ||
if (poolNodeOperator[_poolAddress] != address(0) && operatorSharePercent != 0) { | ||
shares.nodeOperatorShare = (_poolReward * operatorSharePercent) / PERCENT_DENOMINATOR; | ||
} | ||
|
||
shares.validatorShare = | ||
validatorFixedReward - | ||
shares.nodeOperatorShare + | ||
(shares.delegatorsShare * validatorStake) / | ||
totalStake; | ||
} | ||
} | ||
|
||
// slither-disable-end unused-return |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.