@bananapus/core-v6 0.0.21 → 0.0.23
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 +0 -1
- package/AUDIT_INSTRUCTIONS.md +1 -1
- package/CHANGE_LOG.md +3 -3
- package/RISKS.md +3 -3
- package/SKILLS.md +8 -8
- package/USER_JOURNEYS.md +1 -1
- package/foundry.toml +0 -1
- package/package.json +1 -1
- package/src/JBMultiTerminal.sol +92 -192
- package/src/JBTerminalStore.sol +414 -256
- package/src/interfaces/IJBMultiTerminal.sol +0 -4
- package/src/interfaces/IJBTerminal.sol +4 -4
- package/src/interfaces/IJBTerminalStore.sol +65 -33
- package/src/libraries/JBPayoutSplitGroupLib.sol +0 -1
- package/src/libraries/JBSurplus.sol +3 -4
- package/test/ComprehensiveInvariant.t.sol +5 -7
- package/test/CoreExploitTests.t.sol +18 -23
- package/test/TestCashOut.sol +6 -6
- package/test/TestMultiTerminalSurplus.sol +4 -4
- package/test/TestMultiTokenSurplus.sol +6 -23
- package/test/TestTerminalMigration.sol +2 -7
- package/test/fork/TestSequencerPriceFeedFork.sol +1 -1
- package/test/fork/TestTerminalPreviewParityFork.sol +0 -1
- package/test/invariants/TerminalStoreInvariant.t.sol +5 -7
- package/test/units/static/JBMultiTerminal/JBMultiTerminalSetup.sol +1 -2
- package/test/units/static/JBMultiTerminal/TestAccountingContextsOf.sol +23 -24
- package/test/units/static/JBMultiTerminal/TestAddAccountingContextsFor.sol +79 -119
- package/test/units/static/JBMultiTerminal/TestAddToBalanceOf.sol +33 -26
- package/test/units/static/JBMultiTerminal/TestCashOutTokensOf.sol +32 -27
- package/test/units/static/JBMultiTerminal/TestExecutePayout.sol +22 -4
- package/test/units/static/JBMultiTerminal/TestExecuteProcessFee.sol +8 -5
- package/test/units/static/JBMultiTerminal/TestPay.sol +41 -33
- package/test/units/static/JBMultiTerminal/TestPreviewCashOutFrom.sol +19 -18
- package/test/units/static/JBMultiTerminal/TestPreviewPayFor.sol +38 -22
- package/test/units/static/JBMultiTerminal/TestProcessHeldFeesOf.sol +9 -6
- package/test/units/static/JBMultiTerminal/TestSendPayoutsOf.sol +4 -4
- package/test/units/static/JBMultiTerminal/TestUseAllowanceOf.sol +37 -32
- package/test/units/static/JBSurplus/TestSurplusFuzz.sol +5 -20
- package/test/units/static/JBTerminalStore/JBTerminalStoreSetup.sol +17 -0
- package/test/units/static/JBTerminalStore/TestCurrentReclaimableSurplusOf.sol +120 -246
- package/test/units/static/JBTerminalStore/TestCurrentSurplusOf.sol +29 -7
- package/test/units/static/JBTerminalStore/TestCurrentTotalSurplusOf.sol +88 -20
- package/test/units/static/JBTerminalStore/TestPreviewCashOutFrom.sol +30 -29
- package/test/units/static/JBTerminalStore/TestPreviewPayFrom.sol +46 -16
- package/test/units/static/JBTerminalStore/TestRecordCashOutsFor.sol +24 -53
- package/test/units/static/JBTerminalStore/TestRecordPayoutFor.sol +24 -4
- package/test/units/static/JBTerminalStore/TestRecordUsedAllowanceOf.sol +14 -4
- package/test/units/static/JBTerminalStore/TestUint224Overflow.sol +21 -3
|
@@ -10,6 +10,7 @@ import {IJBTerminal} from "../../../../src/interfaces/IJBTerminal.sol";
|
|
|
10
10
|
import {IJBTerminalStore} from "../../../../src/interfaces/IJBTerminalStore.sol";
|
|
11
11
|
import {JBConstants} from "../../../../src/libraries/JBConstants.sol";
|
|
12
12
|
import {JBFees} from "../../../../src/libraries/JBFees.sol";
|
|
13
|
+
import {JBAccountingContext} from "../../../../src/structs/JBAccountingContext.sol";
|
|
13
14
|
import {JBPayHookSpecification} from "../../../../src/structs/JBPayHookSpecification.sol";
|
|
14
15
|
import {JBRuleset} from "../../../../src/structs/JBRuleset.sol";
|
|
15
16
|
import {JBSplit} from "../../../../src/structs/JBSplit.sol";
|
|
@@ -32,6 +33,7 @@ contract TestExecutePayout_Local is JBMultiTerminalSetup {
|
|
|
32
33
|
|
|
33
34
|
address _native = JBConstants.NATIVE_TOKEN;
|
|
34
35
|
address _usdc = makeAddr("USDC");
|
|
36
|
+
// forge-lint: disable-next-line(unsafe-typecast)
|
|
35
37
|
uint32 _usdcCurrency = uint32(uint160(_usdc));
|
|
36
38
|
|
|
37
39
|
JBSplit private _split;
|
|
@@ -42,10 +44,12 @@ contract TestExecutePayout_Local is JBMultiTerminalSetup {
|
|
|
42
44
|
}
|
|
43
45
|
|
|
44
46
|
function _setAccountingContext(uint256 projectId, address token, uint8 decimals, uint32 currency) internal {
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
47
|
+
// Mock the store to return this accounting context
|
|
48
|
+
mockExpect(
|
|
49
|
+
address(store),
|
|
50
|
+
abi.encodeCall(IJBTerminalStore.accountingContextOf, (address(_terminal), projectId, token)),
|
|
51
|
+
abi.encode(JBAccountingContext({token: token, decimals: decimals, currency: currency}))
|
|
52
|
+
);
|
|
49
53
|
}
|
|
50
54
|
|
|
51
55
|
modifier whenASplitHookIsConfigured() {
|
|
@@ -66,6 +70,13 @@ contract TestExecutePayout_Local is JBMultiTerminalSetup {
|
|
|
66
70
|
function test_GivenTheSplitHookEQFeeless() external whenASplitHookIsConfigured {
|
|
67
71
|
// it will not process a fee
|
|
68
72
|
|
|
73
|
+
// Mock accountingContextOf for the decimals lookup in executePayout
|
|
74
|
+
mockExpect(
|
|
75
|
+
address(store),
|
|
76
|
+
abi.encodeCall(IJBTerminalStore.accountingContextOf, (address(_terminal), _noProject, _native)),
|
|
77
|
+
abi.encode(JBAccountingContext({token: _native, decimals: 0, currency: 0}))
|
|
78
|
+
);
|
|
79
|
+
|
|
69
80
|
// mock call to split hook supportsInterface
|
|
70
81
|
mockExpect(
|
|
71
82
|
address(_hook),
|
|
@@ -111,6 +122,13 @@ contract TestExecutePayout_Local is JBMultiTerminalSetup {
|
|
|
111
122
|
function test_GivenTheSplitHookDNEQFeeless() external whenASplitHookIsConfigured {
|
|
112
123
|
// it will process a fee
|
|
113
124
|
|
|
125
|
+
// Mock accountingContextOf for the decimals lookup in executePayout
|
|
126
|
+
mockExpect(
|
|
127
|
+
address(store),
|
|
128
|
+
abi.encodeCall(IJBTerminalStore.accountingContextOf, (address(_terminal), _noProject, _native)),
|
|
129
|
+
abi.encode(JBAccountingContext({token: _native, decimals: 0, currency: 0}))
|
|
130
|
+
);
|
|
131
|
+
|
|
114
132
|
// mock call to split hook supportsInterface
|
|
115
133
|
mockExpect(
|
|
116
134
|
address(_hook),
|
|
@@ -13,6 +13,8 @@ import {JBTokenAmount} from "../../../../src/structs/JBTokenAmount.sol";
|
|
|
13
13
|
import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
|
|
14
14
|
import {JBMultiTerminalSetup} from "./JBMultiTerminalSetup.sol";
|
|
15
15
|
|
|
16
|
+
// Accounting context is now read from the store
|
|
17
|
+
|
|
16
18
|
contract TestExecuteProcessFee_Local is JBMultiTerminalSetup {
|
|
17
19
|
uint256 _projectId = 1;
|
|
18
20
|
uint256 _defaultAmount = 1e18;
|
|
@@ -28,11 +30,12 @@ contract TestExecuteProcessFee_Local is JBMultiTerminalSetup {
|
|
|
28
30
|
}
|
|
29
31
|
|
|
30
32
|
function _setAccountingContext(address token, uint8 decimals, uint32 currency) internal {
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
33
|
+
// Mock the store to return this accounting context
|
|
34
|
+
mockExpect(
|
|
35
|
+
address(store),
|
|
36
|
+
abi.encodeCall(IJBTerminalStore.accountingContextOf, (address(_terminal), _projectId, token)),
|
|
37
|
+
abi.encode(JBAccountingContext({token: token, decimals: decimals, currency: currency}))
|
|
38
|
+
);
|
|
36
39
|
}
|
|
37
40
|
|
|
38
41
|
function test_WhenCallerIsNotItself() external {
|
|
@@ -7,7 +7,6 @@ import {IJBController} from "../../../../src/interfaces/IJBController.sol";
|
|
|
7
7
|
import {IJBDirectory} from "../../../../src/interfaces/IJBDirectory.sol";
|
|
8
8
|
import {IJBPayHook} from "../../../../src/interfaces/IJBPayHook.sol";
|
|
9
9
|
import {IJBRulesetApprovalHook} from "../../../../src/interfaces/IJBRulesetApprovalHook.sol";
|
|
10
|
-
import {IJBRulesets} from "../../../../src/interfaces/IJBRulesets.sol";
|
|
11
10
|
import {IJBTerminal} from "../../../../src/interfaces/IJBTerminal.sol";
|
|
12
11
|
import {IJBTerminalStore} from "../../../../src/interfaces/IJBTerminalStore.sol";
|
|
13
12
|
import {IJBTokens} from "../../../../src/interfaces/IJBTokens.sol";
|
|
@@ -17,7 +16,6 @@ import {JBAfterPayRecordedContext} from "../../../../src/structs/JBAfterPayRecor
|
|
|
17
16
|
import {JBPayHookSpecification} from "../../../../src/structs/JBPayHookSpecification.sol";
|
|
18
17
|
import {JBRuleset} from "../../../../src/structs/JBRuleset.sol";
|
|
19
18
|
import {JBTokenAmount} from "../../../../src/structs/JBTokenAmount.sol";
|
|
20
|
-
import {IERC20Metadata} from "@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol";
|
|
21
19
|
import {IERC721} from "@openzeppelin/contracts/token/ERC721/IERC721.sol";
|
|
22
20
|
import {JBMultiTerminalSetup} from "./JBMultiTerminalSetup.sol";
|
|
23
21
|
|
|
@@ -60,23 +58,22 @@ contract TestPay_Local is JBMultiTerminalSetup {
|
|
|
60
58
|
token: JBConstants.NATIVE_TOKEN, decimals: 18, currency: uint32(uint160(JBConstants.NATIVE_TOKEN))
|
|
61
59
|
});
|
|
62
60
|
|
|
63
|
-
//
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
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));
|
|
61
|
+
// Mock recordAccountingContextOf in the store (validation now happens there)
|
|
62
|
+
mockExpect(
|
|
63
|
+
address(store), abi.encodeCall(IJBTerminalStore.recordAccountingContextOf, (_projectId, _tokens)), ""
|
|
64
|
+
);
|
|
77
65
|
|
|
78
66
|
_terminal.addAccountingContextsFor(_projectId, _tokens);
|
|
79
67
|
|
|
68
|
+
// Mock accountingContextOf for subsequent reads
|
|
69
|
+
mockExpect(
|
|
70
|
+
address(store),
|
|
71
|
+
abi.encodeCall(
|
|
72
|
+
IJBTerminalStore.accountingContextOf, (address(_terminal), _projectId, JBConstants.NATIVE_TOKEN)
|
|
73
|
+
),
|
|
74
|
+
abi.encode(_tokens[0])
|
|
75
|
+
);
|
|
76
|
+
|
|
80
77
|
_;
|
|
81
78
|
}
|
|
82
79
|
|
|
@@ -89,31 +86,25 @@ contract TestPay_Local is JBMultiTerminalSetup {
|
|
|
89
86
|
address(directory), abi.encodeCall(IJBDirectory.controllerOf, (_projectId)), abi.encode(address(this))
|
|
90
87
|
);
|
|
91
88
|
|
|
92
|
-
// mock call to token decimals
|
|
93
|
-
mockExpect(address(_mockToken), abi.encodeCall(IERC20Metadata.decimals, ()), abi.encode(6));
|
|
94
|
-
|
|
95
89
|
JBAccountingContext[] memory _tokens = new JBAccountingContext[](1);
|
|
96
90
|
_tokens[0] = JBAccountingContext({
|
|
97
91
|
token: address(_mockToken), decimals: 6, currency: uint32(uint160(address(_mockToken)))
|
|
98
92
|
});
|
|
99
93
|
|
|
100
|
-
//
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
basedOnId: 0,
|
|
105
|
-
start: 0,
|
|
106
|
-
duration: 0,
|
|
107
|
-
weight: 0,
|
|
108
|
-
weightCutPercent: 0,
|
|
109
|
-
approvalHook: IJBRulesetApprovalHook(address(0)),
|
|
110
|
-
metadata: 0
|
|
111
|
-
});
|
|
112
|
-
|
|
113
|
-
mockExpect(address(rulesets), abi.encodeCall(IJBRulesets.currentOf, (_projectId)), abi.encode(ruleset));
|
|
94
|
+
// Mock recordAccountingContextOf in the store (validation now happens there)
|
|
95
|
+
mockExpect(
|
|
96
|
+
address(store), abi.encodeCall(IJBTerminalStore.recordAccountingContextOf, (_projectId, _tokens)), ""
|
|
97
|
+
);
|
|
114
98
|
|
|
115
99
|
_terminal.addAccountingContextsFor(_projectId, _tokens);
|
|
116
100
|
|
|
101
|
+
// Mock accountingContextOf for subsequent reads
|
|
102
|
+
mockExpect(
|
|
103
|
+
address(store),
|
|
104
|
+
abi.encodeCall(IJBTerminalStore.accountingContextOf, (address(_terminal), _projectId, address(_mockToken))),
|
|
105
|
+
abi.encode(_tokens[0])
|
|
106
|
+
);
|
|
107
|
+
|
|
117
108
|
_;
|
|
118
109
|
}
|
|
119
110
|
|
|
@@ -254,6 +245,7 @@ contract TestPay_Local is JBMultiTerminalSetup {
|
|
|
254
245
|
// forge-lint: disable-next-line(unsafe-typecast)
|
|
255
246
|
token: address(_mockToken),
|
|
256
247
|
decimals: 6,
|
|
248
|
+
// forge-lint: disable-next-line(unsafe-typecast)
|
|
257
249
|
currency: uint32(_mockTokenCurrency),
|
|
258
250
|
value: _defaultAmount
|
|
259
251
|
});
|
|
@@ -441,8 +433,16 @@ contract TestPay_Local is JBMultiTerminalSetup {
|
|
|
441
433
|
function test_WhenTheProjectDNHAccountingContextForTheToken() external {
|
|
442
434
|
// it will revert TOKEN_NOT_ACCEPTED
|
|
443
435
|
|
|
436
|
+
// Mock totalBalanceOf (called before _acceptFundsFor)
|
|
444
437
|
mockExpect(address(tokens), abi.encodeCall(IJBTokens.totalBalanceOf, (_bene, _projectId)), abi.encode(0));
|
|
445
438
|
|
|
439
|
+
// Mock accountingContextOf to return empty context (token not accepted)
|
|
440
|
+
mockExpect(
|
|
441
|
+
address(store),
|
|
442
|
+
abi.encodeCall(IJBTerminalStore.accountingContextOf, (address(_terminal), _projectId, _native)),
|
|
443
|
+
abi.encode(JBAccountingContext({token: address(0), decimals: 0, currency: 0}))
|
|
444
|
+
);
|
|
445
|
+
|
|
446
446
|
vm.expectRevert(abi.encodeWithSelector(JBMultiTerminal.JBMultiTerminal_TokenNotAccepted.selector, _native));
|
|
447
447
|
_terminal.pay{value: 1e18}({
|
|
448
448
|
projectId: _projectId,
|
|
@@ -463,8 +463,16 @@ contract TestPay_Local is JBMultiTerminalSetup {
|
|
|
463
463
|
function test_WhenTheTerminalsTokenEqNativeTokenAndMsgvalueEqZero() external {
|
|
464
464
|
// it will revert NO_MSG_VALUE_ALLOWED
|
|
465
465
|
|
|
466
|
+
// Mock totalBalanceOf (called before _acceptFundsFor)
|
|
466
467
|
mockExpect(address(tokens), abi.encodeCall(IJBTokens.totalBalanceOf, (_bene, _projectId)), abi.encode(0));
|
|
467
468
|
|
|
469
|
+
// Mock accountingContextOf to return empty context (token not accepted)
|
|
470
|
+
mockExpect(
|
|
471
|
+
address(store),
|
|
472
|
+
abi.encodeCall(IJBTerminalStore.accountingContextOf, (address(_terminal), _projectId, _native)),
|
|
473
|
+
abi.encode(JBAccountingContext({token: address(0), decimals: 0, currency: 0}))
|
|
474
|
+
);
|
|
475
|
+
|
|
468
476
|
vm.expectRevert(abi.encodeWithSelector(JBMultiTerminal.JBMultiTerminal_TokenNotAccepted.selector, _native));
|
|
469
477
|
_terminal.pay{value: 0}({
|
|
470
478
|
projectId: _projectId,
|
|
@@ -5,7 +5,6 @@ import {JBMultiTerminal} from "../../../../src/JBMultiTerminal.sol";
|
|
|
5
5
|
import {IJBCashOutHook} from "../../../../src/interfaces/IJBCashOutHook.sol";
|
|
6
6
|
import {IJBDirectory} from "../../../../src/interfaces/IJBDirectory.sol";
|
|
7
7
|
import {IJBFeelessAddresses} from "../../../../src/interfaces/IJBFeelessAddresses.sol";
|
|
8
|
-
import {IJBRulesets} from "../../../../src/interfaces/IJBRulesets.sol";
|
|
9
8
|
import {IJBRulesetApprovalHook} from "../../../../src/interfaces/IJBRulesetApprovalHook.sol";
|
|
10
9
|
import {IJBTerminalStore} from "../../../../src/interfaces/IJBTerminalStore.sol";
|
|
11
10
|
import {JBConstants} from "../../../../src/libraries/JBConstants.sol";
|
|
@@ -32,34 +31,35 @@ contract TestPreviewCashOutFrom_Local is JBMultiTerminalSetup {
|
|
|
32
31
|
address(directory), abi.encodeCall(IJBDirectory.controllerOf, (_projectId)), abi.encode(address(this))
|
|
33
32
|
);
|
|
34
33
|
|
|
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
34
|
JBAccountingContext[] memory contexts = new JBAccountingContext[](1);
|
|
50
35
|
contexts[0] = JBAccountingContext({token: token, decimals: decimals, currency: currency});
|
|
51
36
|
|
|
37
|
+
// Mock recordAccountingContextOf in the store (validation now happens there)
|
|
38
|
+
mockExpect(
|
|
39
|
+
address(store), abi.encodeCall(IJBTerminalStore.recordAccountingContextOf, (_projectId, contexts)), ""
|
|
40
|
+
);
|
|
41
|
+
|
|
52
42
|
vm.prank(address(this));
|
|
53
43
|
_terminal.addAccountingContextsFor(_projectId, contexts);
|
|
44
|
+
|
|
45
|
+
// Mock accountingContextOf for subsequent reads (not all code paths call it, so use mockCall only)
|
|
46
|
+
vm.mockCall(
|
|
47
|
+
address(store),
|
|
48
|
+
abi.encodeCall(IJBTerminalStore.accountingContextOf, (address(_terminal), _projectId, token)),
|
|
49
|
+
abi.encode(contexts[0])
|
|
50
|
+
);
|
|
54
51
|
}
|
|
55
52
|
|
|
56
53
|
function test_RevertsWhenTokenIsNotAccepted() external {
|
|
57
|
-
|
|
54
|
+
// previewCashOutFrom now delegates directly to the store without a token acceptance check,
|
|
55
|
+
// so it reverts during store computation (e.g. unmocked external call) rather than with TokenNotAccepted.
|
|
56
|
+
vm.expectRevert();
|
|
58
57
|
JBMultiTerminal(address(_terminal))
|
|
59
58
|
.previewCashOutFrom(_holder, _projectId, _cashOutCount, _token, _beneficiary, "");
|
|
60
59
|
}
|
|
61
60
|
|
|
62
61
|
function test_ReturnsRulesetAndCashOutPreviewValues() external {
|
|
62
|
+
// forge-lint: disable-next-line(unsafe-typecast)
|
|
63
63
|
_acceptToken(_token, 18, uint32(uint160(_token)));
|
|
64
64
|
|
|
65
65
|
JBRuleset memory ruleset = JBRuleset({
|
|
@@ -80,7 +80,8 @@ contract TestPreviewCashOutFrom_Local is JBMultiTerminalSetup {
|
|
|
80
80
|
});
|
|
81
81
|
|
|
82
82
|
JBAccountingContext memory accountingContext =
|
|
83
|
-
|
|
83
|
+
// forge-lint: disable-next-line(unsafe-typecast)
|
|
84
|
+
JBAccountingContext({token: _token, decimals: 18, currency: uint32(uint160(_token))});
|
|
84
85
|
JBAccountingContext[] memory accountingContexts = new JBAccountingContext[](1);
|
|
85
86
|
accountingContexts[0] = accountingContext;
|
|
86
87
|
|
|
@@ -92,7 +93,7 @@ contract TestPreviewCashOutFrom_Local is JBMultiTerminalSetup {
|
|
|
92
93
|
address(store),
|
|
93
94
|
abi.encodeCall(
|
|
94
95
|
IJBTerminalStore.previewCashOutFrom,
|
|
95
|
-
(_holder, _projectId, _cashOutCount, accountingContext,
|
|
96
|
+
(address(_terminal), _holder, _projectId, _cashOutCount, accountingContext.token, true, bytes(""))
|
|
96
97
|
),
|
|
97
98
|
abi.encode(ruleset, 999, 1234, specs)
|
|
98
99
|
);
|
|
@@ -27,21 +27,50 @@ contract TestPreviewPayFor_Local is JBMultiTerminalSetup {
|
|
|
27
27
|
}
|
|
28
28
|
|
|
29
29
|
function _setAccountingContext(address token, uint8 decimals, uint32 currency) internal {
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
30
|
+
// Mock the store to return this accounting context
|
|
31
|
+
mockExpect(
|
|
32
|
+
address(store),
|
|
33
|
+
abi.encodeCall(IJBTerminalStore.accountingContextOf, (address(_terminal), _projectId, token)),
|
|
34
|
+
abi.encode(JBAccountingContext({token: token, decimals: decimals, currency: currency}))
|
|
35
|
+
);
|
|
34
36
|
}
|
|
35
37
|
|
|
36
38
|
function test_RevertsWhenTokenIsNotAccepted() external {
|
|
39
|
+
// Mock accountingContextOf to return empty context (token not accepted)
|
|
40
|
+
mockExpect(
|
|
41
|
+
address(store),
|
|
42
|
+
abi.encodeCall(IJBTerminalStore.accountingContextOf, (address(_terminal), _projectId, _token)),
|
|
43
|
+
abi.encode(JBAccountingContext({token: address(0), decimals: 0, currency: 0}))
|
|
44
|
+
);
|
|
45
|
+
|
|
37
46
|
vm.prank(_payer);
|
|
38
47
|
vm.expectRevert(abi.encodeWithSelector(JBMultiTerminal.JBMultiTerminal_TokenNotAccepted.selector, _token));
|
|
39
48
|
JBMultiTerminal(address(_terminal)).previewPayFor(_projectId, _token, _amount, _beneficiary, "");
|
|
40
49
|
}
|
|
41
50
|
|
|
42
51
|
function test_ReturnsRulesetMintSplitAndHookSpecifications() external {
|
|
52
|
+
// forge-lint: disable-next-line(unsafe-typecast)
|
|
43
53
|
_setAccountingContext(_token, 18, uint32(uint160(_token)));
|
|
44
54
|
|
|
55
|
+
_mockPreviewPayFrom();
|
|
56
|
+
|
|
57
|
+
vm.prank(_payer);
|
|
58
|
+
(
|
|
59
|
+
JBRuleset memory previewRuleset,
|
|
60
|
+
uint256 beneficiaryTokenCount,
|
|
61
|
+
uint256 reservedTokenCount,
|
|
62
|
+
JBPayHookSpecification[] memory previewSpecs
|
|
63
|
+
) = JBMultiTerminal(address(_terminal)).previewPayFor(_projectId, _token, _amount, _beneficiary, "");
|
|
64
|
+
|
|
65
|
+
assertEq(previewRuleset.id, 1);
|
|
66
|
+
assertEq(beneficiaryTokenCount, 750);
|
|
67
|
+
assertEq(reservedTokenCount, 250);
|
|
68
|
+
assertEq(previewSpecs.length, 1);
|
|
69
|
+
assertEq(previewSpecs[0].amount, 123);
|
|
70
|
+
assertEq(previewSpecs[0].metadata, hex"1234");
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
function _mockPreviewPayFrom() internal {
|
|
45
74
|
JBRuleset memory ruleset = JBRuleset({
|
|
46
75
|
cycleNumber: 1,
|
|
47
76
|
id: 1,
|
|
@@ -59,11 +88,14 @@ contract TestPreviewPayFor_Local is JBMultiTerminalSetup {
|
|
|
59
88
|
JBPayHookSpecification({hook: IJBPayHook(makeAddr("hook")), noop: false, amount: 123, metadata: hex"1234"});
|
|
60
89
|
|
|
61
90
|
JBTokenAmount memory tokenAmount =
|
|
62
|
-
|
|
91
|
+
// forge-lint: disable-next-line(unsafe-typecast)
|
|
92
|
+
JBTokenAmount({token: _token, decimals: 18, currency: uint32(uint160(_token)), value: _amount});
|
|
63
93
|
|
|
64
94
|
mockExpect(
|
|
65
95
|
address(store),
|
|
66
|
-
abi.
|
|
96
|
+
abi.encodeWithSelector(
|
|
97
|
+
bytes4(0xdb6d7e03), address(_terminal), _payer, tokenAmount, _projectId, _beneficiary, bytes("")
|
|
98
|
+
),
|
|
67
99
|
abi.encode(ruleset, 1000, specs)
|
|
68
100
|
);
|
|
69
101
|
|
|
@@ -78,21 +110,5 @@ contract TestPreviewPayFor_Local is JBMultiTerminalSetup {
|
|
|
78
110
|
abi.encodeCall(IJBController.previewMintOf, (_projectId, 1000, true)),
|
|
79
111
|
abi.encode(750, 250)
|
|
80
112
|
);
|
|
81
|
-
|
|
82
|
-
vm.prank(_payer);
|
|
83
|
-
(
|
|
84
|
-
JBRuleset memory previewRuleset,
|
|
85
|
-
uint256 beneficiaryTokenCount,
|
|
86
|
-
uint256 reservedTokenCount,
|
|
87
|
-
JBPayHookSpecification[] memory previewSpecs
|
|
88
|
-
) = JBMultiTerminal(address(_terminal)).previewPayFor(_projectId, _token, _amount, _beneficiary, "");
|
|
89
|
-
|
|
90
|
-
assertEq(previewRuleset.id, ruleset.id);
|
|
91
|
-
assertEq(beneficiaryTokenCount, 750);
|
|
92
|
-
assertEq(reservedTokenCount, 250);
|
|
93
|
-
assertEq(previewSpecs.length, 1);
|
|
94
|
-
assertEq(address(previewSpecs[0].hook), address(specs[0].hook));
|
|
95
|
-
assertEq(previewSpecs[0].amount, specs[0].amount);
|
|
96
|
-
assertEq(previewSpecs[0].metadata, specs[0].metadata);
|
|
97
113
|
}
|
|
98
114
|
}
|
|
@@ -13,6 +13,7 @@ import {IJBSplits} from "../../../../src/interfaces/IJBSplits.sol";
|
|
|
13
13
|
import {IJBTerminalStore} from "../../../../src/interfaces/IJBTerminalStore.sol";
|
|
14
14
|
import {IJBTokens} from "../../../../src/interfaces/IJBTokens.sol";
|
|
15
15
|
import {JBFees} from "../../../../src/libraries/JBFees.sol";
|
|
16
|
+
import {JBAccountingContext} from "../../../../src/structs/JBAccountingContext.sol";
|
|
16
17
|
import {JBFee} from "../../../../src/structs/JBFee.sol";
|
|
17
18
|
import {JBPayHookSpecification} from "../../../../src/structs/JBPayHookSpecification.sol";
|
|
18
19
|
import {JBRuleset} from "../../../../src/structs/JBRuleset.sol";
|
|
@@ -65,16 +66,17 @@ contract TestProcessHeldFeesOf_Local is JBTest {
|
|
|
65
66
|
address _beneficiary = makeAddr("beneficiary");
|
|
66
67
|
|
|
67
68
|
function _setAccountingContext(uint256 projectId, address token, uint8 decimals, uint32 currency) internal {
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
69
|
+
// Mock the store to return this accounting context
|
|
70
|
+
mockExpect(
|
|
71
|
+
address(store),
|
|
72
|
+
abi.encodeCall(IJBTerminalStore.accountingContextOf, (address(_terminal), projectId, token)),
|
|
73
|
+
abi.encode(JBAccountingContext({token: token, decimals: decimals, currency: currency}))
|
|
74
|
+
);
|
|
72
75
|
}
|
|
73
76
|
|
|
74
77
|
function setUp() public {
|
|
75
|
-
// Constructor will call to find directory
|
|
78
|
+
// Constructor will call to find directory from the terminal store
|
|
76
79
|
mockExpect(address(store), abi.encodeCall(IJBTerminalStore.DIRECTORY, ()), abi.encode(address(directory)));
|
|
77
|
-
mockExpect(address(store), abi.encodeCall(IJBTerminalStore.RULESETS, ()), abi.encode(address(rulesets)));
|
|
78
80
|
|
|
79
81
|
_terminal = new ForTest_JBMultiTerminal(
|
|
80
82
|
feelessAddresses, permissions, projects, splits, store, tokens, permit2, trustedForwarder
|
|
@@ -127,6 +129,7 @@ contract TestProcessHeldFeesOf_Local is JBTest {
|
|
|
127
129
|
uint256 expectedFeeAmount = JBFees.feeAmountFrom({amountBeforeFee: heldAmount, feePercent: _terminal.FEE()});
|
|
128
130
|
|
|
129
131
|
// Set up accounting context for the fee beneficiary project (project 1) so _pay can build the token amount.
|
|
132
|
+
// forge-lint: disable-next-line(unsafe-typecast)
|
|
130
133
|
_setAccountingContext(_feeProjectId, _mockToken, 0, uint32(uint160(_mockToken)));
|
|
131
134
|
|
|
132
135
|
// Mock the directory call to find the fee terminal - return _terminal itself so it uses internal _pay
|
|
@@ -47,7 +47,7 @@ contract TestSendPayoutsOf_Local is JBMultiTerminalSetup {
|
|
|
47
47
|
// record payout mock call
|
|
48
48
|
mockExpect(
|
|
49
49
|
address(store),
|
|
50
|
-
abi.encodeCall(IJBTerminalStore.recordPayoutFor, (_projectId, mockTokenContext, 0, 0)),
|
|
50
|
+
abi.encodeCall(IJBTerminalStore.recordPayoutFor, (_projectId, mockTokenContext.token, 0, 0)),
|
|
51
51
|
abi.encode(returnedRuleset, 0)
|
|
52
52
|
);
|
|
53
53
|
|
|
@@ -86,7 +86,7 @@ contract TestSendPayoutsOf_Local is JBMultiTerminalSetup {
|
|
|
86
86
|
// record payout mock call
|
|
87
87
|
mockExpect(
|
|
88
88
|
address(store),
|
|
89
|
-
abi.encodeCall(IJBTerminalStore.recordPayoutFor, (_projectId, mockTokenContext, 0, 0)),
|
|
89
|
+
abi.encodeCall(IJBTerminalStore.recordPayoutFor, (_projectId, mockTokenContext.token, 0, 0)),
|
|
90
90
|
abi.encode(returnedRuleset, 0)
|
|
91
91
|
);
|
|
92
92
|
|
|
@@ -134,7 +134,7 @@ contract TestSendPayoutsOf_Local is JBMultiTerminalSetup {
|
|
|
134
134
|
// record payout mock call
|
|
135
135
|
mockExpect(
|
|
136
136
|
address(store),
|
|
137
|
-
abi.encodeCall(IJBTerminalStore.recordPayoutFor, (_projectId, mockTokenContext, 0, 0)),
|
|
137
|
+
abi.encodeCall(IJBTerminalStore.recordPayoutFor, (_projectId, mockTokenContext.token, 0, 0)),
|
|
138
138
|
abi.encode(returnedRuleset, 0)
|
|
139
139
|
);
|
|
140
140
|
|
|
@@ -204,7 +204,7 @@ contract TestSendPayoutsOf_Local is JBMultiTerminalSetup {
|
|
|
204
204
|
// record payout mock call
|
|
205
205
|
mockExpect(
|
|
206
206
|
address(store),
|
|
207
|
-
abi.encodeCall(IJBTerminalStore.recordPayoutFor, (_projectId, mockTokenContext, 0, 100)),
|
|
207
|
+
abi.encodeCall(IJBTerminalStore.recordPayoutFor, (_projectId, mockTokenContext.token, 0, 100)),
|
|
208
208
|
abi.encode(returnedRuleset, 100)
|
|
209
209
|
);
|
|
210
210
|
|
|
@@ -8,7 +8,6 @@ import {IJBFeeTerminal} from "../../../../src/interfaces/IJBFeeTerminal.sol";
|
|
|
8
8
|
import {IJBFeelessAddresses} from "../../../../src/interfaces/IJBFeelessAddresses.sol";
|
|
9
9
|
import {IJBPayoutTerminal} from "../../../../src/interfaces/IJBPayoutTerminal.sol";
|
|
10
10
|
import {IJBRulesetApprovalHook} from "../../../../src/interfaces/IJBRulesetApprovalHook.sol";
|
|
11
|
-
import {IJBRulesets} from "../../../../src/interfaces/IJBRulesets.sol";
|
|
12
11
|
import {IJBTerminalStore} from "../../../../src/interfaces/IJBTerminalStore.sol";
|
|
13
12
|
import {JBAccountingContext} from "../../../../src/structs/JBAccountingContext.sol";
|
|
14
13
|
import {JBPayHookSpecification} from "../../../../src/structs/JBPayHookSpecification.sol";
|
|
@@ -18,7 +17,6 @@ import {JBRulesetMetadataResolver} from "../../../../src/libraries/JBRulesetMeta
|
|
|
18
17
|
import {JBConstants} from "../../../../src/libraries/JBConstants.sol";
|
|
19
18
|
import {JBTokenAmount} from "../../../../src/structs/JBTokenAmount.sol";
|
|
20
19
|
import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
|
|
21
|
-
import {IERC20Metadata} from "@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol";
|
|
22
20
|
import {IERC721} from "@openzeppelin/contracts/token/ERC721/IERC721.sol";
|
|
23
21
|
import {JBMultiTerminalSetup} from "./JBMultiTerminalSetup.sol";
|
|
24
22
|
|
|
@@ -55,7 +53,7 @@ contract TestUseAllowanceOf_Local is JBMultiTerminalSetup {
|
|
|
55
53
|
// recordUsedAllowance
|
|
56
54
|
mockExpect(
|
|
57
55
|
address(store),
|
|
58
|
-
abi.encodeCall(IJBTerminalStore.recordUsedAllowanceOf, (_projectId, mockTokenContext, 0, 0)),
|
|
56
|
+
abi.encodeCall(IJBTerminalStore.recordUsedAllowanceOf, (_projectId, mockTokenContext.token, 0, 0)),
|
|
59
57
|
abi.encode(returnedRuleset, 0)
|
|
60
58
|
);
|
|
61
59
|
|
|
@@ -88,12 +86,10 @@ contract TestUseAllowanceOf_Local is JBMultiTerminalSetup {
|
|
|
88
86
|
metadata: 0
|
|
89
87
|
});
|
|
90
88
|
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
// recordUsedAllowance
|
|
89
|
+
// recordUsedAllowance — terminal now passes token address directly.
|
|
94
90
|
mockExpect(
|
|
95
91
|
address(store),
|
|
96
|
-
abi.encodeCall(IJBTerminalStore.recordUsedAllowanceOf, (_projectId,
|
|
92
|
+
abi.encodeCall(IJBTerminalStore.recordUsedAllowanceOf, (_projectId, mockToken, 100, 0)),
|
|
97
93
|
abi.encode(returnedRuleset, 100)
|
|
98
94
|
);
|
|
99
95
|
|
|
@@ -140,12 +136,10 @@ contract TestUseAllowanceOf_Local is JBMultiTerminalSetup {
|
|
|
140
136
|
metadata: 0
|
|
141
137
|
});
|
|
142
138
|
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
// recordUsedAllowance
|
|
139
|
+
// recordUsedAllowance — terminal now passes token address directly
|
|
146
140
|
mockExpect(
|
|
147
141
|
address(store),
|
|
148
|
-
abi.encodeCall(IJBTerminalStore.recordUsedAllowanceOf, (_projectId,
|
|
142
|
+
abi.encodeCall(IJBTerminalStore.recordUsedAllowanceOf, (_projectId, mockToken, 100, 0)),
|
|
149
143
|
abi.encode(returnedRuleset, 100)
|
|
150
144
|
);
|
|
151
145
|
|
|
@@ -222,24 +216,30 @@ contract TestUseAllowanceOf_Local is JBMultiTerminalSetup {
|
|
|
222
216
|
metadata: 0
|
|
223
217
|
});
|
|
224
218
|
|
|
225
|
-
// mock call to tokens decimals()
|
|
226
|
-
mockExpect(mockToken, abi.encodeCall(IERC20Metadata.decimals, ()), abi.encode(18));
|
|
227
|
-
|
|
228
|
-
// mock call to rulesets currentOf returning 0 to bypass ruleset checking
|
|
229
|
-
mockExpect(address(rulesets), abi.encodeCall(IJBRulesets.currentOf, (_projectId)), abi.encode(returnedRuleset));
|
|
230
|
-
|
|
231
219
|
// call params
|
|
232
220
|
JBAccountingContext[] memory _tokens = new JBAccountingContext[](1);
|
|
233
221
|
_tokens[0] = JBAccountingContext({token: mockToken, decimals: 18, currency: currencyId});
|
|
234
222
|
|
|
223
|
+
// Mock recordAccountingContextOf in the store (validation now happens there)
|
|
224
|
+
mockExpect(
|
|
225
|
+
address(store), abi.encodeCall(IJBTerminalStore.recordAccountingContextOf, (_projectId, _tokens)), ""
|
|
226
|
+
);
|
|
227
|
+
|
|
235
228
|
_terminal.addAccountingContextsFor(_projectId, _tokens);
|
|
236
229
|
|
|
230
|
+
// Mock accountingContextOf for subsequent reads (used by _tokenAmountOf during fee processing)
|
|
231
|
+
mockExpect(
|
|
232
|
+
address(store),
|
|
233
|
+
abi.encodeCall(IJBTerminalStore.accountingContextOf, (address(_terminal), _projectId, mockToken)),
|
|
234
|
+
abi.encode(_tokens[0])
|
|
235
|
+
);
|
|
236
|
+
|
|
237
237
|
_terminal.accountingContextForTokenOf(_projectId, mockToken);
|
|
238
238
|
|
|
239
239
|
// recordUsedAllowance
|
|
240
240
|
mockExpect(
|
|
241
241
|
address(store),
|
|
242
|
-
abi.encodeCall(IJBTerminalStore.recordUsedAllowanceOf, (_projectId, _tokens[0], 100, 0)),
|
|
242
|
+
abi.encodeCall(IJBTerminalStore.recordUsedAllowanceOf, (_projectId, _tokens[0].token, 100, 0)),
|
|
243
243
|
abi.encode(returnedRuleset, 100)
|
|
244
244
|
);
|
|
245
245
|
|
|
@@ -370,22 +370,21 @@ contract TestUseAllowanceOf_Local is JBMultiTerminalSetup {
|
|
|
370
370
|
metadata: packedMetadata
|
|
371
371
|
});
|
|
372
372
|
|
|
373
|
-
// mock call to tokens decimals()
|
|
374
|
-
mockExpect(mockToken, abi.encodeCall(IERC20Metadata.decimals, ()), abi.encode(18));
|
|
375
|
-
|
|
376
|
-
// mock call to rulesets currentOf
|
|
377
|
-
mockExpect(address(rulesets), abi.encodeCall(IJBRulesets.currentOf, (_projectId)), abi.encode(returnedRuleset));
|
|
378
|
-
|
|
379
373
|
// Set up accounting context so the token is recognized
|
|
380
374
|
JBAccountingContext[] memory _tokens = new JBAccountingContext[](1);
|
|
381
375
|
_tokens[0] = JBAccountingContext({token: mockToken, decimals: 18, currency: currencyId});
|
|
382
376
|
|
|
377
|
+
// Mock recordAccountingContextOf in the store (validation now happens there)
|
|
378
|
+
mockExpect(
|
|
379
|
+
address(store), abi.encodeCall(IJBTerminalStore.recordAccountingContextOf, (_projectId, _tokens)), ""
|
|
380
|
+
);
|
|
381
|
+
|
|
383
382
|
_terminal.addAccountingContextsFor(_projectId, _tokens);
|
|
384
383
|
|
|
385
384
|
// recordUsedAllowance
|
|
386
385
|
mockExpect(
|
|
387
386
|
address(store),
|
|
388
|
-
abi.encodeCall(IJBTerminalStore.recordUsedAllowanceOf, (_projectId, _tokens[0], 100, 0)),
|
|
387
|
+
abi.encodeCall(IJBTerminalStore.recordUsedAllowanceOf, (_projectId, _tokens[0].token, 100, 0)),
|
|
389
388
|
abi.encode(returnedRuleset, 100)
|
|
390
389
|
);
|
|
391
390
|
|
|
@@ -499,22 +498,28 @@ contract TestUseAllowanceOf_Local is JBMultiTerminalSetup {
|
|
|
499
498
|
metadata: packedMetadata
|
|
500
499
|
});
|
|
501
500
|
|
|
502
|
-
// mock call to tokens decimals()
|
|
503
|
-
mockExpect(mockToken, abi.encodeCall(IERC20Metadata.decimals, ()), abi.encode(18));
|
|
504
|
-
|
|
505
|
-
// mock call to rulesets currentOf
|
|
506
|
-
mockExpect(address(rulesets), abi.encodeCall(IJBRulesets.currentOf, (_projectId)), abi.encode(returnedRuleset));
|
|
507
|
-
|
|
508
501
|
// Set up accounting context
|
|
509
502
|
JBAccountingContext[] memory _tokens = new JBAccountingContext[](1);
|
|
510
503
|
_tokens[0] = JBAccountingContext({token: mockToken, decimals: 18, currency: currencyId});
|
|
511
504
|
|
|
505
|
+
// Mock recordAccountingContextOf in the store (validation now happens there)
|
|
506
|
+
mockExpect(
|
|
507
|
+
address(store), abi.encodeCall(IJBTerminalStore.recordAccountingContextOf, (_projectId, _tokens)), ""
|
|
508
|
+
);
|
|
509
|
+
|
|
512
510
|
_terminal.addAccountingContextsFor(_projectId, _tokens);
|
|
513
511
|
|
|
512
|
+
// Mock accountingContextOf for subsequent reads (used by _tokenAmountOf during fee processing)
|
|
513
|
+
mockExpect(
|
|
514
|
+
address(store),
|
|
515
|
+
abi.encodeCall(IJBTerminalStore.accountingContextOf, (address(_terminal), _projectId, mockToken)),
|
|
516
|
+
abi.encode(_tokens[0])
|
|
517
|
+
);
|
|
518
|
+
|
|
514
519
|
// recordUsedAllowance
|
|
515
520
|
mockExpect(
|
|
516
521
|
address(store),
|
|
517
|
-
abi.encodeCall(IJBTerminalStore.recordUsedAllowanceOf, (_projectId, _tokens[0], 100, 0)),
|
|
522
|
+
abi.encodeCall(IJBTerminalStore.recordUsedAllowanceOf, (_projectId, _tokens[0].token, 100, 0)),
|
|
518
523
|
abi.encode(returnedRuleset, 100)
|
|
519
524
|
);
|
|
520
525
|
|