@bananapus/core-v6 0.0.38 → 0.0.39

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 (46) 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 +52 -43
  6. package/src/JBDeadline.sol +4 -4
  7. package/src/JBDirectory.sol +34 -32
  8. package/src/JBERC20.sol +5 -4
  9. package/src/JBFeelessAddresses.sol +6 -3
  10. package/src/JBFundAccessLimits.sol +25 -21
  11. package/src/JBMultiTerminal.sol +53 -50
  12. package/src/JBPermissions.sol +34 -37
  13. package/src/JBPrices.sol +23 -18
  14. package/src/JBProjects.sol +6 -3
  15. package/src/JBRulesets.sol +44 -41
  16. package/src/JBSplits.sol +18 -16
  17. package/src/JBTerminalStore.sol +26 -19
  18. package/src/JBTokens.sol +36 -26
  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/IJBController.sol +3 -2
  23. package/src/interfaces/IJBDirectory.sol +3 -1
  24. package/src/interfaces/IJBMultiTerminal.sol +3 -2
  25. package/src/interfaces/IJBPermissions.sol +2 -1
  26. package/src/interfaces/IJBPrices.sol +3 -1
  27. package/src/interfaces/IJBRulesets.sol +2 -1
  28. package/src/interfaces/IJBSplits.sol +2 -1
  29. package/src/interfaces/IJBTerminal.sol +3 -1
  30. package/src/interfaces/IJBTerminalStore.sol +3 -1
  31. package/src/interfaces/IJBTokens.sol +2 -1
  32. package/src/libraries/JBCashOuts.sol +6 -1
  33. package/src/libraries/JBConstants.sol +12 -3
  34. package/src/libraries/JBCurrencyIds.sol +2 -0
  35. package/src/libraries/JBFees.sol +5 -1
  36. package/src/libraries/JBFixedPointNumber.sol +2 -0
  37. package/src/libraries/JBPayoutSplitGroupLib.sol +5 -2
  38. package/src/libraries/JBRulesetMetadataResolver.sol +4 -0
  39. package/src/libraries/JBSplitGroupIds.sol +2 -1
  40. package/src/libraries/JBSurplus.sol +3 -1
  41. package/src/periphery/JBMatchingPriceFeed.sol +2 -0
  42. package/src/structs/JBAccountingContext.sol +7 -4
  43. package/src/structs/JBFundAccessLimitGroup.sol +10 -17
  44. package/src/structs/JBRuleset.sol +18 -26
  45. package/src/structs/JBRulesetConfig.sol +13 -25
  46. package/src/structs/JBRulesetMetadata.sol +25 -32
@@ -45,8 +45,13 @@ import {JBSplit} from "./structs/JBSplit.sol";
45
45
  import {JBSplitHookContext} from "./structs/JBSplitHookContext.sol";
46
46
  import {JBTokenAmount} from "./structs/JBTokenAmount.sol";
47
47
 
48
- /// @notice `JBMultiTerminal` manages native/ERC-20 payments, cash outs, and surplus allowance usage for any number of
49
- /// projects. Terminals are the entry point for operations involving inflows and outflows of funds.
48
+ /// @notice The main entry point for all money movement in Juicebox. Handles payments (ETH or ERC-20), cash outs
49
+ /// (burning tokens to reclaim funds), payouts (distributing funds to splits), and surplus allowance withdrawals.
50
+ /// Charges a 2.5% protocol fee on outflows (held for 28 days before processing). Supports Permit2 for gasless
51
+ /// ERC-20 approvals.
52
+ /// @dev Each project can have multiple terminals for different tokens. The terminal delegates accounting to
53
+ /// `JBTerminalStore` and splits distribution to `JBSplits`. Fees are sent to project #1 (the fee beneficiary).
54
+ /// All external hook calls (pay hooks, cash-out hooks, split hooks) are wrapped in try-catch to prevent griefing.
50
55
  contract JBMultiTerminal is JBPermissioned, ERC2771Context, IJBMultiTerminal {
51
56
  // A library that parses the packed ruleset metadata into a friendlier format.
52
57
  using JBRulesetMetadataResolver for JBRuleset;
@@ -185,10 +190,10 @@ contract JBMultiTerminal is JBPermissioned, ERC2771Context, IJBMultiTerminal {
185
190
  // ---------------------- external transactions ---------------------- //
186
191
  //*********************************************************************//
187
192
 
188
- /// @notice Adds accounting contexts for a project to this terminal so the project can begin accepting the tokens in
189
- /// those contexts.
190
- /// @dev Only a project's owner, an operator with the `ADD_ACCOUNTING_CONTEXTS` permission from that owner, or a
191
- /// project's controller can add accounting contexts for the project.
193
+ /// @notice Registers new tokens that this terminal can accept for a project. Once a token's accounting context is
194
+ /// added, the project can receive payments in that token.
195
+ /// @dev Only the project's owner, an operator with `ADD_ACCOUNTING_CONTEXTS` permission, or the project's
196
+ /// controller can call this.
192
197
  /// @param projectId The ID of the project having to add accounting contexts for.
193
198
  /// @param accountingContexts The accounting contexts to add.
194
199
  function addAccountingContextsFor(
@@ -219,8 +224,9 @@ contract JBMultiTerminal is JBPermissioned, ERC2771Context, IJBMultiTerminal {
219
224
  }
220
225
  }
221
226
 
222
- /// @notice Adds funds to a project's balance without minting tokens.
223
- /// @dev Adding to balance can unlock held fees if `shouldUnlockHeldFees` is true.
227
+ /// @notice Adds funds to a project's balance without minting tokens. Useful for topping up a project or returning
228
+ /// funds. Can also unlock previously held fees by returning them to the project's balance.
229
+ /// @dev If `shouldReturnHeldFees` is true, the added amount offsets held fees proportionally.
224
230
  /// @param projectId The ID of the project to add funds to the balance of.
225
231
  /// @param amount The amount of tokens to add to the balance, as a fixed point number with the same number of
226
232
  /// decimals as this terminal. If this is a native token terminal, this is ignored and `msg.value` is used instead.
@@ -251,10 +257,9 @@ contract JBMultiTerminal is JBPermissioned, ERC2771Context, IJBMultiTerminal {
251
257
  });
252
258
  }
