Created
October 24, 2018 13:08
-
-
Save 3esmit/8e77761b45358bf2fa3b07533ede9448 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
pragma solidity ^0.4.24; | |
/** | |
* @title MerkleProof | |
* @dev Merkle proof verification based on | |
* https://github.com/ameensol/merkle-tree-solidity/blob/master/src/MerkleProof.sol | |
*/ | |
library MerkleProof { | |
/** | |
* @dev Verifies a Merkle proof proving the existence of a leaf in a Merkle tree. Assumes that each pair of leaves | |
* and each pair of pre-images are sorted. | |
* @param _proof Merkle proof containing sibling hashes on the branch from the leaf to the root of the Merkle tree | |
* @param _root Merkle root | |
* @param _leaf Leaf of Merkle tree | |
*/ | |
function verifyProof( | |
bytes32[] _proof, | |
bytes32 _root, | |
bytes32 _leaf | |
) | |
internal | |
pure | |
returns (bool) | |
{ | |
bytes32 computedHash = _leaf; | |
for (uint256 i = 0; i < _proof.length; i++) { | |
bytes32 proofElement = _proof[i]; | |
if (computedHash < proofElement) { | |
// Hash(current computed hash + current element of the proof) | |
computedHash = keccak256(abi.encodePacked(computedHash, proofElement)); | |
} else { | |
// Hash(current element of the proof + current computed hash) | |
computedHash = keccak256(abi.encodePacked(proofElement, computedHash)); | |
} | |
} | |
// Check if the computed hash (root) is equal to the provided root | |
return computedHash == _root; | |
} | |
} | |
contract Controlled { | |
/// @notice The address of the controller is the only address that can call | |
/// a function with this modifier | |
modifier onlyController { | |
require(msg.sender == controller); | |
_; | |
} | |
address public controller; | |
constructor() internal { | |
controller = msg.sender; | |
} | |
/// @notice Changes the controller of the contract | |
/// @param _newController The new controller of the contract | |
function changeController(address _newController) public onlyController { | |
controller = _newController; | |
} | |
} | |
// Abstract contract for the full ERC 20 Token standard | |
// https://github.com/ethereum/EIPs/issues/20 | |
interface ERC20Token { | |
/** | |
* @notice send `_value` token to `_to` from `msg.sender` | |
* @param _to The address of the recipient | |
* @param _value The amount of token to be transferred | |
* @return Whether the transfer was successful or not | |
*/ | |
function transfer(address _to, uint256 _value) external returns (bool success); | |
/** | |
* @notice `msg.sender` approves `_spender` to spend `_value` tokens | |
* @param _spender The address of the account able to transfer the tokens | |
* @param _value The amount of tokens to be approved for transfer | |
* @return Whether the approval was successful or not | |
*/ | |
function approve(address _spender, uint256 _value) external returns (bool success); | |
/** | |
* @notice send `_value` token to `_to` from `_from` on the condition it is approved by `_from` | |
* @param _from The address of the sender | |
* @param _to The address of the recipient | |
* @param _value The amount of token to be transferred | |
* @return Whether the transfer was successful or not | |
*/ | |
function transferFrom(address _from, address _to, uint256 _value) external returns (bool success); | |
/** | |
* @param _owner The address from which the balance will be retrieved | |
* @return The balance | |
*/ | |
function balanceOf(address _owner) external view returns (uint256 balance); | |
/** | |
* @param _owner The address of the account owning tokens | |
* @param _spender The address of the account able to transfer the tokens | |
* @return Amount of remaining tokens allowed to spent | |
*/ | |
function allowance(address _owner, address _spender) external view returns (uint256 remaining); | |
/** | |
* @notice return total supply of tokens | |
*/ | |
function totalSupply() external view returns (uint256 supply); | |
event Transfer(address indexed _from, address indexed _to, uint256 _value); | |
event Approval(address indexed _owner, address indexed _spender, uint256 _value); | |
} | |
contract ApproveAndCallFallBack { | |
function receiveApproval(address from, uint256 _amount, address _token, bytes _data) public; | |
} | |
interface ENS { | |
// Logged when the owner of a node assigns a new owner to a subnode. | |
event NewOwner(bytes32 indexed node, bytes32 indexed label, address owner); | |
// Logged when the owner of a node transfers ownership to a new account. | |
event Transfer(bytes32 indexed node, address owner); | |
// Logged when the resolver for a node changes. | |
event NewResolver(bytes32 indexed node, address resolver); | |
// Logged when the TTL of a node changes | |
event NewTTL(bytes32 indexed node, uint64 ttl); | |
function setSubnodeOwner(bytes32 node, bytes32 label, address owner) public; | |
function setResolver(bytes32 node, address resolver) public; | |
function setOwner(bytes32 node, address owner) public; | |
function setTTL(bytes32 node, uint64 ttl) public; | |
function owner(bytes32 node) public view returns (address); | |
function resolver(bytes32 node) public view returns (address); | |
function ttl(bytes32 node) public view returns (uint64); | |
} | |
/** | |
* A simple resolver anyone can use; only allows the owner of a node to set its | |
* address. | |
*/ | |
contract PublicResolver { | |
bytes4 constant INTERFACE_META_ID = 0x01ffc9a7; | |
bytes4 constant ADDR_INTERFACE_ID = 0x3b3b57de; | |
bytes4 constant CONTENT_INTERFACE_ID = 0xd8389dc5; | |
bytes4 constant NAME_INTERFACE_ID = 0x691f3431; | |
bytes4 constant ABI_INTERFACE_ID = 0x2203ab56; | |
bytes4 constant PUBKEY_INTERFACE_ID = 0xc8690233; | |
bytes4 constant TEXT_INTERFACE_ID = 0x59d1d43c; | |
bytes4 constant MULTIHASH_INTERFACE_ID = 0xe89401a1; | |
event AddrChanged(bytes32 indexed node, address a); | |
event ContentChanged(bytes32 indexed node, bytes32 hash); | |
event NameChanged(bytes32 indexed node, string name); | |
event ABIChanged(bytes32 indexed node, uint256 indexed contentType); | |
event PubkeyChanged(bytes32 indexed node, bytes32 x, bytes32 y); | |
event TextChanged(bytes32 indexed node, string indexedKey, string key); | |
event MultihashChanged(bytes32 indexed node, bytes hash); | |
struct PublicKey { | |
bytes32 x; | |
bytes32 y; | |
} | |
struct Record { | |
address addr; | |
bytes32 content; | |
string name; | |
PublicKey pubkey; | |
mapping(string=>string) text; | |
mapping(uint256=>bytes) abis; | |
bytes multihash; | |
} | |
ENS ens; | |
mapping (bytes32 => Record) records; | |
modifier only_owner(bytes32 node) { | |
require(ens.owner(node) == msg.sender); | |
_; | |
} | |
/** | |
* Constructor. | |
* @param ensAddr The ENS registrar contract. | |
*/ | |
constructor(ENS ensAddr) public { | |
ens = ensAddr; | |
} | |
/** | |
* Sets the address associated with an ENS node. | |
* May only be called by the owner of that node in the ENS registry. | |
* @param node The node to update. | |
* @param addr The address to set. | |
*/ | |
function setAddr(bytes32 node, address addr) public only_owner(node) { | |
records[node].addr = addr; | |
emit AddrChanged(node, addr); | |
} | |
/** | |
* Sets the content hash associated with an ENS node. | |
* May only be called by the owner of that node in the ENS registry. | |
* Note that this resource type is not standardized, and will likely change | |
* in future to a resource type based on multihash. | |
* @param node The node to update. | |
* @param hash The content hash to set | |
*/ | |
function setContent(bytes32 node, bytes32 hash) public only_owner(node) { | |
records[node].content = hash; | |
emit ContentChanged(node, hash); | |
} | |
/** | |
* Sets the multihash associated with an ENS node. | |
* May only be called by the owner of that node in the ENS registry. | |
* @param node The node to update. | |
* @param hash The multihash to set | |
*/ | |
function setMultihash(bytes32 node, bytes hash) public only_owner(node) { | |
records[node].multihash = hash; | |
emit MultihashChanged(node, hash); | |
} | |
/** | |
* Sets the name associated with an ENS node, for reverse records. | |
* May only be called by the owner of that node in the ENS registry. | |
* @param node The node to update. | |
* @param name The name to set. | |
*/ | |
function setName(bytes32 node, string name) public only_owner(node) { | |
records[node].name = name; | |
emit NameChanged(node, name); | |
} | |
/** | |
* Sets the ABI associated with an ENS node. | |
* Nodes may have one ABI of each content type. To remove an ABI, set it to | |
* the empty string. | |
* @param node The node to update. | |
* @param contentType The content type of the ABI | |
* @param data The ABI data. | |
*/ | |
function setABI(bytes32 node, uint256 contentType, bytes data) public only_owner(node) { | |
// Content types must be powers of 2 | |
require(((contentType - 1) & contentType) == 0); | |
records[node].abis[contentType] = data; | |
emit ABIChanged(node, contentType); | |
} | |
/** | |
* Sets the SECP256k1 public key associated with an ENS node. | |
* @param node The ENS node to query | |
* @param x the X coordinate of the curve point for the public key. | |
* @param y the Y coordinate of the curve point for the public key. | |
*/ | |
function setPubkey(bytes32 node, bytes32 x, bytes32 y) public only_owner(node) { | |
records[node].pubkey = PublicKey(x, y); | |
emit PubkeyChanged(node, x, y); | |
} | |
/** | |
* Sets the text data associated with an ENS node and key. | |
* May only be called by the owner of that node in the ENS registry. | |
* @param node The node to update. | |
* @param key The key to set. | |
* @param value The text data value to set. | |
*/ | |
function setText(bytes32 node, string key, string value) public only_owner(node) { | |
records[node].text[key] = value; | |
emit TextChanged(node, key, key); | |
} | |
/** | |
* Returns the text data associated with an ENS node and key. | |
* @param node The ENS node to query. | |
* @param key The text data key to query. | |
* @return The associated text data. | |
*/ | |
function text(bytes32 node, string key) public view returns (string) { | |
return records[node].text[key]; | |
} | |
/** | |
* Returns the SECP256k1 public key associated with an ENS node. | |
* Defined in EIP 619. | |
* @param node The ENS node to query | |
* @return x, y the X and Y coordinates of the curve point for the public key. | |
*/ | |
function pubkey(bytes32 node) public view returns (bytes32 x, bytes32 y) { | |
return (records[node].pubkey.x, records[node].pubkey.y); | |
} | |
/** | |
* Returns the ABI associated with an ENS node. | |
* Defined in EIP205. | |
* @param node The ENS node to query | |
* @param contentTypes A bitwise OR of the ABI formats accepted by the caller. | |
* @return contentType The content type of the return value | |
* @return data The ABI data | |
*/ | |
function ABI(bytes32 node, uint256 contentTypes) public view returns (uint256 contentType, bytes data) { | |
Record storage record = records[node]; | |
for (contentType = 1; contentType <= contentTypes; contentType <<= 1) { | |
if ((contentType & contentTypes) != 0 && record.abis[contentType].length > 0) { | |
data = record.abis[contentType]; | |
return; | |
} | |
} | |
contentType = 0; | |
} | |
/** | |
* Returns the name associated with an ENS node, for reverse records. | |
* Defined in EIP181. | |
* @param node The ENS node to query. | |
* @return The associated name. | |
*/ | |
function name(bytes32 node) public view returns (string) { | |
return records[node].name; | |
} | |
/** | |
* Returns the content hash associated with an ENS node. | |
* Note that this resource type is not standardized, and will likely change | |
* in future to a resource type based on multihash. | |
* @param node The ENS node to query. | |
* @return The associated content hash. | |
*/ | |
function content(bytes32 node) public view returns (bytes32) { | |
return records[node].content; | |
} | |
/** | |
* Returns the multihash associated with an ENS node. | |
* @param node The ENS node to query. | |
* @return The associated multihash. | |
*/ | |
function multihash(bytes32 node) public view returns (bytes) { | |
return records[node].multihash; | |
} | |
/** | |
* Returns the address associated with an ENS node. | |
* @param node The ENS node to query. | |
* @return The associated address. | |
*/ | |
function addr(bytes32 node) public view returns (address) { | |
return records[node].addr; | |
} | |
/** | |
* Returns true if the resolver implements the interface specified by the provided hash. | |
* @param interfaceID The ID of the interface to check for. | |
* @return True if the contract implements the requested interface. | |
*/ | |
function supportsInterface(bytes4 interfaceID) public pure returns (bool) { | |
return interfaceID == ADDR_INTERFACE_ID || | |
interfaceID == CONTENT_INTERFACE_ID || | |
interfaceID == NAME_INTERFACE_ID || | |
interfaceID == ABI_INTERFACE_ID || | |
interfaceID == PUBKEY_INTERFACE_ID || | |
interfaceID == TEXT_INTERFACE_ID || | |
interfaceID == MULTIHASH_INTERFACE_ID || | |
interfaceID == INTERFACE_META_ID; | |
} | |
} | |
/** | |
* @author Ricardo Guilherme Schmidt (Status Research & Development GmbH) | |
* @notice Registers usernames as ENS subnodes of the domain `ensNode` | |
*/ | |
contract UsernameRegistrar is Controlled, ApproveAndCallFallBack { | |
ERC20Token public token; | |
ENS public ensRegistry; | |
PublicResolver public resolver; | |
address public parentRegistry; | |
uint256 public constant releaseDelay = 365 days; | |
mapping (bytes32 => Account) public accounts; | |
mapping (bytes32 => SlashReserve) reservedSlashers; | |
//Slashing conditions | |
uint256 public usernameMinLength; | |
bytes32 public reservedUsernamesMerkleRoot; | |
event RegistryState(RegistrarState state); | |
event RegistryPrice(uint256 price); | |
event RegistryMoved(address newRegistry); | |
event UsernameOwner(bytes32 indexed nameHash, address owner); | |
enum RegistrarState { Inactive, Active, Moved } | |
bytes32 public ensNode; | |
uint256 public price; | |
RegistrarState public state; | |
uint256 public reserveAmount; | |
struct Account { | |
uint256 balance; | |
uint256 creationTime; | |
address owner; | |
} | |
struct SlashReserve { | |
address reserver; | |
uint256 blockNumber; | |
} | |
/** | |
* @notice Callable only by `parentRegistry()` to continue migration of ENSSubdomainRegistry. | |
*/ | |
modifier onlyParentRegistry { | |
require(msg.sender == parentRegistry, "Migration only."); | |
_; | |
} | |
/** | |
* @notice Initializes UsernameRegistrar contract. | |
* The only parameter from this list that can be changed later is `_resolver`. | |
* Other updates require a new contract and migration of domain. | |
* @param _token ERC20 token with optional `approveAndCall(address,uint256,bytes)` for locking fee. | |
* @param _ensRegistry Ethereum Name Service root contract address. | |
* @param _resolver Public Resolver for resolving usernames. | |
* @param _ensNode ENS node (domain) being used for usernames subnodes (subdomain) | |
* @param _usernameMinLength Minimum length of usernames | |
* @param _reservedUsernamesMerkleRoot Merkle root of reserved usernames | |
* @param _parentRegistry Address of old registry (if any) for optional account migration. | |
*/ | |
constructor( | |
ERC20Token _token, | |
ENS _ensRegistry, | |
PublicResolver _resolver, | |
bytes32 _ensNode, | |
uint256 _usernameMinLength, | |
bytes32 _reservedUsernamesMerkleRoot, | |
address _parentRegistry | |
) | |
public | |
{ | |
require(address(_token) != address(0), "No ERC20Token address defined."); | |
require(address(_ensRegistry) != address(0), "No ENS address defined."); | |
require(address(_resolver) != address(0), "No Resolver address defined."); | |
require(_ensNode != bytes32(0), "No ENS node defined."); | |
token = _token; | |
ensRegistry = _ensRegistry; | |
resolver = _resolver; | |
ensNode = _ensNode; | |
usernameMinLength = _usernameMinLength; | |
reservedUsernamesMerkleRoot = _reservedUsernamesMerkleRoot; | |
parentRegistry = _parentRegistry; | |
setState(RegistrarState.Inactive); | |
} | |
/** | |
* @notice Registers `_label` username to `ensNode` setting msg.sender as owner. | |
* Terms of name registration: | |
* - SNT is deposited, not spent; the amount is locked up for 1 year. | |
* - After 1 year, the user can release the name and receive their deposit back (at any time). | |
* - User deposits are completely protected. The contract controller cannot access them. | |
* - User's address(es) will be publicly associated with the ENS name. | |
* - User must authorise the contract to transfer `price` `token.name()` on their behalf. | |
* - Usernames registered with less then `usernameMinLength` characters can be slashed. | |
* - Usernames contained in the merkle tree of root `reservedUsernamesMerkleRoot` can be slashed. | |
* - Usernames starting with `0x` and bigger then 12 characters can be slashed. | |
* - If terms of the contract change—e.g. Status makes contract upgrades—the user has the right to release the username and get their deposit back. | |
* @param _label Choosen unowned username hash. | |
* @param _account Optional address to set at public resolver. | |
* @param _pubkeyA Optional pubkey part A to set at public resolver. | |
* @param _pubkeyB Optional pubkey part B to set at public resolver. | |
*/ | |
function register( | |
bytes32 _label, | |
address _account, | |
bytes32 _pubkeyA, | |
bytes32 _pubkeyB | |
) | |
external | |
returns(bytes32 namehash) | |
{ | |
return registerUser(msg.sender, _label, _account, _pubkeyA, _pubkeyB); | |
} | |
/** | |
* @notice Release username and retrieve locked fee, needs to be called | |
* after `releasePeriod` from creation time by ENS registry owner of domain | |
* or anytime by account owner when domain migrated to a new registry. | |
* @param _label Username hash. | |
*/ | |
function release( | |
bytes32 _label | |
) | |
external | |
{ | |
bytes32 namehash = keccak256(abi.encodePacked(ensNode, _label)); | |
Account memory account = accounts[_label]; | |
require(account.creationTime > 0, "Username not registered."); | |
if (state == RegistrarState.Active) { | |
require(msg.sender == ensRegistry.owner(namehash), "Not owner of ENS node."); | |
require(block.timestamp > account.creationTime + releaseDelay, "Release period not reached."); | |
} else { | |
require(msg.sender == account.owner, "Not the former account owner."); | |
} | |
delete accounts[_label]; | |
if (account.balance > 0) { | |
reserveAmount -= account.balance; | |
require(token.transfer(msg.sender, account.balance), "Transfer failed"); | |
} | |
if (state == RegistrarState.Active) { | |
ensRegistry.setSubnodeOwner(ensNode, _label, address(this)); | |
ensRegistry.setResolver(namehash, address(0)); | |
ensRegistry.setOwner(namehash, address(0)); | |
} else { | |
address newOwner = ensRegistry.owner(ensNode); | |
//Low level call, case dropUsername not implemented or failing, proceed release. | |
//Invert (!) to supress warning, return of this call have no use. | |
!newOwner.call.gas(80000)( | |
abi.encodeWithSignature( | |
"dropUsername(bytes32)", | |
_label | |
) | |
); | |
} | |
emit UsernameOwner(namehash, address(0)); | |
} | |
/** | |
* @notice update account owner, should be called by new ens node owner | |
* to update this contract registry, otherwise former owner can release | |
* if domain is moved to a new registry. | |
* @param _label Username hash. | |
**/ | |
function updateAccountOwner( | |
bytes32 _label | |
) | |
external | |
{ | |
bytes32 namehash = keccak256(abi.encodePacked(ensNode, _label)); | |
require(msg.sender == ensRegistry.owner(namehash), "Caller not owner of ENS node."); | |
require(accounts[_label].creationTime > 0, "Username not registered."); | |
require(ensRegistry.owner(ensNode) == address(this), "Registry not owner of registry."); | |
accounts[_label].owner = msg.sender; | |
emit UsernameOwner(namehash, msg.sender); | |
} | |
/** | |
* @notice secretly reserve the slashing reward to `msg.sender` | |
* @param _secret keccak256(abi.encodePacked(namehash, creationTime, reserveSecret)) | |
*/ | |
function reserveSlash(bytes32 _secret) external { | |
require(reservedSlashers[_secret].blockNumber == 0, "Already Reserved"); | |
reservedSlashers[_secret] = SlashReserve(msg.sender, block.number); | |
} | |
/** | |
* @notice Slash username smaller then `usernameMinLength`. | |
* @param _username Raw value of offending username. | |
*/ | |
function slashSmallUsername( | |
string _username, | |
uint256 _reserveSecret | |
) | |
external | |
{ | |
bytes memory username = bytes(_username); | |
require(username.length < usernameMinLength, "Not a small username."); | |
slashUsername(username, _reserveSecret); | |
} | |
/** | |
* @notice Slash username starting with "0x" and with length greater than 12. | |
* @param _username Raw value of offending username. | |
*/ | |
function slashAddressLikeUsername( | |
string _username, | |
uint256 _reserveSecret | |
) | |
external | |
{ | |
bytes memory username = bytes(_username); | |
require(username.length > 12, "Too small to look like an address."); | |
require(username[0] == byte("0"), "First character need to be 0"); | |
require(username[1] == byte("x"), "Second character need to be x"); | |
for(uint i = 2; i < 7; i++){ | |
byte b = username[i]; | |
require((b >= 48 && b <= 57) || (b >= 97 && b <= 102), "Does not look like an address"); | |
} | |
slashUsername(username, _reserveSecret); | |
} | |
/** | |
* @notice Slash username that is exactly a reserved name. | |
* @param _username Raw value of offending username. | |
* @param _proof Merkle proof that name is listed on merkle tree. | |
*/ | |
function slashReservedUsername( | |
string _username, | |
bytes32[] _proof, | |
uint256 _reserveSecret | |
) | |
external | |
{ | |
bytes memory username = bytes(_username); | |
require( | |
MerkleProof.verifyProof( | |
_proof, | |
reservedUsernamesMerkleRoot, | |
keccak256(username) | |
), | |
"Invalid Proof." | |
); | |
slashUsername(username, _reserveSecret); | |
} | |
/** | |
* @notice Slash username that contains a non alphanumeric character. | |
* @param _username Raw value of offending username. | |
* @param _offendingPos Position of non alphanumeric character. | |
*/ | |
function slashInvalidUsername( | |
string _username, | |
uint256 _offendingPos, | |
uint256 _reserveSecret | |
) | |
external | |
{ | |
bytes memory username = bytes(_username); | |
require(username.length > _offendingPos, "Invalid position."); | |
byte b = username[_offendingPos]; | |
require(!((b >= 48 && b <= 57) || (b >= 97 && b <= 122)), "Not invalid character."); | |
slashUsername(username, _reserveSecret); | |
} | |
/** | |
* @notice Clear resolver and ownership of unowned subdomians. | |
* @param _labels Sequence to erase. | |
*/ | |
function eraseNode( | |
bytes32[] _labels | |
) | |
external | |
{ | |
uint len = _labels.length; | |
require(len != 0, "Nothing to erase"); | |
bytes32 label = _labels[len - 1]; | |
bytes32 subnode = keccak256(abi.encodePacked(ensNode, label)); | |
require(ensRegistry.owner(subnode) == address(0), "First slash/release top level subdomain"); | |
ensRegistry.setSubnodeOwner(ensNode, label, address(this)); | |
if(len > 1) { | |
eraseNodeHierarchy(len - 2, _labels, subnode); | |
} | |
ensRegistry.setResolver(subnode, 0); | |
ensRegistry.setOwner(subnode, 0); | |
} | |
/** | |
* @notice Migrate account to new registry, opt-in to new contract. | |
* @param _label Username hash. | |
**/ | |
function moveAccount( | |
bytes32 _label, | |
UsernameRegistrar _newRegistry | |
) | |
external | |
{ | |
require(state == RegistrarState.Moved, "Wrong contract state"); | |
require(msg.sender == accounts[_label].owner, "Callable only by account owner."); | |
require(ensRegistry.owner(ensNode) == address(_newRegistry), "Wrong update"); | |
Account memory account = accounts[_label]; | |
delete accounts[_label]; | |
token.approve(_newRegistry, account.balance); | |
_newRegistry.migrateUsername( | |
_label, | |
account.balance, | |
account.creationTime, | |
account.owner | |
); | |
} | |
/** | |
* @notice Activate registration. | |
* @param _price The price of registration. | |
*/ | |
function activate( | |
uint256 _price | |
) | |
external | |
onlyController | |
{ | |
require(state == RegistrarState.Inactive, "Registry state is not Inactive"); | |
require(ensRegistry.owner(ensNode) == address(this), "Registry does not own registry"); | |
price = _price; | |
setState(RegistrarState.Active); | |
emit RegistryPrice(_price); | |
} | |
/** | |
* @notice Updates Public Resolver for resolving users. | |
* @param _resolver New PublicResolver. | |
*/ | |
function setResolver( | |
address _resolver | |
) | |
external | |
onlyController | |
{ | |
resolver = PublicResolver(_resolver); | |
} | |
/** | |
* @notice Updates registration price. | |
* @param _price New registration price. | |
*/ | |
function updateRegistryPrice( | |
uint256 _price | |
) | |
external | |
onlyController | |
{ | |
require(state == RegistrarState.Active, "Registry not owned"); | |
price = _price; | |
emit RegistryPrice(_price); | |
} | |
/** | |
* @notice Transfer ownership of ensNode to `_newRegistry`. | |
* Usernames registered are not affected, but they would be able to instantly release. | |
* @param _newRegistry New UsernameRegistrar for hodling `ensNode` node. | |
*/ | |
function moveRegistry( | |
UsernameRegistrar _newRegistry | |
) | |
external | |
onlyController | |
{ | |
require(_newRegistry != this, "Cannot move to self."); | |
require(ensRegistry.owner(ensNode) == address(this), "Registry not owned anymore."); | |
setState(RegistrarState.Moved); | |
ensRegistry.setOwner(ensNode, _newRegistry); | |
_newRegistry.migrateRegistry(price); | |
emit RegistryMoved(_newRegistry); | |
} | |
/** | |
* @notice Opt-out migration of username from `parentRegistry()`. | |
* Clear ENS resolver and subnode owner. | |
* @param _label Username hash. | |
*/ | |
function dropUsername( | |
bytes32 _label | |
) | |
external | |
onlyParentRegistry | |
{ | |
require(accounts[_label].creationTime == 0, "Already migrated"); | |
bytes32 namehash = keccak256(abi.encodePacked(ensNode, _label)); | |
ensRegistry.setSubnodeOwner(ensNode, _label, address(this)); | |
ensRegistry.setResolver(namehash, address(0)); | |
ensRegistry.setOwner(namehash, address(0)); | |
} | |
/** | |
* @notice Withdraw not reserved tokens | |
* @param _token Address of ERC20 withdrawing excess, or address(0) if want ETH. | |
* @param _beneficiary Address to send the funds. | |
**/ | |
function withdrawExcessBalance( | |
address _token, | |
address _beneficiary | |
) | |
external | |
onlyController | |
{ | |
require(_beneficiary != address(0), "Cannot burn token"); | |
if (_token == address(0)) { | |
_beneficiary.transfer(address(this).balance); | |
} else { | |
ERC20Token excessToken = ERC20Token(_token); | |
uint256 amount = excessToken.balanceOf(address(this)); | |
if(_token == address(token)){ | |
require(amount > reserveAmount, "Is not excess"); | |
amount -= reserveAmount; | |
} else { | |
require(amount > 0, "No balance"); | |
} | |
excessToken.transfer(_beneficiary, amount); | |
} | |
} | |
/** | |
* @notice Withdraw ens nodes not belonging to this contract. | |
* @param _domainHash Ens node namehash. | |
* @param _beneficiary New owner of ens node. | |
**/ | |
function withdrawWrongNode( | |
bytes32 _domainHash, | |
address _beneficiary | |
) | |
external | |
onlyController | |
{ | |
require(_beneficiary != address(0), "Cannot burn node"); | |
require(_domainHash != ensNode, "Cannot withdraw main node"); | |
require(ensRegistry.owner(_domainHash) == address(this), "Not owner of this node"); | |
ensRegistry.setOwner(_domainHash, _beneficiary); | |
} | |
/** | |
* @notice Gets registration price. | |
* @return Registration price. | |
**/ | |
function getPrice() | |
external | |
view | |
returns(uint256 registryPrice) | |
{ | |
return price; | |
} | |
/** | |
* @notice reads amount tokens locked in username | |
* @param _label Username hash. | |
* @return Locked username balance. | |
**/ | |
function getAccountBalance(bytes32 _label) | |
external | |
view | |
returns(uint256 accountBalance) | |
{ | |
accountBalance = accounts[_label].balance; | |
} | |
/** | |
* @notice reads username account owner at this contract, | |
* which can release or migrate in case of upgrade. | |
* @param _label Username hash. | |
* @return Username account owner. | |
**/ | |
function getAccountOwner(bytes32 _label) | |
external | |
view | |
returns(address owner) | |
{ | |
owner = accounts[_label].owner; | |
} | |
/** | |
* @notice reads when the account was registered | |
* @param _label Username hash. | |
* @return Registration time. | |
**/ | |
function getCreationTime(bytes32 _label) | |
external | |
view | |
returns(uint256 creationTime) | |
{ | |
creationTime = accounts[_label].creationTime; | |
} | |
/** | |
* @notice calculate time where username can be released | |
* @param _label Username hash. | |
* @return Exact time when username can be released. | |
**/ | |
function getExpirationTime(bytes32 _label) | |
external | |
view | |
returns(uint256 releaseTime) | |
{ | |
uint256 creationTime = accounts[_label].creationTime; | |
if (creationTime > 0){ | |
releaseTime = creationTime + releaseDelay; | |
} | |
} | |
/** | |
* @notice calculate reward part an account could payout on slash | |
* @param _label Username hash. | |
* @return Part of reward | |
**/ | |
function getSlashRewardPart(bytes32 _label) | |
external | |
view | |
returns(uint256 partReward) | |
{ | |
uint256 balance = accounts[_label].balance; | |
if (balance > 0) { | |
partReward = balance / 3; | |
} | |
} | |
/** | |
* @notice Support for "approveAndCall". Callable only by `token()`. | |
* @param _from Who approved. | |
* @param _amount Amount being approved, need to be equal `getPrice()`. | |
* @param _token Token being approved, need to be equal `token()`. | |
* @param _data Abi encoded data with selector of `register(bytes32,address,bytes32,bytes32)`. | |
*/ | |
function receiveApproval( | |
address _from, | |
uint256 _amount, | |
address _token, | |
bytes _data | |
) | |
public | |
{ | |
require(_amount == price, "Wrong value"); | |
require(_token == address(token), "Wrong token"); | |
require(_token == address(msg.sender), "Wrong call"); | |
require(_data.length <= 132, "Wrong data length"); | |
bytes4 sig; | |
bytes32 label; | |
address account; | |
bytes32 pubkeyA; | |
bytes32 pubkeyB; | |
(sig, label, account, pubkeyA, pubkeyB) = abiDecodeRegister(_data); | |
require( | |
sig == bytes4(0xb82fedbb), //bytes4(keccak256("register(bytes32,address,bytes32,bytes32)")) | |
"Wrong method selector" | |
); | |
registerUser(_from, label, account, pubkeyA, pubkeyB); | |
} | |
/** | |
* @notice Continues migration of username to new registry. | |
* @param _label Username hash. | |
* @param _tokenBalance Amount being transfered from `parentRegistry()`. | |
* @param _creationTime Time user registrated in `parentRegistry()` is preserved. | |
* @param _accountOwner Account owner which migrated the account. | |
**/ | |
function migrateUsername( | |
bytes32 _label, | |
uint256 _tokenBalance, | |
uint256 _creationTime, | |
address _accountOwner | |
) | |
external | |
onlyParentRegistry | |
{ | |
if (_tokenBalance > 0) { | |
require( | |
token.transferFrom( | |
parentRegistry, | |
address(this), | |
_tokenBalance | |
), | |
"Error moving funds from old registar." | |
); | |
reserveAmount += _tokenBalance; | |
} | |
accounts[_label] = Account(_tokenBalance, _creationTime, _accountOwner); | |
} | |
/** | |
* @dev callable only by parent registry to continue migration | |
* of registry and activate registration. | |
* @param _price The price of registration. | |
**/ | |
function migrateRegistry( | |
uint256 _price | |
) | |
external | |
onlyParentRegistry | |
{ | |
require(state == RegistrarState.Inactive, "Not Inactive"); | |
require(ensRegistry.owner(ensNode) == address(this), "ENS registry owner not transfered."); | |
price = _price; | |
setState(RegistrarState.Active); | |
emit RegistryPrice(_price); | |
} | |
/** | |
* @notice Registers `_label` username to `ensNode` setting msg.sender as owner. | |
* @param _owner Address registering the user and paying registry price. | |
* @param _label Choosen unowned username hash. | |
* @param _account Optional address to set at public resolver. | |
* @param _pubkeyA Optional pubkey part A to set at public resolver. | |
* @param _pubkeyB Optional pubkey part B to set at public resolver. | |
*/ | |
function registerUser( | |
address _owner, | |
bytes32 _label, | |
address _account, | |
bytes32 _pubkeyA, | |
bytes32 _pubkeyB | |
) | |
internal | |
returns(bytes32 namehash) | |
{ | |
require(state == RegistrarState.Active, "Registry not active."); | |
namehash = keccak256(abi.encodePacked(ensNode, _label)); | |
require(ensRegistry.owner(namehash) == address(0), "ENS node already owned."); | |
require(accounts[_label].creationTime == 0, "Username already registered."); | |
accounts[_label] = Account(price, block.timestamp, _owner); | |
if(price > 0) { | |
require(token.allowance(_owner, address(this)) >= price, "Unallowed to spend."); | |
require( | |
token.transferFrom( | |
_owner, | |
address(this), | |
price | |
), | |
"Transfer failed" | |
); | |
reserveAmount += price; | |
} | |
bool resolvePubkey = _pubkeyA != 0 || _pubkeyB != 0; | |
bool resolveAccount = _account != address(0); | |
if (resolvePubkey || resolveAccount) { | |
//set to self the ownership to setup initial resolver | |
ensRegistry.setSubnodeOwner(ensNode, _label, address(this)); | |
ensRegistry.setResolver(namehash, resolver); //default resolver | |
if (resolveAccount) { | |
resolver.setAddr(namehash, _account); | |
} | |
if (resolvePubkey) { | |
resolver.setPubkey(namehash, _pubkeyA, _pubkeyB); | |
} | |
ensRegistry.setOwner(namehash, _owner); | |
} else { | |
//transfer ownership of subdone directly to registrant | |
ensRegistry.setSubnodeOwner(ensNode, _label, _owner); | |
} | |
emit UsernameOwner(namehash, _owner); | |
} | |
/** | |
* @dev Removes account hash of `_username` and send account.balance to msg.sender. | |
* @param _username Username being slashed. | |
*/ | |
function slashUsername( | |
bytes _username, | |
uint256 _reserveSecret | |
) | |
internal | |
{ | |
bytes32 label = keccak256(_username); | |
bytes32 namehash = keccak256(abi.encodePacked(ensNode, label)); | |
uint256 amountToTransfer = 0; | |
uint256 creationTime = accounts[label].creationTime; | |
address owner = ensRegistry.owner(namehash); | |
if(creationTime == 0) { | |
require( | |
owner != address(0) || | |
ensRegistry.resolver(namehash) != address(0), | |
"Nothing to slash." | |
); | |
} else { | |
assert(creationTime != block.timestamp); | |
amountToTransfer = accounts[label].balance; | |
delete accounts[label]; | |
} | |
ensRegistry.setSubnodeOwner(ensNode, label, address(this)); | |
ensRegistry.setResolver(namehash, address(0)); | |
ensRegistry.setOwner(namehash, address(0)); | |
if (amountToTransfer > 0) { | |
reserveAmount -= amountToTransfer; | |
uint256 partialDeposit = amountToTransfer / 3; | |
amountToTransfer = partialDeposit * 2; // reserve 1/3 to network (controller) | |
bytes32 secret = keccak256(abi.encodePacked(namehash, creationTime, _reserveSecret)); | |
SlashReserve memory reserve = reservedSlashers[secret]; | |
require(reserve.reserver != address(0), "Not reserved."); | |
require(reserve.blockNumber < block.number, "Cannot reveal in same block"); | |
delete reservedSlashers[secret]; | |
require(token.transfer(reserve.reserver, amountToTransfer), "Error in transfer."); | |
} | |
emit UsernameOwner(namehash, address(0)); | |
} | |
function setState(RegistrarState _state) private { | |
state = _state; | |
emit RegistryState(_state); | |
} | |
/** | |
* @notice recursively erase all _labels in _subnode | |
* @param _idx recursive position of _labels to erase | |
* @param _labels list of subnode labes | |
* @param _subnode subnode being erased | |
*/ | |
function eraseNodeHierarchy( | |
uint _idx, | |
bytes32[] _labels, | |
bytes32 _subnode | |
) | |
private | |
{ | |
// Take ownership of the node | |
ensRegistry.setSubnodeOwner(_subnode, _labels[_idx], address(this)); | |
bytes32 subnode = keccak256(abi.encodePacked(_subnode, _labels[_idx])); | |
// Recurse if there are more labels | |
if (_idx > 0) { | |
eraseNodeHierarchy(_idx - 1, _labels, subnode); | |
} | |
// Erase the resolver and owner records | |
ensRegistry.setResolver(subnode, 0); | |
ensRegistry.setOwner(subnode, 0); | |
} | |
/** | |
* @dev Decodes abi encoded data with selector for "register(bytes32,address,bytes32,bytes32)". | |
* @param _data Abi encoded data. | |
* @return Decoded registry call. | |
*/ | |
function abiDecodeRegister( | |
bytes _data | |
) | |
private | |
pure | |
returns( | |
bytes4 sig, | |
bytes32 label, | |
address account, | |
bytes32 pubkeyA, | |
bytes32 pubkeyB | |
) | |
{ | |
assembly { | |
sig := mload(add(_data, add(0x20, 0))) | |
label := mload(add(_data, 36)) | |
account := mload(add(_data, 68)) | |
pubkeyA := mload(add(_data, 100)) | |
pubkeyB := mload(add(_data, 132)) | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment