@bananapus/core-v6 0.0.37 → 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 (324) hide show
  1. package/foundry.lock +1 -7
  2. package/foundry.toml +1 -1
  3. package/package.json +19 -7
  4. package/src/JBChainlinkV3PriceFeed.sol +4 -1
  5. package/src/JBChainlinkV3SequencerPriceFeed.sol +4 -2
  6. package/src/JBController.sol +71 -44
  7. package/src/JBDeadline.sol +4 -4
  8. package/src/JBDirectory.sol +34 -32
  9. package/src/JBERC20.sol +5 -4
  10. package/src/JBFeelessAddresses.sol +6 -3
  11. package/src/JBFundAccessLimits.sol +25 -21
  12. package/src/JBMultiTerminal.sol +121 -84
  13. package/src/JBPermissions.sol +34 -37
  14. package/src/JBPrices.sol +23 -18
  15. package/src/JBProjects.sol +6 -3
  16. package/src/JBRulesets.sol +44 -41
  17. package/src/JBSplits.sol +18 -16
  18. package/src/JBTerminalStore.sol +32 -25
  19. package/src/JBTokens.sol +36 -26
  20. package/src/abstract/JBControlled.sol +3 -1
  21. package/src/abstract/JBPermissioned.sol +3 -1
  22. package/src/enums/JBApprovalStatus.sol +7 -1
  23. package/src/interfaces/IJBController.sol +7 -3
  24. package/src/interfaces/IJBDirectory.sol +3 -1
  25. package/src/interfaces/IJBMultiTerminal.sol +3 -2
  26. package/src/interfaces/IJBPermissions.sol +2 -1
  27. package/src/interfaces/IJBPrices.sol +3 -1
  28. package/src/interfaces/IJBRulesets.sol +2 -1
  29. package/src/interfaces/IJBSplits.sol +2 -1
  30. package/src/interfaces/IJBTerminal.sol +3 -1
  31. package/src/interfaces/IJBTerminalStore.sol +3 -1
  32. package/src/interfaces/IJBTokens.sol +2 -1
  33. package/src/libraries/JBCashOuts.sol +6 -1
  34. package/src/libraries/JBConstants.sol +12 -3
  35. package/src/libraries/JBCurrencyIds.sol +2 -0
  36. package/src/libraries/JBFees.sol +52 -10
  37. package/src/libraries/JBFixedPointNumber.sol +2 -0
  38. package/src/libraries/JBPayoutSplitGroupLib.sol +7 -4
  39. package/src/libraries/JBRulesetMetadataResolver.sol +4 -0
  40. package/src/libraries/JBSplitGroupIds.sol +2 -1
  41. package/src/libraries/JBSurplus.sol +3 -1
  42. package/src/periphery/JBMatchingPriceFeed.sol +3 -1
  43. package/src/structs/JBAccountingContext.sol +7 -4
  44. package/src/structs/JBFundAccessLimitGroup.sol +10 -17
  45. package/src/structs/JBRuleset.sol +18 -26
  46. package/src/structs/JBRulesetConfig.sol +13 -25
  47. package/src/structs/JBRulesetMetadata.sol +25 -32
  48. package/test/mock/MockMaliciousBeneficiary.sol +15 -15
  49. package/ADMINISTRATION.md +0 -103
  50. package/ARCHITECTURE.md +0 -133
  51. package/AUDIT_INSTRUCTIONS.md +0 -139
  52. package/RISKS.md +0 -215
  53. package/SKILLS.md +0 -55
  54. package/STYLE_GUIDE.md +0 -610
  55. package/USER_JOURNEYS.md +0 -215
  56. package/script/Deploy.s.sol +0 -124
  57. package/script/DeployPeriphery.s.sol +0 -354
  58. package/slither-ci.config.json +0 -10
  59. package/test/AuditFixes.t.sol +0 -808
  60. package/test/ComprehensiveInvariant.t.sol +0 -306
  61. package/test/CoreExploitTests.t.sol +0 -2741
  62. package/test/EconomicSimulation.t.sol +0 -348
  63. package/test/EntryPointPermutations.t.sol +0 -684
  64. package/test/FlashLoanAttacks.t.sol +0 -797
  65. package/test/PermissionEscalation.t.sol +0 -711
  66. package/test/PermissionsInvariant.t.sol +0 -403
  67. package/test/RulesetTransitions.t.sol +0 -713
  68. package/test/SplitLoopTests.t.sol +0 -752
  69. package/test/TestAccessToFunds.sol +0 -2683
  70. package/test/TestAuditResponseDesignProofs.sol +0 -434
  71. package/test/TestCashOut.sol +0 -198
  72. package/test/TestCashOutCountFor.sol +0 -271
  73. package/test/TestCashOutHooks.sol +0 -351
  74. package/test/TestCashOutTimingEdge.sol +0 -241
  75. package/test/TestDataHookFuzzing.sol +0 -524
  76. package/test/TestDurationUnderflow.sol +0 -233
  77. package/test/TestFeeFreeCashOutBypass.sol +0 -949
  78. package/test/TestFeeProcessingFailure.sol +0 -218
  79. package/test/TestFees.sol +0 -619
  80. package/test/TestForwardedTokenConsumption.sol +0 -425
  81. package/test/TestInterfaceSupport.sol +0 -81
  82. package/test/TestJBERC20Inheritance.sol +0 -103
  83. package/test/TestL2SequencerPriceFeed.sol +0 -292
  84. package/test/TestLaunchProject.sol +0 -188
  85. package/test/TestMetaTx.sol +0 -217
  86. package/test/TestMetadataOffsetOverflow.sol +0 -179
  87. package/test/TestMetadataParserLib.sol +0 -471
  88. package/test/TestMigrationHeldFees.sol +0 -255
  89. package/test/TestMintTokensOf.sol +0 -185
  90. package/test/TestMultiTerminalSurplus.sol +0 -348
  91. package/test/TestMultiTokenSurplus.sol +0 -202
  92. package/test/TestMultipleAccessLimits.sol +0 -664
  93. package/test/TestPayBurnRedeemFlow.sol +0 -195
  94. package/test/TestPayHooks.sol +0 -209
  95. package/test/TestPermissions.sol +0 -324
  96. package/test/TestPermissionsEdge.sol +0 -290
  97. package/test/TestPermit2DataHook.t.sol +0 -360
  98. package/test/TestPermit2Terminal.sol +0 -372
  99. package/test/TestRulesetQueueing.sol +0 -1025
  100. package/test/TestRulesetQueuingStress.sol +0 -806
  101. package/test/TestRulesetWeightCaching.sol +0 -178
  102. package/test/TestSplits.sol +0 -391
  103. package/test/TestTerminalMigration.sol +0 -274
  104. package/test/TestTerminalPreviewParity.sol +0 -208
  105. package/test/TestTokenFlow.sol +0 -191
  106. package/test/TestWeightCacheStaleAfterRejection.sol +0 -303
  107. package/test/WeirdTokenTests.t.sol +0 -817
  108. package/test/audit/CashOutReenterPay.t.sol +0 -501
  109. package/test/audit/CodexHeldFeeRounding.t.sol +0 -159
  110. package/test/audit/CodexMigrationFeeFailure.t.sol +0 -163
  111. package/test/audit/CrossTerminalSurplusSpoof.t.sol +0 -140
  112. package/test/audit/CycledSurplusAllowanceReset.t.sol +0 -184
  113. package/test/audit/FeeFreeSurplusLifecycle.t.sol +0 -399
  114. package/test/audit/FeeFreeSurplusStale.t.sol +0 -248
  115. package/test/audit/USDTVoidReturnCompat.t.sol +0 -525
  116. package/test/fork/TestChainlinkPriceFeedFork.sol +0 -254
  117. package/test/fork/TestSequencerPriceFeedFork.sol +0 -168
  118. package/test/fork/TestTerminalPreviewParityFork.sol +0 -108
  119. package/test/formal/BondingCurveProperties.t.sol +0 -420
  120. package/test/formal/FeeProperties.t.sol +0 -252
  121. package/test/invariants/Phase3DeepInvariant.t.sol +0 -412
  122. package/test/invariants/RulesetsInvariant.t.sol +0 -125
  123. package/test/invariants/TerminalStoreInvariant.t.sol +0 -227
  124. package/test/invariants/TokensInvariant.t.sol +0 -195
  125. package/test/invariants/handlers/ComprehensiveHandler.sol +0 -303
  126. package/test/invariants/handlers/EconomicHandler.sol +0 -377
  127. package/test/invariants/handlers/Phase3Handler.sol +0 -443
  128. package/test/invariants/handlers/RulesetsHandler.sol +0 -115
  129. package/test/invariants/handlers/TerminalStoreHandler.sol +0 -151
  130. package/test/invariants/handlers/TokensHandler.sol +0 -126
  131. package/test/regression/HoldFeesCashOutReserved.t.sol +0 -415
  132. package/test/regression/WeightCacheBoundary.t.sol +0 -291
  133. package/test/trees/JBController/burnTokensOf.tree +0 -9
  134. package/test/trees/JBController/claimTokensFor.tree +0 -5
  135. package/test/trees/JBController/deployERC20For.tree +0 -5
  136. package/test/trees/JBController/getRulesetOf.tree +0 -5
  137. package/test/trees/JBController/launchProjectFor.tree +0 -12
  138. package/test/trees/JBController/launchRulesetsFor.tree +0 -8
  139. package/test/trees/JBController/migrateController.tree +0 -12
  140. package/test/trees/JBController/mintTokensOf.tree +0 -12
  141. package/test/trees/JBController/payReservedTokenToTerminal.tree +0 -8
  142. package/test/trees/JBController/receiveMigrationFrom.tree +0 -4
  143. package/test/trees/JBController/sendReservedTokensToSplitsOf.tree +0 -12
  144. package/test/trees/JBController/setMetadataOf.tree +0 -5
  145. package/test/trees/JBController/setSplitGroupsOf.tree +0 -5
  146. package/test/trees/JBController/setTokenFor.tree +0 -5
  147. package/test/trees/JBController/transferCreditsFrom.tree +0 -8
  148. package/test/trees/JBDirectory/primaryTerminalOf.tree +0 -8
  149. package/test/trees/JBDirectory/setControllerOf.tree +0 -11
  150. package/test/trees/JBDirectory/setPrimaryTerminalOf.tree +0 -15
  151. package/test/trees/JBDirectory/setTerminalsOf.tree +0 -11
  152. package/test/trees/JBERC20/initialize.tree +0 -7
  153. package/test/trees/JBERC20/name.tree +0 -5
  154. package/test/trees/JBERC20/nonces.tree +0 -5
  155. package/test/trees/JBERC20/symbol.tree +0 -5
  156. package/test/trees/JBFeelessAddresses/setFeelessAddress.tree +0 -5
  157. package/test/trees/JBFeelessAddresses/supportsInterface.tree +0 -5
  158. package/test/trees/JBFundAccessLimits/payoutLimitOf.tree +0 -5
  159. package/test/trees/JBFundAccessLimits/payoutLimitsOf.tree +0 -8
  160. package/test/trees/JBFundAccessLimits/setFundAccessLimitsFor.tree +0 -18
  161. package/test/trees/JBFundAccessLimits/surplusAllowanceOf.tree +0 -5
  162. package/test/trees/JBFundAccessLimits/surplusAllowancesOf.tree +0 -8
  163. package/test/trees/JBMetadataResolver/getDataFor.tree +0 -8
  164. package/test/trees/JBMultiTerminal/accountingContextsOf.tree +0 -5
  165. package/test/trees/JBMultiTerminal/addAccountingContextsFor.tree +0 -10
  166. package/test/trees/JBMultiTerminal/addToBalanceOf.tree +0 -23
  167. package/test/trees/JBMultiTerminal/cashOutTokensOf.tree +0 -23
  168. package/test/trees/JBMultiTerminal/executePayout.tree +0 -32
  169. package/test/trees/JBMultiTerminal/executeProcessFee.tree +0 -14
  170. package/test/trees/JBMultiTerminal/migrateBalanceOf.tree +0 -12
  171. package/test/trees/JBMultiTerminal/pay.tree +0 -23
  172. package/test/trees/JBMultiTerminal/processHeldFeesOf.tree +0 -8
  173. package/test/trees/JBMultiTerminal/sendPayoutsOf.tree +0 -34
  174. package/test/trees/JBMultiTerminal/useAllowanceOf.tree +0 -16
  175. package/test/trees/JBPermissions/hasPermission.tree +0 -8
  176. package/test/trees/JBPermissions/hasPermissions.tree +0 -8
  177. package/test/trees/JBPermissions/setPermissionsFor.tree +0 -5
  178. package/test/trees/JBPrices/addPriceFeedFor.tree +0 -14
  179. package/test/trees/JBPrices/pricePerUnitOf.tree +0 -11
  180. package/test/trees/JBProjects/createFor.tree +0 -11
  181. package/test/trees/JBProjects/setTokenUriResolver.tree +0 -5
  182. package/test/trees/JBProjects/supportsInterface.tree +0 -9
  183. package/test/trees/JBProjects/tokenURI.tree +0 -5
  184. package/test/trees/JBRulesets/currentApprovalStatusForLatestRulesetOf.tree +0 -8
  185. package/test/trees/JBRulesets/currentOf.tree +0 -12
  186. package/test/trees/JBRulesets/getRulesetOf.tree +0 -5
  187. package/test/trees/JBRulesets/latestQueuedRulesetOf.tree +0 -10
  188. package/test/trees/JBRulesets/rulesetsOf.tree +0 -11
  189. package/test/trees/JBRulesets/upcomingRulesetOf.tree +0 -20
  190. package/test/trees/JBRulesets/updateRulesetWeightCache.tree +0 -5
  191. package/test/trees/JBSplits/setSplitGroupsOf.tree +0 -17
  192. package/test/trees/JBSplits/splitsOf.tree +0 -5
  193. package/test/trees/JBTerminalStore/currentReclaimableSurplusOf.tree +0 -16
  194. package/test/trees/JBTerminalStore/currentSurplusOf.tree +0 -25
  195. package/test/trees/JBTerminalStore/currentTotalSurplusOf.tree +0 -5
  196. package/test/trees/JBTerminalStore/recordCashOutsFor.tree +0 -16
  197. package/test/trees/JBTerminalStore/recordPaymentFrom.tree +0 -14
  198. package/test/trees/JBTerminalStore/recordPayoutFor.tree +0 -10
  199. package/test/trees/JBTerminalStore/recordTerminalMigration.tree +0 -5
  200. package/test/trees/JBTerminalStore/recordUsedAllowanceOf.tree +0 -10
  201. package/test/trees/JBTokens/burnFrom.tree +0 -10
  202. package/test/trees/JBTokens/claimTokensFor.tree +0 -10
  203. package/test/trees/JBTokens/deployERC20For.tree +0 -12
  204. package/test/trees/JBTokens/mintFor.tree +0 -10
  205. package/test/trees/JBTokens/setTokenFor.tree +0 -11
  206. package/test/trees/JBTokens/totalBalanceOf.tree +0 -5
  207. package/test/trees/JBTokens/totalSupplyOf.tree +0 -5
  208. package/test/trees/JBTokens/transferCreditsFrom.tree +0 -8
  209. package/test/trees/mintTokensOf.tree +0 -12
  210. package/test/units/static/JBChainlinkV3PriceFeed/TestPriceFeed.sol +0 -223
  211. package/test/units/static/JBController/JBControllerSetup.sol +0 -50
  212. package/test/units/static/JBController/TestBurnTokensOf.sol +0 -114
  213. package/test/units/static/JBController/TestClaimTokensFor.sol +0 -63
  214. package/test/units/static/JBController/TestDeployErc20For.sol +0 -86
  215. package/test/units/static/JBController/TestLaunchProjectFor.sol +0 -302
  216. package/test/units/static/JBController/TestLaunchRulesetsFor.sol +0 -342
  217. package/test/units/static/JBController/TestMigrateController.sol +0 -157
  218. package/test/units/static/JBController/TestMintTokensOfUnits.sol +0 -111
  219. package/test/units/static/JBController/TestOmnichainRulesetOperator.sol +0 -324
  220. package/test/units/static/JBController/TestPayReservedTokenToTerminal.sol +0 -74
  221. package/test/units/static/JBController/TestPreviewMintOf.sol +0 -117
  222. package/test/units/static/JBController/TestReceiveMigrationFrom.sol +0 -99
  223. package/test/units/static/JBController/TestRulesetViews.sol +0 -225
  224. package/test/units/static/JBController/TestSendReservedTokensToSplitsOf.sol +0 -615
  225. package/test/units/static/JBController/TestSetSplitGroupsOf.sol +0 -68
  226. package/test/units/static/JBController/TestSetTokenFor.sol +0 -239
  227. package/test/units/static/JBController/TestSetUriOf.sol +0 -57
  228. package/test/units/static/JBController/TestTransferCreditsFrom.sol +0 -169
  229. package/test/units/static/JBDeadline/TestDeadlineFuzz.sol +0 -211
  230. package/test/units/static/JBDirectory/JBDirectorySetup.sol +0 -26
  231. package/test/units/static/JBDirectory/TestPrimaryTerminalOf.sol +0 -126
  232. package/test/units/static/JBDirectory/TestSetControllerOf.sol +0 -183
  233. package/test/units/static/JBDirectory/TestSetControllerOfMigrationOrder.sol +0 -104
  234. package/test/units/static/JBDirectory/TestSetPrimaryTerminalOf.sol +0 -179
  235. package/test/units/static/JBDirectory/TestSetTerminalsOf.sol +0 -137
  236. package/test/units/static/JBERC20/JBERC20Setup.sol +0 -34
  237. package/test/units/static/JBERC20/SigUtils.sol +0 -36
  238. package/test/units/static/JBERC20/TestInitialize.sol +0 -60
  239. package/test/units/static/JBERC20/TestName.sol +0 -30
  240. package/test/units/static/JBERC20/TestNonces.sol +0 -62
  241. package/test/units/static/JBERC20/TestSymbol.sol +0 -31
  242. package/test/units/static/JBFeelessAdresses/JBFeelessSetup.sol +0 -22
  243. package/test/units/static/JBFeelessAdresses/TestInterfaces.sol +0 -30
  244. package/test/units/static/JBFeelessAdresses/TestSetFeelessAddress.sol +0 -35
  245. package/test/units/static/JBFees/TestFeesFuzz.sol +0 -79
  246. package/test/units/static/JBFixedPointNumber/TestAdjustDecimals.sol +0 -16
  247. package/test/units/static/JBFixedPointNumber/TestAdjustDecimalsFuzz.sol +0 -71
  248. package/test/units/static/JBFundAccessLimits/JBFundAccessSetup.sol +0 -24
  249. package/test/units/static/JBFundAccessLimits/TestFundAccessLimitsEdge.sol +0 -163
  250. package/test/units/static/JBFundAccessLimits/TestPayoutLimitOf.sol +0 -59
  251. package/test/units/static/JBFundAccessLimits/TestPayoutLimitsOf.sol +0 -101
  252. package/test/units/static/JBFundAccessLimits/TestSetFundAccessLimitsFor.sol +0 -189
  253. package/test/units/static/JBFundAccessLimits/TestSurplusAllowanceOf.sol +0 -64
  254. package/test/units/static/JBFundAccessLimits/TestSurplusAllowancesOf.sol +0 -102
  255. package/test/units/static/JBMetadataResolver/TestGetDataFor.sol +0 -90
  256. package/test/units/static/JBMetadataResolver/TestMetadataResolverEdgeCases.sol +0 -247
  257. package/test/units/static/JBMetadataResolver/TestMetadataResolverFuzz.sol +0 -229
  258. package/test/units/static/JBMultiTerminal/JBMultiTerminalSetup.sol +0 -50
  259. package/test/units/static/JBMultiTerminal/TestAccountingContextsOf.sol +0 -72
  260. package/test/units/static/JBMultiTerminal/TestAddAccountingContextsFor.sol +0 -289
  261. package/test/units/static/JBMultiTerminal/TestAddToBalanceOf.sol +0 -474
  262. package/test/units/static/JBMultiTerminal/TestCashOutTokensOf.sol +0 -624
  263. package/test/units/static/JBMultiTerminal/TestExecutePayout.sol +0 -578
  264. package/test/units/static/JBMultiTerminal/TestExecuteProcessFee.sol +0 -202
  265. package/test/units/static/JBMultiTerminal/TestMigrateBalanceOf.sol +0 -222
  266. package/test/units/static/JBMultiTerminal/TestPay.sol +0 -604
  267. package/test/units/static/JBMultiTerminal/TestPreviewCashOutFrom.sol +0 -117
  268. package/test/units/static/JBMultiTerminal/TestPreviewPayFor.sol +0 -114
  269. package/test/units/static/JBMultiTerminal/TestProcessHeldFeesOf.sol +0 -228
  270. package/test/units/static/JBMultiTerminal/TestSelfPayRevert.sol +0 -55
  271. package/test/units/static/JBMultiTerminal/TestSendPayoutsOf.sol +0 -257
  272. package/test/units/static/JBMultiTerminal/TestUseAllowanceOf.sol +0 -611
  273. package/test/units/static/JBPermissions/JBPermissionsSetup.sol +0 -20
  274. package/test/units/static/JBPermissions/TestHasPermission.sol +0 -50
  275. package/test/units/static/JBPermissions/TestHasPermissions.sol +0 -93
  276. package/test/units/static/JBPermissions/TestSetPermissionsFor.sol +0 -64
  277. package/test/units/static/JBPrices/JBPricesSetup.sol +0 -32
  278. package/test/units/static/JBPrices/TestAddPriceFeedFor.sol +0 -107
  279. package/test/units/static/JBPrices/TestPricePerUnitOf.sol +0 -132
  280. package/test/units/static/JBPrices/TestPrices.sol +0 -265
  281. package/test/units/static/JBProjects/JBProjectsSetup.sol +0 -22
  282. package/test/units/static/JBProjects/TestCreateFor.sol +0 -71
  283. package/test/units/static/JBProjects/TestInitialProject.sol +0 -21
  284. package/test/units/static/JBProjects/TestInterfaces.sol +0 -26
  285. package/test/units/static/JBProjects/TestSetResolver.sol +0 -37
  286. package/test/units/static/JBProjects/TestTokenUri.sol +0 -40
  287. package/test/units/static/JBRulesetMetadataResolver/TestSetCashOutTaxRateTo.sol +0 -108
  288. package/test/units/static/JBRulesets/JBRulesetsSetup.sol +0 -24
  289. package/test/units/static/JBRulesets/TestCurrentApprovalStatusForLatestRulesetOf.sol +0 -265
  290. package/test/units/static/JBRulesets/TestCurrentOf.sol +0 -242
  291. package/test/units/static/JBRulesets/TestGetRulesetOf.sol +0 -100
  292. package/test/units/static/JBRulesets/TestLatestQueuedRulesetOf.sol +0 -260
  293. package/test/units/static/JBRulesets/TestRulesets.sol +0 -632
  294. package/test/units/static/JBRulesets/TestRulesetsOf.sol +0 -37
  295. package/test/units/static/JBRulesets/TestUpcomingRulesetOf.sol +0 -522
  296. package/test/units/static/JBRulesets/TestUpdateRulesetWeightCache.sol +0 -96
  297. package/test/units/static/JBSplits/JBSplitsSetup.sol +0 -26
  298. package/test/units/static/JBSplits/TestSelfManagedSplitGroups.sol +0 -552
  299. package/test/units/static/JBSplits/TestSetSplitGroupsOf.sol +0 -377
  300. package/test/units/static/JBSplits/TestSplitsLockedEdge.sol +0 -267
  301. package/test/units/static/JBSplits/TestSplitsOf.sol +0 -24
  302. package/test/units/static/JBSplits/TestSplitsPacking.sol +0 -36
  303. package/test/units/static/JBSurplus/TestSurplusFuzz.sol +0 -160
  304. package/test/units/static/JBTerminalStore/JBTerminalStoreSetup.sol +0 -45
  305. package/test/units/static/JBTerminalStore/TestCurrentReclaimableSurplusOf.sol +0 -536
  306. package/test/units/static/JBTerminalStore/TestCurrentSurplusOf.sol +0 -463
  307. package/test/units/static/JBTerminalStore/TestCurrentTotalSurplusOf.sol +0 -135
  308. package/test/units/static/JBTerminalStore/TestPreviewCashOutFrom.sol +0 -476
  309. package/test/units/static/JBTerminalStore/TestPreviewPayFrom.sol +0 -494
  310. package/test/units/static/JBTerminalStore/TestRecordCashOutsFor.sol +0 -652
  311. package/test/units/static/JBTerminalStore/TestRecordPaymentFrom.sol +0 -744
  312. package/test/units/static/JBTerminalStore/TestRecordPayoutFor.sol +0 -289
  313. package/test/units/static/JBTerminalStore/TestRecordTerminalMigration.sol +0 -138
  314. package/test/units/static/JBTerminalStore/TestRecordUsedAllowanceOf.sol +0 -415
  315. package/test/units/static/JBTerminalStore/TestUint224Overflow.sol +0 -219
  316. package/test/units/static/JBTokens/JBTokensSetup.sol +0 -32
  317. package/test/units/static/JBTokens/TestBurnFrom.sol +0 -107
  318. package/test/units/static/JBTokens/TestClaimTokensFor.sol +0 -110
  319. package/test/units/static/JBTokens/TestDeployERC20ForUnits.sol +0 -92
  320. package/test/units/static/JBTokens/TestMintFor.sol +0 -100
  321. package/test/units/static/JBTokens/TestSetTokenFor.sol +0 -98
  322. package/test/units/static/JBTokens/TestTotalBalanceOf.sol +0 -65
  323. package/test/units/static/JBTokens/TestTotalSupplyOf.sol +0 -56
  324. package/test/units/static/JBTokens/TestTransferCreditsFrom.sol +0 -56
