@bananapus/core-v6 0.0.15 → 0.0.17

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 (231) hide show
  1. package/ADMINISTRATION.md +5 -1
  2. package/ARCHITECTURE.md +2 -1
  3. package/AUDIT_INSTRUCTIONS.md +342 -0
  4. package/CHANGE_LOG.md +375 -0
  5. package/README.md +6 -6
  6. package/RISKS.md +171 -50
  7. package/SKILLS.md +11 -6
  8. package/STYLE_GUIDE.md +16 -2
  9. package/USER_JOURNEYS.md +622 -0
  10. package/package.json +2 -2
  11. package/script/Deploy.s.sol +22 -13
  12. package/script/DeployPeriphery.s.sol +76 -52
  13. package/script/helpers/CoreDeploymentLib.sol +83 -35
  14. package/src/JBChainlinkV3PriceFeed.sol +1 -0
  15. package/src/JBController.sol +23 -3
  16. package/src/JBDeadline.sol +3 -0
  17. package/src/JBDirectory.sol +2 -1
  18. package/src/JBERC20.sol +12 -3
  19. package/src/JBFundAccessLimits.sol +12 -2
  20. package/src/JBMultiTerminal.sol +53 -10
  21. package/src/JBPermissions.sol +3 -0
  22. package/src/JBPrices.sol +8 -2
  23. package/src/JBProjects.sol +1 -1
  24. package/src/JBRulesets.sol +14 -0
  25. package/src/JBSplits.sol +14 -5
  26. package/src/JBTerminalStore.sol +57 -47
  27. package/src/JBTokens.sol +43 -4
  28. package/src/interfaces/IJBController.sol +6 -0
  29. package/src/interfaces/IJBPermitTerminal.sol +1 -0
  30. package/src/interfaces/IJBTerminalStore.sol +3 -0
  31. package/src/interfaces/IJBToken.sol +5 -0
  32. package/src/interfaces/IJBTokens.sol +13 -0
  33. package/src/libraries/JBFees.sol +2 -0
  34. package/src/libraries/JBMetadataResolver.sol +24 -7
  35. package/src/libraries/JBRulesetMetadataResolver.sol +21 -21
  36. package/src/structs/JBAccountingContext.sol +1 -0
  37. package/src/structs/JBAfterCashOutRecordedContext.sol +1 -0
  38. package/src/structs/JBAfterPayRecordedContext.sol +1 -0
  39. package/src/structs/JBBeforeCashOutRecordedContext.sol +5 -0
  40. package/src/structs/JBBeforePayRecordedContext.sol +1 -0
  41. package/src/structs/JBCashOutHookSpecification.sol +1 -0
  42. package/src/structs/JBCurrencyAmount.sol +1 -0
  43. package/src/structs/JBFee.sol +1 -0
  44. package/src/structs/JBFundAccessLimitGroup.sol +1 -0
  45. package/src/structs/JBPayHookSpecification.sol +1 -0
  46. package/src/structs/JBPermissionsData.sol +1 -0
  47. package/src/structs/JBRuleset.sol +1 -0
  48. package/src/structs/JBRulesetConfig.sol +1 -0
  49. package/src/structs/JBRulesetMetadata.sol +1 -0
  50. package/src/structs/JBRulesetWeightCache.sol +1 -0
  51. package/src/structs/JBRulesetWithMetadata.sol +1 -0
  52. package/src/structs/JBSingleAllowance.sol +1 -0
  53. package/src/structs/JBSplit.sol +1 -0
  54. package/src/structs/JBSplitGroup.sol +1 -0
  55. package/src/structs/JBSplitHookContext.sol +1 -0
  56. package/src/structs/JBTerminalConfig.sol +1 -0
  57. package/src/structs/JBTokenAmount.sol +1 -0
  58. package/test/ComprehensiveInvariant.t.sol +15 -2
  59. package/test/CoreExploitTests.t.sol +34 -1
  60. package/test/EconomicSimulation.t.sol +10 -2
  61. package/test/EntryPointPermutations.t.sol +17 -3
  62. package/test/FlashLoanAttacks.t.sol +12 -1
  63. package/test/PermissionEscalation.t.sol +53 -10
  64. package/test/RulesetTransitions.t.sol +15 -1
  65. package/test/SplitLoopTests.t.sol +25 -2
  66. package/test/TestAccessToFunds.sol +17 -2
  67. package/test/TestAuditResponseDesignProofs.sol +434 -0
  68. package/test/TestCashOut.sol +15 -1
  69. package/test/TestCashOutCountFor.sol +1 -1
  70. package/test/TestCashOutHooks.sol +47 -25
  71. package/test/TestCashOutTimingEdge.sol +13 -1
  72. package/test/TestDataHookFuzzing.sol +520 -0
  73. package/test/TestDurationUnderflow.sol +13 -1
  74. package/test/TestFeeFreeCashOutBypass.sol +617 -0
  75. package/test/TestFeeProcessingFailure.sol +16 -1
  76. package/test/TestFees.sol +14 -1
  77. package/test/TestInterfaceSupport.sol +20 -1
  78. package/test/TestJBERC20Inheritance.sol +11 -1
  79. package/test/TestL2SequencerPriceFeed.sol +292 -0
  80. package/test/TestLaunchProject.sol +13 -1
  81. package/test/TestMetaTx.sol +15 -1
  82. package/test/TestMetadataOffsetOverflow.sol +179 -0
  83. package/test/TestMetadataParserLib.sol +37 -4
  84. package/test/TestMigrationHeldFees.sol +16 -1
  85. package/test/TestMintTokensOf.sol +14 -1
  86. package/test/TestMultiTerminalSurplus.sol +348 -0
  87. package/test/TestMultiTokenSurplus.sol +14 -1
  88. package/test/TestMultipleAccessLimits.sol +23 -1
  89. package/test/TestPayBurnRedeemFlow.sol +16 -1
  90. package/test/TestPayHooks.sol +33 -14
  91. package/test/TestPermissions.sol +20 -1
  92. package/test/TestPermissionsEdge.sol +5 -1
  93. package/test/TestPermit2DataHook.t.sol +360 -0
  94. package/test/TestPermit2Terminal.sol +36 -3
  95. package/test/TestRulesetQueueing.sol +23 -1
  96. package/test/TestRulesetQueuingStress.sol +20 -1
  97. package/test/TestRulesetWeightCaching.sol +127 -125
  98. package/test/TestSplits.sol +23 -1
  99. package/test/TestTerminalMigration.sol +11 -1
  100. package/test/TestTokenFlow.sol +18 -1
  101. package/test/TestWeightCacheStaleAfterRejection.sol +15 -1
  102. package/test/WeirdTokenTests.t.sol +54 -1
  103. package/test/fork/TestChainlinkPriceFeedFork.sol +6 -1
  104. package/test/formal/BondingCurveProperties.t.sol +8 -1
  105. package/test/formal/FeeProperties.t.sol +7 -1
  106. package/test/helpers/JBTest.sol +1 -1
  107. package/test/helpers/TestBaseWorkflow.sol +84 -1
  108. package/test/invariants/Phase3DeepInvariant.t.sol +13 -2
  109. package/test/invariants/RulesetsInvariant.t.sol +12 -2
  110. package/test/invariants/TerminalStoreInvariant.t.sol +11 -2
  111. package/test/invariants/TokensInvariant.t.sol +13 -2
  112. package/test/invariants/handlers/ComprehensiveHandler.sol +19 -1
  113. package/test/invariants/handlers/EconomicHandler.sol +31 -1
  114. package/test/invariants/handlers/Phase3Handler.sol +31 -1
  115. package/test/invariants/handlers/RulesetsHandler.sol +5 -1
  116. package/test/invariants/handlers/TerminalStoreHandler.sol +6 -1
  117. package/test/invariants/handlers/TokensHandler.sol +1 -1
  118. package/test/mock/MockERC20.sol +0 -2
  119. package/test/mock/MockMaliciousBeneficiary.sol +2 -1
  120. package/test/mock/MockMaliciousSplitHook.sol +2 -1
  121. package/test/mock/MockPriceFeed.sol +1 -1
  122. package/test/regression/HoldFeesCashOutReserved.t.sol +415 -0
  123. package/test/regression/WeightCacheBoundary.t.sol +291 -0
  124. package/test/units/static/JBChainlinkV3PriceFeed/TestPriceFeed.sol +0 -1
  125. package/test/units/static/JBController/JBControllerSetup.sol +10 -1
  126. package/test/units/static/JBController/TestBurnTokensOf.sol +8 -1
  127. package/test/units/static/JBController/TestClaimTokensFor.sol +4 -1
  128. package/test/units/static/JBController/TestDeployErc20For.sol +7 -1
  129. package/test/units/static/JBController/TestLaunchProjectFor.sol +21 -1
  130. package/test/units/static/JBController/TestLaunchRulesetsFor.sol +21 -1
  131. package/test/units/static/JBController/TestMigrateController.sol +10 -1
  132. package/test/units/static/JBController/TestMintTokensOfUnits.sol +10 -1
  133. package/test/units/static/JBController/TestPayReservedTokenToTerminal.sol +4 -1
  134. package/test/units/static/JBController/TestReceiveMigrationFrom.sol +5 -1
  135. package/test/units/static/JBController/TestRulesetViews.sol +7 -1
  136. package/test/units/static/JBController/TestSendReservedTokensToSplitsOf.sol +21 -1
  137. package/test/units/static/JBController/TestSetSplitGroupsOf.sol +6 -1
  138. package/test/units/static/JBController/TestSetTokenFor.sol +13 -1
  139. package/test/units/static/JBController/TestSetUriOf.sol +5 -1
  140. package/test/units/static/JBController/TestTransferCreditsFrom.sol +11 -1
  141. package/test/units/static/JBDeadline/TestDeadlineFuzz.sol +12 -1
  142. package/test/units/static/JBDirectory/JBDirectorySetup.sol +4 -1
  143. package/test/units/static/JBDirectory/TestPrimaryTerminalOf.sol +5 -1
  144. package/test/units/static/JBDirectory/TestSetControllerOf.sol +11 -1
  145. package/test/units/static/JBDirectory/TestSetControllerOfMigrationOrder.sol +7 -1
  146. package/test/units/static/JBDirectory/TestSetPrimaryTerminalOf.sol +11 -1
  147. package/test/units/static/JBDirectory/TestSetTerminalsOf.sol +10 -1
  148. package/test/units/static/JBERC20/JBERC20Setup.sol +2 -1
  149. package/test/units/static/JBERC20/SigUtils.sol +2 -0
  150. package/test/units/static/JBERC20/TestInitialize.sol +1 -1
  151. package/test/units/static/JBERC20/TestName.sol +1 -1
  152. package/test/units/static/JBERC20/TestNonces.sol +3 -1
  153. package/test/units/static/JBERC20/TestSymbol.sol +1 -1
  154. package/test/units/static/JBFeelessAdresses/JBFeelessSetup.sol +2 -1
  155. package/test/units/static/JBFeelessAdresses/TestInterfaces.sol +2 -1
  156. package/test/units/static/JBFeelessAdresses/TestSetFeelessAddress.sol +1 -1
  157. package/test/units/static/JBFees/TestFeesFuzz.sol +1 -1
  158. package/test/units/static/JBFixedPointNumber/TestAdjustDecimals.sol +0 -1
  159. package/test/units/static/JBFixedPointNumber/TestAdjustDecimalsFuzz.sol +0 -1
  160. package/test/units/static/JBFundAccessLimits/JBFundAccessSetup.sol +3 -1
  161. package/test/units/static/JBFundAccessLimits/TestFundAccessLimitsEdge.sol +4 -1
  162. package/test/units/static/JBFundAccessLimits/TestPayoutLimitOf.sol +4 -1
  163. package/test/units/static/JBFundAccessLimits/TestPayoutLimitsOf.sol +8 -1
  164. package/test/units/static/JBFundAccessLimits/TestSetFundAccessLimitsFor.sol +8 -1
  165. package/test/units/static/JBFundAccessLimits/TestSurplusAllowanceOf.sol +4 -1
  166. package/test/units/static/JBFundAccessLimits/TestSurplusAllowancesOf.sol +7 -1
  167. package/test/units/static/JBMetadataResolver/TestGetDataFor.sol +1 -1
  168. package/test/units/static/JBMetadataResolver/TestMetadataResolverEdgeCases.sol +2 -1
  169. package/test/units/static/JBMetadataResolver/TestMetadataResolverFuzz.sol +2 -1
  170. package/test/units/static/JBMultiTerminal/JBMultiTerminalSetup.sol +12 -1
  171. package/test/units/static/JBMultiTerminal/TestAccountingContextsOf.sol +9 -1
  172. package/test/units/static/JBMultiTerminal/TestAddAccountingContextsFor.sol +18 -2
  173. package/test/units/static/JBMultiTerminal/TestAddToBalanceOf.sol +44 -9
  174. package/test/units/static/JBMultiTerminal/TestCashOutTokensOf.sol +48 -23
  175. package/test/units/static/JBMultiTerminal/TestExecutePayout.sol +18 -2
  176. package/test/units/static/JBMultiTerminal/TestExecuteProcessFee.sol +13 -3
  177. package/test/units/static/JBMultiTerminal/TestMigrateBalanceOf.sol +21 -4
  178. package/test/units/static/JBMultiTerminal/TestPay.sol +35 -7
  179. package/test/units/static/JBMultiTerminal/TestProcessHeldFeesOf.sol +206 -19
  180. package/test/units/static/JBMultiTerminal/TestSendPayoutsOf.sol +15 -1
  181. package/test/units/static/JBMultiTerminal/TestUseAllowanceOf.sol +297 -1
  182. package/test/units/static/JBPermissions/JBPermissionsSetup.sol +2 -1
  183. package/test/units/static/JBPermissions/TestHasPermission.sol +1 -1
  184. package/test/units/static/JBPermissions/TestHasPermissions.sol +1 -1
  185. package/test/units/static/JBPermissions/TestSetPermissionsFor.sol +3 -1
  186. package/test/units/static/JBPrices/JBPricesSetup.sol +6 -1
  187. package/test/units/static/JBPrices/TestAddPriceFeedFor.sol +6 -1
  188. package/test/units/static/JBPrices/TestPricePerUnitOf.sol +4 -1
  189. package/test/units/static/JBPrices/TestPrices.sol +4 -1
  190. package/test/units/static/JBProjects/JBProjectsSetup.sol +2 -1
  191. package/test/units/static/JBProjects/TestCreateFor.sol +3 -1
  192. package/test/units/static/JBProjects/TestInitialProject.sol +2 -1
  193. package/test/units/static/JBProjects/TestInterfaces.sol +0 -1
  194. package/test/units/static/JBProjects/TestSetResolver.sol +2 -1
  195. package/test/units/static/JBProjects/TestTokenUri.sol +3 -1
  196. package/test/units/static/JBRulesetMetadataResolver/TestSetCashOutTaxRateTo.sol +9 -1
  197. package/test/units/static/JBRulesets/JBRulesetsSetup.sol +3 -1
  198. package/test/units/static/JBRulesets/TestCurrentApprovalStatusForLatestRulesetOf.sol +9 -1
  199. package/test/units/static/JBRulesets/TestCurrentOf.sol +10 -1
  200. package/test/units/static/JBRulesets/TestGetRulesetOf.sol +7 -1
  201. package/test/units/static/JBRulesets/TestLatestQueuedRulesetOf.sol +9 -1
  202. package/test/units/static/JBRulesets/TestRulesets.sol +12 -1
  203. package/test/units/static/JBRulesets/TestRulesetsOf.sol +1 -1
  204. package/test/units/static/JBRulesets/TestUpcomingRulesetOf.sol +10 -1
  205. package/test/units/static/JBRulesets/TestUpdateRulesetWeightCache.sol +6 -1
  206. package/test/units/static/JBSplits/JBSplitsSetup.sol +3 -1
  207. package/test/units/static/JBSplits/TestSelfManagedSplitGroups.sol +63 -13
  208. package/test/units/static/JBSplits/TestSetSplitGroupsOf.sol +8 -1
  209. package/test/units/static/JBSplits/TestSplitsLockedEdge.sol +6 -1
  210. package/test/units/static/JBSplits/TestSplitsOf.sol +1 -1
  211. package/test/units/static/JBSplits/TestSplitsPacking.sol +5 -2
  212. package/test/units/static/JBSurplus/TestSurplusFuzz.sol +3 -1
  213. package/test/units/static/JBTerminalStore/JBTerminalStoreSetup.sol +5 -1
  214. package/test/units/static/JBTerminalStore/TestCurrentReclaimableSurplusOf.sol +14 -1
  215. package/test/units/static/JBTerminalStore/TestCurrentSurplusOf.sol +14 -1
  216. package/test/units/static/JBTerminalStore/TestCurrentTotalSurplusOf.sol +3 -1
  217. package/test/units/static/JBTerminalStore/TestRecordCashOutsFor.sol +92 -1
  218. package/test/units/static/JBTerminalStore/TestRecordPaymentFrom.sol +15 -1
  219. package/test/units/static/JBTerminalStore/TestRecordPayoutFor.sol +13 -1
  220. package/test/units/static/JBTerminalStore/TestRecordTerminalMigration.sol +8 -1
  221. package/test/units/static/JBTerminalStore/TestRecordUsedAllowanceOf.sol +16 -1
  222. package/test/units/static/JBTerminalStore/TestUint224Overflow.sol +15 -1
  223. package/test/units/static/JBTokens/JBTokensSetup.sol +5 -1
  224. package/test/units/static/JBTokens/TestBurnFrom.sol +4 -1
  225. package/test/units/static/JBTokens/TestClaimTokensFor.sol +4 -1
  226. package/test/units/static/JBTokens/TestDeployERC20ForUnits.sol +4 -1
  227. package/test/units/static/JBTokens/TestMintFor.sol +4 -1
  228. package/test/units/static/JBTokens/TestSetTokenFor.sol +4 -1
  229. package/test/units/static/JBTokens/TestTotalBalanceOf.sol +1 -1
  230. package/test/units/static/JBTokens/TestTotalSupplyOf.sol +1 -1
  231. package/test/units/static/JBTokens/TestTransferCreditsFrom.sol +3 -1
