Skip to content
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

Draft
wants to merge 60 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from 14 commits
Commits
Show all changes
60 commits
Select commit Hold shift + click to select a range
fe6f138
Add basic subdomain registrar
jefflau May 20, 2022
dc2aa79
Add expiries and fees
jefflau May 24, 2022
df127ff
Add ownerOf to INameWrapper
jefflau May 24, 2022
82df2e9
set beneficiary, set records
jefflau May 24, 2022
433e4e0
Remove prettier
jefflau May 24, 2022
06551a2
Cleanup
jefflau May 24, 2022
ae2801d
Change data structure
jefflau May 25, 2022
148767f
Merge with subdomain-registrar
jefflau Jun 15, 2022
7ab5e6e
Fix compilation of SubdomainRegistrar, fix merge issues with NameWrapper
jefflau Jun 15, 2022
5438496
Merge branch 'wrapper-expire' into feature/subdomain-registrar
jefflau Jun 15, 2022
d2d036b
Fix tests from new interface
jefflau Jun 15, 2022
c3f1d12
Switch to ERC20
jefflau Jun 15, 2022
54e3be1
Add a mock ERC20 contract
jefflau Jun 15, 2022
f6e25fa
Merge branch 'master' into feature/subdomain-registrar
jefflau Jul 4, 2022
5e7b143
Allow subdomains to be registered with any ERC20
jefflau Jul 7, 2022
d014bc9
Add tests for renew
jefflau Jul 7, 2022
1a58228
merge with master
jefflau Jul 12, 2022
38c713c
Add batchRegister subnames
jefflau Jul 12, 2022
8051947
Move transfers into batchRegister. Add tests for balances
jefflau Jul 12, 2022
b6b00fc
Refactor register/renew into internal funcs, add parent reverts
jefflau Jul 13, 2022
6696059
Add ISubdomainRegistrar Interface and clean up tests
jefflau Jul 14, 2022
b526e3d
Merge branch 'master' of github.com:ensdomains/ens-contracts into fea…
jefflau Jul 15, 2022
16c7698
Switch compiler versions
jefflau Jul 15, 2022
9b7c958
Merge with master
jefflau Sep 28, 2022
672d507
merge with getData
jefflau Sep 28, 2022
26b45ce
Begin fixing tests
jefflau Sep 30, 2022
199e7f7
Fix tests for SubdomainRegistrar
jefflau Oct 5, 2022
c19bf4c
Pull out contracts into separate files for rental
jefflau Oct 18, 2022
94f5305
WIP
jefflau Oct 19, 2022
dfd00db
Setup foundry
jefflau Oct 20, 2022
7f5c408
Replicate first test
jefflau Oct 21, 2022
9940fbc
Rewrite js tests in solidity
jefflau Oct 21, 2022
faf526d
Remove forge tests
jefflau Feb 1, 2023
4b6d4db
Fix Subdomain Registrar tests
jefflau Feb 1, 2023
ffac7c7
Move generic funcs into BaseSubdomainRegistrar
jefflau Feb 7, 2023
b5717d7
Update rental tests, change fuses to uint16
jefflau Feb 7, 2023
2c55ec6
Fix expiry on forever subdomain registration
jefflau Feb 8, 2023
e1f3155
Merge branch 'master' of github.com:ensdomains/ens-contracts into fea…
jefflau Feb 15, 2023
2f03149
Add interface to rental contract
jefflau Feb 15, 2023
092a374
Add interface
jefflau Feb 21, 2023
54566b8
Resolve conflicts
jefflau Apr 5, 2023
2c9bb8c
Fix compilation errors
jefflau Apr 5, 2023
8ab89d7
Add protection for unsetup names
jefflau Apr 5, 2023
11fbea6
Adjust tests
jefflau Apr 5, 2023
83f25a9
Test for allowing name owners to extend their own expiry
jefflau Apr 5, 2023
21fa7d8
Remove forge folders
jefflau Apr 7, 2023
b5938b1
Remove func
jefflau Apr 7, 2023
dfcddd9
Add inline struct, do not default active to true
jefflau May 8, 2023
ccdb370
Remove unnecessary check for balanceOf token
jefflau May 8, 2023
1fa3a75
Add tests for DurationTooLong()
jefflau May 8, 2023
c2cbcac
Add event for NameSetup() and add tests
jefflau May 8, 2023
12b98e9
Switch modifier to authorised
jefflau May 8, 2023
12ba18d
Remove modification of expiry variable
jefflau May 8, 2023
7e03ce2
WIP - adding pricer contracts
jefflau May 23, 2023
2ea9a28
Add FixedPricer and fix ForeverSubdomain tests
jefflau Sep 21, 2023
f84565b
Fix all tests for batchRegister/renew
jefflau Sep 26, 2023
c08939b
Refactor naming and add public functiins to interface
jefflau Nov 8, 2023
d9a8457
Update readme
jefflau Nov 8, 2023
47c98cd
Add code snippet examples
jefflau Nov 9, 2023
134d776
Merge branch 'master' into feature/subdomain-registrar
makoto Dec 18, 2023
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
170 changes: 170 additions & 0 deletions contracts/subdomainregistrar/SubdomainRegistrar.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,170 @@
//SPDX-License-Identifier: MIT
pragma solidity ^0.8.13;

import "../wrapper/INameWrapper.sol";
import "@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 "hardhat/console.sol";

error Unavailable();
error Unauthorised(bytes32 node);
error InsufficientFunds();
error NameNotRegistered();

