@bananapus/core-v6 0.0.21 → 0.0.23

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 (48) hide show
  1. package/ADMINISTRATION.md +0 -1
  2. package/AUDIT_INSTRUCTIONS.md +1 -1
  3. package/CHANGE_LOG.md +3 -3
  4. package/RISKS.md +3 -3
  5. package/SKILLS.md +8 -8
  6. package/USER_JOURNEYS.md +1 -1
  7. package/foundry.toml +0 -1
  8. package/package.json +1 -1
  9. package/src/JBMultiTerminal.sol +92 -192
  10. package/src/JBTerminalStore.sol +414 -256
  11. package/src/interfaces/IJBMultiTerminal.sol +0 -4
  12. package/src/interfaces/IJBTerminal.sol +4 -4
  13. package/src/interfaces/IJBTerminalStore.sol +65 -33
  14. package/src/libraries/JBPayoutSplitGroupLib.sol +0 -1
  15. package/src/libraries/JBSurplus.sol +3 -4
  16. package/test/ComprehensiveInvariant.t.sol +5 -7
  17. package/test/CoreExploitTests.t.sol +18 -23
  18. package/test/TestCashOut.sol +6 -6
  19. package/test/TestMultiTerminalSurplus.sol +4 -4
  20. package/test/TestMultiTokenSurplus.sol +6 -23
  21. package/test/TestTerminalMigration.sol +2 -7
  22. package/test/fork/TestSequencerPriceFeedFork.sol +1 -1
  23. package/test/fork/TestTerminalPreviewParityFork.sol +0 -1
  24. package/test/invariants/TerminalStoreInvariant.t.sol +5 -7
  25. package/test/units/static/JBMultiTerminal/JBMultiTerminalSetup.sol +1 -2
  26. package/test/units/static/JBMultiTerminal/TestAccountingContextsOf.sol +23 -24
  27. package/test/units/static/JBMultiTerminal/TestAddAccountingContextsFor.sol +79 -119
  28. package/test/units/static/JBMultiTerminal/TestAddToBalanceOf.sol +33 -26
  29. package/test/units/static/JBMultiTerminal/TestCashOutTokensOf.sol +32 -27
  30. package/test/units/static/JBMultiTerminal/TestExecutePayout.sol +22 -4
  31. package/test/units/static/JBMultiTerminal/TestExecuteProcessFee.sol +8 -5
  32. package/test/units/static/JBMultiTerminal/TestPay.sol +41 -33
  33. package/test/units/static/JBMultiTerminal/TestPreviewCashOutFrom.sol +19 -18
  34. package/test/units/static/JBMultiTerminal/TestPreviewPayFor.sol +38 -22
  35. package/test/units/static/JBMultiTerminal/TestProcessHeldFeesOf.sol +9 -6
  36. package/test/units/static/JBMultiTerminal/TestSendPayoutsOf.sol +4 -4
  37. package/test/units/static/JBMultiTerminal/TestUseAllowanceOf.sol +37 -32
  38. package/test/units/static/JBSurplus/TestSurplusFuzz.sol +5 -20
  39. package/test/units/static/JBTerminalStore/JBTerminalStoreSetup.sol +17 -0
  40. package/test/units/static/JBTerminalStore/TestCurrentReclaimableSurplusOf.sol +120 -246
  41. package/test/units/static/JBTerminalStore/TestCurrentSurplusOf.sol +29 -7
  42. package/test/units/static/JBTerminalStore/TestCurrentTotalSurplusOf.sol +88 -20
  43. package/test/units/static/JBTerminalStore/TestPreviewCashOutFrom.sol +30 -29
  44. package/test/units/static/JBTerminalStore/TestPreviewPayFrom.sol +46 -16
  45. package/test/units/static/JBTerminalStore/TestRecordCashOutsFor.sol +24 -53
  46. package/test/units/static/JBTerminalStore/TestRecordPayoutFor.sol +24 -4
  47. package/test/units/static/JBTerminalStore/TestRecordUsedAllowanceOf.sol +14 -4
  48. package/test/units/static/JBTerminalStore/TestUint224Overflow.sol +21 -3
@@ -37,31 +37,26 @@ contract TestCurrentReclaimableSurplusOf_Local is JBTerminalStoreSetup {
37
37
  super.terminalStoreSetup();
38
38
  }
