Skip to content

Commit 999fca7

Browse files
authored
staking: add more staking tests (#202)
* staking: add more tests to staking and test improvements * staking: fix edge conditions when settling * staking: - fix edge case when epochSince is in the future - add more tests for settle and claim - use ABDKMath64x64 for sqrt() calculations * staking: add state checks after settle * 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 dce8405 commit 999fca7

File tree

14 files changed

+910
-274
lines changed

14 files changed

+910
-274
lines changed

.soliumignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,3 +4,4 @@ contracts/Migrations.sol
44
contracts/bancor
55
contracts/openzeppelin
66
contracts/MultiSigWallet.sol
7+
contracts/abdk-libraries-solidity

contracts/Curation.sol

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -9,12 +9,10 @@ pragma experimental ABIEncoderV2;
99
import "./Governed.sol";
1010
import "./GraphToken.sol";
1111
import "./bancor/BancorFormula.sol";
12-
import "./bytes/BytesLib.sol";
1312
import "@openzeppelin/contracts/math/SafeMath.sol";
1413

1514

1615
contract Curation is Governed, BancorFormula {
17-
using BytesLib for bytes;
1816
using SafeMath for uint256;
1917

2018
// -- Curation --
@@ -29,9 +27,6 @@ contract Curation is Governed, BancorFormula {
2927
// 100% in parts per million
3028
uint256 private constant MAX_PPM = 1000000;
3129

32-
// 1 basis point (0.01%) is 100 parts per million (PPM)
33-
uint256 private constant BASIS_PT = 100;
34-
3530
// Amount of shares you get with your minimum token stake
3631
uint256 private constant SHARES_PER_MINIMUM_STAKE = 1;
3732

contracts/DisputeManager.sol

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -51,9 +51,6 @@ contract DisputeManager is Governed {
5151
// 100% in parts per million
5252
uint256 private constant MAX_PPM = 1000000;
5353

54-
// 1 basis point (0.01%) is 100 parts per million (PPM)
55-
uint256 private constant BASIS_PT = 100;
56-
5754
// -- State --
5855

5956
bytes32 private DOMAIN_SEPARATOR;
@@ -206,7 +203,7 @@ contract DisputeManager is Governed {
206203
* @return Amount of tokens to slash
207204
*/
208205
function getTokensToSlash(address _indexer) public view returns (uint256) {
209-
uint256 tokens = staking.getIndexerStakeTokens(_indexer); // slashable tokens
206+
uint256 tokens = staking.getIndexerStakedTokens(_indexer); // slashable tokens
210207
return slashingPercentage.mul(tokens).div(MAX_PPM);
211208
}
212209

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

320317
// Have staking contract slash the indexer and reward the fisherman
321318
// Give the fisherman a reward equal to the fishermanRewardPercentage of slashed amount
322-
uint256 tokensToReward = getTokensToReward(dispute.indexer);
323319
uint256 tokensToSlash = getTokensToSlash(dispute.indexer);
320+
uint256 tokensToReward = getTokensToReward(dispute.indexer);
321+
322+
require(tokensToSlash > 0, "Dispute has zero tokens to slash");
324323
staking.slash(dispute.indexer, tokensToSlash, tokensToReward, dispute.fisherman);
325324

326325
// Give the fisherman their deposit back

contracts/EpochManager.sol

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -139,7 +139,7 @@ contract EpochManager is Governed {
139139
*/
140140
function epochsSince(uint256 _epoch) public view returns (uint256, uint256) {
141141
uint256 epoch = currentEpoch();
142-
return (epoch.sub(_epoch), epoch);
142+
return (_epoch < epoch ? epoch.sub(_epoch) : 0, epoch);
143143
}
144144

145145
/**

contracts/Staking.sol

Lines changed: 40 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -31,9 +31,6 @@ contract Staking is Governed {
3131
// 100% in parts per million
3232
uint256 private constant MAX_PPM = 1000000;
3333

34-
// 1 basis point (0.01%) is 100 parts per million (PPM)
35-
uint256 private constant BASIS_PT = 100;
36-
3734
// -- State --
3835

3936
// Percentage of fees going to curators
@@ -157,17 +154,14 @@ contract Staking is Governed {
157154
* @param _governor Owner address of this contract
158155
* @param _token Address of the Graph Protocol token
159156
* @param _epochManager Address of the EpochManager contract
160-
* @param _curation Address of the Curation contract
161157
*/
162158
constructor(
163159
address _governor,
164160
address _token,
165-
address _epochManager,
166-
address _curation
161+
address _epochManager
167162
) public Governed(_governor) {
168163
token = GraphToken(_token);
169164
epochManager = EpochManager(_epochManager);
170-
curation = Curation(_curation);
171165
}
172166

173167
/**
@@ -250,7 +244,7 @@ contract Staking is Governed {
250244
* @param _indexer Address of the indexer
251245
* @return Amount of tokens staked by the indexer
252246
*/
253-
function getIndexerStakeTokens(address _indexer) public view returns (uint256) {
247+
function getIndexerStakedTokens(address _indexer) public view returns (uint256) {
254248
return stakes[_indexer].tokensIndexer;
255249
}
256250

@@ -268,6 +262,21 @@ contract Staking is Governed {
268262
return stakes[_indexer].allocations[_subgraphID];
269263
}
270264

265+
/**
266+
* @dev Get an outstanding unclaimed settlement
267+
* @param _epoch Epoch when the settlement ocurred
268+
* @param _indexer Address of the indexer
269+
* @param _subgraphID ID of the subgraph settled
270+
* @return Settlement data
271+
*/
272+
function getSettlement(
273+
uint256 _epoch,
274+
address _indexer,
275+
bytes32 _subgraphID
276+
) public view returns (Rebates.Settlement memory) {
277+
return rebates[_epoch].settlements[_indexer][_subgraphID];
278+
}
279+
271280
/**
272281
* @dev Slash the indexer stake
273282
* @param _indexer Address of indexer to slash
@@ -283,9 +292,10 @@ contract Staking is Governed {
283292
) external onlySlasher {
284293
Stakes.Indexer storage indexerStake = stakes[_indexer];
285294

295+
require(_tokens > 0, "Slashing: cannot slash zero tokens");
296+
require(_tokens >= _reward, "Slashing: reward cannot be higher than slashed amount");
286297
require(indexerStake.hasTokens(), "Slashing: indexer has no stakes");
287298
require(_beneficiary != address(0), "Slashing: beneficiary must not be an empty address");
288-
require(_tokens >= _reward, "Slashing: reward cannot be higher than slashed amount");
289299
require(
290300
_tokens <= indexerStake.tokensSlashable(),
291301
"Slashing: cannot slash more than staked amount"
@@ -328,11 +338,14 @@ contract Staking is Governed {
328338
function stake(uint256 _tokens) external {
329339
address indexer = msg.sender;
330340

341+
require(_tokens > 0, "Staking: cannot stake zero tokens");
342+
331343
// Transfer tokens to stake from indexer to this contract
332344
require(
333345
token.transferFrom(indexer, address(this), _tokens),
334346
"Staking: Cannot transfer tokens to stake"
335347
);
348+
336349
// Stake the transferred tokens
337350
_stake(indexer, _tokens);
338351
}
@@ -460,24 +473,27 @@ contract Staking is Governed {
460473
epochsSinceSettlement >= channelDisputeEpochs,
461474
"Rebate: need to wait channel dispute period"
462475
);
476+
463477
require(settlement.allocation > 0, "Rebate: settlement does not exist");
464478

465479
// Process rebate
466480
uint256 tokensToClaim = pool.redeem(indexer, _subgraphID);
467-
require(tokensToClaim > 0, "Rebate: no tokens available to claim");
468481

469-
// All settlements processed then prune rebate pool
482+
// When all settlements processed then prune rebate pool
470483
if (pool.settlementsCount == 0) {
471484
delete rebates[_epoch];
472485
}
473486

474-
// Assign claimed tokens
475-
if (_restake) {
476-
// Restake to place fees into the indexer stake
477-
_stake(indexer, tokensToClaim);
478-
} else {
479-
// Transfer funds back to the indexer
480-
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+
}
481497
}
482498

483499
emit RebateClaimed(
@@ -517,7 +533,11 @@ contract Staking is Governed {
517533
bytes32 subgraphID = channels[_channelID].subgraphID;
518534
Stakes.Allocation storage alloc = stakes[indexer].allocations[subgraphID];
519535

520-
require(alloc.hasChannel(), "Channel: Must be active for settlement");
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+
);
521541

522542
// Time conditions
523543
(uint256 epochs, uint256 currentEpoch) = epochManager.epochsSince(alloc.createdAtEpoch);
@@ -558,8 +578,8 @@ contract Staking is Governed {
558578
_tokens,
559579
_channelID,
560580
_from,
561-
rebateFees,
562581
curationFees,
582+
rebateFees,
563583
effectiveAllocation
564584
);
565585
}
Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
/*
2+
* TheGraph is using this software as described in the README.md in this folder
3+
* https://github.com/abdk-consulting/abdk-libraries-solidity/tree/939f0a264f2d07a9e2c7a3a020f0db2c0885dc01
4+
*
5+
* This library has been significantly reduced to only include the functions needed for the Graph Protocol
6+
* Please visit the library at the link above for more details
7+
*/
8+
9+
/*
10+
* ABDK Math 64.64 Smart Contract Library. Copyright © 2019 by ABDK Consulting.
11+
* Author: Mikhail Vladimirov <[email protected]>
12+
*/
13+
pragma solidity 0.6.4;
14+
15+
16+
/**
17+
* Smart contract library of mathematical functions operating with signed
18+
* 64.64-bit fixed point numbers. Signed 64.64-bit fixed point number is
19+
* basically a simple fraction whose numerator is signed 128-bit integer and
20+
* denominator is 2^64. As long as denominator is always the same, there is no
21+
* need to store it, thus in Solidity signed 64.64-bit fixed point numbers are
22+
* represented by int128 type holding only the numerator.
23+
*/
24+
library ABDKMath64x64 {
25+
/**
26+
* Convert unsigned 256-bit integer number into signed 64.64-bit fixed point
27+
* number. Revert on overflow.
28+
*
29+
* @param x unsigned 256-bit integer number
30+
* @return signed 64.64-bit fixed point number
31+
*/
32+
function fromUInt(uint256 x) internal pure returns (int128) {
33+
require(x <= 0x7FFFFFFFFFFFFFFF);
34+
return int128(x << 64);
35+
}
36+
37+
/**
38+
* Convert signed 64.64 fixed point number into unsigned 64-bit integer
39+
* number rounding down. Revert on underflow.
40+
*
41+
* @param x signed 64.64-bit fixed point number
42+
* @return unsigned 64-bit integer number
43+
*/
44+
function toUInt(int128 x) internal pure returns (uint64) {
45+
require(x >= 0);
46+
return uint64(x >> 64);
47+
}
48+
49+
/**
50+
* Calculate sqrt (x) rounding down. Revert if x < 0.
51+
*
52+
* @param x signed 64.64-bit fixed point number
53+
* @return signed 64.64-bit fixed point number
54+
*/
55+
function sqrt(int128 x) internal pure returns (int128) {
56+
require(x >= 0);
57+
return int128(sqrtu(uint256(x) << 64, 0x10000000000000000));
58+
}
59+
60+
/**
61+
* Calculate sqrt (x) rounding down, where x is unsigned 256-bit integer
62+
* number.
63+
*
64+
* @param x unsigned 256-bit integer number
65+
* @return unsigned 128-bit integer number
66+
*/
67+
function sqrtu(uint256 x, uint256 r) private pure returns (uint128) {
68+
if (x == 0) return 0;
69+
else {
70+
require(r > 0);
71+
while (true) {
72+
uint256 rr = x / r;
73+
if (r == rr || r + 1 == rr) return uint128(r);
74+
else if (r == rr + 1) return uint128(rr);
75+
r = (r + rr + 1) >> 1;
76+
}
77+
}
78+
}
79+
}
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
ABDK Libraries for Solidity
2+
===========================
3+
4+
Copyright (c) 2019, [ABDK Consulting](https://abdk.consulting/)
5+
6+
All rights reserved.
7+
8+
Redistribution and use in source and binary forms, with or without modification,
9+
are permitted provided that the following conditions are met:
10+
1. Redistributions of source code must retain the above copyright notice, this
11+
list of conditions and the following disclaimer.
12+
2. Redistributions in binary form must reproduce the above copyright notice,
13+
this list of conditions and the following disclaimer in the documentation
14+
and/or other materials provided with the distribution.
15+
3. All advertising materials mentioning features or use of this software must
16+
display the following acknowledgement: This product includes software
17+
developed by ABDK Consulting.
18+
4. Neither the name of ABDK Consulting nor the names of its contributors may be
19+
used to endorse or promote products derived from this software without
20+
specific prior written permission.
21+
22+
THIS SOFTWARE IS PROVIDED BY ABDK CONSULTING ''AS IS'' AND ANY EXPRESS OR
23+
IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
24+
MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT
25+
SHALL ABDK CONSULTING BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
26+
EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT
27+
OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
28+
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
29+
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING
30+
IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
31+
OF SUCH DAMAGE.
32+
33+
Math 64.64
34+
----------
35+
36+
Library of mathematical functions operating with signed 64.64-bit fixed point
37+
numbers.
38+
39+
\[ [documentation](ABDKMath64x64.md) | [source](ABDKMath64x64.sol) \]
40+
41+
Math Quad
42+
---------
43+
44+
Library of mathematical functions operating with IEEE 754 quadruple precision
45+
(128 bit) floating point numbers.
46+
47+
\[ [documentation](ABDKMathQuad.md) | [source](ABDKMathQuad.sol) \]

0 commit comments

Comments
 (0)