@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,6 +37,21 @@ contract TestCurrentSurplusOf_Local is JBTerminalStoreSetup {
37
37
  super.terminalStoreSetup();
38
38
  }
39
39
 
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 call currentSurplusOf with the new multi-terminal signature.
49
+ function _currentSurplusOf(uint256 decimals, uint256 currency) internal view returns (uint256) {
50
+ IJBTerminal[] memory terminals = new IJBTerminal[](1);
51
+ terminals[0] = _terminal;
52
+ return _store.currentSurplusOf(_projectId, terminals, new address[](0), decimals, currency);
53
+ }
54
+
40
55
  modifier whenProjectHasBalance() {
41
56
  // Find the storage slot
42
57
  bytes32 balanceOfSlot = keccak256(abi.encode(address(_terminal), uint256(0)));
@@ -62,6 +77,7 @@ contract TestCurrentSurplusOf_Local is JBTerminalStoreSetup {
62
77
  JBAccountingContext[] memory _contexts = new JBAccountingContext[](1);
63
78
 
64
79
  _contexts[0] = JBAccountingContext({token: address(_token), decimals: 18, currency: _currency});
80
+ _registerContext(_contexts[0]);
65
81
 
66
82
  // JBRulesets calldata
67
83
  JBRuleset memory _returnedRuleset = JBRuleset({
@@ -101,7 +117,7 @@ contract TestCurrentSurplusOf_Local is JBTerminalStoreSetup {
101
117
  bytes memory _payoutLimitsReturn = abi.encode(_payoutLimits);
102
118
  mockExpect(address(_accessLimits), _payoutLimitsCall, _payoutLimitsReturn);
103
119
 
104
- uint256 currentSurplus = _store.currentSurplusOf(address(_terminal), _projectId, _contexts, 6, _currency);
120
+ uint256 currentSurplus = _currentSurplusOf(6, _currency);
105
121
 
106
122
  // assert correct calcs
107
123
  uint256 expectedSurplus = (1e18 - 1e17) / 10 ** (18 - 6);
@@ -116,6 +132,7 @@ contract TestCurrentSurplusOf_Local is JBTerminalStoreSetup {
116
132
  JBAccountingContext[] memory _contexts = new JBAccountingContext[](1);
117
133
 
118
134
  _contexts[0] = JBAccountingContext({token: address(_token), decimals: 18, currency: _currency});
135
+ _registerContext(_contexts[0]);
119
136
 
120
137
  // JBRulesets calldata
121
138
  JBRuleset memory _returnedRuleset = JBRuleset({
@@ -161,7 +178,7 @@ contract TestCurrentSurplusOf_Local is JBTerminalStoreSetup {
161
178
  bytes memory _pricePerReturn = abi.encode(uint256(1));
162
179
  mockExpect(address(prices), _pricePerCall, _pricePerReturn);
163
180
 
164
- uint256 currentSurplus = _store.currentSurplusOf(address(_terminal), _projectId, _contexts, 6, _nativeCurrency);
181
+ uint256 currentSurplus = _currentSurplusOf(6, _nativeCurrency);
165
182
 
166
183
  // assert correct calcs
167
184
  uint256 expectedSurplus = ((1e18 - 1e17) * 1e18) / 10 ** (18 - 6);
@@ -174,6 +191,7 @@ contract TestCurrentSurplusOf_Local is JBTerminalStoreSetup {
174
191
  JBAccountingContext[] memory _contexts = new JBAccountingContext[](1);
175
192
 
176
193
  _contexts[0] = JBAccountingContext({token: address(_token), decimals: 18, currency: _currency});
194
+ _registerContext(_contexts[0]);
177
195
 
178
196
  // JBRulesets calldata
179
197
  JBRuleset memory _returnedRuleset = JBRuleset({
@@ -213,7 +231,7 @@ contract TestCurrentSurplusOf_Local is JBTerminalStoreSetup {
213
231
  bytes memory _payoutLimitsReturn = abi.encode(_payoutLimits);
214
232
  mockExpect(address(_accessLimits), _payoutLimitsCall, _payoutLimitsReturn);
215
233
 
216
- uint256 currentSurplus = _store.currentSurplusOf(address(_terminal), _projectId, _contexts, 18, _currency);
234
+ uint256 currentSurplus = _currentSurplusOf(18, _currency);
217
235
 
218
236
  // assert correct calcs
219
237
  uint256 expectedSurplus = 1e18 - 1e17;
@@ -225,6 +243,7 @@ contract TestCurrentSurplusOf_Local is JBTerminalStoreSetup {
225
243
  JBAccountingContext[] memory _contexts = new JBAccountingContext[](1);
226
244
 
227
245
  _contexts[0] = JBAccountingContext({token: address(_token), decimals: 18, currency: _currency});
246
+ _registerContext(_contexts[0]);
228
247
 
229
248
  // JBRulesets calldata
230
249
  JBRuleset memory _returnedRuleset = JBRuleset({
@@ -270,7 +289,7 @@ contract TestCurrentSurplusOf_Local is JBTerminalStoreSetup {
270
289
  bytes memory _pricePerReturn = abi.encode(uint256(1));
271
290
  mockExpect(address(prices), _pricePerCall, _pricePerReturn);
272
291
 
273
- uint256 currentSurplus = _store.currentSurplusOf(address(_terminal), _projectId, _contexts, 18, _nativeCurrency);
292
+ uint256 currentSurplus = _currentSurplusOf(18, _nativeCurrency);
274
293
 
275
294
  // assert correct calcs
276
295
  uint256 expectedSurplus = (1e18 - 1e17) * 1e18;
@@ -283,6 +302,7 @@ contract TestCurrentSurplusOf_Local is JBTerminalStoreSetup {
283
302
  JBAccountingContext[] memory _contexts = new JBAccountingContext[](1);
284
303
 
285
304
  _contexts[0] = JBAccountingContext({token: address(_token), decimals: 18, currency: _currency});
305
+ _registerContext(_contexts[0]);
286
306
 
287
307
  // JBRulesets calldata
288
308
  JBRuleset memory _returnedRuleset = JBRuleset({
@@ -322,7 +342,7 @@ contract TestCurrentSurplusOf_Local is JBTerminalStoreSetup {
322
342
  bytes memory _payoutLimitsReturn = abi.encode(_payoutLimits);
323
343
  mockExpect(address(_accessLimits), _payoutLimitsCall, _payoutLimitsReturn);
324
344
 
325
- uint256 currentSurplus = _store.currentSurplusOf(address(_terminal), _projectId, _contexts, 18, _currency);
345
+ uint256 currentSurplus = _currentSurplusOf(18, _currency);
326
346
 
327
347
  // assert correct calcs
328
348
  uint256 expectedSurplus = (1e18 - 1e17);
@@ -335,6 +355,7 @@ contract TestCurrentSurplusOf_Local is JBTerminalStoreSetup {
335
355
  JBAccountingContext[] memory _contexts = new JBAccountingContext[](1);
336
356
 
337
357
  _contexts[0] = JBAccountingContext({token: address(_token), decimals: 18, currency: _nativeCurrency});
358
+ _registerContext(_contexts[0]);
338
359
 
339
360
  // JBRulesets calldata
340
361
  JBRuleset memory _returnedRuleset = JBRuleset({
@@ -380,7 +401,7 @@ contract TestCurrentSurplusOf_Local is JBTerminalStoreSetup {
380
401
  bytes memory _pricePerReturn = abi.encode(uint256(1e18));
381
402
  mockExpect(address(prices), _pricePerCall, _pricePerReturn);
382
403
 
383
- uint256 currentSurplus = _store.currentSurplusOf(address(_terminal), _projectId, _contexts, 18, _nativeCurrency);
404
+ uint256 currentSurplus = _currentSurplusOf(18, _nativeCurrency);
384
405
 
385
406
  // assert correct calcs
386
407
  uint256 expectedSurplus = 1e18 - 1e17;
@@ -393,6 +414,7 @@ contract TestCurrentSurplusOf_Local is JBTerminalStoreSetup {
393
414
  JBAccountingContext[] memory _contexts = new JBAccountingContext[](1);
394
415
 
395
416
  _contexts[0] = JBAccountingContext({token: address(_token), decimals: 18, currency: _currency});
417
+ _registerContext(_contexts[0]);
396
418
 
397
419
  // JBRulesets calldata
398
420
  JBRuleset memory _returnedRuleset = JBRuleset({
@@ -432,7 +454,7 @@ contract TestCurrentSurplusOf_Local is JBTerminalStoreSetup {
432
454
  bytes memory _payoutLimitsReturn = abi.encode(_payoutLimits);
433
455
  mockExpect(address(_accessLimits), _payoutLimitsCall, _payoutLimitsReturn);
434
456
 
435
- uint256 currentSurplus = _store.currentSurplusOf(address(_terminal), _projectId, _contexts, 18, _currency);
457
+ uint256 currentSurplus = _currentSurplusOf(18, _currency);
436
458
 
437
459
  // assert correct calcs
438
460
  uint256 expectedSurplus = 0;
@@ -1,50 +1,107 @@
1
1
  // SPDX-License-Identifier: MIT
2
2
  pragma solidity 0.8.26;
3
3
 
4
+ import {IJBController} from "../../../../src/interfaces/IJBController.sol";
4
5
  import {IJBDirectory} from "../../../../src/interfaces/IJBDirectory.sol";
6
+ import {IJBFundAccessLimits} from "../../../../src/interfaces/IJBFundAccessLimits.sol";
7
+ import {IJBRulesetApprovalHook} from "../../../../src/interfaces/IJBRulesetApprovalHook.sol";
8
+ import {IJBRulesets} from "../../../../src/interfaces/IJBRulesets.sol";
5
9
  import {IJBTerminal} from "../../../../src/interfaces/IJBTerminal.sol";
6
10
  import {JBAccountingContext} from "../../../../src/structs/JBAccountingContext.sol";
11
+ import {JBCurrencyAmount} from "../../../../src/structs/JBCurrencyAmount.sol";
12
+ import {JBRuleset} from "../../../../src/structs/JBRuleset.sol";
7
13
  import {JBTerminalStoreSetup} from "./JBTerminalStoreSetup.sol";
8
14
 
9
15
  contract TestCurrentTotalSurplusOf_Local is JBTerminalStoreSetup {
10
16
  uint256 _projectId = 1;
11
17
  uint256 _decimals = 18;
12
- uint256 _currency = uint32(uint160(makeAddr("token")));
13
18
 
14
19
  // Mocks
15
20
  IJBTerminal _terminal1 = IJBTerminal(makeAddr("terminal1"));
16
21
  IJBTerminal _terminal2 = IJBTerminal(makeAddr("terminal2"));
22
+ address _token = makeAddr("token");
23
+ IJBController _controller = IJBController(makeAddr("controller"));
24
+ IJBFundAccessLimits _accessLimits = IJBFundAccessLimits(makeAddr("funds"));
25
+
26
+ // forge-lint: disable-next-line(unsafe-typecast)
27
+ uint32 _currency = uint32(uint160(_token));
17
28
 
18
29
  function setUp() public {
19
30
  super.terminalStoreSetup();
20
31
  }
21
32
 
33
+ /// @notice Helper to set balance for a terminal/project/token via vm.store.
34
+ function _setBalance(address terminal, uint256 projectId, address token, uint256 balance) internal {
35
+ bytes32 balanceOfSlot = keccak256(abi.encode(terminal, uint256(0)));
36
+ bytes32 projectSlot = keccak256(abi.encode(projectId, uint256(balanceOfSlot)));
37
+ bytes32 slot = keccak256(abi.encode(token, uint256(projectSlot)));
38
+ vm.store(address(_store), slot, bytes32(balance));
39
+ }
40
+
41
+ /// @notice Helper to register an accounting context with the store (pranks as the terminal).
42
+ function _registerContext(address terminal, JBAccountingContext memory ctx) internal {
43
+ JBAccountingContext[] memory ctxs = new JBAccountingContext[](1);
44
+ ctxs[0] = ctx;
45
+ vm.prank(terminal);
46
+ _store.recordAccountingContextOf(_projectId, ctxs);
47
+ }
48
+
22
49
  function test_WhenTerminalsAreConfiguredInJBDirectory() external {
23
50
  // it will return the cumulative surplus
24
51
 
25
- // return data for mock call
52
+ // Set up terminals in directory
26
53
  IJBTerminal[] memory _terminals = new IJBTerminal[](2);
27
54
  _terminals[0] = _terminal1;
28
55
  _terminals[1] = _terminal2;
29
56
 
30
- // mock call to JBDirectory terminalsOf
31
- bytes memory _directoryCall = abi.encodeCall(IJBDirectory.terminalsOf, (_projectId));
32
- bytes memory _returned = abi.encode(_terminals);
33
- mockExpect(address(directory), _directoryCall, _returned);
57
+ mockExpect(address(directory), abi.encodeCall(IJBDirectory.terminalsOf, (_projectId)), abi.encode(_terminals));
34
58
 
35
- // mock call to first terminal currentSurplusOf
36
- bytes memory _terminal1Call = abi.encodeCall(
37
- IJBTerminal.currentSurplusOf, (_projectId, new JBAccountingContext[](0), _decimals, _currency)
59
+ // Set up accounting contexts for each terminal
60
+ JBAccountingContext memory ctx = JBAccountingContext({token: _token, decimals: 18, currency: _currency});
61
+ _registerContext(address(_terminal1), ctx);
62
+ _registerContext(address(_terminal2), ctx);
63
+
64
+ // Set balances: terminal1 = 1e18, terminal2 = 2e18
65
+ _setBalance(address(_terminal1), _projectId, _token, 1e18);
66
+ _setBalance(address(_terminal2), _projectId, _token, 2e18);
67
+
68
+ // Mock ruleset
69
+ JBRuleset memory _returnedRuleset = JBRuleset({
70
+ cycleNumber: uint48(block.timestamp),
71
+ id: uint48(block.timestamp),
72
+ basedOnId: 0,
73
+ start: uint48(block.timestamp),
74
+ duration: uint32(block.timestamp + 1000),
75
+ weight: 1e18,
76
+ weightCutPercent: 0,
77
+ approvalHook: IJBRulesetApprovalHook(address(0)),
78
+ metadata: 0
79
+ });
80
+ mockExpect(address(rulesets), abi.encodeCall(IJBRulesets.currentOf, (_projectId)), abi.encode(_returnedRuleset));
81
+
82
+ // Mock controller and fund access limits (zero payout limits = all balance is surplus)
83
+ mockExpect(address(directory), abi.encodeCall(IJBDirectory.controllerOf, (_projectId)), abi.encode(_controller));
84
+ mockExpect(
85
+ address(_controller), abi.encodeCall(IJBController.FUND_ACCESS_LIMITS, ()), abi.encode(_accessLimits)
38
86
  );
39
- bytes memory _terminal1Return = abi.encode(1e18);
40
- mockExpect(address(_terminal1), _terminal1Call, _terminal1Return);
41
87
 
42
- // mock call to first terminal currentSurplusOf
43
- bytes memory _terminal2Call = abi.encodeCall(
44
- IJBTerminal.currentSurplusOf, (_projectId, new JBAccountingContext[](0), _decimals, _currency)
88
+ JBCurrencyAmount[] memory _emptyLimits = new JBCurrencyAmount[](0);
89
+ // Mock payoutLimitsOf for terminal1
90
+ mockExpect(
91
+ address(_accessLimits),
92
+ abi.encodeCall(
93
+ IJBFundAccessLimits.payoutLimitsOf, (_projectId, block.timestamp, address(_terminal1), _token)
94
+ ),
95
+ abi.encode(_emptyLimits)
96
+ );
97
+ // Mock payoutLimitsOf for terminal2
98
+ mockExpect(
99
+ address(_accessLimits),
100
+ abi.encodeCall(
101
+ IJBFundAccessLimits.payoutLimitsOf, (_projectId, block.timestamp, address(_terminal2), _token)
102
+ ),
103
+ abi.encode(_emptyLimits)
45
104
  );
46
- bytes memory _terminal2Return = abi.encode(2e18);
47
- mockExpect(address(_terminal2), _terminal2Call, _terminal2Return);
48
105
 
49
106
  uint256 sum = _store.currentTotalSurplusOf(_projectId, _decimals, _currency);
50
107
  assertEq(3e18, sum);
@@ -53,13 +110,24 @@ contract TestCurrentTotalSurplusOf_Local is JBTerminalStoreSetup {
53
110
  function test_WhenTerminalsAreNotConfiguredInJBDirectory() external {
54
111
  // it will return zero
55
112
 
56
- // return data for mock call
57
113
  IJBTerminal[] memory _terminals = new IJBTerminal[](0);
58
114
 
59
115
  // mock call to JBDirectory terminalsOf
60
- bytes memory _directoryCall = abi.encodeCall(IJBDirectory.terminalsOf, (_projectId));
61
- bytes memory _returned = abi.encode(_terminals);
62
- mockExpect(address(directory), _directoryCall, _returned);
116
+ mockExpect(address(directory), abi.encodeCall(IJBDirectory.terminalsOf, (_projectId)), abi.encode(_terminals));
117
+
118
+ // Mock ruleset (needed by currentSurplusOf)
119
+ JBRuleset memory _returnedRuleset = JBRuleset({
120
+ cycleNumber: uint48(block.timestamp),
121
+ id: uint48(block.timestamp),
122
+ basedOnId: 0,
123
+ start: uint48(block.timestamp),
124
+ duration: uint32(block.timestamp + 1000),
125
+ weight: 1e18,
126
+ weightCutPercent: 0,
127
+ approvalHook: IJBRulesetApprovalHook(address(0)),
128
+ metadata: 0
129
+ });
130
+ mockExpect(address(rulesets), abi.encodeCall(IJBRulesets.currentOf, (_projectId)), abi.encode(_returnedRuleset));
63
131
 
64
132
  uint256 sum = _store.currentTotalSurplusOf(_projectId, _decimals, _currency);
65
133
  assertEq(0, sum);
@@ -16,7 +16,6 @@ import {JBRulesetMetadataResolver} from "../../../../src/libraries/JBRulesetMeta
16
16
  import {JBAccountingContext} from "../../../../src/structs/JBAccountingContext.sol";
17
17
  import {JBBeforeCashOutRecordedContext} from "../../../../src/structs/JBBeforeCashOutRecordedContext.sol";
18
18
  import {JBCashOutHookSpecification} from "../../../../src/structs/JBCashOutHookSpecification.sol";
19
- import {JBCurrencyAmount} from "../../../../src/structs/JBCurrencyAmount.sol";
20
19
  import {JBRuleset} from "../../../../src/structs/JBRuleset.sol";
21
20
  import {JBRulesetMetadata} from "../../../../src/structs/JBRulesetMetadata.sol";
22
21
  import {JBTokenAmount} from "../../../../src/structs/JBTokenAmount.sol";
@@ -42,6 +41,12 @@ contract TestPreviewCashOutFor_Local is JBTerminalStoreSetup {
42
41
 
43
42
  function setUp() public {
44
43
  super.terminalStoreSetup();
44
+
45
+ // Register accounting context so the store can look up decimals/currency for the token.
46
+ // address(this) acts as the terminal for both preview and record calls.
47
+ JBAccountingContext[] memory _ctxs = new JBAccountingContext[](1);
48
+ _ctxs[0] = JBAccountingContext({token: address(_token), decimals: 18, currency: _currency});
49
+ _store.recordAccountingContextOf(_projectId, _ctxs);
45
50
  }
46
51
 
47
52
  function _setBalance(address terminal, uint256 balance) internal {
@@ -60,16 +65,12 @@ contract TestPreviewCashOutFor_Local is JBTerminalStoreSetup {
60
65
 
61
66
  mockExpect(
62
67
  address(_terminal1),
63
- abi.encodeCall(
64
- IJBTerminal.currentSurplusOf, (_projectId, new JBAccountingContext[](0), _decimals, _currency)
65
- ),
68
+ abi.encodeCall(IJBTerminal.currentSurplusOf, (_projectId, new address[](0), _decimals, _currency)),
66
69
  abi.encode(1e18)
67
70
  );
68
71
  mockExpect(
69
72
  address(_terminal2),
70
- abi.encodeCall(
71
- IJBTerminal.currentSurplusOf, (_projectId, new JBAccountingContext[](0), _decimals, _currency)
72
- ),
73
+ abi.encodeCall(IJBTerminal.currentSurplusOf, (_projectId, new address[](0), _decimals, _currency)),
73
74
  abi.encode(2e18)
74
75
  );
75
76
  }
@@ -119,9 +120,8 @@ contract TestPreviewCashOutFor_Local is JBTerminalStoreSetup {
119
120
  uint256 _cashOutCount = 10e18;
120
121
 
121
122
  JBAccountingContext memory _accountingContext =
122
- JBAccountingContext({token: address(_token), decimals: uint8(_decimals), currency: _currency});
123
-
124
- JBAccountingContext[] memory _balanceContexts = new JBAccountingContext[](0);
123
+ // forge-lint: disable-next-line(unsafe-typecast)
124
+ JBAccountingContext({token: address(_token), decimals: uint8(_decimals), currency: _currency});
125
125
 
126
126
  // Mock for preview call
127
127
  mockExpect(address(rulesets), abi.encodeCall(IJBRulesets.currentOf, (_projectId)), abi.encode(_returnedRuleset));
@@ -133,11 +133,11 @@ contract TestPreviewCashOutFor_Local is JBTerminalStoreSetup {
133
133
  );
134
134
 
135
135
  (, uint256 previewReclaimAmount, uint256 previewTaxRate, JBCashOutHookSpecification[] memory previewSpecs) = _store.previewCashOutFrom({
136
+ terminal: address(this),
136
137
  holder: address(this),
137
138
  projectId: _projectId,
138
139
  cashOutCount: _cashOutCount,
139
- accountingContext: _accountingContext,
140
- balanceAccountingContexts: _balanceContexts,
140
+ tokenToReclaim: _accountingContext.token,
141
141
  beneficiaryIsFeeless: false,
142
142
  metadata: ""
143
143
  });
@@ -156,8 +156,7 @@ contract TestPreviewCashOutFor_Local is JBTerminalStoreSetup {
156
156
  holder: address(this),
157
157
  projectId: _projectId,
158
158
  cashOutCount: _cashOutCount,
159
- accountingContext: _accountingContext,
160
- balanceAccountingContexts: _balanceContexts,
159
+ tokenToReclaim: _accountingContext.token,
161
160
  beneficiaryIsFeeless: false,
162
161
  metadata: ""
163
162
  });
@@ -208,7 +207,8 @@ contract TestPreviewCashOutFor_Local is JBTerminalStoreSetup {
208
207
  });
209
208
 
210
209
  JBAccountingContext memory _accountingContext =
211
- JBAccountingContext({token: address(_token), decimals: uint8(_decimals), currency: _currency});
210
+ // forge-lint: disable-next-line(unsafe-typecast)
211
+ JBAccountingContext({token: address(_token), decimals: uint8(_decimals), currency: _currency});
212
212
 
213
213
  mockExpect(address(rulesets), abi.encodeCall(IJBRulesets.currentOf, (_projectId)), abi.encode(_returnedRuleset));
214
214
  mockExpect(address(directory), abi.encodeCall(IJBDirectory.controllerOf, (_projectId)), abi.encode(_controller));
@@ -221,11 +221,11 @@ contract TestPreviewCashOutFor_Local is JBTerminalStoreSetup {
221
221
  uint256 balanceBefore = _store.balanceOf(address(this), _projectId, address(_token));
222
222
 
223
223
  _store.previewCashOutFrom({
224
+ terminal: address(this),
224
225
  holder: address(this),
225
226
  projectId: _projectId,
226
227
  cashOutCount: 5e18,
227
- accountingContext: _accountingContext,
228
- balanceAccountingContexts: new JBAccountingContext[](0),
228
+ tokenToReclaim: _accountingContext.token,
229
229
  beneficiaryIsFeeless: false,
230
230
  metadata: ""
231
231
  });
@@ -275,7 +275,8 @@ contract TestPreviewCashOutFor_Local is JBTerminalStoreSetup {
275
275
  });
276
276
 
277
277
  JBAccountingContext memory _accountingContext =
278
- JBAccountingContext({token: address(_token), decimals: uint8(_decimals), currency: _currency});
278
+ // forge-lint: disable-next-line(unsafe-typecast)
279
+ JBAccountingContext({token: address(_token), decimals: uint8(_decimals), currency: _currency});
279
280
 
280
281
  mockExpect(address(rulesets), abi.encodeCall(IJBRulesets.currentOf, (_projectId)), abi.encode(_returnedRuleset));
281
282
  mockExpect(address(directory), abi.encodeCall(IJBDirectory.controllerOf, (_projectId)), abi.encode(_controller));
@@ -293,11 +294,11 @@ contract TestPreviewCashOutFor_Local is JBTerminalStoreSetup {
293
294
  )
294
295
  );
295
296
  _store.previewCashOutFrom({
297
+ terminal: address(this),
296
298
  holder: address(this),
297
299
  projectId: _projectId,
298
300
  cashOutCount: _excessiveCashOutCount,
299
- accountingContext: _accountingContext,
300
- balanceAccountingContexts: new JBAccountingContext[](0),
301
+ tokenToReclaim: _accountingContext.token,
301
302
  beneficiaryIsFeeless: false,
302
303
  metadata: ""
303
304
  });
@@ -343,7 +344,8 @@ contract TestPreviewCashOutFor_Local is JBTerminalStoreSetup {
343
344
  });
344
345
 
345
346
  JBAccountingContext memory _accountingContext =
346
- JBAccountingContext({token: address(_token), decimals: uint8(_decimals), currency: _currency});
347
+ // forge-lint: disable-next-line(unsafe-typecast)
348
+ JBAccountingContext({token: address(_token), decimals: uint8(_decimals), currency: _currency});
347
349
 
348
350
  IJBTerminal[] memory _terminals = new IJBTerminal[](1);
349
351
  _terminals[0] = _terminal1;
@@ -352,9 +354,7 @@ contract TestPreviewCashOutFor_Local is JBTerminalStoreSetup {
352
354
  mockExpect(address(directory), abi.encodeCall(IJBDirectory.terminalsOf, (_projectId)), abi.encode(_terminals));
353
355
  mockExpect(
354
356
  address(_terminal1),
355
- abi.encodeCall(
356
- IJBTerminal.currentSurplusOf, (_projectId, new JBAccountingContext[](0), _decimals, _currency)
357
- ),
357
+ abi.encodeCall(IJBTerminal.currentSurplusOf, (_projectId, new address[](0), _decimals, _currency)),
358
358
  abi.encode(0)
359
359
  );
360
360
  mockExpect(address(directory), abi.encodeCall(IJBDirectory.controllerOf, (_projectId)), abi.encode(_controller));
@@ -365,11 +365,11 @@ contract TestPreviewCashOutFor_Local is JBTerminalStoreSetup {
365
365
  );
366
366
 
367
367
  (, uint256 reclaimAmount,,) = _store.previewCashOutFrom({
368
+ terminal: address(this),
368
369
  holder: address(this),
369
370
  projectId: _projectId,
370
371
  cashOutCount: 5e18,
371
- accountingContext: _accountingContext,
372
- balanceAccountingContexts: new JBAccountingContext[](0),
372
+ tokenToReclaim: _accountingContext.token,
373
373
  beneficiaryIsFeeless: false,
374
374
  metadata: ""
375
375
  });
@@ -418,7 +418,8 @@ contract TestPreviewCashOutFor_Local is JBTerminalStoreSetup {
418
418
  });
419
419
 
420
420
  JBAccountingContext memory _accountingContext =
421
- JBAccountingContext({token: address(_token), decimals: uint8(_decimals), currency: _currency});
421
+ // forge-lint: disable-next-line(unsafe-typecast)
422
+ JBAccountingContext({token: address(_token), decimals: uint8(_decimals), currency: _currency});
422
423
 
423
424
  JBBeforeCashOutRecordedContext memory _context = JBBeforeCashOutRecordedContext({
424
425
  terminal: address(this),
@@ -456,11 +457,11 @@ contract TestPreviewCashOutFor_Local is JBTerminalStoreSetup {
456
457
  );
457
458
 
458
459
  (, uint256 reclaimAmount, uint256 cashOutTaxRate, JBCashOutHookSpecification[] memory hookSpecifications) = _store.previewCashOutFrom({
460
+ terminal: address(this),
459
461
  holder: address(this),
460
462
  projectId: _projectId,
461
463
  cashOutCount: 10e18,
462
- accountingContext: _accountingContext,
463
- balanceAccountingContexts: new JBAccountingContext[](0),
464
+ tokenToReclaim: _accountingContext.token,
464
465
  beneficiaryIsFeeless: false,
465
466
  metadata: ""
466
467
  });
@@ -60,9 +60,13 @@ contract TestPreviewPayFrom_Local is JBTerminalStoreSetup {
60
60
  mockExpect(address(rulesets), abi.encodeCall(IJBRulesets.currentOf, (_projectId)), abi.encode(_returnedRuleset));
61
61
 
62
62
  vm.expectRevert(abi.encodeWithSelector(JBTerminalStore.JBTerminalStore_RulesetNotFound.selector, _projectId));
63
- vm.prank(_terminal);
64
63
  _store.previewPayFrom({
65
- payer: address(this), amount: _tokenAmount, projectId: _projectId, beneficiary: address(this), metadata: ""
64
+ terminal: _terminal,
65
+ payer: address(this),
66
+ amount: _tokenAmount,
67
+ projectId: _projectId,
68
+ beneficiary: address(this),
69
+ metadata: ""
66
70
  });
67
71
  }
68
72
 
@@ -112,9 +116,13 @@ contract TestPreviewPayFrom_Local is JBTerminalStoreSetup {
112
116
  mockExpect(address(rulesets), abi.encodeCall(IJBRulesets.currentOf, (_projectId)), abi.encode(_returnedRuleset));
113
117
 
114
118
  vm.expectRevert(JBTerminalStore.JBTerminalStore_RulesetPaymentPaused.selector);
115
- vm.prank(_terminal);
116
119
  _store.previewPayFrom({
117
- payer: address(this), amount: _tokenAmount, projectId: _projectId, beneficiary: address(this), metadata: ""
120
+ terminal: _terminal,
121
+ payer: address(this),
122
+ amount: _tokenAmount,
123
+ projectId: _projectId,
124
+ beneficiary: address(this),
125
+ metadata: ""
118
126
  });
119
127
  }
120
128
 
@@ -165,7 +173,12 @@ contract TestPreviewPayFrom_Local is JBTerminalStoreSetup {
165
173
  mockExpect(address(rulesets), abi.encodeCall(IJBRulesets.currentOf, (_projectId)), abi.encode(_returnedRuleset));
166
174
 
167
175
  (, uint256 previewTokenCount, JBPayHookSpecification[] memory previewSpecs) = _store.previewPayFrom({
168
- payer: address(this), amount: _tokenAmount, projectId: _projectId, beneficiary: address(this), metadata: ""
176
+ terminal: _terminal,
177
+ payer: address(this),
178
+ amount: _tokenAmount,
179
+ projectId: _projectId,
180
+ beneficiary: address(this),
181
+ metadata: ""
169
182
  });
170
183
 
171
184
  // Mock for record call
@@ -225,10 +238,9 @@ contract TestPreviewPayFrom_Local is JBTerminalStoreSetup {
225
238
  JBPayHookSpecification[] memory _spec = new JBPayHookSpecification[](1);
226
239
  _spec[0] = JBPayHookSpecification({hook: _payHook, noop: false, amount: _defaultValue / 2, metadata: ""});
227
240
 
228
- // The data hook context will use the terminal address passed to preview / msg.sender for record.
229
- // Since we call both from address(this), they match.
241
+ // The data hook context uses the explicit terminal parameter for preview / msg.sender for record.
230
242
  JBBeforePayRecordedContext memory _context = JBBeforePayRecordedContext({
231
- terminal: address(this),
243
+ terminal: _terminal,
232
244
  payer: address(this),
233
245
  amount: _tokenAmount,
234
246
  projectId: _projectId,
@@ -248,10 +260,15 @@ contract TestPreviewPayFrom_Local is JBTerminalStoreSetup {
248
260
  );
249
261
 
250
262
  (, uint256 previewTokenCount, JBPayHookSpecification[] memory previewSpecs) = _store.previewPayFrom({
251
- payer: address(this), amount: _tokenAmount, projectId: _projectId, beneficiary: address(this), metadata: ""
263
+ terminal: _terminal,
264
+ payer: address(this),
265
+ amount: _tokenAmount,
266
+ projectId: _projectId,
267
+ beneficiary: address(this),
268
+ metadata: ""
252
269
  });
253
270
 
254
- // Mock for record call
271
+ // Mock for record call — recordPaymentFrom uses msg.sender as terminal, so prank _terminal.
255
272
  mockExpect(address(rulesets), abi.encodeCall(IJBRulesets.currentOf, (_projectId)), abi.encode(_returnedRuleset));
256
273
  mockExpect(
257
274
  address(_dataHook),
@@ -259,6 +276,7 @@ contract TestPreviewPayFrom_Local is JBTerminalStoreSetup {
259
276
  abi.encode(1e18 / 2, _spec)
260
277
  );
261
278
 
279
+ vm.prank(_terminal);
262
280
  (, uint256 recordTokenCount, JBPayHookSpecification[] memory recordSpecs) = _store.recordPaymentFrom({
263
281
  payer: address(this), amount: _tokenAmount, projectId: _projectId, beneficiary: address(this), metadata: ""
264
282
  });
@@ -315,9 +333,13 @@ contract TestPreviewPayFrom_Local is JBTerminalStoreSetup {
315
333
 
316
334
  uint256 balanceBefore = _store.balanceOf(_terminal, _projectId, address(_token));
317
335
 
318
- vm.prank(_terminal);
319
336
  _store.previewPayFrom({
320
- payer: address(this), amount: _tokenAmount, projectId: _projectId, beneficiary: address(this), metadata: ""
337
+ terminal: _terminal,
338
+ payer: address(this),
339
+ amount: _tokenAmount,
340
+ projectId: _projectId,
341
+ beneficiary: address(this),
342
+ metadata: ""
321
343
  });
322
344
 
323
345
  uint256 balanceAfter = _store.balanceOf(_terminal, _projectId, address(_token));
@@ -379,9 +401,13 @@ contract TestPreviewPayFrom_Local is JBTerminalStoreSetup {
379
401
 
380
402
  uint256 expectedCount = mulDiv(_defaultValue, 1e18, 2e18);
381
403
 
382
- vm.prank(_terminal);
383
404
  (, uint256 tokenCount,) = _store.previewPayFrom({
384
- payer: address(this), amount: _tokenAmount, projectId: _projectId, beneficiary: address(this), metadata: ""
405
+ terminal: _terminal,
406
+ payer: address(this),
407
+ amount: _tokenAmount,
408
+ projectId: _projectId,
409
+ beneficiary: address(this),
410
+ metadata: ""
385
411
  });
386
412
 
387
413
  assertEq(tokenCount, expectedCount);
@@ -450,9 +476,13 @@ contract TestPreviewPayFrom_Local is JBTerminalStoreSetup {
450
476
  abi.encode(1e18 / 2, _spec)
451
477
  );
452
478
 
453
- vm.prank(_terminal);
454
479
  (, uint256 tokenCount, JBPayHookSpecification[] memory hookSpecifications) = _store.previewPayFrom({
455
- payer: address(this), amount: _tokenAmount, projectId: _projectId, beneficiary: address(this), metadata: ""
480
+ terminal: _terminal,
481
+ payer: address(this),
482
+ amount: _tokenAmount,
483
+ projectId: _projectId,
484
+ beneficiary: address(this),
485
+ metadata: ""
456
486
  });
457
487
 
458
488
  assertEq(tokenCount, 1e18 / 2);