@bananapus/core-v6 0.0.18 → 0.0.20
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.
- package/ADMINISTRATION.md +3 -0
- package/ARCHITECTURE.md +24 -0
- package/AUDIT_INSTRUCTIONS.md +4 -2
- package/CHANGE_LOG.md +29 -1
- package/README.md +12 -2
- package/RISKS.md +10 -2
- package/SKILLS.md +9 -0
- package/USER_JOURNEYS.md +6 -0
- package/foundry.toml +1 -0
- package/package.json +1 -1
- package/src/JBController.sol +52 -5
- package/src/JBMultiTerminal.sol +197 -179
- package/src/JBTerminalStore.sol +367 -171
- package/src/interfaces/IJBCashOutTerminal.sol +30 -0
- package/src/interfaces/IJBController.sol +15 -0
- package/src/interfaces/IJBTerminal.sol +28 -0
- package/src/interfaces/IJBTerminalStore.sol +66 -0
- package/src/libraries/JBPayoutSplitGroupLib.sol +157 -0
- package/src/structs/JBCashOutHookSpecification.sol +2 -0
- package/src/structs/JBPayHookSpecification.sol +2 -0
- package/test/CoreExploitTests.t.sol +21 -10
- package/test/TestCashOutHooks.sol +6 -4
- package/test/TestDataHookFuzzing.sol +6 -2
- package/test/TestPayHooks.sol +1 -1
- package/test/TestRulesetQueueing.sol +4 -5
- package/test/TestRulesetQueuingStress.sol +5 -3
- package/test/TestTerminalPreviewParity.sol +208 -0
- package/test/fork/TestSequencerPriceFeedFork.sol +168 -0
- package/test/fork/TestTerminalPreviewParityFork.sol +109 -0
- package/test/units/static/JBController/TestPreviewMintOf.sol +116 -0
- package/test/units/static/JBMultiTerminal/TestCashOutTokensOf.sol +144 -25
- package/test/units/static/JBMultiTerminal/TestExecutePayout.sol +11 -1
- package/test/units/static/JBMultiTerminal/TestExecuteProcessFee.sol +15 -2
- package/test/units/static/JBMultiTerminal/TestPay.sol +64 -2
- package/test/units/static/JBMultiTerminal/TestPreviewCashOutFrom.sol +116 -0
- package/test/units/static/JBMultiTerminal/TestPreviewPayFor.sol +98 -0
- package/test/units/static/JBMultiTerminal/TestProcessHeldFeesOf.sol +11 -2
- package/test/units/static/JBRulesets/TestCurrentOf.sol +8 -6
- package/test/units/static/JBRulesets/TestRulesets.sol +25 -24
- package/test/units/static/JBRulesets/TestUpcomingRulesetOf.sol +4 -17
- package/test/units/static/JBSurplus/TestSurplusFuzz.sol +49 -2
- package/test/units/static/JBTerminalStore/TestCurrentReclaimableSurplusOf.sol +215 -0
- package/test/units/static/JBTerminalStore/TestPreviewCashOutFrom.sol +475 -0
- package/test/units/static/JBTerminalStore/TestPreviewPayFrom.sol +464 -0
- package/test/units/static/JBTerminalStore/TestRecordCashOutsFor.sol +113 -2
- package/test/units/static/JBTerminalStore/TestRecordPaymentFrom.sol +227 -5
|
@@ -10,6 +10,7 @@ import {IJBController} from "../../../../src/interfaces/IJBController.sol";
|
|
|
10
10
|
import {IJBDirectory} from "../../../../src/interfaces/IJBDirectory.sol";
|
|
11
11
|
import {IJBFeelessAddresses} from "../../../../src/interfaces/IJBFeelessAddresses.sol";
|
|
12
12
|
import {IJBPermissions} from "../../../../src/interfaces/IJBPermissions.sol";
|
|
13
|
+
import {IJBRulesets} from "../../../../src/interfaces/IJBRulesets.sol";
|
|
13
14
|
import {IJBRulesetApprovalHook} from "../../../../src/interfaces/IJBRulesetApprovalHook.sol";
|
|
14
15
|
import {IJBTerminalStore} from "../../../../src/interfaces/IJBTerminalStore.sol";
|
|
15
16
|
import {JBConstants} from "../../../../src/libraries/JBConstants.sol";
|
|
@@ -21,6 +22,8 @@ import {JBRuleset} from "../../../../src/structs/JBRuleset.sol";
|
|
|
21
22
|
import {JBTokenAmount} from "../../../../src/structs/JBTokenAmount.sol";
|
|
22
23
|
import {JBPermissionIds} from "@bananapus/permission-ids-v6/src/JBPermissionIds.sol";
|
|
23
24
|
import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
|
|
25
|
+
import {IERC20Metadata} from "@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol";
|
|
26
|
+
import {IERC721} from "@openzeppelin/contracts/token/ERC721/IERC721.sol";
|
|
24
27
|
import {JBMultiTerminalSetup} from "./JBMultiTerminalSetup.sol";
|
|
25
28
|
|
|
26
29
|
contract TestCashOutTokensOf_Local is JBMultiTerminalSetup {
|
|
@@ -45,6 +48,37 @@ contract TestCashOutTokensOf_Local is JBMultiTerminalSetup {
|
|
|
45
48
|
_mockToken2 = new MockERC20("testToken", "TT");
|
|
46
49
|
}
|
|
47
50
|
|
|
51
|
+
function _acceptToken(address token, uint8 decimals, uint32 currency) internal {
|
|
52
|
+
mockExpect(address(projects), abi.encodeCall(IERC721.ownerOf, (_projectId)), abi.encode(address(0)));
|
|
53
|
+
mockExpect(
|
|
54
|
+
address(directory), abi.encodeCall(IJBDirectory.controllerOf, (_projectId)), abi.encode(address(this))
|
|
55
|
+
);
|
|
56
|
+
|
|
57
|
+
if (token != JBConstants.NATIVE_TOKEN) {
|
|
58
|
+
mockExpect(token, abi.encodeCall(IERC20Metadata.decimals, ()), abi.encode(decimals));
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
JBAccountingContext[] memory _tokens = new JBAccountingContext[](1);
|
|
62
|
+
_tokens[0] = JBAccountingContext({token: token, decimals: decimals, currency: currency});
|
|
63
|
+
|
|
64
|
+
JBRuleset memory returnedRuleset = JBRuleset({
|
|
65
|
+
cycleNumber: 1,
|
|
66
|
+
id: 0,
|
|
67
|
+
basedOnId: 0,
|
|
68
|
+
start: 0,
|
|
69
|
+
duration: 0,
|
|
70
|
+
weight: 0,
|
|
71
|
+
weightCutPercent: 0,
|
|
72
|
+
approvalHook: IJBRulesetApprovalHook(address(0)),
|
|
73
|
+
metadata: 0
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
mockExpect(address(rulesets), abi.encodeCall(IJBRulesets.currentOf, (_projectId)), abi.encode(returnedRuleset));
|
|
77
|
+
|
|
78
|
+
vm.prank(address(this));
|
|
79
|
+
_terminal.addAccountingContextsFor(_projectId, _tokens);
|
|
80
|
+
}
|
|
81
|
+
|
|
48
82
|
function test_WhenCallerDNHavePermission() external {
|
|
49
83
|
// it will revert UNAUTHORIZED
|
|
50
84
|
|
|
@@ -72,8 +106,6 @@ contract TestCashOutTokensOf_Local is JBMultiTerminalSetup {
|
|
|
72
106
|
}
|
|
73
107
|
|
|
74
108
|
modifier whenCallerHasPermission() {
|
|
75
|
-
vm.prank(_bene);
|
|
76
|
-
|
|
77
109
|
// mock call to JBPermissions hasPermission
|
|
78
110
|
mockExpect(
|
|
79
111
|
address(permissions),
|
|
@@ -92,8 +124,10 @@ contract TestCashOutTokensOf_Local is JBMultiTerminalSetup {
|
|
|
92
124
|
|
|
93
125
|
uint256 reclaimAmount = 1e9;
|
|
94
126
|
JBCashOutHookSpecification[] memory hookSpecifications = new JBCashOutHookSpecification[](0);
|
|
95
|
-
JBAccountingContext
|
|
96
|
-
|
|
127
|
+
JBAccountingContext memory mockTokenContext =
|
|
128
|
+
JBAccountingContext({token: _mockToken, decimals: 18, currency: uint32(uint160(_mockToken))});
|
|
129
|
+
JBAccountingContext[] memory mockBalanceContext = new JBAccountingContext[](1);
|
|
130
|
+
mockBalanceContext[0] = mockTokenContext;
|
|
97
131
|
JBRuleset memory returnedRuleset = JBRuleset({
|
|
98
132
|
cycleNumber: 1,
|
|
99
133
|
id: 1,
|
|
@@ -131,7 +165,9 @@ contract TestCashOutTokensOf_Local is JBMultiTerminalSetup {
|
|
|
131
165
|
|
|
132
166
|
// put code at mockToken address to pass OZ Address check
|
|
133
167
|
vm.etch(_mockToken, abi.encode(1));
|
|
168
|
+
_acceptToken(_mockToken, 18, uint32(uint160(_mockToken)));
|
|
134
169
|
|
|
170
|
+
vm.prank(_bene);
|
|
135
171
|
_terminal.cashOutTokensOf(_holder, _projectId, _defaultAmount, _mockToken, _minReclaimed, _bene, "");
|
|
136
172
|
}
|
|
137
173
|
|
|
@@ -140,8 +176,10 @@ contract TestCashOutTokensOf_Local is JBMultiTerminalSetup {
|
|
|
140
176
|
|
|
141
177
|
uint256 reclaimAmount = 1e9;
|
|
142
178
|
JBCashOutHookSpecification[] memory hookSpecifications = new JBCashOutHookSpecification[](0);
|
|
143
|
-
JBAccountingContext
|
|
144
|
-
|
|
179
|
+
JBAccountingContext memory mockTokenContext =
|
|
180
|
+
JBAccountingContext({token: _mockToken, decimals: 18, currency: uint32(uint160(_mockToken))});
|
|
181
|
+
JBAccountingContext[] memory mockBalanceContext = new JBAccountingContext[](1);
|
|
182
|
+
mockBalanceContext[0] = mockTokenContext;
|
|
145
183
|
JBRuleset memory returnedRuleset = JBRuleset({
|
|
146
184
|
cycleNumber: 1,
|
|
147
185
|
id: 1,
|
|
@@ -179,10 +217,14 @@ contract TestCashOutTokensOf_Local is JBMultiTerminalSetup {
|
|
|
179
217
|
|
|
180
218
|
// put code at mockToken address to pass OZ Address check
|
|
181
219
|
vm.etch(_mockToken, abi.encode(1));
|
|
220
|
+
_acceptToken(_mockToken, 18, uint32(uint160(_mockToken)));
|
|
182
221
|
|
|
183
222
|
vm.expectRevert(
|
|
184
|
-
abi.encodeWithSelector(
|
|
223
|
+
abi.encodeWithSelector(
|
|
224
|
+
JBMultiTerminal.JBMultiTerminal_UnderMinTokensReclaimed.selector, reclaimAmount, 1e18
|
|
225
|
+
)
|
|
185
226
|
);
|
|
227
|
+
vm.prank(_bene);
|
|
186
228
|
_terminal.cashOutTokensOf(_holder, _projectId, _defaultAmount, _mockToken, 1e18, _bene, ""); // minReclaimAmount
|
|
187
229
|
// = 1e18 but only 1e9 reclaimed
|
|
188
230
|
}
|
|
@@ -195,8 +237,10 @@ contract TestCashOutTokensOf_Local is JBMultiTerminalSetup {
|
|
|
195
237
|
|
|
196
238
|
uint256 reclaimAmount = 1e9;
|
|
197
239
|
JBCashOutHookSpecification[] memory hookSpecifications = new JBCashOutHookSpecification[](0);
|
|
198
|
-
JBAccountingContext
|
|
199
|
-
|
|
240
|
+
JBAccountingContext memory mockTokenContext =
|
|
241
|
+
JBAccountingContext({token: _mockToken, decimals: 18, currency: uint32(uint160(_mockToken))});
|
|
242
|
+
JBAccountingContext[] memory mockBalanceContext = new JBAccountingContext[](1);
|
|
243
|
+
mockBalanceContext[0] = mockTokenContext;
|
|
200
244
|
JBRuleset memory returnedRuleset = JBRuleset({
|
|
201
245
|
cycleNumber: 1,
|
|
202
246
|
id: 1,
|
|
@@ -234,6 +278,7 @@ contract TestCashOutTokensOf_Local is JBMultiTerminalSetup {
|
|
|
234
278
|
|
|
235
279
|
// put code at mockToken address to pass OZ Address check
|
|
236
280
|
vm.etch(_mockToken, abi.encode(1));
|
|
281
|
+
_acceptToken(_mockToken, 18, uint32(uint160(_mockToken)));
|
|
237
282
|
|
|
238
283
|
// get fee amount
|
|
239
284
|
uint256 tax = JBFees.feeAmountFrom(reclaimAmount, 25); // 25 = default fee)
|
|
@@ -256,6 +301,7 @@ contract TestCashOutTokensOf_Local is JBMultiTerminalSetup {
|
|
|
256
301
|
""
|
|
257
302
|
);
|
|
258
303
|
|
|
304
|
+
vm.prank(_bene);
|
|
259
305
|
_terminal.cashOutTokensOf(_holder, _projectId, _defaultAmount, _mockToken, _minReclaimed, _bene, "");
|
|
260
306
|
}
|
|
261
307
|
|
|
@@ -306,9 +352,13 @@ contract TestCashOutTokensOf_Local is JBMultiTerminalSetup {
|
|
|
306
352
|
|
|
307
353
|
uint256 reclaimAmount = 1e9;
|
|
308
354
|
JBCashOutHookSpecification[] memory hookSpecifications = new JBCashOutHookSpecification[](1);
|
|
309
|
-
hookSpecifications[0] =
|
|
310
|
-
|
|
311
|
-
JBAccountingContext memory mockTokenContext = JBAccountingContext({
|
|
355
|
+
hookSpecifications[0] =
|
|
356
|
+
JBCashOutHookSpecification({hook: _mockHook, noop: false, amount: _defaultAmount, metadata: ""});
|
|
357
|
+
JBAccountingContext memory mockTokenContext = JBAccountingContext({
|
|
358
|
+
token: address(_mockToken2), decimals: 18, currency: uint32(uint160(address(_mockToken2)))
|
|
359
|
+
});
|
|
360
|
+
JBAccountingContext[] memory mockBalanceContext = new JBAccountingContext[](1);
|
|
361
|
+
mockBalanceContext[0] = mockTokenContext;
|
|
312
362
|
JBRuleset memory returnedRuleset = JBRuleset({
|
|
313
363
|
cycleNumber: 1,
|
|
314
364
|
id: 1,
|
|
@@ -349,10 +399,18 @@ contract TestCashOutTokensOf_Local is JBMultiTerminalSetup {
|
|
|
349
399
|
abi.encode(true)
|
|
350
400
|
);
|
|
351
401
|
|
|
352
|
-
JBTokenAmount memory reclaimedAmount =
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
402
|
+
JBTokenAmount memory reclaimedAmount = JBTokenAmount({
|
|
403
|
+
token: address(_mockToken2),
|
|
404
|
+
decimals: 18,
|
|
405
|
+
currency: uint32(uint160(address(_mockToken2))),
|
|
406
|
+
value: reclaimAmount
|
|
407
|
+
});
|
|
408
|
+
JBTokenAmount memory forwardedAmount = JBTokenAmount({
|
|
409
|
+
token: address(_mockToken2),
|
|
410
|
+
decimals: 18,
|
|
411
|
+
currency: uint32(uint160(address(_mockToken2))),
|
|
412
|
+
value: _defaultAmount
|
|
413
|
+
});
|
|
356
414
|
|
|
357
415
|
// needed for hook call
|
|
358
416
|
JBAfterCashOutRecordedContext memory context = JBAfterCashOutRecordedContext({
|
|
@@ -373,6 +431,8 @@ contract TestCashOutTokensOf_Local is JBMultiTerminalSetup {
|
|
|
373
431
|
// ensure approval is increased
|
|
374
432
|
vm.expectCall(address(_mockToken2), abi.encodeCall(IERC20.approve, (address(_mockHook), _defaultAmount * 2)));
|
|
375
433
|
|
|
434
|
+
_acceptToken(address(_mockToken2), 18, uint32(uint160(address(_mockToken2))));
|
|
435
|
+
|
|
376
436
|
vm.expectEmit();
|
|
377
437
|
emit IJBCashOutTerminal.HookAfterRecordCashOut(_mockHook, context, _defaultAmount, 0, address(_bene));
|
|
378
438
|
|
|
@@ -409,9 +469,13 @@ contract TestCashOutTokensOf_Local is JBMultiTerminalSetup {
|
|
|
409
469
|
uint256 reclaimAmount = 1e9;
|
|
410
470
|
JBCashOutHookSpecification[] memory hookSpecifications = new JBCashOutHookSpecification[](1);
|
|
411
471
|
JBCashOutHookSpecification[] memory paySpecs = new JBCashOutHookSpecification[](0);
|
|
412
|
-
hookSpecifications[0] =
|
|
413
|
-
|
|
414
|
-
JBAccountingContext memory mockTokenContext = JBAccountingContext({
|
|
472
|
+
hookSpecifications[0] =
|
|
473
|
+
JBCashOutHookSpecification({hook: _mockHook, noop: false, amount: _defaultAmount, metadata: ""});
|
|
474
|
+
JBAccountingContext memory mockTokenContext = JBAccountingContext({
|
|
475
|
+
token: address(_mockToken2), decimals: 18, currency: uint32(uint160(address(_mockToken2)))
|
|
476
|
+
});
|
|
477
|
+
JBAccountingContext[] memory mockBalanceContext = new JBAccountingContext[](1);
|
|
478
|
+
mockBalanceContext[0] = mockTokenContext;
|
|
415
479
|
JBRuleset memory returnedRuleset = JBRuleset({
|
|
416
480
|
cycleNumber: 1,
|
|
417
481
|
id: 1,
|
|
@@ -455,12 +519,21 @@ contract TestCashOutTokensOf_Local is JBMultiTerminalSetup {
|
|
|
455
519
|
uint256 hookTax = JBFees.feeAmountFrom(_defaultAmount, 25);
|
|
456
520
|
uint256 passedAfterTax = _defaultAmount - hookTax;
|
|
457
521
|
|
|
458
|
-
JBTokenAmount memory reclaimedAmount =
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
522
|
+
JBTokenAmount memory reclaimedAmount = JBTokenAmount({
|
|
523
|
+
token: address(_mockToken2),
|
|
524
|
+
decimals: 18,
|
|
525
|
+
currency: uint32(uint160(address(_mockToken2))),
|
|
526
|
+
value: reclaimAmount
|
|
527
|
+
});
|
|
528
|
+
JBTokenAmount memory forwardedAmount = JBTokenAmount({
|
|
529
|
+
token: address(_mockToken2),
|
|
530
|
+
decimals: 18,
|
|
531
|
+
currency: uint32(uint160(address(_mockToken2))),
|
|
532
|
+
value: passedAfterTax
|
|
533
|
+
});
|
|
534
|
+
JBTokenAmount memory feeRepayAmount = JBTokenAmount({
|
|
535
|
+
token: address(_mockToken2), decimals: 18, currency: uint32(uint160(address(_mockToken2))), value: hookTax
|
|
536
|
+
});
|
|
464
537
|
|
|
465
538
|
// needed for hook call
|
|
466
539
|
JBAfterCashOutRecordedContext memory context = JBAfterCashOutRecordedContext({
|
|
@@ -494,10 +567,56 @@ contract TestCashOutTokensOf_Local is JBMultiTerminalSetup {
|
|
|
494
567
|
),
|
|
495
568
|
abi.encode(returnedRuleset, 0, paySpecs)
|
|
496
569
|
);
|
|
570
|
+
|
|
571
|
+
_acceptToken(address(_mockToken2), 18, uint32(uint160(address(_mockToken2))));
|
|
572
|
+
|
|
497
573
|
vm.expectEmit();
|
|
498
574
|
emit IJBCashOutTerminal.HookAfterRecordCashOut(_mockHook, context, passedAfterTax, hookTax, address(_bene));
|
|
499
575
|
|
|
500
576
|
vm.prank(_bene);
|
|
501
577
|
_terminal.cashOutTokensOf(_holder, _projectId, _defaultAmount, address(_mockToken2), _minReclaimed, _bene, "");
|
|
502
578
|
}
|
|
579
|
+
|
|
580
|
+
function test_GivenTheCashOutHookSpecIsNoop() external whenCallerHasPermission {
|
|
581
|
+
uint256 reclaimAmount = 1e9;
|
|
582
|
+
JBCashOutHookSpecification[] memory hookSpecifications = new JBCashOutHookSpecification[](1);
|
|
583
|
+
hookSpecifications[0] =
|
|
584
|
+
JBCashOutHookSpecification({hook: IJBCashOutHook(address(this)), noop: true, amount: 0, metadata: "info"});
|
|
585
|
+
JBAccountingContext memory mockTokenContext =
|
|
586
|
+
JBAccountingContext({token: _mockToken, decimals: 18, currency: uint32(uint160(_mockToken))});
|
|
587
|
+
JBAccountingContext[] memory mockBalanceContext = new JBAccountingContext[](1);
|
|
588
|
+
mockBalanceContext[0] = mockTokenContext;
|
|
589
|
+
JBRuleset memory returnedRuleset = JBRuleset({
|
|
590
|
+
cycleNumber: 1,
|
|
591
|
+
id: 1,
|
|
592
|
+
basedOnId: 0,
|
|
593
|
+
start: 0,
|
|
594
|
+
duration: 0,
|
|
595
|
+
weight: 0,
|
|
596
|
+
weightCutPercent: 0,
|
|
597
|
+
approvalHook: IJBRulesetApprovalHook(address(0)),
|
|
598
|
+
metadata: 0
|
|
599
|
+
});
|
|
600
|
+
|
|
601
|
+
mockExpect(address(feelessAddresses), abi.encodeCall(IJBFeelessAddresses.isFeeless, (_bene)), abi.encode(true));
|
|
602
|
+
mockExpect(
|
|
603
|
+
address(store),
|
|
604
|
+
abi.encodeCall(
|
|
605
|
+
IJBTerminalStore.recordCashOutFor,
|
|
606
|
+
(_holder, _projectId, _defaultAmount, mockTokenContext, mockBalanceContext, true, "")
|
|
607
|
+
),
|
|
608
|
+
abi.encode(returnedRuleset, reclaimAmount, _maxCashOutTaxRate, hookSpecifications)
|
|
609
|
+
);
|
|
610
|
+
mockExpect(
|
|
611
|
+
address(directory), abi.encodeCall(IJBDirectory.controllerOf, (_projectId)), abi.encode(address(this))
|
|
612
|
+
);
|
|
613
|
+
mockExpect(
|
|
614
|
+
address(this), abi.encodeCall(IJBController.burnTokensOf, (_holder, _projectId, _defaultAmount, "")), ""
|
|
615
|
+
);
|
|
616
|
+
|
|
617
|
+
_acceptToken(_mockToken, 18, uint32(uint160(_mockToken)));
|
|
618
|
+
|
|
619
|
+
vm.prank(_bene);
|
|
620
|
+
_terminal.cashOutTokensOf(_holder, _projectId, _defaultAmount, _mockToken, _minReclaimed, _bene, "");
|
|
621
|
+
}
|
|
503
622
|
}
|
|
@@ -32,6 +32,7 @@ contract TestExecutePayout_Local is JBMultiTerminalSetup {
|
|
|
32
32
|
|
|
33
33
|
address _native = JBConstants.NATIVE_TOKEN;
|
|
34
34
|
address _usdc = makeAddr("USDC");
|
|
35
|
+
uint32 _usdcCurrency = uint32(uint160(_usdc));
|
|
35
36
|
|
|
36
37
|
JBSplit private _split;
|
|
37
38
|
JBSplit private _emptySplit;
|
|
@@ -40,6 +41,13 @@ contract TestExecutePayout_Local is JBMultiTerminalSetup {
|
|
|
40
41
|
super.multiTerminalSetup();
|
|
41
42
|
}
|
|
42
43
|
|
|
44
|
+
function _setAccountingContext(uint256 projectId, address token, uint8 decimals, uint32 currency) internal {
|
|
45
|
+
bytes32 contextSlot = keccak256(abi.encode(projectId, uint256(0)));
|
|
46
|
+
bytes32 slot = keccak256(abi.encode(token, contextSlot));
|
|
47
|
+
bytes32 packed = bytes32(uint256(uint160(token)) | (uint256(decimals) << 160) | (uint256(currency) << 168));
|
|
48
|
+
vm.store(address(_terminal), slot, packed);
|
|
49
|
+
}
|
|
50
|
+
|
|
43
51
|
modifier whenASplitHookIsConfigured() {
|
|
44
52
|
_split = JBSplit({
|
|
45
53
|
preferAddToBalance: false,
|
|
@@ -342,6 +350,8 @@ contract TestExecutePayout_Local is JBMultiTerminalSetup {
|
|
|
342
350
|
function test_GivenPreferAddToBalanceDNEQTrueAndTerminalEQThisAddress() external {
|
|
343
351
|
// it will call internal _pay
|
|
344
352
|
|
|
353
|
+
_setAccountingContext(_projectId, _usdc, 0, _usdcCurrency);
|
|
354
|
+
|
|
345
355
|
// mock call to directory primaryTerminalOf
|
|
346
356
|
mockExpect(
|
|
347
357
|
address(directory),
|
|
@@ -360,7 +370,7 @@ contract TestExecutePayout_Local is JBMultiTerminalSetup {
|
|
|
360
370
|
|
|
361
371
|
// needed for next mock call returns
|
|
362
372
|
JBTokenAmount memory tokenAmount =
|
|
363
|
-
JBTokenAmount({token: _usdc, decimals: 0, currency:
|
|
373
|
+
JBTokenAmount({token: _usdc, decimals: 0, currency: _usdcCurrency, value: _defaultAmount});
|
|
364
374
|
JBPayHookSpecification[] memory hookSpecifications = new JBPayHookSpecification[](0);
|
|
365
375
|
JBRuleset memory returnedRuleset = JBRuleset({
|
|
366
376
|
cycleNumber: 1,
|
|
@@ -6,6 +6,7 @@ import {IJBRulesetApprovalHook} from "../../../../src/interfaces/IJBRulesetAppro
|
|
|
6
6
|
import {IJBTerminal} from "../../../../src/interfaces/IJBTerminal.sol";
|
|
7
7
|
import {IJBTerminalStore} from "../../../../src/interfaces/IJBTerminalStore.sol";
|
|
8
8
|
import {JBConstants} from "../../../../src/libraries/JBConstants.sol";
|
|
9
|
+
import {JBAccountingContext} from "../../../../src/structs/JBAccountingContext.sol";
|
|
9
10
|
import {JBPayHookSpecification} from "../../../../src/structs/JBPayHookSpecification.sol";
|
|
10
11
|
import {JBRuleset} from "../../../../src/structs/JBRuleset.sol";
|
|
11
12
|
import {JBTokenAmount} from "../../../../src/structs/JBTokenAmount.sol";
|
|
@@ -26,6 +27,14 @@ contract TestExecuteProcessFee_Local is JBMultiTerminalSetup {
|
|
|
26
27
|
super.multiTerminalSetup();
|
|
27
28
|
}
|
|
28
29
|
|
|
30
|
+
function _setAccountingContext(address token, uint8 decimals, uint32 currency) internal {
|
|
31
|
+
bytes32 contextSlot = keccak256(abi.encode(_projectId, uint256(0)));
|
|
32
|
+
bytes32 slot = keccak256(abi.encode(token, contextSlot));
|
|
33
|
+
|
|
34
|
+
bytes32 packed = bytes32(uint256(uint160(token)) | (uint256(decimals) << 160) | (uint256(currency) << 168));
|
|
35
|
+
vm.store(address(_terminal), slot, packed);
|
|
36
|
+
}
|
|
37
|
+
|
|
29
38
|
function test_WhenCallerIsNotItself() external {
|
|
30
39
|
// it will revert
|
|
31
40
|
|
|
@@ -87,9 +96,11 @@ contract TestExecuteProcessFee_Local is JBMultiTerminalSetup {
|
|
|
87
96
|
function test_WhenFeeTerminalEQItself() external {
|
|
88
97
|
// it will call internal _pay
|
|
89
98
|
|
|
99
|
+
_setAccountingContext(_native, 0, 1);
|
|
100
|
+
|
|
90
101
|
// needed for next mock call returns
|
|
91
102
|
JBTokenAmount memory tokenAmount =
|
|
92
|
-
JBTokenAmount({token: _native, decimals: 0, currency:
|
|
103
|
+
JBTokenAmount({token: _native, decimals: 0, currency: 1, value: _defaultAmount});
|
|
93
104
|
JBPayHookSpecification[] memory hookSpecifications = new JBPayHookSpecification[](0);
|
|
94
105
|
JBRuleset memory returnedRuleset = JBRuleset({
|
|
95
106
|
cycleNumber: 1,
|
|
@@ -151,9 +162,11 @@ contract TestExecuteProcessFee_Local is JBMultiTerminalSetup {
|
|
|
151
162
|
function test_GivenTokenDNEQNATIVE_TOKENAndPayingItself() external {
|
|
152
163
|
// it will call external pay with zero msgvalue
|
|
153
164
|
|
|
165
|
+
_setAccountingContext(_usdc, 0, 1);
|
|
166
|
+
|
|
154
167
|
// needed for next mock call returns
|
|
155
168
|
JBTokenAmount memory tokenAmount =
|
|
156
|
-
JBTokenAmount({token: _usdc, decimals: 0, currency:
|
|
169
|
+
JBTokenAmount({token: _usdc, decimals: 0, currency: 1, value: _defaultAmount});
|
|
157
170
|
JBPayHookSpecification[] memory hookSpecifications = new JBPayHookSpecification[](0);
|
|
158
171
|
JBRuleset memory returnedRuleset = JBRuleset({
|
|
159
172
|
cycleNumber: 1,
|
|
@@ -258,7 +258,8 @@ contract TestPay_Local is JBMultiTerminalSetup {
|
|
|
258
258
|
value: _defaultAmount
|
|
259
259
|
});
|
|
260
260
|
JBPayHookSpecification[] memory hookSpecifications = new JBPayHookSpecification[](1);
|
|
261
|
-
hookSpecifications[0] =
|
|
261
|
+
hookSpecifications[0] =
|
|
262
|
+
JBPayHookSpecification({hook: _mockHook, noop: false, amount: _defaultAmount, metadata: ""});
|
|
262
263
|
|
|
263
264
|
JBRuleset memory returnedRuleset = JBRuleset({
|
|
264
265
|
cycleNumber: 1,
|
|
@@ -349,7 +350,8 @@ contract TestPay_Local is JBMultiTerminalSetup {
|
|
|
349
350
|
JBTokenAmount memory tokenAmount =
|
|
350
351
|
JBTokenAmount({token: _native, decimals: 18, currency: uint32(_nativeCurrency), value: _defaultAmount});
|
|
351
352
|
JBPayHookSpecification[] memory hookSpecifications = new JBPayHookSpecification[](1);
|
|
352
|
-
hookSpecifications[0] =
|
|
353
|
+
hookSpecifications[0] =
|
|
354
|
+
JBPayHookSpecification({hook: _mockHook, noop: false, amount: _defaultAmount, metadata: ""});
|
|
353
355
|
|
|
354
356
|
JBRuleset memory returnedRuleset = JBRuleset({
|
|
355
357
|
cycleNumber: 1,
|
|
@@ -527,6 +529,66 @@ contract TestPay_Local is JBMultiTerminalSetup {
|
|
|
527
529
|
});
|
|
528
530
|
}
|
|
529
531
|
|
|
532
|
+
function test_GivenThePayHookSpecIsNoop() external whenNativeTokenIsAccepted {
|
|
533
|
+
JBTokenAmount memory tokenAmount =
|
|
534
|
+
JBTokenAmount({token: _native, decimals: 18, currency: uint32(_nativeCurrency), value: _defaultAmount});
|
|
535
|
+
JBPayHookSpecification[] memory hookSpecifications = new JBPayHookSpecification[](1);
|
|
536
|
+
hookSpecifications[0] =
|
|
537
|
+
JBPayHookSpecification({hook: IJBPayHook(address(this)), noop: true, amount: 0, metadata: "info"});
|
|
538
|
+
|
|
539
|
+
JBRuleset memory returnedRuleset = JBRuleset({
|
|
540
|
+
cycleNumber: 1,
|
|
541
|
+
id: 1,
|
|
542
|
+
basedOnId: 0,
|
|
543
|
+
start: 0,
|
|
544
|
+
duration: 0,
|
|
545
|
+
weight: 0,
|
|
546
|
+
weightCutPercent: 0,
|
|
547
|
+
approvalHook: IJBRulesetApprovalHook(address(0)),
|
|
548
|
+
metadata: 0
|
|
549
|
+
});
|
|
550
|
+
|
|
551
|
+
mockExpect(
|
|
552
|
+
address(store),
|
|
553
|
+
abi.encodeCall(
|
|
554
|
+
IJBTerminalStore.recordPaymentFrom, (address(this), tokenAmount, _projectId, _bene, bytes(""))
|
|
555
|
+
),
|
|
556
|
+
abi.encode(returnedRuleset, 0, hookSpecifications)
|
|
557
|
+
);
|
|
558
|
+
|
|
559
|
+
bytes[] memory subsequentReturns = new bytes[](2);
|
|
560
|
+
subsequentReturns[0] = abi.encode(0);
|
|
561
|
+
subsequentReturns[1] = abi.encode(0);
|
|
562
|
+
|
|
563
|
+
mockExpectSubsequent(
|
|
564
|
+
address(tokens), abi.encodeCall(IJBTokens.totalBalanceOf, (_bene, _projectId)), subsequentReturns
|
|
565
|
+
);
|
|
566
|
+
|
|
567
|
+
vm.expectEmit();
|
|
568
|
+
emit IJBTerminal.Pay(
|
|
569
|
+
returnedRuleset.id,
|
|
570
|
+
returnedRuleset.cycleNumber,
|
|
571
|
+
_projectId,
|
|
572
|
+
address(this),
|
|
573
|
+
_bene,
|
|
574
|
+
_defaultAmount,
|
|
575
|
+
0,
|
|
576
|
+
"",
|
|
577
|
+
bytes(""),
|
|
578
|
+
address(this)
|
|
579
|
+
);
|
|
580
|
+
|
|
581
|
+
_terminal.pay{value: _defaultAmount}({
|
|
582
|
+
projectId: _projectId,
|
|
583
|
+
token: _native,
|
|
584
|
+
amount: _defaultAmount,
|
|
585
|
+
beneficiary: _bene,
|
|
586
|
+
minReturnedTokens: 0,
|
|
587
|
+
memo: "",
|
|
588
|
+
metadata: ""
|
|
589
|
+
});
|
|
590
|
+
}
|
|
591
|
+
|
|
530
592
|
// accept funds with permit2 has been extensively tested in other units
|
|
531
593
|
/* modifier whenPayMetadataContainsPermitData() {
|
|
532
594
|
_;
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
// SPDX-License-Identifier: MIT
|
|
2
|
+
pragma solidity 0.8.26;
|
|
3
|
+
|
|
4
|
+
import {JBMultiTerminal} from "../../../../src/JBMultiTerminal.sol";
|
|
5
|
+
import {IJBCashOutHook} from "../../../../src/interfaces/IJBCashOutHook.sol";
|
|
6
|
+
import {IJBDirectory} from "../../../../src/interfaces/IJBDirectory.sol";
|
|
7
|
+
import {IJBFeelessAddresses} from "../../../../src/interfaces/IJBFeelessAddresses.sol";
|
|
8
|
+
import {IJBRulesets} from "../../../../src/interfaces/IJBRulesets.sol";
|
|
9
|
+
import {IJBRulesetApprovalHook} from "../../../../src/interfaces/IJBRulesetApprovalHook.sol";
|
|
10
|
+
import {IJBTerminalStore} from "../../../../src/interfaces/IJBTerminalStore.sol";
|
|
11
|
+
import {JBConstants} from "../../../../src/libraries/JBConstants.sol";
|
|
12
|
+
import {JBAccountingContext} from "../../../../src/structs/JBAccountingContext.sol";
|
|
13
|
+
import {JBCashOutHookSpecification} from "../../../../src/structs/JBCashOutHookSpecification.sol";
|
|
14
|
+
import {JBRuleset} from "../../../../src/structs/JBRuleset.sol";
|
|
15
|
+
import {IERC721} from "@openzeppelin/contracts/token/ERC721/IERC721.sol";
|
|
16
|
+
import {JBMultiTerminalSetup} from "./JBMultiTerminalSetup.sol";
|
|
17
|
+
|
|
18
|
+
contract TestPreviewCashOutFrom_Local is JBMultiTerminalSetup {
|
|
19
|
+
uint256 _projectId = 1;
|
|
20
|
+
uint256 _cashOutCount = 1e18;
|
|
21
|
+
address _holder = makeAddr("holder");
|
|
22
|
+
address payable _beneficiary = payable(makeAddr("beneficiary"));
|
|
23
|
+
address _token = JBConstants.NATIVE_TOKEN;
|
|
24
|
+
|
|
25
|
+
function setUp() public {
|
|
26
|
+
super.multiTerminalSetup();
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function _acceptToken(address token, uint8 decimals, uint32 currency) internal {
|
|
30
|
+
mockExpect(address(projects), abi.encodeCall(IERC721.ownerOf, (_projectId)), abi.encode(address(0)));
|
|
31
|
+
mockExpect(
|
|
32
|
+
address(directory), abi.encodeCall(IJBDirectory.controllerOf, (_projectId)), abi.encode(address(this))
|
|
33
|
+
);
|
|
34
|
+
|
|
35
|
+
JBRuleset memory returnedRuleset = JBRuleset({
|
|
36
|
+
cycleNumber: 1,
|
|
37
|
+
id: 0,
|
|
38
|
+
basedOnId: 0,
|
|
39
|
+
start: 0,
|
|
40
|
+
duration: 0,
|
|
41
|
+
weight: 0,
|
|
42
|
+
weightCutPercent: 0,
|
|
43
|
+
approvalHook: IJBRulesetApprovalHook(address(0)),
|
|
44
|
+
metadata: 0
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
mockExpect(address(rulesets), abi.encodeCall(IJBRulesets.currentOf, (_projectId)), abi.encode(returnedRuleset));
|
|
48
|
+
|
|
49
|
+
JBAccountingContext[] memory contexts = new JBAccountingContext[](1);
|
|
50
|
+
contexts[0] = JBAccountingContext({token: token, decimals: decimals, currency: currency});
|
|
51
|
+
|
|
52
|
+
vm.prank(address(this));
|
|
53
|
+
_terminal.addAccountingContextsFor(_projectId, contexts);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function test_RevertsWhenTokenIsNotAccepted() external {
|
|
57
|
+
vm.expectRevert(abi.encodeWithSelector(JBMultiTerminal.JBMultiTerminal_TokenNotAccepted.selector, _token));
|
|
58
|
+
JBMultiTerminal(address(_terminal))
|
|
59
|
+
.previewCashOutFrom(_holder, _projectId, _cashOutCount, _token, _beneficiary, "");
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function test_ReturnsRulesetAndCashOutPreviewValues() external {
|
|
63
|
+
_acceptToken(_token, 18, uint32(uint160(_token)));
|
|
64
|
+
|
|
65
|
+
JBRuleset memory ruleset = JBRuleset({
|
|
66
|
+
cycleNumber: 1,
|
|
67
|
+
id: 1,
|
|
68
|
+
basedOnId: 0,
|
|
69
|
+
start: uint48(block.timestamp),
|
|
70
|
+
duration: 0,
|
|
71
|
+
weight: 0,
|
|
72
|
+
weightCutPercent: 0,
|
|
73
|
+
approvalHook: IJBRulesetApprovalHook(address(0)),
|
|
74
|
+
metadata: 0
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
JBCashOutHookSpecification[] memory specs = new JBCashOutHookSpecification[](1);
|
|
78
|
+
specs[0] = JBCashOutHookSpecification({
|
|
79
|
+
hook: IJBCashOutHook(makeAddr("hook")), noop: false, amount: 321, metadata: hex"5678"
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
JBAccountingContext memory accountingContext =
|
|
83
|
+
JBAccountingContext({token: _token, decimals: 18, currency: uint32(uint160(_token))});
|
|
84
|
+
JBAccountingContext[] memory accountingContexts = new JBAccountingContext[](1);
|
|
85
|
+
accountingContexts[0] = accountingContext;
|
|
86
|
+
|
|
87
|
+
mockExpect(
|
|
88
|
+
address(feelessAddresses), abi.encodeCall(IJBFeelessAddresses.isFeeless, (_beneficiary)), abi.encode(true)
|
|
89
|
+
);
|
|
90
|
+
|
|
91
|
+
mockExpect(
|
|
92
|
+
address(store),
|
|
93
|
+
abi.encodeCall(
|
|
94
|
+
IJBTerminalStore.previewCashOutFrom,
|
|
95
|
+
(_holder, _projectId, _cashOutCount, accountingContext, accountingContexts, true, bytes(""))
|
|
96
|
+
),
|
|
97
|
+
abi.encode(ruleset, 999, 1234, specs)
|
|
98
|
+
);
|
|
99
|
+
|
|
100
|
+
(
|
|
101
|
+
JBRuleset memory previewRuleset,
|
|
102
|
+
uint256 reclaimAmount,
|
|
103
|
+
uint256 cashOutTaxRate,
|
|
104
|
+
JBCashOutHookSpecification[] memory previewSpecs
|
|
105
|
+
) = JBMultiTerminal(address(_terminal))
|
|
106
|
+
.previewCashOutFrom(_holder, _projectId, _cashOutCount, _token, _beneficiary, "");
|
|
107
|
+
|
|
108
|
+
assertEq(previewRuleset.id, ruleset.id);
|
|
109
|
+
assertEq(reclaimAmount, 999);
|
|
110
|
+
assertEq(cashOutTaxRate, 1234);
|
|
111
|
+
assertEq(previewSpecs.length, 1);
|
|
112
|
+
assertEq(address(previewSpecs[0].hook), address(specs[0].hook));
|
|
113
|
+
assertEq(previewSpecs[0].amount, specs[0].amount);
|
|
114
|
+
assertEq(previewSpecs[0].metadata, specs[0].metadata);
|
|
115
|
+
}
|
|
116
|
+
}
|