@bananapus/core-v6 0.0.42 → 0.0.44
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/CHANGELOG.md +1 -1
- package/README.md +3 -3
- package/package.json +2 -2
- package/references/entrypoints.md +3 -1
- package/references/types-errors-events.md +2 -3
- package/src/JBChainlinkV3PriceFeed.sol +14 -6
- package/src/JBChainlinkV3SequencerPriceFeed.sol +5 -6
- package/src/JBController.sol +84 -68
- package/src/JBDirectory.sol +4 -7
- package/src/JBERC20.sol +8 -7
- package/src/JBFeelessAddresses.sol +32 -13
- package/src/JBFundAccessLimits.sol +12 -4
- package/src/JBMultiTerminal.sol +39 -61
- package/src/JBPermissions.sol +6 -3
- package/src/JBPrices.sol +18 -12
- package/src/JBRulesets.sol +4 -25
- package/src/JBSplits.sol +13 -5
- package/src/JBTerminalStore.sol +46 -82
- package/src/JBTokens.sol +11 -13
- package/src/abstract/JBControlled.sol +1 -2
- package/src/interfaces/IJBController.sol +0 -6
- package/src/interfaces/IJBFeelessAddresses.sol +17 -7
- package/src/libraries/JBCashOuts.sol +6 -2
- package/src/libraries/JBCurrencyIds.sol +3 -0
- package/src/libraries/JBMetadataResolver.sol +20 -12
- package/src/libraries/JBPayoutSplitGroupLib.sol +8 -3
- package/src/libraries/JBRulesetMetadataResolver.sol +4 -4
- package/src/libraries/JBSplitGroupIds.sol +1 -0
- package/src/periphery/JBDeadline1Day.sol +1 -0
- package/src/periphery/JBDeadline3Days.sol +1 -0
- package/src/periphery/JBDeadline3Hours.sol +1 -0
- package/src/periphery/JBDeadline7Days.sol +1 -0
- package/src/structs/JBBeforeCashOutRecordedContext.sol +3 -2
- package/src/structs/JBRulesetMetadata.sol +3 -3
- package/test/helpers/JBTest.sol +3 -3
package/src/JBDirectory.sol
CHANGED
|
@@ -121,7 +121,7 @@ contract JBDirectory is JBPermissioned, Ownable, IJBDirectory {
|
|
|
121
121
|
|
|
122
122
|
// If setting the controller is not allowed, revert.
|
|
123
123
|
if (!allowSetController) {
|
|
124
|
-
revert JBDirectory_SetControllerNotAllowed(projectId);
|
|
124
|
+
revert JBDirectory_SetControllerNotAllowed({projectId: projectId});
|
|
125
125
|
}
|
|
126
126
|
|
|
127
127
|
// Prepare the new controller to receive the project.
|
|
@@ -140,10 +140,8 @@ contract JBDirectory is JBPermissioned, Ownable, IJBDirectory {
|
|
|
140
140
|
}
|
|
141
141
|
|
|
142
142
|
// Set the new controller after migration completes.
|
|
143
|
-
// slither-disable-next-line reentrancy-no-eth
|
|
144
143
|
controllerOf[projectId] = controller;
|
|
145
144
|
|
|
146
|
-
// slither-disable-next-line reentrancy-events
|
|
147
145
|
emit SetController({projectId: projectId, controller: controller, caller: msg.sender});
|
|
148
146
|
|
|
149
147
|
// Notify the new controller that migration is complete and it is now the active controller.
|
|
@@ -185,7 +183,7 @@ contract JBDirectory is JBPermissioned, Ownable, IJBDirectory {
|
|
|
185
183
|
|
|
186
184
|
// If the terminal doesn't accept the token, revert.
|
|
187
185
|
if (terminal.accountingContextForTokenOf({projectId: projectId, token: token}).token == address(0)) {
|
|
188
|
-
revert JBDirectory_TokenNotAccepted(projectId, token, terminal);
|
|
186
|
+
revert JBDirectory_TokenNotAccepted({projectId: projectId, token: token, terminal: terminal});
|
|
189
187
|
}
|
|
190
188
|
|
|
191
189
|
// If the terminal is not already in the project's terminal list, require ADD_TERMINALS permission.
|
|
@@ -229,7 +227,7 @@ contract JBDirectory is JBPermissioned, Ownable, IJBDirectory {
|
|
|
229
227
|
|
|
230
228
|
// If the caller is not the project's controller, the project's ruleset must allow setting terminals.
|
|
231
229
|
if (msg.sender != address(controller) && !allowSetTerminals) {
|
|
232
|
-
revert JBDirectory_SetTerminalsNotAllowed(projectId);
|
|
230
|
+
revert JBDirectory_SetTerminalsNotAllowed({projectId: projectId});
|
|
233
231
|
}
|
|
234
232
|
|
|
235
233
|
// Set the stored terminals for the project.
|
|
@@ -286,7 +284,6 @@ contract JBDirectory is JBPermissioned, Ownable, IJBDirectory {
|
|
|
286
284
|
IJBTerminal terminal = terminals[i];
|
|
287
285
|
|
|
288
286
|
// If the terminal accepts the specified token, return it.
|
|
289
|
-
// slither-disable-next-line calls-loop
|
|
290
287
|
if (terminal.accountingContextForTokenOf({projectId: projectId, token: token}).token != address(0)) {
|
|
291
288
|
return terminal;
|
|
292
289
|
}
|
|
@@ -355,7 +352,7 @@ contract JBDirectory is JBPermissioned, Ownable, IJBDirectory {
|
|
|
355
352
|
|
|
356
353
|
// The project's ruleset must allow setting terminals.
|
|
357
354
|
if (!allowSetTerminals) {
|
|
358
|
-
revert JBDirectory_SetTerminalsNotAllowed(projectId);
|
|
355
|
+
revert JBDirectory_SetTerminalsNotAllowed({projectId: projectId});
|
|
359
356
|
}
|
|
360
357
|
|
|
361
358
|
// Add the new terminal.
|
package/src/JBERC20.sol
CHANGED
|
@@ -25,8 +25,8 @@ contract JBERC20 is ERC20Votes, ERC20Permit, JBPermissioned, IERC1271, IJBToken
|
|
|
25
25
|
// --------------------------- custom errors ------------------------- //
|
|
26
26
|
//*********************************************************************//
|
|
27
27
|
|
|
28
|
-
error JBERC20_AlreadyInitialized();
|
|
29
|
-
error JBERC20_Unauthorized();
|
|
28
|
+
error JBERC20_AlreadyInitialized(uint256 currentNameLength, uint256 newNameLength);
|
|
29
|
+
error JBERC20_Unauthorized(address caller, address tokens);
|
|
30
30
|
|
|
31
31
|
//*********************************************************************//
|
|
32
32
|
// --------------- public immutable stored properties ---------------- //
|
|
@@ -48,11 +48,9 @@ contract JBERC20 is ERC20Votes, ERC20Permit, JBPermissioned, IERC1271, IJBToken
|
|
|
48
48
|
//*********************************************************************//
|
|
49
49
|
|
|
50
50
|
/// @notice The token's name.
|
|
51
|
-
// slither-disable-next-line shadowing-state
|
|
52
51
|
string private _name;
|
|
53
52
|
|
|
54
53
|
/// @notice The token's symbol.
|
|
55
|
-
// slither-disable-next-line shadowing-state
|
|
56
54
|
string private _symbol;
|
|
57
55
|
|
|
58
56
|
//*********************************************************************//
|
|
@@ -82,7 +80,7 @@ contract JBERC20 is ERC20Votes, ERC20Permit, JBPermissioned, IERC1271, IJBToken
|
|
|
82
80
|
/// @notice Only the JBTokens contract can call this function.
|
|
83
81
|
// forge-lint: disable-next-line(unwrapped-modifier-logic)
|
|
84
82
|
modifier onlyTokens() {
|
|
85
|
-
if (msg.sender != address(TOKENS)) revert JBERC20_Unauthorized();
|
|
83
|
+
if (msg.sender != address(TOKENS)) revert JBERC20_Unauthorized({caller: msg.sender, tokens: address(TOKENS)});
|
|
86
84
|
_;
|
|
87
85
|
}
|
|
88
86
|
|
|
@@ -132,7 +130,6 @@ contract JBERC20 is ERC20Votes, ERC20Permit, JBPermissioned, IERC1271, IJBToken
|
|
|
132
130
|
/// @return magicValue `0x1626ba7e` if the signature is valid, `0xffffffff` otherwise.
|
|
133
131
|
function isValidSignature(bytes32 hash, bytes memory signature) external view override returns (bytes4 magicValue) {
|
|
134
132
|
// Recover the signer from the signature. Return invalid if recovery fails.
|
|
135
|
-
// slither-disable-next-line unused-return
|
|
136
133
|
(address signer, ECDSA.RecoverError error,) = ECDSA.tryRecover(hash, signature);
|
|
137
134
|
if (error != ECDSA.RecoverError.NoError) return 0xffffffff;
|
|
138
135
|
|
|
@@ -201,7 +198,11 @@ contract JBERC20 is ERC20Votes, ERC20Permit, JBPermissioned, IERC1271, IJBToken
|
|
|
201
198
|
/// @param tokens The JBTokens contract that manages this token.
|
|
202
199
|
function initialize(string memory name_, string memory symbol_, address tokens) public override {
|
|
203
200
|
// Prevent re-initialization by reverting if a name is already set or if the provided name is empty.
|
|
204
|
-
if (bytes(_name).length != 0 || bytes(name_).length == 0)
|
|
201
|
+
if (bytes(_name).length != 0 || bytes(name_).length == 0) {
|
|
202
|
+
revert JBERC20_AlreadyInitialized({
|
|
203
|
+
currentNameLength: bytes(_name).length, newNameLength: bytes(name_).length
|
|
204
|
+
});
|
|
205
|
+
}
|
|
205
206
|
|
|
206
207
|
_name = name_;
|
|
207
208
|
_symbol = symbol_;
|
|
@@ -7,19 +7,17 @@ import {IERC165} from "@openzeppelin/contracts/utils/introspection/IERC165.sol";
|
|
|
7
7
|
import {IJBFeelessAddresses} from "./interfaces/IJBFeelessAddresses.sol";
|
|
8
8
|
|
|
9
9
|
/// @notice A registry of addresses exempt from the protocol's 2.5% fee. Feeless addresses don't incur fees on
|
|
10
|
-
/// payouts they receive, surplus allowance they use, or cash outs where they are the beneficiary.
|
|
11
|
-
/// contract owner (typically the protocol multisig).
|
|
10
|
+
/// payouts they receive, surplus allowance they use, or cash outs where they are the beneficiary.
|
|
11
|
+
/// @dev All feeless status is managed by the contract owner (typically the protocol multisig).
|
|
12
|
+
/// @dev `projectId = 0` is the wildcard — an address feeless for project 0 is feeless for ALL projects.
|
|
12
13
|
contract JBFeelessAddresses is Ownable, IJBFeelessAddresses, IERC165 {
|
|
13
14
|
//*********************************************************************//
|
|
14
|
-
//
|
|
15
|
+
// -------------------- internal stored properties ------------------- //
|
|
15
16
|
//*********************************************************************//
|
|
16
17
|
|
|
17
|
-
/// @notice
|
|
18
|
-
/// @dev
|
|
19
|
-
|
|
20
|
-
/// @dev Feeless addresses can be the beneficiary of cash outs without incurring a fee.
|
|
21
|
-
/// @custom:param addr The address to check.
|
|
22
|
-
mapping(address addr => bool) public override isFeeless;
|
|
18
|
+
/// @notice Raw feeless status per project per address.
|
|
19
|
+
/// @dev `projectId = 0` stores the global (all-project) feeless status.
|
|
20
|
+
mapping(uint256 projectId => mapping(address addr => bool)) internal _isFeelessFor;
|
|
23
21
|
|
|
24
22
|
//*********************************************************************//
|
|
25
23
|
// -------------------------- constructor ---------------------------- //
|
|
@@ -32,21 +30,42 @@ contract JBFeelessAddresses is Ownable, IJBFeelessAddresses, IERC165 {
|
|
|
32
30
|
// ---------------------- external transactions ---------------------- //
|
|
33
31
|
//*********************************************************************//
|
|
34
32
|
|
|
35
|
-
/// @notice Add or remove an address from the fee-exempt list.
|
|
36
|
-
///
|
|
33
|
+
/// @notice Add or remove an address from the global (all-project) fee-exempt list.
|
|
34
|
+
/// @dev Equivalent to `setFeelessAddressFor(0, addr, flag)`.
|
|
37
35
|
/// @dev Can only be called by this contract's owner (typically the protocol multisig).
|
|
38
36
|
/// @param addr The address to set as feeless or not feeless.
|
|
39
37
|
/// @param flag Whether the address should be feeless (`true`) or not feeless (`false`).
|
|
40
38
|
function setFeelessAddress(address addr, bool flag) external virtual override onlyOwner {
|
|
41
|
-
|
|
39
|
+
_isFeelessFor[0][addr] = flag;
|
|
42
40
|
|
|
43
|
-
emit SetFeelessAddress({addr: addr, isFeeless: flag, caller: _msgSender()});
|
|
41
|
+
emit SetFeelessAddress({projectId: 0, addr: addr, isFeeless: flag, caller: _msgSender()});
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/// @notice Add or remove an address from a project's fee-exempt list.
|
|
45
|
+
/// @dev Can only be called by this contract's owner (typically the protocol multisig).
|
|
46
|
+
/// @dev Use `projectId = 0` to set the global (all-project) feeless status.
|
|
47
|
+
/// @param projectId The ID of the project. 0 means all projects.
|
|
48
|
+
/// @param addr The address to set as feeless or not feeless for the project.
|
|
49
|
+
/// @param flag Whether the address should be feeless for the project (`true`) or not (`false`).
|
|
50
|
+
function setFeelessAddressFor(uint256 projectId, address addr, bool flag) external virtual override onlyOwner {
|
|
51
|
+
_isFeelessFor[projectId][addr] = flag;
|
|
52
|
+
|
|
53
|
+
emit SetFeelessAddress({projectId: projectId, addr: addr, isFeeless: flag, caller: _msgSender()});
|
|
44
54
|
}
|
|
45
55
|
|
|
46
56
|
//*********************************************************************//
|
|
47
57
|
// -------------------------- public views --------------------------- //
|
|
48
58
|
//*********************************************************************//
|
|
49
59
|
|
|
60
|
+
/// @notice Returns whether the specified address is feeless for a specific project, considering both the wildcard
|
|
61
|
+
/// (projectId 0) and project-specific feeless status.
|
|
62
|
+
/// @param addr The address to check.
|
|
63
|
+
/// @param projectId The ID of the project to check.
|
|
64
|
+
/// @return A flag indicating whether the address is feeless (globally or for the project).
|
|
65
|
+
function isFeelessFor(address addr, uint256 projectId) external view override returns (bool) {
|
|
66
|
+
return _isFeelessFor[0][addr] || _isFeelessFor[projectId][addr];
|
|
67
|
+
}
|
|
68
|
+
|
|
50
69
|
/// @notice Indicates whether this contract adheres to the specified interface.
|
|
51
70
|
/// @dev See {IERC165-supportsInterface}.
|
|
52
71
|
/// @param interfaceId The ID of the interface to check for adherence to.
|
|
@@ -18,8 +18,12 @@ contract JBFundAccessLimits is JBControlled, IJBFundAccessLimits {
|
|
|
18
18
|
// --------------------------- custom errors ------------------------- //
|
|
19
19
|
//*********************************************************************//
|
|
20
20
|
|
|
21
|
-
error JBFundAccessLimits_InvalidPayoutLimitCurrencyOrdering(
|
|
22
|
-
|
|
21
|
+
error JBFundAccessLimits_InvalidPayoutLimitCurrencyOrdering(
|
|
22
|
+
uint256 projectId, uint256 rulesetId, uint256 groupIndex, uint256 limitIndex
|
|
23
|
+
);
|
|
24
|
+
error JBFundAccessLimits_InvalidSurplusAllowanceCurrencyOrdering(
|
|
25
|
+
uint256 projectId, uint256 rulesetId, uint256 groupIndex, uint256 allowanceIndex
|
|
26
|
+
);
|
|
23
27
|
|
|
24
28
|
//*********************************************************************//
|
|
25
29
|
// --------------------- internal stored properties ------------------ //
|
|
@@ -102,7 +106,9 @@ contract JBFundAccessLimits is JBControlled, IJBFundAccessLimits {
|
|
|
102
106
|
// Make sure the payout limits are passed in strictly increasing order (sorted by currency) to prevent
|
|
103
107
|
// duplicates.
|
|
104
108
|
if (j != 0 && payoutLimit.currency <= fundAccessLimitGroup.payoutLimits[j - 1].currency) {
|
|
105
|
-
revert JBFundAccessLimits_InvalidPayoutLimitCurrencyOrdering(
|
|
109
|
+
revert JBFundAccessLimits_InvalidPayoutLimitCurrencyOrdering({
|
|
110
|
+
projectId: projectId, rulesetId: rulesetId, groupIndex: i, limitIndex: j
|
|
111
|
+
});
|
|
106
112
|
}
|
|
107
113
|
|
|
108
114
|
// Set the payout limit if there is one.
|
|
@@ -127,7 +133,9 @@ contract JBFundAccessLimits is JBControlled, IJBFundAccessLimits {
|
|
|
127
133
|
// Make sure the surplus allowances are passed in strictly increasing order (sorted by currency) to
|
|
128
134
|
// prevent duplicates.
|
|
129
135
|
if (j != 0 && surplusAllowance.currency <= fundAccessLimitGroup.surplusAllowances[j - 1].currency) {
|
|
130
|
-
revert JBFundAccessLimits_InvalidSurplusAllowanceCurrencyOrdering(
|
|
136
|
+
revert JBFundAccessLimits_InvalidSurplusAllowanceCurrencyOrdering({
|
|
137
|
+
projectId: projectId, rulesetId: rulesetId, groupIndex: i, allowanceIndex: j
|
|
138
|
+
});
|
|
131
139
|
}
|
|
132
140
|
|
|
133
141
|
// Set the surplus allowance if there is one.
|
package/src/JBMultiTerminal.sol
CHANGED
|
@@ -64,7 +64,7 @@ contract JBMultiTerminal is JBPermissioned, ERC2771Context, IJBMultiTerminal {
|
|
|
64
64
|
//*********************************************************************//
|
|
65
65
|
|
|
66
66
|
error JBMultiTerminal_FeeTerminalNotFound(address token);
|
|
67
|
-
error JBMultiTerminal_MintNotAllowed();
|
|
67
|
+
error JBMultiTerminal_MintNotAllowed(uint256 projectId, uint256 splitProjectId, address terminal);
|
|
68
68
|
error JBMultiTerminal_NoMsgValueAllowed(uint256 value);
|
|
69
69
|
error JBMultiTerminal_OverflowAlert(uint256 value, uint256 limit);
|
|
70
70
|
error JBMultiTerminal_PermitAllowanceNotEnough(uint256 amount, uint256 allowance);
|
|
@@ -213,7 +213,6 @@ contract JBMultiTerminal is JBPermissioned, ERC2771Context, IJBMultiTerminal {
|
|
|
213
213
|
|
|
214
214
|
// Emit an event for each accounting context.
|
|
215
215
|
for (uint256 i; i < accountingContexts.length;) {
|
|
216
|
-
// slither-disable-next-line reentrancy-events
|
|
217
216
|
emit SetAccountingContext({projectId: projectId, context: accountingContexts[i], caller: _msgSender()});
|
|
218
217
|
unchecked {
|
|
219
218
|
++i;
|
|
@@ -248,15 +247,15 @@ contract JBMultiTerminal is JBPermissioned, ERC2771Context, IJBMultiTerminal {
|
|
|
248
247
|
_addToBalanceOf({
|
|
249
248
|
projectId: projectId,
|
|
250
249
|
token: token,
|
|
251
|
-
amount: _acceptFundsFor(projectId, token, amount, metadata),
|
|
250
|
+
amount: _acceptFundsFor({projectId: projectId, token: token, amount: amount, metadata: metadata}),
|
|
252
251
|
shouldReturnHeldFees: shouldReturnHeldFees,
|
|
253
252
|
memo: memo,
|
|
254
253
|
metadata: metadata
|
|
255
254
|
});
|
|
256
255
|
}
|
|
257
256
|
|
|
258
|
-
/// @notice
|
|
259
|
-
///
|
|
257
|
+
/// @notice Cash out project tokens. The project's current ruleset determines the reclaimed surplus and any data
|
|
258
|
+
/// hook or cash out hook behavior.
|
|
260
259
|
/// @dev Only the token holder or an operator with `CASH_OUT_TOKENS` permission from that holder can call this.
|
|
261
260
|
/// @param holder The account cashing out tokens.
|
|
262
261
|
/// @param projectId The ID of the project the project tokens belong to.
|
|
@@ -330,12 +329,12 @@ contract JBMultiTerminal is JBPermissioned, ERC2771Context, IJBMultiTerminal {
|
|
|
330
329
|
if (split.hook != IJBSplitHook(address(0))) {
|
|
331
330
|
// Make sure that the address supports the split hook interface.
|
|
332
331
|
if (!split.hook.supportsInterface(type(IJBSplitHook).interfaceId)) {
|
|
333
|
-
revert JBMultiTerminal_SplitHookInvalid(split.hook);
|
|
332
|
+
revert JBMultiTerminal_SplitHookInvalid({hook: split.hook});
|
|
334
333
|
}
|
|
335
334
|
|
|
336
335
|
// This payout is eligible for a fee since the funds are leaving this contract and the split hook isn't a
|
|
337
336
|
// feeless address.
|
|
338
|
-
if (!_isFeeless(address(split.hook))) {
|
|
337
|
+
if (!_isFeeless({addr: address(split.hook), projectId: projectId})) {
|
|
339
338
|
unchecked {
|
|
340
339
|
netPayoutAmount -= _feeAmountFrom(amount);
|
|
341
340
|
}
|
|
@@ -370,7 +369,7 @@ contract JBMultiTerminal is JBPermissioned, ERC2771Context, IJBMultiTerminal {
|
|
|
370
369
|
|
|
371
370
|
// The project must have a terminal to send funds to.
|
|
372
371
|
if (terminal == IJBTerminal(address(0))) {
|
|
373
|
-
revert JBMultiTerminal_RecipientProjectTerminalNotFound(split.projectId, token);
|
|
372
|
+
revert JBMultiTerminal_RecipientProjectTerminalNotFound({projectId: split.projectId, token: token});
|
|
374
373
|
}
|
|
375
374
|
|
|
376
375
|
// Fees apply to fund egress, not intra-terminal accounting. When both projects share this terminal,
|
|
@@ -378,7 +377,7 @@ contract JBMultiTerminal is JBPermissioned, ERC2771Context, IJBMultiTerminal {
|
|
|
378
377
|
// the fee model taxes value leaving the protocol ecosystem, not internal rebalancing.
|
|
379
378
|
// This payout is eligible for a fee if the funds are leaving this contract and the receiving terminal isn't
|
|
380
379
|
// a feeless address.
|
|
381
|
-
if (terminal != this && !_isFeeless(address(terminal))) {
|
|
380
|
+
if (terminal != this && !_isFeeless({addr: address(terminal), projectId: projectId})) {
|
|
382
381
|
unchecked {
|
|
383
382
|
netPayoutAmount -= _feeAmountFrom(amount);
|
|
384
383
|
}
|
|
@@ -409,7 +408,9 @@ contract JBMultiTerminal is JBPermissioned, ERC2771Context, IJBMultiTerminal {
|
|
|
409
408
|
// Cross-project pay splits on the same terminal are allowed (different project receives the funds).
|
|
410
409
|
// The try-catch in the split group lib catches this revert and restores the balance.
|
|
411
410
|
if (terminal == this && split.projectId == projectId) {
|
|
412
|
-
revert JBMultiTerminal_MintNotAllowed(
|
|
411
|
+
revert JBMultiTerminal_MintNotAllowed({
|
|
412
|
+
projectId: projectId, splitProjectId: split.projectId, terminal: address(terminal)
|
|
413
|
+
});
|
|
413
414
|
}
|
|
414
415
|
|
|
415
416
|
// Keep a reference to the beneficiary of the payment.
|
|
@@ -429,7 +430,6 @@ contract JBMultiTerminal is JBPermissioned, ERC2771Context, IJBMultiTerminal {
|
|
|
429
430
|
// destination project's data hook forwarded part of the payment to pay hooks, the store
|
|
430
431
|
// only recorded a partial balance increase. Without this cap, _feeFreeSurplusOf can exceed
|
|
431
432
|
// STORE.balanceOf, causing users to be overcharged fees on zero-tax cashouts.
|
|
432
|
-
// slither-disable-next-line reentrancy-eth
|
|
433
433
|
_capFeeFreeSurplus({projectId: split.projectId, token: token});
|
|
434
434
|
}
|
|
435
435
|
} else {
|
|
@@ -440,7 +440,7 @@ contract JBMultiTerminal is JBPermissioned, ERC2771Context, IJBMultiTerminal {
|
|
|
440
440
|
|
|
441
441
|
// This payout is eligible for a fee since the funds are leaving this contract and the recipient isn't a
|
|
442
442
|
// feeless address.
|
|
443
|
-
if (!_isFeeless(recipient)) {
|
|
443
|
+
if (!_isFeeless({addr: recipient, projectId: projectId})) {
|
|
444
444
|
unchecked {
|
|
445
445
|
netPayoutAmount -= _feeAmountFrom(amount);
|
|
446
446
|
}
|
|
@@ -473,7 +473,7 @@ contract JBMultiTerminal is JBPermissioned, ERC2771Context, IJBMultiTerminal {
|
|
|
473
473
|
require(msg.sender == address(this));
|
|
474
474
|
|
|
475
475
|
if (address(feeTerminal) == address(0)) {
|
|
476
|
-
revert JBMultiTerminal_FeeTerminalNotFound(token);
|
|
476
|
+
revert JBMultiTerminal_FeeTerminalNotFound({token: token});
|
|
477
477
|
}
|
|
478
478
|
|
|
479
479
|
// Send the projectId in the metadata.
|
|
@@ -537,7 +537,6 @@ contract JBMultiTerminal is JBPermissioned, ERC2771Context, IJBMultiTerminal {
|
|
|
537
537
|
// sends fees to the fee project terminal. The migrated project's balance on this terminal is zero, but held
|
|
538
538
|
// fees are backed by the terminal's own token balance (not the project's recorded balance).
|
|
539
539
|
// Record the migration in the store.
|
|
540
|
-
// slither-disable-next-line reentrancy-events
|
|
541
540
|
balance = STORE.recordTerminalMigration({projectId: projectId, token: token});
|
|
542
541
|
|
|
543
542
|
emit MigrateTerminal({projectId: projectId, token: token, to: to, amount: balance, caller: _msgSender()});
|
|
@@ -547,7 +546,7 @@ contract JBMultiTerminal is JBPermissioned, ERC2771Context, IJBMultiTerminal {
|
|
|
547
546
|
// Migration to a non-feeless terminal incurs the standard 2.5% fee, same as any other fund egress.
|
|
548
547
|
// This also settles any fee-free surplus liability that would otherwise be lost on the new terminal.
|
|
549
548
|
uint256 feeAmount;
|
|
550
|
-
if (!_isFeeless(address(to)) && projectId != _FEE_BENEFICIARY_PROJECT_ID) {
|
|
549
|
+
if (!_isFeeless({addr: address(to), projectId: projectId}) && projectId != _FEE_BENEFICIARY_PROJECT_ID) {
|
|
551
550
|
feeAmount = _takeFeeFrom({
|
|
552
551
|
projectId: projectId,
|
|
553
552
|
token: token,
|
|
@@ -566,8 +565,8 @@ contract JBMultiTerminal is JBPermissioned, ERC2771Context, IJBMultiTerminal {
|
|
|
566
565
|
}
|
|
567
566
|
}
|
|
568
567
|
|
|
569
|
-
/// @notice Pay a project
|
|
570
|
-
///
|
|
568
|
+
/// @notice Pay a project. The project's current ruleset determines how many project tokens the beneficiary receives
|
|
569
|
+
/// and any data hook or pay hook behavior.
|
|
571
570
|
/// @param projectId The ID of the project to pay.
|
|
572
571
|
/// @param amount The amount of tokens to send, as a fixed point number with the same number of
|
|
573
572
|
/// decimals as the token's accounting context. If this terminal's token is native, this is ignored and `msg.value`
|
|
@@ -669,7 +668,6 @@ contract JBMultiTerminal is JBPermissioned, ERC2771Context, IJBMultiTerminal {
|
|
|
669
668
|
_nextHeldFeeIndexOf[projectId][token] = currentIndex + 1;
|
|
670
669
|
|
|
671
670
|
// Process the fee.
|
|
672
|
-
// slither-disable-next-line reentrancy-no-eth
|
|
673
671
|
_processFee({
|
|
674
672
|
projectId: projectId,
|
|
675
673
|
token: token,
|
|
@@ -906,16 +904,16 @@ contract JBMultiTerminal is JBPermissioned, ERC2771Context, IJBMultiTerminal {
|
|
|
906
904
|
JBCashOutHookSpecification[] memory hookSpecifications
|
|
907
905
|
)
|
|
908
906
|
{
|
|
909
|
-
(
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
907
|
+
bool feeless = _isFeeless({addr: beneficiary, projectId: projectId});
|
|
908
|
+
(ruleset, reclaimAmount, cashOutTaxRate, hookSpecifications) = STORE.previewCashOutFrom({
|
|
909
|
+
terminal: address(this),
|
|
910
|
+
holder: holder,
|
|
911
|
+
projectId: projectId,
|
|
912
|
+
cashOutCount: cashOutCount,
|
|
913
|
+
tokenToReclaim: tokenToReclaim,
|
|
914
|
+
beneficiaryIsFeeless: feeless,
|
|
915
|
+
metadata: metadata
|
|
916
|
+
});
|
|
919
917
|
}
|
|
920
918
|
|
|
921
919
|
/// @notice Simulates a payment without modifying state — use this to preview how many project tokens a payer
|
|
@@ -1019,7 +1017,7 @@ contract JBMultiTerminal is JBPermissioned, ERC2771Context, IJBMultiTerminal {
|
|
|
1019
1017
|
|
|
1020
1018
|
// Make sure the permit allowance is enough for this payment. If not we revert early.
|
|
1021
1019
|
if (amount > allowance.amount) {
|
|
1022
|
-
revert JBMultiTerminal_PermitAllowanceNotEnough(amount, allowance.amount);
|
|
1020
|
+
revert JBMultiTerminal_PermitAllowanceNotEnough({amount: amount, allowance: allowance.amount});
|
|
1023
1021
|
}
|
|
1024
1022
|
|
|
1025
1023
|
// Keep a reference to the permit rules.
|
|
@@ -1034,8 +1032,7 @@ contract JBMultiTerminal is JBPermissioned, ERC2771Context, IJBMultiTerminal {
|
|
|
1034
1032
|
// Set the allowance to `spend` tokens for the user.
|
|
1035
1033
|
try PERMIT2.permit({owner: _msgSender(), permitSingle: permitSingle, signature: allowance.signature}) {}
|
|
1036
1034
|
catch (bytes memory reason) {
|
|
1037
|
-
|
|
1038
|
-
emit Permit2AllowanceFailed(token, _msgSender(), reason);
|
|
1035
|
+
emit Permit2AllowanceFailed({token: token, owner: _msgSender(), reason: reason});
|
|
1039
1036
|
}
|
|
1040
1037
|
}
|
|
1041
1038
|
|
|
@@ -1118,7 +1115,6 @@ contract JBMultiTerminal is JBPermissioned, ERC2771Context, IJBMultiTerminal {
|
|
|
1118
1115
|
|
|
1119
1116
|
// Cap fee-free surplus at the remaining balance.
|
|
1120
1117
|
if (feeFreeSurplus > remainingBalance) {
|
|
1121
|
-
// slither-disable-next-line reentrancy-no-eth,reentrancy-eth,reentrancy-benign
|
|
1122
1118
|
_feeFreeSurplusOf[projectId][token] = remainingBalance;
|
|
1123
1119
|
}
|
|
1124
1120
|
}
|
|
@@ -1159,7 +1155,7 @@ contract JBMultiTerminal is JBPermissioned, ERC2771Context, IJBMultiTerminal {
|
|
|
1159
1155
|
uint256 cashOutTaxRate;
|
|
1160
1156
|
|
|
1161
1157
|
// Cache whether the beneficiary is feeless.
|
|
1162
|
-
bool beneficiaryIsFeeless = _isFeeless(beneficiary);
|
|
1158
|
+
bool beneficiaryIsFeeless = _isFeeless({addr: beneficiary, projectId: projectId});
|
|
1163
1159
|
|
|
1164
1160
|
{
|
|
1165
1161
|
// Cache the controller to avoid a redundant external call (also used inside STORE.recordCashOutFor).
|
|
@@ -1199,7 +1195,6 @@ contract JBMultiTerminal is JBPermissioned, ERC2771Context, IJBMultiTerminal {
|
|
|
1199
1195
|
uint256 feeFreeSurplus = _feeFreeSurplusOf[projectId][tokenToReclaim];
|
|
1200
1196
|
if (feeFreeSurplus != 0) {
|
|
1201
1197
|
uint256 feeableAmount = reclaimAmount < feeFreeSurplus ? reclaimAmount : feeFreeSurplus;
|
|
1202
|
-
// slither-disable-next-line reentrancy-no-eth,reentrancy-eth,reentrancy-benign
|
|
1203
1198
|
unchecked {
|
|
1204
1199
|
_feeFreeSurplusOf[projectId][tokenToReclaim] = feeFreeSurplus - feeableAmount;
|
|
1205
1200
|
}
|
|
@@ -1242,7 +1237,6 @@ contract JBMultiTerminal is JBPermissioned, ERC2771Context, IJBMultiTerminal {
|
|
|
1242
1237
|
// balance during a prior inbound payout), overcharging fees on round-trip prevention.
|
|
1243
1238
|
// Placed after hook fulfillment so any further balance reductions from cashout hooks are
|
|
1244
1239
|
// also accounted for.
|
|
1245
|
-
// slither-disable-next-line reentrancy-eth
|
|
1246
1240
|
_capFeeFreeSurplus({projectId: projectId, token: tokenToReclaim});
|
|
1247
1241
|
|
|
1248
1242
|
// Take the fee from all outbound reclaimings.
|
|
@@ -1344,12 +1338,10 @@ contract JBMultiTerminal is JBPermissioned, ERC2771Context, IJBMultiTerminal {
|
|
|
1344
1338
|
} else {
|
|
1345
1339
|
// Trigger any inherited pre-transfer logic.
|
|
1346
1340
|
// Keep a reference to the amount that'll be paid as a `msg.value`.
|
|
1347
|
-
// slither-disable-next-line reentrancy-events
|
|
1348
1341
|
uint256 payValue = _beforeTransferTo({to: address(terminal), token: token, amount: amount});
|
|
1349
1342
|
|
|
1350
1343
|
// Send the fee.
|
|
1351
1344
|
// If this terminal's token is ETH, send it in msg.value.
|
|
1352
|
-
// slither-disable-next-line unused-return
|
|
1353
1345
|
terminal.pay{value: payValue}({
|
|
1354
1346
|
projectId: projectId,
|
|
1355
1347
|
token: token,
|
|
@@ -1382,7 +1374,6 @@ contract JBMultiTerminal is JBPermissioned, ERC2771Context, IJBMultiTerminal {
|
|
|
1382
1374
|
{
|
|
1383
1375
|
// Trigger any inherited pre-transfer logic.
|
|
1384
1376
|
// Keep a reference to the amount that'll be paid as a `msg.value`.
|
|
1385
|
-
// slither-disable-next-line reentrancy-events
|
|
1386
1377
|
uint256 payValue = _beforeTransferTo({to: address(terminal), token: token, amount: amount});
|
|
1387
1378
|
|
|
1388
1379
|
// Add to balance on the recipient terminal.
|
|
@@ -1441,7 +1432,6 @@ contract JBMultiTerminal is JBPermissioned, ERC2771Context, IJBMultiTerminal {
|
|
|
1441
1432
|
cashOutMetadata: metadata
|
|
1442
1433
|
});
|
|
1443
1434
|
|
|
1444
|
-
// slither-disable-next-line calls-loop
|
|
1445
1435
|
for (uint256 i; i < specifications.length;) {
|
|
1446
1436
|
// Set the specification being iterated on.
|
|
1447
1437
|
JBCashOutHookSpecification memory specification = specifications[i];
|
|
@@ -1455,8 +1445,9 @@ contract JBMultiTerminal is JBPermissioned, ERC2771Context, IJBMultiTerminal {
|
|
|
1455
1445
|
}
|
|
1456
1446
|
|
|
1457
1447
|
// Get the fee for the specified amount.
|
|
1458
|
-
uint256 specificationAmountFee =
|
|
1459
|
-
|
|
1448
|
+
uint256 specificationAmountFee = _isFeeless({addr: address(specification.hook), projectId: projectId})
|
|
1449
|
+
? 0
|
|
1450
|
+
: _feeAmountFrom(specification.amount);
|
|
1460
1451
|
|
|
1461
1452
|
// Add the specification's amount to the amount eligible for fees.
|
|
1462
1453
|
if (specificationAmountFee != 0) {
|
|
@@ -1477,13 +1468,11 @@ contract JBMultiTerminal is JBPermissioned, ERC2771Context, IJBMultiTerminal {
|
|
|
1477
1468
|
|
|
1478
1469
|
// Trigger any inherited pre-transfer logic.
|
|
1479
1470
|
// Keep a reference to the amount that'll be paid as a `msg.value`.
|
|
1480
|
-
// slither-disable-next-line reentrancy-events
|
|
1481
1471
|
uint256 payValue = _beforeTransferTo({
|
|
1482
1472
|
to: address(specification.hook), token: beneficiaryReclaimAmount.token, amount: specification.amount
|
|
1483
1473
|
});
|
|
1484
1474
|
|
|
1485
1475
|
// Fulfill the specification.
|
|
1486
|
-
// slither-disable-next-line reentrancy-events,calls-loop
|
|
1487
1476
|
specification.hook.afterCashOutRecordedWith{value: payValue}(context);
|
|
1488
1477
|
|
|
1489
1478
|
// Revoke the temporary pull allowance now that the hook call has finished.
|
|
@@ -1538,7 +1527,6 @@ contract JBMultiTerminal is JBPermissioned, ERC2771Context, IJBMultiTerminal {
|
|
|
1538
1527
|
});
|
|
1539
1528
|
|
|
1540
1529
|
// Fulfill each specification through their pay hooks.
|
|
1541
|
-
// slither-disable-next-line calls-loop
|
|
1542
1530
|
for (uint256 i; i < specifications.length;) {
|
|
1543
1531
|
// Set the specification being iterated on.
|
|
1544
1532
|
JBPayHookSpecification memory specification = specifications[i];
|
|
@@ -1564,13 +1552,11 @@ contract JBMultiTerminal is JBPermissioned, ERC2771Context, IJBMultiTerminal {
|
|
|
1564
1552
|
|
|
1565
1553
|
// Trigger any inherited pre-transfer logic.
|
|
1566
1554
|
// Keep a reference to the amount that'll be paid as a `msg.value`.
|
|
1567
|
-
// slither-disable-next-line reentrancy-events
|
|
1568
1555
|
uint256 payValue = _beforeTransferTo({
|
|
1569
1556
|
to: address(specification.hook), token: tokenAmount.token, amount: specification.amount
|
|
1570
1557
|
});
|
|
1571
1558
|
|
|
1572
1559
|
// Fulfill the specification.
|
|
1573
|
-
// slither-disable-next-line reentrancy-events,calls-loop
|
|
1574
1560
|
specification.hook.afterPayRecordedWith{value: payValue}(context);
|
|
1575
1561
|
|
|
1576
1562
|
// Revoke the temporary pull allowance now that the hook call has finished.
|
|
@@ -1618,7 +1604,6 @@ contract JBMultiTerminal is JBPermissioned, ERC2771Context, IJBMultiTerminal {
|
|
|
1618
1604
|
// Keep a reference to the ruleset the payment is being made during.
|
|
1619
1605
|
// Keep a reference to the pay hook specifications.
|
|
1620
1606
|
// Keep a reference to the token count that'll be minted as a result of the payment.
|
|
1621
|
-
// slither-disable-next-line reentrancy-events
|
|
1622
1607
|
(JBRuleset memory ruleset, uint256 tokenCount, JBPayHookSpecification[] memory hookSpecifications) = STORE.recordPaymentFrom({
|
|
1623
1608
|
payer: payer, amount: tokenAmount, projectId: projectId, beneficiary: beneficiary, metadata: metadata
|
|
1624
1609
|
});
|
|
@@ -1630,7 +1615,6 @@ contract JBMultiTerminal is JBPermissioned, ERC2771Context, IJBMultiTerminal {
|
|
|
1630
1615
|
if (tokenCount != 0) {
|
|
1631
1616
|
// Set the token count to be the number of tokens minted for the beneficiary instead of the total
|
|
1632
1617
|
// amount.
|
|
1633
|
-
// slither-disable-next-line reentrancy-events
|
|
1634
1618
|
newlyIssuedTokenCount = _controllerOf(projectId)
|
|
1635
1619
|
.mintTokensOf({
|
|
1636
1620
|
projectId: projectId,
|
|
@@ -1686,7 +1670,6 @@ contract JBMultiTerminal is JBPermissioned, ERC2771Context, IJBMultiTerminal {
|
|
|
1686
1670
|
)
|
|
1687
1671
|
internal
|
|
1688
1672
|
{
|
|
1689
|
-
// slither-disable-next-line reentrancy-events,calls-loop
|
|
1690
1673
|
try this.executeProcessFee({
|
|
1691
1674
|
projectId: projectId, token: token, amount: amount, beneficiary: beneficiary, feeTerminal: feeTerminal
|
|
1692
1675
|
}) {
|
|
@@ -1723,7 +1706,6 @@ contract JBMultiTerminal is JBPermissioned, ERC2771Context, IJBMultiTerminal {
|
|
|
1723
1706
|
/// this
|
|
1724
1707
|
/// terminal.
|
|
1725
1708
|
function _recordAddedBalanceFor(uint256 projectId, address token, uint256 amount) internal {
|
|
1726
|
-
// slither-disable-next-line calls-loop
|
|
1727
1709
|
STORE.recordAddedBalanceFor({projectId: projectId, token: token, amount: amount});
|
|
1728
1710
|
}
|
|
1729
1711
|
|
|
@@ -1760,7 +1742,6 @@ contract JBMultiTerminal is JBPermissioned, ERC2771Context, IJBMultiTerminal {
|
|
|
1760
1742
|
// Save the fee being iterated on.
|
|
1761
1743
|
JBFee memory heldFee = _heldFeesOf[projectId][token][startIndex + i];
|
|
1762
1744
|
|
|
1763
|
-
// slither-disable-next-line incorrect-equality
|
|
1764
1745
|
if (leftoverAmount == 0) {
|
|
1765
1746
|
break;
|
|
1766
1747
|
} else {
|
|
@@ -1862,7 +1843,8 @@ contract JBMultiTerminal is JBPermissioned, ERC2771Context, IJBMultiTerminal {
|
|
|
1862
1843
|
// Send any leftover funds to the project owner and update the fee tracking accordingly.
|
|
1863
1844
|
if (leftoverPayoutAmount != 0) {
|
|
1864
1845
|
// Keep a reference to the fee for the leftover payout amount.
|
|
1865
|
-
uint256 fee =
|
|
1846
|
+
uint256 fee =
|
|
1847
|
+
_isFeeless({addr: projectOwner, projectId: projectId}) ? 0 : _feeAmountFrom(leftoverPayoutAmount);
|
|
1866
1848
|
|
|
1867
1849
|
uint256 netLeftoverPayoutAmount;
|
|
1868
1850
|
unchecked {
|
|
@@ -1878,7 +1860,6 @@ contract JBMultiTerminal is JBPermissioned, ERC2771Context, IJBMultiTerminal {
|
|
|
1878
1860
|
leftoverPayoutAmount = netLeftoverPayoutAmount;
|
|
1879
1861
|
}
|
|
1880
1862
|
} catch (bytes memory reason) {
|
|
1881
|
-
// slither-disable-next-line reentrancy-events
|
|
1882
1863
|
emit PayoutTransferReverted({
|
|
1883
1864
|
projectId: projectId,
|
|
1884
1865
|
addr: projectOwner,
|
|
@@ -1896,7 +1877,6 @@ contract JBMultiTerminal is JBPermissioned, ERC2771Context, IJBMultiTerminal {
|
|
|
1896
1877
|
|
|
1897
1878
|
// Cap fee-free surplus at remaining balance. Non-fee-free funds leave first.
|
|
1898
1879
|
// Placed after all payouts settle so the cap reflects post-payout state.
|
|
1899
|
-
// slither-disable-next-line reentrancy-no-eth,reentrancy-eth,reentrancy-benign
|
|
1900
1880
|
_capFeeFreeSurplus({projectId: projectId, token: token});
|
|
1901
1881
|
|
|
1902
1882
|
// Take the fee.
|
|
@@ -2042,14 +2022,12 @@ contract JBMultiTerminal is JBPermissioned, ERC2771Context, IJBMultiTerminal {
|
|
|
2042
2022
|
STORE.recordUsedAllowanceOf({projectId: projectId, token: token, amount: amount, currency: currency});
|
|
2043
2023
|
|
|
2044
2024
|
// Cap fee-free surplus at remaining balance. Non-fee-free funds leave first.
|
|
2045
|
-
// slither-disable-next-line reentrancy-no-eth,reentrancy-eth,reentrancy-benign
|
|
2046
2025
|
_capFeeFreeSurplus({projectId: projectId, token: token});
|
|
2047
2026
|
|
|
2048
2027
|
// Take a fee from the `amountPaidOut`, if needed.
|
|
2049
2028
|
// The net amount is the final amount withdrawn after the fee has been taken.
|
|
2050
|
-
// slither-disable-next-line reentrancy-events
|
|
2051
2029
|
netAmountPaidOut = amountPaidOut
|
|
2052
|
-
- (_isFeeless(owner) || _isFeeless(beneficiary)
|
|
2030
|
+
- (_isFeeless({addr: owner, projectId: projectId}) || _isFeeless({addr: beneficiary, projectId: projectId})
|
|
2053
2031
|
? 0
|
|
2054
2032
|
: _takeFeeFrom({
|
|
2055
2033
|
projectId: projectId,
|
|
@@ -2092,7 +2070,6 @@ contract JBMultiTerminal is JBPermissioned, ERC2771Context, IJBMultiTerminal {
|
|
|
2092
2070
|
if (token == JBConstants.NATIVE_TOKEN) return;
|
|
2093
2071
|
|
|
2094
2072
|
// Revert if the callee returned without consuming the full forwarded ERC-20 amount.
|
|
2095
|
-
// slither-disable-next-line calls-loop
|
|
2096
2073
|
uint256 allowance = IERC20(token).allowance({owner: address(this), spender: to});
|
|
2097
2074
|
if (allowance != 0) revert JBMultiTerminal_TemporaryAllowanceNotConsumed(token, to, allowance);
|
|
2098
2075
|
}
|
|
@@ -2138,9 +2115,10 @@ contract JBMultiTerminal is JBPermissioned, ERC2771Context, IJBMultiTerminal {
|
|
|
2138
2115
|
|
|
2139
2116
|
/// @notice Returns a flag indicating if interacting with an address should not incur fees.
|
|
2140
2117
|
/// @param addr The address to check.
|
|
2141
|
-
/// @
|
|
2142
|
-
|
|
2143
|
-
|
|
2118
|
+
/// @param projectId The ID of the project to check the per-project feeless status for.
|
|
2119
|
+
/// @return A flag indicating if the address should not incur fees (globally or for the project).
|
|
2120
|
+
function _isFeeless(address addr, uint256 projectId) internal view returns (bool) {
|
|
2121
|
+
return FEELESS_ADDRESSES.isFeelessFor({addr: addr, projectId: projectId});
|
|
2144
2122
|
}
|
|
2145
2123
|
|
|
2146
2124
|
/// @notice The calldata. Preferred to use over `msg.data`.
|
package/src/JBPermissions.sol
CHANGED
|
@@ -17,8 +17,7 @@ contract JBPermissions is ERC2771Context, IJBPermissions {
|
|
|
17
17
|
// --------------------------- custom errors ------------------------- //
|
|
18
18
|
//*********************************************************************//
|
|
19
19
|
|
|
20
|
-
error
|
|
21
|
-
error JBPermissions_NoZeroPermission();
|
|
20
|
+
error JBPermissions_NoZeroPermission(address account, address operator, uint256 projectId);
|
|
22
21
|
error JBPermissions_PermissionIdOutOfBounds(uint256 permissionId);
|
|
23
22
|
error JBPermissions_Unauthorized(address account, address operator, uint256 projectId, uint256 permissionId);
|
|
24
23
|
|
|
@@ -69,7 +68,11 @@ contract JBPermissions is ERC2771Context, IJBPermissions {
|
|
|
69
68
|
uint256 packed = _packedPermissions(permissionsData.permissionIds);
|
|
70
69
|
|
|
71
70
|
// Make sure the 0 permission is not set.
|
|
72
|
-
if (_includesPermission({permissions: packed, permissionId: 0}))
|
|
71
|
+
if (_includesPermission({permissions: packed, permissionId: 0})) {
|
|
72
|
+
revert JBPermissions_NoZeroPermission({
|
|
73
|
+
account: account, operator: permissionsData.operator, projectId: permissionsData.projectId
|
|
74
|
+
});
|
|
75
|
+
}
|
|
73
76
|
|
|
74
77
|
// Cache the sender.
|
|
75
78
|
address msgSender = _msgSender();
|