@bananapus/core-v6 0.0.17 → 0.0.19

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 (107) hide show
  1. package/CHANGE_LOG.md +53 -13
  2. package/SKILLS.md +6 -0
  3. package/USER_JOURNEYS.md +4 -0
  4. package/package.json +1 -1
  5. package/src/JBTerminalStore.sol +357 -171
  6. package/src/interfaces/IJBTerminalStore.sol +70 -0
  7. package/test/fork/TestSequencerPriceFeedFork.sol +168 -0
  8. package/test/units/static/JBTerminalStore/TestCurrentReclaimableSurplusOf.sol +215 -0
  9. package/test/units/static/JBTerminalStore/TestPreviewCashOutFrom.sol +383 -0
  10. package/test/units/static/JBTerminalStore/TestPreviewPayFrom.sol +415 -0
  11. package/docs/book.css +0 -13
  12. package/docs/book.toml +0 -12
  13. package/docs/solidity.min.js +0 -74
  14. package/docs/src/README.md +0 -703
  15. package/docs/src/SUMMARY.md +0 -94
  16. package/docs/src/src/JBChainlinkV3PriceFeed.sol/contract.JBChainlinkV3PriceFeed.md +0 -83
  17. package/docs/src/src/JBChainlinkV3SequencerPriceFeed.sol/contract.JBChainlinkV3SequencerPriceFeed.md +0 -88
  18. package/docs/src/src/JBController.sol/contract.JBController.md +0 -1121
  19. package/docs/src/src/JBDeadline.sol/contract.JBDeadline.md +0 -84
  20. package/docs/src/src/JBDirectory.sol/contract.JBDirectory.md +0 -294
  21. package/docs/src/src/JBERC20.sol/contract.JBERC20.md +0 -190
  22. package/docs/src/src/JBFeelessAddresses.sol/contract.JBFeelessAddresses.md +0 -80
  23. package/docs/src/src/JBFundAccessLimits.sol/contract.JBFundAccessLimits.md +0 -253
  24. package/docs/src/src/JBMultiTerminal.sol/contract.JBMultiTerminal.md +0 -1472
  25. package/docs/src/src/JBPermissions.sol/contract.JBPermissions.md +0 -199
  26. package/docs/src/src/JBPrices.sol/contract.JBPrices.md +0 -154
  27. package/docs/src/src/JBProjects.sol/contract.JBProjects.md +0 -131
  28. package/docs/src/src/JBRulesets.sol/contract.JBRulesets.md +0 -677
  29. package/docs/src/src/JBSplits.sol/contract.JBSplits.md +0 -237
  30. package/docs/src/src/JBTerminalStore.sol/contract.JBTerminalStore.md +0 -591
  31. package/docs/src/src/JBTokens.sol/contract.JBTokens.md +0 -353
  32. package/docs/src/src/README.md +0 -25
  33. package/docs/src/src/abstract/JBControlled.sol/abstract.JBControlled.md +0 -64
  34. package/docs/src/src/abstract/JBPermissioned.sol/abstract.JBPermissioned.md +0 -84
  35. package/docs/src/src/abstract/README.md +0 -5
  36. package/docs/src/src/enums/JBApprovalStatus.sol/enum.JBApprovalStatus.md +0 -17
  37. package/docs/src/src/enums/README.md +0 -4
  38. package/docs/src/src/interfaces/IJBCashOutHook.sol/interface.IJBCashOutHook.md +0 -29
  39. package/docs/src/src/interfaces/IJBCashOutTerminal.sol/interface.IJBCashOutTerminal.md +0 -57
  40. package/docs/src/src/interfaces/IJBControlled.sol/interface.IJBControlled.md +0 -12
  41. package/docs/src/src/interfaces/IJBController.sol/interface.IJBController.md +0 -334
  42. package/docs/src/src/interfaces/IJBDirectory.sol/interface.IJBDirectory.md +0 -108
  43. package/docs/src/src/interfaces/IJBDirectoryAccessControl.sol/interface.IJBDirectoryAccessControl.md +0 -19
  44. package/docs/src/src/interfaces/IJBFeeTerminal.sol/interface.IJBFeeTerminal.md +0 -91
  45. package/docs/src/src/interfaces/IJBFeelessAddresses.sol/interface.IJBFeelessAddresses.md +0 -26
  46. package/docs/src/src/interfaces/IJBFundAccessLimits.sol/interface.IJBFundAccessLimits.md +0 -88
  47. package/docs/src/src/interfaces/IJBMigratable.sol/interface.IJBMigratable.md +0 -29
  48. package/docs/src/src/interfaces/IJBMultiTerminal.sol/interface.IJBMultiTerminal.md +0 -50
  49. package/docs/src/src/interfaces/IJBPayHook.sol/interface.IJBPayHook.md +0 -28
  50. package/docs/src/src/interfaces/IJBPayoutTerminal.sol/interface.IJBPayoutTerminal.md +0 -105
  51. package/docs/src/src/interfaces/IJBPermissioned.sol/interface.IJBPermissioned.md +0 -12
  52. package/docs/src/src/interfaces/IJBPermissions.sol/interface.IJBPermissions.md +0 -74
  53. package/docs/src/src/interfaces/IJBPermitTerminal.sol/interface.IJBPermitTerminal.md +0 -15
  54. package/docs/src/src/interfaces/IJBPriceFeed.sol/interface.IJBPriceFeed.md +0 -12
  55. package/docs/src/src/interfaces/IJBPrices.sol/interface.IJBPrices.md +0 -74
  56. package/docs/src/src/interfaces/IJBProjectUriRegistry.sol/interface.IJBProjectUriRegistry.md +0 -19
  57. package/docs/src/src/interfaces/IJBProjects.sol/interface.IJBProjects.md +0 -49
  58. package/docs/src/src/interfaces/IJBRulesetApprovalHook.sol/interface.IJBRulesetApprovalHook.md +0 -35
  59. package/docs/src/src/interfaces/IJBRulesetDataHook.sol/interface.IJBRulesetDataHook.md +0 -97
  60. package/docs/src/src/interfaces/IJBRulesets.sol/interface.IJBRulesets.md +0 -165
  61. package/docs/src/src/interfaces/IJBSplitHook.sol/interface.IJBSplitHook.md +0 -31
  62. package/docs/src/src/interfaces/IJBSplits.sol/interface.IJBSplits.md +0 -35
  63. package/docs/src/src/interfaces/IJBTerminal.sol/interface.IJBTerminal.md +0 -141
  64. package/docs/src/src/interfaces/IJBTerminalStore.sol/interface.IJBTerminalStore.md +0 -198
  65. package/docs/src/src/interfaces/IJBToken.sol/interface.IJBToken.md +0 -54
  66. package/docs/src/src/interfaces/IJBTokenUriResolver.sol/interface.IJBTokenUriResolver.md +0 -12
  67. package/docs/src/src/interfaces/IJBTokens.sol/interface.IJBTokens.md +0 -151
  68. package/docs/src/src/interfaces/README.md +0 -33
  69. package/docs/src/src/libraries/JBCashOuts.sol/library.JBCashOuts.md +0 -40
  70. package/docs/src/src/libraries/JBConstants.sol/library.JBConstants.md +0 -52
  71. package/docs/src/src/libraries/JBCurrencyIds.sol/library.JBCurrencyIds.md +0 -19
  72. package/docs/src/src/libraries/JBFees.sol/library.JBFees.md +0 -52
  73. package/docs/src/src/libraries/JBFixedPointNumber.sol/library.JBFixedPointNumber.md +0 -12
  74. package/docs/src/src/libraries/JBMetadataResolver.sol/library.JBMetadataResolver.md +0 -242
  75. package/docs/src/src/libraries/JBRulesetMetadataResolver.sol/library.JBRulesetMetadataResolver.md +0 -180
  76. package/docs/src/src/libraries/JBSplitGroupIds.sol/library.JBSplitGroupIds.md +0 -14
  77. package/docs/src/src/libraries/JBSurplus.sol/library.JBSurplus.md +0 -44
  78. package/docs/src/src/libraries/README.md +0 -12
  79. package/docs/src/src/periphery/JBDeadline1Day.sol/contract.JBDeadline1Day.md +0 -15
  80. package/docs/src/src/periphery/JBDeadline3Days.sol/contract.JBDeadline3Days.md +0 -15
  81. package/docs/src/src/periphery/JBDeadline3Hours.sol/contract.JBDeadline3Hours.md +0 -15
  82. package/docs/src/src/periphery/JBDeadline7Days.sol/contract.JBDeadline7Days.md +0 -15
  83. package/docs/src/src/periphery/JBMatchingPriceFeed.sol/contract.JBMatchingPriceFeed.md +0 -22
  84. package/docs/src/src/periphery/README.md +0 -8
  85. package/docs/src/src/structs/JBAccountingContext.sol/struct.JBAccountingContext.md +0 -20
  86. package/docs/src/src/structs/JBAfterCashOutRecordedContext.sol/struct.JBAfterCashOutRecordedContext.md +0 -43
  87. package/docs/src/src/structs/JBAfterPayRecordedContext.sol/struct.JBAfterPayRecordedContext.md +0 -42
  88. package/docs/src/src/structs/JBBeforeCashOutRecordedContext.sol/struct.JBBeforeCashOutRecordedContext.md +0 -45
  89. package/docs/src/src/structs/JBBeforePayRecordedContext.sol/struct.JBBeforePayRecordedContext.md +0 -41
  90. package/docs/src/src/structs/JBCashOutHookSpecification.sol/struct.JBCashOutHookSpecification.md +0 -22
  91. package/docs/src/src/structs/JBCurrencyAmount.sol/struct.JBCurrencyAmount.md +0 -17
  92. package/docs/src/src/structs/JBFee.sol/struct.JBFee.md +0 -20
  93. package/docs/src/src/structs/JBFundAccessLimitGroup.sol/struct.JBFundAccessLimitGroup.md +0 -39
  94. package/docs/src/src/structs/JBPayHookSpecification.sol/struct.JBPayHookSpecification.md +0 -22
  95. package/docs/src/src/structs/JBPermissionsData.sol/struct.JBPermissionsData.md +0 -21
  96. package/docs/src/src/structs/JBRuleset.sol/struct.JBRuleset.md +0 -55
  97. package/docs/src/src/structs/JBRulesetConfig.sol/struct.JBRulesetConfig.md +0 -51
  98. package/docs/src/src/structs/JBRulesetMetadata.sol/struct.JBRulesetMetadata.md +0 -79
  99. package/docs/src/src/structs/JBRulesetWeightCache.sol/struct.JBRulesetWeightCache.md +0 -16
  100. package/docs/src/src/structs/JBRulesetWithMetadata.sol/struct.JBRulesetWithMetadata.md +0 -16
  101. package/docs/src/src/structs/JBSingleAllowance.sol/struct.JBSingleAllowance.md +0 -26
  102. package/docs/src/src/structs/JBSplit.sol/struct.JBSplit.md +0 -49
  103. package/docs/src/src/structs/JBSplitGroup.sol/struct.JBSplitGroup.md +0 -17
  104. package/docs/src/src/structs/JBSplitHookContext.sol/struct.JBSplitHookContext.md +0 -29
  105. package/docs/src/src/structs/JBTerminalConfig.sol/struct.JBTerminalConfig.md +0 -16
  106. package/docs/src/src/structs/JBTokenAmount.sol/struct.JBTokenAmount.md +0 -23
  107. package/docs/src/src/structs/README.md +0 -25
