@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,806 +0,0 @@
1
- // SPDX-License-Identifier: MIT
2
- pragma solidity ^0.8.6;
3
-
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";
18
-
19
- /// @notice Mock approval hook that always returns a configurable status.
20
- contract MockApprovalHookConfigurable is IJBRulesetApprovalHook {
21
- JBApprovalStatus public immutable STATUS;
22
- uint256 public immutable override DURATION;
23
-
24
- constructor(JBApprovalStatus status, uint256 duration) {
25
- STATUS = status;
26
- DURATION = duration;
27
- }
28
-
29
- function approvalStatusOf(uint256, JBRuleset memory) external view override returns (JBApprovalStatus) {
30
- return STATUS;
31
- }
32
-
33
- function supportsInterface(bytes4 interfaceId) external pure override returns (bool) {
34
- return interfaceId == type(IJBRulesetApprovalHook).interfaceId || interfaceId == type(IERC165).interfaceId;
35
- }
36
- }
37
-
38
- /// @notice Mock approval hook that always reverts (for DoS testing).
39
- contract RevertingApprovalHook is IJBRulesetApprovalHook {
40
- uint256 public constant override DURATION = 3 days;
41
-
42
- function approvalStatusOf(uint256, JBRuleset memory) external pure override returns (JBApprovalStatus) {
43
- revert("HOOK_REVERTED");
44
- }
45
-
46
- function supportsInterface(bytes4 interfaceId) external pure override returns (bool) {
47
- return interfaceId == type(IJBRulesetApprovalHook).interfaceId || interfaceId == type(IERC165).interfaceId;
48
- }
49
- }
50
-
51
- /// @notice Stress tests for JBRulesets queuing logic under wild circumstances.
52
- /// Tests duration transitions, approval hook edge cases, rapid-fire queuing,
53
- /// weight decay to extremes, rollover behavior, start-time alignment, and
54
- /// complex multi-queue scenarios.
55
- contract TestRulesetQueuingStress_Local is TestBaseWorkflow {
56
- IJBController private _controller;
57
- IJBRulesets private _rulesets;
58
- JBRulesetMetadata private _metadata;
59
- JBDeadline private _deadline3Day;
60
-
61
- uint32 constant FOURTEEN_DAYS = 14 days;
62
- uint32 constant SEVEN_DAYS = 7 days;
63
- uint112 constant INITIAL_WEIGHT = 1000e18;
64
-
65
- function setUp() public override {
66
- super.setUp();
67
- _controller = jbController();
68
- _rulesets = jbRulesets();
69
- _deadline3Day = new JBDeadline(3 days);
70
-
71
- _metadata = JBRulesetMetadata({
72
- reservedPercent: 0,
73
- cashOutTaxRate: 0,
74
- baseCurrency: uint32(uint160(JBConstants.NATIVE_TOKEN)),
75
- pausePay: false,
76
- pauseCreditTransfers: false,
77
- allowOwnerMinting: false,
78
- allowSetCustomToken: false,
79
- allowTerminalMigration: false,
80
- allowSetTerminals: false,
81
- ownerMustSendPayouts: false,
82
- allowSetController: false,
83
- allowAddAccountingContext: false,
84
- allowAddPriceFeed: false,
85
- holdFees: false,
86
- useTotalSurplusForCashOuts: false,
87
- useDataHookForPay: false,
88
- useDataHookForCashOut: false,
89
- dataHook: address(0),
90
- metadata: 0
91
- });
92
- }
93
-
94
- /// @dev Launch a project with a single ruleset.
95
- function _launchProject(
96
- uint32 duration,
97
- uint112 weight,
98
- uint32 weightCutPercent,
99
- IJBRulesetApprovalHook approvalHook
100
- )
101
- internal
102
- returns (uint256)
103
- {
104
- JBRulesetConfig[] memory config = new JBRulesetConfig[](1);
105
- config[0].mustStartAtOrAfter = 0;
106
- config[0].duration = duration;
107
- config[0].weight = weight;
108
- config[0].weightCutPercent = weightCutPercent;
109
- config[0].approvalHook = approvalHook;
110
- config[0].metadata = _metadata;
111
- config[0].splitGroups = new JBSplitGroup[](0);
112
- config[0].fundAccessLimitGroups = new JBFundAccessLimitGroup[](0);
113
-
114
- return _controller.launchProjectFor({
115
- owner: multisig(),
116
- projectUri: "stress",
117
- rulesetConfigurations: config,
118
- terminalConfigurations: new JBTerminalConfig[](0),
119
- memo: ""
120
- });
121
- }
122
-
123
- /// @dev Launch a project with start in the future.
124
- function _launchProjectFutureStart(uint48 startAt, uint32 duration, uint112 weight) internal returns (uint256) {
125
- JBRulesetConfig[] memory config = new JBRulesetConfig[](1);
126
- config[0].mustStartAtOrAfter = startAt;
127
- config[0].duration = duration;
128
- config[0].weight = weight;
129
- config[0].weightCutPercent = 0;
130
- config[0].approvalHook = IJBRulesetApprovalHook(address(0));
131
- config[0].metadata = _metadata;
132
- config[0].splitGroups = new JBSplitGroup[](0);
133
- config[0].fundAccessLimitGroups = new JBFundAccessLimitGroup[](0);
134
-
135
- return _controller.launchProjectFor({
136
- owner: multisig(),
137
- projectUri: "future",
138
- rulesetConfigurations: config,
139
- terminalConfigurations: new JBTerminalConfig[](0),
140
- memo: ""
141
- });
142
- }
143
-
144
- /// @dev Queue a ruleset for a project.
145
- function _queueRuleset(
146
- uint256 projectId,
147
- uint48 mustStartAtOrAfter,
148
- uint32 duration,
149
- uint112 weight,
150
- uint32 weightCutPercent,
151
- IJBRulesetApprovalHook approvalHook
152
- )
153
- internal
154
- {
155
- JBRulesetConfig[] memory config = new JBRulesetConfig[](1);
156
- config[0].mustStartAtOrAfter = mustStartAtOrAfter;
157
- config[0].duration = duration;
158
- config[0].weight = weight;
159
- config[0].weightCutPercent = weightCutPercent;
160
- config[0].approvalHook = approvalHook;
161
- config[0].metadata = _metadata;
162
- config[0].splitGroups = new JBSplitGroup[](0);
163
- config[0].fundAccessLimitGroups = new JBFundAccessLimitGroup[](0);
164
-
165
- vm.prank(multisig());
166
- _controller.queueRulesetsOf(projectId, config, "");
167
- }
168
-
169
- // ───────────────────── DURATION CYCLING EDGE CASES
170
- // ─────────────────────
171
-
172
- /// @notice Duration=0 means new queued rulesets start immediately.
173
- function test_durationZero_toNonZero_transition() external {
174
- uint256 pid = _launchProject(0, INITIAL_WEIGHT, 0, IJBRulesetApprovalHook(address(0)));
175
-
176
- JBRuleset memory current = _rulesets.currentOf(pid);
177
- assertEq(current.duration, 0, "Initial should have duration=0");
178
- assertEq(current.cycleNumber, 1);
179
-
180
- // Queue with duration=7 days. Since current has duration=0, new one starts immediately.
181
- _queueRuleset(pid, 0, SEVEN_DAYS, INITIAL_WEIGHT * 2, 0, IJBRulesetApprovalHook(address(0)));
182
-
183
- current = _rulesets.currentOf(pid);
184
- assertEq(current.weight, INITIAL_WEIGHT * 2, "Duration=0 -> new ruleset immediately current");
185
- assertEq(current.duration, SEVEN_DAYS, "New duration should be 7 days");
186
- assertEq(current.cycleNumber, 2, "Should be cycle 2");
187
- }
188
-
189
- /// @notice Transitioning from duration>0 to duration=0 stops cycling.
190
- function test_durationNonZero_toZero_transition() external {
191
- uint256 pid = _launchProject(SEVEN_DAYS, INITIAL_WEIGHT, 0, IJBRulesetApprovalHook(address(0)));
192
-
193
- _queueRuleset(pid, 0, 0, INITIAL_WEIGHT * 2, 0, IJBRulesetApprovalHook(address(0)));
194
-
195
- // Before current ends, original is still active.
196
- JBRuleset memory current = _rulesets.currentOf(pid);
197
- assertEq(current.weight, INITIAL_WEIGHT, "Original still active");
198
-
199
- // Warp past current duration.
200
- vm.warp(block.timestamp + SEVEN_DAYS);
201
-
202
- current = _rulesets.currentOf(pid);
203
- assertEq(current.weight, INITIAL_WEIGHT * 2, "Duration=0 ruleset now current");
204
- assertEq(current.duration, 0, "New ruleset has duration=0");
205
-
206
- // With duration=0, no upcoming.
207
- JBRuleset memory upcoming = _rulesets.upcomingOf(pid);
208
- assertEq(upcoming.cycleNumber, 0, "No upcoming when current has duration=0");
209
- }
210
-
211
- /// @notice Very short duration (1 second) should cycle correctly over many periods.
212
- function test_veryShortDuration_manyCycles() external {
213
- uint256 pid = _launchProject(1, INITIAL_WEIGHT, 0, IJBRulesetApprovalHook(address(0)));
214
-
215
- vm.warp(block.timestamp + 1000);
216
-
217
- JBRuleset memory current = _rulesets.currentOf(pid);
218
- assertEq(current.cycleNumber, 1001, "Should have cycled 1000 times");
219
- assertEq(current.weight, INITIAL_WEIGHT, "No cut -> weight unchanged");
220
- }
221
-
222
- /// @notice Very short duration with weight cut: decay accumulates per cycle.
223
- function test_veryShortDuration_withWeightCut() external {
224
- uint32 tenPercentCut = uint32(JBConstants.MAX_WEIGHT_CUT_PERCENT / 10);
225
- uint256 pid = _launchProject(1, INITIAL_WEIGHT, tenPercentCut, IJBRulesetApprovalHook(address(0)));
226
-
227
- // 10 seconds -> 10 weight cuts.
228
- vm.warp(block.timestamp + 10);
229
-
230
- JBRuleset memory current = _rulesets.currentOf(pid);
231
- assertEq(current.cycleNumber, 11, "Cycle 11 after 10 seconds");
232
- assertLt(current.weight, INITIAL_WEIGHT, "Weight should decrease");
233
- assertGt(current.weight, 0, "Weight should not be zero after only 10 cuts");
234
- }
235
-
236
- /// @notice Mid-cycle queuing: new ruleset starts at next duration boundary.
237
- function test_midCycleQueuing_snapsToNextBoundary() external {
238
- uint256 pid = _launchProject(SEVEN_DAYS, INITIAL_WEIGHT, 0, IJBRulesetApprovalHook(address(0)));
239
- // Capture originalStart from the actual ruleset (avoid via_ir reordering of block.timestamp).
240
- uint256 originalStart = _rulesets.currentOf(pid).start;
241
-
242
- // Warp to mid-cycle (day 3 of 7).
243
- vm.warp(originalStart + 3 days);
244
-
245
- _queueRuleset(pid, 0, SEVEN_DAYS, INITIAL_WEIGHT * 2, 0, IJBRulesetApprovalHook(address(0)));
246
-
247
- // Current should still be the original (we're mid-cycle).
248
- JBRuleset memory current = _rulesets.currentOf(pid);
249
- assertEq(current.weight, INITIAL_WEIGHT, "Original still current mid-cycle");
250
-
251
- // Upcoming should start at the next boundary (T+7d).
252
- JBRuleset memory upcoming = _rulesets.upcomingOf(pid);
253
- assertEq(upcoming.start, originalStart + SEVEN_DAYS, "Should start at next boundary");
254
- assertEq(upcoming.weight, INITIAL_WEIGHT * 2, "Queued weight should match");
255
- }
256
-
257
- // ───────────────────── APPROVAL HOOK STRESS
258
- // ─────────────────────
259
-
260
- /// @notice Queue well before deadline -> approved.
261
- function test_approvalHook_queueBeforeDeadline_approved() external {
262
- uint256 pid = _launchProject(FOURTEEN_DAYS, INITIAL_WEIGHT, 0, _deadline3Day);
263
-
264
- // Queue immediately: start ≈ T+14d, rulesetId ≈ T. Gap = 14d > 3d -> Approved.
265
- _queueRuleset(pid, 0, FOURTEEN_DAYS, INITIAL_WEIGHT * 2, 0, _deadline3Day);
266
-
267
- JBRuleset memory upcoming = _rulesets.upcomingOf(pid);
268
- assertEq(upcoming.weight, INITIAL_WEIGHT * 2, "Queued should be upcoming");
269
-
270
- vm.warp(block.timestamp + FOURTEEN_DAYS);
271
-
272
- JBRuleset memory current = _rulesets.currentOf(pid);
273
- assertEq(current.weight, INITIAL_WEIGHT * 2, "Approved ruleset should be current");
274
- assertEq(current.cycleNumber, 2);
275
- }
276
-
277
- /// @notice Queue too late (past deadline) -> failed -> original rolls over.
278
- function test_approvalHook_queueTooLate_failsAndRollsOver() external {
279
- uint256 pid = _launchProject(FOURTEEN_DAYS, INITIAL_WEIGHT, 0, _deadline3Day);
280
-
281
- // Warp to day 12 of 14 (only 2 days left, less than 3-day deadline).
282
- vm.warp(block.timestamp + 12 days);
283
-
284
- _queueRuleset(pid, 0, FOURTEEN_DAYS, INITIAL_WEIGHT * 5, 0, _deadline3Day);
285
-
286
- // Warp past cycle 1 end.
287
- vm.warp(block.timestamp + 3 days);
288
-
289
- JBRuleset memory current = _rulesets.currentOf(pid);
290
- assertEq(current.weight, INITIAL_WEIGHT, "Failed approval -> original rolls over");
291
- assertEq(current.cycleNumber, 2, "Rolled over to cycle 2");
292
- }
293
-
294
- /// @notice Chain of always-failed approvals: original always rolls over.
295
- function test_approvalHook_chainOfFailures_originalPersists() external {
296
- MockApprovalHookConfigurable alwaysFail = new MockApprovalHookConfigurable(JBApprovalStatus.Failed, 3 days);
297
-
298
- uint256 pid = _launchProject(FOURTEEN_DAYS, INITIAL_WEIGHT, 0, IJBRulesetApprovalHook(address(alwaysFail)));
299
-
300
- // Queue 3 rulesets — all will fail.
301
- _queueRuleset(pid, 0, FOURTEEN_DAYS, INITIAL_WEIGHT * 2, 0, IJBRulesetApprovalHook(address(alwaysFail)));
302
- _queueRuleset(pid, 0, FOURTEEN_DAYS, INITIAL_WEIGHT * 3, 0, IJBRulesetApprovalHook(address(alwaysFail)));
303
- _queueRuleset(pid, 0, FOURTEEN_DAYS, INITIAL_WEIGHT * 4, 0, IJBRulesetApprovalHook(address(alwaysFail)));
304
-
305
- // Warp far past all possible starts.
306
- vm.warp(block.timestamp + 100 days);
307
-
308
- JBRuleset memory current = _rulesets.currentOf(pid);
309
- assertEq(current.weight, INITIAL_WEIGHT, "All failed -> original persists");
310
- assertGt(current.cycleNumber, 1, "Should have rolled over multiple cycles");
311
- }
312
-
313
- /// @notice Reverting approval hook no longer causes DoS.
314
- /// The try/catch in _approvalStatusOf catches the revert and returns Failed status,
315
- /// so currentOf succeeds and falls back to the previous ruleset.
316
- function test_approvalHook_revert_causesDoS() external {
317
- RevertingApprovalHook revertHook = new RevertingApprovalHook();
318
-
319
- uint256 pid = _launchProject(FOURTEEN_DAYS, INITIAL_WEIGHT, 0, IJBRulesetApprovalHook(address(revertHook)));
320
-
321
- // Queue a new ruleset.
322
- _queueRuleset(pid, 0, FOURTEEN_DAYS, INITIAL_WEIGHT * 2, 0, IJBRulesetApprovalHook(address(revertHook)));
323
-
324
- // Warp past current cycle so the queued one is checked.
325
- vm.warp(block.timestamp + FOURTEEN_DAYS);
326
-
327
- // The reverting hook is caught by try/catch, treated as Failed.
328
- // currentOf succeeds and falls back to the original ruleset (weight not doubled).
329
- JBRuleset memory current = _rulesets.currentOf(pid);
330
- assertGt(current.id, 0, "currentOf should succeed, not revert");
331
- assertEq(current.weight, INITIAL_WEIGHT, "Should fall back to original ruleset weight");
332
- }
333
-
334
- /// @notice Approval status transitions from ApprovalExpected to Approved.
335
- function test_approvalHook_statusTransition_expectedToApproved() external {
336
- uint256 pid = _launchProject(FOURTEEN_DAYS, INITIAL_WEIGHT, 0, _deadline3Day);
337
-
338
- _queueRuleset(pid, 0, FOURTEEN_DAYS, INITIAL_WEIGHT * 2, 0, _deadline3Day);
339
-
340
- // Immediately after queuing: ApprovalExpected (deadline not yet passed).
341
- (, JBApprovalStatus status) = _rulesets.latestQueuedOf(pid);
342
- assertEq(
343
- uint256(status), uint256(JBApprovalStatus.ApprovalExpected), "Should be ApprovalExpected immediately"
344
- );
345
-
346
- // Warp past the deadline threshold: need block.timestamp + 3d >= ruleset.start.
347
- // Ruleset starts at ~T+14d, so we need to be at T+11d or later.
348
- vm.warp(block.timestamp + 11 days);
349
-
350
- (, status) = _rulesets.latestQueuedOf(pid);
351
- assertEq(uint256(status), uint256(JBApprovalStatus.Approved), "Should be Approved after deadline");
352
- }
353
-
354
- // ───────────────────── RAPID-FIRE QUEUING
355
- // ─────────────────────
356
-
357
- /// @notice 5 rulesets queued in the same block: rulesetIds increment, last one wins.
358
- function test_sameBlock_fiveQueues_lastOneWins() external {
359
- uint256 pid = _launchProject(FOURTEEN_DAYS, INITIAL_WEIGHT, 0, IJBRulesetApprovalHook(address(0)));
360
- uint256 initialRulesetId = block.timestamp;
361
-
362
- for (uint256 i = 1; i <= 5; i++) {
363
- _queueRuleset(
364
- pid,
365
- 0,
366
- FOURTEEN_DAYS,
367
- // forge-lint: disable-next-line(unsafe-typecast)
368
- uint112(INITIAL_WEIGHT + uint112(i) * 100e18),
369
- 0,
370
- IJBRulesetApprovalHook(address(0))
371
- );
372
- }
373
-
374
- // Latest should be the 5th (timestamp + 5).
375
- uint256 latestId = _rulesets.latestRulesetIdOf(pid);
376
- assertEq(latestId, initialRulesetId + 5, "5th queued should be latest");
377
-
378
- // Warp past current cycle.
379
- vm.warp(block.timestamp + FOURTEEN_DAYS);
380
-
381
- JBRuleset memory current = _rulesets.currentOf(pid);
382
- assertEq(current.weight, INITIAL_WEIGHT + 500e18, "Last queued should be current");
383
- assertEq(current.cycleNumber, 2);
384
- }
385
-
386
- /// @notice Override a queued ruleset before it starts: replacement takes effect.
387
- function test_overrideQueued_replacementTakesEffect() external {
388
- uint256 pid = _launchProject(FOURTEEN_DAYS, INITIAL_WEIGHT, 0, IJBRulesetApprovalHook(address(0)));
389
-
390
- // Queue A.
391
- _queueRuleset(pid, 0, FOURTEEN_DAYS, INITIAL_WEIGHT * 2, 0, IJBRulesetApprovalHook(address(0)));
392
-
393
- JBRuleset memory upcoming = _rulesets.upcomingOf(pid);
394
- assertEq(upcoming.weight, INITIAL_WEIGHT * 2, "A should be upcoming");
395
-
396
- // Queue B — overrides A.
397
- _queueRuleset(pid, 0, FOURTEEN_DAYS, INITIAL_WEIGHT * 3, 0, IJBRulesetApprovalHook(address(0)));
398
-
399
- upcoming = _rulesets.upcomingOf(pid);
400
- assertEq(upcoming.weight, INITIAL_WEIGHT * 3, "B should override A");
401
-
402
- vm.warp(block.timestamp + FOURTEEN_DAYS);
403
-
404
- JBRuleset memory current = _rulesets.currentOf(pid);
405
- assertEq(current.weight, INITIAL_WEIGHT * 3, "B should be current, not A");
406
- }
407
-
408
- // ───────────────────── WEIGHT DECAY STRESS
409
- // ─────────────────────
410
-
411
- /// @notice 50% weight cut over 100 cycles: weight decays to zero.
412
- function test_weightDecay_fiftyPercent_100cycles_toZero() external {
413
- uint32 fiftyPercentCut = uint32(JBConstants.MAX_WEIGHT_CUT_PERCENT / 2);
414
- uint256 pid = _launchProject(
415
- SEVEN_DAYS, INITIAL_WEIGHT, fiftyPercentCut, IJBRulesetApprovalHook(address(0))
416
- );
417
-
418
- // 100 cycles (700 days). 1000e18 * (0.5^100) ≈ 7.9e-13 -> truncated to 0.
419
- vm.warp(block.timestamp + 700 days);
420
-
421
- JBRuleset memory current = _rulesets.currentOf(pid);
422
- assertEq(current.weight, 0, "Weight should decay to 0 after 100 x 50% cuts");
423
- assertEq(current.cycleNumber, 101);
424
- }
425
-
426
- /// @notice 100% weight cut: weight goes to zero after one cycle.
427
- function test_weightCut_100percent_zeroAfterOneCycle() external {
428
- uint32 fullCut = uint32(JBConstants.MAX_WEIGHT_CUT_PERCENT);
429
- uint256 pid = _launchProject(SEVEN_DAYS, INITIAL_WEIGHT, fullCut, IJBRulesetApprovalHook(address(0)));
430
-
431
- vm.warp(block.timestamp + SEVEN_DAYS);
432
-
433
- JBRuleset memory current = _rulesets.currentOf(pid);
434
- assertEq(current.weight, 0, "100% cut -> zero weight after one cycle");
435
- assertEq(current.cycleNumber, 2);
436
- }
437
-
438
- /// @notice 0% weight cut: weight unchanged indefinitely.
439
- function test_weightCut_zeroPercent_unchangedForever() external {
440
- uint256 pid = _launchProject(SEVEN_DAYS, INITIAL_WEIGHT, 0, IJBRulesetApprovalHook(address(0)));
441
-
442
- // 1000 cycles.
443
- vm.warp(block.timestamp + 7000 days);
444
-
445
- JBRuleset memory current = _rulesets.currentOf(pid);
446
- assertEq(current.weight, INITIAL_WEIGHT, "0% cut -> weight unchanged");
447
- assertEq(current.cycleNumber, 1001);
448
- }
449
-
450
- /// @notice weight=1 is a special case that inherits the derived (cut) weight.
451
- function test_weight_inheritSpecialCase_weight1() external {
452
- uint32 tenPercentCut = uint32(JBConstants.MAX_WEIGHT_CUT_PERCENT / 10);
453
- uint256 pid = _launchProject(SEVEN_DAYS, INITIAL_WEIGHT, tenPercentCut, IJBRulesetApprovalHook(address(0)));
454
-
455
- // Queue with weight=1 -> inherits derived weight (one cut applied).
456
- _queueRuleset(pid, 0, SEVEN_DAYS, 1, tenPercentCut, IJBRulesetApprovalHook(address(0)));
457
-
458
- vm.warp(block.timestamp + SEVEN_DAYS);
459
-
460
- JBRuleset memory current = _rulesets.currentOf(pid);
461
- uint256 expectedWeight = (uint256(INITIAL_WEIGHT) * (JBConstants.MAX_WEIGHT_CUT_PERCENT - tenPercentCut))
462
- / JBConstants.MAX_WEIGHT_CUT_PERCENT;
463
- assertEq(current.weight, expectedWeight, "weight=1 should inherit derived weight (one 10% cut)");
464
- }
465
-
466
- // ───────────────────── ROLLOVER BEHAVIOR
467
- // ─────────────────────
468
-
469
- /// @notice Rollover preserves all rules and applies weight cut per cycle.
470
- function test_rollover_manyCycles_preservesRules() external {
471
- uint32 onePercentCut = uint32(JBConstants.MAX_WEIGHT_CUT_PERCENT / 100);
472
- uint256 pid = _launchProject(SEVEN_DAYS, INITIAL_WEIGHT, onePercentCut, IJBRulesetApprovalHook(address(0)));
473
-
474
- // 100 cycles (700 days).
475
- vm.warp(block.timestamp + 700 days);
476
-
477
- JBRuleset memory current = _rulesets.currentOf(pid);
478
- assertEq(current.cycleNumber, 101, "Cycle 101 after 100 rollovers");
479
- assertEq(current.duration, SEVEN_DAYS, "Duration preserved");
480
- assertEq(current.weightCutPercent, onePercentCut, "Weight cut preserved");
481
- assertLt(current.weight, INITIAL_WEIGHT, "Weight decreased");
482
- assertGt(current.weight, 0, "Weight not zero after only 1% cuts");
483
- }
484
-
485
- /// @notice After many rollovers, a newly queued ruleset takes effect at the correct boundary.
486
- function test_rollover_thenQueuedTakesEffect() external {
487
- uint256 pid = _launchProject(SEVEN_DAYS, INITIAL_WEIGHT, 0, IJBRulesetApprovalHook(address(0)));
488
-
489
- // Roll over 5 cycles.
490
- vm.warp(block.timestamp + 5 * uint256(SEVEN_DAYS));
491
-
492
- JBRuleset memory current = _rulesets.currentOf(pid);
493
- assertEq(current.cycleNumber, 6, "Cycle 6 after 5 rollovers");
494
-
495
- // Queue new ruleset.
496
- _queueRuleset(pid, 0, SEVEN_DAYS, INITIAL_WEIGHT * 2, 0, IJBRulesetApprovalHook(address(0)));
497
-
498
- // Warp to next boundary.
499
- vm.warp(block.timestamp + SEVEN_DAYS);
500
-
501
- current = _rulesets.currentOf(pid);
502
- assertEq(current.weight, INITIAL_WEIGHT * 2, "Queued ruleset now current");
503
- assertEq(current.cycleNumber, 7, "Cycle number continues from rollover");
504
- }
505
-
506
- /// @notice After failed approval, original rolls over (not the failed one).
507
- function test_rollover_afterFailedApproval_originalPersists() external {
508
- MockApprovalHookConfigurable alwaysFail = new MockApprovalHookConfigurable(JBApprovalStatus.Failed, 3 days);
509
-
510
- uint256 pid = _launchProject(FOURTEEN_DAYS, INITIAL_WEIGHT, 0, IJBRulesetApprovalHook(address(alwaysFail)));
511
-
512
- _queueRuleset(pid, 0, FOURTEEN_DAYS, INITIAL_WEIGHT * 5, 0, IJBRulesetApprovalHook(address(alwaysFail)));
513
-
514
- // Warp past 3 cycles.
515
- vm.warp(block.timestamp + 3 * uint256(FOURTEEN_DAYS));
516
-
517
- JBRuleset memory current = _rulesets.currentOf(pid);
518
- assertEq(current.weight, INITIAL_WEIGHT, "Original rolls over after failed approval");
519
- assertGt(current.cycleNumber, 1, "Rolled over past cycle 1");
520
- }
521
-
522
- // ───────────────────── START TIME EDGE CASES
523
- // ─────────────────────
524
-
525
- /// @notice mustStartAtOrAfter in distant future: current doesn't change until then.
526
- function test_distantFuture_start_currentUnchanged() external {
527
- uint256 pid = _launchProject(SEVEN_DAYS, INITIAL_WEIGHT, 0, IJBRulesetApprovalHook(address(0)));
528
-
529
- // Queue with start 365 days from now.
530
- _queueRuleset(
531
- pid,
532
- uint48(block.timestamp + 365 days),
533
- SEVEN_DAYS,
534
- INITIAL_WEIGHT * 2,
535
- 0,
536
- IJBRulesetApprovalHook(address(0))
537
- );
538
-
539
- // Current should still be original.
540
- JBRuleset memory current = _rulesets.currentOf(pid);
541
- assertEq(current.weight, INITIAL_WEIGHT, "Current unchanged until distant future");
542
-
543
- // Upcoming should be the rolled-over original (not the distant future one).
544
- JBRuleset memory upcoming = _rulesets.upcomingOf(pid);
545
- assertEq(upcoming.weight, INITIAL_WEIGHT, "Upcoming is rolled-over, not distant future");
546
-
547
- // Now warp close to 365 days.
548
- vm.warp(block.timestamp + 365 days);
549
-
550
- // The distant future ruleset should now be upcoming.
551
- upcoming = _rulesets.upcomingOf(pid);
552
- assertEq(upcoming.weight, INITIAL_WEIGHT * 2, "Distant future ruleset now upcoming");
553
- }
554
-
555
- /// @notice deriveStartFrom at exact duration boundary: starts at that boundary.
556
- function test_deriveStartFrom_exactBoundary() external {
557
- uint256 pid = _launchProject(SEVEN_DAYS, INITIAL_WEIGHT, 0, IJBRulesetApprovalHook(address(0)));
558
- uint256 originalStart = block.timestamp;
559
-
560
- _queueRuleset(
561
- pid,
562
- // forge-lint: disable-next-line(unsafe-typecast)
563
- uint48(originalStart + SEVEN_DAYS),
564
- SEVEN_DAYS,
565
- INITIAL_WEIGHT * 2,
566
- 0,
567
- IJBRulesetApprovalHook(address(0))
568
- );
569
-
570
- JBRuleset memory upcoming = _rulesets.upcomingOf(pid);
571
- assertEq(upcoming.start, originalStart + SEVEN_DAYS, "Should start exactly at boundary");
572
- }
573
-
574
- /// @notice deriveStartFrom one second after boundary: snaps to next boundary.
575
- function test_deriveStartFrom_oneSecondAfterBoundary_snapsToNext() external {
576
- uint256 pid = _launchProject(SEVEN_DAYS, INITIAL_WEIGHT, 0, IJBRulesetApprovalHook(address(0)));
577
- // Capture originalStart from the actual ruleset (avoid via_ir reordering of block.timestamp).
578
- uint256 originalStart = _rulesets.currentOf(pid).start;
579
-
580
- // 1 second after first boundary -> should snap to second boundary.
581
- _queueRuleset(
582
- pid,
583
- // forge-lint: disable-next-line(unsafe-typecast)
584
- uint48(originalStart + SEVEN_DAYS + 1),
585
- SEVEN_DAYS,
586
- INITIAL_WEIGHT * 2,
587
- 0,
588
- IJBRulesetApprovalHook(address(0))
589
- );
590
-
591
- // At T=0, upcomingOf returns the simulated rolled-over cycle (T+7d) since the
592
- // queued ruleset (T+14d) is more than one duration away. Warp to T+7d first.
593
- vm.warp(originalStart + SEVEN_DAYS);
594
-
595
- JBRuleset memory upcoming = _rulesets.upcomingOf(pid);
596
- assertEq(upcoming.start, originalStart + 2 * uint256(SEVEN_DAYS), "Should snap to next boundary");
597
- assertEq(upcoming.weight, INITIAL_WEIGHT * 2, "Should be the queued ruleset");
598
- }
599
-
600
- /// @notice currentOf returns empty when the only ruleset hasn't started yet.
601
- function test_currentOf_onlyRulesetNotStarted_returnsEmpty() external {
602
- uint256 pid = _launchProjectFutureStart(uint48(block.timestamp + 30 days), SEVEN_DAYS, INITIAL_WEIGHT);
603
-
604
- JBRuleset memory current = _rulesets.currentOf(pid);
605
- assertEq(current.cycleNumber, 0, "No current when only ruleset hasn't started");
606
- }
607
-
608
- // ───────────────────── COMPLEX MULTI-QUEUE SCENARIOS
609
- // ─────────────────────
610
-
611
- /// @notice 10 sequential rulesets: each queued, time advanced, verified as current.
612
- function test_tenSequentialRulesets() external {
613
- uint256 pid = _launchProject(SEVEN_DAYS, INITIAL_WEIGHT, 0, IJBRulesetApprovalHook(address(0)));
614
-
615
- for (uint256 i = 1; i <= 10; i++) {
616
- _queueRuleset(
617
- pid,
618
- 0,
619
- SEVEN_DAYS,
620
- // forge-lint: disable-next-line(unsafe-typecast)
621
- uint112(INITIAL_WEIGHT + uint112(i) * 100e18),
622
- 0,
623
- IJBRulesetApprovalHook(address(0))
624
- );
625
- vm.warp(block.timestamp + SEVEN_DAYS);
626
-
627
- JBRuleset memory current = _rulesets.currentOf(pid);
628
- assertEq(
629
- current.weight,
630
- // forge-lint: disable-next-line(unsafe-typecast)
631
- INITIAL_WEIGHT + uint112(i) * 100e18,
632
- string.concat("Cycle ", vm.toString(i + 1), " weight mismatch")
633
- );
634
- }
635
-
636
- JBRuleset memory finalRuleset = _rulesets.currentOf(pid);
637
- assertEq(finalRuleset.cycleNumber, 11, "Should be cycle 11");
638
- }
639
-
640
- /// @notice Interleaved approve/fail: approved ruleset persists through failed attempts.
641
- function test_interleavedApproveAndFail() external {
642
- uint256 pid = _launchProject(FOURTEEN_DAYS, INITIAL_WEIGHT, 0, _deadline3Day);
643
-
644
- // Queue first — queued at T, starts at ~T+14d. Gap = 14d > 3d -> Approved.
645
- _queueRuleset(pid, 0, FOURTEEN_DAYS, uint112(2000e18), 0, _deadline3Day);
646
-
647
- vm.warp(block.timestamp + FOURTEEN_DAYS);
648
-
649
- JBRuleset memory current = _rulesets.currentOf(pid);
650
- assertEq(current.weight, 2000e18, "First approved ruleset is current");
651
-
652
- // Queue second too late (only 2 days before end).
653
- vm.warp(block.timestamp + 12 days);
654
- _queueRuleset(pid, 0, FOURTEEN_DAYS, uint112(3000e18), 0, _deadline3Day);
655
-
656
- vm.warp(block.timestamp + 3 days);
657
-
658
- current = _rulesets.currentOf(pid);
659
- assertEq(current.weight, 2000e18, "Failed attempt -> last approved rolls over");
660
- assertEq(current.cycleNumber, 3, "Cycle 3");
661
-
662
- // Queue third with enough time (queued at start of cycle 3, >3 days before end).
663
- _queueRuleset(pid, 0, FOURTEEN_DAYS, uint112(4000e18), 0, _deadline3Day);
664
-
665
- vm.warp(block.timestamp + FOURTEEN_DAYS);
666
-
667
- current = _rulesets.currentOf(pid);
668
- assertEq(current.weight, 4000e18, "Third queued approved and current");
669
- }
670
-
671
- /// @notice Duration change mid-queue: start with 7d, queue 14d, then queue 1d.
672
- function test_multipleQueuedDurationChanges() external {
673
- uint256 pid = _launchProject(SEVEN_DAYS, INITIAL_WEIGHT, 0, IJBRulesetApprovalHook(address(0)));
674
-
675
- // Queue 14-day ruleset.
676
- _queueRuleset(pid, 0, FOURTEEN_DAYS, INITIAL_WEIGHT * 2, 0, IJBRulesetApprovalHook(address(0)));
677
-
678
- vm.warp(block.timestamp + SEVEN_DAYS);
679
-
680
- JBRuleset memory current = _rulesets.currentOf(pid);
681
- assertEq(current.weight, INITIAL_WEIGHT * 2, "14-day ruleset now current");
682
- assertEq(current.duration, FOURTEEN_DAYS);
683
-
684
- // Queue 1-day ruleset.
685
- _queueRuleset(pid, 0, 1 days, INITIAL_WEIGHT * 3, 0, IJBRulesetApprovalHook(address(0)));
686
-
687
- vm.warp(block.timestamp + FOURTEEN_DAYS);
688
-
689
- current = _rulesets.currentOf(pid);
690
- assertEq(current.weight, INITIAL_WEIGHT * 3, "1-day ruleset now current");
691
- assertEq(current.duration, 1 days);
692
-
693
- // Verify rapid cycling with the 1-day ruleset.
694
- vm.warp(block.timestamp + 5 days);
695
- current = _rulesets.currentOf(pid);
696
- assertEq(current.cycleNumber, current.cycleNumber, "Should have cycled 5 more times");
697
- assertGt(current.cycleNumber, 3, "Cycle number should advance with 1-day duration");
698
- }
699
-
700
- // ───────────────────── FUZZ: CYCLE NUMBER CONSISTENCY
701
- // ─────────────────────
702
-
703
- /// @notice Fuzz: cycle number always equals elapsed cycles + 1.
704
- function testFuzz_cycleNumber_consistency(uint16 numCycles) external {
705
- numCycles = uint16(bound(numCycles, 1, 500));
706
-
707
- uint256 pid = _launchProject(SEVEN_DAYS, INITIAL_WEIGHT, 0, IJBRulesetApprovalHook(address(0)));
708
-
709
- vm.warp(block.timestamp + uint256(numCycles) * uint256(SEVEN_DAYS));
710
-
711
- JBRuleset memory current = _rulesets.currentOf(pid);
712
- assertEq(current.cycleNumber, uint256(numCycles) + 1, "Cycle number should be elapsed + 1");
713
- }
714
-
715
- /// @notice Fuzz: weight decay is monotonically non-increasing.
716
- function testFuzz_weightDecay_monotonic(uint8 numCycles, uint32 cutPercent) external {
717
- numCycles = uint8(bound(numCycles, 1, 50));
718
- cutPercent = uint32(bound(cutPercent, 1, JBConstants.MAX_WEIGHT_CUT_PERCENT));
719
-
720
- uint256 pid = _launchProject(SEVEN_DAYS, INITIAL_WEIGHT, cutPercent, IJBRulesetApprovalHook(address(0)));
721
-
722
- uint256 prevWeight = INITIAL_WEIGHT;
723
- for (uint256 i = 1; i <= numCycles; i++) {
724
- vm.warp(block.timestamp + SEVEN_DAYS);
725
- JBRuleset memory current = _rulesets.currentOf(pid);
726
- assertLe(current.weight, prevWeight, "Weight should never increase");
727
- prevWeight = current.weight;
728
- }
729
- }
730
-
731
- /// @notice Fuzz: deriveStartFrom always returns a value >= mustStartAtOrAfter and aligned to duration.
732
- function testFuzz_deriveStartFrom_alignment(
733
- uint48 baseStart,
734
- uint32 duration,
735
- uint48 mustStartAfter
736
- )
737
- external
738
- view
739
- {
740
- // Bound to reasonable values.
741
- baseStart = uint48(bound(baseStart, 1, type(uint48).max / 2));
742
- duration = uint32(bound(duration, 1, type(uint32).max / 2));
743
- mustStartAfter = uint48(bound(mustStartAfter, baseStart, baseStart + 100 * uint256(duration)));
744
-
745
- uint256 start = _rulesets.deriveStartFrom(baseStart, duration, mustStartAfter);
746
-
747
- // Must be >= mustStartAtOrAfter.
748
- assertGe(start, mustStartAfter, "Start should be >= mustStartAtOrAfter");
749
-
750
- // Must be aligned to duration boundaries from baseStart.
751
- assertEq((start - baseStart) % duration, 0, "Start should be aligned to duration from baseStart");
752
- }
753
-
754
- // ───────────────────── EDGE: ZERO WEIGHT AFTER DECAY
755
- // ─────────────────────
756
-
757
- /// @notice After weight decays to zero, currentOf still works and returns weight=0.
758
- function test_zeroWeight_currentOfStillWorks() external {
759
- uint32 fullCut = uint32(JBConstants.MAX_WEIGHT_CUT_PERCENT);
760
- uint256 pid = _launchProject(SEVEN_DAYS, INITIAL_WEIGHT, fullCut, IJBRulesetApprovalHook(address(0)));
761
-
762
- // Warp 100 cycles past zero.
763
- vm.warp(block.timestamp + 700 days);
764
-
765
- JBRuleset memory current = _rulesets.currentOf(pid);
766
- assertEq(current.weight, 0, "Weight is zero");
767
- assertEq(current.cycleNumber, 101, "Still cycling correctly");
768
-
769
- // Queue a new ruleset with explicit weight.
770
- _queueRuleset(pid, 0, SEVEN_DAYS, 500e18, 0, IJBRulesetApprovalHook(address(0)));
771
-
772
- vm.warp(block.timestamp + SEVEN_DAYS);
773
-
774
- current = _rulesets.currentOf(pid);
775
- assertEq(current.weight, 500e18, "New weight should override zero");
776
- }
777
-
778
- /// @notice allOf returns the correct chain of rulesets after many queues.
779
- function test_allOf_chainIntegrity_afterManyQueues() external {
780
- uint256 pid = _launchProject(SEVEN_DAYS, INITIAL_WEIGHT, 0, IJBRulesetApprovalHook(address(0)));
781
-
782
- // Queue 4 more rulesets.
783
- for (uint256 i = 1; i <= 4; i++) {
784
- _queueRuleset(
785
- pid,
786
- 0,
787
- SEVEN_DAYS,
788
- // forge-lint: disable-next-line(unsafe-typecast)
789
- uint112(INITIAL_WEIGHT + uint112(i) * 100e18),
790
- 0,
791
- IJBRulesetApprovalHook(address(0))
792
- );
793
- vm.warp(block.timestamp + SEVEN_DAYS);
794
- }
795
-
796
- // Get all rulesets (5 total).
797
- JBRuleset[] memory all = _rulesets.allOf(pid, 0, 10);
798
- assertEq(all.length, 5, "Should have 5 rulesets in chain");
799
-
800
- // Verify descending order (latest first).
801
- for (uint256 i = 0; i < all.length - 1; i++) {
802
- assertGt(all[i].id, all[i + 1].id, "Should be in descending ID order");
803
- assertEq(all[i].basedOnId, all[i + 1].id, "basedOnId should link to previous");
804
- }
805
- }
806
- }