@bananapus/721-hook-v6 0.0.14 → 0.0.16
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 +4 -3
- package/ARCHITECTURE.md +2 -2
- package/README.md +2 -2
- package/RISKS.md +4 -3
- package/SKILLS.md +12 -12
- package/STYLE_GUIDE.md +14 -1
- package/package.json +5 -5
- package/script/Deploy.s.sol +11 -2
- package/script/helpers/Hook721DeploymentLib.sol +8 -1
- package/src/JB721TiersHook.sol +149 -123
- package/src/JB721TiersHookProjectDeployer.sol +4 -3
- package/src/JB721TiersHookStore.sol +8 -1
- package/src/abstract/ERC721.sol +38 -19
- package/src/abstract/JB721Hook.sol +5 -1
- package/src/interfaces/IJB721TiersHook.sol +22 -3
- package/src/interfaces/IJB721TiersHookStore.sol +3 -0
- package/src/libraries/JB721TiersHookLib.sol +156 -34
- package/src/libraries/JB721TiersRulesetMetadataResolver.sol +4 -1
- package/src/libraries/JBBitmap.sol +1 -0
- package/src/libraries/JBIpfsDecoder.sol +4 -1
- package/src/structs/JB721InitTiersConfig.sol +1 -5
- package/src/structs/JB721Tier.sol +2 -0
- package/src/structs/JB721TierConfig.sol +2 -0
- package/src/structs/JB721TiersHookFlags.sol +1 -0
- package/src/structs/JB721TiersMintReservesConfig.sol +1 -0
- package/src/structs/JB721TiersRulesetMetadata.sol +1 -0
- package/src/structs/JB721TiersSetDiscountPercentConfig.sol +1 -0
- package/src/structs/JBBitmapWord.sol +1 -0
- package/src/structs/JBDeploy721TiersHookConfig.sol +1 -0
- package/src/structs/JBLaunchProjectConfig.sol +1 -0
- package/src/structs/JBLaunchRulesetsConfig.sol +1 -0
- package/src/structs/JBPayDataHookRulesetConfig.sol +1 -0
- package/src/structs/JBPayDataHookRulesetMetadata.sol +4 -0
- package/src/structs/JBQueueRulesetsConfig.sol +1 -0
- package/src/structs/JBStored721Tier.sol +1 -0
- package/test/721HookAttacks.t.sol +1 -0
- package/test/E2E/Pay_Mint_Redeem_E2E.t.sol +30 -12
- package/test/Fork.t.sol +60 -11
- package/test/fork/ERC20TierSplitFork.t.sol +51 -9
- package/test/invariants/TierLifecycleInvariant.t.sol +3 -0
- package/test/invariants/TieredHookStoreInvariant.t.sol +5 -0
- package/test/invariants/handlers/TierLifecycleHandler.sol +32 -0
- package/test/invariants/handlers/TierStoreHandler.sol +3 -0
- package/test/regression/CacheTierLookup.t.sol +2 -0
- package/test/regression/ReserveBeneficiaryOverwrite.t.sol +1 -0
- package/test/regression/SplitNoBeneficiary.t.sol +2 -0
- package/test/unit/JB721TiersRulesetMetadataResolver.t.sol +3 -0
- package/test/unit/JBBitmap.t.sol +1 -0
- package/test/unit/JBIpfsDecoder.t.sol +5 -0
- package/test/unit/TierSupplyReserveCheck.t.sol +1 -0
- package/test/unit/adjustTier_Unit.t.sol +53 -34
- package/test/unit/deployer_Unit.t.sol +11 -0
- package/test/unit/getters_constructor_Unit.t.sol +54 -27
- package/test/unit/mintFor_mintReservesFor_Unit.t.sol +25 -0
- package/test/unit/pay_CrossCurrency_Unit.t.sol +54 -23
- package/test/unit/pay_Unit.t.sol +56 -13
- package/test/unit/redeem_Unit.t.sol +10 -0
- package/test/unit/tierSplitRouting_Unit.t.sol +13 -2
package/src/JB721TiersHook.sol
CHANGED
|
@@ -7,7 +7,6 @@ import {IJBPrices} from "@bananapus/core-v6/src/interfaces/IJBPrices.sol";
|
|
|
7
7
|
import {IJBRulesetDataHook} from "@bananapus/core-v6/src/interfaces/IJBRulesetDataHook.sol";
|
|
8
8
|
import {IJBRulesets} from "@bananapus/core-v6/src/interfaces/IJBRulesets.sol";
|
|
9
9
|
import {IJBSplits} from "@bananapus/core-v6/src/interfaces/IJBSplits.sol";
|
|
10
|
-
import {JBConstants} from "@bananapus/core-v6/src/libraries/JBConstants.sol";
|
|
11
10
|
import {JBMetadataResolver} from "@bananapus/core-v6/src/libraries/JBMetadataResolver.sol";
|
|
12
11
|
import {JBRulesetMetadataResolver} from "@bananapus/core-v6/src/libraries/JBRulesetMetadataResolver.sol";
|
|
13
12
|
import {JBAfterPayRecordedContext} from "@bananapus/core-v6/src/structs/JBAfterPayRecordedContext.sol";
|
|
@@ -17,8 +16,6 @@ import {JBRuleset} from "@bananapus/core-v6/src/structs/JBRuleset.sol";
|
|
|
17
16
|
import {JBOwnable} from "@bananapus/ownable-v6/src/JBOwnable.sol";
|
|
18
17
|
import {JBPermissionIds} from "@bananapus/permission-ids-v6/src/JBPermissionIds.sol";
|
|
19
18
|
import {ERC2771Context} from "@openzeppelin/contracts/metatx/ERC2771Context.sol";
|
|
20
|
-
import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
|
|
21
|
-
import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
|
|
22
19
|
import {Context} from "@openzeppelin/contracts/utils/Context.sol";
|
|
23
20
|
import {IERC165} from "@openzeppelin/contracts/utils/introspection/IERC165.sol";
|
|
24
21
|
import {JB721Hook} from "./abstract/JB721Hook.sol";
|
|
@@ -26,7 +23,6 @@ import {IJB721TiersHook} from "./interfaces/IJB721TiersHook.sol";
|
|
|
26
23
|
import {IJB721TiersHookStore} from "./interfaces/IJB721TiersHookStore.sol";
|
|
27
24
|
import {IJB721TokenUriResolver} from "./interfaces/IJB721TokenUriResolver.sol";
|
|
28
25
|
import {JB721TiersHookLib} from "./libraries/JB721TiersHookLib.sol";
|
|
29
|
-
import {mulDiv} from "@prb/math/src/Common.sol";
|
|
30
26
|
import {JB721TiersRulesetMetadataResolver} from "./libraries/JB721TiersRulesetMetadataResolver.sol";
|
|
31
27
|
import {JB721InitTiersConfig} from "./structs/JB721InitTiersConfig.sol";
|
|
32
28
|
import {JB721Tier} from "./structs/JB721Tier.sol";
|
|
@@ -41,8 +37,6 @@ import {JB721TiersSetDiscountPercentConfig} from "./structs/JB721TiersSetDiscoun
|
|
|
41
37
|
/// information specified by the payer. The project's owner can enable NFT cash outs through this hook, allowing
|
|
42
38
|
/// holders to burn their NFTs to reclaim funds from the project (in proportion to the NFT's price).
|
|
43
39
|
contract JB721TiersHook is JBOwnable, ERC2771Context, JB721Hook, IJB721TiersHook {
|
|
44
|
-
using SafeERC20 for IERC20;
|
|
45
|
-
|
|
46
40
|
//*********************************************************************//
|
|
47
41
|
// --------------------------- custom errors ------------------------- //
|
|
48
42
|
//*********************************************************************//
|
|
@@ -59,6 +53,9 @@ contract JB721TiersHook is JBOwnable, ERC2771Context, JB721Hook, IJB721TiersHook
|
|
|
59
53
|
// --------------- public immutable stored properties ---------------- //
|
|
60
54
|
//*********************************************************************//
|
|
61
55
|
|
|
56
|
+
/// @notice The contract that exposes price feeds for currency conversions.
|
|
57
|
+
IJBPrices public immutable override PRICES;
|
|
58
|
+
|
|
62
59
|
/// @notice The contract storing and managing project rulesets.
|
|
63
60
|
IJBRulesets public immutable override RULESETS;
|
|
64
61
|
|
|
@@ -94,9 +91,8 @@ contract JB721TiersHook is JBOwnable, ERC2771Context, JB721Hook, IJB721TiersHook
|
|
|
94
91
|
|
|
95
92
|
/// @notice Packed context for the pricing of this contract's tiers.
|
|
96
93
|
/// @dev Packed into a uint256:
|
|
97
|
-
/// - currency in bits 0-31 (32 bits),
|
|
98
|
-
/// - pricing decimals in bits 32-39 (8 bits)
|
|
99
|
-
/// - prices contract in bits 40-199 (160 bits).
|
|
94
|
+
/// - currency in bits 0-31 (32 bits), and
|
|
95
|
+
/// - pricing decimals in bits 32-39 (8 bits).
|
|
100
96
|
uint256 internal _packedPricingContext;
|
|
101
97
|
|
|
102
98
|
//*********************************************************************//
|
|
@@ -105,6 +101,7 @@ contract JB721TiersHook is JBOwnable, ERC2771Context, JB721Hook, IJB721TiersHook
|
|
|
105
101
|
|
|
106
102
|
/// @param directory A directory of terminals and controllers for projects.
|
|
107
103
|
/// @param permissions A contract storing permissions.
|
|
104
|
+
/// @param prices A contract that exposes price feeds for currency conversions.
|
|
108
105
|
/// @param rulesets A contract storing and managing project rulesets.
|
|
109
106
|
/// @param store The contract which stores the NFT's data.
|
|
110
107
|
/// @param splits The contract that stores and manages splits.
|
|
@@ -112,6 +109,7 @@ contract JB721TiersHook is JBOwnable, ERC2771Context, JB721Hook, IJB721TiersHook
|
|
|
112
109
|
constructor(
|
|
113
110
|
IJBDirectory directory,
|
|
114
111
|
IJBPermissions permissions,
|
|
112
|
+
IJBPrices prices,
|
|
115
113
|
IJBRulesets rulesets,
|
|
116
114
|
IJB721TiersHookStore store,
|
|
117
115
|
IJBSplits splits,
|
|
@@ -121,6 +119,7 @@ contract JB721TiersHook is JBOwnable, ERC2771Context, JB721Hook, IJB721TiersHook
|
|
|
121
119
|
JB721Hook(directory)
|
|
122
120
|
ERC2771Context(trustedForwarder)
|
|
123
121
|
{
|
|
122
|
+
PRICES = prices;
|
|
124
123
|
RULESETS = rulesets;
|
|
125
124
|
STORE = store;
|
|
126
125
|
SPLITS = splits;
|
|
@@ -146,19 +145,17 @@ contract JB721TiersHook is JBOwnable, ERC2771Context, JB721Hook, IJB721TiersHook
|
|
|
146
145
|
}
|
|
147
146
|
|
|
148
147
|
/// @notice Context for the pricing of this hook's tiers.
|
|
149
|
-
/// @dev If the `prices` contract is the zero address, this contract only accepts payments in the `currency` token.
|
|
150
148
|
/// @return currency The currency used for tier prices.
|
|
151
149
|
/// @return decimals The amount of decimals being used in tier prices.
|
|
152
|
-
|
|
153
|
-
function pricingContext() external view override returns (uint256 currency, uint256 decimals, IJBPrices prices) {
|
|
150
|
+
function pricingContext() external view override returns (uint256 currency, uint256 decimals) {
|
|
154
151
|
// Get a reference to the packed pricing context.
|
|
155
152
|
uint256 packed = _packedPricingContext;
|
|
156
153
|
// currency in bits 0-31 (32 bits).
|
|
154
|
+
// forge-lint: disable-next-line(unsafe-typecast)
|
|
157
155
|
currency = uint256(uint32(packed));
|
|
158
156
|
// pricing decimals in bits 32-39 (8 bits).
|
|
157
|
+
// forge-lint: disable-next-line(unsafe-typecast)
|
|
159
158
|
decimals = uint256(uint8(packed >> 32));
|
|
160
|
-
// prices contract in bits 40-199 (160 bits).
|
|
161
|
-
prices = IJBPrices(address(uint160(packed >> 40)));
|
|
162
159
|
}
|
|
163
160
|
|
|
164
161
|
//*********************************************************************//
|
|
@@ -188,32 +185,30 @@ contract JB721TiersHook is JBOwnable, ERC2771Context, JB721Hook, IJB721TiersHook
|
|
|
188
185
|
hookSpecifications = new JBPayHookSpecification[](1);
|
|
189
186
|
|
|
190
187
|
// Calculate per-tier split amounts via the library.
|
|
191
|
-
(uint256 totalSplitAmount, bytes memory splitMetadata) =
|
|
192
|
-
|
|
188
|
+
(uint256 totalSplitAmount, bytes memory splitMetadata) = JB721TiersHookLib.calculateSplitAmounts({
|
|
189
|
+
store: STORE, hook: address(this), metadataIdTarget: METADATA_ID_TARGET, metadata: context.metadata
|
|
190
|
+
});
|
|
193
191
|
|
|
194
192
|
// Convert split amounts from tier pricing to payment token denomination if currencies differ.
|
|
195
193
|
if (totalSplitAmount != 0) {
|
|
196
|
-
(totalSplitAmount, splitMetadata) = JB721TiersHookLib.convertSplitAmounts(
|
|
197
|
-
totalSplitAmount,
|
|
198
|
-
splitMetadata,
|
|
199
|
-
_packedPricingContext,
|
|
200
|
-
|
|
201
|
-
context.
|
|
202
|
-
context.amount.
|
|
203
|
-
|
|
194
|
+
(totalSplitAmount, splitMetadata) = JB721TiersHookLib.convertSplitAmounts({
|
|
195
|
+
totalSplitAmount: totalSplitAmount,
|
|
196
|
+
splitMetadata: splitMetadata,
|
|
197
|
+
packedPricingContext: _packedPricingContext,
|
|
198
|
+
prices: PRICES,
|
|
199
|
+
projectId: context.projectId,
|
|
200
|
+
amountCurrency: context.amount.currency,
|
|
201
|
+
amountDecimals: context.amount.decimals
|
|
202
|
+
});
|
|
204
203
|
}
|
|
205
204
|
|
|
206
205
|
// Adjust weight so the terminal mints tokens only for the amount that actually enters the project.
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
} else {
|
|
214
|
-
// Splits consume the entire payment — no tokens should be minted.
|
|
215
|
-
weight = 0;
|
|
216
|
-
}
|
|
206
|
+
weight = JB721TiersHookLib.calculateWeight({
|
|
207
|
+
contextWeight: context.weight,
|
|
208
|
+
amountValue: context.amount.value,
|
|
209
|
+
totalSplitAmount: totalSplitAmount,
|
|
210
|
+
issueTokensForSplits: STORE.flagsOf(address(this)).issueTokensForSplits
|
|
211
|
+
});
|
|
217
212
|
|
|
218
213
|
hookSpecifications[0] = JBPayHookSpecification({hook: this, amount: totalSplitAmount, metadata: splitMetadata});
|
|
219
214
|
}
|
|
@@ -268,8 +263,6 @@ contract JB721TiersHook is JBOwnable, ERC2771Context, JB721Hook, IJB721TiersHook
|
|
|
268
263
|
packed |= uint256(tiersConfig.currency);
|
|
269
264
|
// pack the pricing decimals in bits 32-39 (8 bits).
|
|
270
265
|
packed |= uint256(tiersConfig.decimals) << 32;
|
|
271
|
-
// pack the prices contract in bits 40-199 (160 bits).
|
|
272
|
-
packed |= uint256(uint160(address(tiersConfig.prices))) << 40;
|
|
273
266
|
// Store the packed value.
|
|
274
267
|
// slither-disable-next-line events-maths
|
|
275
268
|
_packedPricingContext = packed;
|
|
@@ -287,9 +280,14 @@ contract JB721TiersHook is JBOwnable, ERC2771Context, JB721Hook, IJB721TiersHook
|
|
|
287
280
|
|
|
288
281
|
// Record the tiers in this hook's store and set any tier split groups.
|
|
289
282
|
if (tiersConfig.tiers.length != 0) {
|
|
290
|
-
JB721TiersHookLib.recordAddTiersFor(
|
|
291
|
-
STORE,
|
|
292
|
-
|
|
283
|
+
JB721TiersHookLib.recordAddTiersFor({
|
|
284
|
+
store: STORE,
|
|
285
|
+
splits: SPLITS,
|
|
286
|
+
projectId: projectId,
|
|
287
|
+
hookAddress: address(this),
|
|
288
|
+
caller: _msgSender(),
|
|
289
|
+
tiersToAdd: tiersConfig.tiers
|
|
290
|
+
});
|
|
293
291
|
}
|
|
294
292
|
|
|
295
293
|
// Set the flags if needed.
|
|
@@ -343,9 +341,15 @@ contract JB721TiersHook is JBOwnable, ERC2771Context, JB721Hook, IJB721TiersHook
|
|
|
343
341
|
});
|
|
344
342
|
|
|
345
343
|
// Delegate to the library (via DELEGATECALL) for tier removal, addition, event emission, and split setting.
|
|
346
|
-
JB721TiersHookLib.adjustTiersFor(
|
|
347
|
-
STORE,
|
|
348
|
-
|
|
344
|
+
JB721TiersHookLib.adjustTiersFor({
|
|
345
|
+
store: STORE,
|
|
346
|
+
splits: SPLITS,
|
|
347
|
+
projectId: PROJECT_ID,
|
|
348
|
+
hookAddress: address(this),
|
|
349
|
+
caller: _msgSender(),
|
|
350
|
+
tiersToAdd: tiersToAdd,
|
|
351
|
+
tierIdsToRemove: tierIdsToRemove
|
|
352
|
+
});
|
|
349
353
|
}
|
|
350
354
|
|
|
351
355
|
/// @notice Manually mint NFTs from the provided tiers .
|
|
@@ -376,7 +380,7 @@ contract JB721TiersHook is JBOwnable, ERC2771Context, JB721Hook, IJB721TiersHook
|
|
|
376
380
|
uint256 tokenId = tokenIds[i];
|
|
377
381
|
|
|
378
382
|
// Mint the NFT.
|
|
379
|
-
_mint(beneficiary, tokenId);
|
|
383
|
+
_mint({to: beneficiary, tokenId: tokenId});
|
|
380
384
|
|
|
381
385
|
emit Mint({
|
|
382
386
|
tokenId: tokenId, tierId: tierIds[i], beneficiary: beneficiary, totalAmountPaid: 0, caller: _msgSender()
|
|
@@ -427,18 +431,24 @@ contract JB721TiersHook is JBOwnable, ERC2771Context, JB721Hook, IJB721TiersHook
|
|
|
427
431
|
}
|
|
428
432
|
}
|
|
429
433
|
|
|
430
|
-
/// @notice Update this hook's
|
|
431
|
-
/// @dev Only this contract's owner can set the metadata.
|
|
434
|
+
/// @notice Update this hook's metadata properties.
|
|
435
|
+
/// @dev Only this contract's owner or an operator with the `SET_721_METADATA` permission can set the metadata.
|
|
436
|
+
/// @param name The new collection name. Send empty to leave unchanged.
|
|
437
|
+
/// @param symbol The new collection symbol. Send empty to leave unchanged.
|
|
432
438
|
/// @param baseUri The new base URI.
|
|
433
439
|
/// @param contractUri The new contract URI.
|
|
434
440
|
/// @param tokenUriResolver The new URI resolver.
|
|
435
441
|
/// @param encodedIPFSUriTierId The ID of the tier to set the encoded IPFS URI of.
|
|
436
442
|
/// @param encodedIPFSUri The encoded IPFS URI to set.
|
|
437
443
|
function setMetadata(
|
|
444
|
+
string calldata name,
|
|
445
|
+
string calldata symbol,
|
|
438
446
|
string calldata baseUri,
|
|
439
447
|
string calldata contractUri,
|
|
440
448
|
IJB721TokenUriResolver tokenUriResolver,
|
|
449
|
+
// forge-lint: disable-next-line(mixed-case-variable)
|
|
441
450
|
uint256 encodedIPFSUriTierId,
|
|
451
|
+
// forge-lint: disable-next-line(mixed-case-variable)
|
|
442
452
|
bytes32 encodedIPFSUri
|
|
443
453
|
)
|
|
444
454
|
external
|
|
@@ -449,6 +459,16 @@ contract JB721TiersHook is JBOwnable, ERC2771Context, JB721Hook, IJB721TiersHook
|
|
|
449
459
|
account: owner(), projectId: PROJECT_ID, permissionId: JBPermissionIds.SET_721_METADATA
|
|
450
460
|
});
|
|
451
461
|
|
|
462
|
+
if (bytes(name).length != 0) {
|
|
463
|
+
// Store the new collection name.
|
|
464
|
+
_setName(name);
|
|
465
|
+
emit SetName({name: name, caller: _msgSender()});
|
|
466
|
+
}
|
|
467
|
+
if (bytes(symbol).length != 0) {
|
|
468
|
+
// Store the new collection symbol.
|
|
469
|
+
_setSymbol(symbol);
|
|
470
|
+
emit SetSymbol({symbol: symbol, caller: _msgSender()});
|
|
471
|
+
}
|
|
452
472
|
if (bytes(baseUri).length != 0) {
|
|
453
473
|
// Store the new base URI.
|
|
454
474
|
baseURI = baseUri;
|
|
@@ -509,7 +529,7 @@ contract JB721TiersHook is JBOwnable, ERC2771Context, JB721Hook, IJB721TiersHook
|
|
|
509
529
|
|
|
510
530
|
// Mint the NFT.
|
|
511
531
|
// slither-disable-next-line reentrency-events
|
|
512
|
-
_mint(reserveBeneficiary, tokenId);
|
|
532
|
+
_mint({to: reserveBeneficiary, tokenId: tokenId});
|
|
513
533
|
}
|
|
514
534
|
}
|
|
515
535
|
|
|
@@ -593,7 +613,7 @@ contract JB721TiersHook is JBOwnable, ERC2771Context, JB721Hook, IJB721TiersHook
|
|
|
593
613
|
|
|
594
614
|
// Mint the NFT.
|
|
595
615
|
// slither-disable-next-line reentrancy-events
|
|
596
|
-
_mint(beneficiary, tokenId);
|
|
616
|
+
_mint({to: beneficiary, tokenId: tokenId});
|
|
597
617
|
}
|
|
598
618
|
}
|
|
599
619
|
|
|
@@ -607,100 +627,106 @@ contract JB721TiersHook is JBOwnable, ERC2771Context, JB721Hook, IJB721TiersHook
|
|
|
607
627
|
uint256 value;
|
|
608
628
|
{
|
|
609
629
|
bool valid;
|
|
610
|
-
(value, valid) = JB721TiersHookLib.normalizePaymentValue(
|
|
611
|
-
_packedPricingContext,
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
context.amount.
|
|
615
|
-
context.amount.
|
|
616
|
-
|
|
630
|
+
(value, valid) = JB721TiersHookLib.normalizePaymentValue({
|
|
631
|
+
packedPricingContext: _packedPricingContext,
|
|
632
|
+
prices: PRICES,
|
|
633
|
+
projectId: PROJECT_ID,
|
|
634
|
+
amountValue: context.amount.value,
|
|
635
|
+
amountCurrency: context.amount.currency,
|
|
636
|
+
amountDecimals: context.amount.decimals
|
|
637
|
+
});
|
|
617
638
|
if (!valid) return;
|
|
618
639
|
}
|
|
619
640
|
|
|
620
|
-
//
|
|
621
|
-
|
|
641
|
+
// Scope block to free stack slots before the distributeAll call below.
|
|
642
|
+
{
|
|
643
|
+
// Keep a reference to the number of NFT credits the beneficiary already has.
|
|
644
|
+
uint256 payCredits = payCreditsOf[context.beneficiary];
|
|
622
645
|
|
|
623
|
-
|
|
624
|
-
|
|
646
|
+
// Set the leftover amount as the initial value.
|
|
647
|
+
uint256 leftoverAmount = value;
|
|
625
648
|
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
649
|
+
// If the payer is the beneficiary, combine their NFT credits with the amount paid.
|
|
650
|
+
uint256 unusedPayCredits;
|
|
651
|
+
if (context.payer == context.beneficiary) {
|
|
652
|
+
leftoverAmount += payCredits;
|
|
653
|
+
} else {
|
|
654
|
+
// Otherwise, the payer's NFT credits won't be used, and we keep track of the unused credits.
|
|
655
|
+
unusedPayCredits = payCredits;
|
|
656
|
+
}
|
|
634
657
|
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
658
|
+
// Keep a reference to the boolean indicating whether paying more than the price of the NFTs being minted
|
|
659
|
+
// is allowed. Defaults to the collection's flag.
|
|
660
|
+
bool allowOverspending = !STORE.flagsOf(address(this)).preventOverspending;
|
|
638
661
|
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
662
|
+
// Resolve the metadata.
|
|
663
|
+
(bool found, bytes memory metadata) = JBMetadataResolver.getDataFor({
|
|
664
|
+
id: JBMetadataResolver.getId({purpose: "pay", target: METADATA_ID_TARGET}),
|
|
665
|
+
metadata: context.payerMetadata
|
|
666
|
+
});
|
|
643
667
|
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
668
|
+
if (found) {
|
|
669
|
+
// Keep a reference to the IDs of the tier be to minted.
|
|
670
|
+
uint16[] memory tierIdsToMint;
|
|
647
671
|
|
|
648
|
-
|
|
649
|
-
|
|
672
|
+
// Keep a reference to the payer's flag indicating whether overspending is allowed.
|
|
673
|
+
bool payerAllowsOverspending;
|
|
650
674
|
|
|
651
|
-
|
|
652
|
-
|
|
675
|
+
// Decode the metadata.
|
|
676
|
+
(payerAllowsOverspending, tierIdsToMint) = abi.decode(metadata, (bool, uint16[]));
|
|
653
677
|
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
678
|
+
// Make sure overspending is allowed if requested.
|
|
679
|
+
if (allowOverspending && !payerAllowsOverspending) {
|
|
680
|
+
allowOverspending = false;
|
|
681
|
+
}
|
|
658
682
|
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
683
|
+
// Mint NFTs from the tiers as specified.
|
|
684
|
+
if (tierIdsToMint.length != 0) {
|
|
685
|
+
// slither-disable-next-line reentrancy-events,reentrancy-no-eth
|
|
686
|
+
leftoverAmount = _mintAll({
|
|
687
|
+
amount: leftoverAmount, mintTierIds: tierIdsToMint, beneficiary: context.beneficiary
|
|
688
|
+
});
|
|
689
|
+
}
|
|
664
690
|
}
|
|
665
|
-
}
|
|
666
|
-
|
|
667
|
-
// If overspending isn't allowed, revert.
|
|
668
|
-
if (leftoverAmount != 0 && !allowOverspending) revert JB721TiersHook_Overspending(leftoverAmount);
|
|
669
691
|
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
692
|
+
// If overspending isn't allowed, revert.
|
|
693
|
+
if (leftoverAmount != 0 && !allowOverspending) revert JB721TiersHook_Overspending(leftoverAmount);
|
|
694
|
+
|
|
695
|
+
// Update NFT credits if they changed.
|
|
696
|
+
uint256 newPayCredits = leftoverAmount + unusedPayCredits;
|
|
697
|
+
|
|
698
|
+
if (newPayCredits != payCredits) {
|
|
699
|
+
if (newPayCredits > payCredits) {
|
|
700
|
+
emit AddPayCredits({
|
|
701
|
+
amount: newPayCredits - payCredits,
|
|
702
|
+
newTotalCredits: newPayCredits,
|
|
703
|
+
account: context.beneficiary,
|
|
704
|
+
caller: _msgSender()
|
|
705
|
+
});
|
|
706
|
+
} else {
|
|
707
|
+
emit UsePayCredits({
|
|
708
|
+
amount: payCredits - newPayCredits,
|
|
709
|
+
newTotalCredits: newPayCredits,
|
|
710
|
+
account: context.beneficiary,
|
|
711
|
+
caller: _msgSender()
|
|
712
|
+
});
|
|
713
|
+
}
|
|
714
|
+
|
|
715
|
+
payCreditsOf[context.beneficiary] = newPayCredits;
|
|
688
716
|
}
|
|
689
|
-
|
|
690
|
-
payCreditsOf[context.beneficiary] = newPayCredits;
|
|
691
717
|
}
|
|
692
718
|
|
|
693
719
|
// Distribute any forwarded funds to tier split groups.
|
|
694
720
|
if (context.hookMetadata.length != 0 && context.forwardedAmount.value != 0) {
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
);
|
|
721
|
+
JB721TiersHookLib.distributeAll({
|
|
722
|
+
directory: DIRECTORY,
|
|
723
|
+
splits: SPLITS,
|
|
724
|
+
projectId: PROJECT_ID,
|
|
725
|
+
hookAddress: address(this),
|
|
726
|
+
token: context.forwardedAmount.token,
|
|
727
|
+
amount: context.forwardedAmount.value,
|
|
728
|
+
encodedSplitData: context.hookMetadata
|
|
729
|
+
});
|
|
704
730
|
}
|
|
705
731
|
}
|
|
706
732
|
|
|
@@ -732,7 +758,7 @@ contract JB721TiersHook is JBOwnable, ERC2771Context, JB721Hook, IJB721TiersHook
|
|
|
732
758
|
JB721Tier memory tier = STORE.tierOfTokenId({hook: address(this), tokenId: tokenId, includeResolvedUri: false});
|
|
733
759
|
|
|
734
760
|
// Record the transfers and keep a reference to where the token is coming from.
|
|
735
|
-
from = super._update(to, tokenId, auth);
|
|
761
|
+
from = super._update({to: to, tokenId: tokenId, auth: auth});
|
|
736
762
|
|
|
737
763
|
// Transfers must not be paused (when not minting or burning).
|
|
738
764
|
if (from != address(0)) {
|
|
@@ -124,6 +124,7 @@ contract JB721TiersHookProjectDeployer is ERC2771Context, JBPermissioned, IJB721
|
|
|
124
124
|
returns (uint256 rulesetId, IJB721TiersHook hook)
|
|
125
125
|
{
|
|
126
126
|
// Get the project's projects contract.
|
|
127
|
+
// forge-lint: disable-next-line(mixed-case-variable)
|
|
127
128
|
IJBProjects PROJECTS = DIRECTORY.PROJECTS();
|
|
128
129
|
|
|
129
130
|
// Enforce permissions.
|
|
@@ -256,7 +257,7 @@ contract JB721TiersHookProjectDeployer is ERC2771Context, JBPermissioned, IJB721
|
|
|
256
257
|
pausePay: payDataRulesetConfig.metadata.pausePay,
|
|
257
258
|
pauseCreditTransfers: payDataRulesetConfig.metadata.pauseCreditTransfers,
|
|
258
259
|
allowOwnerMinting: payDataRulesetConfig.metadata.allowOwnerMinting,
|
|
259
|
-
allowSetCustomToken:
|
|
260
|
+
allowSetCustomToken: payDataRulesetConfig.metadata.allowSetCustomToken,
|
|
260
261
|
allowTerminalMigration: payDataRulesetConfig.metadata.allowTerminalMigration,
|
|
261
262
|
allowSetTerminals: payDataRulesetConfig.metadata.allowSetTerminals,
|
|
262
263
|
allowSetController: payDataRulesetConfig.metadata.allowSetController,
|
|
@@ -324,7 +325,7 @@ contract JB721TiersHookProjectDeployer is ERC2771Context, JBPermissioned, IJB721
|
|
|
324
325
|
pausePay: payDataRulesetConfig.metadata.pausePay,
|
|
325
326
|
pauseCreditTransfers: payDataRulesetConfig.metadata.pauseCreditTransfers,
|
|
326
327
|
allowOwnerMinting: payDataRulesetConfig.metadata.allowOwnerMinting,
|
|
327
|
-
allowSetCustomToken:
|
|
328
|
+
allowSetCustomToken: payDataRulesetConfig.metadata.allowSetCustomToken,
|
|
328
329
|
allowTerminalMigration: payDataRulesetConfig.metadata.allowTerminalMigration,
|
|
329
330
|
allowSetTerminals: payDataRulesetConfig.metadata.allowSetTerminals,
|
|
330
331
|
allowSetController: payDataRulesetConfig.metadata.allowSetController,
|
|
@@ -390,7 +391,7 @@ contract JB721TiersHookProjectDeployer is ERC2771Context, JBPermissioned, IJB721
|
|
|
390
391
|
pausePay: payDataRulesetConfig.metadata.pausePay,
|
|
391
392
|
pauseCreditTransfers: payDataRulesetConfig.metadata.pauseCreditTransfers,
|
|
392
393
|
allowOwnerMinting: payDataRulesetConfig.metadata.allowOwnerMinting,
|
|
393
|
-
allowSetCustomToken:
|
|
394
|
+
allowSetCustomToken: payDataRulesetConfig.metadata.allowSetCustomToken,
|
|
394
395
|
allowTerminalMigration: payDataRulesetConfig.metadata.allowTerminalMigration,
|
|
395
396
|
allowSetTerminals: payDataRulesetConfig.metadata.allowSetTerminals,
|
|
396
397
|
allowSetController: payDataRulesetConfig.metadata.allowSetController,
|
|
@@ -62,6 +62,7 @@ contract JB721TiersHookStore is IJB721TiersHookStore {
|
|
|
62
62
|
/// @custom:param hook The 721 contract that the tier belongs to.
|
|
63
63
|
/// @custom:param tierId The ID of the tier to get the encoded IPFS URI of.
|
|
64
64
|
/// @custom:returns The encoded IPFS URI.
|
|
65
|
+
// forge-lint: disable-next-line(mixed-case-variable)
|
|
65
66
|
mapping(address hook => mapping(uint256 tierId => bytes32)) public override encodedIPFSUriOf;
|
|
66
67
|
|
|
67
68
|
/// @notice Returns the largest tier ID currently used on the provided 721 contract.
|
|
@@ -155,6 +156,7 @@ contract JB721TiersHookStore is IJB721TiersHookStore {
|
|
|
155
156
|
/// @param hook The 721 contract that the encoded IPFS URI belongs to.
|
|
156
157
|
/// @param tokenId The token ID of the 721 to get the encoded tier IPFS URI of.
|
|
157
158
|
/// @return The encoded IPFS URI.
|
|
159
|
+
// forge-lint: disable-next-line(mixed-case-function)
|
|
158
160
|
function encodedTierIPFSUriOf(address hook, uint256 tokenId) external view override returns (bytes32) {
|
|
159
161
|
return encodedIPFSUriOf[hook][tierIdOfToken(tokenId)];
|
|
160
162
|
}
|
|
@@ -532,6 +534,7 @@ contract JB721TiersHookStore is IJB721TiersHookStore {
|
|
|
532
534
|
|
|
533
535
|
// slither-disable-next-line calls-loop
|
|
534
536
|
return JB721Tier({
|
|
537
|
+
// forge-lint: disable-next-line(unsafe-typecast)
|
|
535
538
|
id: uint32(tierId),
|
|
536
539
|
price: storedTier.price,
|
|
537
540
|
remainingSupply: storedTier.remainingSupply,
|
|
@@ -1067,7 +1070,9 @@ contract JB721TiersHookStore is IJB721TiersHookStore {
|
|
|
1067
1070
|
|
|
1068
1071
|
// Apply a discount if needed.
|
|
1069
1072
|
if (storedTier.discountPercent > 0) {
|
|
1070
|
-
price -= mulDiv(
|
|
1073
|
+
price -= mulDiv({
|
|
1074
|
+
x: price, y: storedTier.discountPercent, denominator: JB721Constants.DISCOUNT_DENOMINATOR
|
|
1075
|
+
});
|
|
1071
1076
|
}
|
|
1072
1077
|
|
|
1073
1078
|
// Make sure the `amount` is greater than or equal to the tier's price.
|
|
@@ -1178,12 +1183,14 @@ contract JB721TiersHookStore is IJB721TiersHookStore {
|
|
|
1178
1183
|
}
|
|
1179
1184
|
|
|
1180
1185
|
// Set the discount.
|
|
1186
|
+
// forge-lint: disable-next-line(unsafe-typecast)
|
|
1181
1187
|
storedTier.discountPercent = uint8(discountPercent);
|
|
1182
1188
|
}
|
|
1183
1189
|
|
|
1184
1190
|
/// @notice Record a new encoded IPFS URI for a tier.
|
|
1185
1191
|
/// @param tierId The ID of the tier to set the encoded IPFS URI of.
|
|
1186
1192
|
/// @param encodedIPFSUri The encoded IPFS URI to set for the tier.
|
|
1193
|
+
// forge-lint: disable-next-line(mixed-case-function, mixed-case-variable)
|
|
1187
1194
|
function recordSetEncodedIPFSUriOf(uint256 tierId, bytes32 encodedIPFSUri) external override {
|
|
1188
1195
|
encodedIPFSUriOf[msg.sender][tierId] = encodedIPFSUri;
|
|
1189
1196
|
}
|