Skip to content

Commit

Permalink
new ban validator functionality (#12)
Browse files Browse the repository at this point in the history
* new ban validator functionality

create a new contract and implement a ban function for the validators that will be banned;
create IInspector interface and compile
create new internal function to handle unstake in order to re-use it;
write the tests for the new inspector contract;
adapt the other tests with the new min stake and min delegate;
  • Loading branch information
Vitomir2 authored May 14, 2024
1 parent 5420121 commit de4d60b
Show file tree
Hide file tree
Showing 18 changed files with 2,257 additions and 538 deletions.
4 changes: 3 additions & 1 deletion contracts/ValidatorSet/ValidatorSet.sol
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,14 @@ import "./modules/AccessControl/AccessControl.sol";
import "./modules/PowerExponent/PowerExponent.sol";
import "./modules/Staking/Staking.sol";
import "./modules/Delegation/Delegation.sol";
import "./modules/Inspector/Inspector.sol";
import "./../common/System/System.sol";

import "./../common/libs/SafeMathInt.sol";

// TODO: setup use of reward account that would handle the amounts of rewards

contract ValidatorSet is ValidatorSetBase, System, AccessControl, PowerExponent, Staking, Delegation {
contract ValidatorSet is ValidatorSetBase, System, AccessControl, PowerExponent, Staking, Delegation, Inspector {
using ArraysUpgradeable for uint256[];

/// @notice Epoch data linked with the epoch id
Expand Down Expand Up @@ -57,6 +58,7 @@ contract ValidatorSet is ValidatorSetBase, System, AccessControl, PowerExponent,
__AccessControl_init(governance);
__Staking_init(init.minStake, liquidToken);
__Delegation_init();
__Inspector_init();
__ReentrancyGuard_init();
_initialize(newValidators, initialCommission);
}
Expand Down
41 changes: 41 additions & 0 deletions contracts/ValidatorSet/modules/Inspector/IInspector.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
// SPDX-License-Identifier: MIT
pragma solidity 0.8.17;

/**
* @notice Data type for the banned validators' withdrawals
* @param liquidTokens The amount of liquid tokens to be taken on withdrawal from the penalized validator
* @param withdrawableAmount The amount that is available for withdrawal after validator's penalty
*/
struct WithdrawalInfo {
uint256 liquidTokens;
uint256 withdrawableAmount;
}

interface IInspector {
event ValidatorBanned(address indexed validator);

/**
* @notice Manual ban of a validator
* @dev Function can be executed only by the governor/owner
* @param validator Address of the validator
*/
function banValidator(address validator) external;

/**
* @notice Set the penalty amount for the banned validators
* @param newPenalty Amount of the penalty
*/
function setValidatorPenalty(uint256 newPenalty) external;

/**
* @notice Set the reward of the person who reports a validator
* @param newReward Amount of the reward
*/
function setReporterReward(uint256 newReward) external;

/**
* @notice Withdraw funds left for a banned validator
* @dev Function can be executed only by the banned validator
*/
function withdrawBannedFunds() external;
}
98 changes: 98 additions & 0 deletions contracts/ValidatorSet/modules/Inspector/Inspector.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
// SPDX-License-Identifier: MIT
pragma solidity 0.8.17;

import "./IInspector.sol";
import "./../Staking/Staking.sol";
import "./../../../common/Errors.sol";

abstract contract Inspector is IInspector, Staking {
/// @notice The penalty that will be taken and burned from the bad valiator's staked amount
uint256 public validatorPenalty;

/// @notice The reward for the person who reports a validator that have to be banned
uint256 public reporterReward;

/**
* @notice The withdrawal info that is required for a banned validator to withdraw the funds left
* @dev The withdrawal amount is calculated as the difference between
* the validator's total stake and any penalties applied due to a ban
*/
mapping(address => WithdrawalInfo) public withdrawalBalances;

// _______________ Modifiers _______________

// Only address that is banned
modifier onlyBanned() {
if (validators[msg.sender].status != ValidatorStatus.Banned) revert Unauthorized("BANNED_VALIDATOR");
_;
}

// _______________ Initializer _______________

function __Inspector_init() internal onlyInitializing {
__Inspector_init_unchained();
}

function __Inspector_init_unchained() internal onlyInitializing {
validatorPenalty = 700 ether;
reporterReward = 300 ether;
}

// _______________ External functions _______________

/**
* @inheritdoc IInspector
*/
function banValidator(address validator) external onlyOwner {
if (validators[validator].status != ValidatorStatus.Registered) revert Unauthorized("UNREGISTERED_VALIDATOR");

uint256 stakedAmount = balanceOf(validator);
if (stakedAmount != 0) {
_burn(validator, stakedAmount);
StateSyncer._syncStake(validator, 0);
uint256 amountLeft = rewardPool.onUnstake(validator, stakedAmount, 0);

uint256 penalty = validatorPenalty;
if (amountLeft < penalty) penalty = amountLeft;

withdrawalBalances[validator].liquidTokens = stakedAmount;
withdrawalBalances[validator].withdrawableAmount = amountLeft - penalty;
}

validators[validator].status = ValidatorStatus.Banned;

emit ValidatorBanned(validator);
}

// _______________ Public functions _______________

/**
* @inheritdoc IInspector
*/
function setValidatorPenalty(uint256 newPenalty) public onlyOwner {
validatorPenalty = newPenalty;
}

/**
* @inheritdoc IInspector
*/
function setReporterReward(uint256 newReward) public onlyOwner {
reporterReward = newReward;
}

/**
* @inheritdoc IInspector
*/
function withdrawBannedFunds() public onlyBanned {
WithdrawalInfo memory withdrawalBalance = withdrawalBalances[msg.sender];

delete withdrawalBalances[msg.sender];

LiquidStaking._collectTokens(msg.sender, withdrawalBalance.liquidTokens);

_withdraw(msg.sender, withdrawalBalance.withdrawableAmount);
}

// slither-disable-next-line unused-state,naming-convention
uint256[50] private __gap;
}
1 change: 1 addition & 0 deletions contracts/ValidatorSet/modules/Staking/IStaking.sol
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ interface IStaking {

error InvalidCommission(uint256 commission);
error InvalidMinStake();
error LowStake();

/**
* @notice Sets commission for validator.
Expand Down
12 changes: 6 additions & 6 deletions contracts/ValidatorSet/modules/Staking/Staking.sol
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,7 @@ abstract contract Staking is
* @inheritdoc IStaking
*/
function unstake(uint256 amount) external {
uint256 balanceAfterUnstake = _unstake(amount);
uint256 balanceAfterUnstake = _unstake(msg.sender, amount);
StateSyncer._syncStake(msg.sender, balanceAfterUnstake);
LiquidStaking._collectTokens(msg.sender, amount);
uint256 amountToWithdraw = rewardPool.onUnstake(msg.sender, amount, balanceAfterUnstake);
Expand Down Expand Up @@ -127,21 +127,21 @@ abstract contract Staking is
emit Staked(account, amount);
}

// _______________ Private functions _______________

function _unstake(uint256 amount) private returns (uint256) {
uint256 currentBalance = balanceOf(msg.sender);
function _unstake(address validator, uint256 amount) internal returns (uint256) {
uint256 currentBalance = balanceOf(validator);
if (amount > currentBalance) revert StakeRequirement({src: "unstake", msg: "INSUFFICIENT_BALANCE"});

uint256 balanceAfterUnstake = currentBalance - amount;
if (balanceAfterUnstake < minStake && balanceAfterUnstake != 0)
revert StakeRequirement({src: "unstake", msg: "STAKE_TOO_LOW"});

_burn(msg.sender, amount);
_burn(validator, amount);

return balanceAfterUnstake;
}

// _______________ Private functions _______________

function _ensureStakeIsInRange(uint256 amount, uint256 currentBalance) private view {
if (amount + currentBalance < minStake) revert StakeRequirement({src: "stake", msg: "STAKE_TOO_LOW"});
}
Expand Down
17 changes: 11 additions & 6 deletions contracts/ValidatorSet/modules/Withdrawal/Withdrawal.sol
Original file line number Diff line number Diff line change
Expand Up @@ -15,21 +15,18 @@ abstract contract Withdrawal is IWithdrawal, ReentrancyGuardUpgradeable, Validat
uint256 public WITHDRAWAL_WAIT_PERIOD = 7 days;
mapping(address => WithdrawalQueue) private _withdrawals;

// _______________ External functions _______________

/**
* @inheritdoc IWithdrawal
*/
function withdraw(address to) external nonReentrant {
assert(to != address(0));
WithdrawalQueue storage queue = _withdrawals[msg.sender];
(uint256 amount, uint256 newHead) = queue.withdrawable();
if (amount == 0) revert NoWithdrawalAvailable();
queue.head = newHead;

emit WithdrawalFinished(msg.sender, to, amount);

// slither-disable-next-line low-level-calls
(bool success, ) = to.call{value: amount}(""); // solhint-disable-line avoid-low-level-calls
require(success, "WITHDRAWAL_FAILED");
_withdraw(to, amount);
}

/**
Expand Down Expand Up @@ -59,4 +56,12 @@ abstract contract Withdrawal is IWithdrawal, ReentrancyGuardUpgradeable, Validat
emit WithdrawalRegistered(account, amount);
}

// _______________ Internal functions _______________

function _withdraw(address to, uint256 amount) internal {
(bool success, ) = to.call{value: amount}("");
require(success, "WITHDRAWAL_FAILED");

emit WithdrawalFinished(address(this), to, amount);
}
}
Loading

0 comments on commit de4d60b

Please sign in to comment.