@bananapus/721-hook-v6 0.0.69 → 0.0.72
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 +4 -3
- package/package.json +6 -6
- package/references/operations.md +1 -0
- package/references/runtime.md +1 -1
- package/src/JB721Checkpoints.sol +259 -45
- package/src/JB721TiersHook.sol +3 -5
- package/src/JB721TiersHookProjectDeployer.sol +2 -4
- package/src/JB721TiersHookStore.sol +2 -4
- package/src/abstract/JB721Hook.sol +3 -6
- package/src/interfaces/IJB721Checkpoints.sol +34 -24
- package/src/interfaces/IJB721TiersHook.sol +3 -5
- package/src/libraries/JB721TiersHookLib.sol +1 -2
- package/src/structs/JB721TiersRulesetMetadata.sol +1 -2
- package/src/structs/JBLaunchProjectConfig.sol +1 -2
- package/src/structs/JBPayDataHookRulesetConfig.sol +7 -10
- package/src/structs/JBPayDataHookRulesetMetadata.sol +2 -4
package/README.md
CHANGED
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
- [AUDIT_INSTRUCTIONS.md](./AUDIT_INSTRUCTIONS.md) — what to focus on for a security audit and how to start.
|
|
11
11
|
- [SKILLS.md](./SKILLS.md) — implementation nuances, gotchas, and reading order for working on this codebase.
|
|
12
12
|
- [STYLE_GUIDE.md](./STYLE_GUIDE.md) — Solidity conventions and repo layout used across the V6 ecosystem.
|
|
13
|
-
- [CHANGELOG.md](./CHANGELOG.md)
|
|
13
|
+
- [CHANGELOG.md](./CHANGELOG.md) - V5 to V6 ABI and behavior deltas.
|
|
14
14
|
- [references/runtime.md](./references/runtime.md) — contract roles, the runtime pay and cash-out path, and high-risk areas.
|
|
15
15
|
- [references/operations.md](./references/operations.md) — deployment surface, change checklist, and common failure modes.
|
|
16
16
|
|
|
@@ -38,7 +38,7 @@ This repo does more than "mint NFTs on pay." It changes how payment value, tier
|
|
|
38
38
|
| `JB721TiersHookDeployer` | Clone factory for deploying a hook for an existing project. |
|
|
39
39
|
| `JB721TiersHookProjectDeployer` | Convenience deployer for launching a project with a hook already wired in. |
|
|
40
40
|
| `JB721Hook` | Abstract base for 721 pay and cash-out hook behavior. |
|
|
41
|
-
| `JB721Checkpoints` | Per-hook IVotes checkpoint module. Tracks historical owner checkpoints
|
|
41
|
+
| `JB721Checkpoints` | Per-hook IVotes checkpoint module. Tracks historical owner checkpoints, per-tier owner-tracked voting units (`getPastTierVotingUnits`), global active vote totals (`getPastTotalActiveVotes`), and per-tier active vote totals (`getPastTierActiveVotes`). |
|
|
42
42
|
|
|
43
43
|
## Mental model
|
|
44
44
|
|
|
@@ -64,7 +64,8 @@ If a bug affects supply, reserve minting, or tier lookup, it usually lives in th
|
|
|
64
64
|
- custom token URI resolvers should be treated as part of the trusted surface
|
|
65
65
|
- adding a 721 hook through a deployer is easy; carrying the right ruleset behavior forward is where mistakes happen
|
|
66
66
|
- projects should be explicit about whether the hook affects pay, cash out, or only metadata-facing paths
|
|
67
|
-
- per-tier
|
|
67
|
+
- per-tier owner-tracked voting units are queryable via `getPastTierVotingUnits(tierId, blockNumber)`: mints, transfers, and burns write ownership history, so the trace follows owned units regardless of delegation
|
|
68
|
+
- active delegated vote totals are queryable globally via `getPastTotalActiveVotes(blockNumber)` / `getTotalActiveVotes()` and per tier via `getPastTierActiveVotes(tierId, blockNumber)` / `getTierActiveVotes(tierId)`. These totals include only voting units held by accounts with a nonzero delegate, so a token in undelegated custody does not count and returned tokens become active again if the holder's delegation is still set.
|
|
68
69
|
|
|
69
70
|
## Where state lives
|
|
70
71
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bananapus/721-hook-v6",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.72",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -25,12 +25,12 @@
|
|
|
25
25
|
"artifacts": "source ./.env && npx sphinx artifacts --org-id 'ea165b21-7cdc-4d7b-be59-ecdd4c26bee4' --project-name 'nana-721-hook-v6'"
|
|
26
26
|
},
|
|
27
27
|
"dependencies": {
|
|
28
|
-
"@bananapus/address-registry-v6": "^0.0.
|
|
29
|
-
"@bananapus/core-v6": "^0.0.
|
|
30
|
-
"@bananapus/ownable-v6": "^0.0.
|
|
31
|
-
"@bananapus/permission-ids-v6": "^0.0.
|
|
28
|
+
"@bananapus/address-registry-v6": "^0.0.34",
|
|
29
|
+
"@bananapus/core-v6": "^0.0.86",
|
|
30
|
+
"@bananapus/ownable-v6": "^0.0.37",
|
|
31
|
+
"@bananapus/permission-ids-v6": "^0.0.30",
|
|
32
32
|
"@openzeppelin/contracts": "5.6.1",
|
|
33
|
-
"@prb/math": "4.1.
|
|
33
|
+
"@prb/math": "4.1.2",
|
|
34
34
|
"solady": "0.1.26"
|
|
35
35
|
},
|
|
36
36
|
"devDependencies": {
|
package/references/operations.md
CHANGED
|
@@ -13,6 +13,7 @@
|
|
|
13
13
|
- If you edit reserve behavior, verify pending reserve counts, default reserve beneficiary semantics, and cash-out denominator effects together.
|
|
14
14
|
- If you edit discount behavior, verify mint price and cash-out weight separately. They are intentionally not the same quantity.
|
|
15
15
|
- If you touch checkpoint, `onTransfer`, or `delegate` behavior, verify the per-tier eligible-voting-units trace (`_tierEligibleUnitsOf`, read via `getPastTierVotingUnits`) still moves only on eligibility changes: increment on enrollment or a token's first transfer, decrement on burn, and nothing on mint (so the mint path keeps its zero added checkpoint gas). Keep it in lockstep with `ownerOfAt` eligibility.
|
|
16
|
+
- Also verify the active delegated vote trace (`_activeSupplyCheckpoints`, read via `getPastTotalActiveVotes` and `getTotalActiveVotes`) moves only when voting units enter or leave nonzero delegation. It is separate from owner-based tier reward eligibility.
|
|
16
17
|
- If you touch permissions, verify the caller path and permission constants still line up with the downstream ecosystem package that defines them.
|
|
17
18
|
- If you touch URI behavior, confirm whether the issue belongs in this repo or in a downstream resolver contract that the hook calls.
|
|
18
19
|
|
package/references/runtime.md
CHANGED
|
@@ -20,7 +20,7 @@
|
|
|
20
20
|
- Reserve accounting: edits around `reserveFrequency`, pending reserves, or owner minting must preserve the store's supply protections.
|
|
21
21
|
- Tier splits: split forwarding changes affect both payer economics and project treasury accounting. Check both `beforePayRecordedWith` and the distribution path.
|
|
22
22
|
- Discount behavior: price discounts affect mint eligibility but cash-out weight still tracks the original tier price. Do not conflate the two.
|
|
23
|
-
- Voting units: verify whether a tier uses explicit voting units or falls back to price-based voting power before changing governance-facing math.
|
|
23
|
+
- Voting units: verify whether a tier uses explicit voting units or falls back to price-based voting power before changing governance-facing math. Active vote totals count only units delegated to nonzero delegates and are separate from tier reward eligibility.
|
|
24
24
|
- Tier removal and cleanup: removing tiers is not the same as cleaning the sorted tier list. Storage cleanup behavior matters.
|
|
25
25
|
- Default reserve beneficiary changes: they affect which tiers count pending reserves unless a tier-specific beneficiary overrides it. That is an economic change, not just an admin update.
|
|
26
26
|
|
package/src/JB721Checkpoints.sol
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
// SPDX-License-Identifier: MIT
|
|
2
2
|
pragma solidity 0.8.28;
|
|
3
3
|
|
|
4
|
-
import {IERC721} from "@openzeppelin/contracts/token/ERC721/IERC721.sol";
|
|
5
4
|
import {Votes} from "@openzeppelin/contracts/governance/utils/Votes.sol";
|
|
5
|
+
import {IERC721} from "@openzeppelin/contracts/token/ERC721/IERC721.sol";
|
|
6
6
|
import {EIP712} from "@openzeppelin/contracts/utils/cryptography/EIP712.sol";
|
|
7
|
+
import {SafeCast} from "@openzeppelin/contracts/utils/math/SafeCast.sol";
|
|
7
8
|
import {Checkpoints} from "@openzeppelin/contracts/utils/structs/Checkpoints.sol";
|
|
8
9
|
|
|
9
10
|
import {IJB721Checkpoints} from "./interfaces/IJB721Checkpoints.sol";
|
|
@@ -19,6 +20,7 @@ import {IJB721TiersHookStore} from "./interfaces/IJB721TiersHookStore.sol";
|
|
|
19
20
|
/// `address(this)` — correct behavior, tiny gas overhead.
|
|
20
21
|
contract JB721Checkpoints is Votes, IJB721Checkpoints {
|
|
21
22
|
using Checkpoints for Checkpoints.Trace160;
|
|
23
|
+
using Checkpoints for Checkpoints.Trace208;
|
|
22
24
|
|
|
23
25
|
//*********************************************************************//
|
|
24
26
|
// --------------------------- custom errors ------------------------- //
|
|
@@ -27,7 +29,7 @@ contract JB721Checkpoints is Votes, IJB721Checkpoints {
|
|
|
27
29
|
/// @notice Thrown when `initialize` is called on a module whose hook has already been set.
|
|
28
30
|
error JB721Checkpoints_AlreadyInitialized(address hook);
|
|
29
31
|
|
|
30
|
-
/// @notice Thrown when the caller tries to
|
|
32
|
+
/// @notice Thrown when the caller tries to backfill a token they do not currently own.
|
|
31
33
|
error JB721Checkpoints_NotOwner(uint256 tokenId, address caller);
|
|
32
34
|
|
|
33
35
|
/// @notice Thrown when a hook-only function is called by an address other than the module's hook.
|
|
@@ -38,6 +40,7 @@ contract JB721Checkpoints is Votes, IJB721Checkpoints {
|
|
|
38
40
|
//*********************************************************************//
|
|
39
41
|
|
|
40
42
|
/// @notice The store that holds tier and voting data for the hook's NFTs.
|
|
43
|
+
/// @dev All tier lookups are scoped by `hook`; this immutable only identifies the shared store contract.
|
|
41
44
|
IJB721TiersHookStore public immutable override STORE;
|
|
42
45
|
|
|
43
46
|
//*********************************************************************//
|
|
@@ -45,26 +48,43 @@ contract JB721Checkpoints is Votes, IJB721Checkpoints {
|
|
|
45
48
|
//*********************************************************************//
|
|
46
49
|
|
|
47
50
|
/// @notice The hook that this module tracks voting power for.
|
|
51
|
+
/// @dev Clones set this once in `initialize`; the implementation uses `address(1)` as a locked sentinel.
|
|
48
52
|
address public override hook;
|
|
49
53
|
|
|
50
54
|
//*********************************************************************//
|
|
51
55
|
// -------------------- internal stored properties ------------------- //
|
|
52
56
|
//*********************************************************************//
|
|
53
57
|
|
|
54
|
-
/// @notice Checkpointed token owners for historical reward eligibility.
|
|
58
|
+
/// @notice Checkpointed token owners for historical reward eligibility.
|
|
59
|
+
/// @dev Mint and transfer hooks write this automatically; `delegate` only backfills missing pre-upgrade history.
|
|
55
60
|
/// @custom:param tokenId The token ID to get historical owner checkpoints for.
|
|
56
61
|
mapping(uint256 tokenId => Checkpoints.Trace160) internal _ownerCheckpointsOf;
|
|
57
62
|
|
|
58
|
-
/// @notice Checkpointed total
|
|
59
|
-
///
|
|
60
|
-
///
|
|
61
|
-
/// @custom:param tierId The tier to get the historical
|
|
63
|
+
/// @notice Checkpointed total active voting units per tier.
|
|
64
|
+
/// @dev Maintained when delegation changes activate/deactivate all of an account's tier units, and when transfers
|
|
65
|
+
/// move one token's tier units between delegated and undelegated custody.
|
|
66
|
+
/// @custom:param tierId The tier to get the historical delegated voting units for.
|
|
67
|
+
mapping(uint256 tierId => Checkpoints.Trace160) internal _tierActiveSupplyCheckpointsOf;
|
|
68
|
+
|
|
69
|
+
/// @notice Checkpointed total owner-tracked voting units per tier.
|
|
70
|
+
/// @dev Increased on mint or backfill and decreased on burn. Transfers keep the total unchanged because the token
|
|
71
|
+
/// still has a nonzero owner.
|
|
72
|
+
/// @custom:param tierId The tier to get the historical owner-tracked voting units for.
|
|
62
73
|
mapping(uint256 tierId => Checkpoints.Trace160) internal _tierEligibleUnitsOf;
|
|
63
74
|
|
|
75
|
+
//*********************************************************************//
|
|
76
|
+
// -------------------- private stored properties -------------------- //
|
|
77
|
+
//*********************************************************************//
|
|
78
|
+
|
|
79
|
+
/// @notice Checkpointed total voting units held by accounts with nonzero delegates.
|
|
80
|
+
/// @dev Maintained by `_delegate` and `_transferVotingUnits` using the same clock as OZ `Votes`.
|
|
81
|
+
Checkpoints.Trace208 private _activeSupplyCheckpoints;
|
|
82
|
+
|
|
64
83
|
//*********************************************************************//
|
|
65
84
|
// -------------------------- constructor ---------------------------- //
|
|
66
85
|
//*********************************************************************//
|
|
67
86
|
|
|
87
|
+
/// @notice Initializes the checkpoint implementation and locks it against direct use.
|
|
68
88
|
/// @dev The implementation contract is initialized in the constructor to prevent direct use. Clones are initialized
|
|
69
89
|
/// via `initialize()`.
|
|
70
90
|
/// @param store The store that holds tier data for each hook's NFTs.
|
|
@@ -77,26 +97,25 @@ contract JB721Checkpoints is Votes, IJB721Checkpoints {
|
|
|
77
97
|
// ---------------------- external transactions ---------------------- //
|
|
78
98
|
//*********************************************************************//
|
|
79
99
|
|
|
80
|
-
/// @notice Delegates voting power and
|
|
81
|
-
/// @dev
|
|
82
|
-
///
|
|
83
|
-
/// distribution. The existing `delegate(address)` from OZ Votes still works for pure delegation without enrollment.
|
|
100
|
+
/// @notice Delegates voting power and backfills ownership history for listed tokens if needed.
|
|
101
|
+
/// @dev Mint and transfer hooks normally write owner checkpoints automatically. The token ID list keeps
|
|
102
|
+
/// pre-upgrade or otherwise uncheckpointed tokens recoverable while preserving the owner-only authorization check.
|
|
84
103
|
/// @param delegatee The address to delegate voting power to. Use your own address for self-delegation.
|
|
85
|
-
/// @param tokenIds The token IDs
|
|
104
|
+
/// @param tokenIds The token IDs whose owner checkpoints should be backfilled if missing.
|
|
86
105
|
function delegate(address delegatee, uint256[] calldata tokenIds) external override {
|
|
87
106
|
// Delegate voting power (reuses OZ Votes internals).
|
|
88
107
|
_delegate({account: msg.sender, delegatee: delegatee});
|
|
89
108
|
|
|
90
|
-
// Write per-token owner checkpoints for
|
|
109
|
+
// Write any missing per-token owner checkpoints for historical reward eligibility.
|
|
91
110
|
for (uint256 i; i < tokenIds.length;) {
|
|
92
111
|
uint256 tokenId = tokenIds[i];
|
|
93
112
|
|
|
94
|
-
// Only the current owner can
|
|
113
|
+
// Only the current owner can backfill their token's ownership checkpoint.
|
|
95
114
|
if (IERC721(hook).ownerOf(tokenId) != msg.sender) {
|
|
96
115
|
revert JB721Checkpoints_NotOwner({tokenId: tokenId, caller: msg.sender});
|
|
97
116
|
}
|
|
98
117
|
|
|
99
|
-
// Write an owner checkpoint if the token has none yet, and
|
|
118
|
+
// Write an owner checkpoint if the token has none yet, and track its tier voting units.
|
|
100
119
|
if (_ownerCheckpointsOf[tokenId].length() == 0) {
|
|
101
120
|
// forge-lint: disable-next-line(unsafe-typecast)
|
|
102
121
|
_ownerCheckpointsOf[tokenId].push({key: uint96(block.number), value: uint160(msg.sender)});
|
|
@@ -130,70 +149,195 @@ contract JB721Checkpoints is Votes, IJB721Checkpoints {
|
|
|
130
149
|
function onTransfer(address from, address to, uint256 tokenId) external override {
|
|
131
150
|
if (msg.sender != hook) revert JB721Checkpoints_Unauthorized({caller: msg.sender, hook: hook});
|
|
132
151
|
|
|
152
|
+
// Look up this token's tier ID and voting units once; both the owner and active traces need them.
|
|
153
|
+
uint256 tierId = STORE.tierIdOfToken(tokenId);
|
|
154
|
+
|
|
133
155
|
// Look up this token's tier voting units (lightweight getter — avoids full tier struct construction).
|
|
134
156
|
uint256 votingUnits = STORE.tierVotingUnitsOfTokenId({hook: hook, tokenId: tokenId});
|
|
135
157
|
|
|
136
|
-
//
|
|
137
|
-
|
|
138
|
-
if (from != address(0)) {
|
|
139
|
-
Checkpoints.Trace160 storage ownerTrace = _ownerCheckpointsOf[tokenId];
|
|
140
|
-
bool wasEligible = ownerTrace.length() != 0;
|
|
158
|
+
// Keep a reference to the token's historical owner trace.
|
|
159
|
+
Checkpoints.Trace160 storage ownerTrace = _ownerCheckpointsOf[tokenId];
|
|
141
160
|
|
|
161
|
+
// Existing deployments may have tokens that predate mint checkpointing; detect the first written owner.
|
|
162
|
+
bool wasEligible = ownerTrace.length() != 0;
|
|
163
|
+
|
|
164
|
+
// Mints, transfers, and burns all write ownership history so reward claims can prove snapshot ownership.
|
|
165
|
+
if (to != address(0) || from != address(0)) {
|
|
142
166
|
// forge-lint: disable-next-line(unsafe-typecast)
|
|
143
167
|
ownerTrace.push({key: uint96(block.number), value: uint160(to)});
|
|
168
|
+
}
|
|
144
169
|
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
}
|
|
152
|
-
} else if (!wasEligible) {
|
|
153
|
-
// First transfer of a never-enrolled token makes it eligible: add the tier's units.
|
|
154
|
-
_updateTierEligibleUnits({tierId: STORE.tierIdOfToken(tokenId), amount: votingUnits, increase: true});
|
|
170
|
+
if (from == address(0) && to != address(0)) {
|
|
171
|
+
// Mint: add the token's tier units to the owner-tracked tier supply.
|
|
172
|
+
_updateTierEligibleUnits({tierId: tierId, amount: votingUnits, increase: true});
|
|
173
|
+
} else if (to == address(0)) {
|
|
174
|
+
// Burn: remove the tier's units only if the token was already part of the owner-tracked supply.
|
|
175
|
+
if (wasEligible) {
|
|
176
|
+
_updateTierEligibleUnits({tierId: tierId, amount: votingUnits, increase: false});
|
|
155
177
|
}
|
|
178
|
+
} else if (!wasEligible) {
|
|
179
|
+
// First transfer of a pre-upgrade uncheckpointed token makes its ownership history trackable.
|
|
180
|
+
_updateTierEligibleUnits({tierId: tierId, amount: votingUnits, increase: true});
|
|
156
181
|
}
|
|
157
182
|
|
|
183
|
+
// The tier active total decreases if units leave an account that already has a nonzero delegate.
|
|
184
|
+
bool decreaseTierActiveVotes = from != address(0) && delegates(from) != address(0);
|
|
185
|
+
|
|
186
|
+
// The tier active total increases if units arrive at an account that already has a nonzero delegate.
|
|
187
|
+
bool increaseTierActiveVotes = to != address(0) && delegates(to) != address(0);
|
|
188
|
+
|
|
158
189
|
// Move checkpointed voting power from the previous owner to the new owner.
|
|
159
190
|
_transferVotingUnits({from: from, to: to, amount: votingUnits});
|
|
191
|
+
|
|
192
|
+
// If both sides are delegated or both sides are undelegated, this tier's active total is unchanged.
|
|
193
|
+
if (decreaseTierActiveVotes != increaseTierActiveVotes) {
|
|
194
|
+
// Otherwise apply the one-sided active-tier delta implied by the receiver's delegated status.
|
|
195
|
+
_adjustTierActiveVotes({tierId: tierId, amount: votingUnits, increase: increaseTierActiveVotes});
|
|
196
|
+
}
|
|
160
197
|
}
|
|
161
198
|
|
|
162
199
|
//*********************************************************************//
|
|
163
200
|
// ----------------------- external views ---------------------------- //
|
|
164
201
|
//*********************************************************************//
|
|
165
202
|
|
|
166
|
-
/// @notice The total
|
|
167
|
-
/// @
|
|
203
|
+
/// @notice The total delegated voting units of a tier at a past block.
|
|
204
|
+
/// @dev Counts only tier voting units held by accounts with a nonzero delegate.
|
|
205
|
+
/// @param tierId The tier to get the delegated voting units of.
|
|
206
|
+
/// @param blockNumber The past block number to look up.
|
|
207
|
+
/// @return activeVotes The tier's delegated voting units at `blockNumber`.
|
|
208
|
+
function getPastTierActiveVotes(
|
|
209
|
+
uint256 tierId,
|
|
210
|
+
uint256 blockNumber
|
|
211
|
+
)
|
|
212
|
+
external
|
|
213
|
+
view
|
|
214
|
+
override
|
|
215
|
+
returns (uint256 activeVotes)
|
|
216
|
+
{
|
|
217
|
+
// forge-lint: disable-next-line(unsafe-typecast)
|
|
218
|
+
activeVotes = _tierActiveSupplyCheckpointsOf[tierId].upperLookupRecent(uint96(_validateTimepoint(blockNumber)));
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
/// @notice The total owner-checkpointed voting units of a tier at a past block.
|
|
222
|
+
/// @param tierId The tier to get the owner-checkpointed voting units of.
|
|
168
223
|
/// @param blockNumber The block number to look up (must be strictly in the past).
|
|
169
|
-
/// @return The tier's
|
|
170
|
-
function getPastTierVotingUnits(
|
|
224
|
+
/// @return votingUnits The tier's owner-checkpointed voting units at `blockNumber`.
|
|
225
|
+
function getPastTierVotingUnits(
|
|
226
|
+
uint256 tierId,
|
|
227
|
+
uint256 blockNumber
|
|
228
|
+
)
|
|
229
|
+
external
|
|
230
|
+
view
|
|
231
|
+
override
|
|
232
|
+
returns (uint256 votingUnits)
|
|
233
|
+
{
|
|
171
234
|
// forge-lint: disable-next-line(unsafe-typecast)
|
|
172
|
-
|
|
235
|
+
votingUnits = _tierEligibleUnitsOf[tierId].upperLookupRecent(uint96(_validateTimepoint(blockNumber)));
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
/// @notice The total delegated voting units at a past block.
|
|
239
|
+
/// @dev This tracks delegated vote participation and is separate from tier reward eligibility.
|
|
240
|
+
/// @param blockNumber The past block number to look up.
|
|
241
|
+
/// @return activeVotes The total voting units delegated to nonzero delegates at `blockNumber`.
|
|
242
|
+
function getPastTotalActiveVotes(uint256 blockNumber) external view override returns (uint256 activeVotes) {
|
|
243
|
+
activeVotes = _activeSupplyCheckpoints.upperLookupRecent(_validateTimepoint(blockNumber));
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
/// @notice The current total delegated voting units of a tier.
|
|
247
|
+
/// @param tierId The tier to get the current delegated voting units of.
|
|
248
|
+
/// @return activeVotes The tier's current delegated voting units.
|
|
249
|
+
function getTierActiveVotes(uint256 tierId) external view override returns (uint256 activeVotes) {
|
|
250
|
+
activeVotes = _tierActiveSupplyCheckpointsOf[tierId].latest();
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
/// @notice The current total delegated voting units.
|
|
254
|
+
/// @dev This tracks delegated vote participation and is separate from tier reward eligibility.
|
|
255
|
+
/// @return activeVotes The current total voting units delegated to nonzero delegates.
|
|
256
|
+
function getTotalActiveVotes() external view override returns (uint256 activeVotes) {
|
|
257
|
+
activeVotes = _activeSupplyCheckpoints.latest();
|
|
173
258
|
}
|
|
174
259
|
|
|
175
260
|
/// @notice The owner of an NFT at a past block.
|
|
176
|
-
/// @dev Returns `address(0)`
|
|
177
|
-
/// transferred. Unenrolled tokens are ineligible for snapshot-based distribution.
|
|
261
|
+
/// @dev Returns `address(0)` if no ownership checkpoint exists or the query predates the first checkpoint.
|
|
178
262
|
/// @param tokenId The token ID of the NFT to get the historical owner of.
|
|
179
263
|
/// @param blockNumber The block number to look up.
|
|
180
|
-
/// @return The owner of the token at `blockNumber`, or zero if
|
|
181
|
-
function ownerOfAt(uint256 tokenId, uint256 blockNumber) external view override returns (address) {
|
|
264
|
+
/// @return owner The owner of the token at `blockNumber`, or zero if no owner is proven at that block.
|
|
265
|
+
function ownerOfAt(uint256 tokenId, uint256 blockNumber) external view override returns (address owner) {
|
|
182
266
|
// forge-lint: disable-next-line(unsafe-typecast)
|
|
183
267
|
uint96 blockNumber96 = uint96(blockNumber);
|
|
184
268
|
|
|
185
269
|
Checkpoints.Trace160 storage checkpoints = _ownerCheckpointsOf[tokenId];
|
|
186
270
|
uint256 checkpointCount = checkpoints.length();
|
|
187
271
|
|
|
188
|
-
// No checkpoints
|
|
272
|
+
// No checkpoints means no historical owner can be proven for this token.
|
|
189
273
|
if (checkpointCount == 0) return address(0);
|
|
190
274
|
|
|
191
|
-
// Query is before the first checkpoint
|
|
275
|
+
// Query is before the first checkpoint, so this token had no proven owner at that block.
|
|
192
276
|
if (checkpoints.at(0)._key > blockNumber96) return address(0);
|
|
193
277
|
|
|
194
278
|
return address(uint160(checkpoints.upperLookupRecent(blockNumber96)));
|
|
195
279
|
}
|
|
196
280
|
|
|
281
|
+
//*********************************************************************//
|
|
282
|
+
// ---------------------- internal transactions ---------------------- //
|
|
283
|
+
//*********************************************************************//
|
|
284
|
+
|
|
285
|
+
/// @notice Track active-vote-total changes when an account changes its delegate.
|
|
286
|
+
/// @dev Delegating to a nonzero address makes all of `account`'s voting units active. Clearing delegation removes
|
|
287
|
+
/// all of `account`'s voting units from the active total. Redelegating between two nonzero delegates only moves
|
|
288
|
+
/// votes inside OZ `Votes`, so the active total does not change.
|
|
289
|
+
/// @param account The account whose delegation is changing.
|
|
290
|
+
/// @param delegatee The new delegate. Use `address(0)` to clear delegation.
|
|
291
|
+
function _delegate(address account, address delegatee) internal virtual override {
|
|
292
|
+
// Read the current delegate before OZ mutates the delegate mapping.
|
|
293
|
+
address oldDelegate = delegates(account);
|
|
294
|
+
|
|
295
|
+
// Read the account's current voting units so any active-total delta matches the units OZ moves.
|
|
296
|
+
uint256 votingUnits = _getVotingUnits(account);
|
|
297
|
+
|
|
298
|
+
// Let OZ update the delegate mapping and the per-delegate vote checkpoints.
|
|
299
|
+
super._delegate({account: account, delegatee: delegatee});
|
|
300
|
+
|
|
301
|
+
// If the account had no delegate and now has one, its voting units just became active.
|
|
302
|
+
if (oldDelegate == address(0) && delegatee != address(0)) {
|
|
303
|
+
// Add the account's voting units to the checkpointed active total.
|
|
304
|
+
_updateActiveVotes({amount: votingUnits, increase: true});
|
|
305
|
+
|
|
306
|
+
// Add the account's voting units to each tier-level active total it currently contributes to.
|
|
307
|
+
_applyAccountDelegationToTierActiveVotes({account: account, increase: true});
|
|
308
|
+
} else if (oldDelegate != address(0) && delegatee == address(0)) {
|
|
309
|
+
// If the account had a delegate and now has none, its voting units just became inactive.
|
|
310
|
+
_updateActiveVotes({amount: votingUnits, increase: false});
|
|
311
|
+
|
|
312
|
+
// Remove the account's voting units from each tier-level active total it currently contributes to.
|
|
313
|
+
_applyAccountDelegationToTierActiveVotes({account: account, increase: false});
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
/// @notice Track active-vote-total changes when voting units move between accounts.
|
|
318
|
+
/// @dev Moving voting units between two accounts with the same delegation status does not change the active total.
|
|
319
|
+
/// Moving voting units out of a delegated account and into an undelegated account decreases the active total, while
|
|
320
|
+
/// moving voting units out of an undelegated account and into a delegated account increases it.
|
|
321
|
+
/// @param from The account whose voting units are leaving. `address(0)` means the units are being minted.
|
|
322
|
+
/// @param to The account whose voting units are arriving. `address(0)` means the units are being burned.
|
|
323
|
+
/// @param amount The voting units moving between `from` and `to`.
|
|
324
|
+
function _transferVotingUnits(address from, address to, uint256 amount) internal virtual override {
|
|
325
|
+
// The active total decreases if units leave an account that already has a nonzero delegate.
|
|
326
|
+
bool decreaseActiveVotes = from != address(0) && delegates(from) != address(0);
|
|
327
|
+
|
|
328
|
+
// The active total increases if units arrive at an account that already has a nonzero delegate.
|
|
329
|
+
bool increaseActiveVotes = to != address(0) && delegates(to) != address(0);
|
|
330
|
+
|
|
331
|
+
// Let OZ update total voting-unit supply and per-delegate checkpoints first.
|
|
332
|
+
super._transferVotingUnits({from: from, to: to, amount: amount});
|
|
333
|
+
|
|
334
|
+
// If both sides are delegated or both sides are undelegated, the active total is unchanged.
|
|
335
|
+
if (decreaseActiveVotes == increaseActiveVotes) return;
|
|
336
|
+
|
|
337
|
+
// Otherwise apply the one-sided active-total delta implied by the receiver's delegated status.
|
|
338
|
+
_updateActiveVotes({amount: amount, increase: increaseActiveVotes});
|
|
339
|
+
}
|
|
340
|
+
|
|
197
341
|
//*********************************************************************//
|
|
198
342
|
// ----------------------- internal views ---------------------------- //
|
|
199
343
|
//*********************************************************************//
|
|
@@ -201,8 +345,8 @@ contract JB721Checkpoints is Votes, IJB721Checkpoints {
|
|
|
201
345
|
/// @notice Returns the total voting units held by an account (across all tiers).
|
|
202
346
|
/// @dev Called by OZ Votes when re-delegating to compute the account's total voting units.
|
|
203
347
|
/// @param account The address to get the voting units of.
|
|
204
|
-
/// @return The total voting units the account holds.
|
|
205
|
-
function _getVotingUnits(address account) internal view override returns (uint256) {
|
|
348
|
+
/// @return votingUnits The total voting units the account holds.
|
|
349
|
+
function _getVotingUnits(address account) internal view override returns (uint256 votingUnits) {
|
|
206
350
|
return STORE.votingUnitsOf({hook: hook, account: account});
|
|
207
351
|
}
|
|
208
352
|
|
|
@@ -210,13 +354,83 @@ contract JB721Checkpoints is Votes, IJB721Checkpoints {
|
|
|
210
354
|
// ------------------------ private helpers -------------------------- //
|
|
211
355
|
//*********************************************************************//
|
|
212
356
|
|
|
213
|
-
/// @notice Add or remove units from a tier's
|
|
214
|
-
/// @param tierId The tier whose
|
|
357
|
+
/// @notice Add or remove units from a tier's active-voting-units checkpoint at the current block.
|
|
358
|
+
/// @param tierId The tier whose active-voting-units trace to update.
|
|
359
|
+
/// @param amount The voting units to add or remove.
|
|
360
|
+
/// @param increase Whether to add `amount`; if false, `amount` is removed.
|
|
361
|
+
function _adjustTierActiveVotes(uint256 tierId, uint256 amount, bool increase) private {
|
|
362
|
+
// Ignore zero-unit updates because they do not change this tier's active total.
|
|
363
|
+
if (amount == 0) return;
|
|
364
|
+
|
|
365
|
+
// Keep a reference to the tier's active-voting-units trace.
|
|
366
|
+
Checkpoints.Trace160 storage trace = _tierActiveSupplyCheckpointsOf[tierId];
|
|
367
|
+
|
|
368
|
+
// Calculate the next tier active total by adding or subtracting from the latest checkpointed value.
|
|
369
|
+
uint256 updated = increase ? trace.latest() + amount : trace.latest() - amount;
|
|
370
|
+
|
|
371
|
+
// Write the new tier active total at the current block.
|
|
372
|
+
// forge-lint: disable-next-line(unsafe-typecast)
|
|
373
|
+
trace.push({key: uint96(block.number), value: uint160(updated)});
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
/// @notice Apply an account delegation change to every tier-level active total the account contributes to.
|
|
377
|
+
/// @dev Delegation is account-wide in OZ Votes, so changing an account's delegate activates or deactivates every
|
|
378
|
+
/// tier balance currently held by the account. Transfer hooks handle one-token tier deltas separately.
|
|
379
|
+
/// @param account The account whose tier voting units should be added or removed.
|
|
380
|
+
/// @param increase Whether to add `account`'s tier voting units; if false, they are removed.
|
|
381
|
+
function _applyAccountDelegationToTierActiveVotes(address account, bool increase) private {
|
|
382
|
+
// Read the largest tier ID once; tier IDs are sequential and 1-indexed for each hook.
|
|
383
|
+
uint256 tierId = STORE.maxTierIdOf(hook);
|
|
384
|
+
|
|
385
|
+
// Walk each tier from max to 1 so empty hooks skip cleanly when maxTierId is zero.
|
|
386
|
+
while (tierId != 0) {
|
|
387
|
+
// Read only this account's units for the tier; empty tiers return zero and are ignored below.
|
|
388
|
+
uint256 tierVotingUnits = STORE.tierVotingUnitsOf({hook: hook, account: account, tierId: tierId});
|
|
389
|
+
|
|
390
|
+
// Only write checkpoints for tiers whose active totals actually change.
|
|
391
|
+
if (tierVotingUnits != 0) {
|
|
392
|
+
_adjustTierActiveVotes({tierId: tierId, amount: tierVotingUnits, increase: increase});
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
unchecked {
|
|
396
|
+
// The loop condition proves tierId is nonzero, so the decrement cannot underflow.
|
|
397
|
+
--tierId;
|
|
398
|
+
}
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
/// @notice Update the checkpointed total of delegated voting units.
|
|
403
|
+
/// @dev Writes at most one active-total checkpoint at the current OZ clock. A zero amount is ignored so zero-value
|
|
404
|
+
/// delegation or transfer hooks do not create empty checkpoints.
|
|
405
|
+
/// @param amount The amount of voting units to add or remove.
|
|
406
|
+
/// @param increase Whether to add `amount`; if false, `amount` is removed.
|
|
407
|
+
function _updateActiveVotes(uint256 amount, bool increase) private {
|
|
408
|
+
// Ignore zero-unit updates because they do not change the active total.
|
|
409
|
+
if (amount == 0) return;
|
|
410
|
+
|
|
411
|
+
// Calculate the next active total by adding or subtracting from the latest checkpointed value.
|
|
412
|
+
uint256 updated =
|
|
413
|
+
increase ? _activeSupplyCheckpoints.latest() + amount : _activeSupplyCheckpoints.latest() - amount;
|
|
414
|
+
|
|
415
|
+
// Write the new active total at the current ERC-6372 clock using the same uint208 width as OZ `Votes`.
|
|
416
|
+
_activeSupplyCheckpoints.push({key: clock(), value: SafeCast.toUint208(updated)});
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
/// @notice Add or remove units from a tier's owner-tracked voting-units checkpoint at the current block.
|
|
420
|
+
/// @param tierId The tier whose owner-tracked voting-units trace to update.
|
|
215
421
|
/// @param amount The voting units to add or remove.
|
|
216
422
|
/// @param increase Whether to add `amount`; if false, `amount` is removed.
|
|
217
423
|
function _updateTierEligibleUnits(uint256 tierId, uint256 amount, bool increase) private {
|
|
424
|
+
// Ignore zero-unit updates because they do not change this tier's owner-tracked total.
|
|
425
|
+
if (amount == 0) return;
|
|
426
|
+
|
|
427
|
+
// Keep a reference to the tier's owner-tracked voting-units trace.
|
|
218
428
|
Checkpoints.Trace160 storage trace = _tierEligibleUnitsOf[tierId];
|
|
429
|
+
|
|
430
|
+
// Calculate the next owner-tracked total by adding or subtracting from the latest checkpointed value.
|
|
219
431
|
uint256 updated = increase ? trace.latest() + amount : trace.latest() - amount;
|
|
432
|
+
|
|
433
|
+
// Write the new owner-tracked total at the current block.
|
|
220
434
|
// forge-lint: disable-next-line(unsafe-typecast)
|
|
221
435
|
trace.push({key: uint96(block.number), value: uint160(updated)});
|
|
222
436
|
}
|
package/src/JB721TiersHook.sol
CHANGED
|
@@ -244,8 +244,7 @@ contract JB721TiersHook is JBOwnable, ERC2771Context, JB721Hook, IJB721TiersHook
|
|
|
244
244
|
/// @notice Initialize a cloned copy of the hook. Sets the project association, ERC-721 name/symbol, pricing
|
|
245
245
|
/// context (currency + decimals), metadata URIs, initial tiers, and behavioral flags. Can only be called once
|
|
246
246
|
/// per clone — the implementation contract is pre-initialized in its constructor to prevent misuse.
|
|
247
|
-
/// @dev Called by `JB721TiersHookDeployer`
|
|
248
|
-
/// project ID is zero.
|
|
247
|
+
/// @dev Called by `JB721TiersHookDeployer` after cloning. Reverts if called twice or if the project ID is zero.
|
|
249
248
|
/// @param initialProjectId The ID of the project this hook is associated with.
|
|
250
249
|
/// @param name The name of the NFT collection.
|
|
251
250
|
/// @param symbol The symbol representing the NFT collection.
|
|
@@ -461,9 +460,8 @@ contract JB721TiersHook is JBOwnable, ERC2771Context, JB721Hook, IJB721TiersHook
|
|
|
461
460
|
/// @param symbol The new collection symbol. Send empty to leave unchanged.
|
|
462
461
|
/// @param baseUri The new base URI. Send empty to leave unchanged.
|
|
463
462
|
/// @param contractUri The new contract URI. Send empty to leave unchanged.
|
|
464
|
-
/// @param tokenUriResolver The new URI resolver. Pass `IJB721TokenUriResolver(address(this))`
|
|
465
|
-
///
|
|
466
|
-
/// clears the resolver.
|
|
463
|
+
/// @param tokenUriResolver The new URI resolver. Pass `IJB721TokenUriResolver(address(this))` to leave it
|
|
464
|
+
/// unchanged; `address(0)` clears the resolver.
|
|
467
465
|
/// @param encodedIpfsUriTierId The ID of the tier to set the encoded IPFS URI of.
|
|
468
466
|
/// @param encodedIpfsUri The encoded IPFS URI to set.
|
|
469
467
|
function setMetadata(
|
|
@@ -114,8 +114,7 @@ contract JB721TiersHookProjectDeployer is
|
|
|
114
114
|
}
|
|
115
115
|
|
|
116
116
|
/// @notice Launches rulesets for a project with an attached 721 tiers hook.
|
|
117
|
-
/// @dev Only a project's owner or an operator with
|
|
118
|
-
/// rulesets.
|
|
117
|
+
/// @dev Only a project's owner or an operator with `LAUNCH_RULESETS & SET_TERMINALS` can launch its rulesets.
|
|
119
118
|
/// @param projectId The ID of the project to launch rulesets for.
|
|
120
119
|
/// @param deployTiersHookConfig Configuration which dictates the behavior of the 721 tiers hook to deploy.
|
|
121
120
|
/// @param launchRulesetsConfig Configuration which dictates the project's new rulesets.
|
|
@@ -233,8 +232,7 @@ contract JB721TiersHookProjectDeployer is
|
|
|
233
232
|
//*********************************************************************//
|
|
234
233
|
|
|
235
234
|
/// @notice Configure and launch rulesets for a newly created project. Converts `JBPayDataHookRulesetConfig` entries
|
|
236
|
-
/// into standard `JBRulesetConfig` entries
|
|
237
|
-
/// the data hook.
|
|
235
|
+
/// into standard `JBRulesetConfig` entries that use the deployed hook as their data hook.
|
|
238
236
|
/// @param projectId The ID of the reserved project.
|
|
239
237
|
/// @param launchProjectConfig Configuration which dictates the behavior of the project to launch.
|
|
240
238
|
/// @param dataHook The data hook to use for the project.
|
|
@@ -148,8 +148,7 @@ contract JB721TiersHookStore is IJB721TiersHookStore {
|
|
|
148
148
|
/// @custom:param hook The 721 contract to get the custom token URI resolver of.
|
|
149
149
|
mapping(address hook => IJB721TokenUriResolver) public override tokenUriResolverOf;
|
|
150
150
|
|
|
151
|
-
/// @notice The combined cash-out weight of all
|
|
152
|
-
/// O(1) instead of O(maxTierId).
|
|
151
|
+
/// @notice The combined cash-out weight of all hook NFTs, tracked so cash-out pricing is O(1).
|
|
153
152
|
/// @dev Maintained incrementally in `recordMint` (+ the tier's full price for the new outstanding NFT plus any
|
|
154
153
|
/// newly-accrued pending reserve) and `recordBurn` (- the tier's full price). It is invariant under everything
|
|
155
154
|
/// else: reserve mints are weight-neutral (a pending reserve becomes an outstanding NFT), removed tiers keep
|
|
@@ -610,8 +609,7 @@ contract JB721TiersHookStore is IJB721TiersHookStore {
|
|
|
610
609
|
/// @param hook The 721 contract to get the tier from.
|
|
611
610
|
/// @param tierId The ID of the tier to get.
|
|
612
611
|
/// @param storedTier The stored tier to get the corresponding tier for.
|
|
613
|
-
/// @param includeResolvedUri If
|
|
614
|
-
/// resolved and included.
|
|
612
|
+
/// @param includeResolvedUri If true and the contract has a token URI resolver, resolve and include its content.
|
|
615
613
|
/// @return tier The tier as a `JB721Tier` struct.
|
|
616
614
|
function _getTierFrom(
|
|
617
615
|
address hook,
|
|
@@ -135,8 +135,7 @@ abstract contract JB721Hook is ERC721, IJB721Hook {
|
|
|
135
135
|
cashOutTaxRate = context.cashOutTaxRate;
|
|
136
136
|
}
|
|
137
137
|
|
|
138
|
-
/// @notice The data calculated before a payment is recorded in the terminal store
|
|
139
|
-
/// terminal's `pay(...)` transaction.
|
|
138
|
+
/// @notice The data calculated before a payment is recorded in the terminal store for `pay(...)`.
|
|
140
139
|
/// @dev Sets this contract as the pay hook. Part of `IJBRulesetDataHook`.
|
|
141
140
|
/// @param context The payment context passed to this contract by the `pay(...)` function.
|
|
142
141
|
/// @return weight The new `weight` to use, overriding the ruleset's `weight`.
|
|
@@ -164,8 +163,7 @@ abstract contract JB721Hook is ERC721, IJB721Hook {
|
|
|
164
163
|
// -------------------------- public views --------------------------- //
|
|
165
164
|
//*********************************************************************//
|
|
166
165
|
|
|
167
|
-
/// @notice Returns the cumulative cash out weight of the specified token IDs relative to
|
|
168
|
-
/// `totalCashOutWeight`.
|
|
166
|
+
/// @notice Returns the cumulative cash out weight of the specified token IDs relative to `totalCashOutWeight`.
|
|
169
167
|
/// @param tokenIds The NFT token IDs to calculate the cumulative cash out weight of.
|
|
170
168
|
/// @return The cumulative cash out weight of the specified token IDs.
|
|
171
169
|
function cashOutWeightOf(uint256[] memory tokenIds) public view virtual returns (uint256) {
|
|
@@ -248,8 +246,7 @@ abstract contract JB721Hook is ERC721, IJB721Hook {
|
|
|
248
246
|
_didBurn(decodedTokenIds);
|
|
249
247
|
}
|
|
250
248
|
|
|
251
|
-
/// @notice Mints
|
|
252
|
-
/// `IJBPayHook`.
|
|
249
|
+
/// @notice Mints NFTs to `context.beneficiary` upon payment if conditions are met. Part of `IJBPayHook`.
|
|
253
250
|
/// @dev Reverts if the calling contract is not one of the project's terminals.
|
|
254
251
|
/// @param context The payment context passed in by the terminal.
|
|
255
252
|
function afterPayRecordedWith(JBAfterPayRecordedContext calldata context) external payable virtual override {
|
|
@@ -1,46 +1,56 @@
|
|
|
1
1
|
// SPDX-License-Identifier: MIT
|
|
2
2
|
pragma solidity ^0.8.0;
|
|
3
3
|
|
|
4
|
+
import {IJBActiveVotes} from "@bananapus/core-v6/src/interfaces/IJBActiveVotes.sol";
|
|
4
5
|
import {IERC5805} from "@openzeppelin/contracts/interfaces/IERC5805.sol";
|
|
6
|
+
|
|
5
7
|
import {IJB721TiersHookStore} from "./IJB721TiersHookStore.sol";
|
|
6
8
|
|
|
7
9
|
/// @notice A checkpoint module that provides IVotes-compatible checkpointed voting power for a JB721TiersHook.
|
|
8
10
|
/// @dev Deployed as a clone via JB721CheckpointsDeployer during hook initialization. One module per hook.
|
|
9
11
|
/// Pass this address to JBTokenDistributor as the IVotes token.
|
|
10
|
-
interface IJB721Checkpoints is IERC5805 {
|
|
11
|
-
/// @notice The
|
|
12
|
-
/// @
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
12
|
+
interface IJB721Checkpoints is IERC5805, IJBActiveVotes {
|
|
13
|
+
/// @notice The store that holds tier and voting data for the hook's NFTs.
|
|
14
|
+
/// @return store The store contract.
|
|
15
|
+
// forge-lint: disable-next-line(mixed-case-function)
|
|
16
|
+
function STORE() external view returns (IJB721TiersHookStore store);
|
|
17
|
+
|
|
18
|
+
/// @notice The total delegated voting units of a tier at a past block.
|
|
19
|
+
/// @dev Counts only tier voting units held by accounts with a nonzero delegate.
|
|
20
|
+
/// @param tierId The tier to get the delegated voting units of.
|
|
21
|
+
/// @param blockNumber The past block number to look up.
|
|
22
|
+
/// @return activeVotes The tier's delegated voting units at `blockNumber`.
|
|
23
|
+
function getPastTierActiveVotes(uint256 tierId, uint256 blockNumber) external view returns (uint256 activeVotes);
|
|
24
|
+
|
|
25
|
+
/// @notice The total owner-checkpointed voting units of a tier at a past block.
|
|
26
|
+
/// @dev Owner-checkpointed voting units are the tier's total owned units, regardless of delegation status.
|
|
27
|
+
/// @param tierId The tier to get the owner-checkpointed voting units of.
|
|
16
28
|
/// @param blockNumber The block number to look up (must be strictly in the past).
|
|
17
|
-
/// @return The tier's
|
|
18
|
-
function getPastTierVotingUnits(uint256 tierId, uint256 blockNumber) external view returns (uint256);
|
|
29
|
+
/// @return votingUnits The tier's owner-checkpointed voting units at `blockNumber`.
|
|
30
|
+
function getPastTierVotingUnits(uint256 tierId, uint256 blockNumber) external view returns (uint256 votingUnits);
|
|
31
|
+
|
|
32
|
+
/// @notice The current total delegated voting units of a tier.
|
|
33
|
+
/// @param tierId The tier to get the current delegated voting units of.
|
|
34
|
+
/// @return activeVotes The tier's current delegated voting units.
|
|
35
|
+
function getTierActiveVotes(uint256 tierId) external view returns (uint256 activeVotes);
|
|
19
36
|
|
|
20
37
|
/// @notice The hook that this module tracks voting power for.
|
|
21
|
-
/// @return The hook address.
|
|
38
|
+
/// @return hookAddress The hook address.
|
|
22
39
|
// forge-lint: disable-next-line(mixed-case-function)
|
|
23
|
-
function hook() external view returns (address);
|
|
40
|
+
function hook() external view returns (address hookAddress);
|
|
24
41
|
|
|
25
42
|
/// @notice The owner of an NFT at a past block.
|
|
26
|
-
/// @dev Returns `address(0)`
|
|
27
|
-
/// transferred. Unenrolled tokens are ineligible for snapshot-based distribution.
|
|
43
|
+
/// @dev Returns `address(0)` if no ownership checkpoint exists or the query predates the first checkpoint.
|
|
28
44
|
/// @param tokenId The token ID of the NFT to get the historical owner of.
|
|
29
45
|
/// @param blockNumber The block number to look up.
|
|
30
|
-
/// @return The owner of the token at `blockNumber`, or zero if
|
|
31
|
-
function ownerOfAt(uint256 tokenId, uint256 blockNumber) external view returns (address);
|
|
32
|
-
|
|
33
|
-
/// @notice The store that holds tier and voting data for the hook's NFTs.
|
|
34
|
-
/// @return The store contract.
|
|
35
|
-
// forge-lint: disable-next-line(mixed-case-function)
|
|
36
|
-
function STORE() external view returns (IJB721TiersHookStore);
|
|
46
|
+
/// @return owner The owner of the token at `blockNumber`, or zero if no owner is proven at that block.
|
|
47
|
+
function ownerOfAt(uint256 tokenId, uint256 blockNumber) external view returns (address owner);
|
|
37
48
|
|
|
38
|
-
/// @notice Delegates voting power and
|
|
39
|
-
/// @dev
|
|
40
|
-
///
|
|
41
|
-
/// distribution.
|
|
49
|
+
/// @notice Delegates voting power and backfills ownership history for listed tokens if needed.
|
|
50
|
+
/// @dev Mint and transfer hooks normally write owner checkpoints automatically. The token ID list keeps
|
|
51
|
+
/// pre-upgrade or otherwise uncheckpointed tokens recoverable while preserving the owner-only authorization check.
|
|
42
52
|
/// @param delegatee The address to delegate voting power to. Use your own address for self-delegation.
|
|
43
|
-
/// @param tokenIds The token IDs
|
|
53
|
+
/// @param tokenIds The token IDs whose owner checkpoints should be backfilled if missing.
|
|
44
54
|
function delegate(address delegatee, uint256[] calldata tokenIds) external;
|
|
45
55
|
|
|
46
56
|
/// @notice Initializes a cloned module with its hook reference.
|
|
@@ -96,8 +96,7 @@ interface IJB721TiersHook is IJB721Hook {
|
|
|
96
96
|
/// @param caller The address that called the function.
|
|
97
97
|
event SetTokenUriResolver(IJB721TokenUriResolver indexed resolver, address caller);
|
|
98
98
|
|
|
99
|
-
/// @notice Emitted when a split payout reverts during distribution
|
|
100
|
-
/// project's balance.
|
|
99
|
+
/// @notice Emitted when a split payout reverts during distribution, routing funds to the project's balance.
|
|
101
100
|
/// @param projectId The project ID the split belongs to.
|
|
102
101
|
/// @param split The split that reverted.
|
|
103
102
|
/// @param amount The amount that was paid out.
|
|
@@ -213,9 +212,8 @@ interface IJB721TiersHook is IJB721Hook {
|
|
|
213
212
|
/// @param symbol The new collection symbol. Send empty to leave unchanged.
|
|
214
213
|
/// @param baseUri The new base URI. Send empty to leave unchanged.
|
|
215
214
|
/// @param contractUri The new contract URI. Send empty to leave unchanged.
|
|
216
|
-
/// @param tokenUriResolver The new URI resolver. Pass `IJB721TokenUriResolver(address(this))`
|
|
217
|
-
///
|
|
218
|
-
/// clears the resolver.
|
|
215
|
+
/// @param tokenUriResolver The new URI resolver. Pass `IJB721TokenUriResolver(address(this))` to leave it
|
|
216
|
+
/// unchanged; `address(0)` clears the resolver.
|
|
219
217
|
/// @param encodedIpfsUriTierId The ID of the tier to set the encoded IPFS URI of.
|
|
220
218
|
/// @param encodedIpfsUri The encoded IPFS URI to set.
|
|
221
219
|
function setMetadata(
|
|
@@ -73,8 +73,7 @@ library JB721TiersHookLib {
|
|
|
73
73
|
/// @param caller The address that called the function.
|
|
74
74
|
event SetDiscountPercent(uint256 indexed tierId, uint256 discountPercent, address caller);
|
|
75
75
|
|
|
76
|
-
/// @notice Emitted when a split payout reverts during distribution
|
|
77
|
-
/// project's balance.
|
|
76
|
+
/// @notice Emitted when a split payout reverts during distribution, routing funds to the project's balance.
|
|
78
77
|
/// @param projectId The project ID the split belongs to.
|
|
79
78
|
/// @param split The split that reverted.
|
|
80
79
|
/// @param amount The amount that was paid out.
|
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
// SPDX-License-Identifier: MIT
|
|
2
2
|
pragma solidity ^0.8.0;
|
|
3
3
|
|
|
4
|
-
/// @notice `JB721TiersHook` options
|
|
5
|
-
/// per-ruleset basis.
|
|
4
|
+
/// @notice `JB721TiersHook` options packed into each ruleset's `JBRulesetMetadata.metadata`.
|
|
6
5
|
/// @custom:member pauseTransfers A boolean indicating whether NFT transfers are paused during this ruleset.
|
|
7
6
|
/// @custom:member pauseMintPendingReserves A boolean indicating whether pending/outstanding NFT reserves can be minted
|
|
8
7
|
/// during this ruleset.
|
|
@@ -5,8 +5,7 @@ import {JBTerminalConfig} from "@bananapus/core-v6/src/structs/JBTerminalConfig.
|
|
|
5
5
|
|
|
6
6
|
import {JBPayDataHookRulesetConfig} from "./JBPayDataHookRulesetConfig.sol";
|
|
7
7
|
|
|
8
|
-
/// @custom:member projectUri Metadata URI to associate with the project
|
|
9
|
-
/// the project.
|
|
8
|
+
/// @custom:member projectUri Metadata URI to associate with the project, updatable by the project owner.
|
|
10
9
|
/// @custom:member rulesetConfigurations The ruleset configurations to queue.
|
|
11
10
|
/// @custom:member terminalConfigurations The terminal configurations to add for the project.
|
|
12
11
|
/// @custom:member memo A memo to pass along to the emitted event.
|
|
@@ -14,17 +14,14 @@ import {JBPayDataHookRulesetMetadata} from "./JBPayDataHookRulesetMetadata.sol";
|
|
|
14
14
|
/// a project owner cannot make changes to a ruleset's parameters while it is active – any proposed changes will apply
|
|
15
15
|
/// to the subsequent ruleset. If no changes are proposed, a ruleset rolls over to another one with the same properties
|
|
16
16
|
/// but new `start` timestamp and a decayed `weight`.
|
|
17
|
-
/// @custom:member weight A fixed point number with 18 decimals that contracts can use
|
|
18
|
-
///
|
|
19
|
-
///
|
|
20
|
-
///
|
|
21
|
-
/// the
|
|
22
|
-
/// project owner hasn't queued the subsequent ruleset with an explicit `weight`. If it's 0, each ruleset will have
|
|
23
|
-
/// equal weight. If the number is 90%, the next ruleset will have a 10% smaller weight. This weight is out of
|
|
17
|
+
/// @custom:member weight A fixed point number with 18 decimals that contracts can use for calculations. For example,
|
|
18
|
+
/// payment terminals can use this to determine how many tokens should be minted when a payment is received.
|
|
19
|
+
/// @custom:member weightCutPercent A percent by how much the `weight` of the subsequent ruleset should be reduced if
|
|
20
|
+
/// the project owner hasn't queued the subsequent ruleset with an explicit `weight`. If it's 0, each ruleset will
|
|
21
|
+
/// have equal weight. If the number is 90%, the next ruleset will have a 10% smaller weight. This weight is out of
|
|
24
22
|
/// `JBConstants.MAX_WEIGHT_CUT_PERCENT`.
|
|
25
|
-
/// @custom:member approvalHook
|
|
26
|
-
///
|
|
27
|
-
/// can be used to create rules around how a project owner can change ruleset parameters over time.
|
|
23
|
+
/// @custom:member approvalHook A contract that says whether a proposed ruleset should be accepted or rejected. It can
|
|
24
|
+
/// create rules around how a project owner changes ruleset parameters over time.
|
|
28
25
|
/// @custom:member metadata Metadata specifying the controller-specific parameters that a ruleset can have. These
|
|
29
26
|
/// properties cannot change until the next ruleset starts.
|
|
30
27
|
/// @custom:member splitGroups An array of splits to use for any number of groups while the ruleset is active.
|
|
@@ -13,12 +13,10 @@ pragma solidity ^0.8.0;
|
|
|
13
13
|
/// permission from the owner should be allowed to mint project tokens on demand during this ruleset.
|
|
14
14
|
/// @custom:member allowSetCustomToken A flag indicating if the project owner can set the project's token to a custom
|
|
15
15
|
/// ERC-20.
|
|
16
|
-
/// @custom:member allowTerminalMigration A flag indicating if
|
|
17
|
-
/// ruleset.
|
|
16
|
+
/// @custom:member allowTerminalMigration A flag indicating if terminal migration is allowed during this ruleset.
|
|
18
17
|
/// @custom:member allowSetTerminals A flag indicating if a project's terminals can be added or removed.
|
|
19
18
|
/// @custom:member allowSetController A flag indicating if a project's controller can be changed.
|
|
20
|
-
/// @custom:member allowAddAccountingContext A flag indicating if a project can add
|
|
21
|
-
/// terminals to use.
|
|
19
|
+
/// @custom:member allowAddAccountingContext A flag indicating if a project can add accounting contexts to terminals.
|
|
22
20
|
/// @custom:member allowAddPriceFeed A flag indicating if a project can add new price feeds to calculate exchange rates
|
|
23
21
|
/// between its tokens.
|
|
24
22
|
/// @custom:member ownerMustSendPayouts A flag indicating if the owner must manually trigger payout distributions.
|