1608 lines
56 KiB
Solidity
1608 lines
56 KiB
Solidity
pragma solidity >=0.4.24;
|
|
|
|
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);
|
|
|
|
// Logged when an operator is added or removed.
|
|
event ApprovalForAll(address indexed owner, address indexed operator, bool approved);
|
|
|
|
function setRecord(bytes32 node, address owner, address resolver, uint64 ttl) external;
|
|
function setSubnodeRecord(bytes32 node, bytes32 label, address owner, address resolver, uint64 ttl) external;
|
|
function setSubnodeOwner(bytes32 node, bytes32 label, address owner) external returns(bytes32);
|
|
function setResolver(bytes32 node, address resolver) external;
|
|
function setOwner(bytes32 node, address owner) external;
|
|
function setTTL(bytes32 node, uint64 ttl) external;
|
|
function setApprovalForAll(address operator, bool approved) external;
|
|
function owner(bytes32 node) external view returns (address);
|
|
function resolver(bytes32 node) external view returns (address);
|
|
function ttl(bytes32 node) external view returns (uint64);
|
|
function recordExists(bytes32 node) external view returns (bool);
|
|
function isApprovedForAll(address owner, address operator) external view returns (bool);
|
|
}
|
|
|
|
/**
|
|
* The ENS registry contract.
|
|
*/
|
|
contract ENSRegistry is ENS {
|
|
|
|
struct Record {
|
|
address owner;
|
|
address resolver;
|
|
uint64 ttl;
|
|
}
|
|
|
|
mapping (bytes32 => Record) records;
|
|
mapping (address => mapping(address => bool)) operators;
|
|
|
|
// Permits modifications only by the owner of the specified node.
|
|
modifier authorised(bytes32 node) {
|
|
address owner = records[node].owner;
|
|
require(owner == msg.sender || operators[owner][msg.sender]);
|
|
_;
|
|
}
|
|
|
|
/**
|
|
* @dev Constructs a new ENS registrar.
|
|
*/
|
|
constructor() public {
|
|
records[0x0].owner = msg.sender;
|
|
}
|
|
|
|
/**
|
|
* @dev Sets the record for a node.
|
|
* @param node The node to update.
|
|
* @param owner The address of the new owner.
|
|
* @param resolver The address of the resolver.
|
|
* @param ttl The TTL in seconds.
|
|
*/
|
|
function setRecord(bytes32 node, address owner, address resolver, uint64 ttl) external {
|
|
setOwner(node, owner);
|
|
_setResolverAndTTL(node, resolver, ttl);
|
|
}
|
|
|
|
/**
|
|
* @dev Sets the record for a subnode.
|
|
* @param node The parent node.
|
|
* @param label The hash of the label specifying the subnode.
|
|
* @param owner The address of the new owner.
|
|
* @param resolver The address of the resolver.
|
|
* @param ttl The TTL in seconds.
|
|
*/
|
|
function setSubnodeRecord(bytes32 node, bytes32 label, address owner, address resolver, uint64 ttl) external {
|
|
bytes32 subnode = setSubnodeOwner(node, label, owner);
|
|
_setResolverAndTTL(subnode, resolver, ttl);
|
|
}
|
|
|
|
/**
|
|
* @dev Transfers ownership of a node to a new address. May only be called by the current owner of the node.
|
|
* @param node The node to transfer ownership of.
|
|
* @param owner The address of the new owner.
|
|
*/
|
|
function setOwner(bytes32 node, address owner) public authorised(node) {
|
|
_setOwner(node, owner);
|
|
emit Transfer(node, owner);
|
|
}
|
|
|
|
/**
|
|
* @dev Transfers ownership of a subnode keccak256(node, label) to a new address. May only be called by the owner of the parent node.
|
|
* @param node The parent node.
|
|
* @param label The hash of the label specifying the subnode.
|
|
* @param owner The address of the new owner.
|
|
*/
|
|
function setSubnodeOwner(bytes32 node, bytes32 label, address owner) public authorised(node) returns(bytes32) {
|
|
bytes32 subnode = keccak256(abi.encodePacked(node, label));
|
|
_setOwner(subnode, owner);
|
|
emit NewOwner(node, label, owner);
|
|
return subnode;
|
|
}
|
|
|
|
/**
|
|
* @dev Sets the resolver address for the specified node.
|
|
* @param node The node to update.
|
|
* @param resolver The address of the resolver.
|
|
*/
|
|
function setResolver(bytes32 node, address resolver) public authorised(node) {
|
|
emit NewResolver(node, resolver);
|
|
records[node].resolver = resolver;
|
|
}
|
|
|
|
/**
|
|
* @dev Sets the TTL for the specified node.
|
|
* @param node The node to update.
|
|
* @param ttl The TTL in seconds.
|
|
*/
|
|
function setTTL(bytes32 node, uint64 ttl) public authorised(node) {
|
|
emit NewTTL(node, ttl);
|
|
records[node].ttl = ttl;
|
|
}
|
|
|
|
/**
|
|
* @dev Enable or disable approval for a third party ("operator") to manage
|
|
* all of `msg.sender`'s ENS records. Emits the ApprovalForAll event.
|
|
* @param operator Address to add to the set of authorized operators.
|
|
* @param approved True if the operator is approved, false to revoke approval.
|
|
*/
|
|
function setApprovalForAll(address operator, bool approved) external {
|
|
operators[msg.sender][operator] = approved;
|
|
emit ApprovalForAll(msg.sender, operator, approved);
|
|
}
|
|
|
|
/**
|
|
* @dev Returns the address that owns the specified node.
|
|
* @param node The specified node.
|
|
* @return address of the owner.
|
|
*/
|
|
function owner(bytes32 node) public view returns (address) {
|
|
address addr = records[node].owner;
|
|
if (addr == address(this)) {
|
|
return address(0x0);
|
|
}
|
|
|
|
return addr;
|
|
}
|
|
|
|
/**
|
|
* @dev Returns the address of the resolver for the specified node.
|
|
* @param node The specified node.
|
|
* @return address of the resolver.
|
|
*/
|
|
function resolver(bytes32 node) public view returns (address) {
|
|
return records[node].resolver;
|
|
}
|
|
|
|
/**
|
|
* @dev Returns the TTL of a node, and any records associated with it.
|
|
* @param node The specified node.
|
|
* @return ttl of the node.
|
|
*/
|
|
function ttl(bytes32 node) public view returns (uint64) {
|
|
return records[node].ttl;
|
|
}
|
|
|
|
/**
|
|
* @dev Returns whether a record has been imported to the registry.
|
|
* @param node The specified node.
|
|
* @return Bool if record exists
|
|
*/
|
|
function recordExists(bytes32 node) public view returns (bool) {
|
|
return records[node].owner != address(0x0);
|
|
}
|
|
|
|
/**
|
|
* @dev Query if an address is an authorized operator for another address.
|
|
* @param owner The address that owns the records.
|
|
* @param operator The address that acts on behalf of the owner.
|
|
* @return True if `operator` is an approved operator for `owner`, false otherwise.
|
|
*/
|
|
function isApprovedForAll(address owner, address operator) external view returns (bool) {
|
|
return operators[owner][operator];
|
|
}
|
|
|
|
function _setOwner(bytes32 node, address owner) internal {
|
|
records[node].owner = owner;
|
|
}
|
|
|
|
function _setResolverAndTTL(bytes32 node, address resolver, uint64 ttl) internal {
|
|
if(resolver != records[node].resolver) {
|
|
records[node].resolver = resolver;
|
|
emit NewResolver(node, resolver);
|
|
}
|
|
|
|
if(ttl != records[node].ttl) {
|
|
records[node].ttl = ttl;
|
|
emit NewTTL(node, ttl);
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* The ENS registry contract.
|
|
*/
|
|
contract ENSRegistryWithFallback is ENSRegistry {
|
|
|
|
ENS public old;
|
|
|
|
/**
|
|
* @dev Constructs a new ENS registrar.
|
|
*/
|
|
constructor(ENS _old) public ENSRegistry() {
|
|
old = _old;
|
|
}
|
|
|
|
/**
|
|
* @dev Returns the address of the resolver for the specified node.
|
|
* @param node The specified node.
|
|
* @return address of the resolver.
|
|
*/
|
|
function resolver(bytes32 node) public view returns (address) {
|
|
if (!recordExists(node)) {
|
|
return old.resolver(node);
|
|
}
|
|
|
|
return super.resolver(node);
|
|
}
|
|
|
|
/**
|
|
* @dev Returns the address that owns the specified node.
|
|
* @param node The specified node.
|
|
* @return address of the owner.
|
|
*/
|
|
function owner(bytes32 node) public view returns (address) {
|
|
if (!recordExists(node)) {
|
|
return old.owner(node);
|
|
}
|
|
|
|
return super.owner(node);
|
|
}
|
|
|
|
/**
|
|
* @dev Returns the TTL of a node, and any records associated with it.
|
|
* @param node The specified node.
|
|
* @return ttl of the node.
|
|
*/
|
|
function ttl(bytes32 node) public view returns (uint64) {
|
|
if (!recordExists(node)) {
|
|
return old.ttl(node);
|
|
}
|
|
|
|
return super.ttl(node);
|
|
}
|
|
|
|
function _setOwner(bytes32 node, address owner) internal {
|
|
address addr = owner;
|
|
if (addr == address(0x0)) {
|
|
addr = address(this);
|
|
}
|
|
|
|
super._setOwner(node, addr);
|
|
}
|
|
}
|
|
|
|
contract ResolverBase {
|
|
bytes4 private constant INTERFACE_META_ID = 0x01ffc9a7;
|
|
|
|
function supportsInterface(bytes4 interfaceID) public pure returns(bool) {
|
|
return interfaceID == INTERFACE_META_ID;
|
|
}
|
|
|
|
function isAuthorised(bytes32 node) internal view returns(bool);
|
|
|
|
modifier authorised(bytes32 node) {
|
|
require(isAuthorised(node));
|
|
_;
|
|
}
|
|
|
|
function bytesToAddress(bytes memory b) internal pure returns(address payable a) {
|
|
require(b.length == 20);
|
|
assembly {
|
|
a := div(mload(add(b, 32)), exp(256, 12))
|
|
}
|
|
}
|
|
|
|
function addressToBytes(address a) internal pure returns(bytes memory b) {
|
|
b = new bytes(20);
|
|
assembly {
|
|
mstore(add(b, 32), mul(a, exp(256, 12)))
|
|
}
|
|
}
|
|
}
|
|
|
|
contract ABIResolver is ResolverBase {
|
|
bytes4 constant private ABI_INTERFACE_ID = 0x2203ab56;
|
|
|
|
event ABIChanged(bytes32 indexed node, uint256 indexed contentType);
|
|
|
|
mapping(bytes32=>mapping(uint256=>bytes)) abis;
|
|
|
|
/**
|
|
* 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 calldata data) external authorised(node) {
|
|
// Content types must be powers of 2
|
|
require(((contentType - 1) & contentType) == 0);
|
|
|
|
abis[node][contentType] = data;
|
|
emit ABIChanged(node, contentType);
|
|
}
|
|
|
|
/**
|
|
* 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) external view returns (uint256, bytes memory) {
|
|
mapping(uint256=>bytes) storage abiset = abis[node];
|
|
|
|
for (uint256 contentType = 1; contentType <= contentTypes; contentType <<= 1) {
|
|
if ((contentType & contentTypes) != 0 && abiset[contentType].length > 0) {
|
|
return (contentType, abiset[contentType]);
|
|
}
|
|
}
|
|
|
|
return (0, bytes(""));
|
|
}
|
|
|
|
function supportsInterface(bytes4 interfaceID) public pure returns(bool) {
|
|
return interfaceID == ABI_INTERFACE_ID || super.supportsInterface(interfaceID);
|
|
}
|
|
}
|
|
|
|
contract AddrResolver is ResolverBase {
|
|
bytes4 constant private ADDR_INTERFACE_ID = 0x3b3b57de;
|
|
bytes4 constant private ADDRESS_INTERFACE_ID = 0xf1cb7e06;
|
|
uint constant private COIN_TYPE_ETH = 60;
|
|
|
|
event AddrChanged(bytes32 indexed node, address a);
|
|
event AddressChanged(bytes32 indexed node, uint coinType, bytes newAddress);
|
|
|
|
mapping(bytes32=>mapping(uint=>bytes)) _addresses;
|
|
|
|
/**
|
|
* 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 a The address to set.
|
|
*/
|
|
function setAddr(bytes32 node, address a) external authorised(node) {
|
|
setAddr(node, COIN_TYPE_ETH, addressToBytes(a));
|
|
}
|
|
|
|
/**
|
|
* 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 payable) {
|
|
bytes memory a = addr(node, COIN_TYPE_ETH);
|
|
if(a.length == 0) {
|
|
return address(0);
|
|
}
|
|
return bytesToAddress(a);
|
|
}
|
|
|
|
function setAddr(bytes32 node, uint coinType, bytes memory a) public authorised(node) {
|
|
emit AddressChanged(node, coinType, a);
|
|
if(coinType == COIN_TYPE_ETH) {
|
|
emit AddrChanged(node, bytesToAddress(a));
|
|
}
|
|
_addresses[node][coinType] = a;
|
|
}
|
|
|
|
function addr(bytes32 node, uint coinType) public view returns(bytes memory) {
|
|
return _addresses[node][coinType];
|
|
}
|
|
|
|
function supportsInterface(bytes4 interfaceID) public pure returns(bool) {
|
|
return interfaceID == ADDR_INTERFACE_ID || interfaceID == ADDRESS_INTERFACE_ID || super.supportsInterface(interfaceID);
|
|
}
|
|
}
|
|
|
|
contract ContentHashResolver is ResolverBase {
|
|
bytes4 constant private CONTENT_HASH_INTERFACE_ID = 0xbc1c58d1;
|
|
|
|
event ContenthashChanged(bytes32 indexed node, bytes hash);
|
|
|
|
mapping(bytes32=>bytes) hashes;
|
|
|
|
/**
|
|
* Sets the contenthash 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 contenthash to set
|
|
*/
|
|
function setContenthash(bytes32 node, bytes calldata hash) external authorised(node) {
|
|
hashes[node] = hash;
|
|
emit ContenthashChanged(node, hash);
|
|
}
|
|
|
|
/**
|
|
* Returns the contenthash associated with an ENS node.
|
|
* @param node The ENS node to query.
|
|
* @return The associated contenthash.
|
|
*/
|
|
function contenthash(bytes32 node) external view returns (bytes memory) {
|
|
return hashes[node];
|
|
}
|
|
|
|
function supportsInterface(bytes4 interfaceID) public pure returns(bool) {
|
|
return interfaceID == CONTENT_HASH_INTERFACE_ID || super.supportsInterface(interfaceID);
|
|
}
|
|
}
|
|
|
|
library BytesUtils {
|
|
/*
|
|
* @dev Returns the keccak-256 hash of a byte range.
|
|
* @param self The byte string to hash.
|
|
* @param offset The position to start hashing at.
|
|
* @param len The number of bytes to hash.
|
|
* @return The hash of the byte range.
|
|
*/
|
|
function keccak(bytes memory self, uint offset, uint len) internal pure returns (bytes32 ret) {
|
|
require(offset + len <= self.length);
|
|
assembly {
|
|
ret := keccak256(add(add(self, 32), offset), len)
|
|
}
|
|
}
|
|
|
|
|
|
/*
|
|
* @dev Returns a positive number if `other` comes lexicographically after
|
|
* `self`, a negative number if it comes before, or zero if the
|
|
* contents of the two bytes are equal.
|
|
* @param self The first bytes to compare.
|
|
* @param other The second bytes to compare.
|
|
* @return The result of the comparison.
|
|
*/
|
|
function compare(bytes memory self, bytes memory other) internal pure returns (int) {
|
|
return compare(self, 0, self.length, other, 0, other.length);
|
|
}
|
|
|
|
/*
|
|
* @dev Returns a positive number if `other` comes lexicographically after
|
|
* `self`, a negative number if it comes before, or zero if the
|
|
* contents of the two bytes are equal. Comparison is done per-rune,
|
|
* on unicode codepoints.
|
|
* @param self The first bytes to compare.
|
|
* @param offset The offset of self.
|
|
* @param len The length of self.
|
|
* @param other The second bytes to compare.
|
|
* @param otheroffset The offset of the other string.
|
|
* @param otherlen The length of the other string.
|
|
* @return The result of the comparison.
|
|
*/
|
|
function compare(bytes memory self, uint offset, uint len, bytes memory other, uint otheroffset, uint otherlen) internal pure returns (int) {
|
|
uint shortest = len;
|
|
if (otherlen < len)
|
|
shortest = otherlen;
|
|
|
|
uint selfptr;
|
|
uint otherptr;
|
|
|
|
assembly {
|
|
selfptr := add(self, add(offset, 32))
|
|
otherptr := add(other, add(otheroffset, 32))
|
|
}
|
|
for (uint idx = 0; idx < shortest; idx += 32) {
|
|
uint a;
|
|
uint b;
|
|
assembly {
|
|
a := mload(selfptr)
|
|
b := mload(otherptr)
|
|
}
|
|
if (a != b) {
|
|
// Mask out irrelevant bytes and check again
|
|
uint mask;
|
|
if (shortest > 32) {
|
|
mask = uint256(- 1); // aka 0xffffff....
|
|
} else {
|
|
mask = ~(2 ** (8 * (32 - shortest + idx)) - 1);
|
|
}
|
|
uint diff = (a & mask) - (b & mask);
|
|
if (diff != 0)
|
|
return int(diff);
|
|
}
|
|
selfptr += 32;
|
|
otherptr += 32;
|
|
}
|
|
|
|
return int(len) - int(otherlen);
|
|
}
|
|
|
|
/*
|
|
* @dev Returns true if the two byte ranges are equal.
|
|
* @param self The first byte range to compare.
|
|
* @param offset The offset into the first byte range.
|
|
* @param other The second byte range to compare.
|
|
* @param otherOffset The offset into the second byte range.
|
|
* @param len The number of bytes to compare
|
|
* @return True if the byte ranges are equal, false otherwise.
|
|
*/
|
|
function equals(bytes memory self, uint offset, bytes memory other, uint otherOffset, uint len) internal pure returns (bool) {
|
|
return keccak(self, offset, len) == keccak(other, otherOffset, len);
|
|
}
|
|
|
|
/*
|
|
* @dev Returns true if the two byte ranges are equal with offsets.
|
|
* @param self The first byte range to compare.
|
|
* @param offset The offset into the first byte range.
|
|
* @param other The second byte range to compare.
|
|
* @param otherOffset The offset into the second byte range.
|
|
* @return True if the byte ranges are equal, false otherwise.
|
|
*/
|
|
function equals(bytes memory self, uint offset, bytes memory other, uint otherOffset) internal pure returns (bool) {
|
|
return keccak(self, offset, self.length - offset) == keccak(other, otherOffset, other.length - otherOffset);
|
|
}
|
|
|
|
/*
|
|
* @dev Compares a range of 'self' to all of 'other' and returns True iff
|
|
* they are equal.
|
|
* @param self The first byte range to compare.
|
|
* @param offset The offset into the first byte range.
|
|
* @param other The second byte range to compare.
|
|
* @return True if the byte ranges are equal, false otherwise.
|
|
*/
|
|
function equals(bytes memory self, uint offset, bytes memory other) internal pure returns (bool) {
|
|
return self.length >= offset + other.length && equals(self, offset, other, 0, other.length);
|
|
}
|
|
|
|
/*
|
|
* @dev Returns true if the two byte ranges are equal.
|
|
* @param self The first byte range to compare.
|
|
* @param other The second byte range to compare.
|
|
* @return True if the byte ranges are equal, false otherwise.
|
|
*/
|
|
function equals(bytes memory self, bytes memory other) internal pure returns(bool) {
|
|
return self.length == other.length && equals(self, 0, other, 0, self.length);
|
|
}
|
|
|
|
/*
|
|
* @dev Returns the 8-bit number at the specified index of self.
|
|
* @param self The byte string.
|
|
* @param idx The index into the bytes
|
|
* @return The specified 8 bits of the string, interpreted as an integer.
|
|
*/
|
|
function readUint8(bytes memory self, uint idx) internal pure returns (uint8 ret) {
|
|
return uint8(self[idx]);
|
|
}
|
|
|
|
/*
|
|
* @dev Returns the 16-bit number at the specified index of self.
|
|
* @param self The byte string.
|
|
* @param idx The index into the bytes
|
|
* @return The specified 16 bits of the string, interpreted as an integer.
|
|
*/
|
|
function readUint16(bytes memory self, uint idx) internal pure returns (uint16 ret) {
|
|
require(idx + 2 <= self.length);
|
|
assembly {
|
|
ret := and(mload(add(add(self, 2), idx)), 0xFFFF)
|
|
}
|
|
}
|
|
|
|
/*
|
|
* @dev Returns the 32-bit number at the specified index of self.
|
|
* @param self The byte string.
|
|
* @param idx The index into the bytes
|
|
* @return The specified 32 bits of the string, interpreted as an integer.
|
|
*/
|
|
function readUint32(bytes memory self, uint idx) internal pure returns (uint32 ret) {
|
|
require(idx + 4 <= self.length);
|
|
assembly {
|
|
ret := and(mload(add(add(self, 4), idx)), 0xFFFFFFFF)
|
|
}
|
|
}
|
|
|
|
/*
|
|
* @dev Returns the 32 byte value at the specified index of self.
|
|
* @param self The byte string.
|
|
* @param idx The index into the bytes
|
|
* @return The specified 32 bytes of the string.
|
|
*/
|
|
function readBytes32(bytes memory self, uint idx) internal pure returns (bytes32 ret) {
|
|
require(idx + 32 <= self.length);
|
|
assembly {
|
|
ret := mload(add(add(self, 32), idx))
|
|
}
|
|
}
|
|
|
|
/*
|
|
* @dev Returns the 32 byte value at the specified index of self.
|
|
* @param self The byte string.
|
|
* @param idx The index into the bytes
|
|
* @return The specified 32 bytes of the string.
|
|
*/
|
|
function readBytes20(bytes memory self, uint idx) internal pure returns (bytes20 ret) {
|
|
require(idx + 20 <= self.length);
|
|
assembly {
|
|
ret := and(mload(add(add(self, 32), idx)), 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF000000000000000000000000)
|
|
}
|
|
}
|
|
|
|
/*
|
|
* @dev Returns the n byte value at the specified index of self.
|
|
* @param self The byte string.
|
|
* @param idx The index into the bytes.
|
|
* @param len The number of bytes.
|
|
* @return The specified 32 bytes of the string.
|
|
*/
|
|
function readBytesN(bytes memory self, uint idx, uint len) internal pure returns (bytes32 ret) {
|
|
require(len <= 32);
|
|
require(idx + len <= self.length);
|
|
assembly {
|
|
let mask := not(sub(exp(256, sub(32, len)), 1))
|
|
ret := and(mload(add(add(self, 32), idx)), mask)
|
|
}
|
|
}
|
|
|
|
function memcpy(uint dest, uint src, uint len) private pure {
|
|
// Copy word-length chunks while possible
|
|
for (; len >= 32; len -= 32) {
|
|
assembly {
|
|
mstore(dest, mload(src))
|
|
}
|
|
dest += 32;
|
|
src += 32;
|
|
}
|
|
|
|
// Copy remaining bytes
|
|
uint mask = 256 ** (32 - len) - 1;
|
|
assembly {
|
|
let srcpart := and(mload(src), not(mask))
|
|
let destpart := and(mload(dest), mask)
|
|
mstore(dest, or(destpart, srcpart))
|
|
}
|
|
}
|
|
|
|
/*
|
|
* @dev Copies a substring into a new byte string.
|
|
* @param self The byte string to copy from.
|
|
* @param offset The offset to start copying at.
|
|
* @param len The number of bytes to copy.
|
|
*/
|
|
function substring(bytes memory self, uint offset, uint len) internal pure returns(bytes memory) {
|
|
require(offset + len <= self.length);
|
|
|
|
bytes memory ret = new bytes(len);
|
|
uint dest;
|
|
uint src;
|
|
|
|
assembly {
|
|
dest := add(ret, 32)
|
|
src := add(add(self, 32), offset)
|
|
}
|
|
memcpy(dest, src, len);
|
|
|
|
return ret;
|
|
}
|
|
|
|
// Maps characters from 0x30 to 0x7A to their base32 values.
|
|
// 0xFF represents invalid characters in that range.
|
|
bytes constant base32HexTable = hex'00010203040506070809FFFFFFFFFFFFFF0A0B0C0D0E0F101112131415161718191A1B1C1D1E1FFFFFFFFFFFFFFFFFFFFF0A0B0C0D0E0F101112131415161718191A1B1C1D1E1F';
|
|
|
|
/**
|
|
* @dev Decodes unpadded base32 data of up to one word in length.
|
|
* @param self The data to decode.
|
|
* @param off Offset into the string to start at.
|
|
* @param len Number of characters to decode.
|
|
* @return The decoded data, left aligned.
|
|
*/
|
|
function base32HexDecodeWord(bytes memory self, uint off, uint len) internal pure returns(bytes32) {
|
|
require(len <= 52);
|
|
|
|
uint ret = 0;
|
|
uint8 decoded;
|
|
for(uint i = 0; i < len; i++) {
|
|
bytes1 char = self[off + i];
|
|
require(char >= 0x30 && char <= 0x7A);
|
|
decoded = uint8(base32HexTable[uint(uint8(char)) - 0x30]);
|
|
require(decoded <= 0x20);
|
|
if(i == len - 1) {
|
|
break;
|
|
}
|
|
ret = (ret << 5) | decoded;
|
|
}
|
|
|
|
uint bitlen = len * 5;
|
|
if(len % 8 == 0) {
|
|
// Multiple of 8 characters, no padding
|
|
ret = (ret << 5) | decoded;
|
|
} else if(len % 8 == 2) {
|
|
// Two extra characters - 1 byte
|
|
ret = (ret << 3) | (decoded >> 2);
|
|
bitlen -= 2;
|
|
} else if(len % 8 == 4) {
|
|
// Four extra characters - 2 bytes
|
|
ret = (ret << 1) | (decoded >> 4);
|
|
bitlen -= 4;
|
|
} else if(len % 8 == 5) {
|
|
// Five extra characters - 3 bytes
|
|
ret = (ret << 4) | (decoded >> 1);
|
|
bitlen -= 1;
|
|
} else if(len % 8 == 7) {
|
|
// Seven extra characters - 4 bytes
|
|
ret = (ret << 2) | (decoded >> 3);
|
|
bitlen -= 3;
|
|
} else {
|
|
revert();
|
|
}
|
|
|
|
return bytes32(ret << (256 - bitlen));
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @dev A library for working with mutable byte buffers in Solidity.
|
|
*
|
|
* Byte buffers are mutable and expandable, and provide a variety of primitives
|
|
* for writing to them. At any time you can fetch a bytes object containing the
|
|
* current contents of the buffer. The bytes object should not be stored between
|
|
* operations, as it may change due to resizing of the buffer.
|
|
*/
|
|
library Buffer {
|
|
/**
|
|
* @dev Represents a mutable buffer. Buffers have a current value (buf) and
|
|
* a capacity. The capacity may be longer than the current value, in
|
|
* which case it can be extended without the need to allocate more memory.
|
|
*/
|
|
struct buffer {
|
|
bytes buf;
|
|
uint capacity;
|
|
}
|
|
|
|
/**
|
|
* @dev Initializes a buffer with an initial capacity.
|
|
* @param buf The buffer to initialize.
|
|
* @param capacity The number of bytes of space to allocate the buffer.
|
|
* @return The buffer, for chaining.
|
|
*/
|
|
function init(buffer memory buf, uint capacity) internal pure returns(buffer memory) {
|
|
if (capacity % 32 != 0) {
|
|
capacity += 32 - (capacity % 32);
|
|
}
|
|
// Allocate space for the buffer data
|
|
buf.capacity = capacity;
|
|
assembly {
|
|
let ptr := mload(0x40)
|
|
mstore(buf, ptr)
|
|
mstore(ptr, 0)
|
|
mstore(0x40, add(32, add(ptr, capacity)))
|
|
}
|
|
return buf;
|
|
}
|
|
|
|
/**
|
|
* @dev Initializes a new buffer from an existing bytes object.
|
|
* Changes to the buffer may mutate the original value.
|
|
* @param b The bytes object to initialize the buffer with.
|
|
* @return A new buffer.
|
|
*/
|
|
function fromBytes(bytes memory b) internal pure returns(buffer memory) {
|
|
buffer memory buf;
|
|
buf.buf = b;
|
|
buf.capacity = b.length;
|
|
return buf;
|
|
}
|
|
|
|
function resize(buffer memory buf, uint capacity) private pure {
|
|
bytes memory oldbuf = buf.buf;
|
|
init(buf, capacity);
|
|
append(buf, oldbuf);
|
|
}
|
|
|
|
function max(uint a, uint b) private pure returns(uint) {
|
|
if (a > b) {
|
|
return a;
|
|
}
|
|
return b;
|
|
}
|
|
|
|
/**
|
|
* @dev Sets buffer length to 0.
|
|
* @param buf The buffer to truncate.
|
|
* @return The original buffer, for chaining..
|
|
*/
|
|
function truncate(buffer memory buf) internal pure returns (buffer memory) {
|
|
assembly {
|
|
let bufptr := mload(buf)
|
|
mstore(bufptr, 0)
|
|
}
|
|
return buf;
|
|
}
|
|
|
|
/**
|
|
* @dev Writes a byte string to a buffer. Resizes if doing so would exceed
|
|
* the capacity of the buffer.
|
|
* @param buf The buffer to append to.
|
|
* @param off The start offset to write to.
|
|
* @param data The data to append.
|
|
* @param len The number of bytes to copy.
|
|
* @return The original buffer, for chaining.
|
|
*/
|
|
function write(buffer memory buf, uint off, bytes memory data, uint len) internal pure returns(buffer memory) {
|
|
require(len <= data.length);
|
|
|
|
if (off + len > buf.capacity) {
|
|
resize(buf, max(buf.capacity, len + off) * 2);
|
|
}
|
|
|
|
uint dest;
|
|
uint src;
|
|
assembly {
|
|
// Memory address of the buffer data
|
|
let bufptr := mload(buf)
|
|
// Length of existing buffer data
|
|
let buflen := mload(bufptr)
|
|
// Start address = buffer address + offset + sizeof(buffer length)
|
|
dest := add(add(bufptr, 32), off)
|
|
// Update buffer length if we're extending it
|
|
if gt(add(len, off), buflen) {
|
|
mstore(bufptr, add(len, off))
|
|
}
|
|
src := add(data, 32)
|
|
}
|
|
|
|
// Copy word-length chunks while possible
|
|
for (; len >= 32; len -= 32) {
|
|
assembly {
|
|
mstore(dest, mload(src))
|
|
}
|
|
dest += 32;
|
|
src += 32;
|
|
}
|
|
|
|
// Copy remaining bytes
|
|
uint mask = 256 ** (32 - len) - 1;
|
|
assembly {
|
|
let srcpart := and(mload(src), not(mask))
|
|
let destpart := and(mload(dest), mask)
|
|
mstore(dest, or(destpart, srcpart))
|
|
}
|
|
|
|
return buf;
|
|
}
|
|
|
|
/**
|
|
* @dev Appends a byte string to a buffer. Resizes if doing so would exceed
|
|
* the capacity of the buffer.
|
|
* @param buf The buffer to append to.
|
|
* @param data The data to append.
|
|
* @param len The number of bytes to copy.
|
|
* @return The original buffer, for chaining.
|
|
*/
|
|
function append(buffer memory buf, bytes memory data, uint len) internal pure returns (buffer memory) {
|
|
return write(buf, buf.buf.length, data, len);
|
|
}
|
|
|
|
/**
|
|
* @dev Appends a byte string to a buffer. Resizes if doing so would exceed
|
|
* the capacity of the buffer.
|
|
* @param buf The buffer to append to.
|
|
* @param data The data to append.
|
|
* @return The original buffer, for chaining.
|
|
*/
|
|
function append(buffer memory buf, bytes memory data) internal pure returns (buffer memory) {
|
|
return write(buf, buf.buf.length, data, data.length);
|
|
}
|
|
|
|
/**
|
|
* @dev Writes a byte to the buffer. Resizes if doing so would exceed the
|
|
* capacity of the buffer.
|
|
* @param buf The buffer to append to.
|
|
* @param off The offset to write the byte at.
|
|
* @param data The data to append.
|
|
* @return The original buffer, for chaining.
|
|
*/
|
|
function writeUint8(buffer memory buf, uint off, uint8 data) internal pure returns(buffer memory) {
|
|
if (off >= buf.capacity) {
|
|
resize(buf, buf.capacity * 2);
|
|
}
|
|
|
|
assembly {
|
|
// Memory address of the buffer data
|
|
let bufptr := mload(buf)
|
|
// Length of existing buffer data
|
|
let buflen := mload(bufptr)
|
|
// Address = buffer address + sizeof(buffer length) + off
|
|
let dest := add(add(bufptr, off), 32)
|
|
mstore8(dest, data)
|
|
// Update buffer length if we extended it
|
|
if eq(off, buflen) {
|
|
mstore(bufptr, add(buflen, 1))
|
|
}
|
|
}
|
|
return buf;
|
|
}
|
|
|
|
/**
|
|
* @dev Appends a byte to the buffer. Resizes if doing so would exceed the
|
|
* capacity of the buffer.
|
|
* @param buf The buffer to append to.
|
|
* @param data The data to append.
|
|
* @return The original buffer, for chaining.
|
|
*/
|
|
function appendUint8(buffer memory buf, uint8 data) internal pure returns(buffer memory) {
|
|
return writeUint8(buf, buf.buf.length, data);
|
|
}
|
|
|
|
/**
|
|
* @dev Writes up to 32 bytes to the buffer. Resizes if doing so would
|
|
* exceed the capacity of the buffer.
|
|
* @param buf The buffer to append to.
|
|
* @param off The offset to write at.
|
|
* @param data The data to append.
|
|
* @param len The number of bytes to write (left-aligned).
|
|
* @return The original buffer, for chaining.
|
|
*/
|
|
function write(buffer memory buf, uint off, bytes32 data, uint len) private pure returns(buffer memory) {
|
|
if (len + off > buf.capacity) {
|
|
resize(buf, (len + off) * 2);
|
|
}
|
|
|
|
uint mask = 256 ** len - 1;
|
|
// Right-align data
|
|
data = data >> (8 * (32 - len));
|
|
assembly {
|
|
// Memory address of the buffer data
|
|
let bufptr := mload(buf)
|
|
// Address = buffer address + sizeof(buffer length) + off + len
|
|
let dest := add(add(bufptr, off), len)
|
|
mstore(dest, or(and(mload(dest), not(mask)), data))
|
|
// Update buffer length if we extended it
|
|
if gt(add(off, len), mload(bufptr)) {
|
|
mstore(bufptr, add(off, len))
|
|
}
|
|
}
|
|
return buf;
|
|
}
|
|
|
|
/**
|
|
* @dev Writes a bytes20 to the buffer. Resizes if doing so would exceed the
|
|
* capacity of the buffer.
|
|
* @param buf The buffer to append to.
|
|
* @param off The offset to write at.
|
|
* @param data The data to append.
|
|
* @return The original buffer, for chaining.
|
|
*/
|
|
function writeBytes20(buffer memory buf, uint off, bytes20 data) internal pure returns (buffer memory) {
|
|
return write(buf, off, bytes32(data), 20);
|
|
}
|
|
|
|
/**
|
|
* @dev Appends a bytes20 to the buffer. Resizes if doing so would exceed
|
|
* the capacity of the buffer.
|
|
* @param buf The buffer to append to.
|
|
* @param data The data to append.
|
|
* @return The original buffer, for chhaining.
|
|
*/
|
|
function appendBytes20(buffer memory buf, bytes20 data) internal pure returns (buffer memory) {
|
|
return write(buf, buf.buf.length, bytes32(data), 20);
|
|
}
|
|
|
|
/**
|
|
* @dev Appends a bytes32 to the buffer. Resizes if doing so would exceed
|
|
* the capacity of the buffer.
|
|
* @param buf The buffer to append to.
|
|
* @param data The data to append.
|
|
* @return The original buffer, for chaining.
|
|
*/
|
|
function appendBytes32(buffer memory buf, bytes32 data) internal pure returns (buffer memory) {
|
|
return write(buf, buf.buf.length, data, 32);
|
|
}
|
|
|
|
/**
|
|
* @dev Writes an integer to the buffer. Resizes if doing so would exceed
|
|
* the capacity of the buffer.
|
|
* @param buf The buffer to append to.
|
|
* @param off The offset to write at.
|
|
* @param data The data to append.
|
|
* @param len The number of bytes to write (right-aligned).
|
|
* @return The original buffer, for chaining.
|
|
*/
|
|
function writeInt(buffer memory buf, uint off, uint data, uint len) private pure returns(buffer memory) {
|
|
if (len + off > buf.capacity) {
|
|
resize(buf, (len + off) * 2);
|
|
}
|
|
|
|
uint mask = 256 ** len - 1;
|
|
assembly {
|
|
// Memory address of the buffer data
|
|
let bufptr := mload(buf)
|
|
// Address = buffer address + off + sizeof(buffer length) + len
|
|
let dest := add(add(bufptr, off), len)
|
|
mstore(dest, or(and(mload(dest), not(mask)), data))
|
|
// Update buffer length if we extended it
|
|
if gt(add(off, len), mload(bufptr)) {
|
|
mstore(bufptr, add(off, len))
|
|
}
|
|
}
|
|
return buf;
|
|
}
|
|
|
|
/**
|
|
* @dev Appends a byte to the end of the buffer. Resizes if doing so would
|
|
* exceed the capacity of the buffer.
|
|
* @param buf The buffer to append to.
|
|
* @param data The data to append.
|
|
* @return The original buffer.
|
|
*/
|
|
function appendInt(buffer memory buf, uint data, uint len) internal pure returns(buffer memory) {
|
|
return writeInt(buf, buf.buf.length, data, len);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @dev RRUtils is a library that provides utilities for parsing DNS resource records.
|
|
*/
|
|
library RRUtils {
|
|
using BytesUtils for *;
|
|
using Buffer for *;
|
|
|
|
/**
|
|
* @dev Returns the number of bytes in the DNS name at 'offset' in 'self'.
|
|
* @param self The byte array to read a name from.
|
|
* @param offset The offset to start reading at.
|
|
* @return The length of the DNS name at 'offset', in bytes.
|
|
*/
|
|
function nameLength(bytes memory self, uint offset) internal pure returns(uint) {
|
|
uint idx = offset;
|
|
while (true) {
|
|
assert(idx < self.length);
|
|
uint labelLen = self.readUint8(idx);
|
|
idx += labelLen + 1;
|
|
if (labelLen == 0) {
|
|
break;
|
|
}
|
|
}
|
|
return idx - offset;
|
|
}
|
|
|
|
/**
|
|
* @dev Returns a DNS format name at the specified offset of self.
|
|
* @param self The byte array to read a name from.
|
|
* @param offset The offset to start reading at.
|
|
* @return The name.
|
|
*/
|
|
function readName(bytes memory self, uint offset) internal pure returns(bytes memory ret) {
|
|
uint len = nameLength(self, offset);
|
|
return self.substring(offset, len);
|
|
}
|
|
|
|
/**
|
|
* @dev Returns the number of labels in the DNS name at 'offset' in 'self'.
|
|
* @param self The byte array to read a name from.
|
|
* @param offset The offset to start reading at.
|
|
* @return The number of labels in the DNS name at 'offset', in bytes.
|
|
*/
|
|
function labelCount(bytes memory self, uint offset) internal pure returns(uint) {
|
|
uint count = 0;
|
|
while (true) {
|
|
assert(offset < self.length);
|
|
uint labelLen = self.readUint8(offset);
|
|
offset += labelLen + 1;
|
|
if (labelLen == 0) {
|
|
break;
|
|
}
|
|
count += 1;
|
|
}
|
|
return count;
|
|
}
|
|
|
|
/**
|
|
* @dev An iterator over resource records.
|
|
*/
|
|
struct RRIterator {
|
|
bytes data;
|
|
uint offset;
|
|
uint16 dnstype;
|
|
uint16 class;
|
|
uint32 ttl;
|
|
uint rdataOffset;
|
|
uint nextOffset;
|
|
}
|
|
|
|
/**
|
|
* @dev Begins iterating over resource records.
|
|
* @param self The byte string to read from.
|
|
* @param offset The offset to start reading at.
|
|
* @return An iterator object.
|
|
*/
|
|
function iterateRRs(bytes memory self, uint offset) internal pure returns (RRIterator memory ret) {
|
|
ret.data = self;
|
|
ret.nextOffset = offset;
|
|
next(ret);
|
|
}
|
|
|
|
/**
|
|
* @dev Returns true iff there are more RRs to iterate.
|
|
* @param iter The iterator to check.
|
|
* @return True iff the iterator has finished.
|
|
*/
|
|
function done(RRIterator memory iter) internal pure returns(bool) {
|
|
return iter.offset >= iter.data.length;
|
|
}
|
|
|
|
/**
|
|
* @dev Moves the iterator to the next resource record.
|
|
* @param iter The iterator to advance.
|
|
*/
|
|
function next(RRIterator memory iter) internal pure {
|
|
iter.offset = iter.nextOffset;
|
|
if (iter.offset >= iter.data.length) {
|
|
return;
|
|
}
|
|
|
|
// Skip the name
|
|
uint off = iter.offset + nameLength(iter.data, iter.offset);
|
|
|
|
// Read type, class, and ttl
|
|
iter.dnstype = iter.data.readUint16(off);
|
|
off += 2;
|
|
iter.class = iter.data.readUint16(off);
|
|
off += 2;
|
|
iter.ttl = iter.data.readUint32(off);
|
|
off += 4;
|
|
|
|
// Read the rdata
|
|
uint rdataLength = iter.data.readUint16(off);
|
|
off += 2;
|
|
iter.rdataOffset = off;
|
|
iter.nextOffset = off + rdataLength;
|
|
}
|
|
|
|
/**
|
|
* @dev Returns the name of the current record.
|
|
* @param iter The iterator.
|
|
* @return A new bytes object containing the owner name from the RR.
|
|
*/
|
|
function name(RRIterator memory iter) internal pure returns(bytes memory) {
|
|
return iter.data.substring(iter.offset, nameLength(iter.data, iter.offset));
|
|
}
|
|
|
|
/**
|
|
* @dev Returns the rdata portion of the current record.
|
|
* @param iter The iterator.
|
|
* @return A new bytes object containing the RR's RDATA.
|
|
*/
|
|
function rdata(RRIterator memory iter) internal pure returns(bytes memory) {
|
|
return iter.data.substring(iter.rdataOffset, iter.nextOffset - iter.rdataOffset);
|
|
}
|
|
|
|
/**
|
|
* @dev Checks if a given RR type exists in a type bitmap.
|
|
* @param self The byte string to read the type bitmap from.
|
|
* @param offset The offset to start reading at.
|
|
* @param rrtype The RR type to check for.
|
|
* @return True if the type is found in the bitmap, false otherwise.
|
|
*/
|
|
function checkTypeBitmap(bytes memory self, uint offset, uint16 rrtype) internal pure returns (bool) {
|
|
uint8 typeWindow = uint8(rrtype >> 8);
|
|
uint8 windowByte = uint8((rrtype & 0xff) / 8);
|
|
uint8 windowBitmask = uint8(uint8(1) << (uint8(7) - uint8(rrtype & 0x7)));
|
|
for (uint off = offset; off < self.length;) {
|
|
uint8 window = self.readUint8(off);
|
|
uint8 len = self.readUint8(off + 1);
|
|
if (typeWindow < window) {
|
|
// We've gone past our window; it's not here.
|
|
return false;
|
|
} else if (typeWindow == window) {
|
|
// Check this type bitmap
|
|
if (len * 8 <= windowByte) {
|
|
// Our type is past the end of the bitmap
|
|
return false;
|
|
}
|
|
return (self.readUint8(off + windowByte + 2) & windowBitmask) != 0;
|
|
} else {
|
|
// Skip this type bitmap
|
|
off += len + 2;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
function compareNames(bytes memory self, bytes memory other) internal pure returns (int) {
|
|
if (self.equals(other)) {
|
|
return 0;
|
|
}
|
|
|
|
uint off;
|
|
uint otheroff;
|
|
uint prevoff;
|
|
uint otherprevoff;
|
|
uint counts = labelCount(self, 0);
|
|
uint othercounts = labelCount(other, 0);
|
|
|
|
// Keep removing labels from the front of the name until both names are equal length
|
|
while (counts > othercounts) {
|
|
prevoff = off;
|
|
off = progress(self, off);
|
|
counts--;
|
|
}
|
|
|
|
while (othercounts > counts) {
|
|
otherprevoff = otheroff;
|
|
otheroff = progress(other, otheroff);
|
|
othercounts--;
|
|
}
|
|
|
|
// Compare the last nonequal labels to each other
|
|
while (counts > 0 && !self.equals(off, other, otheroff)) {
|
|
prevoff = off;
|
|
off = progress(self, off);
|
|
otherprevoff = otheroff;
|
|
otheroff = progress(other, otheroff);
|
|
counts -= 1;
|
|
}
|
|
|
|
if (off == 0) {
|
|
return -1;
|
|
}
|
|
if(otheroff == 0) {
|
|
return 1;
|
|
}
|
|
|
|
return self.compare(prevoff + 1, self.readUint8(prevoff), other, otherprevoff + 1, other.readUint8(otherprevoff));
|
|
}
|
|
|
|
function progress(bytes memory body, uint off) internal pure returns(uint) {
|
|
return off + 1 + body.readUint8(off);
|
|
}
|
|
}
|
|
|
|
contract DNSResolver is ResolverBase {
|
|
using RRUtils for *;
|
|
using BytesUtils for bytes;
|
|
|
|
bytes4 constant private DNS_RECORD_INTERFACE_ID = 0xa8fa5682;
|
|
|
|
// DNSRecordChanged is emitted whenever a given node/name/resource's RRSET is updated.
|
|
event DNSRecordChanged(bytes32 indexed node, bytes name, uint16 resource, bytes record);
|
|
// DNSRecordDeleted is emitted whenever a given node/name/resource's RRSET is deleted.
|
|
event DNSRecordDeleted(bytes32 indexed node, bytes name, uint16 resource);
|
|
// DNSZoneCleared is emitted whenever a given node's zone information is cleared.
|
|
event DNSZoneCleared(bytes32 indexed node);
|
|
|
|
// Version the mapping for each zone. This allows users who have lost
|
|
// track of their entries to effectively delete an entire zone by bumping
|
|
// the version number.
|
|
// node => version
|
|
mapping(bytes32=>uint256) private versions;
|
|
|
|
// The records themselves. Stored as binary RRSETs
|
|
// node => version => name => resource => data
|
|
mapping(bytes32=>mapping(uint256=>mapping(bytes32=>mapping(uint16=>bytes)))) private records;
|
|
|
|
// Count of number of entries for a given name. Required for DNS resolvers
|
|
// when resolving wildcards.
|
|
// node => version => name => number of records
|
|
mapping(bytes32=>mapping(uint256=>mapping(bytes32=>uint16))) private nameEntriesCount;
|
|
|
|
/**
|
|
* Set one or more DNS records. Records are supplied in wire-format.
|
|
* Records with the same node/name/resource must be supplied one after the
|
|
* other to ensure the data is updated correctly. For example, if the data
|
|
* was supplied:
|
|
* a.example.com IN A 1.2.3.4
|
|
* a.example.com IN A 5.6.7.8
|
|
* www.example.com IN CNAME a.example.com.
|
|
* then this would store the two A records for a.example.com correctly as a
|
|
* single RRSET, however if the data was supplied:
|
|
* a.example.com IN A 1.2.3.4
|
|
* www.example.com IN CNAME a.example.com.
|
|
* a.example.com IN A 5.6.7.8
|
|
* then this would store the first A record, the CNAME, then the second A
|
|
* record which would overwrite the first.
|
|
*
|
|
* @param node the namehash of the node for which to set the records
|
|
* @param data the DNS wire format records to set
|
|
*/
|
|
function setDNSRecords(bytes32 node, bytes calldata data) external authorised(node) {
|
|
uint16 resource = 0;
|
|
uint256 offset = 0;
|
|
bytes memory name;
|
|
bytes memory value;
|
|
bytes32 nameHash;
|
|
// Iterate over the data to add the resource records
|
|
for (RRUtils.RRIterator memory iter = data.iterateRRs(0); !iter.done(); iter.next()) {
|
|
if (resource == 0) {
|
|
resource = iter.dnstype;
|
|
name = iter.name();
|
|
nameHash = keccak256(abi.encodePacked(name));
|
|
value = bytes(iter.rdata());
|
|
} else {
|
|
bytes memory newName = iter.name();
|
|
if (resource != iter.dnstype || !name.equals(newName)) {
|
|
setDNSRRSet(node, name, resource, data, offset, iter.offset - offset, value.length == 0);
|
|
resource = iter.dnstype;
|
|
offset = iter.offset;
|
|
name = newName;
|
|
nameHash = keccak256(name);
|
|
value = bytes(iter.rdata());
|
|
}
|
|
}
|
|
}
|
|
if (name.length > 0) {
|
|
setDNSRRSet(node, name, resource, data, offset, data.length - offset, value.length == 0);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Obtain a DNS record.
|
|
* @param node the namehash of the node for which to fetch the record
|
|
* @param name the keccak-256 hash of the fully-qualified name for which to fetch the record
|
|
* @param resource the ID of the resource as per https://en.wikipedia.org/wiki/List_of_DNS_record_types
|
|
* @return the DNS record in wire format if present, otherwise empty
|
|
*/
|
|
function dnsRecord(bytes32 node, bytes32 name, uint16 resource) public view returns (bytes memory) {
|
|
return records[node][versions[node]][name][resource];
|
|
}
|
|
|
|
/**
|
|
* Check if a given node has records.
|
|
* @param node the namehash of the node for which to check the records
|
|
* @param name the namehash of the node for which to check the records
|
|
*/
|
|
function hasDNSRecords(bytes32 node, bytes32 name) public view returns (bool) {
|
|
return (nameEntriesCount[node][versions[node]][name] != 0);
|
|
}
|
|
|
|
/**
|
|
* Clear all information for a DNS zone.
|
|
* @param node the namehash of the node for which to clear the zone
|
|
*/
|
|
function clearDNSZone(bytes32 node) public authorised(node) {
|
|
versions[node]++;
|
|
emit DNSZoneCleared(node);
|
|
}
|
|
|
|
function supportsInterface(bytes4 interfaceID) public pure returns(bool) {
|
|
return interfaceID == DNS_RECORD_INTERFACE_ID || super.supportsInterface(interfaceID);
|
|
}
|
|
|
|
function setDNSRRSet(
|
|
bytes32 node,
|
|
bytes memory name,
|
|
uint16 resource,
|
|
bytes memory data,
|
|
uint256 offset,
|
|
uint256 size,
|
|
bool deleteRecord) private
|
|
{
|
|
uint256 version = versions[node];
|
|
bytes32 nameHash = keccak256(name);
|
|
bytes memory rrData = data.substring(offset, size);
|
|
if (deleteRecord) {
|
|
if (records[node][version][nameHash][resource].length != 0) {
|
|
nameEntriesCount[node][version][nameHash]--;
|
|
}
|
|
delete(records[node][version][nameHash][resource]);
|
|
emit DNSRecordDeleted(node, name, resource);
|
|
} else {
|
|
if (records[node][version][nameHash][resource].length == 0) {
|
|
nameEntriesCount[node][version][nameHash]++;
|
|
}
|
|
records[node][version][nameHash][resource] = rrData;
|
|
emit DNSRecordChanged(node, name, resource, rrData);
|
|
}
|
|
}
|
|
}
|
|
|
|
contract InterfaceResolver is ResolverBase, AddrResolver {
|
|
bytes4 constant private INTERFACE_INTERFACE_ID = bytes4(keccak256("interfaceImplementer(bytes32,bytes4)"));
|
|
bytes4 private constant INTERFACE_META_ID = 0x01ffc9a7;
|
|
|
|
event InterfaceChanged(bytes32 indexed node, bytes4 indexed interfaceID, address implementer);
|
|
|
|
mapping(bytes32=>mapping(bytes4=>address)) interfaces;
|
|
|
|
/**
|
|
* Sets an interface associated with a name.
|
|
* Setting the address to 0 restores the default behaviour of querying the contract at `addr()` for interface support.
|
|
* @param node The node to update.
|
|
* @param interfaceID The EIP 168 interface ID.
|
|
* @param implementer The address of a contract that implements this interface for this node.
|
|
*/
|
|
function setInterface(bytes32 node, bytes4 interfaceID, address implementer) external authorised(node) {
|
|
interfaces[node][interfaceID] = implementer;
|
|
emit InterfaceChanged(node, interfaceID, implementer);
|
|
}
|
|
|
|
/**
|
|
* Returns the address of a contract that implements the specified interface for this name.
|
|
* If an implementer has not been set for this interfaceID and name, the resolver will query
|
|
* the contract at `addr()`. If `addr()` is set, a contract exists at that address, and that
|
|
* contract implements EIP168 and returns `true` for the specified interfaceID, its address
|
|
* will be returned.
|
|
* @param node The ENS node to query.
|
|
* @param interfaceID The EIP 168 interface ID to check for.
|
|
* @return The address that implements this interface, or 0 if the interface is unsupported.
|
|
*/
|
|
function interfaceImplementer(bytes32 node, bytes4 interfaceID) external view returns (address) {
|
|
address implementer = interfaces[node][interfaceID];
|
|
if(implementer != address(0)) {
|
|
return implementer;
|
|
}
|
|
|
|
address a = addr(node);
|
|
if(a == address(0)) {
|
|
return address(0);
|
|
}
|
|
|
|
(bool success, bytes memory returnData) = a.staticcall(abi.encodeWithSignature("supportsInterface(bytes4)", INTERFACE_META_ID));
|
|
if(!success || returnData.length < 32 || returnData[31] == 0) {
|
|
// EIP 168 not supported by target
|
|
return address(0);
|
|
}
|
|
|
|
(success, returnData) = a.staticcall(abi.encodeWithSignature("supportsInterface(bytes4)", interfaceID));
|
|
if(!success || returnData.length < 32 || returnData[31] == 0) {
|
|
// Specified interface not supported by target
|
|
return address(0);
|
|
}
|
|
|
|
return a;
|
|
}
|
|
|
|
function supportsInterface(bytes4 interfaceID) public pure returns(bool) {
|
|
return interfaceID == INTERFACE_INTERFACE_ID || super.supportsInterface(interfaceID);
|
|
}
|
|
}
|
|
|
|
contract NameResolver is ResolverBase {
|
|
bytes4 constant private NAME_INTERFACE_ID = 0x691f3431;
|
|
|
|
event NameChanged(bytes32 indexed node, string name);
|
|
|
|
mapping(bytes32=>string) names;
|
|
|
|
/**
|
|
* 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 calldata name) external authorised(node) {
|
|
names[node] = name;
|
|
emit NameChanged(node, name);
|
|
}
|
|
|
|
/**
|
|
* 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) external view returns (string memory) {
|
|
return names[node];
|
|
}
|
|
|
|
function supportsInterface(bytes4 interfaceID) public pure returns(bool) {
|
|
return interfaceID == NAME_INTERFACE_ID || super.supportsInterface(interfaceID);
|
|
}
|
|
}
|
|
|
|
contract PubkeyResolver is ResolverBase {
|
|
bytes4 constant private PUBKEY_INTERFACE_ID = 0xc8690233;
|
|
|
|
event PubkeyChanged(bytes32 indexed node, bytes32 x, bytes32 y);
|
|
|
|
struct PublicKey {
|
|
bytes32 x;
|
|
bytes32 y;
|
|
}
|
|
|
|
mapping(bytes32=>PublicKey) pubkeys;
|
|
|
|
/**
|
|
* 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) external authorised(node) {
|
|
pubkeys[node] = PublicKey(x, y);
|
|
emit PubkeyChanged(node, x, y);
|
|
}
|
|
|
|
/**
|
|
* 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) external view returns (bytes32 x, bytes32 y) {
|
|
return (pubkeys[node].x, pubkeys[node].y);
|
|
}
|
|
|
|
function supportsInterface(bytes4 interfaceID) public pure returns(bool) {
|
|
return interfaceID == PUBKEY_INTERFACE_ID || super.supportsInterface(interfaceID);
|
|
}
|
|
}
|
|
|
|
contract TextResolver is ResolverBase {
|
|
bytes4 constant private TEXT_INTERFACE_ID = 0x59d1d43c;
|
|
|
|
event TextChanged(bytes32 indexed node, string indexed indexedKey, string key);
|
|
|
|
mapping(bytes32=>mapping(string=>string)) texts;
|
|
|
|
/**
|
|
* 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 calldata key, string calldata value) external authorised(node) {
|
|
texts[node][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 calldata key) external view returns (string memory) {
|
|
return texts[node][key];
|
|
}
|
|
|
|
function supportsInterface(bytes4 interfaceID) public pure returns(bool) {
|
|
return interfaceID == TEXT_INTERFACE_ID || super.supportsInterface(interfaceID);
|
|
}
|
|
}
|
|
|
|
pragma experimental ABIEncoderV2;
|
|
|
|
/**
|
|
* A simple resolver anyone can use; only allows the owner of a node to set its
|
|
* address.
|
|
*/
|
|
contract PublicResolver is ABIResolver, AddrResolver, ContentHashResolver, DNSResolver, InterfaceResolver, NameResolver, PubkeyResolver, TextResolver {
|
|
ENS ens;
|
|
|
|
/**
|
|
* A mapping of authorisations. An address that is authorised for a name
|
|
* may make any changes to the name that the owner could, but may not update
|
|
* the set of authorisations.
|
|
* (node, owner, caller) => isAuthorised
|
|
*/
|
|
mapping(bytes32=>mapping(address=>mapping(address=>bool))) public authorisations;
|
|
|
|
event AuthorisationChanged(bytes32 indexed node, address indexed owner, address indexed target, bool isAuthorised);
|
|
|
|
constructor(ENS _ens) public {
|
|
ens = _ens;
|
|
}
|
|
|
|
/**
|
|
* @dev Sets or clears an authorisation.
|
|
* Authorisations are specific to the caller. Any account can set an authorisation
|
|
* for any name, but the authorisation that is checked will be that of the
|
|
* current owner of a name. Thus, transferring a name effectively clears any
|
|
* existing authorisations, and new authorisations can be set in advance of
|
|
* an ownership transfer if desired.
|
|
*
|
|
* @param node The name to change the authorisation on.
|
|
* @param target The address that is to be authorised or deauthorised.
|
|
* @param isAuthorised True if the address should be authorised, or false if it should be deauthorised.
|
|
*/
|
|
function setAuthorisation(bytes32 node, address target, bool isAuthorised) external {
|
|
authorisations[node][msg.sender][target] = isAuthorised;
|
|
emit AuthorisationChanged(node, msg.sender, target, isAuthorised);
|
|
}
|
|
|
|
function isAuthorised(bytes32 node) internal view returns(bool) {
|
|
address owner = ens.owner(node);
|
|
return owner == msg.sender || authorisations[node][owner][msg.sender];
|
|
}
|
|
|
|
function multicall(bytes[] calldata data) external returns(bytes[] memory results) {
|
|
results = new bytes[](data.length);
|
|
for(uint i = 0; i < data.length; i++) {
|
|
(bool success, bytes memory result) = address(this).delegatecall(data[i]);
|
|
require(success);
|
|
results[i] = result;
|
|
}
|
|
return results;
|
|
}
|
|
} |