@bananapus/core-v6 0.0.1

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 (436) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +112 -0
  3. package/SKILLS.md +151 -0
  4. package/docs/book.css +13 -0
  5. package/docs/book.toml +12 -0
  6. package/docs/solidity.min.js +74 -0
  7. package/docs/src/README.md +703 -0
  8. package/docs/src/SUMMARY.md +94 -0
  9. package/docs/src/src/JBChainlinkV3PriceFeed.sol/contract.JBChainlinkV3PriceFeed.md +83 -0
  10. package/docs/src/src/JBChainlinkV3SequencerPriceFeed.sol/contract.JBChainlinkV3SequencerPriceFeed.md +88 -0
  11. package/docs/src/src/JBController.sol/contract.JBController.md +1121 -0
  12. package/docs/src/src/JBDeadline.sol/contract.JBDeadline.md +84 -0
  13. package/docs/src/src/JBDirectory.sol/contract.JBDirectory.md +294 -0
  14. package/docs/src/src/JBERC20.sol/contract.JBERC20.md +190 -0
  15. package/docs/src/src/JBFeelessAddresses.sol/contract.JBFeelessAddresses.md +80 -0
  16. package/docs/src/src/JBFundAccessLimits.sol/contract.JBFundAccessLimits.md +253 -0
  17. package/docs/src/src/JBMultiTerminal.sol/contract.JBMultiTerminal.md +1472 -0
  18. package/docs/src/src/JBPermissions.sol/contract.JBPermissions.md +199 -0
  19. package/docs/src/src/JBPrices.sol/contract.JBPrices.md +154 -0
  20. package/docs/src/src/JBProjects.sol/contract.JBProjects.md +131 -0
  21. package/docs/src/src/JBRulesets.sol/contract.JBRulesets.md +677 -0
  22. package/docs/src/src/JBSplits.sol/contract.JBSplits.md +237 -0
  23. package/docs/src/src/JBTerminalStore.sol/contract.JBTerminalStore.md +591 -0
  24. package/docs/src/src/JBTokens.sol/contract.JBTokens.md +353 -0
  25. package/docs/src/src/README.md +25 -0
  26. package/docs/src/src/abstract/JBControlled.sol/abstract.JBControlled.md +64 -0
  27. package/docs/src/src/abstract/JBPermissioned.sol/abstract.JBPermissioned.md +84 -0
  28. package/docs/src/src/abstract/README.md +5 -0
  29. package/docs/src/src/enums/JBApprovalStatus.sol/enum.JBApprovalStatus.md +17 -0
  30. package/docs/src/src/enums/README.md +4 -0
  31. package/docs/src/src/interfaces/IJBCashOutHook.sol/interface.IJBCashOutHook.md +29 -0
  32. package/docs/src/src/interfaces/IJBCashOutTerminal.sol/interface.IJBCashOutTerminal.md +57 -0
  33. package/docs/src/src/interfaces/IJBControlled.sol/interface.IJBControlled.md +12 -0
  34. package/docs/src/src/interfaces/IJBController.sol/interface.IJBController.md +334 -0
  35. package/docs/src/src/interfaces/IJBDirectory.sol/interface.IJBDirectory.md +108 -0
  36. package/docs/src/src/interfaces/IJBDirectoryAccessControl.sol/interface.IJBDirectoryAccessControl.md +19 -0
  37. package/docs/src/src/interfaces/IJBFeeTerminal.sol/interface.IJBFeeTerminal.md +91 -0
  38. package/docs/src/src/interfaces/IJBFeelessAddresses.sol/interface.IJBFeelessAddresses.md +26 -0
  39. package/docs/src/src/interfaces/IJBFundAccessLimits.sol/interface.IJBFundAccessLimits.md +88 -0
  40. package/docs/src/src/interfaces/IJBMigratable.sol/interface.IJBMigratable.md +29 -0
  41. package/docs/src/src/interfaces/IJBMultiTerminal.sol/interface.IJBMultiTerminal.md +50 -0
  42. package/docs/src/src/interfaces/IJBPayHook.sol/interface.IJBPayHook.md +28 -0
  43. package/docs/src/src/interfaces/IJBPayoutTerminal.sol/interface.IJBPayoutTerminal.md +105 -0
  44. package/docs/src/src/interfaces/IJBPermissioned.sol/interface.IJBPermissioned.md +12 -0
  45. package/docs/src/src/interfaces/IJBPermissions.sol/interface.IJBPermissions.md +74 -0
  46. package/docs/src/src/interfaces/IJBPermitTerminal.sol/interface.IJBPermitTerminal.md +15 -0
  47. package/docs/src/src/interfaces/IJBPriceFeed.sol/interface.IJBPriceFeed.md +12 -0
  48. package/docs/src/src/interfaces/IJBPrices.sol/interface.IJBPrices.md +74 -0
  49. package/docs/src/src/interfaces/IJBProjectUriRegistry.sol/interface.IJBProjectUriRegistry.md +19 -0
  50. package/docs/src/src/interfaces/IJBProjects.sol/interface.IJBProjects.md +49 -0
  51. package/docs/src/src/interfaces/IJBRulesetApprovalHook.sol/interface.IJBRulesetApprovalHook.md +35 -0
  52. package/docs/src/src/interfaces/IJBRulesetDataHook.sol/interface.IJBRulesetDataHook.md +97 -0
  53. package/docs/src/src/interfaces/IJBRulesets.sol/interface.IJBRulesets.md +165 -0
  54. package/docs/src/src/interfaces/IJBSplitHook.sol/interface.IJBSplitHook.md +31 -0
  55. package/docs/src/src/interfaces/IJBSplits.sol/interface.IJBSplits.md +35 -0
  56. package/docs/src/src/interfaces/IJBTerminal.sol/interface.IJBTerminal.md +141 -0
  57. package/docs/src/src/interfaces/IJBTerminalStore.sol/interface.IJBTerminalStore.md +198 -0
  58. package/docs/src/src/interfaces/IJBToken.sol/interface.IJBToken.md +54 -0
  59. package/docs/src/src/interfaces/IJBTokenUriResolver.sol/interface.IJBTokenUriResolver.md +12 -0
  60. package/docs/src/src/interfaces/IJBTokens.sol/interface.IJBTokens.md +151 -0
  61. package/docs/src/src/interfaces/README.md +33 -0
  62. package/docs/src/src/libraries/JBCashOuts.sol/library.JBCashOuts.md +40 -0
  63. package/docs/src/src/libraries/JBConstants.sol/library.JBConstants.md +52 -0
  64. package/docs/src/src/libraries/JBCurrencyIds.sol/library.JBCurrencyIds.md +19 -0
  65. package/docs/src/src/libraries/JBFees.sol/library.JBFees.md +52 -0
  66. package/docs/src/src/libraries/JBFixedPointNumber.sol/library.JBFixedPointNumber.md +12 -0
  67. package/docs/src/src/libraries/JBMetadataResolver.sol/library.JBMetadataResolver.md +242 -0
  68. package/docs/src/src/libraries/JBRulesetMetadataResolver.sol/library.JBRulesetMetadataResolver.md +180 -0
  69. package/docs/src/src/libraries/JBSplitGroupIds.sol/library.JBSplitGroupIds.md +14 -0
  70. package/docs/src/src/libraries/JBSurplus.sol/library.JBSurplus.md +44 -0
  71. package/docs/src/src/libraries/README.md +12 -0
  72. package/docs/src/src/periphery/JBDeadline1Day.sol/contract.JBDeadline1Day.md +15 -0
  73. package/docs/src/src/periphery/JBDeadline3Days.sol/contract.JBDeadline3Days.md +15 -0
  74. package/docs/src/src/periphery/JBDeadline3Hours.sol/contract.JBDeadline3Hours.md +15 -0
  75. package/docs/src/src/periphery/JBDeadline7Days.sol/contract.JBDeadline7Days.md +15 -0
  76. package/docs/src/src/periphery/JBMatchingPriceFeed.sol/contract.JBMatchingPriceFeed.md +22 -0
  77. package/docs/src/src/periphery/README.md +8 -0
  78. package/docs/src/src/structs/JBAccountingContext.sol/struct.JBAccountingContext.md +20 -0
  79. package/docs/src/src/structs/JBAfterCashOutRecordedContext.sol/struct.JBAfterCashOutRecordedContext.md +43 -0
  80. package/docs/src/src/structs/JBAfterPayRecordedContext.sol/struct.JBAfterPayRecordedContext.md +42 -0
  81. package/docs/src/src/structs/JBBeforeCashOutRecordedContext.sol/struct.JBBeforeCashOutRecordedContext.md +45 -0
  82. package/docs/src/src/structs/JBBeforePayRecordedContext.sol/struct.JBBeforePayRecordedContext.md +41 -0
  83. package/docs/src/src/structs/JBCashOutHookSpecification.sol/struct.JBCashOutHookSpecification.md +22 -0
  84. package/docs/src/src/structs/JBCurrencyAmount.sol/struct.JBCurrencyAmount.md +17 -0
  85. package/docs/src/src/structs/JBFee.sol/struct.JBFee.md +20 -0
  86. package/docs/src/src/structs/JBFundAccessLimitGroup.sol/struct.JBFundAccessLimitGroup.md +39 -0
  87. package/docs/src/src/structs/JBPayHookSpecification.sol/struct.JBPayHookSpecification.md +22 -0
  88. package/docs/src/src/structs/JBPermissionsData.sol/struct.JBPermissionsData.md +21 -0
  89. package/docs/src/src/structs/JBRuleset.sol/struct.JBRuleset.md +55 -0
  90. package/docs/src/src/structs/JBRulesetConfig.sol/struct.JBRulesetConfig.md +51 -0
  91. package/docs/src/src/structs/JBRulesetMetadata.sol/struct.JBRulesetMetadata.md +79 -0
  92. package/docs/src/src/structs/JBRulesetWeightCache.sol/struct.JBRulesetWeightCache.md +16 -0
  93. package/docs/src/src/structs/JBRulesetWithMetadata.sol/struct.JBRulesetWithMetadata.md +16 -0
  94. package/docs/src/src/structs/JBSingleAllowance.sol/struct.JBSingleAllowance.md +26 -0
  95. package/docs/src/src/structs/JBSplit.sol/struct.JBSplit.md +49 -0
  96. package/docs/src/src/structs/JBSplitGroup.sol/struct.JBSplitGroup.md +17 -0
  97. package/docs/src/src/structs/JBSplitHookContext.sol/struct.JBSplitHookContext.md +29 -0
  98. package/docs/src/src/structs/JBTerminalConfig.sol/struct.JBTerminalConfig.md +16 -0
  99. package/docs/src/src/structs/JBTokenAmount.sol/struct.JBTokenAmount.md +23 -0
  100. package/docs/src/src/structs/README.md +25 -0
  101. package/foundry.lock +11 -0
  102. package/foundry.toml +41 -0
  103. package/package.json +38 -0
  104. package/remappings.txt +1 -0
  105. package/script/Deploy.s.sol +111 -0
  106. package/script/DeployPeriphery.s.sol +287 -0
  107. package/script/helpers/CoreDeploymentLib.sol +121 -0
  108. package/slither-ci.config.json +10 -0
  109. package/sphinx.lock +507 -0
  110. package/src/JBChainlinkV3PriceFeed.sol +77 -0
  111. package/src/JBChainlinkV3SequencerPriceFeed.sol +75 -0
  112. package/src/JBController.sol +1186 -0
  113. package/src/JBDeadline.sol +73 -0
  114. package/src/JBDirectory.sol +343 -0
  115. package/src/JBERC20.sol +131 -0
  116. package/src/JBFeelessAddresses.sol +54 -0
  117. package/src/JBFundAccessLimits.sol +308 -0
  118. package/src/JBMultiTerminal.sol +2024 -0
  119. package/src/JBPermissions.sol +252 -0
  120. package/src/JBPrices.sol +227 -0
  121. package/src/JBProjects.sol +126 -0
  122. package/src/JBRulesets.sol +1093 -0
  123. package/src/JBSplits.sol +324 -0
  124. package/src/JBTerminalStore.sol +908 -0
  125. package/src/JBTokens.sol +376 -0
  126. package/src/abstract/JBControlled.sol +48 -0
  127. package/src/abstract/JBPermissioned.sol +77 -0
  128. package/src/enums/JBApprovalStatus.sol +12 -0
  129. package/src/interfaces/IJBCashOutHook.sol +15 -0
  130. package/src/interfaces/IJBCashOutTerminal.sol +51 -0
  131. package/src/interfaces/IJBControlled.sol +10 -0
  132. package/src/interfaces/IJBController.sol +280 -0
  133. package/src/interfaces/IJBDirectory.sol +69 -0
  134. package/src/interfaces/IJBDirectoryAccessControl.sol +15 -0
  135. package/src/interfaces/IJBFeeTerminal.sol +61 -0
  136. package/src/interfaces/IJBFeelessAddresses.sol +17 -0
  137. package/src/interfaces/IJBFundAccessLimits.sol +94 -0
  138. package/src/interfaces/IJBMigratable.sol +24 -0
  139. package/src/interfaces/IJBMultiTerminal.sol +36 -0
  140. package/src/interfaces/IJBPayHook.sol +14 -0
  141. package/src/interfaces/IJBPayoutTerminal.sol +92 -0
  142. package/src/interfaces/IJBPermissioned.sol +10 -0
  143. package/src/interfaces/IJBPermissions.sol +71 -0
  144. package/src/interfaces/IJBPermitTerminal.sol +14 -0
  145. package/src/interfaces/IJBPriceFeed.sol +10 -0
  146. package/src/interfaces/IJBPrices.sol +65 -0
  147. package/src/interfaces/IJBProjectUriRegistry.sol +15 -0
  148. package/src/interfaces/IJBProjects.sol +27 -0
  149. package/src/interfaces/IJBRulesetApprovalHook.sol +21 -0
  150. package/src/interfaces/IJBRulesetDataHook.sol +56 -0
  151. package/src/interfaces/IJBRulesets.sol +151 -0
  152. package/src/interfaces/IJBSplitHook.sol +16 -0
  153. package/src/interfaces/IJBSplits.sol +28 -0
  154. package/src/interfaces/IJBTerminal.sol +120 -0
  155. package/src/interfaces/IJBTerminalStore.sol +225 -0
  156. package/src/interfaces/IJBToken.sol +39 -0
  157. package/src/interfaces/IJBTokenUriResolver.sol +10 -0
  158. package/src/interfaces/IJBTokens.sol +113 -0
  159. package/src/libraries/JBCashOuts.sol +120 -0
  160. package/src/libraries/JBConstants.sol +14 -0
  161. package/src/libraries/JBCurrencyIds.sol +7 -0
  162. package/src/libraries/JBFees.sol +28 -0
  163. package/src/libraries/JBFixedPointNumber.sol +12 -0
  164. package/src/libraries/JBMetadataResolver.sol +306 -0
  165. package/src/libraries/JBRulesetMetadataResolver.sol +160 -0
  166. package/src/libraries/JBSplitGroupIds.sol +7 -0
  167. package/src/libraries/JBSurplus.sol +40 -0
  168. package/src/periphery/JBDeadline1Day.sol +8 -0
  169. package/src/periphery/JBDeadline3Days.sol +8 -0
  170. package/src/periphery/JBDeadline3Hours.sol +8 -0
  171. package/src/periphery/JBDeadline7Days.sol +8 -0
  172. package/src/periphery/JBMatchingPriceFeed.sol +13 -0
  173. package/src/structs/JBAccountingContext.sol +12 -0
  174. package/src/structs/JBAfterCashOutRecordedContext.sol +30 -0
  175. package/src/structs/JBAfterPayRecordedContext.sol +29 -0
  176. package/src/structs/JBBeforeCashOutRecordedContext.sol +31 -0
  177. package/src/structs/JBBeforePayRecordedContext.sol +28 -0
  178. package/src/structs/JBCashOutHookSpecification.sol +15 -0
  179. package/src/structs/JBCurrencyAmount.sol +10 -0
  180. package/src/structs/JBFee.sol +12 -0
  181. package/src/structs/JBFundAccessLimitGroup.sol +28 -0
  182. package/src/structs/JBPayHookSpecification.sol +15 -0
  183. package/src/structs/JBPermissionsData.sol +13 -0
  184. package/src/structs/JBRuleset.sol +42 -0
  185. package/src/structs/JBRulesetConfig.sol +43 -0
  186. package/src/structs/JBRulesetMetadata.sol +56 -0
  187. package/src/structs/JBRulesetWeightCache.sol +9 -0
  188. package/src/structs/JBRulesetWithMetadata.sol +12 -0
  189. package/src/structs/JBSingleAllowance.sol +16 -0
  190. package/src/structs/JBSplit.sol +37 -0
  191. package/src/structs/JBSplitGroup.sol +12 -0
  192. package/src/structs/JBSplitHookContext.sol +20 -0
  193. package/src/structs/JBTerminalConfig.sol +12 -0
  194. package/src/structs/JBTokenAmount.sol +14 -0
  195. package/test/AuditExploits.t.sol +2710 -0
  196. package/test/ComprehensiveInvariant.t.sol +298 -0
  197. package/test/EconomicSimulation.t.sol +340 -0
  198. package/test/EntryPointPermutations.t.sol +671 -0
  199. package/test/FlashLoanAttacks.t.sol +792 -0
  200. package/test/PermissionEscalation.t.sol +679 -0
  201. package/test/RulesetTransitions.t.sol +699 -0
  202. package/test/SplitLoopTests.t.sol +731 -0
  203. package/test/TestAccessToFunds.sol +2644 -0
  204. package/test/TestCashOut.sol +185 -0
  205. package/test/TestCashOutCountFor.sol +272 -0
  206. package/test/TestCashOutHooks.sol +317 -0
  207. package/test/TestCashOutTimingEdge.sol +229 -0
  208. package/test/TestDurationUnderflow.sol +220 -0
  209. package/test/TestFeeProcessingFailure.sol +208 -0
  210. package/test/TestFees.sol +604 -0
  211. package/test/TestInterfaceSupport.sol +62 -0
  212. package/test/TestJBERC20Inheritance.sol +91 -0
  213. package/test/TestLaunchProject.sol +176 -0
  214. package/test/TestMetaTx.sol +203 -0
  215. package/test/TestMetadataParserLib.sol +438 -0
  216. package/test/TestMigrationHeldFees.sol +249 -0
  217. package/test/TestMintTokensOf.sol +172 -0
  218. package/test/TestMultiTokenSurplus.sol +206 -0
  219. package/test/TestMultipleAccessLimits.sol +642 -0
  220. package/test/TestPayBurnRedeemFlow.sol +180 -0
  221. package/test/TestPayHooks.sol +190 -0
  222. package/test/TestPermissions.sol +305 -0
  223. package/test/TestPermissionsEdge.sol +286 -0
  224. package/test/TestPermit2Terminal.sol +339 -0
  225. package/test/TestRulesetQueueing.sol +1001 -0
  226. package/test/TestRulesetQueuingStress.sol +778 -0
  227. package/test/TestRulesetWeightCaching.sol +177 -0
  228. package/test/TestSplits.sol +369 -0
  229. package/test/TestTerminalMigration.sol +167 -0
  230. package/test/TestTokenFlow.sol +174 -0
  231. package/test/WeirdTokenTests.t.sol +764 -0
  232. package/test/formal/BondingCurveProperties.t.sol +411 -0
  233. package/test/formal/FeeProperties.t.sol +246 -0
  234. package/test/helpers/JBTest.sol +129 -0
  235. package/test/helpers/MetadataResolverHelper.sol +116 -0
  236. package/test/helpers/TestBaseWorkflow.sol +317 -0
  237. package/test/invariants/Phase3DeepInvariant.t.sol +404 -0
  238. package/test/invariants/RulesetsInvariant.t.sol +115 -0
  239. package/test/invariants/TerminalStoreInvariant.t.sol +220 -0
  240. package/test/invariants/TokensInvariant.t.sol +184 -0
  241. package/test/invariants/handlers/ComprehensiveHandler.sol +285 -0
  242. package/test/invariants/handlers/EconomicHandler.sol +347 -0
  243. package/test/invariants/handlers/Phase3Handler.sol +414 -0
  244. package/test/invariants/handlers/RulesetsHandler.sol +111 -0
  245. package/test/invariants/handlers/TerminalStoreHandler.sol +146 -0
  246. package/test/invariants/handlers/TokensHandler.sol +127 -0
  247. package/test/mock/ERC2771ForwarderMock.sol +37 -0
  248. package/test/mock/MockERC20.sol +18 -0
  249. package/test/mock/MockMaliciousBeneficiary.sol +67 -0
  250. package/test/mock/MockMaliciousSplitHook.sol +42 -0
  251. package/test/mock/MockPriceFeed.sol +20 -0
  252. package/test/trees/JBController/burnTokensOf.tree +9 -0
  253. package/test/trees/JBController/claimTokensFor.tree +5 -0
  254. package/test/trees/JBController/deployERC20For.tree +5 -0
  255. package/test/trees/JBController/getRulesetOf.tree +5 -0
  256. package/test/trees/JBController/launchProjectFor.tree +12 -0
  257. package/test/trees/JBController/launchRulesetsFor.tree +8 -0
  258. package/test/trees/JBController/migrateController.tree +12 -0
  259. package/test/trees/JBController/mintTokensOf.tree +12 -0
  260. package/test/trees/JBController/payReservedTokenToTerminal.tree +8 -0
  261. package/test/trees/JBController/receiveMigrationFrom.tree +4 -0
  262. package/test/trees/JBController/sendReservedTokensToSplitsOf.tree +12 -0
  263. package/test/trees/JBController/setMetadataOf.tree +5 -0
  264. package/test/trees/JBController/setSplitGroupsOf.tree +5 -0
  265. package/test/trees/JBController/setTokenFor.tree +5 -0
  266. package/test/trees/JBController/transferCreditsFrom.tree +8 -0
  267. package/test/trees/JBDirectory/primaryTerminalOf.tree +8 -0
  268. package/test/trees/JBDirectory/setControllerOf.tree +11 -0
  269. package/test/trees/JBDirectory/setPrimaryTerminalOf.tree +15 -0
  270. package/test/trees/JBDirectory/setTerminalsOf.tree +11 -0
  271. package/test/trees/JBERC20/initialize.tree +7 -0
  272. package/test/trees/JBERC20/name.tree +5 -0
  273. package/test/trees/JBERC20/nonces.tree +5 -0
  274. package/test/trees/JBERC20/symbol.tree +5 -0
  275. package/test/trees/JBFeelessAddresses/setFeelessAddress.tree +5 -0
  276. package/test/trees/JBFeelessAddresses/supportsInterface.tree +5 -0
  277. package/test/trees/JBFundAccessLimits/payoutLimitOf.tree +5 -0
  278. package/test/trees/JBFundAccessLimits/payoutLimitsOf.tree +8 -0
  279. package/test/trees/JBFundAccessLimits/setFundAccessLimitsFor.tree +18 -0
  280. package/test/trees/JBFundAccessLimits/surplusAllowanceOf.tree +5 -0
  281. package/test/trees/JBFundAccessLimits/surplusAllowancesOf.tree +8 -0
  282. package/test/trees/JBMetadataResolver/getDataFor.tree +8 -0
  283. package/test/trees/JBMultiTerminal/accountingContextsOf.tree +5 -0
  284. package/test/trees/JBMultiTerminal/addAccountingContextsFor.tree +10 -0
  285. package/test/trees/JBMultiTerminal/addToBalanceOf.tree +23 -0
  286. package/test/trees/JBMultiTerminal/cashOutTokensOf.tree +23 -0
  287. package/test/trees/JBMultiTerminal/executePayout.tree +32 -0
  288. package/test/trees/JBMultiTerminal/executeProcessFee.tree +14 -0
  289. package/test/trees/JBMultiTerminal/migrateBalanceOf.tree +12 -0
  290. package/test/trees/JBMultiTerminal/pay.tree +23 -0
  291. package/test/trees/JBMultiTerminal/processHeldFeesOf.tree +8 -0
  292. package/test/trees/JBMultiTerminal/sendPayoutsOf.tree +34 -0
  293. package/test/trees/JBMultiTerminal/useAllowanceOf.tree +16 -0
  294. package/test/trees/JBPermissions/hasPermission.tree +8 -0
  295. package/test/trees/JBPermissions/hasPermissions.tree +8 -0
  296. package/test/trees/JBPermissions/setPermissionsFor.tree +5 -0
  297. package/test/trees/JBPrices/addPriceFeedFor.tree +14 -0
  298. package/test/trees/JBPrices/pricePerUnitOf.tree +11 -0
  299. package/test/trees/JBProjects/createFor.tree +11 -0
  300. package/test/trees/JBProjects/setTokenUriResolver.tree +5 -0
  301. package/test/trees/JBProjects/supportsInterface.tree +9 -0
  302. package/test/trees/JBProjects/tokenURI.tree +5 -0
  303. package/test/trees/JBRulesets/currentApprovalStatusForLatestRulesetOf.tree +8 -0
  304. package/test/trees/JBRulesets/currentOf.tree +12 -0
  305. package/test/trees/JBRulesets/getRulesetOf.tree +5 -0
  306. package/test/trees/JBRulesets/latestQueuedRulesetOf.tree +10 -0
  307. package/test/trees/JBRulesets/rulesetsOf.tree +11 -0
  308. package/test/trees/JBRulesets/upcomingRulesetOf.tree +20 -0
  309. package/test/trees/JBRulesets/updateRulesetWeightCache.tree +5 -0
  310. package/test/trees/JBSplits/setSplitGroupsOf.tree +17 -0
  311. package/test/trees/JBSplits/splitsOf.tree +5 -0
  312. package/test/trees/JBTerminalStore/currentReclaimableSurplusOf.tree +16 -0
  313. package/test/trees/JBTerminalStore/currentSurplusOf.tree +25 -0
  314. package/test/trees/JBTerminalStore/currentTotalSurplusOf.tree +5 -0
  315. package/test/trees/JBTerminalStore/recordCashOutsFor.tree +16 -0
  316. package/test/trees/JBTerminalStore/recordPaymentFrom.tree +14 -0
  317. package/test/trees/JBTerminalStore/recordPayoutFor.tree +10 -0
  318. package/test/trees/JBTerminalStore/recordTerminalMigration.tree +5 -0
  319. package/test/trees/JBTerminalStore/recordUsedAllowanceOf.tree +10 -0
  320. package/test/trees/JBTokens/burnFrom.tree +10 -0
  321. package/test/trees/JBTokens/claimTokensFor.tree +10 -0
  322. package/test/trees/JBTokens/deployERC20For.tree +12 -0
  323. package/test/trees/JBTokens/mintFor.tree +10 -0
  324. package/test/trees/JBTokens/setTokenFor.tree +11 -0
  325. package/test/trees/JBTokens/totalBalanceOf.tree +5 -0
  326. package/test/trees/JBTokens/totalSupplyOf.tree +5 -0
  327. package/test/trees/JBTokens/transferCreditsFrom.tree +8 -0
  328. package/test/trees/mintTokensOf.tree +12 -0
  329. package/test/units/static/JBChainlinkV3PriceFeed/TestPriceFeed.sol +220 -0
  330. package/test/units/static/JBController/JBControllerSetup.sol +40 -0
  331. package/test/units/static/JBController/TestBurnTokensOf.sol +107 -0
  332. package/test/units/static/JBController/TestClaimTokensFor.sol +60 -0
  333. package/test/units/static/JBController/TestDeployErc20For.sol +80 -0
  334. package/test/units/static/JBController/TestLaunchProjectFor.sol +282 -0
  335. package/test/units/static/JBController/TestLaunchRulesetsFor.sol +322 -0
  336. package/test/units/static/JBController/TestMigrateController.sol +148 -0
  337. package/test/units/static/JBController/TestMintTokensOfUnits.sol +102 -0
  338. package/test/units/static/JBController/TestPayReservedTokenToTerminal.sol +71 -0
  339. package/test/units/static/JBController/TestReceiveMigrationFrom.sol +95 -0
  340. package/test/units/static/JBController/TestRulesetViews.sol +219 -0
  341. package/test/units/static/JBController/TestSendReservedTokensToSplitsOf.sol +595 -0
  342. package/test/units/static/JBController/TestSetSplitGroupsOf.sol +63 -0
  343. package/test/units/static/JBController/TestSetTokenFor.sol +227 -0
  344. package/test/units/static/JBController/TestSetUriOf.sol +53 -0
  345. package/test/units/static/JBController/TestTransferCreditsFrom.sol +159 -0
  346. package/test/units/static/JBDeadline/TestDeadlineFuzz.sol +194 -0
  347. package/test/units/static/JBDirectory/JBDirectorySetup.sol +22 -0
  348. package/test/units/static/JBDirectory/TestPrimaryTerminalOf.sol +122 -0
  349. package/test/units/static/JBDirectory/TestSetControllerOf.sol +173 -0
  350. package/test/units/static/JBDirectory/TestSetControllerOfMigrationOrder.sol +98 -0
  351. package/test/units/static/JBDirectory/TestSetPrimaryTerminalOf.sol +169 -0
  352. package/test/units/static/JBDirectory/TestSetTerminalsOf.sol +128 -0
  353. package/test/units/static/JBERC20/JBERC20Setup.sol +20 -0
  354. package/test/units/static/JBERC20/SigUtils.sol +34 -0
  355. package/test/units/static/JBERC20/TestInitialize.sol +54 -0
  356. package/test/units/static/JBERC20/TestName.sol +30 -0
  357. package/test/units/static/JBERC20/TestNonces.sol +59 -0
  358. package/test/units/static/JBERC20/TestSymbol.sol +31 -0
  359. package/test/units/static/JBFeelessAdresses/JBFeelessSetup.sol +20 -0
  360. package/test/units/static/JBFeelessAdresses/TestInterfaces.sol +29 -0
  361. package/test/units/static/JBFeelessAdresses/TestSetFeelessAddress.sol +35 -0
  362. package/test/units/static/JBFees/TestFeesFuzz.sol +78 -0
  363. package/test/units/static/JBFixedPointNumber/TestAdjustDecimals.sol +16 -0
  364. package/test/units/static/JBFixedPointNumber/TestAdjustDecimalsFuzz.sol +71 -0
  365. package/test/units/static/JBFundAccessLimits/JBFundAccessSetup.sol +21 -0
  366. package/test/units/static/JBFundAccessLimits/TestFundAccessLimitsEdge.sol +159 -0
  367. package/test/units/static/JBFundAccessLimits/TestPayoutLimitOf.sol +56 -0
  368. package/test/units/static/JBFundAccessLimits/TestPayoutLimitsOf.sol +94 -0
  369. package/test/units/static/JBFundAccessLimits/TestSetFundAccessLimitsFor.sol +182 -0
  370. package/test/units/static/JBFundAccessLimits/TestSurplusAllowanceOf.sol +61 -0
  371. package/test/units/static/JBFundAccessLimits/TestSurplusAllowancesOf.sol +96 -0
  372. package/test/units/static/JBMetadataResolver/TestGetDataFor.sol +89 -0
  373. package/test/units/static/JBMetadataResolver/TestMetadataResolverFuzz.sol +227 -0
  374. package/test/units/static/JBMetadataResolver/TestMetadataResolverM20M21.sol +245 -0
  375. package/test/units/static/JBMultiTerminal/JBMultiTerminalSetup.sol +39 -0
  376. package/test/units/static/JBMultiTerminal/TestAccountingContextsOf.sol +65 -0
  377. package/test/units/static/JBMultiTerminal/TestAddAccountingContextsFor.sol +313 -0
  378. package/test/units/static/JBMultiTerminal/TestAddToBalanceOf.sol +432 -0
  379. package/test/units/static/JBMultiTerminal/TestCashOutTokensOf.sol +478 -0
  380. package/test/units/static/JBMultiTerminal/TestExecutePayout.sol +577 -0
  381. package/test/units/static/JBMultiTerminal/TestExecuteProcessFee.sol +176 -0
  382. package/test/units/static/JBMultiTerminal/TestMigrateBalanceOf.sol +190 -0
  383. package/test/units/static/JBMultiTerminal/TestPay.sol +514 -0
  384. package/test/units/static/JBMultiTerminal/TestProcessHeldFeesOf.sol +29 -0
  385. package/test/units/static/JBMultiTerminal/TestSendPayoutsOf.sol +243 -0
  386. package/test/units/static/JBMultiTerminal/TestUseAllowanceOf.sol +310 -0
  387. package/test/units/static/JBPermissions/JBPermissionsSetup.sol +18 -0
  388. package/test/units/static/JBPermissions/TestHasPermission.sol +50 -0
  389. package/test/units/static/JBPermissions/TestHasPermissions.sol +93 -0
  390. package/test/units/static/JBPermissions/TestSetPermissionsFor.sol +62 -0
  391. package/test/units/static/JBPrices/JBPricesSetup.sol +26 -0
  392. package/test/units/static/JBPrices/TestAddPriceFeedFor.sol +102 -0
  393. package/test/units/static/JBPrices/TestPricePerUnitOf.sol +129 -0
  394. package/test/units/static/JBPrices/TestPrices.sol +262 -0
  395. package/test/units/static/JBProjects/JBProjectsSetup.sol +20 -0
  396. package/test/units/static/JBProjects/TestCreateFor.sol +69 -0
  397. package/test/units/static/JBProjects/TestInitialProject.sol +19 -0
  398. package/test/units/static/JBProjects/TestInterfaces.sol +27 -0
  399. package/test/units/static/JBProjects/TestSetResolver.sol +36 -0
  400. package/test/units/static/JBProjects/TestTokenUri.sol +38 -0
  401. package/test/units/static/JBRulesetMetadataResolver/TestSetCashOutTaxRateTo.sol +99 -0
  402. package/test/units/static/JBRulesets/JBRulesetsSetup.sol +21 -0
  403. package/test/units/static/JBRulesets/TestCurrentApprovalStatusForLatestRulesetOf.sol +257 -0
  404. package/test/units/static/JBRulesets/TestCurrentOf.sol +231 -0
  405. package/test/units/static/JBRulesets/TestGetRulesetOf.sol +94 -0
  406. package/test/units/static/JBRulesets/TestLatestQueuedRulesetOf.sol +252 -0
  407. package/test/units/static/JBRulesets/TestRulesets.sol +617 -0
  408. package/test/units/static/JBRulesets/TestRulesetsOf.sol +37 -0
  409. package/test/units/static/JBRulesets/TestUpcomingRulesetOf.sol +526 -0
  410. package/test/units/static/JBRulesets/TestUpdateRulesetWeightCache.sol +91 -0
  411. package/test/units/static/JBSplits/JBSplitsSetup.sol +23 -0
  412. package/test/units/static/JBSplits/TestSelfManagedSplitGroups.sol +502 -0
  413. package/test/units/static/JBSplits/TestSetSplitGroupsOf.sol +370 -0
  414. package/test/units/static/JBSplits/TestSplitsLockedEdge.sol +262 -0
  415. package/test/units/static/JBSplits/TestSplitsOf.sol +24 -0
  416. package/test/units/static/JBSplits/TestSplitsPacking.sol +33 -0
  417. package/test/units/static/JBSurplus/TestSurplusFuzz.sol +125 -0
  418. package/test/units/static/JBTerminalStore/JBTerminalStoreSetup.sol +23 -0
  419. package/test/units/static/JBTerminalStore/TestCurrentReclaimableSurplusOf.sol +434 -0
  420. package/test/units/static/JBTerminalStore/TestCurrentSurplusOf.sol +428 -0
  421. package/test/units/static/JBTerminalStore/TestCurrentTotalSurplusOf.sol +65 -0
  422. package/test/units/static/JBTerminalStore/TestRecordCashOutsFor.sol +479 -0
  423. package/test/units/static/JBTerminalStore/TestRecordPaymentFrom.sol +508 -0
  424. package/test/units/static/JBTerminalStore/TestRecordPayoutFor.sol +257 -0
  425. package/test/units/static/JBTerminalStore/TestRecordTerminalMigration.sol +131 -0
  426. package/test/units/static/JBTerminalStore/TestRecordUsedAllowanceOf.sol +390 -0
  427. package/test/units/static/JBTerminalStore/TestUint224Overflow.sol +187 -0
  428. package/test/units/static/JBTokens/JBTokensSetup.sol +23 -0
  429. package/test/units/static/JBTokens/TestBurnFrom.sol +104 -0
  430. package/test/units/static/JBTokens/TestClaimTokensFor.sol +107 -0
  431. package/test/units/static/JBTokens/TestDeployERC20ForUnits.sol +89 -0
  432. package/test/units/static/JBTokens/TestMintFor.sol +97 -0
  433. package/test/units/static/JBTokens/TestSetTokenFor.sol +95 -0
  434. package/test/units/static/JBTokens/TestTotalBalanceOf.sol +65 -0
  435. package/test/units/static/JBTokens/TestTotalSupplyOf.sol +56 -0
  436. package/test/units/static/JBTokens/TestTransferCreditsFrom.sol +54 -0
