@bananapus/core-v6 0.0.38 → 0.0.40

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.
Files changed (56) hide show
  1. package/foundry.toml +1 -1
  2. package/package.json +1 -1
  3. package/src/JBChainlinkV3PriceFeed.sol +4 -1
  4. package/src/JBChainlinkV3SequencerPriceFeed.sol +4 -2
  5. package/src/JBController.sol +67 -58
  6. package/src/JBDeadline.sol +4 -4
  7. package/src/JBDirectory.sol +38 -36
  8. package/src/JBERC20.sol +10 -9
  9. package/src/JBFeelessAddresses.sol +6 -3
  10. package/src/JBFundAccessLimits.sol +26 -22
  11. package/src/JBMultiTerminal.sol +128 -125
  12. package/src/JBPermissions.sol +35 -38
  13. package/src/JBPrices.sol +25 -20
  14. package/src/JBProjects.sol +6 -3
  15. package/src/JBRulesets.sol +45 -42
  16. package/src/JBSplits.sol +19 -17
  17. package/src/JBTerminalStore.sol +57 -50
  18. package/src/JBTokens.sol +42 -32
  19. package/src/abstract/JBControlled.sol +3 -1
  20. package/src/abstract/JBPermissioned.sol +3 -1
  21. package/src/enums/JBApprovalStatus.sol +7 -1
  22. package/src/interfaces/IJBCashOutTerminal.sol +5 -5
  23. package/src/interfaces/IJBController.sol +13 -12
  24. package/src/interfaces/IJBDirectory.sol +3 -1
  25. package/src/interfaces/IJBMigratable.sol +5 -5
  26. package/src/interfaces/IJBMultiTerminal.sol +3 -2
  27. package/src/interfaces/IJBPayoutTerminal.sol +3 -3
  28. package/src/interfaces/IJBPermissions.sol +6 -5
  29. package/src/interfaces/IJBPermitTerminal.sol +1 -1
  30. package/src/interfaces/IJBPrices.sol +7 -5
  31. package/src/interfaces/IJBRulesets.sol +2 -1
  32. package/src/interfaces/IJBSplits.sol +2 -1
  33. package/src/interfaces/IJBTerminal.sol +13 -11
  34. package/src/interfaces/IJBTerminalStore.sol +23 -21
  35. package/src/interfaces/IJBTokens.sol +8 -7
  36. package/src/libraries/JBCashOuts.sol +8 -3
  37. package/src/libraries/JBConstants.sol +12 -3
  38. package/src/libraries/JBCurrencyIds.sol +2 -0
  39. package/src/libraries/JBFees.sol +5 -1
  40. package/src/libraries/JBFixedPointNumber.sol +2 -0
  41. package/src/libraries/JBPayoutSplitGroupLib.sol +10 -7
  42. package/src/libraries/JBRulesetMetadataResolver.sol +4 -0
  43. package/src/libraries/JBSplitGroupIds.sol +2 -1
  44. package/src/libraries/JBSurplus.sol +4 -2
  45. package/src/periphery/JBMatchingPriceFeed.sol +2 -0
  46. package/src/structs/JBAccountingContext.sol +7 -4
  47. package/src/structs/JBAfterCashOutRecordedContext.sol +8 -8
  48. package/src/structs/JBAfterPayRecordedContext.sol +5 -5
  49. package/src/structs/JBBeforeCashOutRecordedContext.sol +7 -7
  50. package/src/structs/JBBeforePayRecordedContext.sol +5 -5
  51. package/src/structs/JBFundAccessLimitGroup.sol +10 -17
  52. package/src/structs/JBPermissionsData.sol +3 -3
  53. package/src/structs/JBRuleset.sol +18 -26
  54. package/src/structs/JBRulesetConfig.sol +13 -25
  55. package/src/structs/JBRulesetMetadata.sol +25 -32
  56. package/src/structs/JBSplitHookContext.sol +2 -2
@@ -7,8 +7,11 @@ import {ERC2771Context} from "@openzeppelin/contracts/metatx/ERC2771Context.sol"
7
7
  import {IJBPermissions} from "./interfaces/IJBPermissions.sol";
8
8
  import {JBPermissionsData} from "./structs/JBPermissionsData.sol";
9
9
 