253
259
 
254
- /// @notice Holders can cash out a project's tokens to reclaim some of that project's surplus tokens, or to trigger
255
- /// rules determined by the current ruleset's data hook and cash out hook.
256
- /// @dev Only a token's holder or an operator with the `CASH_OUT_TOKENS` permission from that holder can cash out
257
- /// those tokens.
260
+ /// @notice Burns project tokens to reclaim a share of the project's surplus (determined by the bonding curve) or
261
+ /// to trigger custom logic through the ruleset's data hook and cash out hook.
262
+ /// @dev Only the token holder or an operator with `CASH_OUT_TOKENS` permission from that holder can call this.
258
263
  /// @param holder The account whose tokens are being cashed out.
259
264
  /// @param projectId The ID of the project the project tokens belong to.
260
265
  /// @param cashOutCount The number of project tokens to cash out and burn, as a fixed point number with 18
@@ -563,7 +568,8 @@ contract JBMultiTerminal is JBPermissioned, ERC2771Context, IJBMultiTerminal {
563
568
  }
564
569
  }
565
570
 
566
- /// @notice Pay a project with tokens.
571
+ /// @notice Pay a project with tokens. The project's current ruleset determines how many project tokens the
572
+ /// beneficiary receives in return.
567
573
  /// @param projectId The ID of the project being paid.
568
574
  /// @param amount The amount of terminal tokens being received, as a fixed point number with the same number of
569
575
  /// decimals as this terminal. If this terminal's token is native, this is ignored and `msg.value` is used in its
@@ -622,19 +628,13 @@ contract JBMultiTerminal is JBPermissioned, ERC2771Context, IJBMultiTerminal {
622
628
  _checkMin({value: beneficiaryTokenCount, min: minReturnedTokens});
623
629
  }
624
630
 
625
- /// @notice Process any fees that are being held for the project.
626
- /// @dev Reentrancy safety: the loop re-reads `_nextHeldFeeIndexOf` from storage each iteration and advances the
627
- /// index before the external `_processFee` call, so a reentrant call cannot double-process the same fee entry.
628
- /// @dev Held fees after migration: held fees remain in this terminal after `migrateBalanceOf` because their backing
629
- /// tokens are not part of `balanceOf` they were already deducted from the recorded balance during the payout
630
- /// that
631
- /// created them. The actual fee-backing tokens remain in this terminal's token holdings. If `_processFee` reverts
632
- /// (e.g. the fee terminal rejects the payment), the catch block calls `_recordAddedBalanceFor` to credit the fee
633
- /// amount back to the project. This credit is backed by the tokens that failed to transfer out. No phantom balance
634
- /// is created because the tokens never left.
635
- /// @dev The index-increment-before-`_processFee` pattern is intentional: locked (not-yet-unlocked) fees are skipped
636
- /// via the `unlockTimestamp` check, and advancing the index before the external call prevents reentrancy from
637
- /// reprocessing the same fee entry.
631
+ /// @notice Processes held fees for a project, sending them to the protocol's fee project. Fees are held for 28 days
632
+ /// after a payout processing them finalizes the fee payment.
633
+ /// @dev Only processes fees whose `unlockTimestamp` has passed. Stops early if it encounters a still-locked fee.
634
+ /// @dev Reentrancy-safe: re-reads `_nextHeldFeeIndexOf` from storage each iteration and advances the index before
635
+ /// the external `_processFee` call, preventing double-processing.
636
+ /// @dev If `_processFee` reverts (fee terminal rejects), the fee amount is returned to the project's balance
637
+ /// (forgiven, not retried). A `FeeReverted` event is emitted for off-chain observability.
638
638
  /// @param projectId The ID of the project to process held fees for.
