@bananapus/core-v6 0.0.30 → 0.0.32

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 (49) hide show
  1. package/ADMINISTRATION.md +43 -13
  2. package/ARCHITECTURE.md +62 -137
  3. package/AUDIT_INSTRUCTIONS.md +149 -428
  4. package/CHANGELOG.md +73 -0
  5. package/README.md +90 -201
  6. package/RISKS.md +27 -12
  7. package/SKILLS.md +31 -441
  8. package/STYLE_GUIDE.md +52 -19
  9. package/USER_JOURNEYS.md +76 -627
  10. package/package.json +1 -2
  11. package/references/entrypoints.md +160 -0
  12. package/references/types-errors-events.md +297 -0
  13. package/script/Deploy.s.sol +7 -2
  14. package/script/DeployPeriphery.s.sol +51 -4
  15. package/src/JBController.sol +45 -17
  16. package/src/JBDirectory.sol +26 -13
  17. package/src/JBFundAccessLimits.sol +28 -7
  18. package/src/JBMultiTerminal.sol +180 -86
  19. package/src/JBPermissions.sol +17 -17
  20. package/src/JBRulesets.sol +82 -23
  21. package/src/JBSplits.sol +31 -12
  22. package/src/JBTerminalStore.sol +137 -53
  23. package/src/JBTokens.sol +5 -2
  24. package/src/abstract/JBControlled.sol +10 -3
  25. package/src/abstract/JBPermissioned.sol +1 -1
  26. package/src/interfaces/IJBRulesetDataHook.sol +5 -4
  27. package/src/libraries/JBCashOuts.sol +1 -1
  28. package/src/libraries/JBConstants.sol +1 -1
  29. package/src/libraries/JBCurrencyIds.sol +1 -1
  30. package/src/libraries/JBFees.sol +1 -1
  31. package/src/libraries/JBFixedPointNumber.sol +1 -1
  32. package/src/libraries/JBMetadataResolver.sol +5 -2
  33. package/src/libraries/JBPayoutSplitGroupLib.sol +7 -2
  34. package/src/libraries/JBRulesetMetadataResolver.sol +1 -1
  35. package/src/libraries/JBSplitGroupIds.sol +1 -1
  36. package/src/libraries/JBSurplus.sol +5 -2
  37. package/src/structs/JBSplit.sol +4 -1
  38. package/test/TestForwardedTokenConsumption.sol +419 -0
  39. package/test/audit/CrossTerminalSurplusSpoof.t.sol +140 -0
  40. package/test/audit/CycledSurplusAllowanceReset.t.sol +184 -0
  41. package/test/units/static/JBController/TestPreviewMintOf.sol +5 -4
  42. package/test/units/static/JBMultiTerminal/TestCashOutTokensOf.sol +15 -12
  43. package/test/units/static/JBMultiTerminal/TestExecutePayout.sol +6 -0
  44. package/test/units/static/JBMultiTerminal/TestExecuteProcessFee.sol +3 -0
  45. package/test/units/static/JBMultiTerminal/TestMigrateBalanceOf.sol +3 -0
  46. package/test/units/static/JBMultiTerminal/TestPay.sol +7 -15
  47. package/test/units/static/JBMultiTerminal/TestSendPayoutsOf.sol +1 -1
  48. package/test/units/static/JBMultiTerminal/TestUseAllowanceOf.sol +1 -1
  49. package/CHANGE_LOG.md +0 -479
