pragma solidity ^0.5.3; /* Temporary Hash Registrar ======================== This is a simplified version of a hash registrar. It is purporsefully limited: names cannot be six letters or shorter, new auctions will stop after 4 years. The plan is to test the basic features and then move to a new contract in at most 2 years, when some sort of renewal mechanism will be enabled. Refund schedule =============== Case A: Reveal Period ------------------------------------------------------------------------------------ | (I) Auction winner | Funds are locked in Deed. 80% are returned on Deed release | ------------------------------------------------------------------------------------ | (II) Auction losser | 100% refund over the losing bid | ------------------------------------------------------------------------------------ Case B: Late-Reveal Period (2 weeks after auction finish) --------------------------------------------------------------------------------------------------- | (I) Bid that would have won | 20% taken as fee | --------------------------------------------------------------------------------------------------- | (II) Bid that would have affected 2nd place | Difference with the actual 2nd place taken as fee | --------------------------------------------------------------------------------------------------- | (III) Otherwise | 0.5% taken as fee | --------------------------------------------------------------------------------------------------- Case C: Any bid unsealed beyond the Late-Reveal period won't be refunded */ import './TokenDeed.sol'; import '@rsksmart/rns-registry/contracts/RNS.sol'; import '@rsksmart/erc677/contracts/IERC677.sol'; import '@rsksmart/erc677/contracts/ERC677TransferReceiver.sol'; import "@openzeppelin/contracts/math/SafeMath.sol"; /** * @title TokenRegistrar * @dev The registrar handles the auction process for each subnode of the node it owns. */ contract TokenRegistrar is ERC677TransferReceiver { using SafeMath for uint256; RNS public rns; bytes32 public rootNode; IERC677 public tokenContract; mapping (bytes32 => Entry) _entries; mapping (address => mapping (bytes32 => TokenDeed)) public sealedBids; enum Mode { Open, Auction, Owned, Forbidden, Reveal } bytes4 constant SIGN_NEW_BID = 0x1413151f; // sha3('newBidWithToken(address,uint256,bytes32)') bytes4 constant SIGN_PAY_RENT = 0xe1ac9915; // sha3('payRentWithToken(address,uint256,bytes32)') uint32 constant TOTAL_AUCTION_LENGTH = 5 days; uint32 constant REVEAL_PERIOD = 48 hours; uint32 constant LATE_UNSEAL_PERIOD = 15 days; uint constant RELEASE_FEE_PER_MIL = 200; // 200 of 1000 = 20% uint constant MIN_TOKEN_QUANTITY = 1 * 10**18; // 1 token uint constant RENT_VALUE = 1 * 10**18; // 1 token event AuctionStarted(bytes32 indexed hash, uint registrationDate); event NewBid(bytes32 indexed hash, address indexed bidder, uint deposit); event BidRevealed(bytes32 indexed hash, address indexed owner, uint value, uint8 status); event HashRegistered(bytes32 indexed hash, address indexed owner, uint value, uint registrationDate); event HashReleased(bytes32 indexed hash, uint value); struct Entry { TokenDeed deed; uint registrationDate; uint value; uint highestBid; } modifier inState(bytes32 _hash, Mode _state) { require(state(_hash) == _state); _; } modifier onlyOwner(bytes32 _hash) { require(state(_hash) == Mode.Owned && msg.sender == _entries[_hash].deed.owner()); _; } modifier registryOpen() { require(rns.owner(rootNode) == address(this)); _; } /** * @dev Constructs a new Registrar, with the provided address as the owner of the root node. * * @param _rns The address of the RNS * @param _rootNode The hash of the rootnode. * @param _tokenAddr The ERC677 contract address to handle tokens */ constructor(RNS _rns, bytes32 _rootNode, IERC677 _tokenAddr) public { rns = _rns; rootNode = _rootNode; tokenContract = _tokenAddr; } /** * @dev Start an auction for an available hash * * @param _hash The hash to start an auction on */ function startAuction(bytes32 _hash) public registryOpen() { Mode mode = state(_hash); if (mode == Mode.Auction) return; require(mode == Mode.Open); Entry storage newAuction = _entries[_hash]; newAuction.registrationDate = now + TOTAL_AUCTION_LENGTH; newAuction.value = 0; newAuction.highestBid = 0; newAuction.deed = TokenDeed(0); emit AuctionStarted(_hash, newAuction.registrationDate); } /** * @dev Start multiple auctions for better anonymity * * Anyone can start an auction by sending an array of hashes that they want to bid for. * Arrays are sent so that someone can open up an auction for X dummy hashes when they * are only really interested in bidding for one. This will increase the cost for an * attacker to simply bid blindly on all new auctions. Dummy auctions that are * open but not bid on are closed after a week. * * @param _hashes An array of hashes, at least one of which you presumably want to bid on */ function startAuctions(bytes32[] memory _hashes) public { for (uint i = 0; i < _hashes.length; i ++) { startAuction(_hashes[i]); } } /** * @dev Submit a new sealed bid on a desired hash in a blind auction * * Bids are sent by sending a message to the main contract with a hash and an amount. The hash * contains information about the bid, including the bidded hash, the bid amount, and a random * salt. Bids are not tied to any one auction until they are revealed. The value of the bid * itself can be masqueraded by sending more than the value of your actual bid. This is * followed by a 48h reveal period. For bids revealed after this period, a percentage (defined in the late unsealing * Refund schedule) will be sent to a special resource pool address. * Since this is an auction, it is expected that most public hashes, like known domains and common dictionary * words, will have multiple bidders pushing the price up. * * This method requires the sender to approve the Registrar to use the specified tokenQuantity in the ERC677 contract. * Otherwise it can be done through the tokenFallback after a transfer with the corresponding parameters * * @param _sealedBid A sealedBid, created by the shaBid function * @param _tokenQuantity token quantity to bid */ function newBid(bytes32 _sealedBid, uint _tokenQuantity) public { require(tokenContract.transferFrom(msg.sender, address(this), _tokenQuantity)); newBidAfterTransfer(msg.sender, _tokenQuantity, _sealedBid); } /** * @dev Method to be called through a dynamic invocation from an ERC677 token contract * * @param _from Address sending the tokens as well as submitting the bid * @param _tokenQuantity Amount in tokens received throuh the transference * @param _sealedBid Sealed bid, created through the shaBid function **/ function newBidWithToken(address _from, uint _tokenQuantity, bytes32 _sealedBid) public { require(msg.sender == address(tokenContract)); newBidAfterTransfer(_from, _tokenQuantity, _sealedBid); } /** * @dev Internal method which handles the new bidding logic * * @param _from Address sending the tokens as well as submitting the bid * @param _tokenQuantity Amount in tokens received through the transference. To be used to mask the actual bidded value * @param _sealedBid Sealed bid, created through the shaBid function **/ function newBidAfterTransfer(address _from, uint _tokenQuantity, bytes32 _sealedBid) private { require(address(sealedBids[_from][_sealedBid]) == address(0)); // Creates a new hash contract with the owner TokenDeed createdBid = new TokenDeed(_from, _tokenQuantity, tokenContract); require(tokenContract.transfer(address(createdBid), _tokenQuantity)); sealedBids[_from][_sealedBid] = createdBid; emit NewBid(_sealedBid, _from, _tokenQuantity); } /** * @dev Start a set of auctions and bid on one of them * * This method functions identically to calling `startAuctions` followed by `newBid`, * but all in one transaction. * * @param _hashes A list of hashes to start auctions on. * @param _sealedBid A sealed bid for one of the auctions. * @param _tokenQuantity Amount of tokens to mask the bid with. */ function startAuctionsAndBid(bytes32[] memory _hashes, bytes32 _sealedBid, uint _tokenQuantity) public payable { startAuctions(_hashes); newBid(_sealedBid, _tokenQuantity); } /** * @dev Submit the properties of a bid to reveal them * * @param _hash The node in the sealedBid * @param _value The bid amount in the sealedBid * @param _salt The sale in the sealedBid */ function unsealBid(bytes32 _hash, uint _value, bytes32 _salt) public { bytes32 seal = shaBid(_hash, msg.sender, _value, _salt); TokenDeed bid = sealedBids[msg.sender][seal]; require(address(bid) != address(0)); sealedBids[msg.sender][seal] = TokenDeed(0); Entry storage h = _entries[_hash]; uint value = min(_value, bid.tokenQuantity()); bid.setBalance(value, true); Mode auctionState = state(_hash); if (auctionState == Mode.Owned) { // By this point the auction has ended. The refund values are defined in the above Refund Schedule uint256 refundRatio = 995; // See: Case B)III if (h.registrationDate + LATE_UNSEAL_PERIOD < now) { refundRatio = 0; // See: Case C } else { if (value > h.highestBid) { refundRatio = 800; // See: Case B)I } else if (value > h.value) { // See: Case B)II // Compute percentage corresponding to the difference between the actual 2nd place (h.value) // and the late unsealed bid (value) refundRatio = h.value.mul(1000).div(value); } } bid.closeDeed(refundRatio); emit BidRevealed(_hash, msg.sender, value, 1); } else if (auctionState != Mode.Reveal) { // invalid phase for unsealing revert(); } else if (value < MIN_TOKEN_QUANTITY || bid.creationDate() > h.registrationDate - REVEAL_PERIOD) { // Bid below the minimum or too late (created in the reveal period) bid.closeDeed(1000); emit BidRevealed(_hash, msg.sender, value, 0); } else if (value > h.highestBid) { // New winner // Cancel the other bid if (address(h.deed) != address(0)) { TokenDeed previousWinner = h.deed; previousWinner.closeDeed(1000); } // Set new winner // Per the rules of a vickery auction, the value becomes the previous highestBid h.value = h.highestBid; // will be zero if there's only 1 bidder h.highestBid = value; h.deed = bid; emit BidRevealed(_hash, msg.sender, value, 2); } else if (value > h.value) { // Not winner, but affects second place h.value = value; bid.closeDeed(1000); emit BidRevealed(_hash, msg.sender, value, 3); } else { // Bid doesn't affect auction bid.closeDeed(1000); emit BidRevealed(_hash, msg.sender, value, 4); } } /** * @dev Finalize an auction after the registration date has passed * * Updates the Registry to reflect the new node owner. Starts the winning Deed's expiration period. * * @param _hash The hash of the name the auction is for */ function finalizeAuction(bytes32 _hash) public onlyOwner(_hash) { Entry storage h = _entries[_hash]; // Handles the case when there's only a single bidder (h.value is zero) h.value = max(h.value, MIN_TOKEN_QUANTITY); h.deed.setBalance(h.value, true); h.deed.startExpiration(h.registrationDate, RENT_VALUE); trySetSubnodeOwner(_hash, h.deed.owner()); emit HashRegistered(_hash, h.deed.owner(), h.value, h.registrationDate); } /** * @dev The owner of a domain may transfer it to someone else at any time. * * @param _hash The node to transfer * @param _newOwner The address to transfer ownership to */ function transfer(bytes32 _hash, address _newOwner) public onlyOwner(_hash) { require(_newOwner != address(0)); Entry storage h = _entries[_hash]; h.deed.setOwner(_newOwner); trySetSubnodeOwner(_hash, _newOwner); } /** * @dev After some time, or if we're no longer the registrar, the owner can release * the name and get a part of their tokens back. * * The allowed release period is within the rent payment period, which starts 3 months before the expiration date * * @param _hash The node to release */ function releaseDeed(bytes32 _hash) public onlyOwner(_hash) { Entry storage h = _entries[_hash]; TokenDeed deedContract = h.deed; require(now < deedContract.expirationDate() || rns.owner(rootNode) != address(this)); h.value = 0; h.highestBid = 0; h.deed = TokenDeed(0); _tryEraseSingleNode(_hash); // return funds after deducting a fee and close the deed deedContract.closeDeed(1000 - RELEASE_FEE_PER_MIL); emit HashReleased(_hash, h.value); } /** * @dev Allows anyone to delete the owner and resolver records for a (subdomain of) a * name that is not currently owned in the registrar. If passing, eg, 'foo.bar.rsk', * the owner and resolver fields on 'foo.bar.rsk' and 'bar.rsk' will all be cleared. * * @param _labels A series of label hashes identifying the name to zero out, rooted at the * registrar's root. Must contain at least one element. For instance, to zero * 'foo.bar.rsk' on a registrar that owns '.rsk', pass an array containing * [keccak256('foo'), keccak256('bar')]. */ function eraseNode(bytes32[] memory _labels) public { require(_labels.length != 0); require(state(_labels[_labels.length - 1]) != Mode.Owned); _eraseNodeHierarchy(_labels.length - 1, _labels, rootNode); } /** * @dev Transfers the deed to the current registrar, if different from this one. * * Used during the upgrade process to a permanent registrar. * * @param _hash The name hash to transfer. */ function transferRegistrars(bytes32 _hash) public onlyOwner(_hash) { address registrar = rns.owner(rootNode); require(registrar != address(this)); // Migrate the deed Entry storage h = _entries[_hash]; h.deed.setRegistrar(registrar); // Call the new registrar to accept the transfer TokenRegistrar(registrar).acceptRegistrarTransfer(_hash, h.deed, h.registrationDate); // Zero out the Entry h.deed = TokenDeed(0); h.registrationDate = 0; h.value = 0; h.highestBid = 0; } /** * @dev Pay the yearly rent for a name * * Names have a vigency of 1 year after the registration date. Said expiry date is reflected through the associated Deed * contract. Owners have a period of 3 months before the expiryDate to pay the rent and extend their ownership for 1 year * * @param _hash The hash of the name to pay the rent for */ function payRent(bytes32 _hash) public { require(tokenContract.transferFrom(msg.sender, address(this), RENT_VALUE)); payRentAfterTransfer(_hash); } /** * @dev Method to be called through a dynamic invocation from an ERC677 token contract * * @param _from Address sending the tokens as well as submitting the bid * @param _tokenQuantity Amount in tokens received throuh the transference * @param _hash Hash of the name to pay the rent for **/ function payRentWithToken(address _from, uint _tokenQuantity, bytes32 _hash) public { require(_tokenQuantity == RENT_VALUE); require(msg.sender == address(tokenContract)); payRentAfterTransfer(_hash); } /** * @dev Internal method which handles the rent payment logic * * @param _hash Hash of the name to pay the rent for **/ function payRentAfterTransfer(bytes32 _hash) private { require(state(_hash) == Mode.Owned); Entry storage h = _entries[_hash]; require(address(h.deed) != address(0)); require(tokenContract.transfer(address(h.deed), RENT_VALUE)); require(h.deed.payRent(RENT_VALUE)); } /** * @dev Accepts a transfer from a previous registrar; stubbed out here since there * is no previous registrar implementing this interface. * * @param _hash The sha3 hash of the label to transfer. * @param _deed The TokenDeed object for the name being transferred in. * @param _registrationDate The date at which the name was originally registered. */ function acceptRegistrarTransfer(bytes32 _hash, TokenDeed _deed, uint _registrationDate) public { _hash; _deed; _registrationDate; // Don't warn about unused variables } // State transitions for names: // Open -> Auction (startAuction) // Auction -> Reveal // Reveal -> Owned // Reveal -> Open (if nobody bid) // Owned -> Open (releaseDeed or the deed has expired) function state(bytes32 _hash) public view returns (Mode) { Entry storage entry = _entries[_hash]; if (now < entry.registrationDate) { if (now < entry.registrationDate - REVEAL_PERIOD) { return Mode.Auction; } else { return Mode.Reveal; } } else { // there may not be any bid (not yet auctioned) or there is and it may have already expired (Expired) if (entry.highestBid == 0 || entry.deed.expired()) { return Mode.Open; } else { return Mode.Owned; } } } /** * @dev Returns information related to a certain name * * @param _hash Hash of the name to query about **/ function entries(bytes32 _hash) public view returns (Mode, address, uint, uint, uint) { Entry storage h = _entries[_hash]; return (state(_hash), address(h.deed), h.registrationDate, h.value, h.highestBid); } /** * @dev Hash the values required for a secret bid * * @param _hash The node corresponding to the desired namehash * @param _value The bid amount in tokens * @param _salt A random value to ensure secrecy of the bid * @return The hash of the bid values */ function shaBid(bytes32 _hash, address _owner, uint _value, bytes32 _salt) public pure returns (bytes32) { return keccak256(abi.encodePacked(_hash, _owner, _value, _salt)); } function _tryEraseSingleNode(bytes32 _label) internal { if (rns.owner(rootNode) == address(this)) { rns.setSubnodeOwner(rootNode, _label, address(this)); bytes32 node = keccak256(abi.encodePacked(rootNode, _label)); rns.setResolver(node, address(0)); rns.setOwner(node, address(0)); } } function _eraseNodeHierarchy(uint _idx, bytes32[] memory _labels, bytes32 _node) internal { // Take ownership of the node rns.setSubnodeOwner(_node, _labels[_idx], address(this)); _node = keccak256(abi.encodePacked(_node, _labels[_idx])); // Recurse if there are more labels if (_idx > 0) { _eraseNodeHierarchy(_idx - 1, _labels, _node); } // Erase the resolver and owner records rns.setResolver(_node, address(0)); rns.setOwner(_node, address(0)); } /** * @dev Assign the owner in RNS, if we're still the registrar * * @param _hash hash to change owner * @param _newOwner new owner to transfer to */ function trySetSubnodeOwner(bytes32 _hash, address _newOwner) internal { if (rns.owner(rootNode) == address(this)) rns.setSubnodeOwner(rootNode, _hash, _newOwner); } /** * @dev Returns the maximum of two unsigned integers * * @param a A number to compare * @param b A number to compare * @return The maximum of two unsigned integers */ function max(uint a, uint b) internal pure returns (uint) { if (a > b) return a; else return b; } /** * @dev Returns the minimum of two unsigned integers * * @param a A number to compare * @param b A number to compare * @return The minimum of two unsigned integers */ function min(uint a, uint b) internal pure returns (uint) { if (a < b) return a; else return b; } /** * @dev Fallback function to be called when the contract receives a transference through an ERC677 contract * * Functions supported: * - newBidWithToken (signature 0x1413151f) with a 32 byte parameter (sealedBid to submit) * - payRentWithToken (signature 0xe1ac9915) with a 32 byte parameter (hash of the name to pay the rent for) * * @param _from Address which sent the tokens * @param _value Amount of tokens sent * @param _data Byte array with information of which function to call and the parameters used for the invocation **/ function tokenFallback(address _from, uint256 _value, bytes memory _data) public returns (bool) { if (_data.length < 4) return true; require(msg.sender == address(tokenContract)); bytes4 signature = bytesToBytes4(_data, 0); if (signature == SIGN_NEW_BID) { bytes32 sealedBid = bytesToBytes32(_data, 4); newBidWithToken(_from, _value, sealedBid); } else if (signature == SIGN_PAY_RENT) { bytes32 name = bytesToBytes32(_data, 4); payRentWithToken(_from, _value, name); } else { revert(); } return true; } function bytesToBytes4(bytes memory _b, uint _offset) private pure returns (bytes4) { bytes4 out; for (uint i = 0; i < 4; i++) { out |= bytes4(_b[_offset + i] & 0xFF) >> (i * 8); } return out; } /** * @dev Given a byte array and a given offset, extract the following 32 bytes into an array * * from https://ethereum.stackexchange.com/questions/7702/how-to-convert-byte-array-to-bytes32-in-solidity **/ function bytesToBytes32(bytes memory _b, uint _offset) private pure returns (bytes32) { bytes32 out; for (uint i = 0; i < 32; i++) { out |= bytes32(_b[_offset + i] & 0xFF) >> (i * 8); } return out; } }