@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,2644 @@
1
+ // SPDX-License-Identifier: MIT
2
+ pragma solidity >=0.8.6;
3
+
4
+ import /* {*} from */ "./helpers/TestBaseWorkflow.sol";
5
+ import "@openzeppelin/contracts/utils/Address.sol";
6
+ import {MockPriceFeed} from "./mock/MockPriceFeed.sol";
7
+ import {MaliciousAllowanceBeneficiary, MaliciousPayoutBeneficiary} from "./mock/MockMaliciousBeneficiary.sol";
8
+
9
+ /// Funds can be accessed in three ways:
10
+ /// 1. project owners set a payout limit to prioritize spending to pre-determined destinations. funds being removed from
11
+ /// the protocol incurs fees unless the recipients are feeless addresses.
12
+ /// 2. project owners set a surplus allowance to allow spending funds from the project's surplus balance in the terminal
13
+ /// (i.e. the balance in excess of their payout limit). incurs fees unless the caller is a feeless address.
14
+ /// 3. token holders can cash out tokens to access surplus funds. incurs fees if the cash out tax rate != 100%, unless
15
+ /// the
16
+ /// beneficiary is a feeless address.
17
+ /// Each of these only incurs protocol fees if the `_FEE_PROJECT_ID` (project with ID #1) accepts the token being
18
+ /// accessed.
19
+ contract TestAccessToFunds_Local is TestBaseWorkflow {
20
+ uint256 private constant _FEE_PROJECT_ID = 1;
21
+ uint8 private constant _WEIGHT_DECIMALS = 18; // FIXED
22
+ uint8 private constant _NATIVE_DECIMALS = 18; // FIXED
23
+ uint8 private constant _PRICE_FEED_DECIMALS = 10;
24
+ uint256 private constant _USD_PRICE_PER_NATIVE = 2000 * 10 ** _PRICE_FEED_DECIMALS; // 2000 USDC == 1 native token
25
+
26
+ IJBController private _controller;
27
+ IJBPrices private _prices;
28
+ IJBMultiTerminal private _terminal;
29
+ IJBMultiTerminal private _terminal2;
30
+ IJBTokens private _tokens;
31
+ address private _projectOwner;
32
+ address private _beneficiary;
33
+ MockERC20 private _usdcToken;
34
+ uint256 private _projectId;
35
+
36
+ uint112 private _weight;
37
+ JBRulesetMetadata private _metadata;
38
+
39
+ function setUp() public override {
40
+ super.setUp();
41
+
42
+ _projectOwner = multisig();
43
+ _beneficiary = beneficiary();
44
+ _usdcToken = usdcToken();
45
+ _tokens = jbTokens();
46
+ _controller = jbController();
47
+ _prices = jbPrices();
48
+ _terminal = jbMultiTerminal();
49
+ _terminal2 = jbMultiTerminal2();
50
+ _weight = uint112(1000 * 10 ** _WEIGHT_DECIMALS);
51
+
52
+ _metadata = JBRulesetMetadata({
53
+ reservedPercent: JBConstants.MAX_RESERVED_PERCENT / 2, //50%
54
+ cashOutTaxRate: JBConstants.MAX_CASH_OUT_TAX_RATE / 2, //50%
55
+ baseCurrency: uint32(uint160(JBConstants.NATIVE_TOKEN)),
56
+ pausePay: false,
57
+ pauseCreditTransfers: false,
58
+ allowOwnerMinting: false,
59
+ allowSetCustomToken: false,
60
+ allowTerminalMigration: false,
61
+ allowSetTerminals: false,
62
+ ownerMustSendPayouts: false,
63
+ allowSetController: false,
64
+ allowAddAccountingContext: true,
65
+ allowAddPriceFeed: true,
66
+ holdFees: false,
67
+ useTotalSurplusForCashOuts: true,
68
+ useDataHookForPay: false,
69
+ useDataHookForCashOut: false,
70
+ dataHook: address(0),
71
+ metadata: 0
72
+ });
73
+ }
74
+
75
+ // Tests that basic payout limit and surplus allowance limits work as intended.
76
+
77
+ function testNativeAllowance() public {
78
+ // Hardcode values to use.
79
+ uint224 _nativeCurrencyPayoutLimit = uint224(10 * 10 ** _NATIVE_DECIMALS);
80
+ uint224 _nativeCurrencySurplusAllowance = uint224(5 * 10 ** _NATIVE_DECIMALS);
81
+
82
+ // Package up the limits for the given terminal.
83
+ JBFundAccessLimitGroup[] memory _fundAccessLimitGroup = new JBFundAccessLimitGroup[](1);
84
+ {
85
+ // Specify a payout limit.
86
+ JBCurrencyAmount[] memory _payoutLimits = new JBCurrencyAmount[](1);
87
+ _payoutLimits[0] = JBCurrencyAmount({
88
+ amount: _nativeCurrencyPayoutLimit, currency: uint32(uint160(JBConstants.NATIVE_TOKEN))
89
+ });
90
+
91
+ // Specify a surplus allowance.
92
+ JBCurrencyAmount[] memory _surplusAllowances = new JBCurrencyAmount[](1);
93
+ _surplusAllowances[0] = JBCurrencyAmount({
94
+ amount: _nativeCurrencySurplusAllowance, currency: uint32(uint160(JBConstants.NATIVE_TOKEN))
95
+ });
96
+
97
+ _fundAccessLimitGroup[0] = JBFundAccessLimitGroup({
98
+ terminal: address(_terminal),
99
+ token: JBConstants.NATIVE_TOKEN,
100
+ payoutLimits: _payoutLimits,
101
+ surplusAllowances: _surplusAllowances
102
+ });
103
+ }
104
+
105
+ {
106
+ // Package up the ruleset configuration.
107
+ JBRulesetConfig[] memory _rulesetConfigurations = new JBRulesetConfig[](1);
108
+ _rulesetConfigurations[0].mustStartAtOrAfter = 0;
109
+ _rulesetConfigurations[0].duration = 0;
110
+ _rulesetConfigurations[0].weight = _weight;
111
+ _rulesetConfigurations[0].weightCutPercent = 0;
112
+ _rulesetConfigurations[0].approvalHook = IJBRulesetApprovalHook(address(0));
113
+ _rulesetConfigurations[0].metadata = _metadata;
114
+ _rulesetConfigurations[0].splitGroups = new JBSplitGroup[](0);
115
+ _rulesetConfigurations[0].fundAccessLimitGroups = _fundAccessLimitGroup;
116
+
117
+ JBTerminalConfig[] memory _terminalConfigurations = new JBTerminalConfig[](1);
118
+ JBAccountingContext[] memory _tokensToAccept = new JBAccountingContext[](1);
119
+ _tokensToAccept[0] = JBAccountingContext({
120
+ token: JBConstants.NATIVE_TOKEN, decimals: 18, currency: uint32(uint160(JBConstants.NATIVE_TOKEN))
121
+ });
122
+
123
+ _terminalConfigurations[0] =
124
+ JBTerminalConfig({terminal: _terminal, accountingContextsToAccept: _tokensToAccept});
125
+
126
+ // Create a first project to collect fees.
127
+ _controller.launchProjectFor({
128
+ owner: address(420), // Random.
129
+ projectUri: "whatever",
130
+ rulesetConfigurations: _rulesetConfigurations,
131
+ terminalConfigurations: _terminalConfigurations, // Set terminals to receive fees.
132
+ memo: ""
133
+ });
134
+
135
+ // Create the project to test.
136
+ _projectId = _controller.launchProjectFor({
137
+ owner: _projectOwner,
138
+ projectUri: "myIPFSHash",
139
+ rulesetConfigurations: _rulesetConfigurations,
140
+ terminalConfigurations: _terminalConfigurations,
141
+ memo: ""
142
+ });
143
+ }
144
+
145
+ // Get a reference to the amount being paid.
146
+ // The amount being paid is the payout limit plus two times the surplus allowance.
147
+ uint256 _nativePayAmount = _nativeCurrencyPayoutLimit + (2 * _nativeCurrencySurplusAllowance);
148
+
149
+ // Pay the project such that the `_beneficiary` receives project tokens.
150
+ _terminal.pay{value: _nativePayAmount}({
151
+ projectId: _projectId,
152
+ amount: _nativePayAmount,
153
+ token: JBConstants.NATIVE_TOKEN,
154
+ beneficiary: _beneficiary,
155
+ minReturnedTokens: 0,
156
+ memo: "",
157
+ metadata: new bytes(0)
158
+ });
159
+
160
+ // Make sure the beneficiary got the expected number of tokens.
161
+ uint256 _beneficiaryTokenBalance = mulDiv(_nativePayAmount, _weight, 10 ** _NATIVE_DECIMALS)
162
+ * _metadata.reservedPercent / JBConstants.MAX_RESERVED_PERCENT;
163
+ assertEq(_tokens.totalBalanceOf(_beneficiary, _projectId), _beneficiaryTokenBalance);
164
+
165
+ // Make sure the terminal holds the full native token balance.
166
+ assertEq(
167
+ jbTerminalStore().balanceOf(address(_terminal), _projectId, JBConstants.NATIVE_TOKEN), _nativePayAmount
168
+ );
169
+
170
+ // Use the full surplus allowance.
171
+ vm.prank(_projectOwner);
172
+ _terminal.useAllowanceOf({
173
+ projectId: _projectId,
174
+ amount: _nativeCurrencySurplusAllowance,
175
+ currency: uint32(uint160(JBConstants.NATIVE_TOKEN)),
176
+ token: JBConstants.NATIVE_TOKEN,
177
+ minTokensPaidOut: 0,
178
+ beneficiary: payable(_beneficiary),
179
+ feeBeneficiary: payable(_projectOwner),
180
+ memo: "MEMO"
181
+ });
182
+
183
+ // Make sure the beneficiary received the funds and that they are no longer in the terminal.
184
+ uint256 _beneficiaryNativeBalance = _nativeCurrencySurplusAllowance
185
+ - mulDiv(_nativeCurrencySurplusAllowance, _terminal.FEE(), JBConstants.MAX_FEE);
186
+ assertEq(_beneficiary.balance, _beneficiaryNativeBalance);
187
+ assertEq(
188
+ jbTerminalStore().balanceOf(address(_terminal), _projectId, JBConstants.NATIVE_TOKEN),
189
+ _nativePayAmount - _nativeCurrencySurplusAllowance
190
+ );
191
+
192
+ // Make sure the fee was paid correctly.
193
+ assertEq(
194
+ jbTerminalStore().balanceOf(address(_terminal), _FEE_PROJECT_ID, JBConstants.NATIVE_TOKEN),
195
+ _nativeCurrencySurplusAllowance - _beneficiaryNativeBalance
196
+ );
197
+ assertEq(address(_terminal).balance, _nativePayAmount - _beneficiaryNativeBalance);
198
+
199
+ // Make sure the project owner got the expected number of tokens.
200
+ assertEq(
201
+ _tokens.totalBalanceOf(_projectOwner, _FEE_PROJECT_ID),
202
+ mulDiv(_nativeCurrencySurplusAllowance - _beneficiaryNativeBalance, _weight, 10 ** _NATIVE_DECIMALS)
203
+ * _metadata.reservedPercent / JBConstants.MAX_RESERVED_PERCENT
204
+ );
205
+
206
+ // Pay out native tokens up to the payout limit. Since `splits[]` is empty, everything goes to project owner.
207
+ _terminal.sendPayoutsOf({
208
+ projectId: _projectId,
209
+ amount: _nativeCurrencyPayoutLimit,
210
+ currency: uint32(uint160(JBConstants.NATIVE_TOKEN)),
211
+ token: JBConstants.NATIVE_TOKEN,
212
+ minTokensPaidOut: 0
213
+ });
214
+
215
+ // Make sure the project owner received the funds which were paid out.
216
+ uint256 _projectOwnerNativeBalance =
217
+ _nativeCurrencyPayoutLimit - _nativeCurrencyPayoutLimit * _terminal.FEE() / JBConstants.MAX_FEE;
218
+
219
+ // Make sure the project owner received the full amount.
220
+ assertEq(_projectOwner.balance, _projectOwnerNativeBalance);
221
+
222
+ // Make sure the fee was paid correctly.
223
+ assertEq(
224
+ jbTerminalStore().balanceOf(address(_terminal), _FEE_PROJECT_ID, JBConstants.NATIVE_TOKEN),
225
+ (_nativeCurrencySurplusAllowance - _beneficiaryNativeBalance)
226
+ + (_nativeCurrencyPayoutLimit - _projectOwnerNativeBalance)
227
+ );
228
+ assertEq(address(_terminal).balance, _nativePayAmount - _beneficiaryNativeBalance - _projectOwnerNativeBalance);
229
+
230
+ // Make sure the project owner got the expected number of tokens.
231
+ assertEq(
232
+ _tokens.totalBalanceOf(_projectOwner, _FEE_PROJECT_ID),
233
+ mulDiv(
234
+ (_nativeCurrencySurplusAllowance - _beneficiaryNativeBalance)
235
+ + (_nativeCurrencyPayoutLimit - _projectOwnerNativeBalance),
236
+ _weight,
237
+ 10 ** _NATIVE_DECIMALS
238
+ ) * _metadata.reservedPercent / JBConstants.MAX_RESERVED_PERCENT
239
+ );
240
+
241
+ // Cash out native tokens from the surplus using all of the `_beneficiary`'s tokens.
242
+ vm.prank(_beneficiary);
243
+ _terminal.cashOutTokensOf({
244
+ holder: _beneficiary,
245
+ projectId: _projectId,
246
+ cashOutCount: _beneficiaryTokenBalance,
247
+ tokenToReclaim: JBConstants.NATIVE_TOKEN,
248
+ minTokensReclaimed: 0,
249
+ beneficiary: payable(_beneficiary),
250
+ metadata: new bytes(0)
251
+ });
252
+
253
+ // Make sure the beneficiary doesn't have any project tokens left.
254
+ assertEq(_tokens.totalBalanceOf(_beneficiary, _projectId), 0);
255
+
256
+ // Get the expected amount of native tokens reclaimed by the cash out.
257
+ uint256 _nativeReclaimAmount = mulDiv(
258
+ mulDiv(
259
+ _nativePayAmount - _nativeCurrencySurplusAllowance - _nativeCurrencyPayoutLimit,
260
+ _beneficiaryTokenBalance,
261
+ mulDiv(_nativePayAmount, _weight, 10 ** _NATIVE_DECIMALS)
262
+ ),
263
+ _metadata.cashOutTaxRate
264
+ + mulDiv(
265
+ _beneficiaryTokenBalance,
266
+ JBConstants.MAX_CASH_OUT_TAX_RATE - _metadata.cashOutTaxRate,
267
+ mulDiv(_nativePayAmount, _weight, 10 ** _NATIVE_DECIMALS)
268
+ ),
269
+ JBConstants.MAX_CASH_OUT_TAX_RATE
270
+ );
271
+
272
+ // Calculate the fee from the cash out.
273
+ uint256 _feeAmount = _nativeReclaimAmount * _terminal.FEE() / JBConstants.MAX_FEE;
274
+ assertEq(_beneficiary.balance, _beneficiaryNativeBalance + _nativeReclaimAmount - _feeAmount);
275
+
276
+ // Make sure the fee was paid correctly.
277
+ assertEq(
278
+ jbTerminalStore().balanceOf(address(_terminal), _FEE_PROJECT_ID, JBConstants.NATIVE_TOKEN),
279
+ (_nativeCurrencySurplusAllowance - _beneficiaryNativeBalance)
280
+ + (_nativeCurrencyPayoutLimit - _projectOwnerNativeBalance) + _feeAmount
281
+ );
282
+ assertEq(
283
+ address(_terminal).balance,
284
+ _nativePayAmount - _beneficiaryNativeBalance - _projectOwnerNativeBalance
285
+ - (_nativeReclaimAmount - _feeAmount)
286
+ );
287
+
288
+ // Make sure the project owner got the expected number of the fee project's tokens by paying the fee.
289
+ assertEq(
290
+ _tokens.totalBalanceOf(_beneficiary, _FEE_PROJECT_ID),
291
+ mulDiv(_feeAmount, _weight, 10 ** _NATIVE_DECIMALS) * _metadata.reservedPercent
292
+ / JBConstants.MAX_RESERVED_PERCENT
293
+ );
294
+ }
295
+
296
+ function testFuzzNativeAllowance(
297
+ uint224 _nativeCurrencySurplusAllowance,
298
+ uint224 _nativeCurrencyPayoutLimit,
299
+ uint256 _nativePayAmount
300
+ )
301
+ public
302
+ {
303
+ // Make sure the amount of native tokens to pay is bounded.
304
+ _nativePayAmount = bound(_nativePayAmount, 0, 1_000_000 * 10 ** _NATIVE_DECIMALS);
305
+
306
+ // Make sure the values don't overflow the registry.
307
+ unchecked {
308
+ vm.assume(
309
+ _nativeCurrencySurplusAllowance + _nativeCurrencyPayoutLimit >= _nativeCurrencySurplusAllowance
310
+ && _nativeCurrencySurplusAllowance + _nativeCurrencyPayoutLimit >= _nativeCurrencyPayoutLimit
311
+ );
312
+ }
313
+
314
+ // Package up the limits for the given terminal.
315
+ JBFundAccessLimitGroup[] memory _fundAccessLimitGroup = new JBFundAccessLimitGroup[](1);
316
+ {
317
+ // Specify a payout limit.
318
+ JBCurrencyAmount[] memory _payoutLimits = new JBCurrencyAmount[](1);
319
+ _payoutLimits[0] = JBCurrencyAmount({
320
+ amount: _nativeCurrencyPayoutLimit, currency: uint32(uint160(JBConstants.NATIVE_TOKEN))
321
+ });
322
+
323
+ // Specify a surplus allowance.
324
+ JBCurrencyAmount[] memory _surplusAllowances = new JBCurrencyAmount[](1);
325
+ _surplusAllowances[0] = JBCurrencyAmount({
326
+ amount: _nativeCurrencySurplusAllowance, currency: uint32(uint160(JBConstants.NATIVE_TOKEN))
327
+ });
328
+
329
+ _fundAccessLimitGroup[0] = JBFundAccessLimitGroup({
330
+ terminal: address(_terminal),
331
+ token: JBConstants.NATIVE_TOKEN,
332
+ payoutLimits: _payoutLimits,
333
+ surplusAllowances: _surplusAllowances
334
+ });
335
+ }
336
+
337
+ {
338
+ // Package up the ruleset configuration.
339
+ JBRulesetConfig[] memory _rulesetConfigurations = new JBRulesetConfig[](1);
340
+ _rulesetConfigurations[0].mustStartAtOrAfter = 0;
341
+ _rulesetConfigurations[0].duration = 0;
342
+ _rulesetConfigurations[0].weight = _weight;
343
+ _rulesetConfigurations[0].weightCutPercent = 0;
344
+ _rulesetConfigurations[0].approvalHook = IJBRulesetApprovalHook(address(0));
345
+ _rulesetConfigurations[0].metadata = _metadata;
346
+ _rulesetConfigurations[0].splitGroups = new JBSplitGroup[](0);
347
+ _rulesetConfigurations[0].fundAccessLimitGroups = _fundAccessLimitGroup;
348
+
349
+ JBTerminalConfig[] memory _terminalConfigurations = new JBTerminalConfig[](1);
350
+ JBAccountingContext[] memory _tokensToAccept = new JBAccountingContext[](1);
351
+ _tokensToAccept[0] = JBAccountingContext({
352
+ token: JBConstants.NATIVE_TOKEN, decimals: 18, currency: uint32(uint160(JBConstants.NATIVE_TOKEN))
353
+ });
354
+ _terminalConfigurations[0] =
355
+ JBTerminalConfig({terminal: _terminal, accountingContextsToAccept: _tokensToAccept});
356
+
357
+ // Create a project to collect fees.
358
+ _controller.launchProjectFor({
359
+ owner: address(420), // Random.
360
+ projectUri: "whatever",
361
+ rulesetConfigurations: _rulesetConfigurations, // Use the same ruleset configurations.
362
+ terminalConfigurations: _terminalConfigurations, // set the terminals where fees will be received
363
+ memo: ""
364
+ });
365
+
366
+ // Create the project to test.
367
+ _projectId = _controller.launchProjectFor({
368
+ owner: _projectOwner,
369
+ projectUri: "myIPFSHash",
370
+ rulesetConfigurations: _rulesetConfigurations,
371
+ terminalConfigurations: _terminalConfigurations,
372
+ memo: ""
373
+ });
374
+ }
375
+
376
+ // Make a payment to the test project to give it a starting balance. Send the tokens to the `_beneficiary`.
377
+ _terminal.pay{value: _nativePayAmount}({
378
+ projectId: _projectId,
379
+ amount: _nativePayAmount,
380
+ token: JBConstants.NATIVE_TOKEN,
381
+ beneficiary: _beneficiary,
382
+ minReturnedTokens: 0,
383
+ memo: "",
384
+ metadata: new bytes(0)
385
+ });
386
+
387
+ // Make sure the beneficiary got the expected number of tokens.
388
+ uint256 _beneficiaryTokenBalance = mulDiv(_nativePayAmount, _weight, 10 ** _NATIVE_DECIMALS)
389
+ * _metadata.reservedPercent / JBConstants.MAX_RESERVED_PERCENT;
390
+ assertEq(_tokens.totalBalanceOf(_beneficiary, _projectId), _beneficiaryTokenBalance);
391
+
392
+ // Make sure the terminal holds the full native token balance.
393
+ assertEq(
394
+ jbTerminalStore().balanceOf(address(_terminal), _projectId, JBConstants.NATIVE_TOKEN), _nativePayAmount
395
+ );
396
+
397
+ // Revert if there's no surplus allowance.
398
+ if (_nativeCurrencySurplusAllowance == 0) {
399
+ vm.expectRevert(
400
+ abi.encodeWithSelector(JBTerminalStore.JBTerminalStore_InadequateControllerAllowance.selector, 0, 0)
401
+ );
402
+ // Revert if there's no surplus, or if too much is being withdrawn.
403
+ } else if (_nativeCurrencySurplusAllowance + _nativeCurrencyPayoutLimit > _nativePayAmount) {
404
+ vm.expectRevert(
405
+ abi.encodeWithSelector(
406
+ JBTerminalStore.JBTerminalStore_InadequateTerminalStoreBalance.selector,
407
+ _nativeCurrencySurplusAllowance,
408
+ _nativeCurrencyPayoutLimit > _nativePayAmount ? 0 : _nativePayAmount - _nativeCurrencyPayoutLimit
409
+ )
410
+ );
411
+ }
412
+
413
+ // Use the full surplus allowance.
414
+ vm.prank(_projectOwner);
415
+ _terminal.useAllowanceOf({
416
+ projectId: _projectId,
417
+ amount: _nativeCurrencySurplusAllowance,
418
+ currency: uint32(uint160(JBConstants.NATIVE_TOKEN)),
419
+ token: JBConstants.NATIVE_TOKEN,
420
+ minTokensPaidOut: 0,
421
+ beneficiary: payable(_beneficiary),
422
+ feeBeneficiary: payable(_projectOwner),
423
+ memo: "MEMO"
424
+ });
425
+
426
+ // Keep a reference to the beneficiary's balance.
427
+ uint256 _beneficiaryNativeBalance;
428
+
429
+ // Check the collected balance if one is expected.
430
+ if (_nativeCurrencySurplusAllowance + _nativeCurrencyPayoutLimit <= _nativePayAmount) {
431
+ // Make sure the beneficiary received the funds and that they are no longer in the terminal.
432
+ _beneficiaryNativeBalance = _nativeCurrencySurplusAllowance
433
+ - mulDiv(_nativeCurrencySurplusAllowance, _terminal.FEE(), JBConstants.MAX_FEE);
434
+ assertEq(_beneficiary.balance, _beneficiaryNativeBalance);
435
+ assertEq(
436
+ jbTerminalStore().balanceOf(address(_terminal), _projectId, JBConstants.NATIVE_TOKEN),
437
+ _nativePayAmount - _nativeCurrencySurplusAllowance
438
+ );
439
+
440
+ // Make sure the fee was paid correctly.
441
+ assertEq(
442
+ jbTerminalStore().balanceOf(address(_terminal), _FEE_PROJECT_ID, JBConstants.NATIVE_TOKEN),
443
+ _nativeCurrencySurplusAllowance - _beneficiaryNativeBalance
444
+ );
445
+ assertEq(address(_terminal).balance, _nativePayAmount - _beneficiaryNativeBalance);
446
+
447
+ // Make sure the beneficiary got the expected number of tokens.
448
+ assertEq(
449
+ _tokens.totalBalanceOf(_projectOwner, _FEE_PROJECT_ID),
450
+ mulDiv(_nativeCurrencySurplusAllowance - _beneficiaryNativeBalance, _weight, 10 ** _NATIVE_DECIMALS)
451
+ * _metadata.reservedPercent / JBConstants.MAX_RESERVED_PERCENT
452
+ );
453
+ } else {
454
+ // Set the native token surplus allowance to 0 if it wasn't used.
455
+ _nativeCurrencySurplusAllowance = 0;
456
+ }
457
+
458
+ // Revert if the payout limit is greater than the balance.
459
+ if (_nativeCurrencyPayoutLimit > _nativePayAmount) {
460
+ vm.expectRevert(
461
+ abi.encodeWithSelector(
462
+ JBTerminalStore.JBTerminalStore_InadequateTerminalStoreBalance.selector,
463
+ _nativeCurrencyPayoutLimit,
464
+ _nativePayAmount
465
+ )
466
+ );
467
+
468
+ // Revert if there's no payout limit.
469
+ } else if (_nativeCurrencyPayoutLimit == 0) {
470
+ vm.expectRevert(
471
+ abi.encodeWithSelector(JBTerminalStore.JBTerminalStore_InadequateControllerPayoutLimit.selector, 0, 0)
472
+ );
473
+ }
474
+
475
+ // Pay out native tokens up to the payout limit. Since `splits[]` is empty, everything goes to project owner.
476
+ _terminal.sendPayoutsOf({
477
+ projectId: _projectId,
478
+ amount: _nativeCurrencyPayoutLimit,
479
+ currency: uint32(uint160(JBConstants.NATIVE_TOKEN)),
480
+ token: JBConstants.NATIVE_TOKEN,
481
+ minTokensPaidOut: 0
482
+ });
483
+
484
+ uint256 _projectOwnerNativeBalance;
485
+
486
+ // Check the payout if one is expected.
487
+ if (_nativeCurrencyPayoutLimit <= _nativePayAmount && _nativeCurrencyPayoutLimit != 0) {
488
+ // Make sure the project owner received the payout.
489
+ _projectOwnerNativeBalance =
490
+ _nativeCurrencyPayoutLimit - _nativeCurrencyPayoutLimit * _terminal.FEE() / JBConstants.MAX_FEE;
491
+ assertEq(_projectOwner.balance, _projectOwnerNativeBalance);
492
+ assertEq(
493
+ jbTerminalStore().balanceOf(address(_terminal), _projectId, JBConstants.NATIVE_TOKEN),
494
+ _nativePayAmount - _nativeCurrencySurplusAllowance - _nativeCurrencyPayoutLimit
495
+ );
496
+
497
+ // Make sure the fee was paid correctly.
498
+ assertEq(
499
+ jbTerminalStore().balanceOf(address(_terminal), _FEE_PROJECT_ID, JBConstants.NATIVE_TOKEN),
500
+ (_nativeCurrencySurplusAllowance - _beneficiaryNativeBalance)
501
+ + (_nativeCurrencyPayoutLimit - _projectOwnerNativeBalance)
502
+ );
503
+ assertEq(
504
+ address(_terminal).balance, _nativePayAmount - _beneficiaryNativeBalance - _projectOwnerNativeBalance
505
+ );
506
+
507
+ // Make sure the project owner got the expected number of the fee project's tokens.
508
+ assertEq(
509
+ _tokens.totalBalanceOf(_projectOwner, _FEE_PROJECT_ID),
510
+ mulDiv(
511
+ (_nativeCurrencySurplusAllowance - _beneficiaryNativeBalance)
512
+ + (_nativeCurrencyPayoutLimit - _projectOwnerNativeBalance),
513
+ _weight,
514
+ 10 ** _NATIVE_DECIMALS
515
+ ) * _metadata.reservedPercent / JBConstants.MAX_RESERVED_PERCENT
516
+ );
517
+ }
518
+
519
+ // Reclaim native tokens from the surplus by cashing out all of the `_beneficiary`'s tokens.
520
+ vm.prank(_beneficiary);
521
+ _terminal.cashOutTokensOf({
522
+ holder: _beneficiary,
523
+ projectId: _projectId,
524
+ cashOutCount: _beneficiaryTokenBalance,
525
+ tokenToReclaim: JBConstants.NATIVE_TOKEN,
526
+ minTokensReclaimed: 0,
527
+ beneficiary: payable(_beneficiary),
528
+ metadata: new bytes(0)
529
+ });
530
+
531
+ // Make sure the beneficiary doesn't have tokens left.
532
+ assertEq(_tokens.totalBalanceOf(_beneficiary, _projectId), 0);
533
+
534
+ // Check for a new beneficiary balance if one is expected.
535
+ if (_nativePayAmount > _nativeCurrencySurplusAllowance + _nativeCurrencyPayoutLimit) {
536
+ // Get the expected amount reclaimed.
537
+ uint256 _nativeReclaimAmount = mulDiv(
538
+ mulDiv(
539
+ _nativePayAmount - _nativeCurrencySurplusAllowance - _nativeCurrencyPayoutLimit,
540
+ _beneficiaryTokenBalance,
541
+ mulDiv(_nativePayAmount, _weight, 10 ** _NATIVE_DECIMALS)
542
+ ),
543
+ _metadata.cashOutTaxRate
544
+ + mulDiv(
545
+ _beneficiaryTokenBalance,
546
+ JBConstants.MAX_CASH_OUT_TAX_RATE - _metadata.cashOutTaxRate,
547
+ mulDiv(_nativePayAmount, _weight, 10 ** _NATIVE_DECIMALS)
548
+ ),
549
+ JBConstants.MAX_CASH_OUT_TAX_RATE
550
+ );
551
+ // Calculate the fee from the cash out.
552
+ uint256 _feeAmount = _nativeReclaimAmount * _terminal.FEE() / JBConstants.MAX_FEE;
553
+ assertEq(_beneficiary.balance, _beneficiaryNativeBalance + _nativeReclaimAmount - _feeAmount);
554
+
555
+ // Make sure the fee was paid correctly.
556
+ assertEq(
557
+ jbTerminalStore().balanceOf(address(_terminal), _FEE_PROJECT_ID, JBConstants.NATIVE_TOKEN),
558
+ (_nativeCurrencySurplusAllowance - _beneficiaryNativeBalance)
559
+ + (_nativeCurrencyPayoutLimit - _projectOwnerNativeBalance) + _feeAmount
560
+ );
561
+ assertEq(
562
+ address(_terminal).balance,
563
+ _nativePayAmount - _beneficiaryNativeBalance - _projectOwnerNativeBalance
564
+ - (_nativeReclaimAmount - _feeAmount)
565
+ );
566
+
567
+ // Make sure the project owner got the expected number of tokens from the fee.
568
+ assertEq(
569
+ _tokens.totalBalanceOf(_beneficiary, _FEE_PROJECT_ID),
570
+ mulDiv(_feeAmount, _weight, 10 ** _NATIVE_DECIMALS) * _metadata.reservedPercent
571
+ / JBConstants.MAX_RESERVED_PERCENT
572
+ );
573
+ }
574
+ }
575
+
576
+ function testFuzzNativeAllowanceWithRevertingFeeProject(
577
+ uint224 _nativeCurrencySurplusAllowance,
578
+ uint224 _nativeCurrencyPayoutLimit,
579
+ uint256 _nativePayAmount,
580
+ bool _feeProjectAcceptsToken
581
+ )
582
+ public
583
+ {
584
+ // Make sure the amount of native tokens to pay is bounded.
585
+ _nativePayAmount = bound(_nativePayAmount, 0, 1_000_000 * 10 ** _NATIVE_DECIMALS);
586
+
587
+ // Make sure the values don't overflow the registry.
588
+ unchecked {
589
+ vm.assume(
590
+ _nativeCurrencySurplusAllowance + _nativeCurrencyPayoutLimit >= _nativeCurrencySurplusAllowance
591
+ && _nativeCurrencySurplusAllowance + _nativeCurrencyPayoutLimit >= _nativeCurrencyPayoutLimit
592
+ );
593
+ }
594
+
595
+ // Package up the limits for the given terminal.
596
+ JBFundAccessLimitGroup[] memory _fundAccessLimitGroup = new JBFundAccessLimitGroup[](1);
597
+ {
598
+ // Specify a payout limit.
599
+ JBCurrencyAmount[] memory _payoutLimits = new JBCurrencyAmount[](1);
600
+ _payoutLimits[0] = JBCurrencyAmount({
601
+ amount: _nativeCurrencyPayoutLimit, currency: uint32(uint160(JBConstants.NATIVE_TOKEN))
602
+ });
603
+
604
+ // Specify a surplus allowance.
605
+ JBCurrencyAmount[] memory _surplusAllowances = new JBCurrencyAmount[](1);
606
+ _surplusAllowances[0] = JBCurrencyAmount({
607
+ amount: _nativeCurrencySurplusAllowance, currency: uint32(uint160(JBConstants.NATIVE_TOKEN))
608
+ });
609
+
610
+ _fundAccessLimitGroup[0] = JBFundAccessLimitGroup({
611
+ terminal: address(_terminal),
612
+ token: JBConstants.NATIVE_TOKEN,
613
+ payoutLimits: _payoutLimits,
614
+ surplusAllowances: _surplusAllowances
615
+ });
616
+ }
617
+
618
+ {
619
+ JBTerminalConfig[] memory _terminalConfigurations = new JBTerminalConfig[](1);
620
+ JBAccountingContext[] memory _tokensToAccept = new JBAccountingContext[](1);
621
+ _tokensToAccept[0] = JBAccountingContext({
622
+ token: JBConstants.NATIVE_TOKEN, decimals: 18, currency: uint32(uint160(JBConstants.NATIVE_TOKEN))
623
+ });
624
+ _terminalConfigurations[0] =
625
+ JBTerminalConfig({terminal: _terminal, accountingContextsToAccept: _tokensToAccept});
626
+
627
+ // Create a first project to collect fees.
628
+ _controller.launchProjectFor({
629
+ owner: address(420), // Random.
630
+ projectUri: "whatever",
631
+ rulesetConfigurations: new JBRulesetConfig[](0), // No ruleset config will force revert when paid.
632
+ // Set the fee collecting terminal's native token accounting context if the test calls for doing so.
633
+ terminalConfigurations: _feeProjectAcceptsToken ? _terminalConfigurations : new JBTerminalConfig[](0), // Set
634
+ // terminals to receive fees.
635
+ memo: ""
636
+ });
637
+
638
+ // Package up the ruleset configuration.
639
+ JBRulesetConfig[] memory _rulesetConfigurations = new JBRulesetConfig[](1);
640
+ _rulesetConfigurations[0].mustStartAtOrAfter = 0;
641
+ _rulesetConfigurations[0].duration = 0;
642
+ _rulesetConfigurations[0].weight = _weight;
643
+ _rulesetConfigurations[0].weightCutPercent = 0;
644
+ _rulesetConfigurations[0].approvalHook = IJBRulesetApprovalHook(address(0));
645
+ _rulesetConfigurations[0].metadata = _metadata;
646
+ _rulesetConfigurations[0].splitGroups = new JBSplitGroup[](0);
647
+ _rulesetConfigurations[0].fundAccessLimitGroups = _fundAccessLimitGroup;
648
+
649
+ // Create the project to test.
650
+ _projectId = _controller.launchProjectFor({
651
+ owner: _projectOwner,
652
+ projectUri: "myIPFSHash",
653
+ rulesetConfigurations: _rulesetConfigurations,
654
+ terminalConfigurations: _terminalConfigurations,
655
+ memo: ""
656
+ });
657
+ }
658
+
659
+ // Make a payment to the project to give it a starting balance. Send the tokens to the `_beneficiary`.
660
+ _terminal.pay{value: _nativePayAmount}({
661
+ projectId: _projectId,
662
+ amount: _nativePayAmount,
663
+ token: JBConstants.NATIVE_TOKEN,
664
+ beneficiary: _beneficiary,
665
+ minReturnedTokens: 0,
666
+ memo: "",
667
+ metadata: new bytes(0)
668
+ });
669
+
670
+ // Make sure the beneficiary got the expected number of tokens.
671
+ uint256 _beneficiaryTokenBalance = mulDiv(_nativePayAmount, _weight, 10 ** _NATIVE_DECIMALS)
672
+ * _metadata.reservedPercent / JBConstants.MAX_RESERVED_PERCENT;
673
+ assertEq(_tokens.totalBalanceOf(_beneficiary, _projectId), _beneficiaryTokenBalance);
674
+
675
+ // Make sure the terminal holds the full native token balance.
676
+ assertEq(
677
+ jbTerminalStore().balanceOf(address(_terminal), _projectId, JBConstants.NATIVE_TOKEN), _nativePayAmount
678
+ );
679
+
680
+ // Revert if there's no surplus allowance.
681
+ if (_nativeCurrencySurplusAllowance == 0) {
682
+ vm.expectRevert(
683
+ abi.encodeWithSelector(JBTerminalStore.JBTerminalStore_InadequateControllerAllowance.selector, 0, 0)
684
+ );
685
+ // Revert if there's no surplus, or if too much is being withdrawn.
686
+ } else if (_nativeCurrencySurplusAllowance + _nativeCurrencyPayoutLimit > _nativePayAmount) {
687
+ vm.expectRevert(
688
+ abi.encodeWithSelector(
689
+ JBTerminalStore.JBTerminalStore_InadequateTerminalStoreBalance.selector,
690
+ _nativeCurrencySurplusAllowance,
691
+ _nativeCurrencyPayoutLimit > _nativePayAmount ? 0 : _nativePayAmount - _nativeCurrencyPayoutLimit
692
+ )
693
+ );
694
+ }
695
+
696
+ // Use the full surplus allowance.
697
+ vm.prank(_projectOwner);
698
+ _terminal.useAllowanceOf({
699
+ projectId: _projectId,
700
+ amount: _nativeCurrencySurplusAllowance,
701
+ currency: uint32(uint160(JBConstants.NATIVE_TOKEN)),
702
+ token: JBConstants.NATIVE_TOKEN,
703
+ minTokensPaidOut: 0,
704
+ beneficiary: payable(_beneficiary),
705
+ feeBeneficiary: payable(_projectOwner),
706
+ memo: "MEMO"
707
+ });
708
+
709
+ // Keep a reference to the beneficiary's balance.
710
+ uint256 _beneficiaryNativeBalance;
711
+
712
+ // Check the collected balance if one is expected.
713
+ if (_nativeCurrencySurplusAllowance + _nativeCurrencyPayoutLimit <= _nativePayAmount) {
714
+ // Make sure the beneficiary received the funds and that they are no longer in the terminal.
715
+ _beneficiaryNativeBalance = _nativeCurrencySurplusAllowance
716
+ - mulDiv(_nativeCurrencySurplusAllowance, _terminal.FEE(), JBConstants.MAX_FEE);
717
+ assertEq(_beneficiary.balance, _beneficiaryNativeBalance);
718
+ // Make sure the fee stays in the terminal.
719
+ assertEq(
720
+ jbTerminalStore().balanceOf(address(_terminal), _projectId, JBConstants.NATIVE_TOKEN),
721
+ _nativePayAmount - _beneficiaryNativeBalance
722
+ );
723
+
724
+ // Make sure the fee was not taken.
725
+ assertEq(jbTerminalStore().balanceOf(address(_terminal), _FEE_PROJECT_ID, JBConstants.NATIVE_TOKEN), 0);
726
+ assertEq(address(_terminal).balance, _nativePayAmount - _beneficiaryNativeBalance);
727
+
728
+ // Make sure the beneficiary got no tokens.
729
+ assertEq(_tokens.totalBalanceOf(_projectOwner, _FEE_PROJECT_ID), 0);
730
+ } else {
731
+ // Set the native token's surplus allowance to 0 if it wasn't used.
732
+ _nativeCurrencySurplusAllowance = 0;
733
+ }
734
+
735
+ // Revert if the payout limit is greater than the balance.
736
+ if (_nativeCurrencyPayoutLimit > _nativePayAmount) {
737
+ vm.expectRevert(
738
+ abi.encodeWithSelector(
739
+ JBTerminalStore.JBTerminalStore_InadequateTerminalStoreBalance.selector,
740
+ _nativeCurrencyPayoutLimit,
741
+ _nativePayAmount
742
+ )
743
+ );
744
+
745
+ // Revert if there's no payout limit.
746
+ } else if (_nativeCurrencyPayoutLimit == 0) {
747
+ vm.expectRevert(
748
+ abi.encodeWithSelector(JBTerminalStore.JBTerminalStore_InadequateControllerPayoutLimit.selector, 0, 0)
749
+ );
750
+ }
751
+
752
+ // Pay out native tokens up to the payout limit. Since `splits[]` is empty, everything goes to project owner.
753
+ _terminal.sendPayoutsOf({
754
+ projectId: _projectId,
755
+ amount: _nativeCurrencyPayoutLimit,
756
+ currency: uint32(uint160(JBConstants.NATIVE_TOKEN)),
757
+ token: JBConstants.NATIVE_TOKEN,
758
+ minTokensPaidOut: 0
759
+ });
760
+
761
+ uint256 _projectOwnerNativeBalance;
762
+
763
+ // Check the received payout if one is expected.
764
+ if (_nativeCurrencyPayoutLimit <= _nativePayAmount && _nativeCurrencyPayoutLimit != 0) {
765
+ // Make sure the project owner received the funds that were paid out.
766
+ _projectOwnerNativeBalance =
767
+ _nativeCurrencyPayoutLimit - _nativeCurrencyPayoutLimit * _terminal.FEE() / JBConstants.MAX_FEE;
768
+ assertEq(_projectOwner.balance, _projectOwnerNativeBalance);
769
+ // Make sure the fee stays in the terminal.
770
+ assertEq(
771
+ jbTerminalStore().balanceOf(address(_terminal), _projectId, JBConstants.NATIVE_TOKEN),
772
+ _nativePayAmount - _beneficiaryNativeBalance - _projectOwnerNativeBalance
773
+ );
774
+
775
+ // Make sure the fee was paid correctly.
776
+ assertEq(jbTerminalStore().balanceOf(address(_terminal), _FEE_PROJECT_ID, JBConstants.NATIVE_TOKEN), 0);
777
+ assertEq(
778
+ address(_terminal).balance, _nativePayAmount - _beneficiaryNativeBalance - _projectOwnerNativeBalance
779
+ );
780
+
781
+ // Make sure the project owner got the expected number of tokens.
782
+ assertEq(_tokens.totalBalanceOf(_projectOwner, _FEE_PROJECT_ID), 0);
783
+ }
784
+
785
+ // Reclaim native tokens from the surplus by cashing out all of the `_beneficiary`'s tokens.
786
+ vm.prank(_beneficiary);
787
+ _terminal.cashOutTokensOf({
788
+ holder: _beneficiary,
789
+ projectId: _projectId,
790
+ cashOutCount: _beneficiaryTokenBalance,
791
+ tokenToReclaim: JBConstants.NATIVE_TOKEN,
792
+ minTokensReclaimed: 0,
793
+ beneficiary: payable(_beneficiary),
794
+ metadata: new bytes(0)
795
+ });
796
+
797
+ // Make sure the beneficiary doesn't have tokens left.
798
+ assertEq(_tokens.totalBalanceOf(_beneficiary, _projectId), 0);
799
+
800
+ // Check for a new beneficiary balance if one is expected.
801
+ if (_nativePayAmount > _nativeCurrencySurplusAllowance + _nativeCurrencyPayoutLimit) {
802
+ // Get the expected amount reclaimed.
803
+ uint256 _nativeReclaimAmount = mulDiv(
804
+ mulDiv(
805
+ _nativePayAmount - _beneficiaryNativeBalance - _projectOwnerNativeBalance,
806
+ _beneficiaryTokenBalance,
807
+ mulDiv(_nativePayAmount, _weight, 10 ** _NATIVE_DECIMALS)
808
+ ),
809
+ _metadata.cashOutTaxRate
810
+ + mulDiv(
811
+ _beneficiaryTokenBalance,
812
+ JBConstants.MAX_CASH_OUT_TAX_RATE - _metadata.cashOutTaxRate,
813
+ mulDiv(_nativePayAmount, _weight, 10 ** _NATIVE_DECIMALS)
814
+ ),
815
+ JBConstants.MAX_CASH_OUT_TAX_RATE
816
+ );
817
+
818
+ // Calculate the fee from the cash out.
819
+ uint256 _feeAmount = _nativeReclaimAmount * _terminal.FEE() / JBConstants.MAX_FEE;
820
+ assertEq(_beneficiary.balance, _beneficiaryNativeBalance + _nativeReclaimAmount - _feeAmount);
821
+ // Make sure the fee stays in the terminal.
822
+ assertEq(
823
+ jbTerminalStore().balanceOf(address(_terminal), _projectId, JBConstants.NATIVE_TOKEN),
824
+ _nativePayAmount - _beneficiaryNativeBalance - _projectOwnerNativeBalance
825
+ - (_nativeReclaimAmount - _feeAmount)
826
+ );
827
+
828
+ // Make sure the fee was paid correctly.
829
+ assertEq(jbTerminalStore().balanceOf(address(_terminal), _FEE_PROJECT_ID, JBConstants.NATIVE_TOKEN), 0);
830
+ assertEq(
831
+ address(_terminal).balance,
832
+ _nativePayAmount - _beneficiaryNativeBalance - _projectOwnerNativeBalance
833
+ - (_nativeReclaimAmount - _feeAmount)
834
+ );
835
+
836
+ // Make sure the project owner got the expected number of tokens from the fee.
837
+ assertEq(_tokens.totalBalanceOf(_beneficiary, _FEE_PROJECT_ID), 0);
838
+ }
839
+ }
840
+
841
+ function testFuzzNativeTokenAllowanceForTheFeeProject(
842
+ uint224 _nativeCurrencySurplusAllowance,
843
+ uint224 _nativeCurrencyPayoutLimit,
844
+ uint256 _nativePayAmount
845
+ )
846
+ public
847
+ {
848
+ // Make sure the amount of native tokens to pay is bounded.
849
+ _nativePayAmount = bound(_nativePayAmount, 0, 1_000_000 * 10 ** _NATIVE_DECIMALS);
850
+
851
+ // Make sure the values don't overflow the registry.
852
+ unchecked {
853
+ vm.assume(
854
+ _nativeCurrencySurplusAllowance + _nativeCurrencyPayoutLimit >= _nativeCurrencySurplusAllowance
855
+ && _nativeCurrencySurplusAllowance + _nativeCurrencyPayoutLimit >= _nativeCurrencyPayoutLimit
856
+ );
857
+ }
858
+
859
+ // Package up the limits for the given terminal.
860
+ JBFundAccessLimitGroup[] memory _fundAccessLimitGroup = new JBFundAccessLimitGroup[](1);
861
+ {
862
+ // Specify a payout limit.
863
+ JBCurrencyAmount[] memory _payoutLimits = new JBCurrencyAmount[](1);
864
+ _payoutLimits[0] = JBCurrencyAmount({
865
+ amount: _nativeCurrencyPayoutLimit, currency: uint32(uint160(JBConstants.NATIVE_TOKEN))
866
+ });
867
+
868
+ // Specify a surplus allowance.
869
+ JBCurrencyAmount[] memory _surplusAllowances = new JBCurrencyAmount[](1);
870
+ _surplusAllowances[0] = JBCurrencyAmount({
871
+ amount: _nativeCurrencySurplusAllowance, currency: uint32(uint160(JBConstants.NATIVE_TOKEN))
872
+ });
873
+
874
+ _fundAccessLimitGroup[0] = JBFundAccessLimitGroup({
875
+ terminal: address(_terminal),
876
+ token: JBConstants.NATIVE_TOKEN,
877
+ payoutLimits: _payoutLimits,
878
+ surplusAllowances: _surplusAllowances
879
+ });
880
+ }
881
+
882
+ {
883
+ // Package up the ruleset configuration.
884
+ JBRulesetConfig[] memory _rulesetConfigurations = new JBRulesetConfig[](1);
885
+ _rulesetConfigurations[0].mustStartAtOrAfter = 0;
886
+ _rulesetConfigurations[0].duration = 0;
887
+ _rulesetConfigurations[0].weight = _weight;
888
+ _rulesetConfigurations[0].weightCutPercent = 0;
889
+ _rulesetConfigurations[0].approvalHook = IJBRulesetApprovalHook(address(0));
890
+ _rulesetConfigurations[0].metadata = _metadata;
891
+ _rulesetConfigurations[0].splitGroups = new JBSplitGroup[](0);
892
+ _rulesetConfigurations[0].fundAccessLimitGroups = _fundAccessLimitGroup;
893
+
894
+ JBTerminalConfig[] memory _terminalConfigurations = new JBTerminalConfig[](1);
895
+ JBAccountingContext[] memory _tokensToAccept = new JBAccountingContext[](1);
896
+ _tokensToAccept[0] = JBAccountingContext({
897
+ token: JBConstants.NATIVE_TOKEN, decimals: 18, currency: uint32(uint160(JBConstants.NATIVE_TOKEN))
898
+ });
899
+ _terminalConfigurations[0] =
900
+ JBTerminalConfig({terminal: _terminal, accountingContextsToAccept: _tokensToAccept});
901
+
902
+ // Create the project to test.
903
+ _projectId = _controller.launchProjectFor({
904
+ owner: _projectOwner,
905
+ projectUri: "myIPFSHash",
906
+ rulesetConfigurations: _rulesetConfigurations,
907
+ terminalConfigurations: _terminalConfigurations,
908
+ memo: ""
909
+ });
910
+ }
911
+
912
+ // Make a payment to the project to give it a starting balance. Send the tokens to the `_beneficiary`.
913
+ _terminal.pay{value: _nativePayAmount}({
914
+ projectId: _projectId,
915
+ amount: _nativePayAmount,
916
+ token: JBConstants.NATIVE_TOKEN,
917
+ beneficiary: _beneficiary,
918
+ minReturnedTokens: 0,
919
+ memo: "",
920
+ metadata: new bytes(0)
921
+ });
922
+
923
+ // Make sure the beneficiary got the expected number of tokens.
924
+ uint256 _beneficiaryTokenBalance = mulDiv(_nativePayAmount, _weight, 10 ** _NATIVE_DECIMALS)
925
+ * _metadata.reservedPercent / JBConstants.MAX_RESERVED_PERCENT;
926
+ assertEq(_tokens.totalBalanceOf(_beneficiary, _projectId), _beneficiaryTokenBalance);
927
+
928
+ // Make sure the terminal holds the full native token balance.
929
+ assertEq(
930
+ jbTerminalStore().balanceOf(address(_terminal), _projectId, JBConstants.NATIVE_TOKEN), _nativePayAmount
931
+ );
932
+
933
+ // Revert if there's no surplus allowance.
934
+ if (_nativeCurrencySurplusAllowance == 0) {
935
+ vm.expectRevert(
936
+ abi.encodeWithSelector(JBTerminalStore.JBTerminalStore_InadequateControllerAllowance.selector, 0, 0)
937
+ );
938
+ // Revert if there's no surplus, or if too much is being withdrawn.
939
+ } else if (_nativeCurrencySurplusAllowance + _nativeCurrencyPayoutLimit > _nativePayAmount) {
940
+ vm.expectRevert(
941
+ abi.encodeWithSelector(
942
+ JBTerminalStore.JBTerminalStore_InadequateTerminalStoreBalance.selector,
943
+ _nativeCurrencySurplusAllowance,
944
+ _nativeCurrencyPayoutLimit > _nativePayAmount ? 0 : _nativePayAmount - _nativeCurrencyPayoutLimit
945
+ )
946
+ );
947
+ }
948
+
949
+ // Use the full surplus allowance.
950
+ vm.prank(_projectOwner);
951
+ _terminal.useAllowanceOf({
952
+ projectId: _projectId,
953
+ amount: _nativeCurrencySurplusAllowance,
954
+ currency: uint32(uint160(JBConstants.NATIVE_TOKEN)),
955
+ token: JBConstants.NATIVE_TOKEN,
956
+ minTokensPaidOut: 0,
957
+ beneficiary: payable(_beneficiary),
958
+ feeBeneficiary: payable(_projectOwner),
959
+ memo: "MEMO"
960
+ });
961
+
962
+ // Keep a reference to the beneficiary's balance.
963
+ uint256 _beneficiaryNativeBalance;
964
+
965
+ // Check the collected balance if one is expected.
966
+ if (_nativeCurrencySurplusAllowance + _nativeCurrencyPayoutLimit <= _nativePayAmount) {
967
+ // Make sure the beneficiary received the funds and that they are no longer in the terminal.
968
+ _beneficiaryNativeBalance = _nativeCurrencySurplusAllowance
969
+ - mulDiv(_nativeCurrencySurplusAllowance, _terminal.FEE(), JBConstants.MAX_FEE);
970
+ assertEq(_beneficiary.balance, _beneficiaryNativeBalance);
971
+ assertEq(
972
+ jbTerminalStore().balanceOf(address(_terminal), _projectId, JBConstants.NATIVE_TOKEN),
973
+ _nativePayAmount - _beneficiaryNativeBalance
974
+ );
975
+ assertEq(address(_terminal).balance, _nativePayAmount - _beneficiaryNativeBalance);
976
+
977
+ // Make sure the beneficiary got the expected number of tokens.
978
+ assertEq(
979
+ _tokens.totalBalanceOf(_projectOwner, _FEE_PROJECT_ID),
980
+ mulDiv(_nativeCurrencySurplusAllowance - _beneficiaryNativeBalance, _weight, 10 ** _NATIVE_DECIMALS)
981
+ * _metadata.reservedPercent / JBConstants.MAX_RESERVED_PERCENT
982
+ );
983
+ } else {
984
+ // Set the native token surplus allowance to 0 if it wasn't used.
985
+ _nativeCurrencySurplusAllowance = 0;
986
+ }
987
+
988
+ // Revert if the payout limit is greater than the balance.
989
+ if (_nativeCurrencyPayoutLimit > _nativePayAmount) {
990
+ vm.expectRevert(
991
+ abi.encodeWithSelector(
992
+ JBTerminalStore.JBTerminalStore_InadequateTerminalStoreBalance.selector,
993
+ _nativeCurrencyPayoutLimit,
994
+ _nativePayAmount
995
+ )
996
+ );
997
+
998
+ // Revert if there's no payout limit.
999
+ } else if (_nativeCurrencyPayoutLimit == 0) {
1000
+ vm.expectRevert(
1001
+ abi.encodeWithSelector(JBTerminalStore.JBTerminalStore_InadequateControllerPayoutLimit.selector, 0, 0)
1002
+ );
1003
+ }
1004
+
1005
+ // Pay out native tokens up to the payout limit. Since `splits[]` is empty, everything goes to project owner.
1006
+ _terminal.sendPayoutsOf({
1007
+ projectId: _projectId,
1008
+ amount: _nativeCurrencyPayoutLimit,
1009
+ currency: uint32(uint160(JBConstants.NATIVE_TOKEN)),
1010
+ token: JBConstants.NATIVE_TOKEN,
1011
+ minTokensPaidOut: 0
1012
+ });
1013
+
1014
+ uint256 _projectOwnerNativeBalance;
1015
+
1016
+ // Check the received payout if one is expected.
1017
+ if (_nativeCurrencyPayoutLimit <= _nativePayAmount && _nativeCurrencyPayoutLimit != 0) {
1018
+ // Make sure the project owner received the funds that were paid out.
1019
+ _projectOwnerNativeBalance =
1020
+ _nativeCurrencyPayoutLimit - _nativeCurrencyPayoutLimit * _terminal.FEE() / JBConstants.MAX_FEE;
1021
+ assertEq(_projectOwner.balance, _projectOwnerNativeBalance);
1022
+ assertEq(
1023
+ jbTerminalStore().balanceOf(address(_terminal), _projectId, JBConstants.NATIVE_TOKEN),
1024
+ _nativePayAmount - _beneficiaryNativeBalance - _projectOwnerNativeBalance
1025
+ );
1026
+ assertEq(
1027
+ address(_terminal).balance, _nativePayAmount - _beneficiaryNativeBalance - _projectOwnerNativeBalance
1028
+ );
1029
+
1030
+ // Make sure the project owner got the expected number of tokens.
1031
+ assertEq(
1032
+ _tokens.totalBalanceOf(_projectOwner, _FEE_PROJECT_ID),
1033
+ mulDiv(
1034
+ (_nativeCurrencySurplusAllowance - _beneficiaryNativeBalance)
1035
+ + (_nativeCurrencyPayoutLimit - _projectOwnerNativeBalance),
1036
+ _weight,
1037
+ 10 ** _NATIVE_DECIMALS
1038
+ ) * _metadata.reservedPercent / JBConstants.MAX_RESERVED_PERCENT
1039
+ );
1040
+ }
1041
+
1042
+ // Reclaim native tokens from the surplus by cashing out all of the `_beneficiary`'s tokens.
1043
+ vm.prank(_beneficiary);
1044
+ _terminal.cashOutTokensOf({
1045
+ holder: _beneficiary,
1046
+ projectId: _projectId,
1047
+ cashOutCount: _beneficiaryTokenBalance,
1048
+ tokenToReclaim: JBConstants.NATIVE_TOKEN,
1049
+ minTokensReclaimed: 0,
1050
+ beneficiary: payable(_beneficiary),
1051
+ metadata: new bytes(0)
1052
+ });
1053
+
1054
+ // Check for a new beneficiary balance if one is expected.
1055
+ if (_nativePayAmount > _nativeCurrencySurplusAllowance + _nativeCurrencyPayoutLimit) {
1056
+ // Keep a reference to the total amount paid, including from fees.
1057
+ uint256 _totalPaid = _nativePayAmount + (_nativeCurrencySurplusAllowance - _beneficiaryNativeBalance)
1058
+ + (_nativeCurrencyPayoutLimit - _projectOwnerNativeBalance);
1059
+
1060
+ // Get the expected amount reclaimed.
1061
+ uint256 _nativeReclaimAmount = mulDiv(
1062
+ mulDiv(
1063
+ _nativePayAmount - _beneficiaryNativeBalance - _projectOwnerNativeBalance,
1064
+ _beneficiaryTokenBalance,
1065
+ mulDiv(_totalPaid, _weight, 10 ** _NATIVE_DECIMALS)
1066
+ ),
1067
+ _metadata.cashOutTaxRate
1068
+ + mulDiv(
1069
+ _beneficiaryTokenBalance,
1070
+ JBConstants.MAX_CASH_OUT_TAX_RATE - _metadata.cashOutTaxRate,
1071
+ mulDiv(_totalPaid, _weight, 10 ** _NATIVE_DECIMALS)
1072
+ ),
1073
+ JBConstants.MAX_CASH_OUT_TAX_RATE
1074
+ );
1075
+ // Calculate the fee from the cash out.
1076
+ uint256 _feeAmount = _nativeReclaimAmount * _terminal.FEE() / JBConstants.MAX_FEE;
1077
+
1078
+ // Make sure the beneficiary received tokens from the fee just paid.
1079
+ assertEq(
1080
+ _tokens.totalBalanceOf(_beneficiary, _projectId),
1081
+ mulDiv(_feeAmount, _weight, 10 ** _NATIVE_DECIMALS) * _metadata.reservedPercent
1082
+ / JBConstants.MAX_RESERVED_PERCENT
1083
+ );
1084
+
1085
+ // Make sure the beneficiary received the funds.
1086
+ assertEq(_beneficiary.balance, _beneficiaryNativeBalance + _nativeReclaimAmount - _feeAmount);
1087
+
1088
+ assertEq(
1089
+ jbTerminalStore().balanceOf(address(_terminal), _projectId, JBConstants.NATIVE_TOKEN),
1090
+ _nativePayAmount - _beneficiaryNativeBalance - _projectOwnerNativeBalance
1091
+ - (_nativeReclaimAmount - _feeAmount)
1092
+ );
1093
+ assertEq(
1094
+ address(_terminal).balance,
1095
+ _nativePayAmount - _beneficiaryNativeBalance - _projectOwnerNativeBalance
1096
+ - (_nativeReclaimAmount - _feeAmount)
1097
+ );
1098
+ }
1099
+ }
1100
+
1101
+ function testFuzzMultiCurrencyAllowance(
1102
+ uint224 _nativeCurrencySurplusAllowance,
1103
+ uint224 _nativeCurrencyPayoutLimit,
1104
+ uint256 _nativePayAmount,
1105
+ uint224 _usdCurrencySurplusAllowance,
1106
+ uint224 _usdCurrencyPayoutLimit,
1107
+ uint256 _usdcPayAmount
1108
+ )
1109
+ public
1110
+ {
1111
+ // Make sure the amount of native tokens to pay is bounded.
1112
+ _nativePayAmount = bound(_nativePayAmount, 0, 1_000_000 * 10 ** _NATIVE_DECIMALS);
1113
+ _usdcPayAmount = bound(_usdcPayAmount, 0, 1_000_000 * 10 ** _usdcToken.decimals());
1114
+
1115
+ // Make sure the values don't overflow the registry.
1116
+ unchecked {
1117
+ vm.assume(
1118
+ _nativeCurrencySurplusAllowance + _nativeCurrencyPayoutLimit >= _nativeCurrencySurplusAllowance
1119
+ && _nativeCurrencySurplusAllowance + _nativeCurrencyPayoutLimit >= _nativeCurrencyPayoutLimit
1120
+ );
1121
+ vm.assume(
1122
+ _usdCurrencySurplusAllowance + _usdCurrencyPayoutLimit >= _usdCurrencySurplusAllowance
1123
+ && _usdCurrencySurplusAllowance + _usdCurrencyPayoutLimit >= _usdCurrencyPayoutLimit
1124
+ );
1125
+ }
1126
+
1127
+ // Ensure there is always a difference of at least 1 between the pay amount and payout limit.
1128
+ vm.assume(
1129
+ _nativeCurrencyPayoutLimit > _nativePayAmount
1130
+ ? _nativeCurrencyPayoutLimit - _nativePayAmount > 1
1131
+ : _nativePayAmount - _nativeCurrencyPayoutLimit > 1
1132
+ );
1133
+
1134
+ {
1135
+ // Package up the limits for the given terminal.
1136
+ JBFundAccessLimitGroup[] memory _fundAccessLimitGroup = new JBFundAccessLimitGroup[](1);
1137
+
1138
+ // Specify payout limits.
1139
+ JBCurrencyAmount[] memory _payoutLimits = new JBCurrencyAmount[](2);
1140
+ _payoutLimits[0] = JBCurrencyAmount({
1141
+ amount: _nativeCurrencyPayoutLimit, currency: uint32(uint160(JBConstants.NATIVE_TOKEN))
1142
+ });
1143
+ _payoutLimits[1] =
1144
+ JBCurrencyAmount({amount: _usdCurrencyPayoutLimit, currency: uint32(uint160(address(_usdcToken)))});
1145
+
1146
+ // Specify surplus allowances.
1147
+ JBCurrencyAmount[] memory _surplusAllowances = new JBCurrencyAmount[](2);
1148
+ _surplusAllowances[0] = JBCurrencyAmount({
1149
+ amount: _nativeCurrencySurplusAllowance, currency: uint32(uint160(JBConstants.NATIVE_TOKEN))
1150
+ });
1151
+ _surplusAllowances[1] = JBCurrencyAmount({
1152
+ amount: _usdCurrencySurplusAllowance, currency: uint32(uint160(address(_usdcToken)))
1153
+ });
1154
+
1155
+ _fundAccessLimitGroup[0] = JBFundAccessLimitGroup({
1156
+ terminal: address(_terminal),
1157
+ token: JBConstants.NATIVE_TOKEN,
1158
+ payoutLimits: _payoutLimits,
1159
+ surplusAllowances: _surplusAllowances
1160
+ });
1161
+
1162
+ // Package up the ruleset configuration.
1163
+ JBRulesetConfig[] memory _rulesetConfigurations = new JBRulesetConfig[](1);
1164
+ _rulesetConfigurations[0].mustStartAtOrAfter = 0;
1165
+ _rulesetConfigurations[0].duration = 0;
1166
+ _rulesetConfigurations[0].weight = _weight;
1167
+ _rulesetConfigurations[0].weightCutPercent = 0;
1168
+ _rulesetConfigurations[0].approvalHook = IJBRulesetApprovalHook(address(0));
1169
+ _rulesetConfigurations[0].metadata = _metadata;
1170
+ _rulesetConfigurations[0].splitGroups = new JBSplitGroup[](0);
1171
+ _rulesetConfigurations[0].fundAccessLimitGroups = _fundAccessLimitGroup;
1172
+
1173
+ JBTerminalConfig[] memory _terminalConfigurations = new JBTerminalConfig[](1);
1174
+ JBAccountingContext[] memory _tokensToAccept = new JBAccountingContext[](2);
1175
+ _tokensToAccept[0] = JBAccountingContext({
1176
+ token: JBConstants.NATIVE_TOKEN, decimals: 18, currency: uint32(uint160(JBConstants.NATIVE_TOKEN))
1177
+ });
1178
+ _tokensToAccept[1] = JBAccountingContext({
1179
+ token: address(_usdcToken), decimals: 6, currency: uint32(uint160(address(_usdcToken)))
1180
+ });
1181
+ _terminalConfigurations[0] =
1182
+ JBTerminalConfig({terminal: _terminal, accountingContextsToAccept: _tokensToAccept});
1183
+
1184
+ // Create a first project to collect fees.
1185
+ _controller.launchProjectFor({
1186
+ owner: _projectOwner, // Random.
1187
+ projectUri: "whatever",
1188
+ rulesetConfigurations: _rulesetConfigurations, // Use the same ruleset configurations.
1189
+ terminalConfigurations: _terminalConfigurations, // Set terminals to receive fees.
1190
+ memo: ""
1191
+ });
1192
+
1193
+ // Create the project to test.
1194
+ _projectId = _controller.launchProjectFor({
1195
+ owner: _projectOwner,
1196
+ projectUri: "myIPFSHash",
1197
+ rulesetConfigurations: _rulesetConfigurations,
1198
+ terminalConfigurations: _terminalConfigurations,
1199
+ memo: ""
1200
+ });
1201
+ }
1202
+
1203
+ // Add a price feed to convert from native token to USD currencies.
1204
+ {
1205
+ vm.startPrank(_projectOwner);
1206
+ MockPriceFeed _priceFeedNativeUsd = new MockPriceFeed(_USD_PRICE_PER_NATIVE, _PRICE_FEED_DECIMALS);
1207
+ vm.label(address(_priceFeedNativeUsd), "Mock Price Feed Native-USDC");
1208
+
1209
+ _controller.addPriceFeed({
1210
+ projectId: 1,
1211
+ pricingCurrency: uint32(uint160(address(_usdcToken))),
1212
+ unitCurrency: uint32(uint160(JBConstants.NATIVE_TOKEN)),
1213
+ feed: _priceFeedNativeUsd
1214
+ });
1215
+
1216
+ _controller.addPriceFeed({
1217
+ projectId: 2,
1218
+ pricingCurrency: uint32(uint160(address(_usdcToken))),
1219
+ unitCurrency: uint32(uint160(JBConstants.NATIVE_TOKEN)),
1220
+ feed: _priceFeedNativeUsd
1221
+ });
1222
+
1223
+ vm.stopPrank();
1224
+ }
1225
+
1226
+ // Make a payment to the project to give it a starting balance. Send the tokens to the `_beneficiary`.
1227
+ _terminal.pay{value: _nativePayAmount}({
1228
+ projectId: _projectId,
1229
+ amount: _nativePayAmount,
1230
+ token: JBConstants.NATIVE_TOKEN,
1231
+ beneficiary: _beneficiary,
1232
+ minReturnedTokens: 0,
1233
+ memo: "",
1234
+ metadata: new bytes(0)
1235
+ });
1236
+
1237
+ // Make sure the beneficiary got the expected number of tokens from the native token payment.
1238
+ uint256 _beneficiaryTokenBalance = _unreservedPortion(mulDiv(_nativePayAmount, _weight, 10 ** _NATIVE_DECIMALS));
1239
+ assertEq(_tokens.totalBalanceOf(_beneficiary, _projectId), _beneficiaryTokenBalance);
1240
+ // Mint USDC to this contract.
1241
+ _usdcToken.mint(address(this), _usdcPayAmount);
1242
+
1243
+ // Allow the terminal to spend the USDC.
1244
+ _usdcToken.approve(address(_terminal), _usdcPayAmount);
1245
+
1246
+ // Make a payment to the project to give it a starting balance. Send the tokens to the `_beneficiary`.
1247
+ _terminal.pay({
1248
+ projectId: _projectId,
1249
+ amount: _usdcPayAmount,
1250
+ token: address(_usdcToken),
1251
+ beneficiary: _beneficiary,
1252
+ minReturnedTokens: 0,
1253
+ memo: "",
1254
+ metadata: new bytes(0)
1255
+ });
1256
+
1257
+ // Make sure the terminal holds the full native token balance.
1258
+ assertEq(
1259
+ jbTerminalStore().balanceOf(address(_terminal), _projectId, JBConstants.NATIVE_TOKEN), _nativePayAmount
1260
+ );
1261
+ // Make sure the USDC is accounted for.
1262
+ assertEq(jbTerminalStore().balanceOf(address(_terminal), _projectId, address(_usdcToken)), _usdcPayAmount);
1263
+ assertEq(_usdcToken.balanceOf(address(_terminal)), _usdcPayAmount);
1264
+
1265
+ {
1266
+ // Convert the USD amount to a native token amount, by way of the current weight used for issuance.
1267
+ uint256 _usdWeightedPayAmountConvertedToNative = mulDiv(
1268
+ _usdcPayAmount,
1269
+ _weight,
1270
+ mulDiv(_USD_PRICE_PER_NATIVE, 10 ** _usdcToken.decimals(), 10 ** _PRICE_FEED_DECIMALS)
1271
+ );
1272
+
1273
+ // Make sure the beneficiary got the expected number of tokens from the USDC payment.
1274
+ _beneficiaryTokenBalance += _unreservedPortion(_usdWeightedPayAmountConvertedToNative);
1275
+ assertEq(_tokens.totalBalanceOf(_beneficiary, _projectId), _beneficiaryTokenBalance);
1276
+ }
1277
+
1278
+ // Revert if there's no native token allowance.
1279
+ if (_nativeCurrencySurplusAllowance == 0) {
1280
+ vm.expectRevert(
1281
+ abi.encodeWithSelector(JBTerminalStore.JBTerminalStore_InadequateControllerAllowance.selector, 0, 0)
1282
+ );
1283
+ } else if (
1284
+ _nativeCurrencySurplusAllowance + _nativeCurrencyPayoutLimit + _toNative(_usdCurrencyPayoutLimit)
1285
+ > _nativePayAmount
1286
+ ) {
1287
+ vm.expectRevert(
1288
+ abi.encodeWithSelector(
1289
+ JBTerminalStore.JBTerminalStore_InadequateTerminalStoreBalance.selector,
1290
+ _nativeCurrencySurplusAllowance,
1291
+ _nativeCurrencyPayoutLimit + _toNative(_usdCurrencyPayoutLimit) > _nativePayAmount
1292
+ ? 0
1293
+ : _nativePayAmount - _nativeCurrencyPayoutLimit - _toNative(_usdCurrencyPayoutLimit)
1294
+ )
1295
+ );
1296
+ }
1297
+
1298
+ // Use the full native token surplus allowance.
1299
+ vm.prank(_projectOwner);
1300
+ _terminal.useAllowanceOf({
1301
+ projectId: _projectId,
1302
+ amount: _nativeCurrencySurplusAllowance,
1303
+ currency: uint32(uint160(JBConstants.NATIVE_TOKEN)),
1304
+ token: JBConstants.NATIVE_TOKEN,
1305
+ minTokensPaidOut: 0,
1306
+ beneficiary: payable(_beneficiary),
1307
+ feeBeneficiary: payable(_projectOwner),
1308
+ memo: "MEMO"
1309
+ });
1310
+
1311
+ // Keep a reference to the beneficiary's native token balance.
1312
+ uint256 _beneficiaryNativeBalance;
1313
+
1314
+ // Check the collected balance if one is expected.
1315
+ if (
1316
+ _nativeCurrencySurplusAllowance + _nativeCurrencyPayoutLimit + _toNative(_usdCurrencyPayoutLimit)
1317
+ <= _nativePayAmount
1318
+ ) {
1319
+ // Make sure the beneficiary received the funds and that they are no longer in the terminal.
1320
+ _beneficiaryNativeBalance = _nativeCurrencySurplusAllowance
1321
+ - mulDiv(_nativeCurrencySurplusAllowance, _terminal.FEE(), JBConstants.MAX_FEE);
1322
+ assertEq(_beneficiary.balance, _beneficiaryNativeBalance);
1323
+ assertEq(
1324
+ jbTerminalStore().balanceOf(address(_terminal), _projectId, JBConstants.NATIVE_TOKEN),
1325
+ _nativePayAmount - _nativeCurrencySurplusAllowance
1326
+ );
1327
+
1328
+ // Make sure the fee was paid correctly.
1329
+ assertEq(
1330
+ jbTerminalStore().balanceOf(address(_terminal), _FEE_PROJECT_ID, JBConstants.NATIVE_TOKEN),
1331
+ _nativeCurrencySurplusAllowance - _beneficiaryNativeBalance
1332
+ );
1333
+ assertEq(address(_terminal).balance, _nativePayAmount - _beneficiaryNativeBalance);
1334
+
1335
+ // Make sure the beneficiary got the expected number of tokens.
1336
+ assertEq(
1337
+ _tokens.totalBalanceOf(_projectOwner, _FEE_PROJECT_ID),
1338
+ _unreservedPortion(
1339
+ mulDiv(_nativeCurrencySurplusAllowance - _beneficiaryNativeBalance, _weight, 10 ** _NATIVE_DECIMALS)
1340
+ )
1341
+ );
1342
+ } else {
1343
+ // Set the native token surplus allowance to 0 if it wasn't used.
1344
+ _nativeCurrencySurplusAllowance = 0;
1345
+ }
1346
+
1347
+ // Revert if there's no native token allowance.
1348
+ if (_usdCurrencySurplusAllowance == 0) {
1349
+ vm.expectRevert(
1350
+ abi.encodeWithSelector(JBTerminalStore.JBTerminalStore_InadequateControllerAllowance.selector, 0, 0)
1351
+ );
1352
+ // revert if the USD surplus allowance resolved to native tokens is greater than 0, and there is sufficient
1353
+ // surplus to pull from including what was already pulled from.
1354
+ } else if (
1355
+ _toNative(_usdCurrencySurplusAllowance) > 0
1356
+ && _toNative(_usdCurrencySurplusAllowance + _usdCurrencyPayoutLimit) + _nativeCurrencyPayoutLimit
1357
+ + _nativeCurrencySurplusAllowance > _nativePayAmount
1358
+ ) {
1359
+ vm.expectRevert(
1360
+ abi.encodeWithSelector(
1361
+ JBTerminalStore.JBTerminalStore_InadequateTerminalStoreBalance.selector,
1362
+ _toNative(_usdCurrencySurplusAllowance),
1363
+ _toNative(_usdCurrencyPayoutLimit) + _nativeCurrencyPayoutLimit + _nativeCurrencySurplusAllowance
1364
+ > _nativePayAmount
1365
+ ? 0
1366
+ : _nativePayAmount - _toNative(_usdCurrencyPayoutLimit) - _nativeCurrencyPayoutLimit
1367
+ - _nativeCurrencySurplusAllowance
1368
+ )
1369
+ );
1370
+ }
1371
+
1372
+ // Use the full native token surplus allowance.
1373
+ vm.prank(_projectOwner);
1374
+ _terminal.useAllowanceOf({
1375
+ projectId: _projectId,
1376
+ amount: _usdCurrencySurplusAllowance,
1377
+ currency: uint32(uint160(address(_usdcToken))),
1378
+ token: JBConstants.NATIVE_TOKEN,
1379
+ minTokensPaidOut: 0,
1380
+ beneficiary: payable(_beneficiary),
1381
+ feeBeneficiary: payable(_projectOwner),
1382
+ memo: "MEMO"
1383
+ });
1384
+
1385
+ // Check the collected balance if one is expected.
1386
+ if (
1387
+ _nativeCurrencySurplusAllowance + _nativeCurrencyPayoutLimit
1388
+ + _toNative(_usdCurrencySurplusAllowance + _usdCurrencyPayoutLimit) <= _nativePayAmount
1389
+ ) {
1390
+ // Make sure the beneficiary received the funds and that they are no longer in the terminal.
1391
+ _beneficiaryNativeBalance += _toNative(_usdCurrencySurplusAllowance)
1392
+ - mulDiv(_toNative(_usdCurrencySurplusAllowance), _terminal.FEE(), JBConstants.MAX_FEE);
1393
+ assertEq(_beneficiary.balance, _beneficiaryNativeBalance);
1394
+ assertEq(
1395
+ jbTerminalStore().balanceOf(address(_terminal), _projectId, JBConstants.NATIVE_TOKEN),
1396
+ _nativePayAmount - _nativeCurrencySurplusAllowance - _toNative(_usdCurrencySurplusAllowance)
1397
+ );
1398
+
1399
+ // Make sure the fee was paid correctly.
1400
+ assertEq(
1401
+ jbTerminalStore().balanceOf(address(_terminal), _FEE_PROJECT_ID, JBConstants.NATIVE_TOKEN),
1402
+ _nativeCurrencySurplusAllowance + _toNative(_usdCurrencySurplusAllowance) - _beneficiaryNativeBalance
1403
+ );
1404
+ assertEq(address(_terminal).balance, _nativePayAmount - _beneficiaryNativeBalance);
1405
+
1406
+ // Make sure the beneficiary got the expected number of tokens.
1407
+ assertEq(
1408
+ _tokens.totalBalanceOf(_projectOwner, _FEE_PROJECT_ID),
1409
+ _unreservedPortion(
1410
+ mulDiv(
1411
+ _nativeCurrencySurplusAllowance + _toNative(_usdCurrencySurplusAllowance)
1412
+ - _beneficiaryNativeBalance,
1413
+ _weight,
1414
+ 10 ** _NATIVE_DECIMALS
1415
+ )
1416
+ )
1417
+ );
1418
+ } else {
1419
+ // Set the native token surplus allowance to 0 if it wasn't used.
1420
+ _usdCurrencySurplusAllowance = 0;
1421
+ }
1422
+
1423
+ // Payout limits
1424
+ {
1425
+ // Revert if the payout limit is greater than the balance.
1426
+ if (_nativeCurrencyPayoutLimit > _nativePayAmount) {
1427
+ vm.expectRevert(
1428
+ abi.encodeWithSelector(
1429
+ JBTerminalStore.JBTerminalStore_InadequateTerminalStoreBalance.selector,
1430
+ _nativeCurrencyPayoutLimit,
1431
+ _nativePayAmount
1432
+ )
1433
+ );
1434
+ // Revert if there's no payout limit.
1435
+ } else if (_nativeCurrencyPayoutLimit == 0) {
1436
+ vm.expectRevert(
1437
+ abi.encodeWithSelector(
1438
+ JBTerminalStore.JBTerminalStore_InadequateControllerPayoutLimit.selector, 0, 0
1439
+ )
1440
+ );
1441
+ }
1442
+
1443
+ // Pay out native tokens up to the payout limit. Since `splits[]` is empty, everything goes to project
1444
+ // owner.
1445
+ _terminal.sendPayoutsOf({
1446
+ projectId: _projectId,
1447
+ amount: _nativeCurrencyPayoutLimit,
1448
+ currency: uint32(uint160(JBConstants.NATIVE_TOKEN)),
1449
+ token: JBConstants.NATIVE_TOKEN,
1450
+ minTokensPaidOut: 0
1451
+ });
1452
+
1453
+ uint256 _projectOwnerNativeBalance;
1454
+
1455
+ // Check the received payout if one is expected.
1456
+ if (_nativeCurrencyPayoutLimit <= _nativePayAmount && _nativeCurrencyPayoutLimit != 0) {
1457
+ // Make sure the project owner received the funds that were paid out.
1458
+ _projectOwnerNativeBalance =
1459
+ _nativeCurrencyPayoutLimit - _nativeCurrencyPayoutLimit * _terminal.FEE() / JBConstants.MAX_FEE;
1460
+ assertEq(_projectOwner.balance, _projectOwnerNativeBalance);
1461
+ assertEq(
1462
+ jbTerminalStore().balanceOf(address(_terminal), _projectId, JBConstants.NATIVE_TOKEN),
1463
+ _nativePayAmount - _nativeCurrencySurplusAllowance - _toNative(_usdCurrencySurplusAllowance)
1464
+ - _nativeCurrencyPayoutLimit
1465
+ );
1466
+
1467
+ // Make sure the fee was paid correctly.
1468
+ assertEq(
1469
+ jbTerminalStore().balanceOf(address(_terminal), _FEE_PROJECT_ID, JBConstants.NATIVE_TOKEN),
1470
+ _nativeCurrencySurplusAllowance + _toNative(_usdCurrencySurplusAllowance)
1471
+ - _beneficiaryNativeBalance + _nativeCurrencyPayoutLimit - _projectOwnerNativeBalance
1472
+ );
1473
+ assertEq(
1474
+ address(_terminal).balance,
1475
+ _nativePayAmount - _beneficiaryNativeBalance - _projectOwnerNativeBalance
1476
+ );
1477
+
1478
+ uint256 _fullPortion = mulDiv(
1479
+ _nativeCurrencySurplusAllowance + _toNative(_usdCurrencySurplusAllowance)
1480
+ - _beneficiaryNativeBalance + _nativeCurrencyPayoutLimit - _projectOwnerNativeBalance,
1481
+ _weight,
1482
+ 10 ** _NATIVE_DECIMALS
1483
+ );
1484
+
1485
+ // Make sure the project owner got the expected number of tokens.
1486
+ assertEq(_tokens.totalBalanceOf(_projectOwner, _FEE_PROJECT_ID), _unreservedPortion(_fullPortion));
1487
+ }
1488
+
1489
+ // Revert if the payout limit is greater than the balance.
1490
+ if (
1491
+ _nativeCurrencyPayoutLimit <= _nativePayAmount
1492
+ && _toNative(_usdCurrencyPayoutLimit) + _nativeCurrencyPayoutLimit > _nativePayAmount
1493
+ ) {
1494
+ vm.expectRevert(
1495
+ abi.encodeWithSelector(
1496
+ JBTerminalStore.JBTerminalStore_InadequateTerminalStoreBalance.selector,
1497
+ _toNative(_usdCurrencyPayoutLimit),
1498
+ _nativeCurrencyPayoutLimit > _nativePayAmount
1499
+ ? _nativePayAmount
1500
+ : _nativePayAmount - _nativeCurrencyPayoutLimit
1501
+ )
1502
+ );
1503
+ } else if (
1504
+ _nativeCurrencyPayoutLimit > _nativePayAmount && _toNative(_usdCurrencyPayoutLimit) > _nativePayAmount
1505
+ ) {
1506
+ vm.expectRevert(
1507
+ abi.encodeWithSelector(
1508
+ JBTerminalStore.JBTerminalStore_InadequateTerminalStoreBalance.selector,
1509
+ _toNative(_usdCurrencyPayoutLimit),
1510
+ _nativePayAmount
1511
+ )
1512
+ );
1513
+ // Revert if there's no payout limit.
1514
+ } else if (_usdCurrencyPayoutLimit == 0) {
1515
+ vm.expectRevert(
1516
+ abi.encodeWithSelector(
1517
+ JBTerminalStore.JBTerminalStore_InadequateControllerPayoutLimit.selector, 0, 0
1518
+ )
1519
+ );
1520
+ }
1521
+
1522
+ // Pay out usdc tokens up to the payout limit. Since `splits[]` is empty, everything goes to project
1523
+ // owner.
1524
+ _terminal.sendPayoutsOf({
1525
+ projectId: _projectId,
1526
+ amount: _usdCurrencyPayoutLimit,
1527
+ currency: uint32(uint160(address(_usdcToken))),
1528
+ token: JBConstants.NATIVE_TOKEN,
1529
+ minTokensPaidOut: 0
1530
+ });
1531
+
1532
+ // Check the received payout if one is expected.
1533
+ if (
1534
+ _toNative(_usdCurrencyPayoutLimit) + _nativeCurrencyPayoutLimit <= _nativePayAmount
1535
+ && _usdCurrencyPayoutLimit > 0
1536
+ ) {
1537
+ // Make sure the project owner received the funds that were paid out.
1538
+ _projectOwnerNativeBalance += _toNative(_usdCurrencyPayoutLimit) - _toNative(_usdCurrencyPayoutLimit)
1539
+ * _terminal.FEE() / JBConstants.MAX_FEE;
1540
+ assertEq(_projectOwner.balance, _projectOwnerNativeBalance);
1541
+ assertEq(
1542
+ jbTerminalStore().balanceOf(address(_terminal), _projectId, JBConstants.NATIVE_TOKEN),
1543
+ _nativePayAmount - _nativeCurrencySurplusAllowance - _toNative(_usdCurrencySurplusAllowance)
1544
+ - _nativeCurrencyPayoutLimit - _toNative(_usdCurrencyPayoutLimit)
1545
+ );
1546
+
1547
+ // Make sure the fee was paid correctly.
1548
+ assertEq(
1549
+ jbTerminalStore().balanceOf(address(_terminal), _FEE_PROJECT_ID, JBConstants.NATIVE_TOKEN),
1550
+ (_nativeCurrencySurplusAllowance
1551
+ + _toNative(_usdCurrencySurplusAllowance)
1552
+ - _beneficiaryNativeBalance)
1553
+ + (_nativeCurrencyPayoutLimit + _toNative(_usdCurrencyPayoutLimit) - _projectOwnerNativeBalance)
1554
+ );
1555
+ assertEq(
1556
+ address(_terminal).balance,
1557
+ _nativePayAmount - _beneficiaryNativeBalance - _projectOwnerNativeBalance
1558
+ );
1559
+ }
1560
+ }
1561
+
1562
+ // Keep a reference to the remaining native token surplus.
1563
+ uint256 _nativeSurplus = _nativeCurrencyPayoutLimit + _toNative(_usdCurrencyPayoutLimit)
1564
+ + _nativeCurrencySurplusAllowance + _toNative(_usdCurrencySurplusAllowance) >= _nativePayAmount
1565
+ ? 0
1566
+ : _nativePayAmount - _nativeCurrencyPayoutLimit - _toNative(_usdCurrencyPayoutLimit)
1567
+ - _nativeCurrencySurplusAllowance - _toNative(_usdCurrencySurplusAllowance);
1568
+
1569
+ // Keep a reference to the remaining native token balance.
1570
+ uint256 _nativeBalance =
1571
+ _nativePayAmount - _nativeCurrencySurplusAllowance - _toNative(_usdCurrencySurplusAllowance);
1572
+ if (_nativeCurrencyPayoutLimit <= _nativePayAmount) {
1573
+ _nativeBalance -= _nativeCurrencyPayoutLimit;
1574
+ if (_toNative(_usdCurrencyPayoutLimit) + _nativeCurrencyPayoutLimit <= _nativePayAmount) {
1575
+ _nativeBalance -= _toNative(_usdCurrencyPayoutLimit);
1576
+ }
1577
+ } else if (_toNative(_usdCurrencyPayoutLimit) <= _nativePayAmount) {
1578
+ _nativeBalance -= _toNative(_usdCurrencyPayoutLimit);
1579
+ }
1580
+
1581
+ // Make sure it's correct.
1582
+ assertEq(jbTerminalStore().balanceOf(address(_terminal), _projectId, JBConstants.NATIVE_TOKEN), _nativeBalance);
1583
+
1584
+ // Make sure the USDC surplus is correct.
1585
+ assertEq(jbTerminalStore().balanceOf(address(_terminal), _projectId, address(_usdcToken)), _usdcPayAmount);
1586
+
1587
+ // Make sure the total token supply is correct.
1588
+ assertEq(
1589
+ _controller.totalTokenSupplyWithReservedTokensOf(_projectId),
1590
+ mulDiv(
1591
+ _beneficiaryTokenBalance,
1592
+ JBConstants.MAX_RESERVED_PERCENT,
1593
+ JBConstants.MAX_RESERVED_PERCENT - _metadata.reservedPercent
1594
+ )
1595
+ );
1596
+
1597
+ // Keep a reference to the amount of native tokens being reclaimed.
1598
+ uint256 _nativeReclaimAmount;
1599
+
1600
+ vm.startPrank(_beneficiary);
1601
+
1602
+ // If there's surplus.
1603
+ if (_toNative(mulDiv(_usdcPayAmount, 10 ** _NATIVE_DECIMALS, 10 ** _usdcToken.decimals())) + _nativeSurplus > 0)
1604
+ {
1605
+ // Get the expected amount reclaimed.
1606
+ _nativeReclaimAmount = mulDiv(
1607
+ mulDiv(
1608
+ _toNative(mulDiv(_usdcPayAmount, 10 ** _NATIVE_DECIMALS, 10 ** _usdcToken.decimals()))
1609
+ + _nativeSurplus,
1610
+ _beneficiaryTokenBalance,
1611
+ mulDiv(
1612
+ _beneficiaryTokenBalance,
1613
+ JBConstants.MAX_RESERVED_PERCENT,
1614
+ JBConstants.MAX_RESERVED_PERCENT - _metadata.reservedPercent
1615
+ )
1616
+ ),
1617
+ _metadata.cashOutTaxRate
1618
+ + mulDiv(
1619
+ _beneficiaryTokenBalance,
1620
+ JBConstants.MAX_CASH_OUT_TAX_RATE - _metadata.cashOutTaxRate,
1621
+ mulDiv(
1622
+ _beneficiaryTokenBalance,
1623
+ JBConstants.MAX_RESERVED_PERCENT,
1624
+ JBConstants.MAX_RESERVED_PERCENT - _metadata.reservedPercent
1625
+ )
1626
+ ),
1627
+ JBConstants.MAX_CASH_OUT_TAX_RATE
1628
+ );
1629
+
1630
+ // If there is more to reclaim than there are native tokens in the tank.
1631
+ if (_nativeReclaimAmount > _nativeSurplus) {
1632
+ // Keep a reference to the amount to cash out for native tokens, a proportion of available surplus in
1633
+ // native tokens.
1634
+ uint256 _tokenCountToCashOutForNative = mulDiv(
1635
+ _beneficiaryTokenBalance,
1636
+ _nativeSurplus,
1637
+ _nativeSurplus
1638
+ + _toNative(mulDiv(_usdcPayAmount, 10 ** _NATIVE_DECIMALS, 10 ** _usdcToken.decimals()))
1639
+ );
1640
+ uint256 _tokenSupply = mulDiv(
1641
+ _beneficiaryTokenBalance,
1642
+ JBConstants.MAX_RESERVED_PERCENT,
1643
+ JBConstants.MAX_RESERVED_PERCENT - _metadata.reservedPercent
1644
+ );
1645
+ // Cash out native tokens from the surplus using only the `_beneficiary`'s tokens needed to clear the
1646
+ // native token balance.
1647
+ _terminal.cashOutTokensOf({
1648
+ holder: _beneficiary,
1649
+ projectId: _projectId,
1650
+ cashOutCount: _tokenCountToCashOutForNative,
1651
+ tokenToReclaim: JBConstants.NATIVE_TOKEN,
1652
+ minTokensReclaimed: 0,
1653
+ beneficiary: payable(_beneficiary),
1654
+ metadata: new bytes(0)
1655
+ });
1656
+
1657
+ // Cash out USDC from the surplus using only the `_beneficiary`'s tokens needed to clear the USDC
1658
+ // balance.
1659
+ _terminal.cashOutTokensOf({
1660
+ holder: _beneficiary,
1661
+ projectId: _projectId,
1662
+ cashOutCount: _beneficiaryTokenBalance - _tokenCountToCashOutForNative,
1663
+ tokenToReclaim: address(_usdcToken),
1664
+ minTokensReclaimed: 0,
1665
+ beneficiary: payable(_beneficiary),
1666
+ metadata: new bytes(0)
1667
+ });
1668
+
1669
+ _nativeReclaimAmount = mulDiv(
1670
+ mulDiv(
1671
+ _toNative(mulDiv(_usdcPayAmount, 10 ** _NATIVE_DECIMALS, 10 ** _usdcToken.decimals()))
1672
+ + _nativeSurplus,
1673
+ _tokenCountToCashOutForNative,
1674
+ _tokenSupply
1675
+ ),
1676
+ _metadata.cashOutTaxRate
1677
+ + mulDiv(
1678
+ _tokenCountToCashOutForNative,
1679
+ JBConstants.MAX_CASH_OUT_TAX_RATE - _metadata.cashOutTaxRate,
1680
+ _tokenSupply
1681
+ ),
1682
+ JBConstants.MAX_CASH_OUT_TAX_RATE
1683
+ );
1684
+
1685
+ uint256 _usdcReclaimAmount = mulDiv(
1686
+ mulDiv(
1687
+ _usdcPayAmount
1688
+ + _toUsd(
1689
+ mulDiv(
1690
+ _nativeSurplus - _nativeReclaimAmount,
1691
+ 10 ** _usdcToken.decimals(),
1692
+ 10 ** _NATIVE_DECIMALS
1693
+ )
1694
+ ),
1695
+ _beneficiaryTokenBalance - _tokenCountToCashOutForNative,
1696
+ _tokenSupply - _tokenCountToCashOutForNative
1697
+ ),
1698
+ _metadata.cashOutTaxRate
1699
+ + mulDiv(
1700
+ _beneficiaryTokenBalance - _tokenCountToCashOutForNative,
1701
+ JBConstants.MAX_CASH_OUT_TAX_RATE - _metadata.cashOutTaxRate,
1702
+ _tokenSupply - _tokenCountToCashOutForNative
1703
+ ),
1704
+ JBConstants.MAX_CASH_OUT_TAX_RATE
1705
+ );
1706
+
1707
+ assertEq(
1708
+ jbTerminalStore().balanceOf(address(_terminal), _projectId, address(_usdcToken)),
1709
+ _usdcPayAmount - _usdcReclaimAmount
1710
+ );
1711
+
1712
+ uint256 _usdcFeeAmount = _usdcReclaimAmount * _terminal.FEE() / JBConstants.MAX_FEE;
1713
+ assertEq(_usdcToken.balanceOf(_beneficiary), _usdcReclaimAmount - _usdcFeeAmount);
1714
+
1715
+ // Make sure the fee was paid correctly.
1716
+ assertEq(
1717
+ jbTerminalStore().balanceOf(address(_terminal), _FEE_PROJECT_ID, address(_usdcToken)),
1718
+ _usdcFeeAmount
1719
+ );
1720
+ assertEq(_usdcToken.balanceOf(address(_terminal)), _usdcPayAmount - _usdcReclaimAmount + _usdcFeeAmount);
1721
+ } else {
1722
+ // Reclaim native tokens from the surplus by cashing out all of the `_beneficiary`'s tokens.
1723
+ _terminal.cashOutTokensOf({
1724
+ holder: _beneficiary,
1725
+ projectId: _projectId,
1726
+ cashOutCount: _beneficiaryTokenBalance,
1727
+ tokenToReclaim: JBConstants.NATIVE_TOKEN,
1728
+ minTokensReclaimed: 0,
1729
+ beneficiary: payable(_beneficiary),
1730
+ metadata: new bytes(0)
1731
+ });
1732
+ }
1733
+ // Burn the tokens.
1734
+ } else {
1735
+ _terminal.cashOutTokensOf({
1736
+ holder: _beneficiary,
1737
+ projectId: _projectId,
1738
+ cashOutCount: _beneficiaryTokenBalance,
1739
+ tokenToReclaim: address(_usdcToken),
1740
+ minTokensReclaimed: 0,
1741
+ beneficiary: payable(_beneficiary),
1742
+ metadata: new bytes(0)
1743
+ });
1744
+ }
1745
+ vm.stopPrank();
1746
+
1747
+ // Make sure the balance is adjusted by the reclaim amount.
1748
+ assertEq(
1749
+ jbTerminalStore().balanceOf(address(_terminal), _projectId, JBConstants.NATIVE_TOKEN),
1750
+ _nativeBalance - _nativeReclaimAmount
1751
+ );
1752
+ }
1753
+
1754
+ // Project 2 accepts native tokens into `_terminal` and USDC into `_terminal2`.
1755
+ // Project 1 accepts USDC and native token fees into `_terminal`.
1756
+ function testFuzzMultiTerminalAllowance(
1757
+ uint224 _nativeCurrencySurplusAllowance,
1758
+ uint224 _nativeCurrencyPayoutLimit,
1759
+ uint256 _nativePayAmount,
1760
+ uint224 _usdCurrencySurplusAllowance,
1761
+ uint224 _usdCurrencyPayoutLimit,
1762
+ uint256 _usdcPayAmount
1763
+ )
1764
+ public
1765
+ {
1766
+ // Make sure the amount of native tokens to pay is bounded.
1767
+ _nativePayAmount = bound(_nativePayAmount, 0, 1_000_000 * 10 ** _NATIVE_DECIMALS);
1768
+ _usdcPayAmount = bound(_usdcPayAmount, 0, 1_000_000 * 10 ** _usdcToken.decimals());
1769
+ _usdCurrencyPayoutLimit = uint224(
1770
+ bound(_usdCurrencyPayoutLimit, 0, type(uint224).max / 10 ** (_NATIVE_DECIMALS - _usdcToken.decimals()))
1771
+ );
1772
+
1773
+ // Make sure the values don't overflow the registry.
1774
+ unchecked {
1775
+ vm.assume(
1776
+ _nativeCurrencySurplusAllowance + _nativeCurrencyPayoutLimit >= _nativeCurrencySurplusAllowance
1777
+ && _nativeCurrencySurplusAllowance + _nativeCurrencyPayoutLimit >= _nativeCurrencyPayoutLimit
1778
+ );
1779
+ vm.assume(
1780
+ _usdCurrencySurplusAllowance + _usdCurrencyPayoutLimit >= _usdCurrencySurplusAllowance
1781
+ && _usdCurrencySurplusAllowance + _usdCurrencyPayoutLimit >= _usdCurrencyPayoutLimit
1782
+ );
1783
+ }
1784
+
1785
+ {
1786
+ // Package up the limits for the given terminal.
1787
+ JBFundAccessLimitGroup[] memory _fundAccessLimitGroup = new JBFundAccessLimitGroup[](2);
1788
+
1789
+ // Specify payout limits.
1790
+ JBCurrencyAmount[] memory _payoutLimits1 = new JBCurrencyAmount[](1);
1791
+ JBCurrencyAmount[] memory _payoutLimits2 = new JBCurrencyAmount[](1);
1792
+ _payoutLimits1[0] = JBCurrencyAmount({
1793
+ amount: _nativeCurrencyPayoutLimit, currency: uint32(uint160(JBConstants.NATIVE_TOKEN))
1794
+ });
1795
+ _payoutLimits2[0] =
1796
+ JBCurrencyAmount({amount: _usdCurrencyPayoutLimit, currency: uint32(uint160(address(_usdcToken)))});
1797
+
1798
+ // Specify surplus allowances.
1799
+ JBCurrencyAmount[] memory _surplusAllowances1 = new JBCurrencyAmount[](1);
1800
+ JBCurrencyAmount[] memory _surplusAllowances2 = new JBCurrencyAmount[](1);
1801
+ _surplusAllowances1[0] = JBCurrencyAmount({
1802
+ amount: _nativeCurrencySurplusAllowance, currency: uint32(uint160(JBConstants.NATIVE_TOKEN))
1803
+ });
1804
+ _surplusAllowances2[0] = JBCurrencyAmount({
1805
+ amount: _usdCurrencySurplusAllowance, currency: uint32(uint160(address(_usdcToken)))
1806
+ });
1807
+
1808
+ _fundAccessLimitGroup[0] = JBFundAccessLimitGroup({
1809
+ terminal: address(_terminal),
1810
+ token: JBConstants.NATIVE_TOKEN,
1811
+ payoutLimits: _payoutLimits1,
1812
+ surplusAllowances: _surplusAllowances1
1813
+ });
1814
+
1815
+ _fundAccessLimitGroup[1] = JBFundAccessLimitGroup({
1816
+ terminal: address(_terminal2),
1817
+ token: address(_usdcToken),
1818
+ payoutLimits: _payoutLimits2,
1819
+ surplusAllowances: _surplusAllowances2
1820
+ });
1821
+
1822
+ // Package up the ruleset configuration.
1823
+ JBRulesetConfig[] memory _rulesetConfigurations = new JBRulesetConfig[](1);
1824
+ _rulesetConfigurations[0].mustStartAtOrAfter = 0;
1825
+ _rulesetConfigurations[0].duration = 0;
1826
+ _rulesetConfigurations[0].weight = _weight;
1827
+ _rulesetConfigurations[0].weightCutPercent = 0;
1828
+ _rulesetConfigurations[0].approvalHook = IJBRulesetApprovalHook(address(0));
1829
+ _rulesetConfigurations[0].metadata = _metadata;
1830
+ _rulesetConfigurations[0].splitGroups = new JBSplitGroup[](0);
1831
+ _rulesetConfigurations[0].fundAccessLimitGroups = _fundAccessLimitGroup;
1832
+
1833
+ JBTerminalConfig[] memory _terminalConfigurations1 = new JBTerminalConfig[](1);
1834
+ JBTerminalConfig[] memory _terminalConfigurations2 = new JBTerminalConfig[](2);
1835
+ JBAccountingContext[] memory _tokensToAccept1 = new JBAccountingContext[](2);
1836
+ _tokensToAccept1[0] = JBAccountingContext({
1837
+ token: JBConstants.NATIVE_TOKEN, decimals: 18, currency: uint32(uint160(JBConstants.NATIVE_TOKEN))
1838
+ });
1839
+ _tokensToAccept1[1] = JBAccountingContext({
1840
+ token: address(_usdcToken), decimals: 6, currency: uint32(uint160(address(_usdcToken)))
1841
+ });
1842
+
1843
+ JBAccountingContext[] memory _tokensToAccept2 = new JBAccountingContext[](1);
1844
+ _tokensToAccept2[0] = JBAccountingContext({
1845
+ token: JBConstants.NATIVE_TOKEN, decimals: 18, currency: uint32(uint160(JBConstants.NATIVE_TOKEN))
1846
+ });
1847
+
1848
+ JBAccountingContext[] memory _tokensToAccept3 = new JBAccountingContext[](1);
1849
+ _tokensToAccept3[0] = JBAccountingContext({
1850
+ token: address(_usdcToken), decimals: 6, currency: uint32(uint160(address(_usdcToken)))
1851
+ });
1852
+
1853
+ // Fee takes USDC and native token in same terminal.
1854
+ _terminalConfigurations1[0] =
1855
+ JBTerminalConfig({terminal: _terminal, accountingContextsToAccept: _tokensToAccept1});
1856
+ _terminalConfigurations2[0] =
1857
+ JBTerminalConfig({terminal: _terminal, accountingContextsToAccept: _tokensToAccept2});
1858
+ _terminalConfigurations2[1] =
1859
+ JBTerminalConfig({terminal: _terminal2, accountingContextsToAccept: _tokensToAccept3});
1860
+
1861
+ // Create a first project to collect fees.
1862
+ _controller.launchProjectFor({
1863
+ owner: _projectOwner, // Random.
1864
+ projectUri: "whatever",
1865
+ rulesetConfigurations: _rulesetConfigurations, // Use the same ruleset configurations.
1866
+ terminalConfigurations: _terminalConfigurations1, // Set terminals to receive fees.
1867
+ memo: ""
1868
+ });
1869
+
1870
+ // Create the project to test.
1871
+ _projectId = _controller.launchProjectFor({
1872
+ owner: _projectOwner,
1873
+ projectUri: "myIPFSHash",
1874
+ rulesetConfigurations: _rulesetConfigurations,
1875
+ terminalConfigurations: _terminalConfigurations2,
1876
+ memo: ""
1877
+ });
1878
+ }
1879
+
1880
+ // Add a price feed to convert from native token to USD currencies.
1881
+ {
1882
+ vm.startPrank(_projectOwner);
1883
+ MockPriceFeed _priceFeedNativeUsd = new MockPriceFeed(_USD_PRICE_PER_NATIVE, _PRICE_FEED_DECIMALS);
1884
+ vm.label(address(_priceFeedNativeUsd), "Mock Price Feed Native-USDC");
1885
+
1886
+ _controller.addPriceFeed({
1887
+ projectId: 1,
1888
+ pricingCurrency: uint32(uint160(address(_usdcToken))),
1889
+ unitCurrency: uint32(uint160(JBConstants.NATIVE_TOKEN)),
1890
+ feed: _priceFeedNativeUsd
1891
+ });
1892
+
1893
+ _controller.addPriceFeed({
1894
+ projectId: 2,
1895
+ pricingCurrency: uint32(uint160(address(_usdcToken))),
1896
+ unitCurrency: uint32(uint160(JBConstants.NATIVE_TOKEN)),
1897
+ feed: _priceFeedNativeUsd
1898
+ });
1899
+
1900
+ vm.stopPrank();
1901
+ }
1902
+
1903
+ // Make a payment to the project to give it a starting balance. Send the tokens to the `_beneficiary`.
1904
+ _terminal.pay{value: _nativePayAmount}({
1905
+ projectId: _projectId,
1906
+ amount: _nativePayAmount,
1907
+ token: JBConstants.NATIVE_TOKEN,
1908
+ beneficiary: _beneficiary,
1909
+ minReturnedTokens: 0,
1910
+ memo: "",
1911
+ metadata: new bytes(0)
1912
+ });
1913
+
1914
+ // Make sure the beneficiary got the expected number of tokens from the native token payment.
1915
+ uint256 _beneficiaryTokenBalance = _unreservedPortion(mulDiv(_nativePayAmount, _weight, 10 ** _NATIVE_DECIMALS));
1916
+ assertEq(_tokens.totalBalanceOf(_beneficiary, _projectId), _beneficiaryTokenBalance);
1917
+ // Mint USDC to this contract.
1918
+ _usdcToken.mint(address(this), _usdcPayAmount);
1919
+
1920
+ // Allow the terminal to spend the USDC.
1921
+ _usdcToken.approve(address(_terminal2), _usdcPayAmount);
1922
+
1923
+ // Make a payment to the project to give it a starting balance. Send the tokens to the `_beneficiary`.
1924
+ _terminal2.pay({
1925
+ projectId: _projectId,
1926
+ amount: _usdcPayAmount,
1927
+ token: address(_usdcToken),
1928
+ beneficiary: _beneficiary,
1929
+ minReturnedTokens: 0,
1930
+ memo: "",
1931
+ metadata: new bytes(0)
1932
+ });
1933
+
1934
+ // Make sure the terminal holds the full native token balance.
1935
+ assertEq(
1936
+ jbTerminalStore().balanceOf(address(_terminal), _projectId, JBConstants.NATIVE_TOKEN), _nativePayAmount
1937
+ );
1938
+ // Make sure the USDC is accounted for.
1939
+ assertEq(jbTerminalStore().balanceOf(address(_terminal2), _projectId, address(_usdcToken)), _usdcPayAmount);
1940
+ assertEq(_usdcToken.balanceOf(address(_terminal2)), _usdcPayAmount);
1941
+
1942
+ {
1943
+ // Convert the USD amount to a native token amount, by way of the current weight used for issuance.
1944
+ uint256 _usdWeightedPayAmountConvertedToNative = mulDiv(
1945
+ _usdcPayAmount,
1946
+ _weight,
1947
+ mulDiv(_USD_PRICE_PER_NATIVE, 10 ** _usdcToken.decimals(), 10 ** _PRICE_FEED_DECIMALS)
1948
+ );
1949
+
1950
+ // Make sure the beneficiary got the expected number of tokens from the USDC payment.
1951
+ _beneficiaryTokenBalance += _unreservedPortion(_usdWeightedPayAmountConvertedToNative);
1952
+ assertEq(_tokens.totalBalanceOf(_beneficiary, _projectId), _beneficiaryTokenBalance);
1953
+ }
1954
+
1955
+ // Revert if there's no native token allowance.
1956
+ if (_nativeCurrencySurplusAllowance == 0) {
1957
+ vm.expectRevert(
1958
+ abi.encodeWithSelector(JBTerminalStore.JBTerminalStore_InadequateControllerAllowance.selector, 0, 0)
1959
+ );
1960
+ } else if (_nativeCurrencySurplusAllowance + _nativeCurrencyPayoutLimit > _nativePayAmount) {
1961
+ vm.expectRevert(
1962
+ abi.encodeWithSelector(
1963
+ JBTerminalStore.JBTerminalStore_InadequateTerminalStoreBalance.selector,
1964
+ _nativeCurrencySurplusAllowance,
1965
+ _nativeCurrencyPayoutLimit > _nativePayAmount ? 0 : _nativePayAmount - _nativeCurrencyPayoutLimit
1966
+ )
1967
+ );
1968
+ }
1969
+
1970
+ // Use the full native token surplus allowance.
1971
+ vm.prank(_projectOwner);
1972
+ _terminal.useAllowanceOf({
1973
+ projectId: _projectId,
1974
+ amount: _nativeCurrencySurplusAllowance,
1975
+ currency: uint32(uint160(JBConstants.NATIVE_TOKEN)),
1976
+ token: JBConstants.NATIVE_TOKEN,
1977
+ minTokensPaidOut: 0,
1978
+ beneficiary: payable(_beneficiary),
1979
+ feeBeneficiary: payable(_projectOwner),
1980
+ memo: "MEMO"
1981
+ });
1982
+
1983
+ // Keep a reference to the beneficiary's native token balance.
1984
+ uint256 _beneficiaryNativeBalance;
1985
+
1986
+ // Check the collected balance if one is expected.
1987
+ if (_nativeCurrencySurplusAllowance + _nativeCurrencyPayoutLimit <= _nativePayAmount) {
1988
+ // Make sure the beneficiary received the funds and that they are no longer in the terminal.
1989
+ _beneficiaryNativeBalance = _nativeCurrencySurplusAllowance
1990
+ - mulDiv(_nativeCurrencySurplusAllowance, _terminal.FEE(), JBConstants.MAX_FEE);
1991
+ assertEq(_beneficiary.balance, _beneficiaryNativeBalance);
1992
+ assertEq(
1993
+ jbTerminalStore().balanceOf(address(_terminal), _projectId, JBConstants.NATIVE_TOKEN),
1994
+ _nativePayAmount - _nativeCurrencySurplusAllowance
1995
+ );
1996
+
1997
+ // Make sure the fee was paid correctly.
1998
+ assertEq(
1999
+ jbTerminalStore().balanceOf(address(_terminal), _FEE_PROJECT_ID, JBConstants.NATIVE_TOKEN),
2000
+ _nativeCurrencySurplusAllowance - _beneficiaryNativeBalance
2001
+ );
2002
+ assertEq(address(_terminal).balance, _nativePayAmount - _beneficiaryNativeBalance);
2003
+
2004
+ // Make sure the beneficiary got the expected number of tokens.
2005
+ assertEq(
2006
+ _tokens.totalBalanceOf(_projectOwner, _FEE_PROJECT_ID),
2007
+ _unreservedPortion(
2008
+ mulDiv(_nativeCurrencySurplusAllowance - _beneficiaryNativeBalance, _weight, 10 ** _NATIVE_DECIMALS)
2009
+ )
2010
+ );
2011
+ } else {
2012
+ // Set the native token surplus allowance to 0 if it wasn't used.
2013
+ _nativeCurrencySurplusAllowance = 0;
2014
+ }
2015
+
2016
+ // Revert if there's no native token allowance.
2017
+ if (_usdCurrencySurplusAllowance == 0) {
2018
+ vm.expectRevert(
2019
+ abi.encodeWithSelector(JBTerminalStore.JBTerminalStore_InadequateControllerAllowance.selector, 0, 0)
2020
+ );
2021
+ // Revert if the USD surplus allowance resolved to native tokens is greater than 0, and there is sufficient
2022
+ // surplus to pull from including what was already pulled from.
2023
+ } else if (_usdCurrencySurplusAllowance + _usdCurrencyPayoutLimit > _usdcPayAmount) {
2024
+ vm.expectRevert(
2025
+ abi.encodeWithSelector(
2026
+ JBTerminalStore.JBTerminalStore_InadequateTerminalStoreBalance.selector,
2027
+ _usdCurrencySurplusAllowance,
2028
+ _usdCurrencyPayoutLimit > _usdcPayAmount ? 0 : _usdcPayAmount - _usdCurrencyPayoutLimit
2029
+ )
2030
+ );
2031
+ }
2032
+
2033
+ // Use the full native token surplus allowance.
2034
+ vm.prank(_projectOwner);
2035
+ _terminal2.useAllowanceOf({
2036
+ projectId: _projectId,
2037
+ amount: _usdCurrencySurplusAllowance,
2038
+ currency: uint32(uint160(address(_usdcToken))),
2039
+ token: address(_usdcToken),
2040
+ minTokensPaidOut: 0,
2041
+ beneficiary: payable(_beneficiary),
2042
+ feeBeneficiary: payable(_projectOwner),
2043
+ memo: "MEMO"
2044
+ });
2045
+
2046
+ // Keep a reference to the beneficiary's USDC balance.
2047
+ uint256 _beneficiaryUsdcBalance;
2048
+
2049
+ // Check the collected balance if one is expected.
2050
+ if (_usdCurrencySurplusAllowance + _usdCurrencyPayoutLimit <= _usdcPayAmount) {
2051
+ // Make sure the beneficiary received the funds and that they are no longer in the terminal.
2052
+ _beneficiaryUsdcBalance += _usdCurrencySurplusAllowance
2053
+ - mulDiv(_usdCurrencySurplusAllowance, _terminal.FEE(), JBConstants.MAX_FEE);
2054
+ assertEq(_usdcToken.balanceOf(_beneficiary), _beneficiaryUsdcBalance);
2055
+ assertEq(
2056
+ jbTerminalStore().balanceOf(address(_terminal2), _projectId, address(_usdcToken)),
2057
+ _usdcPayAmount - _usdCurrencySurplusAllowance
2058
+ );
2059
+
2060
+ // Make sure the fee was paid correctly.
2061
+ assertEq(
2062
+ jbTerminalStore().balanceOf(address(_terminal), _FEE_PROJECT_ID, address(_usdcToken)),
2063
+ _usdCurrencySurplusAllowance - _beneficiaryUsdcBalance
2064
+ );
2065
+ assertEq(_usdcToken.balanceOf(address(_terminal2)), _usdcPayAmount - _usdCurrencySurplusAllowance);
2066
+ assertEq(_usdcToken.balanceOf(address(_terminal)), _usdCurrencySurplusAllowance - _beneficiaryUsdcBalance);
2067
+
2068
+ // Make sure the beneficiary got the expected number of tokens.
2069
+ assertEq(
2070
+ _tokens.totalBalanceOf(_projectOwner, _FEE_PROJECT_ID),
2071
+ _unreservedPortion(
2072
+ mulDiv(
2073
+ _nativeCurrencySurplusAllowance
2074
+ + _toNative(
2075
+ mulDiv(
2076
+ _usdCurrencySurplusAllowance, 10 ** _NATIVE_DECIMALS, 10 ** _usdcToken.decimals()
2077
+ )
2078
+ ) - _beneficiaryNativeBalance
2079
+ - _toNative(
2080
+ mulDiv(_beneficiaryUsdcBalance, 10 ** _NATIVE_DECIMALS, 10 ** _usdcToken.decimals())
2081
+ ),
2082
+ _weight,
2083
+ 10 ** _NATIVE_DECIMALS
2084
+ )
2085
+ )
2086
+ );
2087
+ } else {
2088
+ // Set the native token surplus allowance to 0 if it wasn't used.
2089
+ _usdCurrencySurplusAllowance = 0;
2090
+ }
2091
+
2092
+ // Payout limits
2093
+ {
2094
+ // Revert if the payout limit is greater than the balance.
2095
+ if (_nativeCurrencyPayoutLimit > _nativePayAmount) {
2096
+ vm.expectRevert(
2097
+ abi.encodeWithSelector(
2098
+ JBTerminalStore.JBTerminalStore_InadequateTerminalStoreBalance.selector,
2099
+ _nativeCurrencyPayoutLimit,
2100
+ _nativePayAmount
2101
+ )
2102
+ );
2103
+ // Revert if there's no payout limit.
2104
+ } else if (_nativeCurrencyPayoutLimit == 0) {
2105
+ vm.expectRevert(
2106
+ abi.encodeWithSelector(
2107
+ JBTerminalStore.JBTerminalStore_InadequateControllerPayoutLimit.selector, 0, 0
2108
+ )
2109
+ );
2110
+ }
2111
+
2112
+ // Pay out native tokens up to the payout limit. Since `splits[]` is empty, everything goes to project
2113
+ // owner.
2114
+ _terminal.sendPayoutsOf({
2115
+ projectId: _projectId,
2116
+ amount: _nativeCurrencyPayoutLimit,
2117
+ currency: uint32(uint160(JBConstants.NATIVE_TOKEN)),
2118
+ token: JBConstants.NATIVE_TOKEN,
2119
+ minTokensPaidOut: 0
2120
+ });
2121
+
2122
+ uint256 _projectOwnerNativeBalance;
2123
+
2124
+ // Check the received payout if one is expected.
2125
+ if (_nativeCurrencyPayoutLimit <= _nativePayAmount && _nativeCurrencyPayoutLimit != 0) {
2126
+ // Make sure the project owner received the funds that were paid out.
2127
+ _projectOwnerNativeBalance =
2128
+ _nativeCurrencyPayoutLimit - _nativeCurrencyPayoutLimit * _terminal.FEE() / JBConstants.MAX_FEE;
2129
+ assertEq(_projectOwner.balance, _projectOwnerNativeBalance);
2130
+ assertEq(
2131
+ jbTerminalStore().balanceOf(address(_terminal), _projectId, JBConstants.NATIVE_TOKEN),
2132
+ _nativePayAmount - _nativeCurrencySurplusAllowance - _nativeCurrencyPayoutLimit
2133
+ );
2134
+
2135
+ // Make sure the fee was paid correctly.
2136
+ assertEq(
2137
+ jbTerminalStore().balanceOf(address(_terminal), _FEE_PROJECT_ID, JBConstants.NATIVE_TOKEN),
2138
+ _nativeCurrencySurplusAllowance - _beneficiaryNativeBalance + _nativeCurrencyPayoutLimit
2139
+ - _projectOwnerNativeBalance
2140
+ );
2141
+ assertEq(
2142
+ address(_terminal).balance,
2143
+ _nativePayAmount - _beneficiaryNativeBalance - _projectOwnerNativeBalance
2144
+ );
2145
+
2146
+ // // // Make sure the project owner got the expected number of tokens.
2147
+ // assertEq(
2148
+ // _unreservedPortion(mulDiv(_nativeCurrencySurplusAllowance + _toNative(_usdCurrencySurplusAllowance) -
2149
+ // _beneficiaryNativeBalance + _nativeCurrencyPayoutLimit - _projectOwnerNativeBalance, _weight, 10
2150
+ // ** _NATIVE_DECIMALS)), _tokens.totalBalanceOf(_projectOwner, _FEE_PROJECT_ID));
2151
+ }
2152
+
2153
+ // Revert if the payout limit is greater than the balance.
2154
+ if (_usdCurrencyPayoutLimit > _usdcPayAmount) {
2155
+ vm.expectRevert(
2156
+ abi.encodeWithSelector(
2157
+ JBTerminalStore.JBTerminalStore_InadequateTerminalStoreBalance.selector,
2158
+ _usdCurrencyPayoutLimit,
2159
+ _usdcPayAmount
2160
+ )
2161
+ );
2162
+ // Revert if there's no payout limit.
2163
+ } else if (_usdCurrencyPayoutLimit == 0) {
2164
+ vm.expectRevert(
2165
+ abi.encodeWithSelector(
2166
+ JBTerminalStore.JBTerminalStore_InadequateControllerPayoutLimit.selector, 0, 0
2167
+ )
2168
+ );
2169
+ }
2170
+
2171
+ // Pay out native tokens up to the payout limit. Since `splits[]` is empty, everything goes to project
2172
+ // owner.
2173
+ _terminal2.sendPayoutsOf({
2174
+ projectId: _projectId,
2175
+ amount: _usdCurrencyPayoutLimit,
2176
+ currency: uint32(uint160(address(_usdcToken))),
2177
+ token: address(_usdcToken),
2178
+ minTokensPaidOut: 0
2179
+ });
2180
+
2181
+ uint256 _projectOwnerUsdcBalance;
2182
+
2183
+ // Check the received payout if one is expected.
2184
+ if (_usdCurrencyPayoutLimit <= _usdcPayAmount && _usdCurrencyPayoutLimit != 0) {
2185
+ // Make sure the project owner received the funds that were paid out.
2186
+ _projectOwnerUsdcBalance =
2187
+ _usdCurrencyPayoutLimit - _usdCurrencyPayoutLimit * _terminal.FEE() / JBConstants.MAX_FEE;
2188
+ assertEq(_usdcToken.balanceOf(_projectOwner), _projectOwnerUsdcBalance);
2189
+ assertEq(
2190
+ jbTerminalStore().balanceOf(address(_terminal2), _projectId, address(_usdcToken)),
2191
+ _usdcPayAmount - _usdCurrencySurplusAllowance - _usdCurrencyPayoutLimit
2192
+ );
2193
+
2194
+ // Make sure the fee was paid correctly.
2195
+ assertEq(
2196
+ jbTerminalStore().balanceOf(address(_terminal), _FEE_PROJECT_ID, address(_usdcToken)),
2197
+ _usdCurrencySurplusAllowance - _beneficiaryUsdcBalance + _usdCurrencyPayoutLimit
2198
+ - _projectOwnerUsdcBalance
2199
+ );
2200
+ assertEq(
2201
+ _usdcToken.balanceOf(address(_terminal2)),
2202
+ _usdcPayAmount - _usdCurrencySurplusAllowance - _usdCurrencyPayoutLimit
2203
+ );
2204
+ assertEq(
2205
+ _usdcToken.balanceOf(address(_terminal)),
2206
+ _usdCurrencySurplusAllowance + _usdCurrencyPayoutLimit - _beneficiaryUsdcBalance
2207
+ - _projectOwnerUsdcBalance
2208
+ );
2209
+ }
2210
+ }
2211
+
2212
+ // Keep a reference to the remaining native token surplus.
2213
+ uint256 _nativeSurplus = _nativeCurrencyPayoutLimit + _nativeCurrencySurplusAllowance >= _nativePayAmount
2214
+ ? 0
2215
+ : _nativePayAmount - _nativeCurrencyPayoutLimit - _nativeCurrencySurplusAllowance;
2216
+
2217
+ uint256 _usdcSurplus = _usdCurrencyPayoutLimit + _usdCurrencySurplusAllowance >= _usdcPayAmount
2218
+ ? 0
2219
+ : _usdcPayAmount - _usdCurrencyPayoutLimit - _usdCurrencySurplusAllowance;
2220
+
2221
+ // Keep a reference to the remaining native token balance.
2222
+ uint256 _usdcBalanceInTerminal = _usdcPayAmount - _usdCurrencySurplusAllowance;
2223
+
2224
+ if (_usdCurrencyPayoutLimit <= _usdcPayAmount) {
2225
+ _usdcBalanceInTerminal -= _usdCurrencyPayoutLimit;
2226
+ }
2227
+
2228
+ assertEq(_usdcToken.balanceOf(address(_terminal2)), _usdcBalanceInTerminal);
2229
+
2230
+ // Make sure the total token supply is correct.
2231
+ assertEq(
2232
+ jbController().totalTokenSupplyWithReservedTokensOf(_projectId),
2233
+ mulDiv(
2234
+ _beneficiaryTokenBalance,
2235
+ JBConstants.MAX_RESERVED_PERCENT,
2236
+ JBConstants.MAX_RESERVED_PERCENT - _metadata.reservedPercent
2237
+ )
2238
+ );
2239
+
2240
+ // Keep a reference to the amount of native tokens being reclaimed.
2241
+ uint256 _nativeReclaimAmount;
2242
+
2243
+ vm.startPrank(_beneficiary);
2244
+
2245
+ // If there's native token surplus.
2246
+ if (_nativeSurplus + _toNative(mulDiv(_usdcSurplus, 10 ** _NATIVE_DECIMALS, 10 ** _usdcToken.decimals())) > 0) {
2247
+ // Get the expected amount reclaimed.
2248
+ _nativeReclaimAmount = mulDiv(
2249
+ mulDiv(
2250
+ _nativeSurplus
2251
+ + _toNative(mulDiv(_usdcSurplus, 10 ** _NATIVE_DECIMALS, 10 ** _usdcToken.decimals())),
2252
+ _beneficiaryTokenBalance,
2253
+ mulDiv(
2254
+ _beneficiaryTokenBalance,
2255
+ JBConstants.MAX_RESERVED_PERCENT,
2256
+ JBConstants.MAX_RESERVED_PERCENT - _metadata.reservedPercent
2257
+ )
2258
+ ),
2259
+ _metadata.cashOutTaxRate
2260
+ + mulDiv(
2261
+ _beneficiaryTokenBalance,
2262
+ JBConstants.MAX_CASH_OUT_TAX_RATE - _metadata.cashOutTaxRate,
2263
+ mulDiv(
2264
+ _beneficiaryTokenBalance,
2265
+ JBConstants.MAX_RESERVED_PERCENT,
2266
+ JBConstants.MAX_RESERVED_PERCENT - _metadata.reservedPercent
2267
+ )
2268
+ ),
2269
+ JBConstants.MAX_CASH_OUT_TAX_RATE
2270
+ );
2271
+
2272
+ // If there is more to reclaim than there are native tokens in the tank.
2273
+ if (_nativeReclaimAmount > _nativeSurplus) {
2274
+ uint256 _usdcReclaimAmount;
2275
+ {
2276
+ // Keep a reference to the amount of project tokens to cash out for native tokens, a proportion of
2277
+ // available native token surplus.
2278
+ uint256 _tokenCountToCashOutForNative = mulDiv(
2279
+ _beneficiaryTokenBalance,
2280
+ _nativeSurplus,
2281
+ _nativeSurplus
2282
+ + _toNative(mulDiv(_usdcSurplus, 10 ** _NATIVE_DECIMALS, 10 ** _usdcToken.decimals()))
2283
+ );
2284
+ uint256 _tokenSupply = mulDiv(
2285
+ _beneficiaryTokenBalance,
2286
+ JBConstants.MAX_RESERVED_PERCENT,
2287
+ JBConstants.MAX_RESERVED_PERCENT - _metadata.reservedPercent
2288
+ );
2289
+ // Cash out native tokens from the surplus using only the `_beneficiary`'s tokens needed to clear
2290
+ // the
2291
+ // native token balance.
2292
+ _terminal.cashOutTokensOf({
2293
+ holder: _beneficiary,
2294
+ projectId: _projectId,
2295
+ cashOutCount: _tokenCountToCashOutForNative,
2296
+ tokenToReclaim: JBConstants.NATIVE_TOKEN,
2297
+ minTokensReclaimed: 0,
2298
+ beneficiary: payable(_beneficiary),
2299
+ metadata: new bytes(0)
2300
+ });
2301
+
2302
+ // Cash out USDC from the surplus using only the `_beneficiary`'s tokens needed to clear the USDC
2303
+ // balance.
2304
+ _terminal2.cashOutTokensOf({
2305
+ holder: _beneficiary,
2306
+ projectId: _projectId,
2307
+ cashOutCount: _beneficiaryTokenBalance - _tokenCountToCashOutForNative,
2308
+ tokenToReclaim: address(_usdcToken),
2309
+ minTokensReclaimed: 0,
2310
+ beneficiary: payable(_beneficiary),
2311
+ metadata: new bytes(0)
2312
+ });
2313
+
2314
+ _nativeReclaimAmount = mulDiv(
2315
+ mulDiv(
2316
+ _nativeSurplus
2317
+ + _toNative(mulDiv(_usdcSurplus, 10 ** _NATIVE_DECIMALS, 10 ** _usdcToken.decimals())),
2318
+ _tokenCountToCashOutForNative,
2319
+ _tokenSupply
2320
+ ),
2321
+ _metadata.cashOutTaxRate
2322
+ + mulDiv(
2323
+ _tokenCountToCashOutForNative,
2324
+ JBConstants.MAX_CASH_OUT_TAX_RATE - _metadata.cashOutTaxRate,
2325
+ _tokenSupply
2326
+ ),
2327
+ JBConstants.MAX_CASH_OUT_TAX_RATE
2328
+ );
2329
+ _usdcReclaimAmount = mulDiv(
2330
+ mulDiv(
2331
+ _usdcSurplus
2332
+ + _toUsd(
2333
+ mulDiv(
2334
+ _nativeSurplus - _nativeReclaimAmount,
2335
+ 10 ** _usdcToken.decimals(),
2336
+ 10 ** _NATIVE_DECIMALS
2337
+ )
2338
+ ),
2339
+ _beneficiaryTokenBalance - _tokenCountToCashOutForNative,
2340
+ _tokenSupply - _tokenCountToCashOutForNative
2341
+ ),
2342
+ _metadata.cashOutTaxRate
2343
+ + mulDiv(
2344
+ _beneficiaryTokenBalance - _tokenCountToCashOutForNative,
2345
+ JBConstants.MAX_CASH_OUT_TAX_RATE - _metadata.cashOutTaxRate,
2346
+ _tokenSupply - _tokenCountToCashOutForNative
2347
+ ),
2348
+ JBConstants.MAX_CASH_OUT_TAX_RATE
2349
+ );
2350
+ }
2351
+
2352
+ assertEq(
2353
+ jbTerminalStore().balanceOf(address(_terminal2), _projectId, address(_usdcToken)),
2354
+ _usdcSurplus - _usdcReclaimAmount
2355
+ );
2356
+
2357
+ uint256 _usdcFeeAmount = _usdcReclaimAmount * _terminal.FEE() / JBConstants.MAX_FEE;
2358
+
2359
+ _beneficiaryUsdcBalance += _usdcReclaimAmount - _usdcFeeAmount;
2360
+ assertEq(_usdcToken.balanceOf(_beneficiary), _beneficiaryUsdcBalance);
2361
+
2362
+ assertEq(_usdcToken.balanceOf(address(_terminal2)), _usdcBalanceInTerminal - _usdcReclaimAmount);
2363
+
2364
+ // Only the fees left.
2365
+ assertEq(
2366
+ _usdcToken.balanceOf(address(_terminal)),
2367
+ _usdcPayAmount - _usdcToken.balanceOf(address(_terminal2)) - _usdcToken.balanceOf(_beneficiary)
2368
+ - _usdcToken.balanceOf(_projectOwner)
2369
+ );
2370
+ } else {
2371
+ // Reclaim native tokens from the surplus by cashing out all of the `_beneficiary`'s tokens.
2372
+ _terminal.cashOutTokensOf({
2373
+ holder: _beneficiary,
2374
+ projectId: _projectId,
2375
+ cashOutCount: _beneficiaryTokenBalance,
2376
+ tokenToReclaim: JBConstants.NATIVE_TOKEN,
2377
+ minTokensReclaimed: 0,
2378
+ beneficiary: payable(_beneficiary),
2379
+ metadata: new bytes(0)
2380
+ });
2381
+ }
2382
+ // Burn the tokens.
2383
+ } else {
2384
+ _terminal2.cashOutTokensOf({
2385
+ holder: _beneficiary,
2386
+ projectId: _projectId,
2387
+ cashOutCount: _beneficiaryTokenBalance,
2388
+ tokenToReclaim: address(_usdcToken),
2389
+ minTokensReclaimed: 0,
2390
+ beneficiary: payable(_beneficiary),
2391
+ metadata: new bytes(0)
2392
+ });
2393
+ }
2394
+ vm.stopPrank();
2395
+
2396
+ // Keep a reference to the remaining native token balance.
2397
+ uint256 _projectNativeBalance = _nativePayAmount - _nativeCurrencySurplusAllowance;
2398
+ if (_nativeCurrencyPayoutLimit <= _nativePayAmount) {
2399
+ _projectNativeBalance -= _nativeCurrencyPayoutLimit;
2400
+ }
2401
+
2402
+ // Make sure the balance is adjusted by the reclaim amount.
2403
+ assertEq(
2404
+ jbTerminalStore().balanceOf(address(_terminal), _projectId, JBConstants.NATIVE_TOKEN),
2405
+ _projectNativeBalance - _nativeReclaimAmount
2406
+ );
2407
+ }
2408
+
2409
+ // Tests that recent changes to JBTerminalStore are safe wrt reetrency via useAllowanceOf.
2410
+ function testNativeAllowanceReentry() public {
2411
+ // Hardcode values to use.
2412
+ uint224 _nativeCurrencyPayoutLimit = uint224(10 * 10 ** _NATIVE_DECIMALS);
2413
+ uint224 _nativeCurrencySurplusAllowance = uint224(5 * 10 ** _NATIVE_DECIMALS);
2414
+
2415
+ MaliciousAllowanceBeneficiary maliciousOwner = new MaliciousAllowanceBeneficiary();
2416
+
2417
+ // Package up the limits for the given terminal.
2418
+ JBFundAccessLimitGroup[] memory _fundAccessLimitGroup = new JBFundAccessLimitGroup[](1);
2419
+ {
2420
+ // Specify a payout limit.
2421
+ JBCurrencyAmount[] memory _payoutLimits = new JBCurrencyAmount[](1);
2422
+ _payoutLimits[0] = JBCurrencyAmount({
2423
+ amount: _nativeCurrencyPayoutLimit, currency: uint32(uint160(JBConstants.NATIVE_TOKEN))
2424
+ });
2425
+
2426
+ // Specify a surplus allowance.
2427
+ JBCurrencyAmount[] memory _surplusAllowances = new JBCurrencyAmount[](1);
2428
+ _surplusAllowances[0] = JBCurrencyAmount({
2429
+ amount: _nativeCurrencySurplusAllowance, currency: uint32(uint160(JBConstants.NATIVE_TOKEN))
2430
+ });
2431
+
2432
+ _fundAccessLimitGroup[0] = JBFundAccessLimitGroup({
2433
+ terminal: address(_terminal),
2434
+ token: JBConstants.NATIVE_TOKEN,
2435
+ payoutLimits: _payoutLimits,
2436
+ surplusAllowances: _surplusAllowances
2437
+ });
2438
+ }
2439
+
2440
+ {
2441
+ // Package up the ruleset configuration.
2442
+ JBRulesetConfig[] memory _rulesetConfigurations = new JBRulesetConfig[](1);
2443
+ _rulesetConfigurations[0].mustStartAtOrAfter = 0;
2444
+ _rulesetConfigurations[0].duration = 0;
2445
+ _rulesetConfigurations[0].weight = _weight;
2446
+ _rulesetConfigurations[0].weightCutPercent = 0;
2447
+ _rulesetConfigurations[0].approvalHook = IJBRulesetApprovalHook(address(0));
2448
+ _rulesetConfigurations[0].metadata = _metadata;
2449
+ _rulesetConfigurations[0].splitGroups = new JBSplitGroup[](0);
2450
+ _rulesetConfigurations[0].fundAccessLimitGroups = _fundAccessLimitGroup;
2451
+
2452
+ JBTerminalConfig[] memory _terminalConfigurations = new JBTerminalConfig[](1);
2453
+ JBAccountingContext[] memory _tokensToAccept = new JBAccountingContext[](1);
2454
+ _tokensToAccept[0] = JBAccountingContext({
2455
+ token: JBConstants.NATIVE_TOKEN, decimals: 18, currency: uint32(uint160(JBConstants.NATIVE_TOKEN))
2456
+ });
2457
+
2458
+ _terminalConfigurations[0] =
2459
+ JBTerminalConfig({terminal: _terminal, accountingContextsToAccept: _tokensToAccept});
2460
+
2461
+ // Create a first project to collect fees.
2462
+ _controller.launchProjectFor({
2463
+ owner: address(420), // Random.
2464
+ projectUri: "whatever",
2465
+ rulesetConfigurations: _rulesetConfigurations,
2466
+ terminalConfigurations: _terminalConfigurations, // Set terminals to receive fees.
2467
+ memo: ""
2468
+ });
2469
+
2470
+ // Create the project to test.
2471
+ _projectId = _controller.launchProjectFor({
2472
+ owner: address(maliciousOwner),
2473
+ projectUri: "myIPFSHash",
2474
+ rulesetConfigurations: _rulesetConfigurations,
2475
+ terminalConfigurations: _terminalConfigurations,
2476
+ memo: ""
2477
+ });
2478
+ }
2479
+
2480
+ // Get a reference to the amount being paid.
2481
+ // The amount being paid is the payout limit plus two times the surplus allowance.
2482
+ uint256 _nativePayAmount = _nativeCurrencyPayoutLimit + (5 * _nativeCurrencySurplusAllowance);
2483
+
2484
+ // Pay the project such that the `_beneficiary` receives project tokens.
2485
+ _terminal.pay{value: _nativePayAmount}({
2486
+ projectId: _projectId,
2487
+ amount: _nativePayAmount,
2488
+ token: JBConstants.NATIVE_TOKEN,
2489
+ beneficiary: _beneficiary,
2490
+ minReturnedTokens: 0,
2491
+ memo: "",
2492
+ metadata: new bytes(0)
2493
+ });
2494
+
2495
+ // Make sure the beneficiary got the expected number of tokens.
2496
+ uint256 _beneficiaryTokenBalance = mulDiv(_nativePayAmount, _weight, 10 ** _NATIVE_DECIMALS)
2497
+ * _metadata.reservedPercent / JBConstants.MAX_RESERVED_PERCENT;
2498
+ assertEq(_tokens.totalBalanceOf(_beneficiary, _projectId), _beneficiaryTokenBalance);
2499
+
2500
+ // Make sure the terminal holds the full native token balance.
2501
+ assertEq(
2502
+ jbTerminalStore().balanceOf(address(_terminal), _projectId, JBConstants.NATIVE_TOKEN), _nativePayAmount
2503
+ );
2504
+
2505
+ // Attempt to use more than surplus allowance via malicious beneficiary contract.
2506
+ // This will fail via the mock contract itself, with an expected revert therein corresponding to the amounts.
2507
+ // See {MockMaliciousAllowanceBeneficiary}
2508
+ vm.prank(address(maliciousOwner));
2509
+ _terminal.useAllowanceOf({
2510
+ projectId: _projectId,
2511
+ amount: _nativeCurrencySurplusAllowance,
2512
+ currency: uint32(uint160(JBConstants.NATIVE_TOKEN)),
2513
+ token: JBConstants.NATIVE_TOKEN,
2514
+ minTokensPaidOut: 0,
2515
+ beneficiary: payable(address(maliciousOwner)),
2516
+ feeBeneficiary: payable(_projectOwner),
2517
+ memo: "MEMO"
2518
+ });
2519
+ }
2520
+
2521
+ // Tests that recent changes to JBTerminalStore are safe wrt reetrency via sendPayoutsOf.
2522
+ function testNativePayoutReentry() public {
2523
+ // Hardcode values to use.
2524
+ uint224 _nativeCurrencyPayoutLimit = uint224(10 * 10 ** _NATIVE_DECIMALS);
2525
+ uint224 _nativeCurrencySurplusAllowance = uint224(5 * 10 ** _NATIVE_DECIMALS);
2526
+
2527
+ MaliciousPayoutBeneficiary maliciousPayoutCaller = new MaliciousPayoutBeneficiary();
2528
+
2529
+ // Package up the limits for the given terminal.
2530
+ JBFundAccessLimitGroup[] memory _fundAccessLimitGroup = new JBFundAccessLimitGroup[](1);
2531
+ {
2532
+ // Specify a payout limit.
2533
+ JBCurrencyAmount[] memory _payoutLimits = new JBCurrencyAmount[](1);
2534
+ _payoutLimits[0] = JBCurrencyAmount({
2535
+ amount: _nativeCurrencyPayoutLimit, currency: uint32(uint160(JBConstants.NATIVE_TOKEN))
2536
+ });
2537
+
2538
+ // Specify a surplus allowance.
2539
+ JBCurrencyAmount[] memory _surplusAllowances = new JBCurrencyAmount[](1);
2540
+ _surplusAllowances[0] = JBCurrencyAmount({
2541
+ amount: _nativeCurrencySurplusAllowance, currency: uint32(uint160(JBConstants.NATIVE_TOKEN))
2542
+ });
2543
+
2544
+ _fundAccessLimitGroup[0] = JBFundAccessLimitGroup({
2545
+ terminal: address(_terminal),
2546
+ token: JBConstants.NATIVE_TOKEN,
2547
+ payoutLimits: _payoutLimits,
2548
+ surplusAllowances: _surplusAllowances
2549
+ });
2550
+ }
2551
+
2552
+ {
2553
+ // Package up the ruleset configuration.
2554
+ JBRulesetConfig[] memory _rulesetConfigurations = new JBRulesetConfig[](1);
2555
+ _rulesetConfigurations[0].mustStartAtOrAfter = 0;
2556
+ _rulesetConfigurations[0].duration = 0;
2557
+ _rulesetConfigurations[0].weight = _weight;
2558
+ _rulesetConfigurations[0].weightCutPercent = 0;
2559
+ _rulesetConfigurations[0].approvalHook = IJBRulesetApprovalHook(address(0));
2560
+ _rulesetConfigurations[0].metadata = _metadata;
2561
+ _rulesetConfigurations[0].splitGroups = new JBSplitGroup[](0);
2562
+ _rulesetConfigurations[0].fundAccessLimitGroups = _fundAccessLimitGroup;
2563
+
2564
+ JBTerminalConfig[] memory _terminalConfigurations = new JBTerminalConfig[](1);
2565
+ JBAccountingContext[] memory _tokensToAccept = new JBAccountingContext[](1);
2566
+ _tokensToAccept[0] = JBAccountingContext({
2567
+ token: JBConstants.NATIVE_TOKEN, decimals: 18, currency: uint32(uint160(JBConstants.NATIVE_TOKEN))
2568
+ });
2569
+
2570
+ _terminalConfigurations[0] =
2571
+ JBTerminalConfig({terminal: _terminal, accountingContextsToAccept: _tokensToAccept});
2572
+
2573
+ // Create a first project to collect fees.
2574
+ _controller.launchProjectFor({
2575
+ owner: address(420), // Random.
2576
+ projectUri: "whatever",
2577
+ rulesetConfigurations: _rulesetConfigurations,
2578
+ terminalConfigurations: _terminalConfigurations, // Set terminals to receive fees.
2579
+ memo: ""
2580
+ });
2581
+
2582
+ // Create the project to test.
2583
+ _projectId = _controller.launchProjectFor({
2584
+ owner: address(maliciousPayoutCaller),
2585
+ projectUri: "myIPFSHash",
2586
+ rulesetConfigurations: _rulesetConfigurations,
2587
+ terminalConfigurations: _terminalConfigurations,
2588
+ memo: ""
2589
+ });
2590
+ }
2591
+
2592
+ // Get a reference to the amount being paid.
2593
+ // The amount being paid is the payout limit plus five times the surplus allowance.
2594
+ uint256 _nativePayAmount = _nativeCurrencyPayoutLimit + (5 * _nativeCurrencySurplusAllowance);
2595
+
2596
+ // Pay the project such that the `_beneficiary` receives project tokens.
2597
+ _terminal.pay{value: _nativePayAmount}({
2598
+ projectId: _projectId,
2599
+ amount: _nativePayAmount,
2600
+ token: JBConstants.NATIVE_TOKEN,
2601
+ beneficiary: _beneficiary,
2602
+ minReturnedTokens: 0,
2603
+ memo: "",
2604
+ metadata: new bytes(0)
2605
+ });
2606
+
2607
+ // Make sure the beneficiary got the expected number of tokens.
2608
+ uint256 _beneficiaryTokenBalance = mulDiv(_nativePayAmount, _weight, 10 ** _NATIVE_DECIMALS)
2609
+ * _metadata.reservedPercent / JBConstants.MAX_RESERVED_PERCENT;
2610
+ assertEq(_tokens.totalBalanceOf(_beneficiary, _projectId), _beneficiaryTokenBalance);
2611
+
2612
+ // Make sure the terminal holds the full native token balance.
2613
+ assertEq(
2614
+ jbTerminalStore().balanceOf(address(_terminal), _projectId, JBConstants.NATIVE_TOKEN), _nativePayAmount
2615
+ );
2616
+
2617
+ // Pay out native tokens up to the payout limit. Since `splits[]` is empty, everything goes to project owner.
2618
+ // Project owner is our malicious contract that attempts to hijack control flow and execute subsequent calls
2619
+ // successfully.
2620
+ // This will fail via the mock contract itself, with an expected revert corresponding to the amounts.
2621
+ // See {MockMaliciousPayoutBeneficiary}
2622
+ _terminal.sendPayoutsOf({
2623
+ projectId: _projectId,
2624
+ amount: _nativeCurrencyPayoutLimit,
2625
+ currency: uint32(uint160(JBConstants.NATIVE_TOKEN)),
2626
+ token: JBConstants.NATIVE_TOKEN,
2627
+ minTokensPaidOut: 0
2628
+ });
2629
+ }
2630
+
2631
+ function _toNative(uint256 _usdVal) internal pure returns (uint256) {
2632
+ return mulDiv(_usdVal, 10 ** _PRICE_FEED_DECIMALS, _USD_PRICE_PER_NATIVE);
2633
+ }
2634
+
2635
+ function _toUsd(uint256 _nativeVal) internal pure returns (uint256) {
2636
+ return mulDiv(_nativeVal, _USD_PRICE_PER_NATIVE, 10 ** _PRICE_FEED_DECIMALS);
2637
+ }
2638
+
2639
+ function _unreservedPortion(uint256 _fullPortion) internal view returns (uint256) {
2640
+ return mulDiv(
2641
+ _fullPortion, JBConstants.MAX_RESERVED_PERCENT - _metadata.reservedPercent, JBConstants.MAX_RESERVED_PERCENT
2642
+ );
2643
+ }
2644
+ }