639
639
  /// @param token The token to process held fees for.
640
640
  /// @param count The number of fees to process.
@@ -657,6 +657,7 @@ contract JBMultiTerminal is JBPermissioned, ERC2771Context, IJBMultiTerminal {
657
657
 
658
658
  // Can't process fees that aren't yet unlocked. Fees unlock sequentially in the array, so nothing left to do
659
659
  // if the current fee isn't yet unlocked.
660
+ // forge-lint: disable-next-line(block-timestamp)
660
661
  if (heldFee.unlockTimestamp > block.timestamp) break;
661
662
 
662
663
  // Delete the entry and advance the index *before* the external call. This is intentional:
@@ -694,15 +695,12 @@ contract JBMultiTerminal is JBPermissioned, ERC2771Context, IJBMultiTerminal {
694
695
  }
695
696
  }
696
697
 
697
- /// @notice Sends payouts to a project's current payout split group, according to its ruleset, up to its current
698
- /// payout limit.
699
- /// @dev If the percentages of the splits in the project's payout split group do not add up to 100%, the remainder
700
- /// is sent to the project's owner.
701
- /// @dev Anyone can send payouts on a project's behalf. Projects can include a wildcard split (a split with no
702
- /// `hook`, `projectId`, or `beneficiary`) to send funds to the `_msgSender()` which calls this function. This can
703
- /// be used to incentivize calling this function.
704
- /// @dev payouts sent to addresses which aren't feeless incur the protocol fee.
705
- /// @dev Payouts a projects don't incur fees if its terminal is feeless.
698
+ /// @notice Distributes funds from a project's balance to its payout split recipients, up to the current ruleset's
699
+ /// payout limit. Anyone can call this on behalf of any project.
700
+ /// @dev If splits don't add up to 100%, the remainder goes to the project owner. A wildcard split (no hook,
701
+ /// projectId, or beneficiary) sends its share to `msg.sender` — useful for incentivizing the call.
702
+ /// @dev Payouts to non-feeless addresses incur the 2.5% protocol fee. Projects whose terminal is feeless are
703
+ /// exempt.
706
704
  /// @param projectId The ID of the project having its payouts sent.
707
705
  /// @param token The token being sent.
708
706
  /// @param amount The total number of terminal tokens to send, as a fixed point number with same number of decimals
@@ -730,10 +728,10 @@ contract JBMultiTerminal is JBPermissioned, ERC2771Context, IJBMultiTerminal {
730
728
  _checkMin({value: amountPaidOut, min: minTokensPaidOut});
731
729
  }
732
730
 
733
- /// @notice Allows a project to pay out funds from its surplus up to the current surplus allowance.
734
- /// @dev Only a project's owner or an operator with the `USE_ALLOWANCE` permission from that owner can use the
735
- /// surplus allowance.
736
- /// @dev Incurs the protocol fee unless the caller is a feeless address.
731
+ /// @notice Withdraws funds from a project's surplus (beyond what's needed for payouts) up to the current ruleset's
732
+ /// surplus allowance. Sent directly to a beneficiary address rather than through splits.
733
+ /// @dev Only the project's owner or an operator with `USE_ALLOWANCE` permission can call this.
734
+ /// @dev Incurs the 2.5% protocol fee unless the caller is a feeless address.
737
735
  /// @param projectId The ID of the project to use the surplus allowance of.
738
736
  /// @param token The token being paid out from the surplus.
739
737
  /// @param amount The amount of terminal tokens to use from the project's current surplus allowance, as a fixed