struct Name {
uint256 registrationFee; // per second
address token; // ERC20 token
address beneficiary;
}

contract SubdomainRegistrar is ERC1155Holder {
INameWrapper public immutable wrapper;
using Address for address;

mapping(bytes32 => Name) public names;
mapping(bytes32 => uint256) public expiries;

event NameRenewed(bytes32 node, uint256 duration);

constructor(INameWrapper _wrapper) {
wrapper = _wrapper;
}

modifier onlyOwner(bytes32 node) {
if (!wrapper.isTokenOwnerOrApproved(node, msg.sender)) {
revert Unauthorised(node);
}
_;
}

function setupDomain(
bytes32 node,
address token,
uint256 fee,
address beneficiary
) public onlyOwner(node) {
setRegistrationFee(node, fee);
names[node].beneficiary = beneficiary;
names[node].token = token;
}

function setRegistrationFee(bytes32 node, uint256 fee)
public
onlyOwner(node)
{
names[node].registrationFee = fee;
}

function available(bytes32 node) public returns (bool) {
try wrapper.getFuses(node) returns (uint32, uint64 expiry) {
return expiry < block.timestamp;
} catch {
return true;
}
}

function register(
bytes32 parentNode,
string calldata label,
address newOwner,
address resolver,
uint32 fuses,
uint64 duration,
bytes[] calldata records
) public payable {
bytes32 node = keccak256(
abi.encodePacked(parentNode, keccak256(bytes(label)))
);
uint256 fee = duration * names[parentNode].registrationFee;
// check max registration possible
// refund the rest

if (!available(node)) {
revert Unavailable();
}
if (msg.value < fee) {
revert InsufficientFunds();
}

if (records.length > 0) {
wrapper.setSubnodeOwner(
parentNode,
label,
address(this),
0,
uint64(block.timestamp + duration)
);
_setRecords(node, resolver, records);
}

wrapper.setSubnodeRecord(
parentNode,
label,
newOwner,
resolver,
0,
fuses | PARENT_CANNOT_CONTROL, // burn the ability for the parent to control
uint64(block.timestamp + duration)
);

IERC20(names[parentNode].token).transferFrom(
msg.sender,
address(names[parentNode].beneficiary),
fee
);
}

function renew(
bytes32 parentNode,
bytes32 labelhash,
uint64 duration
) external payable returns (uint64 newExpiry) {
bytes32 node = _makeNode(parentNode, labelhash);
(, uint64 expiry) = wrapper.getFuses(node);
if (expiry < block.timestamp) {
revert NameNotRegistered();
}

uint256 fee = duration * names[parentNode].registrationFee;

newExpiry = expiry += duration;

wrapper.setChildFuses(parentNode, labelhash, 0, newExpiry);

IERC20(names[parentNode].token).transferFrom(
msg.sender,
address(names[parentNode].beneficiary),
fee
);

emit NameRenewed(node, newExpiry);
}

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"
);
resolver.functionCall(
records[i],
"SubdomainRegistrar: Failed to set Record"
);
}
}

function _makeNode(bytes32 node, bytes32 labelhash)
private
pure
returns (bytes32)
{
return keccak256(abi.encodePacked(node, labelhash));
}
}
18 changes: 18 additions & 0 deletions contracts/subdomainregistrar/mocks/MockERC20.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
//SPDX-License-Identifier: MIT
pragma solidity ^0.8.13;

import {ERC20} from "@openzeppelin/contracts/token/ERC20/ERC20.sol";

contract MockERC20 is ERC20 {
constructor(
string memory name,
string memory symbol,
address[] memory addresses
) ERC20(name, symbol) {
_mint(msg.sender, 100 * 10**uint256(decimals()));

for (uint256 i = 0; i < addresses.length; i++) {
_mint(addresses[i], 100 * 10**uint256(decimals()));
}
}
}
4 changes: 2 additions & 2 deletions contracts/wrapper/INameWrapper.sol
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,8 @@ interface INameWrapper is IERC1155 {

function names(bytes32) external view returns (bytes memory);

function ownerOf(uint256) external view returns (address);

function wrap(
bytes calldata name,
address wrappedOwner,
Expand Down Expand Up @@ -121,8 +123,6 @@ interface INameWrapper is IERC1155 {

function setTTL(bytes32 node, uint64 ttl) external;

function ownerOf(uint256 id) external returns (address owner);

function getFuses(bytes32 node)
external
returns (uint32 fuses, uint64 expiry);
Expand Down
26 changes: 15 additions & 11 deletions contracts/wrapper/NameWrapper.sol
Original file line number Diff line number Diff line change
Expand Up @@ -85,17 +85,6 @@ contract NameWrapper is
super.supportsInterface(interfaceId);
}

/* ERC1155 */

function ownerOf(uint256 id)
public
view
override(ERC1155Fuse, INameWrapper)
returns (address owner)
{
return super.ownerOf(id);
}

/* Metadata service */

/**
Expand Down Expand Up @@ -284,6 +273,21 @@ contract NameWrapper is
_setData(node, owner, fuses, expiry);
}

/**
* @dev Returns the owner of the ERC1155 token
*
* @param tokenId The hash of the label to register (eg, `keccak256('foo')`, for 'foo.eth').
* @return expires The expiry date of the name, in seconds since the Unix epoch.
*/
function ownerOf(uint256 tokenId)
public
view
override(ERC1155Fuse, INameWrapper)
returns (address)
{
return super.ownerOf(tokenId);
}

/**
* @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
Expand Down
Loading