-
Notifications
You must be signed in to change notification settings - Fork 8
/
Copy pathDCLRegistrar.sol
523 lines (444 loc) · 18.1 KB
/
DCLRegistrar.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
pragma solidity ^0.5.15;
import "@openzeppelin/contracts/ownership/Ownable.sol";
import "@openzeppelin/contracts/token/ERC721/ERC721Full.sol";
import "@openzeppelin/contracts/utils/Address.sol";
import "../interfaces/IENSRegistry.sol";
import "../interfaces/IENSResolver.sol";
import "../interfaces/IBaseRegistrar.sol";
import "../interfaces/IERC20Token.sol";
contract DCLRegistrar is ERC721Full, Ownable {
using Address for address;
bytes4 public constant ERC721_RECEIVED = 0x150b7a02;
// The ENS registry
IENSRegistry public registry;
// The ENS base registrar
IBaseRegistrar public base;
// A map of addresses that are authorised to register and renew names.
mapping(address => bool) public controllers;
// Empty hash
bytes32 emptyNamehash = 0x00;
// Top domain e.g: eth
string public topdomain;
// Domain e.g: dcl
string public domain;
// Top domain hash
bytes32 public topdomainNameHash;
// Domain hash
bytes32 public domainNameHash;
// Base URI
string public baseURI;
// Whether the migration of v1 names has finished or not
bool public migrated;
// A map of subdomain hashes to its string for reverse lookup
mapping (bytes32 => string) public subdomains;
// Emitted when a new name is registered
event NameRegistered(
address indexed _caller,
address indexed _beneficiary,
bytes32 indexed _labelHash,
string _subdomain,
uint256 _createdDate
);
// Emitted when a user reclaim a subdomain to the ENS Registry
event Reclaimed(address indexed _caller, address indexed _owner, uint256 indexed _tokenId);
// Emitted when the owner of the contract reclaim the domain to the ENS Registry
event DomainReclaimed(uint256 indexed _tokenId);
// Emitted when the domain was transferred
event DomainTransferred(address indexed _newOwner, uint256 indexed _tokenId);
// Emitted when the registry was updated
event RegistryUpdated(IENSRegistry indexed _previousRegistry, IENSRegistry indexed _newRegistry);
// Emitted when the base was updated
event BaseUpdated(IBaseRegistrar indexed _previousBase, IBaseRegistrar indexed _newBase);
// Emitted when a controller was added
event ControllerAdded(address indexed _controller);
// Emitted when a controller was removed
event ControllerRemoved(address indexed _controller);
// Emitted when the migration was finished
event MigrationFinished();
// Emitted when base URI is was changed
event BaseURI(string _oldBaseURI, string _newBaseURI);
// Emit when the resolver is set to the owned domain
event ResolverUpdated(address indexed _oldResolver, address indexed _newResolver);
// Emit when a call is forwarred to the resolver
event CallForwarwedToResolver(address indexed _resolver, bytes _data, bytes res);
/**
* @dev Check if the sender is an authorized controller
*/
modifier onlyController() {
require(controllers[msg.sender], "Only a controller can call this method");
_;
}
/**
* @dev Check if the migration is pending
*/
modifier isNotMigrated() {
require(!migrated, "The migration has finished");
_;
}
/**
* @dev Check if the migration is completed
*/
modifier isMigrated() {
require(migrated, "The migration has not finished");
_;
}
/**
* @dev Constructor of the contract
* @param _registry - address of the ENS registry contract
* @param _base - address of the ENS base registrar contract
* @param _topdomain - top domain (e.g. "eth")
* @param _domain - domain (e.g. "dcl")
* @param _baseURI - base URI for token URIs
*/
constructor(
IENSRegistry _registry,
IBaseRegistrar _base,
string memory _topdomain,
string memory _domain,
string memory _baseURI
) public ERC721Full("DCL Registrar", "DCLENS") {
// ENS registry
updateRegistry(_registry);
// ENS base registrar
updateBase(_base);
// Top domain string
require(bytes(_topdomain).length > 0, "Top domain can not be empty");
topdomain = _topdomain;
// Domain string
require(bytes(_domain).length > 0, "Domain can not be empty");
domain = _domain;
// Generate namehash for the top domain
topdomainNameHash = keccak256(abi.encodePacked(emptyNamehash, keccak256(abi.encodePacked(topdomain))));
// Generate namehash for the domain
domainNameHash = keccak256(abi.encodePacked(topdomainNameHash, keccak256(abi.encodePacked(domain))));
// Set base URI
updateBaseURI(_baseURI);
}
/**
* @dev Migrate names from v1
* @param _names - array of names
* @param _beneficiaries - array of beneficiaries
* @param _createdDates - array of created dates
*/
function migrateNames(
bytes32[] calldata _names,
address[] calldata _beneficiaries,
uint256[] calldata _createdDates
) external onlyOwner isNotMigrated {
for (uint256 i = 0; i < _names.length; i++) {
string memory name = _bytes32ToString(_names[i]);
_register(
name,
keccak256(abi.encodePacked(_toLowerCase(name))),
_beneficiaries[i],
_createdDates[i]
);
}
}
/**
* @dev Allows to create a subdomain (e.g. "nacho.dcl.eth"), set its resolver, owner and target address
* @param _subdomain - subdomain (e.g. "nacho")
* @param _beneficiary - address that will become owner of this new subdomain
*/
function register(
string calldata _subdomain,
address _beneficiary
) external onlyController isMigrated {
// Make sure this contract owns the domain
_checkOwnerOfDomain();
// Create labelhash for the subdomain
bytes32 subdomainLabelHash = keccak256(abi.encodePacked(_toLowerCase(_subdomain)));
// Make sure it is free
require(_available(subdomainLabelHash), "Subdomain already owned");
// solium-disable-next-line security/no-block-members
_register(_subdomain, subdomainLabelHash, _beneficiary, now);
}
/**
* @dev Internal function to register a subdomain
* @param _subdomain - subdomain (e.g. "nacho")
* @param subdomainLabelHash - hash of the subdomain
* @param _beneficiary - address that will become owner of this new subdomain
*/
function _register(
string memory _subdomain,
bytes32 subdomainLabelHash,
address _beneficiary,
uint256 _createdDate
) internal {
// Create new subdomain and assign the _beneficiary as the owner
registry.setSubnodeOwner(domainNameHash, subdomainLabelHash, _beneficiary);
// Mint an ERC721 token with the sud domain label hash as its id
_mint(_beneficiary, uint256(subdomainLabelHash));
// Map the ERC721 token id with the subdomain for reversion.
subdomains[subdomainLabelHash] = _subdomain;
// Emit registered name event
emit NameRegistered(msg.sender, _beneficiary, subdomainLabelHash, _subdomain, _createdDate);
}
/**
* @dev Re-claim the ownership of a subdomain (e.g. "nacho").
* @notice After a subdomain is transferred by this contract, the owner in the ENS registry contract
* is still the old owner. Therefore, the owner should call `reclaim` to update the owner of the subdomain.
* It is also useful to recreate the subdomains in case of an ENS migration.
* @param _tokenId - erc721 token id which represents the node (subdomain).
*/
function reclaim(uint256 _tokenId) public onlyController {
address owner = ownerOf(_tokenId);
registry.setSubnodeOwner(domainNameHash, bytes32(_tokenId), ownerOf(_tokenId));
emit Reclaimed(msg.sender, owner, _tokenId);
}
/**
* @dev Re-claim the ownership of a subdomain (e.g. "nacho").
* @notice After a subdomain is transferred by this contract, the owner in the ENS registry contract
* is still the old owner. Therefore, the owner should call `reclaim` to update the owner of the subdomain.
* It is also useful to recreate the subdomains in case of an ENS migration.
* @param _tokenId - erc721 token id which represents the node (subdomain).
* @param _owner - new owner.
*/
function reclaim(uint256 _tokenId, address _owner) public {
// Check if the sender is authorized to manage the subdomain
require(
_isApprovedOrOwner(msg.sender, _tokenId),
"Only an authorized account can change the subdomain settings"
);
registry.setSubnodeOwner(domainNameHash, bytes32(_tokenId), _owner);
emit Reclaimed(msg.sender, _owner, _tokenId);
}
/**
* @dev The ERC721 smart contract calls this function on the recipient
* after a `safetransfer`. This function MAY throw to revert and reject the
* transfer. Return of other than the magic value MUST result in the
* transaction being reverted.
* Note: the contract address is always the message sender.
* @notice Handle the receipt of an NFT. Used to re-claim ownership at the ENS registry contract
* @param _tokenId The NFT identifier which is being transferred
* @return `bytes4(keccak256("onERC721Received(address,address,uint256,bytes)"))`
*/
function onERC721Received(
address /* _operator */,
address /* _from */,
uint256 _tokenId,
bytes memory /* _data */
)
public
returns (bytes4)
{
require(msg.sender == address(base), "Only base can send NFTs to this contract");
// Re-claim to update the owner at the ENS Registry
base.reclaim(_tokenId, address(this));
return ERC721_RECEIVED;
}
/**
* @dev Check whether a name is available to be registered or not
* @param _subdomain - name to check
* @return whether the name is available or not
*/
function available(string memory _subdomain) public view returns (bool) {
// Create labelhash for the subdomain
bytes32 subdomainLabelHash = keccak256(abi.encodePacked(_toLowerCase(_subdomain)));
return _available(subdomainLabelHash);
}
/**
* @dev Check whether a name is available to be registered or not
* @param _subdomainLabelHash - hash of the name to check
* @return whether the name is available or not
*/
function _available(bytes32 _subdomainLabelHash) internal view returns (bool) {
// Create namehash for the subdomain (node)
bytes32 subdomainNameHash = keccak256(abi.encodePacked(domainNameHash, _subdomainLabelHash));
// Make sure it is free
return registry.owner(subdomainNameHash) == address(0) && !_exists(uint256(_subdomainLabelHash));
}
/**
* @dev Get the token id by its subdomain
* @param _subdomain - string of the subdomain
* @return token id mapped to the subdomain
*/
function getTokenId(string memory _subdomain) public view returns (uint256) {
string memory subdomain = _toLowerCase(_subdomain);
bytes32 subdomainLabelHash = keccak256(abi.encodePacked(subdomain));
uint256 tokenId = uint256(subdomainLabelHash);
require(
_exists(tokenId),
"The subdomain is not registered"
);
return tokenId;
}
/**
* @dev Get the owner of a subdomain
* @param _subdomain - string of the subdomain
* @return owner of the subdomain
*/
function getOwnerOf(string memory _subdomain) public view returns (address) {
return ownerOf(getTokenId(_subdomain));
}
/**
* @dev Returns an URI for a given token ID.
* @notice that throws if the token ID does not exist. May return an empty string.
* Also, if baseURI is empty, an empty string will be returned.
* @param _tokenId - uint256 ID of the token queried
* @return token URI
*/
function tokenURI(uint256 _tokenId) external view returns (string memory) {
if (bytes(baseURI).length == 0) {
return "";
}
require(_exists(_tokenId), "ERC721Metadata: received a URI query for a nonexistent token");
return string(abi.encodePacked(baseURI, _toLowerCase(subdomains[bytes32(_tokenId)])));
}
/**
* @dev Re-claim the ownership of the domain (e.g. "dcl")
* @notice After a domain is transferred by the ENS base
* registrar to this contract, the owner in the ENS registry contract
* is still the old owner. Therefore, the owner should call `reclaimDomain`
* to update the owner of the domain
* @param _tokenId - erc721 token id which represents the node (domain)
*/
function reclaimDomain(uint256 _tokenId) public onlyOwner {
base.reclaim(_tokenId, address(this));
emit DomainReclaimed(_tokenId);
}
/**
* @dev The contract owner can take away the ownership of any domain owned by this contract
* @param _owner - new owner for the domain
* @param _tokenId - erc721 token id which represents the node (domain)
*/
function transferDomainOwnership(address _owner, uint256 _tokenId) public onlyOwner {
base.transferFrom(address(this), _owner, _tokenId);
emit DomainTransferred(_owner, _tokenId);
}
/**
* @dev Update owned domain resolver
* @param _resolver - new resolver
*/
function setResolver(address _resolver) public onlyOwner {
address resolver = registry.resolver(domainNameHash);
require(_resolver.isContract(), "New resolver should be a contract");
require(_resolver != resolver, "New resolver should be different from old");
_checkNotAllowedAddresses(_resolver);
registry.setResolver(domainNameHash, _resolver);
emit ResolverUpdated(resolver, _resolver);
}
/**
* @dev Forward calls to resolver
* @param _data - data to be send in the call
*/
function forwardToResolver(bytes memory _data) public onlyOwner {
address resolver = registry.resolver(domainNameHash);
_checkNotAllowedAddresses(resolver);
(bool success, bytes memory res) = resolver.call(_data);
require(success, "Call failed");
// Make sure this contract is still the owner of the domain
_checkOwnerOfDomain();
emit CallForwarwedToResolver(resolver, _data, res);
}
/**
* @dev Authorises a controller, who can register subdomains
* @param controller - address of the controller
*/
function addController(address controller) external onlyOwner {
require(!controllers[controller], "The controller was already added");
controllers[controller] = true;
emit ControllerAdded(controller);
}
/**
* @dev Revoke controller permission for an address
* @param controller - address of the controller
*/
function removeController(address controller) external onlyOwner {
require(controllers[controller], "The controller is already disabled");
controllers[controller] = false;
emit ControllerRemoved(controller);
}
/**
* @dev Update to new ENS registry
* @param _registry The address of new ENS registry to use
*/
function updateRegistry(IENSRegistry _registry) public onlyOwner {
require(registry != _registry, "New registry should be different from old");
require(address(_registry).isContract(), "New registry should be a contract");
emit RegistryUpdated(registry, _registry);
registry = _registry;
}
/**
* @dev Update to new ENS base registrar
* @param _base The address of new ENS base registrar to use
*/
function updateBase(IBaseRegistrar _base) public onlyOwner {
require(base != _base, "New base should be different from old");
require(address(_base).isContract(), "New base should be a contract");
emit BaseUpdated(base, _base);
base = _base;
}
/**
* @dev Set Base URI.
* @param _baseURI - base URI for token URIs
*/
function updateBaseURI(string memory _baseURI) public onlyOwner {
require(
keccak256(abi.encodePacked((baseURI))) != keccak256(abi.encodePacked((_baseURI))),
"Base URI should be different from old"
);
emit BaseURI(baseURI, _baseURI);
baseURI = _baseURI;
}
/**
* @dev Set the migration as finished
*/
function migrationFinished() external onlyOwner isNotMigrated {
migrated = true;
emit MigrationFinished();
}
function _checkOwnerOfDomain() internal view {
require(
registry.owner(domainNameHash) == address(this) &&
base.ownerOf(uint256(keccak256(abi.encodePacked(domain)))) == address(this),
"The contract does not own the domain"
);
}
function _checkNotAllowedAddresses(address _address) internal view {
require(
_address != address(base) && _address != address(registry) && _address != address(this),
"Invalid address"
);
}
/**
* @dev Convert bytes32 to string.
* @param _x - to be converted to string.
* @return string
*/
function _bytes32ToString(bytes32 _x) internal pure returns (string memory) {
uint256 charCount = 0;
for (uint256 j = 0; j <= 256; j += 8) {
byte char = byte(_x << j);
if (char == 0) {
break;
}
charCount++;
}
string memory out = new string(charCount);
// solium-disable-next-line security/no-inline-assembly
assembly {
mstore(add(0x20, out), _x)
}
return out;
}
/**
* @dev Lowercase a string.
* @param _str - to be converted to string.
* @return string
*/
function _toLowerCase(string memory _str) internal pure returns (string memory) {
bytes memory bStr = bytes(_str);
bytes memory bLower = new bytes(bStr.length);
for (uint i = 0; i < bStr.length; i++) {
// Uppercase character...
if ((bStr[i] >= 0x41) && (bStr[i] <= 0x5A)) {
// So we add 0x20 to make it lowercase
bLower[i] = bytes1(uint8(bStr[i]) + 0x20);
} else {
bLower[i] = bStr[i];
}
}
return string(bLower);
}
}