@bananapus/core-v6 0.0.30 → 0.0.31

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 (44) 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 +11 -3
  16. package/src/JBDirectory.sol +1 -0
  17. package/src/JBMultiTerminal.sol +126 -72
  18. package/src/JBRulesets.sol +2 -1
  19. package/src/JBTerminalStore.sol +22 -11
  20. package/src/abstract/JBControlled.sol +7 -1
  21. package/src/abstract/JBPermissioned.sol +1 -1
  22. package/src/interfaces/IJBRulesetDataHook.sol +5 -4
  23. package/src/libraries/JBCashOuts.sol +1 -1
  24. package/src/libraries/JBConstants.sol +1 -1
  25. package/src/libraries/JBCurrencyIds.sol +1 -1
  26. package/src/libraries/JBFees.sol +1 -1
  27. package/src/libraries/JBFixedPointNumber.sol +1 -1
  28. package/src/libraries/JBMetadataResolver.sol +1 -1
  29. package/src/libraries/JBPayoutSplitGroupLib.sol +3 -1
  30. package/src/libraries/JBRulesetMetadataResolver.sol +1 -1
  31. package/src/libraries/JBSplitGroupIds.sol +1 -1
  32. package/src/libraries/JBSurplus.sol +1 -1
  33. package/src/structs/JBSplit.sol +4 -1
  34. package/test/TestForwardedTokenConsumption.sol +418 -0
  35. package/test/audit/CycledSurplusAllowanceReset.t.sol +184 -0
  36. package/test/units/static/JBController/TestPreviewMintOf.sol +5 -3
  37. package/test/units/static/JBMultiTerminal/TestCashOutTokensOf.sol +15 -11
  38. package/test/units/static/JBMultiTerminal/TestExecutePayout.sol +6 -0
  39. package/test/units/static/JBMultiTerminal/TestExecuteProcessFee.sol +3 -0
  40. package/test/units/static/JBMultiTerminal/TestMigrateBalanceOf.sol +3 -0
  41. package/test/units/static/JBMultiTerminal/TestPay.sol +7 -15
  42. package/test/units/static/JBMultiTerminal/TestSendPayoutsOf.sol +1 -1
  43. package/test/units/static/JBMultiTerminal/TestUseAllowanceOf.sol +1 -1
  44. 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
+ }
@@ -17,9 +17,11 @@ contract TestPreviewMintOf_Local is JBControllerSetup {
17
17
  super.controllerSetup();
18
18
  }
19
19
 
20
- function test_RevertsWhenTokenCountIsZero() external {
21
- vm.expectRevert(JBController.JBController_ZeroTokensToMint.selector);
22
- _controller.previewMintOf(_projectId, 0, true);
20
+ function test_ReturnsZeroCountsWhenTokenCountIsZero() external view {
21
+ (uint256 beneficiaryTokenCount, uint256 reservedTokenCount) = _controller.previewMintOf(_projectId, 0, true);
22
+
23
+ assertEq(beneficiaryTokenCount, 0);
24
+ assertEq(reservedTokenCount, 0);
23
25
  }
24
26
 
25
27
  function test_ReturnsSplitCountsWhenUsingReservedPercent() external {
@@ -220,11 +220,7 @@ contract TestCashOutTokensOf_Local is JBMultiTerminalSetup {
220
220
  // forge-lint: disable-next-line(unsafe-typecast)
221
221
  _acceptToken(_mockToken, 18, uint32(uint160(_mockToken)));
222
222
 
223
- vm.expectRevert(
224
- abi.encodeWithSelector(
225
- JBMultiTerminal.JBMultiTerminal_UnderMinTokensReclaimed.selector, reclaimAmount, 1e18
226
- )
227
- );
223
+ vm.expectRevert(abi.encodeWithSelector(JBMultiTerminal.JBMultiTerminal_UnderMin.selector, reclaimAmount, 1e18));
228
224
  vm.prank(_bene);
229
225
  _terminal.cashOutTokensOf(_holder, _projectId, _defaultAmount, _mockToken, 1e18, _bene, ""); // minReclaimAmount
230
226
  // = 1e18 but only 1e9 reclaimed
@@ -431,10 +427,14 @@ contract TestCashOutTokensOf_Local is JBMultiTerminalSetup {
431
427
  // ensure approval is set via forceApprove
432
428
  vm.expectCall(address(_mockToken2), abi.encodeCall(IERC20.approve, (address(_mockHook), _defaultAmount)));
433
429
 
434
- _acceptToken(address(_mockToken2), 18, uint32(uint160(address(_mockToken2))));
430
+ // Mock the temporary allowance as fully consumed by the hook so the cleanup guard passes.
431
+ vm.mockCall(
432
+ address(_mockToken2),
433
+ abi.encodeCall(IERC20.allowance, (address(_terminal), address(_mockHook))),
434
+ abi.encode(0)
435
+ );
435
436
 
436
- vm.expectEmit();
437
- emit IJBCashOutTerminal.HookAfterRecordCashOut(_mockHook, context, _defaultAmount, 0, address(_bene));
437
+ _acceptToken(address(_mockToken2), 18, uint32(uint160(address(_mockToken2))));
438
438
 
439
439
  vm.prank(_bene);
440
440
  _terminal.cashOutTokensOf(_holder, _projectId, _defaultAmount, address(_mockToken2), _minReclaimed, _bene, "");
@@ -548,6 +548,13 @@ contract TestCashOutTokensOf_Local is JBMultiTerminalSetup {
548
548
 
549
549
  mockExpect(address(_mockHook), abi.encodeCall(IJBCashOutHook.afterCashOutRecordedWith, (context)), "");
550
550
 
551
+ // Mock the temporary allowance as fully consumed by the hook so the cleanup guard passes.
552
+ vm.mockCall(
553
+ address(_mockToken2),
554
+ abi.encodeCall(IERC20.allowance, (address(_terminal), address(_mockHook))),
555
+ abi.encode(0)
556
+ );
557
+
551
558
  // primary terminal check
552
559
  mockExpect(
553
560
  address(directory),
@@ -567,9 +574,6 @@ contract TestCashOutTokensOf_Local is JBMultiTerminalSetup {
567
574
 
568
575
  _acceptToken(address(_mockToken2), 18, uint32(uint160(address(_mockToken2))));
569
576
 
570
- vm.expectEmit();
571
- emit IJBCashOutTerminal.HookAfterRecordCashOut(_mockHook, context, passedAfterTax, hookTax, address(_bene));
572
-
573
577
  vm.prank(_bene);
574
578
  _terminal.cashOutTokensOf(_holder, _projectId, _defaultAmount, address(_mockToken2), _minReclaimed, _bene, "");
575
579
  }
@@ -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