@@ -0,0 +1,168 @@
1
+ // SPDX-License-Identifier: MIT
2
+ pragma solidity 0.8.26;
3
+
4
+ import {Test} from "forge-std/Test.sol";
5
+
6
+ import {AggregatorV2V3Interface} from "@chainlink/contracts/src/v0.8/shared/interfaces/AggregatorV2V3Interface.sol";
7
+ import {AggregatorV3Interface} from "@chainlink/contracts/src/v0.8/shared/interfaces/AggregatorV3Interface.sol";
8
+
9
+ import {JBChainlinkV3SequencerPriceFeed} from "../../src/JBChainlinkV3SequencerPriceFeed.sol";
10
+
11
+ /// @notice Fork tests for JBChainlinkV3SequencerPriceFeed against the live Arbitrum sequencer uptime feed and
12
+ /// ETH/USD Chainlink oracle.
13
+ contract TestSequencerPriceFeedFork is Test {
14
+ // Chainlink feed addresses (Arbitrum mainnet).
15
+ address constant ARB_ETH_USD_FEED = 0x639Fe6ab55C921f74e7fac1ee960C0B6293ba612;
16
+ address constant ARB_SEQUENCER_FEED = 0xFdB631F5EE196F0ed6FAa767959853A9F217697D;
17
+
18
+ // Staleness threshold (1 hour).
19
+ uint256 constant THRESHOLD = 3600;
20
+
21
+ // Grace period after sequencer restart (1 hour).
22
+ uint256 constant GRACE_PERIOD = 3600;
23
+
24
+ // Pinned block for reproducibility (sequencer is up at this block).
25
+ uint256 constant FORK_BLOCK = 300_000_000;
26
+
27
+ JBChainlinkV3SequencerPriceFeed feed;
28
+
29
+ function setUp() public {
30
+ string memory rpc = vm.envOr("RPC_ARBITRUM_MAINNET", string(""));
31
+ if (bytes(rpc).length == 0) {
32
+ // Skip all tests if no Arbitrum RPC is configured.
33
+ return;
34
+ }
35
+
36
+ vm.createSelectFork(rpc, FORK_BLOCK);
37
+
38
+ feed = new JBChainlinkV3SequencerPriceFeed(
39
+ AggregatorV3Interface(ARB_ETH_USD_FEED),
40
+ THRESHOLD,
41
+ AggregatorV2V3Interface(ARB_SEQUENCER_FEED),
42
+ GRACE_PERIOD
43
+ );
44
+ }
45
+
46
+ // ------------------------------------------------------------------
47
+ // Helpers
48
+ // ------------------------------------------------------------------
49
+
50
+ modifier skipIfNoRpc() {
51
+ if (address(feed) == address(0)) {
52
+ return;
53
+ }
54
+ _;
55
+ }
56
+
57
+ // ------------------------------------------------------------------
58
+ // 1. Normal operation — valid price returned
59
+ // ------------------------------------------------------------------
60
+
61
+ /// @notice Under normal conditions (sequencer up, grace period elapsed), currentUnitPrice returns a sane price.
62
+ function test_normalOperation_returnsValidPrice() public skipIfNoRpc {
63
+ uint256 price18 = feed.currentUnitPrice(18);
64
+
65
+ // ETH price should be between $500 and $50,000.
66
+ assertGt(price18, 500e18, "ETH price too low");
67
+ assertLt(price18, 50_000e18, "ETH price too high");
68
+
69
+ // Cross-check against raw latestRoundData from the price feed.
70
+ (, int256 rawPrice,,,) = AggregatorV3Interface(ARB_ETH_USD_FEED).latestRoundData();
71
+ uint256 feedDecimals = AggregatorV3Interface(ARB_ETH_USD_FEED).decimals();
72
+ // forge-lint: disable-next-line(unsafe-typecast)
73
+ uint256 expected18 = uint256(rawPrice) * 10 ** (18 - feedDecimals);
74
+ assertEq(price18, expected18, "Price mismatch vs raw feed");
75
+ }
76
+
77
+ // ------------------------------------------------------------------
78
+ // 2. Sequencer down — reverts
79
+ // ------------------------------------------------------------------
80
+
81
+ /// @notice When the sequencer feed reports answer=1 (down), currentUnitPrice reverts.
82
+ function test_sequencerDown_reverts() public skipIfNoRpc {
83
+ // Mock the sequencer feed to report answer=1 (down).
84
+ // latestRoundData() selector = 0xfeaf968c
85
+ vm.mockCall(
86
+ ARB_SEQUENCER_FEED,
87
+ abi.encodeWithSelector(AggregatorV3Interface.latestRoundData.selector),
88
+ abi.encode(
89
+ uint80(1), // roundId
90
+ int256(1), // answer = 1 → sequencer down
91
+ block.timestamp - GRACE_PERIOD - 100, // startedAt (irrelevant when answer=1)
92
+ block.timestamp, // updatedAt
93
+ uint80(1) // answeredInRound
94
+ )
95
+ );
96
+
97
+ vm.expectRevert(
98
+ abi.encodeWithSelector(
99
+ JBChainlinkV3SequencerPriceFeed.JBChainlinkV3SequencerPriceFeed_SequencerDownOrRestarting.selector,
100
+ block.timestamp,
101
+ GRACE_PERIOD,
102
+ block.timestamp - GRACE_PERIOD - 100
103
+ )
104
+ );
105
+ feed.currentUnitPrice(18);
106
+ }
107
+
108
+ // ------------------------------------------------------------------
109
+ // 3. Grace period active — reverts
110
+ // ------------------------------------------------------------------
111
+
112
+ /// @notice When the sequencer just came back up (within grace period), currentUnitPrice reverts.
113
+ function test_gracePeriodActive_reverts() public skipIfNoRpc {
114
+ // Mock the sequencer feed: answer=0 (up) but startedAt is 1 second ago (within grace period).
115
+ uint256 startedAt = block.timestamp - 1;
116
+
117
+ vm.mockCall(
118
+ ARB_SEQUENCER_FEED,
119
+ abi.encodeWithSelector(AggregatorV3Interface.latestRoundData.selector),
120
+ abi.encode(
121
+ uint80(1), // roundId
122
+ int256(0), // answer = 0 → sequencer up
123
+ startedAt, // startedAt = very recent
124
+ block.timestamp, // updatedAt
125
+ uint80(1) // answeredInRound
126
+ )
127
+ );
128
+
129
+ vm.expectRevert(
130
+ abi.encodeWithSelector(
131
+ JBChainlinkV3SequencerPriceFeed.JBChainlinkV3SequencerPriceFeed_SequencerDownOrRestarting.selector,
132
+ block.timestamp,
133
+ GRACE_PERIOD,
134
+ startedAt
135
+ )
136
+ );
137
+ feed.currentUnitPrice(18);
138
+ }
139
+
140
+ // ------------------------------------------------------------------
141
+ // 4. Post-grace recovery — succeeds
142
+ // ------------------------------------------------------------------
143
+
144
+ /// @notice After the grace period has elapsed, currentUnitPrice succeeds again.
145
+ function test_postGraceRecovery_succeeds() public skipIfNoRpc {
146
+ // Mock the sequencer feed: answer=0 (up), startedAt is well before the grace period ended.
147
+ uint256 startedAt = block.timestamp - GRACE_PERIOD - 100;
148
+
149
+ vm.mockCall(
150
+ ARB_SEQUENCER_FEED,
151
+ abi.encodeWithSelector(AggregatorV3Interface.latestRoundData.selector),
152
+ abi.encode(
153
+ uint80(1), // roundId
154
+ int256(0), // answer = 0 → sequencer up
155
+ startedAt, // startedAt = well in the past
156
+ block.timestamp, // updatedAt
157
+ uint80(1) // answeredInRound
158
+ )
159
+ );
160
+
161
+ // The price feed itself is still the real Chainlink feed, so this should succeed.
162
+ uint256 price18 = feed.currentUnitPrice(18);
163
+
164
+ // Same sanity check: ETH price between $500 and $50,000.
165
+ assertGt(price18, 500e18, "ETH price too low after recovery");
166
+ assertLt(price18, 50_000e18, "ETH price too high after recovery");
167
+ }
168
+ }
@@ -444,4 +444,219 @@ contract TestCurrentReclaimableSurplusOf_Local is JBTerminalStoreSetup {
444
444
  uint256 reclaimable = _store.currentReclaimableSurplusOf(_projectId, _tokenCount, 1e18, 1e18);
445
445
  assertEq(1e18, reclaimable);
446
446
  }
