@bananapus/core-v6 0.0.26 → 0.0.28

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 (169) hide show
  1. package/ADMINISTRATION.md +1 -1
  2. package/AUDIT_INSTRUCTIONS.md +3 -3
  3. package/CHANGE_LOG.md +17 -2
  4. package/README.md +1 -1
  5. package/RISKS.md +3 -3
  6. package/SKILLS.md +1 -1
  7. package/STYLE_GUIDE.md +2 -2
  8. package/USER_JOURNEYS.md +6 -3
  9. package/foundry.toml +1 -1
  10. package/package.json +5 -5
  11. package/script/Deploy.s.sol +1 -1
  12. package/script/DeployPeriphery.s.sol +1 -1
  13. package/script/helpers/CoreDeploymentLib.sol +1 -1
  14. package/src/JBChainlinkV3PriceFeed.sol +1 -1
  15. package/src/JBChainlinkV3SequencerPriceFeed.sol +1 -1
  16. package/src/JBController.sol +1 -1
  17. package/src/JBDeadline.sol +1 -1
  18. package/src/JBDirectory.sol +1 -1
  19. package/src/JBERC20.sol +1 -1
  20. package/src/JBFeelessAddresses.sol +1 -1
  21. package/src/JBFundAccessLimits.sol +1 -1
  22. package/src/JBMultiTerminal.sol +41 -13
  23. package/src/JBPermissions.sol +1 -1
  24. package/src/JBPrices.sol +1 -1
  25. package/src/JBProjects.sol +1 -1
  26. package/src/JBRulesets.sol +1 -1
  27. package/src/JBSplits.sol +1 -1
  28. package/src/JBTerminalStore.sol +1 -1
  29. package/src/JBTokens.sol +1 -1
  30. package/src/libraries/JBPayoutSplitGroupLib.sol +1 -1
  31. package/src/periphery/JBDeadline1Day.sol +1 -1
  32. package/src/periphery/JBDeadline3Days.sol +1 -1
  33. package/src/periphery/JBDeadline3Hours.sol +1 -1
  34. package/src/periphery/JBDeadline7Days.sol +1 -1
  35. package/src/periphery/JBMatchingPriceFeed.sol +1 -1
  36. package/test/TestFees.sol +6 -4
  37. package/test/TestJBERC20Inheritance.sol +1 -1
  38. package/test/TestMetadataOffsetOverflow.sol +1 -1
  39. package/test/TestMetadataParserLib.sol +1 -1
  40. package/test/TestTerminalMigration.sol +104 -2
  41. package/test/TestTerminalPreviewParity.sol +1 -1
  42. package/test/audit/FeeFreeSurplusLifecycle.t.sol +11 -4
  43. package/test/audit/FeeFreeSurplusStale.t.sol +11 -5
  44. package/test/audit/USDTVoidReturnCompat.t.sol +6 -0
  45. package/test/fork/TestChainlinkPriceFeedFork.sol +1 -1
  46. package/test/fork/TestSequencerPriceFeedFork.sol +1 -1
  47. package/test/fork/TestTerminalPreviewParityFork.sol +1 -1
  48. package/test/helpers/JBTest.sol +1 -1
  49. package/test/helpers/MetadataResolverHelper.sol +1 -1
  50. package/test/mock/MockERC20.sol +1 -1
  51. package/test/mock/MockMaliciousBeneficiary.sol +1 -1
  52. package/test/mock/MockMaliciousSplitHook.sol +1 -1
  53. package/test/mock/MockPriceFeed.sol +1 -1
  54. package/test/mock/MockUSDT.sol +1 -1
  55. package/test/units/static/JBChainlinkV3PriceFeed/TestPriceFeed.sol +1 -1
  56. package/test/units/static/JBController/JBControllerSetup.sol +1 -1
  57. package/test/units/static/JBController/TestBurnTokensOf.sol +1 -1
  58. package/test/units/static/JBController/TestClaimTokensFor.sol +1 -1
  59. package/test/units/static/JBController/TestDeployErc20For.sol +1 -1
  60. package/test/units/static/JBController/TestLaunchProjectFor.sol +1 -1
  61. package/test/units/static/JBController/TestLaunchRulesetsFor.sol +1 -1
  62. package/test/units/static/JBController/TestMigrateController.sol +1 -1
  63. package/test/units/static/JBController/TestMintTokensOfUnits.sol +1 -1
  64. package/test/units/static/JBController/TestOmnichainRulesetOperator.sol +1 -1
  65. package/test/units/static/JBController/TestPayReservedTokenToTerminal.sol +1 -1
  66. package/test/units/static/JBController/TestPreviewMintOf.sol +1 -1
  67. package/test/units/static/JBController/TestReceiveMigrationFrom.sol +1 -1
  68. package/test/units/static/JBController/TestRulesetViews.sol +1 -1
  69. package/test/units/static/JBController/TestSendReservedTokensToSplitsOf.sol +1 -1
  70. package/test/units/static/JBController/TestSetSplitGroupsOf.sol +1 -1
  71. package/test/units/static/JBController/TestSetTokenFor.sol +1 -1
  72. package/test/units/static/JBController/TestSetUriOf.sol +1 -1
  73. package/test/units/static/JBController/TestTransferCreditsFrom.sol +1 -1
  74. package/test/units/static/JBDeadline/TestDeadlineFuzz.sol +1 -1
  75. package/test/units/static/JBDirectory/JBDirectorySetup.sol +1 -1
  76. package/test/units/static/JBDirectory/TestPrimaryTerminalOf.sol +1 -1
  77. package/test/units/static/JBDirectory/TestSetControllerOf.sol +1 -1
  78. package/test/units/static/JBDirectory/TestSetControllerOfMigrationOrder.sol +1 -1
  79. package/test/units/static/JBDirectory/TestSetPrimaryTerminalOf.sol +1 -1
  80. package/test/units/static/JBDirectory/TestSetTerminalsOf.sol +1 -1
  81. package/test/units/static/JBERC20/JBERC20Setup.sol +1 -1
  82. package/test/units/static/JBERC20/SigUtils.sol +1 -1
  83. package/test/units/static/JBERC20/TestInitialize.sol +1 -1
  84. package/test/units/static/JBERC20/TestName.sol +1 -1
  85. package/test/units/static/JBERC20/TestNonces.sol +1 -1
  86. package/test/units/static/JBERC20/TestSymbol.sol +1 -1
  87. package/test/units/static/JBFeelessAdresses/JBFeelessSetup.sol +1 -1
  88. package/test/units/static/JBFeelessAdresses/TestInterfaces.sol +1 -1
  89. package/test/units/static/JBFeelessAdresses/TestSetFeelessAddress.sol +1 -1
  90. package/test/units/static/JBFees/TestFeesFuzz.sol +1 -1
  91. package/test/units/static/JBFixedPointNumber/TestAdjustDecimals.sol +1 -1
  92. package/test/units/static/JBFixedPointNumber/TestAdjustDecimalsFuzz.sol +1 -1
  93. package/test/units/static/JBFundAccessLimits/JBFundAccessSetup.sol +1 -1
  94. package/test/units/static/JBFundAccessLimits/TestFundAccessLimitsEdge.sol +1 -1
  95. package/test/units/static/JBFundAccessLimits/TestPayoutLimitOf.sol +1 -1
  96. package/test/units/static/JBFundAccessLimits/TestPayoutLimitsOf.sol +1 -1
  97. package/test/units/static/JBFundAccessLimits/TestSetFundAccessLimitsFor.sol +1 -1
  98. package/test/units/static/JBFundAccessLimits/TestSurplusAllowanceOf.sol +1 -1
  99. package/test/units/static/JBFundAccessLimits/TestSurplusAllowancesOf.sol +1 -1
  100. package/test/units/static/JBMetadataResolver/TestGetDataFor.sol +1 -1
  101. package/test/units/static/JBMetadataResolver/TestMetadataResolverEdgeCases.sol +1 -1
  102. package/test/units/static/JBMetadataResolver/TestMetadataResolverFuzz.sol +1 -1
  103. package/test/units/static/JBMultiTerminal/JBMultiTerminalSetup.sol +1 -1
  104. package/test/units/static/JBMultiTerminal/TestAccountingContextsOf.sol +1 -1
  105. package/test/units/static/JBMultiTerminal/TestAddAccountingContextsFor.sol +1 -1
  106. package/test/units/static/JBMultiTerminal/TestAddToBalanceOf.sol +1 -1
  107. package/test/units/static/JBMultiTerminal/TestCashOutTokensOf.sol +1 -1
  108. package/test/units/static/JBMultiTerminal/TestExecutePayout.sol +3 -42
  109. package/test/units/static/JBMultiTerminal/TestExecuteProcessFee.sol +1 -1
  110. package/test/units/static/JBMultiTerminal/TestMigrateBalanceOf.sol +16 -1
  111. package/test/units/static/JBMultiTerminal/TestPay.sol +1 -1
  112. package/test/units/static/JBMultiTerminal/TestPreviewCashOutFrom.sol +1 -1
  113. package/test/units/static/JBMultiTerminal/TestPreviewPayFor.sol +1 -1
  114. package/test/units/static/JBMultiTerminal/TestProcessHeldFeesOf.sol +1 -1
  115. package/test/units/static/JBMultiTerminal/TestSelfPayRevert.sol +55 -0
  116. package/test/units/static/JBMultiTerminal/TestSendPayoutsOf.sol +1 -1
  117. package/test/units/static/JBMultiTerminal/TestUseAllowanceOf.sol +1 -1
  118. package/test/units/static/JBPermissions/JBPermissionsSetup.sol +1 -1
  119. package/test/units/static/JBPermissions/TestHasPermission.sol +1 -1
  120. package/test/units/static/JBPermissions/TestHasPermissions.sol +1 -1
  121. package/test/units/static/JBPermissions/TestSetPermissionsFor.sol +1 -1
  122. package/test/units/static/JBPrices/JBPricesSetup.sol +1 -1
  123. package/test/units/static/JBPrices/TestAddPriceFeedFor.sol +1 -1
  124. package/test/units/static/JBPrices/TestPricePerUnitOf.sol +1 -1
  125. package/test/units/static/JBPrices/TestPrices.sol +1 -1
  126. package/test/units/static/JBProjects/JBProjectsSetup.sol +1 -1
  127. package/test/units/static/JBProjects/TestCreateFor.sol +1 -1
  128. package/test/units/static/JBProjects/TestInitialProject.sol +1 -1
  129. package/test/units/static/JBProjects/TestInterfaces.sol +1 -1
  130. package/test/units/static/JBProjects/TestSetResolver.sol +1 -1
  131. package/test/units/static/JBProjects/TestTokenUri.sol +1 -1
  132. package/test/units/static/JBRulesetMetadataResolver/TestSetCashOutTaxRateTo.sol +1 -1
  133. package/test/units/static/JBRulesets/JBRulesetsSetup.sol +1 -1
  134. package/test/units/static/JBRulesets/TestCurrentApprovalStatusForLatestRulesetOf.sol +1 -1
  135. package/test/units/static/JBRulesets/TestCurrentOf.sol +1 -1
  136. package/test/units/static/JBRulesets/TestGetRulesetOf.sol +1 -1
  137. package/test/units/static/JBRulesets/TestLatestQueuedRulesetOf.sol +1 -1
  138. package/test/units/static/JBRulesets/TestRulesets.sol +1 -1
  139. package/test/units/static/JBRulesets/TestRulesetsOf.sol +1 -1
  140. package/test/units/static/JBRulesets/TestUpcomingRulesetOf.sol +1 -1
  141. package/test/units/static/JBRulesets/TestUpdateRulesetWeightCache.sol +1 -1
  142. package/test/units/static/JBSplits/JBSplitsSetup.sol +1 -1
  143. package/test/units/static/JBSplits/TestSelfManagedSplitGroups.sol +1 -1
  144. package/test/units/static/JBSplits/TestSetSplitGroupsOf.sol +1 -1
  145. package/test/units/static/JBSplits/TestSplitsLockedEdge.sol +1 -1
  146. package/test/units/static/JBSplits/TestSplitsOf.sol +1 -1
  147. package/test/units/static/JBSplits/TestSplitsPacking.sol +1 -1
  148. package/test/units/static/JBSurplus/TestSurplusFuzz.sol +1 -1
  149. package/test/units/static/JBTerminalStore/JBTerminalStoreSetup.sol +1 -1
  150. package/test/units/static/JBTerminalStore/TestCurrentReclaimableSurplusOf.sol +1 -1
  151. package/test/units/static/JBTerminalStore/TestCurrentSurplusOf.sol +1 -1
  152. package/test/units/static/JBTerminalStore/TestCurrentTotalSurplusOf.sol +1 -1
  153. package/test/units/static/JBTerminalStore/TestPreviewCashOutFrom.sol +1 -1
  154. package/test/units/static/JBTerminalStore/TestPreviewPayFrom.sol +1 -1
  155. package/test/units/static/JBTerminalStore/TestRecordCashOutsFor.sol +1 -1
  156. package/test/units/static/JBTerminalStore/TestRecordPaymentFrom.sol +1 -1
  157. package/test/units/static/JBTerminalStore/TestRecordPayoutFor.sol +1 -1
  158. package/test/units/static/JBTerminalStore/TestRecordTerminalMigration.sol +1 -1
  159. package/test/units/static/JBTerminalStore/TestRecordUsedAllowanceOf.sol +1 -1
  160. package/test/units/static/JBTerminalStore/TestUint224Overflow.sol +1 -1
  161. package/test/units/static/JBTokens/JBTokensSetup.sol +1 -1
  162. package/test/units/static/JBTokens/TestBurnFrom.sol +1 -1
  163. package/test/units/static/JBTokens/TestClaimTokensFor.sol +1 -1
  164. package/test/units/static/JBTokens/TestDeployERC20ForUnits.sol +1 -1
  165. package/test/units/static/JBTokens/TestMintFor.sol +1 -1
  166. package/test/units/static/JBTokens/TestSetTokenFor.sol +1 -1
  167. package/test/units/static/JBTokens/TestTotalBalanceOf.sol +1 -1
  168. package/test/units/static/JBTokens/TestTotalSupplyOf.sol +1 -1
  169. package/test/units/static/JBTokens/TestTransferCreditsFrom.sol +1 -1
