-
Notifications
You must be signed in to change notification settings - Fork 424
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Feature/subdomain registrar #98
base: master
Are you sure you want to change the base?
Changes from 47 commits
fe6f138
dc2aa79
df127ff
82df2e9
433e4e0
06551a2
ae2801d
148767f
7ab5e6e
5438496
d2d036b
c3f1d12
54e3be1
f6e25fa
5e7b143
d014bc9
1a58228
38c713c
8051947
b6b00fc
6696059
b526e3d
16c7698
9b7c958
672d507
26b45ce
199e7f7
c19bf4c
94f5305
dfd00db
7f5c408
9940fbc
faf526d
4b6d4db
ffac7c7
b5717d7
2c55ec6
e1f3155
2f03149
092a374
54566b8
2c9bb8c
8ab89d7
11fbea6
83f25a9
21fa7d8
b5938b1
dfcddd9
ccdb370
1fa3a75
c2cbcac
12b98e9
12ba18d
7e03ce2
2ea9a28
f84565b
c08939b
d9a8457
47c98cd
134d776
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -6,5 +6,5 @@ gasreport-* | |
*.DS_Store | ||
node_modules | ||
build | ||
out/* | ||
forge-cache | ||
out |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,132 @@ | ||
//SPDX-License-Identifier: MIT | ||
pragma solidity ^0.8.17; | ||
import {INameWrapper, PARENT_CANNOT_CONTROL, IS_DOT_ETH} from "../wrapper/INameWrapper.sol"; | ||
import {Address} from "@openzeppelin/contracts/utils/Address.sol"; | ||
|
||
error Unavailable(); | ||
error Unauthorised(bytes32 node); | ||
error InsufficientFunds(); | ||
error NameNotRegistered(); | ||
error InvalidTokenAddress(address); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. There is no test case to cover this error |
||
error NameNotSetup(bytes32 node); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. There is no test case to cover this error |
||
error DataMissing(); | ||
error ParentExpired(bytes32 node); | ||
error ParentNotWrapped(bytes32 node); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. There is no test case to cover this error |
||
error DurationTooLong(bytes32 node); | ||
|
||
abstract contract BaseSubdomainRegistrar { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Please add natspec comment of what this contract does |
||
INameWrapper public immutable wrapper; | ||
using Address for address; | ||
|
||
event NameRegistered(bytes32 node, uint256 expiry); | ||
event NameRenewed(bytes32 node, uint256 expiry); | ||
uint64 private GRACE_PERIOD = 90 days; | ||
|
||
constructor(address _wrapper) { | ||
wrapper = INameWrapper(_wrapper); | ||
} | ||
|
||
modifier onlyOwner(bytes32 node) { | ||
jefflau marked this conversation as resolved.
Show resolved
Hide resolved
|
||
if (!wrapper.canModifyName(node, msg.sender)) { | ||
revert Unauthorised(node); | ||
} | ||
_; | ||
} | ||
modifier canBeRegistered(bytes32 parentNode, uint64 duration) { | ||
_checkParent(parentNode); | ||
_; | ||
} | ||
|
||
function available(bytes32 node) public view virtual returns (bool) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. public functions should have comments in natspec format |
||
try wrapper.getData(uint256(node)) returns ( | ||
address, | ||
uint32, | ||
uint64 expiry | ||
) { | ||
return expiry < block.timestamp; | ||
} catch { | ||
return true; | ||
} | ||
} | ||
|
||
/* Internal Functions */ | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Maybe better to rename as |
||
|
||
function _register( | ||
bytes32 parentNode, | ||
string calldata label, | ||
address newOwner, | ||
address resolver, | ||
uint32 fuses, | ||
uint64 expiry, | ||
bytes[] calldata records | ||
) internal { | ||
bytes32 node = keccak256( | ||
abi.encodePacked(parentNode, keccak256(bytes(label))) | ||
); | ||
|
||
if (!available(node)) { | ||
revert Unavailable(); | ||
} | ||
|
||
if (records.length > 0) { | ||
wrapper.setSubnodeOwner( | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Shouldn't we still set resolver etc if there are records? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We set the resolver in the next function call |
||
parentNode, | ||
label, | ||
address(this), | ||
0, | ||
expiry | ||
); | ||
_setRecords(node, resolver, records); | ||
} | ||
|
||
wrapper.setSubnodeRecord( | ||
parentNode, | ||
label, | ||
newOwner, | ||
resolver, | ||
0, | ||
fuses | PARENT_CANNOT_CONTROL, // burn the ability for the parent to control | ||
expiry | ||
); | ||
|
||
emit NameRegistered(node, expiry); | ||
} | ||
|
||
function _setRecords( | ||
bytes32 node, | ||
address resolver, | ||
bytes[] calldata records | ||
) internal { | ||
for (uint256 i = 0; i < records.length; i++) { | ||
// check first few bytes are namehash | ||
bytes32 txNamehash = bytes32(records[i][4:36]); | ||
require( | ||
txNamehash == node, | ||
"SubdomainRegistrar: Namehash on record do not match the name being registered" | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
); | ||
resolver.functionCall( | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. What does There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
records[i], | ||
"SubdomainRegistrar: Failed to set Record" | ||
); | ||
} | ||
} | ||
|
||
function _checkParent(bytes32 node) internal view returns (uint32, uint64) { | ||
try wrapper.getData(uint256(node)) returns ( | ||
address, | ||
uint32 fuses, | ||
uint64 expiry | ||
) { | ||
if (fuses & IS_DOT_ETH == IS_DOT_ETH) { | ||
expiry = expiry - GRACE_PERIOD; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Rather than overriding |
||
} | ||
|
||
if (block.timestamp > expiry) { | ||
revert ParentExpired(node); | ||
} | ||
return (fuses, expiry); | ||
} catch { | ||
revert ParentNotWrapped(node); | ||
} | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,91 @@ | ||
//SPDX-License-Identifier: MIT | ||
pragma solidity ^0.8.17; | ||
|
||
import {INameWrapper, PARENT_CANNOT_CONTROL, CAN_EXTEND_EXPIRY} from "../wrapper/INameWrapper.sol"; | ||
import {Address} from "@openzeppelin/contracts/utils/Address.sol"; | ||
import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; | ||
import {ERC1155Holder} from "@openzeppelin/contracts/token/ERC1155/utils/ERC1155Holder.sol"; | ||
import {BaseSubdomainRegistrar, InsufficientFunds, DataMissing, Unavailable, NameNotRegistered} from "./BaseSubdomainRegistrar.sol"; | ||
import {IForeverSubdomainRegistrar} from "./IForeverSubdomainRegistrar.sol"; | ||
|
||
error ParentNameNotSetup(bytes32 parentNode); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. There is no test case to cover this error |
||
|
||
struct Name { | ||
uint256 registrationFee; // per registration | ||
address token; // ERC20 token | ||
address beneficiary; | ||
bool active; | ||
} | ||
|
||
contract ForeverSubdomainRegistrar is | ||
BaseSubdomainRegistrar, | ||
ERC1155Holder, | ||
IForeverSubdomainRegistrar | ||
{ | ||
mapping(bytes32 => Name) public names; | ||
|
||
constructor(address wrapper) BaseSubdomainRegistrar(wrapper) {} | ||
|
||
function setupDomain( | ||
bytes32 node, | ||
address token, | ||
uint256 fee, | ||
address beneficiary, | ||
bool active | ||
) public onlyOwner(node) { | ||
names[node].registrationFee = fee; | ||
jefflau marked this conversation as resolved.
Show resolved
Hide resolved
|
||
names[node].token = token; | ||
names[node].beneficiary = beneficiary; | ||
names[node].active = true; | ||
} | ||
jefflau marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
function available( | ||
bytes32 node | ||
) | ||
public | ||
view | ||
override(BaseSubdomainRegistrar, IForeverSubdomainRegistrar) | ||
returns (bool) | ||
{ | ||
return super.available(node); | ||
} | ||
|
||
function register( | ||
bytes32 parentNode, | ||
string calldata label, | ||
address newOwner, | ||
address resolver, | ||
uint16 fuses, | ||
bytes[] calldata records | ||
) public payable { | ||
if (!names[parentNode].active) { | ||
revert ParentNameNotSetup(parentNode); | ||
} | ||
|
||
uint256 fee = names[parentNode].registrationFee; | ||
|
||
if (fee > 0) { | ||
if (IERC20(names[parentNode].token).balanceOf(msg.sender) < fee) { | ||
jefflau marked this conversation as resolved.
Show resolved
Hide resolved
|
||
revert InsufficientFunds(); | ||
} | ||
|
||
IERC20(names[parentNode].token).transferFrom( | ||
msg.sender, | ||
address(names[parentNode].beneficiary), | ||
fee | ||
); | ||
} | ||
|
||
(, , uint64 parentExpiry) = wrapper.getData(uint256(parentNode)); | ||
|
||
_register( | ||
parentNode, | ||
label, | ||
newOwner, | ||
resolver, | ||
uint32(fuses) | CAN_EXTEND_EXPIRY, | ||
parentExpiry, | ||
records | ||
); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
//SPDX-License-Identifier: MIT | ||
pragma solidity ^0.8.15; | ||
|
||
interface IForeverSubdomainRegistrar { | ||
function setupDomain( | ||
bytes32 node, | ||
address token, | ||
uint256 fee, | ||
address beneficiary, | ||
bool active | ||
) external; | ||
|
||
function register( | ||
bytes32 parentNode, | ||
string calldata label, | ||
address newOwner, | ||
address resolver, | ||
uint16 ownerControlledfuses, | ||
bytes[] calldata records | ||
) external payable; | ||
|
||
function available(bytes32 node) external view returns (bool); | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,46 @@ | ||
//SPDX-License-Identifier: MIT | ||
pragma solidity ^0.8.15; | ||
|
||
interface IRentalSubdomainRegistrar { | ||
function setupDomain( | ||
bytes32 node, | ||
address token, | ||
uint256 fee, | ||
address beneficiary, | ||
bool active | ||
) external; | ||
|
||
function register( | ||
bytes32 parentNode, | ||
string calldata label, | ||
address newOwner, | ||
address resolver, | ||
uint16 ownerControlledfuses, | ||
uint64 duration, | ||
bytes[] calldata records | ||
) external payable; | ||
|
||
function renew( | ||
bytes32 parentNode, | ||
bytes32 labelhash, | ||
uint64 duration | ||
) external payable returns (uint64 newExpiry); | ||
|
||
function batchRegister( | ||
bytes32 parentNode, | ||
string[] calldata labels, | ||
address[] calldata addresses, | ||
address resolver, | ||
uint16 fuses, | ||
uint64 duration, | ||
bytes[][] calldata records | ||
) external; | ||
|
||
function batchRenew( | ||
bytes32 parentNode, | ||
bytes32[] calldata labelhashes, | ||
uint64 duration | ||
) external payable; | ||
|
||
function available(bytes32 node) external view returns (bool); | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
# Subdomain Registrars | ||
|
||
The Subdomain registrar is an example contract, that can be deployed to allow registration of subnames under one or more names. | ||
|
||
The Base Registrar allows registration of names indiscriminately by simply allowing approval. The other registrars layer on top of this base contract to allow additional functionality such as renting or forever subnames. They also restrict it to names that have called setupDomain with active set to true to not accidentally allow any names to have subnames registered underneath it. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There is no test case to cover this error