@bananapus/core-v6 0.0.24 → 0.0.25

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 (177) hide show
  1. package/ADMINISTRATION.md +16 -3
  2. package/ARCHITECTURE.md +28 -9
  3. package/AUDIT_INSTRUCTIONS.md +102 -17
  4. package/CHANGE_LOG.md +18 -8
  5. package/README.md +58 -2
  6. package/RISKS.md +13 -20
  7. package/SKILLS.md +158 -11
  8. package/STYLE_GUIDE.md +11 -6
  9. package/USER_JOURNEYS.md +53 -16
  10. package/foundry.toml +1 -1
  11. package/package.json +2 -2
  12. package/script/Deploy.s.sol +2 -2
  13. package/script/DeployPeriphery.s.sol +2 -5
  14. package/script/helpers/CoreDeploymentLib.sol +2 -2
  15. package/src/JBChainlinkV3PriceFeed.sol +1 -1
  16. package/src/JBChainlinkV3SequencerPriceFeed.sol +1 -1
  17. package/src/JBController.sol +14 -7
  18. package/src/JBDeadline.sol +1 -1
  19. package/src/JBDirectory.sol +1 -1
  20. package/src/JBERC20.sol +6 -2
  21. package/src/JBFeelessAddresses.sol +1 -1
  22. package/src/JBFundAccessLimits.sol +1 -1
  23. package/src/JBMultiTerminal.sol +53 -4
  24. package/src/JBPermissions.sol +6 -2
  25. package/src/JBPrices.sol +1 -1
  26. package/src/JBProjects.sol +1 -1
  27. package/src/JBRulesets.sol +1 -1
  28. package/src/JBSplits.sol +1 -1
  29. package/src/JBTerminalStore.sol +57 -53
  30. package/src/JBTokens.sol +5 -1
  31. package/src/interfaces/IJBController.sol +7 -1
  32. package/src/libraries/JBPayoutSplitGroupLib.sol +1 -1
  33. package/src/periphery/JBDeadline1Day.sol +1 -1
  34. package/src/periphery/JBDeadline3Days.sol +1 -1
  35. package/src/periphery/JBDeadline3Hours.sol +1 -1
  36. package/src/periphery/JBDeadline7Days.sol +1 -1
  37. package/src/periphery/JBMatchingPriceFeed.sol +1 -1
  38. package/test/TestAccessToFunds.sol +4 -4
  39. package/test/TestFeeFreeCashOutBypass.sol +332 -0
  40. package/test/TestJBERC20Inheritance.sol +1 -1
  41. package/test/TestMetadataOffsetOverflow.sol +1 -1
  42. package/test/TestMetadataParserLib.sol +1 -1
  43. package/test/TestMultiTerminalSurplus.sol +1 -1
  44. package/test/TestMultiTokenSurplus.sol +1 -1
  45. package/test/TestMultipleAccessLimits.sol +4 -4
  46. package/test/TestPermit2DataHook.t.sol +1 -1
  47. package/test/TestPermit2Terminal.sol +1 -1
  48. package/test/TestTerminalPreviewParity.sol +1 -1
  49. package/test/audit/CashOutReenterPay.t.sol +496 -0
  50. package/test/audit/FeeFreeSurplusLifecycle.t.sol +392 -0
  51. package/test/audit/FeeFreeSurplusStale.t.sol +242 -0
  52. package/test/audit/USDTVoidReturnCompat.t.sol +519 -0
  53. package/test/fork/TestChainlinkPriceFeedFork.sol +1 -1
  54. package/test/fork/TestSequencerPriceFeedFork.sol +1 -1
  55. package/test/fork/TestTerminalPreviewParityFork.sol +1 -1
  56. package/test/helpers/JBTest.sol +1 -1
  57. package/test/helpers/MetadataResolverHelper.sol +1 -1
  58. package/test/mock/MockERC20.sol +1 -1
  59. package/test/mock/MockMaliciousBeneficiary.sol +1 -1
  60. package/test/mock/MockMaliciousSplitHook.sol +1 -1
  61. package/test/mock/MockPriceFeed.sol +1 -1
  62. package/test/mock/MockUSDT.sol +80 -0
  63. package/test/regression/HoldFeesCashOutReserved.t.sol +2 -2
  64. package/test/units/static/JBChainlinkV3PriceFeed/TestPriceFeed.sol +1 -1
  65. package/test/units/static/JBController/JBControllerSetup.sol +1 -1
  66. package/test/units/static/JBController/TestBurnTokensOf.sol +1 -1
  67. package/test/units/static/JBController/TestClaimTokensFor.sol +1 -1
  68. package/test/units/static/JBController/TestDeployErc20For.sol +1 -1
  69. package/test/units/static/JBController/TestLaunchProjectFor.sol +1 -1
  70. package/test/units/static/JBController/TestLaunchRulesetsFor.sol +1 -1
  71. package/test/units/static/JBController/TestMigrateController.sol +1 -1
  72. package/test/units/static/JBController/TestMintTokensOfUnits.sol +1 -1
  73. package/test/units/static/JBController/TestOmnichainRulesetOperator.sol +324 -0
  74. package/test/units/static/JBController/TestPayReservedTokenToTerminal.sol +1 -1
  75. package/test/units/static/JBController/TestPreviewMintOf.sol +1 -1
  76. package/test/units/static/JBController/TestReceiveMigrationFrom.sol +1 -1
  77. package/test/units/static/JBController/TestRulesetViews.sol +1 -1
  78. package/test/units/static/JBController/TestSendReservedTokensToSplitsOf.sol +1 -1
  79. package/test/units/static/JBController/TestSetSplitGroupsOf.sol +1 -1
  80. package/test/units/static/JBController/TestSetTokenFor.sol +1 -1
  81. package/test/units/static/JBController/TestSetUriOf.sol +1 -1
  82. package/test/units/static/JBController/TestTransferCreditsFrom.sol +1 -1
  83. package/test/units/static/JBDeadline/TestDeadlineFuzz.sol +1 -1
  84. package/test/units/static/JBDirectory/JBDirectorySetup.sol +1 -1
  85. package/test/units/static/JBDirectory/TestPrimaryTerminalOf.sol +1 -1
  86. package/test/units/static/JBDirectory/TestSetControllerOf.sol +1 -1
  87. package/test/units/static/JBDirectory/TestSetControllerOfMigrationOrder.sol +1 -1
  88. package/test/units/static/JBDirectory/TestSetPrimaryTerminalOf.sol +1 -1
  89. package/test/units/static/JBDirectory/TestSetTerminalsOf.sol +1 -1
  90. package/test/units/static/JBERC20/JBERC20Setup.sol +11 -4
  91. package/test/units/static/JBERC20/SigUtils.sol +1 -1
  92. package/test/units/static/JBERC20/TestInitialize.sol +8 -1
  93. package/test/units/static/JBERC20/TestName.sol +1 -1
  94. package/test/units/static/JBERC20/TestNonces.sol +1 -1
  95. package/test/units/static/JBERC20/TestSymbol.sol +1 -1
  96. package/test/units/static/JBFeelessAdresses/JBFeelessSetup.sol +1 -1
  97. package/test/units/static/JBFeelessAdresses/TestInterfaces.sol +1 -1
  98. package/test/units/static/JBFeelessAdresses/TestSetFeelessAddress.sol +1 -1
  99. package/test/units/static/JBFees/TestFeesFuzz.sol +1 -1
  100. package/test/units/static/JBFixedPointNumber/TestAdjustDecimals.sol +1 -1
  101. package/test/units/static/JBFixedPointNumber/TestAdjustDecimalsFuzz.sol +1 -1
  102. package/test/units/static/JBFundAccessLimits/JBFundAccessSetup.sol +1 -1
  103. package/test/units/static/JBFundAccessLimits/TestFundAccessLimitsEdge.sol +1 -1
  104. package/test/units/static/JBFundAccessLimits/TestPayoutLimitOf.sol +1 -1
  105. package/test/units/static/JBFundAccessLimits/TestPayoutLimitsOf.sol +1 -1
  106. package/test/units/static/JBFundAccessLimits/TestSetFundAccessLimitsFor.sol +1 -1
  107. package/test/units/static/JBFundAccessLimits/TestSurplusAllowanceOf.sol +1 -1
  108. package/test/units/static/JBFundAccessLimits/TestSurplusAllowancesOf.sol +1 -1
  109. package/test/units/static/JBMetadataResolver/TestGetDataFor.sol +1 -1
  110. package/test/units/static/JBMetadataResolver/TestMetadataResolverEdgeCases.sol +1 -1
  111. package/test/units/static/JBMetadataResolver/TestMetadataResolverFuzz.sol +1 -1
  112. package/test/units/static/JBMultiTerminal/JBMultiTerminalSetup.sol +1 -1
  113. package/test/units/static/JBMultiTerminal/TestAccountingContextsOf.sol +1 -1
  114. package/test/units/static/JBMultiTerminal/TestAddAccountingContextsFor.sol +1 -1
  115. package/test/units/static/JBMultiTerminal/TestAddToBalanceOf.sol +1 -1
  116. package/test/units/static/JBMultiTerminal/TestCashOutTokensOf.sol +1 -1
  117. package/test/units/static/JBMultiTerminal/TestExecutePayout.sol +1 -1
  118. package/test/units/static/JBMultiTerminal/TestExecuteProcessFee.sol +1 -1
  119. package/test/units/static/JBMultiTerminal/TestMigrateBalanceOf.sol +1 -1
  120. package/test/units/static/JBMultiTerminal/TestPay.sol +1 -1
  121. package/test/units/static/JBMultiTerminal/TestPreviewCashOutFrom.sol +1 -1
  122. package/test/units/static/JBMultiTerminal/TestPreviewPayFor.sol +1 -1
  123. package/test/units/static/JBMultiTerminal/TestProcessHeldFeesOf.sol +1 -1
  124. package/test/units/static/JBMultiTerminal/TestSendPayoutsOf.sol +1 -1
  125. package/test/units/static/JBMultiTerminal/TestUseAllowanceOf.sol +1 -1
  126. package/test/units/static/JBPermissions/JBPermissionsSetup.sol +1 -1
  127. package/test/units/static/JBPermissions/TestHasPermission.sol +1 -1
  128. package/test/units/static/JBPermissions/TestHasPermissions.sol +1 -1
  129. package/test/units/static/JBPermissions/TestSetPermissionsFor.sol +1 -1
  130. package/test/units/static/JBPrices/JBPricesSetup.sol +1 -1
  131. package/test/units/static/JBPrices/TestAddPriceFeedFor.sol +1 -1
  132. package/test/units/static/JBPrices/TestPricePerUnitOf.sol +1 -1
  133. package/test/units/static/JBPrices/TestPrices.sol +1 -1
  134. package/test/units/static/JBProjects/JBProjectsSetup.sol +1 -1
  135. package/test/units/static/JBProjects/TestCreateFor.sol +1 -1
  136. package/test/units/static/JBProjects/TestInitialProject.sol +1 -1
  137. package/test/units/static/JBProjects/TestInterfaces.sol +1 -1
  138. package/test/units/static/JBProjects/TestSetResolver.sol +1 -1
  139. package/test/units/static/JBProjects/TestTokenUri.sol +1 -1
  140. package/test/units/static/JBRulesetMetadataResolver/TestSetCashOutTaxRateTo.sol +1 -1
  141. package/test/units/static/JBRulesets/JBRulesetsSetup.sol +1 -1
  142. package/test/units/static/JBRulesets/TestCurrentApprovalStatusForLatestRulesetOf.sol +1 -1
  143. package/test/units/static/JBRulesets/TestCurrentOf.sol +1 -1
  144. package/test/units/static/JBRulesets/TestGetRulesetOf.sol +1 -1
  145. package/test/units/static/JBRulesets/TestLatestQueuedRulesetOf.sol +1 -1
  146. package/test/units/static/JBRulesets/TestRulesets.sol +1 -1
  147. package/test/units/static/JBRulesets/TestRulesetsOf.sol +1 -1
  148. package/test/units/static/JBRulesets/TestUpcomingRulesetOf.sol +1 -1
  149. package/test/units/static/JBRulesets/TestUpdateRulesetWeightCache.sol +1 -1
  150. package/test/units/static/JBSplits/JBSplitsSetup.sol +1 -1
  151. package/test/units/static/JBSplits/TestSelfManagedSplitGroups.sol +1 -1
  152. package/test/units/static/JBSplits/TestSetSplitGroupsOf.sol +1 -1
  153. package/test/units/static/JBSplits/TestSplitsLockedEdge.sol +1 -1
  154. package/test/units/static/JBSplits/TestSplitsOf.sol +1 -1
  155. package/test/units/static/JBSplits/TestSplitsPacking.sol +1 -1
  156. package/test/units/static/JBSurplus/TestSurplusFuzz.sol +1 -1
  157. package/test/units/static/JBTerminalStore/JBTerminalStoreSetup.sol +1 -1
  158. package/test/units/static/JBTerminalStore/TestCurrentReclaimableSurplusOf.sol +1 -1
  159. package/test/units/static/JBTerminalStore/TestCurrentSurplusOf.sol +1 -1
  160. package/test/units/static/JBTerminalStore/TestCurrentTotalSurplusOf.sol +1 -1
  161. package/test/units/static/JBTerminalStore/TestPreviewCashOutFrom.sol +1 -1
  162. package/test/units/static/JBTerminalStore/TestPreviewPayFrom.sol +1 -1
  163. package/test/units/static/JBTerminalStore/TestRecordCashOutsFor.sol +1 -1
  164. package/test/units/static/JBTerminalStore/TestRecordPaymentFrom.sol +1 -1
  165. package/test/units/static/JBTerminalStore/TestRecordPayoutFor.sol +1 -1
  166. package/test/units/static/JBTerminalStore/TestRecordTerminalMigration.sol +1 -1
  167. package/test/units/static/JBTerminalStore/TestRecordUsedAllowanceOf.sol +1 -1
  168. package/test/units/static/JBTerminalStore/TestUint224Overflow.sol +1 -1
  169. package/test/units/static/JBTokens/JBTokensSetup.sol +1 -1
  170. package/test/units/static/JBTokens/TestBurnFrom.sol +1 -1
  171. package/test/units/static/JBTokens/TestClaimTokensFor.sol +1 -1
  172. package/test/units/static/JBTokens/TestDeployERC20ForUnits.sol +1 -1
  173. package/test/units/static/JBTokens/TestMintFor.sol +1 -1
  174. package/test/units/static/JBTokens/TestSetTokenFor.sol +1 -1
  175. package/test/units/static/JBTokens/TestTotalBalanceOf.sol +1 -1
  176. package/test/units/static/JBTokens/TestTotalSupplyOf.sol +1 -1
  177. package/test/units/static/JBTokens/TestTransferCreditsFrom.sol +1 -1