10
- /// @notice Stores permissions for all addresses and operators. Addresses can give permissions to any other address
11
- /// (i.e. an *operator*) to execute specific operations on their behalf.
10
+ /// @notice The permission system for Juicebox. Any address can authorize another address (an "operator") to perform
11
+ /// specific actions on its behalf like queuing rulesets, distributing payouts, or managing terminals.
12
+ /// @dev Permissions are stored as a packed `uint256` bitmap: each of the 256 bits represents one permission's
13
+ /// on/off state. Project ID 0 is a wildcard that grants an operator access across all projects — use with caution.
14
+ /// @dev The ROOT permission (ID 1) implicitly grants every other permission for the scoped project.
12
15
  contract JBPermissions is ERC2771Context, IJBPermissions {
13
16
  //*********************************************************************//
14
17
  // --------------------------- custom errors ------------------------- //
@@ -23,21 +26,21 @@ contract JBPermissions is ERC2771Context, IJBPermissions {
23
26
  // ------------------------- public constants ------------------------ //
24
27
  //*********************************************************************//
25
28
 
26
- /// @notice The project ID considered a wildcard, meaning it will grant permissions to all projects.
29
+ /// @notice The project ID that acts as a wildcard granting the operator permissions across all projects owned
30
+ /// by
31
+ /// the account. Setting permissions for project ID 0 is powerful and should be done carefully.
27
32
  uint256 public constant override WILDCARD_PROJECT_ID = 0;
28
33
 
29
34
  //*********************************************************************//
30
35
  // --------------------- public stored properties -------------------- //
31
36
  //*********************************************************************//
32
37
 
33
- /// @notice The permissions that an operator has been given by an account for a specific project.
34
- /// @dev An account can give an operator permissions that only pertain to a specific project ID.
35
- /// @dev There is no project with a ID of 0 – this ID is a wildcard which gives an operator permissions pertaining
36
- /// to *all* project IDs on an account's behalf. Use this with caution.
37
- /// @dev Permissions are stored in a packed `uint256`. Each of the 256 bits represents the on/off state of a
38
- /// permission. Applications can specify the significance of each permission ID.
38
+ /// @notice The packed permission bitmap that an account has granted to an operator for a specific project.
39
+ /// @dev Each bit in the returned `uint256` corresponds to a permission ID (0–255). A `1` bit means that
40
+ /// permission
41
+ /// is granted. See `JBPermissionIds` for the meaning of each ID.
39
42
  /// @custom:param operator The address of the operator.
40
- /// @custom:param account The address of the account being operated on behalf of.
43
+ /// @custom:param account The address of the account operated on behalf of.
41
44
  /// @custom:param projectId The project ID the permissions are scoped to. An ID of 0 grants permissions across all
42
45
  /// projects.
43
46
  mapping(address operator => mapping(address account => mapping(uint256 projectId => uint256)))
@@ -55,14 +58,12 @@ contract JBPermissions is ERC2771Context, IJBPermissions {
55
58
  // ---------------------- external transactions ---------------------- //
56
59
  //*********************************************************************//
57
60
 
58
- /// @notice Sets permissions for an operator.
59
- /// @dev Only the `account` itself (i.e. `msg.sender == account`) can grant or revoke its operators' permissions
60
- /// without restriction including ROOT on the wildcard project ID (`projectId = 0`).
61
- /// @dev A third-party caller who holds ROOT for a specific project may set *non-ROOT* permissions for that project
62
- /// on someone else's behalf, but **cannot**: (a) grant ROOT to others, or (b) set any permissions on the wildcard
63
- /// project ID. This prevents ROOT operators from escalating their own privileges.
64
- /// @param account The account setting its operators' permissions.
65
- /// @param permissionsData The data which specifies the permissions the operator is being given.
61
+ /// @notice Grant or revoke permissions for an operator on a specific project.
62
+ /// @dev Only the account itself can set permissions without restriction. A ROOT operator on a specific project can
63
+ /// set non-ROOT permissions for that same project on the account's behalf, but cannot grant ROOT or set wildcard
64
+ /// (project ID 0) permissions preventing privilege escalation.
65
+ /// @param account The account to configure operator permissions for.
66
+ /// @param permissionsData The operator address, project scope, and permission IDs to set.
66
67
  function setPermissionsFor(address account, JBPermissionsData calldata permissionsData) external override {
67
68
  // Pack the permission IDs into a uint256.
68
69
  uint256 packed = _packedPermissions(permissionsData.permissionIds);
@@ -113,16 +114,14 @@ contract JBPermissions is ERC2771Context, IJBPermissions {
113
114
  // ------------------------- external views -------------------------- //
114
115
  //*********************************************************************//
115
116
 
116
- /// @notice Check if an operator has all of the specified permissions for a specific address and project ID.
117
- /// @param operator The operator to check.
118
- /// @param account The account being operated on behalf of.
119
- /// @param projectId The project ID that the operator has permission to operate under. 0 represents all projects.
120
- /// @param permissionIds An array of permission IDs to check for.
121
- /// @param includeRoot A flag indicating if the return value should default to true if the operator has the ROOT
122
- /// permission.
123
- /// @param includeWildcardProjectId A flag indicating if the return value should return true if the operator has the
124
- /// specified permission on the wildcard project ID.
125
- /// @return A flag indicating whether the operator has all specified permissions.
117
+ /// @notice Check whether an operator has *all* of the specified permissions for an account and project.
118
+ /// @param operator The address to check permissions for.
119
+ /// @param account The account that granted (or didn't grant) the permissions.
120
+ /// @param projectId The project ID to check within. Pass 0 to check the wildcard scope directly.
121
+ /// @param permissionIds The permission IDs that must all be present.
122
+ /// @param includeRoot If `true`, returns `true` immediately when the operator has the ROOT permission.
123
+ /// @param includeWildcardProjectId If `true`, also checks wildcard (project 0) permissions as a fallback.
124
+ /// @return Whether the operator holds every requested permission.
126
125
  function hasPermissions(
127
126
  address operator,
128
127
  address account,
@@ -187,16 +186,14 @@ contract JBPermissions is ERC2771Context, IJBPermissions {
187
186
  // -------------------------- public views --------------------------- //
188
187
  //*********************************************************************//
189
188
 
190
- /// @notice Check if an operator has a specific permission for a specific address and project ID.
191
- /// @param operator The operator to check.
192
- /// @param account The account being operated on behalf of.
193
- /// @param projectId The project ID that the operator has permission to operate under. 0 represents all projects.
194
- /// @param permissionId The permission ID to check for.
195
- /// @param includeRoot A flag indicating if the return value should default to true if the operator has the ROOT
196
- /// permission.
197
- /// @param includeWildcardProjectId A flag indicating if the return value should return true if the operator has the
198
- /// specified permission on the wildcard project ID.
199
- /// @return A flag indicating whether the operator has the specified permission.
189
+ /// @notice Check whether an operator has a single permission for an account and project.
190
+ /// @param operator The address to check.
191
+ /// @param account The account that granted (or didn't grant) the permission.
192
+ /// @param projectId The project ID to check within. Pass 0 to check the wildcard scope directly.
193
+ /// @param permissionId The specific permission ID to look for (0–255).
194
+ /// @param includeRoot If `true`, returns `true` immediately when the operator has the ROOT permission.
195
+ /// @param includeWildcardProjectId If `true`, also checks wildcard (project 0) permissions as a fallback.
196
+ /// @return Whether the operator holds the requested permission.
200
197
  function hasPermission(
201
198
  address operator,
202
199
  address account,
package/src/JBPrices.sol CHANGED
@@ -13,12 +13,13 @@ import {IJBPriceFeed} from "./interfaces/IJBPriceFeed.sol";
13
13
  import {IJBPrices} from "./interfaces/IJBPrices.sol";
14
14
  import {IJBProjects} from "./interfaces/IJBProjects.sol";
15
15
 
16
- /// @notice Manages and normalizes price feeds. Price feeds are contracts which return the "pricing currency" cost of 1
17
- /// "unit currency".
18
- /// @dev Price feeds are immutable once set and cannot be replaced or removed. This prevents oracle manipulation via
19
- /// admin-key attacks, but means a misconfigured or failing feed will cause operations using that currency pair to
20
- /// revert (DoS, not fund loss). Select feeds carefully recovery requires deploying a new JBPrices contract and
21
- /// migrating projects.
16
+ /// @notice Provides currency conversion for the protocol. When a project's payout limits or surplus allowances are
17
+ /// denominated in a currency different from the token held in its terminal (e.g. USD limits with ETH held), this
18
+ /// contract resolves the exchange rate via registered price feeds (typically Chainlink oracles).
19
+ /// @dev Price feeds are immutable once set they cannot be replaced or removed. This protects against oracle
20
+ /// manipulation via admin-key attacks. If a feed is misconfigured, operations using that pair will revert (DoS, not
21
+ /// fund loss). The inverse of any registered feed is auto-calculated. Projects can have their own feeds; project ID 0
22
+ /// holds protocol-wide defaults.
22
23
  contract JBPrices is JBControlled, JBPermissioned, ERC2771Context, Ownable, IJBPrices {
23
24
  //*********************************************************************//
24
25
  // --------------------------- custom errors ------------------------- //
@@ -52,7 +53,7 @@ contract JBPrices is JBControlled, JBPermissioned, ERC2771Context, Ownable, IJBP
52
53
  /// @custom:param projectId The ID of the project the feed applies to. Feeds stored in ID 0 are used by default for
53
54
  /// all projects.
54
55
  /// @custom:param pricingCurrency The currency the feed's resulting price is in terms of.
55
- /// @custom:param unitCurrency The currency being priced by the feed.
56
+ /// @custom:param unitCurrency The currency the feed prices.
56
57
  mapping(uint256 projectId => mapping(uint256 pricingCurrency => mapping(uint256 unitCurrency => IJBPriceFeed)))
57
58
  public
58
59
  override priceFeedFor;
@@ -85,15 +86,16 @@ contract JBPrices is JBControlled, JBPermissioned, ERC2771Context, Ownable, IJBP
85
86
  // ---------------------- external transactions ---------------------- //
86
87
  //*********************************************************************//
87
88
 
88
- /// @notice Add a price feed for the `unitCurrency`, priced in terms of the `pricingCurrency`.
89
- /// @dev Price feeds can only be added, not modified or removed. Once a feed is set for a currency pair (in either
90
- /// direction), it is permanent for that project ID. Recovery from a misconfigured feed requires deploying a new
91
- /// JBPrices contract.
92
- /// @dev This contract's owner can add protocol-wide default price feed by passing a `projectId` of 0.
93
- /// @param projectId The ID of the project to add a feed for. If `projectId` is 0, add a protocol-wide default price
94
- /// feed.
89
+ /// @notice Register a price feed that provides the exchange rate between two currencies. For example, registering
90
+ /// an ETH/USD feed allows payout limits denominated in USD to be enforced against ETH balances.
91
+ /// @dev Price feeds are immutable once set for a currency pair, they cannot be replaced or removed. This
92
+ /// prevents
93
+ /// admin-key oracle manipulation. The inverse rate is auto-calculated, so registering A→B also provides B→A.
94
+ /// @dev Pass `projectId` = 0 to set a protocol-wide default (owner only). Non-zero project IDs require controller
95
+ /// authorization. A default feed for a pair blocks per-project overrides for that same pair.
96
+ /// @param projectId The ID of the project to add a feed for. Pass 0 for a protocol-wide default.
95
97
  /// @param pricingCurrency The currency the feed's output price is in terms of.
96
- /// @param unitCurrency The currency being priced by the feed.
98
+ /// @param unitCurrency The currency the feed prices.
97
99
  /// @param feed The address of the price feed to add.
98
100
  function addPriceFeedFor(
99
101
  uint256 projectId,
@@ -156,11 +158,14 @@ contract JBPrices is JBControlled, JBPermissioned, ERC2771Context, Ownable, IJBP
156
158
  // -------------------------- public views --------------------------- //
157
159
  //*********************************************************************//
158
160
 
159
- /// @notice Gets the `pricingCurrency` cost for one unit of the `unitCurrency`.
160
- /// @param projectId The ID of the project to check the feed for. Feeds stored in ID 0 are used by default for all
161
- /// projects.
162
- /// @param pricingCurrency The currency the feed's resulting price is in terms of.
163
- /// @param unitCurrency The currency being priced by the feed.
161
+ /// @notice Convert between currencies — returns how much of `pricingCurrency` one unit of `unitCurrency` is
162
+ /// worth.
163
+ /// For example, `pricePerUnitOf(id, USD, ETH, 18)` returns the USD price of 1 ETH with 18 decimals.
164
+ /// @dev Lookup order: project-specific feed → inverse of project feed default feed (project 0) → inverse of
165
+ /// default. Reverts with `JBPrices_PriceFeedNotFound` if no feed exists in any direction.
166
+ /// @param projectId The ID of the project to check the feed for. Falls back to project 0 (protocol defaults).
167
+ /// @param pricingCurrency The currency the result is denominated in.
168
+ /// @param unitCurrency The currency to price.
164
169
  /// @param decimals The number of decimals the returned fixed point price should include.
165
170
  /// @return The `pricingCurrency` price of 1 `unitCurrency`, as a fixed point number with the specified number of
166
171
  /// decimals.
@@ -9,8 +9,9 @@ import {IERC165} from "@openzeppelin/contracts/utils/introspection/IERC165.sol";
9
9
  import {IJBProjects} from "./interfaces/IJBProjects.sol";
10
10
  import {IJBTokenUriResolver} from "./interfaces/IJBTokenUriResolver.sol";
11
11
 
12
- /// @notice Stores project ownership and metadata.
13
- /// @dev Projects are represented as ERC-721s.
12
+ /// @notice Each Juicebox project is an ERC-721 NFT. Whoever holds the NFT owns the project and can configure its
13
+ /// rulesets, terminals, and permissions. Projects are created with `createFor` and the resulting token ID is used as
14
+ /// the project's ID across the entire protocol.
14
15
  contract JBProjects is ERC721, ERC2771Context, Ownable, IJBProjects {
15
16
  //*********************************************************************//
16
17
  // --------------------- public stored properties -------------------- //
@@ -50,7 +51,9 @@ contract JBProjects is ERC721, ERC2771Context, Ownable, IJBProjects {
50
51
  // ---------------------- external transactions ---------------------- //
51
52
  //*********************************************************************//
52
53
 
53
- /// @notice Sets the address of the resolver used to retrieve the tokenURI of projects.
54
+ /// @notice Set the contract that resolves project NFT metadata (the `tokenURI`). This controls what artwork and
55
+ /// JSON metadata is returned for each project's ERC-721 token.
56
+ /// @dev Only this contract's owner can change the resolver.
54
57
  /// @param resolver The address of the new resolver.
55
58
  function setTokenUriResolver(IJBTokenUriResolver resolver) external override onlyOwner {
56
59
  // Store the new resolver.
@@ -12,12 +12,14 @@ import {JBConstants} from "./libraries/JBConstants.sol";
12
12
  import {JBRuleset} from "./structs/JBRuleset.sol";
13
13
  import {JBRulesetWeightCache} from "./structs/JBRulesetWeightCache.sol";
14
14
 
15
- /// @notice Manages rulesets and queuing.
16
- /// @dev Rulesets dictate how a project behaves for a period of time. To learn more about their functionality, see the
17
- /// `JBRuleset` data structure.
18
- /// @dev Throughout this contract, `rulesetId` is an identifier for each ruleset. The `rulesetId` is the unix timestamp
19
- /// when the ruleset was initialized.
20
- /// @dev `approvable` means a ruleset which may or may not be approved.
15
+ /// @notice Stores and manages the economic rules for every Juicebox project. A "ruleset" defines how a project behaves
16
+ /// for a period of time: its token issuance weight, cash-out tax rate, payout limits, reserved rate, and more.
17
+ /// Projects queue future rulesets to schedule changes; once a ruleset's duration expires, the next approved ruleset
18
+ /// takes effect automatically.
19
+ /// @dev Rulesets form a linked list via `basedOnId`. Each ruleset ID is the unix timestamp when it was first stored.
20
+ /// Weight decays across cycles using `weightCutPercent` if many cycles elapse, the decay is computed iteratively
21
+ /// (capped at 20,000 iterations; use `updateRulesetWeightCache` for longer gaps). Approval hooks can gate whether a
22
+ /// queued ruleset actually takes effect.
21
23
  contract JBRulesets is JBControlled, IJBRulesets {
22
24
  //*********************************************************************//
23
25
  // --------------------------- custom errors ------------------------- //
@@ -86,32 +88,20 @@ contract JBRulesets is JBControlled, IJBRulesets {
86
88
  // ---------------------- external transactions ---------------------- //
87
89
  //*********************************************************************//
88
90
 
89
- /// @notice Queues the upcoming approvable ruleset for the specified project.
90
- /// @dev Only a project's current controller can queue its rulesets.
91
+ /// @notice Queues a new ruleset for a project. The ruleset takes effect after the current one ends (or immediately
92
+ /// if the current ruleset has `duration = 0`). Must be approved by the previous ruleset's approval hook.
93
+ /// @dev Only the project's current controller can call this.
91
94
  /// @param projectId The ID of the project to queue the ruleset for.
92
- /// @param duration The number of seconds the ruleset lasts for, after which a new ruleset starts.
93
- /// - A `duration` of 0 means this ruleset will remain active until the project owner queues a new ruleset. That new
94
- /// ruleset will start immediately.
95
- /// - A ruleset with a non-zero `duration` applies until the duration ends – any newly queued rulesets will be
96
- /// *queued* to take effect afterwards.
97
- /// - If a duration ends and no new rulesets are queued, the ruleset rolls over to a new ruleset with the same rules
98
- /// (except for a new `start` timestamp and a cut `weight`).
99
- /// @param weight A fixed point number with 18 decimals that contracts can use to base arbitrary calculations on.
100
- /// Payment terminals generally use this to determine how many tokens should be minted when the project is paid.
101
- /// @param weightCutPercent A fraction (out of `JBConstants.MAX_WEIGHT_CUT_PERCENT`) to reduce the next ruleset's
102
- /// `weight`
103
- /// by.
104
- /// - If a ruleset specifies a non-zero `weight`, the `weightCutPercent` does not apply.
105
- /// - If the `weightCutPercent` is 0, the `weight` stays the same.
106
- /// - If the `weightCutPercent` is 10% of `JBConstants.MAX_WEIGHT_CUT_PERCENT`, next ruleset's `weight` will be 90%
107
- /// of the
108
- /// current
109
- /// one.
110
- /// @param approvalHook A contract which dictates whether a proposed ruleset should be accepted or rejected. It can
111
- /// be used to constrain a project owner's ability to change ruleset parameters over time.
112
- /// @param metadata Arbitrary extra data to associate with this ruleset. This metadata is not used by `JBRulesets`.
113
- /// @param mustStartAtOrAfter The earliest time the ruleset can start. The ruleset cannot start before this
114
- /// timestamp.
95
+ /// @param duration How long the ruleset lasts (seconds). 0 = no auto-cycling (stays active until explicitly
96
+ /// replaced). When a duration ends without a queued replacement, the ruleset rolls over with decayed weight.
97
+ /// @param weight Tokens minted per unit paid (18 decimals). Terminals divide payment amount by weight to determine
98
+ /// issuance. A value of 1 means "inherit decayed weight from previous ruleset".
99
+ /// @param weightCutPercent How much to reduce weight each auto-cycle (out of 1,000,000,000). Only applies when no
100
+ /// explicit replacement is queued. 0 = no decay. 100,000,000 = 10% cut per cycle.
101
+ /// @param approvalHook A contract that gates whether the *next* queued ruleset can take effect (e.g. `JBDeadline`
102
+ /// for minimum notice periods). Set to address(0) for no approval gate.
103
+ /// @param metadata Packed 256-bit field decoded by `JBRulesetMetadataResolver`. Not used by `JBRulesets` itself.
104
+ /// @param mustStartAtOrAfter The earliest unix timestamp the ruleset can start.
115
105
  /// @return The struct of the new ruleset.
116
106
  function queueFor(
117
107
  uint256 projectId,
@@ -169,6 +159,7 @@ contract JBRulesets is JBControlled, IJBRulesets {
169
159
  uint256 latestId = latestRulesetIdOf[projectId];
170
160
 
171
161
  // The new rulesetId timestamp is now, or an increment from now if the current timestamp is taken.
162
+ // forge-lint: disable-next-line(block-timestamp)
172
163
  uint256 rulesetId = latestId >= block.timestamp ? latestId + 1 : block.timestamp;
173
164
 
174
165
  // Set up the ruleset by configuring intrinsic properties.
@@ -239,6 +230,7 @@ contract JBRulesets is JBControlled, IJBRulesets {
239
230
  * targetRuleset.duration;
240
231
 
241
232
  // Determine the start timestamp to derive a weight from for the cache.
233
+ // forge-lint: disable-next-line(block-timestamp)
242
234
  uint256 start = block.timestamp < maxStart ? block.timestamp : maxStart;
243
235
 
244
236
  // The difference between the start of the latest queued ruleset and the start of the ruleset we're caching the
@@ -275,7 +267,7 @@ contract JBRulesets is JBControlled, IJBRulesets {
275
267
  // ------------------------- external views -------------------------- //
276
268
  //*********************************************************************//
277
269
 
278
- /// @notice Get an array of a project's rulesets up to a maximum array size, sorted from latest to earliest.
270
+ /// @notice Returns a paginated history of a project's rulesets, sorted newest-first.
279
271
  /// @param projectId The ID of the project to get the rulesets of.
280
272
  /// @param startingId The ID of the ruleset to begin with. This will be the latest ruleset in the result. If 0 is
281
273
  /// passed, the project's latest ruleset will be used.
@@ -336,7 +328,7 @@ contract JBRulesets is JBControlled, IJBRulesets {
336
328
  }
337
329
  }
338
330
 
339
- /// @notice The current approval status of a given project's latest ruleset.
331
+ /// @notice Whether the project's most recently queued ruleset has been approved, rejected, or is still pending.
340
332
  /// @param projectId The ID of the project to check the approval status of.
341
333
  /// @return The project's current approval status.
342
334
  function currentApprovalStatusForLatestRulesetOf(uint256 projectId)
@@ -354,12 +346,11 @@ contract JBRulesets is JBControlled, IJBRulesets {
354
346
  return _approvalStatusOf({projectId: projectId, ruleset: ruleset});
355
347
  }
356
348
 
357
- /// @notice The ruleset that is currently active for the specified project.
358
- /// @dev If a current ruleset of the project is not found, returns an empty ruleset with all properties set to 0.
359
- /// @dev The first cycle returns the stored ruleset directly (cycleNumber=1, original weight). Subsequent cycles
360
- /// simulate cycling with weight decay via `_simulateCycledRulesetBasedOn`. Payout limits reset each cycle because
361
- /// they are keyed by `cycleNumber`, but the simulated cycle keeps the same `rulesetId` / config id as the stored
362
- /// ruleset it is based on.
349
+ /// @notice Returns the ruleset currently governing a project. If the stored ruleset has auto-cycled, simulates
350
+ /// cycling with weight decay and returns the simulated current cycle.
351
+ /// @dev Returns an empty ruleset (all zeros) if the project has never had a ruleset queued.
352
+ /// @dev Payout limits reset each cycle (keyed by `cycleNumber`), but the `rulesetId` stays the same across
353
+ /// auto-cycles of the same stored ruleset.
363
354
  /// @param projectId The ID of the project to get the current ruleset of.
364
355
  /// @return ruleset The project's current ruleset.
365
356
  function currentOf(uint256 projectId) external view override returns (JBRuleset memory ruleset) {
@@ -411,6 +402,7 @@ contract JBRulesets is JBControlled, IJBRulesets {
411
402
  // the ruleset that the latest is based on, which has the latest approved configuration.
412
403
  while (
413
404
  (approvalStatus != JBApprovalStatus.Approved && approvalStatus != JBApprovalStatus.Empty)
405
+ // forge-lint: disable-next-line(block-timestamp)
414
406
  || block.timestamp < ruleset.start
415
407
  ) {
416
408
  rulesetId = ruleset.basedOnId;
@@ -466,8 +458,9 @@ contract JBRulesets is JBControlled, IJBRulesets {
466
458
  approvalStatus = _approvalStatusOf({projectId: projectId, ruleset: ruleset});
467
459
  }
468
460
 
469
- /// @notice The ruleset that's up next for a project.
470
- /// @dev If an upcoming ruleset is not found for the project, returns an empty ruleset with all properties set to 0.
461
+ /// @notice Returns the ruleset that will take effect after the current one ends. This could be an explicitly queued
462
+ /// ruleset or a simulated auto-cycle of the current one (with decayed weight).
463
+ /// @dev Returns an empty ruleset (all zeros) if there is no upcoming ruleset.
471
464
  /// @param projectId The ID of the project to get the upcoming ruleset of.
472
465
  /// @return ruleset The struct for the project's upcoming ruleset.
473
466
  function upcomingOf(uint256 projectId) external view override returns (JBRuleset memory ruleset) {
@@ -509,6 +502,7 @@ contract JBRulesets is JBControlled, IJBRulesets {
509
502
  // If the latest ruleset starts in the future, it must start in the distant future
510
503
  // Since its not the upcoming approvable ruleset. In this case, base the upcoming ruleset on the base
511
504
  // ruleset.
505
+ // forge-lint: disable-next-line(block-timestamp)
512
506
  while (ruleset.start > block.timestamp) {
513
507
  ruleset = _getStructFor({projectId: projectId, rulesetId: ruleset.basedOnId, withMetadata: true});
514
508
  }
@@ -747,12 +741,15 @@ contract JBRulesets is JBControlled, IJBRulesets {
747
741
  // OR it hasn't started but it is likely to be approved and takes place before the proposed one,
748
742
  // set the struct to be the ruleset it's based on, which carries the latest approved ruleset.
749
743
  if (
744
+ // forge-lint: disable-next-line(block-timestamp)
750
745
  (block.timestamp >= baseRuleset.start
751
746
  && approvalStatus != JBApprovalStatus.Approved
752
747
  && approvalStatus != JBApprovalStatus.Empty)
748
+ // forge-lint: disable-next-line(block-timestamp)
753
749
  || (block.timestamp < baseRuleset.start
754
750
  && mustStartAtOrAfter < baseRuleset.start + baseRuleset.duration
755
751
  && approvalStatus != JBApprovalStatus.Approved)
752
+ // forge-lint: disable-next-line(block-timestamp)
756
753
  || (block.timestamp < baseRuleset.start
757
754
  && mustStartAtOrAfter >= baseRuleset.start + baseRuleset.duration
758
755
  && approvalStatus != JBApprovalStatus.Approved
@@ -794,7 +791,7 @@ contract JBRulesets is JBControlled, IJBRulesets {
794
791
  /// @notice Initializes a ruleset with the specified properties.
795
792
  /// @param projectId The ID of the project to initialize the ruleset for.
796
793
  /// @param baseRuleset The ruleset struct to base the newly initialized one on.
797
- /// @param rulesetId The `rulesetId` for the ruleset being initialized.
794
+ /// @param rulesetId The `rulesetId` for the ruleset to initialize.
798
795
  /// @param mustStartAtOrAfter The earliest time the ruleset can start. The ruleset cannot start before this
799
796
  /// timestamp.
800
797
  /// @param weight The weight to give the newly initialized ruleset.
@@ -955,11 +952,13 @@ contract JBRulesets is JBControlled, IJBRulesets {
955
952
  do {
956
953
  // If the latest ruleset is expired, return an empty ruleset.
957
954
  // A ruleset with a duration of 0 cannot expire.
955
+ // forge-lint: disable-next-line(block-timestamp)
958
956
  if (ruleset.duration != 0 && block.timestamp >= ruleset.start + ruleset.duration) {
959
957
  return 0;
960
958
  }
961
959
 
962
960
  // Return the ruleset's `rulesetId` if it has started.
961
+ // forge-lint: disable-next-line(block-timestamp)
963
962
  if (block.timestamp >= ruleset.start) {
964
963
  return ruleset.id;
965
964
  }
@@ -1047,6 +1046,7 @@ contract JBRulesets is JBControlled, IJBRulesets {
1047
1046
  // future.
1048
1047
  uint256 mustStartAtOrAfter = !allowMidRuleset
1049
1048
  ? block.timestamp + 1
1049
+ // forge-lint: disable-next-line(block-timestamp)
1050
1050
  : baseRuleset.duration >= block.timestamp ? 1 : block.timestamp - baseRuleset.duration + 1;
1051
1051
 
1052
1052
  // Calculate what the start time should be.
@@ -1103,6 +1103,7 @@ contract JBRulesets is JBControlled, IJBRulesets {
1103
1103
 
1104
1104
  // There is no upcoming ruleset if the latest ruleset has already started.
1105
1105
  // slither-disable-next-line incorrect-equality
1106
+ // forge-lint: disable-next-line(block-timestamp)
1106
1107
  if (block.timestamp >= ruleset.start) return 0;
1107
1108
 
1108
1109
  // If this is the first ruleset, it is queued.
@@ -1120,6 +1121,7 @@ contract JBRulesets is JBControlled, IJBRulesets {
1120
1121
  baseRuleset = _getStructFor({projectId: projectId, rulesetId: basedOnId, withMetadata: false});
1121
1122
 
1122
1123
  // If the base ruleset starts in the future,
1124
+ // forge-lint: disable-next-line(block-timestamp)
1123
1125
  if (block.timestamp < baseRuleset.start) {
1124
1126
  // Set the `rulesetId` to the one found.
1125
1127
  rulesetId = baseRuleset.id;
@@ -1135,6 +1137,7 @@ contract JBRulesets is JBControlled, IJBRulesets {
1135
1137
  ruleset = _getStructFor({projectId: projectId, rulesetId: rulesetId, withMetadata: false});
1136
1138
 
1137
1139
  // If the latest ruleset doesn't start until after another base ruleset return 0.
1140
+ // forge-lint: disable-next-line(block-timestamp)
1138
1141
  if (baseRuleset.duration != 0 && block.timestamp < ruleset.start - baseRuleset.duration) {
1139
1142
  return 0;
1140
1143
  }
package/src/JBSplits.sol CHANGED
@@ -9,7 +9,11 @@ import {JBConstants} from "./libraries/JBConstants.sol";
9
9
  import {JBSplit} from "./structs/JBSplit.sol";
10
10
  import {JBSplitGroup} from "./structs/JBSplitGroup.sol";
11
11
 
12
- /// @notice Stores and manages splits for each project.
12
+ /// @notice Manages how a project distributes its payouts and reserved tokens. Each "split" specifies a recipient and
13
+ /// their share (as a fraction of 1,000,000,000). Splits can be locked until a timestamp — locked splits cannot be
14
+ /// removed or reduced until the lock expires, providing recipients with guaranteed revenue streams.
15
+ /// @dev Splits are organized by project, ruleset, and group. Ruleset 0 is the fallback used when no splits are set for
16
+ /// the active ruleset. The payout group ID is derived from the token address to distribute.
13
17
  contract JBSplits is JBControlled, IJBSplits {
14
18
  //*********************************************************************//
15
19
  // --------------------------- custom errors ------------------------- //
@@ -75,16 +79,15 @@ contract JBSplits is JBControlled, IJBSplits {
75
79
  // ---------------------- external transactions ---------------------- //
76
80
  //*********************************************************************//
77
81
 
78
- /// @notice Sets a project's split groups.
79
- /// @dev Only a project's controller can set its splits, unless the first 160 bits of the group's ID match
80
- /// `msg.sender` AND the upper 96 bits are non-zero, in which case the caller can set its own splits.
81
- /// GroupIds with zero upper 96 bits (i.e. bare addresses) are reserved for protocol use (e.g. terminal
82
- /// payout groups keyed by token address) and always require controller authorization.
83
- /// @dev The new split groups must include any currently set splits that are locked.
82
+ /// @notice Configure how a project distributes funds to its split recipients. Each split group defines a list of
83
+ /// recipients (with percentage shares) for a specific purpose (e.g. payouts in ETH, reserved token distribution).
84
+ /// @dev Only the project's controller can set splits unless the group ID encodes `msg.sender` in its lower 160
85
+ /// bits with non-zero upper 96 bits, which enables self-managed splits (e.g. hooks managing their own groups).
86
+ /// @dev Locked splits cannot be removed or reduced until their lock expires. The new groups must include all
87
+ /// currently-locked splits.
84
88
  /// @param projectId The ID of the project to set the split groups of.
85
- /// @param rulesetId The ID of the ruleset the split groups should be active in. Send
86
- /// 0 to set the default split that'll be active if no ruleset has specific splits set. The default's default is the
87
- /// project's owner.
89
+ /// @param rulesetId The ID of the ruleset the split groups should be active in. Pass 0 to set the default splits
90
+ /// (used when no ruleset-specific splits are configured). The default's default is the project's owner.
88
91
  /// @param splitGroups An array of split groups to set.
89
92
  function setSplitGroupsOf(
90
93
  uint256 projectId,
@@ -127,13 +130,11 @@ contract JBSplits is JBControlled, IJBSplits {
127
130
  // ------------------------- external views -------------------------- //
128
131
  //*********************************************************************//
129
132
 
130
- /// @notice Get the split structs for the specified project ID, within the specified ruleset, for the specified
131
- /// group. The splits stored at ruleset 0 are used by default during a ruleset if the splits for the specific
132
- /// ruleset aren't set.
133
- /// @dev If splits aren't found at the given `rulesetId`, they'll be sought in the FALLBACK_RULESET_ID of 0.
133
+ /// @notice Get the list of split recipients for a project's group within a given ruleset. Falls back to the
134
+ /// default splits (ruleset 0) if none are set for the specific ruleset.
134
135
  /// @param projectId The ID of the project to get splits for.
135
- /// @param rulesetId An identifier within which the returned splits should be considered active.
136
- /// @param groupId The identifying group of the splits.
136
+ /// @param rulesetId The ruleset to look up splits in. Falls back to ruleset 0 if none are found.
137
+ /// @param groupId The split group ID (e.g. token address for payout groups, or a custom ID for reserved tokens).
137
138
  /// @return splits An array of all splits for the project.
138
139
  function splitsOf(
139
140
  uint256 projectId,
@@ -160,7 +161,7 @@ contract JBSplits is JBControlled, IJBSplits {
160
161
  /// @notice Sets the splits for a group given a project, ruleset, and group ID.
161
162
  /// @dev The new splits must include any currently set splits that are locked.
162
163
  /// @dev The sum of the split `percent`s within one group must be less than 100%.
163
- /// @param projectId The ID of the project splits are being set for.
164
+ /// @param projectId The ID of the project to set splits for.
164
165
  /// @param rulesetId The ID of the ruleset the splits should be considered active within.
165
166
  /// @param groupId The ID of the group to set the splits within.
166
167
  /// @param splits An array of splits to set.
@@ -175,6 +176,7 @@ contract JBSplits is JBControlled, IJBSplits {
175
176
  for (uint256 i; i < numberOfCurrentSplits;) {
176
177
  // If not locked, continue.
177
178
  if (
179
+ // forge-lint: disable-next-line(block-timestamp)
178
180
  block.timestamp < currentSplits[i].lockedUntil
179
181
  && !_includesLockedSplits({splits: splits, lockedSplit: currentSplits[i]})
180
182
  ) {