@@ -0,0 +1,184 @@
1
+ // SPDX-License-Identifier: MIT
2
+ pragma solidity ^0.8.6;
3
+
4
+ import {TestBaseWorkflow} from "../helpers/TestBaseWorkflow.sol";
5
+ import {IJBRulesetApprovalHook} from "../../src/interfaces/IJBRulesetApprovalHook.sol";
6
+ import {IJBTerminal} from "../../src/interfaces/IJBTerminal.sol";
7
+ import {JBConstants} from "../../src/libraries/JBConstants.sol";
8
+ import {JBAccountingContext} from "../../src/structs/JBAccountingContext.sol";
9
+ import {JBCurrencyAmount} from "../../src/structs/JBCurrencyAmount.sol";
10
+ import {JBFundAccessLimitGroup} from "../../src/structs/JBFundAccessLimitGroup.sol";
11
+ import {JBRuleset} from "../../src/structs/JBRuleset.sol";
12
+ import {JBRulesetConfig} from "../../src/structs/JBRulesetConfig.sol";
13
+ import {JBRulesetMetadata} from "../../src/structs/JBRulesetMetadata.sol";
14
+ import {JBSplitGroup} from "../../src/structs/JBSplitGroup.sol";
15
+ import {JBTerminalConfig} from "../../src/structs/JBTerminalConfig.sol";
16
+
17
+ contract CycledSurplusAllowanceResetTest is TestBaseWorkflow {
18
+ function test_surplusAllowanceDoesNotResetAcrossImplicitCycles() external {
19
+ _launchFeeProject();
20
+
21
+ JBRulesetConfig[] memory rulesetConfigurations = new JBRulesetConfig[](1);
22
+ rulesetConfigurations[0] = _makeRulesetConfig({
23
+ duration: 1 days,
24
+ metadata: _defaultMetadata(),
25
+ splitGroups: new JBSplitGroup[](0),
26
+ fundAccessLimitGroups: _makeFundAccessLimitGroup(1 ether)
27
+ });
28
+
29
+ uint256 projectId = jbController()
30
+ .launchProjectFor({
31
+ owner: multisig(),
32
+ projectUri: "cycle-allowance",
33
+ rulesetConfigurations: rulesetConfigurations,
34
+ terminalConfigurations: _makeTerminalConfig(),
35
+ memo: ""
36
+ });
37
+
38
+ address payer = makeAddr("payer");
39
+ vm.deal(payer, 5 ether);
40
+ vm.prank(payer);
41
+ jbMultiTerminal().pay{value: 5 ether}({
42
+ projectId: projectId,
43
+ amount: 5 ether,
44
+ token: JBConstants.NATIVE_TOKEN,
45
+ beneficiary: payer,
46
+ minReturnedTokens: 0,
47
+ memo: "",
48
+ metadata: new bytes(0)
49
+ });
50
+
51
+ vm.prank(multisig());
52
+ jbMultiTerminal()
53
+ .useAllowanceOf({
54
+ projectId: projectId,
55
+ token: JBConstants.NATIVE_TOKEN,
56
+ amount: 1 ether,
57
+ currency: uint32(uint160(JBConstants.NATIVE_TOKEN)),
58
+ minTokensPaidOut: 0,
59
+ beneficiary: payable(makeAddr("cycle1-beneficiary")),
60
+ feeBeneficiary: payable(multisig()),
61
+ memo: ""
62
+ });
63
+
64
+ JBRuleset memory cycleOneRuleset = jbRulesets().currentOf(projectId);
65
+ assertEq(cycleOneRuleset.cycleNumber, 1, "expected first cycle before warp");
66
+
67
+ vm.warp(block.timestamp + 1 days + 1);
68
+
69
+ JBRuleset memory cycledRuleset = jbRulesets().currentOf(projectId);
70
+ assertEq(cycledRuleset.cycleNumber, 2, "expected implicit second cycle");
71
+ assertEq(cycledRuleset.id, cycleOneRuleset.id, "cycled ruleset reuses base ruleset id");
72
+
73
+ vm.expectRevert();
74
+ vm.prank(multisig());
75
+ jbMultiTerminal()
76
+ .useAllowanceOf({
77
+ projectId: projectId,
78
+ token: JBConstants.NATIVE_TOKEN,
79
+ amount: 1 ether,
80
+ currency: uint32(uint160(JBConstants.NATIVE_TOKEN)),
81
+ minTokensPaidOut: 0,
82
+ beneficiary: payable(makeAddr("cycle2-beneficiary")),
83
+ feeBeneficiary: payable(multisig()),
84
+ memo: ""
85
+ });
86
+ }
87
+
88
+ function _launchFeeProject() private returns (uint256) {
89
+ JBRulesetConfig[] memory rulesetConfigurations = new JBRulesetConfig[](1);
90
+ rulesetConfigurations[0] = _makeRulesetConfig({
91
+ duration: 1 days,
92
+ metadata: _defaultMetadata(),
93
+ splitGroups: new JBSplitGroup[](0),
94
+ fundAccessLimitGroups: new JBFundAccessLimitGroup[](0)
95
+ });
96
+
97
+ return jbController()
98
+ .launchProjectFor({
99
+ owner: multisig(),
100
+ projectUri: "fee-project",
101
+ rulesetConfigurations: rulesetConfigurations,
102
+ terminalConfigurations: _makeTerminalConfig(),
103
+ memo: ""
104
+ });
105
+ }
106
+
107
+ function _defaultMetadata() private pure returns (JBRulesetMetadata memory) {
108
+ return JBRulesetMetadata({
109
+ reservedPercent: 0,
110
+ cashOutTaxRate: 0,
111
+ baseCurrency: uint32(uint160(JBConstants.NATIVE_TOKEN)),
112
+ pausePay: false,
113
+ pauseCreditTransfers: false,
114
+ allowOwnerMinting: true,
115
+ allowSetCustomToken: true,
116
+ allowTerminalMigration: true,
117
+ allowSetTerminals: true,
118
+ ownerMustSendPayouts: false,
119
+ allowSetController: true,
120
+ allowAddAccountingContext: true,
121
+ allowAddPriceFeed: true,
122
+ holdFees: false,
123
+ useTotalSurplusForCashOuts: false,
124
+ useDataHookForPay: false,
125
+ useDataHookForCashOut: false,
126
+ dataHook: address(0),
127
+ metadata: 0
128
+ });
129
+ }
130
+
131
+ function _makeTerminalConfig() private view returns (JBTerminalConfig[] memory terminalConfigurations) {
132
+ terminalConfigurations = new JBTerminalConfig[](1);
133
+
134
+ JBAccountingContext[] memory accountingContexts = new JBAccountingContext[](1);
135
+ accountingContexts[0] = JBAccountingContext({
136
+ token: JBConstants.NATIVE_TOKEN, decimals: 18, currency: uint32(uint160(JBConstants.NATIVE_TOKEN))
137
+ });
138
+
139
+ terminalConfigurations[0] = JBTerminalConfig({
140
+ terminal: IJBTerminal(address(jbMultiTerminal())), accountingContextsToAccept: accountingContexts
141
+ });
142
+ }
143
+
144
+ function _makeFundAccessLimitGroup(uint224 surplusAllowanceAmount)
145
+ private
146
+ view
147
+ returns (JBFundAccessLimitGroup[] memory groups)
148
+ {
149
+ groups = new JBFundAccessLimitGroup[](1);
150
+
151
+ JBCurrencyAmount[] memory surplusAllowances = new JBCurrencyAmount[](1);
152
+ surplusAllowances[0] =
153
+ JBCurrencyAmount({amount: surplusAllowanceAmount, currency: uint32(uint160(JBConstants.NATIVE_TOKEN))});
154
+
155
+ groups[0] = JBFundAccessLimitGroup({
156
+ terminal: address(jbMultiTerminal()),
157
+ token: JBConstants.NATIVE_TOKEN,
158
+ payoutLimits: new JBCurrencyAmount[](0),
159
+ surplusAllowances: surplusAllowances
160
+ });
161
+ }
162
+
163
+ function _makeRulesetConfig(
164
+ uint32 duration,
165
+ JBRulesetMetadata memory metadata,
166
+ JBSplitGroup[] memory splitGroups,
167
+ JBFundAccessLimitGroup[] memory fundAccessLimitGroups
168
+ )
169
+ private
170
+ pure
171
+ returns (JBRulesetConfig memory)
172
+ {
173
+ return JBRulesetConfig({
174
+ mustStartAtOrAfter: 0,
175
+ duration: duration,
176
+ weight: 1000 * 10 ** 18,
177
+ weightCutPercent: 0,
178
+ approvalHook: IJBRulesetApprovalHook(address(0)),
179
+ metadata: metadata,
180
+ splitGroups: splitGroups,
181
+ fundAccessLimitGroups: fundAccessLimitGroups
182
+ });
183
+ }
184
+ }
@@ -1,7 +1,6 @@
1
1
  // SPDX-License-Identifier: MIT
