-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathStakingExtension.sol
682 lines (601 loc) · 26.4 KB
/
StakingExtension.sol
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
// SPDX-License-Identifier: GPL-2.0-or-later
pragma solidity ^0.7.6;
pragma abicoder v2;
import { SafeMath } from "@openzeppelin/contracts/math/SafeMath.sol";
import { StakingV4Storage } from "./StakingStorage.sol";
import { IStakingExtension } from "./IStakingExtension.sol";
import { TokenUtils } from "../utils/TokenUtils.sol";
import { IGraphToken } from "../token/IGraphToken.sol";
import { GraphUpgradeable } from "../upgrades/GraphUpgradeable.sol";
import { Stakes } from "./libs/Stakes.sol";
import { IStakingData } from "./IStakingData.sol";
import { MathUtils } from "./libs/MathUtils.sol";
/**
* @title StakingExtension contract
* @dev This contract provides the logic to manage delegations and other Staking
* extension features (e.g. storage getters). It is meant to be called through delegatecall from the
* Staking contract, and is only kept separate to keep the Staking contract size
* within limits.
*/
contract StakingExtension is StakingV4Storage, GraphUpgradeable, IStakingExtension {
using SafeMath for uint256;
using Stakes for Stakes.Indexer;
/// @dev 100% in parts per million
uint32 private constant MAX_PPM = 1000000;
/**
* @dev Check if the caller is the slasher.
*/
modifier onlySlasher() {
require(__slashers[msg.sender] == true, "!slasher");
_;
}
/**
* @notice Initialize the StakingExtension contract
* @dev This function is meant to be delegatecalled from the Staking contract's
* initialize() function, so it uses the same access control check to ensure it is
* being called by the Staking implementation as part of the proxy upgrade process.
* @param _delegationUnbondingPeriod Delegation unbonding period in blocks
* @param _cooldownBlocks Minimum time between changes to delegation parameters, in blocks
* @param _delegationRatio Delegation capacity multiplier (e.g. 10 means 10x the indexer stake)
* @param _delegationTaxPercentage Percentage of delegated tokens to burn as delegation tax, expressed in parts per million
*/
function initialize(
uint32 _delegationUnbondingPeriod,
uint32 _cooldownBlocks,
uint32 _delegationRatio,
uint32 _delegationTaxPercentage
) external onlyImpl {
_setDelegationUnbondingPeriod(_delegationUnbondingPeriod);
_setDelegationParametersCooldown(_cooldownBlocks);
_setDelegationRatio(_delegationRatio);
_setDelegationTaxPercentage(_delegationTaxPercentage);
}
/**
* @notice Set a delegation tax percentage to burn when delegated funds are deposited.
* @dev This function is only callable by the governor
* @param _percentage Percentage of delegated tokens to burn as delegation tax, expressed in parts per million
*/
function setDelegationTaxPercentage(uint32 _percentage) external override onlyGovernor {
_setDelegationTaxPercentage(_percentage);
}
/**
* @notice Set the delegation ratio.
* If set to 10 it means the indexer can use up to 10x the indexer staked amount
* from their delegated tokens
* @dev This function is only callable by the governor
* @param _delegationRatio Delegation capacity multiplier
*/
function setDelegationRatio(uint32 _delegationRatio) external override onlyGovernor {
_setDelegationRatio(_delegationRatio);
}
/**
* @notice Set the minimum time in blocks an indexer needs to wait to change delegation parameters.
* Indexers can set a custom amount time for their own cooldown, but it must be greater than this.
* @dev This function is only callable by the governor
* @param _blocks Number of blocks to set the delegation parameters cooldown period
*/
function setDelegationParametersCooldown(uint32 _blocks) external override onlyGovernor {
_setDelegationParametersCooldown(_blocks);
}
/**
* @notice Set the time, in epochs, a Delegator needs to wait to withdraw tokens after undelegating.
* @dev This function is only callable by the governor
* @param _delegationUnbondingPeriod Period in epochs to wait for token withdrawals after undelegating
*/
function setDelegationUnbondingPeriod(uint32 _delegationUnbondingPeriod)
external
override
onlyGovernor
{
_setDelegationUnbondingPeriod(_delegationUnbondingPeriod);
}
/**
* @notice Set or unset an address as allowed slasher.
* @param _slasher Address of the party allowed to slash indexers
* @param _allowed True if slasher is allowed
*/
function setSlasher(address _slasher, bool _allowed) external override onlyGovernor {
require(_slasher != address(0), "!slasher");
__slashers[_slasher] = _allowed;
emit SlasherUpdate(msg.sender, _slasher, _allowed);
}
/**
* @notice Delegate tokens to an indexer.
* @param _indexer Address of the indexer to which tokens are delegated
* @param _tokens Amount of tokens to delegate
* @return Amount of shares issued from the delegation pool
*/
function delegate(address _indexer, uint256 _tokens)
external
override
notPartialPaused
returns (uint256)
{
address delegator = msg.sender;
// Transfer tokens to delegate to this contract
TokenUtils.pullTokens(graphToken(), delegator, _tokens);
// Update state
return _delegate(delegator, _indexer, _tokens);
}
/**
* @notice Undelegate tokens from an indexer. Tokens will be locked for the unbonding period.
* @param _indexer Address of the indexer to which tokens had been delegated
* @param _shares Amount of shares to return and undelegate tokens
* @return Amount of tokens returned for the shares of the delegation pool
*/
function undelegate(address _indexer, uint256 _shares)
external
override
notPartialPaused
returns (uint256)
{
return _undelegate(msg.sender, _indexer, _shares);
}
/**
* @notice Withdraw undelegated tokens once the unbonding period has passed, and optionally
* re-delegate to a new indexer.
* @param _indexer Withdraw available tokens delegated to indexer
* @param _newIndexer Re-delegate to indexer address if non-zero, withdraw if zero address
*/
function withdrawDelegated(address _indexer, address _newIndexer)
external
override
notPaused
returns (uint256)
{
return _withdrawDelegated(msg.sender, _indexer, _newIndexer);
}
/**
* @notice Slash the indexer stake. Delegated tokens are not subject to slashing.
* @dev Can only be called by the slasher role.
* @param _indexer Address of indexer to slash
* @param _tokens Amount of tokens to slash from the indexer stake
* @param _reward Amount of reward tokens to send to a beneficiary
* @param _beneficiary Address of a beneficiary to receive a reward for the slashing
*/
function slash(
address _indexer,
uint256 _tokens,
uint256 _reward,
address _beneficiary
) external override onlySlasher notPartialPaused {
Stakes.Indexer storage indexerStake = __stakes[_indexer];
// Only able to slash a non-zero number of tokens
require(_tokens > 0, "!tokens");
// Rewards comes from tokens slashed balance
require(_tokens >= _reward, "rewards>slash");
// Cannot slash stake of an indexer without any or enough stake
require(indexerStake.tokensStaked > 0, "!stake");
require(_tokens <= indexerStake.tokensStaked, "slash>stake");
// Validate beneficiary of slashed tokens
require(_beneficiary != address(0), "!beneficiary");
// Slashing more tokens than freely available (over allocation condition)
// Unlock locked tokens to avoid the indexer to withdraw them
if (_tokens > indexerStake.tokensAvailable() && indexerStake.tokensLocked > 0) {
uint256 tokensOverAllocated = _tokens.sub(indexerStake.tokensAvailable());
uint256 tokensToUnlock = MathUtils.min(tokensOverAllocated, indexerStake.tokensLocked);
indexerStake.unlockTokens(tokensToUnlock);
}
// Remove tokens to slash from the stake
indexerStake.release(_tokens);
// -- Interactions --
IGraphToken graphToken = graphToken();
// Set apart the reward for the beneficiary and burn remaining slashed stake
TokenUtils.burnTokens(graphToken, _tokens.sub(_reward));
// Give the beneficiary a reward for slashing
TokenUtils.pushTokens(graphToken, _beneficiary, _reward);
emit StakeSlashed(_indexer, _tokens, _reward, _beneficiary);
}
/**
* @notice Return the delegation from a delegator to an indexer.
* @param _indexer Address of the indexer where funds have been delegated
* @param _delegator Address of the delegator
* @return Delegation data
*/
function getDelegation(address _indexer, address _delegator)
external
view
override
returns (Delegation memory)
{
return __delegationPools[_indexer].delegators[_delegator];
}
/**
* @notice Getter for the delegationRatio, i.e. the delegation capacity multiplier:
* If delegation ratio is 100, and an Indexer has staked 5 GRT,
* then they can use up to 500 GRT from the delegated stake
* @return Delegation ratio
*/
function delegationRatio() external view override returns (uint32) {
return __delegationRatio;
}
/**
* @notice Getter for delegationParametersCooldown:
* Minimum time in blocks an indexer needs to wait to change delegation parameters
* @return Delegation parameters cooldown in blocks
*/
function delegationParametersCooldown() external view override returns (uint32) {
return __delegationParametersCooldown;
}
/**
* @notice Getter for delegationUnbondingPeriod:
* Time in epochs a delegator needs to wait to withdraw delegated stake
* @return Delegation unbonding period in epochs
*/
function delegationUnbondingPeriod() external view override returns (uint32) {
return __delegationUnbondingPeriod;
}
/**
* @notice Getter for delegationTaxPercentage:
* Percentage of tokens to tax a delegation deposit, expressed in parts per million
* @return Delegation tax percentage in parts per million
*/
function delegationTaxPercentage() external view override returns (uint32) {
return __delegationTaxPercentage;
}
/**
* @notice Getter for delegationPools[_indexer]:
* gets the delegation pool structure for a particular indexer.
* @param _indexer Address of the indexer for which to query the delegation pool
* @return Delegation pool as a DelegationPoolReturn struct
*/
function delegationPools(address _indexer)
external
view
override
returns (DelegationPoolReturn memory)
{
DelegationPool storage pool = __delegationPools[_indexer];
return
DelegationPoolReturn(
pool.cooldownBlocks, // Blocks to wait before updating parameters
pool.indexingRewardCut, // in PPM
pool.queryFeeCut, // in PPM
pool.updatedAtBlock, // Block when the pool was last updated
pool.tokens, // Total tokens as pool reserves
pool.shares // Total shares minted in the pool
);
}
/**
* @notice Getter for rewardsDestination[_indexer]:
* returns the address where the indexer's rewards are sent.
* @param _indexer The indexer address for which to query the rewards destination
* @return The address where the indexer's rewards are sent, zero if none is set in which case rewards are re-staked
*/
function rewardsDestination(address _indexer) external view override returns (address) {
return __rewardsDestination[_indexer];
}
/**
* @notice Getter for assetHolders[_maybeAssetHolder]:
* returns true if the address is an asset holder, i.e. an entity that can collect
* query fees into the Staking contract.
* @param _maybeAssetHolder The address that may or may not be an asset holder
* @return True if the address is an asset holder
*/
function assetHolders(address _maybeAssetHolder) external view override returns (bool) {
return __assetHolders[_maybeAssetHolder];
}
/**
* @notice Getter for operatorAuth[_indexer][_maybeOperator]:
* returns true if the operator is authorized to operate on behalf of the indexer.
* @param _indexer The indexer address for which to query authorization
* @param _maybeOperator The address that may or may not be an operator
* @return True if the operator is authorized to operate on behalf of the indexer
*/
function operatorAuth(address _indexer, address _maybeOperator)
external
view
override
returns (bool)
{
return __operatorAuth[_indexer][_maybeOperator];
}
/**
* @notice Getter for subgraphAllocations[_subgraphDeploymentId]:
* returns the amount of tokens allocated to a subgraph deployment.
* @param _subgraphDeploymentId The subgraph deployment for which to query the allocations
* @return The amount of tokens allocated to the subgraph deployment
*/
function subgraphAllocations(bytes32 _subgraphDeploymentId)
external
view
override
returns (uint256)
{
return __subgraphAllocations[_subgraphDeploymentId];
}
/**
* @notice Getter for slashers[_maybeSlasher]:
* returns true if the address is a slasher, i.e. an entity that can slash indexers
* @param _maybeSlasher Address for which to check the slasher role
* @return True if the address is a slasher
*/
function slashers(address _maybeSlasher) external view override returns (bool) {
return __slashers[_maybeSlasher];
}
/**
* @notice Getter for minimumIndexerStake: the minimum
* amount of GRT that an indexer needs to stake.
* @return Minimum indexer stake in GRT
*/
function minimumIndexerStake() external view override returns (uint256) {
return __minimumIndexerStake;
}
/**
* @notice Getter for thawingPeriod: the time in blocks an
* indexer needs to wait to unstake tokens.
* @return Thawing period in blocks
*/
function thawingPeriod() external view override returns (uint32) {
return __thawingPeriod;
}
/**
* @notice Getter for curationPercentage: the percentage of
* query fees that are distributed to curators.
* @return Curation percentage in parts per million
*/
function curationPercentage() external view override returns (uint32) {
return __curationPercentage;
}
/**
* @notice Getter for protocolPercentage: the percentage of
* query fees that are burned as protocol fees.
* @return Protocol percentage in parts per million
*/
function protocolPercentage() external view override returns (uint32) {
return __protocolPercentage;
}
/**
* @notice Getter for maxAllocationEpochs: the maximum time in epochs
* that an allocation can be open before anyone is allowed to close it. This
* also caps the effective allocation when sending the allocation's query fees
* to the rebate pool.
* @return Maximum allocation period in epochs
*/
function maxAllocationEpochs() external view override returns (uint32) {
return __maxAllocationEpochs;
}
/**
* @notice Getter for the numerator of the rebates alpha parameter
* @return Alpha numerator
*/
function alphaNumerator() external view override returns (uint32) {
return __alphaNumerator;
}
/**
* @notice Getter for the denominator of the rebates alpha parameter
* @return Alpha denominator
*/
function alphaDenominator() external view override returns (uint32) {
return __alphaDenominator;
}
/**
* @notice Getter for the numerator of the rebates lambda parameter
* @return Lambda numerator
*/
function lambdaNumerator() external view override returns (uint32) {
return __lambdaNumerator;
}
/**
* @notice Getter for the denominator of the rebates lambda parameter
* @return Lambda denominator
*/
function lambdaDenominator() external view override returns (uint32) {
return __lambdaDenominator;
}
/**
* @notice Getter for stakes[_indexer]:
* gets the stake information for an indexer as a Stakes.Indexer struct.
* @param _indexer Indexer address for which to query the stake information
* @return Stake information for the specified indexer, as a Stakes.Indexer struct
*/
function stakes(address _indexer) external view override returns (Stakes.Indexer memory) {
return __stakes[_indexer];
}
/**
* @notice Getter for allocations[_allocationID]:
* gets an allocation's information as an IStakingData.Allocation struct.
* @param _allocationID Allocation ID for which to query the allocation information
* @return The specified allocation, as an IStakingData.Allocation struct
*/
function allocations(address _allocationID)
external
view
override
returns (IStakingData.Allocation memory)
{
return __allocations[_allocationID];
}
/**
* @notice Return whether the delegator has delegated to the indexer.
* @param _indexer Address of the indexer where funds have been delegated
* @param _delegator Address of the delegator
* @return True if delegator has tokens delegated to the indexer
*/
function isDelegator(address _indexer, address _delegator) public view override returns (bool) {
return __delegationPools[_indexer].delegators[_delegator].shares > 0;
}
/**
* @notice Returns amount of delegated tokens ready to be withdrawn after unbonding period.
* @param _delegation Delegation of tokens from delegator to indexer
* @return Amount of tokens to withdraw
*/
function getWithdraweableDelegatedTokens(Delegation memory _delegation)
public
view
override
returns (uint256)
{
// There must be locked tokens and period passed
uint256 currentEpoch = epochManager().currentEpoch();
if (_delegation.tokensLockedUntil > 0 && currentEpoch >= _delegation.tokensLockedUntil) {
return _delegation.tokensLocked;
}
return 0;
}
/**
* @dev Internal: Set a delegation tax percentage to burn when delegated funds are deposited.
* @param _percentage Percentage of delegated tokens to burn as delegation tax
*/
function _setDelegationTaxPercentage(uint32 _percentage) private {
// Must be within 0% to 100% (inclusive)
require(_percentage <= MAX_PPM, ">percentage");
__delegationTaxPercentage = _percentage;
emit ParameterUpdated("delegationTaxPercentage");
}
/**
* @dev Internal: Set the delegation ratio.
* If set to 10 it means the indexer can use up to 10x the indexer staked amount
* from their delegated tokens
* @param _delegationRatio Delegation capacity multiplier
*/
function _setDelegationRatio(uint32 _delegationRatio) private {
__delegationRatio = _delegationRatio;
emit ParameterUpdated("delegationRatio");
}
/**
* @dev Internal: Set the time in blocks an indexer needs to wait to change delegation parameters.
* @param _blocks Number of blocks to set the delegation parameters cooldown period
*/
function _setDelegationParametersCooldown(uint32 _blocks) private {
__delegationParametersCooldown = _blocks;
emit ParameterUpdated("delegationParametersCooldown");
}
/**
* @dev Internal: Set the period for undelegation of stake from indexer.
* @param _delegationUnbondingPeriod Period in epochs to wait for token withdrawals after undelegating
*/
function _setDelegationUnbondingPeriod(uint32 _delegationUnbondingPeriod) private {
require(_delegationUnbondingPeriod > 0, "!delegationUnbondingPeriod");
__delegationUnbondingPeriod = _delegationUnbondingPeriod;
emit ParameterUpdated("delegationUnbondingPeriod");
}
/**
* @dev Delegate tokens to an indexer.
* @param _delegator Address of the delegator
* @param _indexer Address of the indexer to delegate tokens to
* @param _tokens Amount of tokens to delegate
* @return Amount of shares issued of the delegation pool
*/
function _delegate(
address _delegator,
address _indexer,
uint256 _tokens
) private returns (uint256) {
// Only delegate a non-zero amount of tokens
require(_tokens > 0, "!tokens");
// Only delegate to non-empty address
require(_indexer != address(0), "!indexer");
// Only delegate to staked indexer
require(__stakes[_indexer].tokensStaked > 0, "!stake");
// Get the delegation pool of the indexer
DelegationPool storage pool = __delegationPools[_indexer];
Delegation storage delegation = pool.delegators[_delegator];
// Collect delegation tax
uint256 delegationTax = _collectTax(graphToken(), _tokens, __delegationTaxPercentage);
uint256 delegatedTokens = _tokens.sub(delegationTax);
// Calculate shares to issue
uint256 shares = (pool.tokens == 0)
? delegatedTokens
: delegatedTokens.mul(pool.shares).div(pool.tokens);
require(shares > 0, "!shares");
// Update the delegation pool
pool.tokens = pool.tokens.add(delegatedTokens);
pool.shares = pool.shares.add(shares);
// Update the individual delegation
delegation.shares = delegation.shares.add(shares);
emit StakeDelegated(_indexer, _delegator, delegatedTokens, shares);
return shares;
}
/**
* @dev Undelegate tokens from an indexer.
* @param _delegator Address of the delegator
* @param _indexer Address of the indexer where tokens had been delegated
* @param _shares Amount of shares to return and undelegate tokens
* @return Amount of tokens returned for the shares of the delegation pool
*/
function _undelegate(
address _delegator,
address _indexer,
uint256 _shares
) private returns (uint256) {
// Can only undelegate a non-zero amount of shares
require(_shares > 0, "!shares");
// Get the delegation pool of the indexer
DelegationPool storage pool = __delegationPools[_indexer];
Delegation storage delegation = pool.delegators[_delegator];
// Delegator need to have enough shares in the pool to undelegate
require(delegation.shares >= _shares, "!shares-avail");
// Withdraw tokens if available
if (getWithdraweableDelegatedTokens(delegation) > 0) {
_withdrawDelegated(_delegator, _indexer, address(0));
}
// Calculate tokens to get in exchange for the shares
uint256 tokens = _shares.mul(pool.tokens).div(pool.shares);
// Update the delegation pool
pool.tokens = pool.tokens.sub(tokens);
pool.shares = pool.shares.sub(_shares);
// Update the delegation
delegation.shares = delegation.shares.sub(_shares);
delegation.tokensLocked = delegation.tokensLocked.add(tokens);
delegation.tokensLockedUntil = epochManager().currentEpoch().add(
__delegationUnbondingPeriod
);
emit StakeDelegatedLocked(
_indexer,
_delegator,
tokens,
_shares,
delegation.tokensLockedUntil
);
return tokens;
}
/**
* @dev Withdraw delegated tokens once the unbonding period has passed.
* @param _delegator Delegator that is withdrawing tokens
* @param _indexer Withdraw available tokens delegated to indexer
* @param _delegateToIndexer Re-delegate to indexer address if non-zero, withdraw if zero address
* @return Amount of tokens withdrawn or re-delegated
*/
function _withdrawDelegated(
address _delegator,
address _indexer,
address _delegateToIndexer
) private returns (uint256) {
// Get the delegation pool of the indexer
DelegationPool storage pool = __delegationPools[_indexer];
Delegation storage delegation = pool.delegators[_delegator];
// Validation
uint256 tokensToWithdraw = getWithdraweableDelegatedTokens(delegation);
require(tokensToWithdraw > 0, "!tokens");
// Reset lock
delegation.tokensLocked = 0;
delegation.tokensLockedUntil = 0;
emit StakeDelegatedWithdrawn(_indexer, _delegator, tokensToWithdraw);
// -- Interactions --
if (_delegateToIndexer != address(0)) {
// Re-delegate tokens to a new indexer
_delegate(_delegator, _delegateToIndexer, tokensToWithdraw);
} else {
// Return tokens to the delegator
TokenUtils.pushTokens(graphToken(), _delegator, tokensToWithdraw);
}
return tokensToWithdraw;
}
/**
* @dev Collect tax to burn for an amount of tokens.
* @param _graphToken Token to burn
* @param _tokens Total tokens received used to calculate the amount of tax to collect
* @param _percentage Percentage of tokens to burn as tax
* @return Amount of tax charged
*/
function _collectTax(
IGraphToken _graphToken,
uint256 _tokens,
uint256 _percentage
) private returns (uint256) {
uint256 tax = uint256(_percentage).mul(_tokens).div(MAX_PPM);
TokenUtils.burnTokens(_graphToken, tax); // Burn tax if any
return tax;
}
}