@@ -0,0 +1,2024 @@
1
+ // SPDX-License-Identifier: MIT
2
+ pragma solidity 0.8.23;
3
+
4
+ import {JBPermissionIds} from "@bananapus/permission-ids-v6/src/JBPermissionIds.sol";
5
+ import {ERC2771Context} from "@openzeppelin/contracts/metatx/ERC2771Context.sol";
6
+ import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
7
+ import {IERC20Metadata} from "@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol";
8
+ import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
9
+ import {Address} from "@openzeppelin/contracts/utils/Address.sol";
10
+ import {Context} from "@openzeppelin/contracts/utils/Context.sol";
11
+ import {IERC165} from "@openzeppelin/contracts/utils/introspection/IERC165.sol";
12
+ import {mulDiv} from "@prb/math/src/Common.sol";
13
+ import {IAllowanceTransfer} from "@uniswap/permit2/src/interfaces/IAllowanceTransfer.sol";
14
+ import {IPermit2} from "@uniswap/permit2/src/interfaces/IPermit2.sol";
15
+
16
+ import {JBPermissioned} from "./abstract/JBPermissioned.sol";
17
+ import {IJBCashOutTerminal} from "./interfaces/IJBCashOutTerminal.sol";
18
+ import {IJBController} from "./interfaces/IJBController.sol";
19
+ import {IJBDirectory} from "./interfaces/IJBDirectory.sol";
20
+ import {IJBFeelessAddresses} from "./interfaces/IJBFeelessAddresses.sol";
21
+ import {IJBFeeTerminal} from "./interfaces/IJBFeeTerminal.sol";
22
+ import {IJBMultiTerminal} from "./interfaces/IJBMultiTerminal.sol";
23
+ import {IJBPayoutTerminal} from "./interfaces/IJBPayoutTerminal.sol";
24
+ import {IJBPermissioned} from "./interfaces/IJBPermissioned.sol";
25
+ import {IJBPermissions} from "./interfaces/IJBPermissions.sol";
26
+ import {IJBPermitTerminal} from "./interfaces/IJBPermitTerminal.sol";
27
+ import {IJBProjects} from "./interfaces/IJBProjects.sol";
28
+ import {IJBRulesets} from "./interfaces/IJBRulesets.sol";
29
+ import {IJBSplitHook} from "./interfaces/IJBSplitHook.sol";
30
+ import {IJBSplits} from "./interfaces/IJBSplits.sol";
31
+ import {IJBTerminal} from "./interfaces/IJBTerminal.sol";
32
+ import {IJBTerminalStore} from "./interfaces/IJBTerminalStore.sol";
33
+ import {IJBTokens} from "./interfaces/IJBTokens.sol";
34
+ import {JBConstants} from "./libraries/JBConstants.sol";
35
+ import {JBFees} from "./libraries/JBFees.sol";
36
+ import {JBMetadataResolver} from "./libraries/JBMetadataResolver.sol";
37
+ import {JBRulesetMetadataResolver} from "./libraries/JBRulesetMetadataResolver.sol";
38
+ import {JBAccountingContext} from "./structs/JBAccountingContext.sol";
39
+ import {JBAfterPayRecordedContext} from "./structs/JBAfterPayRecordedContext.sol";
40
+ import {JBAfterCashOutRecordedContext} from "./structs/JBAfterCashOutRecordedContext.sol";
41
+ import {JBCashOutHookSpecification} from "./structs/JBCashOutHookSpecification.sol";
42
+ import {JBFee} from "./structs/JBFee.sol";
43
+ import {JBPayHookSpecification} from "./structs/JBPayHookSpecification.sol";
44
+ import {JBRuleset} from "./structs/JBRuleset.sol";
45
+ import {JBSingleAllowance} from "./structs/JBSingleAllowance.sol";
46
+ import {JBSplit} from "./structs/JBSplit.sol";
47
+ import {JBSplitHookContext} from "./structs/JBSplitHookContext.sol";
48
+ import {JBTokenAmount} from "./structs/JBTokenAmount.sol";
49
+
50
+ /// @notice `JBMultiTerminal` manages native/ERC-20 payments, cash outs, and surplus allowance usage for any number of
51
+ /// projects. Terminals are the entry point for operations involving inflows and outflows of funds.
52
+ contract JBMultiTerminal is JBPermissioned, ERC2771Context, IJBMultiTerminal {
53
+ // A library that parses the packed ruleset metadata into a friendlier format.
54
+ using JBRulesetMetadataResolver for JBRuleset;
55
+
56
+ // A library that adds default safety checks to ERC20 functionality.
57
+ using SafeERC20 for IERC20;
58
+
59
+ //*********************************************************************//
60
+ // --------------------------- custom errors ------------------------- //
61
+ //*********************************************************************//
62
+
63
+ error JBMultiTerminal_AccountingContextAlreadySet(address token);
64
+ error JBMultiTerminal_AccountingContextDecimalsMismatch();
65
+ error JBMultiTerminal_AddingAccountingContextNotAllowed();
66
+ error JBMultiTerminal_FeeTerminalNotFound(address token);
67
+ error JBMultiTerminal_NoMsgValueAllowed(uint256 value);
68
+ error JBMultiTerminal_OverflowAlert(uint256 value, uint256 limit);
69
+ error JBMultiTerminal_PermitAllowanceNotEnough(uint256 amount, uint256 allowance);
70
+ error JBMultiTerminal_RecipientProjectTerminalNotFound(uint256 projectId, address token);
71
+ error JBMultiTerminal_SplitHookInvalid(IJBSplitHook hook);
72
+ error JBMultiTerminal_TerminalTokensIncompatible(uint256 projectId, address token, IJBTerminal terminal);
73
+ error JBMultiTerminal_TokenNotAccepted(address token);
74
+ error JBMultiTerminal_UnderMinReturnedTokens(uint256 count, uint256 min);
75
+ error JBMultiTerminal_UnderMinTokensPaidOut(uint256 amount, uint256 min);
76
+ error JBMultiTerminal_UnderMinTokensReclaimed(uint256 amount, uint256 min);
77
+ error JBMultiTerminal_ZeroAccountingContextCurrency();
78
+
79
+ //*********************************************************************//
80
+ // ------------------------- public constants ------------------------ //
81
+ //*********************************************************************//
82
+
83
+ /// @notice This terminal's fee (as a fraction out of `JBConstants.MAX_FEE`).
84
+ /// @dev Fees are charged on payouts to addresses and surplus allowance usage, as well as cash outs while the
85
+ /// cash out tax rate is less than 100%.
86
+ uint256 public constant override FEE = 25; // 2.5%
87
+
88
+ //*********************************************************************//
89
+ // ------------------------ internal constants ----------------------- //
90
+ //*********************************************************************//
91
+
92
+ /// @notice Project ID #1 receives fees. It should be the first project launched during the deployment process.
93
+ uint256 internal constant _FEE_BENEFICIARY_PROJECT_ID = 1;
94
+
95
+ /// @notice The number of seconds fees can be held for.
96
+ uint256 internal constant _FEE_HOLDING_SECONDS = 2_419_200; // 28 days
97
+
98
+ //*********************************************************************//
99
+ // ---------------- public immutable stored properties --------------- //
100
+ //*********************************************************************//
101
+
102
+ /// @notice The directory of terminals and controllers for PROJECTS.
103
+ IJBDirectory public immutable override DIRECTORY;
104
+
105
+ /// @notice The contract that stores addresses that shouldn't incur fees when being paid towards or from.
106
+ IJBFeelessAddresses public immutable override FEELESS_ADDRESSES;
107
+
108
+ /// @notice The permit2 utility.
109
+ IPermit2 public immutable override PERMIT2;
110
+
111
+ /// @notice Mints ERC-721s that represent project ownership and transfers.
112
+ IJBProjects public immutable override PROJECTS;
113
+
114
+ /// @notice The contract storing and managing project rulesets.
115
+ IJBRulesets public immutable override RULESETS;
116
+
117
+ /// @notice The contract that stores splits for each project.
118
+ IJBSplits public immutable override SPLITS;
119
+
120
+ /// @notice The contract that stores and manages the terminal's data.
121
+ IJBTerminalStore public immutable override STORE;
122
+
123
+ /// @notice The contract storing and managing project rulesets.
124
+ IJBTokens public immutable override TOKENS;
125
+
126
+ //*********************************************************************//
127
+ // --------------------- internal stored properties ------------------ //
128
+ //*********************************************************************//
129
+
130
+ /// @notice Context describing how a token is accounted for by a project.
131
+ /// @custom:param projectId The ID of the project that the token accounting context applies to.
132
+ /// @custom:param token The address of the token being accounted for.
133
+ mapping(uint256 projectId => mapping(address token => JBAccountingContext)) internal _accountingContextForTokenOf;
134
+
135
+ /// @notice A list of tokens accepted by each project.
136
+ /// @custom:param projectId The ID of the project to get a list of accepted tokens for.
137
+ mapping(uint256 projectId => JBAccountingContext[]) internal _accountingContextsOf;
138
+
139
+ /// @notice Fees that are being held for each project.
140
+ /// @dev Projects can temporarily hold fees and unlock them later by adding funds to the project's balance.
141
+ /// @dev Held fees can be processed at any time by this terminal's owner.
142
+ /// @custom:param projectId The ID of the project that is holding fees.
143
+ /// @custom:param token The token that the fees are held in.
144
+ mapping(uint256 projectId => mapping(address token => JBFee[])) internal _heldFeesOf;
145
+
146
+ /// @notice The next index to use when processing a next held fee.
147
+ /// @custom:param projectId The ID of the project that is holding fees.
148
+ /// @custom:param token The token that the fees are held in.
149
+ mapping(uint256 projectId => mapping(address token => uint256)) internal _nextHeldFeeIndexOf;
150
+
151
+ //*********************************************************************//
152
+ // -------------------------- constructor ---------------------------- //
153
+ //*********************************************************************//
154
+
155
+ /// @param feelessAddresses A contract that stores addresses that shouldn't incur fees when being paid towards or
156
+ /// from.
157
+ /// @param permissions A contract storing permissions.
158
+ /// @param projects A contract which mints ERC-721s that represent project ownership and transfers.
159
+ /// @param splits A contract that stores splits for each project.
160
+ /// @param store A contract that stores the terminal's data.
161
+ /// @param permit2 A permit2 utility.
162
+ /// @param trustedForwarder A trusted forwarder of transactions to this contract.
163
+ constructor(
164
+ IJBFeelessAddresses feelessAddresses,
165
+ IJBPermissions permissions,
166
+ IJBProjects projects,
167
+ IJBSplits splits,
168
+ IJBTerminalStore store,
169
+ IJBTokens tokens,
170
+ IPermit2 permit2,
171
+ address trustedForwarder
172
+ )
173
+ JBPermissioned(permissions)
174
+ ERC2771Context(trustedForwarder)
175
+ {
176
+ DIRECTORY = store.DIRECTORY();
177
+ FEELESS_ADDRESSES = feelessAddresses;
178
+ PROJECTS = projects;
179
+ RULESETS = store.RULESETS();
180
+ SPLITS = splits;
181
+ STORE = store;
182
+ TOKENS = tokens;
183
+ PERMIT2 = permit2;
184
+ }
185
+
186
+ //*********************************************************************//
187
+ // ------------------------- external views -------------------------- //
188
+ //*********************************************************************//
189
+
190
+ /// @notice A project's accounting context for a token.
191
+ /// @dev See the `JBAccountingContext` struct for more information.
192
+ /// @param projectId The ID of the project to get token accounting context of.
193
+ /// @param token The token to check the accounting context of.
194
+ /// @return The token's accounting context for the token.
195
+ function accountingContextForTokenOf(
196
+ uint256 projectId,
197
+ address token
198
+ )
199
+ external
200
+ view
201
+ override
202
+ returns (JBAccountingContext memory)
203
+ {
204
+ return _accountingContextForTokenOf[projectId][token];
205
+ }
206
+
207
+ /// @notice The tokens accepted by a project.
208
+ /// @param projectId The ID of the project to get the accepted tokens of.
209
+ /// @return tokenContexts The accounting contexts of the accepted tokens.
210
+ function accountingContextsOf(uint256 projectId) external view override returns (JBAccountingContext[] memory) {
211
+ return _accountingContextsOf[projectId];
212
+ }
213
+
214
+ /// @notice Gets the total current surplus amount in this terminal for a project, in terms of a given currency.
215
+ /// @dev This total surplus only includes tokens that the project accepts (as returned by
216
+ /// `accountingContextsOf(...)`).
217
+ /// @param projectId The ID of the project to get the current total surplus of.
218
+ /// @param accountingContexts The accounting contexts to use to calculate the surplus. Pass an empty array to use
219
+ /// all of the project's accounting contexts.
220
+ /// @param decimals The number of decimals to include in the fixed point returned value.
221
+ /// @param currency The currency to express the returned value in terms of.
222
+ /// @return The current surplus amount the project has in this terminal, in terms of `currency` and with the
223
+ /// specified number of decimals.
224
+ function currentSurplusOf(
225
+ uint256 projectId,
226
+ JBAccountingContext[] memory accountingContexts,
227
+ uint256 decimals,
228
+ uint256 currency
229
+ )
230
+ external
231
+ view
232
+ override
233
+ returns (uint256)
234
+ {
235
+ return STORE.currentSurplusOf({
236
+ terminal: address(this),
237
+ projectId: projectId,
238
+ accountingContexts: accountingContexts.length != 0 ? accountingContexts : _accountingContextsOf[projectId],
239
+ decimals: decimals,
240
+ currency: currency
241
+ });
242
+ }
243
+
244
+ /// @notice Fees that are being held for a project.
245
+ /// @dev Projects can temporarily hold fees and unlock them later by adding funds to the project's balance.
246
+ /// @dev Held fees can be processed at any time by this terminal's owner.
247
+ /// @param projectId The ID of the project that is holding fees.
248
+ /// @param token The token that the fees are held in.
249
+ function heldFeesOf(
250
+ uint256 projectId,
251
+ address token,
252
+ uint256 count
253
+ )
254
+ external
255
+ view
256
+ override
257
+ returns (JBFee[] memory heldFees)
258
+ {
259
+ // Keep a reference to the start index.
260
+ uint256 startIndex = _nextHeldFeeIndexOf[projectId][token];
261
+
262
+ // Get a reference to the number of held fees.
263
+ uint256 numberOfHeldFees = _heldFeesOf[projectId][token].length;
264
+
265
+ // If the start index is greater than or equal to the number of held fees, return 0.
266
+ if (startIndex >= numberOfHeldFees) return new JBFee[](0);
267
+
268
+ // If the start index plus the count is greater than the number of fees, set the count to the number of fees
269
+ if (startIndex + count > numberOfHeldFees) count = numberOfHeldFees - startIndex;
270
+
271
+ // Create a new array to hold the fees.
272
+ heldFees = new JBFee[](count);
273
+
274
+ // Copy the fees into the array.
275
+ for (uint256 i; i < count; i++) {
276
+ heldFees[i] = _heldFeesOf[projectId][token][startIndex + i];
277
+ }
278
+ }
279
+
280
+ //*********************************************************************//
281
+ // -------------------------- public views --------------------------- //
282
+ //*********************************************************************//
283
+
284
+ /// @notice Indicates whether this contract adheres to the specified interface.
285
+ /// @dev See {IERC165-supportsInterface}.
286
+ /// @param interfaceId The ID of the interface to check for adherence to.
287
+ /// @return A flag indicating if the provided interface ID is supported.
288
+ function supportsInterface(bytes4 interfaceId) public pure override returns (bool) {
289
+ return interfaceId == type(IJBMultiTerminal).interfaceId || interfaceId == type(IJBPermissioned).interfaceId
290
+ || interfaceId == type(IJBTerminal).interfaceId || interfaceId == type(IJBCashOutTerminal).interfaceId
291
+ || interfaceId == type(IJBPayoutTerminal).interfaceId || interfaceId == type(IJBPermitTerminal).interfaceId
292
+ || interfaceId == type(IJBFeeTerminal).interfaceId || interfaceId == type(IERC165).interfaceId;
293
+ }
294
+
295
+ //*********************************************************************//
296
+ // -------------------------- internal views ------------------------- //
297
+ //*********************************************************************//
298
+
299
+ /// @notice Checks this terminal's balance of a specific token.
300
+ /// @param token The address of the token to get this terminal's balance of.
301
+ /// @return This terminal's balance.
302
+ function _balanceOf(address token) internal view returns (uint256) {
303
+ // If the `token` is native, get the native token balance.
304
+ return token == JBConstants.NATIVE_TOKEN ? address(this).balance : IERC20(token).balanceOf(address(this));
305
+ }
306
+
307
+ /// @dev `ERC-2771` specifies the context as being a single address (20 bytes).
308
+ function _contextSuffixLength() internal view override(ERC2771Context, Context) returns (uint256) {
309
+ return super._contextSuffixLength();
310
+ }
311
+
312
+ /// @notice Returns the current controller of a project.
313
+ /// @param projectId The ID of the project to get the controller of.
314
+ /// @return controller The project's controller.
315
+ function _controllerOf(uint256 projectId) internal view returns (IJBController) {
316
+ return IJBController(address(DIRECTORY.controllerOf(projectId)));
317
+ }
318
+
319
+ /// @notice Returns a flag indicating if interacting with an address should not incur fees.
320
+ /// @param addr The address to check.
321
+ /// @return A flag indicating if the address should not incur fees.
322
+ function _isFeeless(address addr) internal view returns (bool) {
323
+ return FEELESS_ADDRESSES.isFeeless(addr);
324
+ }
325
+
326
+ /// @notice The calldata. Preferred to use over `msg.data`.
327
+ /// @return calldata The `msg.data` of this call.
328
+ function _msgData() internal view override(ERC2771Context, Context) returns (bytes calldata) {
329
+ return ERC2771Context._msgData();
330
+ }
331
+
332
+ /// @notice The message's sender. Preferred to use over `msg.sender`.
333
+ /// @return sender The address which sent this call.
334
+ function _msgSender() internal view override(ERC2771Context, Context) returns (address sender) {
335
+ return ERC2771Context._msgSender();
336
+ }
337
+
338
+ /// @notice The owner of a project.
339
+ /// @param projectId The ID of the project to get the owner of.
340
+ /// @return The owner of the project.
341
+ function _ownerOf(uint256 projectId) internal view returns (address) {
342
+ return PROJECTS.ownerOf(projectId);
343
+ }
344
+
345
+ /// @notice The primary terminal of a project for a token.
346
+ /// @param projectId The ID of the project to get the primary terminal of.
347
+ /// @param token The token to get the primary terminal of.
348
+ /// @return The primary terminal of the project for the token.
349
+ function _primaryTerminalOf(uint256 projectId, address token) internal view returns (IJBTerminal) {
350
+ return DIRECTORY.primaryTerminalOf({projectId: projectId, token: token});
351
+ }
352
+
353
+ //*********************************************************************//
354
+ // ---------------------- external transactions ---------------------- //
355
+ //*********************************************************************//
356
+
357
+ /// @notice Adds accounting contexts for a project to this terminal so the project can begin accepting the tokens in
358
+ /// those contexts.
359
+ /// @dev Only a project's owner, an operator with the `ADD_ACCOUNTING_CONTEXTS` permission from that owner, or a
360
+ /// project's controller can add accounting contexts for the project.
361
+ /// @param projectId The ID of the project having to add accounting contexts for.
362
+ /// @param accountingContexts The accounting contexts to add.
363
+ function addAccountingContextsFor(
364
+ uint256 projectId,
365
+ JBAccountingContext[] calldata accountingContexts
366
+ )
367
+ external
368
+ override
369
+ {
370
+ // Enforce permissions.
371
+ _requirePermissionAllowingOverrideFrom({
372
+ account: _ownerOf(projectId),
373
+ projectId: projectId,
374
+ permissionId: JBPermissionIds.ADD_ACCOUNTING_CONTEXTS,
375
+ alsoGrantAccessIf: _msgSender() == address(_controllerOf(projectId))
376
+ });
377
+
378
+ // Get a reference to the project's current ruleset.
379
+ JBRuleset memory ruleset = RULESETS.currentOf(projectId);
380
+
381
+ // Make sure that if there's a ruleset, it allows adding accounting contexts.
382
+ if (ruleset.id != 0 && !ruleset.allowAddAccountingContext()) {
383
+ revert JBMultiTerminal_AddingAccountingContextNotAllowed();
384
+ }
385
+
386
+ // Start accepting each token.
387
+ for (uint256 i; i < accountingContexts.length; i++) {
388
+ // Set the accounting context being iterated on.
389
+ JBAccountingContext memory accountingContext = accountingContexts[i];
390
+
391
+ // Get a storage reference to the currency accounting context for the token.
392
+ JBAccountingContext storage storedAccountingContext =
393
+ _accountingContextForTokenOf[projectId][accountingContext.token];
394
+
395
+ // Make sure the token accounting context isn't already set.
396
+ if (storedAccountingContext.token != address(0)) {
397
+ revert JBMultiTerminal_AccountingContextAlreadySet(storedAccountingContext.token);
398
+ }
399
+
400
+ // Keep track of a flag indiciating if we know the provided decimals are incorrect.
401
+ bool knownInvalidDecimals;
402
+
403
+ // Check if the token is the native token and has the correct decimals
404
+ if (accountingContext.token == JBConstants.NATIVE_TOKEN && accountingContext.decimals != 18) {
405
+ knownInvalidDecimals = true;
406
+ } else if (accountingContext.token != JBConstants.NATIVE_TOKEN) {
407
+ // slither-disable-next-line calls-loop
408
+ try IERC20Metadata(accountingContext.token).decimals() returns (uint8 decimals) {
409
+ // slither-disable-next-line calls-loop
410
+ if (accountingContext.decimals != decimals) {
411
+ knownInvalidDecimals = true;
412
+ }
413
+ } catch {
414
+ // The token didn't support `decimals`.
415
+ // @dev Non-standard ERC20s that revert on `decimals()` will bypass decimal validation.
416
+ // The caller is responsible for providing the correct decimals for such tokens.
417
+ knownInvalidDecimals = false;
418
+ }
419
+ }
420
+
421
+ // Make sure the decimals are correct.
422
+ if (knownInvalidDecimals) {
423
+ revert JBMultiTerminal_AccountingContextDecimalsMismatch();
424
+ }
425
+
426
+ // Make sure the currency is non-zero.
427
+ if (accountingContext.currency == 0) revert JBMultiTerminal_ZeroAccountingContextCurrency();
428
+
429
+ // Define the context from the config.
430
+ storedAccountingContext.token = accountingContext.token;
431
+ storedAccountingContext.decimals = accountingContext.decimals;
432
+ storedAccountingContext.currency = accountingContext.currency;
433
+
434
+ // Add the token to the list of accepted tokens of the project.
435
+ _accountingContextsOf[projectId].push(storedAccountingContext);
436
+
437
+ emit SetAccountingContext({projectId: projectId, context: storedAccountingContext, caller: _msgSender()});
438
+ }
439
+ }
440
+
441
+ /// @notice Adds funds to a project's balance without minting tokens.
442
+ /// @dev Adding to balance can unlock held fees if `shouldUnlockHeldFees` is true.
443
+ /// @param projectId The ID of the project to add funds to the balance of.
444
+ /// @param amount The amount of tokens to add to the balance, as a fixed point number with the same number of
445
+ /// decimals as this terminal. If this is a native token terminal, this is ignored and `msg.value` is used instead.
446
+ /// @param token The token being added to the balance.
447
+ /// @param shouldReturnHeldFees A flag indicating if held fees should be returned based on the amount being added.
448
+ /// @param memo A memo to pass along to the emitted event.
449
+ /// @param metadata Extra data to pass along to the emitted event.
450
+ function addToBalanceOf(
451
+ uint256 projectId,
452
+ address token,
453
+ uint256 amount,
454
+ bool shouldReturnHeldFees,
455
+ string calldata memo,
456
+ bytes calldata metadata
457
+ )
458
+ external
459
+ payable
460
+ override
461
+ {
462
+ // Add to balance.
463
+ _addToBalanceOf({
464
+ projectId: projectId,
465
+ token: token,
466
+ amount: _acceptFundsFor(projectId, token, amount, metadata),
467
+ shouldReturnHeldFees: shouldReturnHeldFees,
468
+ memo: memo,
469
+ metadata: metadata
470
+ });
471
+ }
472
+
473
+ /// @notice Holders can cash out a project's tokens to reclaim some of that project's surplus tokens, or to trigger
474
+ /// rules determined by the current ruleset's data hook and cash out hook.
475
+ /// @dev Only a token's holder or an operator with the `CASH_OUT_TOKENS` permission from that holder can cash out
476
+ /// those tokens.
477
+ /// @param holder The account whose tokens are being cashed out.
478
+ /// @param projectId The ID of the project the project tokens belong to.
479
+ /// @param cashOutCount The number of project tokens to cash out, as a fixed point number with 18 decimals.
480
+ /// @param tokenToReclaim The token being reclaimed.
481
+ /// @param minTokensReclaimed The minimum number of terminal tokens expected in return, as a fixed point number with
482
+ /// the same number of decimals as this terminal. If the amount of tokens minted for the beneficiary would be less
483
+ /// than this amount, the cash out is reverted.
484
+ /// @param beneficiary The address to send the cashed out terminal tokens to, and to pass along to the ruleset's
485
+ /// data hook and cash out hook if applicable.
486
+ /// @param metadata Bytes to send along to the emitted event, as well as the data hook and cash out hook if
487
+ /// applicable.
488
+ /// @return reclaimAmount The amount of terminal tokens that the project tokens were cashed out for, as a fixed
489
+ /// point
490
+ /// number with 18 decimals.
491
+ function cashOutTokensOf(
492
+ address holder,
493
+ uint256 projectId,
494
+ uint256 cashOutCount,
495
+ address tokenToReclaim,
496
+ uint256 minTokensReclaimed,
497
+ address payable beneficiary,
498
+ bytes calldata metadata
499
+ )
500
+ external
501
+ override
502
+ returns (uint256 reclaimAmount)
503
+ {
504
+ // Enforce permissions.
505
+ _requirePermissionFrom({account: holder, projectId: projectId, permissionId: JBPermissionIds.CASH_OUT_TOKENS});
506
+
507
+ reclaimAmount = _cashOutTokensOf({
508
+ holder: holder,
509
+ projectId: projectId,
510
+ cashOutCount: cashOutCount,
511
+ tokenToReclaim: tokenToReclaim,
512
+ beneficiary: beneficiary,
513
+ metadata: metadata
514
+ });
515
+
516
+ // The amount being reclaimed must be at least as much as was expected.
517
+ if (reclaimAmount < minTokensReclaimed) {
518
+ revert JBMultiTerminal_UnderMinTokensReclaimed(reclaimAmount, minTokensReclaimed);
519
+ }
520
+ }
521
+
522
+ /// @notice Executes a payout to a split.
523
+ /// @dev Only accepts calls from this terminal itself.
524
+ /// @param split The split to pay.
525
+ /// @param projectId The ID of the project the split belongs to.
526
+ /// @param token The address of the token being paid to the split.
527
+ /// @param amount The total amount being paid to the split, as a fixed point number with the same number of
528
+ /// decimals as this terminal.
529
+ /// @return netPayoutAmount The amount sent to the split after subtracting fees.
530
+ function executePayout(
531
+ JBSplit calldata split,
532
+ uint256 projectId,
533
+ address token,
534
+ uint256 amount,
535
+ address originalMessageSender
536
+ )
537
+ external
538
+ returns (uint256 netPayoutAmount)
539
+ {
540
+ // NOTICE: May only be called by this terminal itself.
541
+ require(msg.sender == address(this));
542
+
543
+ // By default, the net payout amount is the full amount. This will be adjusted if fees are taken.
544
+ netPayoutAmount = amount;
545
+
546
+ // If there's a split hook set, transfer to its `process` function.
547
+ if (split.hook != IJBSplitHook(address(0))) {
548
+ // Make sure that the address supports the split hook interface.
549
+ if (!split.hook.supportsInterface(type(IJBSplitHook).interfaceId)) {
550
+ revert JBMultiTerminal_SplitHookInvalid(split.hook);
551
+ }
552
+
553
+ // This payout is eligible for a fee since the funds are leaving this contract and the split hook isn't a
554
+ // feeless address.
555
+ if (!_isFeeless(address(split.hook))) {
556
+ netPayoutAmount -= JBFees.feeAmountFrom({amountBeforeFee: amount, feePercent: FEE});
557
+ }
558
+
559
+ // Create the context to send to the split hook.
560
+ JBSplitHookContext memory context = JBSplitHookContext({
561
+ token: token,
562
+ amount: netPayoutAmount,
563
+ decimals: _accountingContextForTokenOf[projectId][token].decimals,
564
+ projectId: projectId,
565
+ groupId: uint256(uint160(token)),
566
+ split: split
567
+ });
568
+
569
+ // Trigger any inherited pre-transfer logic.
570
+ // Get a reference to the amount being paid in `msg.value`.
571
+ uint256 payValue = _beforeTransferTo({to: address(split.hook), token: token, amount: netPayoutAmount});
572
+
573
+ // If this terminal's token is the native token, send it in `msg.value`.
574
+ split.hook.processSplitWith{value: payValue}(context);
575
+
576
+ // Otherwise, if a project is specified, make a payment to it.
577
+ } else if (split.projectId != 0) {
578
+ // Get a reference to the terminal being used.
579
+ IJBTerminal terminal = _primaryTerminalOf({projectId: split.projectId, token: token});
580
+
581
+ // The project must have a terminal to send funds to.
582
+ if (terminal == IJBTerminal(address(0))) {
583
+ revert JBMultiTerminal_RecipientProjectTerminalNotFound(split.projectId, token);
584
+ }
585
+
586
+ // This payout is eligible for a fee if the funds are leaving this contract and the receiving terminal isn't
587
+ // a feelss address.
588
+ if (terminal != this && !_isFeeless(address(terminal))) {
589
+ netPayoutAmount -= JBFees.feeAmountFrom({amountBeforeFee: amount, feePercent: FEE});
590
+ }
591
+
592
+ // Send the `projectId` in the metadata as a referral.
593
+ bytes memory metadata = bytes(abi.encodePacked(projectId));
594
+
595
+ // Add to balance if preferred.
596
+ if (split.preferAddToBalance) {
597
+ _efficientAddToBalance({
598
+ terminal: terminal,
599
+ projectId: split.projectId,
600
+ token: token,
601
+ amount: netPayoutAmount,
602
+ metadata: metadata
603
+ });
604
+ } else {
605
+ // Keep a reference to the beneficiary of the payment.
606
+ address beneficiary = split.beneficiary != address(0) ? split.beneficiary : originalMessageSender;
607
+
608
+ _efficientPay({
609
+ terminal: terminal,
610
+ projectId: split.projectId,
611
+ token: token,
612
+ amount: netPayoutAmount,
613
+ beneficiary: beneficiary,
614
+ metadata: metadata
615
+ });
616
+ }
617
+ } else {
618
+ // If there's a beneficiary, send the funds directly to the beneficiary.
619
+ // If there isn't a beneficiary, send the funds to the `_msgSender()`.
620
+ address payable recipient =
621
+ split.beneficiary != address(0) ? split.beneficiary : payable(originalMessageSender);
622
+
623
+ // This payout is eligible for a fee since the funds are leaving this contract and the recipient isn't a
624
+ // feeless address.
625
+ if (!_isFeeless(recipient)) {
626
+ netPayoutAmount -= JBFees.feeAmountFrom({amountBeforeFee: amount, feePercent: FEE});
627
+ }
628
+
629
+ // If there's a beneficiary, send the funds directly to the beneficiary. Otherwise send to the
630
+ // `_msgSender()`.
631
+ _transferFrom({from: address(this), to: recipient, token: token, amount: netPayoutAmount});
632
+ }
633
+ }
634
+
635
+ /// @notice Process a specified amount of fees for a project.
636
+ /// @dev Only accepts calls from this terminal itself.
637
+ /// @param projectId The ID of the project paying the fee.
638
+ /// @param token The token the fee is being paid in.
639
+ /// @param amount The fee amount, as a fixed point number with 18 decimals.
640
+ /// @param beneficiary The address to mint tokens to (from the project which receives fees), and pass along to the
641
+ /// ruleset's data hook and pay hook if applicable.
642
+ /// @param feeTerminal The terminal that'll receive the fees.
643
+ function executeProcessFee(
644
+ uint256 projectId,
645
+ address token,
646
+ uint256 amount,
647
+ address beneficiary,
648
+ IJBTerminal feeTerminal
649
+ )
650
+ external
651
+ {
652
+ // NOTICE: May only be called by this terminal itself.
653
+ require(msg.sender == address(this));
654
+
655
+ if (address(feeTerminal) == address(0)) {
656
+ revert JBMultiTerminal_FeeTerminalNotFound(token);
657
+ }
658
+
659
+ // Send the projectId in the metadata.
660
+ bytes memory metadata = bytes(abi.encodePacked(projectId));
661
+
662
+ _efficientPay({
663
+ terminal: feeTerminal,
664
+ projectId: _FEE_BENEFICIARY_PROJECT_ID,
665
+ token: token,
666
+ amount: amount,
667
+ beneficiary: beneficiary,
668
+ metadata: metadata
669
+ });
670
+ }
671
+
672
+ /// @notice Transfer funds to an address.
673
+ /// @dev Only accepts calls from this terminal itself.
674
+ /// @param addr The address to transfer funds to.
675
+ /// @param token The token to transfer.
676
+ /// @param amount The amount of tokens to transfer.
677
+ function executeTransferTo(address payable addr, address token, uint256 amount) external {
678
+ // NOTICE: May only be called by this terminal itself.
679
+ require(msg.sender == address(this));
680
+
681
+ _transferFrom({from: address(this), to: addr, token: token, amount: amount});
682
+ }
683
+
684
+ /// @notice Migrate a project's funds and operations to a new terminal that accepts the same token type.
685
+ /// @dev Only a project's owner or an operator with the `MIGRATE_TERMINAL` permission from that owner can migrate
686
+ /// the project's terminal.
687
+ /// @param projectId The ID of the project being migrated.
688
+ /// @param token The address of the token being migrated.
689
+ /// @param to The terminal contract being migrated to, which will receive the project's funds and operations.
690
+ /// @return balance The amount of funds that were migrated, as a fixed point number with the same amount of decimals
691
+ /// as this terminal.
692
+ function migrateBalanceOf(
693
+ uint256 projectId,
694
+ address token,
695
+ IJBTerminal to
696
+ )
697
+ external
698
+ override
699
+ returns (uint256 balance)
700
+ {
701
+ // Enforce permissions.
702
+ _requirePermissionFrom({
703
+ account: _ownerOf(projectId), projectId: projectId, permissionId: JBPermissionIds.MIGRATE_TERMINAL
704
+ });
705
+
706
+ // The terminal being migrated to must accept the same token as this terminal.
707
+ if (to.accountingContextForTokenOf({projectId: projectId, token: token}).currency == 0) {
708
+ revert JBMultiTerminal_TerminalTokensIncompatible({projectId: projectId, token: token, terminal: to});
709
+ }
710
+
711
+ // Record the migration in the store.
712
+ // slither-disable-next-line reentrancy-events
713
+ balance = STORE.recordTerminalMigration({projectId: projectId, token: token});
714
+
715
+ emit MigrateTerminal({projectId: projectId, token: token, to: to, amount: balance, caller: _msgSender()});
716
+
717
+ // Transfer the balance if needed.
718
+ if (balance != 0) {
719
+ // Trigger any inherited pre-transfer logic.
720
+ // If this terminal's token is the native token, send it in `msg.value`.
721
+ // slither-disable-next-line reentrancy-events
722
+ uint256 payValue = _beforeTransferTo({to: address(to), token: token, amount: balance});
723
+
724
+ // Withdraw the balance to transfer to the new terminal;
725
+ // slither-disable-next-line reentrancy-events
726
+ to.addToBalanceOf{value: payValue}({
727
+ projectId: projectId,
728
+ token: token,
729
+ amount: balance,
730
+ shouldReturnHeldFees: false,
731
+ memo: "",
732
+ metadata: bytes("")
733
+ });
734
+ }
735
+ }
736
+
737
+ /// @notice Pay a project with tokens.
738
+ /// @param projectId The ID of the project being paid.
739
+ /// @param amount The amount of terminal tokens being received, as a fixed point number with the same number of
740
+ /// decimals as this terminal. If this terminal's token is native, this is ignored and `msg.value` is used in its
741
+ /// place.
742
+ /// @param token The token being paid.
743
+ /// @param beneficiary The address to mint tokens to, and pass along to the ruleset's data hook and pay hook if
744
+ /// applicable.
745
+ /// @param minReturnedTokens The minimum number of project tokens expected in return for this payment, as a fixed
746
+ /// point number with the same number of decimals as this terminal. If the amount of tokens minted for the
747
+ /// beneficiary would be less than this amount, the payment is reverted.
748
+ /// @param memo A memo to pass along to the emitted event.
749
+ /// @param metadata Bytes to pass along to the emitted event, as well as the data hook and pay hook if applicable.
750
+ /// @return beneficiaryTokenCount The number of tokens minted to the beneficiary, as a fixed point number with 18
751
+ /// decimals.
752
+ function pay(
753
+ uint256 projectId,
754
+ address token,
755
+ uint256 amount,
756
+ address beneficiary,
757
+ uint256 minReturnedTokens,
758
+ string calldata memo,
759
+ bytes calldata metadata
760
+ )
761
+ external
762
+ payable
763
+ override
764
+ returns (uint256 beneficiaryTokenCount)
765
+ {
766
+ // Get a reference to the beneficiary's balance before the payment.
767
+ uint256 beneficiaryBalanceBefore = TOKENS.totalBalanceOf({holder: beneficiary, projectId: projectId});
768
+
769
+ // Pay the project.
770
+ _pay({
771
+ projectId: projectId,
772
+ token: token,
773
+ amount: _acceptFundsFor(projectId, token, amount, metadata),
774
+ payer: _msgSender(),
775
+ beneficiary: beneficiary,
776
+ memo: memo,
777
+ metadata: metadata
778
+ });
779
+
780
+ // Get a reference to the beneficiary's balance after the payment.
781
+ uint256 beneficiaryBalanceAfter = TOKENS.totalBalanceOf({holder: beneficiary, projectId: projectId});
782
+
783
+ // Set the beneficiary token count.
784
+ if (beneficiaryBalanceAfter > beneficiaryBalanceBefore) {
785
+ beneficiaryTokenCount = beneficiaryBalanceAfter - beneficiaryBalanceBefore;
786
+ }
787
+
788
+ // The token count for the beneficiary must be greater than or equal to the specified minimum.
789
+ if (beneficiaryTokenCount < minReturnedTokens) {
790
+ revert JBMultiTerminal_UnderMinReturnedTokens(beneficiaryTokenCount, minReturnedTokens);
791
+ }
792
+ }
793
+
794
+ /// @notice Process any fees that are being held for the project.
795
+ /// @param projectId The ID of the project to process held fees for.
796
+ /// @param token The token to process held fees for.
797
+ /// @param count The number of fees to process.
798
+ function processHeldFeesOf(uint256 projectId, address token, uint256 count) external override {
799
+ // Keep a reference to the terminal that'll receive the fees.
800
+ IJBTerminal feeTerminal = _primaryTerminalOf({projectId: _FEE_BENEFICIARY_PROJECT_ID, token: token});
801
+
802
+ // Process each fee. Re-read the index and array length from storage each iteration to account for reentrant
803
+ // calls that may have already advanced the index or cleaned up the array.
804
+ for (uint256 i; i < count; i++) {
805
+ // Read the current index from storage (not a cached value) to prevent reentrancy from
806
+ // causing double-processing.
807
+ uint256 currentIndex = _nextHeldFeeIndexOf[projectId][token];
808
+
809
+ // If all fees have been processed, break to cleanup.
810
+ if (currentIndex >= _heldFeesOf[projectId][token].length) break;
811
+
812
+ // Keep a reference to the held fee being iterated on.
813
+ JBFee memory heldFee = _heldFeesOf[projectId][token][currentIndex];
814
+
815
+ // Can't process fees that aren't yet unlocked. Fees unlock sequentially in the array, so nothing left to do
816
+ // if the current fee isn't yet unlocked.
817
+ if (heldFee.unlockTimestamp > block.timestamp) break;
818
+
819
+ // Delete the entry to reclaim gas before the external call.
820
+ delete _heldFeesOf[projectId][token][currentIndex];
821
+
822
+ // Update the index before the external call to prevent reentrancy from reprocessing the same fee.
823
+ _nextHeldFeeIndexOf[projectId][token] = currentIndex + 1;
824
+
825
+ // Process the fee.
826
+ // slither-disable-next-line reentrancy-no-eth
827
+ _processFee({
828
+ projectId: projectId,
829
+ token: token,
830
+ amount: JBFees.feeAmountFrom({amountBeforeFee: heldFee.amount, feePercent: FEE}),
831
+ beneficiary: heldFee.beneficiary,
832
+ feeTerminal: feeTerminal,
833
+ wasHeld: true
834
+ });
835
+ }
836
+
837
+ // If all held fees have been processed, reset the array and index entirely to bound storage growth.
838
+ if (
839
+ _nextHeldFeeIndexOf[projectId][token] >= _heldFeesOf[projectId][token].length
840
+ && _heldFeesOf[projectId][token].length > 0
841
+ ) {
842
+ delete _heldFeesOf[projectId][token];
843
+ delete _nextHeldFeeIndexOf[projectId][token];
844
+ }
845
+ }
846
+
847
+ /// @notice Sends payouts to a project's current payout split group, according to its ruleset, up to its current
848
+ /// payout limit.
849
+ /// @dev If the percentages of the splits in the project's payout split group do not add up to 100%, the remainder
850
+ /// is sent to the project's owner.
851
+ /// @dev Anyone can send payouts on a project's behalf. Projects can include a wildcard split (a split with no
852
+ /// `hook`, `projectId`, or `beneficiary`) to send funds to the `_msgSender()` which calls this function. This can
853
+ /// be used to incentivize calling this function.
854
+ /// @dev payouts sent to addresses which aren't feeless incur the protocol fee.
855
+ /// @dev Payouts a projects don't incur fees if its terminal is feeless.
856
+ /// @param projectId The ID of the project having its payouts sent.
857
+ /// @param token The token being sent.
858
+ /// @param amount The total number of terminal tokens to send, as a fixed point number with same number of decimals
859
+ /// as this terminal.
860
+ /// @param currency The expected currency of the payouts being sent. Must match the currency of one of the
861
+ /// project's current ruleset's payout limits.
862
+ /// @param minTokensPaidOut The minimum number of terminal tokens that the `amount` should be worth (if expressed
863
+ /// in terms of this terminal's currency), as a fixed point number with the same number of decimals as this
864
+ /// terminal. If the amount of tokens paid out would be less than this amount, the send is reverted.
865
+ /// @return amountPaidOut The total amount paid out.
866
+ function sendPayoutsOf(
867
+ uint256 projectId,
868
+ address token,
869
+ uint256 amount,
870
+ uint256 currency,
871
+ uint256 minTokensPaidOut
872
+ )
873
+ external
874
+ override
875
+ returns (uint256 amountPaidOut)
876
+ {
877
+ amountPaidOut = _sendPayoutsOf({projectId: projectId, token: token, amount: amount, currency: currency});
878
+
879
+ // The amount being paid out must be at least as much as was expected.
880
+ if (amountPaidOut < minTokensPaidOut) {
881
+ revert JBMultiTerminal_UnderMinTokensPaidOut(amountPaidOut, minTokensPaidOut);
882
+ }
883
+ }
884
+
885
+ /// @notice Allows a project to pay out funds from its surplus up to the current surplus allowance.
886
+ /// @dev Only a project's owner or an operator with the `USE_ALLOWANCE` permission from that owner can use the
887
+ /// surplus allowance.
888
+ /// @dev Incurs the protocol fee unless the caller is a feeless address.
889
+ /// @param projectId The ID of the project to use the surplus allowance of.
890
+ /// @param token The token being paid out from the surplus.
891
+ /// @param amount The amount of terminal tokens to use from the project's current surplus allowance, as a fixed
892
+ /// point number with the same amount of decimals as this terminal.
893
+ /// @param currency The expected currency of the amount being paid out. Must match the currency of one of the
894
+ /// project's current ruleset's surplus allowances.
895
+ /// @param minTokensPaidOut The minimum number of terminal tokens that should be returned from the surplus allowance
896
+ /// (excluding fees), as a fixed point number with 18 decimals. If the amount of surplus used would be less than
897
+ /// this amount, the transaction is reverted.
898
+ /// @param beneficiary The address to send the surplus funds to.
899
+ /// @param feeBeneficiary The address to send the tokens resulting from paying the fee.
900
+ /// @param memo A memo to pass along to the emitted event.
901
+ /// @return netAmountPaidOut The number of tokens that were sent to the beneficiary, as a fixed point number with
902
+ /// the same amount of decimals as the terminal.
903
+ function useAllowanceOf(
904
+ uint256 projectId,
905
+ address token,
906
+ uint256 amount,
907
+ uint256 currency,
908
+ uint256 minTokensPaidOut,
909
+ address payable beneficiary,
910
+ address payable feeBeneficiary,
911
+ string calldata memo
912
+ )
913
+ external
914
+ override
915
+ returns (uint256 netAmountPaidOut)
916
+ {
917
+ // Keep a reference to the project's owner.
918
+ address owner = _ownerOf(projectId);
919
+
920
+ // Enforce permissions.
921
+ _requirePermissionFrom({account: owner, projectId: projectId, permissionId: JBPermissionIds.USE_ALLOWANCE});
922
+
923
+ netAmountPaidOut = _useAllowanceOf({
924
+ projectId: projectId,
925
+ owner: owner,
926
+ token: token,
927
+ amount: amount,
928
+ currency: currency,
929
+ beneficiary: beneficiary,
930
+ feeBeneficiary: feeBeneficiary,
931
+ memo: memo
932
+ });
933
+
934
+ // The amount being withdrawn must be at least as much as was expected.
935
+ if (netAmountPaidOut < minTokensPaidOut) {
936
+ revert JBMultiTerminal_UnderMinTokensPaidOut(netAmountPaidOut, minTokensPaidOut);
937
+ }
938
+ }
939
+
940
+ //*********************************************************************//
941
+ // ------------------------ internal functions ----------------------- //
942
+ //*********************************************************************//
943
+
944
+ /// @notice Accepts an incoming token.
945
+ /// @param projectId The ID of the project that the transfer is being accepted for.
946
+ /// @param token The token being accepted.
947
+ /// @param amount The number of tokens being accepted.
948
+ /// @param metadata The metadata in which permit2 context is provided.
949
+ /// @return amount The number of tokens which have been accepted.
950
+ function _acceptFundsFor(
951
+ uint256 projectId,
952
+ address token,
953
+ uint256 amount,
954
+ bytes calldata metadata
955
+ )
956
+ internal
957
+ returns (uint256)
958
+ {
959
+ // Make sure the project has an accounting context for the token being paid.
960
+ if (_accountingContextForTokenOf[projectId][token].token == address(0)) {
961
+ revert JBMultiTerminal_TokenNotAccepted(token);
962
+ }
963
+
964
+ // If the terminal's token is the native token, override `amount` with `msg.value`.
965
+ if (token == JBConstants.NATIVE_TOKEN) return msg.value;
966
+
967
+ // If the terminal's token is not native, revert if there is a non-zero `msg.value`.
968
+ if (msg.value != 0) revert JBMultiTerminal_NoMsgValueAllowed(msg.value);
969
+
970
+ // Unpack the allowance to use, if any, given by the frontend.
971
+ (bool exists, bytes memory parsedMetadata) =
972
+ JBMetadataResolver.getDataFor({id: JBMetadataResolver.getId("permit2"), metadata: metadata});
973
+
974
+ // Check if the metadata contains permit data.
975
+ if (exists) {
976
+ // Keep a reference to the allowance context parsed from the metadata.
977
+ (JBSingleAllowance memory allowance) = abi.decode(parsedMetadata, (JBSingleAllowance));
978
+
979
+ // Make sure the permit allowance is enough for this payment. If not we revert early.
980
+ if (amount > allowance.amount) {
981
+ revert JBMultiTerminal_PermitAllowanceNotEnough(amount, allowance.amount);
982
+ }
983
+
984
+ // Keep a reference to the permit rules.
985
+ IAllowanceTransfer.PermitSingle memory permitSingle = IAllowanceTransfer.PermitSingle({
986
+ details: IAllowanceTransfer.PermitDetails({
987
+ token: token, amount: allowance.amount, expiration: allowance.expiration, nonce: allowance.nonce
988
+ }),
989
+ spender: address(this),
990
+ sigDeadline: allowance.sigDeadline
991
+ });
992
+
993
+ // Set the allowance to `spend` tokens for the user.
994
+ try PERMIT2.permit({owner: _msgSender(), permitSingle: permitSingle, signature: allowance.signature}) {}
995
+ catch (bytes memory reason) {
996
+ emit Permit2AllowanceFailed(token, _msgSender(), reason);
997
+ }
998
+ }
999
+
1000
+ // Get a reference to the balance before receiving tokens.
1001
+ uint256 balanceBefore = _balanceOf(token);
1002
+
1003
+ // Transfer tokens to this terminal from the msg sender.
1004
+ _transferFrom({from: _msgSender(), to: payable(address(this)), token: token, amount: amount});
1005
+
1006
+ // The amount should reflect the change in balance.
1007
+ return _balanceOf(token) - balanceBefore;
1008
+ }
1009
+
1010
+ /// @notice Adds funds to a project's balance without minting tokens.
1011
+ /// @param projectId The ID of the project to add funds to the balance of.
1012
+ /// @param token The address of the token being added to the project's balance.
1013
+ /// @param amount The amount of tokens to add as a fixed point number with the same number of decimals as this
1014
+ /// terminal. If this is a native token terminal, this is ignored and `msg.value` is used instead.
1015
+ /// @param shouldReturnHeldFees A flag indicating if held fees should be returned based on the amount being added.
1016
+ /// @param memo A memo to pass along to the emitted event.
1017
+ /// @param metadata Extra data to pass along to the emitted event.
1018
+ function _addToBalanceOf(
1019
+ uint256 projectId,
1020
+ address token,
1021
+ uint256 amount,
1022
+ bool shouldReturnHeldFees,
1023
+ string memory memo,
1024
+ bytes memory metadata
1025
+ )
1026
+ internal
1027
+ {
1028
+ // Return held fees if desired. This mechanism means projects don't pay fees multiple times when funds go out of
1029
+ // and back into the protocol.
1030
+ uint256 returnedFees =
1031
+ shouldReturnHeldFees ? _returnHeldFees({projectId: projectId, token: token, amount: amount}) : 0;
1032
+
1033
+ emit AddToBalance({
1034
+ projectId: projectId,
1035
+ amount: amount,
1036
+ returnedFees: returnedFees,
1037
+ memo: memo,
1038
+ metadata: metadata,
1039
+ caller: _msgSender()
1040
+ });
1041
+
1042
+ // Record the added funds with any returned fees.
1043
+ _recordAddedBalanceFor({projectId: projectId, token: token, amount: amount + returnedFees});
1044
+ }
1045
+
1046
+ /// @notice Logic to be triggered before transferring tokens from this terminal.
1047
+ /// @param to The address the transfer is going to.
1048
+ /// @param token The token being transferred.
1049
+ /// @param amount The number of tokens being transferred, as a fixed point number with the same number of decimals
1050
+ /// as this terminal.
1051
+ /// @return payValue The value to attach to the transaction being sent.
1052
+ function _beforeTransferTo(address to, address token, uint256 amount) internal returns (uint256) {
1053
+ // If the token is the native token, no allowance needed, and the full amount should be used as the payValue.
1054
+ if (token == JBConstants.NATIVE_TOKEN) return amount;
1055
+
1056
+ // Otherwise, set the allowance, and the payValue should be 0.
1057
+ IERC20(token).safeIncreaseAllowance({spender: to, value: amount});
1058
+ return 0;
1059
+ }
1060
+
1061
+ /// @notice Holders can cash out their tokens to reclaim some of a project's surplus, or to trigger rules determined
1062
+ /// by
1063
+ /// the project's current ruleset's data hook.
1064
+ /// @dev Only a token holder or an operator with the `CASH_OUT_TOKENS` permission from that holder can cash out
1065
+ /// those
1066
+ /// tokens.
1067
+ /// @param holder The account cashing out tokens.
1068
+ /// @param projectId The ID of the project whose tokens are being cashed out.
1069
+ /// @param cashOutCount The number of project tokens to cash out, as a fixed point number with 18 decimals.
1070
+ /// @param tokenToReclaim The address of the token which is being cashed out.
1071
+ /// @param beneficiary The address to send the reclaimed terminal tokens to.
1072
+ /// @param metadata Bytes to send along to the emitted event, as well as the data hook and cash out hook if
1073
+ /// applicable.
1074
+ /// @return reclaimAmount The number of terminal tokens reclaimed for the `beneficiary`, as a fixed point number
1075
+ /// with 18 decimals.
1076
+ function _cashOutTokensOf(
1077
+ address holder,
1078
+ uint256 projectId,
1079
+ uint256 cashOutCount,
1080
+ address tokenToReclaim,
1081
+ address payable beneficiary,
1082
+ bytes memory metadata
1083
+ )
1084
+ internal
1085
+ returns (uint256 reclaimAmount)
1086
+ {
1087
+ // Keep a reference to the ruleset the cash out is being made during.
1088
+ JBRuleset memory ruleset;
1089
+
1090
+ // Keep a reference to the cash out hook specifications.
1091
+ JBCashOutHookSpecification[] memory hookSpecifications;
1092
+
1093
+ // Keep a reference to the cash out tax rate being used.
1094
+ uint256 cashOutTaxRate;
1095
+
1096
+ // Keep a reference to the accounting context of the token being reclaimed.
1097
+ JBAccountingContext memory accountingContext = _accountingContextForTokenOf[projectId][tokenToReclaim];
1098
+
1099
+ // Scoped section prevents stack too deep.
1100
+ {
1101
+ JBAccountingContext[] memory balanceAccountingContexts = _accountingContextsOf[projectId];
1102
+
1103
+ // Record the cash out.
1104
+ (ruleset, reclaimAmount, cashOutTaxRate, hookSpecifications) = STORE.recordCashOutFor({
1105
+ holder: holder,
1106
+ projectId: projectId,
1107
+ accountingContext: accountingContext,
1108
+ balanceAccountingContexts: balanceAccountingContexts,
1109
+ cashOutCount: cashOutCount,
1110
+ metadata: metadata
1111
+ });
1112
+ }
1113
+
1114
+ // Burn the project tokens.
1115
+ if (cashOutCount != 0) {
1116
+ _controllerOf(projectId)
1117
+ .burnTokensOf({holder: holder, projectId: projectId, tokenCount: cashOutCount, memo: ""});
1118
+ }
1119
+
1120
+ // Keep a reference to the amount being reclaimed that is subject to fees.
1121
+ uint256 amountEligibleForFees;
1122
+
1123
+ // Send the reclaimed funds to the beneficiary.
1124
+ if (reclaimAmount != 0) {
1125
+ // Determine if a fee should be taken. Fees are not taked if the cash out tax rate is zero,
1126
+ // if the beneficiary is feeless, or if the fee beneficiary doesn't accept the given token.
1127
+ if (!_isFeeless(beneficiary) && cashOutTaxRate != 0) {
1128
+ amountEligibleForFees += reclaimAmount;
1129
+ // Subtract the fee for the reclaimed amount.
1130
+ reclaimAmount -= JBFees.feeAmountFrom({amountBeforeFee: reclaimAmount, feePercent: FEE});
1131
+ }
1132
+
1133
+ // Subtract the fee from the reclaim amount.
1134
+ if (reclaimAmount != 0) {
1135
+ _transferFrom({from: address(this), to: beneficiary, token: tokenToReclaim, amount: reclaimAmount});
1136
+ }
1137
+ }
1138
+
1139
+ // If the data hook returned cash out hook specifications, fulfill them.
1140
+ if (hookSpecifications.length != 0) {
1141
+ // Fulfill the cash out hook specifications.
1142
+ amountEligibleForFees += _fulfillCashOutHookSpecificationsFor({
1143
+ projectId: projectId,
1144
+ holder: holder,
1145
+ cashOutCount: cashOutCount,
1146
+ ruleset: ruleset,
1147
+ cashOutTaxRate: cashOutTaxRate,
1148
+ beneficiary: beneficiary,
1149
+ beneficiaryReclaimAmount: JBTokenAmount({
1150
+ token: tokenToReclaim,
1151
+ decimals: accountingContext.decimals,
1152
+ currency: accountingContext.currency,
1153
+ value: reclaimAmount
1154
+ }),
1155
+ specifications: hookSpecifications,
1156
+ metadata: metadata
1157
+ });
1158
+ }
1159
+
1160
+ // Take the fee from all outbound reclaimings.
1161
+ if (amountEligibleForFees != 0) {
1162
+ _takeFeeFrom({
1163
+ projectId: projectId,
1164
+ token: tokenToReclaim,
1165
+ amount: amountEligibleForFees,
1166
+ beneficiary: beneficiary,
1167
+ shouldHoldFees: false
1168
+ });
1169
+ }
1170
+
1171
+ emit CashOutTokens({
1172
+ rulesetId: ruleset.id,
1173
+ rulesetCycleNumber: ruleset.cycleNumber,
1174
+ projectId: projectId,
1175
+ holder: holder,
1176
+ beneficiary: beneficiary,
1177
+ cashOutCount: cashOutCount,
1178
+ cashOutTaxRate: cashOutTaxRate,
1179
+ reclaimAmount: reclaimAmount,
1180
+ metadata: metadata,
1181
+ caller: _msgSender()
1182
+ });
1183
+ }
1184
+
1185
+ /// @notice Fund a project either by calling this terminal's internal `addToBalance` function or by calling the
1186
+ /// recipient
1187
+ /// terminal's `addToBalance` function.
1188
+ /// @param terminal The terminal on which the project is expecting to receive funds.
1189
+ /// @param projectId The ID of the project being funded.
1190
+ /// @param token The token being used.
1191
+ /// @param amount The amount being funded, as a fixed point number with the amount of decimals that the terminal's
1192
+ /// accounting context specifies.
1193
+ /// @param metadata Additional metadata to include with the payment.
1194
+ function _efficientAddToBalance(
1195
+ IJBTerminal terminal,
1196
+ uint256 projectId,
1197
+ address token,
1198
+ uint256 amount,
1199
+ bytes memory metadata
1200
+ )
1201
+ internal
1202
+ {
1203
+ // Call the internal method if this terminal is being used.
1204
+ if (terminal == IJBTerminal(address(this))) {
1205
+ _addToBalanceOf({
1206
+ projectId: projectId,
1207
+ token: token,
1208
+ amount: amount,
1209
+ shouldReturnHeldFees: false,
1210
+ memo: "",
1211
+ metadata: metadata
1212
+ });
1213
+ } else {
1214
+ // Trigger any inherited pre-transfer logic.
1215
+ // Keep a reference to the amount that'll be paid as a `msg.value`.
1216
+ // slither-disable-next-line reentrancy-events
1217
+ uint256 payValue = _beforeTransferTo({to: address(terminal), token: token, amount: amount});
1218
+
1219
+ // Add to balance.
1220
+ // If this terminal's token is the native token, send it in `msg.value`.
1221
+ terminal.addToBalanceOf{value: payValue}({
1222
+ projectId: projectId,
1223
+ token: token,
1224
+ amount: amount,
1225
+ shouldReturnHeldFees: false,
1226
+ memo: "",
1227
+ metadata: metadata
1228
+ });
1229
+ }
1230
+ }
1231
+
1232
+ /// @notice Pay a project either by calling this terminal's internal `pay` function or by calling the recipient
1233
+ /// terminal's `pay` function.
1234
+ /// @param terminal The terminal on which the project is expecting to receive payments.
1235
+ /// @param projectId The ID of the project being paid.
1236
+ /// @param token The token being paid in.
1237
+ /// @param amount The amount being paid, as a fixed point number with the amount of decimals that the terminal's
1238
+ /// accounting context specifies.
1239
+ /// @param beneficiary The address to receive any platform tokens minted.
1240
+ /// @param metadata Additional metadata to include with the payment.
1241
+ function _efficientPay(
1242
+ IJBTerminal terminal,
1243
+ uint256 projectId,
1244
+ address token,
1245
+ uint256 amount,
1246
+ address beneficiary,
1247
+ bytes memory metadata
1248
+ )
1249
+ internal
1250
+ {
1251
+ if (terminal == IJBTerminal(address(this))) {
1252
+ _pay({
1253
+ projectId: projectId,
1254
+ token: token,
1255
+ amount: amount,
1256
+ payer: address(this),
1257
+ beneficiary: beneficiary,
1258
+ memo: "",
1259
+ metadata: metadata
1260
+ });
1261
+ } else {
1262
+ // Trigger any inherited pre-transfer logic.
1263
+ // Keep a reference to the amount that'll be paid as a `msg.value`.
1264
+ // slither-disable-next-line reentrancy-events
1265
+ uint256 payValue = _beforeTransferTo({to: address(terminal), token: token, amount: amount});
1266
+
1267
+ // Send the fee.
1268
+ // If this terminal's token is ETH, send it in msg.value.
1269
+ // slither-disable-next-line unused-return
1270
+ terminal.pay{value: payValue}({
1271
+ projectId: projectId,
1272
+ token: token,
1273
+ amount: amount,
1274
+ beneficiary: beneficiary,
1275
+ minReturnedTokens: 0,
1276
+ memo: "",
1277
+ metadata: metadata
1278
+ });
1279
+ }
1280
+ }
1281
+
1282
+ /// @notice Fulfills a list of pay hook specifications.
1283
+ /// @param projectId The ID of the project being paid.
1284
+ /// @param specifications The pay hook specifications to be fulfilled.
1285
+ /// @param tokenAmount The amount of tokens that the project was paid.
1286
+ /// @param payer The address that sent the payment.
1287
+ /// @param ruleset The ruleset the payment is being accepted during.
1288
+ /// @param beneficiary The address which will receive any tokens that the payment yields.
1289
+ /// @param newlyIssuedTokenCount The amount of tokens that are being issued and sent to the beneificary.
1290
+ /// @param metadata Bytes to send along to the emitted event and pay hooks as applicable.
1291
+ function _fulfillPayHookSpecificationsFor(
1292
+ uint256 projectId,
1293
+ JBPayHookSpecification[] memory specifications,
1294
+ JBTokenAmount memory tokenAmount,
1295
+ address payer,
1296
+ JBRuleset memory ruleset,
1297
+ address beneficiary,
1298
+ uint256 newlyIssuedTokenCount,
1299
+ bytes memory metadata
1300
+ )
1301
+ internal
1302
+ {
1303
+ // Keep a reference to payment context for the pay hooks.
1304
+ JBAfterPayRecordedContext memory context = JBAfterPayRecordedContext({
1305
+ payer: payer,
1306
+ projectId: projectId,
1307
+ rulesetId: ruleset.id,
1308
+ amount: tokenAmount,
1309
+ forwardedAmount: tokenAmount,
1310
+ weight: ruleset.weight,
1311
+ newlyIssuedTokenCount: newlyIssuedTokenCount,
1312
+ beneficiary: beneficiary,
1313
+ hookMetadata: bytes(""),
1314
+ payerMetadata: metadata
1315
+ });
1316
+
1317
+ // Fulfill each specification through their pay hooks.
1318
+ for (uint256 i; i < specifications.length; i++) {
1319
+ // Set the specification being iterated on.
1320
+ JBPayHookSpecification memory specification = specifications[i];
1321
+
1322
+ // Pass the correct token `forwardedAmount` to the hook.
1323
+ context.forwardedAmount = JBTokenAmount({
1324
+ value: specification.amount,
1325
+ token: tokenAmount.token,
1326
+ decimals: tokenAmount.decimals,
1327
+ currency: tokenAmount.currency
1328
+ });
1329
+
1330
+ // Pass the correct metadata from the data hook's specification.
1331
+ context.hookMetadata = specification.metadata;
1332
+
1333
+ // Trigger any inherited pre-transfer logic.
1334
+ // Keep a reference to the amount that'll be paid as a `msg.value`.
1335
+ // slither-disable-next-line reentrancy-events
1336
+ uint256 payValue = _beforeTransferTo({
1337
+ to: address(specification.hook), token: tokenAmount.token, amount: specification.amount
1338
+ });
1339
+
1340
+ // Fulfill the specification.
1341
+ // slither-disable-next-line reentrancy-events
1342
+ specification.hook.afterPayRecordedWith{value: payValue}(context);
1343
+
1344
+ emit HookAfterRecordPay({
1345
+ hook: specification.hook,
1346
+ context: context,
1347
+ specificationAmount: specification.amount,
1348
+ caller: _msgSender()
1349
+ });
1350
+ }
1351
+ }
1352
+
1353
+ /// @notice Fulfills a list of cash out hook specifications.
1354
+ /// @param projectId The ID of the project being cashed out from.
1355
+ /// @param beneficiaryReclaimAmount The number of tokens that are being cashed out from the project.
1356
+ /// @param holder The address that holds the tokens being cashed out.
1357
+ /// @param cashOutCount The number of tokens being cashed out.
1358
+ /// @param metadata Bytes to send along to the emitted event and cash out hooks as applicable.
1359
+ /// @param ruleset The ruleset the cash out is being made during as a `JBRuleset` struct.
1360
+ /// @param cashOutTaxRate The cash out tax rate influencing the reclaim amount.
1361
+ /// @param beneficiary The address which will receive any terminal tokens that are cashed out.
1362
+ /// @param specifications The hook specifications being fulfilled.
1363
+ /// @return amountEligibleForFees The amount of funds which were allocated to cash out hooks and are eligible for
1364
+ /// fees.
1365
+ function _fulfillCashOutHookSpecificationsFor(
1366
+ uint256 projectId,
1367
+ JBTokenAmount memory beneficiaryReclaimAmount,
1368
+ address holder,
1369
+ uint256 cashOutCount,
1370
+ bytes memory metadata,
1371
+ JBRuleset memory ruleset,
1372
+ uint256 cashOutTaxRate,
1373
+ address payable beneficiary,
1374
+ JBCashOutHookSpecification[] memory specifications
1375
+ )
1376
+ internal
1377
+ returns (uint256 amountEligibleForFees)
1378
+ {
1379
+ // Keep a reference to cash out context for the cash out hooks.
1380
+ JBAfterCashOutRecordedContext memory context = JBAfterCashOutRecordedContext({
1381
+ holder: holder,
1382
+ projectId: projectId,
1383
+ rulesetId: ruleset.id,
1384
+ cashOutCount: cashOutCount,
1385
+ reclaimedAmount: beneficiaryReclaimAmount,
1386
+ forwardedAmount: beneficiaryReclaimAmount,
1387
+ cashOutTaxRate: cashOutTaxRate,
1388
+ beneficiary: beneficiary,
1389
+ hookMetadata: "",
1390
+ cashOutMetadata: metadata
1391
+ });
1392
+
1393
+ for (uint256 i; i < specifications.length; i++) {
1394
+ // Set the specification being iterated on.
1395
+ JBCashOutHookSpecification memory specification = specifications[i];
1396
+
1397
+ // Get the fee for the specified amount.
1398
+ uint256 specificationAmountFee = _isFeeless(address(specification.hook))
1399
+ ? 0
1400
+ : JBFees.feeAmountFrom({amountBeforeFee: specification.amount, feePercent: FEE});
1401
+
1402
+ // Add the specification's amount to the amount eligible for fees.
1403
+ if (specificationAmountFee != 0) {
1404
+ amountEligibleForFees += specification.amount;
1405
+ specification.amount -= specificationAmountFee;
1406
+ }
1407
+
1408
+ // Pass the correct token `forwardedAmount` to the hook.
1409
+ context.forwardedAmount = JBTokenAmount({
1410
+ value: specification.amount,
1411
+ token: beneficiaryReclaimAmount.token,
1412
+ decimals: beneficiaryReclaimAmount.decimals,
1413
+ currency: beneficiaryReclaimAmount.currency
1414
+ });
1415
+
1416
+ // Pass the correct metadata from the data hook's specification.
1417
+ context.hookMetadata = specification.metadata;
1418
+
1419
+ // Trigger any inherited pre-transfer logic.
1420
+ // Keep a reference to the amount that'll be paid as a `msg.value`.
1421
+ // slither-disable-next-line reentrancy-events
1422
+ uint256 payValue = _beforeTransferTo({
1423
+ to: address(specification.hook), token: beneficiaryReclaimAmount.token, amount: specification.amount
1424
+ });
1425
+
1426
+ // Fulfill the specification.
1427
+ // slither-disable-next-line reentrancy-events
1428
+ specification.hook.afterCashOutRecordedWith{value: payValue}(context);
1429
+
1430
+ emit HookAfterRecordCashOut({
1431
+ hook: specification.hook,
1432
+ context: context,
1433
+ specificationAmount: specification.amount,
1434
+ fee: specificationAmountFee,
1435
+ caller: _msgSender()
1436
+ });
1437
+ }
1438
+ }
1439
+
1440
+ /// @notice Pay a project with tokens.
1441
+ /// @param projectId The ID of the project being paid.
1442
+ /// @param token The address of the token which the project is being paid with.
1443
+ /// @param amount The amount of terminal tokens being received, as a fixed point number with the same number of
1444
+ /// decimals as this terminal. If this terminal's token is the native token, `amount` is ignored and `msg.value` is
1445
+ /// used in its place.
1446
+ /// @param payer The address making the payment.
1447
+ /// @param beneficiary The address to mint tokens to, and pass along to the ruleset's data hook and pay hook if
1448
+ /// applicable.
1449
+ /// @param memo A memo to pass along to the emitted event.
1450
+ /// @param metadata Bytes to send along to the emitted event, as well as the data hook and pay hook if applicable.
1451
+ function _pay(
1452
+ uint256 projectId,
1453
+ address token,
1454
+ uint256 amount,
1455
+ address payer,
1456
+ address beneficiary,
1457
+ string memory memo,
1458
+ bytes memory metadata
1459
+ )
1460
+ internal
1461
+ {
1462
+ // Keep a reference to the token amount to forward to the store.
1463
+ JBTokenAmount memory tokenAmount;
1464
+
1465
+ // Scoped section prevents stack too deep. `context` only used within scope.
1466
+ {
1467
+ // Get a reference to the token's accounting context.
1468
+ JBAccountingContext memory context = _accountingContextForTokenOf[projectId][token];
1469
+
1470
+ // Bundle the amount info into a `JBTokenAmount` struct.
1471
+ tokenAmount =
1472
+ JBTokenAmount({token: token, decimals: context.decimals, currency: context.currency, value: amount});
1473
+ }
1474
+
1475
+ // Record the payment.
1476
+ // Keep a reference to the ruleset the payment is being made during.
1477
+ // Keep a reference to the pay hook specifications.
1478
+ // Keep a reference to the token count that'll be minted as a result of the payment.
1479
+ // slither-disable-next-line reentrancy-events
1480
+ (JBRuleset memory ruleset, uint256 tokenCount, JBPayHookSpecification[] memory hookSpecifications) = STORE.recordPaymentFrom({
1481
+ payer: payer, amount: tokenAmount, projectId: projectId, beneficiary: beneficiary, metadata: metadata
1482
+ });
1483
+
1484
+ // Keep a reference to the number of tokens issued for the beneficiary.
1485
+ uint256 newlyIssuedTokenCount;
1486
+
1487
+ // Mint tokens if needed.
1488
+ if (tokenCount != 0) {
1489
+ // Set the token count to be the number of tokens minted for the beneficiary instead of the total
1490
+ // amount.
1491
+ // slither-disable-next-line reentrancy-events
1492
+ newlyIssuedTokenCount = _controllerOf(projectId)
1493
+ .mintTokensOf({
1494
+ projectId: projectId,
1495
+ tokenCount: tokenCount,
1496
+ beneficiary: beneficiary,
1497
+ memo: "",
1498
+ useReservedPercent: true
1499
+ });
1500
+ }
1501
+
1502
+ emit Pay({
1503
+ rulesetId: ruleset.id,
1504
+ rulesetCycleNumber: ruleset.cycleNumber,
1505
+ projectId: projectId,
1506
+ payer: payer,
1507
+ beneficiary: beneficiary,
1508
+ amount: amount,
1509
+ newlyIssuedTokenCount: newlyIssuedTokenCount,
1510
+ memo: memo,
1511
+ metadata: metadata,
1512
+ caller: _msgSender()
1513
+ });
1514
+
1515
+ // If the data hook returned pay hook specifications, fulfill them.
1516
+ if (hookSpecifications.length != 0) {
1517
+ _fulfillPayHookSpecificationsFor({
1518
+ projectId: projectId,
1519
+ specifications: hookSpecifications,
1520
+ tokenAmount: tokenAmount,
1521
+ payer: payer,
1522
+ ruleset: ruleset,
1523
+ beneficiary: beneficiary,
1524
+ newlyIssuedTokenCount: newlyIssuedTokenCount,
1525
+ metadata: metadata
1526
+ });
1527
+ }
1528
+ }
1529
+
1530
+ /// @notice Process a fee of the specified amount from a project.
1531
+ /// @param projectId The ID of the project paying the fee.
1532
+ /// @param token The token the fee is being paid in.
1533
+ /// @param amount The fee amount, as a fixed point number with 18 decimals.
1534
+ /// @param beneficiary The address which will receive any platform tokens minted.
1535
+ /// @param feeTerminal The terminal that'll receive the fee.
1536
+ /// @param wasHeld A flag indicating if the fee being processed was being held by this terminal.
1537
+ function _processFee(
1538
+ uint256 projectId,
1539
+ address token,
1540
+ uint256 amount,
1541
+ address beneficiary,
1542
+ IJBTerminal feeTerminal,
1543
+ bool wasHeld
1544
+ )
1545
+ internal
1546
+ {
1547
+ // slither-disable-next-line reentrancy-events,calls-loop
1548
+ try this.executeProcessFee({
1549
+ projectId: projectId, token: token, amount: amount, beneficiary: beneficiary, feeTerminal: feeTerminal
1550
+ }) {
1551
+ emit ProcessFee({
1552
+ projectId: projectId,
1553
+ token: token,
1554
+ amount: amount,
1555
+ wasHeld: wasHeld,
1556
+ beneficiary: beneficiary,
1557
+ caller: _msgSender()
1558
+ });
1559
+ } catch (bytes memory reason) {
1560
+ emit FeeReverted({
1561
+ projectId: projectId,
1562
+ token: token,
1563
+ feeProjectId: _FEE_BENEFICIARY_PROJECT_ID,
1564
+ amount: amount,
1565
+ reason: reason,
1566
+ caller: _msgSender()
1567
+ });
1568
+
1569
+ _recordAddedBalanceFor({projectId: projectId, token: token, amount: amount});
1570
+ }
1571
+ }
1572
+
1573
+ /// @notice Records an added balance for a project.
1574
+ /// @param projectId The ID of the project to record the added balance for.
1575
+ /// @param token The token to record the added balance for.
1576
+ /// @param amount The amount of the token to record, as a fixed point number with the same number of decimals as
1577
+ /// this
1578
+ /// terminal.
1579
+ function _recordAddedBalanceFor(uint256 projectId, address token, uint256 amount) internal {
1580
+ // slither-disable-next-line calls-loop
1581
+ STORE.recordAddedBalanceFor({projectId: projectId, token: token, amount: amount});
1582
+ }
1583
+
1584
+ /// @notice Returns held fees to the project who paid them based on the specified amount.
1585
+ /// @param projectId The project held fees are being returned to.
1586
+ /// @param token The token that the held fees are in.
1587
+ /// @param amount The amount to base the calculation on, as a fixed point number with the same number of decimals
1588
+ /// as this terminal.
1589
+ /// @return returnedFees The amount of held fees that were returned, as a fixed point number with the same number of
1590
+ /// decimals as this terminal
1591
+ function _returnHeldFees(uint256 projectId, address token, uint256 amount) internal returns (uint256 returnedFees) {
1592
+ // Keep a reference to the start index.
1593
+ uint256 startIndex = _nextHeldFeeIndexOf[projectId][token];
1594
+
1595
+ // Get a reference to the project's held fees.
1596
+ uint256 numberOfHeldFees = _heldFeesOf[projectId][token].length;
1597
+
1598
+ // If the start index is greater than or equal to the number of held fees, return 0.
1599
+ if (startIndex >= numberOfHeldFees) return 0;
1600
+
1601
+ // Get a reference to the leftover amount once all fees have been settled.
1602
+ uint256 leftoverAmount = amount;
1603
+
1604
+ // Keep a reference to the number of iterations to perform.
1605
+ uint256 count = numberOfHeldFees - startIndex;
1606
+
1607
+ // Keep a reference to the new start index.
1608
+ uint256 newStartIndex = startIndex;
1609
+
1610
+ // Process each fee.
1611
+ for (uint256 i; i < count; i++) {
1612
+ // Save the fee being iterated on.
1613
+ JBFee memory heldFee = _heldFeesOf[projectId][token][startIndex + i];
1614
+
1615
+ // slither-disable-next-line incorrect-equality
1616
+ if (leftoverAmount == 0) {
1617
+ break;
1618
+ } else {
1619
+ // Notice here we take `feeAmountFrom` on the stored `.amount`.
1620
+ uint256 feeAmount = JBFees.feeAmountFrom({amountBeforeFee: heldFee.amount, feePercent: FEE});
1621
+
1622
+ // Keep a reference to the amount from which the fee was taken.
1623
+ uint256 amountPaidOut = heldFee.amount - feeAmount;
1624
+
1625
+ if (leftoverAmount >= amountPaidOut) {
1626
+ unchecked {
1627
+ leftoverAmount -= amountPaidOut;
1628
+ returnedFees += feeAmount;
1629
+ }
1630
+
1631
+ // Move the start index forward to the held fee after the current one.
1632
+ newStartIndex = startIndex + i + 1;
1633
+ } else {
1634
+ // And here we overwrite with `feeAmountResultingIn` the `leftoverAmount`
1635
+ feeAmount = JBFees.feeAmountResultingIn({amountAfterFee: leftoverAmount, feePercent: FEE});
1636
+
1637
+ // Get fee from `leftoverAmount`.
1638
+ unchecked {
1639
+ _heldFeesOf[projectId][token][startIndex + i].amount -= (leftoverAmount + feeAmount);
1640
+ returnedFees += feeAmount;
1641
+ }
1642
+ leftoverAmount = 0;
1643
+ }
1644
+ }
1645
+ }
1646
+
1647
+ // Update the next held fee index.
1648
+ if (startIndex != newStartIndex) _nextHeldFeeIndexOf[projectId][token] = newStartIndex;
1649
+
1650
+ emit ReturnHeldFees({
1651
+ projectId: projectId,
1652
+ token: token,
1653
+ amount: amount,
1654
+ returnedFees: returnedFees,
1655
+ leftoverAmount: leftoverAmount,
1656
+ caller: _msgSender()
1657
+ });
1658
+ }
1659
+
1660
+ /// @notice Sends payouts to a project's current payout split group, according to its ruleset, up to its current
1661
+ /// payout limit.
1662
+ /// @dev If the percentages of the splits in the project's payout split group do not add up to 100%, the remainder
1663
+ /// is sent to the project's owner.
1664
+ /// @dev Anyone can send payouts on a project's behalf. Projects can include a wildcard split (a split with no
1665
+ /// `hook`, `projectId`, or `beneficiary`) to send funds to the `_msgSender()` which calls this function. This can
1666
+ /// be used to incentivize calling this function.
1667
+ /// @dev Payouts sent to addresses which aren't feeless incur the protocol fee.
1668
+ /// @param projectId The ID of the project to send the payouts of.
1669
+ /// @param token The token being paid out.
1670
+ /// @param amount The number of terminal tokens to pay out, as a fixed point number with same number of decimals as
1671
+ /// this terminal.
1672
+ /// @param currency The expected currency of the amount being paid out. Must match the currency of one of the
1673
+ /// project's current ruleset's payout limits.
1674
+ /// @return amountPaidOut The total amount that was paid out.
1675
+ function _sendPayoutsOf(
1676
+ uint256 projectId,
1677
+ address token,
1678
+ uint256 amount,
1679
+ uint256 currency
1680
+ )
1681
+ internal
1682
+ returns (uint256 amountPaidOut)
1683
+ {
1684
+ // Keep a reference to the ruleset.
1685
+ JBRuleset memory ruleset;
1686
+
1687
+ // Record the payout.
1688
+ (ruleset, amountPaidOut) = STORE.recordPayoutFor({
1689
+ projectId: projectId,
1690
+ accountingContext: _accountingContextForTokenOf[projectId][token],
1691
+ amount: amount,
1692
+ currency: currency
1693
+ });
1694
+
1695
+ // Get a reference to the project's owner.
1696
+ // The owner will receive tokens minted by paying the platform fee and receive any leftover funds not sent to
1697
+ // payout splits.
1698
+ address payable projectOwner = payable(_ownerOf(projectId));
1699
+
1700
+ // If the ruleset requires privileged payout distribution, ensure the caller has the permission.
1701
+ if (ruleset.ownerMustSendPayouts()) {
1702
+ // Enforce permissions.
1703
+ _requirePermissionFrom({
1704
+ account: projectOwner, projectId: projectId, permissionId: JBPermissionIds.SEND_PAYOUTS
1705
+ });
1706
+ }
1707
+
1708
+ // Send payouts to the splits and get a reference to the amount left over after the splits have been paid.
1709
+ // Also get a reference to the amount which was paid out to splits that is eligible for fees.
1710
+ (uint256 leftoverPayoutAmount, uint256 amountEligibleForFees) = _sendPayoutsToSplitGroupOf({
1711
+ projectId: projectId, token: token, rulesetId: ruleset.id, amount: amountPaidOut
1712
+ });
1713
+
1714
+ // Send any leftover funds to the project owner and update the fee tracking accordingly.
1715
+ if (leftoverPayoutAmount != 0) {
1716
+ // Keep a reference to the fee for the leftover payout amount.
1717
+ uint256 fee = _isFeeless(projectOwner)
1718
+ ? 0
1719
+ : JBFees.feeAmountFrom({amountBeforeFee: leftoverPayoutAmount, feePercent: FEE});
1720
+
1721
+ // Transfer the amount to the project owner.
1722
+ try this.executeTransferTo({addr: projectOwner, token: token, amount: leftoverPayoutAmount - fee}) {
1723
+ if (fee > 0) {
1724
+ amountEligibleForFees += leftoverPayoutAmount;
1725
+ leftoverPayoutAmount -= fee;
1726
+ }
1727
+ } catch (bytes memory reason) {
1728
+ emit PayoutTransferReverted({
1729
+ projectId: projectId,
1730
+ addr: projectOwner,
1731
+ token: token,
1732
+ amount: leftoverPayoutAmount - fee,
1733
+ fee: fee,
1734
+ reason: reason,
1735
+ caller: _msgSender()
1736
+ });
1737
+
1738
+ // Add balance back to the project.
1739
+ _recordAddedBalanceFor({projectId: projectId, token: token, amount: leftoverPayoutAmount});
1740
+ }
1741
+ }
1742
+
1743
+ // Take the fee.
1744
+ uint256 feeTaken = _takeFeeFrom({
1745
+ projectId: projectId,
1746
+ token: token,
1747
+ amount: amountEligibleForFees,
1748
+ beneficiary: projectOwner,
1749
+ shouldHoldFees: ruleset.holdFees()
1750
+ });
1751
+
1752
+ emit SendPayouts({
1753
+ rulesetId: ruleset.id,
1754
+ rulesetCycleNumber: ruleset.cycleNumber,
1755
+ projectId: projectId,
1756
+ projectOwner: projectOwner,
1757
+ amount: amount,
1758
+ amountPaidOut: amountPaidOut,
1759
+ fee: feeTaken,
1760
+ netLeftoverPayoutAmount: leftoverPayoutAmount,
1761
+ caller: _msgSender()
1762
+ });
1763
+ }
1764
+
1765
+ /// @notice Sends a payout to a split.
1766
+ /// @param split The split to pay.
1767
+ /// @param projectId The ID of the project the split was specified by.
1768
+ /// @param token The address of the token being paid out.
1769
+ /// @param amount The total amount that the split is being paid, as a fixed point number with the same number of
1770
+ /// decimals as this terminal.
1771
+ /// @return netPayoutAmount The amount sent to the split after subtracting fees.
1772
+ function _sendPayoutToSplit(
1773
+ JBSplit memory split,
1774
+ uint256 projectId,
1775
+ address token,
1776
+ uint256 amount
1777
+ )
1778
+ internal
1779
+ returns (uint256)
1780
+ {
1781
+ // Attempt to distribute this split.
1782
+ // slither-disable-next-line reentrancy-events
1783
+ try this.executePayout({
1784
+ split: split, projectId: projectId, token: token, amount: amount, originalMessageSender: _msgSender()
1785
+ }) returns (
1786
+ uint256 netPayoutAmount
1787
+ ) {
1788
+ return netPayoutAmount;
1789
+ } catch (bytes memory failureReason) {
1790
+ emit PayoutReverted({
1791
+ projectId: projectId, split: split, amount: amount, reason: failureReason, caller: _msgSender()
1792
+ });
1793
+
1794
+ // Add balance back to the project.
1795
+ _recordAddedBalanceFor({projectId: projectId, token: token, amount: amount});
1796
+
1797
+ // Since the payout failed the netPayoutAmount is zero.
1798
+ return 0;
1799
+ }
1800
+ }
1801
+
1802
+ /// @notice Sends payouts to the payout splits group specified in a project's ruleset.
1803
+ /// @param projectId The ID of the project to send the payouts of.
1804
+ /// @param token The address of the token being paid out.
1805
+ /// @param rulesetId The ID of the ruleset of the split group being paid.
1806
+ /// @param amount The total amount being paid out, as a fixed point number with the same number of decimals as this
1807
+ /// terminal.
1808
+ /// @return amount The leftover amount (zero if the splits add up to 100%).
1809
+ /// @return amountEligibleForFees The total amount of funds which were paid out and are eligible for fees.
1810
+ function _sendPayoutsToSplitGroupOf(
1811
+ uint256 projectId,
1812
+ address token,
1813
+ uint256 rulesetId,
1814
+ uint256 amount
1815
+ )
1816
+ internal
1817
+ returns (uint256, uint256 amountEligibleForFees)
1818
+ {
1819
+ // The total percentage available to split
1820
+ uint256 leftoverPercentage = JBConstants.SPLITS_TOTAL_PERCENT;
1821
+
1822
+ // Get a reference to the project's payout splits.
1823
+ JBSplit[] memory splits =
1824
+ SPLITS.splitsOf({projectId: projectId, rulesetId: rulesetId, groupId: uint256(uint160(token))});
1825
+
1826
+ // Transfer between all splits.
1827
+ for (uint256 i; i < splits.length; i++) {
1828
+ // Get a reference to the split being iterated on.
1829
+ JBSplit memory split = splits[i];
1830
+
1831
+ // The amount to send to the split.
1832
+ uint256 payoutAmount = mulDiv(amount, split.percent, leftoverPercentage);
1833
+
1834
+ // The final payout amount after taking out any fees.
1835
+ uint256 netPayoutAmount =
1836
+ _sendPayoutToSplit({split: split, projectId: projectId, token: token, amount: payoutAmount});
1837
+
1838
+ // If the split hook is a feeless address, this payout doesn't incur a fee.
1839
+ if (netPayoutAmount != 0 && netPayoutAmount != payoutAmount) {
1840
+ amountEligibleForFees += payoutAmount;
1841
+ }
1842
+
1843
+ if (payoutAmount != 0) {
1844
+ // Subtract from the amount to be sent to the beneficiary.
1845
+ unchecked {
1846
+ amount -= payoutAmount;
1847
+ }
1848
+ }
1849
+
1850
+ unchecked {
1851
+ // Decrement the leftover percentage.
1852
+ leftoverPercentage -= split.percent;
1853
+ }
1854
+
1855
+ emit SendPayoutToSplit({
1856
+ projectId: projectId,
1857
+ rulesetId: rulesetId,
1858
+ group: uint256(uint160(token)),
1859
+ split: split,
1860
+ amount: payoutAmount,
1861
+ netAmount: netPayoutAmount,
1862
+ caller: _msgSender()
1863
+ });
1864
+ }
1865
+
1866
+ return (amount, amountEligibleForFees);
1867
+ }
1868
+
1869
+ /// @notice Takes a fee into the platform's project (with the `_FEE_BENEFICIARY_PROJECT_ID`).
1870
+ /// @param projectId The ID of the project paying the fee.
1871
+ /// @param token The address of the token that the fee is being paid in.
1872
+ /// @param amount The fee's token amount, as a fixed point number with 18 decimals.
1873
+ /// @param beneficiary The address to mint the platform's project's tokens for.
1874
+ /// @param shouldHoldFees If fees should be tracked and held instead of being exercised immediately.
1875
+ /// @return feeAmount The amount of the fee taken.
1876
+ function _takeFeeFrom(
1877
+ uint256 projectId,
1878
+ address token,
1879
+ uint256 amount,
1880
+ address beneficiary,
1881
+ bool shouldHoldFees
1882
+ )
1883
+ internal
1884
+ returns (uint256 feeAmount)
1885
+ {
1886
+ // Get a reference to the fee amount.
1887
+ feeAmount = JBFees.feeAmountFrom({amountBeforeFee: amount, feePercent: FEE});
1888
+
1889
+ if (shouldHoldFees) {
1890
+ // Store the held fee.
1891
+ _heldFeesOf[projectId][token].push(
1892
+ JBFee({
1893
+ amount: amount,
1894
+ beneficiary: beneficiary,
1895
+ unlockTimestamp: uint48(block.timestamp + _FEE_HOLDING_SECONDS)
1896
+ })
1897
+ );
1898
+
1899
+ emit HoldFee({
1900
+ projectId: projectId,
1901
+ token: token,
1902
+ amount: amount,
1903
+ fee: FEE,
1904
+ beneficiary: beneficiary,
1905
+ caller: _msgSender()
1906
+ });
1907
+ } else {
1908
+ // Get the terminal that'll receive the fee if one wasn't provided.
1909
+ IJBTerminal feeTerminal = _primaryTerminalOf({projectId: _FEE_BENEFICIARY_PROJECT_ID, token: token});
1910
+
1911
+ // Process the fee.
1912
+ _processFee({
1913
+ projectId: projectId,
1914
+ token: token,
1915
+ amount: feeAmount,
1916
+ beneficiary: beneficiary,
1917
+ feeTerminal: feeTerminal,
1918
+ wasHeld: false
1919
+ });
1920
+ }
1921
+ }
1922
+
1923
+ /// @notice Transfers tokens.
1924
+ /// @param from The address the transfer should originate from.
1925
+ /// @param to The address the transfer should go to.
1926
+ /// @param token The token being transfered.
1927
+ /// @param amount The number of tokens being transferred, as a fixed point number with the same number of decimals
1928
+ /// as this terminal.
1929
+ function _transferFrom(address from, address payable to, address token, uint256 amount) internal {
1930
+ if (from == address(this)) {
1931
+ // If the token is the native token, transfer natively.
1932
+ if (token == JBConstants.NATIVE_TOKEN) return Address.sendValue({recipient: to, amount: amount});
1933
+
1934
+ return IERC20(token).safeTransfer({to: to, value: amount});
1935
+ }
1936
+
1937
+ // If there's sufficient approval, transfer normally.
1938
+ if (IERC20(token).allowance(address(from), address(this)) >= amount) {
1939
+ return IERC20(token).safeTransferFrom({from: from, to: to, value: amount});
1940
+ }
1941
+
1942
+ // Make sure the amount being paid is less than the maximum permit2 allowance.
1943
+ if (amount > type(uint160).max) revert JBMultiTerminal_OverflowAlert(amount, type(uint160).max);
1944
+
1945
+ // Otherwise we attempt to use the PERMIT2 method.
1946
+ PERMIT2.transferFrom({from: from, to: to, amount: uint160(amount), token: token});
1947
+ }
1948
+
1949
+ /// @notice Allows a project to send out funds from its surplus up to the current surplus allowance.
1950
+ /// @dev Only a project's owner or an operator with the `USE_ALLOWANCE` permission from that owner can use the
1951
+ /// surplus allowance.
1952
+ /// @dev Incurs the protocol fee unless the caller is a feeless address.
1953
+ /// @param projectId The ID of the project to use the surplus allowance of.
1954
+ /// @param owner The project's owner.
1955
+ /// @param token The token being paid out from the surplus.
1956
+ /// @param amount The amount of terminal tokens to use from the project's current surplus allowance, as a fixed
1957
+ /// point number with the same amount of decimals as this terminal.
1958
+ /// @param currency The expected currency of the amount being paid out. Must match the currency of one of the
1959
+ /// project's current ruleset's surplus allowances.
1960
+ /// @param beneficiary The address to send the funds to.
1961
+ /// @param feeBeneficiary The address to send the tokens resulting from paying the fee.
1962
+ /// @param memo A memo to pass along to the emitted event.
1963
+ /// @return netAmountPaidOut The amount of tokens paid out.
1964
+ function _useAllowanceOf(
1965
+ uint256 projectId,
1966
+ address owner,
1967
+ address token,
1968
+ uint256 amount,
1969
+ uint256 currency,
1970
+ address payable beneficiary,
1971
+ address payable feeBeneficiary,
1972
+ string memory memo
1973
+ )
1974
+ internal
1975
+ returns (uint256 netAmountPaidOut)
1976
+ {
1977
+ // Keep a reference to the ruleset.
1978
+ JBRuleset memory ruleset;
1979
+
1980
+ // Keep a reference to the amount paid out before fees.
1981
+ uint256 amountPaidOut;
1982
+
1983
+ // Record the use of the allowance.
1984
+ (ruleset, amountPaidOut) = STORE.recordUsedAllowanceOf({
1985
+ projectId: projectId,
1986
+ accountingContext: _accountingContextForTokenOf[projectId][token],
1987
+ amount: amount,
1988
+ currency: currency
1989
+ });
1990
+
1991
+ // Take a fee from the `amountPaidOut`, if needed.
1992
+ // The net amount is the final amount withdrawn after the fee has been taken.
1993
+ // slither-disable-next-line reentrancy-events
1994
+ netAmountPaidOut = amountPaidOut
1995
+ - (_isFeeless(owner) || _isFeeless(beneficiary)
1996
+ ? 0
1997
+ : _takeFeeFrom({
1998
+ projectId: projectId,
1999
+ token: token,
2000
+ amount: amountPaidOut,
2001
+ // The project owner will receive tokens minted by paying the platform fee.
2002
+ beneficiary: feeBeneficiary,
2003
+ shouldHoldFees: ruleset.holdFees()
2004
+ }));
2005
+
2006
+ emit UseAllowance({
2007
+ rulesetId: ruleset.id,
2008
+ rulesetCycleNumber: ruleset.cycleNumber,
2009
+ projectId: projectId,
2010
+ beneficiary: beneficiary,
2011
+ feeBeneficiary: feeBeneficiary,
2012
+ amount: amount,
2013
+ amountPaidOut: amountPaidOut,
2014
+ netAmountPaidOut: netAmountPaidOut,
2015
+ memo: memo,
2016
+ caller: _msgSender()
2017
+ });
2018
+
2019
+ // Transfer any remaining balance to the beneficiary.
2020
+ if (netAmountPaidOut != 0) {
2021
+ _transferFrom({from: address(this), to: beneficiary, token: token, amount: netAmountPaidOut});
2022
+ }
2023
+ }
2024
+ }