@bananapus/721-hook-v6 0.0.1 → 0.0.2
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/JB721TiersHook.sol +236 -146
- package/src/JB721TiersHookDeployer.sol +1 -0
- package/src/JB721TiersHookStore.sol +17 -4
- package/src/interfaces/IJB721TiersHook.sol +17 -2
- package/src/libraries/JB721TiersHookLib.sol +336 -0
- package/src/structs/JB721Tier.sol +3 -0
- package/src/structs/JB721TierConfig.sol +8 -0
- package/src/structs/JBStored721Tier.sol +5 -4
- package/test/721HookAttacks.t.sol +6 -2
- package/test/E2E/Pay_Mint_Redeem_E2E.t.sol +6 -2
- package/test/invariants/handlers/TierLifecycleHandler.sol +3 -1
- package/test/invariants/handlers/TierStoreHandler.sol +4 -1
- package/test/unit/adjustTier_Unit.t.sol +75 -22
- package/test/unit/getters_constructor_Unit.t.sol +14 -9
- package/test/unit/mintFor_mintReservesFor_Unit.t.sol +12 -12
- package/test/unit/pay_Unit.t.sol +6 -10
- package/test/unit/redeem_Unit.t.sol +13 -11
- package/test/unit/tierSplitRouting_Unit.t.sol +275 -0
- package/src/abstract/JB721Hook.sol +0 -279
- package/src/interfaces/IJB721Hook.sol +0 -21
|
@@ -1,10 +1,13 @@
|
|
|
1
1
|
// SPDX-License-Identifier: MIT
|
|
2
2
|
pragma solidity ^0.8.0;
|
|
3
3
|
|
|
4
|
+
import {IJBCashOutHook} from "@bananapus/core-v6/src/interfaces/IJBCashOutHook.sol";
|
|
5
|
+
import {IJBDirectory} from "@bananapus/core-v6/src/interfaces/IJBDirectory.sol";
|
|
6
|
+
import {IJBPayHook} from "@bananapus/core-v6/src/interfaces/IJBPayHook.sol";
|
|
4
7
|
import {IJBPrices} from "@bananapus/core-v6/src/interfaces/IJBPrices.sol";
|
|
8
|
+
import {IJBRulesetDataHook} from "@bananapus/core-v6/src/interfaces/IJBRulesetDataHook.sol";
|
|
5
9
|
import {IJBRulesets} from "@bananapus/core-v6/src/interfaces/IJBRulesets.sol";
|
|
6
10
|
|
|
7
|
-
import {IJB721Hook} from "./IJB721Hook.sol";
|
|
8
11
|
import {IJB721TiersHookStore} from "./IJB721TiersHookStore.sol";
|
|
9
12
|
import {IJB721TokenUriResolver} from "./IJB721TokenUriResolver.sol";
|
|
10
13
|
import {JB721InitTiersConfig} from "../structs/JB721InitTiersConfig.sol";
|
|
@@ -13,7 +16,7 @@ import {JB721TiersHookFlags} from "../structs/JB721TiersHookFlags.sol";
|
|
|
13
16
|
import {JB721TiersMintReservesConfig} from "../structs/JB721TiersMintReservesConfig.sol";
|
|
14
17
|
import {JB721TiersSetDiscountPercentConfig} from "../structs/JB721TiersSetDiscountPercentConfig.sol";
|
|
15
18
|
|
|
16
|
-
interface IJB721TiersHook is
|
|
19
|
+
interface IJB721TiersHook is IJBRulesetDataHook, IJBPayHook, IJBCashOutHook {
|
|
17
20
|
event AddPayCredits(
|
|
18
21
|
uint256 indexed amount, uint256 indexed newTotalCredits, address indexed account, address caller
|
|
19
22
|
);
|
|
@@ -36,6 +39,18 @@ interface IJB721TiersHook is IJB721Hook {
|
|
|
36
39
|
uint256 indexed amount, uint256 indexed newTotalCredits, address indexed account, address caller
|
|
37
40
|
);
|
|
38
41
|
|
|
42
|
+
/// @notice The directory of terminals and controllers for projects.
|
|
43
|
+
/// @return The directory contract.
|
|
44
|
+
function DIRECTORY() external view returns (IJBDirectory);
|
|
45
|
+
|
|
46
|
+
/// @notice The ID used when parsing metadata.
|
|
47
|
+
/// @return The address of the metadata ID target.
|
|
48
|
+
function METADATA_ID_TARGET() external view returns (address);
|
|
49
|
+
|
|
50
|
+
/// @notice The ID of the project that this contract is associated with.
|
|
51
|
+
/// @return The project ID.
|
|
52
|
+
function PROJECT_ID() external view returns (uint256);
|
|
53
|
+
|
|
39
54
|
/// @notice The contract storing and managing project rulesets.
|
|
40
55
|
/// @return The rulesets contract.
|
|
41
56
|
function RULESETS() external view returns (IJBRulesets);
|
|
@@ -0,0 +1,336 @@
|
|
|
1
|
+
// SPDX-License-Identifier: MIT
|
|
2
|
+
pragma solidity 0.8.23;
|
|
3
|
+
|
|
4
|
+
import {IJBController} from "@bananapus/core-v6/src/interfaces/IJBController.sol";
|
|
5
|
+
import {IJBDirectory} from "@bananapus/core-v6/src/interfaces/IJBDirectory.sol";
|
|
6
|
+
import {IJBPrices} from "@bananapus/core-v6/src/interfaces/IJBPrices.sol";
|
|
7
|
+
import {IJBSplits} from "@bananapus/core-v6/src/interfaces/IJBSplits.sol";
|
|
8
|
+
import {IJBTerminal} from "@bananapus/core-v6/src/interfaces/IJBTerminal.sol";
|
|
9
|
+
import {JBSplit} from "@bananapus/core-v6/src/structs/JBSplit.sol";
|
|
10
|
+
import {JBConstants} from "@bananapus/core-v6/src/libraries/JBConstants.sol";
|
|
11
|
+
import {JBMetadataResolver} from "@bananapus/core-v6/src/libraries/JBMetadataResolver.sol";
|
|
12
|
+
import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
|
|
13
|
+
import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
|
|
14
|
+
import {mulDiv} from "@prb/math/src/Common.sol";
|
|
15
|
+
|
|
16
|
+
import {JBSplitGroup} from "@bananapus/core-v6/src/structs/JBSplitGroup.sol";
|
|
17
|
+
|
|
18
|
+
import {IJB721TiersHookStore} from "../interfaces/IJB721TiersHookStore.sol";
|
|
19
|
+
import {JB721TierConfig} from "../structs/JB721TierConfig.sol";
|
|
20
|
+
|
|
21
|
+
/// @notice External library for JB721TiersHook operations extracted to stay within the EIP-170 contract size limit.
|
|
22
|
+
/// @dev Handles tier adjustments, split calculations, price normalization, and split fund distribution.
|
|
23
|
+
library JB721TiersHookLib {
|
|
24
|
+
// Events mirrored from IJB721TiersHook (emitted via DELEGATECALL from the hook's context).
|
|
25
|
+
event AddTier(uint256 indexed tierId, JB721TierConfig tier, address caller);
|
|
26
|
+
event RemoveTier(uint256 indexed tierId, address caller);
|
|
27
|
+
|
|
28
|
+
/// @notice Handles the full tier adjustment logic: removes tiers, adds tiers, emits events, and sets splits.
|
|
29
|
+
/// @dev Called via DELEGATECALL from the hook, so events are emitted from the hook's address.
|
|
30
|
+
/// @param store The 721 tiers hook store.
|
|
31
|
+
/// @param directory The directory to look up controllers.
|
|
32
|
+
/// @param projectId The project ID.
|
|
33
|
+
/// @param hookAddress The hook address.
|
|
34
|
+
/// @param caller The msg.sender of the original call (for event emission).
|
|
35
|
+
/// @param tiersToAdd The tier configs to add.
|
|
36
|
+
/// @param tierIdsToRemove The tier IDs to remove.
|
|
37
|
+
function adjustTiersFor(
|
|
38
|
+
IJB721TiersHookStore store,
|
|
39
|
+
IJBDirectory directory,
|
|
40
|
+
uint256 projectId,
|
|
41
|
+
address hookAddress,
|
|
42
|
+
address caller,
|
|
43
|
+
JB721TierConfig[] calldata tiersToAdd,
|
|
44
|
+
uint256[] calldata tierIdsToRemove
|
|
45
|
+
)
|
|
46
|
+
external
|
|
47
|
+
{
|
|
48
|
+
// Remove tiers.
|
|
49
|
+
if (tierIdsToRemove.length != 0) {
|
|
50
|
+
for (uint256 i; i < tierIdsToRemove.length; i++) {
|
|
51
|
+
emit RemoveTier({tierId: tierIdsToRemove[i], caller: caller});
|
|
52
|
+
}
|
|
53
|
+
store.recordRemoveTierIds(tierIdsToRemove);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// Add tiers.
|
|
57
|
+
if (tiersToAdd.length != 0) {
|
|
58
|
+
uint256[] memory tierIdsAdded = store.recordAddTiers(tiersToAdd);
|
|
59
|
+
|
|
60
|
+
// slither-disable-next-line reentrancy-events
|
|
61
|
+
for (uint256 i; i < tiersToAdd.length; i++) {
|
|
62
|
+
emit AddTier({tierId: tierIdsAdded[i], tier: tiersToAdd[i], caller: caller});
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// Set split groups for tiers that have splits configured.
|
|
66
|
+
_setSplitGroupsFor(directory, projectId, hookAddress, tiersToAdd, tierIdsAdded);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/// @notice Normalizes a payment value based on the packed pricing context.
|
|
71
|
+
/// @param packedPricingContext The packed pricing context (currency, decimals, prices address).
|
|
72
|
+
/// @param projectId The project ID.
|
|
73
|
+
/// @param amountValue The payment amount value.
|
|
74
|
+
/// @param amountCurrency The payment amount currency.
|
|
75
|
+
/// @param amountDecimals The payment amount decimals.
|
|
76
|
+
/// @return value The normalized value.
|
|
77
|
+
/// @return valid Whether the value is valid (false means no prices contract and currencies differ).
|
|
78
|
+
function normalizePaymentValue(
|
|
79
|
+
uint256 packedPricingContext,
|
|
80
|
+
uint256 projectId,
|
|
81
|
+
uint256 amountValue,
|
|
82
|
+
uint256 amountCurrency,
|
|
83
|
+
uint256 amountDecimals
|
|
84
|
+
)
|
|
85
|
+
external
|
|
86
|
+
view
|
|
87
|
+
returns (uint256 value, bool valid)
|
|
88
|
+
{
|
|
89
|
+
uint256 pricingCurrency = uint256(uint32(packedPricingContext));
|
|
90
|
+
if (amountCurrency == pricingCurrency) return (amountValue, true);
|
|
91
|
+
|
|
92
|
+
IJBPrices prices = IJBPrices(address(uint160(packedPricingContext >> 40)));
|
|
93
|
+
if (address(prices) == address(0)) return (0, false);
|
|
94
|
+
|
|
95
|
+
uint256 pricingDecimals = uint256(uint8(packedPricingContext >> 32));
|
|
96
|
+
value = mulDiv(
|
|
97
|
+
amountValue,
|
|
98
|
+
10 ** pricingDecimals,
|
|
99
|
+
prices.pricePerUnitOf({
|
|
100
|
+
projectId: projectId,
|
|
101
|
+
pricingCurrency: amountCurrency,
|
|
102
|
+
unitCurrency: pricingCurrency,
|
|
103
|
+
decimals: amountDecimals
|
|
104
|
+
})
|
|
105
|
+
);
|
|
106
|
+
valid = true;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/// @notice Calculates per-tier split amounts for a pay event.
|
|
110
|
+
/// @param store The 721 tiers hook store.
|
|
111
|
+
/// @param hook The hook address.
|
|
112
|
+
/// @param metadataIdTarget The metadata ID target for resolving pay metadata.
|
|
113
|
+
/// @param metadata The payer metadata.
|
|
114
|
+
/// @return totalSplitAmount The total amount to forward for splits.
|
|
115
|
+
/// @return hookMetadata Encoded per-tier breakdown (tierIds, amounts) for afterPay.
|
|
116
|
+
function calculateSplitAmounts(
|
|
117
|
+
IJB721TiersHookStore store,
|
|
118
|
+
address hook,
|
|
119
|
+
address metadataIdTarget,
|
|
120
|
+
bytes calldata metadata
|
|
121
|
+
)
|
|
122
|
+
external
|
|
123
|
+
view
|
|
124
|
+
returns (uint256 totalSplitAmount, bytes memory hookMetadata)
|
|
125
|
+
{
|
|
126
|
+
bytes memory data;
|
|
127
|
+
{
|
|
128
|
+
bool found;
|
|
129
|
+
(found, data) = JBMetadataResolver.getDataFor(JBMetadataResolver.getId("pay", metadataIdTarget), metadata);
|
|
130
|
+
if (!found) return (0, bytes(""));
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
(, uint16[] memory tierIdsToMint) = abi.decode(data, (bool, uint16[]));
|
|
134
|
+
if (tierIdsToMint.length == 0) return (0, bytes(""));
|
|
135
|
+
|
|
136
|
+
uint16[] memory splitTierIds = new uint16[](tierIdsToMint.length);
|
|
137
|
+
uint256[] memory splitAmounts = new uint256[](tierIdsToMint.length);
|
|
138
|
+
uint256 splitTierCount;
|
|
139
|
+
|
|
140
|
+
for (uint256 i; i < tierIdsToMint.length; i++) {
|
|
141
|
+
// slither-disable-next-line calls-loop
|
|
142
|
+
uint256 splitPercent = store.tierOf(hook, tierIdsToMint[i], false).splitPercent;
|
|
143
|
+
if (splitPercent != 0) {
|
|
144
|
+
// slither-disable-next-line calls-loop
|
|
145
|
+
uint256 price = store.tierOf(hook, tierIdsToMint[i], false).price;
|
|
146
|
+
splitTierIds[splitTierCount] = tierIdsToMint[i];
|
|
147
|
+
splitAmounts[splitTierCount] = mulDiv(price, splitPercent, JBConstants.SPLITS_TOTAL_PERCENT);
|
|
148
|
+
totalSplitAmount += splitAmounts[splitTierCount];
|
|
149
|
+
splitTierCount++;
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
if (splitTierCount != 0) {
|
|
154
|
+
assembly {
|
|
155
|
+
mstore(splitTierIds, splitTierCount)
|
|
156
|
+
mstore(splitAmounts, splitTierCount)
|
|
157
|
+
}
|
|
158
|
+
hookMetadata = abi.encode(splitTierIds, splitAmounts);
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
/// @notice Sets split groups in JBSplits for tiers that have splits configured.
|
|
163
|
+
function _setSplitGroupsFor(
|
|
164
|
+
IJBDirectory directory,
|
|
165
|
+
uint256 projectId,
|
|
166
|
+
address hookAddress,
|
|
167
|
+
JB721TierConfig[] calldata tiersToAdd,
|
|
168
|
+
uint256[] memory tierIdsAdded
|
|
169
|
+
)
|
|
170
|
+
private
|
|
171
|
+
{
|
|
172
|
+
uint256 splitGroupCount;
|
|
173
|
+
for (uint256 i; i < tiersToAdd.length; i++) {
|
|
174
|
+
if (tiersToAdd[i].splits.length != 0) splitGroupCount++;
|
|
175
|
+
}
|
|
176
|
+
if (splitGroupCount == 0) return;
|
|
177
|
+
|
|
178
|
+
JBSplitGroup[] memory splitGroups = new JBSplitGroup[](splitGroupCount);
|
|
179
|
+
uint256 groupIndex;
|
|
180
|
+
for (uint256 i; i < tiersToAdd.length; i++) {
|
|
181
|
+
if (tiersToAdd[i].splits.length != 0) {
|
|
182
|
+
splitGroups[groupIndex] = JBSplitGroup({
|
|
183
|
+
groupId: uint256(uint160(hookAddress)) | (tierIdsAdded[i] << 160), splits: tiersToAdd[i].splits
|
|
184
|
+
});
|
|
185
|
+
groupIndex++;
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
IJBController(address(directory.controllerOf(projectId))).SPLITS().setSplitGroupsOf(projectId, 0, splitGroups);
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
/// @notice Distributes forwarded funds for all tiers in the hook metadata.
|
|
192
|
+
/// @param directory The directory to look up controllers and terminals.
|
|
193
|
+
/// @param projectId The project ID of the hook.
|
|
194
|
+
/// @param hookAddress The hook address (for computing split group IDs).
|
|
195
|
+
/// @param token The token being distributed.
|
|
196
|
+
/// @param encodedSplitData The encoded per-tier breakdown from hookMetadata.
|
|
197
|
+
function distributeAll(
|
|
198
|
+
IJBDirectory directory,
|
|
199
|
+
uint256 projectId,
|
|
200
|
+
address hookAddress,
|
|
201
|
+
address token,
|
|
202
|
+
bytes calldata encodedSplitData
|
|
203
|
+
)
|
|
204
|
+
external
|
|
205
|
+
{
|
|
206
|
+
(uint16[] memory tierIds, uint256[] memory amounts) = abi.decode(encodedSplitData, (uint16[], uint256[]));
|
|
207
|
+
|
|
208
|
+
IJBSplits splitsContract = IJBController(address(directory.controllerOf(projectId))).SPLITS();
|
|
209
|
+
|
|
210
|
+
for (uint256 i; i < tierIds.length; i++) {
|
|
211
|
+
if (amounts[i] == 0) continue;
|
|
212
|
+
uint256 groupId = uint256(uint160(hookAddress)) | (uint256(tierIds[i]) << 160);
|
|
213
|
+
_distributeSingleSplit(directory, splitsContract, projectId, token, groupId, amounts[i]);
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
/// @notice Distributes funds for a single tier's split group.
|
|
218
|
+
function _distributeSingleSplit(
|
|
219
|
+
IJBDirectory directory,
|
|
220
|
+
IJBSplits splitsContract,
|
|
221
|
+
uint256 projectId,
|
|
222
|
+
address token,
|
|
223
|
+
uint256 groupId,
|
|
224
|
+
uint256 amount
|
|
225
|
+
)
|
|
226
|
+
private
|
|
227
|
+
{
|
|
228
|
+
// slither-disable-next-line calls-loop
|
|
229
|
+
JBSplit[] memory tierSplits = splitsContract.splitsOf(projectId, 0, groupId);
|
|
230
|
+
|
|
231
|
+
bool isNativeToken = token == JBConstants.NATIVE_TOKEN;
|
|
232
|
+
uint256 leftoverPercentage = JBConstants.SPLITS_TOTAL_PERCENT;
|
|
233
|
+
uint256 leftoverAmount = amount;
|
|
234
|
+
|
|
235
|
+
for (uint256 j; j < tierSplits.length; j++) {
|
|
236
|
+
uint256 payoutAmount = mulDiv(amount, tierSplits[j].percent, leftoverPercentage);
|
|
237
|
+
if (payoutAmount != 0) {
|
|
238
|
+
_sendPayoutToSplit(directory, tierSplits[j], token, payoutAmount, isNativeToken);
|
|
239
|
+
unchecked {
|
|
240
|
+
leftoverAmount -= payoutAmount;
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
unchecked {
|
|
244
|
+
leftoverPercentage -= tierSplits[j].percent;
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
if (leftoverAmount != 0) {
|
|
249
|
+
_addToBalance(directory, projectId, token, leftoverAmount, isNativeToken);
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
function _sendPayoutToSplit(
|
|
254
|
+
IJBDirectory directory,
|
|
255
|
+
JBSplit memory split,
|
|
256
|
+
address token,
|
|
257
|
+
uint256 amount,
|
|
258
|
+
bool isNativeToken
|
|
259
|
+
)
|
|
260
|
+
private
|
|
261
|
+
{
|
|
262
|
+
if (split.projectId != 0) {
|
|
263
|
+
// slither-disable-next-line calls-loop
|
|
264
|
+
IJBTerminal terminal = directory.primaryTerminalOf(split.projectId, token);
|
|
265
|
+
if (address(terminal) == address(0)) return;
|
|
266
|
+
|
|
267
|
+
if (split.preferAddToBalance) {
|
|
268
|
+
_terminalAddToBalance(terminal, split.projectId, token, amount, isNativeToken);
|
|
269
|
+
} else {
|
|
270
|
+
_terminalPay(terminal, split.projectId, token, amount, split.beneficiary, isNativeToken);
|
|
271
|
+
}
|
|
272
|
+
} else if (split.beneficiary != address(0)) {
|
|
273
|
+
if (isNativeToken) {
|
|
274
|
+
// slither-disable-next-line arbitrary-send-eth,calls-loop
|
|
275
|
+
(bool success,) = split.beneficiary.call{value: amount}("");
|
|
276
|
+
if (!success) revert();
|
|
277
|
+
} else {
|
|
278
|
+
SafeERC20.safeTransfer(IERC20(token), split.beneficiary, amount);
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
function _addToBalance(
|
|
284
|
+
IJBDirectory directory,
|
|
285
|
+
uint256 projectId,
|
|
286
|
+
address token,
|
|
287
|
+
uint256 amount,
|
|
288
|
+
bool isNativeToken
|
|
289
|
+
)
|
|
290
|
+
private
|
|
291
|
+
{
|
|
292
|
+
// slither-disable-next-line calls-loop
|
|
293
|
+
IJBTerminal terminal = directory.primaryTerminalOf(projectId, token);
|
|
294
|
+
if (address(terminal) == address(0)) return;
|
|
295
|
+
_terminalAddToBalance(terminal, projectId, token, amount, isNativeToken);
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
function _terminalAddToBalance(
|
|
299
|
+
IJBTerminal terminal,
|
|
300
|
+
uint256 projectId,
|
|
301
|
+
address token,
|
|
302
|
+
uint256 amount,
|
|
303
|
+
bool isNativeToken
|
|
304
|
+
)
|
|
305
|
+
private
|
|
306
|
+
{
|
|
307
|
+
if (isNativeToken) {
|
|
308
|
+
// slither-disable-next-line arbitrary-send-eth,calls-loop
|
|
309
|
+
terminal.addToBalanceOf{value: amount}(projectId, token, amount, false, "", bytes(""));
|
|
310
|
+
} else {
|
|
311
|
+
SafeERC20.forceApprove(IERC20(token), address(terminal), amount);
|
|
312
|
+
// slither-disable-next-line calls-loop
|
|
313
|
+
terminal.addToBalanceOf(projectId, token, amount, false, "", bytes(""));
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
function _terminalPay(
|
|
318
|
+
IJBTerminal terminal,
|
|
319
|
+
uint256 projectId,
|
|
320
|
+
address token,
|
|
321
|
+
uint256 amount,
|
|
322
|
+
address beneficiary,
|
|
323
|
+
bool isNativeToken
|
|
324
|
+
)
|
|
325
|
+
private
|
|
326
|
+
{
|
|
327
|
+
if (isNativeToken) {
|
|
328
|
+
// slither-disable-next-line arbitrary-send-eth,unused-return,calls-loop
|
|
329
|
+
terminal.pay{value: amount}(projectId, token, amount, beneficiary, 0, "", bytes(""));
|
|
330
|
+
} else {
|
|
331
|
+
SafeERC20.forceApprove(IERC20(token), address(terminal), amount);
|
|
332
|
+
// slither-disable-next-line unused-return,calls-loop
|
|
333
|
+
terminal.pay(projectId, token, amount, beneficiary, 0, "", bytes(""));
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
}
|
|
@@ -18,6 +18,8 @@ pragma solidity ^0.8.0;
|
|
|
18
18
|
/// @custom:member cannotBeRemoved A boolean indicating whether attempts to remove this tier will revert.
|
|
19
19
|
/// @custom:member cannotIncreaseDiscountPercent If the tier cannot have its discount increased.
|
|
20
20
|
/// @custom:member transfersPausable A boolean indicating whether transfers for NFTs in tier can be paused.
|
|
21
|
+
/// @custom:member splitPercent The percentage of the tier's price that gets routed to the project's split group when
|
|
22
|
+
/// an NFT from this tier is minted. Out of `JBConstants.SPLITS_TOTAL_PERCENT`.
|
|
21
23
|
/// @custom:member resolvedUri A resolved token URI for NFTs in this tier. Only available if the NFT this tier belongs
|
|
22
24
|
/// to has a resolver.
|
|
23
25
|
struct JB721Tier {
|
|
@@ -35,5 +37,6 @@ struct JB721Tier {
|
|
|
35
37
|
bool transfersPausable;
|
|
36
38
|
bool cannotBeRemoved;
|
|
37
39
|
bool cannotIncreaseDiscountPercent;
|
|
40
|
+
uint32 splitPercent;
|
|
38
41
|
string resolvedUri;
|
|
39
42
|
}
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
// SPDX-License-Identifier: MIT
|
|
2
2
|
pragma solidity ^0.8.0;
|
|
3
3
|
|
|
4
|
+
import {JBSplit} from "@bananapus/core-v6/src/structs/JBSplit.sol";
|
|
5
|
+
|
|
4
6
|
/// @notice Config for a single NFT tier within a `JB721TiersHook`.
|
|
5
7
|
/// @custom:member price The price to buy an NFT in this tier, in terms of the currency in its `JBInitTiersConfig`.
|
|
6
8
|
/// @custom:member initialSupply The total number of NFTs which can be minted from this tier.
|
|
@@ -22,6 +24,10 @@ pragma solidity ^0.8.0;
|
|
|
22
24
|
/// power. If `useVotingUnits` is false, voting power is based on the tier's price.
|
|
23
25
|
/// @custom:member cannotBeRemoved If the tier cannot be removed once added.
|
|
24
26
|
/// @custom:member cannotIncreaseDiscount If the tier cannot have its discount increased.
|
|
27
|
+
/// @custom:member splitPercent The percentage of the tier's price that gets routed to the tier's split group when
|
|
28
|
+
/// an NFT from this tier is minted. Out of `JBConstants.SPLITS_TOTAL_PERCENT`.
|
|
29
|
+
/// @custom:member splits The splits to use for this tier's split group. These define where the split portion of the
|
|
30
|
+
/// tier's price gets routed when an NFT from this tier is minted.
|
|
25
31
|
struct JB721TierConfig {
|
|
26
32
|
uint104 price;
|
|
27
33
|
uint32 initialSupply;
|
|
@@ -37,4 +43,6 @@ struct JB721TierConfig {
|
|
|
37
43
|
bool useVotingUnits;
|
|
38
44
|
bool cannotBeRemoved;
|
|
39
45
|
bool cannotIncreaseDiscountPercent;
|
|
46
|
+
uint32 splitPercent;
|
|
47
|
+
JBSplit[] splits;
|
|
40
48
|
}
|
|
@@ -4,19 +4,20 @@ pragma solidity ^0.8.0;
|
|
|
4
4
|
/// @custom:member price The price to buy an NFT in this tier, in terms of the currency in its `JBInitTiersConfig`.
|
|
5
5
|
/// @custom:member remainingSupply The remaining number of NFTs which can be minted from this tier.
|
|
6
6
|
/// @custom:member initialSupply The total number of NFTs which can be minted from this tier.
|
|
7
|
-
/// @custom:member
|
|
7
|
+
/// @custom:member splitPercent The percentage of the tier's price that gets routed to the tier's split group when
|
|
8
|
+
/// an NFT from this tier is minted. Out of `JBConstants.SPLITS_TOTAL_PERCENT`.
|
|
8
9
|
/// @custom:member category The category that NFTs in this tier belongs to. Used to group NFT tiers.
|
|
9
10
|
/// @custom:member discountPercent The discount that should be applied to the tier.
|
|
10
11
|
/// @custom:member reserveFrequency The frequency at which an extra NFT is minted for the `reserveBeneficiary` from this
|
|
11
12
|
/// tier. With a `reserveFrequency` of 5, an extra NFT will be minted for the `reserveBeneficiary` for every 5 NFTs
|
|
12
13
|
/// purchased.
|
|
13
|
-
/// @custom:member packedBools
|
|
14
|
-
///
|
|
14
|
+
/// @custom:member packedBools Packed boolean flags: allowOwnerMint, transfersPausable, useVotingUnits,
|
|
15
|
+
/// cannotBeRemoved, cannotIncreaseDiscountPercent.
|
|
15
16
|
struct JBStored721Tier {
|
|
16
17
|
uint104 price;
|
|
17
18
|
uint32 remainingSupply;
|
|
18
19
|
uint32 initialSupply;
|
|
19
|
-
uint32
|
|
20
|
+
uint32 splitPercent;
|
|
20
21
|
uint24 category;
|
|
21
22
|
uint8 discountPercent;
|
|
22
23
|
uint16 reserveFrequency;
|
|
@@ -85,7 +85,9 @@ contract NFTHookAttacks is UnitTestSetup {
|
|
|
85
85
|
transfersPausable: false,
|
|
86
86
|
cannotBeRemoved: false,
|
|
87
87
|
cannotIncreaseDiscountPercent: false,
|
|
88
|
-
useVotingUnits: false
|
|
88
|
+
useVotingUnits: false,
|
|
89
|
+
splitPercent: 0,
|
|
90
|
+
splits: new JBSplit[](0)
|
|
89
91
|
});
|
|
90
92
|
|
|
91
93
|
vm.prank(owner);
|
|
@@ -370,7 +372,9 @@ contract NFTHookAttacks is UnitTestSetup {
|
|
|
370
372
|
transfersPausable: false,
|
|
371
373
|
cannotBeRemoved: false,
|
|
372
374
|
cannotIncreaseDiscountPercent: false,
|
|
373
|
-
useVotingUnits: false
|
|
375
|
+
useVotingUnits: false,
|
|
376
|
+
splitPercent: 0,
|
|
377
|
+
splits: new JBSplit[](0)
|
|
374
378
|
});
|
|
375
379
|
|
|
376
380
|
vm.prank(attacker);
|
|
@@ -778,7 +778,9 @@ contract Test_TiersHook_E2E is TestBaseWorkflow {
|
|
|
778
778
|
transfersPausable: false,
|
|
779
779
|
useVotingUnits: false,
|
|
780
780
|
cannotBeRemoved: false,
|
|
781
|
-
cannotIncreaseDiscountPercent: false
|
|
781
|
+
cannotIncreaseDiscountPercent: false,
|
|
782
|
+
splitPercent: 0,
|
|
783
|
+
splits: new JBSplit[](0)
|
|
782
784
|
});
|
|
783
785
|
}
|
|
784
786
|
tiersHookConfig = JBDeploy721TiersHookConfig({
|
|
@@ -869,7 +871,9 @@ contract Test_TiersHook_E2E is TestBaseWorkflow {
|
|
|
869
871
|
transfersPausable: false,
|
|
870
872
|
useVotingUnits: false,
|
|
871
873
|
cannotBeRemoved: false,
|
|
872
|
-
cannotIncreaseDiscountPercent: false
|
|
874
|
+
cannotIncreaseDiscountPercent: false,
|
|
875
|
+
splitPercent: 0,
|
|
876
|
+
splits: new JBSplit[](0)
|
|
873
877
|
});
|
|
874
878
|
|
|
875
879
|
tiersHookConfig = JBDeploy721TiersHookConfig({
|
|
@@ -163,7 +163,9 @@ contract TierLifecycleHandler is Test {
|
|
|
163
163
|
transfersPausable: false,
|
|
164
164
|
useVotingUnits: false,
|
|
165
165
|
cannotBeRemoved: false,
|
|
166
|
-
cannotIncreaseDiscountPercent: false
|
|
166
|
+
cannotIncreaseDiscountPercent: false,
|
|
167
|
+
splitPercent: 0,
|
|
168
|
+
splits: new JBSplit[](0)
|
|
167
169
|
});
|
|
168
170
|
|
|
169
171
|
vm.prank(hookAddress);
|
|
@@ -8,6 +8,7 @@ import {StdUtils} from "forge-std/StdUtils.sol";
|
|
|
8
8
|
import {JB721TiersHookStore} from "../../../src/JB721TiersHookStore.sol";
|
|
9
9
|
import {JB721TierConfig} from "../../../src/structs/JB721TierConfig.sol";
|
|
10
10
|
import {JB721TiersHookFlags} from "../../../src/structs/JB721TiersHookFlags.sol";
|
|
11
|
+
import {JBSplit} from "@bananapus/core-v6/src/structs/JBSplit.sol";
|
|
11
12
|
|
|
12
13
|
/// @notice Handler for JB721TiersHookStore invariant tests.
|
|
13
14
|
/// @dev Acts as the "hook" address itself, so msg.sender (this) == hook in the store.
|
|
@@ -61,7 +62,9 @@ contract TierStoreHandler is CommonBase, StdCheats, StdUtils {
|
|
|
61
62
|
transfersPausable: false,
|
|
62
63
|
useVotingUnits: false,
|
|
63
64
|
cannotBeRemoved: false,
|
|
64
|
-
cannotIncreaseDiscountPercent: false
|
|
65
|
+
cannotIncreaseDiscountPercent: false,
|
|
66
|
+
splitPercent: 0,
|
|
67
|
+
splits: new JBSplit[](0)
|
|
65
68
|
});
|
|
66
69
|
|
|
67
70
|
try this._doAddTiers(configs) {
|