package/SKILLS.md CHANGED
@@ -46,7 +46,7 @@ The core Juicebox V6 protocol on EVM: a modular system for launching treasury-ba
46
46
  | `setUriOf(uint256 projectId, string uri)` | Sets the project's metadata URI. |
47
47
  | `transferCreditsFrom(address holder, uint256 projectId, address recipient, uint256 creditCount)` | Transfers credits between addresses (reverts if `pauseCreditTransfers` is set in ruleset). |
48
48
  | `addPriceFeedFor(uint256 projectId, uint256 pricingCurrency, uint256 unitCurrency, IJBPriceFeed feed)` | Registers a price feed (requires `allowAddPriceFeed` in ruleset). |
49
- | `migrateController(uint256 projectId, IJBMigratable to)` | Migrates the project to a new controller. Calls `beforeReceiveMigrationFrom`, `migrate`, updates directory, then `afterReceiveMigrationFrom`. |
49
+ | `migrate(uint256 projectId, IERC165 to)` | Migrates the project to a new controller. Calls `beforeReceiveMigrationFrom`, `migrate`, updates directory, then `afterReceiveMigrationFrom`. |
50
50
  | `currentRulesetOf(uint256 projectId)` | Returns the current ruleset and unpacked metadata. |
51
51
  | `upcomingRulesetOf(uint256 projectId)` | Returns the upcoming ruleset and unpacked metadata. |
52
52
  | `allRulesetsOf(uint256 projectId, uint256 startingId, uint256 size)` | Returns an array of rulesets with metadata, paginated. |
@@ -59,7 +59,7 @@ The core Juicebox V6 protocol on EVM: a modular system for launching treasury-ba
59
59
  | Function | What it does |
60
60
  |----------|--------------|
61
61
  | `pay(uint256 projectId, address token, uint256 amount, address beneficiary, uint256 minReturnedTokens, string memo, bytes metadata)` | Pays a project. Mints project tokens to beneficiary based on ruleset weight. Returns token count. |
62
- | `cashOutTokensOf(address holder, uint256 projectId, uint256 cashOutCount, address tokenToReclaim, uint256 minTokensReclaimed, address beneficiary, bytes metadata)` | Burns project tokens and reclaims surplus terminal tokens via bonding curve. |
62
+ | `cashOutTokensOf(address holder, uint256 projectId, uint256 cashOutCount, address tokenToReclaim, uint256 minTokensReclaimed, address payable beneficiary, bytes metadata)` | Burns project tokens and reclaims surplus terminal tokens via bonding curve. |
63
63
  | `sendPayoutsOf(uint256 projectId, address token, uint256 amount, uint256 currency, uint256 minTokensPaidOut)` | Distributes payouts from the project's balance to its payout split group, up to the payout limit. |
