@bananapus/distributor-v6 0.0.33 → 0.0.35
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +7 -0
- package/package.json +2 -2
- package/references/operations.md +2 -1
- package/src/JB721Distributor.sol +335 -248
- package/src/JBDistributor.sol +344 -390
- package/src/JBTokenDistributor.sol +53 -69
- package/src/interfaces/IJB721Distributor.sol +139 -0
- package/src/interfaces/IJBDistributor.sol +22 -38
- package/src/structs/JBBorrowContext.sol +2 -0
- package/src/structs/JBClaimContext.sol +5 -0
- package/src/structs/JBVestContext.sol +5 -0
- package/src/structs/JBVestingLoan.sol +2 -0
- package/src/structs/JBTokenSnapshotData.sol +0 -11
package/src/JBDistributor.sol
CHANGED
|
@@ -20,7 +20,6 @@ import {IJBDistributor} from "./interfaces/IJBDistributor.sol";
|
|
|
20
20
|
import {JBVestingMath} from "./libraries/JBVestingMath.sol";
|
|
21
21
|
import {JBBorrowContext} from "./structs/JBBorrowContext.sol";
|
|
22
22
|
import {JBRewardRoundData} from "./structs/JBRewardRoundData.sol";
|
|
23
|
-
import {JBTokenSnapshotData} from "./structs/JBTokenSnapshotData.sol";
|
|
24
23
|
import {JBVestingData} from "./structs/JBVestingData.sol";
|
|
25
24
|
import {JBVestingLoan} from "./structs/JBVestingLoan.sol";
|
|
26
25
|
|
|
@@ -59,9 +58,6 @@ abstract contract JBDistributor is IJBDistributor {
|
|
|
59
58
|
/// @notice Thrown when the caller does not have access to the token.
|
|
60
59
|
error JBDistributor_NoAccess(address hook, uint256 tokenId, address account);
|
|
61
60
|
|
|
62
|
-
/// @notice Thrown when there is nothing to distribute for a token in the current round.
|
|
63
|
-
error JBDistributor_NothingToDistribute(address hook, address token, uint256 round);
|
|
64
|
-
|
|
65
61
|
/// @notice Thrown when there are no uncollected vesting revnet tokens to collateralize a loan.
|
|
66
62
|
error JBDistributor_NothingToBorrow(address hook, address token);
|
|
67
63
|
|
|
@@ -147,17 +143,23 @@ abstract contract JBDistributor is IJBDistributor {
|
|
|
147
143
|
|
|
148
144
|
/// @notice The active Revnet loan using one token ID's vesting rewards as collateral.
|
|
149
145
|
/// @custom:param hook The hook the token ID belongs to.
|
|
146
|
+
/// @custom:param groupId The reward group (0 = the default group).
|
|
150
147
|
/// @custom:param tokenId The token ID whose vesting rewards are collateralized.
|
|
151
148
|
/// @custom:param token The reward token used as loan collateral.
|
|
152
|
-
mapping(
|
|
149
|
+
mapping(
|
|
150
|
+
address hook => mapping(uint256 groupId => mapping(uint256 tokenId => mapping(IERC20 token => uint256)))
|
|
151
|
+
)
|
|
153
152
|
public
|
|
154
153
|
override activeVestingLoanIdOf;
|
|
155
154
|
|
|
156
155
|
/// @notice The index within `vestingDataOf` of the latest vest.
|
|
157
156
|
/// @custom:param hook The hook the tokenId belongs to.
|
|
157
|
+
/// @custom:param groupId The reward group (0 = the default group).
|
|
158
158
|
/// @custom:param tokenId The ID of the token to which the vests belong.
|
|
159
159
|
/// @custom:param token The address of the token vested.
|
|
160
|
-
mapping(
|
|
160
|
+
mapping(
|
|
161
|
+
address hook => mapping(uint256 groupId => mapping(uint256 tokenId => mapping(IERC20 token => uint256)))
|
|
162
|
+
) public latestVestedIndexOf;
|
|
161
163
|
|
|
162
164
|
/// @notice The block number recorded as the snapshot point for each round.
|
|
163
165
|
/// @dev Set to `block.number - 1` on first interaction in a round, so that `IVotes.getPastVotes` works.
|
|
@@ -165,9 +167,12 @@ abstract contract JBDistributor is IJBDistributor {
|
|
|
165
167
|
|
|
166
168
|
/// @notice Reward data assigned to each funding round.
|
|
167
169
|
/// @custom:param hook The stake source whose stakers receive rewards.
|
|
170
|
+
/// @custom:param groupId The reward group (0 = the default group).
|
|
168
171
|
/// @custom:param token The reward token.
|
|
169
172
|
/// @custom:param round The reward round.
|
|
170
|
-
mapping(
|
|
173
|
+
mapping(
|
|
174
|
+
address hook => mapping(uint256 groupId => mapping(IERC20 token => mapping(uint256 round => JBRewardRoundData)))
|
|
175
|
+
) public rewardRoundOf;
|
|
171
176
|
|
|
172
177
|
/// @notice The amount of a token that is currently vesting for a hook's stakers.
|
|
173
178
|
/// @custom:param hook The hook whose stakers are vesting.
|
|
@@ -181,9 +186,12 @@ abstract contract JBDistributor is IJBDistributor {
|
|
|
181
186
|
|
|
182
187
|
/// @notice All vesting data of a tokenId for any number of vesting tokens.
|
|
183
188
|
/// @custom:param hook The hook the tokenId belongs to.
|
|
189
|
+
/// @custom:param groupId The reward group (0 = the default group).
|
|
184
190
|
/// @custom:param tokenId The ID of the token to which the vests belong.
|
|
185
191
|
/// @custom:param token The address of the token vested.
|
|
186
|
-
mapping(
|
|
192
|
+
mapping(
|
|
193
|
+
address hook => mapping(uint256 groupId => mapping(uint256 tokenId => mapping(IERC20 token => JBVestingData[])))
|
|
194
|
+
) public vestingDataOf;
|
|
187
195
|
|
|
188
196
|
//*********************************************************************//
|
|
189
197
|
// -------------------- internal stored properties ------------------- //
|
|
@@ -202,21 +210,6 @@ abstract contract JBDistributor is IJBDistributor {
|
|
|
202
210
|
/// @custom:param loanId The Revnet loan NFT ID.
|
|
203
211
|
mapping(uint256 loanId => JBVestingLoan) internal _vestingLoanOf;
|
|
204
212
|
|
|
205
|
-
/// @notice The snapshot data of the token information for each round.
|
|
206
|
-
/// @custom:param hook The hook the snapshot is for.
|
|
207
|
-
/// @custom:param token The address of the token claimed and vested.
|
|
208
|
-
/// @custom:param round The round to which the data applies.
|
|
209
|
-
mapping(address hook => mapping(IERC20 token => mapping(uint256 round => JBTokenSnapshotData snapshot))) internal
|
|
210
|
-
_snapshotAtRoundOf;
|
|
211
|
-
|
|
212
|
-
/// @notice Whether a snapshot has been taken for a given (hook, token, round).
|
|
213
|
-
/// @dev Required because a snapshot can legitimately store `{balance: 0, vestingAmount: 0}`,
|
|
214
|
-
/// so a zero balance is not a usable sentinel for "uninitialized".
|
|
215
|
-
/// @custom:param hook The hook the snapshot is for.
|
|
216
|
-
/// @custom:param token The address of the token claimed and vested.
|
|
217
|
-
/// @custom:param round The round to which the data applies.
|
|
218
|
-
mapping(address hook => mapping(IERC20 token => mapping(uint256 round => bool))) internal _snapshotInitializedFor;
|
|
219
|
-
|
|
220
213
|
//*********************************************************************//
|
|
221
214
|
// ------------------- transient stored properties ------------------- //
|
|
222
215
|
//*********************************************************************//
|
|
@@ -271,9 +264,11 @@ abstract contract JBDistributor is IJBDistributor {
|
|
|
271
264
|
// ---------------------- external transactions ---------------------- //
|
|
272
265
|
//*********************************************************************//
|
|
273
266
|
|
|
274
|
-
/// @notice
|
|
275
|
-
///
|
|
276
|
-
///
|
|
267
|
+
/// @notice Begin vesting all unclaimed past reward rounds for the specified token IDs.
|
|
268
|
+
/// @dev Materializes each token ID's pro-rata share of every past (non-current) reward round into fresh vesting
|
|
269
|
+
/// entries that start now and unlock over `VESTING_ROUNDS`. Current-round funding is excluded until a later round
|
|
270
|
+
/// starts. The model-specific per-round claim math and the authorization check live in the `_claimPastRewards`
|
|
271
|
+
/// and `_requireCanClaimTokenIds` hooks each concrete distributor implements.
|
|
277
272
|
/// @param hook The hook (IVotes token or 721 hook) whose stakers are vesting.
|
|
278
273
|
/// @param tokenIds The staker token IDs to claim rewards for.
|
|
279
274
|
/// @param tokens The reward tokens to begin vesting.
|
|
@@ -286,56 +281,7 @@ abstract contract JBDistributor is IJBDistributor {
|
|
|
286
281
|
virtual
|
|
287
282
|
override
|
|
288
283
|
{
|
|
289
|
-
|
|
290
|
-
// token could otherwise snapshot, vest, or collect against balances between `balanceBefore` and
|
|
291
|
-
// `balanceAfter`, distorting the delta credited to the funder.
|
|
292
|
-
_requireNotAcceptingToken();
|
|
293
|
-
|
|
294
|
-
// Revert if no token IDs are provided.
|
|
295
|
-
if (tokenIds.length == 0) revert JBDistributor_EmptyTokenIds({tokenIdCount: tokenIds.length});
|
|
296
|
-
|
|
297
|
-
// Keep a reference to the current round.
|
|
298
|
-
uint256 round = currentRound();
|
|
299
|
-
|
|
300
|
-
// Ensure the snapshot block is recorded for this round.
|
|
301
|
-
_ensureSnapshotBlock(round);
|
|
302
|
-
|
|
303
|
-
// Keep a reference to the total staked amount at the snapshot block.
|
|
304
|
-
uint256 totalStakeAmount = _totalStake({hook: hook, blockNumber: roundSnapshotBlock[round]});
|
|
305
|
-
|
|
306
|
-
// Skip vesting when there are no stakers — funds carry over to the next round.
|
|
307
|
-
if (totalStakeAmount == 0) return;
|
|
308
|
-
|
|
309
|
-
// Loop through each token for which vesting is beginning.
|
|
310
|
-
for (uint256 i; i < tokens.length;) {
|
|
311
|
-
IERC20 token = tokens[i];
|
|
312
|
-
|
|
313
|
-
// Take a snapshot of the token balance if it hasn't been taken already.
|
|
314
|
-
JBTokenSnapshotData memory snapshot = _takeSnapshotOf({hook: hook, token: token});
|
|
315
|
-
uint256 distributable = snapshot.balance - snapshot.vestingAmount;
|
|
316
|
-
|
|
317
|
-
// Revert if there is nothing to distribute for this token.
|
|
318
|
-
if (distributable == 0) {
|
|
319
|
-
revert JBDistributor_NothingToDistribute({hook: hook, token: address(token), round: round});
|
|
320
|
-
}
|
|
321
|
-
|
|
322
|
-
// Vest each token ID and get the total amount vested.
|
|
323
|
-
uint256 totalVestingAmount = _vestTokenIds({
|
|
324
|
-
hook: hook,
|
|
325
|
-
tokenIds: tokenIds,
|
|
326
|
-
token: token,
|
|
327
|
-
distributable: distributable,
|
|
328
|
-
totalStakeAmount: totalStakeAmount,
|
|
329
|
-
vestingReleaseRound: round + VESTING_ROUNDS
|
|
330
|
-
});
|
|
331
|
-
|
|
332
|
-
unchecked {
|
|
333
|
-
// Store the updated total claimed amount now vesting.
|
|
334
|
-
totalVestingAmountOf[hook][token] += totalVestingAmount;
|
|
335
|
-
|
|
336
|
-
++i;
|
|
337
|
-
}
|
|
338
|
-
}
|
|
284
|
+
_beginVesting({hook: hook, groupId: 0, tokenIds: tokenIds, tokens: tokens});
|
|
339
285
|
}
|
|
340
286
|
|
|
341
287
|
/// @notice Directly fund the distributor for a specific hook by pulling tokens from the caller. An alternative
|
|
@@ -346,7 +292,7 @@ abstract contract JBDistributor is IJBDistributor {
|
|
|
346
292
|
/// @param token The token to fund with.
|
|
347
293
|
/// @param amount The amount to fund (ignored for native ETH — `msg.value` is used instead).
|
|
348
294
|
function fund(address hook, IERC20 token, uint256 amount) external payable virtual override {
|
|
349
|
-
_fund({hook: hook, token: token, amount: amount});
|
|
295
|
+
_fund({hook: hook, groupId: 0, token: token, amount: amount});
|
|
350
296
|
}
|
|
351
297
|
|
|
352
298
|
/// @notice Recycle unclaimed rewards from expired reward rounds into the current reward round.
|
|
@@ -365,19 +311,7 @@ abstract contract JBDistributor is IJBDistributor {
|
|
|
365
311
|
override
|
|
366
312
|
returns (uint256 amount)
|
|
367
313
|
{
|
|
368
|
-
|
|
369
|
-
_requireNotAcceptingToken();
|
|
370
|
-
|
|
371
|
-
// Process every requested round independently so callers can batch keeper work.
|
|
372
|
-
for (uint256 i; i < rounds.length;) {
|
|
373
|
-
// Add this round's expired remainder to the batch total.
|
|
374
|
-
amount += _recycleExpiredRewardRound({hook: hook, token: token, round: rounds[i]});
|
|
375
|
-
|
|
376
|
-
unchecked {
|
|
377
|
-
// Safe because the loop is bounded by calldata length.
|
|
378
|
-
++i;
|
|
379
|
-
}
|
|
380
|
-
}
|
|
314
|
+
amount = _burnExpiredRewards({hook: hook, groupId: 0, token: token, rounds: rounds});
|
|
381
315
|
}
|
|
382
316
|
|
|
383
317
|
/// @notice Record the snapshot block for the current round (and eagerly for the next round). Callable by anyone —
|
|
@@ -401,21 +335,7 @@ abstract contract JBDistributor is IJBDistributor {
|
|
|
401
335
|
external
|
|
402
336
|
override
|
|
403
337
|
{
|
|
404
|
-
|
|
405
|
-
_requireNotAcceptingToken();
|
|
406
|
-
|
|
407
|
-
// Make sure that all staker token IDs are burned.
|
|
408
|
-
for (uint256 i; i < tokenIds.length;) {
|
|
409
|
-
if (!_tokenBurned({hook: hook, tokenId: tokenIds[i]})) {
|
|
410
|
-
revert JBDistributor_NoAccess({hook: hook, tokenId: tokenIds[i], account: msg.sender});
|
|
411
|
-
}
|
|
412
|
-
unchecked {
|
|
413
|
-
++i;
|
|
414
|
-
}
|
|
415
|
-
}
|
|
416
|
-
|
|
417
|
-
// Unlock the rewards and recycle the forfeited amount.
|
|
418
|
-
_unlockRewards({hook: hook, tokenIds: tokenIds, tokens: tokens, beneficiary: beneficiary, ownerClaim: false});
|
|
338
|
+
_releaseForfeitedRewards({hook: hook, groupId: 0, tokenIds: tokenIds, tokens: tokens, beneficiary: beneficiary});
|
|
419
339
|
}
|
|
420
340
|
|
|
421
341
|
//*********************************************************************//
|
|
@@ -445,7 +365,7 @@ abstract contract JBDistributor is IJBDistributor {
|
|
|
445
365
|
override
|
|
446
366
|
returns (uint256 tokenAmount)
|
|
447
367
|
{
|
|
448
|
-
tokenAmount = _unclaimedVestingAmountOf({hook: hook, tokenId: tokenId, token: token});
|
|
368
|
+
tokenAmount = _unclaimedVestingAmountOf({hook: hook, groupId: 0, tokenId: tokenId, token: token});
|
|
449
369
|
}
|
|
450
370
|
|
|
451
371
|
/// @notice Calculate how much of a reward token is currently unlocked and ready to be collected for a given
|
|
@@ -464,63 +384,7 @@ abstract contract JBDistributor is IJBDistributor {
|
|
|
464
384
|
override
|
|
465
385
|
returns (uint256 tokenAmount)
|
|
466
386
|
{
|
|
467
|
-
|
|
468
|
-
if (activeVestingLoanIdOf[hook][tokenId][token] != 0) return 0;
|
|
469
|
-
|
|
470
|
-
// The round that we are in right now.
|
|
471
|
-
uint256 round = currentRound();
|
|
472
|
-
|
|
473
|
-
// Keep a reference to the latest vested index.
|
|
474
|
-
uint256 vestedIndex = latestVestedIndexOf[hook][tokenId][token];
|
|
475
|
-
|
|
476
|
-
// Keep a reference to the vesting data array.
|
|
477
|
-
JBVestingData[] storage vestings = vestingDataOf[hook][tokenId][token];
|
|
478
|
-
uint256 numberOfVestingRounds = vestings.length;
|
|
479
|
-
|
|
480
|
-
while (vestedIndex < numberOfVestingRounds) {
|
|
481
|
-
uint256 lockedShare;
|
|
482
|
-
|
|
483
|
-
// Keep a reference to the vested data being iterated on.
|
|
484
|
-
JBVestingData memory vesting = vestings[vestedIndex];
|
|
485
|
-
|
|
486
|
-
lockedShare = JBVestingMath.lockedShareOf({
|
|
487
|
-
releaseRound: vesting.releaseRound,
|
|
488
|
-
currentRound: round,
|
|
489
|
-
vestingRounds: VESTING_ROUNDS,
|
|
490
|
-
maxShare: MAX_SHARE
|
|
491
|
-
});
|
|
492
|
-
|
|
493
|
-
// Calculate the newly unlocked amount from cumulative shares rather than the incremental share delta.
|
|
494
|
-
// Incremental floor rounding can otherwise underpay partial collections and leave dust stranded.
|
|
495
|
-
(uint256 claimAmount,) = JBVestingMath.newlyClaimableAmountOf({
|
|
496
|
-
amount: vesting.amount,
|
|
497
|
-
shareClaimed: vesting.shareClaimed,
|
|
498
|
-
lockedShare: lockedShare,
|
|
499
|
-
maxShare: MAX_SHARE
|
|
500
|
-
});
|
|
501
|
-
tokenAmount += claimAmount;
|
|
502
|
-
|
|
503
|
-
unchecked {
|
|
504
|
-
++vestedIndex;
|
|
505
|
-
}
|
|
506
|
-
}
|
|
507
|
-
}
|
|
508
|
-
|
|
509
|
-
/// @notice The snapshot data of the token information for each round.
|
|
510
|
-
/// @param hook The hook the snapshot is for.
|
|
511
|
-
/// @param token The address of the token claimed and vested.
|
|
512
|
-
/// @param round The round to which the data applies.
|
|
513
|
-
function snapshotAtRoundOf(
|
|
514
|
-
address hook,
|
|
515
|
-
IERC20 token,
|
|
516
|
-
uint256 round
|
|
517
|
-
)
|
|
518
|
-
external
|
|
519
|
-
view
|
|
520
|
-
override
|
|
521
|
-
returns (JBTokenSnapshotData memory)
|
|
522
|
-
{
|
|
523
|
-
return _snapshotAtRoundOf[hook][token][round];
|
|
387
|
+
tokenAmount = _collectableFor({hook: hook, groupId: 0, tokenId: tokenId, token: token});
|
|
524
388
|
}
|
|
525
389
|
|
|
526
390
|
/// @notice The vesting position collateralized by a Revnet loan.
|
|
@@ -548,11 +412,12 @@ abstract contract JBDistributor is IJBDistributor {
|
|
|
548
412
|
// ----------------------- public transactions ----------------------- //
|
|
549
413
|
//*********************************************************************//
|
|
550
414
|
|
|
551
|
-
/// @notice
|
|
552
|
-
///
|
|
553
|
-
///
|
|
415
|
+
/// @notice Begin vesting any unclaimed past reward rounds, then collect everything that has since unlocked and
|
|
416
|
+
/// transfer it to the beneficiary — so callers don't need to separately call `beginVesting`.
|
|
417
|
+
/// @dev The model-specific per-round claim math and the authorization check live in the `_claimPastRewards`
|
|
418
|
+
/// and `_requireCanClaimTokenIds` hooks each concrete distributor implements.
|
|
554
419
|
/// @param hook The hook whose stakers are collecting.
|
|
555
|
-
/// @param tokenIds The IDs of the tokens to collect for (caller must
|
|
420
|
+
/// @param tokenIds The IDs of the tokens to collect for (caller must be authorized for all of them).
|
|
556
421
|
/// @param tokens The reward tokens to collect vested amounts of.
|
|
557
422
|
/// @param beneficiary The recipient of the collected tokens.
|
|
558
423
|
function collectVestedRewards(
|
|
@@ -565,63 +430,7 @@ abstract contract JBDistributor is IJBDistributor {
|
|
|
565
430
|
virtual
|
|
566
431
|
override
|
|
567
432
|
{
|
|
568
|
-
|
|
569
|
-
// outgoing transfer can net against the incoming balance delta and strand the new funds unaccounted.
|
|
570
|
-
_requireNotAcceptingToken();
|
|
571
|
-
|
|
572
|
-
// Revert if no token IDs are provided.
|
|
573
|
-
if (tokenIds.length == 0) revert JBDistributor_EmptyTokenIds({tokenIdCount: tokenIds.length});
|
|
574
|
-
|
|
575
|
-
// Make sure that all tokens can be claimed by this sender.
|
|
576
|
-
for (uint256 i; i < tokenIds.length;) {
|
|
577
|
-
if (!_canClaim({hook: hook, tokenId: tokenIds[i], account: msg.sender})) {
|
|
578
|
-
revert JBDistributor_NoAccess({hook: hook, tokenId: tokenIds[i], account: msg.sender});
|
|
579
|
-
}
|
|
580
|
-
unchecked {
|
|
581
|
-
++i;
|
|
582
|
-
}
|
|
583
|
-
}
|
|
584
|
-
|
|
585
|
-
// --- Auto-vest for the current round ---
|
|
586
|
-
uint256 round = currentRound();
|
|
587
|
-
|
|
588
|
-
// Ensure the snapshot block is recorded for this round.
|
|
589
|
-
_ensureSnapshotBlock(round);
|
|
590
|
-
|
|
591
|
-
// Keep a reference to the total staked amount at the snapshot block.
|
|
592
|
-
uint256 totalStakeAmount = _totalStake({hook: hook, blockNumber: roundSnapshotBlock[round]});
|
|
593
|
-
|
|
594
|
-
// Loop through each token and auto-vest if there's something distributable.
|
|
595
|
-
for (uint256 i; i < tokens.length;) {
|
|
596
|
-
IERC20 token = tokens[i];
|
|
597
|
-
|
|
598
|
-
// Take a snapshot of the token balance if it hasn't been taken already.
|
|
599
|
-
JBTokenSnapshotData memory snapshot = _takeSnapshotOf({hook: hook, token: token});
|
|
600
|
-
uint256 distributable = snapshot.balance - snapshot.vestingAmount;
|
|
601
|
-
|
|
602
|
-
// Only auto-vest if there's something to distribute and there's stake.
|
|
603
|
-
if (distributable > 0 && totalStakeAmount > 0) {
|
|
604
|
-
uint256 totalVestingAmount = _vestTokenIds({
|
|
605
|
-
hook: hook,
|
|
606
|
-
tokenIds: tokenIds,
|
|
607
|
-
token: token,
|
|
608
|
-
distributable: distributable,
|
|
609
|
-
totalStakeAmount: totalStakeAmount,
|
|
610
|
-
vestingReleaseRound: round + VESTING_ROUNDS
|
|
611
|
-
});
|
|
612
|
-
|
|
613
|
-
unchecked {
|
|
614
|
-
totalVestingAmountOf[hook][token] += totalVestingAmount;
|
|
615
|
-
}
|
|
616
|
-
}
|
|
617
|
-
|
|
618
|
-
unchecked {
|
|
619
|
-
++i;
|
|
620
|
-
}
|
|
621
|
-
}
|
|
622
|
-
|
|
623
|
-
// Unlock the rewards and send them to the beneficiary.
|
|
624
|
-
_unlockRewards({hook: hook, tokenIds: tokenIds, tokens: tokens, beneficiary: beneficiary, ownerClaim: true});
|
|
433
|
+
_collectVestedRewards({hook: hook, groupId: 0, tokenIds: tokenIds, tokens: tokens, beneficiary: beneficiary});
|
|
625
434
|
}
|
|
626
435
|
|
|
627
436
|
/// @notice Borrow from a revnet using one token ID's uncollected vesting rewards as collateral.
|
|
@@ -650,41 +459,16 @@ abstract contract JBDistributor is IJBDistributor {
|
|
|
650
459
|
override
|
|
651
460
|
returns (uint256 loanId, uint256 collateralCount)
|
|
652
461
|
{
|
|
653
|
-
|
|
654
|
-
_requireNotAcceptingToken();
|
|
655
|
-
|
|
656
|
-
// Revert if no token IDs are provided.
|
|
657
|
-
if (tokenIds.length == 0) revert JBDistributor_EmptyTokenIds({tokenIdCount: tokenIds.length});
|
|
658
|
-
|
|
659
|
-
// One distributor-held Revnet loan tracks one token ID so one repayment restores one vesting schedule.
|
|
660
|
-
if (tokenIds.length != 1) revert JBDistributor_UnexpectedTokenCount({tokenCount: tokenIds.length});
|
|
661
|
-
|
|
662
|
-
// One loan collateralizes one revnet reward token.
|
|
663
|
-
if (tokens.length != 1) revert JBDistributor_UnexpectedTokenCount({tokenCount: tokens.length});
|
|
664
|
-
|
|
665
|
-
// Zero vesting means rewards are immediately collectible, so there is no locked position to borrow against.
|
|
666
|
-
if (VESTING_ROUNDS == 0) revert JBDistributor_VestingLoansDisabled();
|
|
667
|
-
|
|
668
|
-
// Revnet loan-backed collection is disabled unless a trusted loans contract was set at deployment.
|
|
669
|
-
if (address(REV_LOANS) == address(0)) revert JBDistributor_RevnetLoansNotConfigured();
|
|
670
|
-
|
|
671
|
-
// Make sure that all tokens can be claimed by this sender.
|
|
672
|
-
_requireCanClaimTokenIds({hook: hook, tokenIds: tokenIds});
|
|
673
|
-
|
|
674
|
-
// Bundle the remaining borrow parameters to keep the loan workflow readable and stack-safe.
|
|
675
|
-
JBBorrowContext memory ctx = JBBorrowContext({
|
|
462
|
+
(loanId, collateralCount) = _borrowAgainstVestingFor({
|
|
676
463
|
hook: hook,
|
|
677
|
-
|
|
678
|
-
|
|
464
|
+
groupId: 0,
|
|
465
|
+
tokenIds: tokenIds,
|
|
466
|
+
tokens: tokens,
|
|
679
467
|
sourceToken: sourceToken,
|
|
680
468
|
minBorrowAmount: minBorrowAmount,
|
|
681
469
|
prepaidFeePercent: prepaidFeePercent,
|
|
682
|
-
beneficiary: beneficiary
|
|
683
|
-
revnetId: _revnetIdOf(tokens[0])
|
|
470
|
+
beneficiary: beneficiary
|
|
684
471
|
});
|
|
685
|
-
|
|
686
|
-
// Open and track the distributor-owned loan.
|
|
687
|
-
(loanId, collateralCount) = _borrowAgainstVesting({ctx: ctx, tokenIds: tokenIds, tokens: tokens});
|
|
688
472
|
}
|
|
689
473
|
|
|
690
474
|
/// @notice Repay a distributor-held Revnet loan and restore its collateral to the original vesting schedule.
|
|
@@ -767,15 +551,210 @@ abstract contract JBDistributor is IJBDistributor {
|
|
|
767
551
|
|
|
768
552
|
/// @notice Claim all past reward rounds for the given token IDs and reward tokens into fresh vesting entries.
|
|
769
553
|
/// @param hook The hook whose stakers are claiming.
|
|
554
|
+
/// @param groupId The reward group being claimed (0 = the default group).
|
|
770
555
|
/// @param tokenIds The token IDs to claim for.
|
|
771
556
|
/// @param tokens The reward tokens to claim.
|
|
772
|
-
function _claimPastRewards(
|
|
557
|
+
function _claimPastRewards(
|
|
558
|
+
address hook,
|
|
559
|
+
uint256 groupId,
|
|
560
|
+
uint256[] calldata tokenIds,
|
|
561
|
+
IERC20[] calldata tokens
|
|
562
|
+
)
|
|
563
|
+
internal
|
|
564
|
+
virtual;
|
|
773
565
|
|
|
774
566
|
/// @notice Revert unless the caller is authorized to claim each token ID.
|
|
775
567
|
/// @param hook The hook whose token IDs are being checked.
|
|
776
568
|
/// @param tokenIds The token IDs to check.
|
|
777
569
|
function _requireCanClaimTokenIds(address hook, uint256[] calldata tokenIds) internal view virtual;
|
|
778
570
|
|
|
571
|
+
/// @notice Shared begin-vesting logic across reward groups.
|
|
572
|
+
/// @param hook The hook whose stakers are vesting.
|
|
573
|
+
/// @param groupId The reward group (0 = the default group).
|
|
574
|
+
/// @param tokenIds The staker token IDs to claim rewards for.
|
|
575
|
+
/// @param tokens The reward tokens to begin vesting.
|
|
576
|
+
function _beginVesting(
|
|
577
|
+
address hook,
|
|
578
|
+
uint256 groupId,
|
|
579
|
+
uint256[] calldata tokenIds,
|
|
580
|
+
IERC20[] calldata tokens
|
|
581
|
+
)
|
|
582
|
+
internal
|
|
583
|
+
{
|
|
584
|
+
// Reward accounting cannot change while an ERC-20 `transferFrom` is in progress.
|
|
585
|
+
_requireNotAcceptingToken();
|
|
586
|
+
|
|
587
|
+
// Revert if no token IDs are provided.
|
|
588
|
+
if (tokenIds.length == 0) revert JBDistributor_EmptyTokenIds({tokenIdCount: tokenIds.length});
|
|
589
|
+
|
|
590
|
+
// Only the entity authorized for these token IDs may start their vesting clock.
|
|
591
|
+
_requireCanClaimTokenIds({hook: hook, tokenIds: tokenIds});
|
|
592
|
+
|
|
593
|
+
// Materialize all unclaimed historical reward rounds into fresh vesting entries that start now.
|
|
594
|
+
_claimPastRewards({hook: hook, groupId: groupId, tokenIds: tokenIds, tokens: tokens});
|
|
595
|
+
}
|
|
596
|
+
|
|
597
|
+
/// @notice Shared begin-vesting-then-collect logic across reward groups.
|
|
598
|
+
/// @param hook The hook whose stakers are collecting.
|
|
599
|
+
/// @param groupId The reward group (0 = the default group).
|
|
600
|
+
/// @param tokenIds The token IDs to collect for.
|
|
601
|
+
/// @param tokens The reward tokens to collect.
|
|
602
|
+
/// @param beneficiary The recipient of the collected tokens.
|
|
603
|
+
function _collectVestedRewards(
|
|
604
|
+
address hook,
|
|
605
|
+
uint256 groupId,
|
|
606
|
+
uint256[] calldata tokenIds,
|
|
607
|
+
IERC20[] calldata tokens,
|
|
608
|
+
address beneficiary
|
|
609
|
+
)
|
|
610
|
+
internal
|
|
611
|
+
{
|
|
612
|
+
// Collections transfer reward tokens out; block them mid inbound transfer.
|
|
613
|
+
_requireNotAcceptingToken();
|
|
614
|
+
|
|
615
|
+
// Revert if no token IDs are provided.
|
|
616
|
+
if (tokenIds.length == 0) revert JBDistributor_EmptyTokenIds({tokenIdCount: tokenIds.length});
|
|
617
|
+
|
|
618
|
+
// Only the entity authorized for these token IDs may materialize and collect their rewards.
|
|
619
|
+
_requireCanClaimTokenIds({hook: hook, tokenIds: tokenIds});
|
|
620
|
+
|
|
621
|
+
// Before collecting, bring the token IDs current by starting vesting for any past reward rounds.
|
|
622
|
+
_claimPastRewards({hook: hook, groupId: groupId, tokenIds: tokenIds, tokens: tokens});
|
|
623
|
+
|
|
624
|
+
// Release whatever portion of existing vesting entries has unlocked by this round.
|
|
625
|
+
_unlockRewards({
|
|
626
|
+
hook: hook, groupId: groupId, tokenIds: tokenIds, tokens: tokens, beneficiary: beneficiary, ownerClaim: true
|
|
627
|
+
});
|
|
628
|
+
}
|
|
629
|
+
|
|
630
|
+
/// @notice Shared forfeiture-release logic across reward groups.
|
|
631
|
+
/// @param hook The hook whose tokens were burned.
|
|
632
|
+
/// @param groupId The reward group (0 = the default group).
|
|
633
|
+
/// @param tokenIds The IDs of the burned tokens.
|
|
634
|
+
/// @param tokens The reward tokens to recycle.
|
|
635
|
+
/// @param beneficiary Unused for forfeiture. Kept for interface compatibility.
|
|
636
|
+
function _releaseForfeitedRewards(
|
|
637
|
+
address hook,
|
|
638
|
+
uint256 groupId,
|
|
639
|
+
uint256[] calldata tokenIds,
|
|
640
|
+
IERC20[] calldata tokens,
|
|
641
|
+
address beneficiary
|
|
642
|
+
)
|
|
643
|
+
internal
|
|
644
|
+
{
|
|
645
|
+
// Do not let reward-token callbacks mutate vesting state during inbound balance-delta accounting.
|
|
646
|
+
_requireNotAcceptingToken();
|
|
647
|
+
|
|
648
|
+
// Make sure that all staker token IDs are burned.
|
|
649
|
+
for (uint256 i; i < tokenIds.length;) {
|
|
650
|
+
if (!_tokenBurned({hook: hook, tokenId: tokenIds[i]})) {
|
|
651
|
+
revert JBDistributor_NoAccess({hook: hook, tokenId: tokenIds[i], account: msg.sender});
|
|
652
|
+
}
|
|
653
|
+
unchecked {
|
|
654
|
+
++i;
|
|
655
|
+
}
|
|
656
|
+
}
|
|
657
|
+
|
|
658
|
+
// Unlock the rewards and recycle the forfeited amount.
|
|
659
|
+
_unlockRewards({
|
|
660
|
+
hook: hook,
|
|
661
|
+
groupId: groupId,
|
|
662
|
+
tokenIds: tokenIds,
|
|
663
|
+
tokens: tokens,
|
|
664
|
+
beneficiary: beneficiary,
|
|
665
|
+
ownerClaim: false
|
|
666
|
+
});
|
|
667
|
+
}
|
|
668
|
+
|
|
669
|
+
/// @notice Shared expired-reward recycling logic across reward groups.
|
|
670
|
+
/// @param hook The hook whose expired rewards should be recycled.
|
|
671
|
+
/// @param groupId The reward group (0 = the default group).
|
|
672
|
+
/// @param token The reward token to recycle.
|
|
673
|
+
/// @param rounds The reward rounds to recycle.
|
|
674
|
+
/// @return amount The total amount recycled.
|
|
675
|
+
function _burnExpiredRewards(
|
|
676
|
+
address hook,
|
|
677
|
+
uint256 groupId,
|
|
678
|
+
IERC20 token,
|
|
679
|
+
uint256[] calldata rounds
|
|
680
|
+
)
|
|
681
|
+
internal
|
|
682
|
+
returns (uint256 amount)
|
|
683
|
+
{
|
|
684
|
+
// Do not let reward-token callbacks recycle inventory during an inbound balance-delta measurement.
|
|
685
|
+
_requireNotAcceptingToken();
|
|
686
|
+
|
|
687
|
+
// Process every requested round independently so callers can batch keeper work.
|
|
688
|
+
for (uint256 i; i < rounds.length;) {
|
|
689
|
+
amount += _recycleExpiredRewardRound({hook: hook, groupId: groupId, token: token, round: rounds[i]});
|
|
690
|
+
unchecked {
|
|
691
|
+
++i;
|
|
692
|
+
}
|
|
693
|
+
}
|
|
694
|
+
}
|
|
695
|
+
|
|
696
|
+
/// @notice Shared borrow-against-vesting logic across reward groups.
|
|
697
|
+
/// @param hook The hook whose staker is borrowing against vesting rewards.
|
|
698
|
+
/// @param groupId The reward group (0 = the default group).
|
|
699
|
+
/// @param tokenIds The single token ID to borrow against.
|
|
700
|
+
/// @param tokens The single revnet reward token to collateralize.
|
|
701
|
+
/// @param sourceToken The token to borrow from the revnet.
|
|
702
|
+
/// @param minBorrowAmount The minimum amount to borrow, denominated in `sourceToken`.
|
|
703
|
+
/// @param prepaidFeePercent The fee percent to charge upfront.
|
|
704
|
+
/// @param beneficiary The recipient of the borrowed funds.
|
|
705
|
+
/// @return loanId The Revnet loan NFT ID held by this distributor.
|
|
706
|
+
/// @return collateralCount The amount of vesting rewards used as collateral.
|
|
707
|
+
function _borrowAgainstVestingFor(
|
|
708
|
+
address hook,
|
|
709
|
+
uint256 groupId,
|
|
710
|
+
uint256[] calldata tokenIds,
|
|
711
|
+
IERC20[] calldata tokens,
|
|
712
|
+
address sourceToken,
|
|
713
|
+
uint256 minBorrowAmount,
|
|
714
|
+
uint256 prepaidFeePercent,
|
|
715
|
+
address payable beneficiary
|
|
716
|
+
)
|
|
717
|
+
internal
|
|
718
|
+
returns (uint256 loanId, uint256 collateralCount)
|
|
719
|
+
{
|
|
720
|
+
// Do not let reward-token callbacks mutate claim accounting during an inbound transfer.
|
|
721
|
+
_requireNotAcceptingToken();
|
|
722
|
+
|
|
723
|
+
// Revert if no token IDs are provided.
|
|
724
|
+
if (tokenIds.length == 0) revert JBDistributor_EmptyTokenIds({tokenIdCount: tokenIds.length});
|
|
725
|
+
|
|
726
|
+
// One distributor-held Revnet loan tracks one token ID so one repayment restores one vesting schedule.
|
|
727
|
+
if (tokenIds.length != 1) revert JBDistributor_UnexpectedTokenCount({tokenCount: tokenIds.length});
|
|
728
|
+
|
|
729
|
+
// One loan collateralizes one revnet reward token.
|
|
730
|
+
if (tokens.length != 1) revert JBDistributor_UnexpectedTokenCount({tokenCount: tokens.length});
|
|
731
|
+
|
|
732
|
+
// Zero vesting means rewards are immediately collectible, so there is no locked position to borrow against.
|
|
733
|
+
if (VESTING_ROUNDS == 0) revert JBDistributor_VestingLoansDisabled();
|
|
734
|
+
|
|
735
|
+
// Revnet loan-backed collection is disabled unless a trusted loans contract was set at deployment.
|
|
736
|
+
if (address(REV_LOANS) == address(0)) revert JBDistributor_RevnetLoansNotConfigured();
|
|
737
|
+
|
|
738
|
+
// Make sure that all tokens can be claimed by this sender.
|
|
739
|
+
_requireCanClaimTokenIds({hook: hook, tokenIds: tokenIds});
|
|
740
|
+
|
|
741
|
+
// Bundle the remaining borrow parameters to keep the loan workflow readable and stack-safe.
|
|
742
|
+
JBBorrowContext memory ctx = JBBorrowContext({
|
|
743
|
+
hook: hook,
|
|
744
|
+
groupId: groupId,
|
|
745
|
+
tokenId: tokenIds[0],
|
|
746
|
+
token: tokens[0],
|
|
747
|
+
sourceToken: sourceToken,
|
|
748
|
+
minBorrowAmount: minBorrowAmount,
|
|
749
|
+
prepaidFeePercent: prepaidFeePercent,
|
|
750
|
+
beneficiary: beneficiary,
|
|
751
|
+
revnetId: _revnetIdOf(tokens[0])
|
|
752
|
+
});
|
|
753
|
+
|
|
754
|
+
// Open and track the distributor-owned loan.
|
|
755
|
+
(loanId, collateralCount) = _borrowAgainstVesting({ctx: ctx, tokenIds: tokenIds, tokens: tokens});
|
|
756
|
+
}
|
|
757
|
+
|
|
779
758
|
/// @notice Open and track a distributor-held Revnet loan against one vesting position.
|
|
780
759
|
/// @param ctx The borrow context.
|
|
781
760
|
/// @param tokenIds The single token ID being collateralized.
|
|
@@ -791,7 +770,7 @@ abstract contract JBDistributor is IJBDistributor {
|
|
|
791
770
|
returns (uint256 loanId, uint256 collateralCount)
|
|
792
771
|
{
|
|
793
772
|
// One vesting position cannot be collateralized by two outstanding loans.
|
|
794
|
-
uint256 activeLoanId = activeVestingLoanIdOf[ctx.hook][ctx.tokenId][ctx.token];
|
|
773
|
+
uint256 activeLoanId = activeVestingLoanIdOf[ctx.hook][ctx.groupId][ctx.tokenId][ctx.token];
|
|
795
774
|
if (activeLoanId != 0) {
|
|
796
775
|
revert JBDistributor_VestingLoanOutstanding({
|
|
797
776
|
hook: ctx.hook, tokenId: ctx.tokenId, token: address(ctx.token), loanId: activeLoanId
|
|
@@ -799,13 +778,14 @@ abstract contract JBDistributor is IJBDistributor {
|
|
|
799
778
|
}
|
|
800
779
|
|
|
801
780
|
// Bring the claimant current before measuring collateral.
|
|
802
|
-
_claimPastRewards({hook: ctx.hook, tokenIds: tokenIds, tokens: tokens});
|
|
781
|
+
_claimPastRewards({hook: ctx.hook, groupId: ctx.groupId, tokenIds: tokenIds, tokens: tokens});
|
|
803
782
|
|
|
804
783
|
// Use the remaining uncollected vesting amount as collateral without advancing the vesting schedule.
|
|
805
|
-
collateralCount =
|
|
784
|
+
collateralCount =
|
|
785
|
+
_unclaimedVestingAmountOf({hook: ctx.hook, groupId: ctx.groupId, tokenId: ctx.tokenId, token: ctx.token});
|
|
806
786
|
|
|
807
787
|
// Remember the vesting-entry boundary so liquidation write-off cannot consume later rewards.
|
|
808
|
-
uint48 vestingDataCount = _toUint48(vestingDataOf[ctx.hook][ctx.tokenId][ctx.token].length);
|
|
788
|
+
uint48 vestingDataCount = _toUint48(vestingDataOf[ctx.hook][ctx.groupId][ctx.tokenId][ctx.token].length);
|
|
809
789
|
|
|
810
790
|
// A zero-collateral loan would revert in Revnet, but this local error explains why.
|
|
811
791
|
if (collateralCount == 0) {
|
|
@@ -818,7 +798,7 @@ abstract contract JBDistributor is IJBDistributor {
|
|
|
818
798
|
totalLoanedVestingAmountOf[ctx.hook][ctx.token] += collateralCount;
|
|
819
799
|
|
|
820
800
|
// Block same-position reentrancy before the loan contract burns collateral and returns the real loan ID.
|
|
821
|
-
activeVestingLoanIdOf[ctx.hook][ctx.tokenId][ctx.token] = _PENDING_VESTING_LOAN_ID;
|
|
801
|
+
activeVestingLoanIdOf[ctx.hook][ctx.groupId][ctx.tokenId][ctx.token] = _PENDING_VESTING_LOAN_ID;
|
|
822
802
|
|
|
823
803
|
// Open the Revnet loan with this distributor as the holder whose tokens are burned as collateral.
|
|
824
804
|
loanId = _openVestingLoan({ctx: ctx, collateralCount: collateralCount});
|
|
@@ -827,9 +807,10 @@ abstract contract JBDistributor is IJBDistributor {
|
|
|
827
807
|
}
|
|
828
808
|
|
|
829
809
|
// Track the distributor-held loan so repayment can restore the same vesting position.
|
|
830
|
-
activeVestingLoanIdOf[ctx.hook][ctx.tokenId][ctx.token] = loanId;
|
|
810
|
+
activeVestingLoanIdOf[ctx.hook][ctx.groupId][ctx.tokenId][ctx.token] = loanId;
|
|
831
811
|
_vestingLoanOf[loanId] = JBVestingLoan({
|
|
832
812
|
hook: ctx.hook,
|
|
813
|
+
groupId: ctx.groupId,
|
|
833
814
|
tokenId: ctx.tokenId,
|
|
834
815
|
token: ctx.token,
|
|
835
816
|
vestingDataCount: vestingDataCount,
|
|
@@ -979,7 +960,7 @@ abstract contract JBDistributor is IJBDistributor {
|
|
|
979
960
|
totalLoanedVestingAmountOf[vestingLoan.hook][vestingLoan.token] -= vestingLoan.collateralCount;
|
|
980
961
|
|
|
981
962
|
// Clear the lock that prevented this position from being collected while collateralized.
|
|
982
|
-
delete activeVestingLoanIdOf[vestingLoan.hook][vestingLoan.tokenId][vestingLoan.token];
|
|
963
|
+
delete activeVestingLoanIdOf[vestingLoan.hook][vestingLoan.groupId][vestingLoan.tokenId][vestingLoan.token];
|
|
983
964
|
delete _vestingLoanOf[loanId];
|
|
984
965
|
|
|
985
966
|
// Return any excess reward tokens created during source-fee payment to the repayer.
|
|
@@ -1013,10 +994,12 @@ abstract contract JBDistributor is IJBDistributor {
|
|
|
1013
994
|
collateralCount = vestingLoan.collateralCount;
|
|
1014
995
|
|
|
1015
996
|
// Load the vesting entries for the token ID whose rewards were collateralized.
|
|
1016
|
-
JBVestingData[] storage vestings =
|
|
997
|
+
JBVestingData[] storage vestings =
|
|
998
|
+
vestingDataOf[vestingLoan.hook][vestingLoan.groupId][vestingLoan.tokenId][vestingLoan.token];
|
|
1017
999
|
|
|
1018
1000
|
// Start at the first unexhausted vesting entry.
|
|
1019
|
-
uint256 vestedIndex =
|
|
1001
|
+
uint256 vestedIndex =
|
|
1002
|
+
latestVestedIndexOf[vestingLoan.hook][vestingLoan.groupId][vestingLoan.tokenId][vestingLoan.token];
|
|
1020
1003
|
|
|
1021
1004
|
// Stop at the boundary recorded when the loan opened, preserving newer vesting entries.
|
|
1022
1005
|
uint256 vestingDataCount = vestingLoan.vestingDataCount;
|
|
@@ -1032,7 +1015,7 @@ abstract contract JBDistributor is IJBDistributor {
|
|
|
1032
1015
|
}
|
|
1033
1016
|
|
|
1034
1017
|
// Skip over the written-off vesting entries without ever moving the cursor backwards.
|
|
1035
|
-
latestVestedIndexOf[vestingLoan.hook][vestingLoan.tokenId][vestingLoan.token] = vestedIndex;
|
|
1018
|
+
latestVestedIndexOf[vestingLoan.hook][vestingLoan.groupId][vestingLoan.tokenId][vestingLoan.token] = vestedIndex;
|
|
1036
1019
|
|
|
1037
1020
|
// Remove the liquidated collateral from the amount still considered vesting.
|
|
1038
1021
|
totalVestingAmountOf[vestingLoan.hook][vestingLoan.token] -= collateralCount;
|
|
@@ -1041,7 +1024,7 @@ abstract contract JBDistributor is IJBDistributor {
|
|
|
1041
1024
|
totalLoanedVestingAmountOf[vestingLoan.hook][vestingLoan.token] -= collateralCount;
|
|
1042
1025
|
|
|
1043
1026
|
// Clear the active loan lock for this token ID and reward token.
|
|
1044
|
-
delete activeVestingLoanIdOf[vestingLoan.hook][vestingLoan.tokenId][vestingLoan.token];
|
|
1027
|
+
delete activeVestingLoanIdOf[vestingLoan.hook][vestingLoan.groupId][vestingLoan.tokenId][vestingLoan.token];
|
|
1045
1028
|
|
|
1046
1029
|
// Clear the loan metadata so it cannot be written off or repaid again.
|
|
1047
1030
|
delete _vestingLoanOf[loanId];
|
|
@@ -1092,9 +1075,10 @@ abstract contract JBDistributor is IJBDistributor {
|
|
|
1092
1075
|
|
|
1093
1076
|
/// @notice Accept funds and assign them to this round's reward ledger.
|
|
1094
1077
|
/// @param hook The stake source whose stakers receive the rewards.
|
|
1078
|
+
/// @param groupId The reward group being funded (0 = the default group).
|
|
1095
1079
|
/// @param token The reward token being funded.
|
|
1096
1080
|
/// @param amount The nominal amount to fund.
|
|
1097
|
-
function _fund(address hook, IERC20 token, uint256 amount) internal {
|
|
1081
|
+
function _fund(address hook, uint256 groupId, IERC20 token, uint256 amount) internal {
|
|
1098
1082
|
// Native funding is measured by msg.value, not the caller-provided amount.
|
|
1099
1083
|
if (address(token) == JBConstants.NATIVE_TOKEN) {
|
|
1100
1084
|
amount = msg.value;
|
|
@@ -1109,38 +1093,41 @@ abstract contract JBDistributor is IJBDistributor {
|
|
|
1109
1093
|
}
|
|
1110
1094
|
|
|
1111
1095
|
// Store the accepted amount in this round's historical reward ledger.
|
|
1112
|
-
_recordRewardFunding({hook: hook, token: token, amount: amount});
|
|
1096
|
+
_recordRewardFunding({hook: hook, groupId: groupId, token: token, amount: amount});
|
|
1113
1097
|
}
|
|
1114
1098
|
|
|
1115
1099
|
/// @notice Record accepted funding as the current round's reward pot.
|
|
1116
1100
|
/// @param hook The stake source whose stakers receive the rewards.
|
|
1101
|
+
/// @param groupId The reward group (0 = the default group).
|
|
1117
1102
|
/// @param token The reward token.
|
|
1118
1103
|
/// @param amount The accepted funding amount.
|
|
1119
|
-
function _recordRewardFunding(address hook, IERC20 token, uint256 amount) internal {
|
|
1104
|
+
function _recordRewardFunding(address hook, uint256 groupId, IERC20 token, uint256 amount) internal {
|
|
1120
1105
|
// Zero-value transfers do not create reward rounds or alter tracked balances.
|
|
1121
1106
|
if (amount == 0) return;
|
|
1122
1107
|
|
|
1123
1108
|
// Add the accepted amount to the current reward ledger.
|
|
1124
|
-
_recordRewardRound({hook: hook, token: token, amount: amount});
|
|
1109
|
+
_recordRewardRound({hook: hook, groupId: groupId, token: token, amount: amount});
|
|
1125
1110
|
|
|
1126
|
-
// Keep the base distributor's balance accounting in sync for collection and conservation checks.
|
|
1111
|
+
// Keep the base distributor's balance accounting in sync for collection and conservation checks. Balances
|
|
1112
|
+
// are tracked per (hook, token) across all groups because they share one token custody pool.
|
|
1127
1113
|
_balanceOf[hook][token] += amount;
|
|
1128
1114
|
_accountedBalanceOf[token] += amount;
|
|
1129
1115
|
}
|
|
1130
1116
|
|
|
1131
1117
|
/// @notice Record rewards as the current round's claimable historical reward pot.
|
|
1132
1118
|
/// @param hook The stake source whose stakers receive the rewards.
|
|
1119
|
+
/// @param groupId The reward group (0 = the default group).
|
|
1133
1120
|
/// @param token The reward token.
|
|
1134
1121
|
/// @param amount The amount to add to the current reward round.
|
|
1135
|
-
function _recordRewardRound(address hook, IERC20 token, uint256 amount) internal {
|
|
1122
|
+
function _recordRewardRound(address hook, uint256 groupId, IERC20 token, uint256 amount) internal {
|
|
1136
1123
|
// Zero-value rewards do not create reward rounds.
|
|
1137
1124
|
if (amount == 0) return;
|
|
1138
1125
|
|
|
1139
1126
|
// Rewards belong to the round in progress when they enter the ledger.
|
|
1140
1127
|
uint256 round = currentRound();
|
|
1141
1128
|
|
|
1142
|
-
// Load the current round's ledger entry for this hook and reward token.
|
|
1143
|
-
JBRewardRoundData storage rewardRound = rewardRoundOf[hook][token][round];
|
|
1129
|
+
// Load the current round's ledger entry for this hook, group, and reward token.
|
|
1130
|
+
JBRewardRoundData storage rewardRound = rewardRoundOf[hook][groupId][token][round];
|
|
1144
1131
|
|
|
1145
1132
|
// Every reward round in this contract uses the same immutable claim duration.
|
|
1146
1133
|
uint48 claimDeadline = _claimDeadlineFor(round);
|
|
@@ -1156,8 +1143,8 @@ abstract contract JBDistributor is IJBDistributor {
|
|
|
1156
1143
|
// Store the packed claim deadline fixed for this distributor.
|
|
1157
1144
|
rewardRound.claimDeadline = claimDeadline;
|
|
1158
1145
|
|
|
1159
|
-
// Store the packed total stake that shares this
|
|
1160
|
-
rewardRound.totalStake = _toUint208(_totalStake({hook: hook, blockNumber: snapshotBlock}));
|
|
1146
|
+
// Store the packed total stake that shares this group's round reward pot.
|
|
1147
|
+
rewardRound.totalStake = _toUint208(_totalStake({hook: hook, groupId: groupId, blockNumber: snapshotBlock}));
|
|
1161
1148
|
}
|
|
1162
1149
|
|
|
1163
1150
|
// Multiple additions in the same round share the same snapshot and reward pot.
|
|
@@ -1166,11 +1153,13 @@ abstract contract JBDistributor is IJBDistributor {
|
|
|
1166
1153
|
|
|
1167
1154
|
/// @notice Recycle one expired reward round's unclaimed inventory into the current reward round.
|
|
1168
1155
|
/// @param hook The hook whose expired rewards should be recycled.
|
|
1156
|
+
/// @param groupId The reward group (0 = the default group).
|
|
1169
1157
|
/// @param token The reward token to recycle.
|
|
1170
1158
|
/// @param round The reward round to recycle.
|
|
1171
1159
|
/// @return recycleAmount The amount recycled.
|
|
1172
1160
|
function _recycleExpiredRewardRound(
|
|
1173
1161
|
address hook,
|
|
1162
|
+
uint256 groupId,
|
|
1174
1163
|
IERC20 token,
|
|
1175
1164
|
uint256 round
|
|
1176
1165
|
)
|
|
@@ -1178,7 +1167,7 @@ abstract contract JBDistributor is IJBDistributor {
|
|
|
1178
1167
|
returns (uint256 recycleAmount)
|
|
1179
1168
|
{
|
|
1180
1169
|
// Load the reward round once so expiry, claimed amount, and funded amount stay in sync.
|
|
1181
|
-
JBRewardRoundData storage rewardRound = rewardRoundOf[hook][token][round];
|
|
1170
|
+
JBRewardRoundData storage rewardRound = rewardRoundOf[hook][groupId][token][round];
|
|
1182
1171
|
|
|
1183
1172
|
// Ignore rounds that either never expire or have not reached their deadline yet.
|
|
1184
1173
|
if (!_rewardRoundExpired(rewardRound)) return 0;
|
|
@@ -1194,7 +1183,7 @@ abstract contract JBDistributor is IJBDistributor {
|
|
|
1194
1183
|
|
|
1195
1184
|
// Keep the inventory in the distributor and give the current staker set a new claimable round.
|
|
1196
1185
|
uint256 recycledToRound = currentRound();
|
|
1197
|
-
_recordRewardRound({hook: hook, token: token, amount: recycleAmount});
|
|
1186
|
+
_recordRewardRound({hook: hook, groupId: groupId, token: token, amount: recycleAmount});
|
|
1198
1187
|
|
|
1199
1188
|
// Surface the permissionless recycle for off-chain accounting.
|
|
1200
1189
|
emit ExpiredRewardsRecycled({
|
|
@@ -1261,41 +1250,6 @@ abstract contract JBDistributor is IJBDistributor {
|
|
|
1261
1250
|
}
|
|
1262
1251
|
}
|
|
1263
1252
|
|
|
1264
|
-
/// @notice Takes a snapshot of the token balance and vesting amount for the current round.
|
|
1265
|
-
/// @param hook The hook to take the snapshot for.
|
|
1266
|
-
/// @param token The token address to take a snapshot of.
|
|
1267
|
-
/// @return snapshot The snapshot data.
|
|
1268
|
-
function _takeSnapshotOf(address hook, IERC20 token) internal returns (JBTokenSnapshotData memory snapshot) {
|
|
1269
|
-
// Keep a reference to the current round.
|
|
1270
|
-
uint256 round = currentRound();
|
|
1271
|
-
|
|
1272
|
-
// If a snapshot was already taken at this round, do not take a new one. The init flag must be used as the
|
|
1273
|
-
// sentinel: a zero balance is a valid snapshot value (round started with no funded balance), not a signal
|
|
1274
|
-
// to re-snapshot. Re-snapshotting would let mid-round deposits leak into the current round's allocation.
|
|
1275
|
-
if (_snapshotInitializedFor[hook][token][round]) {
|
|
1276
|
-
return _snapshotAtRoundOf[hook][token][round];
|
|
1277
|
-
}
|
|
1278
|
-
|
|
1279
|
-
// Exclude collateralized vesting inventory because those tokens have been burned into distributor-held loans.
|
|
1280
|
-
uint256 vestingAmount = totalVestingAmountOf[hook][token] - totalLoanedVestingAmountOf[hook][token];
|
|
1281
|
-
|
|
1282
|
-
// Take a snapshot using the hook's tracked balance.
|
|
1283
|
-
snapshot = JBTokenSnapshotData({balance: _balanceOf[hook][token], vestingAmount: vestingAmount});
|
|
1284
|
-
|
|
1285
|
-
// Store the snapshot and mark it initialized.
|
|
1286
|
-
_snapshotAtRoundOf[hook][token][round] = snapshot;
|
|
1287
|
-
_snapshotInitializedFor[hook][token][round] = true;
|
|
1288
|
-
|
|
1289
|
-
emit SnapshotCreated({
|
|
1290
|
-
hook: hook,
|
|
1291
|
-
round: round,
|
|
1292
|
-
token: token,
|
|
1293
|
-
balance: snapshot.balance,
|
|
1294
|
-
vestingAmount: snapshot.vestingAmount,
|
|
1295
|
-
caller: msg.sender
|
|
1296
|
-
});
|
|
1297
|
-
}
|
|
1298
|
-
|
|
1299
1253
|
/// @notice The deadline for a reward round using this distributor's immutable claim duration.
|
|
1300
1254
|
/// @param round The reward round.
|
|
1301
1255
|
/// @return claimDeadline The deadline timestamp. Zero means no expiration.
|
|
@@ -1321,12 +1275,14 @@ abstract contract JBDistributor is IJBDistributor {
|
|
|
1321
1275
|
|
|
1322
1276
|
/// @notice Unlocks rewards for the given token IDs and tokens, either for collection or forfeiture.
|
|
1323
1277
|
/// @param hook The hook the tokens belong to.
|
|
1278
|
+
/// @param groupId The reward group (0 = the default group).
|
|
1324
1279
|
/// @param tokenIds The IDs of the tokens to unlock rewards for.
|
|
1325
1280
|
/// @param tokens The addresses of the tokens to unlock.
|
|
1326
1281
|
/// @param beneficiary The recipient of the unlocked tokens.
|
|
1327
1282
|
/// @param ownerClaim Whether this is a claim by the owner (true) or a forfeiture release (false).
|
|
1328
1283
|
function _unlockRewards(
|
|
1329
1284
|
address hook,
|
|
1285
|
+
uint256 groupId,
|
|
1330
1286
|
uint256[] calldata tokenIds,
|
|
1331
1287
|
IERC20[] calldata tokens,
|
|
1332
1288
|
address beneficiary,
|
|
@@ -1341,7 +1297,8 @@ abstract contract JBDistributor is IJBDistributor {
|
|
|
1341
1297
|
IERC20 token = tokens[i];
|
|
1342
1298
|
|
|
1343
1299
|
// Process all token IDs for this reward token.
|
|
1344
|
-
uint256 totalTokenAmount =
|
|
1300
|
+
uint256 totalTokenAmount =
|
|
1301
|
+
_unlockTokenIds({hook: hook, groupId: groupId, tokenIds: tokenIds, token: token, round: round});
|
|
1345
1302
|
|
|
1346
1303
|
// Perform the transfer.
|
|
1347
1304
|
if (totalTokenAmount != 0) {
|
|
@@ -1368,7 +1325,7 @@ abstract contract JBDistributor is IJBDistributor {
|
|
|
1368
1325
|
}
|
|
1369
1326
|
} else {
|
|
1370
1327
|
// If forfeiture: keep inventory in the distributor and give the current staker set a fresh round.
|
|
1371
|
-
_recordRewardRound({hook: hook, token: token, amount: totalTokenAmount});
|
|
1328
|
+
_recordRewardRound({hook: hook, groupId: groupId, token: token, amount: totalTokenAmount});
|
|
1372
1329
|
emit ForfeitedRewardsRecycled({
|
|
1373
1330
|
hook: hook, round: round, token: token, amount: totalTokenAmount, caller: msg.sender
|
|
1374
1331
|
});
|
|
@@ -1383,12 +1340,14 @@ abstract contract JBDistributor is IJBDistributor {
|
|
|
1383
1340
|
|
|
1384
1341
|
/// @notice Unlocks rewards for a set of token IDs for a single reward token.
|
|
1385
1342
|
/// @param hook The hook the tokens belong to.
|
|
1343
|
+
/// @param groupId The reward group (0 = the default group).
|
|
1386
1344
|
/// @param tokenIds The IDs of the tokens to unlock rewards for.
|
|
1387
1345
|
/// @param token The reward token to unlock.
|
|
1388
1346
|
/// @param round The current round.
|
|
1389
1347
|
/// @return totalTokenAmount The total amount of reward tokens unlocked.
|
|
1390
1348
|
function _unlockTokenIds(
|
|
1391
1349
|
address hook,
|
|
1350
|
+
uint256 groupId,
|
|
1392
1351
|
uint256[] calldata tokenIds,
|
|
1393
1352
|
IERC20 token,
|
|
1394
1353
|
uint256 round
|
|
@@ -1400,13 +1359,13 @@ abstract contract JBDistributor is IJBDistributor {
|
|
|
1400
1359
|
uint256 tokenId = tokenIds[j];
|
|
1401
1360
|
|
|
1402
1361
|
// Loan collateral stays locked until repayment restores it to this distributor.
|
|
1403
|
-
_requireNoActiveVestingLoan({hook: hook, tokenId: tokenId, token: token});
|
|
1362
|
+
_requireNoActiveVestingLoan({hook: hook, groupId: groupId, tokenId: tokenId, token: token});
|
|
1404
1363
|
|
|
1405
1364
|
// Keep a reference to the latest vested index.
|
|
1406
|
-
uint256 vestedIndex = latestVestedIndexOf[hook][tokenId][token];
|
|
1365
|
+
uint256 vestedIndex = latestVestedIndexOf[hook][groupId][tokenId][token];
|
|
1407
1366
|
|
|
1408
1367
|
// Keep a reference to the vesting data array.
|
|
1409
|
-
JBVestingData[] storage vestings = vestingDataOf[hook][tokenId][token];
|
|
1368
|
+
JBVestingData[] storage vestings = vestingDataOf[hook][groupId][tokenId][token];
|
|
1410
1369
|
uint256 numberOfVestingRounds = vestings.length;
|
|
1411
1370
|
|
|
1412
1371
|
// Keep a reference to a vested index that will be incremented.
|
|
@@ -1441,6 +1400,7 @@ abstract contract JBDistributor is IJBDistributor {
|
|
|
1441
1400
|
emit Collected({
|
|
1442
1401
|
hook: hook,
|
|
1443
1402
|
tokenId: tokenId,
|
|
1403
|
+
groupId: groupId,
|
|
1444
1404
|
token: token,
|
|
1445
1405
|
amount: claimAmount,
|
|
1446
1406
|
vestingReleaseRound: vesting.releaseRound,
|
|
@@ -1462,7 +1422,7 @@ abstract contract JBDistributor is IJBDistributor {
|
|
|
1462
1422
|
}
|
|
1463
1423
|
}
|
|
1464
1424
|
|
|
1465
|
-
latestVestedIndexOf[hook][tokenId][token] = newLatestVestedIndex;
|
|
1425
|
+
latestVestedIndexOf[hook][groupId][tokenId][token] = newLatestVestedIndex;
|
|
1466
1426
|
|
|
1467
1427
|
unchecked {
|
|
1468
1428
|
++j;
|
|
@@ -1470,93 +1430,77 @@ abstract contract JBDistributor is IJBDistributor {
|
|
|
1470
1430
|
}
|
|
1471
1431
|
}
|
|
1472
1432
|
|
|
1473
|
-
|
|
1474
|
-
|
|
1475
|
-
|
|
1476
|
-
|
|
1477
|
-
/// @
|
|
1478
|
-
/// @param
|
|
1479
|
-
/// @param
|
|
1480
|
-
/// @param
|
|
1481
|
-
/// @
|
|
1482
|
-
|
|
1433
|
+
//*********************************************************************//
|
|
1434
|
+
// ----------------------- internal views ---------------------------- //
|
|
1435
|
+
//*********************************************************************//
|
|
1436
|
+
|
|
1437
|
+
/// @notice The collectable (unlocked, uncollected) amount for a token ID in a specific reward group.
|
|
1438
|
+
/// @param hook The hook the tokenId belongs to.
|
|
1439
|
+
/// @param groupId The reward group (0 = the default group).
|
|
1440
|
+
/// @param tokenId The ID of the staker token to calculate for.
|
|
1441
|
+
/// @param token The reward token to check.
|
|
1442
|
+
/// @return tokenAmount The amount of tokens that can be collected right now.
|
|
1443
|
+
function _collectableFor(
|
|
1483
1444
|
address hook,
|
|
1484
|
-
uint256
|
|
1485
|
-
|
|
1486
|
-
|
|
1487
|
-
uint256 totalStakeAmount,
|
|
1488
|
-
uint256 vestingReleaseRound
|
|
1445
|
+
uint256 groupId,
|
|
1446
|
+
uint256 tokenId,
|
|
1447
|
+
IERC20 token
|
|
1489
1448
|
)
|
|
1490
1449
|
internal
|
|
1491
|
-
|
|
1492
|
-
returns (uint256
|
|
1450
|
+
view
|
|
1451
|
+
returns (uint256 tokenAmount)
|
|
1493
1452
|
{
|
|
1494
|
-
|
|
1495
|
-
|
|
1453
|
+
// A loan keeps this token ID's vesting rewards in collateral custody until the loan is repaid.
|
|
1454
|
+
if (activeVestingLoanIdOf[hook][groupId][tokenId][token] != 0) return 0;
|
|
1496
1455
|
|
|
1497
|
-
|
|
1498
|
-
|
|
1499
|
-
unchecked {
|
|
1500
|
-
++j;
|
|
1501
|
-
}
|
|
1502
|
-
continue;
|
|
1503
|
-
}
|
|
1456
|
+
// The round that we are in right now.
|
|
1457
|
+
uint256 round = currentRound();
|
|
1504
1458
|
|
|
1505
|
-
|
|
1506
|
-
|
|
1459
|
+
// Keep a reference to the latest vested index.
|
|
1460
|
+
uint256 vestedIndex = latestVestedIndexOf[hook][groupId][tokenId][token];
|
|
1507
1461
|
|
|
1508
|
-
|
|
1509
|
-
|
|
1510
|
-
|
|
1511
|
-
unchecked {
|
|
1512
|
-
++j;
|
|
1513
|
-
}
|
|
1514
|
-
continue;
|
|
1515
|
-
}
|
|
1462
|
+
// Keep a reference to the vesting data array.
|
|
1463
|
+
JBVestingData[] storage vestings = vestingDataOf[hook][groupId][tokenId][token];
|
|
1464
|
+
uint256 numberOfVestingRounds = vestings.length;
|
|
1516
1465
|
|
|
1517
|
-
|
|
1518
|
-
uint256
|
|
1519
|
-
x: distributable, y: _tokenStake({hook: hook, tokenId: tokenId}), denominator: totalStakeAmount
|
|
1520
|
-
});
|
|
1466
|
+
while (vestedIndex < numberOfVestingRounds) {
|
|
1467
|
+
uint256 lockedShare;
|
|
1521
1468
|
|
|
1522
|
-
//
|
|
1523
|
-
|
|
1524
|
-
unchecked {
|
|
1525
|
-
++j;
|
|
1526
|
-
}
|
|
1527
|
-
continue;
|
|
1528
|
-
}
|
|
1469
|
+
// Keep a reference to the vested data being iterated on.
|
|
1470
|
+
JBVestingData memory vesting = vestings[vestedIndex];
|
|
1529
1471
|
|
|
1530
|
-
|
|
1531
|
-
|
|
1472
|
+
lockedShare = JBVestingMath.lockedShareOf({
|
|
1473
|
+
releaseRound: vesting.releaseRound,
|
|
1474
|
+
currentRound: round,
|
|
1475
|
+
vestingRounds: VESTING_ROUNDS,
|
|
1476
|
+
maxShare: MAX_SHARE
|
|
1477
|
+
});
|
|
1532
1478
|
|
|
1533
|
-
|
|
1534
|
-
|
|
1535
|
-
|
|
1536
|
-
|
|
1537
|
-
|
|
1538
|
-
|
|
1539
|
-
|
|
1479
|
+
// Calculate the newly unlocked amount from cumulative shares rather than the incremental share delta.
|
|
1480
|
+
// Incremental floor rounding can otherwise underpay partial collections and leave dust stranded.
|
|
1481
|
+
(uint256 claimAmount,) = JBVestingMath.newlyClaimableAmountOf({
|
|
1482
|
+
amount: vesting.amount,
|
|
1483
|
+
shareClaimed: vesting.shareClaimed,
|
|
1484
|
+
lockedShare: lockedShare,
|
|
1485
|
+
maxShare: MAX_SHARE
|
|
1540
1486
|
});
|
|
1487
|
+
tokenAmount += claimAmount;
|
|
1541
1488
|
|
|
1542
1489
|
unchecked {
|
|
1543
|
-
|
|
1544
|
-
++j;
|
|
1490
|
+
++vestedIndex;
|
|
1545
1491
|
}
|
|
1546
1492
|
}
|
|
1547
1493
|
}
|
|
1548
1494
|
|
|
1549
|
-
//*********************************************************************//
|
|
1550
|
-
// ----------------------- internal views ---------------------------- //
|
|
1551
|
-
//*********************************************************************//
|
|
1552
|
-
|
|
1553
1495
|
/// @notice The remaining uncollected vesting amount for one token ID and reward token.
|
|
1554
1496
|
/// @param hook The hook the token ID belongs to.
|
|
1497
|
+
/// @param groupId The reward group (0 = the default group).
|
|
1555
1498
|
/// @param tokenId The token ID to check.
|
|
1556
1499
|
/// @param token The reward token to check.
|
|
1557
1500
|
/// @return tokenAmount The amount still locked or unlocked-but-uncollected.
|
|
1558
1501
|
function _unclaimedVestingAmountOf(
|
|
1559
1502
|
address hook,
|
|
1503
|
+
uint256 groupId,
|
|
1560
1504
|
uint256 tokenId,
|
|
1561
1505
|
IERC20 token
|
|
1562
1506
|
)
|
|
@@ -1565,10 +1509,10 @@ abstract contract JBDistributor is IJBDistributor {
|
|
|
1565
1509
|
returns (uint256 tokenAmount)
|
|
1566
1510
|
{
|
|
1567
1511
|
// Keep a reference to the latest fully vested index.
|
|
1568
|
-
uint256 vestedIndex = latestVestedIndexOf[hook][tokenId][token];
|
|
1512
|
+
uint256 vestedIndex = latestVestedIndexOf[hook][groupId][tokenId][token];
|
|
1569
1513
|
|
|
1570
1514
|
// Keep a reference to the vesting data array.
|
|
1571
|
-
JBVestingData[] storage vestings = vestingDataOf[hook][tokenId][token];
|
|
1515
|
+
JBVestingData[] storage vestings = vestingDataOf[hook][groupId][tokenId][token];
|
|
1572
1516
|
uint256 numberOfVestingRounds = vestings.length;
|
|
1573
1517
|
|
|
1574
1518
|
while (vestedIndex < numberOfVestingRounds) {
|
|
@@ -1604,10 +1548,11 @@ abstract contract JBDistributor is IJBDistributor {
|
|
|
1604
1548
|
|
|
1605
1549
|
/// @notice Revert if a token ID's vesting rewards are locked in a distributor-owned loan.
|
|
1606
1550
|
/// @param hook The hook the token ID belongs to.
|
|
1551
|
+
/// @param groupId The reward group (0 = the default group).
|
|
1607
1552
|
/// @param tokenId The token ID to check.
|
|
1608
1553
|
/// @param token The reward token to check.
|
|
1609
|
-
function _requireNoActiveVestingLoan(address hook, uint256 tokenId, IERC20 token) internal view {
|
|
1610
|
-
uint256 loanId = activeVestingLoanIdOf[hook][tokenId][token];
|
|
1554
|
+
function _requireNoActiveVestingLoan(address hook, uint256 groupId, uint256 tokenId, IERC20 token) internal view {
|
|
1555
|
+
uint256 loanId = activeVestingLoanIdOf[hook][groupId][tokenId][token];
|
|
1611
1556
|
if (loanId != 0) {
|
|
1612
1557
|
revert JBDistributor_VestingLoanOutstanding({
|
|
1613
1558
|
hook: hook, tokenId: tokenId, token: address(token), loanId: loanId
|
|
@@ -1623,17 +1568,26 @@ abstract contract JBDistributor is IJBDistributor {
|
|
|
1623
1568
|
function _tokenBurned(address hook, uint256 tokenId) internal view virtual returns (bool tokenWasBurned);
|
|
1624
1569
|
|
|
1625
1570
|
/// @notice The stake weight of a specific token ID, used to calculate its pro-rata share of distributions.
|
|
1626
|
-
///
|
|
1571
|
+
/// @dev Subclasses define how stake is measured.
|
|
1627
1572
|
/// @param hook The hook the token belongs to.
|
|
1628
1573
|
/// @param tokenId The ID of the token to get the stake weight of.
|
|
1629
1574
|
/// @return tokenStakeAmount The stake weight represented by this token ID.
|
|
1630
1575
|
function _tokenStake(address hook, uint256 tokenId) internal view virtual returns (uint256 tokenStakeAmount);
|
|
1631
1576
|
|
|
1632
|
-
/// @notice The total stake
|
|
1633
|
-
/// token ID's pro-rata share.
|
|
1634
|
-
///
|
|
1577
|
+
/// @notice The total stake sharing a group's round rewards at a given block. Used as the denominator when
|
|
1578
|
+
/// calculating each token ID's pro-rata share.
|
|
1579
|
+
/// @dev Subclasses define how the per-group total stake is measured.
|
|
1635
1580
|
/// @param hook The hook to get the total stake for.
|
|
1581
|
+
/// @param groupId The reward group (0 = the default group).
|
|
1636
1582
|
/// @param blockNumber The block number to query (must be strictly in the past).
|
|
1637
1583
|
/// @return totalStakedAmount The total stake at the given block.
|
|
1638
|
-
function _totalStake(
|
|
1584
|
+
function _totalStake(
|
|
1585
|
+
address hook,
|
|
1586
|
+
uint256 groupId,
|
|
1587
|
+
uint256 blockNumber
|
|
1588
|
+
)
|
|
1589
|
+
internal
|
|
1590
|
+
view
|
|
1591
|
+
virtual
|
|
1592
|
+
returns (uint256 totalStakedAmount);
|
|
1639
1593
|
}
|