@@ -1,7 +1,20 @@
1
1
  // SPDX-License-Identifier: MIT
2
2
  pragma solidity ^0.8.6;
3
3
 
4
- import /* {*} from */ "./helpers/TestBaseWorkflow.sol";
4
+ import {TestBaseWorkflow} from "./helpers/TestBaseWorkflow.sol";
5
+ import {JBDeadline} from "../src/JBDeadline.sol";
6
+ import {JBApprovalStatus} from "../src/enums/JBApprovalStatus.sol";
7
+ import {IJBController} from "../src/interfaces/IJBController.sol";
8
+ import {IJBRulesetApprovalHook} from "../src/interfaces/IJBRulesetApprovalHook.sol";
9
+ import {IJBRulesets} from "../src/interfaces/IJBRulesets.sol";
10
+ import {JBConstants} from "../src/libraries/JBConstants.sol";
11
+ import {JBFundAccessLimitGroup} from "../src/structs/JBFundAccessLimitGroup.sol";
12
+ import {JBRuleset} from "../src/structs/JBRuleset.sol";
13
+ import {JBRulesetConfig} from "../src/structs/JBRulesetConfig.sol";
14
+ import {JBRulesetMetadata} from "../src/structs/JBRulesetMetadata.sol";
15
+ import {JBSplitGroup} from "../src/structs/JBSplitGroup.sol";
16
+ import {JBTerminalConfig} from "../src/structs/JBTerminalConfig.sol";
17
+ import {IERC165} from "@openzeppelin/contracts/utils/introspection/ERC165.sol";
5
18
 