64
64
  | `useAllowanceOf(uint256 projectId, address token, uint256 amount, uint256 currency, uint256 minTokensPaidOut, address payable beneficiary, address payable feeBeneficiary, string memo)` | Withdraws from the project's surplus allowance to a beneficiary. The `feeBeneficiary` receives tokens minted by the fee payment. |
65
65
  | `addToBalanceOf(uint256 projectId, address token, uint256 amount, bool shouldReturnHeldFees, string memo, bytes metadata)` | Adds funds to a project's balance without minting tokens. Can unlock held fees. |
@@ -70,7 +70,7 @@ The core Juicebox V6 protocol on EVM: a modular system for launching treasury-ba
70
70
  | `accountingContextForTokenOf(uint256 projectId, address token)` | Returns the accounting context for a specific token. |
71
71
  | `accountingContextsOf(uint256 projectId)` | Returns all accounting contexts for a project. |
72
72
  | `previewPayFor(uint256 projectId, address token, uint256 amount, address beneficiary, bytes metadata)` | Simulates a full payment including the reserved/beneficiary token split. Returns `(ruleset, beneficiaryTokenCount, reservedTokenCount, hookSpecifications)`. Composes `STORE.previewPayFrom` + `controller.previewMintOf`. |
73
- | `previewCashOutFrom(address holder, uint256 projectId, uint256 cashOutCount, address tokenToReclaim, address beneficiary, bytes metadata)` | Simulates a full cash out including bonding curve and data hook effects. Returns `(ruleset, reclaimAmount, cashOutTaxRate, hookSpecifications)`. Delegates to `STORE.previewCashOutFrom`. |
73
+ | `previewCashOutFrom(address holder, uint256 projectId, uint256 cashOutCount, address tokenToReclaim, address payable beneficiary, bytes metadata)` | Simulates a full cash out including bonding curve and data hook effects. Returns `(ruleset, reclaimAmount, cashOutTaxRate, hookSpecifications)`. Delegates to `STORE.previewCashOutFrom`. |
74
74
  | `heldFeesOf(uint256 projectId, address token, uint256 count)` | Returns up to `count` held fees for a project/token. |
75
75
 
76
76
  ### JBTerminalStore
@@ -78,9 +78,9 @@ The core Juicebox V6 protocol on EVM: a modular system for launching treasury-ba
78
78
  | Function | What it does |
79
79
  |----------|--------------|
80
80
  | `recordPaymentFrom(address payer, JBTokenAmount amount, uint256 projectId, address beneficiary, bytes metadata)` | Records a payment. Applies data hook if enabled. Returns ruleset, token count, hook specifications. |
81
- | `recordPayoutFor(uint256 projectId, JBAccountingContext accountingContext, uint256 amount, uint256 currency)` | Records a payout. Enforces payout limits. Returns ruleset and amount paid out. |
82
- | `recordCashOutFor(address holder, uint256 projectId, uint256 cashOutCount, JBAccountingContext accountingContext, JBAccountingContext[] balanceAccountingContexts, bool beneficiaryIsFeeless, bytes metadata)` | Records a cash out. Computes reclaim via bonding curve. Returns ruleset, reclaim amount, tax rate, and hook specifications. |
83
- | `recordUsedAllowanceOf(uint256 projectId, JBAccountingContext accountingContext, uint256 amount, uint256 currency)` | Records surplus allowance usage. Enforces allowance limits. Returns ruleset and used amount. |
81
+ | `recordPayoutFor(uint256 projectId, address token, uint256 amount, uint256 currency)` | Records a payout. Enforces payout limits. Returns ruleset and amount paid out. |
82
+ | `recordCashOutFor(address holder, uint256 projectId, uint256 cashOutCount, address tokenToReclaim, bool beneficiaryIsFeeless, bytes metadata)` | Records a cash out. Computes reclaim via bonding curve. Returns ruleset, reclaim amount, tax rate, and hook specifications. |
83
+ | `recordUsedAllowanceOf(uint256 projectId, address token, uint256 amount, uint256 currency)` | Records surplus allowance usage. Enforces allowance limits. Returns ruleset and used amount. |
84
84
  | `recordAddedBalanceFor(uint256 projectId, address token, uint256 amount)` | Records funds added to a project's balance. |
85
85
  | `recordTerminalMigration(uint256 projectId, address token)` | Records a terminal migration, returning the full balance. |
86
86
  | `balanceOf(address terminal, uint256 projectId, address token)` | Returns the balance of a project at a terminal for a given token. |
@@ -101,7 +101,7 @@ The core Juicebox V6 protocol on EVM: a modular system for launching treasury-ba
101
101
  | `currentOf(uint256 projectId)` | Returns the currently active ruleset with decayed weight and correct cycle number. |
102
102
  | `latestQueuedOf(uint256 projectId)` | Returns the latest queued ruleset and its approval status. |
103
103
  | `queueFor(uint256 projectId, uint256 duration, uint256 weight, uint256 weightCutPercent, IJBRulesetApprovalHook approvalHook, uint256 metadata, uint256 mustStartAtOrAfter)` | Queues a new ruleset. Only callable by the project's controller. |
104
- | `updateRulesetWeightCache(uint256 projectId)` | Updates the weight cache for long-running rulesets. Required when `weightCutMultiple > 20,000` to avoid gas limits. |
104
+ | `updateRulesetWeightCache(uint256 projectId, uint256 rulesetId)` | Updates the weight cache for long-running rulesets. Required when `weightCutMultiple > 20,000` to avoid gas limits. |
105
105
 
106
106
  ### JBPermissions
107
107
 
@@ -186,8 +186,8 @@ The core Juicebox V6 protocol on EVM: a modular system for launching treasury-ba
186
186
  | `JBBeforeCashOutRecordedContext` | `terminal`, `holder`, `projectId`, `rulesetId`, `cashOutCount`, `totalSupply`, `surplus (JBTokenAmount)`, `useTotalSurplus`, `cashOutTaxRate`, `beneficiaryIsFeeless`, `metadata` | `IJBRulesetDataHook.beforeCashOutRecordedWith()` input |
187
187
  | `JBAfterPayRecordedContext` | `payer`, `projectId`, `rulesetId`, `amount (JBTokenAmount)`, `forwardedAmount (JBTokenAmount)`, `weight`, `newlyIssuedTokenCount`, `beneficiary`, `hookMetadata`, `payerMetadata` | `IJBPayHook.afterPayRecordedWith()` input |
188
188
  | `JBAfterCashOutRecordedContext` | `holder`, `projectId`, `rulesetId`, `cashOutCount`, `reclaimedAmount (JBTokenAmount)`, `forwardedAmount (JBTokenAmount)`, `cashOutTaxRate`, `beneficiary`, `hookMetadata`, `cashOutMetadata` | `IJBCashOutHook.afterCashOutRecordedWith()` input |
189
- | `JBPayHookSpecification` | `hook (IJBPayHook)`, `amount`, `metadata` | Returned by data hook; specifies which pay hooks to call and how much to forward |
190
- | `JBCashOutHookSpecification` | `hook (IJBCashOutHook)`, `amount`, `metadata` | Returned by data hook; specifies which cash out hooks to call and how much to forward |
189
+ | `JBPayHookSpecification` | `hook (IJBPayHook)`, `noop (bool)`, `amount`, `metadata` | Returned by data hook; specifies which pay hooks to call and how much to forward. `noop = true` means informational-only (callback skipped, amount must be 0). |
190
+ | `JBCashOutHookSpecification` | `hook (IJBCashOutHook)`, `noop (bool)`, `amount`, `metadata` | Returned by data hook; specifies which cash out hooks to call and how much to forward. `noop = true` means informational-only (callback skipped, amount must be 0). |
191
191
  | `JBSplitHookContext` | `token`, `amount`, `decimals`, `projectId`, `groupId`, `split (JBSplit)` | `IJBSplitHook.processSplitWith()` input |
192
192
 
193
193
  ### Constants (`JBConstants`)
@@ -244,7 +244,7 @@ The core Juicebox V6 protocol on EVM: a modular system for launching treasury-ba
244
244
  - `JBERC20` is cloned via `Clones.clone()` -- its constructor sets invalid name/symbol; real values set in `initialize()`
245
245
  - Fee is 2.5% (`FEE = 25` out of `MAX_FEE = 1000`)
246
246
  - Project #1 is the fee beneficiary project (receives all protocol fees)
