Skip to content

Allow unstake passing a larger amount than available #487

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

Merged
merged 2 commits into from
Sep 22, 2021
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
17 changes: 12 additions & 5 deletions contracts/staking/Staking.sol
Original file line number Diff line number Diff line change
Expand Up @@ -707,27 +707,34 @@ contract Staking is StakingV2Storage, GraphUpgradeable, IStaking {

/**
* @dev Unstake tokens from the indexer stake, lock them until thawing period expires.
* NOTE: The function accepts an amount greater than the currently staked tokens.
* If that happens, it will try to unstake the max amount of tokens it can.
* The reason for this behaviour is to avoid time conditions while the transaction
* is in flight.
* @param _tokens Amount of tokens to unstake
*/
function unstake(uint256 _tokens) external override notPartialPaused {
address indexer = msg.sender;
Stakes.Indexer storage indexerStake = stakes[indexer];

require(_tokens > 0, "!tokens");
require(indexerStake.tokensStaked > 0, "!stake");
require(indexerStake.tokensAvailable() >= _tokens, "!stake-avail");

// Tokens to lock is capped to the available tokens
uint256 tokensToLock = MathUtils.min(indexerStake.tokensAvailable(), _tokens);
require(tokensToLock > 0, "!stake-avail");

// Ensure minimum stake
uint256 newStake = indexerStake.tokensSecureStake().sub(_tokens);
uint256 newStake = indexerStake.tokensSecureStake().sub(tokensToLock);
require(newStake == 0 || newStake >= minimumIndexerStake, "!minimumIndexerStake");

// Before locking more tokens, withdraw any unlocked ones
// Before locking more tokens, withdraw any unlocked ones if possible
uint256 tokensToWithdraw = indexerStake.tokensWithdrawable();
if (tokensToWithdraw > 0) {
_withdraw(indexer);
}

indexerStake.lockTokens(_tokens, thawingPeriod);
// Update the indexer stake locking tokens
indexerStake.lockTokens(tokensToLock, thawingPeriod);

emit StakeLocked(indexer, indexerStake.tokensLocked, indexerStake.tokensLockedUntil);
}
Expand Down
41 changes: 34 additions & 7 deletions test/staking/staking.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,11 @@ import {
latestBlock,
toBN,
toGRT,
provider,
Account,
} from '../lib/testHelpers'

const { AddressZero } = constants
const { AddressZero, MaxUint256 } = constants

function weightedAverage(
valueA: BigNumber,
Expand Down Expand Up @@ -269,14 +270,18 @@ describe('Staking:Stakes', () => {
expect(afterIndexerBalance).eq(beforeIndexerBalance.add(tokensToUnstake))
})

it('reject unstake zero tokens', async function () {
const tx = staking.connect(indexer.signer).unstake(toGRT('0'))
await expect(tx).revertedWith('!tokens')
it('should unstake available tokens even if passed a higher amount', async function () {
// Try to unstake a bit more than currently staked
const tokensOverCapacity = tokensToStake.add(toGRT('1'))
await staking.connect(indexer.signer).unstake(tokensOverCapacity)

// Check state
const tokensLocked = (await staking.stakes(indexer.address)).tokensLocked
expect(tokensLocked).eq(tokensToStake)
})

it('reject unstake more than available tokens', async function () {
const tokensOverCapacity = tokensToStake.add(toGRT('1'))
const tx = staking.connect(indexer.signer).unstake(tokensOverCapacity)
it('reject unstake zero tokens', async function () {
const tx = staking.connect(indexer.signer).unstake(toGRT('0'))
await expect(tx).revertedWith('!stake-avail')
})

Expand Down Expand Up @@ -305,6 +310,28 @@ describe('Staking:Stakes', () => {
await staking.connect(indexer.signer).unstake(tokensToStake)
expect(await staking.getIndexerCapacity(indexer.address)).eq(0)
})

it('should allow unstake of full amount with no upper limits', async function () {
// Use manual mining
await provider().send('evm_setAutomine', [false])

// Setup
const newTokens = toGRT('2')
const stakedTokens = await staking.getIndexerStakedTokens(indexer.address)
const tokensToUnstake = stakedTokens.add(newTokens)

// StakeTo & Unstake
await staking.connect(indexer.signer).stakeTo(indexer.address, newTokens)
await staking.connect(indexer.signer).unstake(MaxUint256)
await provider().send('evm_mine', [])

// Check state
const tokensLocked = (await staking.stakes(indexer.address)).tokensLocked
expect(tokensLocked).eq(tokensToUnstake)

// Restore automine
await provider().send('evm_setAutomine', [true])
})
})

describe('withdraw', function () {
Expand Down