@bananapus/core-v6 0.0.37 → 0.0.38

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