6
19
  /// @notice Mock approval hook that always returns a configurable status.
7
20
  contract MockApprovalHookConfigurable is IJBRulesetApprovalHook {
@@ -350,6 +363,7 @@ contract MockApprovalHookConfigurable is IJBRulesetApprovalHook {
350
363
  pid,
351
364
  0,
352
365
  FOURTEEN_DAYS,
366
+ // forge-lint: disable-next-line(unsafe-typecast)
353
367
  uint112(INITIAL_WEIGHT + uint112(i) * 100e18),
354
368
  0,
355
369
  IJBRulesetApprovalHook(address(0))
@@ -544,6 +558,7 @@ contract MockApprovalHookConfigurable is IJBRulesetApprovalHook {
544
558
 
545
559
  _queueRuleset(
546
560
  pid,
561
+ // forge-lint: disable-next-line(unsafe-typecast)
547
562
  uint48(originalStart + SEVEN_DAYS),
548
563
  SEVEN_DAYS,
549
564
  INITIAL_WEIGHT * 2,
@@ -563,6 +578,7 @@ contract MockApprovalHookConfigurable is IJBRulesetApprovalHook {
563
578
  // 1 second after first boundary -> should snap to second boundary.
564
579
  _queueRuleset(
565
580
  pid,
581
+ // forge-lint: disable-next-line(unsafe-typecast)
566
582
  uint48(originalStart + SEVEN_DAYS + 1),
567
583
  SEVEN_DAYS,
568
584
  INITIAL_WEIGHT * 2,
@@ -599,6 +615,7 @@ contract MockApprovalHookConfigurable is IJBRulesetApprovalHook {
599
615
  pid,
600
616
  0,
601
617
  SEVEN_DAYS,
618
+ // forge-lint: disable-next-line(unsafe-typecast)
602
619
  uint112(INITIAL_WEIGHT + uint112(i) * 100e18),
603
620
  0,
604
621
  IJBRulesetApprovalHook(address(0))
@@ -608,6 +625,7 @@ contract MockApprovalHookConfigurable is IJBRulesetApprovalHook {
608
625
  JBRuleset memory current = _rulesets.currentOf(pid);
609
626
  assertEq(
610
627
  current.weight,
628
+ // forge-lint: disable-next-line(unsafe-typecast)
611
629
  INITIAL_WEIGHT + uint112(i) * 100e18,
612
630
  string.concat("Cycle ", vm.toString(i + 1), " weight mismatch")
613
631
  );
@@ -765,6 +783,7 @@ contract MockApprovalHookConfigurable is IJBRulesetApprovalHook {
765
783
  pid,
766
784
  0,
767
785
  SEVEN_DAYS,
786
+ // forge-lint: disable-next-line(unsafe-typecast)
768
787
  uint112(INITIAL_WEIGHT + uint112(i) * 100e18),
769
788
  0,
770
789
  IJBRulesetApprovalHook(address(0))
@@ -1,7 +1,17 @@
1
1
  // SPDX-License-Identifier: MIT
2
2
  pragma solidity >=0.8.6;
3
3
 
4
- import /* {*} from */ "./helpers/TestBaseWorkflow.sol";
4
+ import {TestBaseWorkflow} from "./helpers/TestBaseWorkflow.sol";
5
+ import {IJBController} from "../src/interfaces/IJBController.sol";
6
+ import {IJBRulesets} from "../src/interfaces/IJBRulesets.sol";
7
+ import {IJBRulesetApprovalHook} from "../src/interfaces/IJBRulesetApprovalHook.sol";
8
+ import {JBConstants} from "../src/libraries/JBConstants.sol";
9
+ import {JBRulesetMetadata} from "../src/structs/JBRulesetMetadata.sol";
10
+ import {JBRulesetConfig} from "../src/structs/JBRulesetConfig.sol";
11
+ import {JBRuleset} from "../src/structs/JBRuleset.sol";
12
+ import {JBSplitGroup} from "../src/structs/JBSplitGroup.sol";
13
+ import {JBFundAccessLimitGroup} from "../src/structs/JBFundAccessLimitGroup.sol";
14
+ import {JBTerminalConfig} from "../src/structs/JBTerminalConfig.sol";
5
15
 
6
16
  // A ruleset's weight can be cached to make larger intervals calculable while staying within the gas limit.
7
17
  contract TestRulesetWeightCaching_Local is TestBaseWorkflow {
@@ -47,130 +57,122 @@ contract TestRulesetWeightCaching_Local is TestBaseWorkflow {
47
57
  }
48
58
 
49
59
  /// Test that caching a ruleset's weight yields the same result as computing it.
60
+ /// @dev Bounded to 1,000 rulesets for CI speed. For full coverage (80,000), run:
61
+ /// forge test --match-test testWeightCaching -vvv --fuzz-runs 8
50
62
  function testWeightCaching(uint256 _rulesetDiff) public {
51
- // TODO temporarily removed for faster test suite
52
- // // Bound to 8x the weight cut multiple cache threshold.
53
- // _rulesetDiff = bound(_rulesetDiff, 0, 80000);
54
-
55
- // // Keep references to the projects.
56
- // uint256 _projectId1;
57
- // uint256 _projectId2;
58
-
59
- // // Package up the ruleset configuration.
60
- // JBRulesetConfig[] memory _rulesetConfigurations = new JBRulesetConfig[](1);
61
-
62
- // {
63
- // _rulesetConfigurations[0].mustStartAtOrAfter = 0;
64
- // _rulesetConfigurations[0].duration = _DURATION;
65
- // _rulesetConfigurations[0].weight = 1000 * 10 ** _WEIGHT_DECIMALS;
66
- // _rulesetConfigurations[0].weightCutPercent = _WEIGHT_CUT_PERCENT;
67
- // _rulesetConfigurations[0].approvalHook = IJBRulesetApprovalHook(address(0));
68
- // _rulesetConfigurations[0].metadata = _metadata;
69
- // _rulesetConfigurations[0].splitGroups = new JBSplitGroup[](0);
70
- // _rulesetConfigurations[0].fundAccessLimitGroups = new JBFundAccessLimitGroup[](0);
71
-
72
- // // Create the project to test.
73
- // _projectId1 = _controller.launchProjectFor({
74
- // owner: _projectOwner,
75
- // projectUri: "myIPFSHash",
76
- // rulesetConfigurations: _rulesetConfigurations,
77
- // terminalConfigurations: new JBTerminalConfig[](0),
78
- // memo: ""
79
- // });
80
-
81
- // // Create the project to test.
82
- // _projectId2 = _controller.launchProjectFor({
83
- // owner: _projectOwner,
84
- // projectUri: "myIPFSHash",
85
- // rulesetConfigurations: _rulesetConfigurations,
86
- // terminalConfigurations: new JBTerminalConfig[](0),
87
- // memo: ""
88
- // });
89
- // }
90
-
91
- // // Keep a reference to the current rulesets.
92
- // JBRuleset memory _ruleset1 = jbRulesets().currentOf(_projectId1);
93
- // JBRuleset memory _ruleset2 = jbRulesets().currentOf(_projectId2);
94
-
95
- // // Go a few rolled over rulesets into the future.
96
- // vm.warp(block.timestamp + (_DURATION * 10));
97
-
98
- // // Keep a reference to the amount of gas before the caching call.
99
- // uint256 _gasBeforeCache = gasleft();
100
-
101
- // // Cache the weight in the second project.
102
- // _rulesets.updateRulesetWeightCache(_projectId2);
103
-
104
- // // Keep a reference to the amout of gas spent on the call.
105
- // uint256 _gasDiffCache = _gasBeforeCache - gasleft();
106
-
107
- // // Make sure the difference is within the gas limit.
108
- // assertLe(_gasDiffCache, _GAS_LIMIT);
109
-
110
- // // Go many rolled over rulesets into the future.
111
- // vm.warp(block.timestamp + (_DURATION * _rulesetDiff));
112
-
113
- // // Cache the weight in the second project again.
114
- // _rulesets.updateRulesetWeightCache(_projectId2);
115
-
116
- // // Inherit the weight.
117
- // _rulesetConfigurations[0].data.weight = 0;
118
-
119
- // // Keep a reference to the amount of gas before the call.
120
- // uint256 _gasBefore1 = gasleft();
121
-
122
- // // Queue the ruleset.
123
- // vm.startPrank(_projectOwner);
124
- // _controller.queueRulesetsOf({
125
- // projectId: _projectId1,
126
- // rulesetConfigurations: _rulesetConfigurations,
127
- // memo: ""
128
- // });
129
-
130
- // // Keep a reference to the amout of gas spent on the call.
131
- // uint256 _gasDiff1 = _gasBefore1 - gasleft();
132
-
133
- // // Make sure the difference is within the gas limit.
134
- // assertLe(_gasDiff1, _GAS_LIMIT);
135
-
136
- // // Keep a reference to the amount of gas before the call.
137
- // uint256 _gasBefore2 = gasleft();
138
-
139
- // _controller.queueRulesetsOf({
140
- // projectId: _projectId2,
141
- // rulesetConfigurations: _rulesetConfigurations,
142
- // memo: ""
143
- // });
144
- // vm.stopPrank();
145
-
146
- // // Keep a reference to the amout of gas spent on the call.
147
- // uint256 _gasDiff2 = _gasBefore2 - gasleft();
148
-
149
- // // Make sure the difference is within the gas limit.
150
- // assertLe(_gasDiff2, _GAS_LIMIT);
151
-
152
- // // Renew the reference to the current ruleset.
153
- // _ruleset1 = jbRulesets().currentOf(_projectId1);
154
- // _ruleset2 = jbRulesets().currentOf(_projectId2);
155
-
156
- // // The cached call should have been cheaper.
157
- // assertLe(_gasDiff2, _gasDiff1);
158
-
159
- // // Make sure the rulesets have the same weight.
160
- // assertEq(_ruleset1.weight, _ruleset2.weight);
161
-
162
- // // Cache the weight in the second project again.
163
- // _rulesets.updateRulesetWeightCache(_projectId2);
164
-
165
- // // Go many rolled over rulesets into the future.
166
- // vm.warp(block.timestamp + (_DURATION * _rulesetDiff));
167
-
168
- // // Queue the ruleset.
169
- // vm.prank(_projectOwner);
170
- // _controller.queueRulesetsOf({
171
- // projectId: _projectId2,
172
- // rulesetConfigurations: _rulesetConfigurations,
173
- // memo: ""
174
- // });
63
+ // Bound to a CI-friendly range. The cache threshold is 20,000 iterations;
64
+ // 1,000 is enough to exercise the caching path without extreme gas usage.
65
+ _rulesetDiff = bound(_rulesetDiff, 0, 1000);
66
+
67
+ // Keep references to the projects.
68
+ uint256 _projectId1;
69
+ uint256 _projectId2;
70
+
71
+ // Package up the ruleset configuration.
72
+ JBRulesetConfig[] memory _rulesetConfigurations = new JBRulesetConfig[](1);
73
+
74
+ {
75
+ _rulesetConfigurations[0].mustStartAtOrAfter = 0;
76
+ // forge-lint: disable-next-line(unsafe-typecast)
77
+ _rulesetConfigurations[0].duration = uint32(_DURATION); // safe: _DURATION = 1
78
+ _rulesetConfigurations[0].weight = uint112(1000 * 10 ** _WEIGHT_DECIMALS);
79
+ // forge-lint: disable-next-line(unsafe-typecast)
80
+ _rulesetConfigurations[0].weightCutPercent = uint32(_WEIGHT_CUT_PERCENT); // safe: _WEIGHT_CUT_PERCENT = 1
81
+ _rulesetConfigurations[0].approvalHook = IJBRulesetApprovalHook(address(0));
82
+ _rulesetConfigurations[0].metadata = _metadata;
83
+ _rulesetConfigurations[0].splitGroups = new JBSplitGroup[](0);
84
+ _rulesetConfigurations[0].fundAccessLimitGroups = new JBFundAccessLimitGroup[](0);
85
+
86
+ // Create the project to test.
87
+ _projectId1 = _controller.launchProjectFor({
88
+ owner: _projectOwner,
89
+ projectUri: "myIPFSHash",
90
+ rulesetConfigurations: _rulesetConfigurations,
91
+ terminalConfigurations: new JBTerminalConfig[](0),
92
+ memo: ""
93
+ });
94
+
95
+ // Create the project to test.
96
+ _projectId2 = _controller.launchProjectFor({
97
+ owner: _projectOwner,
98
+ projectUri: "myIPFSHash",
99
+ rulesetConfigurations: _rulesetConfigurations,
100
+ terminalConfigurations: new JBTerminalConfig[](0),
101
+ memo: ""
102
+ });
103
+ }
104
+
105
+ // Keep a reference to the current rulesets.
106
+ JBRuleset memory _ruleset1 = jbRulesets().currentOf(_projectId1);
107
+ JBRuleset memory _ruleset2 = jbRulesets().currentOf(_projectId2);
108
+
109
+ // Go a few rolled over rulesets into the future.
110
+ vm.warp(block.timestamp + (_DURATION * 10));
111
+
112
+ // Keep a reference to the amount of gas before the caching call.
113
+ uint256 _gasBeforeCache = gasleft();
114
+
115
+ // Cache the weight in the second project using the latest ruleset ID.
116
+ _rulesets.updateRulesetWeightCache(_projectId2, _rulesets.latestRulesetIdOf(_projectId2));
117
+
118
+ // Keep a reference to the amount of gas spent on the call.
119
+ uint256 _gasDiffCache = _gasBeforeCache - gasleft();
120
+
121
+ // Make sure the difference is within the gas limit.
122
+ assertLe(_gasDiffCache, _GAS_LIMIT);
123
+
124
+ // Go many rolled over rulesets into the future.
125
+ vm.warp(block.timestamp + (_DURATION * _rulesetDiff));
126
+
127
+ // Cache the weight in the second project again.
128
+ _rulesets.updateRulesetWeightCache(_projectId2, _rulesets.latestRulesetIdOf(_projectId2));
129
+
130
+ // Inherit the weight (weight=1 is the inherit sentinel in the current API).
131
+ _rulesetConfigurations[0].weight = 1;
132
+
133
+ // Keep a reference to the amount of gas before the call.
134
+ uint256 _gasBefore1 = gasleft();
135
+
136
+ // Queue the ruleset.
137
+ vm.startPrank(_projectOwner);
138
+ _controller.queueRulesetsOf({projectId: _projectId1, rulesetConfigurations: _rulesetConfigurations, memo: ""});
139
+
140
+ // Keep a reference to the amount of gas spent on the call.
141
+ uint256 _gasDiff1 = _gasBefore1 - gasleft();
142
+
143
+ // Make sure the difference is within the gas limit.
144
+ assertLe(_gasDiff1, _GAS_LIMIT);
145
+
146
+ // Keep a reference to the amount of gas before the call.
147
+ uint256 _gasBefore2 = gasleft();
148
+
149
+ _controller.queueRulesetsOf({projectId: _projectId2, rulesetConfigurations: _rulesetConfigurations, memo: ""});
150
+ vm.stopPrank();
151
+
152
+ // Keep a reference to the amount of gas spent on the call.
153
+ uint256 _gasDiff2 = _gasBefore2 - gasleft();
154
+
155
+ // Make sure the difference is within the gas limit.
156
+ assertLe(_gasDiff2, _GAS_LIMIT);
157
+
158
+ // Renew the reference to the current ruleset.
159
+ _ruleset1 = jbRulesets().currentOf(_projectId1);
160
+ _ruleset2 = jbRulesets().currentOf(_projectId2);
161
+
162
+ // The cached call should have been cheaper.
163
+ assertLe(_gasDiff2, _gasDiff1);
164
+
165
+ // Make sure the rulesets have the same weight.
166
+ assertEq(_ruleset1.weight, _ruleset2.weight);
167
+
168
+ // Cache the weight in the second project again.
169
+ _rulesets.updateRulesetWeightCache(_projectId2, _rulesets.latestRulesetIdOf(_projectId2));
170
+
171
+ // Go many rolled over rulesets into the future.
172
+ vm.warp(block.timestamp + (_DURATION * _rulesetDiff));
173
+
174
+ // Queue the ruleset.
175
+ vm.prank(_projectOwner);
176
+ _controller.queueRulesetsOf({projectId: _projectId2, rulesetConfigurations: _rulesetConfigurations, memo: ""});
175
177
  }
176
178
  }
@@ -1,7 +1,27 @@
1
1
  // SPDX-License-Identifier: MIT
2
2
  pragma solidity ^0.8.6;
3
3
 
4
- import /* {*} from */ "./helpers/TestBaseWorkflow.sol";
4
+ import {TestBaseWorkflow} from "./helpers/TestBaseWorkflow.sol";
5
+ import {JBTerminalStore} from "../src/JBTerminalStore.sol";
6
+ import {IJBController} from "../src/interfaces/IJBController.sol";
7
+ import {IJBDirectory} from "../src/interfaces/IJBDirectory.sol";
8
+ import {IJBMultiTerminal} from "../src/interfaces/IJBMultiTerminal.sol";
9
+ import {IJBRulesetApprovalHook} from "../src/interfaces/IJBRulesetApprovalHook.sol";
10
+ import {IJBSplitHook} from "../src/interfaces/IJBSplitHook.sol";
11
+ import {IJBTerminal} from "../src/interfaces/IJBTerminal.sol";
12
+ import {IJBTokens} from "../src/interfaces/IJBTokens.sol";
13
+ import {JBConstants} from "../src/libraries/JBConstants.sol";
14
+ import {JBSplitGroupIds} from "../src/libraries/JBSplitGroupIds.sol";
15
+ import {JBAccountingContext} from "../src/structs/JBAccountingContext.sol";
16
+ import {JBCurrencyAmount} from "../src/structs/JBCurrencyAmount.sol";
17
+ import {JBFundAccessLimitGroup} from "../src/structs/JBFundAccessLimitGroup.sol";
18
+ import {JBRulesetConfig} from "../src/structs/JBRulesetConfig.sol";
19
+ import {JBRulesetMetadata} from "../src/structs/JBRulesetMetadata.sol";
20
+ import {JBSplit} from "../src/structs/JBSplit.sol";
21
+ import {JBSplitGroup} from "../src/structs/JBSplitGroup.sol";
22
+ import {JBTerminalConfig} from "../src/structs/JBTerminalConfig.sol";
23
+ import {IERC20Metadata} from "@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol";
24
+ import {mulDiv} from "@prb/math/src/Common.sol";
5
25
 
6
26
  contract TestSplits_Local is TestBaseWorkflow {
7
27
  IJBController private _controller;
@@ -304,6 +324,7 @@ contract TestSplits_Local is TestBaseWorkflow {
304
324
  // Set up a payout split recipient.
305
325
  _splits[0] = JBSplit({
306
326
  preferAddToBalance: false,
327
+ // forge-lint: disable-next-line(unsafe-typecast)
307
328
  percent: uint32(JBConstants.SPLITS_TOTAL_PERCENT / _multiplier),
308
329
  projectId: 0,
309
330
  beneficiary: _splitsGuy,
@@ -314,6 +335,7 @@ contract TestSplits_Local is TestBaseWorkflow {
314
335
  // A dummy used to check that splits groups of "0" don't bypass payout limits.
315
336
  _splits[1] = JBSplit({
316
337
  preferAddToBalance: false,
338
+ // forge-lint: disable-next-line(unsafe-typecast)
317
339
  percent: uint32(JBConstants.SPLITS_TOTAL_PERCENT / _multiplier),
318
340
  projectId: 0,
319
341
  beneficiary: _splitsGuy,
@@ -1,7 +1,17 @@
1
1
  // SPDX-License-Identifier: MIT
2
2
  pragma solidity ^0.8.6;
3
3
 
4
- import /* {*} from */ "./helpers/TestBaseWorkflow.sol";
4
+ import {TestBaseWorkflow} from "./helpers/TestBaseWorkflow.sol";
5
+ import {JBMultiTerminal} from "../src/JBMultiTerminal.sol";
6
+ import {IJBController} from "../src/interfaces/IJBController.sol";
7
+ import {IJBRulesetApprovalHook} from "../src/interfaces/IJBRulesetApprovalHook.sol";
8
+ import {JBConstants} from "../src/libraries/JBConstants.sol";
9
+ import {JBAccountingContext} from "../src/structs/JBAccountingContext.sol";
10
+ import {JBFundAccessLimitGroup} from "../src/structs/JBFundAccessLimitGroup.sol";
11
+ import {JBRulesetConfig} from "../src/structs/JBRulesetConfig.sol";
12
+ import {JBRulesetMetadata} from "../src/structs/JBRulesetMetadata.sol";
13
+ import {JBSplitGroup} from "../src/structs/JBSplitGroup.sol";
14
+ import {JBTerminalConfig} from "../src/structs/JBTerminalConfig.sol";
5
15
 
6
16
  /// @notice E2E test: Pay into terminal A -> migrate to terminal B -> verify balances, surplus, cash outs.
7
17
  contract TestTerminalMigration_Local is TestBaseWorkflow {
@@ -1,7 +1,24 @@
1
1
  // SPDX-License-Identifier: MIT
2
2
  pragma solidity ^0.8.6;
3
3
 
4
- import /* {*} from */ "./helpers/TestBaseWorkflow.sol";
4
+ import {TestBaseWorkflow} from "./helpers/TestBaseWorkflow.sol";
5
+ import {JBController} from "../src/JBController.sol";
6
+ import {JBERC20} from "../src/JBERC20.sol";
7
+ import {JBTokens} from "../src/JBTokens.sol";
8
+ import {IJBController} from "../src/interfaces/IJBController.sol";
9
+ import {IJBRulesetApprovalHook} from "../src/interfaces/IJBRulesetApprovalHook.sol";
10
+ import {IJBTerminal} from "../src/interfaces/IJBTerminal.sol";
11
+ import {IJBToken} from "../src/interfaces/IJBToken.sol";
12
+ import {IJBTokens} from "../src/interfaces/IJBTokens.sol";
13
+ import {JBConstants} from "../src/libraries/JBConstants.sol";
14
+ import {JBAccountingContext} from "../src/structs/JBAccountingContext.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 {Clones} from "@openzeppelin/contracts/proxy/Clones.sol";
21
+ import {mulDiv} from "@prb/math/src/Common.sol";
5
22
 
6
23
  // Launch project, issue token or set the token, mint token, burn token.
7
24
  contract TestTokenFlow_Local is TestBaseWorkflow {
@@ -1,10 +1,24 @@
1
1
  // SPDX-License-Identifier: MIT
2
2
  pragma solidity >=0.8.6;
3
3
 
4
- import /* {*} from */ "./helpers/TestBaseWorkflow.sol";
4
+ import {TestBaseWorkflow} from "./helpers/TestBaseWorkflow.sol";
5
+ import {JBApprovalStatus} from "../src/enums/JBApprovalStatus.sol";
6
+ import {IJBController} from "../src/interfaces/IJBController.sol";
7
+ import {IJBRulesetApprovalHook} from "../src/interfaces/IJBRulesetApprovalHook.sol";
8
+ import {IJBRulesets} from "../src/interfaces/IJBRulesets.sol";
9
+ import {JBConstants} from "../src/libraries/JBConstants.sol";
10
+ import {JBAccountingContext} from "../src/structs/JBAccountingContext.sol";
11
+ import {JBFundAccessLimitGroup} from "../src/structs/JBFundAccessLimitGroup.sol";
12
+ import {JBRuleset} from "../src/structs/JBRuleset.sol";
13
+ import {JBRulesetConfig} from "../src/structs/JBRulesetConfig.sol";
14
+ import {JBRulesetMetadata} from "../src/structs/JBRulesetMetadata.sol";
15
+ import {JBSplitGroup} from "../src/structs/JBSplitGroup.sol";
16
+ import {JBTerminalConfig} from "../src/structs/JBTerminalConfig.sol";
17
+ import {IERC165} from "@openzeppelin/contracts/utils/introspection/ERC165.sol";
5
18
 
6
19
  /// @notice Mock approval hook that ALWAYS rejects queued rulesets.
7
20
  contract AlwaysRejectApprovalHook is IJBRulesetApprovalHook {
21
+ // forge-lint: disable-next-line(mixed-case-function)
8
22
  function DURATION() external pure override returns (uint256) {
9
23
  return 0;
10
24
  }
@@ -1,7 +1,19 @@
1
1
  // SPDX-License-Identifier: MIT
2
2
  pragma solidity ^0.8.6;
3
3
 
4
- import /* {*} from */ "./helpers/TestBaseWorkflow.sol";
4
+ import {TestBaseWorkflow} from "./helpers/TestBaseWorkflow.sol";
5
+ import {IJBRulesetApprovalHook} from "../src/interfaces/IJBRulesetApprovalHook.sol";
6
+ import {IJBSplitHook} from "../src/interfaces/IJBSplitHook.sol";
7
+ import {JBConstants} from "../src/libraries/JBConstants.sol";
8
+ import {JBRulesetMetadataResolver} from "../src/libraries/JBRulesetMetadataResolver.sol";
9
+ import {JBCurrencyAmount} from "../src/structs/JBCurrencyAmount.sol";
10
+ import {JBFundAccessLimitGroup} from "../src/structs/JBFundAccessLimitGroup.sol";
11
+ import {JBRuleset} from "../src/structs/JBRuleset.sol";
12
+ import {JBRulesetConfig} from "../src/structs/JBRulesetConfig.sol";
13
+ import {JBRulesetMetadata} from "../src/structs/JBRulesetMetadata.sol";
14
+ import {JBSplit} from "../src/structs/JBSplit.sol";
15
+ import {JBSplitGroup} from "../src/structs/JBSplitGroup.sol";
16
+ import {JBTerminalConfig} from "../src/structs/JBTerminalConfig.sol";
5
17
  import {JBAccountingContext} from "../src/structs/JBAccountingContext.sol";
6
18
  import {ERC20} from "@openzeppelin/contracts/token/ERC20/ERC20.sol";
7
19
 
@@ -92,6 +104,7 @@ contract WeirdTokenTests_Local is TestBaseWorkflow {
92
104
  rulesetConfig[0].metadata = JBRulesetMetadata({
93
105
  reservedPercent: 0,
94
106
  cashOutTaxRate: 0,
107
+ // forge-lint: disable-next-line(unsafe-typecast)
95
108
  baseCurrency: uint32(uint160(token)),
96
109
  pausePay: false,
97
110
  pauseCreditTransfers: false,
@@ -115,6 +128,7 @@ contract WeirdTokenTests_Local is TestBaseWorkflow {
115
128
 
116
129
  JBTerminalConfig[] memory terminalConfigurations = new JBTerminalConfig[](1);
117
130
  JBAccountingContext[] memory tokensToAccept = new JBAccountingContext[](1);
131
+ // forge-lint: disable-next-line(unsafe-typecast)
118
132
  tokensToAccept[0] = JBAccountingContext({token: token, decimals: decimals, currency: uint32(uint160(token))});
119
133
  terminalConfigurations[0] =
120
134
  JBTerminalConfig({terminal: jbMultiTerminal(), accountingContextsToAccept: tokensToAccept});
@@ -161,6 +175,14 @@ contract WeirdTokenTests_Local is TestBaseWorkflow {
161
175
  // 1% fee: 1000e18 * 99/100 = 990e18
162
176
  uint256 expectedDelta = 990e18;
163
177
  assertEq(recordedBalance, expectedDelta, "Terminal should record delta amount for FOT tokens");
178
+
179
+ // INVARIANT: recorded balance <= actual token balance at all times.
180
+ uint256 actualTerminalBalance = fotToken.balanceOf(address(jbMultiTerminal()));
181
+ assertLe(
182
+ recordedBalance,
183
+ actualTerminalBalance,
184
+ "FOT: recorded balance must not exceed actual terminal token balance after pay"
185
+ );
164
186
  }
165
187
 
166
188
  // ═══════════════════════════════════════════════════════════════════
@@ -209,6 +231,16 @@ contract WeirdTokenTests_Local is TestBaseWorkflow {
209
231
 
210
232
  // Beneficiary receives less than reclaimAmount due to outbound fee
211
233
  assertLt(actualReceived, reclaimAmount, "FOT: beneficiary receives less than reclaimAmount on outbound");
234
+
235
+ // INVARIANT: recorded balance <= actual token balance after cash-out.
236
+ uint256 recordedBalanceAfterCashOut =
237
+ jbTerminalStore().balanceOf(address(jbMultiTerminal()), pid, address(fotToken));
238
+ uint256 actualTerminalBalanceAfterCashOut = fotToken.balanceOf(address(jbMultiTerminal()));
239
+ assertLe(
240
+ recordedBalanceAfterCashOut,
241
+ actualTerminalBalanceAfterCashOut,
242
+ "FOT: recorded balance must not exceed actual terminal token balance after cash-out"
243
+ );
212
244
  }
213
245
 
214
246
  // ═══════════════════════════════════════════════════════════════════
@@ -322,6 +354,17 @@ contract WeirdTokenTests_Local is TestBaseWorkflow {
322
354
  assertTrue(actualReceived > 0, "Split beneficiary should receive something");
323
355
  // The terminal sends a computed net amount, but the FOT tax reduces what arrives
324
356
  // This documents the information finding: FOT tokens cause split recipients to receive less
357
+
358
+ // INVARIANT: Terminal's recorded balance must never exceed actual token balance.
359
+ // With FOT tokens, the terminal may hold fewer tokens than recorded (inbound fee on pay).
360
+ // After a payout, the recorded balance should still be <= actual balance.
361
+ uint256 recordedBalance = jbTerminalStore().balanceOf(address(jbMultiTerminal()), pid, address(fotToken));
362
+ uint256 actualTerminalBalance = fotToken.balanceOf(address(jbMultiTerminal()));
363
+ assertLe(
364
+ recordedBalance,
365
+ actualTerminalBalance,
366
+ "FOT: recorded balance must not exceed actual terminal token balance after payout"
367
+ );
325
368
  }
326
369
 
327
370
  // ═══════════════════════════════════════════════════════════════════
@@ -560,6 +603,14 @@ contract WeirdTokenTests_Local is TestBaseWorkflow {
560
603
 
561
604
  // Balance should increase (delta accounting handles the fee)
562
605
  assertGt(recordedAfter, recordedBefore, "Balance should increase with addToBalance");
606
+
607
+ // INVARIANT: recorded balance <= actual token balance after addToBalance.
608
+ uint256 actualTerminalBalance = fotToken.balanceOf(address(jbMultiTerminal()));
609
+ assertLe(
610
+ recordedAfter,
611
+ actualTerminalBalance,
612
+ "FOT: recorded balance must not exceed actual terminal token balance after addToBalance"
613
+ );
563
614
  }
564
615
 
565
616
  // ═══════════════════════════════════════════════════════════════════
@@ -676,9 +727,11 @@ contract RebasingToken is ERC20 {
676
727
  function rebaseHolder(address target, int256 percent) external {
677
728
  uint256 balance = balanceOf(target);
678
729
  if (percent > 0) {
730
+ // forge-lint: disable-next-line(unsafe-typecast)
679
731
  uint256 increase = (balance * uint256(percent)) / 100;
680
732
  _mint(target, increase);
681
733
  } else if (percent < 0) {
734
+ // forge-lint: disable-next-line(unsafe-typecast)
682
735
  uint256 decrease = (balance * uint256(-percent)) / 100;
683
736
  if (decrease > 0 && decrease <= balance) {
684
737
  _burn(target, decrease);
@@ -1,7 +1,7 @@
1
1
  // SPDX-License-Identifier: MIT
2
2
  pragma solidity 0.8.26;
3
3
 
4
- import "forge-std/Test.sol";
4
+ import {Test} from "forge-std/Test.sol";
5
5
 
6
6
  import {AggregatorV3Interface} from "@chainlink/contracts/src/v0.8/shared/interfaces/AggregatorV3Interface.sol";
7
7
 
@@ -68,6 +68,7 @@ contract TestChainlinkPriceFeedFork is Test {
68
68
  // Cross-check against raw latestRoundData.
69
69
  (, int256 rawPrice,,,) = AggregatorV3Interface(ETH_USD_FEED).latestRoundData();
70
70
  uint256 feedDecimals = AggregatorV3Interface(ETH_USD_FEED).decimals();
71
+ // forge-lint: disable-next-line(unsafe-typecast)
71
72
  uint256 expected18 = uint256(rawPrice) * 10 ** (18 - feedDecimals);
72
73
  assertEq(price18, expected18, "Price mismatch vs raw feed");
73
74
  }
@@ -85,15 +86,19 @@ contract TestChainlinkPriceFeedFork is Test {
85
86
 
86
87
  // Raw feed is 8 decimals — price8 should match it exactly.
87
88
  (, int256 rawPrice,,,) = AggregatorV3Interface(ETH_USD_FEED).latestRoundData();
89
+ // forge-lint: disable-next-line(unsafe-typecast)
88
90
  assertEq(price8, uint256(rawPrice), "8-decimal mismatch");
89
91
 
90
92
  // 6 decimals = raw / 100 (truncated).
93
+ // forge-lint: disable-next-line(unsafe-typecast)
91
94
  assertEq(price6, uint256(rawPrice) / 1e2, "6-decimal mismatch");
92
95
 
93
96
  // 18 decimals = raw * 1e10.
97
+ // forge-lint: disable-next-line(unsafe-typecast)
94
98
  assertEq(price18, uint256(rawPrice) * 1e10, "18-decimal mismatch");
95
99
 
96
100
  // 27 decimals = raw * 1e19.
101
+ // forge-lint: disable-next-line(unsafe-typecast)
97
102
  assertEq(price27, uint256(rawPrice) * 1e19, "27-decimal mismatch");
98
103
  }
99
104