@@ -6,6 +6,7 @@ import {JBMultiTerminal} from "../src/JBMultiTerminal.sol";
6
6
  import {IJBController} from "../src/interfaces/IJBController.sol";
7
7
  import {IJBRulesetApprovalHook} from "../src/interfaces/IJBRulesetApprovalHook.sol";
8
8
  import {JBConstants} from "../src/libraries/JBConstants.sol";
9
+ import {JBFees} from "../src/libraries/JBFees.sol";
9
10
  import {JBAccountingContext} from "../src/structs/JBAccountingContext.sol";
10
11
  import {JBFundAccessLimitGroup} from "../src/structs/JBFundAccessLimitGroup.sol";
11
12
  import {JBRulesetConfig} from "../src/structs/JBRulesetConfig.sol";
@@ -105,7 +106,7 @@ contract TestTerminalMigration_Local is TestBaseWorkflow {
105
106
  uint256 migratedBalance = _terminalA.migrateBalanceOf(_projectId, JBConstants.NATIVE_TOKEN, _terminalB);
106
107
  assertEq(migratedBalance, payAmount, "full balance should be migrated");
107
108
 
108
- // Step 4: Verify balances after migration
109
+ // Step 4: Verify balances after migration (fee project is exempt from migration fee)
109
110
  uint256 balanceAAfter = jbTerminalStore().balanceOf(address(_terminalA), _projectId, JBConstants.NATIVE_TOKEN);
110
111
  assertEq(balanceAAfter, 0, "terminal A should have zero after migration");
111
112
 
@@ -152,7 +153,7 @@ contract TestTerminalMigration_Local is TestBaseWorkflow {
152
153
  vm.prank(_projectOwner);
153
154
  _terminalA.migrateBalanceOf(_projectId, JBConstants.NATIVE_TOKEN, _terminalB);
154
155
 
155
- // Check surplus from terminal B
156
+ // Check surplus from terminal B (fee project exempt from migration fee)
156
157
  uint256 surplusAfter =
157
158
  _terminalB.currentSurplusOf(_projectId, new address[](0), 18, uint32(uint160(JBConstants.NATIVE_TOKEN)));
158
159
  assertEq(surplusAfter, surplusBefore, "surplus should be preserved after migration");
@@ -169,4 +170,105 @@ contract TestTerminalMigration_Local is TestBaseWorkflow {
169
170
  vm.expectRevert();
170
171
  _terminalA.migrateBalanceOf(_projectId, JBConstants.NATIVE_TOKEN, _terminalB);
171
172
  }
173
+
174
+ /// @notice Non-fee project migration charges the 2.5% fee; fee project balance increases.
175
+ function test_migration_nonFeeProject_chargesFee() public {
176
+ // Launch a second project (project 2) — this is NOT the fee project.
177
+ JBRulesetMetadata memory _metadata2 = JBRulesetMetadata({
178
+ reservedPercent: 0,
179
+ cashOutTaxRate: 0,
180
+ baseCurrency: uint32(uint160(JBConstants.NATIVE_TOKEN)),
181
+ pausePay: false,
182
+ pauseCreditTransfers: false,
183
+ allowOwnerMinting: false,
184
+ allowSetCustomToken: false,
185
+ allowTerminalMigration: true,
186
+ allowSetTerminals: true,
187
+ allowSetController: false,
188
+ allowAddAccountingContext: false,
189
+ allowAddPriceFeed: false,
190
+ ownerMustSendPayouts: false,
191
+ holdFees: false,
192
+ useTotalSurplusForCashOuts: false,
193
+ useDataHookForPay: false,
194
+ useDataHookForCashOut: false,
195
+ dataHook: address(0),
196
+ metadata: 0
197
+ });
198
+
199
+ JBRulesetConfig[] memory _rulesetConfig2 = new JBRulesetConfig[](1);
200
+ _rulesetConfig2[0].mustStartAtOrAfter = 0;
201
+ _rulesetConfig2[0].duration = 0;
202
+ _rulesetConfig2[0].weight = 1000 * 10 ** 18;
203
+ _rulesetConfig2[0].weightCutPercent = 0;
204
+ _rulesetConfig2[0].approvalHook = IJBRulesetApprovalHook(address(0));
205
+ _rulesetConfig2[0].metadata = _metadata2;
206
+ _rulesetConfig2[0].splitGroups = new JBSplitGroup[](0);
207
+ _rulesetConfig2[0].fundAccessLimitGroups = new JBFundAccessLimitGroup[](0);
208
+
209
+ JBAccountingContext[] memory _tokensToAccept = new JBAccountingContext[](1);
210
+ _tokensToAccept[0] = JBAccountingContext({
211
+ token: JBConstants.NATIVE_TOKEN, decimals: 18, currency: uint32(uint160(JBConstants.NATIVE_TOKEN))
212
+ });
213
+
214
+ JBTerminalConfig[] memory _terminalConfigs2 = new JBTerminalConfig[](2);
215
+ _terminalConfigs2[0] = JBTerminalConfig({terminal: _terminalA, accountingContextsToAccept: _tokensToAccept});
216
+ _terminalConfigs2[1] = JBTerminalConfig({terminal: _terminalB, accountingContextsToAccept: _tokensToAccept});
217
+
218
+ uint256 project2 = _controller.launchProjectFor({
219
+ owner: _projectOwner,
220
+ projectUri: "non-fee-project",
221
+ rulesetConfigurations: _rulesetConfig2,
222
+ terminalConfigurations: _terminalConfigs2,
223
+ memo: ""
224
+ });
225
+
226
+ // Confirm project 2 is NOT the fee project.
227
+ assertGt(project2, 1, "project2 should not be the fee project");
228
+
229
+ uint256 payAmount = 10 ether;
230
+
231
+ // Pay into terminal A for project 2.
232
+ vm.deal(_beneficiary, payAmount);
233
+ vm.prank(_beneficiary);
234
+ _terminalA.pay{value: payAmount}(project2, JBConstants.NATIVE_TOKEN, payAmount, _beneficiary, 0, "", "");
235
+
236
+ // Snapshot fee project balance before migration.
237
+ uint256 feeBalanceBefore = jbTerminalStore().balanceOf(address(_terminalA), 1, JBConstants.NATIVE_TOKEN);
238
+
239
+ // Migrate project 2 from terminal A to terminal B.
240
+ vm.prank(_projectOwner);
241
+ _terminalA.migrateBalanceOf(project2, JBConstants.NATIVE_TOKEN, _terminalB);
242
+
243
+ // Fee project balance should have increased (fee was charged).
244
+ uint256 feeBalanceAfter = jbTerminalStore().balanceOf(address(_terminalA), 1, JBConstants.NATIVE_TOKEN);
245
+ assertGt(feeBalanceAfter, feeBalanceBefore, "fee project should receive migration fee");
246
+
247
+ // Terminal B should have received payAmount minus the 2.5% fee.
248
+ uint256 expectedFee = JBFees.feeAmountFrom({amountBeforeFee: payAmount, feePercent: 25});
249
+ uint256 balanceBAfter = jbTerminalStore().balanceOf(address(_terminalB), project2, JBConstants.NATIVE_TOKEN);
250
+ assertEq(balanceBAfter, payAmount - expectedFee, "terminal B balance should reflect fee deduction");
251
+ }
252
+
253
+ /// @notice Fee project (project 1) migration is exempt from fees.
254
+ function test_migration_feeProject_noFeeCharged() public {
255
+ // _projectId is project 1, which IS the fee project.
256
+ assertEq(_projectId, 1, "project under test should be the fee project");
257
+
258
+ uint256 payAmount = 10 ether;
259
+
260
+ // Pay into terminal A for the fee project.
261
+ vm.deal(_beneficiary, payAmount);
262
+ vm.prank(_beneficiary);
263
+ _terminalA.pay{value: payAmount}(_projectId, JBConstants.NATIVE_TOKEN, payAmount, _beneficiary, 0, "", "");
264
+
265
+ // Migrate fee project from terminal A to terminal B.
266
+ vm.prank(_projectOwner);
267
+ uint256 migratedBalance = _terminalA.migrateBalanceOf(_projectId, JBConstants.NATIVE_TOKEN, _terminalB);
268
+ assertEq(migratedBalance, payAmount, "full balance should be migrated without fee");
269
+
270
+ // Terminal B should have the full amount (no fee deducted).
271
+ uint256 balanceBAfter = jbTerminalStore().balanceOf(address(_terminalB), _projectId, JBConstants.NATIVE_TOKEN);
272
+ assertEq(balanceBAfter, payAmount, "fee project should not be charged migration fee");
273
+ }
172
274
  }
@@ -1,5 +1,5 @@
1
1
  // SPDX-License-Identifier: MIT
2
- pragma solidity ^0.8.26;
2
+ pragma solidity 0.8.28;
3
3
 
4
4
  import {TestBaseWorkflow} from "./helpers/TestBaseWorkflow.sol";
5
5
  import {IJBController} from "../src/interfaces/IJBController.sol";
@@ -114,6 +114,7 @@ contract FeeFreeSurplusLifecycleTest is TestBaseWorkflow {
114
114
  splits[0] = JBSplit({
115
115
  preferAddToBalance: true,
116
116
  percent: JBConstants.SPLITS_TOTAL_PERCENT,
117
+ // forge-lint: disable-next-line(unsafe-typecast)
117
118
  projectId: uint64(_recipientProjectId),
118
119
  beneficiary: payable(address(0)),
119
120
  lockedUntil: 0,
@@ -126,8 +127,11 @@ contract FeeFreeSurplusLifecycleTest is TestBaseWorkflow {
126
127
 
127
128
  // Payout limit: allow paying out the full PAY_AMOUNT each cycle.
128
129
  JBCurrencyAmount[] memory payoutLimits = new JBCurrencyAmount[](1);
129
- payoutLimits[0] =
130
- JBCurrencyAmount({amount: uint224(PAY_AMOUNT), currency: uint32(uint160(JBConstants.NATIVE_TOKEN))});
130
+ payoutLimits[0] = JBCurrencyAmount({
131
+ // forge-lint: disable-next-line(unsafe-typecast)
132
+ amount: uint224(PAY_AMOUNT),
133
+ currency: uint32(uint160(JBConstants.NATIVE_TOKEN))
134
+ });
131
135
 
132
136
  // Fund access limits with the payout limit defined above.
133
137
  JBFundAccessLimitGroup[] memory payerLimits = new JBFundAccessLimitGroup[](1);
@@ -314,10 +318,13 @@ contract FeeFreeSurplusLifecycleTest is TestBaseWorkflow {
314
318
  jbTerminalStore().balanceOf(address(_terminal), _recipientProjectId, JBConstants.NATIVE_TOKEN);
315
319
  assertEq(balanceAfterOnOldTerminal, 0, "Old terminal balance should be zero after migration");
316
320
 
317
- // Verify the new terminal received the balance.
321
+ // Verify the new terminal received the balance minus the 2.5% migration fee.
318
322
  uint256 balanceOnNewTerminal =
319
323
  jbTerminalStore().balanceOf(address(_terminal2), _recipientProjectId, JBConstants.NATIVE_TOKEN);
320
- assertEq(balanceOnNewTerminal, balanceBefore, "New terminal should have the migrated balance");
324
+ uint256 migrationFee = balanceBefore * 25 / 1000;
325
+ assertEq(
326
+ balanceOnNewTerminal, balanceBefore - migrationFee, "New terminal should have balance minus migration fee"
327
+ );
321
328
  }
322
329
 
323
330
  // --- Helpers ---
@@ -16,7 +16,6 @@ import {JBRulesetMetadata} from "../../src/structs/JBRulesetMetadata.sol";
16
16
  import {JBSplit} from "../../src/structs/JBSplit.sol";
17
17
  import {JBSplitGroup} from "../../src/structs/JBSplitGroup.sol";
18
18
  import {JBTerminalConfig} from "../../src/structs/JBTerminalConfig.sol";
19
- import {mulDiv} from "@prb/math/src/Common.sol";
20
19
 
21
20
  contract FeeFreeSurplusStaleTest is TestBaseWorkflow {
22
21
  IJBController private _controller;
@@ -63,8 +62,11 @@ contract FeeFreeSurplusStaleTest is TestBaseWorkflow {
63
62
  });
64
63
 
65
64
  JBCurrencyAmount[] memory surplusAllowances = new JBCurrencyAmount[](1);
66
- surplusAllowances[0] =
67
- JBCurrencyAmount({amount: uint224(PAY_AMOUNT), currency: uint32(uint160(JBConstants.NATIVE_TOKEN))});
65
+ surplusAllowances[0] = JBCurrencyAmount({
66
+ // forge-lint: disable-next-line(unsafe-typecast)
67
+ amount: uint224(PAY_AMOUNT),
68
+ currency: uint32(uint160(JBConstants.NATIVE_TOKEN))
69
+ });
68
70
 
69
71
  JBFundAccessLimitGroup[] memory fundAccessLimitGroups = new JBFundAccessLimitGroup[](1);
70
72
  fundAccessLimitGroups[0] = JBFundAccessLimitGroup({
@@ -91,6 +93,7 @@ contract FeeFreeSurplusStaleTest is TestBaseWorkflow {
91
93
  splits[0] = JBSplit({
92
94
  preferAddToBalance: true,
93
95
  percent: JBConstants.SPLITS_TOTAL_PERCENT,
96
+ // forge-lint: disable-next-line(unsafe-typecast)
94
97
  projectId: uint64(_projectIdB),
95
98
  beneficiary: payable(address(0)),
96
99
  lockedUntil: 0,
@@ -101,8 +104,11 @@ contract FeeFreeSurplusStaleTest is TestBaseWorkflow {
101
104
  splitGroups[0] = JBSplitGroup({groupId: uint32(uint160(JBConstants.NATIVE_TOKEN)), splits: splits});
102
105
 
103
106
  JBCurrencyAmount[] memory payoutLimits = new JBCurrencyAmount[](1);
104
- payoutLimits[0] =
105
- JBCurrencyAmount({amount: uint224(PAY_AMOUNT), currency: uint32(uint160(JBConstants.NATIVE_TOKEN))});
107
+ payoutLimits[0] = JBCurrencyAmount({
108
+ // forge-lint: disable-next-line(unsafe-typecast)
109
+ amount: uint224(PAY_AMOUNT),
110
+ currency: uint32(uint160(JBConstants.NATIVE_TOKEN))
111
+ });
106
112
 
107
113
  JBFundAccessLimitGroup[] memory projectALimits = new JBFundAccessLimitGroup[](1);
108
114
  projectALimits[0] = JBFundAccessLimitGroup({
@@ -150,6 +150,7 @@ contract USDTVoidReturnCompat is TestBaseWorkflow {
150
150
 
151
151
  // Grant Permit2 allowance for the terminal to pull tokens.
152
152
  vm.prank(payer);
153
+ // forge-lint: disable-next-line(unsafe-typecast)
153
154
  permit2().approve(address(usdt), address(jbMultiTerminal()), uint160(payAmount), type(uint48).max);
154
155
 
155
156
  // Execute the payment — this is the critical call that must handle void returns.
@@ -196,6 +197,7 @@ contract USDTVoidReturnCompat is TestBaseWorkflow {
196
197
 
197
198
  // Grant Permit2 allowance for the terminal.
198
199
  vm.prank(payer);
200
+ // forge-lint: disable-next-line(unsafe-typecast)
199
201
  permit2().approve(address(usdt), address(jbMultiTerminal()), uint160(payAmount), type(uint48).max);
200
202
 
201
203
  // Pay into the project to fund it.
@@ -254,6 +256,7 @@ contract USDTVoidReturnCompat is TestBaseWorkflow {
254
256
 
255
257
  // Grant Permit2 allowance for the terminal.
256
258
  vm.prank(payer);
259
+ // forge-lint: disable-next-line(unsafe-typecast)
257
260
  permit2().approve(address(usdt), address(jbMultiTerminal()), uint160(payAmount), type(uint48).max);
258
261
 
259
262
  // Pay into the project to receive project tokens.
@@ -316,6 +319,7 @@ contract USDTVoidReturnCompat is TestBaseWorkflow {
316
319
 
317
320
  // Grant Permit2 allowance for the terminal.
318
321
  vm.prank(payer);
322
+ // forge-lint: disable-next-line(unsafe-typecast)
319
323
  permit2().approve(address(usdt), address(jbMultiTerminal()), uint160(payAmount), type(uint48).max);
320
324
 
321
325
  // Step 1: Pay into the project.
@@ -367,6 +371,7 @@ contract USDTVoidReturnCompat is TestBaseWorkflow {
367
371
  // =========================================================================
368
372
 
369
373
  /// @notice Launches a project that accepts MockUSDT with no splits and no payout limits.
374
+ // forge-lint: disable-next-line(mixed-case-function)
370
375
  function _launchUSDTProject() internal returns (uint256) {
371
376
  // Create a single-element array for the ruleset configuration.
372
377
  JBRulesetConfig[] memory rulesetConfig = new JBRulesetConfig[](1);
@@ -428,6 +433,7 @@ contract USDTVoidReturnCompat is TestBaseWorkflow {
428
433
  }
429
434
 
430
435
  /// @notice Launches a project that accepts MockUSDT with a 100% split to splitBeneficiary.
436
+ // forge-lint: disable-next-line(mixed-case-function)
431
437
  function _launchUSDTProjectWithSplit() internal returns (uint256) {
432
438
  // Create a single-element array for the ruleset configuration.
433
439
  JBRulesetConfig[] memory rulesetConfig = new JBRulesetConfig[](1);
@@ -1,5 +1,5 @@
1
1
  // SPDX-License-Identifier: MIT
2
- pragma solidity ^0.8.26;
2
+ pragma solidity 0.8.28;
3
3
 
4
4
  import {Test} from "forge-std/Test.sol";
5
5
 
@@ -1,5 +1,5 @@
1
1
  // SPDX-License-Identifier: MIT
2
- pragma solidity ^0.8.26;
2
+ pragma solidity 0.8.28;
3
3
 
4
4
  import {Test} from "forge-std/Test.sol";
5
5
 
@@ -1,5 +1,5 @@
1
1
  // SPDX-License-Identifier: MIT
2
- pragma solidity ^0.8.26;
2
+ pragma solidity 0.8.28;
3
3
 
4
4
  import {TestTerminalPreviewParity_Local} from "../TestTerminalPreviewParity.sol";
5
5
  import {IJBTerminal} from "../../src/interfaces/IJBTerminal.sol";
@@ -1,5 +1,5 @@
1
1
  // SPDX-License-Identifier: MIT
2
- pragma solidity ^0.8.26;
2
+ pragma solidity 0.8.28;
3
3
 
4
4
  import {JBRuleset} from "../../src/structs/JBRuleset.sol";
5
5
  import {JBRulesetMetadata} from "../../src/structs/JBRulesetMetadata.sol";
@@ -1,5 +1,5 @@
1
1
  // SPDX-License-Identifier: MIT
2
- pragma solidity ^0.8.26;
2
+ pragma solidity 0.8.28;
3
3
 
4
4
  import {JBMetadataResolver} from "../../src/libraries/JBMetadataResolver.sol";
5
5
 
@@ -1,5 +1,5 @@
1
1
  // SPDX-License-Identifier: MIT
2
- pragma solidity ^0.8.26;
2
+ pragma solidity 0.8.28;
3
3
 
4
4
  import {ERC20} from "@openzeppelin/contracts/token/ERC20/ERC20.sol";
5
5
 
@@ -1,5 +1,5 @@
1
1
  // SPDX-License-Identifier: MIT
2
- pragma solidity ^0.8.26;
2
+ pragma solidity 0.8.28;
3
3
 
4
4
  import {Test} from "forge-std/Test.sol";
5
5
  import {JBTerminalStore} from "../../src/JBTerminalStore.sol";
@@ -1,5 +1,5 @@
1
1
  // SPDX-License-Identifier: MIT
2
- pragma solidity ^0.8.26;
2
+ pragma solidity 0.8.28;
3
3
 
4
4
  import {IJBSplitHook} from "../../src/interfaces/IJBSplitHook.sol";
5
5
  import {JBSplitHookContext} from "../../src/structs/JBSplitHookContext.sol";
@@ -1,5 +1,5 @@
1
1
  // SPDX-License-Identifier: MIT
2
- pragma solidity ^0.8.26;
2
+ pragma solidity 0.8.28;
3
3
 
4
4
  import {IJBPriceFeed} from "../../src/interfaces/IJBPriceFeed.sol";
5
5
 
@@ -1,5 +1,5 @@
1
1
  // SPDX-License-Identifier: MIT
2
- pragma solidity ^0.8.26;
2
+ pragma solidity 0.8.28;
3
3
 
4
4
  /// @notice Mimics Tether (USDT) whose transfer/transferFrom/approve return void instead of bool.
5
5
  /// @dev Uses inline assembly to return empty data (no bool), matching USDT's on-chain behavior.
@@ -1,5 +1,5 @@
1
1
  // SPDX-License-Identifier: MIT
2
- pragma solidity ^0.8.26;
2
+ pragma solidity 0.8.28;
3
3
 
4
4
  import {JBTest} from "../../../helpers/JBTest.sol";
5
5
  import {JBChainlinkV3PriceFeed} from "../../../../src/JBChainlinkV3PriceFeed.sol";
@@ -1,5 +1,5 @@
1
1
  // SPDX-License-Identifier: MIT
2
- pragma solidity ^0.8.26;
2
+ pragma solidity 0.8.28;
3
3
 
4
4
  import {JBController} from "../../../../src/JBController.sol";
5
5
  import {IJBController} from "../../../../src/interfaces/IJBController.sol";
@@ -1,5 +1,5 @@
1
1
  // SPDX-License-Identifier: MIT
2
- pragma solidity ^0.8.26;
2
+ pragma solidity 0.8.28;
3
3
 
4
4
  import {JBController} from "../../../../src/JBController.sol";
5
5
  import {JBPermissioned} from "../../../../src/abstract/JBPermissioned.sol";
@@ -1,5 +1,5 @@
1
1
  // SPDX-License-Identifier: MIT
2
- pragma solidity ^0.8.26;
2
+ pragma solidity 0.8.28;
3
3
 
4
4
  import {JBPermissioned} from "../../../../src/abstract/JBPermissioned.sol";
5
5
  import {IJBPermissions} from "../../../../src/interfaces/IJBPermissions.sol";
@@ -1,5 +1,5 @@
1
1
  // SPDX-License-Identifier: MIT
2
- pragma solidity ^0.8.26;
2
+ pragma solidity 0.8.28;
3
3
 
4
4
  import {JBPermissioned} from "../../../../src/abstract/JBPermissioned.sol";
5
5
  import {IJBPermissions} from "../../../../src/interfaces/IJBPermissions.sol";
@@ -1,5 +1,5 @@
1
1
  // SPDX-License-Identifier: MIT
2
- pragma solidity ^0.8.26;
2
+ pragma solidity 0.8.28;
3
3
 
4
4
  import {JBController} from "../../../../src/JBController.sol";
5
5
  import {IJBController} from "../../../../src/interfaces/IJBController.sol";
@@ -1,5 +1,5 @@
1
1
  // SPDX-License-Identifier: MIT
2
- pragma solidity ^0.8.26;
2
+ pragma solidity 0.8.28;
3
3
 
4
4
  import {JBController} from "../../../../src/JBController.sol";
5
5
  import {JBPermissioned} from "../../../../src/abstract/JBPermissioned.sol";
@@ -1,5 +1,5 @@
1
1
  // SPDX-License-Identifier: MIT
2
- pragma solidity ^0.8.26;
2
+ pragma solidity 0.8.28;
3
3
 
4
4
  import {StdStorage, stdStorage} from "forge-std/StdStorage.sol";
5
5
  import {JBController} from "../../../../src/JBController.sol";
@@ -1,5 +1,5 @@
1
1
  // SPDX-License-Identifier: MIT
2
- pragma solidity ^0.8.26;
2
+ pragma solidity 0.8.28;
3
3
 
4
4
  import {JBController} from "../../../../src/JBController.sol";
5
5
  import {IJBDirectory} from "../../../../src/interfaces/IJBDirectory.sol";
@@ -1,5 +1,5 @@
1
1
  // SPDX-License-Identifier: MIT
2
- pragma solidity ^0.8.26;
2
+ pragma solidity 0.8.28;
3
3
 
4
4
  import {JBController} from "../../../../src/JBController.sol";
5
5
  import {JBPermissioned} from "../../../../src/abstract/JBPermissioned.sol";
@@ -1,5 +1,5 @@
1
1
  // SPDX-License-Identifier: MIT
2
- pragma solidity ^0.8.26;
2
+ pragma solidity 0.8.28;
3
3
 
4
4
  import {JBController} from "../../../../src/JBController.sol";
5
5
  import {IJBTerminal} from "../../../../src/interfaces/IJBTerminal.sol";
@@ -1,5 +1,5 @@
1
1
  // SPDX-License-Identifier: MIT
2
- pragma solidity ^0.8.26;
2
+ pragma solidity 0.8.28;
3
3
 
4
4
  import {JBController} from "../../../../src/JBController.sol";
5
5
  import {IJBRulesetApprovalHook} from "../../../../src/interfaces/IJBRulesetApprovalHook.sol";
@@ -1,5 +1,5 @@
1
1
  // SPDX-License-Identifier: MIT
2
- pragma solidity ^0.8.26;
2
+ pragma solidity 0.8.28;
3
3
 
4
4
  import {JBController} from "../../../../src/JBController.sol";
5
5
  import {IJBController} from "../../../../src/interfaces/IJBController.sol";
@@ -1,5 +1,5 @@
1
1
  // SPDX-License-Identifier: MIT
2
- pragma solidity ^0.8.26;
2
+ pragma solidity 0.8.28;
3
3
 
4
4
  import {JBApprovalStatus} from "../../../../src/enums/JBApprovalStatus.sol";
5
5
  import {IJBRulesetApprovalHook} from "../../../../src/interfaces/IJBRulesetApprovalHook.sol";
@@ -1,5 +1,5 @@
1
1
  // SPDX-License-Identifier: MIT
2
- pragma solidity ^0.8.26;
2
+ pragma solidity 0.8.28;
3
3
 
4
4
  import {StdStorage, stdStorage} from "forge-std/StdStorage.sol";
5
5
  import {JBController} from "../../../../src/JBController.sol";
@@ -1,5 +1,5 @@
1
1
  // SPDX-License-Identifier: MIT
2
- pragma solidity ^0.8.26;
2
+ pragma solidity 0.8.28;
3
3
 
4
4
  import {JBPermissioned} from "../../../../src/abstract/JBPermissioned.sol";
5
5
  import {IJBPermissions} from "../../../../src/interfaces/IJBPermissions.sol";
@@ -1,5 +1,5 @@
1
1
  // SPDX-License-Identifier: MIT
2
- pragma solidity ^0.8.26;
2
+ pragma solidity 0.8.28;
3
3
 
4
4
  import {JBController} from "../../../../src/JBController.sol";
5
5
  import {JBPermissioned} from "../../../../src/abstract/JBPermissioned.sol";
@@ -1,5 +1,5 @@
1
1
  // SPDX-License-Identifier: MIT
2
- pragma solidity ^0.8.26;
2
+ pragma solidity 0.8.28;
3
3
 
4
4
  import {JBPermissioned} from "../../../../src/abstract/JBPermissioned.sol";
5
5
  import {IJBController} from "../../../../src/interfaces/IJBController.sol";
@@ -1,5 +1,5 @@
1
1
  // SPDX-License-Identifier: MIT
2
- pragma solidity ^0.8.26;
2
+ pragma solidity 0.8.28;
3
3
 
4
4
  import {JBController} from "../../../../src/JBController.sol";
5
5
  import {JBPermissioned} from "../../../../src/abstract/JBPermissioned.sol";
@@ -1,5 +1,5 @@
1
1
  // SPDX-License-Identifier: MIT
2
- pragma solidity ^0.8.26;
2
+ pragma solidity 0.8.28;
3
3
 
4
4
  import {IJBRulesetApprovalHook} from "../../../../src/interfaces/IJBRulesetApprovalHook.sol";
5
5
  import {IERC165} from "@openzeppelin/contracts/utils/introspection/ERC165.sol";
@@ -1,5 +1,5 @@
1
1
  // SPDX-License-Identifier: MIT
2
- pragma solidity ^0.8.26;
2
+ pragma solidity 0.8.28;
3
3
 
4
4
  import {JBDirectory} from "../../../../src/JBDirectory.sol";
5
5
  import {IJBDirectory} from "../../../../src/interfaces/IJBDirectory.sol";
@@ -1,5 +1,5 @@
1
1
  // SPDX-License-Identifier: MIT
2
- pragma solidity ^0.8.26;
2
+ pragma solidity 0.8.28;
3
3
 
4
4
  import {IJBDirectoryAccessControl} from "../../../../src/interfaces/IJBDirectoryAccessControl.sol";
5
5
  import {IJBTerminal} from "../../../../src/interfaces/IJBTerminal.sol";
@@ -1,5 +1,5 @@
1
1
  // SPDX-License-Identifier: MIT
2
- pragma solidity ^0.8.26;
2
+ pragma solidity 0.8.28;
3
3
 
4
4
  import {StdStorage, stdStorage} from "forge-std/StdStorage.sol";
5
5
  import {JBDirectory} from "../../../../src/JBDirectory.sol";
@@ -1,5 +1,5 @@
1
1
  // SPDX-License-Identifier: MIT
2
- pragma solidity ^0.8.26;
2
+ pragma solidity 0.8.28;
3
3
 
4
4
  import {StdStorage, stdStorage} from "forge-std/StdStorage.sol";
5
5
  import {IJBDirectory} from "../../../../src/interfaces/IJBDirectory.sol";
@@ -1,5 +1,5 @@
1
1
  // SPDX-License-Identifier: MIT
2
- pragma solidity ^0.8.26;
2
+ pragma solidity 0.8.28;
3
3
 
4
4
  import {StdStorage, stdStorage} from "forge-std/StdStorage.sol";
5
5
  import {JBDirectory} from "../../../../src/JBDirectory.sol";
@@ -1,5 +1,5 @@
1
1
  // SPDX-License-Identifier: MIT
2
- pragma solidity ^0.8.26;
2
+ pragma solidity 0.8.28;
3
3
 
4
4
  import {StdStorage, stdStorage} from "forge-std/StdStorage.sol";
5
5
  import {JBDirectory} from "../../../../src/JBDirectory.sol";
@@ -1,5 +1,5 @@
1
1
  // SPDX-License-Identifier: MIT
2
- pragma solidity ^0.8.26;
2
+ pragma solidity 0.8.28;
3
3
 
4
4
  import {Clones} from "@openzeppelin/contracts/proxy/Clones.sol";
5
5
  import {JBERC20} from "../../../../src/JBERC20.sol";
@@ -1,5 +1,5 @@
1
1
  // SPDX-License-Identifier: MIT
2
- pragma solidity ^0.8.26;
2
+ pragma solidity 0.8.28;
3
3
 
4
4
  contract SigUtils {
5
5
  // forge-lint: disable-next-line(mixed-case-variable)
@@ -1,5 +1,5 @@
1
1
  // SPDX-License-Identifier: MIT
2
- pragma solidity ^0.8.26;
2
+ pragma solidity 0.8.28;
3
3
 
4
4
  import {IERC20Metadata} from "@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol";
5
5
  import {JBERC20} from "../../../../src/JBERC20.sol";
@@ -1,5 +1,5 @@
1
1
  // SPDX-License-Identifier: MIT
2
- pragma solidity ^0.8.26;
2
+ pragma solidity 0.8.28;
3
3
 
4
4
  import {IERC20Metadata} from "@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol";
5
5
  import {JBERC20Setup} from "./JBERC20Setup.sol";
@@ -1,5 +1,5 @@
1
1
  // SPDX-License-Identifier: MIT
2
- pragma solidity ^0.8.26;
2
+ pragma solidity 0.8.28;
3
3
 
4
4
  import {IERC20Permit} from "@openzeppelin/contracts/token/ERC20/extensions/IERC20Permit.sol";
5
5
  import {JBERC20Setup} from "./JBERC20Setup.sol";
@@ -1,5 +1,5 @@
1
1
  // SPDX-License-Identifier: MIT
2
- pragma solidity ^0.8.26;
2
+ pragma solidity 0.8.28;
3
3
 
4
4
  import {IERC20Metadata} from "@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol";
5
5
  import {JBERC20Setup} from "./JBERC20Setup.sol";
@@ -1,5 +1,5 @@
1
1
  // SPDX-License-Identifier: MIT
2
- pragma solidity ^0.8.26;
2
+ pragma solidity 0.8.28;
3
3
 
4
4
  import {JBFeelessAddresses} from "../../../../src/JBFeelessAddresses.sol";
5
5
  import {IJBFeelessAddresses} from "../../../../src/interfaces/IJBFeelessAddresses.sol";