@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.
@@ -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
- if (address(DIRECTORY.controllerOf(projectId)) != msg.sender) {
50
- // slither-disable-next-line calls-loop
51
- revert JBControlled_ControllerUnauthorized(address(DIRECTORY.controllerOf(projectId)));
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; i++) {
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; i++) {
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; i++) {
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";