@bananapus/core-v6 0.0.19 → 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 +17 -4
- package/README.md +12 -2
- package/RISKS.md +10 -2
- package/SKILLS.md +5 -2
- package/USER_JOURNEYS.md +4 -2
- 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 +17 -7
- 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 +1 -5
- 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/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/TestPreviewCashOutFrom.sol +96 -4
- package/test/units/static/JBTerminalStore/TestPreviewPayFrom.sol +81 -32
- package/test/units/static/JBTerminalStore/TestRecordCashOutsFor.sol +113 -2
- package/test/units/static/JBTerminalStore/TestRecordPaymentFrom.sol +227 -5
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
// SPDX-License-Identifier: MIT
|
|
2
|
+
pragma solidity ^0.8.26;
|
|
3
|
+
|
|
4
|
+
import {TestTerminalPreviewParity_Local} from "../TestTerminalPreviewParity.sol";
|
|
5
|
+
import {IJBController} from "../../src/interfaces/IJBController.sol";
|
|
6
|
+
import {IJBTerminal} from "../../src/interfaces/IJBTerminal.sol";
|
|
7
|
+
import {IJBCashOutTerminal} from "../../src/interfaces/IJBCashOutTerminal.sol";
|
|
8
|
+
import {JBConstants} from "../../src/libraries/JBConstants.sol";
|
|
9
|
+
import {JBCashOutHookSpecification} from "../../src/structs/JBCashOutHookSpecification.sol";
|
|
10
|
+
import {JBPayHookSpecification} from "../../src/structs/JBPayHookSpecification.sol";
|
|
11
|
+
import {JBRuleset} from "../../src/structs/JBRuleset.sol";
|
|
12
|
+
|
|
13
|
+
contract TestTerminalPreviewParityFork is TestTerminalPreviewParity_Local {
|
|
14
|
+
uint256 internal constant FORK_BLOCK = 22_000_000;
|
|
15
|
+
|
|
16
|
+
function setUp() public override {
|
|
17
|
+
vm.createSelectFork("ethereum", FORK_BLOCK);
|
|
18
|
+
super.setUp();
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function testForkPreviewPayForMatchesPay() external {
|
|
22
|
+
_launchFeeProject();
|
|
23
|
+
uint256 projectId = _launchProject(2500, 10_000);
|
|
24
|
+
|
|
25
|
+
(
|
|
26
|
+
JBRuleset memory ruleset,
|
|
27
|
+
uint256 previewBeneficiaryTokenCount,
|
|
28
|
+
uint256 previewReservedTokenCount,
|
|
29
|
+
JBPayHookSpecification[] memory hookSpecifications
|
|
30
|
+
) = _terminal.previewPayFor(projectId, JBConstants.NATIVE_TOKEN, 1 ether, _beneficiary, "");
|
|
31
|
+
|
|
32
|
+
assertEq(hookSpecifications.length, 0);
|
|
33
|
+
|
|
34
|
+
uint256 balanceBefore = jbTokens().totalBalanceOf(_beneficiary, projectId);
|
|
35
|
+
uint256 reservedBefore = _controller.pendingReservedTokenBalanceOf(projectId);
|
|
36
|
+
|
|
37
|
+
address payer = makeAddr("forkPayer");
|
|
38
|
+
vm.deal(payer, 1 ether);
|
|
39
|
+
|
|
40
|
+
vm.expectEmit();
|
|
41
|
+
emit IJBTerminal.Pay(
|
|
42
|
+
ruleset.id,
|
|
43
|
+
ruleset.cycleNumber,
|
|
44
|
+
projectId,
|
|
45
|
+
payer,
|
|
46
|
+
_beneficiary,
|
|
47
|
+
1 ether,
|
|
48
|
+
previewBeneficiaryTokenCount,
|
|
49
|
+
"",
|
|
50
|
+
"",
|
|
51
|
+
payer
|
|
52
|
+
);
|
|
53
|
+
|
|
54
|
+
vm.prank(payer);
|
|
55
|
+
uint256 beneficiaryTokenCount =
|
|
56
|
+
_terminal.pay{value: 1 ether}(projectId, JBConstants.NATIVE_TOKEN, 1 ether, _beneficiary, 0, "", "");
|
|
57
|
+
|
|
58
|
+
assertEq(beneficiaryTokenCount, previewBeneficiaryTokenCount);
|
|
59
|
+
assertEq(jbTokens().totalBalanceOf(_beneficiary, projectId) - balanceBefore, previewBeneficiaryTokenCount);
|
|
60
|
+
assertEq(_controller.pendingReservedTokenBalanceOf(projectId) - reservedBefore, previewReservedTokenCount);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function testForkPreviewCashOutFromMatchesCashOut() external {
|
|
64
|
+
_launchFeeProject();
|
|
65
|
+
uint256 projectId = _launchProject(0, 5000);
|
|
66
|
+
|
|
67
|
+
vm.prank(_projectOwner);
|
|
68
|
+
jbFeelessAddresses().setFeelessAddress(_beneficiary, true);
|
|
69
|
+
|
|
70
|
+
vm.deal(_beneficiary, 1 ether);
|
|
71
|
+
vm.prank(_beneficiary);
|
|
72
|
+
uint256 minted =
|
|
73
|
+
_terminal.pay{value: 1 ether}(projectId, JBConstants.NATIVE_TOKEN, 1 ether, _beneficiary, 0, "", "");
|
|
74
|
+
|
|
75
|
+
uint256 cashOutCount = minted / 2;
|
|
76
|
+
|
|
77
|
+
(
|
|
78
|
+
JBRuleset memory ruleset,
|
|
79
|
+
uint256 previewReclaimAmount,
|
|
80
|
+
uint256 previewCashOutTaxRate,
|
|
81
|
+
JBCashOutHookSpecification[] memory hookSpecifications
|
|
82
|
+
) = _terminal.previewCashOutFrom(
|
|
83
|
+
_beneficiary, projectId, cashOutCount, JBConstants.NATIVE_TOKEN, payable(_beneficiary), ""
|
|
84
|
+
);
|
|
85
|
+
|
|
86
|
+
assertEq(hookSpecifications.length, 0);
|
|
87
|
+
|
|
88
|
+
vm.expectEmit();
|
|
89
|
+
emit IJBCashOutTerminal.CashOutTokens(
|
|
90
|
+
ruleset.id,
|
|
91
|
+
ruleset.cycleNumber,
|
|
92
|
+
projectId,
|
|
93
|
+
_beneficiary,
|
|
94
|
+
_beneficiary,
|
|
95
|
+
cashOutCount,
|
|
96
|
+
previewCashOutTaxRate,
|
|
97
|
+
previewReclaimAmount,
|
|
98
|
+
"",
|
|
99
|
+
_beneficiary
|
|
100
|
+
);
|
|
101
|
+
|
|
102
|
+
vm.prank(_beneficiary);
|
|
103
|
+
uint256 reclaimAmount = _terminal.cashOutTokensOf(
|
|
104
|
+
_beneficiary, projectId, cashOutCount, JBConstants.NATIVE_TOKEN, 0, payable(_beneficiary), ""
|
|
105
|
+
);
|
|
106
|
+
|
|
107
|
+
assertEq(reclaimAmount, previewReclaimAmount);
|
|
108
|
+
}
|
|
109
|
+
}
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
// SPDX-License-Identifier: MIT
|
|
2
|
+
pragma solidity 0.8.26;
|
|
3
|
+
|
|
4
|
+
import {JBController} from "../../../../src/JBController.sol";
|
|
5
|
+
import {IJBRulesetApprovalHook} from "../../../../src/interfaces/IJBRulesetApprovalHook.sol";
|
|
6
|
+
import {IJBRulesets} from "../../../../src/interfaces/IJBRulesets.sol";
|
|
7
|
+
import {JBConstants} from "../../../../src/libraries/JBConstants.sol";
|
|
8
|
+
import {JBRulesetMetadataResolver} from "../../../../src/libraries/JBRulesetMetadataResolver.sol";
|
|
9
|
+
import {JBRuleset} from "../../../../src/structs/JBRuleset.sol";
|
|
10
|
+
import {JBRulesetMetadata} from "../../../../src/structs/JBRulesetMetadata.sol";
|
|
11
|
+
import {JBControllerSetup} from "./JBControllerSetup.sol";
|
|
12
|
+
|
|
13
|
+
contract TestPreviewMintOf_Local is JBControllerSetup {
|
|
14
|
+
uint256 _projectId = 1;
|
|
15
|
+
|
|
16
|
+
function setUp() public {
|
|
17
|
+
super.controllerSetup();
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function test_RevertsWhenTokenCountIsZero() external {
|
|
21
|
+
vm.expectRevert(JBController.JBController_ZeroTokensToMint.selector);
|
|
22
|
+
_controller.previewMintOf(_projectId, 0, true);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function test_ReturnsSplitCountsWhenUsingReservedPercent() external {
|
|
26
|
+
uint256 tokenCount = 1000;
|
|
27
|
+
|
|
28
|
+
JBRulesetMetadata memory metadata = JBRulesetMetadata({
|
|
29
|
+
reservedPercent: 2500,
|
|
30
|
+
cashOutTaxRate: JBConstants.MAX_CASH_OUT_TAX_RATE,
|
|
31
|
+
baseCurrency: uint32(uint160(JBConstants.NATIVE_TOKEN)),
|
|
32
|
+
pausePay: false,
|
|
33
|
+
pauseCreditTransfers: false,
|
|
34
|
+
allowOwnerMinting: false,
|
|
35
|
+
allowSetCustomToken: false,
|
|
36
|
+
allowTerminalMigration: false,
|
|
37
|
+
allowSetTerminals: false,
|
|
38
|
+
ownerMustSendPayouts: false,
|
|
39
|
+
allowSetController: false,
|
|
40
|
+
allowAddAccountingContext: true,
|
|
41
|
+
allowAddPriceFeed: false,
|
|
42
|
+
holdFees: false,
|
|
43
|
+
useTotalSurplusForCashOuts: false,
|
|
44
|
+
useDataHookForPay: false,
|
|
45
|
+
useDataHookForCashOut: false,
|
|
46
|
+
dataHook: address(0),
|
|
47
|
+
metadata: 0
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
JBRuleset memory ruleset = JBRuleset({
|
|
51
|
+
cycleNumber: 1,
|
|
52
|
+
id: 1,
|
|
53
|
+
basedOnId: 0,
|
|
54
|
+
start: uint48(block.timestamp),
|
|
55
|
+
duration: 0,
|
|
56
|
+
weight: 0,
|
|
57
|
+
weightCutPercent: 0,
|
|
58
|
+
approvalHook: IJBRulesetApprovalHook(address(0)),
|
|
59
|
+
metadata: JBRulesetMetadataResolver.packRulesetMetadata(metadata)
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
mockExpect(address(rulesets), abi.encodeCall(IJBRulesets.currentOf, (_projectId)), abi.encode(ruleset));
|
|
63
|
+
|
|
64
|
+
(uint256 beneficiaryTokenCount, uint256 reservedTokenCount) =
|
|
65
|
+
_controller.previewMintOf(_projectId, tokenCount, true);
|
|
66
|
+
|
|
67
|
+
assertEq(beneficiaryTokenCount, 750);
|
|
68
|
+
assertEq(reservedTokenCount, 250);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function test_IgnoresReservedPercentWhenFlagIsFalse() external {
|
|
72
|
+
uint256 tokenCount = 1000;
|
|
73
|
+
|
|
74
|
+
JBRulesetMetadata memory metadata = JBRulesetMetadata({
|
|
75
|
+
reservedPercent: 9000,
|
|
76
|
+
cashOutTaxRate: JBConstants.MAX_CASH_OUT_TAX_RATE,
|
|
77
|
+
baseCurrency: uint32(uint160(JBConstants.NATIVE_TOKEN)),
|
|
78
|
+
pausePay: false,
|
|
79
|
+
pauseCreditTransfers: false,
|
|
80
|
+
allowOwnerMinting: false,
|
|
81
|
+
allowSetCustomToken: false,
|
|
82
|
+
allowTerminalMigration: false,
|
|
83
|
+
allowSetTerminals: false,
|
|
84
|
+
ownerMustSendPayouts: false,
|
|
85
|
+
allowSetController: false,
|
|
86
|
+
allowAddAccountingContext: true,
|
|
87
|
+
allowAddPriceFeed: false,
|
|
88
|
+
holdFees: false,
|
|
89
|
+
useTotalSurplusForCashOuts: false,
|
|
90
|
+
useDataHookForPay: false,
|
|
91
|
+
useDataHookForCashOut: false,
|
|
92
|
+
dataHook: address(0),
|
|
93
|
+
metadata: 0
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
JBRuleset memory ruleset = JBRuleset({
|
|
97
|
+
cycleNumber: 1,
|
|
98
|
+
id: 1,
|
|
99
|
+
basedOnId: 0,
|
|
100
|
+
start: uint48(block.timestamp),
|
|
101
|
+
duration: 0,
|
|
102
|
+
weight: 0,
|
|
103
|
+
weightCutPercent: 0,
|
|
104
|
+
approvalHook: IJBRulesetApprovalHook(address(0)),
|
|
105
|
+
metadata: JBRulesetMetadataResolver.packRulesetMetadata(metadata)
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
mockExpect(address(rulesets), abi.encodeCall(IJBRulesets.currentOf, (_projectId)), abi.encode(ruleset));
|
|
109
|
+
|
|
110
|
+
(uint256 beneficiaryTokenCount, uint256 reservedTokenCount) =
|
|
111
|
+
_controller.previewMintOf(_projectId, tokenCount, false);
|
|
112
|
+
|
|
113
|
+
assertEq(beneficiaryTokenCount, tokenCount);
|
|
114
|
+
assertEq(reservedTokenCount, 0);
|
|
115
|
+
}
|
|
116
|
+
}
|
|
@@ -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,
|