447
+
448
+ function test_GivenTotalReclaimableWithSurplus() external whenProjectHasBalance {
449
+ // it will default to all terminals and all accounting contexts and return the reclaimable surplus
450
+
451
+ // setup calldata
452
+ JBAccountingContext[] memory _contexts = new JBAccountingContext[](1);
453
+ _contexts[0] = JBAccountingContext({token: address(_token), decimals: 18, currency: _currency});
454
+
455
+ JBRulesetMetadata memory _metadata = JBRulesetMetadata({
456
+ reservedPercent: 0,
457
+ cashOutTaxRate: JBConstants.MAX_CASH_OUT_TAX_RATE / 2,
458
+ baseCurrency: uint32(uint160(JBConstants.NATIVE_TOKEN)),
459
+ pausePay: false,
460
+ pauseCreditTransfers: false,
461
+ allowOwnerMinting: false,
462
+ allowSetCustomToken: false,
463
+ allowTerminalMigration: false,
464
+ allowSetTerminals: false,
465
+ ownerMustSendPayouts: false,
466
+ allowSetController: false,
467
+ allowAddAccountingContext: true,
468
+ allowAddPriceFeed: false,
469
+ holdFees: false,
470
+ useTotalSurplusForCashOuts: false,
471
+ useDataHookForPay: false,
472
+ useDataHookForCashOut: false,
473
+ dataHook: address(0),
474
+ metadata: 0
475
+ });
476
+
477
+ uint256 _packedMetadata = JBRulesetMetadataResolver.packRulesetMetadata(_metadata);
478
+
479
+ // JBRulesets return calldata
480
+ JBRuleset memory _returnedRuleset = JBRuleset({
481
+ cycleNumber: uint48(block.timestamp),
482
+ id: uint48(block.timestamp),
483
+ basedOnId: 0,
484
+ start: uint48(block.timestamp),
485
+ duration: uint32(block.timestamp + 1000),
486
+ weight: 1e18,
487
+ weightCutPercent: 0,
488
+ approvalHook: IJBRulesetApprovalHook(address(0)),
489
+ metadata: _packedMetadata
490
+ });
491
+
492
+ // mock call to JBRulesets currentOf
493
+ mockExpect(address(rulesets), abi.encodeCall(IJBRulesets.currentOf, (_projectId)), abi.encode(_returnedRuleset));
494
+
495
+ // mock call to JBDirectory controllerOf
496
+ mockExpect(address(directory), abi.encodeCall(IJBDirectory.controllerOf, (_projectId)), abi.encode(_controller));
497
+
498
+ uint256 _supply = 1e19;
499
+ uint256 _surplus = 1e18;
500
+ uint256 _cashoutAmount = 1e18;
501
+
502
+ // mock JBDirectory terminalsOf to return the terminal
503
+ IJBTerminal[] memory _terminals = new IJBTerminal[](1);
504
+ _terminals[0] = _terminal;
505
+ mockExpect(address(directory), abi.encodeCall(IJBDirectory.terminalsOf, (_projectId)), abi.encode(_terminals));
506
+
507
+ // surplus call to the terminal (empty accounting contexts passed through)
508
+ JBAccountingContext[] memory _emptyContexts = new JBAccountingContext[](0);
509
+ mockExpect(
510
+ address(_terminal),
511
+ abi.encodeCall(IJBTerminal.currentSurplusOf, (_projectId, _emptyContexts, 18, _currency)),
512
+ abi.encode(_surplus)
513
+ );
514
+
515
+ // mock JBController totalTokenSupplyWithReservedTokensOf
516
+ mockExpect(
517
+ address(_controller),
518
+ abi.encodeCall(IJBController.totalTokenSupplyWithReservedTokensOf, (_projectId)),
519
+ abi.encode(_supply)
520
+ );
521
+
522
+ // Call the new convenience function (no terminals, no accounting contexts).
523
+ uint256 reclaimable = _store.currentTotalReclaimableSurplusOf(_projectId, _cashoutAmount, 18, _currency);
524
+
525
+ // Should match the 6-param overload result.
526
+ uint256 assumed =
527
+ JBCashOuts.cashOutFrom(_surplus, _cashoutAmount, _supply, JBConstants.MAX_CASH_OUT_TAX_RATE / 2);
528
+
529
+ assertEq(assumed, reclaimable);
530
+ }
531
+
532
+ function test_GivenTotalReclaimableWithZeroSurplus() external {
533
+ // it will return zero when there is no surplus
534
+
535
+ JBAccountingContext[] memory _emptyContexts = new JBAccountingContext[](0);
536
+
537
+ // JBRulesets return calldata
538
+ JBRuleset memory _returnedRuleset = JBRuleset({
539
+ cycleNumber: uint48(block.timestamp),
540
+ id: uint48(block.timestamp),
541
+ basedOnId: 0,
542
+ start: uint48(block.timestamp),
543
+ duration: uint32(block.timestamp + 1000),
544
+ weight: 1e18,
545
+ weightCutPercent: 0,
546
+ approvalHook: IJBRulesetApprovalHook(address(0)),
547
+ metadata: 0
548
+ });
549
+
550
+ // mock call to JBRulesets currentOf
551
+ mockExpect(address(rulesets), abi.encodeCall(IJBRulesets.currentOf, (_projectId)), abi.encode(_returnedRuleset));
552
+
553
+ // mock JBDirectory terminalsOf to return the terminal
554
+ IJBTerminal[] memory _terminals = new IJBTerminal[](1);
555
+ _terminals[0] = _terminal;
556
+ mockExpect(address(directory), abi.encodeCall(IJBDirectory.terminalsOf, (_projectId)), abi.encode(_terminals));
557
+
558
+ // mock current surplus as zero
559
+ mockExpect(
560
+ address(_terminal),
561
+ abi.encodeCall(IJBTerminal.currentSurplusOf, (_projectId, _emptyContexts, 18, _currency)),
562
+ abi.encode(0)
563
+ );
564
+
565
+ uint256 reclaimable = _store.currentTotalReclaimableSurplusOf(_projectId, _tokenCount, 18, _currency);
566
+ assertEq(0, reclaimable);
567
+ }
568
+
569
+ function test_GivenTotalReclaimableMatchesSixParamOverload() external whenProjectHasBalance {
570
+ // it will produce the same result as calling the 6-param overload with empty arrays
571
+
572
+ JBAccountingContext[] memory _emptyContexts = new JBAccountingContext[](0);
573
+
574
+ JBRulesetMetadata memory _metadata = JBRulesetMetadata({
575
+ reservedPercent: 0,
576
+ cashOutTaxRate: JBConstants.MAX_CASH_OUT_TAX_RATE / 2,
577
+ baseCurrency: uint32(uint160(JBConstants.NATIVE_TOKEN)),
578
+ pausePay: false,
579
+ pauseCreditTransfers: false,
580
+ allowOwnerMinting: false,
581
+ allowSetCustomToken: false,
582
+ allowTerminalMigration: false,
583
+ allowSetTerminals: false,
584
+ ownerMustSendPayouts: false,
585
+ allowSetController: false,
586
+ allowAddAccountingContext: true,
587
+ allowAddPriceFeed: false,
588
+ holdFees: false,
589
+ useTotalSurplusForCashOuts: false,
590
+ useDataHookForPay: false,
591
+ useDataHookForCashOut: false,
592
+ dataHook: address(0),
593
+ metadata: 0
594
+ });
595
+
596
+ uint256 _packedMetadata = JBRulesetMetadataResolver.packRulesetMetadata(_metadata);
597
+
598
+ JBRuleset memory _returnedRuleset = JBRuleset({
599
+ cycleNumber: uint48(block.timestamp),
600
+ id: uint48(block.timestamp),
601
+ basedOnId: 0,
602
+ start: uint48(block.timestamp),
603
+ duration: uint32(block.timestamp + 1000),
604
+ weight: 1e18,
605
+ weightCutPercent: 0,
606
+ approvalHook: IJBRulesetApprovalHook(address(0)),
607
+ metadata: _packedMetadata
608
+ });
609
+
610
+ uint256 _supply = 1e19;
611
+ uint256 _surplus = 5e17;
612
+ uint256 _cashoutAmount = 1e18;
613
+
614
+ IJBTerminal[] memory _terminals = new IJBTerminal[](1);
615
+ _terminals[0] = _terminal;
616
+
617
+ // The new overload calls the 6-param via `this`, so JBRulesets.currentOf gets called twice.
618
+ // Mock it to return the same ruleset both times.
619
+ mockExpect(address(rulesets), abi.encodeCall(IJBRulesets.currentOf, (_projectId)), abi.encode(_returnedRuleset));
620
+
621
+ mockExpect(address(directory), abi.encodeCall(IJBDirectory.controllerOf, (_projectId)), abi.encode(_controller));
622
+
623
+ mockExpect(address(directory), abi.encodeCall(IJBDirectory.terminalsOf, (_projectId)), abi.encode(_terminals));
624
+
625
+ mockExpect(
626
+ address(_terminal),
627
+ abi.encodeCall(IJBTerminal.currentSurplusOf, (_projectId, _emptyContexts, 18, _currency)),
628
+ abi.encode(_surplus)
629
+ );
630
+
631
+ mockExpect(
632
+ address(_controller),
633
+ abi.encodeCall(IJBController.totalTokenSupplyWithReservedTokensOf, (_projectId)),
634
+ abi.encode(_supply)
635
+ );
636
+
637
+ // Call the new convenience function.
638
+ uint256 reclaimableDefault = _store.currentTotalReclaimableSurplusOf(_projectId, _cashoutAmount, 18, _currency);
639
+
640
+ // Re-mock for the 6-param call (mocks are consumed).
641
+ mockExpect(address(rulesets), abi.encodeCall(IJBRulesets.currentOf, (_projectId)), abi.encode(_returnedRuleset));
642
+ mockExpect(address(directory), abi.encodeCall(IJBDirectory.controllerOf, (_projectId)), abi.encode(_controller));
643
+ mockExpect(address(directory), abi.encodeCall(IJBDirectory.terminalsOf, (_projectId)), abi.encode(_terminals));
644
+ mockExpect(
645
+ address(_terminal),
646
+ abi.encodeCall(IJBTerminal.currentSurplusOf, (_projectId, _emptyContexts, 18, _currency)),
647
+ abi.encode(_surplus)
648
+ );
649
+ mockExpect(
650
+ address(_controller),
651
+ abi.encodeCall(IJBController.totalTokenSupplyWithReservedTokensOf, (_projectId)),
652
+ abi.encode(_supply)
653
+ );
654
+
655
+ // Call the 6-param overload with empty arrays.
656
+ uint256 reclaimableExplicit = _store.currentReclaimableSurplusOf(
657
+ _projectId, _cashoutAmount, new IJBTerminal[](0), new JBAccountingContext[](0), 18, _currency
658
+ );
659
+
660
+ assertEq(reclaimableDefault, reclaimableExplicit);
661
+ }
447
662
  }