39
39
 
40
- modifier whenProjectHasBalance() {
41
- // Find the storage slot
40
+ /// @notice Helper to register an accounting context with the store (pranks as the terminal).
41
+ function _registerContext(JBAccountingContext memory ctx) internal {
42
+ JBAccountingContext[] memory ctxs = new JBAccountingContext[](1);
43
+ ctxs[0] = ctx;
44
+ vm.prank(address(_terminal));
45
+ _store.recordAccountingContextOf(_projectId, ctxs);
46
+ }
47
+
48
+ /// @notice Helper to set balance for the terminal/project/token via vm.store.
49
+ function _setBalance(uint256 balance) internal {
42
50
  bytes32 balanceOfSlot = keccak256(abi.encode(address(_terminal), uint256(0)));
43
51
  bytes32 projectSlot = keccak256(abi.encode(_projectId, uint256(balanceOfSlot)));
44
52
  bytes32 slot = keccak256(abi.encode(address(_token), uint256(projectSlot)));
45
-
46
- bytes32 balanceBytes = bytes32(_balance);
47
-
48
- // Set balance
49
- vm.store(address(_store), slot, balanceBytes);
50
-
51
- // Ensure balance is set correctly
52
- uint256 _balanceCallReturn = _store.balanceOf(address(_terminal), _projectId, address(_token));
53
- assertEq(_balanceCallReturn, _balance);
54
- _;
53
+ vm.store(address(_store), slot, bytes32(balance));
55
54
  }
56
55
 
57
- function test_GivenCurrentSurplusEqZero() external {
58
- // it will return zero
59
-
60
- // setup calldata
61
- JBAccountingContext[] memory _contexts = new JBAccountingContext[](1);
62
- _contexts[0] = JBAccountingContext({token: address(_token), decimals: 18, currency: _currency});
63
-
64
- // JBRulesets return calldata
56
+ /// @notice Helper to set up common mocks for surplus computation.
57
+ /// @param packedMetadata The packed ruleset metadata.
58
+ /// @param payoutLimits The payout limits to mock.
59
+ function _mockSurplusInfra(uint256 packedMetadata, JBCurrencyAmount[] memory payoutLimits) internal {
65
60
  JBRuleset memory _returnedRuleset = JBRuleset({
66
61
  cycleNumber: uint48(block.timestamp),
67
62
  id: uint48(block.timestamp),
@@ -71,32 +66,45 @@ contract TestCurrentReclaimableSurplusOf_Local is JBTerminalStoreSetup {
71
66
  weight: 1e18,
72
67
  weightCutPercent: 0,
73
68
  approvalHook: IJBRulesetApprovalHook(address(0)),
74
- metadata: 0
69
+ metadata: packedMetadata
75
70
  });
76
71
 
77
- // mock call to JBRulesets currentOf
78
72
  mockExpect(address(rulesets), abi.encodeCall(IJBRulesets.currentOf, (_projectId)), abi.encode(_returnedRuleset));
79
-
80
- // mock current surplus as zero
73
+ mockExpect(address(directory), abi.encodeCall(IJBDirectory.controllerOf, (_projectId)), abi.encode(_controller));
81
74
  mockExpect(
82
- address(_terminal),
83
- abi.encodeCall(IJBTerminal.currentSurplusOf, (_projectId, _contexts, 18, _currency)),
84
- abi.encode(0)
75
+ address(_controller), abi.encodeCall(IJBController.FUND_ACCESS_LIMITS, ()), abi.encode(_accessLimits)
85
76
  );
77
+ mockExpect(
78
+ address(_accessLimits),
79
+ abi.encodeCall(
80
+ IJBFundAccessLimits.payoutLimitsOf, (_projectId, block.timestamp, address(_terminal), address(_token))
81
+ ),
82
+ abi.encode(payoutLimits)
83
+ );
84
+ }
85
+
86
+ function test_GivenCurrentSurplusEqZero() external {
87
+ // it will return zero
88
+
89
+ // Register accounting context so the store can resolve it.
90
+ _registerContext(JBAccountingContext({token: address(_token), decimals: 18, currency: _currency}));
91
+
92
+ // No balance set — surplus is 0.
93
+ JBCurrencyAmount[] memory _emptyLimits = new JBCurrencyAmount[](0);
94
+ _mockSurplusInfra(0, _emptyLimits);
86
95
 
87
96
  IJBTerminal[] memory _terminals = new IJBTerminal[](1);
88
97
  _terminals[0] = _terminal;
89
98
  uint256 reclaimable =
90
- _store.currentReclaimableSurplusOf(_projectId, _tokenCount, _terminals, _contexts, 18, _currency);
99
+ _store.currentReclaimableSurplusOf(_projectId, _tokenCount, _terminals, new address[](0), 18, _currency);
91
100
  assertEq(0, reclaimable);
92
101
  }
93
102
 
94
- function test_GivenCurrentSurplusGtZero() external whenProjectHasBalance {
103
+ function test_GivenCurrentSurplusGtZero() external {
95
104
  // it will get the number of outstanding tokens and return the reclaimable surplus
96
105
 
97
- // setup calldata
98
- JBAccountingContext[] memory _contexts = new JBAccountingContext[](1);
99
- _contexts[0] = JBAccountingContext({token: address(_token), decimals: 18, currency: _currency});
106
+ // Register accounting context.
107
+ _registerContext(JBAccountingContext({token: address(_token), decimals: 18, currency: _currency}));
100
108
 
101
109
  JBRulesetMetadata memory _metadata = JBRulesetMetadata({
102
110
  reservedPercent: 0,
@@ -122,36 +130,18 @@ contract TestCurrentReclaimableSurplusOf_Local is JBTerminalStoreSetup {
122
130
 
123
131
  uint256 _packedMetadata = JBRulesetMetadataResolver.packRulesetMetadata(_metadata);
124
132
 
125
- // JBRulesets return calldata
126
- JBRuleset memory _returnedRuleset = JBRuleset({
127
- cycleNumber: uint48(block.timestamp),
128
- id: uint48(block.timestamp),
129
- basedOnId: 0,
130
- start: uint48(block.timestamp),
131
- duration: uint32(block.timestamp + 1000),
132
- weight: 1e18,
133
- weightCutPercent: 0,
134
- approvalHook: IJBRulesetApprovalHook(address(0)),
135
- metadata: _packedMetadata
136
- });
137
-
138
- // mock call to JBRulesets currentOf
139
- mockExpect(address(rulesets), abi.encodeCall(IJBRulesets.currentOf, (_projectId)), abi.encode(_returnedRuleset));
140
-
141
- // mock call to JBDirectory controllerOf
142
- mockExpect(address(directory), abi.encodeCall(IJBDirectory.controllerOf, (_projectId)), abi.encode(_controller));
143
-
144
- // "mock" payout amount since the currentSurplusOf call is mocked
145
133
  uint224 _payout = 1e17;
146
134
  uint256 _supply = 1e19;
147
135
  uint256 _cashoutAmount = 1e18;
136
+ uint256 _surplus = _supply - _payout;
148
137
 
149
- // surplus call to the terminal
150
- mockExpect(
151
- address(_terminal),
152
- abi.encodeCall(IJBTerminal.currentSurplusOf, (_projectId, _contexts, 18, _currency)),
153
- abi.encode(_supply - _payout)
154
- );
138
+ // Set balance = surplus + payout limit to get the desired surplus after deducting limits.
139
+ // balance - payoutLimit = surplus => balance = surplus + payoutLimit
140
+ // But we want surplus = _supply - _payout = 9.9e18. So set balance = surplus (with zero limits).
141
+ _setBalance(_surplus);
142
+
143
+ JBCurrencyAmount[] memory _emptyLimits = new JBCurrencyAmount[](0);
144
+ _mockSurplusInfra(_packedMetadata, _emptyLimits);
155
145
 
156
146
  // mock JBController totalTokenSupplyWithReservedTokensOf
157
147
  mockExpect(
@@ -163,21 +153,20 @@ contract TestCurrentReclaimableSurplusOf_Local is JBTerminalStoreSetup {
163
153
  IJBTerminal[] memory _terminals = new IJBTerminal[](1);
164
154
  _terminals[0] = _terminal;
165
155
  uint256 reclaimable =
166
- _store.currentReclaimableSurplusOf(_projectId, _cashoutAmount, _terminals, _contexts, 18, _currency);
156
+ _store.currentReclaimableSurplusOf(_projectId, _cashoutAmount, _terminals, new address[](0), 18, _currency);
167
157
 
168
158
  // The above call should be calculating the reclaimable amount as we are here, so they will be congruent.
169
159
  uint256 assumed =
170
- JBCashOuts.cashOutFrom(_supply - _payout, _cashoutAmount, _supply, JBConstants.MAX_CASH_OUT_TAX_RATE / 2);
160
+ JBCashOuts.cashOutFrom(_surplus, _cashoutAmount, _supply, JBConstants.MAX_CASH_OUT_TAX_RATE / 2);
171
161
 
172
162
  assertEq(assumed, reclaimable);
173
163
  }
174
164
 
175
- function test_GivenTokenCountIsEqToTotalSupply() external whenProjectHasBalance {
165
+ function test_GivenTokenCountIsEqToTotalSupply() external {
176
166
  // it will return the rest of the surplus
177
167
 
178
- // setup calldata
179
- JBAccountingContext[] memory _contexts = new JBAccountingContext[](1);
180
- _contexts[0] = JBAccountingContext({token: address(_token), decimals: 18, currency: _currency});
168
+ // Register accounting context.
169
+ _registerContext(JBAccountingContext({token: address(_token), decimals: 18, currency: _currency}));
181
170
 
182
171
  JBRulesetMetadata memory _metadata = JBRulesetMetadata({
183
172
  reservedPercent: 0,
@@ -203,53 +192,34 @@ contract TestCurrentReclaimableSurplusOf_Local is JBTerminalStoreSetup {
203
192
 
204
193
  uint256 _packedMetadata = JBRulesetMetadataResolver.packRulesetMetadata(_metadata);
205
194
 
206
- // JBRulesets return calldata
207
- JBRuleset memory _returnedRuleset = JBRuleset({
208
- cycleNumber: uint48(block.timestamp),
209
- id: uint48(block.timestamp),
210
- basedOnId: 0,
211
- start: uint48(block.timestamp),
212
- duration: uint32(block.timestamp + 1000),
213
- weight: 1e18,
214
- weightCutPercent: 0,
215
- approvalHook: IJBRulesetApprovalHook(address(0)),
216
- metadata: _packedMetadata
217
- });
218
-
219
- // mock call to JBRulesets currentOf
220
- mockExpect(address(rulesets), abi.encodeCall(IJBRulesets.currentOf, (_projectId)), abi.encode(_returnedRuleset));
195
+ // Set balance = tokenCount so surplus = tokenCount (with zero payout limits).
196
+ _setBalance(_tokenCount);
221
197
 
222
- // mock call to JBDirectory controllerOf
223
- mockExpect(address(directory), abi.encodeCall(IJBDirectory.controllerOf, (_projectId)), abi.encode(_controller));
198
+ JBCurrencyAmount[] memory _emptyLimits = new JBCurrencyAmount[](0);
199
+ _mockSurplusInfra(_packedMetadata, _emptyLimits);
224
200
 
225
- // mock call to get cumulative surplus
201
+ // mock JBController totalTokenSupplyWithReservedTokensOf
226
202
  mockExpect(
227
- address(_terminal),
228
- abi.encodeCall(IJBTerminal.currentSurplusOf, (_projectId, _contexts, 18, _currency)),
203
+ address(_controller),
204
+ abi.encodeCall(IJBController.totalTokenSupplyWithReservedTokensOf, (_projectId)),
229
205
  abi.encode(_tokenCount)
230
206
  );
231
207
 
232
- // mock JBController totalTokenSupplyWithReservedTokensOf
233
- bytes memory _totalTokenCall = abi.encodeCall(IJBController.totalTokenSupplyWithReservedTokensOf, (_projectId));
234
- bytes memory _tokenTotal = abi.encode(_tokenCount);
235
- mockExpect(address(_controller), _totalTokenCall, _tokenTotal);
236
-
237
208
  IJBTerminal[] memory _terminals = new IJBTerminal[](1);
238
209
  _terminals[0] = _terminal;
239
210
  uint256 reclaimable =
240
- _store.currentReclaimableSurplusOf(_projectId, _tokenCount, _terminals, _contexts, 18, _currency);
211
+ _store.currentReclaimableSurplusOf(_projectId, _tokenCount, _terminals, new address[](0), 18, _currency);
241
212
 
242
213
  // The tokenCount is equal to the total supply, so the reclaimable amount will be the same as the supply. We
243
214
  // couldn't reclaim more.
244
215
  assertEq(_tokenCount, reclaimable);
245
216
  }
246
217
 
247
- function test_GivenCashOutTaxRateEqZero() external whenProjectHasBalance {
248
- // it will return zero
218
+ function test_GivenCashOutTaxRateEqZero() external {
219
+ // it will return zero (cashOutTaxRate = MAX means no surplus can be reclaimed)
249
220
 
250
- // setup calldata
251
- JBAccountingContext[] memory _contexts = new JBAccountingContext[](1);
252
- _contexts[0] = JBAccountingContext({token: address(_token), decimals: 18, currency: _currency});
221
+ // Register accounting context.
222
+ _registerContext(JBAccountingContext({token: address(_token), decimals: 18, currency: _currency}));
253
223
 
254
224
  JBRulesetMetadata memory _metadata = JBRulesetMetadata({
255
225
  reservedPercent: 0,
@@ -275,55 +245,33 @@ contract TestCurrentReclaimableSurplusOf_Local is JBTerminalStoreSetup {
275
245
 
276
246
  uint256 _packedMetadata = JBRulesetMetadataResolver.packRulesetMetadata(_metadata);
277
247
 
278
- // JBRulesets return calldata
279
- JBRuleset memory _returnedRuleset = JBRuleset({
280
- cycleNumber: uint48(block.timestamp),
281
- id: uint48(block.timestamp),
282
- basedOnId: 0,
283
- start: uint48(block.timestamp),
284
- duration: uint32(block.timestamp + 1000),
285
- weight: 1e18,
286
- weightCutPercent: 0,
287
- approvalHook: IJBRulesetApprovalHook(address(0)),
288
- metadata: _packedMetadata
289
- });
248
+ // Set balance = 1e18 so surplus = 1e18 (with zero payout limits).
249
+ _setBalance(1e18);
290
250
 
291
- // mock call to JBRulesets currentOf
292
- mockExpect(address(rulesets), abi.encodeCall(IJBRulesets.currentOf, (_projectId)), abi.encode(_returnedRuleset));
293
-
294
- // mock call to JBDirectory controllerOf
295
- mockExpect(address(directory), abi.encodeCall(IJBDirectory.controllerOf, (_projectId)), abi.encode(_controller));
296
-
297
- JBCurrencyAmount[] memory _limits = new JBCurrencyAmount[](1);
298
- _limits[0] = JBCurrencyAmount({amount: 0, currency: _currency});
251
+ JBCurrencyAmount[] memory _emptyLimits = new JBCurrencyAmount[](0);
252
+ _mockSurplusInfra(_packedMetadata, _emptyLimits);
299
253
 
300
254
  // mock JBController totalTokenSupplyWithReservedTokensOf
301
- bytes memory _totalTokenCall = abi.encodeCall(IJBController.totalTokenSupplyWithReservedTokensOf, (_projectId));
302
- bytes memory _tokenTotal = abi.encode(1e18);
303
- mockExpect(address(_controller), _totalTokenCall, _tokenTotal);
304
-
305
- // mock current surplus
306
255
  mockExpect(
307
- address(_terminal),
308
- abi.encodeCall(IJBTerminal.currentSurplusOf, (_projectId, _contexts, 18, _currency)),
256
+ address(_controller),
257
+ abi.encodeCall(IJBController.totalTokenSupplyWithReservedTokensOf, (_projectId)),
309
258
  abi.encode(1e18)
310
259
  );
311
260
 
312
261
  IJBTerminal[] memory _terminals = new IJBTerminal[](1);
313
262
  _terminals[0] = _terminal;
314
263
  uint256 reclaimable =
315
- _store.currentReclaimableSurplusOf(_projectId, _tokenCount, _terminals, _contexts, 18, _currency);
264
+ _store.currentReclaimableSurplusOf(_projectId, _tokenCount, _terminals, new address[](0), 18, _currency);
316
265
 
317
266
  // No surplus can be reclaimed.
318
267
  assertEq(0, reclaimable);
319
268
  }
320
269
 
321
- function test_GivenCashOutRateDneqMAX_CASH_OUT_RATE() external whenProjectHasBalance {
270
+ function test_GivenCashOutRateDneqMAX_CASH_OUT_RATE() external {
322
271
  // it will return the calculated proportion
323
272
 
324
- // setup calldata
325
- JBAccountingContext[] memory _contexts = new JBAccountingContext[](1);
326
- _contexts[0] = JBAccountingContext({token: address(_token), decimals: 18, currency: _currency});
273
+ // Register accounting context.
274
+ _registerContext(JBAccountingContext({token: address(_token), decimals: 18, currency: _currency}));
327
275
 
328
276
  JBRulesetMetadata memory _metadata = JBRulesetMetadata({
329
277
  reservedPercent: 0,
@@ -349,43 +297,26 @@ contract TestCurrentReclaimableSurplusOf_Local is JBTerminalStoreSetup {
349
297
 
350
298
  uint256 _packedMetadata = JBRulesetMetadataResolver.packRulesetMetadata(_metadata);
351
299
 
352
- // JBRulesets return calldata
353
- JBRuleset memory _returnedRuleset = JBRuleset({
354
- cycleNumber: uint48(block.timestamp),
355
- id: uint48(block.timestamp),
356
- basedOnId: 0,
357
- start: uint48(block.timestamp),
358
- duration: uint32(block.timestamp + 1000),
359
- weight: 1e18,
360
- weightCutPercent: 0,
361
- approvalHook: IJBRulesetApprovalHook(address(0)),
362
- metadata: _packedMetadata
363
- });
300
+ // Set balance = 1e18 so surplus = 1e18 (with zero payout limits).
301
+ _setBalance(1e18);
364
302
 
365
- // mock call to JBRulesets currentOf
366
- mockExpect(address(rulesets), abi.encodeCall(IJBRulesets.currentOf, (_projectId)), abi.encode(_returnedRuleset));
303
+ JBCurrencyAmount[] memory _emptyLimits = new JBCurrencyAmount[](0);
304
+ _mockSurplusInfra(_packedMetadata, _emptyLimits);
367
305
 
368
- // mock call to JBDirectory controllerOf
369
- mockExpect(address(directory), abi.encodeCall(IJBDirectory.controllerOf, (_projectId)), abi.encode(_controller));
370
-
371
- // mock current surplus
306
+ // mock JBController totalTokenSupplyWithReservedTokensOf
372
307
  mockExpect(
373
- address(_terminal),
374
- abi.encodeCall(IJBTerminal.currentSurplusOf, (_projectId, _contexts, 18, _currency)),
308
+ address(_controller),
309
+ abi.encodeCall(IJBController.totalTokenSupplyWithReservedTokensOf, (_projectId)),
375
310
  abi.encode(1e18)
376
311
  );
377
312
 
378
- // mock JBController totalTokenSupplyWithReservedTokensOf
379
- bytes memory _totalTokenCall = abi.encodeCall(IJBController.totalTokenSupplyWithReservedTokensOf, (_projectId));
380
- bytes memory _tokenTotal = abi.encode(1e18);
381
- mockExpect(address(_controller), _totalTokenCall, _tokenTotal);
382
-
383
313
  uint256 reclaimable;
384
314
  {
385
315
  IJBTerminal[] memory _terminals = new IJBTerminal[](1);
386
316
  _terminals[0] = _terminal;
387
- reclaimable =
388
- _store.currentReclaimableSurplusOf(_projectId, _tokenCount, _terminals, _contexts, 18, _currency);
317
+ reclaimable = _store.currentReclaimableSurplusOf(
318
+ _projectId, _tokenCount, _terminals, new address[](0), 18, _currency
319
+ );
389
320
  }
390
321
 
391
322
  uint256 assumed = mulDiv(
@@ -397,10 +328,11 @@ contract TestCurrentReclaimableSurplusOf_Local is JBTerminalStoreSetup {
397
328
  assertEq(assumed, reclaimable);
398
329
  }
399
330
 
400
- function test_GivenNotOverloaded() external whenProjectHasBalance {
331
+ function test_GivenNotOverloaded() external {
401
332
  // it will get the current ruleset and proceed to return reclaimable as above
402
333
 
403
- // Params
334
+ // This test uses the 4-param overload (projectId, cashOutCount, totalSupply, surplus)
335
+ // which does not go through the store's currentSurplusOf — it takes surplus directly.
404
336
  JBRulesetMetadata memory _metadata = JBRulesetMetadata({
405
337
  reservedPercent: 0,
406
338
  cashOutTaxRate: JBConstants.MAX_CASH_OUT_TAX_RATE / 2,
@@ -425,7 +357,6 @@ contract TestCurrentReclaimableSurplusOf_Local is JBTerminalStoreSetup {
425
357
 
426
358
  uint256 _packedMetadata = JBRulesetMetadataResolver.packRulesetMetadata(_metadata);
427
359
 
428
- // JBRulesets return calldata
429
360
  JBRuleset memory _returnedRuleset = JBRuleset({
430
361
  cycleNumber: uint48(block.timestamp),
431
362
  id: uint48(block.timestamp),
@@ -438,19 +369,17 @@ contract TestCurrentReclaimableSurplusOf_Local is JBTerminalStoreSetup {
438
369
  metadata: _packedMetadata
439
370
  });
440
371
 
441
- // mock call to JBRulesets currentOf
442
372
  mockExpect(address(rulesets), abi.encodeCall(IJBRulesets.currentOf, (_projectId)), abi.encode(_returnedRuleset));
443
373
 
444
374
  uint256 reclaimable = _store.currentReclaimableSurplusOf(_projectId, _tokenCount, 1e18, 1e18);
445
375
  assertEq(1e18, reclaimable);
446
376
  }
447
377
 
448
- function test_GivenTotalReclaimableWithSurplus() external whenProjectHasBalance {
378
+ function test_GivenTotalReclaimableWithSurplus() external {
449
379
  // it will default to all terminals and all accounting contexts and return the reclaimable surplus
450
380
 
451
- // setup calldata
452
- JBAccountingContext[] memory _contexts = new JBAccountingContext[](1);
453
- _contexts[0] = JBAccountingContext({token: address(_token), decimals: 18, currency: _currency});
381
+ // Register accounting context.
382
+ _registerContext(JBAccountingContext({token: address(_token), decimals: 18, currency: _currency}));
454
383
 
455
384
  JBRulesetMetadata memory _metadata = JBRulesetMetadata({
456
385
  reservedPercent: 0,
@@ -476,41 +405,20 @@ contract TestCurrentReclaimableSurplusOf_Local is JBTerminalStoreSetup {
476
405
 
477
406
  uint256 _packedMetadata = JBRulesetMetadataResolver.packRulesetMetadata(_metadata);
478
407
 
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
408
  uint256 _supply = 1e19;
499
409
  uint256 _surplus = 1e18;
500
410
  uint256 _cashoutAmount = 1e18;
501
411
 
502
- // mock JBDirectory terminalsOf to return the terminal
412
+ // Set balance = surplus (with zero payout limits, surplus = balance).
413
+ _setBalance(_surplus);
414
+
415
+ // Mock terminalsOf to return our terminal (for currentTotalReclaimableSurplusOf which uses empty terminals).
503
416
  IJBTerminal[] memory _terminals = new IJBTerminal[](1);
504
417
  _terminals[0] = _terminal;
505
418
  mockExpect(address(directory), abi.encodeCall(IJBDirectory.terminalsOf, (_projectId)), abi.encode(_terminals));
506
419
 
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
- );
420
+ JBCurrencyAmount[] memory _emptyLimits = new JBCurrencyAmount[](0);
421
+ _mockSurplusInfra(_packedMetadata, _emptyLimits);
514
422
 
515
423
  // mock JBController totalTokenSupplyWithReservedTokensOf
516
424
  mockExpect(
@@ -532,44 +440,28 @@ contract TestCurrentReclaimableSurplusOf_Local is JBTerminalStoreSetup {
532
440
  function test_GivenTotalReclaimableWithZeroSurplus() external {
533
441
  // it will return zero when there is no surplus
534
442
 
535
- JBAccountingContext[] memory _emptyContexts = new JBAccountingContext[](0);
443
+ // Register accounting context.
444
+ _registerContext(JBAccountingContext({token: address(_token), decimals: 18, currency: _currency}));
536
445
 
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
- });
446
+ // No balance set — surplus is 0.
549
447
 
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
448
+ // Mock terminalsOf to return our terminal.
554
449
  IJBTerminal[] memory _terminals = new IJBTerminal[](1);
555
450
  _terminals[0] = _terminal;
556
451
  mockExpect(address(directory), abi.encodeCall(IJBDirectory.terminalsOf, (_projectId)), abi.encode(_terminals));
557
452
 
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
- );
453
+ JBCurrencyAmount[] memory _emptyLimits = new JBCurrencyAmount[](0);
454
+ _mockSurplusInfra(0, _emptyLimits);
564
455
 
565
456
  uint256 reclaimable = _store.currentTotalReclaimableSurplusOf(_projectId, _tokenCount, 18, _currency);
566
457
  assertEq(0, reclaimable);
567
458
  }
568
459
 
569
- function test_GivenTotalReclaimableMatchesSixParamOverload() external whenProjectHasBalance {
460
+ function test_GivenTotalReclaimableMatchesSixParamOverload() external {
570
461
  // it will produce the same result as calling the 6-param overload with empty arrays
571
462
 
572
- JBAccountingContext[] memory _emptyContexts = new JBAccountingContext[](0);
463
+ // Register accounting context.
464
+ _registerContext(JBAccountingContext({token: address(_token), decimals: 18, currency: _currency}));
573
465
 
574
466
  JBRulesetMetadata memory _metadata = JBRulesetMetadata({
575
467
  reservedPercent: 0,
@@ -595,38 +487,24 @@ contract TestCurrentReclaimableSurplusOf_Local is JBTerminalStoreSetup {
595
487
 
596
488
  uint256 _packedMetadata = JBRulesetMetadataResolver.packRulesetMetadata(_metadata);
597
489
 
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
490
  uint256 _supply = 1e19;
611
491
  uint256 _surplus = 5e17;
612
492
  uint256 _cashoutAmount = 1e18;
613
493
 
494
+ // Set balance = surplus (with zero payout limits).
495
+ _setBalance(_surplus);
496
+
614
497
  IJBTerminal[] memory _terminals = new IJBTerminal[](1);
615
498
  _terminals[0] = _terminal;
616
499
 
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));
500
+ JBCurrencyAmount[] memory _emptyLimits = new JBCurrencyAmount[](0);
620
501
 
621
- mockExpect(address(directory), abi.encodeCall(IJBDirectory.controllerOf, (_projectId)), abi.encode(_controller));
502
+ // --- Call 1: currentTotalReclaimableSurplusOf (uses empty terminals -> resolves from directory) ---
622
503
 
504
+ // Mock terminalsOf for the total reclaimable call.
623
505
  mockExpect(address(directory), abi.encodeCall(IJBDirectory.terminalsOf, (_projectId)), abi.encode(_terminals));
624
506
 
625
- mockExpect(
626
- address(_terminal),
627
- abi.encodeCall(IJBTerminal.currentSurplusOf, (_projectId, _emptyContexts, 18, _currency)),
628
- abi.encode(_surplus)
629
- );
507
+ _mockSurplusInfra(_packedMetadata, _emptyLimits);
630
508
 
631
509
  mockExpect(
632
510
  address(_controller),
@@ -634,27 +512,23 @@ contract TestCurrentReclaimableSurplusOf_Local is JBTerminalStoreSetup {
634
512
  abi.encode(_supply)
635
513
  );
636
514
 
637
- // Call the new convenience function.
638
515
  uint256 reclaimableDefault = _store.currentTotalReclaimableSurplusOf(_projectId, _cashoutAmount, 18, _currency);
639
516
 
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));
517
+ // --- Call 2: 6-param overload with empty arrays ---
518
+
519
+ // Re-mock for the 6-param call (mocks are consumed by the first call).
643
520
  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
- );
521
+
522
+ _mockSurplusInfra(_packedMetadata, _emptyLimits);
523
+
649
524
  mockExpect(
650
525
  address(_controller),
651
526
  abi.encodeCall(IJBController.totalTokenSupplyWithReservedTokensOf, (_projectId)),
652
527
  abi.encode(_supply)
653
528
  );
654
529
 
655
- // Call the 6-param overload with empty arrays.
656
530
  uint256 reclaimableExplicit = _store.currentReclaimableSurplusOf(
657
- _projectId, _cashoutAmount, new IJBTerminal[](0), new JBAccountingContext[](0), 18, _currency
531
+ _projectId, _cashoutAmount, new IJBTerminal[](0), new address[](0), 18, _currency
658
532
  );
659
533
 
660
534
  assertEq(reclaimableDefault, reclaimableExplicit);