@bananapus/core-v6 0.0.18 → 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.
@@ -0,0 +1,383 @@
1
+ // SPDX-License-Identifier: MIT
2
+ pragma solidity 0.8.26;
3
+
4
+ import {JBTerminalStore} from "../../../../src/JBTerminalStore.sol";
5
+ import {IJBCashOutHook} from "../../../../src/interfaces/IJBCashOutHook.sol";
6
+ import {IJBController} from "../../../../src/interfaces/IJBController.sol";
7
+ import {IJBDirectory} from "../../../../src/interfaces/IJBDirectory.sol";
8
+ import {IJBFundAccessLimits} from "../../../../src/interfaces/IJBFundAccessLimits.sol";
9
+ import {IJBRulesetApprovalHook} from "../../../../src/interfaces/IJBRulesetApprovalHook.sol";
10
+ import {IJBRulesetDataHook} from "../../../../src/interfaces/IJBRulesetDataHook.sol";
11
+ import {IJBRulesets} from "../../../../src/interfaces/IJBRulesets.sol";
12
+ import {IJBTerminal} from "../../../../src/interfaces/IJBTerminal.sol";
13
+ import {IJBToken} from "../../../../src/interfaces/IJBToken.sol";
14
+ import {JBConstants} from "../../../../src/libraries/JBConstants.sol";
15
+ import {JBRulesetMetadataResolver} from "../../../../src/libraries/JBRulesetMetadataResolver.sol";
16
+ import {JBAccountingContext} from "../../../../src/structs/JBAccountingContext.sol";
17
+ import {JBBeforeCashOutRecordedContext} from "../../../../src/structs/JBBeforeCashOutRecordedContext.sol";
18
+ import {JBCashOutHookSpecification} from "../../../../src/structs/JBCashOutHookSpecification.sol";
19
+ import {JBCurrencyAmount} from "../../../../src/structs/JBCurrencyAmount.sol";
20
+ import {JBRuleset} from "../../../../src/structs/JBRuleset.sol";
21
+ import {JBRulesetMetadata} from "../../../../src/structs/JBRulesetMetadata.sol";
22
+ import {JBTokenAmount} from "../../../../src/structs/JBTokenAmount.sol";
23
+ import {mulDiv} from "@prb/math/src/Common.sol";
24
+ import {JBTerminalStoreSetup} from "./JBTerminalStoreSetup.sol";
25
+
26
+ contract TestPreviewCashOutFor_Local is JBTerminalStoreSetup {
27
+ uint64 _projectId = 1;
28
+ uint256 _decimals = 18;
29
+ uint256 _balance = 10e18;
30
+ uint256 _totalSupply = 20e18;
31
+
32
+ // Mocks
33
+ IJBTerminal _terminal1 = IJBTerminal(makeAddr("terminal1"));
34
+ IJBTerminal _terminal2 = IJBTerminal(makeAddr("terminal2"));
35
+ IJBToken _token = IJBToken(makeAddr("token"));
36
+ IJBController _controller = IJBController(makeAddr("controller"));
37
+ IJBFundAccessLimits _accessLimits = IJBFundAccessLimits(makeAddr("funds"));
38
+ IJBRulesetDataHook _dataHook = IJBRulesetDataHook(makeAddr("dataHook"));
39
+ IJBCashOutHook _cashOutHook = IJBCashOutHook(makeAddr("cashOutHook"));
40
+
41
+ uint32 _currency = uint32(uint160(address(_token)));
42
+
43
+ function setUp() public {
44
+ super.terminalStoreSetup();
45
+ }
46
+
47
+ function _setBalance(address terminal, uint256 balance) internal {
48
+ bytes32 balanceOfSlot = keccak256(abi.encode(terminal, uint256(0)));
49
+ bytes32 projectSlot = keccak256(abi.encode(_projectId, uint256(balanceOfSlot)));
50
+ bytes32 slot = keccak256(abi.encode(address(_token), uint256(projectSlot)));
51
+ vm.store(address(_store), slot, bytes32(balance));
52
+ }
53
+
54
+ function _mockUseTotalSurplus() internal {
55
+ IJBTerminal[] memory _terminals = new IJBTerminal[](2);
56
+ _terminals[0] = _terminal1;
57
+ _terminals[1] = _terminal2;
58
+
59
+ mockExpect(address(directory), abi.encodeCall(IJBDirectory.terminalsOf, (_projectId)), abi.encode(_terminals));
60
+
61
+ mockExpect(
62
+ address(_terminal1),
63
+ abi.encodeCall(
64
+ IJBTerminal.currentSurplusOf, (_projectId, new JBAccountingContext[](0), _decimals, _currency)
65
+ ),
66
+ abi.encode(1e18)
67
+ );
68
+ mockExpect(
69
+ address(_terminal2),
70
+ abi.encodeCall(
71
+ IJBTerminal.currentSurplusOf, (_projectId, new JBAccountingContext[](0), _decimals, _currency)
72
+ ),
73
+ abi.encode(2e18)
74
+ );
75
+ }
76
+
77
+ function test_MatchesRecordCashOutFor() external {
78
+ // Setup: set balance for address(this) (acting as terminal for both preview and record)
79
+ _setBalance(address(this), _balance);
80
+
81
+ _mockUseTotalSurplus();
82
+
83
+ JBRulesetMetadata memory _metadata = JBRulesetMetadata({
84
+ reservedPercent: 0,
85
+ cashOutTaxRate: 0,
86
+ baseCurrency: uint32(uint160(JBConstants.NATIVE_TOKEN)),
87
+ pausePay: false,
88
+ pauseCreditTransfers: false,
89
+ allowOwnerMinting: false,
90
+ allowSetCustomToken: false,
91
+ allowTerminalMigration: false,
92
+ allowSetTerminals: false,
93
+ ownerMustSendPayouts: false,
94
+ allowSetController: false,
95
+ allowAddAccountingContext: true,
96
+ allowAddPriceFeed: false,
97
+ holdFees: false,
98
+ useTotalSurplusForCashOuts: true,
99
+ useDataHookForPay: false,
100
+ useDataHookForCashOut: false,
101
+ dataHook: address(0),
102
+ metadata: 0
103
+ });
104
+
105
+ uint256 _packedMetadata = JBRulesetMetadataResolver.packRulesetMetadata(_metadata);
106
+
107
+ JBRuleset memory _returnedRuleset = JBRuleset({
108
+ cycleNumber: uint48(block.timestamp),
109
+ id: uint48(block.timestamp),
110
+ basedOnId: 0,
111
+ start: uint48(block.timestamp),
112
+ duration: uint32(block.timestamp + 1000),
113
+ weight: 1e18,
114
+ weightCutPercent: 0,
115
+ approvalHook: IJBRulesetApprovalHook(address(0)),
116
+ metadata: _packedMetadata
117
+ });
118
+
119
+ uint256 _cashOutCount = 10e18;
120
+
121
+ JBAccountingContext memory _accountingContext =
122
+ JBAccountingContext({token: address(_token), decimals: uint8(_decimals), currency: _currency});
123
+
124
+ JBAccountingContext[] memory _balanceContexts = new JBAccountingContext[](0);
125
+
126
+ // Mock for preview call
127
+ mockExpect(address(rulesets), abi.encodeCall(IJBRulesets.currentOf, (_projectId)), abi.encode(_returnedRuleset));
128
+ mockExpect(address(directory), abi.encodeCall(IJBDirectory.controllerOf, (_projectId)), abi.encode(_controller));
129
+ mockExpect(
130
+ address(_controller),
131
+ abi.encodeCall(IJBController.totalTokenSupplyWithReservedTokensOf, (_projectId)),
132
+ abi.encode(_totalSupply)
133
+ );
134
+
135
+ (, uint256 previewReclaimAmount, uint256 previewTaxRate, JBCashOutHookSpecification[] memory previewSpecs) = _store.previewCashOutFrom({
136
+ terminal: address(this),
137
+ holder: address(this),
138
+ projectId: _projectId,
139
+ cashOutCount: _cashOutCount,
140
+ accountingContext: _accountingContext,
141
+ balanceAccountingContexts: _balanceContexts,
142
+ beneficiaryIsFeeless: false,
143
+ metadata: ""
144
+ });
145
+
146
+ // Re-mock for record call (same mocks, fresh expectations)
147
+ _mockUseTotalSurplus();
148
+ mockExpect(address(rulesets), abi.encodeCall(IJBRulesets.currentOf, (_projectId)), abi.encode(_returnedRuleset));
149
+ mockExpect(address(directory), abi.encodeCall(IJBDirectory.controllerOf, (_projectId)), abi.encode(_controller));
150
+ mockExpect(
151
+ address(_controller),
152
+ abi.encodeCall(IJBController.totalTokenSupplyWithReservedTokensOf, (_projectId)),
153
+ abi.encode(_totalSupply)
154
+ );
155
+
156
+ (, uint256 recordReclaimAmount, uint256 recordTaxRate, JBCashOutHookSpecification[] memory recordSpecs) = _store.recordCashOutFor({
157
+ holder: address(this),
158
+ projectId: _projectId,
159
+ cashOutCount: _cashOutCount,
160
+ accountingContext: _accountingContext,
161
+ balanceAccountingContexts: _balanceContexts,
162
+ beneficiaryIsFeeless: false,
163
+ metadata: ""
164
+ });
165
+
166
+ assertEq(previewReclaimAmount, recordReclaimAmount);
167
+ assertEq(previewTaxRate, recordTaxRate);
168
+ assertEq(previewSpecs.length, recordSpecs.length);
169
+ }
170
+
171
+ function test_DoesNotModifyState() external {
172
+ _setBalance(address(this), _balance);
173
+ _mockUseTotalSurplus();
174
+
175
+ JBRulesetMetadata memory _metadata = JBRulesetMetadata({
176
+ reservedPercent: 0,
177
+ cashOutTaxRate: 0,
178
+ baseCurrency: uint32(uint160(JBConstants.NATIVE_TOKEN)),
179
+ pausePay: false,
180
+ pauseCreditTransfers: false,
181
+ allowOwnerMinting: false,
182
+ allowSetCustomToken: false,
183
+ allowTerminalMigration: false,
184
+ allowSetTerminals: false,
185
+ ownerMustSendPayouts: false,
186
+ allowSetController: false,
187
+ allowAddAccountingContext: true,
188
+ allowAddPriceFeed: false,
189
+ holdFees: false,
190
+ useTotalSurplusForCashOuts: true,
191
+ useDataHookForPay: false,
192
+ useDataHookForCashOut: false,
193
+ dataHook: address(0),
194
+ metadata: 0
195
+ });
196
+
197
+ uint256 _packedMetadata = JBRulesetMetadataResolver.packRulesetMetadata(_metadata);
198
+
199
+ JBRuleset memory _returnedRuleset = JBRuleset({
200
+ cycleNumber: uint48(block.timestamp),
201
+ id: uint48(block.timestamp),
202
+ basedOnId: 0,
203
+ start: uint48(block.timestamp),
204
+ duration: uint32(block.timestamp + 1000),
205
+ weight: 1e18,
206
+ weightCutPercent: 0,
207
+ approvalHook: IJBRulesetApprovalHook(address(0)),
208
+ metadata: _packedMetadata
209
+ });
210
+
211
+ JBAccountingContext memory _accountingContext =
212
+ JBAccountingContext({token: address(_token), decimals: uint8(_decimals), currency: _currency});
213
+
214
+ mockExpect(address(rulesets), abi.encodeCall(IJBRulesets.currentOf, (_projectId)), abi.encode(_returnedRuleset));
215
+ mockExpect(address(directory), abi.encodeCall(IJBDirectory.controllerOf, (_projectId)), abi.encode(_controller));
216
+ mockExpect(
217
+ address(_controller),
218
+ abi.encodeCall(IJBController.totalTokenSupplyWithReservedTokensOf, (_projectId)),
219
+ abi.encode(_totalSupply)
220
+ );
221
+
222
+ uint256 balanceBefore = _store.balanceOf(address(this), _projectId, address(_token));
223
+
224
+ _store.previewCashOutFrom({
225
+ terminal: address(this),
226
+ holder: address(this),
227
+ projectId: _projectId,
228
+ cashOutCount: 5e18,
229
+ accountingContext: _accountingContext,
230
+ balanceAccountingContexts: new JBAccountingContext[](0),
231
+ beneficiaryIsFeeless: false,
232
+ metadata: ""
233
+ });
234
+
235
+ uint256 balanceAfter = _store.balanceOf(address(this), _projectId, address(_token));
236
+ assertEq(balanceBefore, balanceAfter);
237
+ }
238
+
239
+ function test_RevertsWhenCashOutCountExceedsTotalSupply() external {
240
+ _setBalance(address(this), _balance);
241
+ _mockUseTotalSurplus();
242
+
243
+ JBRulesetMetadata memory _metadata = JBRulesetMetadata({
244
+ reservedPercent: 0,
245
+ cashOutTaxRate: 0,
246
+ baseCurrency: uint32(uint160(JBConstants.NATIVE_TOKEN)),
247
+ pausePay: false,
248
+ pauseCreditTransfers: false,
249
+ allowOwnerMinting: false,
250
+ allowSetCustomToken: false,
251
+ allowTerminalMigration: false,
252
+ allowSetTerminals: false,
253
+ ownerMustSendPayouts: false,
254
+ allowSetController: false,
255
+ allowAddAccountingContext: true,
256
+ allowAddPriceFeed: false,
257
+ holdFees: false,
258
+ useTotalSurplusForCashOuts: true,
259
+ useDataHookForPay: false,
260
+ useDataHookForCashOut: false,
261
+ dataHook: address(0),
262
+ metadata: 0
263
+ });
264
+
265
+ uint256 _packedMetadata = JBRulesetMetadataResolver.packRulesetMetadata(_metadata);
266
+
267
+ JBRuleset memory _returnedRuleset = JBRuleset({
268
+ cycleNumber: uint48(block.timestamp),
269
+ id: uint48(block.timestamp),
270
+ basedOnId: 0,
271
+ start: uint48(block.timestamp),
272
+ duration: uint32(block.timestamp + 1000),
273
+ weight: 1e18,
274
+ weightCutPercent: 0,
275
+ approvalHook: IJBRulesetApprovalHook(address(0)),
276
+ metadata: _packedMetadata
277
+ });
278
+
279
+ JBAccountingContext memory _accountingContext =
280
+ JBAccountingContext({token: address(_token), decimals: uint8(_decimals), currency: _currency});
281
+
282
+ mockExpect(address(rulesets), abi.encodeCall(IJBRulesets.currentOf, (_projectId)), abi.encode(_returnedRuleset));
283
+ mockExpect(address(directory), abi.encodeCall(IJBDirectory.controllerOf, (_projectId)), abi.encode(_controller));
284
+ mockExpect(
285
+ address(_controller),
286
+ abi.encodeCall(IJBController.totalTokenSupplyWithReservedTokensOf, (_projectId)),
287
+ abi.encode(_totalSupply)
288
+ );
289
+
290
+ uint256 _excessiveCashOutCount = _totalSupply + 1;
291
+
292
+ vm.expectRevert(
293
+ abi.encodeWithSelector(
294
+ JBTerminalStore.JBTerminalStore_InsufficientTokens.selector, _excessiveCashOutCount, _totalSupply
295
+ )
296
+ );
297
+ _store.previewCashOutFrom({
298
+ terminal: address(this),
299
+ holder: address(this),
300
+ projectId: _projectId,
301
+ cashOutCount: _excessiveCashOutCount,
302
+ accountingContext: _accountingContext,
303
+ balanceAccountingContexts: new JBAccountingContext[](0),
304
+ beneficiaryIsFeeless: false,
305
+ metadata: ""
306
+ });
307
+ }
308
+
309
+ function test_ReturnsZeroWhenSurplusIsZero() external {
310
+ // No balance set, no surplus
311
+
312
+ JBRulesetMetadata memory _metadata = JBRulesetMetadata({
313
+ reservedPercent: 0,
314
+ cashOutTaxRate: 0,
315
+ baseCurrency: uint32(uint160(JBConstants.NATIVE_TOKEN)),
316
+ pausePay: false,
317
+ pauseCreditTransfers: false,
318
+ allowOwnerMinting: false,
319
+ allowSetCustomToken: false,
320
+ allowTerminalMigration: false,
321
+ allowSetTerminals: false,
322
+ ownerMustSendPayouts: false,
323
+ allowSetController: false,
324
+ allowAddAccountingContext: true,
325
+ allowAddPriceFeed: false,
326
+ holdFees: false,
327
+ useTotalSurplusForCashOuts: true,
328
+ useDataHookForPay: false,
329
+ useDataHookForCashOut: false,
330
+ dataHook: address(0),
331
+ metadata: 0
332
+ });
333
+
334
+ uint256 _packedMetadata = JBRulesetMetadataResolver.packRulesetMetadata(_metadata);
335
+
336
+ JBRuleset memory _returnedRuleset = JBRuleset({
337
+ cycleNumber: uint48(block.timestamp),
338
+ id: uint48(block.timestamp),
339
+ basedOnId: 0,
340
+ start: uint48(block.timestamp),
341
+ duration: uint32(block.timestamp + 1000),
342
+ weight: 1e18,
343
+ weightCutPercent: 0,
344
+ approvalHook: IJBRulesetApprovalHook(address(0)),
345
+ metadata: _packedMetadata
346
+ });
347
+
348
+ JBAccountingContext memory _accountingContext =
349
+ JBAccountingContext({token: address(_token), decimals: uint8(_decimals), currency: _currency});
350
+
351
+ IJBTerminal[] memory _terminals = new IJBTerminal[](1);
352
+ _terminals[0] = _terminal1;
353
+
354
+ mockExpect(address(rulesets), abi.encodeCall(IJBRulesets.currentOf, (_projectId)), abi.encode(_returnedRuleset));
355
+ mockExpect(address(directory), abi.encodeCall(IJBDirectory.terminalsOf, (_projectId)), abi.encode(_terminals));
356
+ mockExpect(
357
+ address(_terminal1),
358
+ abi.encodeCall(
359
+ IJBTerminal.currentSurplusOf, (_projectId, new JBAccountingContext[](0), _decimals, _currency)
360
+ ),
361
+ abi.encode(0)
362
+ );
363
+ mockExpect(address(directory), abi.encodeCall(IJBDirectory.controllerOf, (_projectId)), abi.encode(_controller));
364
+ mockExpect(
365
+ address(_controller),
366
+ abi.encodeCall(IJBController.totalTokenSupplyWithReservedTokensOf, (_projectId)),
367
+ abi.encode(_totalSupply)
368
+ );
369
+
370
+ (, uint256 reclaimAmount,,) = _store.previewCashOutFrom({
371
+ terminal: address(this),
372
+ holder: address(this),
373
+ projectId: _projectId,
374
+ cashOutCount: 5e18,
375
+ accountingContext: _accountingContext,
376
+ balanceAccountingContexts: new JBAccountingContext[](0),
377
+ beneficiaryIsFeeless: false,
378
+ metadata: ""
379
+ });
380
+
381
+ assertEq(reclaimAmount, 0);
382
+ }
383
+ }