2
2
  pragma solidity 0.8.28;
3
3
 
4
- import {JBController} from "../../../../src/JBController.sol";
5
4
  import {IJBRulesetApprovalHook} from "../../../../src/interfaces/IJBRulesetApprovalHook.sol";
6
5
  import {IJBRulesets} from "../../../../src/interfaces/IJBRulesets.sol";
7
6
  import {JBConstants} from "../../../../src/libraries/JBConstants.sol";
@@ -17,9 +16,11 @@ contract TestPreviewMintOf_Local is JBControllerSetup {
17
16
  super.controllerSetup();
18
17
  }
19
18
 
20
- function test_RevertsWhenTokenCountIsZero() external {
21
- vm.expectRevert(JBController.JBController_ZeroTokensToMint.selector);
22
- _controller.previewMintOf(_projectId, 0, true);
19
+ function test_ReturnsZeroCountsWhenTokenCountIsZero() external view {
20
+ (uint256 beneficiaryTokenCount, uint256 reservedTokenCount) = _controller.previewMintOf(_projectId, 0, true);
21
+
22
+ assertEq(beneficiaryTokenCount, 0);
23
+ assertEq(reservedTokenCount, 0);
23
24
  }
24
25
 
25
26
  function test_ReturnsSplitCountsWhenUsingReservedPercent() external {
@@ -5,7 +5,6 @@ import {MockERC20} from "../../../mock/MockERC20.sol";
5
5
  import {JBMultiTerminal} from "../../../../src/JBMultiTerminal.sol";
6
6
  import {JBPermissioned} from "../../../../src/abstract/JBPermissioned.sol";
7
7
  import {IJBCashOutHook} from "../../../../src/interfaces/IJBCashOutHook.sol";
8
- import {IJBCashOutTerminal} from "../../../../src/interfaces/IJBCashOutTerminal.sol";
9
8
  import {IJBController} from "../../../../src/interfaces/IJBController.sol";
10
9
  import {IJBDirectory} from "../../../../src/interfaces/IJBDirectory.sol";
11
10
  import {IJBFeelessAddresses} from "../../../../src/interfaces/IJBFeelessAddresses.sol";
@@ -220,11 +219,7 @@ contract TestCashOutTokensOf_Local is JBMultiTerminalSetup {
220
219
  // forge-lint: disable-next-line(unsafe-typecast)
221
220
  _acceptToken(_mockToken, 18, uint32(uint160(_mockToken)));
222
221
 
223
- vm.expectRevert(
224
- abi.encodeWithSelector(
225
- JBMultiTerminal.JBMultiTerminal_UnderMinTokensReclaimed.selector, reclaimAmount, 1e18
226
- )
227
- );
222
+ vm.expectRevert(abi.encodeWithSelector(JBMultiTerminal.JBMultiTerminal_UnderMin.selector, reclaimAmount, 1e18));
228
223
  vm.prank(_bene);
229
224
  _terminal.cashOutTokensOf(_holder, _projectId, _defaultAmount, _mockToken, 1e18, _bene, ""); // minReclaimAmount
230
225
  // = 1e18 but only 1e9 reclaimed
@@ -431,10 +426,14 @@ contract TestCashOutTokensOf_Local is JBMultiTerminalSetup {
431
426
  // ensure approval is set via forceApprove
432
427
  vm.expectCall(address(_mockToken2), abi.encodeCall(IERC20.approve, (address(_mockHook), _defaultAmount)));
433
428
 
434
- _acceptToken(address(_mockToken2), 18, uint32(uint160(address(_mockToken2))));
429
+ // Mock the temporary allowance as fully consumed by the hook so the cleanup guard passes.
430
+ vm.mockCall(
431
+ address(_mockToken2),
432
+ abi.encodeCall(IERC20.allowance, (address(_terminal), address(_mockHook))),
433
+ abi.encode(0)
434
+ );
435
435
 
436
- vm.expectEmit();
437
- emit IJBCashOutTerminal.HookAfterRecordCashOut(_mockHook, context, _defaultAmount, 0, address(_bene));
436
+ _acceptToken(address(_mockToken2), 18, uint32(uint160(address(_mockToken2))));
438
437
 
439
438
  vm.prank(_bene);
440
439
  _terminal.cashOutTokensOf(_holder, _projectId, _defaultAmount, address(_mockToken2), _minReclaimed, _bene, "");
@@ -548,6 +547,13 @@ contract TestCashOutTokensOf_Local is JBMultiTerminalSetup {
548
547
 
549
548
  mockExpect(address(_mockHook), abi.encodeCall(IJBCashOutHook.afterCashOutRecordedWith, (context)), "");
550
549
 
550
+ // Mock the temporary allowance as fully consumed by the hook so the cleanup guard passes.
551
+ vm.mockCall(
552
+ address(_mockToken2),
553
+ abi.encodeCall(IERC20.allowance, (address(_terminal), address(_mockHook))),
554
+ abi.encode(0)
555
+ );
556
+
551
557
  // primary terminal check
552
558
  mockExpect(
553
559
  address(directory),
@@ -567,9 +573,6 @@ contract TestCashOutTokensOf_Local is JBMultiTerminalSetup {
567
573
 
568
574
  _acceptToken(address(_mockToken2), 18, uint32(uint160(address(_mockToken2))));
569
575
 
570
- vm.expectEmit();
571
- emit IJBCashOutTerminal.HookAfterRecordCashOut(_mockHook, context, passedAfterTax, hookTax, address(_bene));
572
-
573
576
  vm.prank(_bene);
574
577
  _terminal.cashOutTokensOf(_holder, _projectId, _defaultAmount, address(_mockToken2), _minReclaimed, _bene, "");
575
578
  }
@@ -335,6 +335,9 @@ contract TestExecutePayout_Local is JBMultiTerminalSetup {
335
335
  // mock call for SafeERC20s forceApprove approval
336
336
  mockExpect(_usdc, abi.encodeCall(IERC20.approve, (_mockSecondTerminal, amountAfterTax)), "");
337
337
 
338
+ // Mock the forwarded allowance as fully consumed by the recipient terminal.
339
+ vm.mockCall(_usdc, abi.encodeCall(IERC20.allowance, (address(_terminal), _mockSecondTerminal)), abi.encode(0));
340
+
338
341
  // mock call to second terminals addToBalanceOf
339
342
  mockExpect(
340
343
  _mockSecondTerminal,
@@ -419,6 +422,9 @@ contract TestExecutePayout_Local is JBMultiTerminalSetup {
419
422
  // mock call for SafeERC20s forceApprove approval
420
423
  mockExpect(_usdc, abi.encodeCall(IERC20.approve, (_mockSecondTerminal, amountAfterTax)), "");
421
424
 
425
+ // Mock the forwarded allowance as fully consumed by the recipient terminal.
426
+ vm.mockCall(_usdc, abi.encodeCall(IERC20.allowance, (address(_terminal), _mockSecondTerminal)), abi.encode(0));
427
+
422
428
  // mock call to second terminals pay function
423
429
  mockExpect(
424
430
  _mockSecondTerminal,
@@ -73,6 +73,9 @@ contract TestExecuteProcessFee_Local is JBMultiTerminalSetup {
73
73
  // mock approval call for forceApprove
74
74
  mockExpect(_usdc, abi.encodeCall(IERC20.approve, (address(_feeTerminal), _defaultAmount)), "");
75
75
 
76
+ // Mock the forwarded allowance as fully consumed by the fee terminal.
77
+ vm.mockCall(_usdc, abi.encodeCall(IERC20.allowance, (address(_terminal), address(_feeTerminal))), abi.encode(0));
78
+
76
79
  // mock pay call to fee terminal
77
80
  mockExpect(
78
81
  address(_feeTerminal),
@@ -135,6 +135,9 @@ contract TestMigrateBalanceOf_Local is JBMultiTerminalSetup {
135
135
  // mock call for SafeERC20s forceApprove approval
136
136
  mockExpect(_usdc, abi.encodeCall(IERC20.approve, (address(_newTerminal), _defaultAmount)), "");
137
137
 
138
+ // Mock the forwarded allowance as fully consumed by the destination terminal.
139
+ vm.mockCall(_usdc, abi.encodeCall(IERC20.allowance, (address(_terminal), address(_newTerminal))), abi.encode(0));
140
+
138
141
  // mock call to new terminal addToBalance
139
142
  mockExpect(
140
143
  address(_newTerminal),
@@ -16,6 +16,7 @@ import {JBAfterPayRecordedContext} from "../../../../src/structs/JBAfterPayRecor
16
16
  import {JBPayHookSpecification} from "../../../../src/structs/JBPayHookSpecification.sol";
17
17
  import {JBRuleset} from "../../../../src/structs/JBRuleset.sol";
18
18
  import {JBTokenAmount} from "../../../../src/structs/JBTokenAmount.sol";
19
+ import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
19
20
  import {IERC721} from "@openzeppelin/contracts/token/ERC721/IERC721.sol";
20
21
  import {JBMultiTerminalSetup} from "./JBMultiTerminalSetup.sol";
21
22
 
@@ -138,7 +139,7 @@ contract TestPay_Local is JBMultiTerminalSetup {
138
139
 
139
140
  mockExpect(address(tokens), abi.encodeCall(IJBTokens.totalBalanceOf, (_bene, _projectId)), abi.encode(0));
140
141
 
141
- vm.expectRevert(abi.encodeWithSelector(JBMultiTerminal.JBMultiTerminal_UnderMinReturnedTokens.selector, 0, 1));
142
+ vm.expectRevert(abi.encodeWithSelector(JBMultiTerminal.JBMultiTerminal_UnderMin.selector, 0, 1));
142
143
  _terminal.pay{value: 1e18}({
143
144
  projectId: _projectId,
144
145
  token: _native,
@@ -298,21 +299,12 @@ contract TestPay_Local is JBMultiTerminalSetup {
298
299
  // mock call to hook
299
300
  mockExpect(address(_mockHook), abi.encodeCall(IJBPayHook.afterPayRecordedWith, (context)), "");
300
301
 
301
- vm.expectEmit();
302
- emit IJBTerminal.Pay(
303
- returnedRuleset.id,
304
- returnedRuleset.cycleNumber,
305
- _projectId,
306
- address(this),
307
- _bene,
308
- _defaultAmount,
309
- _mintAmount,
310
- "",
311
- bytes(""),
312
- address(this)
302
+ // Mock the temporary allowance as fully consumed by the hook so the cleanup guard passes.
303
+ vm.mockCall(
304
+ address(_mockToken),
305
+ abi.encodeCall(IERC20.allowance, (address(_terminal), address(_mockHook))),
306
+ abi.encode(0)
313
307
  );
314
- vm.expectEmit();
315
- emit IJBTerminal.HookAfterRecordPay(_mockHook, context, _defaultAmount, address(this));
316
308
 
317
309
  // Data for subsequent calls made for balance checks
318
310
  bytes[] memory subsequentReturns = new bytes[](2);
@@ -71,7 +71,7 @@ contract TestSendPayoutsOf_Local is JBMultiTerminalSetup {
71
71
  abi.encode(address(_terminal))
72
72
  );
73
73
 
74
- vm.expectRevert(abi.encodeWithSelector(JBMultiTerminal.JBMultiTerminal_UnderMinTokensPaidOut.selector, 0, 1));
74
+ vm.expectRevert(abi.encodeWithSelector(JBMultiTerminal.JBMultiTerminal_UnderMin.selector, 0, 1));
75
75
  _terminal.sendPayoutsOf(_projectId, address(0), 0, 0, 1);
76
76
  }
77
77
 
@@ -62,7 +62,7 @@ contract TestUseAllowanceOf_Local is JBMultiTerminalSetup {
62
62
  address(feelessAddresses), abi.encodeCall(IJBFeelessAddresses.isFeeless, (address(this))), abi.encode(true)
63
63
  );
64
64
 
65
- vm.expectRevert(abi.encodeWithSelector(JBMultiTerminal.JBMultiTerminal_UnderMinTokensPaidOut.selector, 0, 1));
65
+ vm.expectRevert(abi.encodeWithSelector(JBMultiTerminal.JBMultiTerminal_UnderMin.selector, 0, 1));
66
66
  _terminal.useAllowanceOf(_projectId, address(0), 0, 0, 1, payable(address(this)), payable(address(this)), "");
67
67
  }
68
68