-
Notifications
You must be signed in to change notification settings - Fork 418
/
Copy pathNameWrapper.sol
1180 lines (1032 loc) · 37.6 KB
/
NameWrapper.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.17;
import {ERC1155Fuse, IERC165, IERC1155MetadataURI} from "./ERC1155Fuse.sol";
import {Controllable} from "./Controllable.sol";
import {INameWrapper, CANNOT_UNWRAP, CANNOT_BURN_FUSES, CANNOT_TRANSFER, CANNOT_SET_RESOLVER, CANNOT_SET_TTL, CANNOT_CREATE_SUBDOMAIN, CANNOT_APPROVE, PARENT_CANNOT_CONTROL, CAN_DO_EVERYTHING, IS_DOT_ETH, CAN_EXTEND_EXPIRY, PARENT_CONTROLLED_FUSES, USER_SETTABLE_FUSES} from "./INameWrapper.sol";
import {INameWrapperUpgrade} from "./INameWrapperUpgrade.sol";
import {IMetadataService} from "./IMetadataService.sol";
import {ENS} from "../registry/ENS.sol";
import {IReverseRegistrar} from "../reverseRegistrar/IReverseRegistrar.sol";
import {ReverseClaimer} from "../reverseRegistrar/ReverseClaimer.sol";
import {IBaseRegistrar} from "../ethregistrar/IBaseRegistrar.sol";
import {IERC721Receiver} from "@openzeppelin/contracts/token/ERC721/IERC721Receiver.sol";
import "@openzeppelin/contracts/token/ERC1155/IERC1155.sol";
import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol";
import {BytesUtils} from "./BytesUtils.sol";
import {ERC20Recoverable} from "../utils/ERC20Recoverable.sol";
error Unauthorised(bytes32 node, address addr);
error IncompatibleParent();
error IncorrectTokenType();
error LabelMismatch(bytes32 labelHash, bytes32 expectedLabelhash);
error LabelTooShort();
error LabelTooLong(string label);
error IncorrectTargetOwner(address owner);
error CannotUpgrade();
error OperationProhibited(bytes32 node);
error NameIsNotWrapped();
error NameIsStillExpired();
contract NameWrapper is
Ownable,
ERC1155Fuse,
INameWrapper,
Controllable,
IERC721Receiver,
ERC20Recoverable,
ReverseClaimer
{
using BytesUtils for bytes;
ENS public immutable ens;
IBaseRegistrar public immutable registrar;
IMetadataService public metadataService;
mapping(bytes32 => bytes) public names;
string public constant name = "NameWrapper";
uint64 private constant GRACE_PERIOD = 90 days;
bytes32 private constant ETH_NODE =
0x93cdeb708b7545dc668eb9280176169d1c33cfd8ed6f04690a0bcc88a93fc4ae;
bytes32 private constant ETH_LABELHASH =
0x4f5b812789fc606be1b3b16908db13fc7a9adf7ca72641f84d75b47069d3d7f0;
bytes32 private constant ROOT_NODE =
0x0000000000000000000000000000000000000000000000000000000000000000;
INameWrapperUpgrade public upgradeContract;
uint64 private constant MAX_EXPIRY = type(uint64).max;
constructor(
ENS _ens,
IBaseRegistrar _registrar,
IMetadataService _metadataService
) ReverseClaimer(_ens, msg.sender) {
ens = _ens;
registrar = _registrar;
metadataService = _metadataService;
/* Burn PARENT_CANNOT_CONTROL and CANNOT_UNWRAP fuses for ROOT_NODE and ETH_NODE and set expiry to max */
_setData(
uint256(ETH_NODE),
address(0),
uint32(PARENT_CANNOT_CONTROL | CANNOT_UNWRAP),
MAX_EXPIRY
);
_setData(
uint256(ROOT_NODE),
address(0),
uint32(PARENT_CANNOT_CONTROL | CANNOT_UNWRAP),
MAX_EXPIRY
);
names[ROOT_NODE] = "\x00";
names[ETH_NODE] = "\x03eth\x00";
}
function supportsInterface(
bytes4 interfaceId
) public view virtual override(ERC1155Fuse, INameWrapper) returns (bool) {
return
interfaceId == type(INameWrapper).interfaceId ||
interfaceId == type(IERC721Receiver).interfaceId ||
super.supportsInterface(interfaceId);
}
/* ERC1155 Fuse */
/**
* @notice Gets the owner of a name
* @param id Label as a string of the .eth domain to wrap
* @return owner The owner of the name
*/
function ownerOf(
uint256 id
) public view override(ERC1155Fuse, INameWrapper) returns (address owner) {
return super.ownerOf(id);
}
/**
* @notice Gets the owner of a name
* @param id Namehash of the name
* @return operator Approved operator of a name
*/
function getApproved(
uint256 id
)
public
view
override(ERC1155Fuse, INameWrapper)
returns (address operator)
{
address owner = ownerOf(id);
if (owner == address(0)) {
return address(0);
}
return super.getApproved(id);
}
/**
* @notice Approves an address for a name
* @param to address to approve
* @param tokenId name to approve
*/
function approve(
address to,
uint256 tokenId
) public override(ERC1155Fuse, INameWrapper) {
(, uint32 fuses, ) = getData(tokenId);
if (fuses & CANNOT_APPROVE == CANNOT_APPROVE) {
revert OperationProhibited(bytes32(tokenId));
}
super.approve(to, tokenId);
}
/**
* @notice Gets the data for a name
* @param id Namehash of the name
* @return owner Owner of the name
* @return fuses Fuses of the name
* @return expiry Expiry of the name
*/
function getData(
uint256 id
)
public
view
override(ERC1155Fuse, INameWrapper)
returns (address owner, uint32 fuses, uint64 expiry)
{
(owner, fuses, expiry) = super.getData(id);
(owner, fuses) = _clearOwnerAndFuses(owner, fuses, expiry);
}
/* Metadata service */
/**
* @notice Set the metadata service. Only the owner can do this
* @param _metadataService The new metadata service
*/
function setMetadataService(
IMetadataService _metadataService
) public onlyOwner {
metadataService = _metadataService;
}
/**
* @notice Get the metadata uri
* @param tokenId The id of the token
* @return string uri of the metadata service
*/
function uri(
uint256 tokenId
)
public
view
override(INameWrapper, IERC1155MetadataURI)
returns (string memory)
{
return metadataService.uri(tokenId);
}
/**
* @notice Set the address of the upgradeContract of the contract. only admin can do this
* @dev The default value of upgradeContract is the 0 address. Use the 0 address at any time
* to make the contract not upgradable.
* @param _upgradeAddress address of an upgraded contract
*/
function setUpgradeContract(
INameWrapperUpgrade _upgradeAddress
) public onlyOwner {
if (address(upgradeContract) != address(0)) {
registrar.setApprovalForAll(address(upgradeContract), false);
ens.setApprovalForAll(address(upgradeContract), false);
}
upgradeContract = _upgradeAddress;
if (address(upgradeContract) != address(0)) {
registrar.setApprovalForAll(address(upgradeContract), true);
ens.setApprovalForAll(address(upgradeContract), true);
}
}
/**
* @notice Checks if msg.sender is the owner or operator of the owner of a name
* @param node namehash of the name to check
*/
modifier onlyTokenOwner(bytes32 node) {
if (!canModifyName(node, msg.sender)) {
revert Unauthorised(node, msg.sender);
}
_;
}
/**
* @notice Checks if owner or operator of the owner
* @param node namehash of the name to check
* @param addr which address to check permissions for
* @return whether or not is owner or operator
*/
function canModifyName(
bytes32 node,
address addr
) public view returns (bool) {
(address owner, uint32 fuses, uint64 expiry) = getData(uint256(node));
return
(owner == addr || isApprovedForAll(owner, addr)) &&
!_isETH2LDInGracePeriod(fuses, expiry);
}
/**
* @notice Checks if owner/operator or approved by owner
* @param node namehash of the name to check
* @param addr which address to check permissions for
* @return whether or not is owner/operator or approved
*/
function canExtendSubnames(
bytes32 node,
address addr
) public view returns (bool) {
(address owner, uint32 fuses, uint64 expiry) = getData(uint256(node));
return
(owner == addr ||
isApprovedForAll(owner, addr) ||
getApproved(uint256(node)) == addr) &&
!_isETH2LDInGracePeriod(fuses, expiry);
}
/**
* @notice Wraps a .eth domain, creating a new token and sending the original ERC721 token to this contract
* @dev Can be called by the owner of the name on the .eth registrar or an authorised caller on the registrar
* @param label Label as a string of the .eth domain to wrap
* @param wrappedOwner Owner of the name in this contract
* @param ownerControlledFuses Initial owner-controlled fuses to set
* @param resolver Resolver contract address
*/
function wrapETH2LD(
string calldata label,
address wrappedOwner,
uint16 ownerControlledFuses,
address resolver
) public returns (uint64 expiry) {
uint256 tokenId = uint256(keccak256(bytes(label)));
address registrant = registrar.ownerOf(tokenId);
if (
registrant != msg.sender &&
!registrar.isApprovedForAll(registrant, msg.sender)
) {
revert Unauthorised(
_makeNode(ETH_NODE, bytes32(tokenId)),
msg.sender
);
}
// transfer the token from the user to this contract
registrar.transferFrom(registrant, address(this), tokenId);
// transfer the ens record back to the new owner (this contract)
registrar.reclaim(tokenId, address(this));
expiry = uint64(registrar.nameExpires(tokenId)) + GRACE_PERIOD;
_wrapETH2LD(
label,
wrappedOwner,
ownerControlledFuses,
expiry,
resolver
);
}
/**
* @dev Registers a new .eth second-level domain and wraps it.
* Only callable by authorised controllers.
* @param label The label to register (Eg, 'foo' for 'foo.eth').
* @param wrappedOwner The owner of the wrapped name.
* @param duration The duration, in seconds, to register the name for.
* @param resolver The resolver address to set on the ENS registry (optional).
* @param ownerControlledFuses Initial owner-controlled fuses to set
* @return registrarExpiry The expiry date of the new name on the .eth registrar, in seconds since the Unix epoch.
*/
function registerAndWrapETH2LD(
string calldata label,
address wrappedOwner,
uint256 duration,
address resolver,
uint16 ownerControlledFuses
) external onlyController returns (uint256 registrarExpiry) {
uint256 tokenId = uint256(keccak256(bytes(label)));
registrarExpiry = registrar.register(tokenId, address(this), duration);
_wrapETH2LD(
label,
wrappedOwner,
ownerControlledFuses,
uint64(registrarExpiry) + GRACE_PERIOD,
resolver
);
}
/**
* @notice Renews a .eth second-level domain.
* @dev Only callable by authorised controllers.
* @param tokenId The hash of the label to register (eg, `keccak256('foo')`, for 'foo.eth').
* @param duration The number of seconds to renew the name for.
* @return expires The expiry date of the name on the .eth registrar, in seconds since the Unix epoch.
*/
function renew(
uint256 tokenId,
uint256 duration
) external onlyController returns (uint256 expires) {
bytes32 node = _makeNode(ETH_NODE, bytes32(tokenId));
uint256 registrarExpiry = registrar.renew(tokenId, duration);
// Do not set anything in wrapper if name is not wrapped
try registrar.ownerOf(tokenId) returns (address registrarOwner) {
if (
registrarOwner != address(this) ||
ens.owner(node) != address(this)
) {
return registrarExpiry;
}
} catch {
return registrarExpiry;
}
// Set expiry in Wrapper
uint64 expiry = uint64(registrarExpiry) + GRACE_PERIOD;
// Use super to allow names expired on the wrapper, but not expired on the registrar to renew()
(address owner, uint32 fuses, ) = super.getData(uint256(node));
_setData(node, owner, fuses, expiry);
return registrarExpiry;
}
/**
* @notice Wraps a non .eth domain, of any kind. Could be a DNSSEC name vitalik.xyz or a subdomain
* @dev Can be called by the owner in the registry or an authorised caller in the registry
* @param name The name to wrap, in DNS format
* @param wrappedOwner Owner of the name in this contract
* @param resolver Resolver contract
*/
function wrap(
bytes calldata name,
address wrappedOwner,
address resolver
) public {
(bytes32 labelhash, uint256 offset) = name.readLabel(0);
bytes32 parentNode = name.namehash(offset);
bytes32 node = _makeNode(parentNode, labelhash);
names[node] = name;
if (parentNode == ETH_NODE) {
revert IncompatibleParent();
}
address owner = ens.owner(node);
if (owner != msg.sender && !ens.isApprovedForAll(owner, msg.sender)) {
revert Unauthorised(node, msg.sender);
}
if (resolver != address(0)) {
ens.setResolver(node, resolver);
}
ens.setOwner(node, address(this));
_wrap(node, name, wrappedOwner, 0, 0);
}
/**
* @notice Unwraps a .eth domain. e.g. vitalik.eth
* @dev Can be called by the owner in the wrapper or an authorised caller in the wrapper
* @param labelhash Labelhash of the .eth domain
* @param registrant Sets the owner in the .eth registrar to this address
* @param controller Sets the owner in the registry to this address
*/
function unwrapETH2LD(
bytes32 labelhash,
address registrant,
address controller
) public onlyTokenOwner(_makeNode(ETH_NODE, labelhash)) {
if (registrant == address(this)) {
revert IncorrectTargetOwner(registrant);
}
_unwrap(_makeNode(ETH_NODE, labelhash), controller);
registrar.safeTransferFrom(
address(this),
registrant,
uint256(labelhash)
);
}
/**
* @notice Unwraps a non .eth domain, of any kind. Could be a DNSSEC name vitalik.xyz or a subdomain
* @dev Can be called by the owner in the wrapper or an authorised caller in the wrapper
* @param parentNode Parent namehash of the name e.g. vitalik.xyz would be namehash('xyz')
* @param labelhash Labelhash of the name, e.g. vitalik.xyz would be keccak256('vitalik')
* @param controller Sets the owner in the registry to this address
*/
function unwrap(
bytes32 parentNode,
bytes32 labelhash,
address controller
) public onlyTokenOwner(_makeNode(parentNode, labelhash)) {
if (parentNode == ETH_NODE) {
revert IncompatibleParent();
}
if (controller == address(0x0) || controller == address(this)) {
revert IncorrectTargetOwner(controller);
}
_unwrap(_makeNode(parentNode, labelhash), controller);
}
/**
* @notice Sets fuses of a name
* @param node Namehash of the name
* @param ownerControlledFuses Owner-controlled fuses to burn
* @return Old fuses
*/
function setFuses(
bytes32 node,
uint16 ownerControlledFuses
)
public
onlyTokenOwner(node)
operationAllowed(node, CANNOT_BURN_FUSES)
returns (uint32)
{
// owner protected by onlyTokenOwner
(address owner, uint32 oldFuses, uint64 expiry) = getData(
uint256(node)
);
_setFuses(node, owner, ownerControlledFuses | oldFuses, expiry, expiry);
return oldFuses;
}
/**
* @notice Extends expiry for a name
* @param parentNode Parent namehash of the name e.g. vitalik.xyz would be namehash('xyz')
* @param labelhash Labelhash of the name, e.g. vitalik.xyz would be keccak256('vitalik')
* @param expiry When the name will expire in seconds since the Unix epoch
* @return New expiry
*/
function extendExpiry(
bytes32 parentNode,
bytes32 labelhash,
uint64 expiry
) public returns (uint64) {
bytes32 node = _makeNode(parentNode, labelhash);
if (!_isWrapped(node)) {
revert NameIsNotWrapped();
}
// this flag is used later, when checking fuses
bool canExtendSubname = canExtendSubnames(parentNode, msg.sender);
// only allow the owner of the name or owner of the parent name
if (!canExtendSubname && !canModifyName(node, msg.sender)) {
revert Unauthorised(node, msg.sender);
}
(address owner, uint32 fuses, uint64 oldExpiry) = getData(
uint256(node)
);
// Either CAN_EXTEND_EXPIRY must be set, or the caller must have permission to modify the parent name
if (!canExtendSubname && fuses & CAN_EXTEND_EXPIRY == 0) {
revert OperationProhibited(node);
}
// Max expiry is set to the expiry of the parent
(, , uint64 maxExpiry) = getData(uint256(parentNode));
expiry = _normaliseExpiry(expiry, oldExpiry, maxExpiry);
_setData(node, owner, fuses, expiry);
emit ExpiryExtended(node, expiry);
return expiry;
}
/**
* @notice Upgrades a domain of any kind. Could be a .eth name vitalik.eth, a DNSSEC name vitalik.xyz, or a subdomain
* @dev Can be called by the owner or an authorised caller
* @param name The name to upgrade, in DNS format
* @param extraData Extra data to pass to the upgrade contract
*/
function upgrade(bytes calldata name, bytes calldata extraData) public {
bytes32 node = name.namehash(0);
if (address(upgradeContract) == address(0)) {
revert CannotUpgrade();
}
if (!canModifyName(node, msg.sender)) {
revert Unauthorised(node, msg.sender);
}
(address currentOwner, uint32 fuses, uint64 expiry) = getData(
uint256(node)
);
address approved = getApproved(uint256(node));
_burn(uint256(node));
upgradeContract.wrapFromUpgrade(
name,
currentOwner,
fuses,
expiry,
approved,
extraData
);
}
/**
/* @notice Sets fuses of a name that you own the parent of
* @param parentNode Parent namehash of the name e.g. vitalik.xyz would be namehash('xyz')
* @param labelhash Labelhash of the name, e.g. vitalik.xyz would be keccak256('vitalik')
* @param fuses Fuses to burn
* @param expiry When the name will expire in seconds since the Unix epoch
*/
function setChildFuses(
bytes32 parentNode,
bytes32 labelhash,
uint32 fuses,
uint64 expiry
) public {
bytes32 node = _makeNode(parentNode, labelhash);
_checkFusesAreSettable(node, fuses);
(address owner, uint32 oldFuses, uint64 oldExpiry) = getData(
uint256(node)
);
if (owner == address(0) || ens.owner(node) != address(this)) {
revert NameIsNotWrapped();
}
// max expiry is set to the expiry of the parent
(, uint32 parentFuses, uint64 maxExpiry) = getData(uint256(parentNode));
if (parentNode == ROOT_NODE) {
if (!canModifyName(node, msg.sender)) {
revert Unauthorised(node, msg.sender);
}
} else {
if (!canModifyName(parentNode, msg.sender)) {
revert Unauthorised(parentNode, msg.sender);
}
}
_checkParentFuses(node, fuses, parentFuses);
expiry = _normaliseExpiry(expiry, oldExpiry, maxExpiry);
// if PARENT_CANNOT_CONTROL has been burned and fuses have changed
if (
oldFuses & PARENT_CANNOT_CONTROL != 0 &&
oldFuses | fuses != oldFuses
) {
revert OperationProhibited(node);
}
fuses |= oldFuses;
_setFuses(node, owner, fuses, oldExpiry, expiry);
}
/**
* @notice Sets the subdomain owner in the registry and then wraps the subdomain
* @param parentNode Parent namehash of the subdomain
* @param label Label of the subdomain as a string
* @param owner New owner in the wrapper
* @param fuses Initial fuses for the wrapped subdomain
* @param expiry When the name will expire in seconds since the Unix epoch
* @return node Namehash of the subdomain
*/
function setSubnodeOwner(
bytes32 parentNode,
string calldata label,
address owner,
uint32 fuses,
uint64 expiry
) public onlyTokenOwner(parentNode) returns (bytes32 node) {
bytes32 labelhash = keccak256(bytes(label));
node = _makeNode(parentNode, labelhash);
_checkCanCallSetSubnodeOwner(parentNode, node);
_checkFusesAreSettable(node, fuses);
bytes memory name = _saveLabel(parentNode, node, label);
expiry = _checkParentFusesAndExpiry(parentNode, node, fuses, expiry);
if (!_isWrapped(node)) {
ens.setSubnodeOwner(parentNode, labelhash, address(this));
_wrap(node, name, owner, fuses, expiry);
} else {
_updateName(parentNode, node, label, owner, fuses, expiry);
}
}
/**
* @notice Sets the subdomain owner in the registry with records and then wraps the subdomain
* @param parentNode parent namehash of the subdomain
* @param label label of the subdomain as a string
* @param owner new owner in the wrapper
* @param resolver resolver contract in the registry
* @param ttl ttl in the registry
* @param fuses initial fuses for the wrapped subdomain
* @param expiry When the name will expire in seconds since the Unix epoch
* @return node Namehash of the subdomain
*/
function setSubnodeRecord(
bytes32 parentNode,
string memory label,
address owner,
address resolver,
uint64 ttl,
uint32 fuses,
uint64 expiry
) public onlyTokenOwner(parentNode) returns (bytes32 node) {
bytes32 labelhash = keccak256(bytes(label));
node = _makeNode(parentNode, labelhash);
_checkCanCallSetSubnodeOwner(parentNode, node);
_checkFusesAreSettable(node, fuses);
_saveLabel(parentNode, node, label);
expiry = _checkParentFusesAndExpiry(parentNode, node, fuses, expiry);
if (!_isWrapped(node)) {
ens.setSubnodeRecord(
parentNode,
labelhash,
address(this),
resolver,
ttl
);
_storeNameAndWrap(parentNode, node, label, owner, fuses, expiry);
} else {
ens.setSubnodeRecord(
parentNode,
labelhash,
address(this),
resolver,
ttl
);
_updateName(parentNode, node, label, owner, fuses, expiry);
}
}
/**
* @notice Sets records for the name in the ENS Registry
* @param node Namehash of the name to set a record for
* @param owner New owner in the registry
* @param resolver Resolver contract
* @param ttl Time to live in the registry
*/
function setRecord(
bytes32 node,
address owner,
address resolver,
uint64 ttl
)
public
onlyTokenOwner(node)
operationAllowed(
node,
CANNOT_TRANSFER | CANNOT_SET_RESOLVER | CANNOT_SET_TTL
)
{
ens.setRecord(node, address(this), resolver, ttl);
if (owner == address(0)) {
(, uint32 fuses, ) = getData(uint256(node));
if (fuses & IS_DOT_ETH == IS_DOT_ETH) {
revert IncorrectTargetOwner(owner);
}
_unwrap(node, address(0));
} else {
address oldOwner = ownerOf(uint256(node));
_transfer(oldOwner, owner, uint256(node), 1, "");
}
}
/**
* @notice Sets resolver contract in the registry
* @param node namehash of the name
* @param resolver the resolver contract
*/
function setResolver(
bytes32 node,
address resolver
) public onlyTokenOwner(node) operationAllowed(node, CANNOT_SET_RESOLVER) {
ens.setResolver(node, resolver);
}
/**
* @notice Sets TTL in the registry
* @param node Namehash of the name
* @param ttl TTL in the registry
*/
function setTTL(
bytes32 node,
uint64 ttl
) public onlyTokenOwner(node) operationAllowed(node, CANNOT_SET_TTL) {
ens.setTTL(node, ttl);
}
/**
* @dev Allows an operation only if none of the specified fuses are burned.
* @param node The namehash of the name to check fuses on.
* @param fuseMask A bitmask of fuses that must not be burned.
*/
modifier operationAllowed(bytes32 node, uint32 fuseMask) {
(, uint32 fuses, ) = getData(uint256(node));
if (fuses & fuseMask != 0) {
revert OperationProhibited(node);
}
_;
}
/**
* @notice Check whether a name can call setSubnodeOwner/setSubnodeRecord
* @dev Checks both CANNOT_CREATE_SUBDOMAIN and PARENT_CANNOT_CONTROL and whether not they have been burnt
* and checks whether the owner of the subdomain is 0x0 for creating or already exists for
* replacing a subdomain. If either conditions are true, then it is possible to call
* setSubnodeOwner
* @param parentNode Namehash of the parent name to check
* @param subnode Namehash of the subname to check
*/
function _checkCanCallSetSubnodeOwner(
bytes32 parentNode,
bytes32 subnode
) internal view {
(
address subnodeOwner,
uint32 subnodeFuses,
uint64 subnodeExpiry
) = getData(uint256(subnode));
// check if the registry owner is 0 and expired
// check if the wrapper owner is 0 and expired
// If either, then check parent fuses for CANNOT_CREATE_SUBDOMAIN
bool expired = subnodeExpiry < block.timestamp;
if (
expired &&
// protects a name that has been unwrapped with PCC and doesn't allow the parent to take control by recreating it if unexpired
(subnodeOwner == address(0) ||
// protects a name that has been burnt and doesn't allow the parent to take control by recreating it if unexpired
ens.owner(subnode) == address(0))
) {
(, uint32 parentFuses, ) = getData(uint256(parentNode));
if (parentFuses & CANNOT_CREATE_SUBDOMAIN != 0) {
revert OperationProhibited(subnode);
}
} else {
if (subnodeFuses & PARENT_CANNOT_CONTROL != 0) {
revert OperationProhibited(subnode);
}
}
}
/**
* @notice Checks all Fuses in the mask are burned for the node
* @param node Namehash of the name
* @param fuseMask The fuses you want to check
* @return Boolean of whether or not all the selected fuses are burned
*/
function allFusesBurned(
bytes32 node,
uint32 fuseMask
) public view returns (bool) {
(, uint32 fuses, ) = getData(uint256(node));
return fuses & fuseMask == fuseMask;
}
/**
* @notice Checks if a name is wrapped
* @param node Namehash of the name
* @return Boolean of whether or not the name is wrapped
*/
function isWrapped(bytes32 node) public view returns (bool) {
bytes memory name = names[node];
if (name.length == 0) {
return false;
}
(bytes32 labelhash, uint256 offset) = name.readLabel(0);
bytes32 parentNode = name.namehash(offset);
return isWrapped(parentNode, labelhash);
}
/**
* @notice Checks if a name is wrapped in a more gas efficient way
* @param parentNode Namehash of the name
* @param labelhash Namehash of the name
* @return Boolean of whether or not the name is wrapped
*/
function isWrapped(
bytes32 parentNode,
bytes32 labelhash
) public view returns (bool) {
bytes32 node = _makeNode(parentNode, labelhash);
bool wrapped = _isWrapped(node);
if (parentNode != ETH_NODE) {
return wrapped;
}
try registrar.ownerOf(uint256(labelhash)) returns (address owner) {
return owner == address(this);
} catch {
return false;
}
}
function onERC721Received(
address to,
address,
uint256 tokenId,
bytes calldata data
) public returns (bytes4) {
//check if it's the eth registrar ERC721
if (msg.sender != address(registrar)) {
revert IncorrectTokenType();
}
(
string memory label,
address owner,
uint16 ownerControlledFuses,
address resolver
) = abi.decode(data, (string, address, uint16, address));
bytes32 labelhash = bytes32(tokenId);
bytes32 labelhashFromData = keccak256(bytes(label));
if (labelhashFromData != labelhash) {
revert LabelMismatch(labelhashFromData, labelhash);
}
// transfer the ens record back to the new owner (this contract)
registrar.reclaim(uint256(labelhash), address(this));
uint64 expiry = uint64(registrar.nameExpires(tokenId)) + GRACE_PERIOD;
_wrapETH2LD(label, owner, ownerControlledFuses, expiry, resolver);
return IERC721Receiver(to).onERC721Received.selector;
}
/***** Internal functions */
function _beforeTransfer(
uint256 id,
uint32 fuses,
uint64 expiry
) internal override {
// For this check, treat .eth 2LDs as expiring at the start of the grace period.
if (fuses & IS_DOT_ETH == IS_DOT_ETH) {
expiry -= GRACE_PERIOD;
}
if (expiry < block.timestamp) {
// Transferable if the name was not emancipated
if (fuses & PARENT_CANNOT_CONTROL != 0) {
revert("ERC1155: insufficient balance for transfer");
}
} else {
// Transferable if CANNOT_TRANSFER is unburned
if (fuses & CANNOT_TRANSFER != 0) {
revert OperationProhibited(bytes32(id));
}
}
// delete token approval if CANNOT_APPROVE has not been burnt
if (fuses & CANNOT_APPROVE == 0) {
delete _tokenApprovals[id];
}
}
function _clearOwnerAndFuses(
address owner,
uint32 fuses,
uint64 expiry
) internal view override returns (address, uint32) {
if (expiry < block.timestamp) {
if (fuses & PARENT_CANNOT_CONTROL == PARENT_CANNOT_CONTROL) {
owner = address(0);
}
fuses = 0;
}
return (owner, fuses);
}
function _makeNode(
bytes32 node,
bytes32 labelhash
) private pure returns (bytes32) {
return keccak256(abi.encodePacked(node, labelhash));
}
function _addLabel(
string memory label,
bytes memory name
) internal pure returns (bytes memory ret) {
if (bytes(label).length < 1) {
revert LabelTooShort();
}
if (bytes(label).length > 255) {
revert LabelTooLong(label);
}
return abi.encodePacked(uint8(bytes(label).length), label, name);
}
function _mint(
bytes32 node,
address owner,
uint32 fuses,
uint64 expiry
) internal override {
_canFusesBeBurned(node, fuses);
(address oldOwner, , ) = super.getData(uint256(node));
if (oldOwner != address(0)) {
// burn and unwrap old token of old owner
_burn(uint256(node));
emit NameUnwrapped(node, address(0));
}
super._mint(node, owner, fuses, expiry);
}
function _wrap(
bytes32 node,
bytes memory name,
address wrappedOwner,
uint32 fuses,
uint64 expiry
) internal {
_mint(node, wrappedOwner, fuses, expiry);
emit NameWrapped(node, name, wrappedOwner, fuses, expiry);
}
function _storeNameAndWrap(
bytes32 parentNode,
bytes32 node,
string memory label,
address owner,
uint32 fuses,