247
- - **Fee-free cashout exemption is scoped to fee-free intra-terminal payout amounts.** `_feeFreeSurplusOf[projectId][token]` accumulates the value of fee-free payouts. During cashout with `cashOutTaxRate=0`, the 2.5% fee applies only up to this surplus, then depletes. Once consumed, subsequent cashouts are fee-free again. This prevents a round-trip fee bypass (intra-terminal payout → zero-tax cashout) while scoping fees precisely to the fee-free inflow.
247
+ - **Fee-free cashout exemption is scoped to fee-free intra-terminal payout amounts.** `_feeFreeSurplusOf[projectId][token]` accumulates the value of fee-free payouts. After any outflow (payouts, `useAllowanceOf`, non-zero-tax or feeless cashouts), the counter is capped at the remaining balance — non-fee-free funds leave first, preserving the fee-free counter. During cashout with `cashOutTaxRate=0`, the 2.5% fee applies only up to this surplus, then depletes. Once consumed, subsequent cashouts are fee-free again. Cleared on terminal migration. This prevents a round-trip fee bypass (intra-terminal payout → zero-tax cashout) while scoping fees precisely to the fee-free inflow.
248
248
  - `JBProjects` constructor optionally mints project #1 to `feeProjectOwner` -- if `address(0)`, no fee project is created
249
249
  - `JBMultiTerminal` derives `DIRECTORY` from the provided `store` in its constructor -- not passed directly
250
250
  - `JBPrices.pricePerUnitOf()` checks project-specific feed, then inverse, then falls back to `DEFAULT_PROJECT_ID = 0`
@@ -265,12 +265,159 @@ The core Juicebox V6 protocol on EVM: a modular system for launching treasury-ba
265
265
  - **`JBAccountingContext.currency` is NOT `baseCurrency` — by design.** `baseCurrency` in ruleset metadata uses abstract real-world values (1 = ETH, 2 = USD) so rulesets are portable across chains — `baseCurrency=2` means "issue X tokens per USD" whether on Ethereum, Base, or Arbitrum. `JBAccountingContext.currency` uses token-derived values (`uint32(uint160(tokenAddress))`) because terminals track specific tokens at specific addresses — e.g. NATIVE_TOKEN = 61166, USDC on Ethereum = 909516616, USDC on Base = 3169378579. `JBPrices` mediates between the two: it converts token-derived currencies to/from abstract currencies (e.g. USDC token → USD concept, NATIVE_TOKEN → ETH concept) so that payout limits denominated in USD work correctly regardless of which token the terminal holds. The separation is what makes cross-chain consistency possible: same ruleset, different terminal accounting per chain.
266
266
  - **Don't queue multiple identical rulesets.** A ruleset with a `duration` automatically cycles — no need to queue copies. Queue multiple rulesets only when configuration actually changes between periods (e.g. different weight, splits, or limits).
267
267
  - **`NATIVE_TOKEN` represents a different token on each chain.** `NATIVE_TOKEN` (`0x000000000000000000000000000000000000EEEe`) is the token received via `msg.value` — ETH on Ethereum/Base/Optimism/Arbitrum, CELO on Celo, etc. Its currency is `uint32(uint160(NATIVE_TOKEN))` = 61166. A `JBMatchingPriceFeed` (returns 1:1) is deployed for `ETH:NATIVE_TOKEN` on ETH-native chains so that `baseCurrency=ETH` resolves correctly to the native token. On non-ETH-native chains, a different price feed would be needed.
