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

Slashing issue #6

Open
wants to merge 10 commits into
base: master
Choose a base branch
from
6 changes: 4 additions & 2 deletions contracts/Core/StakeManager.sol
Original file line number Diff line number Diff line change
Expand Up @@ -100,13 +100,14 @@ contract StakeManager is Initializable, StakeStorage, StateManager, Pause, Stake
stakerIds[msg.sender] = stakerId;
// slither-disable-next-line reentrancy-benign
IStakedToken sToken = IStakedToken(stakedTokenFactory.createStakedToken(address(this), numStakers));
stakers[numStakers] = Structs.Staker(false, 0, numStakers, 10000, msg.sender, address(sToken), epoch, 0, amount);
stakers[numStakers] = Structs.Staker(false, false, 0, numStakers, 10000, msg.sender, address(sToken), epoch, 0, amount);

// Minting
require(sToken.mint(msg.sender, amount, amount), "tokens not minted"); // as 1RZR = 1 sRZR
totalSupply = amount;
} else {
require(amount + stakers[stakerId].stake >= minStake, "amount + stake below min Stake");
require(!stakers[stakerId].isSlashed, "staker is slashed");
IStakedToken sToken = IStakedToken(stakers[stakerId].tokenAddress);
totalSupply = sToken.totalSupply();
uint256 toMint = _convertRZRtoSRZR(amount, stakers[stakerId].stake, totalSupply); // RZRs to sRZRs
Expand Down Expand Up @@ -143,7 +144,7 @@ contract StakeManager is Initializable, StakeStorage, StateManager, Pause, Stake
) external initialized checkEpoch(epoch, epochLength) whenNotPaused {
require(stakers[stakerId].acceptDelegation, "Delegetion not accpected");
require(_isStakerActive(stakerId, epoch), "Staker is inactive");

require(!stakers[stakerId].isSlashed, "Staker is slashed");
// Step 1 : Calculate Mintable amount
IStakedToken sToken = IStakedToken(stakers[stakerId].tokenAddress);
uint256 totalSupply = sToken.totalSupply();
Expand Down Expand Up @@ -337,6 +338,7 @@ contract StakeManager is Initializable, StakeStorage, StateManager, Pause, Stake

uint256 slashPenaltyAmount = bounty + amountToBeBurned + amountToBeKept;
_stake = _stake - slashPenaltyAmount;
stakers[stakerId].isSlashed = true;
_setStakerStake(epoch, stakerId, StakeChanged.Slashed, _stake + slashPenaltyAmount, _stake);

if (bounty == 0) return 0;
Expand Down
1 change: 1 addition & 0 deletions contracts/Core/VoteManager.sol
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ contract VoteManager is Initializable, VoteStorage, StateManager, VoteManagerPar
function commit(uint32 epoch, bytes32 commitment) external initialized checkEpochAndState(State.Commit, epoch, epochLength) {
require(commitment != 0x0, "Invalid commitment");
uint32 stakerId = stakeManager.getStakerId(msg.sender);
require(!stakeManager.getStaker(stakerId).isSlashed, "VM : staker is slashed");
require(stakerId > 0, "Staker does not exist");
require(commitments[stakerId].epoch != epoch, "already commited");

Expand Down
1 change: 1 addition & 0 deletions contracts/lib/Structs.sol
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ library Structs {
struct Staker {
// Slot 1
bool acceptDelegation;
bool isSlashed;
uint8 commission;
uint32 id;
uint32 age;
Expand Down
393 changes: 194 additions & 199 deletions test/BlockManager.js

Large diffs are not rendered by default.

54 changes: 26 additions & 28 deletions test/StakeManager.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@ const {
STAKE_MODIFIER_ROLE,
WITHDRAW_RELEASE_PERIOD,
GOVERNER_ROLE,
BASE_DENOMINATOR,
PAUSE_ROLE,
} = require('./helpers/constants');
const {
Expand Down Expand Up @@ -344,7 +343,7 @@ describe('StakeManager', function () {
assertBNEqual(await razor.balanceOf(staker._address), prevBalance.add(lock.amount), 'Balance should be equal');
});

it('should allow staker to add stake after withdraw or slash if either withdrawnAmount or slashPenaltyAmount is not the whole stake', async function () {
it('should allow staker to add stake after withdraw if either withdrawnAmount is not the whole stake', async function () {
const epoch = await getEpoch();
const stake = tokenAmount('20000');
const stakerIdAcc1 = await stakeManager.stakerIds(signers[1].address);
Expand All @@ -353,30 +352,6 @@ describe('StakeManager', function () {
await stakeManager.connect(signers[1]).stake(epoch, stake); // adding stake after withdraw
const stakeAfterAcc1 = (await stakeManager.stakers(stakerIdAcc1)).stake;
assertBNEqual(stakeAfterAcc1, stakeBeforeAcc1.add(stake), 'Stake did not increase on staking after withdraw');

await stakeManager.grantRole(STAKE_MODIFIER_ROLE, signers[0].address);
await governance.grantRole(GOVERNER_ROLE, signers[0].address);
await governance.setSlashParams(500, 4500, 0); // slashing only half stake
await stakeManager.slash(epoch, stakerIdAcc1, signers[10].address); // slashing signers[1]

const slashNums = await stakeManager.slashNums();
const bountySlashNum = slashNums[0];
const burnSlashNum = slashNums[1];
const keepSlashNum = slashNums[2];
const amountToBeBurned = stakeAfterAcc1.mul(burnSlashNum).div(BASE_DENOMINATOR);
const bounty = stakeAfterAcc1.mul(bountySlashNum).div(BASE_DENOMINATOR);
const amountTobeKept = stakeAfterAcc1.mul(keepSlashNum).div(BASE_DENOMINATOR);
const slashPenaltyAmount = amountToBeBurned.add(bounty).add(amountTobeKept);

let staker = await stakeManager.getStaker(stakerIdAcc1);
const stakeAfterSlash = staker.stake;
assertBNEqual(stakeAfterSlash, stakeAfterAcc1.sub(slashPenaltyAmount), 'Stake should be less by slashPenalty');

const stake2 = tokenAmount('20000');
await razor.connect(signers[1]).approve(stakeManager.address, stake2);
await stakeManager.connect(signers[1]).stake(epoch, stake2);
staker = await stakeManager.getStaker(stakerIdAcc1);
assertBNEqual(staker.stake, stakeAfterSlash.add(stake2), 'Stake did not increase on staking after slash');
});

it('should not allow staker to add stake after withdrawing whole amount', async function () {
Expand Down Expand Up @@ -1032,9 +1007,11 @@ describe('StakeManager', function () {
assertBNEqual(DelegatorBalance, newBalance, 'Delagators balance does not match the calculated balance');
});

it('should not allow staker to add stake after being slashed the whole amount', async function () {
it('should not allow staker to add stake after being slashed', async function () {
const epoch = await getEpoch();
const stake1 = tokenAmount('423000');
await stakeManager.grantRole(STAKE_MODIFIER_ROLE, signers[0].address);
await governance.grantRole(GOVERNER_ROLE, signers[0].address);
await razor.connect(signers[7]).approve(stakeManager.address, stake1);
await stakeManager.connect(signers[7]).stake(epoch, stake1);
const stakerIdAcc7 = await stakeManager.stakerIds(signers[7].address);
Expand All @@ -1044,7 +1021,7 @@ describe('StakeManager', function () {
const stake2 = tokenAmount('20000');
await razor.connect(signers[7]).approve(stakeManager.address, stake2);
const tx = stakeManager.connect(signers[7]).stake(epoch, stake2);
await assertRevert(tx, 'Stakers Stake is 0');
await assertRevert(tx, 'staker is slashed');
});

it('non admin should not be able to withdraw funds in emergency', async function () {
Expand Down Expand Up @@ -1524,5 +1501,26 @@ describe('StakeManager', function () {
const tx1 = stakeManager.connect(signers[4]).unstake(epoch, stakerIdAcc, 1);
await assertRevert(tx1, 'Unstake: NA Dispute');
});

it('Delegator should not be able to delegate funds to slashed Staker', async function () {
await mineToNextEpoch();
const epoch = await getEpoch();

const stakerIdAcc4 = await stakeManager.stakerIds(signers[4].address);
const votes1 = [100, 200, 300, 400, 500, 600, 700, 800, 900];
const commitment1 = utils.solidityKeccak256(
['uint32', 'uint48[]', 'bytes32'],
[epoch, votes1, '0x727d5c9e6d18ed15ce7ac8d3cce6ec8a0e9c02481415c0823ea49d847ccb9ddd']
);
await voteManager.connect(signers[4]).commit(epoch, commitment1);
await mineToNextState();
await voteManager.connect(signers[4]).reveal(epoch, votes1,
'0x727d5c9e6d18ed15ce7ac8d3cce6ec8a0e9c02481415c0823ea49d847ccb9ddd');
await governance.setSlashParams(500, 4500, 0); // slashing only half stake
await stakeManager.slash(epoch, stakerIdAcc4, signers[10].address); // slashing signers[1]
const amount = tokenAmount('1000');
const tx = stakeManager.connect(signers[10]).delegate(epoch, stakerIdAcc4, amount);
await assertRevert(tx, 'Staker is slashed');
});
});
});
34 changes: 30 additions & 4 deletions test/VoteManager.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
test unstake and withdraw
test cases where nobody votes, too low stake (1-4) */

const { assert } = require('chai');
const { utils } = require('ethers');
const {
DEFAULT_ADMIN_ROLE_HASH,
Expand Down Expand Up @@ -499,10 +500,10 @@ describe('VoteManager', function () {
const epoch = await getEpoch();
await stakeManager.connect(signers[7]).stake(epoch, tokenAmount('1000'));
const stakerId = await stakeManager.stakerIds(signers[7].address);
const staker = await stakeManager.getStaker(stakerId);
// slashing the staker to make his stake below minstake
await stakeManager.grantRole(STAKE_MODIFIER_ROLE, signers[0].address);
await governance.setSlashParams(500, 4500, 0);
await stakeManager.slash(epoch, stakerId, signers[11].address);
await stakeManager.setStakerStake(epoch, stakerId, 2, staker.stake, tokenAmount('999'));

const votes = [100, 200, 300, 400, 500, 600, 700, 800, 900];

Expand Down Expand Up @@ -616,6 +617,7 @@ describe('VoteManager', function () {
it('Staker should not be able to reveal if stake is zero', async function () {
const epoch = await getEpoch();
const stakerId = await stakeManager.stakerIds(signers[7].address);
const staker = await stakeManager.getStaker(stakerId);
const votes = [100, 200, 300, 400, 500, 600, 700, 800, 900]; // 900 changed to 950 for having incorrect value

const commitment1 = utils.solidityKeccak256(
Expand All @@ -626,8 +628,8 @@ describe('VoteManager', function () {
await voteManager.connect(signers[7]).commit(epoch, commitment1);

await stakeManager.grantRole(STAKE_MODIFIER_ROLE, signers[0].address);
await governance.setSlashParams(500, 9500, 0);
await stakeManager.slash(epoch, stakerId, signers[10].address); // slashing signers[7] 100% making his stake zero
// setting stake below minstake
await stakeManager.setStakerStake(epoch, stakerId, 2, staker.stake, tokenAmount('0'));

await mineToNextState(); // reveal
const tx = voteManager.connect(signers[7]).reveal(epoch, votes,
Expand Down Expand Up @@ -923,6 +925,30 @@ describe('VoteManager', function () {
const stakeAfter = (await stakeManager.stakers(stakerIdAcc3)).stake;
assertBNLessThan(stakeAfter, stakeBefore, 'stake should reduce');
});
it('slashed staker should not be able to participate after it is slashed', async function () {
await mineToNextEpoch();
const epoch = await getEpoch();
const votes = [100, 200, 300, 400, 500, 600, 700, 800];
const stakerIdAcc4 = await stakeManager.stakerIds(signers[4].address);
const staker = await stakeManager.getStaker(stakerIdAcc4);
const commitment = utils.solidityKeccak256(
['uint32', 'uint48[]', 'bytes32'],
[epoch, votes, '0x727d5c9e6d18ed15ce7ac8d3cce6ec8a0e9c02481415c0823ea49d847ccb9ddd']
);
const tx = voteManager.connect(signers[4]).commit(epoch, commitment);
await assertRevert(tx, 'staker is slashed');
await mineToNextState();
const tx1 = voteManager.connect(signers[4]).reveal(epoch, votes, '0x727d5c9e6d18ed15ce7ac8d3cce6ec8a0e9c02481415c0823ea49d847ccb9ddd');
await assertRevert(tx1, 'not committed in this epoch');
await mineToNextState();
const { biggestInfluence, biggestInfluencerId } = await getBiggestInfluenceAndId(stakeManager, voteManager);
const iteration = await getIteration(voteManager, stakeManager, staker, biggestInfluence);
const tx2 = blockManager.connect(signers[4]).propose(epoch,
[100, 200, 300, 400, 500, 600, 700, 800],
iteration,
biggestInfluencerId);
await assertRevert(tx2, 'Cannot propose without revealing');
});
});
});
});
93 changes: 0 additions & 93 deletions test/scenarios.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ const { utils } = require('ethers');
const { getState } = require('./helpers/utils');
const {
ASSET_MODIFIER_ROLE,
STAKE_MODIFIER_ROLE,
GRACE_PERIOD,
WITHDRAW_LOCK_PERIOD,
GOVERNER_ROLE,
Expand Down Expand Up @@ -1158,96 +1157,4 @@ describe('Scenarios', async () => {

await assertRevert(tx, 'stake below minimum stake');
}).timeout(5000);

it('MinStake is decreased after staker getting slashed, have a chance to participate', async () => {
let epoch = await getEpoch();
const razors = tokenAmount('444000');
await razor.transfer(signers[6].address, razors);
const stake = tokenAmount('442000');
await razor.connect(signers[6]).approve(stakeManager.address, stake);
await stakeManager.connect(signers[6]).stake(epoch, stake);
let votes = await getVote(medians);
const commitment = utils.solidityKeccak256(
['uint32', 'uint48[]', 'bytes32'],
[epoch, votes, '0x727d5c9e6d18ed15ce7ac8d3cce6ec8a0e9c02481415c0823ea49d847ccb9ddd']
);
await voteManager.connect(signers[6]).commit(epoch, commitment);

await mineToNextState(); // reveal
let stakerId = await stakeManager.stakerIds(signers[6].address);
let staker = await stakeManager.getStaker(stakerId);

await voteManager.connect(signers[6]).reveal(epoch, votes,
'0x727d5c9e6d18ed15ce7ac8d3cce6ec8a0e9c02481415c0823ea49d847ccb9ddd');

await stakeManager.grantRole(STAKE_MODIFIER_ROLE, signers[0].address);
await governance.setSlashParams(500, 9480, 0); // slashing only half stake
await stakeManager.slash(epoch, stakerId, signers[6].address); // slashing signers[6]

staker = await stakeManager.getStaker(stakerId);

await mineToNextState(); // propose

const { biggestInfluence, biggestInfluencerId } = await getBiggestInfluenceAndId(stakeManager, voteManager);
let iteration = await getIteration(voteManager, stakeManager, staker, biggestInfluence);
const tx = blockManager.connect(signers[6]).propose(epoch,
medians,
iteration,
biggestInfluencerId);

await assertRevert(tx, 'stake below minimum stake');
await mineToNextState(); // dispute
await mineToNextState(); // confirm
stakerId = await stakeManager.stakerIds(signers[6].address);
staker = await stakeManager.getStaker(stakerId);

let amount = staker.stake;
let newMinStake = amount.sub(tokenAmount('10'));
await governance.connect(signers[0]).setMinStake(newMinStake);
await mineToNextEpoch(); // commit
epoch = await getEpoch();
votes = await getVote(medians);
const commitment1 = utils.solidityKeccak256(
['uint32', 'uint48[]', 'bytes32'],
[epoch, votes, '0x727d5c9e6d18ed15ce7ac8d3cce6ec8a0e9c02481415c0823ea49d847ccb9ddd']
);
await voteManager.connect(signers[6]).commit(epoch, commitment1);
stakerId = await stakeManager.stakerIds(signers[6].address);
staker = await stakeManager.getStaker(stakerId);
let commitmentAcc1 = await voteManager.getCommitment(stakerId);
assertBNEqual(epoch, commitmentAcc1.epoch, 'Staker is not able to participate');
await mineToNextState(); // reveal
await voteManager.connect(signers[6]).reveal(epoch, votes,
'0x727d5c9e6d18ed15ce7ac8d3cce6ec8a0e9c02481415c0823ea49d847ccb9ddd');
await mineToNextState(); // propose

iteration = await getIteration(voteManager, stakeManager, staker, biggestInfluence);
await blockManager.connect(signers[6]).propose(epoch,
medians,
iteration,
biggestInfluencerId);

await mineToNextState(); // Dispute
await stakeManager.grantRole(STAKE_MODIFIER_ROLE, signers[0].address);
await governance.setSlashParams(500, 9480, 0); // slashing only half stake
await stakeManager.slash(epoch, stakerId, signers[6].address); // slashing signers[6]
await mineToNextState(); // Confirm
stakerId = await stakeManager.stakerIds(signers[6].address);
staker = await stakeManager.getStaker(stakerId);
amount = staker.stake;
newMinStake = amount.sub(tokenAmount('1'));
await governance.connect(signers[0]).setMinStake(newMinStake);
await mineToNextEpoch(); // commit
epoch = await getEpoch();
votes = await getVote(medians);
const commitment2 = utils.solidityKeccak256(
['uint32', 'uint48[]', 'bytes32'],
[epoch, votes, '0x727d5c9e6d18ed15ce7ac8d3cce6ec8a0e9c02481415c0823ea49d847ccb9ddd']
);
await voteManager.connect(signers[6]).commit(epoch, commitment2);
stakerId = await stakeManager.stakerIds(signers[6].address);
staker = await stakeManager.getStaker(stakerId);
commitmentAcc1 = await voteManager.getCommitment(stakerId);
assertBNEqual(epoch, commitmentAcc1.epoch, 'Staker is not able to participate');
});
});