Skip to content

Commit

Permalink
Allow unstake passing a larger amount than available (#487)
Browse files Browse the repository at this point in the history
* staking: allow unstake with larger amount than available
* staking: remove unused check and add comment
  • Loading branch information
abarmat authored Sep 22, 2021
1 parent 834854a commit f34e90b
Show file tree
Hide file tree
Showing 2 changed files with 46 additions and 12 deletions.
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

0 comments on commit f34e90b

Please sign in to comment.