268
+ - **Noop hook specifications are informational-only.** `noop = true` + `amount != 0` reverts with `JBTerminalStore_NoopHookSpecHasAmount`. Data hooks use noop specs to return diagnostics to preview clients without triggering a hook callback. The `noop` flag only suppresses the callback — parameter overrides (weight, tax rate, supply) from the data hook still apply.
269
+
270
+ ## Permission IDs
271
+
272
+ Quick-reference for the most common `JBPermissionIds` values (from `@bananapus/permission-ids-v6`). Pass these to `JBPermissions.setPermissionsFor()`.
273
+
274
+ | ID | Name | Gates |
275
+ |----|------|-------|
276
+ | `1` | `ROOT` | All permissions for the scoped project. Cannot be combined with `projectId = 0`. |
277
+ | `2` | `QUEUE_RULESETS` | `JBController.queueRulesetsOf` |
278
+ | `3` | `LAUNCH_RULESETS` | `JBController.launchRulesetsFor` |
279
+ | `4` | `CASH_OUT_TOKENS` | `JBMultiTerminal.cashOutTokensOf` |
280
+ | `5` | `SEND_PAYOUTS` | `JBMultiTerminal.sendPayoutsOf` |
281
+ | `6` | `MIGRATE_TERMINAL` | `JBMultiTerminal.migrateBalanceOf` |
282
+ | `7` | `SET_PROJECT_URI` | `JBController.setUriOf` |
283
+ | `8` | `DEPLOY_ERC20` | `JBController.deployERC20For` |
284
+ | `9` | `SET_TOKEN` | `JBController.setTokenFor` |
285
+ | `10` | `MINT_TOKENS` | `JBController.mintTokensOf` |
286
+ | `11` | `BURN_TOKENS` | `JBController.burnTokensOf` |
287
+ | `12` | `CLAIM_TOKENS` | `JBController.claimTokensFor` |
288
+ | `13` | `TRANSFER_CREDITS` | `JBController.transferCreditsFrom` |
289
+ | `14` | `SET_CONTROLLER` | `JBDirectory.setControllerOf` |
290
+ | `15` | `SET_TERMINALS` | `JBDirectory.setTerminalsOf` (can remove primary terminal) |
291
+ | `16` | `SET_PRIMARY_TERMINAL` | `JBDirectory.setPrimaryTerminalOf` |
292
+ | `17` | `USE_ALLOWANCE` | `JBMultiTerminal.useAllowanceOf` |
293
+ | `18` | `SET_SPLIT_GROUPS` | `JBController.setSplitGroupsOf` |
294
+ | `19` | `ADD_PRICE_FEED` | `JBController.addPriceFeedFor` |
295
+ | `20` | `ADD_ACCOUNTING_CONTEXTS` | `JBMultiTerminal.addAccountingContextsFor` |
296
+ | `21` | `SET_TOKEN_METADATA` | `JBController.setTokenMetadataOf` |
297
+
298
+ IDs 22-33 are used by extension contracts (721 hook, buyback hook, router terminal, suckers).
299
+
300
+ ## Common Errors
301
+
302
+ Errors an agent is most likely to encounter. All are custom errors (revert with selector).
303
+
304
+ | Error | Contract | When |
305
+ |-------|----------|------|
306
+ | `JBPermissioned_Unauthorized` | `JBPermissioned` | Caller lacks the required permission ID for the project. |
307
+ | `JBController_RulesetsArrayEmpty` | `JBController` | `launchProjectFor` / `queueRulesetsOf` called with empty rulesets array. |
308
+ | `JBController_RulesetsAlreadyLaunched` | `JBController` | `launchRulesetsFor` called on a project that already has rulesets. |
309
+ | `JBController_MintNotAllowedAndNotTerminalOrHook` | `JBController` | `mintTokensOf` called but `allowOwnerMinting` is false and caller is not a terminal/hook. |
310
+ | `JBController_ZeroTokensToMint` | `JBController` | `mintTokensOf` called with `tokenCount = 0`. |
311
+ | `JBController_ZeroTokensToBurn` | `JBController` | `burnTokensOf` called with `tokenCount = 0`. |
312
+ | `JBController_NoReservedTokens` | `JBController` | `sendReservedTokensToSplitsOf` called but no pending reserved tokens. |
313
+ | `JBController_CreditTransfersPaused` | `JBController` | `transferCreditsFrom` called but `pauseCreditTransfers` is set in ruleset. |
314
+ | `JBController_RulesetSetTokenNotAllowed` | `JBController` | `setTokenFor` called but `allowSetCustomToken` is false in ruleset. |
315
+ | `JBController_AddingPriceFeedNotAllowed` | `JBController` | `addPriceFeedFor` called but `allowAddPriceFeed` is false in ruleset. |
316
+ | `JBController_InvalidReservedPercent` | `JBController` | `reservedPercent` exceeds `MAX_RESERVED_PERCENT` (10,000). |
317
+ | `JBController_InvalidCashOutTaxRate` | `JBController` | `cashOutTaxRate` exceeds `MAX_CASH_OUT_TAX_RATE` (10,000). |
318
+ | `JBMultiTerminal_UnderMinReturnedTokens` | `JBMultiTerminal` | Payment minted fewer tokens than `minReturnedTokens`. |
319
+ | `JBMultiTerminal_UnderMinTokensReclaimed` | `JBMultiTerminal` | Cash out reclaimed less than `minTokensReclaimed`. |
320
+ | `JBMultiTerminal_UnderMinTokensPaidOut` | `JBMultiTerminal` | Payout distributed less than `minTokensPaidOut`. |
321
+ | `JBMultiTerminal_TokenNotAccepted` | `JBMultiTerminal` | Token has no accounting context for the project in this terminal. |
322
+ | `JBMultiTerminal_NoMsgValueAllowed` | `JBMultiTerminal` | `msg.value > 0` sent with an ERC-20 payment (not `NATIVE_TOKEN`). |
323
+ | `JBMultiTerminal_PermitAllowanceNotEnough` | `JBMultiTerminal` | Permit2 allowance insufficient for the payment amount. |
324
+ | `JBTerminalStore_RulesetPaymentPaused` | `JBTerminalStore` | `pausePay` is set in the current ruleset. |
325
+ | `JBTerminalStore_RulesetNotFound` | `JBTerminalStore` | No ruleset exists for the project (not launched). |
326
+ | `JBTerminalStore_InadequateControllerPayoutLimit` | `JBTerminalStore` | `sendPayoutsOf` amount exceeds the payout limit for this cycle. |
327
+ | `JBTerminalStore_InadequateControllerAllowance` | `JBTerminalStore` | `useAllowanceOf` amount exceeds the surplus allowance. |
328
+ | `JBTerminalStore_InadequateTerminalStoreBalance` | `JBTerminalStore` | Withdrawal exceeds the terminal's recorded balance. |
329
+ | `JBTerminalStore_InsufficientTokens` | `JBTerminalStore` | Cash out count exceeds the holder's token balance. |
330
+ | `JBTerminalStore_TerminalMigrationNotAllowed` | `JBTerminalStore` | `migrateBalanceOf` called but `allowTerminalMigration` is false. |
331
+ | `JBTerminalStore_NoopHookSpecHasAmount` | `JBTerminalStore` | Data hook returned a noop spec with `amount != 0`. |
332
+ | `JBTerminalStore_AccountingContextAlreadySet` | `JBTerminalStore` | Accounting context already exists for that token. |
333
+ | `JBDirectory_SetControllerNotAllowed` | `JBDirectory` | Controller change not allowed by the current ruleset. |
334
+ | `JBDirectory_SetTerminalsNotAllowed` | `JBDirectory` | Terminal change not allowed by the current ruleset. |
335
+ | `JBDirectory_DuplicateTerminals` | `JBDirectory` | Duplicate terminal in the terminals array. |
336
+ | `JBTokens_ProjectAlreadyHasToken` | `JBTokens` | `deployERC20For` / `setTokenFor` called but project already has an ERC-20. |
337
+ | `JBTokens_InsufficientCredits` | `JBTokens` | `claimTokensFor` count exceeds credit balance. |
338
+ | `JBTokens_TokensMustHave18Decimals` | `JBTokens` | Custom token does not use 18 decimals. |
339
+ | `JBSplits_TotalPercentExceeds100` | `JBSplits` | Split percentages sum exceeds `SPLITS_TOTAL_PERCENT`. |
340
+ | `JBPrices_PriceFeedAlreadyExists` | `JBPrices` | Feed already set for that currency pair (immutable). |
341
+ | `JBPrices_PriceFeedNotFound` | `JBPrices` | No feed found for the requested currency pair. |
342
+ | `JBPermissions_CantSetRootPermissionForWildcardProject` | `JBPermissions` | Tried to grant ROOT with `projectId = 0` (wildcard). |
343
+ | `JBRulesets_InvalidWeight` | `JBRulesets` | Weight exceeds `uint112.max`. |
344
+ | `JBRulesets_InvalidWeightCutPercent` | `JBRulesets` | `weightCutPercent` exceeds `MAX_WEIGHT_CUT_PERCENT`. |
345
+ | `JBFundAccessLimits_InvalidPayoutLimitCurrencyOrdering` | `JBFundAccessLimits` | Payout limit currencies not in strictly increasing order. |
346
+
347
+ ## Key Events
348
+
349
+ The most important events for indexing and off-chain monitoring. Indexed params marked with `*`.
350
+
351
+ | Event | Interface | Key Params |
352
+ |-------|-----------|------------|
353
+ | `Pay` | `IJBTerminal` | `rulesetId*`, `rulesetCycleNumber*`, `projectId*`, `payer`, `beneficiary`, `amount`, `newlyIssuedTokenCount` |
354
+ | `CashOutTokens` | `IJBCashOutTerminal` | `rulesetId*`, `rulesetCycleNumber*`, `projectId*`, `holder`, `beneficiary`, `cashOutCount`, `cashOutTaxRate`, `reclaimAmount` |
355
+ | `SendPayouts` | `IJBPayoutTerminal` | `rulesetId*`, `rulesetCycleNumber*`, `projectId*`, `projectOwner`, `amount`, `amountPaidOut`, `fee`, `netLeftoverPayoutAmount` |
356
+ | `SendPayoutToSplit` | `IJBPayoutTerminal` | `projectId*`, `rulesetId*`, `group*`, `split`, `amount`, `netAmount` |
357
+ | `UseAllowance` | `IJBPayoutTerminal` | `rulesetId*`, `rulesetCycleNumber*`, `projectId*`, `beneficiary`, `amount`, `netAmountPaidOut` |
358
+ | `MintTokens` | `IJBController` | `beneficiary*`, `projectId*`, `tokenCount`, `beneficiaryTokenCount`, `reservedPercent` |
359
+ | `BurnTokens` | `IJBController` | `holder*`, `projectId*`, `tokenCount` |
360
+ | `SendReservedTokensToSplits` | `IJBController` | `rulesetId*`, `rulesetCycleNumber*`, `projectId*`, `owner`, `tokenCount`, `leftoverAmount` |
361
+ | `SendReservedTokensToSplit` | `IJBController` | `projectId*`, `rulesetId*`, `groupId*`, `split`, `tokenCount` |
362
+ | `LaunchProject` | `IJBController` | `rulesetId`, `projectId`, `projectUri` |
363
+ | `QueueRulesets` | `IJBController` | `rulesetId`, `projectId` |
364
+ | `DeployERC20` | `IJBController` | `projectId*`, `deployer*`, `salt`, `saltHash`, `caller` |
365
+ | `SetUri` | `IJBController` | `projectId*`, `uri` |
366
+ | `AddToBalance` | `IJBTerminal` | `projectId*`, `amount`, `returnedFees` |
367
+ | `MigrateTerminal` | `IJBTerminal` | `projectId*`, `token*`, `to*`, `amount` |
368
+ | `HoldFee` | `IJBFeeTerminal` | `projectId*`, `token*`, `amount*`, `fee`, `beneficiary` |
369
+ | `ProcessFee` | `IJBFeeTerminal` | `projectId*`, `token*`, `amount*`, `wasHeld`, `beneficiary` |
370
+ | `ReturnHeldFees` | `IJBFeeTerminal` | `projectId*`, `token*`, `amount*`, `returnedFees`, `leftoverAmount` |
371
+ | `Create` | `IJBProjects` | `projectId*`, `owner*` |
372
+ | `OperatorPermissionsSet` | `IJBPermissions` | (operator, account, projectId, permissionIds, packed, caller) |
373
+ | `RulesetQueued` | `IJBRulesets` | (rulesetId, projectId, duration, weight, weightCutPercent, approvalHook, metadata, mustStartAtOrAfter, caller) |
374
+ | `SetSplit` | `IJBSplits` | (projectId, rulesetId, groupId, split, caller) |
375
+ | `AddPriceFeed` | `IJBPrices` | (projectId, pricingCurrency, unitCurrency, feed, caller) |
376
+
377
+ ## Hook Interface Return Types
378
+
379
+ ### `IJBRulesetDataHook.beforePayRecordedWith()`
380
+
381
+ ```solidity
382
+ function beforePayRecordedWith(JBBeforePayRecordedContext calldata context)
383
+ external view
384
+ returns (
385
+ uint256 weight, // Overrides the ruleset's weight for token issuance
386
+ JBPayHookSpecification[] memory hookSpecifications // Pay hooks to call + amounts to forward
387
+ );
388
+ ```
389
+
390
+ The data hook can override `weight` to change how many tokens the payer receives. Return `hookSpecifications` to redirect funds to pay hooks (each spec has `hook`, `amount`, `metadata`, and `noop`). An empty array means all funds stay in the terminal balance.
391
+
392
+ ### `IJBRulesetDataHook.beforeCashOutRecordedWith()`
393
+
394
+ ```solidity
395
+ function beforeCashOutRecordedWith(JBBeforeCashOutRecordedContext calldata context)
396
+ external view
397
+ returns (
398
+ uint256 cashOutTaxRate, // Overrides the ruleset's cash out tax rate
399
+ uint256 cashOutCount, // Overrides the number of tokens being cashed out
400
+ uint256 totalSupply, // Overrides total supply for bonding curve calc
401
+ JBCashOutHookSpecification[] memory hookSpecifications // Cash out hooks to call + amounts to forward
402
+ );
403
+ ```
404
+
405
+ The data hook can override `cashOutTaxRate` (0 = proportional, 10000 = nothing reclaimable), `cashOutCount` and `totalSupply` (to shift the bonding curve), and return `hookSpecifications` to redirect reclaimed funds to cash out hooks.
406
+
407
+ ### `IJBRulesetDataHook.hasMintPermissionFor()`
408
+
409
+ ```solidity
410
+ function hasMintPermissionFor(uint256 projectId, JBRuleset memory ruleset, address addr)
411
+ external view returns (bool flag);
412
+ ```
413
+
414
+ Returns whether `addr` is allowed to mint tokens for the project. Called by `JBController.mintTokensOf` when the caller is not the owner and `allowOwnerMinting` is false -- the data hook can grant mint permission to specific addresses (e.g. suckers for omnichain bridging).
268
415
 
