-
Notifications
You must be signed in to change notification settings - Fork 25
/
Hats.sol
1409 lines (1237 loc) · 61 KB
/
Hats.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: AGPL-3.0
// Copyright (C) 2023 Haberdasher Labs
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
pragma solidity >=0.8.13;
import { ERC1155 } from "lib/ERC1155/ERC1155.sol";
// import { console2 } from "forge-std/Test.sol"; //remove after testing
import "./Interfaces/IHats.sol";
import "./HatsIdUtilities.sol";
import "./Interfaces/IHatsToggle.sol";
import "./Interfaces/IHatsEligibility.sol";
import "solbase/utils/Base64.sol";
import "solbase/utils/LibString.sol";
import "solady/utils/Multicallable.sol";
/// @title Hats Protocol v1
/// @notice Hats are DAO-native, revocable, and programmable roles that are represented as non-transferable ERC-1155-similar tokens for composability
/// @dev This is a multi-tenant contract that can manage all hats for a given chain. While it fully implements the ERC1155 interface, it does not fully comply with the ERC1155 standard.
/// @author Haberdasher Labs
contract Hats is IHats, ERC1155, Multicallable, HatsIdUtilities {
/// @notice This contract's version is labeled v1. Previous versions labeled similarly as v1 and v1.0 are deprecated,
/// and should be treated as beta deployments.
/*//////////////////////////////////////////////////////////////
HATS DATA MODELS
//////////////////////////////////////////////////////////////*/
/// @notice A Hat object containing the hat's properties
/// @dev The members are packed to minimize storage costs
/// @custom:member eligibility Module that rules on wearer eligibiliy and standing
/// @custom:member maxSupply The max number of hats with this id that can exist
/// @custom:member supply The number of this hat that currently exist
/// @custom:member lastHatId Indexes how many different child hats an admin has
/// @custom:member toggle Module that sets the hat's status
/**
* @custom:member config Holds status and other settings, with this bitwise schema:
*
* 0th bit | `active` status; can be altered by toggle
* 1 | `mutable` setting
* 2 - 95 | unassigned
*/
/// @custom:member details Holds arbitrary metadata about the hat
/// @custom:member imageURI A uri pointing to an image for the hat
struct Hat {
// 1st storage slot
address eligibility; // ─┐ 20
uint32 maxSupply; // │ 4
uint32 supply; // │ 4
uint16 lastHatId; // ─┘ 2
// 2nd slot
address toggle; // ─┐ 20
uint96 config; // ─┘ 12
// 3rd+ slot (optional)
string details;
string imageURI;
}
/*//////////////////////////////////////////////////////////////
HATS STORAGE
//////////////////////////////////////////////////////////////*/
/// @notice The name of the contract, typically including the version
string public name;
/// @notice The first 4 bytes of the id of the last tophat created.
uint32 public lastTopHatId; // first tophat id starts at 1
/// @notice The fallback image URI for hat tokens with no `imageURI` specified in their branch
string public baseImageURI;
/// @dev Internal mapping of hats to hat ids. See HatsIdUtilities.sol for more info on how hat ids work
mapping(uint256 => Hat) internal _hats; // key: hatId => value: Hat struct
/// @notice Mapping of wearers in bad standing for certain hats
/// @dev Used by external contracts to trigger penalties for wearers in bad standing
/// hatId => wearer => !standing
mapping(uint256 => mapping(address => bool)) public badStandings;
/*//////////////////////////////////////////////////////////////
CONSTRUCTOR
//////////////////////////////////////////////////////////////*/
/// @notice All arguments are immutable; they can only be set once during construction
/// @param _name The name of this contract, typically including the version
/// @param _baseImageURI The fallback image URI
constructor(string memory _name, string memory _baseImageURI) {
name = _name;
baseImageURI = _baseImageURI;
}
/*//////////////////////////////////////////////////////////////
HATS LOGIC
//////////////////////////////////////////////////////////////*/
/// @notice Creates and mints a Hat that is its own admin, i.e. a "topHat"
/// @dev A topHat has no eligibility and no toggle
/// @param _target The address to which the newly created topHat is minted
/// @param _details A description of the Hat [optional]. Should not be larger than 7000 bytes
/// (enforced in changeHatDetails)
/// @param _imageURI The image uri for this top hat and the fallback for its
/// downstream hats [optional]. Should not be large than 7000 bytes
/// (enforced in changeHatImageURI)
/// @return topHatId The id of the newly created topHat
function mintTopHat(address _target, string calldata _details, string calldata _imageURI)
public
returns (uint256 topHatId)
{
// create hat
topHatId = uint256(++lastTopHatId) << 224;
_createHat(
topHatId,
_details, // details
1, // maxSupply = 1
address(0), // there is no eligibility
address(0), // it has no toggle
false, // its immutable
_imageURI
);
_mintHat(_target, topHatId);
}
/// @notice Creates a new hat. The msg.sender must wear the `_admin` hat.
/// @dev Initializes a new Hat struct, but does not mint any tokens.
/// @param _details A description of the Hat. Should not be larger than 7000 bytes (enforced in changeHatDetails)
/// @param _maxSupply The total instances of the Hat that can be worn at once
/// @param _admin The id of the Hat that will control who wears the newly created hat
/// @param _eligibility The address that can report on the Hat wearer's status
/// @param _toggle The address that can deactivate the Hat
/// @param _mutable Whether the hat's properties are changeable after creation
/// @param _imageURI The image uri for this hat and the fallback for its
/// downstream hats [optional]. Should not be larger than 7000 bytes (enforced in changeHatImageURI)
/// @return newHatId The id of the newly created Hat
function createHat(
uint256 _admin,
string calldata _details,
uint32 _maxSupply,
address _eligibility,
address _toggle,
bool _mutable,
string calldata _imageURI
) public returns (uint256 newHatId) {
if (uint16(_admin) > 0) {
revert MaxLevelsReached();
}
if (_eligibility == address(0)) revert ZeroAddress();
if (_toggle == address(0)) revert ZeroAddress();
// check that the admin id is valid, ie does not contain empty levels between filled levels
if (!isValidHatId(_admin)) revert InvalidHatId();
// construct the next hat id
newHatId = getNextId(_admin);
// to create a hat, you must be wearing one of its admin hats
_checkAdmin(newHatId);
// create the new hat
_createHat(newHatId, _details, _maxSupply, _eligibility, _toggle, _mutable, _imageURI);
// increment _admin.lastHatId
// use the overflow check to constrain to correct number of hats per level
++_hats[_admin].lastHatId;
}
/// @notice Creates new hats in batch. The msg.sender must be an admin of each hat.
/// @dev This is a convenience function that loops through the arrays and calls `createHat`.
/// @param _admins Array of ids of admins for each hat to create
/// @param _details Array of details for each hat to create
/// @param _maxSupplies Array of supply caps for each hat to create
/// @param _eligibilityModules Array of eligibility module addresses for each hat to
/// create
/// @param _toggleModules Array of toggle module addresses for each hat to create
/// @param _mutables Array of mutable flags for each hat to create
/// @param _imageURIs Array of imageURIs for each hat to create
/// @return success True if all createHat calls succeeded
function batchCreateHats(
uint256[] calldata _admins,
string[] calldata _details,
uint32[] calldata _maxSupplies,
address[] memory _eligibilityModules,
address[] memory _toggleModules,
bool[] calldata _mutables,
string[] calldata _imageURIs
) public returns (bool success) {
// check if array lengths are the same
uint256 length = _admins.length; // save an MLOAD
{
bool sameLengths = (
length == _details.length // details
&& length == _maxSupplies.length // supplies
&& length == _eligibilityModules.length // eligibility
&& length == _toggleModules.length // toggle
&& length == _mutables.length // mutable
&& length == _imageURIs.length
); // imageURI
if (!sameLengths) revert BatchArrayLengthMismatch();
}
// loop through and create each hat
for (uint256 i = 0; i < length;) {
createHat(
_admins[i],
_details[i],
_maxSupplies[i],
_eligibilityModules[i],
_toggleModules[i],
_mutables[i],
_imageURIs[i]
);
unchecked {
++i;
}
}
success = true;
}
/// @notice Gets the id of the next child hat of the hat `_admin`
/// @dev Does not incrememnt lastHatId
/// @param _admin The id of the hat to serve as the admin for the next child hat
/// @return nextId The new hat id
function getNextId(uint256 _admin) public view returns (uint256 nextId) {
uint16 nextHatId = _hats[_admin].lastHatId + 1;
nextId = buildHatId(_admin, nextHatId);
}
/// @notice Mints an ERC1155-similar token of the Hat to an eligible recipient, who then "wears" the hat
/// @dev The msg.sender must wear an admin Hat of `_hatId`, and the recipient must be eligible to wear `_hatId`
/// @param _hatId The id of the Hat to mint
/// @param _wearer The address to which the Hat is minted
/// @return success Whether the mint succeeded
function mintHat(uint256 _hatId, address _wearer) public returns (bool success) {
Hat storage hat = _hats[_hatId];
if (hat.maxSupply == 0) revert HatDoesNotExist(_hatId);
// only eligible wearers can receive minted hats
if (!isEligible(_wearer, _hatId)) revert NotEligible();
// only active hats can be minted
if (!_isActive(hat, _hatId)) revert HatNotActive();
// only the wearer of one of a hat's admins can mint it
_checkAdmin(_hatId);
// hat supply cannot exceed maxSupply
if (hat.supply >= hat.maxSupply) revert AllHatsWorn(_hatId);
// wearers cannot wear the same hat more than once
if (_staticBalanceOf(_wearer, _hatId) > 0) revert AlreadyWearingHat(_wearer, _hatId);
// if we've made it through all the checks, mint the hat
_mintHat(_wearer, _hatId);
success = true;
}
/// @notice Mints new hats in batch. The msg.sender must be an admin of each hat.
/// @dev This is a convenience function that loops through the arrays and calls `mintHat`.
/// @param _hatIds Array of ids of hats to mint
/// @param _wearers Array of addresses to which the hats will be minted
/// @return success True if all mintHat calls succeeded
function batchMintHats(uint256[] calldata _hatIds, address[] calldata _wearers) public returns (bool success) {
uint256 length = _hatIds.length;
if (length != _wearers.length) revert BatchArrayLengthMismatch();
for (uint256 i = 0; i < length;) {
mintHat(_hatIds[i], _wearers[i]);
unchecked {
++i;
}
}
success = true;
}
/// @notice Toggles a Hat's status from active to deactive, or vice versa
/// @dev The msg.sender must be set as the hat's toggle
/// @param _hatId The id of the Hat for which to adjust status
/// @param _newStatus The new status to set
/// @return toggled Whether the status was toggled
function setHatStatus(uint256 _hatId, bool _newStatus) external returns (bool toggled) {
Hat storage hat = _hats[_hatId];
if (msg.sender != hat.toggle) {
revert NotHatsToggle();
}
toggled = _processHatStatus(_hatId, _newStatus);
}
/// @notice Checks a hat's toggle module and processes the returned status
/// @dev May change the hat's status in storage
/// @param _hatId The id of the Hat whose toggle we are checking
/// @return toggled Whether there was a new status
function checkHatStatus(uint256 _hatId) public returns (bool toggled) {
Hat storage hat = _hats[_hatId];
// attempt to retrieve the hat's status from the toggle module
(bool success, bool newStatus) = _pullHatStatus(hat, _hatId);
// if unsuccessful (ie toggle was humanistic), process the new status
if (!success) revert NotHatsToggle();
// if successful (ie toggle was mechanistic), process the new status
toggled = _processHatStatus(_hatId, newStatus);
}
function _pullHatStatus(Hat storage _hat, uint256 _hatId) internal view returns (bool success, bool newStatus) {
bytes memory data = abi.encodeWithSignature("getHatStatus(uint256)", _hatId);
bytes memory returndata;
(success, returndata) = _hat.toggle.staticcall(data);
/*
* if function call succeeds with data of length == 32, then we know the contract exists
* and has the getHatStatus function.
* But — since function selectors don't include return types — we still can't assume that the return data is a boolean,
* so we treat it as a uint so it will always safely decode without throwing.
*/
if (success && returndata.length == 32) {
// check the returndata manually
uint256 uintReturndata = abi.decode(returndata, (uint256));
// false condition
if (uintReturndata == 0) {
newStatus = false;
// true condition
} else if (uintReturndata == 1) {
newStatus = true;
}
// invalid condition
else {
success = false;
}
} else {
success = false;
}
}
/// @notice Report from a hat's eligibility on the status of one of its wearers and, if `false`, revoke their hat
/// @dev Burns the wearer's hat, if revoked
/// @param _hatId The id of the hat
/// @param _wearer The address of the hat wearer whose status is being reported
/// @param _eligible Whether the wearer is eligible for the hat (will be revoked if
/// false)
/// @param _standing False if the wearer is no longer in good standing (and potentially should be penalized)
/// @return updated Whether the report succeeded
function setHatWearerStatus(uint256 _hatId, address _wearer, bool _eligible, bool _standing)
external
returns (bool updated)
{
Hat storage hat = _hats[_hatId];
if (msg.sender != hat.eligibility) {
revert NotHatsEligibility();
}
updated = _processHatWearerStatus(_hatId, _wearer, _eligible, _standing);
}
/// @notice Check a hat's eligibility for a report on the status of one of the hat's wearers and, if `false`, revoke their hat
/// @dev Burns the wearer's hat, if revoked
/// @param _hatId The id of the hat
/// @param _wearer The address of the Hat wearer whose status report is being requested
/// @return updated Whether the wearer's status was altered
function checkHatWearerStatus(uint256 _hatId, address _wearer) public returns (bool updated) {
bool eligible;
bool standing;
(bool success, bytes memory returndata) = _hats[_hatId].eligibility.staticcall(
abi.encodeWithSignature("getWearerStatus(address,uint256)", _wearer, _hatId)
);
/*
* if function call succeeds with data of length == 64, then we know the contract exists
* and has the getWearerStatus function (which returns two words).
* But — since function selectors don't include return types — we still can't assume that the return data is two booleans,
* so we treat it as a uint so it will always safely decode without throwing.
*/
if (success && returndata.length == 64) {
// check the returndata manually
(uint256 firstWord, uint256 secondWord) = abi.decode(returndata, (uint256, uint256));
// returndata is valid
if (firstWord < 2 && secondWord < 2) {
standing = (secondWord == 1) ? true : false;
// never eligible if in bad standing
eligible = (standing && firstWord == 1) ? true : false;
}
// returndata is invalid
else {
revert NotHatsEligibility();
}
} else {
revert NotHatsEligibility();
}
updated = _processHatWearerStatus(_hatId, _wearer, eligible, standing);
}
/// @notice Stop wearing a hat, aka "renounce" it
/// @dev Burns the msg.sender's hat
/// @param _hatId The id of the Hat being renounced
function renounceHat(uint256 _hatId) external {
if (_staticBalanceOf(msg.sender, _hatId) < 1) {
revert NotHatWearer();
}
// remove the hat
_burnHat(msg.sender, _hatId);
}
/*//////////////////////////////////////////////////////////////
HATS INTERNAL LOGIC
//////////////////////////////////////////////////////////////*/
/// @notice Internal call for creating a new hat
/// @dev Initializes a new Hat in storage, but does not mint any tokens
/// @param _id ID of the hat to be stored
/// @param _details A description of the hat
/// @param _maxSupply The total instances of the Hat that can be worn at once
/// @param _eligibility The address that can report on the Hat wearer's status
/// @param _toggle The address that can deactivate the hat [optional]
/// @param _mutable Whether the hat's properties are changeable after creation
/// @param _imageURI The image uri for this top hat and the fallback for its
/// downstream hats [optional]
function _createHat(
uint256 _id,
string calldata _details,
uint32 _maxSupply,
address _eligibility,
address _toggle,
bool _mutable,
string calldata _imageURI
) internal {
/*
We write directly to storage instead of first building the Hat struct in memory.
This allows us to cheaply use the existing lastHatId value in case it was incremented by creating a hat while skipping admin levels.
(Resetting it to 0 would be bad since this hat's child hat(s) would overwrite the previously created hat(s) at that level.)
*/
Hat storage hat = _hats[_id];
hat.details = _details;
hat.maxSupply = _maxSupply;
hat.eligibility = _eligibility;
hat.toggle = _toggle;
hat.imageURI = _imageURI;
// config is a concatenation of the status and mutability properties
hat.config = _mutable ? uint96(3 << 94) : uint96(1 << 95);
emit HatCreated(_id, _details, _maxSupply, _eligibility, _toggle, _mutable, _imageURI);
}
/// @notice Internal function to process hat status
/// @dev Updates a hat's status if different from current
/// @param _hatId The id of the Hat in quest
/// @param _newStatus The status to potentially change to
/// @return updated - Whether the status was updated
function _processHatStatus(uint256 _hatId, bool _newStatus) internal returns (bool updated) {
// optimize later
Hat storage hat = _hats[_hatId];
if (_newStatus != _getHatStatus(hat)) {
_setHatStatus(hat, _newStatus);
emit HatStatusChanged(_hatId, _newStatus);
updated = true;
}
}
/// @notice Internal call to process wearer status from the eligibility module
/// @dev Burns the wearer's Hat token if _eligible is false, and updates badStandings
/// state if necessary
/// @param _hatId The id of the Hat to revoke
/// @param _wearer The address of the wearer in question
/// @param _eligible Whether _wearer is eligible for the Hat (if false, this function
/// will revoke their Hat)
/// @param _standing Whether _wearer is in good standing (to be recorded in storage)
/// @return updated Whether the wearer standing was updated
function _processHatWearerStatus(uint256 _hatId, address _wearer, bool _eligible, bool _standing)
internal
returns (bool updated)
{
// revoke/burn the hat if _wearer has a positive balance
if (_staticBalanceOf(_wearer, _hatId) > 0) {
// always ineligible if in bad standing
if (!_eligible || !_standing) {
_burnHat(_wearer, _hatId);
}
}
// record standing for use by other contracts
// note: here, standing and badStandings are opposite
// i.e. if standing (true = good standing)
// then badStandings[_hatId][wearer] will be false
// if they are different, then something has changed, and we need to update
// badStandings marker
if (_standing == badStandings[_hatId][_wearer]) {
badStandings[_hatId][_wearer] = !_standing;
updated = true;
emit WearerStandingChanged(_hatId, _wearer, _standing);
}
}
/// @notice Internal function to set a hat's status in storage
/// @dev Flips the 0th bit of _hat.config via bitwise operation
/// @param _hat The hat object
/// @param _status The status to set for the hat
function _setHatStatus(Hat storage _hat, bool _status) internal {
if (_status) {
_hat.config |= uint96(1 << 95);
} else {
_hat.config &= ~uint96(1 << 95);
}
}
/**
* @notice Internal function to retrieve an account's internal "static" balance directly from internal storage,
* @dev This function bypasses the dynamic `_isActive` and `_isEligible` checks
* @param _account The account to check
* @param _hatId The hat to check
* @return staticBalance The account's static of the hat, from internal storage
*/
function _staticBalanceOf(address _account, uint256 _hatId) internal view returns (uint256 staticBalance) {
staticBalance = _balanceOf[_account][_hatId];
}
/*//////////////////////////////////////////////////////////////
HATS ADMIN FUNCTIONS
//////////////////////////////////////////////////////////////*/
/// @notice Checks whether msg.sender is an admin of a hat, and reverts if not
function _checkAdmin(uint256 _hatId) internal view {
if (!isAdminOfHat(msg.sender, _hatId)) {
revert NotAdmin(msg.sender, _hatId);
}
}
/// @notice checks whether the msg.sender is either an admin or wearer or a hat, and reverts the appropriate error if not
function _checkAdminOrWearer(uint256 _hatId) internal view {
if (!isAdminOfHat(msg.sender, _hatId) && !isWearerOfHat(msg.sender, _hatId)) {
revert NotAdminOrWearer();
}
}
/// @notice Transfers a hat from one wearer to another eligible wearer
/// @dev The hat must be mutable, and the transfer must be initiated by an admin
/// @param _hatId The hat in question
/// @param _from The current wearer
/// @param _to The new wearer
function transferHat(uint256 _hatId, address _from, address _to) public {
_checkAdmin(_hatId);
// cannot transfer immutable hats, except for tophats, which can always transfer themselves
if (!isTopHat(_hatId)) {
if (!_isMutable(_hats[_hatId])) revert Immutable();
}
// Checks storage instead of `isWearerOfHat` since admins may want to transfer revoked Hats to new wearers
if (_staticBalanceOf(_from, _hatId) < 1) revert NotHatWearer();
// Check if recipient is already wearing hat; also checks storage to maintain balance == 1 invariant
if (_staticBalanceOf(_to, _hatId) > 0) revert AlreadyWearingHat(_to, _hatId);
// only eligible wearers can receive transferred hats
if (!isEligible(_to, _hatId)) revert NotEligible();
// only active hats can be transferred
if (!_isActive(_hats[_hatId], _hatId)) revert HatNotActive();
// we've made it passed all the checks, so adjust balances to execute the transfer
_balanceOf[_from][_hatId] = 0;
_balanceOf[_to][_hatId] = 1;
// emit the ERC1155 standard transfer event
emit TransferSingle(msg.sender, _from, _to, _hatId, 1);
}
/// @notice Set a mutable hat to immutable
/// @dev Sets the second bit of hat.config to 0
/// @param _hatId The id of the Hat to make immutable
function makeHatImmutable(uint256 _hatId) external {
_checkAdmin(_hatId);
Hat storage hat = _hats[_hatId];
if (!_isMutable(hat)) {
revert Immutable();
}
hat.config &= ~uint96(1 << 94);
emit HatMutabilityChanged(_hatId);
}
/// @notice Change a hat's details
/// @dev Hat must be mutable, except for tophats.
/// @param _hatId The id of the Hat to change
/// @param _newDetails The new details. Must not be larger than 7000 bytes.
function changeHatDetails(uint256 _hatId, string calldata _newDetails) external {
if (bytes(_newDetails).length > 7000) revert StringTooLong();
_checkAdmin(_hatId);
Hat storage hat = _hats[_hatId];
// a tophat can change its own details, but otherwise only mutable hat details can be changed
if (!isTopHat(_hatId)) {
if (!_isMutable(hat)) revert Immutable();
}
hat.details = _newDetails;
emit HatDetailsChanged(_hatId, _newDetails);
}
/// @notice Change a hat's details
/// @dev Hat must be mutable
/// @param _hatId The id of the Hat to change
/// @param _newEligibility The new eligibility module
function changeHatEligibility(uint256 _hatId, address _newEligibility) external {
if (_newEligibility == address(0)) revert ZeroAddress();
_checkAdmin(_hatId);
Hat storage hat = _hats[_hatId];
if (!_isMutable(hat)) {
revert Immutable();
}
hat.eligibility = _newEligibility;
emit HatEligibilityChanged(_hatId, _newEligibility);
}
/// @notice Change a hat's details
/// @dev Hat must be mutable
/// @param _hatId The id of the Hat to change
/// @param _newToggle The new toggle module
function changeHatToggle(uint256 _hatId, address _newToggle) external {
if (_newToggle == address(0)) revert ZeroAddress();
_checkAdmin(_hatId);
Hat storage hat = _hats[_hatId];
if (!_isMutable(hat)) {
revert Immutable();
}
// record hat status from old toggle before changing; ensures smooth transition to new toggle,
// especially in case of switching from mechanistic to humanistic toggle
// a) attempt to retrieve hat status from old toggle
(bool success, bool newStatus) = _pullHatStatus(hat, _hatId);
// b) if succeeded, (ie if old toggle was mechanistic), store the retrieved status
if (success) _processHatStatus(_hatId, newStatus);
// set the new toggle
hat.toggle = _newToggle;
emit HatToggleChanged(_hatId, _newToggle);
}
/// @notice Change a hat's details
/// @dev Hat must be mutable, except for tophats
/// @param _hatId The id of the Hat to change
/// @param _newImageURI The new imageURI. Must not be larger than 7000 bytes.
function changeHatImageURI(uint256 _hatId, string calldata _newImageURI) external {
if (bytes(_newImageURI).length > 7000) revert StringTooLong();
_checkAdmin(_hatId);
Hat storage hat = _hats[_hatId];
// a tophat can change its own imageURI, but otherwise only mutable hat imageURIs can be changed
if (!isTopHat(_hatId)) {
if (!_isMutable(hat)) revert Immutable();
}
hat.imageURI = _newImageURI;
emit HatImageURIChanged(_hatId, _newImageURI);
}
/// @notice Change a hat's details
/// @dev Hat must be mutable; new max supply cannot be less than current supply
/// @param _hatId The id of the Hat to change
/// @param _newMaxSupply The new max supply
function changeHatMaxSupply(uint256 _hatId, uint32 _newMaxSupply) external {
_checkAdmin(_hatId);
Hat storage hat = _hats[_hatId];
if (!_isMutable(hat)) {
revert Immutable();
}
if (_newMaxSupply < hat.supply) {
revert NewMaxSupplyTooLow();
}
if (_newMaxSupply != hat.maxSupply) {
hat.maxSupply = _newMaxSupply;
emit HatMaxSupplyChanged(_hatId, _newMaxSupply);
}
}
/// @notice Submits a request to link a Hat Tree under a parent tree. Requests can be
/// submitted by either...
/// a) the wearer of a topHat, previous to any linkage, or
/// b) the admin(s) of an already-linked topHat (aka tree root), where such a
/// request is to move the tree root to another admin within the same parent
/// tree
/// @dev A topHat can have at most 1 request at a time. Submitting a new request will
/// replace the existing request.
/// @param _topHatDomain The domain of the topHat to link
/// @param _requestedAdminHat The hat that will administer the linked tree
function requestLinkTopHatToTree(uint32 _topHatDomain, uint256 _requestedAdminHat) external {
uint256 fullTopHatId = uint256(_topHatDomain) << 224; // (256 - TOPHAT_ADDRESS_SPACE);
// The wearer of an unlinked tophat is also the admin of same; once a tophat is linked, its wearer is no longer its admin
_checkAdmin(fullTopHatId);
linkedTreeRequests[_topHatDomain] = _requestedAdminHat;
emit TopHatLinkRequested(_topHatDomain, _requestedAdminHat);
}
/// @notice Approve a request to link a Tree under a parent tree, with options to add eligibility or toggle modules and change its metadata
/// @dev Requests can only be approved by wearer or an admin of the `_newAdminHat`, and there
/// can only be one link per tree root at a given time.
/// @param _topHatDomain The 32 bit domain of the topHat to link
/// @param _newAdminHat The hat that will administer the linked tree
/// @param _eligibility Optional new eligibility module for the linked topHat
/// @param _toggle Optional new toggle module for the linked topHat
/// @param _details Optional new details for the linked topHat
/// @param _imageURI Optional new imageURI for the linked topHat
function approveLinkTopHatToTree(
uint32 _topHatDomain,
uint256 _newAdminHat,
address _eligibility,
address _toggle,
string calldata _details,
string calldata _imageURI
) external {
// for everything but the last hat level, check the admin of `_newAdminHat`'s theoretical child hat, since either wearer or admin of `_newAdminHat` can approve
if (getHatLevel(_newAdminHat) < MAX_LEVELS) {
_checkAdmin(buildHatId(_newAdminHat, 1));
} else {
// the above buildHatId trick doesn't work for the last hat level, so we need to explicitly check both admin and wearer in this case
_checkAdminOrWearer(_newAdminHat);
}
// Linkages must be initiated by a request
if (_newAdminHat != linkedTreeRequests[_topHatDomain]) revert LinkageNotRequested();
// remove the request -- ensures all linkages are initialized by unique requests,
// except for relinks (see `relinkTopHatWithinTree`)
delete linkedTreeRequests[_topHatDomain];
// execute the link. Replaces existing link, if any.
_linkTopHatToTree(_topHatDomain, _newAdminHat, _eligibility, _toggle, _details, _imageURI);
}
/**
* @notice Unlink a Tree from the parent tree
* @dev This can only be called by an admin of the tree root. Fails if the topHat to unlink has no non-zero wearer, which can occur if...
* - It's wearer is in badStanding
* - It has been revoked from its wearer (and possibly burned)˘
* - It is not active (ie toggled off)
* @param _topHatDomain The 32 bit domain of the topHat to unlink
* @param _wearer The current wearer of the topHat to unlink
*/
function unlinkTopHatFromTree(uint32 _topHatDomain, address _wearer) external {
uint256 fullTopHatId = uint256(_topHatDomain) << 224; // (256 - TOPHAT_ADDRESS_SPACE);
_checkAdmin(fullTopHatId);
// prevent unlinking if the topHat has no non-zero wearer
// since we cannot search the entire address space for a wearer, we require the caller to provide the wearer
if (_wearer == address(0) || !isWearerOfHat(_wearer, fullTopHatId)) revert HatsErrors.InvalidUnlink();
// execute the unlink
delete linkedTreeAdmins[_topHatDomain];
// remove the request — ensures all linkages are initialized by unique requests
delete linkedTreeRequests[_topHatDomain];
// reset eligibility and storage to defaults for unlinked top hats
Hat storage hat = _hats[fullTopHatId];
delete hat.eligibility;
delete hat.toggle;
emit TopHatLinked(_topHatDomain, 0);
}
/// @notice Move a tree root to a different position within the same parent tree,
/// without a request. Valid destinations include within the same local tree as the origin,
/// or to the local tree of the tippyTopHat. TippyTopHat wearers can bypass this restriction
/// to relink to anywhere in its full tree.
/// @dev Caller must be both an admin tree root and admin or wearer of `_newAdminHat`.
/// @param _topHatDomain The 32 bit domain of the topHat to relink
/// @param _newAdminHat The new admin for the linked tree
/// @param _eligibility Optional new eligibility module for the linked topHat
/// @param _toggle Optional new toggle module for the linked topHat
/// @param _details Optional new details for the linked topHat
/// @param _imageURI Optional new imageURI for the linked topHat
function relinkTopHatWithinTree(
uint32 _topHatDomain,
uint256 _newAdminHat,
address _eligibility,
address _toggle,
string calldata _details,
string calldata _imageURI
) external {
uint256 fullTopHatId = uint256(_topHatDomain) << 224; // (256 - TOPHAT_ADDRESS_SPACE);
// msg.sender being capable of both requesting and approving allows us to skip the request step
_checkAdmin(fullTopHatId); // "requester" must be admin
// "approver" can be wearer or admin
if (getHatLevel(_newAdminHat) < MAX_LEVELS) {
_checkAdmin(buildHatId(_newAdminHat, 1));
} else {
// the above buildHatId trick doesn't work for the last hat level, so we need to explicitly check both admin and wearer in this case
_checkAdminOrWearer(_newAdminHat);
}
// execute the new link, replacing the old link
_linkTopHatToTree(_topHatDomain, _newAdminHat, _eligibility, _toggle, _details, _imageURI);
}
/// @notice Internal function to link a Tree under a parent Tree, with protection against circular linkages and relinking to a separate Tree,
/// with options to add eligibility or toggle modules and change its metadata
/// @dev Linking `_topHatDomain` replaces any existing links
/// @param _topHatDomain The 32 bit domain of the topHat to link
/// @param _newAdminHat The new admin for the linked tree
/// @param _eligibility Optional new eligibility module for the linked topHat
/// @param _toggle Optional new toggle module for the linked topHat
/// @param _details Optional new details for the linked topHat
/// @param _imageURI Optional new imageURI for the linked topHat
function _linkTopHatToTree(
uint32 _topHatDomain,
uint256 _newAdminHat,
address _eligibility,
address _toggle,
string calldata _details,
string calldata _imageURI
) internal {
if (!noCircularLinkage(_topHatDomain, _newAdminHat)) revert CircularLinkage();
{
uint256 linkedAdmin = linkedTreeAdmins[_topHatDomain];
// disallow relinking to separate tree
if (linkedAdmin > 0) {
uint256 tippyTopHat = uint256(getTippyTopHatDomain(_topHatDomain)) << 224;
if (!isWearerOfHat(msg.sender, tippyTopHat)) {
uint256 destLocalTopHat = uint256(_newAdminHat >> 224 << 224); // (256 - TOPHAT_ADDRESS_SPACE);
// for non-tippyTopHat wearers: destination local tophat must be either...
// a) the same as origin local tophat, or
// b) within the tippy top hat's local tree
uint256 originLocalTopHat = linkedAdmin >> 224 << 224; // (256 - TOPHAT_ADDRESS_SPACE);
if (destLocalTopHat != originLocalTopHat && destLocalTopHat != tippyTopHat) {
revert CrossTreeLinkage();
}
// for tippyTopHat weerers: destination must be within the same super tree
} else if (!sameTippyTopHatDomain(_topHatDomain, _newAdminHat)) {
revert CrossTreeLinkage();
}
}
}
// update and log the linked topHat's modules and metadata, if any changes
uint256 topHatId = uint256(_topHatDomain) << 224;
Hat storage hat = _hats[topHatId];
if (_eligibility != address(0)) {
hat.eligibility = _eligibility;
emit HatEligibilityChanged(topHatId, _eligibility);
}
if (_toggle != address(0)) {
hat.toggle = _toggle;
emit HatToggleChanged(topHatId, _toggle);
}
uint256 length = bytes(_details).length;
if (length > 0) {
if (length > 7000) revert StringTooLong();
hat.details = _details;
emit HatDetailsChanged(topHatId, _details);
}
length = bytes(_imageURI).length;
if (length > 0) {
if (length > 7000) revert StringTooLong();
hat.imageURI = _imageURI;
emit HatImageURIChanged(topHatId, _imageURI);
}
// store the new linked admin
linkedTreeAdmins[_topHatDomain] = _newAdminHat;
emit TopHatLinked(_topHatDomain, _newAdminHat);
}
/*//////////////////////////////////////////////////////////////
HATS VIEW FUNCTIONS
//////////////////////////////////////////////////////////////*/
/// @notice View the properties of a given Hat
/// @param _hatId The id of the Hat
/// @return details The details of the Hat
/// @return maxSupply The max supply of tokens for this Hat
/// @return supply The number of current wearers of this Hat
/// @return eligibility The eligibility address for this Hat
/// @return toggle The toggle address for this Hat
/// @return imageURI The image URI used for this Hat
/// @return lastHatId The most recently created Hat with this Hat as admin; also the count of Hats with this Hat as admin
/// @return mutable_ Whether this hat's properties can be changed
/// @return active Whether the Hat is current active, as read from `_isActive`
function viewHat(uint256 _hatId)
public
view
returns (
string memory details,
uint32 maxSupply,
uint32 supply,
address eligibility,
address toggle,
string memory imageURI,
uint16 lastHatId,
bool mutable_,
bool active
)
{
Hat storage hat = _hats[_hatId];
details = hat.details;
maxSupply = hat.maxSupply;
supply = hat.supply;
eligibility = hat.eligibility;
toggle = hat.toggle;
imageURI = getImageURIForHat(_hatId);
lastHatId = hat.lastHatId;
mutable_ = _isMutable(hat);
active = _isActive(hat, _hatId);
}
/// @notice Checks whether a given address wears a given Hat
/// @dev Convenience function that wraps `balanceOf`
/// @param _user The address in question
/// @param _hatId The id of the Hat that the `_user` might wear
/// @return isWearer Whether the `_user` wears the Hat.
function isWearerOfHat(address _user, uint256 _hatId) public view returns (bool isWearer) {
isWearer = (balanceOf(_user, _hatId) > 0);
}
/// @notice Checks whether a given address serves as the admin of a given Hat
/// @dev Recursively checks if `_user` wears the admin Hat of the Hat in question. This is recursive since there may be a string of Hats as admins of Hats.
/// @param _user The address in question
/// @param _hatId The id of the Hat for which the `_user` might be the admin
/// @return isAdmin Whether the `_user` has admin rights for the Hat
function isAdminOfHat(address _user, uint256 _hatId) public view returns (bool isAdmin) {
uint256 linkedTreeAdmin;
uint32 adminLocalHatLevel;
if (isLocalTopHat(_hatId)) {
linkedTreeAdmin = linkedTreeAdmins[getTopHatDomain(_hatId)];
if (linkedTreeAdmin == 0) {
// tree is not linked
return isAdmin = isWearerOfHat(_user, _hatId);
} else {
// tree is linked
if (isWearerOfHat(_user, linkedTreeAdmin)) {
return isAdmin = true;
} // user wears the treeAdmin
else {
adminLocalHatLevel = getLocalHatLevel(linkedTreeAdmin);
_hatId = linkedTreeAdmin;
}
}
} else {
// if we get here, _hatId is not a tophat of any kind
// get the local tree level of _hatId's admin
adminLocalHatLevel = getLocalHatLevel(_hatId) - 1;
}
// search up _hatId's local address space for an admin hat that the _user wears
while (adminLocalHatLevel > 0) {
if (isWearerOfHat(_user, getAdminAtLocalLevel(_hatId, adminLocalHatLevel))) {
return isAdmin = true;
}
// should not underflow given stopping condition > 0
unchecked {
--adminLocalHatLevel;
}
}
// if we get here, we've reached the top of _hatId's local tree, ie the local tophat
// check if the user wears the local tophat
if (isWearerOfHat(_user, getAdminAtLocalLevel(_hatId, 0))) return isAdmin = true;
// if not, we check if it's linked to another tree
linkedTreeAdmin = linkedTreeAdmins[getTopHatDomain(_hatId)];
if (linkedTreeAdmin == 0) {
// tree is not linked
// we've already learned that user doesn't wear the local tophat, so there's nothing else to check; we return false
return isAdmin = false;
} else {
// tree is linked
// check if user is wearer of linkedTreeAdmin
if (isWearerOfHat(_user, linkedTreeAdmin)) return true;
// if not, recurse to traverse the parent tree for a hat that the user wears