Skip to content

Commit

Permalink
feat: rework slash implementation on CustomSupernetManager
Browse files Browse the repository at this point in the history
  • Loading branch information
DhairyaSethi committed Aug 16, 2023
1 parent 49bf4ab commit 4994dfd
Show file tree
Hide file tree
Showing 3 changed files with 123 additions and 14 deletions.
32 changes: 24 additions & 8 deletions contracts/root/staking/CustomSupernetManager.sol
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ contract CustomSupernetManager is ICustomSupernetManager, Ownable2StepUpgradeabl
bytes32 private constant UNSTAKE_SIG = keccak256("UNSTAKE");
bytes32 private constant SLASH_SIG = keccak256("SLASH");
uint256 public constant SLASHING_PERCENTAGE = 50;
uint256 public constant SLASH_INCENTIVE_PERCENTAGE = 30;

IBLS private bls;
IStateSender private stateSender;
Expand Down Expand Up @@ -121,8 +122,8 @@ 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) = abi.decode(data, (bytes32, address[]));
_slash(id, validatorsToSlash);
}
}

Expand Down Expand Up @@ -157,12 +158,27 @@ 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) internal {
uint256 length = validatorsToSlash.length;
uint256 totalSlashedAmount;
for (uint256 i = 0; i < length; ) {
uint256 slashedAmount = (stakeManager.stakeOf(validatorsToSlash[i], id) * SLASHING_PERCENTAGE) / 100;
// slither-disable-next-line reentrancy-benign,reentrancy-events
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 * SLASH_INCENTIVE_PERCENTAGE) / 100;
// solhint-disable avoid-tx-origin
matic.safeTransfer(tx.origin, rewardAmount);

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

function _verifyValidatorRegistration(
Expand Down
17 changes: 17 additions & 0 deletions docs/root/staking/CustomSupernetManager.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,23 @@ 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
88 changes: 82 additions & 6 deletions test/forge/root/staking/CustomSupernetManager.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -147,7 +147,9 @@ abstract contract Slashed is EnabledStaking {

function setUp() public virtual override {
super.setUp();
bytes memory callData = abi.encode(SLASH_SIG, address(this));
address[] memory validatorsToSlash = new address[](1);
validatorsToSlash[0] = address(this);
bytes memory callData = abi.encode(SLASH_SIG, validatorsToSlash);
vm.prank(exitHelper);
supernetManager.onL2StateReceive(1, childValidatorSet, callData);
}
Expand Down Expand Up @@ -376,16 +378,35 @@ contract CustomSupernetManager_Unstake is EnabledStaking {
}

contract CustomSupernetManager_Slash is EnabledStaking {
address private mev = makeAddr("MEV");
bytes32 private constant SLASH_SIG = keccak256("SLASH");
event ValidatorDeactivated(address indexed validator);
event StateSynced(uint256 indexed id, address indexed sender, address indexed receiver, bytes data);
event StakeRemoved(uint256 indexed id, address indexed validator, uint256 amount);
event StakeWithdrawn(address indexed validator, address indexed recipient, uint256 amount);
event Transfer(address indexed from, address indexed to, uint256 value);

function test_SuccessfulFullWithdrawal() public {
uint256 exitEventId = 1;
uint256 slashingPercentage = supernetManager.SLASHING_PERCENTAGE();
bytes memory callData = abi.encode(SLASH_SIG, address(this));

address[] memory validatorsToSlash = new address[](1);
validatorsToSlash[0] = address(this);
bytes memory callData = abi.encode(SLASH_SIG, validatorsToSlash);
uint256 slashedAmount = (amount * slashingPercentage) / 100;

vm.expectEmit(true, true, true, true);
emit StakeWithdrawn(address(this), address(supernetManager), slashedAmount);
vm.expectEmit(true, true, true, true);
emit StakeRemoved(exitEventId, address(this), amount);
vm.expectEmit(true, true, true, true);
emit ValidatorDeactivated(address(this));
// emits state sync event to complete slashing on child chain
vm.expectEmit(true, true, true, true);
emit StateSynced(exitEventId, address(supernetManager), childValidatorSet, abi.encode(SLASH_SIG, exitEventId, validatorsToSlash));
vm.prank(exitHelper);
supernetManager.onL2StateReceive(1, childValidatorSet, callData);
supernetManager.onL2StateReceive(exitEventId, childValidatorSet, callData);

assertEq(stakeManager.stakeOf(address(this), 1), 0, "should unstake all");
assertEq(
stakeManager.withdrawableStake(address(this)),
Expand All @@ -394,6 +415,59 @@ contract CustomSupernetManager_Slash is EnabledStaking {
);
assertEq(supernetManager.getValidator(address(this)).isActive, false, "should deactivate");
}


function test_SlashIncentiveDistribution() external {
uint256 exitEventId = 1;
address[] memory validatorsToSlash = new address[](1);
validatorsToSlash[0] = address(this);
bytes memory callData = abi.encode(SLASH_SIG, validatorsToSlash);
uint256 slashedAmount = (amount * supernetManager.SLASHING_PERCENTAGE()) / 100;
uint256 exitorReward = (slashedAmount * supernetManager.SLASH_INCENTIVE_PERCENTAGE()) / 100;

assertEq(token.balanceOf(mev), 0); // balance before
vm.expectEmit(true, true, true, true);
emit Transfer(address(supernetManager), mev, exitorReward);
vm.prank(exitHelper, mev /* tx.origin */);
supernetManager.onL2StateReceive(exitEventId, childValidatorSet, callData);
assertEq(token.balanceOf(mev), exitorReward, "should transfer slashing reward");
}

function test_SlashEntireValidatorSet() external {
uint256 slashingPercentage = supernetManager.SLASHING_PERCENTAGE();
uint256 aliceStakedAmount = amount << 3;
token.mint(alice, aliceStakedAmount);
vm.prank(alice);
stakeManager.stakeFor(1, aliceStakedAmount);

uint256 thisSlashedAmount = (amount * slashingPercentage) / 100;
uint256 aliceSlashedAmount = (aliceStakedAmount * slashingPercentage) / 100;

address[] memory validatorsToSlash = new address[](2);
validatorsToSlash[0] = alice;
validatorsToSlash[1] = address(this);
bytes memory callData = abi.encode(SLASH_SIG, validatorsToSlash);

vm.prank(exitHelper, mev /* tx.origin */);
supernetManager.onL2StateReceive(1, childValidatorSet, callData);

assertEq(stakeManager.stakeOf(address(this), 1), 0, "should unstake all");
assertEq(stakeManager.stakeOf(alice, 1), 0, "should unstake all");
assertEq(
stakeManager.withdrawableStake(address(this)),
amount - thisSlashedAmount,
"should slash"
);
assertEq(
stakeManager.withdrawableStake(alice),
aliceStakedAmount - aliceSlashedAmount,
"should slash"
);
assertEq(supernetManager.getValidator(address(this)).isActive, false, "should deactivate");
assertEq(supernetManager.getValidator(alice).isActive, false, "should deactivate");
uint256 exitorReward = ((thisSlashedAmount + aliceSlashedAmount) * supernetManager.SLASH_INCENTIVE_PERCENTAGE()) / 100;
assertEq(token.balanceOf(mev), exitorReward, "should transfer slashing reward");
}
}

contract CustomSupernetManager_WithdrawSlash is Slashed {
Expand All @@ -406,10 +480,12 @@ contract CustomSupernetManager_WithdrawSlash is Slashed {
}

function test_WithdrawSlashedAmount() public {
uint256 slashedAmount = amount / 2;
assertEq(token.balanceOf(address(supernetManager)), slashedAmount);
uint256 slashedAmount = (amount * supernetManager.SLASHING_PERCENTAGE()) / 100;
uint256 slashingReward = (slashedAmount * supernetManager.SLASH_INCENTIVE_PERCENTAGE()) / 100; // given to exitor after slash
uint256 withdrawableAmount = slashedAmount - slashingReward;
assertEq(token.balanceOf(address(supernetManager)), withdrawableAmount);
vm.expectEmit(true, true, true, true);
emit Transfer(address(supernetManager), alice, slashedAmount);
emit Transfer(address(supernetManager), alice, withdrawableAmount);
supernetManager.withdrawSlashedStake(alice);
assertEq(token.balanceOf(address(supernetManager)), 0);
}
Expand Down

0 comments on commit 4994dfd

Please sign in to comment.