/
ZoraCreator1155Impl.sol
937 lines (802 loc) · 42.7 KB
/
ZoraCreator1155Impl.sol
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
// SPDX-License-Identifier: MIT
pragma solidity 0.8.17;
import {ERC1155Upgradeable} from "@zoralabs/openzeppelin-contracts-upgradeable/contracts/token/ERC1155/ERC1155Upgradeable.sol";
import {ReentrancyGuardUpgradeable} from "@zoralabs/openzeppelin-contracts-upgradeable/contracts/security/ReentrancyGuardUpgradeable.sol";
import {UUPSUpgradeable} from "@zoralabs/openzeppelin-contracts-upgradeable/contracts/proxy/utils/UUPSUpgradeable.sol";
import {IERC1155MetadataURIUpgradeable} from "@zoralabs/openzeppelin-contracts-upgradeable/contracts/interfaces/IERC1155MetadataURIUpgradeable.sol";
import {IERC165Upgradeable} from "@zoralabs/openzeppelin-contracts-upgradeable/contracts/interfaces/IERC165Upgradeable.sol";
import {IProtocolRewards} from "@zoralabs/protocol-rewards/src/interfaces/IProtocolRewards.sol";
import {RewardSplits, RewardSplitsLib} from "@zoralabs/protocol-rewards/src/abstract/RewardSplits.sol";
import {ERC1155RewardsStorageV1} from "@zoralabs/protocol-rewards/src/abstract/ERC1155/ERC1155RewardsStorageV1.sol";
import {ReentrancyGuardUpgradeable} from "@zoralabs/openzeppelin-contracts-upgradeable/contracts/security/ReentrancyGuardUpgradeable.sol";
import {UUPSUpgradeable} from "@zoralabs/openzeppelin-contracts-upgradeable/contracts/proxy/utils/UUPSUpgradeable.sol";
import {MathUpgradeable} from "@zoralabs/openzeppelin-contracts-upgradeable/contracts/utils/math/MathUpgradeable.sol";
import {IZoraCreator1155} from "../interfaces/IZoraCreator1155.sol";
import {IZoraCreator1155Initializer} from "../interfaces/IZoraCreator1155Initializer.sol";
import {IERC7572} from "../interfaces/IERC7572.sol";
import {ContractVersionBase} from "../version/ContractVersionBase.sol";
import {CreatorPermissionControl} from "../permissions/CreatorPermissionControl.sol";
import {CreatorRendererControl} from "../renderer/CreatorRendererControl.sol";
import {CreatorRoyaltiesControl} from "../royalties/CreatorRoyaltiesControl.sol";
import {ICreatorCommands} from "../interfaces/ICreatorCommands.sol";
import {IMinter1155} from "../interfaces/IMinter1155.sol";
import {IRenderer1155} from "../interfaces/IRenderer1155.sol";
import {ITransferHookReceiver} from "../interfaces/ITransferHookReceiver.sol";
import {IUpgradeGate} from "../interfaces/IUpgradeGate.sol";
import {IZoraCreator1155} from "../interfaces/IZoraCreator1155.sol";
import {LegacyNamingControl} from "../legacy-naming/LegacyNamingControl.sol";
import {PublicMulticall} from "../utils/PublicMulticall.sol";
import {SharedBaseConstants} from "../shared/SharedBaseConstants.sol";
import {TransferHelperUtils} from "../utils/TransferHelperUtils.sol";
import {ZoraCreator1155StorageV1} from "./ZoraCreator1155StorageV1.sol";
import {IZoraCreator1155Errors} from "../interfaces/IZoraCreator1155Errors.sol";
import {ERC1155DelegationStorageV1} from "../delegation/ERC1155DelegationStorageV1.sol";
import {IZoraCreator1155DelegatedCreation, ISupportsAABasedDelegatedTokenCreation, IHasSupportedPremintSignatureVersions} from "../interfaces/IZoraCreator1155DelegatedCreation.sol";
import {IMintWithRewardsRecipients} from "../interfaces/IMintWithRewardsRecipients.sol";
import {ZoraCreator1155Attribution, DecodedCreatorAttribution, PremintTokenSetup, PremintConfigV2, DelegatedTokenCreation, DelegatedTokenSetup} from "../delegation/ZoraCreator1155Attribution.sol";
import {ContractCreationConfig, PremintConfig} from "@zoralabs/shared-contracts/entities/Premint.sol";
import {IZoraMintsMinterManager} from "@zoralabs/mints-contracts/src/interfaces/IZoraMintsMinterManager.sol";
import {IZoraMints1155} from "@zoralabs/mints-contracts/src/interfaces/IZoraMints1155.sol";
import {IERC1155} from "@openzeppelin/contracts/token/ERC1155/IERC1155.sol";
import {IERC1155Receiver} from "@openzeppelin/contracts/token/ERC1155/IERC1155Receiver.sol";
import {IMintWithMints} from "@zoralabs/mints-contracts/src/IMintWithMints.sol";
import {Redemption} from "@zoralabs/mints-contracts/src/ZoraMintsTypes.sol";
/// Imagine. Mint. Enjoy.
/// @title ZoraCreator1155Impl
/// @notice The core implementation contract for a creator's 1155 token
/// @author @iainnash / @tbtstl
contract ZoraCreator1155Impl is
IZoraCreator1155,
IZoraCreator1155Initializer,
ContractVersionBase,
ReentrancyGuardUpgradeable,
PublicMulticall,
ERC1155Upgradeable,
UUPSUpgradeable,
CreatorRendererControl,
LegacyNamingControl,
ZoraCreator1155StorageV1,
CreatorPermissionControl,
CreatorRoyaltiesControl,
RewardSplits,
ERC1155RewardsStorageV1,
IERC7572,
ERC1155DelegationStorageV1
{
/// @notice This user role allows for any action to be performed
uint256 public constant PERMISSION_BIT_ADMIN = 2 ** 1;
/// @notice This user role allows for only mint actions to be performed
uint256 public constant PERMISSION_BIT_MINTER = 2 ** 2;
/// @notice This user role allows for only managing sales configurations
uint256 public constant PERMISSION_BIT_SALES = 2 ** 3;
/// @notice This user role allows for only managing metadata configuration
uint256 public constant PERMISSION_BIT_METADATA = 2 ** 4;
/// @notice This user role allows for only withdrawing funds and setting funds withdraw address
uint256 public constant PERMISSION_BIT_FUNDS_MANAGER = 2 ** 5;
/// @notice Factory contract
IUpgradeGate internal immutable upgradeGate;
/// @notice The mint token contract
IZoraMintsMinterManager internal immutable mintsManager;
bytes4 constant ON_ERC1155_RECEIVED_HASH = IERC1155Receiver.onERC1155Received.selector;
bytes4 constant ON_ERC1155_BATCH_RECEIVED_HASH = IERC1155Receiver.onERC1155BatchReceived.selector;
constructor(
address _mintFeeRecipient,
address _upgradeGate,
address _protocolRewards,
address _mints
) RewardSplits(_protocolRewards, _mintFeeRecipient) initializer {
upgradeGate = IUpgradeGate(_upgradeGate);
mintsManager = IZoraMintsMinterManager(_mints);
}
/// @notice Initializes the contract
/// @param contractName the legacy on-chain contract name
/// @param newContractURI The contract URI
/// @param defaultRoyaltyConfiguration The default royalty configuration
/// @param defaultAdmin The default admin to manage the token
/// @param setupActions The setup actions to run, if any
function initialize(
string memory contractName,
string memory newContractURI,
RoyaltyConfiguration memory defaultRoyaltyConfiguration,
address payable defaultAdmin,
bytes[] calldata setupActions
) external nonReentrant initializer {
// We are not initalizing the OZ 1155 implementation
// to save contract storage space and runtime
// since the only thing affected here is the uri.
// __ERC1155_init("");
// Setup uups
__UUPSUpgradeable_init();
// Setup re-entracy guard
__ReentrancyGuard_init();
// Setup contract-default token ID
_setupDefaultToken(defaultAdmin, newContractURI, defaultRoyaltyConfiguration);
// Set owner to default admin
_setOwner(defaultAdmin);
_setFundsRecipient(defaultAdmin);
_setName(contractName);
// Run Setup actions
if (setupActions.length > 0) {
// Temporarily make sender admin
_addPermission(CONTRACT_BASE_ID, msg.sender, PERMISSION_BIT_ADMIN);
// Make calls
multicall(setupActions);
// Remove admin
_removePermission(CONTRACT_BASE_ID, msg.sender, PERMISSION_BIT_ADMIN);
}
}
/// @notice sets up the global configuration for the 1155 contract
/// @param newContractURI The contract URI
/// @param defaultRoyaltyConfiguration The default royalty configuration
function _setupDefaultToken(address defaultAdmin, string memory newContractURI, RoyaltyConfiguration memory defaultRoyaltyConfiguration) internal {
// Add admin permission to default admin to manage contract
_addPermission(CONTRACT_BASE_ID, defaultAdmin, PERMISSION_BIT_ADMIN);
// Mint token ID 0 / don't allow any user mints
_setupNewToken(newContractURI, 0);
// Update default royalties
_updateRoyalties(CONTRACT_BASE_ID, defaultRoyaltyConfiguration);
}
/// @notice Updates the royalty configuration for a token
/// @param tokenId The token ID to update
/// @param newConfiguration The new royalty configuration
function updateRoyaltiesForToken(
uint256 tokenId,
RoyaltyConfiguration memory newConfiguration
) external onlyAdminOrRole(tokenId, PERMISSION_BIT_FUNDS_MANAGER) {
_updateRoyalties(tokenId, newConfiguration);
}
/// @notice remove this function from openzeppelin impl
/// @dev This makes this internal function a no-op
function _setURI(string memory newuri) internal virtual override {}
/// @notice This gets the next token in line to be minted when minting linearly (default behavior) and updates the counter
function _getAndUpdateNextTokenId() internal returns (uint256) {
unchecked {
return nextTokenId++;
}
}
/// @notice Ensure that the next token ID is correct
/// @dev This reverts if the invariant doesn't match. This is used for multil token id assumptions
/// @param lastTokenId The last token ID
function assumeLastTokenIdMatches(uint256 lastTokenId) external view {
unchecked {
if (nextTokenId - 1 != lastTokenId) {
revert TokenIdMismatch(lastTokenId, nextTokenId - 1);
}
}
}
/// @notice Checks if a user either has a role for a token or if they are the admin
/// @dev This is an internal function that is called by the external getter and internal functions
/// @param user The user to check
/// @param tokenId The token ID to check
/// @param role The role to check
/// @return true or false if the permission exists for the user given the token id
function _isAdminOrRole(address user, uint256 tokenId, uint256 role) internal view returns (bool) {
return _hasAnyPermission(tokenId, user, PERMISSION_BIT_ADMIN | role);
}
/// @notice Checks if a user either has a role for a token or if they are the admin
/// @param user The user to check
/// @param tokenId The token ID to check
/// @param role The role to check
/// @return true or false if the permission exists for the user given the token id
function isAdminOrRole(address user, uint256 tokenId, uint256 role) external view returns (bool) {
return _isAdminOrRole(user, tokenId, role);
}
/// @notice Checks if the user is an admin for the given tokenId
/// @dev This function reverts if the permission does not exist for the given user and tokenId
/// @param user user to check
/// @param tokenId tokenId to check
/// @param role role to check for admin
function _requireAdminOrRole(address user, uint256 tokenId, uint256 role) internal view {
if (!(_hasAnyPermission(tokenId, user, PERMISSION_BIT_ADMIN | role) || _hasAnyPermission(CONTRACT_BASE_ID, user, PERMISSION_BIT_ADMIN | role))) {
revert UserMissingRoleForToken(user, tokenId, role);
}
}
/// @notice Checks if the user is an admin
/// @dev This reverts if the user is not an admin for the given token id or contract
/// @param user user to check
/// @param tokenId tokenId to check
function _requireAdmin(address user, uint256 tokenId) internal view {
if (!(_hasAnyPermission(tokenId, user, PERMISSION_BIT_ADMIN) || _hasAnyPermission(CONTRACT_BASE_ID, user, PERMISSION_BIT_ADMIN))) {
revert UserMissingRoleForToken(user, tokenId, PERMISSION_BIT_ADMIN);
}
}
modifier onlyMints() {
if (msg.sender != address(mintsManager.zoraMints1155())) {
revert OnlyTransfersFromZoraMints();
}
_;
}
/// @notice Modifier checking if the user is an admin or has a role
/// @dev This reverts if the msg.sender is not an admin for the given token id or contract
/// @param tokenId tokenId to check
/// @param role role to check
modifier onlyAdminOrRole(uint256 tokenId, uint256 role) {
_requireAdminOrRole(msg.sender, tokenId, role);
_;
}
/// @notice Modifier checking if the user is an admin
/// @dev This reverts if the msg.sender is not an admin for the given token id or contract
/// @param tokenId tokenId to check
modifier onlyAdmin(uint256 tokenId) {
_requireAdmin(msg.sender, tokenId);
_;
}
/// @notice Modifier checking if the requested quantity of tokens can be minted for the tokenId
/// @dev This reverts if the number that can be minted is exceeded
/// @param tokenId token id to check available allowed quantity
/// @param quantity requested to be minted
modifier canMintQuantity(uint256 tokenId, uint256 quantity) {
_requireCanMintQuantity(tokenId, quantity);
_;
}
/// @notice Only from approved address for burn
/// @param from address that the tokens will be burned from, validate that this is msg.sender or that msg.sender is approved
modifier onlyFromApprovedForBurn(address from) {
if (from != msg.sender && !isApprovedForAll(from, msg.sender)) {
revert Burn_NotOwnerOrApproved(msg.sender, from);
}
_;
}
/// @notice Checks if a user can mint a quantity of a token
/// @dev Reverts if the mint exceeds the allowed quantity (or if the token does not exist)
/// @param tokenId The token ID to check
/// @param quantity The quantity of tokens to mint to check
function _requireCanMintQuantity(uint256 tokenId, uint256 quantity) internal view {
TokenData storage tokenInformation = tokens[tokenId];
if (tokenInformation.totalMinted + quantity > tokenInformation.maxSupply) {
revert CannotMintMoreTokens(tokenId, quantity, tokenInformation.totalMinted, tokenInformation.maxSupply);
}
}
/// @notice Set up a new token
/// @param newURI The URI for the token
/// @param maxSupply The maximum supply of the token
function setupNewToken(
string calldata newURI,
uint256 maxSupply
) public onlyAdminOrRole(CONTRACT_BASE_ID, PERMISSION_BIT_MINTER) nonReentrant returns (uint256) {
uint256 tokenId = _setupNewTokenAndPermission(newURI, maxSupply, msg.sender, PERMISSION_BIT_ADMIN);
return tokenId;
}
/// @notice Set up a new token with a create referral
/// @param newURI The URI for the token
/// @param maxSupply The maximum supply of the token
/// @param createReferral The address of the create referral
function setupNewTokenWithCreateReferral(
string calldata newURI,
uint256 maxSupply,
address createReferral
) public onlyAdminOrRole(CONTRACT_BASE_ID, PERMISSION_BIT_MINTER) nonReentrant returns (uint256) {
uint256 tokenId = _setupNewTokenAndPermission(newURI, maxSupply, msg.sender, PERMISSION_BIT_ADMIN);
_setCreateReferral(tokenId, createReferral);
return tokenId;
}
function _setupNewTokenAndPermission(string memory newURI, uint256 maxSupply, address user, uint256 permission) internal returns (uint256) {
uint256 tokenId = _setupNewToken(newURI, maxSupply);
_addPermission(tokenId, user, permission);
if (bytes(newURI).length > 0) {
emit URI(newURI, tokenId);
}
emit SetupNewToken(tokenId, user, newURI, maxSupply);
return tokenId;
}
/// @notice Update the token URI for a token
/// @param tokenId The token ID to update the URI for
/// @param _newURI The new URI
function updateTokenURI(uint256 tokenId, string memory _newURI) external onlyAdminOrRole(tokenId, PERMISSION_BIT_METADATA) {
if (tokenId == CONTRACT_BASE_ID) {
revert();
}
emit URI(_newURI, tokenId);
tokens[tokenId].uri = _newURI;
}
/// @notice Update the global contract metadata
/// @param _newURI The new contract URI
/// @param _newName The new contract name
function updateContractMetadata(string memory _newURI, string memory _newName) external onlyAdminOrRole(0, PERMISSION_BIT_METADATA) {
tokens[CONTRACT_BASE_ID].uri = _newURI;
_setName(_newName);
emit ContractMetadataUpdated(msg.sender, _newURI, _newName);
emit ContractURIUpdated();
}
function _setupNewToken(string memory newURI, uint256 maxSupply) internal returns (uint256 tokenId) {
tokenId = _getAndUpdateNextTokenId();
TokenData memory tokenData = TokenData({uri: newURI, maxSupply: maxSupply, totalMinted: 0});
tokens[tokenId] = tokenData;
emit UpdatedToken(msg.sender, tokenId, tokenData);
}
/// @notice Add a role to a user for a token
/// @param tokenId The token ID to add the role to
/// @param user The user to add the role to
/// @param permissionBits The permission bit to add
function addPermission(uint256 tokenId, address user, uint256 permissionBits) external onlyAdmin(tokenId) {
_addPermission(tokenId, user, permissionBits);
}
/// @notice Remove a role from a user for a token
/// @param tokenId The token ID to remove the role from
/// @param user The user to remove the role from
/// @param permissionBits The permission bit to remove
function removePermission(uint256 tokenId, address user, uint256 permissionBits) external onlyAdmin(tokenId) {
_removePermission(tokenId, user, permissionBits);
// Clear owner field
if (tokenId == CONTRACT_BASE_ID && user == config.owner && !_hasAnyPermission(CONTRACT_BASE_ID, user, PERMISSION_BIT_ADMIN)) {
_setOwner(address(0));
}
}
/// @notice Set the owner of the contract
/// @param newOwner The new owner of the contract
function setOwner(address newOwner) external onlyAdmin(CONTRACT_BASE_ID) {
if (!_hasAnyPermission(CONTRACT_BASE_ID, newOwner, PERMISSION_BIT_ADMIN)) {
revert NewOwnerNeedsToBeAdmin();
}
// Update owner field
_setOwner(newOwner);
}
/// @notice Getter for the owner singleton of the contract for outside interfaces
/// @return the owner of the contract singleton for compat.
function owner() external view returns (address) {
return config.owner;
}
/// @notice Mint a token to a user as the admin or minter
/// @param recipient The recipient of the token
/// @param tokenId The token ID to mint
/// @param quantity The quantity of tokens to mint
/// @param data The data to pass to the onERC1155Received function
function adminMint(
address recipient,
uint256 tokenId,
uint256 quantity,
bytes memory data
) external nonReentrant onlyAdminOrRole(tokenId, PERMISSION_BIT_MINTER) {
// Mint the specified tokens
_mint(recipient, tokenId, quantity, data);
}
/// @custom:deprecated mintWithRewards has been deprecated use mint instead
/// @notice Mint tokens and payout rewards given a minter contract, minter arguments, and a mint referral
/// @param minter The minter contract to use
/// @param tokenId The token ID to mint
/// @param quantity The quantity of tokens to mint
/// @param minterArguments The arguments to pass to the minter
/// @param mintReferral The referrer of the mint
function mintWithRewards(
IMinter1155 minter,
uint256 tokenId,
uint256 quantity,
bytes calldata minterArguments,
address mintReferral
) external payable nonReentrant {
address[] memory rewardsRecipients = new address[](1);
rewardsRecipients[0] = mintReferral;
return _mint(minter, tokenId, quantity, rewardsRecipients, minterArguments);
}
/// @notice Mint tokens and payout rewards given a minter contract, minter arguments, and rewards arguments
/// @param minter The minter contract to use
/// @param tokenId The token ID to mint
/// @param quantity The quantity of tokens to mint
/// @param rewardsRecipients The addresses of rewards arguments - rewardsRecipients[0] = mintReferral, rewardsRecipients[1] = platformReferral
/// @param minterArguments The arguments to pass to the minter
function mint(
IMinter1155 minter,
uint256 tokenId,
uint256 quantity,
address[] calldata rewardsRecipients,
bytes calldata minterArguments
) external payable nonReentrant {
_mint(minter, tokenId, quantity, rewardsRecipients, minterArguments);
}
function _mintAndHandleRewards(
IMinter1155 minter,
address[] memory rewardsRecipients,
uint256 valueSent,
uint256 totalReward,
uint256 tokenId,
uint256 quantity,
bytes calldata minterArguments
) private {
uint256 ethValueSent = _handleRewardsAndGetValueRemaining(valueSent, totalReward, tokenId, rewardsRecipients);
_executeCommands(minter.requestMint(msg.sender, tokenId, quantity, ethValueSent, minterArguments).commands, ethValueSent, tokenId);
emit Purchased(msg.sender, address(minter), tokenId, quantity, valueSent);
}
function _handleRewardsAndGetValueRemaining(
uint256 totalSentValue,
uint256 totalReward,
uint256 tokenId,
address[] memory rewardsRecipients
) internal returns (uint256 valueRemaining) {
// 1. Get rewards recipients
// create referral is pulled from storage, if it's not set, defaults to zora reward recipient
address createReferral = createReferrals[tokenId];
if (createReferral == address(0)) {
createReferral = zoraRewardRecipient;
}
// mint referral is passed in arguments to minting functions; if it's not set, defaults to zora reward recipient
address mintReferral = rewardsRecipients.length > 0 ? rewardsRecipients[0] : zoraRewardRecipient;
if (mintReferral == address(0)) {
mintReferral = zoraRewardRecipient;
}
// creator reward recipient is pulled from storage, if it's not set, defaults to zora reward recipient
address creatorRewardRecipient = getCreatorRewardRecipient(tokenId);
if (creatorRewardRecipient == address(0)) {
creatorRewardRecipient = zoraRewardRecipient;
}
// first minter is pulled from storage, if it's not set, defaults to creator reward recipient (which is zora if there is no creator reward recipient set)
address firstMinter = firstMinters[tokenId];
if (firstMinter == address(0)) {
firstMinter = creatorRewardRecipient;
}
// 2. Get rewards amounts - which varies if its a paid or free mint
RewardsSettings memory settings;
if (totalSentValue < totalReward) {
revert INVALID_ETH_AMOUNT();
// if value sent is the same as the reward amount, we assume its a free mint
} else if (totalSentValue == totalReward) {
settings = RewardSplitsLib.getRewards(false, totalReward);
// otherwise, we assume its a paid mint
} else {
settings = RewardSplitsLib.getRewards(true, totalReward);
unchecked {
valueRemaining = totalSentValue - totalReward;
}
}
// 3. Deposit rewards rewards
protocolRewards.depositRewards{value: totalReward}(
// if there was no creator reward amount, 0 out that address
settings.creatorReward == 0 ? address(0) : creatorRewardRecipient,
settings.creatorReward,
createReferral,
settings.createReferralReward,
mintReferral,
settings.mintReferralReward,
firstMinter,
settings.firstMinterReward,
zoraRewardRecipient,
settings.zoraReward
);
}
function _mint(IMinter1155 minter, uint256 tokenId, uint256 quantity, address[] memory rewardsRecipients, bytes calldata minterArguments) private {
// Require admin from the minter to mint
_requireAdminOrRole(address(minter), tokenId, PERMISSION_BIT_MINTER);
uint256 totalReward = mintsManager.getEthPrice() * quantity;
_mintAndHandleRewards(minter, rewardsRecipients, msg.value, totalReward, tokenId, quantity, minterArguments);
}
function _getTotalMintsQuantity(uint256[] calldata mintTokenIds, uint256[] calldata quantities) private pure returns (uint256 totalQuantity) {
if (mintTokenIds.length != quantities.length) {
revert Mint_InvalidMintArrayLength();
}
for (uint256 i = 0; i < mintTokenIds.length; i++) {
totalQuantity += quantities[i];
}
}
/// @notice Mint tokens and payout rewards given a minter contract, minter arguments, and rewards arguments,
/// while MINTs are redeemed to pay for the mint fee, instead of paying with ETH directly.
/// The MINTs must have been approved to be transferred to this contract from the msg.sender before calling this function.
/// Value sent is used for paid mints, if this is a paid mint
/// @param mintTokenIds The MINT token IDs that are to be redeemed
/// @param quantities The quantities of each MINT token id to redeem
/// @param minter The minter contract to use
/// @param tokenId The token ID to mint
/// @param rewardsRecipients The addresses of rewards arguments - rewardsRecipients[0] = mintReferral, rewardsRecipients[1] = platformReferral
/// @param minterArguments The arguments to pass to the minter
/// @param quantityMinted The total quantity of tokens minted
function mintWithMints(
uint256[] calldata mintTokenIds,
uint256[] calldata quantities,
IMinter1155 minter,
uint256 tokenId,
address[] memory rewardsRecipients,
bytes calldata minterArguments
) external payable returns (uint256 quantityMinted) {
// Require admin from the minter to mint
_requireAdminOrRole(address(minter), tokenId, PERMISSION_BIT_MINTER);
quantityMinted = _getTotalMintsQuantity(mintTokenIds, quantities);
IZoraMints1155 mints = mintsManager.zoraMints1155();
mints.safeBatchTransferFrom(msg.sender, address(this), mintTokenIds, quantities, "");
Redemption[] memory redemptions = mints.redeemBatch(mintTokenIds, quantities, address(this));
uint256 totalEthReward = _getEthRedemptionTotal(redemptions);
_mintAndHandleRewards(minter, rewardsRecipients, msg.value + totalEthReward, totalEthReward, tokenId, quantityMinted, minterArguments);
}
function _getEthRedemptionTotal(Redemption[] memory redemptions) private pure returns (uint256 totalEth) {
for (uint256 i = 0; i < redemptions.length; i++) {
if (redemptions[i].tokenAddress != address(0)) {
revert NonEthRedemption();
}
totalEth += redemptions[i].valueRedeemed;
}
}
function mintFee() external view returns (uint256) {
return mintsManager.getEthPrice();
}
/// @notice Get the creator reward recipient address for a specific token.
/// @param tokenId The token id to get the creator reward recipient for
/// @dev Returns the royalty recipient address for the token if set; otherwise uses the fundsRecipient.
/// If both are not set, this contract will be set as the recipient, and an account with
/// `PERMISSION_BIT_FUNDS_MANAGER` will be able to withdraw via the `withdrawFor` function.
function getCreatorRewardRecipient(uint256 tokenId) public view returns (address) {
address royaltyRecipient = getRoyalties(tokenId).royaltyRecipient;
if (royaltyRecipient != address(0)) {
return royaltyRecipient;
}
if (config.fundsRecipient != address(0)) {
return config.fundsRecipient;
}
return address(this);
}
/// @notice Set a metadata renderer for a token
/// @param tokenId The token ID to set the renderer for
/// @param renderer The renderer to set
function setTokenMetadataRenderer(uint256 tokenId, IRenderer1155 renderer) external nonReentrant onlyAdminOrRole(tokenId, PERMISSION_BIT_METADATA) {
_setRenderer(tokenId, renderer);
if (tokenId == 0) {
emit ContractRendererUpdated(renderer);
} else {
// We don't know the uri from the renderer but can emit a notification to the indexer here
emit URI("", tokenId);
}
}
/// Execute Minter Commands ///
/// @notice Internal functions to execute commands returned by the minter
/// @param commands list of command structs
/// @param ethValueSent the ethereum value sent in the mint transaction into the contract
/// @param tokenId the token id the user requested to mint (0 if the token id is set by the minter itself across the whole contract)
function _executeCommands(ICreatorCommands.Command[] memory commands, uint256 ethValueSent, uint256 tokenId) internal {
for (uint256 i = 0; i < commands.length; ++i) {
ICreatorCommands.CreatorActions method = commands[i].method;
if (method == ICreatorCommands.CreatorActions.SEND_ETH) {
(address recipient, uint256 amount) = abi.decode(commands[i].args, (address, uint256));
if (ethValueSent > amount) {
revert Mint_InsolventSaleTransfer();
}
if (!TransferHelperUtils.safeSendETH(recipient, amount, TransferHelperUtils.FUNDS_SEND_NORMAL_GAS_LIMIT)) {
revert Mint_ValueTransferFail();
}
} else if (method == ICreatorCommands.CreatorActions.MINT) {
(address recipient, uint256 mintTokenId, uint256 quantity) = abi.decode(commands[i].args, (address, uint256, uint256));
if (tokenId != 0 && mintTokenId != tokenId) {
revert Mint_TokenIDMintNotAllowed();
}
_mint(recipient, tokenId, quantity, "");
} else {
// no-op
}
}
}
/// @notice Token info getter
/// @param tokenId token id to get info for
/// @return TokenData struct returned
function getTokenInfo(uint256 tokenId) external view returns (TokenData memory) {
return tokens[tokenId];
}
/// @notice Proxy setter for sale contracts (only callable by SALES permission or admin)
/// @param tokenId The token ID to call the sale contract with
/// @param salesConfig The sales config contract to call
/// @param data The data to pass to the sales config contract
function callSale(uint256 tokenId, IMinter1155 salesConfig, bytes calldata data) external onlyAdminOrRole(tokenId, PERMISSION_BIT_SALES) {
_requireAdminOrRole(address(salesConfig), tokenId, PERMISSION_BIT_MINTER);
if (!salesConfig.supportsInterface(type(IMinter1155).interfaceId)) {
revert Sale_CannotCallNonSalesContract(address(salesConfig));
}
// Get the token id encoded in the calldata for the sales config
// Assume that it is the first 32 bytes following the function selector
uint256 encodedTokenId = uint256(bytes32(data[4:36]));
// Ensure the encoded token id matches the passed token id
if (encodedTokenId != tokenId) {
revert IZoraCreator1155Errors.Call_TokenIdMismatch();
}
(bool success, bytes memory why) = address(salesConfig).call(data);
if (!success) {
revert CallFailed(why);
}
}
/// @notice Proxy setter for renderer contracts (only callable by METADATA permission or admin)
/// @param tokenId The token ID to call the renderer contract with
/// @param data The data to pass to the renderer contract
function callRenderer(uint256 tokenId, bytes memory data) external onlyAdminOrRole(tokenId, PERMISSION_BIT_METADATA) {
// We assume any renderers set are checked for EIP165 signature during write stage.
(bool success, bytes memory why) = address(getCustomRenderer(tokenId)).call(data);
if (!success) {
revert CallFailed(why);
}
}
/// @notice Returns true if the contract implements the interface defined by interfaceId
/// @param interfaceId The interface to check for
/// @return if the interfaceId is marked as supported
function supportsInterface(
bytes4 interfaceId
) public view virtual override(CreatorRoyaltiesControl, ERC1155Upgradeable, IERC165Upgradeable) returns (bool) {
return
super.supportsInterface(interfaceId) ||
interfaceId == type(IZoraCreator1155).interfaceId ||
ERC1155Upgradeable.supportsInterface(interfaceId) ||
interfaceId == type(IHasSupportedPremintSignatureVersions).interfaceId ||
interfaceId == type(ISupportsAABasedDelegatedTokenCreation).interfaceId ||
interfaceId == type(IMintWithRewardsRecipients).interfaceId ||
interfaceId == type(IMintWithMints).interfaceId;
}
/// Generic 1155 function overrides ///
/// @notice Mint function that 1) checks quantity 2) keeps track of allowed tokens
/// @param to to mint to
/// @param id token id to mint
/// @param amount of tokens to mint
/// @param data as specified by 1155 standard
function _mint(address to, uint256 id, uint256 amount, bytes memory data) internal virtual override {
_requireCanMintQuantity(id, amount);
tokens[id].totalMinted += amount;
super._mint(to, id, amount, data);
}
/// @notice Burns a batch of tokens
/// @dev Only the current owner is allowed to burn
/// @param from the user to burn from
/// @param tokenIds The token ID to burn
/// @param amounts The amount of tokens to burn
function burnBatch(address from, uint256[] calldata tokenIds, uint256[] calldata amounts) external {
if (from != msg.sender && !isApprovedForAll(from, msg.sender)) {
revert Burn_NotOwnerOrApproved(msg.sender, from);
}
_burnBatch(from, tokenIds, amounts);
}
function setTransferHook(ITransferHookReceiver transferHook) external onlyAdmin(CONTRACT_BASE_ID) {
if (address(transferHook) != address(0)) {
if (!transferHook.supportsInterface(type(ITransferHookReceiver).interfaceId)) {
revert Config_TransferHookNotSupported(address(transferHook));
}
}
config.transferHook = transferHook;
emit ConfigUpdated(msg.sender, ConfigUpdate.TRANSFER_HOOK, config);
}
/// @notice Hook before token transfer that checks for a transfer hook integration
/// @param operator operator moving the tokens
/// @param from from address
/// @param to to address
/// @param id token id to move
/// @param amount amount of token
/// @param data data of token
function _beforeTokenTransfer(address operator, address from, address to, uint256 id, uint256 amount, bytes memory data) internal override {
super._beforeTokenTransfer(operator, from, to, id, amount, data);
if (address(config.transferHook) != address(0)) {
config.transferHook.onTokenTransfer(address(this), operator, from, to, id, amount, data);
}
}
/// @notice Hook before token transfer that checks for a transfer hook integration
/// @param operator operator moving the tokens
/// @param from from address
/// @param to to address
/// @param ids token ids to move
/// @param amounts amounts of tokens
/// @param data data of tokens
function _beforeBatchTokenTransfer(
address operator,
address from,
address to,
uint256[] memory ids,
uint256[] memory amounts,
bytes memory data
) internal override {
super._beforeBatchTokenTransfer(operator, from, to, ids, amounts, data);
if (address(config.transferHook) != address(0)) {
config.transferHook.onTokenTransferBatch({target: address(this), operator: operator, from: from, to: to, ids: ids, amounts: amounts, data: data});
}
}
/// @notice Returns the URI for the contract
function contractURI() external view override(IERC7572, IZoraCreator1155) returns (string memory) {
IRenderer1155 customRenderer = getCustomRenderer(CONTRACT_BASE_ID);
if (address(customRenderer) != address(0)) {
return customRenderer.contractURI();
}
return uri(0);
}
/// @notice Returns the URI for a token
/// @param tokenId The token ID to return the URI for
function uri(uint256 tokenId) public view override(ERC1155Upgradeable, IERC1155MetadataURIUpgradeable) returns (string memory) {
if (bytes(tokens[tokenId].uri).length > 0) {
return tokens[tokenId].uri;
}
return _render(tokenId);
}
/// @notice Internal setter for contract admin with no access checks
/// @param newOwner new owner address
function _setOwner(address newOwner) internal {
address lastOwner = config.owner;
config.owner = newOwner;
emit OwnershipTransferred(lastOwner, newOwner);
emit ConfigUpdated(msg.sender, ConfigUpdate.OWNER, config);
}
/// @notice Set funds recipient address
/// @param fundsRecipient new funds recipient address
function setFundsRecipient(address payable fundsRecipient) external onlyAdminOrRole(CONTRACT_BASE_ID, PERMISSION_BIT_FUNDS_MANAGER) {
_setFundsRecipient(fundsRecipient);
}
/// @notice Internal no-checks set funds recipient address
/// @param fundsRecipient new funds recipient address
function _setFundsRecipient(address payable fundsRecipient) internal {
config.fundsRecipient = fundsRecipient;
emit ConfigUpdated(msg.sender, ConfigUpdate.FUNDS_RECIPIENT, config);
}
/// @notice Allows the create referral to update the address that can claim their rewards
function updateCreateReferral(uint256 tokenId, address recipient) external {
if (msg.sender != createReferrals[tokenId]) revert ONLY_CREATE_REFERRAL();
_setCreateReferral(tokenId, recipient);
}
function _setCreateReferral(uint256 tokenId, address recipient) internal {
createReferrals[tokenId] = recipient;
}
/// @notice Withdraws all ETH from the contract to the funds recipient address
function withdraw() public onlyAdminOrRole(CONTRACT_BASE_ID, PERMISSION_BIT_FUNDS_MANAGER) {
uint256 contractValue = address(this).balance;
if (!TransferHelperUtils.safeSendETH(config.fundsRecipient, contractValue, TransferHelperUtils.FUNDS_SEND_NORMAL_GAS_LIMIT)) {
revert ETHWithdrawFailed(config.fundsRecipient, contractValue);
}
}
receive() external payable {}
/// ///
/// MANAGER UPGRADE ///
/// ///
/// @notice Ensures the caller is authorized to upgrade the contract
/// @dev This function is called in `upgradeTo` & `upgradeToAndCall`
/// @param _newImpl The new implementation address
function _authorizeUpgrade(address _newImpl) internal view override onlyAdmin(CONTRACT_BASE_ID) {
if (!upgradeGate.isRegisteredUpgradePath(_getImplementation(), _newImpl)) {
revert();
}
}
/// @notice Returns the current implementation address
function implementation() external view returns (address) {
return _getImplementation();
}
function supportedPremintSignatureVersions() external pure returns (string[] memory) {
return DelegatedTokenCreation.supportedPremintSignatureVersions();
}
/// Sets up a new token using a token configuration and a signature created for the token creation parameters.
/// The signature must be created by an account with the PERMISSION_BIT_MINTER role on the contract.
/// @param premintConfig abi encoded configuration of token to be created
/// @param premintVersion version of the premint configuration
/// @param signature EIP-712 Signature created on the premintConfig by an account with the PERMISSION_BIT_MINTER role on the contract.
/// @param firstMinter original sender of the transaction, used to set the firstMinter
/// @param premintSignerContract if an EIP-1271 based premint, the contract that signed the premint
function delegateSetupNewToken(
bytes memory premintConfig,
bytes32 premintVersion,
bytes calldata signature,
address firstMinter,
address premintSignerContract
) external nonReentrant returns (uint256 newTokenId) {
if (firstMinter == address(0)) {
revert FirstMinterAddressZero();
}
(DelegatedTokenSetup memory params, DecodedCreatorAttribution memory attribution, bytes[] memory tokenSetupActions) = DelegatedTokenCreation
.decodeAndRecoverDelegatedTokenSetup(premintConfig, premintVersion, signature, address(this), nextTokenId, premintSignerContract);
// if a token has already been created for a premint config with this uid:
if (delegatedTokenId[params.uid] != 0) {
// return its token id
return delegatedTokenId[params.uid];
}
// this is what attributes this token to have been created by the original creator
emit CreatorAttribution(attribution.structHash, attribution.domainName, attribution.version, attribution.creator, attribution.signature);
return _delegateSetupNewToken(params, attribution.creator, tokenSetupActions, firstMinter);
}
function _delegateSetupNewToken(
DelegatedTokenSetup memory params,
address creator,
bytes[] memory tokenSetupActions,
address sender
) internal returns (uint256 newTokenId) {
// require that the signer can create new tokens (is a valid creator)
_requireAdminOrRole(creator, CONTRACT_BASE_ID, PERMISSION_BIT_MINTER);
// create the new token; msg sender will have PERMISSION_BIT_ADMIN on the new token
newTokenId = _setupNewTokenAndPermission(params.tokenURI, params.maxSupply, msg.sender, PERMISSION_BIT_ADMIN);
_setCreateReferral(newTokenId, params.createReferral);
delegatedTokenId[params.uid] = newTokenId;
firstMinters[newTokenId] = sender;
// then invoke them, calling account should be original msg.sender, which has admin on the new token
_multicallInternal(tokenSetupActions);
// remove the token creator as admin of the newly created token:
_removePermission(newTokenId, msg.sender, PERMISSION_BIT_ADMIN);
// grant the token creator as admin of the newly created token
_addPermission(newTokenId, creator, PERMISSION_BIT_ADMIN);
}
// /// Allows receiving ERC1155 tokens
function onERC1155Received(address, address, uint256, uint256, bytes calldata) external view onlyMints returns (bytes4) {
return ON_ERC1155_RECEIVED_HASH;
}
function onERC1155BatchReceived(address, address, uint256[] calldata, uint256[] calldata, bytes calldata) external view onlyMints returns (bytes4) {
return ON_ERC1155_BATCH_RECEIVED_HASH;
}
}