@bananapus/distributor-v6 0.0.37 → 0.0.42
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 +40 -30
- package/package.json +6 -6
- package/references/operations.md +4 -4
- package/references/runtime.md +1 -1
- package/src/JB721Distributor.sol +198 -166
- package/src/JBDistributor.sol +115 -55
- package/src/JBTokenDistributor.sol +135 -82
- package/src/interfaces/IJB721Distributor.sol +27 -21
- package/src/interfaces/IJBDistributor.sol +60 -34
- package/src/interfaces/IJBTokenDistributor.sol +4 -3
- package/src/libraries/JBVestingMath.sol +3 -2
- package/src/structs/JBClaimContext.sol +1 -0
- package/src/structs/JBRewardRoundData.sol +2 -2
- package/src/structs/JBVestContext.sol +1 -0
package/README.md
CHANGED
|
@@ -6,13 +6,13 @@
|
|
|
6
6
|
|
|
7
7
|
- [ARCHITECTURE.md](./ARCHITECTURE.md) — system overview, modules, trust boundaries, and core invariants.
|
|
8
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,
|
|
9
|
+
- [INVARIANTS.md](./INVARIANTS.md) — per-section invariants for snapshot fairness, vesting math, beneficiary routing, loans, and recycling.
|
|
10
10
|
- [RISKS.md](./RISKS.md) — risk register with priority risks and the minimum invariants to verify.
|
|
11
11
|
- [ADMINISTRATION.md](./ADMINISTRATION.md) — deployment parameters, control posture, and recovery guidance.
|
|
12
12
|
- [SKILLS.md](./SKILLS.md) — quick index for routing tasks into the right sub-document.
|
|
13
13
|
- [STYLE_GUIDE.md](./STYLE_GUIDE.md) — Solidity and repo conventions used across the Juicebox V6 ecosystem.
|
|
14
14
|
- [AUDIT_INSTRUCTIONS.md](./AUDIT_INSTRUCTIONS.md) — audit framing, targets, and suggested hunting grounds.
|
|
15
|
-
- [CHANGELOG.md](./CHANGELOG.md)
|
|
15
|
+
- [CHANGELOG.md](./CHANGELOG.md) - V5 to V6 migration changelog.
|
|
16
16
|
|
|
17
17
|
## Overview
|
|
18
18
|
|
|
@@ -22,7 +22,7 @@ The package separates distribution mechanics by asset type:
|
|
|
22
22
|
|
|
23
23
|
- `JBDistributor` coordinates shared round and vesting logic
|
|
24
24
|
- `JBTokenDistributor` distributes ERC-20 balances using `IVotes` checkpointed voting power
|
|
25
|
-
- `JB721Distributor` distributes value to 721 holders using checkpointed
|
|
25
|
+
- `JB721Distributor` distributes value to active 721 holders using the hook's checkpointed owner and active-vote data, ensuring only holders at the funded round's snapshot block are eligible
|
|
26
26
|
|
|
27
27
|
Both concrete distributors implement `IJBSplitHook`, which makes them usable directly from Juicebox payout splits.
|
|
28
28
|
|
|
@@ -30,44 +30,52 @@ Use this repo when the problem is "how do we distribute already-owned assets ove
|
|
|
30
30
|
|
|
31
31
|
If the issue is "where did the project's value come from?" start in `nana-core-v6`, `nana-721-hook-v6`, or the upstream repo that minted or received the assets first.
|
|
32
32
|
|
|
33
|
-
## Key
|
|
33
|
+
## Key contracts
|
|
34
34
|
|
|
35
35
|
| Contract | Role |
|
|
36
36
|
| --- | --- |
|
|
37
37
|
| `JBDistributor` | Shared round-based vesting, claiming, and accounting logic. |
|
|
38
38
|
| `JBTokenDistributor` | ERC-20 distributor keyed to `IVotes` checkpointed voting power. |
|
|
39
|
-
| `JB721Distributor` | NFT-aware distributor keyed to checkpointed voting power from the hook's
|
|
39
|
+
| `JB721Distributor` | NFT-aware distributor keyed to checkpointed voting power from the hook's checkpoint module. Only NFTs held at the funded round's snapshot block are eligible. |
|
|
40
40
|
|
|
41
|
-
## Mental
|
|
41
|
+
## Mental model
|
|
42
42
|
|
|
43
43
|
1. a project funds the distributor, often through a payout split
|
|
44
44
|
2. accepted funding is assigned to the current reward round for the chosen token or 721 stake source
|
|
45
|
-
3.
|
|
46
|
-
4.
|
|
47
|
-
5. anyone can recycle expired
|
|
48
|
-
6. recipients collect their vested share as the configured vesting schedule unlocks
|
|
45
|
+
3. each funded reward round records an active-vote snapshot denominator for its token, collection, or tier group
|
|
46
|
+
4. anyone can start vesting completed past reward rounds for an encoded token staker or current NFT owner
|
|
47
|
+
5. anyone can recycle expired reward-round inventory that has not started vesting after the claim deadline
|
|
48
|
+
6. recipients collect their vested share as the configured vesting schedule unlocks; helpers can collect only to the
|
|
49
|
+
canonical holder
|
|
49
50
|
7. eligible claimants can borrow against vesting revnet rewards without bypassing the vesting schedule
|
|
50
|
-
8.
|
|
51
|
+
8. burned 721 rewards can be materialized and recycled through explicit cleanup paths as they vest
|
|
51
52
|
|
|
52
53
|
This repo does not explain why an allocation exists. It only defines how funded inventory is handed out.
|
|
53
54
|
|
|
54
|
-
## Read
|
|
55
|
+
## Read these files first
|
|
55
56
|
|
|
56
57
|
1. `src/interfaces/IJBDistributor.sol`
|
|
57
58
|
2. `src/JBDistributor.sol`
|
|
58
59
|
3. `src/JBTokenDistributor.sol`
|
|
59
60
|
4. `src/JB721Distributor.sol`
|
|
60
61
|
|
|
61
|
-
## Integration
|
|
62
|
+
## Integration traps
|
|
62
63
|
|
|
63
64
|
- distribution correctness depends on the distributor actually holding the assets it is expected to vest
|
|
64
|
-
- ERC-20 and ERC-721 distributions share historical reward-round accounting, but
|
|
65
|
-
token rewards
|
|
66
|
-
- `
|
|
67
|
-
|
|
68
|
-
- `
|
|
69
|
-
|
|
70
|
-
-
|
|
65
|
+
- ERC-20 and ERC-721 distributions share historical reward-round accounting, but their canonical beneficiaries differ:
|
|
66
|
+
token rewards belong to the encoded staker address, while 721 rewards belong to the current NFT owner
|
|
67
|
+
- `beginVesting` is permissionless because no value leaves the distributor; `collectVestedRewards` is permissionless
|
|
68
|
+
only when paid to the canonical beneficiary, while an authorized holder can still choose any beneficiary
|
|
69
|
+
- `CLAIM_DURATION` is fixed at deployment; `0` means reward rounds do not expire, while nonzero values set the window
|
|
70
|
+
after which unmaterialized reward inventory can be recycled
|
|
71
|
+
- token distributors record `IJBActiveVotes.getPastTotalActiveVotes` at the funded round's snapshot block; only
|
|
72
|
+
addresses with `getPastVotes` at that block share the pot
|
|
73
|
+
- 721 distributors record the hook checkpoint module's active total for all-tiers rewards, or the summed active totals
|
|
74
|
+
for a tier-scoped reward group; both modes cap each NFT claim by the snapshot owner's remaining active units for that
|
|
75
|
+
NFT's tier
|
|
76
|
+
- `recycleExpiredRewards` is permissionless; it recycles the expired round's unmaterialized remainder while preserving
|
|
77
|
+
amounts that already started vesting
|
|
78
|
+
- eligible expired and forfeited rewards stay in distributor inventory and are recycled into the current reward round
|
|
71
79
|
- revnet loan-backed vesting is opt-in at deployment; the reward token must be a REVOwner-owned revnet token, the
|
|
72
80
|
distributor keeps the loan NFT, and repayment restores the original vesting schedule instead of releasing all
|
|
73
81
|
collateral immediately
|
|
@@ -75,26 +83,28 @@ This repo does not explain why an allocation exists. It only defines how funded
|
|
|
75
83
|
stale collection lock and forfeit only the vesting rewards that were collateralized by that loan
|
|
76
84
|
- distributors deployed with `VESTING_ROUNDS == 0` disable revnet vesting loans because rewards are immediately
|
|
77
85
|
collectible instead of locked in a vesting position
|
|
78
|
-
- `releaseForfeitedRewards` matters for 721 distributions;
|
|
79
|
-
|
|
86
|
+
- `releaseForfeitedRewards` matters for 721 distributions; it first materializes any unclaimed historical shares for
|
|
87
|
+
burned NFTs, then recycles only the amount unlocked by the vesting schedule. Token-vote distributions do not have the
|
|
88
|
+
same burned-token forfeiture path
|
|
80
89
|
- reward, vesting, and loan accounting carries a `groupId`: `0` is the all-tiers group (the default pool), a non-zero
|
|
81
90
|
group is `keccak256(abi.encode(tierIds))`. The tier overloads live on `JB721Distributor`; the base is tier-agnostic.
|
|
82
91
|
Split funding via `processSplitWith` always lands in group 0 — a split cannot carry a tier set; tier-scoped pots
|
|
83
92
|
require the explicit `fund(hook, tierIds, token, amount)` overload, and claims/collections must pass the same
|
|
84
93
|
`tierIds` to hit that group
|
|
85
94
|
- tier-scoped 721 pots weigh each eligible NFT by its tier's `votingUnits` against a summed
|
|
86
|
-
`
|
|
95
|
+
`getPastTotalTierActiveVotes` denominator, then cap each numerator with `getPastAccountTierActiveVotes`; this
|
|
96
|
+
requires `@bananapus/721-hook-v6 >= 0.0.73` for the active-vote checkpoints API
|
|
87
97
|
- snapshot timing is part of the trusted surface
|
|
88
98
|
- this repo settles distributions, but it does not prove the upstream entitlement math was correct
|
|
89
99
|
|
|
90
|
-
## Where
|
|
100
|
+
## Where state lives
|
|
91
101
|
|
|
92
102
|
- round and vesting state: `JBDistributor`
|
|
93
103
|
- historical reward-round inputs: `JBRewardRoundData`
|
|
94
104
|
- vesting schedule state: `JBVestingData`
|
|
95
105
|
- asset-specific claim behavior: the concrete distributor
|
|
96
106
|
|
|
97
|
-
## High-
|
|
107
|
+
## High-signal tests
|
|
98
108
|
|
|
99
109
|
1. `test/JBTokenDistributor.t.sol`
|
|
100
110
|
2. `test/JB721Distributor.t.sol`
|
|
@@ -121,7 +131,7 @@ Useful scripts:
|
|
|
121
131
|
- `npm run deploy:mainnets`
|
|
122
132
|
- `npm run deploy:testnets`
|
|
123
133
|
|
|
124
|
-
## Repository
|
|
134
|
+
## Repository layout
|
|
125
135
|
|
|
126
136
|
```text
|
|
127
137
|
src/
|
|
@@ -136,15 +146,15 @@ script/
|
|
|
136
146
|
Deploy.s.sol
|
|
137
147
|
```
|
|
138
148
|
|
|
139
|
-
## Risks
|
|
149
|
+
## Risks and notes
|
|
140
150
|
|
|
141
151
|
- distributors are only as trustworthy as the vesting parameters and funding they receive
|
|
142
152
|
- operational mistakes often come from funding the wrong asset or underfunding the distributor
|
|
143
153
|
- teams should review claim timing and snapshot assumptions with the same care they review the payout source
|
|
144
|
-
-
|
|
145
|
-
|
|
154
|
+
- token distributors require hooks that expose `IJBActiveVotes`; expiring token rounds recycle any unmaterialized
|
|
155
|
+
remainder after the deadline
|
|
146
156
|
|
|
147
|
-
## For AI
|
|
157
|
+
## For AI agents
|
|
148
158
|
|
|
149
159
|
- Treat this repo as distribution plumbing, not as the source of upstream entitlement math.
|
|
150
160
|
- Read both the ERC-20 and ERC-721 tests before claiming the flows are equivalent.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bananapus/distributor-v6",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.42",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -24,12 +24,12 @@
|
|
|
24
24
|
"deploy:testnets": "source ./.env && npx sphinx propose ./script/Deploy.s.sol --networks testnets"
|
|
25
25
|
},
|
|
26
26
|
"dependencies": {
|
|
27
|
-
"@bananapus/721-hook-v6": "^0.0.
|
|
28
|
-
"@bananapus/core-v6": "^0.0.
|
|
29
|
-
"@bananapus/permission-ids-v6": "^0.0.
|
|
27
|
+
"@bananapus/721-hook-v6": "^0.0.73",
|
|
28
|
+
"@bananapus/core-v6": "^0.0.86",
|
|
29
|
+
"@bananapus/permission-ids-v6": "^0.0.30",
|
|
30
30
|
"@openzeppelin/contracts": "5.6.1",
|
|
31
|
-
"@prb/math": "4.1.
|
|
32
|
-
"@rev-net/core-v6": "^0.0.
|
|
31
|
+
"@prb/math": "4.1.2",
|
|
32
|
+
"@rev-net/core-v6": "^0.0.89"
|
|
33
33
|
},
|
|
34
34
|
"devDependencies": {
|
|
35
35
|
"@sphinx-labs/plugins": "0.33.3"
|
package/references/operations.md
CHANGED
|
@@ -5,10 +5,10 @@
|
|
|
5
5
|
| If you're editing... | Verify... |
|
|
6
6
|
|---|---|
|
|
7
7
|
| `JBDistributor` vesting math | Claim totals, `totalVestingAmountOf`, and pool balances still reconcile across rounds |
|
|
8
|
-
| `JBTokenDistributor` checkpoint logic | `getPastVotes` and `getPastTotalSupply`
|
|
8
|
+
| `JBTokenDistributor` checkpoint logic | Non-expiring rounds read `getPastVotes` and `getPastTotalSupply` at the intended snapshot block; active-voter rounds read `getPastVotes` and `getPastTotalActiveVotes` at the intended snapshot block |
|
|
9
9
|
| `JB721Distributor` stake math | Minted, remaining, and burned supply still produce the intended tier-weighted total stake |
|
|
10
10
|
| `processSplitWith` | Allowance-based `transferFrom` flow preserves actual received balances; split funding still records under `groupId == 0` only (a split cannot carry a tier set) |
|
|
11
|
-
| `groupId` threading | The `groupId` dimension stays consistent across the reward (`rewardRoundOf`), vesting (`vestingDataOf`, `latestVestedIndexOf`, `nextClaimRoundOf`), and loan (`activeVestingLoanIdOf`, `JBVestingLoan`) maps; the `tierIds` overloads (`fund`/`beginVesting`/`collectVestedRewards`/`borrowAgainstVesting`/`
|
|
11
|
+
| `groupId` threading | The `groupId` dimension stays consistent across the reward (`rewardRoundOf`), vesting (`vestingDataOf`, `latestVestedIndexOf`, `nextClaimRoundOf`), and loan (`activeVestingLoanIdOf`, `JBVestingLoan`) maps; the `tierIds` overloads (`fund`/`beginVesting`/`collectVestedRewards`/`borrowAgainstVesting`/`recycleExpiredRewards`/`releaseForfeitedRewards`) live on `JB721Distributor` and derive the group ID via `_groupIdFor`; the base is tier-agnostic and group 0 is the default all-tiers pool |
|
|
12
12
|
| Deployment inputs | `DIRECTORY_ADDRESS`, `ROUND_DURATION`, and `VESTING_ROUNDS` match the intended chain and operator plan |
|
|
13
13
|
|
|
14
14
|
## Common failure modes
|
|
@@ -16,11 +16,11 @@
|
|
|
16
16
|
| Symptom | Likely cause |
|
|
17
17
|
|---|---|
|
|
18
18
|
| A holder gets no rewards in the token distributor | They never delegated, so `getPastVotes` returned zero |
|
|
19
|
-
| Rewards appear stuck in the distributor | Supply was undelegated, vesting never began for the target token IDs, or the round boundary assumption is wrong |
|
|
19
|
+
| Rewards appear stuck in the distributor | Supply was undelegated in a non-expiring token round, an active-voter token round had zero active votes and has not been recycled, vesting never began for the target token IDs, or the round boundary assumption is wrong |
|
|
20
20
|
| 721 reward shares look diluted | Burned supply was not excluded correctly or token-to-tier mapping is wrong |
|
|
21
21
|
| Split-hook funding credits the wrong amount | The caller did not grant a sufficient ERC-20 allowance before calling `processSplitWith` |
|
|
22
22
|
|
|
23
|
-
## Read
|
|
23
|
+
## Read next
|
|
24
24
|
|
|
25
25
|
- [`script/Deploy.s.sol`](../script/Deploy.s.sol) when the failure might be deployment config rather than distributor math.
|
|
26
26
|
- [`test/invariant/JB721DistributorInvariant.t.sol`](../test/invariant/JB721DistributorInvariant.t.sol) when a local patch looks safe but may have broken a longer-lived accounting invariant.
|
package/references/runtime.md
CHANGED
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
|
|
13
13
|
### Round and checkpoint semantics
|
|
14
14
|
|
|
15
|
-
The token distributor depends on checkpointed voting power at the round
|
|
15
|
+
The token distributor depends on checkpointed voting power at the reward round's snapshot block. Holders must delegate for `getPastVotes` to count them. Deployments with `CLAIM_DURATION == 0` use `getPastTotalSupply`, so undelegated supply can dilute claims; deployments with nonzero claim duration use `IJBActiveVotes.getPastTotalActiveVotes`, so undelegated balances such as AMM custody with no delegate do not share the pot.
|
|
16
16
|
|
|
17
17
|
### Funding path
|
|
18
18
|
|