forked from limitbreakinc/creator-token-standards
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathCreatorTokenTransferValidator.sol
2114 lines (1907 loc) · 98.7 KB
/
CreatorTokenTransferValidator.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
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;
import "../Constants.sol";
import "../interfaces/IEOARegistry.sol";
import "../interfaces/ITransferValidator.sol";
import "./TransferPolicy.sol";
import {CreatorTokenTransferValidatorConfiguration} from "./CreatorTokenTransferValidatorConfiguration.sol";
import "@limitbreak/permit-c/PermitC.sol";
import "@openzeppelin/contracts/utils/introspection/ERC165.sol";
import "@openzeppelin/contracts/utils/structs/EnumerableSet.sol";
import "@opensea/tstorish/Tstorish.sol";
/**
* @title CreatorTokenTransferValidator
* @author Limit Break, Inc.
* @notice The CreatorTokenTransferValidator contract is designed to provide a customizable and secure transfer
* validation mechanism for NFT collections. This contract allows the owner of an NFT collection to configure
* the transfer security level, blacklisted accounts and codehashes, whitelisted accounts and codehashes, and
* authorized accounts and codehashes for each collection.
*
* @dev <h4>Features</h4>
* - Transfer security levels: Provides different levels of transfer security,
* from open transfers to completely restricted transfers.
* - Blacklist: Allows the owner of a collection to blacklist specific operator addresses or codehashes
* from executing transfers on behalf of others.
* - Whitelist: Allows the owner of a collection to whitelist specific operator addresses or codehashes
* permitted to execute transfers on behalf of others or send/receive tokens when otherwise disabled by
* security policy.
* - Authorizers: Allows the owner of a collection to enable authorizer contracts, that can perform
* authorization-based filtering of transfers.
*
* @dev <h4>Benefits</h4>
* - Enhanced security: Allows creators to have more control over their NFT collections, ensuring the safety
* and integrity of their assets.
* - Flexibility: Provides collection owners the ability to customize transfer rules as per their requirements.
* - Compliance: Facilitates compliance with regulations by enabling creators to restrict transfers based on
* specific criteria.
*
* @dev <h4>Intended Usage</h4>
* - The CreatorTokenTransferValidatorV3 contract is intended to be used by NFT collection owners to manage and
* enforce transfer policies. This contract is integrated with the following varations of creator token
* NFT contracts to validate transfers according to the defined security policies.
*
* - ERC721-C: Creator token implenting OpenZeppelin's ERC-721 standard.
* - ERC721-AC: Creator token implenting Azuki's ERC-721A standard.
* - ERC721-CW: Creator token implementing OpenZeppelin's ERC-721 standard with opt-in staking to
* wrap/upgrade a pre-existing ERC-721 collection.
* - ERC721-ACW: Creator token implementing Azuki's ERC721-A standard with opt-in staking to
* wrap/upgrade a pre-existing ERC-721 collection.
* - ERC1155-C: Creator token implenting OpenZeppelin's ERC-1155 standard.
* - ERC1155-CW: Creator token implementing OpenZeppelin's ERC-1155 standard with opt-in staking to
* wrap/upgrade a pre-existing ERC-1155 collection.
*
* <h4>Transfer Security Levels</h4>
* - Recommended: Recommended defaults are same as Level 3 (Whitelisting with OTC Enabled).
* - Caller Constraints: OperatorWhitelistEnableOTC
* - Receiver Constraints: None
* - Level 1: No transfer restrictions.
* - Caller Constraints: None
* - Receiver Constraints: None
* - Level 2: Only non-blacklisted operators can initiate transfers, over-the-counter (OTC) trading enabled.
* - Caller Constraints: OperatorBlacklistEnableOTC
* - Receiver Constraints: None
* - Level 3: Only whitelisted accounts can initiate transfers, over-the-counter (OTC) trading enabled.
* - Caller Constraints: OperatorWhitelistEnableOTC
* - Receiver Constraints: None
* - Level 4: Only whitelisted accounts can initiate transfers, over-the-counter (OTC) trading disabled.
* - Caller Constraints: OperatorWhitelistDisableOTC
* - Receiver Constraints: None
* - Level 5: Only whitelisted accounts can initiate transfers, over-the-counter (OTC) trading enabled.
* Transfers to contracts with code are not allowed, unless present on the whitelist.
* - Caller Constraints: OperatorWhitelistEnableOTC
* - Receiver Constraints: NoCode
* - Level 6: Only whitelisted accounts can initiate transfers, over-the-counter (OTC) trading enabled.
* Transfers are allowed only to Externally Owned Accounts (EOAs), unless present on the whitelist.
* - Caller Constraints: OperatorWhitelistEnableOTC
* - Receiver Constraints: EOA
* - Level 7: Only whitelisted accounts can initiate transfers, over-the-counter (OTC) trading disabled.
* Transfers to contracts with code are not allowed, unless present on the whitelist.
* - Caller Constraints: OperatorWhitelistDisableOTC
* - Receiver Constraints: NoCode
* - Level 8: Only whitelisted accounts can initiate transfers, over-the-counter (OTC) trading disabled.
* Transfers are allowed only to Externally Owned Accounts (EOAs), unless present on the whitelist.
* - Caller Constraints: OperatorWhitelistDisableOTC
* - Receiver Constraints: EOA
*/
contract CreatorTokenTransferValidator is IEOARegistry, ITransferValidator, ERC165, Tstorish, PermitC {
using EnumerableSet for EnumerableSet.AddressSet;
using EnumerableSet for EnumerableSet.Bytes32Set;
/*************************************************************************/
/* CUSTOM ERRORS */
/*************************************************************************/
/// @dev Thrown when attempting to set a list id that does not exist.
error CreatorTokenTransferValidator__ListDoesNotExist();
/// @dev Thrown when attempting to transfer the ownership of a list to the zero address.
error CreatorTokenTransferValidator__ListOwnershipCannotBeTransferredToZeroAddress();
/// @dev Thrown when attempting to call a function that requires the caller to be the list owner.
error CreatorTokenTransferValidator__CallerDoesNotOwnList();
/// @dev Thrown when validating a transfer for a collection using whitelists and the operator is not on the whitelist.
error CreatorTokenTransferValidator__CallerMustBeWhitelisted();
/// @dev Thrown when authorizing a transfer for a collection using authorizers and the msg.sender is not in the authorizer list.
error CreatorTokenTransferValidator__CallerMustBeAnAuthorizer();
/// @dev Thrown when attempting to call a function that requires owner or default admin role for a collection that the caller does not have.
error CreatorTokenTransferValidator__CallerMustHaveElevatedPermissionsForSpecifiedNFT();
/// @dev Thrown when constructor args are not valid
error CreatorTokenTransferValidator__InvalidConstructorArgs();
/// @dev Thrown when setting the transfer security level to an invalid value.
error CreatorTokenTransferValidator__InvalidTransferSecurityLevel();
/// @dev Thrown when validating a transfer for a collection using blacklists and the operator is on the blacklist.
error CreatorTokenTransferValidator__OperatorIsBlacklisted();
/// @dev Thrown when validating a transfer for a collection that does not allow receiver to have code and the receiver has code.
error CreatorTokenTransferValidator__ReceiverMustNotHaveDeployedCode();
/// @dev Thrown when validating a transfer for a collection that requires receivers be verified EOAs and the receiver is not verified.
error CreatorTokenTransferValidator__ReceiverProofOfEOASignatureUnverified();
/// @dev Thrown when a frozen account is the receiver of a transfer
error CreatorTokenTransferValidator__ReceiverAccountIsFrozen();
/// @dev Thrown when a frozen account is the sender of a transfer
error CreatorTokenTransferValidator__SenderAccountIsFrozen();
/// @dev Thrown when validating a transfer for a collection that is in soulbound token mode.
error CreatorTokenTransferValidator__TokenIsSoulbound();
/// @dev Thrown when an authorizer attempts to set a wildcard authorized operator on collections that don't allow wildcards
error CreatorTokenTransferValidator__WildcardOperatorsCannotBeAuthorizedForCollection();
/// @dev Thrown when attempting to set a authorized operator when authorization mode is disabled.
error CreatorTokenTransferValidator__AuthorizationDisabledForCollection();
/// @dev Thrown when attempting to validate a permitted transfer where the permit type does not match the collection-defined token type.
error CreatorTokenTransferValidator__TokenTypesDoNotMatch();
/*************************************************************************/
/* EVENTS */
/*************************************************************************/
/// @dev Emitted when a new list is created.
event CreatedList(uint256 indexed id, string name);
/// @dev Emitted when a list is applied to a collection.
event AppliedListToCollection(address indexed collection, uint120 indexed id);
/// @dev Emitted when the ownership of a list is transferred to a new owner.
event ReassignedListOwnership(uint256 indexed id, address indexed newOwner);
/// @dev Emitted when an account is added to the list of frozen accounts for a collection.
event AccountFrozenForCollection(address indexed collection, address indexed account);
/// @dev Emitted when an account is removed from the list of frozen accounts for a collection.
event AccountUnfrozenForCollection(address indexed collection, address indexed account);
/// @dev Emitted when an address is added to a list.
event AddedAccountToList(uint8 indexed kind, uint256 indexed id, address indexed account);
/// @dev Emitted when a codehash is added to a list.
event AddedCodeHashToList(uint8 indexed kind, uint256 indexed id, bytes32 indexed codehash);
/// @dev Emitted when an address is removed from a list.
event RemovedAccountFromList(uint8 indexed kind, uint256 indexed id, address indexed account);
/// @dev Emitted when a codehash is removed from a list.
event RemovedCodeHashFromList(uint8 indexed kind, uint256 indexed id, bytes32 indexed codehash);
/// @dev Emitted when the security level for a collection is updated.
event SetTransferSecurityLevel(address indexed collection, uint8 level);
/// @dev Emitted when a collection updates its authorization mode.
event SetAuthorizationModeEnabled(address indexed collection, bool disabled, bool authorizersCannotSetWildcardOperators);
/// @dev Emitted when a collection turns account freezing on or off.
event SetAccountFreezingModeEnabled(address indexed collection, bool enabled);
/// @dev Emitted when a collection's token type is updated.
event SetTokenType(address indexed collection, uint16 tokenType);
/*************************************************************************/
/* STRUCTS */
/*************************************************************************/
/**
* @dev This struct is internally for the storage of account and codehash lists.
*/
struct List {
EnumerableSet.AddressSet enumerableAccounts;
EnumerableSet.Bytes32Set enumerableCodehashes;
mapping (address => bool) nonEnumerableAccounts;
mapping (bytes32 => bool) nonEnumerableCodehashes;
}
/**
* @dev This struct is internally for the storage of account lists.
*/
struct AccountList {
EnumerableSet.AddressSet enumerableAccounts;
mapping (address => bool) nonEnumerableAccounts;
}
/*************************************************************************/
/* CONSTANTS */
/*************************************************************************/
/// @dev Immutable lookup table for constant gas determination of caller constraints by security level.
/// @dev Created during contract construction using defined constants.
uint256 private immutable _callerConstraintsLookup;
/// @dev Immutable lookup table for constant gas determination of receiver constraints by security level.
/// @dev Created during contract construction using defined constants.
uint256 private immutable _receiverConstraintsLookup;
/// @dev The address of the EOA Registry to use to validate an account is a verified EOA.
address private immutable _eoaRegistry;
/// @dev The legacy Creator Token Transfer Validator Interface
bytes4 private constant LEGACY_TRANSFER_VALIDATOR_INTERFACE_ID = 0x00000000;
/// @dev The default admin role value for contracts that implement access control.
bytes32 private constant DEFAULT_ACCESS_CONTROL_ADMIN_ROLE = 0x00;
/// @dev Value representing a zero value code hash.
bytes32 private constant BYTES32_ZERO = 0x0000000000000000000000000000000000000000000000000000000000000000;
address private constant WILDCARD_OPERATOR_ADDRESS = address(0x01);
uint16 private constant DEFAULT_TOKEN_TYPE = 0;
/*************************************************************************/
/* STORAGE */
/*************************************************************************/
/// @notice Keeps track of the most recently created list id.
uint120 public lastListId;
/// @notice Mapping of list ids to list owners
mapping (uint120 => address) public listOwners;
/// @dev Mapping of collection addresses to their security policy settings
mapping (address => CollectionSecurityPolicyV3) internal collectionSecurityPolicies;
/// @dev Mapping of list ids to blacklist settings
mapping (uint120 => List) internal blacklists;
/// @dev Mapping of list ids to whitelist settings
mapping (uint120 => List) internal whitelists;
/// @dev Mapping of list ids to authorizers
mapping (uint120 => List) internal authorizers;
/// @dev Mapping of collections to accounts that are frozen for those collections
mapping (address => AccountList) internal frozenAccounts;
constructor(
address defaultOwner,
address eoaRegistry_,
string memory name,
string memory version,
address validatorConfiguration
)
Tstorish()
PermitC(
name,
version,
defaultOwner,
CreatorTokenTransferValidatorConfiguration(validatorConfiguration).getNativeValueToCheckPauseState()
) {
if (defaultOwner == address(0) || eoaRegistry_ == address(0)) {
revert CreatorTokenTransferValidator__InvalidConstructorArgs();
}
_createDefaultList(defaultOwner);
_eoaRegistry = eoaRegistry_;
_callerConstraintsLookup = _constructCallerConstraintsTable();
_receiverConstraintsLookup = _constructReceiverConstraintsTable();
}
/**
* @dev This function is only called during contract construction to create the default list.
*/
function _createDefaultList(address defaultOwner) internal {
uint120 id = 0;
listOwners[id] = defaultOwner;
emit CreatedList(id, "DEFAULT LIST");
emit ReassignedListOwnership(id, defaultOwner);
}
/**
* @dev This function is only called during contract construction to create the caller constraints
* @dev lookup table.
*/
function _constructCallerConstraintsTable() internal pure returns (uint256) {
return
(CALLER_CONSTRAINTS_OPERATOR_WHITELIST_ENABLE_OTC << (TRANSFER_SECURITY_LEVEL_RECOMMENDED << 3))
| (CALLER_CONSTRAINTS_NONE << (TRANSFER_SECURITY_LEVEL_ONE << 3))
| (CALLER_CONSTRAINTS_OPERATOR_BLACKLIST_ENABLE_OTC << (TRANSFER_SECURITY_LEVEL_TWO << 3))
| (CALLER_CONSTRAINTS_OPERATOR_WHITELIST_ENABLE_OTC << (TRANSFER_SECURITY_LEVEL_THREE << 3))
| (CALLER_CONSTRAINTS_OPERATOR_WHITELIST_DISABLE_OTC << (TRANSFER_SECURITY_LEVEL_FOUR << 3))
| (CALLER_CONSTRAINTS_OPERATOR_WHITELIST_ENABLE_OTC << (TRANSFER_SECURITY_LEVEL_FIVE << 3))
| (CALLER_CONSTRAINTS_OPERATOR_WHITELIST_ENABLE_OTC << (TRANSFER_SECURITY_LEVEL_SIX << 3))
| (CALLER_CONSTRAINTS_OPERATOR_WHITELIST_DISABLE_OTC << (TRANSFER_SECURITY_LEVEL_SEVEN << 3))
| (CALLER_CONSTRAINTS_OPERATOR_WHITELIST_DISABLE_OTC << (TRANSFER_SECURITY_LEVEL_EIGHT << 3))
| (CALLER_CONSTRAINTS_SBT << (TRANSFER_SECURITY_LEVEL_NINE << 3));
}
/**
* @dev This function is only called during contract construction to create the receiver constraints
* @dev lookup table.
*/
function _constructReceiverConstraintsTable() internal pure returns (uint256) {
return
(RECEIVER_CONSTRAINTS_NONE << (TRANSFER_SECURITY_LEVEL_RECOMMENDED << 3))
| (RECEIVER_CONSTRAINTS_NONE << (TRANSFER_SECURITY_LEVEL_ONE << 3))
| (RECEIVER_CONSTRAINTS_NONE << (TRANSFER_SECURITY_LEVEL_TWO << 3))
| (RECEIVER_CONSTRAINTS_NONE << (TRANSFER_SECURITY_LEVEL_THREE << 3))
| (RECEIVER_CONSTRAINTS_NONE << (TRANSFER_SECURITY_LEVEL_FOUR << 3))
| (RECEIVER_CONSTRAINTS_NO_CODE << (TRANSFER_SECURITY_LEVEL_FIVE << 3))
| (RECEIVER_CONSTRAINTS_EOA << (TRANSFER_SECURITY_LEVEL_SIX << 3))
| (RECEIVER_CONSTRAINTS_NO_CODE << (TRANSFER_SECURITY_LEVEL_SEVEN << 3))
| (RECEIVER_CONSTRAINTS_EOA << (TRANSFER_SECURITY_LEVEL_EIGHT << 3))
| (RECEIVER_CONSTRAINTS_SBT << (TRANSFER_SECURITY_LEVEL_NINE << 3));
}
/*************************************************************************/
/* MODIFIERS */
/*************************************************************************/
/**
* @dev This modifier restricts a function call to the owner of the list `id`.
* @dev Throws when the caller is not the list owner.
*/
modifier onlyListOwner(uint120 id) {
_requireCallerOwnsList(id);
_;
}
/*************************************************************************/
/* APPLY TRANSFER POLICIES */
/*************************************************************************/
/**
* @notice Apply the collection transfer policy to a transfer operation of a creator token.
*
* @dev If the caller is self (Permit-C Processor) it means we have already applied operator validation in the
* _beforeTransferFrom callback. In this case, the security policy was already applied and the operator
* that used the Permit-C processor passed the security policy check and transfer can be safely allowed.
*
* @dev The order of checking whitelisted accounts, authorized operator check and whitelisted codehashes
* is very deliberate. The order of operations is determined by the most frequently used settings that are
* expected in the wild.
*
* @dev Throws when the collection has enabled account freezing mode and either the `from` or `to` addresses
* are on the list of frozen accounts for the collection.
* @dev Throws when the collection is set to Level 9 - Soulbound Token.
* @dev Throws when the receiver has deployed code and isn't whitelisted, if ReceiverConstraints.NoCode is set
* and the transfer is not approved by an authorizer for the collection.
* @dev Throws when the receiver has never verified a signature to prove they are an EOA and the receiver
* isn't whitelisted, if the ReceiverConstraints.EOA is set and the transfer is not approved by an
* authorizer for the collection..
* @dev Throws when `msg.sender` is blacklisted, if CallerConstraints.OperatorBlacklistEnableOTC is set, unless
* `msg.sender` is also the `from` address or the transfer is approved by an authorizer for the collection.
* @dev Throws when `msg.sender` isn't whitelisted, if CallerConstraints.OperatorWhitelistEnableOTC is set, unless
* `msg.sender` is also the `from` address or the transfer is approved by an authorizer for the collection.
* @dev Throws when neither `msg.sender` nor `from` are whitelisted, if
* CallerConstraints.OperatorWhitelistDisableOTC is set and the transfer
* is not approved by an authorizer for the collection.
*
* @dev <h4>Postconditions:</h4>
* 1. Transfer is allowed or denied based on the applied transfer policy.
*
* @param caller The address initiating the transfer.
* @param from The address of the token owner.
* @param to The address of the token receiver.
*/
function validateTransfer(address caller, address from, address to) public view {
(bytes4 errorSelector,) = _validateTransfer(_callerAuthorizedCheckCollection, msg.sender, caller, from, to, 0);
if (errorSelector != SELECTOR_NO_ERROR) {
_revertCustomErrorSelectorAsm(errorSelector);
}
}
/**
* @notice Apply the collection transfer policy to a transfer operation of a creator token.
*
* @dev If the caller is self (Permit-C Processor) it means we have already applied operator validation in the
* _beforeTransferFrom callback. In this case, the security policy was already applied and the operator
* that used the Permit-C processor passed the security policy check and transfer can be safely allowed.
*
* @dev The order of checking whitelisted accounts, authorized operator check and whitelisted codehashes
* is very deliberate. The order of operations is determined by the most frequently used settings that are
* expected in the wild.
*
* @dev Throws when the collection has enabled account freezing mode and either the `from` or `to` addresses
* are on the list of frozen accounts for the collection.
* @dev Throws when the collection is set to Level 9 - Soulbound Token.
* @dev Throws when the receiver has deployed code and isn't whitelisted, if ReceiverConstraints.NoCode is set
* and the transfer is not approved by an authorizer for the collection.
* @dev Throws when the receiver has never verified a signature to prove they are an EOA and the receiver
* isn't whitelisted, if the ReceiverConstraints.EOA is set and the transfer is not approved by an
* authorizer for the collection..
* @dev Throws when `msg.sender` is blacklisted, if CallerConstraints.OperatorBlacklistEnableOTC is set, unless
* `msg.sender` is also the `from` address or the transfer is approved by an authorizer for the collection.
* @dev Throws when `msg.sender` isn't whitelisted, if CallerConstraints.OperatorWhitelistEnableOTC is set, unless
* `msg.sender` is also the `from` address or the transfer is approved by an authorizer for the collection.
* @dev Throws when neither `msg.sender` nor `from` are whitelisted, if
* CallerConstraints.OperatorWhitelistDisableOTC is set and the transfer
* is not approved by an authorizer for the collection.
*
* @dev <h4>Postconditions:</h4>
* 1. Transfer is allowed or denied based on the applied transfer policy.
*
* @param caller The address initiating the transfer.
* @param from The address of the token owner.
* @param to The address of the token receiver.
* @param tokenId The token id being transferred.
*/
function validateTransfer(address caller, address from, address to, uint256 tokenId) public view {
(bytes4 errorSelector,) = _validateTransfer(_callerAuthorizedCheckToken, msg.sender, caller, from, to, tokenId);
if (errorSelector != SELECTOR_NO_ERROR) {
_revertCustomErrorSelectorAsm(errorSelector);
}
}
/**
* @notice Apply the collection transfer policy to a transfer operation of a creator token.
*
* @dev If the caller is self (Permit-C Processor) it means we have already applied operator validation in the
* _beforeTransferFrom callback. In this case, the security policy was already applied and the operator
* that used the Permit-C processor passed the security policy check and transfer can be safely allowed.
*
* @dev The order of checking whitelisted accounts, authorized operator check and whitelisted codehashes
* is very deliberate. The order of operations is determined by the most frequently used settings that are
* expected in the wild.
*
* @dev Throws when the collection has enabled account freezing mode and either the `from` or `to` addresses
* are on the list of frozen accounts for the collection.
* @dev Throws when the collection is set to Level 9 - Soulbound Token.
* @dev Throws when the receiver has deployed code and isn't whitelisted, if ReceiverConstraints.NoCode is set
* and the transfer is not approved by an authorizer for the collection.
* @dev Throws when the receiver has never verified a signature to prove they are an EOA and the receiver
* isn't whitelisted, if the ReceiverConstraints.EOA is set and the transfer is not approved by an
* authorizer for the collection..
* @dev Throws when `msg.sender` is blacklisted, if CallerConstraints.OperatorBlacklistEnableOTC is set, unless
* `msg.sender` is also the `from` address or the transfer is approved by an authorizer for the collection.
* @dev Throws when `msg.sender` isn't whitelisted, if CallerConstraints.OperatorWhitelistEnableOTC is set, unless
* `msg.sender` is also the `from` address or the transfer is approved by an authorizer for the collection.
* @dev Throws when neither `msg.sender` nor `from` are whitelisted, if
* CallerConstraints.OperatorWhitelistDisableOTC is set and the transfer
* is not approved by an authorizer for the collection.
*
* @dev <h4>Postconditions:</h4>
* 1. Transfer is allowed or denied based on the applied transfer policy.
*
* @param caller The address initiating the transfer.
* @param from The address of the token owner.
* @param to The address of the token receiver.
* @param tokenId The token id being transferred.
*/
function validateTransfer(address caller, address from, address to, uint256 tokenId, uint256 /*amount*/) external {
validateTransfer(caller, from, to, tokenId);
}
/**
* @notice Apply the collection transfer policy to a transfer operation of a creator token.
*
* @dev If the caller is self (Permit-C Processor) it means we have already applied operator validation in the
* _beforeTransferFrom callback. In this case, the security policy was already applied and the operator
* that used the Permit-C processor passed the security policy check and transfer can be safely allowed.
*
* @dev The order of checking whitelisted accounts, authorized operator check and whitelisted codehashes
* is very deliberate. The order of operations is determined by the most frequently used settings that are
* expected in the wild.
*
* @dev Throws when the collection has enabled account freezing mode and either the `from` or `to` addresses
* are on the list of frozen accounts for the collection.
* @dev Throws when the collection is set to Level 9 - Soulbound Token.
* @dev Throws when the receiver has deployed code and isn't whitelisted, if ReceiverConstraints.NoCode is set
* and the transfer is not approved by an authorizer for the collection.
* @dev Throws when the receiver has never verified a signature to prove they are an EOA and the receiver
* isn't whitelisted, if the ReceiverConstraints.EOA is set and the transfer is not approved by an
* authorizer for the collection..
* @dev Throws when `msg.sender` is blacklisted, if CallerConstraints.OperatorBlacklistEnableOTC is set, unless
* `msg.sender` is also the `from` address or the transfer is approved by an authorizer for the collection.
* @dev Throws when `msg.sender` isn't whitelisted, if CallerConstraints.OperatorWhitelistEnableOTC is set, unless
* `msg.sender` is also the `from` address or the transfer is approved by an authorizer for the collection.
* @dev Throws when neither `msg.sender` nor `from` are whitelisted, if
* CallerConstraints.OperatorWhitelistDisableOTC is set and the transfer
* is not approved by an authorizer for the collection.
*
* @dev <h4>Postconditions:</h4>
* 1. Transfer is allowed or denied based on the applied transfer policy.
*
* @param caller The address initiating the transfer.
* @param from The address of the token owner.
* @param to The address of the token receiver.
*/
function applyCollectionTransferPolicy(address caller, address from, address to) external view {
validateTransfer(caller, from, to);
}
/**
* @notice Returns the caller and receiver constraints for the specified transfer security level.
*
* @param level The transfer security level to return the caller and receiver constraints for.
*
* @return callerConstraints The `CallerConstraints` value for the level.
* @return receiverConstraints The `ReceiverConstraints` value for the level.
*/
function transferSecurityPolicies(
uint256 level
) public view returns (uint256 callerConstraints, uint256 receiverConstraints) {
callerConstraints = uint8((_callerConstraintsLookup >> (level << 3)));
receiverConstraints = uint8((_receiverConstraintsLookup >> (level << 3)));
}
/**
* @notice Sets an operator for an authorized transfer that skips transfer security level
* validation for caller and receiver constraints.
*
* @dev An authorizer *MUST* clear the authorization with a call to `afterAuthorizedTransfer`
* to prevent unauthorized transfers of the token.
*
* @dev Throws when authorization mode is disabled for the collection.
* @dev Throws when using the wildcard operator address and the collection does not allow
* for wildcard authorized operators.
* @dev Throws when the caller is not an allowed authorizer for the collection.
*
* @dev <h4>Postconditions:</h4>
* 1. The `operator` is stored as an authorized operator for transfers.
*
* @param operator The address of the operator to set as authorized for transfers.
* @param token The address of the token to authorize.
* @param tokenId The token id to set the authorized operator for.
*/
function beforeAuthorizedTransfer(address operator, address token, uint256 tokenId) external {
_setOperatorInTransientStorage(operator, token, tokenId, false);
}
/**
* @notice Clears the authorized operator for a token to prevent additional transfers that
* do not conform to the transfer security level for the token.
*
* @dev Throws when authorization mode is disabled for the collection.
* @dev Throws when using the wildcard operator address and the collection does not allow
* for wildcard authorized operators.
* @dev Throws when the caller is not an allowed authorizer for the collection.
*
* @dev <h4>Postconditions:</h4>
* 1. The authorized operator for the token is cleared from storage.
*
* @param token The address of the token to authorize.
* @param tokenId The token id to set the authorized operator for.
*/
function afterAuthorizedTransfer(address token, uint256 tokenId) public {
_setOperatorInTransientStorage(address(uint160(uint256(BYTES32_ZERO))), token, tokenId, false);
}
/**
* @notice Sets an operator for an authorized transfer that skips transfer security level
* validation for caller and receiver constraints.
* @notice This overload of `beforeAuthorizedTransfer` defaults to a tokenId of 0.
*
* @dev An authorizer *MUST* clear the authorization with a call to `afterAuthorizedTransfer`
* to prevent unauthorized transfers of the token.
*
* @dev Throws when authorization mode is disabled for the collection.
* @dev Throws when using the wildcard operator address and the collection does not allow
* for wildcard authorized operators.
* @dev Throws when the caller is not an allowed authorizer for the collection.
*
* @dev <h4>Postconditions:</h4>
* 1. The `operator` is stored as an authorized operator for transfers.
*
* @param operator The address of the operator to set as authorized for transfers.
* @param token The address of the token to authorize.
*/
function beforeAuthorizedTransfer(address operator, address token) external {
_setOperatorInTransientStorage(operator, token, 0, true);
}
/**
* @notice Clears the authorized operator for a token to prevent additional transfers that
* do not conform to the transfer security level for the token.
* @notice This overload of `afterAuthorizedTransfer` defaults to a tokenId of 0.
*
* @dev Throws when authorization mode is disabled for the collection.
* @dev Throws when using the wildcard operator address and the collection does not allow
* for wildcard authorized operators.
* @dev Throws when the caller is not an allowed authorizer for the collection.
*
* @dev <h4>Postconditions:</h4>
* 1. The authorized operator for the token is cleared from storage.
*
* @param token The address of the token to authorize.
*/
function afterAuthorizedTransfer(address token) external {
afterAuthorizedTransfer(token, 0);
}
/**
* @notice Sets the wildcard operator to authorize any operator to transfer a token while
* skipping transfer security level validation for caller and receiver constraints.
*
* @dev An authorizer *MUST* clear the authorization with a call to `afterAuthorizedTransfer`
* to prevent unauthorized transfers of the token.
*
* @dev Throws when authorization mode is disabled for the collection.
* @dev Throws when the collection does not allow for wildcard authorized operators.
* @dev Throws when the caller is not an allowed authorizer for the collection.
*
* @dev <h4>Postconditions:</h4>
* 1. The wildcard operator is stored as an authorized operator for transfers.
*
* @param token The address of the token to authorize.
* @param tokenId The token id to set the authorized operator for.
*/
function beforeAuthorizedTransfer(address token, uint256 tokenId) external {
_setOperatorInTransientStorage(WILDCARD_OPERATOR_ADDRESS, token, tokenId, false);
}
/**
* @notice Sets the wildcard operator to authorize any operator to transfer a token while
* skipping transfer security level validation for caller and receiver constraints.
*
* @dev An authorizer *MUST* clear the authorization with a call to `afterAuthorizedTransfer`
* to prevent unauthorized transfers of the token.
*
* @dev Throws when authorization mode is disabled for the collection.
* @dev Throws when the collection does not allow for wildcard authorized operators.
* @dev Throws when the caller is not an allowed authorizer for the collection.
*
* @dev <h4>Postconditions:</h4>
* 1. The wildcard operator is stored as an authorized operator for transfers.
*
* @param token The address of the token to authorize.
* @param tokenId The token id to set the authorized operator for.
*/
function beforeAuthorizedTransferWithAmount(address token, uint256 tokenId, uint256 /*amount*/) external {
_setOperatorInTransientStorage(WILDCARD_OPERATOR_ADDRESS, token, tokenId, false);
}
/**
* @notice Clears the authorized operator for a token to prevent additional transfers that
* do not conform to the transfer security level for the token.
*
* @dev Throws when authorization mode is disabled for the collection.
* @dev Throws when using the wildcard operator address and the collection does not allow
* for wildcard authorized operators.
* @dev Throws when the caller is not an allowed authorizer for the collection.
*
* @dev <h4>Postconditions:</h4>
* 1. The authorized operator for the token is cleared from storage.
*
* @param token The address of the token to authorize.
* @param tokenId The token id to set the authorized operator for.
*/
function afterAuthorizedTransferWithAmount(address token, uint256 tokenId) external {
afterAuthorizedTransfer(token, tokenId);
}
/*************************************************************************/
/* LIST MANAGEMENT */
/*************************************************************************/
/**
* @notice Creates a new list id. The list id is a handle to allow editing of blacklisted and whitelisted accounts
* and codehashes.
*
* @dev <h4>Postconditions:</h4>
* 1. A new list with the specified name is created.
* 2. The caller is set as the owner of the new list.
* 3. A `CreatedList` event is emitted.
* 4. A `ReassignedListOwnership` event is emitted.
*
* @param name The name of the new list.
* @return id The id of the new list.
*/
function createList(string calldata name) public returns (uint120 id) {
unchecked {
id = ++lastListId;
}
listOwners[id] = msg.sender;
emit CreatedList(id, name);
emit ReassignedListOwnership(id, msg.sender);
}
/**
* @notice Creates a new list id, and copies all blacklisted and whitelisted accounts and codehashes from the
* specified source list.
*
* @dev <h4>Postconditions:</h4>
* 1. A new list with the specified name is created.
* 2. The caller is set as the owner of the new list.
* 3. A `CreatedList` event is emitted.
* 4. A `ReassignedListOwnership` event is emitted.
* 5. All blacklisted and whitelisted accounts and codehashes from the specified source list are copied
* to the new list.
* 6. An `AddedAccountToList` event is emitted for each blacklisted and whitelisted account copied.
* 7. An `AddedCodeHashToList` event is emitted for each blacklisted and whitelisted codehash copied.
*
* @param name The name of the new list.
* @param sourceListId The id of the source list to copy from.
* @return id The id of the new list.
*/
function createListCopy(string calldata name, uint120 sourceListId) external returns (uint120 id) {
unchecked {
id = ++lastListId;
}
unchecked {
if (sourceListId > id - 1) {
revert CreatorTokenTransferValidator__ListDoesNotExist();
}
}
listOwners[id] = msg.sender;
emit CreatedList(id, name);
emit ReassignedListOwnership(id, msg.sender);
List storage sourceBlacklist = blacklists[sourceListId];
List storage sourceWhitelist = whitelists[sourceListId];
List storage sourceAuthorizers = authorizers[sourceListId];
List storage targetBlacklist = blacklists[id];
List storage targetWhitelist = whitelists[id];
List storage targetAuthorizers = authorizers[id];
_copyAddressSet(LIST_TYPE_BLACKLIST, id, sourceBlacklist, targetBlacklist);
_copyBytes32Set(LIST_TYPE_BLACKLIST, id, sourceBlacklist, targetBlacklist);
_copyAddressSet(LIST_TYPE_WHITELIST, id, sourceWhitelist, targetWhitelist);
_copyBytes32Set(LIST_TYPE_WHITELIST, id, sourceWhitelist, targetWhitelist);
_copyAddressSet(LIST_TYPE_AUTHORIZERS, id, sourceAuthorizers, targetAuthorizers);
_copyBytes32Set(LIST_TYPE_AUTHORIZERS, id, sourceAuthorizers, targetAuthorizers);
}
/**
* @notice Transfer ownership of a list to a new owner.
*
* @dev Throws when the new owner is the zero address.
* @dev Throws when the caller does not own the specified list.
*
* @dev <h4>Postconditions:</h4>
* 1. The list ownership is transferred to the new owner.
* 2. A `ReassignedListOwnership` event is emitted.
*
* @param id The id of the list.
* @param newOwner The address of the new owner.
*/
function reassignOwnershipOfList(uint120 id, address newOwner) public {
if(newOwner == address(0)) {
revert CreatorTokenTransferValidator__ListOwnershipCannotBeTransferredToZeroAddress();
}
_reassignOwnershipOfList(id, newOwner);
}
/**
* @notice Renounce the ownership of a list, rendering the list immutable.
*
* @dev Throws when the caller does not own the specified list.
*
* @dev <h4>Postconditions:</h4>
* 1. The ownership of the specified list is renounced.
* 2. A `ReassignedListOwnership` event is emitted.
*
* @param id The id of the list.
*/
function renounceOwnershipOfList(uint120 id) public {
_reassignOwnershipOfList(id, address(0));
}
/**
* @notice Set the transfer security level, authorization mode and account freezing mode settings of a collection.
*
* @dev Throws when the caller is neither collection contract, nor the owner or admin of the specified collection.
*
* @dev <h4>Postconditions:</h4>
* 1. The transfer security level of the specified collection is set to the new value.
* 2. The authorization mode setting of the specified collection is set to the new value.
* 3. The authorization wildcard operator mode setting of the specified collection is set to the new value.
* 4. The account freezing mode setting of the specified collection is set to the new value.
* 5. A `SetTransferSecurityLevel` event is emitted.
* 6. A `SetAuthorizationModeEnabled` event is emitted.
* 7. A `SetAccountFreezingModeEnabled` event is emitted.
*
* @param collection The address of the collection.
* @param level The new transfer security level to apply.
* @param disableAuthorizationMode Flag if the collection allows for authorizer mode.
* @param disableWildcardOperators Flag if the authorizer can set wildcard operators.
* @param enableAccountFreezingMode Flag if the collection is using account freezing.
*/
function setTransferSecurityLevelOfCollection(
address collection,
uint8 level,
bool disableAuthorizationMode,
bool disableWildcardOperators,
bool enableAccountFreezingMode) external {
if (level > TRANSFER_SECURITY_LEVEL_NINE) {
revert CreatorTokenTransferValidator__InvalidTransferSecurityLevel();
}
_requireCallerIsNFTOrContractOwnerOrAdmin(collection);
collectionSecurityPolicies[collection].transferSecurityLevel = level;
collectionSecurityPolicies[collection].disableAuthorizationMode = disableAuthorizationMode;
collectionSecurityPolicies[collection].authorizersCannotSetWildcardOperators = disableWildcardOperators;
collectionSecurityPolicies[collection].enableAccountFreezingMode = enableAccountFreezingMode;
emit SetTransferSecurityLevel(collection, level);
emit SetAuthorizationModeEnabled(collection, disableAuthorizationMode, disableWildcardOperators);
emit SetAccountFreezingModeEnabled(collection, enableAccountFreezingMode);
}
/**
* @notice Set the token type setting of a collection.
*
* @dev Throws when the caller is neither collection contract, nor the owner or admin of the specified collection.
*
* @dev <h4>Postconditions:</h4>
* 1. The token type of the specified collection is set to the new value.
* 2. A `SetTokenType` event is emitted.
*
* @param collection The address of the collection.
* @param tokenType The new transfer security level to apply.
*/
function setTokenTypeOfCollection(
address collection,
uint16 tokenType
) external {
_requireCallerIsNFTOrContractOwnerOrAdmin(collection);
collectionSecurityPolicies[collection].tokenType = tokenType;
emit SetTokenType(collection, tokenType);
}
/**
* @notice Applies the specified list to a collection.
*
* @dev Throws when the caller is neither collection contract, nor the owner or admin of the specified collection.
* @dev Throws when the specified list id does not exist.
*
* @dev <h4>Postconditions:</h4>
* 1. The list of the specified collection is set to the new value.
* 2. An `AppliedListToCollection` event is emitted.
*
* @param collection The address of the collection.
* @param id The id of the operator whitelist.
*/
function applyListToCollection(address collection, uint120 id) public {
_requireCallerIsNFTOrContractOwnerOrAdmin(collection);
if (id > lastListId) {
revert CreatorTokenTransferValidator__ListDoesNotExist();
}
collectionSecurityPolicies[collection].listId = id;
emit AppliedListToCollection(collection, id);
}
/**
* @notice Adds accounts to the frozen accounts list of a collection.
*
* @dev Throws when the caller is neither collection contract, nor the owner or admin of the specified collection.
*
* @dev <h4>Postconditions:</h4>
* 1. The accounts are added to the list of frozen accounts for a collection.
* 2. A `AccountFrozenForCollection` event is emitted for each account added to the list.
*
* @param collection The address of the collection.
* @param accountsToFreeze The list of accounts to added to frozen accounts.
*/
function freezeAccountsForCollection(address collection, address[] calldata accountsToFreeze) external {
_requireCallerIsNFTOrContractOwnerOrAdmin(collection);
AccountList storage accounts = frozenAccounts[collection];
for (uint256 i = 0; i < accountsToFreeze.length;) {
address accountToFreeze = accountsToFreeze[i];
if (accounts.enumerableAccounts.add(accountToFreeze)) {
emit AccountFrozenForCollection(collection, accountToFreeze);
accounts.nonEnumerableAccounts[accountToFreeze] = true;
}
unchecked {
++i;
}
}
}
/**
* @notice Removes accounts to the frozen accounts list of a collection.
*
* @dev Throws when the caller is neither collection contract, nor the owner or admin of the specified collection.
*
* @dev <h4>Postconditions:</h4>
* 1. The accounts are removed from the list of frozen accounts for a collection.
* 2. A `AccountUnfrozenForCollection` event is emitted for each account removed from the list.
*
* @param collection The address of the collection.
* @param accountsToUnfreeze The list of accounts to remove from frozen accounts.
*/
function unfreezeAccountsForCollection(address collection, address[] calldata accountsToUnfreeze) external {
_requireCallerIsNFTOrContractOwnerOrAdmin(collection);
AccountList storage accounts = frozenAccounts[collection];
for (uint256 i = 0; i < accountsToUnfreeze.length;) {
address accountToUnfreeze = accountsToUnfreeze[i];
if (accounts.enumerableAccounts.remove(accountToUnfreeze)) {
emit AccountUnfrozenForCollection(collection, accountToUnfreeze);
accounts.nonEnumerableAccounts[accountToUnfreeze] = false;
}
unchecked {
++i;
}
}
}
/**
* @notice Get the security policy of the specified collection.
* @param collection The address of the collection.
* @return The security policy of the specified collection, which includes:
* Transfer security level, operator whitelist id, permitted contract receiver allowlist id,
* authorizer mode, if authorizer can set a wildcard operator, and if account freezing is
* enabled.
*/
function getCollectionSecurityPolicy(
address collection
) external view returns (CollectionSecurityPolicyV3 memory) {
return collectionSecurityPolicies[collection];
}
/**
* @notice Adds one or more accounts to a blacklist.
*
* @dev Throws when the caller does not own the specified list.
* @dev Throws when the accounts array is empty.
*
* @dev <h4>Postconditions:</h4>
* 1. Accounts not previously in the list are added.
* 2. An `AddedAccountToList` event is emitted for each account that is newly added to the list.
*
* @param id The id of the list.
* @param accounts The addresses of the accounts to add.
*/
function addAccountsToBlacklist(
uint120 id,
address[] calldata accounts
) external {
_addAccountsToList(blacklists[id], LIST_TYPE_BLACKLIST, id, accounts);
}
/**
* @notice Adds one or more accounts to a whitelist.
*
* @dev Throws when the caller does not own the specified list.
* @dev Throws when the accounts array is empty.
*
* @dev <h4>Postconditions:</h4>
* 1. Accounts not previously in the list are added.
* 2. An `AddedAccountToList` event is emitted for each account that is newly added to the list.
*
* @param id The id of the list.
* @param accounts The addresses of the accounts to add.
*/
function addAccountsToWhitelist(
uint120 id,
address[] calldata accounts
) external {
_addAccountsToList(whitelists[id], LIST_TYPE_WHITELIST, id, accounts);
}
/**
* @notice Adds one or more accounts to authorizers.
*
* @dev Throws when the caller does not own the specified list.
* @dev Throws when the accounts array is empty.
*
* @dev <h4>Postconditions:</h4>
* 1. Accounts not previously in the list are added.
* 2. An `AddedAccountToList` event is emitted for each account that is newly added to the list.
*