@@ -7,8 +7,12 @@ import {IJBFundAccessLimits} from "./interfaces/IJBFundAccessLimits.sol";
7
7
  import {JBCurrencyAmount} from "./structs/JBCurrencyAmount.sol";
8
8
  import {JBFundAccessLimitGroup} from "./structs/JBFundAccessLimitGroup.sol";
9
9
 
10
- /// @notice Stores and manages terminal fund access limits for each project.
11
- /// @dev See the `JBFundAccessLimitGroup` struct to learn about payout limits and surplus allowances.
10
+ /// @notice Controls how much a project can withdraw from its terminals each funding cycle. Two types of limits:
11
+ /// **Payout limits** cap how much can be distributed to splits and the project owner. **Surplus allowances** cap how
12
+ /// much the project owner can pull from the surplus (funds above payout limits). Both reset each ruleset cycle.
13
+ /// @dev Limits are denominated in a currency (which may differ from the held token) and resolved at withdrawal time
14
+ /// via `JBPrices`. An empty `fundAccessLimitGroups` array means zero access (not unlimited) — use `type(uint224).max`
15
+ /// for unlimited.
12
16
  contract JBFundAccessLimits is JBControlled, IJBFundAccessLimits {
13
17
  //*********************************************************************//
14
18
  // --------------------------- custom errors ------------------------- //
@@ -61,10 +65,11 @@ contract JBFundAccessLimits is JBControlled, IJBFundAccessLimits {
61
65
  // ---------------------- external transactions ---------------------- //
62
66
  //*********************************************************************//
63
67
 
64
- /// @notice Sets limits on the amount of funds a project can access from its terminals during a ruleset.
65
- /// @dev Only a project's controller can set its fund access limits.
66
- /// @dev Payout limits and surplus allowances must be specified in strictly increasing order (by currency) to
67
- /// prevent duplicates.
68
+ /// @notice Configure how much a project can withdraw from each of its terminals during a ruleset. Payout limits
69
+ /// cap how much can be distributed to splits/owner; surplus allowances cap how much extra the owner can pull from
70
+ /// surplus. Both reset each funding cycle.
71
+ /// @dev Only a project's controller can set fund access limits (called during `queueRulesetsOf`).
72
+ /// @dev Limits within each group must be sorted by currency in strictly increasing order to prevent duplicates.
68
73
  /// @param projectId The ID of the project whose fund access limits are being set.
69
74
  /// @param rulesetId The ID of the ruleset that the limits will apply within.
70
75
  /// @param fundAccessLimitGroups An array containing payout limits and surplus allowances for each payment terminal.
@@ -152,14 +157,15 @@ contract JBFundAccessLimits is JBControlled, IJBFundAccessLimits {
152
157
  // ------------------------- external views -------------------------- //
153
158
  //*********************************************************************//
154
159
 
155
- /// @notice A project's payout limit for a given ruleset, terminal, token, and currency.
156
- /// @dev The fixed point return amount will use the same number of decimals as the `terminal`.
160
+ /// @notice Look up how much a project can distribute (via `sendPayoutsOf`) from a specific terminal and token,
161
+ /// denominated in a specific currency. Returns 0 if no limit is configured for that currency.
162
+ /// @dev The fixed point return amount uses the same number of decimals as the terminal.
157
163
  /// @param projectId The project's ID.
158
164
  /// @param rulesetId The ruleset's ID.
159
165
  /// @param terminal The terminal the payout limit applies to.
160
166
  /// @param token The token the payout limit applies to.
161
167
  /// @param currency The currency the payout limit is denominated in.
162
- /// @return payoutLimit The payout limit, as a fixed point number with the same number of decimals as the provided
168
+ /// @return payoutLimit The payout limit, as a fixed point number with the same number of decimals as the
163
169
  /// terminal.
164
170
  function payoutLimitOf(
165
171
  uint256 projectId,
@@ -195,10 +201,9 @@ contract JBFundAccessLimits is JBControlled, IJBFundAccessLimits {
195
201
  }
196
202
  }
197
203
 
198
- /// @notice A project's payout limits for a given ruleset, terminal, and token.
199
- /// @dev The total value of `token`s that a project can pay out from the terminal during the ruleset is dictated
200
- /// by a list of payout limits. Each payout limit is a fixed-point amount in terms of a currency.
201
- /// @dev The fixed point `amount`s returned will use the same number of decimals as the `terminal`.
204
+ /// @notice Get all payout limits for a project's terminal and token during a ruleset. A project can have multiple
205
+ /// payout limits denominated in different currencies (e.g. 10,000 USD + 5 ETH). Each is enforced independently.
206
+ /// @dev The fixed point `amount`s returned use the same number of decimals as the terminal.
202
207
  /// @param projectId The project's ID.
203
208
  /// @param rulesetId The ruleset's ID.
204
209
  /// @param terminal The terminal the payout limits apply to.
@@ -243,15 +248,16 @@ contract JBFundAccessLimits is JBControlled, IJBFundAccessLimits {
243
248
  }
244
249
  }
245
250
 
246
- /// @notice A project's surplus allowance for a given ruleset, terminal, token, and currency.
247
- /// @dev The fixed point return amount will use the same number of decimals as the `terminal`.
251
+ /// @notice Look up how much a project's owner can withdraw from the surplus (via `useAllowanceOf`) from a specific
252
+ /// terminal and token, denominated in a specific currency. Returns 0 if no allowance is configured.
253
+ /// @dev The fixed point return amount uses the same number of decimals as the terminal.
248
254
  /// @param projectId The project's ID.
249
255
  /// @param rulesetId The ruleset's ID.
250
256
  /// @param terminal The terminal the surplus allowance applies to.
251
257
  /// @param token The token the surplus allowance applies to.
252
258
  /// @param currency The currency that the surplus allowance is denominated in.
253
259
  /// @return surplusAllowance The surplus allowance, as a fixed point number with the same number of decimals as the
254
- /// provided terminal.
260
+ /// terminal.
255
261
  function surplusAllowanceOf(
256
262
  uint256 projectId,
257
263
  uint256 rulesetId,
@@ -287,11 +293,9 @@ contract JBFundAccessLimits is JBControlled, IJBFundAccessLimits {
287
293
  }
288
294
  }
289
295
 
290
- /// @notice A project's surplus allowances for a given ruleset, terminal, and token.
291
- /// @dev The total value of `token`s that a project can pay out from its surplus in a terminal during the ruleset
292
- /// is dictated by a list of surplus allowances. Each surplus allowance is a fixed-point amount in terms of a
293
- /// currency.
294
- /// @dev The fixed point `amount`s returned will use the same number of decimals as the `terminal`.
296
+ /// @notice Get all surplus allowances for a project's terminal and token during a ruleset. Like payout limits, a
297
+ /// project can have multiple surplus allowances in different currencies, each enforced independently.
298
+ /// @dev The fixed point `amount`s returned use the same number of decimals as the terminal.
295
299
  /// @param projectId The project's ID.
296
300
  /// @param rulesetId The ruleset's ID.
297
301
  /// @param terminal The terminal the surplus allowances apply to.
@@ -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;
@@ -83,6 +88,9 @@ contract JBMultiTerminal is JBPermissioned, ERC2771Context, IJBMultiTerminal {
83
88
  // ------------------------ internal constants ----------------------- //
84
89
  //*********************************************************************//
85
90
 
91
+ /// @notice Denominator for forward-calculating this terminal's fee from a pre-fee amount.
92
+ uint256 internal constant _FEE_AMOUNT_FROM_DENOMINATOR = JBConstants.MAX_FEE / FEE;
93
+
86
94
  /// @notice Project ID #1 receives fees. It should be the first project launched during the deployment process.
87
95
  uint256 internal constant _FEE_BENEFICIARY_PROJECT_ID = 1;
88
96
 
@@ -182,10 +190,10 @@ contract JBMultiTerminal is JBPermissioned, ERC2771Context, IJBMultiTerminal {
182
190
  // ---------------------- external transactions ---------------------- //
183
191
  //*********************************************************************//
184
192
 
185
- /// @notice Adds accounting contexts for a project to this terminal so the project can begin accepting the tokens in
186
- /// those contexts.
187
- /// @dev Only a project's owner, an operator with the `ADD_ACCOUNTING_CONTEXTS` permission from that owner, or a
188
- /// 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.
189
197
  /// @param projectId The ID of the project having to add accounting contexts for.
190
198
  /// @param accountingContexts The accounting contexts to add.
191
199
  function addAccountingContextsFor(
@@ -216,8 +224,9 @@ contract JBMultiTerminal is JBPermissioned, ERC2771Context, IJBMultiTerminal {
216
224
  }
217
225
  }
218
226
 
219
- /// @notice Adds funds to a project's balance without minting tokens.
220
- /// @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.
221
230
  /// @param projectId The ID of the project to add funds to the balance of.
222
231
  /// @param amount The amount of tokens to add to the balance, as a fixed point number with the same number of
223
232
  /// decimals as this terminal. If this is a native token terminal, this is ignored and `msg.value` is used instead.
@@ -248,10 +257,9 @@ contract JBMultiTerminal is JBPermissioned, ERC2771Context, IJBMultiTerminal {
248
257
  });
249
258
  }
250
259
 
251
- /// @notice Holders can cash out a project's tokens to reclaim some of that project's surplus tokens, or to trigger
252
- /// rules determined by the current ruleset's data hook and cash out hook.
253
- /// @dev Only a token's holder or an operator with the `CASH_OUT_TOKENS` permission from that holder can cash out
254
- /// 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.
255
263
  /// @param holder The account whose tokens are being cashed out.
256
264
  /// @param projectId The ID of the project the project tokens belong to.
257
265
  /// @param cashOutCount The number of project tokens to cash out and burn, as a fixed point number with 18
@@ -330,15 +338,18 @@ contract JBMultiTerminal is JBPermissioned, ERC2771Context, IJBMultiTerminal {
330
338
  // This payout is eligible for a fee since the funds are leaving this contract and the split hook isn't a
331
339
  // feeless address.
332
340
  if (!_isFeeless(address(split.hook))) {
333
- netPayoutAmount -= JBFees.feeAmountFrom({amountBeforeFee: amount, feePercent: FEE});
341
+ unchecked {
342
+ netPayoutAmount -= _feeAmountFrom(amount);
343
+ }
334
344
  }
335
345
 
336
346
  // Create the context to send to the split hook.
337
347
  JBSplitHookContext memory context = JBSplitHookContext({
338
348
  token: token,
339
349
  amount: netPayoutAmount,
340
- decimals: STORE.accountingContextOf({terminal: address(this), projectId: projectId, token: token})
341
- .decimals,
350
+ decimals: STORE.accountingContextOf({
351
+ terminal: address(this), projectId: projectId, token: token
352
+ }).decimals,
342
353
  projectId: projectId,
343
354
  groupId: uint256(uint160(token)),
344
355
  split: split
@@ -370,7 +381,9 @@ contract JBMultiTerminal is JBPermissioned, ERC2771Context, IJBMultiTerminal {
370
381
  // This payout is eligible for a fee if the funds are leaving this contract and the receiving terminal isn't
371
382
  // a feeless address.
372
383
  if (terminal != this && !_isFeeless(address(terminal))) {
373
- netPayoutAmount -= JBFees.feeAmountFrom({amountBeforeFee: amount, feePercent: FEE});
384
+ unchecked {
385
+ netPayoutAmount -= _feeAmountFrom(amount);
386
+ }
374
387
  }
375
388
 
376
389
  // Track the fee-free payout amount. During cashout at zero tax rate, fees apply
@@ -430,7 +443,9 @@ contract JBMultiTerminal is JBPermissioned, ERC2771Context, IJBMultiTerminal {
430
443
  // This payout is eligible for a fee since the funds are leaving this contract and the recipient isn't a
431
444
  // feeless address.
432
445
  if (!_isFeeless(recipient)) {
433
- netPayoutAmount -= JBFees.feeAmountFrom({amountBeforeFee: amount, feePercent: FEE});
446
+ unchecked {
447
+ netPayoutAmount -= _feeAmountFrom(amount);
448
+ }
434
449
  }
435
450
 
436
451
  // If there's a beneficiary, send the funds directly to the beneficiary. Otherwise send to the
@@ -553,7 +568,8 @@ contract JBMultiTerminal is JBPermissioned, ERC2771Context, IJBMultiTerminal {
553
568
  }
554
569
  }
555
570
 
556
- /// @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.
557
573
  /// @param projectId The ID of the project being paid.
558
574
  /// @param amount The amount of terminal tokens being received, as a fixed point number with the same number of
559
575
  /// decimals as this terminal. If this terminal's token is native, this is ignored and `msg.value` is used in its
@@ -612,19 +628,13 @@ contract JBMultiTerminal is JBPermissioned, ERC2771Context, IJBMultiTerminal {
612
628
  _checkMin({value: beneficiaryTokenCount, min: minReturnedTokens});
613
629
  }
614
630
 
615
- /// @notice Process any fees that are being held for the project.
616
- /// @dev Reentrancy safety: the loop re-reads `_nextHeldFeeIndexOf` from storage each iteration and advances the
617
- /// index before the external `_processFee` call, so a reentrant call cannot double-process the same fee entry.
618
- /// @dev Held fees after migration: held fees remain in this terminal after `migrateBalanceOf` because their backing
619
- /// tokens are not part of `balanceOf` they were already deducted from the recorded balance during the payout
620
- /// that
621
- /// created them. The actual fee-backing tokens remain in this terminal's token holdings. If `_processFee` reverts
622
- /// (e.g. the fee terminal rejects the payment), the catch block calls `_recordAddedBalanceFor` to credit the fee
623
- /// amount back to the project. This credit is backed by the tokens that failed to transfer out. No phantom balance
624
- /// is created because the tokens never left.
625
- /// @dev The index-increment-before-`_processFee` pattern is intentional: locked (not-yet-unlocked) fees are skipped
626
- /// via the `unlockTimestamp` check, and advancing the index before the external call prevents reentrancy from
627
- /// 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.
628
638
  /// @param projectId The ID of the project to process held fees for.
629
639
  /// @param token The token to process held fees for.
630
640
  /// @param count The number of fees to process.
@@ -647,6 +657,7 @@ contract JBMultiTerminal is JBPermissioned, ERC2771Context, IJBMultiTerminal {
647
657
 
648
658
  // Can't process fees that aren't yet unlocked. Fees unlock sequentially in the array, so nothing left to do
649
659
  // if the current fee isn't yet unlocked.
660
+ // forge-lint: disable-next-line(block-timestamp)
650
661
  if (heldFee.unlockTimestamp > block.timestamp) break;
651
662
 
652
663
  // Delete the entry and advance the index *before* the external call. This is intentional:
@@ -664,7 +675,7 @@ contract JBMultiTerminal is JBPermissioned, ERC2771Context, IJBMultiTerminal {
664
675
  _processFee({
665
676
  projectId: projectId,
666
677
  token: token,
667
- amount: JBFees.feeAmountFrom({amountBeforeFee: heldFee.amount, feePercent: FEE}),
678
+ amount: _feeAmountFrom(heldFee.amount),
668
679
  beneficiary: heldFee.beneficiary,
669
680
  feeTerminal: feeTerminal,
670
681
  wasHeld: true
@@ -684,15 +695,12 @@ contract JBMultiTerminal is JBPermissioned, ERC2771Context, IJBMultiTerminal {
684
695
  }
685
696
  }
686
697
 
687
- /// @notice Sends payouts to a project's current payout split group, according to its ruleset, up to its current
688
- /// payout limit.
689
- /// @dev If the percentages of the splits in the project's payout split group do not add up to 100%, the remainder
690
- /// is sent to the project's owner.
691
- /// @dev Anyone can send payouts on a project's behalf. Projects can include a wildcard split (a split with no
692
- /// `hook`, `projectId`, or `beneficiary`) to send funds to the `_msgSender()` which calls this function. This can
693
- /// be used to incentivize calling this function.
694
- /// @dev payouts sent to addresses which aren't feeless incur the protocol fee.
695
- /// @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.
696
704
  /// @param projectId The ID of the project having its payouts sent.
697
705
  /// @param token The token being sent.
698
706
  /// @param amount The total number of terminal tokens to send, as a fixed point number with same number of decimals
@@ -720,10 +728,10 @@ contract JBMultiTerminal is JBPermissioned, ERC2771Context, IJBMultiTerminal {
720
728
  _checkMin({value: amountPaidOut, min: minTokensPaidOut});
721
729
  }
722
730
 
723
- /// @notice Allows a project to pay out funds from its surplus up to the current surplus allowance.
724
- /// @dev Only a project's owner or an operator with the `USE_ALLOWANCE` permission from that owner can use the
725
- /// surplus allowance.
726
- /// @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.
727
735
  /// @param projectId The ID of the project to use the surplus allowance of.
728
736
  /// @param token The token being paid out from the surplus.
729
737
  /// @param amount The amount of terminal tokens to use from the project's current surplus allowance, as a fixed
@@ -777,8 +785,7 @@ contract JBMultiTerminal is JBPermissioned, ERC2771Context, IJBMultiTerminal {
777
785
  // ------------------------- external views -------------------------- //
778
786
  //*********************************************************************//
779
787
 
780
- /// @notice A project's accounting context for a token.
781
- /// @dev See the `JBAccountingContext` struct for more information.
788
+ /// @notice Returns the accounting context (decimals, currency) for a specific token that a project accepts.
782
789
  /// @param projectId The ID of the project to get token accounting context of.
783
790
  /// @param token The token to check the accounting context of.
784
791
  /// @return The token's accounting context for the token.
@@ -794,15 +801,16 @@ contract JBMultiTerminal is JBPermissioned, ERC2771Context, IJBMultiTerminal {
794
801
  return STORE.accountingContextOf({terminal: address(this), projectId: projectId, token: token});
795
802
  }
796
803
 
797
- /// @notice The tokens accepted by a project.
804
+ /// @notice Returns accounting contexts for all tokens a project currently accepts through this terminal.
798
805
  /// @param projectId The ID of the project to get the accepted tokens of.
799
806
  /// @return tokenContexts The accounting contexts of the accepted tokens.
800
807
  function accountingContextsOf(uint256 projectId) external view override returns (JBAccountingContext[] memory) {
801
808
  return STORE.accountingContextsOf({terminal: address(this), projectId: projectId});
802
809
  }
803
810
 
804
- /// @notice Gets the current surplus amount in this terminal for a project, in terms of a given currency.
805
- /// @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.
806
814
  /// @param projectId The ID of the project to get the current surplus of.
807
815
  /// @param tokens The tokens to include in the surplus calculation. If empty, all tokens are included.
808
816
  /// @param decimals The number of decimals to include in the fixed point returned value.
@@ -827,9 +835,10 @@ contract JBMultiTerminal is JBPermissioned, ERC2771Context, IJBMultiTerminal {
827
835
  });
828
836
  }
829
837
 
830
- /// @notice Fees that are being held for a project.
831
- /// @dev Projects can temporarily hold fees and unlock them later by adding funds to the project's balance.
832
- /// @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.
833
842
  /// @param projectId The ID of the project that is holding fees.
834
843
  /// @param token The token that the fees are held in.
835
844
  /// @param count The maximum number of held fees to return.
@@ -868,7 +877,8 @@ contract JBMultiTerminal is JBPermissioned, ERC2771Context, IJBMultiTerminal {
868
877
  }
869
878
  }
870
879
 
871
- /// @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.
872
882
  /// @param holder The address whose tokens are being cashed out.
873
883
  /// @param projectId The ID of the project whose tokens are being cashed out.
874
884
  /// @param cashOutCount The number of project tokens to cash out.
@@ -909,7 +919,9 @@ contract JBMultiTerminal is JBPermissioned, ERC2771Context, IJBMultiTerminal {
909
919
  });
910
920
  }
911
921
 
912
- /// @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.
913
925
  /// @param projectId The ID of the project being paid.
914
926
  /// @param token The token being paid in.
915
927
  /// @param amount The amount of tokens being paid.
@@ -1180,16 +1192,22 @@ contract JBMultiTerminal is JBPermissioned, ERC2771Context, IJBMultiTerminal {
1180
1192
  if (cashOutTaxRate != 0) {
1181
1193
  // Non-zero tax: fees apply to the full reclaim amount.
1182
1194
  amountEligibleForFees += reclaimAmount;
1183
- reclaimAmount -= JBFees.feeAmountFrom({amountBeforeFee: reclaimAmount, feePercent: FEE});
1195
+ unchecked {
1196
+ reclaimAmount -= _feeAmountFrom(reclaimAmount);
1197
+ }
1184
1198
  } else {
1185
1199
  // Zero tax: fees apply only up to the fee-free surplus (round-trip prevention).
1186
1200
  uint256 feeFreeSurplus = _feeFreeSurplusOf[projectId][tokenToReclaim];
1187
1201
  if (feeFreeSurplus != 0) {
1188
1202
  uint256 feeableAmount = reclaimAmount < feeFreeSurplus ? reclaimAmount : feeFreeSurplus;
1189
1203
  // slither-disable-next-line reentrancy-no-eth,reentrancy-eth,reentrancy-benign
1190
- _feeFreeSurplusOf[projectId][tokenToReclaim] = feeFreeSurplus - feeableAmount;
1204
+ unchecked {
1205
+ _feeFreeSurplusOf[projectId][tokenToReclaim] = feeFreeSurplus - feeableAmount;
1206
+ }
1191
1207
  amountEligibleForFees += feeableAmount;
1192
- reclaimAmount -= JBFees.feeAmountFrom({amountBeforeFee: feeableAmount, feePercent: FEE});
1208
+ unchecked {
1209
+ reclaimAmount -= _feeAmountFrom(feeableAmount);
1210
+ }
1193
1211
  }
1194
1212
  }
1195
1213
  }
@@ -1437,9 +1455,8 @@ contract JBMultiTerminal is JBPermissioned, ERC2771Context, IJBMultiTerminal {
1437
1455
  }
1438
1456
 
1439
1457
  // Get the fee for the specified amount.
1440
- uint256 specificationAmountFee = _isFeeless(address(specification.hook))
1441
- ? 0
1442
- : JBFees.feeAmountFrom({amountBeforeFee: specification.amount, feePercent: FEE});
1458
+ uint256 specificationAmountFee =
1459
+ _isFeeless(address(specification.hook)) ? 0 : _feeAmountFrom(specification.amount);
1443
1460
 
1444
1461
  // Add the specification's amount to the amount eligible for fees.
1445
1462
  if (specificationAmountFee != 0) {
@@ -1571,7 +1588,8 @@ contract JBMultiTerminal is JBPermissioned, ERC2771Context, IJBMultiTerminal {
1571
1588
  }
1572
1589
  }
1573
1590
 
1574
- /// @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.
1575
1593
  /// @param projectId The ID of the project being paid.
1576
1594
  /// @param token The address of the token which the project is being paid with.
1577
1595
  /// @param amount The amount of terminal tokens being received, as a fixed point number with the same number of
@@ -1615,12 +1633,12 @@ contract JBMultiTerminal is JBPermissioned, ERC2771Context, IJBMultiTerminal {
1615
1633
  // slither-disable-next-line reentrancy-events
1616
1634
  newlyIssuedTokenCount = _controllerOf(projectId)
1617
1635
  .mintTokensOf({
1618
- projectId: projectId,
1619
- tokenCount: tokenCount,
1620
- beneficiary: beneficiary,
1621
- memo: "",
1622
- useReservedPercent: true
1623
- });
1636
+ projectId: projectId,
1637
+ tokenCount: tokenCount,
1638
+ beneficiary: beneficiary,
1639
+ memo: "",
1640
+ useReservedPercent: true
1641
+ });
1624
1642
  }
1625
1643
 
1626
1644
  emit Pay({
@@ -1710,8 +1728,8 @@ contract JBMultiTerminal is JBPermissioned, ERC2771Context, IJBMultiTerminal {
1710
1728
  }
1711
1729
 
1712
1730
  /// @notice Returns held fees to the project who paid them based on the specified amount.
1713
- /// @dev Fee rounding during partial replenishment can zero out dust-level fee entries (< 40 wei at 2.5% fee).
1714
- /// This is accepted behavior dust fees are economically insignificant.
1731
+ /// @dev Partial replenishments use the raw floor calculation so repaying a dust amount cannot both credit the
1732
+ /// payer project and leave the fee project owed the 1-unit minimum fee.
1715
1733
  /// @param projectId The project held fees are being returned to.
1716
1734
  /// @param token The token that the held fees are in.
1717
1735
  /// @param amount The amount to base the calculation on, as a fixed point number with the same number of decimals
@@ -1747,7 +1765,7 @@ contract JBMultiTerminal is JBPermissioned, ERC2771Context, IJBMultiTerminal {
1747
1765
  break;
1748
1766
  } else {
1749
1767
  // Notice here we take `feeAmountFrom` on the stored `.amount`.
1750
- uint256 feeAmount = JBFees.feeAmountFrom({amountBeforeFee: heldFee.amount, feePercent: FEE});
1768
+ uint256 feeAmount = _feeAmountFrom(heldFee.amount);
1751
1769
 
1752
1770
  // Keep a reference to the amount from which the fee was taken.
1753
1771
  uint256 amountPaidOut = heldFee.amount - feeAmount;
@@ -1761,8 +1779,9 @@ contract JBMultiTerminal is JBPermissioned, ERC2771Context, IJBMultiTerminal {
1761
1779
  // Move the start index forward to the held fee after the current one.
1762
1780
  newStartIndex = startIndex + i + 1;
1763
1781
  } else {
1764
- // And here we overwrite with `feeAmountResultingIn` the `leftoverAmount`
1765
- feeAmount = JBFees.feeAmountResultingIn({amountAfterFee: leftoverAmount, feePercent: FEE});
1782
+ // Use the floor variant here. The minimum 1-unit fee applies when a fee is created, not while
1783
+ // splitting an already-held fee entry across repayments.
1784
+ feeAmount = JBFees.feeAmountResultingInFloorForFee25(leftoverAmount);
1766
1785
 
1767
1786
  // Get fee from `leftoverAmount`.
1768
1787
  unchecked {
@@ -1817,10 +1836,6 @@ contract JBMultiTerminal is JBPermissioned, ERC2771Context, IJBMultiTerminal {
1817
1836
  (ruleset, amountPaidOut) =
1818
1837
  STORE.recordPayoutFor({projectId: projectId, token: token, amount: amount, currency: currency});
1819
1838
 
1820
- // Cap fee-free surplus at remaining balance. Non-fee-free funds leave first.
1821
- // slither-disable-next-line reentrancy-no-eth,reentrancy-eth,reentrancy-benign
1822
- _capFeeFreeSurplus({projectId: projectId, token: token});
1823
-
1824
1839
  // Get a reference to the project's owner.
1825
1840
  // The owner will receive tokens minted by paying the platform fee and receive any leftover funds not sent to
1826
1841
  // payout splits.
@@ -1849,17 +1864,20 @@ contract JBMultiTerminal is JBPermissioned, ERC2771Context, IJBMultiTerminal {
1849
1864
  // Send any leftover funds to the project owner and update the fee tracking accordingly.
1850
1865
  if (leftoverPayoutAmount != 0) {
1851
1866
  // Keep a reference to the fee for the leftover payout amount.
1852
- uint256 fee = _isFeeless(projectOwner)
1853
- ? 0
1854
- : JBFees.feeAmountFrom({amountBeforeFee: leftoverPayoutAmount, feePercent: FEE});
1867
+ uint256 fee = _isFeeless(projectOwner) ? 0 : _feeAmountFrom(leftoverPayoutAmount);
1868
+
1869
+ uint256 netLeftoverPayoutAmount;
1870
+ unchecked {
1871
+ netLeftoverPayoutAmount = leftoverPayoutAmount - fee;
1872
+ }
1855
1873
 
1856
1874
  // Failed owner transfer consumes the payout limit by design. Same pattern as split payouts:
1857
1875
  // the try-catch prevents revert, failed amount is returned to project balance, and the owner can retry
1858
1876
  // via addToBalanceOf or in the next cycle.
1859
- try this.executeTransferTo({addr: projectOwner, token: token, amount: leftoverPayoutAmount - fee}) {
1877
+ try this.executeTransferTo({addr: projectOwner, token: token, amount: netLeftoverPayoutAmount}) {
1860
1878
  if (fee > 0) {
1861
1879
  amountEligibleForFees += leftoverPayoutAmount;
1862
- leftoverPayoutAmount -= fee;
1880
+ leftoverPayoutAmount = netLeftoverPayoutAmount;
1863
1881
  }
1864
1882
  } catch (bytes memory reason) {
1865
1883
  // slither-disable-next-line reentrancy-events
@@ -1867,7 +1885,7 @@ contract JBMultiTerminal is JBPermissioned, ERC2771Context, IJBMultiTerminal {
1867
1885
  projectId: projectId,
1868
1886
  addr: projectOwner,
1869
1887
  token: token,
1870
- amount: leftoverPayoutAmount - fee,
1888
+ amount: netLeftoverPayoutAmount,
1871
1889
  fee: fee,
1872
1890
  reason: reason,
1873
1891
  caller: sender
@@ -1878,6 +1896,11 @@ contract JBMultiTerminal is JBPermissioned, ERC2771Context, IJBMultiTerminal {
1878
1896
  }
1879
1897
  }
1880
1898
 
1899
+ // Cap fee-free surplus at remaining balance. Non-fee-free funds leave first.
1900
+ // Placed after all payouts settle so the cap reflects post-payout state.
1901
+ // slither-disable-next-line reentrancy-no-eth,reentrancy-eth,reentrancy-benign
1902
+ _capFeeFreeSurplus({projectId: projectId, token: token});
1903
+
1881
1904
  // Take the fee.
1882
1905
  uint256 feeTaken = _takeFeeFrom({
1883
1906
  projectId: projectId,
@@ -1918,7 +1941,7 @@ contract JBMultiTerminal is JBPermissioned, ERC2771Context, IJBMultiTerminal {
1918
1941
  returns (uint256 feeAmount)
1919
1942
  {
1920
1943
  // Get a reference to the fee amount.
1921
- feeAmount = JBFees.feeAmountFrom({amountBeforeFee: amount, feePercent: FEE});
1944
+ feeAmount = _feeAmountFrom(amount);
1922
1945
 
1923
1946
  if (shouldHoldFees) {
1924
1947
  // Store the held fee.
@@ -2170,4 +2193,18 @@ contract JBMultiTerminal is JBPermissioned, ERC2771Context, IJBMultiTerminal {
2170
2193
  tokenAmount =
2171
2194
  JBTokenAmount({token: token, decimals: context.decimals, currency: context.currency, value: value});
2172
2195
  }
2196
+
2197
+ //*********************************************************************//
2198
+ // -------------------------- private helpers ------------------------ //
2199
+ //*********************************************************************//
2200
+
2201
+ /// @notice The terminal fee charged from a pre-fee `amount`.
2202
+ /// @dev Returns at least 1 for nonzero feeable amounts so dust payouts cannot bypass protocol fees.
2203
+ /// @param amount The amount before the fee is applied.
2204
+ /// @return feeAmount The fee amount.
2205
+ function _feeAmountFrom(uint256 amount) private pure returns (uint256 feeAmount) {
2206
+ feeAmount = amount / _FEE_AMOUNT_FROM_DENOMINATOR;
2207
+
2208
+ return feeAmount == 0 && amount != 0 ? 1 : feeAmount;
2209
+ }
2173
2210
  }