@@ -787,8 +785,7 @@ contract JBMultiTerminal is JBPermissioned, ERC2771Context, IJBMultiTerminal {
787
785
  // ------------------------- external views -------------------------- //
788
786
  //*********************************************************************//
789
787
 
790
- /// @notice A project's accounting context for a token.
791
- /// @dev See the `JBAccountingContext` struct for more information.
788
+ /// @notice Returns the accounting context (decimals, currency) for a specific token that a project accepts.
792
789
  /// @param projectId The ID of the project to get token accounting context of.
793
790
  /// @param token The token to check the accounting context of.
794
791
  /// @return The token's accounting context for the token.
@@ -804,15 +801,16 @@ contract JBMultiTerminal is JBPermissioned, ERC2771Context, IJBMultiTerminal {
804
801
  return STORE.accountingContextOf({terminal: address(this), projectId: projectId, token: token});
805
802
  }
806
803
 
807
- /// @notice The tokens accepted by a project.
804
+ /// @notice Returns accounting contexts for all tokens a project currently accepts through this terminal.
808
805
  /// @param projectId The ID of the project to get the accepted tokens of.
809
806
  /// @return tokenContexts The accounting contexts of the accepted tokens.
810
807
  function accountingContextsOf(uint256 projectId) external view override returns (JBAccountingContext[] memory) {
811
808
  return STORE.accountingContextsOf({terminal: address(this), projectId: projectId});
812
809
  }
813
810
 
814
- /// @notice Gets the current surplus amount in this terminal for a project, in terms of a given currency.
815
- /// @dev If `tokens` is empty, includes all tokens the project accepts (as returned by `accountingContextsOf(...)`).
811
+ /// @notice Returns the project's current surplus in this terminal, converted to a specified currency. The surplus
812
+ /// is the balance minus what's needed for payout limits.
813
+ /// @dev If `tokens` is empty, includes all tokens the project accepts.
816
814
  /// @param projectId The ID of the project to get the current surplus of.
817
815
  /// @param tokens The tokens to include in the surplus calculation. If empty, all tokens are included.
818
816
  /// @param decimals The number of decimals to include in the fixed point returned value.
@@ -837,9 +835,10 @@ contract JBMultiTerminal is JBPermissioned, ERC2771Context, IJBMultiTerminal {
837
835
  });
838
836
  }
839
837
 
840
- /// @notice Fees that are being held for a project.
841
- /// @dev Projects can temporarily hold fees and unlock them later by adding funds to the project's balance.
842
- /// @dev Held fees can be processed at any time by this terminal's owner.
838
+ /// @notice Returns the fees currently being held for a project. Fees are held for 28 days after a payout before
839
+ /// they can be processed (sent to the fee project).
840
+ /// @dev Held fees can be returned to the project by calling `addToBalanceOf` with `shouldReturnHeldFees = true`
841
+ /// before the 28-day lock expires.
843
842
  /// @param projectId The ID of the project that is holding fees.
844
843
  /// @param token The token that the fees are held in.
845
844
  /// @param count The maximum number of held fees to return.
@@ -878,7 +877,8 @@ contract JBMultiTerminal is JBPermissioned, ERC2771Context, IJBMultiTerminal {
878
877
  }
879
878
  }
880
879
 
881
- /// @notice Simulates cashing out project tokens from this terminal without modifying state.
880
+ /// @notice Simulates a cash out without modifying state — use this to preview how many tokens a holder would
881
+ /// reclaim.
882
882
  /// @param holder The address whose tokens are being cashed out.
883
883
  /// @param projectId The ID of the project whose tokens are being cashed out.
884
884
  /// @param cashOutCount The number of project tokens to cash out.
@@ -919,7 +919,9 @@ contract JBMultiTerminal is JBPermissioned, ERC2771Context, IJBMultiTerminal {
919
919
  });
920
920
  }
921
921
 
922
- /// @notice Simulates paying a project through this terminal without modifying state.
922
+ /// @notice Simulates a payment without modifying state — use this to preview how many project tokens a payer
923
+ /// would
924
+ /// receive.
923
925
  /// @param projectId The ID of the project being paid.
924
926
  /// @param token The token being paid in.
925
927
  /// @param amount The amount of tokens being paid.
@@ -1586,7 +1588,8 @@ contract JBMultiTerminal is JBPermissioned, ERC2771Context, IJBMultiTerminal {
1586
1588
  }
1587
1589
  }
1588
1590
 
1589
- /// @notice Pay a project with tokens.
1591
+ /// @notice Internal implementation of payment logic. Records the payment in the store, mints tokens via the
1592
+ /// controller, and fulfills any pay hook specifications from the data hook.
1590
1593
  /// @param projectId The ID of the project being paid.
1591
1594
  /// @param token The address of the token which the project is being paid with.
1592
1595
  /// @param amount The amount of terminal tokens being received, as a fixed point number with the same number of
@@ -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,19 +26,19 @@ 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
43
  /// @custom:param account The address of the account being 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
@@ -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 whose operator permissions are being configured.
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 ------------------------- //
@@ -85,13 +86,14 @@ 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
98
  /// @param unitCurrency The currency being priced by the feed.
97
99
  /// @param feed The address of the price feed to add.
@@ -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 being priced.
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.