diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index a7a992b4..470a94c2 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -4,7 +4,7 @@ on: push: branches: [main, dev] pull_request: - branches: [main, dev] + branches: [main, dev, feat/*] jobs: build: diff --git a/contracts/staking/stakeManager/StakeManager.sol b/contracts/staking/stakeManager/StakeManager.sol index 68fef49f..e1e70cb6 100644 --- a/contracts/staking/stakeManager/StakeManager.sol +++ b/contracts/staking/stakeManager/StakeManager.sol @@ -421,14 +421,14 @@ contract StakeManager is } function unstake(uint256 validatorId) external onlyStaker(validatorId) { - _unstake(validatorId, false); + _unstakeValidator(validatorId, false); } function unstakePOL(uint256 validatorId) external onlyStaker(validatorId) { - _unstake(validatorId, true); + _unstakeValidator(validatorId, true); } - function _unstake(uint256 validatorId, bool pol) internal { + function _unstakeValidator(uint256 validatorId, bool pol) internal { require(validatorAuction[validatorId].amount == 0); Status status = validators[validatorId].status; @@ -1114,6 +1114,8 @@ contract StakeManager is } function _unstake(uint256 validatorId, uint256 exitEpoch, bool pol) internal { + require(validators[validatorId].deactivationEpoch == 0); + _updateRewards(validatorId); uint256 amount = validators[validatorId].amount; diff --git a/test/units/staking/stakeManager/StakeManager.Staking.js b/test/units/staking/stakeManager/StakeManager.Staking.js index 29187610..da71b106 100644 --- a/test/units/staking/stakeManager/StakeManager.Staking.js +++ b/test/units/staking/stakeManager/StakeManager.Staking.js @@ -410,6 +410,58 @@ describe('unstake', function () { }) }) + describe('forceUnstake cannot only unstake active validators', function () { + const AliceWallet = wallets[1] + const others = [wallets[2], wallets[3]] + + before(async function() { + await freshDeploy.call(this, true) + }) + before(doStake(AliceWallet)) + before(doStake(others[0])) + before(doStake(others[1])) + before('Alice is forceUnstaked', async function () { + this.validatorId = await this.stakeManager.getValidatorId(AliceWallet.getAddressString()) + await this.governance.update( + this.stakeManager.address, + this.stakeManager.interface.encodeFunctionData('forceUnstake', [this.validatorId]) + ) + this.lastSyncedEpoch = await this.stakeManager.currentEpoch() + await checkPoint(others, this.rootChainOwner, this.stakeManager) + }) + it('Alice is unstaked', async function () { + const { deactivationEpoch } = await this.stakeManager.validators(this.validatorId) + assertBigNumberEquality(deactivationEpoch, this.lastSyncedEpoch) + }) + it('Alice cannot be forceUnstaked again', async function () { + await expectRevert(this.governance.update( + this.stakeManager.address, + this.stakeManager.interface.encodeFunctionData('forceUnstake', [this.validatorId]) + ), 'Update failed') + }) + it('Alice cannot be forceUnstaked after claiming', async function () { + const endEpoch = this.lastSyncedEpoch.add(await this.stakeManager.WITHDRAWAL_DELAY()) + // mock for i ... range(delay) checkPoint() + await this.governance.update( + this.stakeManager.address, + this.stakeManager.interface.encodeFunctionData('setCurrentEpoch', [endEpoch + 1]) + ) + + await this.stakeManager + .connect(this.stakeManager.provider.getSigner(AliceWallet.getAddressString())) + .unstakeClaim(this.validatorId) + await checkPoint(others, this.rootChainOwner, this.stakeManager) + + await expectRevert( + this.governance.update( + this.stakeManager.address, + this.stakeManager.interface.encodeFunctionData('forceUnstake', [this.validatorId]) + ), + 'Update failed' + ) + }) + }) + describe('when user unstakes right after stake', async function () { const user = wallets[2].getChecksumAddressString() const amounts = walletAmounts[wallets[2].getAddressString()]