269
416
  ## Example Integration
270
417
 
271
418
  ```solidity
272
419
  // SPDX-License-Identifier: MIT
273
- pragma solidity 0.8.26;
420
+ pragma solidity 0.8.28;
274
421
 
275
422
  import {IJBController} from "@bananapus/core-v6/src/interfaces/IJBController.sol";
276
423
  import {IJBDirectory} from "@bananapus/core-v6/src/interfaces/IJBDirectory.sol";
package/STYLE_GUIDE.md CHANGED
@@ -21,7 +21,7 @@ One contract/interface/struct/enum per file. Name the file after the type it con
21
21
 
22
22
  ```solidity
23
23
  // Contracts — pin to exact version
24
- pragma solidity 0.8.26;
24
+ pragma solidity 0.8.28;
25
25
 
26
26
  // Interfaces, structs, enums — caret for forward compatibility
27
27
  pragma solidity ^0.8.0;
@@ -112,6 +112,10 @@ contract JBExample is JBPermissioned, IJBExample {
112
112
  // ----------------------- external views ---------------------------- //
113
113
  //*********************************************************************//
114
114
 
115
+ //*********************************************************************//
116
+ // -------------------------- public views --------------------------- //
117
+ //*********************************************************************//
118
+
115
119
  //*********************************************************************//
116
120
  // ----------------------- public transactions ----------------------- //
117
121
  //*********************************************************************//
@@ -141,10 +145,11 @@ contract JBExample is JBPermissioned, IJBExample {
141
145
  8. Constructor
142
146
  9. External transactions
143
147
  10. External views
144
- 11. Public transactions
145
- 12. Internal helpers
146
- 13. Internal views
147
- 14. Private helpers
148
+ 11. Public views
149
+ 12. Public transactions
150
+ 13. Internal helpers
151
+ 14. Internal views
152
+ 15. Private helpers
148
153
 
149
154
  Functions are alphabetized within each section.
150
155
 
@@ -326,7 +331,7 @@ Standard config across all repos:
326
331
 
327
332
  ```toml
328
333
  [profile.default]
329
- solc = '0.8.26'
334
+ solc = '0.8.28'
330
335
  evm_version = 'cancun'
331
336
  optimizer_runs = 200
332
337
  libs = ["node_modules", "lib"]
package/USER_JOURNEYS.md CHANGED
@@ -89,15 +89,15 @@ All user paths through the Juicebox V6 core protocol. For each journey: entry po
89
89
  - `metadata` -- Hook-specific data
90
90
 
91
91
  **State changes**:
92
- 1. `JBTerminalStore.balanceOf[terminal][projectId][token]` decremented by `reclaimAmount + hookSpec amounts`
93
- 2. Project tokens burned via `JBController.burnTokensOf()` (credits first, then ERC-20)
92
+ 1. `STORE.recordCashOutFor()` -- computes the reclaim amount via bonding curve (using current `totalSupply` which still includes the tokens being cashed out), then decrements `JBTerminalStore.balanceOf[terminal][projectId][token]` by `reclaimAmount + hookSpec amounts`
93
+ 2. Project tokens burned via `JBController.burnTokensOf()` (credits first, then ERC-20) -- happens AFTER the reclaim calculation, so the bonding curve sees the pre-burn supply
94
94
  3. Reclaimed tokens transferred to beneficiary
95
95
  4. Cash out hooks execute (if data hook returns specifications)
96
- 5. Fee taken (2.5%) on total amount eligible for fees, unless beneficiary is feeless. When `cashOutTaxRate == 0`, the fee applies only up to the project's unconsumed fee-free surplus (`_feeFreeSurplusOf`) from intra-terminal payouts once depleted, cashouts are fee-free.
96
+ 5. Fee taken (2.5%) on total amount eligible for fees, unless beneficiary is feeless. When `cashOutTaxRate == 0`, the fee applies only up to the project's unconsumed fee-free surplus (`_feeFreeSurplusOf`) from intra-terminal payouts -- once depleted, cashouts are fee-free.
97
97
 
98
98
  **Events**: `CashOutTokens(rulesetId, rulesetCycleNumber, projectId, holder, beneficiary, cashOutCount, cashOutTaxRate, reclaimAmount, metadata, caller)`
99
99
 
100
- **Preview**: Call `JBMultiTerminal.previewCashOutFrom(holder, projectId, cashOutCount, tokenToReclaim, beneficiary, metadata)` to simulate the full cash out on-chain -- including data hook effects on tax rate, supply, and hook specifications. Returns `(ruleset, reclaimAmount, cashOutTaxRate, hookSpecifications)`. This is a `view` function that does not modify state. The terminal delegates to `JBTerminalStore.previewCashOutFrom(holder, projectId, cashOutCount, accountingContext, balanceAccountingContexts, beneficiaryIsFeeless, metadata)` for the bonding curve computation. For a simpler estimate without data hook effects, use `currentTotalReclaimableSurplusOf(projectId, cashOutCount, decimals, currency)` or the 6-param `currentReclaimableSurplusOf` overload.
100
+ **Preview**: Call `JBMultiTerminal.previewCashOutFrom(holder, projectId, cashOutCount, tokenToReclaim, beneficiary, metadata)` to simulate the full cash out on-chain -- including data hook effects on tax rate, supply, and hook specifications. Returns `(ruleset, reclaimAmount, cashOutTaxRate, hookSpecifications)`. This is a `view` function that does not modify state. The terminal delegates to `JBTerminalStore.previewCashOutFrom(terminal, holder, projectId, cashOutCount, tokenToReclaim, beneficiaryIsFeeless, metadata)` for the bonding curve computation. For a simpler estimate without data hook effects, use `currentTotalReclaimableSurplusOf(projectId, cashOutCount, decimals, currency)` or the 6-param `currentReclaimableSurplusOf` overload.
101
101
 
102
102
  **Edge cases**:
103
103
  - `cashOutCount = 0` with `totalSupply = 0` -- returns entire surplus (C-5 known bug)
@@ -169,7 +169,7 @@ All user paths through the Juicebox V6 core protocol. For each journey: entry po
169
169
  - `usedAmount > surplus` -- reverts (`InadequateTerminalStoreBalance`)
170
170
  - Surplus allowance resets each ruleset (keyed by `rulesetId`, not `cycleNumber`)
171
171
  - Amount validated against surplus BEFORE checking allowance limit
172
- - If both owner and beneficiary are feeless, no fee is taken
172
+ - If either the owner or the beneficiary is feeless, no fee is taken
173
173
 
174
174
  ---
175
175
 
@@ -277,7 +277,7 @@ All user paths through the Juicebox V6 core protocol. For each journey: entry po
277
277
  1. `JBSplits` stores the new split groups for the project/ruleset/group combination
278
278
  2. Locked splits from existing configuration must be preserved (validated by `JBSplits`)
279
279
 
280
- **Events**: Emitted by `JBSplits` (not `JBController`)
280
+ **Events**: `SetSplit(projectId, rulesetId, groupId, split, caller)` per split (emitted by `JBSplits`, not `JBController`)
281
281
 
282
282
  **Edge cases**:
283
283
  - Locked splits (`lockedUntil > block.timestamp`) cannot be removed or modified
@@ -321,15 +321,19 @@ All user paths through the Juicebox V6 core protocol. For each journey: entry po
321
321
 
322
322
  **Who can call**: Project owner, address with `SET_CONTROLLER` permission, or an address in `isAllowedToSetFirstController` (for first controller only). The current controller's ruleset must have `allowSetController` enabled.
323
323
 
324
+ **Parameters**:
325
+ - `projectId` -- The project being migrated
326
+ - `controller` -- The new controller (must support `IERC165`)
327
+
324
328
  **Flow**:
325
329
  1. `JBDirectory.setControllerOf(projectId, newController)` is called
326
- 2. Directory calls `newController.beforeReceiveMigrationFrom(oldController, projectId)`:
327
- - Copies metadata URI from old controller
328
- - Distributes pending reserved tokens from old controller
329
- 3. Directory calls `oldController.migrate(projectId, newController)`:
330
+ 2. If old controller exists AND new controller supports `IJBMigratable`: directory calls `newController.beforeReceiveMigrationFrom(oldController, projectId)`:
331
+ - Copies metadata URI from old controller (if it supports `IJBProjectUriRegistry`)
332
+ - Distributes pending reserved tokens from old controller (if it supports `IJBController` and has pending tokens)
333
+ 3. If old controller exists AND old controller supports `IJBMigratable`: directory calls `oldController.migrate(projectId, newController)`:
330
334
  - Reverts if pending reserved tokens > 0 (must distribute first)
331
335
  4. Directory updates `controllerOf[projectId] = newController`
332
- 5. Directory calls `newController.afterReceiveMigrationFrom(oldController, projectId)`
336
+ 5. If old controller exists AND new controller supports `IJBMigratable`: directory calls `newController.afterReceiveMigrationFrom(oldController, projectId)` (currently a no-op; verifies caller is the directory)
333
337
 
334
338
  **Events**: `Migrate(projectId, to, caller)` from old controller; `SetController(projectId, controller, caller)` from directory
335
339
 
@@ -358,7 +362,7 @@ All user paths through the Juicebox V6 core protocol. For each journey: entry po
358
362
  2. Clone's `initialize(name, symbol, owner=JBTokens)` is called
359
363
  3. `JBTokens.tokenOf[projectId]` set to the new token
360
364
 
361
- **Events**: `DeployERC20(projectId, deployer, salt, saltHash, caller)`
365
+ **Events**: `DeployERC20(projectId, deployer, salt, saltHash, caller)` from `JBController`, `DeployERC20(projectId, token, name, symbol, salt, caller)` from `JBTokens`
362
366
 
363
367
  **Edge cases**:
364
368
  - Can only be called once per project. If a token is already set, `JBTokens` reverts.
@@ -383,6 +387,8 @@ All user paths through the Juicebox V6 core protocol. For each journey: entry po
383
387
  1. `JBTokens.creditBalanceOf[holder][projectId]` decreased by `tokenCount`
384
388
  2. ERC-20 tokens minted to `beneficiary` for `tokenCount`
385
389
 
390
+ **Events**: `ClaimTokens(holder, projectId, creditBalance, count, beneficiary, caller)` (emitted by `JBTokens`)
391
+
386
392
  **Edge cases**:
387
393
  - Requires an ERC-20 token to be deployed for the project (reverts otherwise)
388
394
  - Credits and ERC-20 tokens are fungible -- this is a one-way conversion from internal credits to on-chain ERC-20
@@ -421,7 +427,7 @@ All user paths through the Juicebox V6 core protocol. For each journey: entry po
421
427
 
422
428
  ## 15. Add Price Feeds
423
429
 
424
- **Entry point**: `JBController.addPriceFeed(uint256 projectId, uint256 pricingCurrency, uint256 unitCurrency, IJBPriceFeed feed)`
430
+ **Entry point**: `JBController.addPriceFeedFor(uint256 projectId, uint256 pricingCurrency, uint256 unitCurrency, IJBPriceFeed feed)`
425
431
 
426
432
  **Who can call**: Project owner or address with `ADD_PRICE_FEED` permission.
427
433
 
@@ -435,7 +441,7 @@ All user paths through the Juicebox V6 core protocol. For each journey: entry po
435
441
  1. `JBPrices` stores the feed for the `(projectId, pricingCurrency, unitCurrency)` triple
436
442
  2. Feed is **immutable** once set -- cannot be replaced or removed
437
443
 
438
- **Events**: Emitted by `JBPrices`
444
+ **Events**: `AddPriceFeed(projectId, pricingCurrency, unitCurrency, feed, caller)` (emitted by `JBPrices`)
439
445
 
440
446
  **Edge cases**:
441
447
  - Requires `allowAddPriceFeed` in current ruleset
@@ -488,6 +494,8 @@ All user paths through the Juicebox V6 core protocol. For each journey: entry po
488
494
  2. All `PROJECTS.ownerOf(projectId)` calls now return the new owner
489
495
  3. All permission checks that reference the owner now apply to the new owner
490
496
 
497
+ **Events**: `Transfer(from, to, tokenId)` (standard ERC-721 event)
498
+
491
499
  **Edge cases**:
492
500
  - This is a standard ERC-721 transfer. All ERC-721 rules apply (approval, operator, etc.)
493
501
  - **Permissions are NOT transferred**. Existing operators retain their permissions scoped to the account that granted them (the old owner). The new owner must grant their own permissions.
@@ -539,7 +547,7 @@ All user paths through the Juicebox V6 core protocol. For each journey: entry po
539
547
  1. `JBTokens.creditBalanceOf[holder][projectId]` decreased
540
548
  2. `JBTokens.creditBalanceOf[recipient][projectId]` increased
541
549
 
542
- **Events**: Emitted by `JBTokens`
550
+ **Events**: `TransferCredits(holder, projectId, recipient, count, caller)` (emitted by `JBTokens`)
543
551
 
544
552
  **Edge cases**:
545
553
  - Reverts if `pauseCreditTransfers` is set in current ruleset
@@ -554,10 +562,18 @@ All user paths through the Juicebox V6 core protocol. For each journey: entry po
554
562
 
555
563
  **Who can call**: Project owner or address with `SET_PROJECT_URI` permission.
556
564
 
565
+ **Parameters**:
566
+ - `projectId` -- Target project
567
+ - `uri` -- Metadata URI (typically an IPFS hash)
568
+
557
569
  **State changes**: `uriOf[projectId] = uri`
558
570
 
559
571
  **Events**: `SetUri(projectId, uri, caller)`
560
572
 
573
+ **Edge cases**:
574
+ - Empty string is valid -- clears the metadata URI
575
+ - No ruleset flag required (always allowed with permission)
576
+
561
577
  ---
562
578
 
563
579
  ## 21. Set Custom Token
@@ -566,8 +582,14 @@ All user paths through the Juicebox V6 core protocol. For each journey: entry po
566
582
 
567
583
  **Who can call**: Project owner or address with `SET_TOKEN` permission.
568
584
 
585
+ **Parameters**:
586
+ - `projectId` -- Target project
587
+ - `token` -- The custom token contract (must implement `IJBToken`)
588
+
569
589
  **State changes**: `JBTokens.tokenOf[projectId] = token`
570
590
 
591
+ **Events**: `SetToken(projectId, token, caller)` (emitted by `JBTokens`)
592
+
571
593
  **Edge cases**:
572
594
  - Requires `allowSetCustomToken` in current or upcoming ruleset
573
595
  - Can only be called if no token is currently set for the project
@@ -581,7 +603,18 @@ All user paths through the Juicebox V6 core protocol. For each journey: entry po
581
603
 
582
604
  **Who can call**: Project owner or address with `SET_TOKEN_METADATA` permission.
583
605
 
584
- **State changes**: Updates the ERC-20 token's name and symbol via `JBERC20.setNameAndSymbol()`
606
+ **Parameters**:
607
+ - `projectId` -- Target project
608
+ - `name` -- New ERC-20 token name
609
+ - `symbol` -- New ERC-20 token symbol
610
+
611
+ **State changes**: Updates the ERC-20 token's name and symbol via `JBERC20.setMetadata()`
612
+
613
+ **Events**: `SetTokenMetadata(projectId, name, symbol, caller)` (emitted by `JBTokens`)
614
+
615
+ **Edge cases**:
616
+ - Requires an ERC-20 token to be deployed for the project (reverts if no token set)
617
+ - Only works with `JBERC20` clones (custom tokens that implement `IJBToken` may not support `setMetadata`)
585
618
 
586
619
  ---
587
620
 
@@ -615,6 +648,10 @@ All user paths through the Juicebox V6 core protocol. For each journey: entry po
615
648
 
616
649
  **Who can call**: Project owner, address with `ADD_ACCOUNTING_CONTEXTS` permission, or the project's controller.
617
650
 
651
+ **Parameters**:
652
+ - `projectId` -- Target project
653
+ - `accountingContexts` -- Array of `JBAccountingContext` structs, each specifying a token address, decimals, and currency
654
+
618
655
  **State changes**:
619
656
  1. For each context: validates token decimals, stores `_accountingContextForTokenOf[projectId][token]`
620
657
  2. Appends to `_accountingContextsOf[projectId]` array
package/foundry.toml CHANGED
@@ -1,5 +1,5 @@
1
1
  [profile.default]
2
- solc = '0.8.26'
2
+ solc = '0.8.28'
3
3
  evm_version = 'cancun'
4
4
  optimizer_runs = 200
5
5
  libs = ["node_modules", "lib"]
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bananapus/core-v6",
3
- "version": "0.0.24",
3
+ "version": "0.0.25",
4
4
  "license": "MIT",
5
5
  "repository": {
6
6
  "type": "git",
@@ -26,7 +26,7 @@
26
26
  "artifacts": "source ./.env && npx sphinx artifacts --org-id 'ea165b21-7cdc-4d7b-be59-ecdd4c26bee4' --project-name 'nana-core-v6'"
27
27
  },
28
28
  "dependencies": {
29
- "@bananapus/permission-ids-v6": "^0.0.10",
29
+ "@bananapus/permission-ids-v6": "^0.0.11",
30
30
  "@chainlink/contracts": "^1.3.0",
31
31
  "@openzeppelin/contracts": "^5.6.1",
32
32
  "@prb/math": "^4.1.1",
@@ -1,5 +1,5 @@
1
1
  // SPDX-License-Identifier: MIT
2
- pragma solidity 0.8.26;
2
+ pragma solidity 0.8.28;
3
3
 
4
4
  import {Sphinx} from "@sphinx-labs/contracts/contracts/foundry/SphinxPlugin.sol";
5
5
  import {Script} from "forge-std/Script.sol";
@@ -43,7 +43,7 @@ contract Deploy is Script, Sphinx {
43
43
  uint256 private CORE_DEPLOYMENT_NONCE = 6;
44
44
 
45
45
  function configureSphinx() public override {
46
- sphinxConfig.projectName = "nana-core-v5";
46
+ sphinxConfig.projectName = "nana-core-v6";
47
47
  sphinxConfig.mainnets = ["ethereum", "optimism", "base", "arbitrum"];
48
48
  sphinxConfig.testnets = ["ethereum_sepolia", "optimism_sepolia", "base_sepolia", "arbitrum_sepolia"];
49
49
  }
@@ -1,5 +1,5 @@
1
1
  // SPDX-License-Identifier: MIT
2
- pragma solidity 0.8.26;
2
+ pragma solidity 0.8.28;
3
3
 
4
4
  import {Sphinx} from "@sphinx-labs/contracts/contracts/foundry/SphinxPlugin.sol";
5
5
  import {Script} from "forge-std/Script.sol";
@@ -47,7 +47,7 @@ contract DeployPeriphery is Script, Sphinx {
47
47
  address private OMNICHAIN_RULESET_OPERATOR = address(0x8f5DED85c40b50d223269C1F922A056E72101590);
48
48
 
49
49
  function configureSphinx() public override {
50
- sphinxConfig.projectName = "nana-core-v5";
50
+ sphinxConfig.projectName = "nana-core-v6";
51
51
  sphinxConfig.mainnets = ["ethereum", "optimism", "base", "arbitrum"];
52
52
  sphinxConfig.testnets = ["ethereum_sepolia", "optimism_sepolia", "base_sepolia", "arbitrum_sepolia"];
53
53
  }
@@ -254,9 +254,6 @@ contract DeployPeriphery is Script, Sphinx {
254
254
  });
255
255
  } else if (block.chainid == 84_532) {
256
256
  usdc = address(0x036CbD53842c5426634e7929541eC2318f3dCF7e);
257
- // TODO: Verify this feed address — 0xd30e2101... is the Arbitrum Sepolia ETH/USD feed and is likely
258
- // incorrect for Base Sepolia USDC/USD. Replace with the correct Chainlink Base Sepolia USDC/USD address
259
- // before deploying. Testnet-only; does not affect mainnet deployments.
260
257
  usdcFeed = new JBChainlinkV3PriceFeed({
261
258
  feed: AggregatorV3Interface(address(0xd30e2101a97dcbAeBCBC04F14C3f624E67A35165)),
262
259
  threshold: 86_400 seconds
@@ -1,5 +1,5 @@
1
1
  // SPDX-License-Identifier: MIT
2
- pragma solidity 0.8.26;
2
+ pragma solidity 0.8.28;
3
3
 
4
4
  import {stdJson} from "forge-std/Script.sol";
5
5
  import {Vm} from "forge-std/Vm.sol";
@@ -40,7 +40,7 @@ library CoreDeploymentLib {
40
40
  address internal constant VM_ADDRESS = address(uint160(uint256(keccak256("hevm cheat code"))));
41
41
  // forge-lint: disable-next-line(screaming-snake-case-const)
42
42
  Vm internal constant vm = Vm(VM_ADDRESS);
43
- string constant PROJECT_NAME = "nana-core-v5";
43
+ string constant PROJECT_NAME = "nana-core-v6";
44
44
 
45
45
  function getDeployment(string memory path) internal returns (CoreDeployment memory deployment) {
46
46
  // get chainId for which we need to get the deployment.
@@ -1,5 +1,5 @@
1
1
  // SPDX-License-Identifier: MIT
2
- pragma solidity 0.8.26;
2
+ pragma solidity 0.8.28;
3
3
 
4
4
  import {AggregatorV3Interface} from "@chainlink/contracts/src/v0.8/shared/interfaces/AggregatorV3Interface.sol";
5
5
 
@@ -1,5 +1,5 @@
1
1
  // SPDX-License-Identifier: MIT
2
- pragma solidity 0.8.26;
2
+ pragma solidity 0.8.28;
3
3
 
4
4
  import {AggregatorV2V3Interface} from "@chainlink/contracts/src/v0.8/shared/interfaces/AggregatorV2V3Interface.sol";
5
5
  import {AggregatorV3Interface} from "@chainlink/contracts/src/v0.8/shared/interfaces/AggregatorV3Interface.sol";
@@ -1,5 +1,5 @@
1
1
  // SPDX-License-Identifier: MIT
2
- pragma solidity 0.8.26;
2
+ pragma solidity 0.8.28;
3
3
 
4
4
  import {JBPermissionIds} from "@bananapus/permission-ids-v6/src/JBPermissionIds.sol";
5
5
  import {ERC2771Context} from "@openzeppelin/contracts/metatx/ERC2771Context.sol";
@@ -95,11 +95,18 @@ contract JBController is JBPermissioned, ERC2771Context, IJBController, IJBMigra
95
95
  IJBTokens public immutable override TOKENS;
96
96
 
97
97
  /// @notice The address of the contract that manages omnichain ruleset ops.
98
- /// @dev This is a deterministic CREATE2 deployment; bytecode verification at runtime would add
99
- /// gas
100
- /// cost for marginal security benefit since the address is verified at deploy time.
101
- /// @dev Trust assumption is bounded: the operator can only queue rulesets that the omnichain
102
- /// deployer's logic permits. It cannot drain funds or bypass access controls.
98
+ /// @dev This is a deterministic CREATE2 deployment; bytecode verification at runtime would add gas cost for
99
+ /// marginal security benefit since the address is verified at deploy time.
100
+ /// @dev TRUST BOUNDARY: This hardcoded address can call `launchRulesetsFor` and `queueRulesetsOf` for ANY project,
101
+ /// bypassing normal `JBPermissions` checks. If this address is compromised or its implementation is malicious:
102
+ /// - Projects WITH approval hooks are partially protected: the attacker can queue rulesets, but they must pass
103
+ /// the project's approval hook before becoming active.
104
+ /// - Projects WITHOUT approval hooks (duration == 0 or no approval hook set) are IMMEDIATELY affected: queued
105
+ /// rulesets take effect as soon as the current ruleset expires (or immediately if duration == 0).
106
+ /// @dev The operator can also call `setTerminalsOf` during `launchRulesetsFor`, which could redirect a project's
107
+ /// payment routing. This is limited to the initial launch flow (reverts if rulesets already exist).
108
+ /// @dev Mitigation: verify the deployed bytecode at this address matches the expected `JBOmnichainDeployer`
109
+ /// contract from `nana-omnichain-deployers-v6` on every target chain before running deployment scripts.
103
110
  address public immutable override OMNICHAIN_RULESET_OPERATOR;
104
111
 
105
112
  //*********************************************************************//
@@ -164,7 +171,7 @@ contract JBController is JBPermissioned, ERC2771Context, IJBController, IJBMigra
164
171
  /// @param pricingCurrency The currency the feed's output price is in terms of.
165
172
  /// @param unitCurrency The currency being priced by the feed.
166
173
  /// @param feed The address of the price feed to add.
167
- function addPriceFeed(
174
+ function addPriceFeedFor(
168
175
  uint256 projectId,
169
176
  uint256 pricingCurrency,
170
177
  uint256 unitCurrency,
@@ -1,5 +1,5 @@
1
1
  // SPDX-License-Identifier: MIT
2
- pragma solidity 0.8.26;
2
+ pragma solidity 0.8.28;
3
3
 
4
4
  import {IERC165} from "@openzeppelin/contracts/utils/introspection/IERC165.sol";
5
5
 
@@ -1,5 +1,5 @@
1
1
  // SPDX-License-Identifier: MIT
2
- pragma solidity 0.8.26;
2
+ pragma solidity 0.8.28;
3
3
 
4
4
  import {JBPermissionIds} from "@bananapus/permission-ids-v6/src/JBPermissionIds.sol";
5
5
  import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol";