@bananapus/core-v6 0.0.31 → 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.
- package/package.json +1 -1
- package/src/JBController.sol +34 -14
- package/src/JBDirectory.sol +25 -13
- package/src/JBFundAccessLimits.sol +28 -7
- package/src/JBMultiTerminal.sol +54 -14
- package/src/JBPermissions.sol +17 -17
- package/src/JBRulesets.sol +80 -22
- package/src/JBSplits.sol +31 -12
- package/src/JBTerminalStore.sol +115 -42
- package/src/JBTokens.sol +5 -2
- package/src/abstract/JBControlled.sol +4 -3
- package/src/libraries/JBMetadataResolver.sol +4 -1
- package/src/libraries/JBPayoutSplitGroupLib.sol +4 -1
- package/src/libraries/JBSurplus.sol +4 -1
- package/test/TestForwardedTokenConsumption.sol +1 -0
- package/test/audit/CrossTerminalSurplusSpoof.t.sol +140 -0
- package/test/units/static/JBController/TestPreviewMintOf.sol +0 -1
- package/test/units/static/JBMultiTerminal/TestCashOutTokensOf.sol +0 -1
|
@@ -45,10 +45,11 @@ abstract contract JBControlled is IJBControlled {
|
|
|
45
45
|
|
|
46
46
|
/// @notice Only allows the controller of the specified project to proceed.
|
|
47
47
|
function _onlyControllerOf(uint256 projectId) internal view {
|
|
48
|
+
// Cache the controller address to avoid a redundant external call on revert.
|
|
48
49
|
// slither-disable-next-line calls-loop
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
revert JBControlled_ControllerUnauthorized(
|
|
50
|
+
address controller = address(DIRECTORY.controllerOf(projectId));
|
|
51
|
+
if (controller != msg.sender) {
|
|
52
|
+
revert JBControlled_ControllerUnauthorized(controller);
|
|
52
53
|
}
|
|
53
54
|
}
|
|
54
55
|
}
|
|
@@ -214,7 +214,7 @@ library JBMetadataResolver {
|
|
|
214
214
|
uint256 numberOfDatas = datas.length;
|
|
215
215
|
|
|
216
216
|
// Add each metadata to the array, each padded to 32 bytes
|
|
217
|
-
for (uint256 i; i < numberOfDatas;
|
|
217
|
+
for (uint256 i; i < numberOfDatas;) {
|
|
218
218
|
metadata = abi.encodePacked(metadata, datas[i]);
|
|
219
219
|
paddedLength = metadata.length % JBMetadataResolver.WORD_SIZE == 0
|
|
220
220
|
? metadata.length
|
|
@@ -223,6 +223,9 @@ library JBMetadataResolver {
|
|
|
223
223
|
assembly ("memory-safe") {
|
|
224
224
|
mstore(metadata, paddedLength)
|
|
225
225
|
}
|
|
226
|
+
unchecked {
|
|
227
|
+
++i;
|
|
228
|
+
}
|
|
226
229
|
}
|
|
227
230
|
}
|
|
228
231
|
|
|
@@ -69,7 +69,7 @@ library JBPayoutSplitGroupLib {
|
|
|
69
69
|
leftoverAmount = amount;
|
|
70
70
|
|
|
71
71
|
// Transfer between all splits.
|
|
72
|
-
for (uint256 i; i < payoutSplits.length;
|
|
72
|
+
for (uint256 i; i < payoutSplits.length;) {
|
|
73
73
|
// Get a reference to the split being iterated on.
|
|
74
74
|
JBSplit memory split = payoutSplits[i];
|
|
75
75
|
|
|
@@ -108,6 +108,9 @@ library JBPayoutSplitGroupLib {
|
|
|
108
108
|
netAmount: netPayoutAmount,
|
|
109
109
|
caller: caller
|
|
110
110
|
});
|
|
111
|
+
unchecked {
|
|
112
|
+
++i;
|
|
113
|
+
}
|
|
111
114
|
}
|
|
112
115
|
}
|
|
113
116
|
|
|
@@ -30,10 +30,13 @@ library JBSurplus {
|
|
|
30
30
|
uint256 numberOfTerminals = terminals.length;
|
|
31
31
|
|
|
32
32
|
// Add the current surplus for each terminal.
|
|
33
|
-
for (uint256 i; i < numberOfTerminals;
|
|
33
|
+
for (uint256 i; i < numberOfTerminals;) {
|
|
34
34
|
surplus += terminals[i].currentSurplusOf({
|
|
35
35
|
projectId: projectId, tokens: tokens, decimals: decimals, currency: currency
|
|
36
36
|
});
|
|
37
|
+
unchecked {
|
|
38
|
+
++i;
|
|
39
|
+
}
|
|
37
40
|
}
|
|
38
41
|
}
|
|
39
42
|
}
|
|
@@ -386,6 +386,7 @@ contract TestForwardedTokenConsumption_Local is TestBaseWorkflow {
|
|
|
386
386
|
returns (JBFundAccessLimitGroup[] memory)
|
|
387
387
|
{
|
|
388
388
|
JBCurrencyAmount[] memory payoutLimits = new JBCurrencyAmount[](1);
|
|
389
|
+
// forge-lint: disable-next-line(unsafe-typecast)
|
|
389
390
|
payoutLimits[0] =
|
|
390
391
|
JBCurrencyAmount({amount: uint224(payoutAmount), currency: uint32(uint160(address(usdcToken())))});
|
|
391
392
|
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
// SPDX-License-Identifier: MIT
|
|
2
|
+
pragma solidity ^0.8.6;
|
|
3
|
+
|
|
4
|
+
import {TestBaseWorkflow} from "../helpers/TestBaseWorkflow.sol";
|
|
5
|
+
import {IJBController} from "../../src/interfaces/IJBController.sol";
|
|
6
|
+
import {IJBDirectory} from "../../src/interfaces/IJBDirectory.sol";
|
|
7
|
+
import {IJBTerminal} from "../../src/interfaces/IJBTerminal.sol";
|
|
8
|
+
import {JBMultiTerminal} from "../../src/JBMultiTerminal.sol";
|
|
9
|
+
import {JBConstants} from "../../src/libraries/JBConstants.sol";
|
|
10
|
+
import {JBAccountingContext} from "../../src/structs/JBAccountingContext.sol";
|
|
11
|
+
import {JBFundAccessLimitGroup} from "../../src/structs/JBFundAccessLimitGroup.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
|
+
import {IJBRulesetApprovalHook} from "../../src/interfaces/IJBRulesetApprovalHook.sol";
|
|
17
|
+
|
|
18
|
+
/// @notice Verifies that `useTotalSurplusForCashOuts` trusts every terminal in the directory,
|
|
19
|
+
/// even though settlement still comes from the specific terminal the holder cashes out through.
|
|
20
|
+
contract CrossTerminalSurplusSpoof_Local is TestBaseWorkflow {
|
|
21
|
+
IJBController private _controller;
|
|
22
|
+
IJBDirectory private _directory;
|
|
23
|
+
JBMultiTerminal private _terminal;
|
|
24
|
+
address private _projectOwner;
|
|
25
|
+
address private _holder;
|
|
26
|
+
uint32 private _nativeCurrency;
|
|
27
|
+
uint256 private _projectId;
|
|
28
|
+
|
|
29
|
+
function setUp() public override {
|
|
30
|
+
super.setUp();
|
|
31
|
+
|
|
32
|
+
_controller = jbController();
|
|
33
|
+
_directory = jbDirectory();
|
|
34
|
+
_terminal = jbMultiTerminal();
|
|
35
|
+
_projectOwner = multisig();
|
|
36
|
+
_holder = beneficiary();
|
|
37
|
+
_nativeCurrency = uint32(uint160(JBConstants.NATIVE_TOKEN));
|
|
38
|
+
|
|
39
|
+
JBRulesetMetadata memory metadata = JBRulesetMetadata({
|
|
40
|
+
reservedPercent: 0,
|
|
41
|
+
cashOutTaxRate: 0,
|
|
42
|
+
baseCurrency: _nativeCurrency,
|
|
43
|
+
pausePay: false,
|
|
44
|
+
pauseCreditTransfers: false,
|
|
45
|
+
allowOwnerMinting: true,
|
|
46
|
+
allowSetCustomToken: true,
|
|
47
|
+
allowTerminalMigration: false,
|
|
48
|
+
allowSetTerminals: true,
|
|
49
|
+
allowSetController: false,
|
|
50
|
+
allowAddAccountingContext: true,
|
|
51
|
+
allowAddPriceFeed: false,
|
|
52
|
+
ownerMustSendPayouts: false,
|
|
53
|
+
holdFees: false,
|
|
54
|
+
useTotalSurplusForCashOuts: true,
|
|
55
|
+
useDataHookForPay: false,
|
|
56
|
+
useDataHookForCashOut: false,
|
|
57
|
+
dataHook: address(0),
|
|
58
|
+
metadata: 0
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
JBRulesetConfig[] memory rulesetConfigs = new JBRulesetConfig[](1);
|
|
62
|
+
rulesetConfigs[0] = JBRulesetConfig({
|
|
63
|
+
mustStartAtOrAfter: 0,
|
|
64
|
+
duration: 0,
|
|
65
|
+
weight: 1000e18,
|
|
66
|
+
weightCutPercent: 0,
|
|
67
|
+
approvalHook: IJBRulesetApprovalHook(address(0)),
|
|
68
|
+
metadata: metadata,
|
|
69
|
+
splitGroups: new JBSplitGroup[](0),
|
|
70
|
+
fundAccessLimitGroups: new JBFundAccessLimitGroup[](0)
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
JBAccountingContext[] memory contexts = new JBAccountingContext[](1);
|
|
74
|
+
contexts[0] = JBAccountingContext({token: JBConstants.NATIVE_TOKEN, decimals: 18, currency: _nativeCurrency});
|
|
75
|
+
|
|
76
|
+
JBTerminalConfig[] memory terminalConfigs = new JBTerminalConfig[](1);
|
|
77
|
+
terminalConfigs[0] = JBTerminalConfig({terminal: _terminal, accountingContextsToAccept: contexts});
|
|
78
|
+
|
|
79
|
+
_projectId = _controller.launchProjectFor({
|
|
80
|
+
owner: _projectOwner,
|
|
81
|
+
projectUri: "spoofed-surplus",
|
|
82
|
+
rulesetConfigurations: rulesetConfigs,
|
|
83
|
+
terminalConfigurations: terminalConfigs,
|
|
84
|
+
memo: ""
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
function test_partialCashOutCanDrainLocalTerminalUsingSpoofedSiblingSurplus() external {
|
|
89
|
+
vm.deal(_holder, 1 ether);
|
|
90
|
+
|
|
91
|
+
vm.prank(_holder);
|
|
92
|
+
uint256 minted = _terminal.pay{value: 1 ether}({
|
|
93
|
+
projectId: _projectId,
|
|
94
|
+
token: JBConstants.NATIVE_TOKEN,
|
|
95
|
+
amount: 1 ether,
|
|
96
|
+
beneficiary: _holder,
|
|
97
|
+
minReturnedTokens: 0,
|
|
98
|
+
memo: "",
|
|
99
|
+
metadata: ""
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
assertEq(address(_terminal).balance, 1 ether, "terminal should hold the paid ETH");
|
|
103
|
+
|
|
104
|
+
address spoofedTerminal = makeAddr("spoofedTerminal");
|
|
105
|
+
IJBTerminal[] memory terminals = new IJBTerminal[](2);
|
|
106
|
+
terminals[0] = IJBTerminal(address(_terminal));
|
|
107
|
+
terminals[1] = IJBTerminal(spoofedTerminal);
|
|
108
|
+
|
|
109
|
+
vm.prank(_projectOwner);
|
|
110
|
+
_directory.setTerminalsOf(_projectId, terminals);
|
|
111
|
+
|
|
112
|
+
vm.mockCall(
|
|
113
|
+
spoofedTerminal,
|
|
114
|
+
abi.encodeCall(IJBTerminal.currentSurplusOf, (_projectId, new address[](0), 18, _nativeCurrency)),
|
|
115
|
+
abi.encode(1 ether)
|
|
116
|
+
);
|
|
117
|
+
|
|
118
|
+
uint256 holderBalanceBefore = _holder.balance;
|
|
119
|
+
|
|
120
|
+
vm.prank(_holder);
|
|
121
|
+
uint256 reclaimed = _terminal.cashOutTokensOf({
|
|
122
|
+
holder: _holder,
|
|
123
|
+
projectId: _projectId,
|
|
124
|
+
cashOutCount: minted / 2,
|
|
125
|
+
tokenToReclaim: JBConstants.NATIVE_TOKEN,
|
|
126
|
+
minTokensReclaimed: 0,
|
|
127
|
+
beneficiary: payable(_holder),
|
|
128
|
+
metadata: new bytes(0)
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
assertEq(reclaimed, 1 ether, "spoofed global surplus should let a half burn reclaim the full local balance");
|
|
132
|
+
assertEq(_holder.balance - holderBalanceBefore, 1 ether, "holder should receive the full terminal balance");
|
|
133
|
+
assertEq(address(_terminal).balance, 0, "the honest terminal should be fully drained");
|
|
134
|
+
assertEq(
|
|
135
|
+
jbTokens().totalBalanceOf(_holder, _projectId),
|
|
136
|
+
minted / 2,
|
|
137
|
+
"the holder should keep half their project tokens after draining the terminal"
|
|
138
|
+
);
|
|
139
|
+
}
|
|
140
|
+
}
|
|
@@ -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";
|
|
@@ -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";
|