Skip to content

Commit c309d50

Browse files
committed
staking:
- Add check to DisputeManager to avoid accepting disputes of indexers with zero tokens to slash - Add check to Staking to avoid slashing zero tokens - Add check to Staking to avoid staking zero tokens - Add check in Rebates calculation to avoid division by zero when no fees in rebate pool - Improve check in Staking contract `settle()` when comparing allocation channelID - Allow to claim a zero tokens settlement from the rebate pool - Fix comment in test helpers config - Add tests for all of the above
1 parent 0a4216d commit c309d50

File tree

6 files changed

+271
-134
lines changed

6 files changed

+271
-134
lines changed

contracts/DisputeManager.sol

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -203,7 +203,7 @@ contract DisputeManager is Governed {
203203
* @return Amount of tokens to slash
204204
*/
205205
function getTokensToSlash(address _indexer) public view returns (uint256) {
206-
uint256 tokens = staking.getIndexerStakeTokens(_indexer); // slashable tokens
206+
uint256 tokens = staking.getIndexerStakedTokens(_indexer); // slashable tokens
207207
return slashingPercentage.mul(tokens).div(MAX_PPM);
208208
}
209209

@@ -316,8 +316,10 @@ contract DisputeManager is Governed {
316316

317317
// Have staking contract slash the indexer and reward the fisherman
318318
// Give the fisherman a reward equal to the fishermanRewardPercentage of slashed amount
319-
uint256 tokensToReward = getTokensToReward(dispute.indexer);
320319
uint256 tokensToSlash = getTokensToSlash(dispute.indexer);
320+
uint256 tokensToReward = getTokensToReward(dispute.indexer);
321+
322+
require(tokensToSlash > 0, "Dispute has zero tokens to slash");
321323
staking.slash(dispute.indexer, tokensToSlash, tokensToReward, dispute.fisherman);
322324

323325
// Give the fisherman their deposit back

contracts/Staking.sol

Lines changed: 22 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -244,7 +244,7 @@ contract Staking is Governed {
244244
* @param _indexer Address of the indexer
245245
* @return Amount of tokens staked by the indexer
246246
*/
247-
function getIndexerStakeTokens(address _indexer) public view returns (uint256) {
247+
function getIndexerStakedTokens(address _indexer) public view returns (uint256) {
248248
return stakes[_indexer].tokensIndexer;
249249
}
250250

@@ -292,9 +292,10 @@ contract Staking is Governed {
292292
) external onlySlasher {
293293
Stakes.Indexer storage indexerStake = stakes[_indexer];
294294

295+
require(_tokens > 0, "Slashing: cannot slash zero tokens");
296+
require(_tokens >= _reward, "Slashing: reward cannot be higher than slashed amount");
295297
require(indexerStake.hasTokens(), "Slashing: indexer has no stakes");
296298
require(_beneficiary != address(0), "Slashing: beneficiary must not be an empty address");
297-
require(_tokens >= _reward, "Slashing: reward cannot be higher than slashed amount");
298299
require(
299300
_tokens <= indexerStake.tokensSlashable(),
300301
"Slashing: cannot slash more than staked amount"
@@ -337,11 +338,14 @@ contract Staking is Governed {
337338
function stake(uint256 _tokens) external {
338339
address indexer = msg.sender;
339340

341+
require(_tokens > 0, "Staking: cannot stake zero tokens");
342+
340343
// Transfer tokens to stake from indexer to this contract
341344
require(
342345
token.transferFrom(indexer, address(this), _tokens),
343346
"Staking: Cannot transfer tokens to stake"
344347
);
348+
345349
// Stake the transferred tokens
346350
_stake(indexer, _tokens);
347351
}
@@ -474,20 +478,22 @@ contract Staking is Governed {
474478

475479
// Process rebate
476480
uint256 tokensToClaim = pool.redeem(indexer, _subgraphID);
477-
require(tokensToClaim > 0, "Rebate: no tokens available to claim");
478481

479-
// All settlements processed then prune rebate pool
482+
// When all settlements processed then prune rebate pool
480483
if (pool.settlementsCount == 0) {
481484
delete rebates[_epoch];
482485
}
483486

484-
// Assign claimed tokens
485-
if (_restake) {
486-
// Restake to place fees into the indexer stake
487-
_stake(indexer, tokensToClaim);
488-
} else {
489-
// Transfer funds back to the indexer
490-
require(token.transfer(indexer, tokensToClaim), "Rebate: cannot transfer tokens");
487+
// When there are tokens to claim from the rebate pool, transfer or restake
488+
if (tokensToClaim > 0) {
489+
// Assign claimed tokens
490+
if (_restake) {
491+
// Restake to place fees into the indexer stake
492+
_stake(indexer, tokensToClaim);
493+
} else {
494+
// Transfer funds back to the indexer
495+
require(token.transfer(indexer, tokensToClaim), "Rebate: cannot transfer tokens");
496+
}
491497
}
492498

493499
emit RebateClaimed(
@@ -527,8 +533,11 @@ contract Staking is Governed {
527533
bytes32 subgraphID = channels[_channelID].subgraphID;
528534
Stakes.Allocation storage alloc = stakes[indexer].allocations[subgraphID];
529535

530-
require(alloc.hasChannel(), "Channel: Must be active for settlement");
531-
require(alloc.channelID == _channelID, "Channel: Cannot settle an already closed channel");
536+
require(_channelID != address(0), "Channel: ChannelID cannot be empty address");
537+
require(
538+
alloc.channelID == _channelID,
539+
"Channel: The allocation has no channel, or the channel was already settled"
540+
);
532541

533542
// Time conditions
534543
(uint256 epochs, uint256 currentEpoch) = epochManager.epochsSince(alloc.createdAtEpoch);

contracts/libs/Rebates.sol

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,9 @@ library Rebates {
9797
uint256 _poolFees
9898
) public pure returns (uint256) {
9999
// NOTE: We sqrt() because alpha is fractional so we expect the inverse of alpha
100+
if (_poolAlloc == 0 || _poolFees == 0) {
101+
return 0;
102+
}
100103

101104
// Here we use ABDKMath64x64 to do the square root of terms
102105
// We have to covert it to a 64.64 fixed point number, do sqrt(), then convert it

test/disputes.test.js

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -397,8 +397,18 @@ contract('Disputes', ([me, other, governor, arbitrator, indexer, fisherman, othe
397397
)
398398
})
399399

400+
it('reject to accept a dispute if zero tokens to slash', async function() {
401+
await this.disputeManager.setSlashingPercentage(new BN(0), { from: governor })
402+
await expectRevert(
403+
this.disputeManager.acceptDispute(this.dispute.id, {
404+
from: arbitrator,
405+
}),
406+
'Dispute has zero tokens to slash',
407+
)
408+
})
409+
400410
it('should resolve dispute, slash indexer and reward the fisherman', async function() {
401-
const indexerStakeBefore = await this.staking.getIndexerStakeTokens(indexer)
411+
const indexerStakeBefore = await this.staking.getIndexerStakedTokens(indexer)
402412
const tokensToSlash = await this.disputeManager.getTokensToSlash(indexer)
403413
const fishermanBalanceBefore = await this.grt.balanceOf(fisherman)
404414
const totalSupplyBefore = await this.grt.totalSupply()
@@ -416,7 +426,7 @@ contract('Disputes', ([me, other, governor, arbitrator, indexer, fisherman, othe
416426
)
417427

418428
// Indexer slashed
419-
const indexerStakeAfter = await this.staking.getIndexerStakeTokens(indexer)
429+
const indexerStakeAfter = await this.staking.getIndexerStakedTokens(indexer)
420430
expect(indexerStakeAfter).to.be.bignumber.eq(indexerStakeBefore.sub(tokensToSlash))
421431

422432
// Slashed funds burned

test/lib/testHelpers.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,7 @@ module.exports = {
7878
slashingPercentage: new BN(1000), // in basis points
7979
},
8080
epochs: {
81-
lengthInBlocks: new BN((5 * 60) / 15), // One day in blocks
81+
lengthInBlocks: new BN((5 * 60) / 15), // 5 minutes in blocks
8282
},
8383
staking: {
8484
channelDisputeEpochs: 1,

0 commit comments

Comments
 (0)