@bananapus/distributor-v6 0.0.32 → 0.0.34
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 +11 -6
- package/package.json +1 -1
- package/src/JB721Distributor.sol +4 -248
- package/src/JBDistributor.sol +23 -246
- package/src/JBTokenDistributor.sol +4 -52
- package/src/interfaces/IJBDistributor.sol +0 -30
- package/src/structs/JBTokenSnapshotData.sol +0 -11
package/README.md
CHANGED
|
@@ -2,12 +2,17 @@
|
|
|
2
2
|
|
|
3
3
|
`@bananapus/distributor-v6` distributes ERC-20 balances or 721 token inventories to many recipients under round-based vesting rules. It is a payout utility package for Juicebox-adjacent flows, not a protocol accounting layer.
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
5
|
+
## Documentation
|
|
6
|
+
|
|
7
|
+
- [ARCHITECTURE.md](./ARCHITECTURE.md) — system overview, modules, trust boundaries, and core invariants.
|
|
8
|
+
- [USER_JOURNEYS.md](./USER_JOURNEYS.md) — end-to-end flows for funders and claimants across token and 721 variants.
|
|
9
|
+
- [INVARIANTS.md](./INVARIANTS.md) — per-section invariants for snapshot fairness, vesting math, claim authority, loans, and recycling.
|
|
10
|
+
- [RISKS.md](./RISKS.md) — risk register with priority risks and the minimum invariants to verify.
|
|
11
|
+
- [ADMINISTRATION.md](./ADMINISTRATION.md) — deployment parameters, control posture, and recovery guidance.
|
|
12
|
+
- [SKILLS.md](./SKILLS.md) — quick index for routing tasks into the right sub-document.
|
|
13
|
+
- [STYLE_GUIDE.md](./STYLE_GUIDE.md) — Solidity and repo conventions used across the Juicebox V6 ecosystem.
|
|
14
|
+
- [AUDIT_INSTRUCTIONS.md](./AUDIT_INSTRUCTIONS.md) — audit framing, targets, and suggested hunting grounds.
|
|
15
|
+
- [CHANGELOG.md](./CHANGELOG.md) — release notes and dependency bumps.
|
|
11
16
|
|
|
12
17
|
## Overview
|
|
13
18
|
|
package/package.json
CHANGED
package/src/JB721Distributor.sol
CHANGED
|
@@ -162,57 +162,10 @@ contract JB721Distributor is JBDistributor, IJB721Distributor {
|
|
|
162
162
|
}
|
|
163
163
|
}
|
|
164
164
|
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
/// @param tokens The reward tokens to begin vesting.
|
|
170
|
-
function beginVesting(
|
|
171
|
-
address hook,
|
|
172
|
-
uint256[] calldata tokenIds,
|
|
173
|
-
IERC20[] calldata tokens
|
|
174
|
-
)
|
|
175
|
-
external
|
|
176
|
-
override(JBDistributor, IJBDistributor)
|
|
177
|
-
{
|
|
178
|
-
// Do not let reward-token callbacks mutate claim accounting during an inbound transfer.
|
|
179
|
-
_requireNotAcceptingToken();
|
|
180
|
-
if (tokenIds.length == 0) revert JBDistributor_EmptyTokenIds({tokenIdCount: tokenIds.length});
|
|
181
|
-
|
|
182
|
-
// Only the current NFT owner can start vesting for each token ID.
|
|
183
|
-
_requireCanClaimTokenIds({hook: hook, tokenIds: tokenIds});
|
|
184
|
-
|
|
185
|
-
// Materialize all unclaimed historical rewards into fresh vesting entries that start now.
|
|
186
|
-
_claimPastRewards({hook: hook, tokenIds: tokenIds, tokens: tokens});
|
|
187
|
-
}
|
|
188
|
-
|
|
189
|
-
/// @notice Collect already-vested rewards and first start vesting any unclaimed past reward rounds.
|
|
190
|
-
/// @param hook The 721 hook whose NFTs are collecting.
|
|
191
|
-
/// @param tokenIds The NFT token IDs to collect for.
|
|
192
|
-
/// @param tokens The reward tokens to collect.
|
|
193
|
-
/// @param beneficiary The recipient of collected vested rewards.
|
|
194
|
-
function collectVestedRewards(
|
|
195
|
-
address hook,
|
|
196
|
-
uint256[] calldata tokenIds,
|
|
197
|
-
IERC20[] calldata tokens,
|
|
198
|
-
address beneficiary
|
|
199
|
-
)
|
|
200
|
-
public
|
|
201
|
-
override(JBDistributor, IJBDistributor)
|
|
202
|
-
{
|
|
203
|
-
// Do not let reward-token callbacks mutate claim accounting during an inbound transfer.
|
|
204
|
-
_requireNotAcceptingToken();
|
|
205
|
-
if (tokenIds.length == 0) revert JBDistributor_EmptyTokenIds({tokenIdCount: tokenIds.length});
|
|
206
|
-
|
|
207
|
-
// Only the current NFT owner can materialize and collect rewards for each token ID.
|
|
208
|
-
_requireCanClaimTokenIds({hook: hook, tokenIds: tokenIds});
|
|
209
|
-
|
|
210
|
-
// Before collecting, bring the token IDs current by starting vesting for any past reward rounds.
|
|
211
|
-
_claimPastRewards({hook: hook, tokenIds: tokenIds, tokens: tokens});
|
|
212
|
-
|
|
213
|
-
// Release whatever portion of existing vesting entries has unlocked by this round.
|
|
214
|
-
_unlockRewards({hook: hook, tokenIds: tokenIds, tokens: tokens, beneficiary: beneficiary, ownerClaim: true});
|
|
215
|
-
}
|
|
165
|
+
// `beginVesting` and `collectVestedRewards` are provided by `JBDistributor`. Both distributors share the exact
|
|
166
|
+
// same flow (authorize -> materialize past rounds via `_claimPastRewards` -> optionally release unlocked), so the
|
|
167
|
+
// round-claim logic lives once in the base and dispatches to this contract's `_claimPastRewards` /
|
|
168
|
+
// `_requireCanClaimTokenIds` overrides below.
|
|
216
169
|
|
|
217
170
|
//*********************************************************************//
|
|
218
171
|
// -------------------------- public views --------------------------- //
|
|
@@ -473,75 +426,6 @@ contract JB721Distributor is JBDistributor, IJB721Distributor {
|
|
|
473
426
|
consumed[ownerIndex] += stake;
|
|
474
427
|
}
|
|
475
428
|
|
|
476
|
-
/// @notice Override vesting to cap each owner's consumed voting power across all their NFTs.
|
|
477
|
-
/// @dev Prevents an owner with N NFTs of V voting units each from claiming N*V when their pastVotes < N*V.
|
|
478
|
-
/// Iterates over all token IDs in the batch, delegating per-token logic to `_vestSingleToken`. A pair of
|
|
479
|
-
/// scratch arrays (`owners` and `consumed`) tracks how much voting power each distinct owner has used so far,
|
|
480
|
-
/// ensuring the aggregate claim never exceeds the owner's snapshot voting power.
|
|
481
|
-
/// Silently skips burned tokens, already-vested tokens, and tokens whose owner had no snapshot voting power.
|
|
482
|
-
/// @param hook The address of the 721 hook whose stakers are vesting.
|
|
483
|
-
/// @param tokenIds The NFT token IDs to vest rewards for.
|
|
484
|
-
/// @param token The ERC-20 reward token to distribute.
|
|
485
|
-
/// @param distributable The total distributable amount of `token` for this round.
|
|
486
|
-
/// @param totalStakeAmount The aggregate voting power at the round's snapshot block.
|
|
487
|
-
/// @param vestingReleaseRound The round number at which the vesting period ends and tokens become fully claimable.
|
|
488
|
-
/// @return totalVestingAmount The sum of reward tokens that began vesting across all processed token IDs.
|
|
489
|
-
function _vestTokenIds(
|
|
490
|
-
address hook,
|
|
491
|
-
uint256[] calldata tokenIds,
|
|
492
|
-
IERC20 token,
|
|
493
|
-
uint256 distributable,
|
|
494
|
-
uint256 totalStakeAmount,
|
|
495
|
-
uint256 vestingReleaseRound
|
|
496
|
-
)
|
|
497
|
-
internal
|
|
498
|
-
override
|
|
499
|
-
returns (uint256 totalVestingAmount)
|
|
500
|
-
{
|
|
501
|
-
// Bundle iteration-constant parameters into a struct to avoid stack-too-deep errors.
|
|
502
|
-
JBVestContext memory ctx = JBVestContext({
|
|
503
|
-
hook: hook,
|
|
504
|
-
token: token,
|
|
505
|
-
distributable: distributable,
|
|
506
|
-
totalStakeAmount: totalStakeAmount,
|
|
507
|
-
vestingReleaseRound: vestingReleaseRound,
|
|
508
|
-
rewardRound: currentRound(),
|
|
509
|
-
snapshotBlock: roundSnapshotBlock[currentRound()]
|
|
510
|
-
});
|
|
511
|
-
|
|
512
|
-
// Allocate scratch arrays sized to the maximum possible number of distinct owners (one per token ID).
|
|
513
|
-
address[] memory owners = new address[](tokenIds.length);
|
|
514
|
-
uint256[] memory consumed = new uint256[](tokenIds.length);
|
|
515
|
-
|
|
516
|
-
// Track how many distinct owners have been recorded in the scratch arrays so far.
|
|
517
|
-
uint256 uniqueCount;
|
|
518
|
-
|
|
519
|
-
// Iterate over every token ID in the batch.
|
|
520
|
-
for (uint256 j; j < tokenIds.length;) {
|
|
521
|
-
// Vest the single token, receiving its reward amount and the updated distinct owner count.
|
|
522
|
-
(uint256 tokenAmount, uint256 newUniqueCount) = _vestSingleToken({
|
|
523
|
-
ctx: ctx, tokenId: tokenIds[j], owners: owners, consumed: consumed, uniqueCount: uniqueCount
|
|
524
|
-
});
|
|
525
|
-
|
|
526
|
-
// Carry the updated owner count forward so subsequent tokens can reference the same tracking data.
|
|
527
|
-
uniqueCount = newUniqueCount;
|
|
528
|
-
|
|
529
|
-
unchecked {
|
|
530
|
-
// Accumulate the individual token's reward into the batch-wide total.
|
|
531
|
-
totalVestingAmount += tokenAmount;
|
|
532
|
-
++j;
|
|
533
|
-
}
|
|
534
|
-
}
|
|
535
|
-
|
|
536
|
-
// Persist consumed voting power to storage to prevent cap resets across calls.
|
|
537
|
-
for (uint256 k; k < uniqueCount;) {
|
|
538
|
-
_consumedVotesOf[hook][token][ctx.rewardRound][owners[k]] = consumed[k];
|
|
539
|
-
unchecked {
|
|
540
|
-
++k;
|
|
541
|
-
}
|
|
542
|
-
}
|
|
543
|
-
}
|
|
544
|
-
|
|
545
429
|
//*********************************************************************//
|
|
546
430
|
// ----------------------- internal views ---------------------------- //
|
|
547
431
|
//*********************************************************************//
|
|
@@ -664,132 +548,4 @@ contract JB721Distributor is JBDistributor, IJB721Distributor {
|
|
|
664
548
|
// A zero owner means the token was not owned at the snapshot block and is not eligible this round.
|
|
665
549
|
owner = abi.decode(data, (address));
|
|
666
550
|
}
|
|
667
|
-
|
|
668
|
-
/// @notice Vest a single NFT token, enforcing a per-owner voting power cap across the batch.
|
|
669
|
-
/// @dev Returns 0 for burned tokens, already-vested tokens, tokens whose owner had no snapshot voting power,
|
|
670
|
-
/// and tokens whose owner has already exhausted their voting power cap within this batch.
|
|
671
|
-
/// The `owners` and `consumed` arrays form a compact map that tracks how much voting power each unique
|
|
672
|
-
/// owner has consumed so far. `uniqueCount` tracks how many slots are used.
|
|
673
|
-
/// @param ctx The vesting context containing hook address, reward token, distributable amount, total stake,
|
|
674
|
-
/// and release round.
|
|
675
|
-
/// @param tokenId The NFT token ID to process.
|
|
676
|
-
/// @param owners A scratch array mapping slot indices to owner addresses for deduplication within this batch.
|
|
677
|
-
/// @param consumed A scratch array tracking how much voting power each owner (by slot index) has consumed.
|
|
678
|
-
/// @param uniqueCount The number of distinct owners seen so far in the batch.
|
|
679
|
-
/// @return tokenAmount The reward amount vested for this token ID (0 if skipped).
|
|
680
|
-
/// @return newUniqueCount The updated count of distinct owners after processing this token ID.
|
|
681
|
-
function _vestSingleToken(
|
|
682
|
-
JBVestContext memory ctx,
|
|
683
|
-
uint256 tokenId,
|
|
684
|
-
address[] memory owners,
|
|
685
|
-
uint256[] memory consumed,
|
|
686
|
-
uint256 uniqueCount
|
|
687
|
-
)
|
|
688
|
-
private
|
|
689
|
-
returns (uint256 tokenAmount, uint256 newUniqueCount)
|
|
690
|
-
{
|
|
691
|
-
// Initialize the return value to the current count of distinct owners.
|
|
692
|
-
newUniqueCount = uniqueCount;
|
|
693
|
-
|
|
694
|
-
// Skip burned tokens — they are excluded from _totalStake, so including them would overbook vesting.
|
|
695
|
-
if (_tokenBurned({hook: ctx.hook, tokenId: tokenId})) return (0, newUniqueCount);
|
|
696
|
-
|
|
697
|
-
// Skip already-vested tokenIds — check if the last vesting entry targets the same release round.
|
|
698
|
-
{
|
|
699
|
-
// Load the number of existing vesting entries for this token.
|
|
700
|
-
JBVestingData[] storage vestings = vestingDataOf[ctx.hook][tokenId][ctx.token];
|
|
701
|
-
uint256 numVesting = vestings.length;
|
|
702
|
-
|
|
703
|
-
// If at least one entry exists and its release round matches, this token was already vested this round.
|
|
704
|
-
if (numVesting != 0 && vestings[numVesting - 1].releaseRound == ctx.vestingReleaseRound) {
|
|
705
|
-
return (0, newUniqueCount);
|
|
706
|
-
}
|
|
707
|
-
}
|
|
708
|
-
|
|
709
|
-
// Look up the NFT's voting units from its tier in the hook's store.
|
|
710
|
-
uint256 votingUnits =
|
|
711
|
-
IJB721TiersHook(ctx.hook)
|
|
712
|
-
.STORE()
|
|
713
|
-
.tierOfTokenId({hook: ctx.hook, tokenId: tokenId, includeResolvedUri: false}).votingUnits;
|
|
714
|
-
|
|
715
|
-
// Look up the snapshot owner, verify snapshot eligibility, and find or create the owner's tracking slot.
|
|
716
|
-
uint256 ownerIndex;
|
|
717
|
-
uint256 pastVotes;
|
|
718
|
-
{
|
|
719
|
-
// Reuse the same round snapshot block for every token in this vesting batch.
|
|
720
|
-
uint256 snapshotBlock = ctx.snapshotBlock;
|
|
721
|
-
address owner = _snapshotOwnerOf({hook: ctx.hook, tokenId: tokenId, snapshotBlock: snapshotBlock});
|
|
722
|
-
if (owner == address(0)) return (0, newUniqueCount);
|
|
723
|
-
|
|
724
|
-
// Query the owner's checkpointed voting power at the round's snapshot block.
|
|
725
|
-
pastVotes = IVotes(address(IJB721TiersHook(ctx.hook).checkpoints()))
|
|
726
|
-
.getPastVotes({account: owner, timepoint: snapshotBlock});
|
|
727
|
-
|
|
728
|
-
// If the snapshot owner had no voting power at the snapshot block, the token is ineligible for this round.
|
|
729
|
-
if (pastVotes == 0) return (0, newUniqueCount);
|
|
730
|
-
|
|
731
|
-
// Search the owners array for an existing slot belonging to this owner.
|
|
732
|
-
bool found;
|
|
733
|
-
for (uint256 k; k < newUniqueCount;) {
|
|
734
|
-
if (owners[k] == owner) {
|
|
735
|
-
// Re-use the existing tracking slot for this owner.
|
|
736
|
-
ownerIndex = k;
|
|
737
|
-
found = true;
|
|
738
|
-
break;
|
|
739
|
-
}
|
|
740
|
-
unchecked {
|
|
741
|
-
++k;
|
|
742
|
-
}
|
|
743
|
-
}
|
|
744
|
-
|
|
745
|
-
// If no existing slot was found, allocate a new one at the end of the arrays.
|
|
746
|
-
if (!found) {
|
|
747
|
-
ownerIndex = newUniqueCount;
|
|
748
|
-
owners[newUniqueCount] = owner;
|
|
749
|
-
// Initialize from persistent storage to prevent cap resets across calls.
|
|
750
|
-
consumed[newUniqueCount] = _consumedVotesOf[ctx.hook][ctx.token][ctx.rewardRound][owner];
|
|
751
|
-
unchecked {
|
|
752
|
-
++newUniqueCount;
|
|
753
|
-
}
|
|
754
|
-
}
|
|
755
|
-
}
|
|
756
|
-
|
|
757
|
-
// Cap this NFT's effective stake at the owner's remaining voting power budget for this batch.
|
|
758
|
-
uint256 stake;
|
|
759
|
-
{
|
|
760
|
-
// Calculate how much voting power the owner has left after prior tokens in this batch.
|
|
761
|
-
uint256 remaining = pastVotes > consumed[ownerIndex] ? pastVotes - consumed[ownerIndex] : 0;
|
|
762
|
-
|
|
763
|
-
// The effective stake is the lesser of the NFT's voting units and the owner's remaining budget.
|
|
764
|
-
stake = votingUnits < remaining ? votingUnits : remaining;
|
|
765
|
-
}
|
|
766
|
-
|
|
767
|
-
// If the effective stake is zero, the owner's budget is exhausted — skip this token.
|
|
768
|
-
if (stake == 0) return (0, newUniqueCount);
|
|
769
|
-
|
|
770
|
-
// Calculate the pro-rata reward amount: (distributable * stake) / totalStakeAmount.
|
|
771
|
-
tokenAmount = mulDiv({x: ctx.distributable, y: stake, denominator: ctx.totalStakeAmount});
|
|
772
|
-
|
|
773
|
-
// If the pro-rata amount rounds to zero, do not consume the owner's voting budget.
|
|
774
|
-
if (tokenAmount == 0) return (0, newUniqueCount);
|
|
775
|
-
|
|
776
|
-
// Record that this owner has consumed additional voting power from their budget.
|
|
777
|
-
consumed[ownerIndex] += stake;
|
|
778
|
-
|
|
779
|
-
// Only create a vesting entry and emit an event if there is a non-zero reward.
|
|
780
|
-
// Push a new vesting data entry for this token ID, starting with zero shareClaimed.
|
|
781
|
-
vestingDataOf[ctx.hook][tokenId][ctx.token].push(
|
|
782
|
-
JBVestingData({releaseRound: ctx.vestingReleaseRound, amount: tokenAmount, shareClaimed: 0})
|
|
783
|
-
);
|
|
784
|
-
|
|
785
|
-
// Emit the claim event for off-chain indexers.
|
|
786
|
-
emit Claimed({
|
|
787
|
-
hook: ctx.hook,
|
|
788
|
-
tokenId: tokenId,
|
|
789
|
-
token: ctx.token,
|
|
790
|
-
amount: tokenAmount,
|
|
791
|
-
vestingReleaseRound: ctx.vestingReleaseRound,
|
|
792
|
-
caller: msg.sender
|
|
793
|
-
});
|
|
794
|
-
}
|
|
795
551
|
}
|
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
|
|
|
@@ -202,21 +198,6 @@ abstract contract JBDistributor is IJBDistributor {
|
|
|
202
198
|
/// @custom:param loanId The Revnet loan NFT ID.
|
|
203
199
|
mapping(uint256 loanId => JBVestingLoan) internal _vestingLoanOf;
|
|
204
200
|
|
|
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
201
|
//*********************************************************************//
|
|
221
202
|
// ------------------- transient stored properties ------------------- //
|
|
222
203
|
//*********************************************************************//
|
|
@@ -271,9 +252,11 @@ abstract contract JBDistributor is IJBDistributor {
|
|
|
271
252
|
// ---------------------- external transactions ---------------------- //
|
|
272
253
|
//*********************************************************************//
|
|
273
254
|
|
|
274
|
-
/// @notice
|
|
275
|
-
///
|
|
276
|
-
///
|
|
255
|
+
/// @notice Begin vesting all unclaimed past reward rounds for the specified token IDs.
|
|
256
|
+
/// @dev Materializes each token ID's pro-rata share of every past (non-current) reward round into fresh vesting
|
|
257
|
+
/// entries that start now and unlock over `VESTING_ROUNDS`. Current-round funding is excluded until a later round
|
|
258
|
+
/// starts. The model-specific per-round claim math and the authorization check live in the `_claimPastRewards`
|
|
259
|
+
/// and `_requireCanClaimTokenIds` hooks each concrete distributor implements.
|
|
277
260
|
/// @param hook The hook (IVotes token or 721 hook) whose stakers are vesting.
|
|
278
261
|
/// @param tokenIds The staker token IDs to claim rewards for.
|
|
279
262
|
/// @param tokens The reward tokens to begin vesting.
|
|
@@ -287,55 +270,18 @@ abstract contract JBDistributor is IJBDistributor {
|
|
|
287
270
|
override
|
|
288
271
|
{
|
|
289
272
|
// Reward accounting cannot change while an ERC-20 `transferFrom` is in progress. A callback-capable reward
|
|
290
|
-
// token could otherwise
|
|
291
|
-
// `balanceAfter`, distorting the delta credited to the funder.
|
|
273
|
+
// token could otherwise vest or collect against balances mid-transfer, distorting the credited delta.
|
|
292
274
|
_requireNotAcceptingToken();
|
|
293
275
|
|
|
294
276
|
// Revert if no token IDs are provided.
|
|
295
277
|
if (tokenIds.length == 0) revert JBDistributor_EmptyTokenIds({tokenIdCount: tokenIds.length});
|
|
296
278
|
|
|
297
|
-
//
|
|
298
|
-
|
|
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;
|
|
279
|
+
// Only the entity authorized for these token IDs (current NFT owner / encoded staker) may start their
|
|
280
|
+
// vesting clock — third parties must not start it for them.
|
|
281
|
+
_requireCanClaimTokenIds({hook: hook, tokenIds: tokenIds});
|
|
335
282
|
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
}
|
|
283
|
+
// Materialize all unclaimed historical reward rounds into fresh vesting entries that start now.
|
|
284
|
+
_claimPastRewards({hook: hook, 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
|
|
@@ -506,23 +452,6 @@ abstract contract JBDistributor is IJBDistributor {
|
|
|
506
452
|
}
|
|
507
453
|
}
|
|
508
454
|
|
|
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];
|
|
524
|
-
}
|
|
525
|
-
|
|
526
455
|
/// @notice The vesting position collateralized by a Revnet loan.
|
|
527
456
|
/// @param loanId The Revnet loan NFT ID.
|
|
528
457
|
function vestingLoanOf(uint256 loanId) external view override returns (JBVestingLoan memory) {
|
|
@@ -548,11 +477,12 @@ abstract contract JBDistributor is IJBDistributor {
|
|
|
548
477
|
// ----------------------- public transactions ----------------------- //
|
|
549
478
|
//*********************************************************************//
|
|
550
479
|
|
|
551
|
-
/// @notice
|
|
552
|
-
///
|
|
553
|
-
///
|
|
480
|
+
/// @notice Begin vesting any unclaimed past reward rounds, then collect everything that has since unlocked and
|
|
481
|
+
/// transfer it to the beneficiary — so callers don't need to separately call `beginVesting`.
|
|
482
|
+
/// @dev The model-specific per-round claim math and the authorization check live in the `_claimPastRewards`
|
|
483
|
+
/// and `_requireCanClaimTokenIds` hooks each concrete distributor implements.
|
|
554
484
|
/// @param hook The hook whose stakers are collecting.
|
|
555
|
-
/// @param tokenIds The IDs of the tokens to collect for (caller must
|
|
485
|
+
/// @param tokenIds The IDs of the tokens to collect for (caller must be authorized for all of them).
|
|
556
486
|
/// @param tokens The reward tokens to collect vested amounts of.
|
|
557
487
|
/// @param beneficiary The recipient of the collected tokens.
|
|
558
488
|
function collectVestedRewards(
|
|
@@ -565,62 +495,20 @@ abstract contract JBDistributor is IJBDistributor {
|
|
|
565
495
|
virtual
|
|
566
496
|
override
|
|
567
497
|
{
|
|
568
|
-
// Collections transfer reward tokens out
|
|
569
|
-
//
|
|
498
|
+
// Collections transfer reward tokens out; block them mid inbound transfer so the outgoing transfer cannot
|
|
499
|
+
// net against the incoming balance delta and strand the new funds unaccounted.
|
|
570
500
|
_requireNotAcceptingToken();
|
|
571
501
|
|
|
572
502
|
// Revert if no token IDs are provided.
|
|
573
503
|
if (tokenIds.length == 0) revert JBDistributor_EmptyTokenIds({tokenIdCount: tokenIds.length});
|
|
574
504
|
|
|
575
|
-
//
|
|
576
|
-
|
|
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
|
-
});
|
|
505
|
+
// Only the entity authorized for these token IDs may materialize and collect their rewards.
|
|
506
|
+
_requireCanClaimTokenIds({hook: hook, tokenIds: tokenIds});
|
|
612
507
|
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
}
|
|
616
|
-
}
|
|
508
|
+
// Before collecting, bring the token IDs current by starting vesting for any past reward rounds.
|
|
509
|
+
_claimPastRewards({hook: hook, tokenIds: tokenIds, tokens: tokens});
|
|
617
510
|
|
|
618
|
-
|
|
619
|
-
++i;
|
|
620
|
-
}
|
|
621
|
-
}
|
|
622
|
-
|
|
623
|
-
// Unlock the rewards and send them to the beneficiary.
|
|
511
|
+
// Release whatever portion of existing vesting entries has unlocked by this round.
|
|
624
512
|
_unlockRewards({hook: hook, tokenIds: tokenIds, tokens: tokens, beneficiary: beneficiary, ownerClaim: true});
|
|
625
513
|
}
|
|
626
514
|
|
|
@@ -1261,41 +1149,6 @@ abstract contract JBDistributor is IJBDistributor {
|
|
|
1261
1149
|
}
|
|
1262
1150
|
}
|
|
1263
1151
|
|
|
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
1152
|
/// @notice The deadline for a reward round using this distributor's immutable claim duration.
|
|
1300
1153
|
/// @param round The reward round.
|
|
1301
1154
|
/// @return claimDeadline The deadline timestamp. Zero means no expiration.
|
|
@@ -1470,82 +1323,6 @@ abstract contract JBDistributor is IJBDistributor {
|
|
|
1470
1323
|
}
|
|
1471
1324
|
}
|
|
1472
1325
|
|
|
1473
|
-
/// @notice Vests each token ID for a given reward token and returns the total amount vested.
|
|
1474
|
-
/// @dev Silently skips already-vested tokenIds instead of reverting, to support auto-vest.
|
|
1475
|
-
/// @param hook The hook whose stakers are vesting.
|
|
1476
|
-
/// @param tokenIds The IDs to claim rewards for.
|
|
1477
|
-
/// @param token The reward token.
|
|
1478
|
-
/// @param distributable The distributable amount for this round.
|
|
1479
|
-
/// @param totalStakeAmount The total stake amount.
|
|
1480
|
-
/// @param vestingReleaseRound The round at which vesting will be released.
|
|
1481
|
-
/// @return totalVestingAmount The total amount that began vesting.
|
|
1482
|
-
function _vestTokenIds(
|
|
1483
|
-
address hook,
|
|
1484
|
-
uint256[] calldata tokenIds,
|
|
1485
|
-
IERC20 token,
|
|
1486
|
-
uint256 distributable,
|
|
1487
|
-
uint256 totalStakeAmount,
|
|
1488
|
-
uint256 vestingReleaseRound
|
|
1489
|
-
)
|
|
1490
|
-
internal
|
|
1491
|
-
virtual
|
|
1492
|
-
returns (uint256 totalVestingAmount)
|
|
1493
|
-
{
|
|
1494
|
-
for (uint256 j; j < tokenIds.length;) {
|
|
1495
|
-
uint256 tokenId = tokenIds[j];
|
|
1496
|
-
|
|
1497
|
-
// Skip burned tokens — they are excluded from _totalStake, so including them would overbook vesting.
|
|
1498
|
-
if (_tokenBurned({hook: hook, tokenId: tokenId})) {
|
|
1499
|
-
unchecked {
|
|
1500
|
-
++j;
|
|
1501
|
-
}
|
|
1502
|
-
continue;
|
|
1503
|
-
}
|
|
1504
|
-
|
|
1505
|
-
// Keep a reference to the vesting data for this hook/tokenId/token.
|
|
1506
|
-
JBVestingData[] storage vestings = vestingDataOf[hook][tokenId][token];
|
|
1507
|
-
|
|
1508
|
-
// Skip if this token has already been vested for this round (same releaseRound).
|
|
1509
|
-
uint256 numVesting = vestings.length;
|
|
1510
|
-
if (numVesting != 0 && vestings[numVesting - 1].releaseRound == vestingReleaseRound) {
|
|
1511
|
-
unchecked {
|
|
1512
|
-
++j;
|
|
1513
|
-
}
|
|
1514
|
-
continue;
|
|
1515
|
-
}
|
|
1516
|
-
|
|
1517
|
-
// Keep a reference to the amount of tokens being claimed.
|
|
1518
|
-
uint256 tokenAmount = mulDiv({
|
|
1519
|
-
x: distributable, y: _tokenStake({hook: hook, tokenId: tokenId}), denominator: totalStakeAmount
|
|
1520
|
-
});
|
|
1521
|
-
|
|
1522
|
-
// Skip zero-amount entries to prevent stalling latestVestedIndexOf advancement.
|
|
1523
|
-
if (tokenAmount == 0) {
|
|
1524
|
-
unchecked {
|
|
1525
|
-
++j;
|
|
1526
|
-
}
|
|
1527
|
-
continue;
|
|
1528
|
-
}
|
|
1529
|
-
|
|
1530
|
-
// Add to the list of vesting data.
|
|
1531
|
-
vestings.push(JBVestingData({releaseRound: vestingReleaseRound, amount: tokenAmount, shareClaimed: 0}));
|
|
1532
|
-
|
|
1533
|
-
emit Claimed({
|
|
1534
|
-
hook: hook,
|
|
1535
|
-
tokenId: tokenId,
|
|
1536
|
-
token: token,
|
|
1537
|
-
amount: tokenAmount,
|
|
1538
|
-
vestingReleaseRound: vestingReleaseRound,
|
|
1539
|
-
caller: msg.sender
|
|
1540
|
-
});
|
|
1541
|
-
|
|
1542
|
-
unchecked {
|
|
1543
|
-
totalVestingAmount += tokenAmount;
|
|
1544
|
-
++j;
|
|
1545
|
-
}
|
|
1546
|
-
}
|
|
1547
|
-
}
|
|
1548
|
-
|
|
1549
1326
|
//*********************************************************************//
|
|
1550
1327
|
// ----------------------- internal views ---------------------------- //
|
|
1551
1328
|
//*********************************************************************//
|
|
@@ -144,58 +144,10 @@ contract JBTokenDistributor is JBDistributor, IJBTokenDistributor {
|
|
|
144
144
|
}
|
|
145
145
|
}
|
|
146
146
|
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
/// @param tokenIds The encoded staker addresses to claim for.
|
|
152
|
-
/// @param tokens The reward tokens to begin vesting.
|
|
153
|
-
function beginVesting(
|
|
154
|
-
address hook,
|
|
155
|
-
uint256[] calldata tokenIds,
|
|
156
|
-
IERC20[] calldata tokens
|
|
157
|
-
)
|
|
158
|
-
external
|
|
159
|
-
override(JBDistributor, IJBDistributor)
|
|
160
|
-
{
|
|
161
|
-
// Do not let reward-token callbacks mutate claim accounting during an inbound transfer.
|
|
162
|
-
_requireNotAcceptingToken();
|
|
163
|
-
if (tokenIds.length == 0) revert JBDistributor_EmptyTokenIds({tokenIdCount: tokenIds.length});
|
|
164
|
-
|
|
165
|
-
// Token IDs encode staker addresses, so only the encoded staker can start their own vesting clock.
|
|
166
|
-
_requireCanClaimTokenIds({hook: hook, tokenIds: tokenIds});
|
|
167
|
-
|
|
168
|
-
// Materialize all unclaimed historical rewards into fresh vesting entries that start now.
|
|
169
|
-
_claimPastRewards({hook: hook, tokenIds: tokenIds, tokens: tokens});
|
|
170
|
-
}
|
|
171
|
-
|
|
172
|
-
/// @notice Collect already-vested rewards and first start vesting any unclaimed past reward rounds.
|
|
173
|
-
/// @param hook The IVotes token whose stakers are collecting.
|
|
174
|
-
/// @param tokenIds The encoded staker addresses to collect for.
|
|
175
|
-
/// @param tokens The reward tokens to collect.
|
|
176
|
-
/// @param beneficiary The recipient of collected vested rewards.
|
|
177
|
-
function collectVestedRewards(
|
|
178
|
-
address hook,
|
|
179
|
-
uint256[] calldata tokenIds,
|
|
180
|
-
IERC20[] calldata tokens,
|
|
181
|
-
address beneficiary
|
|
182
|
-
)
|
|
183
|
-
public
|
|
184
|
-
override(JBDistributor, IJBDistributor)
|
|
185
|
-
{
|
|
186
|
-
// Do not let reward-token callbacks mutate claim accounting during an inbound transfer.
|
|
187
|
-
_requireNotAcceptingToken();
|
|
188
|
-
if (tokenIds.length == 0) revert JBDistributor_EmptyTokenIds({tokenIdCount: tokenIds.length});
|
|
189
|
-
|
|
190
|
-
// Only the encoded staker can materialize and collect their token rewards.
|
|
191
|
-
_requireCanClaimTokenIds({hook: hook, tokenIds: tokenIds});
|
|
192
|
-
|
|
193
|
-
// Before collecting, bring the caller current by starting vesting for any past reward rounds.
|
|
194
|
-
_claimPastRewards({hook: hook, tokenIds: tokenIds, tokens: tokens});
|
|
195
|
-
|
|
196
|
-
// Release whatever portion of existing vesting entries has unlocked by this round.
|
|
197
|
-
_unlockRewards({hook: hook, tokenIds: tokenIds, tokens: tokens, beneficiary: beneficiary, ownerClaim: true});
|
|
198
|
-
}
|
|
147
|
+
// `beginVesting` and `collectVestedRewards` are provided by `JBDistributor`. Both distributors share the exact
|
|
148
|
+
// same flow (authorize -> materialize past rounds via `_claimPastRewards` -> optionally release unlocked), so the
|
|
149
|
+
// round-claim logic lives once in the base and dispatches to this contract's `_claimPastRewards` /
|
|
150
|
+
// `_requireCanClaimTokenIds` overrides below.
|
|
199
151
|
|
|
200
152
|
//*********************************************************************//
|
|
201
153
|
// -------------------------- public views --------------------------- //
|
|
@@ -6,7 +6,6 @@ import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
|
|
|
6
6
|
import {IREVLoans} from "@rev-net/core-v6/src/interfaces/IREVLoans.sol";
|
|
7
7
|
import {IREVOwner} from "@rev-net/core-v6/src/interfaces/IREVOwner.sol";
|
|
8
8
|
|
|
9
|
-
import {JBTokenSnapshotData} from "../structs/JBTokenSnapshotData.sol";
|
|
10
9
|
import {JBVestingLoan} from "../structs/JBVestingLoan.sol";
|
|
11
10
|
|
|
12
11
|
/// @notice Interface for round-based reward distributors with linear vesting. Stakers claim their share of funded
|
|
@@ -139,22 +138,6 @@ interface IJBDistributor {
|
|
|
139
138
|
address caller
|
|
140
139
|
);
|
|
141
140
|
|
|
142
|
-
/// @notice Emitted when a snapshot is created for a round.
|
|
143
|
-
/// @param hook The hook the snapshot is for.
|
|
144
|
-
/// @param round The round the snapshot was created for.
|
|
145
|
-
/// @param token The token the snapshot is of.
|
|
146
|
-
/// @param balance The token balance at the time of the snapshot.
|
|
147
|
-
/// @param vestingAmount The amount of tokens vesting at the time of the snapshot.
|
|
148
|
-
/// @param caller The address that triggered the snapshot.
|
|
149
|
-
event SnapshotCreated(
|
|
150
|
-
address indexed hook,
|
|
151
|
-
uint256 indexed round,
|
|
152
|
-
IERC20 indexed token,
|
|
153
|
-
uint256 balance,
|
|
154
|
-
uint256 vestingAmount,
|
|
155
|
-
address caller
|
|
156
|
-
);
|
|
157
|
-
|
|
158
141
|
//*********************************************************************//
|
|
159
142
|
// ----------------------------- views ------------------------------- //
|
|
160
143
|
//*********************************************************************//
|
|
@@ -216,19 +199,6 @@ interface IJBDistributor {
|
|
|
216
199
|
/// @param round The round to get the start timestamp of.
|
|
217
200
|
function roundStartTimestamp(uint256 round) external view returns (uint256);
|
|
218
201
|
|
|
219
|
-
/// @notice The snapshot data of the token information for each round.
|
|
220
|
-
/// @param hook The hook the snapshot is for.
|
|
221
|
-
/// @param token The address of the token to check.
|
|
222
|
-
/// @param round The round to which the data applies.
|
|
223
|
-
function snapshotAtRoundOf(
|
|
224
|
-
address hook,
|
|
225
|
-
IERC20 token,
|
|
226
|
-
uint256 round
|
|
227
|
-
)
|
|
228
|
-
external
|
|
229
|
-
view
|
|
230
|
-
returns (JBTokenSnapshotData memory);
|
|
231
|
-
|
|
232
202
|
/// @notice The amount of a token that is currently vesting for a hook's stakers.
|
|
233
203
|
/// @param hook The hook whose vesting amount to check.
|
|
234
204
|
/// @param token The address of the token that is vesting.
|
|
@@ -1,11 +0,0 @@
|
|
|
1
|
-
// SPDX-License-Identifier: MIT
|
|
2
|
-
pragma solidity ^0.8.0;
|
|
3
|
-
|
|
4
|
-
/// @notice A point-in-time snapshot of a reward token's state for a specific hook and round. The distributable
|
|
5
|
-
/// amount for the round is `balance - vestingAmount`.
|
|
6
|
-
/// @custom:member balance The total token balance held for the hook's stakers at snapshot time.
|
|
7
|
-
/// @custom:member vestingAmount The amount currently locked in vesting at snapshot time (not yet distributable).
|
|
8
|
-
struct JBTokenSnapshotData {
|
|
9
|
-
uint256 balance;
|
|
10
|
-
uint256 vestingAmount;
|
|
11
|
-
}
|