Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

RFC 11: Double Signer Slashing #336

Merged
merged 8 commits into from
Aug 18, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
44 changes: 34 additions & 10 deletions contracts/child/validator/ValidatorSet.sol
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ contract ValidatorSet is IValidatorSet, ERC20VotesUpgradeable, System {
bytes32 private constant STAKE_SIG = keccak256("STAKE");
bytes32 private constant UNSTAKE_SIG = keccak256("UNSTAKE");
bytes32 private constant SLASH_SIG = keccak256("SLASH");
uint256 public constant SLASHING_PERCENTAGE = 50; // to be read through NetworkParams later
uint256 public constant SLASH_INCENTIVE_PERCENTAGE = 30; // exitor reward, to be read through NetworkParams later

IStateSender private stateSender;
address private stateReceiver;
Expand All @@ -25,6 +27,7 @@ contract ValidatorSet is IValidatorSet, ERC20VotesUpgradeable, System {
mapping(uint256 => uint256) private _commitBlockNumbers;
mapping(address => WithdrawalQueue) private withdrawals;
mapping(uint256 => Epoch) public epochs;
mapping(uint256 => bool) public slashProcessed;
uint256[] public epochEndBlocks;

function initialize(
Expand Down Expand Up @@ -68,11 +71,27 @@ contract ValidatorSet is IValidatorSet, ERC20VotesUpgradeable, System {
emit NewEpoch(id, epoch.startBlock, epoch.endBlock, epoch.epochRoot);
}

/**
* @inheritdoc IValidatorSet
*/
function slash(address[] calldata validators) external onlySystemCall {
stateSender.syncState(
rootChainManager,
abi.encode(SLASH_SIG, validators, SLASHING_PERCENTAGE, SLASH_INCENTIVE_PERCENTAGE)
);
}

function onStateReceive(uint256 /*counter*/, address sender, bytes calldata data) external override {
require(msg.sender == stateReceiver && sender == rootChainManager, "INVALID_SENDER");
if (bytes32(data[:32]) == STAKE_SIG) {
(address validator, uint256 amount) = abi.decode(data[32:], (address, uint256));
_stake(validator, amount);
} else if (bytes32(data[:32]) == SLASH_SIG) {
(, uint256 exitEventId, address[] memory validatorsToSlash, uint256 slashingPercentage) = abi.decode(
data,
(bytes32, uint256, address[], uint256)
);
_slash(exitEventId, validatorsToSlash, slashingPercentage); // reuse slashingPercentage present during this slash's initiation
}
}

Expand Down Expand Up @@ -131,16 +150,21 @@ contract ValidatorSet is IValidatorSet, ERC20VotesUpgradeable, System {
emit WithdrawalRegistered(account, amount);
}

/// @dev no public facing slashing function implemented yet
// slither-disable-next-line dead-code
function _slash(address validator) internal {
// unstake validator
_burn(validator, balanceOf(validator));
// remove pending withdrawals
// slither-disable-next-line mapping-deletion
delete withdrawals[validator];
// slash validator
stateSender.syncState(rootChainManager, abi.encode(SLASH_SIG, validator));
function _slash(uint256 exitEventId, address[] memory validatorsToSlash, uint256 slashingPercentage) internal {
require(!slashProcessed[exitEventId], "SLASH_ALREADY_PROCESSED"); // sanity check
slashProcessed[exitEventId] = true;
uint256 length = validatorsToSlash.length;
uint256[] memory slashedAmounts = new uint256[](length);
for (uint256 i = 0; i < length; ) {
slashedAmounts[i] = (balanceOf(validatorsToSlash[i]) * slashingPercentage) / 100;
_burn(validatorsToSlash[i], slashedAmounts[i]); // partially unstake validator
// slither-disable-next-line mapping-deletion
delete withdrawals[validatorsToSlash[i]]; // remove pending withdrawals
unchecked {
++i;
}
}
emit Slashed(exitEventId, validatorsToSlash, slashedAmounts);
}

function _stake(address validator, uint256 amount) internal {
Expand Down
9 changes: 8 additions & 1 deletion contracts/interfaces/child/validator/IValidatorSet.sol
Original file line number Diff line number Diff line change
Expand Up @@ -22,14 +22,21 @@ struct Epoch {
*/
interface IValidatorSet is IStateReceiver {
event NewEpoch(uint256 indexed id, uint256 indexed startBlock, uint256 indexed endBlock, bytes32 epochRoot);
event Slashed(uint256 indexed validator, uint256 amount);
event Slashed(uint256 indexed exitId, address[] validators, uint256[] amounts);
event WithdrawalRegistered(address indexed account, uint256 amount);
event Withdrawal(address indexed account, uint256 amount);

/// @notice commits a new epoch
/// @dev system call
function commitEpoch(uint256 id, Epoch calldata epoch, uint256 epochSize) external;

/// @notice initialises slashing process
/// @dev system call,
/// @dev given list of validators are slashed on L2
/// subsequently after their stake is slashed on L1
/// @param validators list of validators to be slashed
function slash(address[] calldata validators) external;

/// @notice allows a validator to announce their intention to withdraw a given amount of tokens
/// @dev initializes a waiting period before the tokens can be withdrawn
function unstake(uint256 amount) external;
Expand Down
7 changes: 7 additions & 0 deletions contracts/interfaces/root/IExitHelper.sol
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,13 @@ interface IExitHelper {
bytes32[] proof;
}

/**
* @notice Returns the address that called the exit function
* @dev only available in the context of the exit function
* @return address of the caller
*/
function caller() external view returns (address);

/**
* @notice Perform an exit for one event
* @param blockNumber Block number of the exit event on L2
Expand Down
7 changes: 6 additions & 1 deletion contracts/root/ExitHelper.sol
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import "../interfaces/root/IExitHelper.sol";
contract ExitHelper is IExitHelper, Initializable {
mapping(uint256 => bool) public processedExits;
ICheckpointManager public checkpointManager;
address public caller;

event ExitProcessed(uint256 indexed id, bool indexed success, bytes returnData);

Expand Down Expand Up @@ -83,10 +84,14 @@ contract ExitHelper is IExitHelper, Initializable {

processedExits[id] = true;

// slither-disable-next-line calls-loop,low-level-calls,reentrancy-events,reentrancy-no-eth
// slither-disable-next-line costly-loop
caller = msg.sender;
// slither-disable-next-line calls-loop,low-level-calls,reentrancy-events,reentrancy-no-eth,reentrancy-benign
(bool success, bytes memory returnData) = receiver.call(
abi.encodeWithSignature("onL2StateReceive(uint256,address,bytes)", id, sender, data)
);
// slither-disable-next-line costly-loop
caller = address(0);

// if state sync fails, revert flag
if (!success) processedExits[id] = false;
Expand Down
41 changes: 32 additions & 9 deletions contracts/root/staking/CustomSupernetManager.sol
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import "./SupernetManager.sol";
import "../../interfaces/common/IBLS.sol";
import "../../interfaces/IStateSender.sol";
import "../../interfaces/root/staking/ICustomSupernetManager.sol";
import "../../interfaces/root/IExitHelper.sol";

contract CustomSupernetManager is ICustomSupernetManager, Ownable2StepUpgradeable, SupernetManager {
using SafeERC20 for IERC20;
Expand All @@ -16,7 +17,6 @@ contract CustomSupernetManager is ICustomSupernetManager, Ownable2StepUpgradeabl
bytes32 private constant STAKE_SIG = keccak256("STAKE");
bytes32 private constant UNSTAKE_SIG = keccak256("UNSTAKE");
bytes32 private constant SLASH_SIG = keccak256("SLASH");
uint256 public constant SLASHING_PERCENTAGE = 50;

IBLS private bls;
IStateSender private stateSender;
Expand Down Expand Up @@ -121,8 +121,9 @@ contract CustomSupernetManager is ICustomSupernetManager, Ownable2StepUpgradeabl
(address validator, uint256 amount) = abi.decode(data[32:], (address, uint256));
_unstake(validator, amount);
} else if (bytes32(data[:32]) == SLASH_SIG) {
address validator = abi.decode(data[32:], (address));
_slash(validator);
(, address[] memory validatorsToSlash, uint256 slashingPercentage, uint256 slashIncentivePercentage) = abi
.decode(data, (bytes32, address[], uint256, uint256));
_slash(id, validatorsToSlash, slashingPercentage, slashIncentivePercentage);
}
}

Expand Down Expand Up @@ -157,12 +158,34 @@ contract CustomSupernetManager is ICustomSupernetManager, Ownable2StepUpgradeabl
_removeIfValidatorUnstaked(validator);
}

function _slash(address validator) internal {
uint256 stake = stakeManager.stakeOf(validator, id);
uint256 slashedAmount = (stake * SLASHING_PERCENTAGE) / 100;
// slither-disable-next-line reentrancy-benign,reentrancy-events
stakeManager.slashStakeOf(validator, slashedAmount);
_removeIfValidatorUnstaked(validator);
function _slash(
uint256 exitEventId,
address[] memory validatorsToSlash,
uint256 slashingPercentage,
uint256 slashIncentivePercentage
) internal {
uint256 length = validatorsToSlash.length;
uint256 totalSlashedAmount;
for (uint256 i = 0; i < length; ) {
uint256 slashedAmount = (stakeManager.stakeOf(validatorsToSlash[i], id) * slashingPercentage) / 100;
// slither-disable-next-line reentrancy-benign,reentrancy-events,reentrancy-no-eth
stakeManager.slashStakeOf(validatorsToSlash[i], slashedAmount);
_removeIfValidatorUnstaked(validatorsToSlash[i]);
totalSlashedAmount += slashedAmount;
unchecked {
++i;
}
}

// contract will always have enough balance since slashStakeOf returns entire slashed amt
uint256 rewardAmount = (totalSlashedAmount * slashIncentivePercentage) / 100;
matic.safeTransfer(IExitHelper(exitHelper).caller(), rewardAmount);

// complete slashing on child chain
stateSender.syncState(
childValidatorSet,
abi.encode(SLASH_SIG, exitEventId, validatorsToSlash, slashingPercentage)
);
}

function _verifyValidatorRegistration(
Expand Down
79 changes: 76 additions & 3 deletions docs/child/validator/ValidatorSet.md
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,40 @@ function READ_ADDRESSLIST_GAS() external view returns (uint256)



#### Returns

| Name | Type | Description |
|---|---|---|
| _0 | uint256 | undefined |

### SLASHING_PERCENTAGE

```solidity
function SLASHING_PERCENTAGE() external view returns (uint256)
```






#### Returns

| Name | Type | Description |
|---|---|---|
| _0 | uint256 | undefined |

### SLASH_INCENTIVE_PERCENTAGE

```solidity
function SLASH_INCENTIVE_PERCENTAGE() external view returns (uint256)
```






#### Returns

| Name | Type | Description |
Expand Down Expand Up @@ -764,6 +798,44 @@ function permit(address owner, address spender, uint256 value, uint256 deadline,
| r | bytes32 | undefined |
| s | bytes32 | undefined |

### slash

```solidity
function slash(address[] validators) external nonpayable
```

initialises slashing process

*system call,given list of validators are slashed on L2 subsequently after their stake is slashed on L1*

#### Parameters

| Name | Type | Description |
|---|---|---|
| validators | address[] | list of validators to be slashed |

### slashProcessed

```solidity
function slashProcessed(uint256) external view returns (bool)
```





#### Parameters

| Name | Type | Description |
|---|---|---|
| _0 | uint256 | undefined |

#### Returns

| Name | Type | Description |
|---|---|---|
| _0 | bool | undefined |

### symbol

```solidity
Expand Down Expand Up @@ -1045,7 +1117,7 @@ event NewEpoch(uint256 indexed id, uint256 indexed startBlock, uint256 indexed e
### Slashed

```solidity
event Slashed(uint256 indexed validator, uint256 amount)
event Slashed(uint256 indexed exitId, address[] validators, uint256[] amounts)
```


Expand All @@ -1056,8 +1128,9 @@ event Slashed(uint256 indexed validator, uint256 amount)

| Name | Type | Description |
|---|---|---|
| validator `indexed` | uint256 | undefined |
| amount | uint256 | undefined |
| exitId `indexed` | uint256 | undefined |
| validators | address[] | undefined |
| amounts | uint256[] | undefined |

### Transfer

Expand Down
23 changes: 20 additions & 3 deletions docs/interfaces/child/validator/IValidatorSet.md
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,22 @@ Calculates how much is yet to become withdrawable for account.
|---|---|---|
| _0 | uint256 | Amount not yet withdrawable (in MATIC wei) |

### slash

```solidity
function slash(address[] validators) external nonpayable
```

initialises slashing process

*system call,given list of validators are slashed on L2 subsequently after their stake is slashed on L1*

#### Parameters

| Name | Type | Description |
|---|---|---|
| validators | address[] | list of validators to be slashed |

### totalBlocks

```solidity
Expand Down Expand Up @@ -210,7 +226,7 @@ event NewEpoch(uint256 indexed id, uint256 indexed startBlock, uint256 indexed e
### Slashed

```solidity
event Slashed(uint256 indexed validator, uint256 amount)
event Slashed(uint256 indexed exitId, address[] validators, uint256[] amounts)
```


Expand All @@ -221,8 +237,9 @@ event Slashed(uint256 indexed validator, uint256 amount)

| Name | Type | Description |
|---|---|---|
| validator `indexed` | uint256 | undefined |
| amount | uint256 | undefined |
| exitId `indexed` | uint256 | undefined |
| validators | address[] | undefined |
| amounts | uint256[] | undefined |

### Withdrawal

Expand Down
17 changes: 17 additions & 0 deletions docs/interfaces/root/IExitHelper.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,23 @@ function batchExit(IExitHelper.BatchExitInput[] inputs) external nonpayable
|---|---|---|
| inputs | IExitHelper.BatchExitInput[] | undefined |

### caller

```solidity
function caller() external view returns (address)
```

Returns the address that called the exit function

*only available in the context of the exit function*


#### Returns

| Name | Type | Description |
|---|---|---|
| _0 | address | address of the caller |

### exit

```solidity
Expand Down
Loading
Loading