diff --git a/test_apps/test_app/app/contracts/ENS.sol b/test_apps/test_app/app/contracts/ENS.sol new file mode 100644 index 00000000..61e0edac --- /dev/null +++ b/test_apps/test_app/app/contracts/ENS.sol @@ -0,0 +1,26 @@ +pragma solidity ^0.4.18; + +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); + +} diff --git a/test_apps/test_app/app/contracts/ENSRegistry.sol b/test_apps/test_app/app/contracts/ENSRegistry.sol new file mode 100644 index 00000000..37917c81 --- /dev/null +++ b/test_apps/test_app/app/contracts/ENSRegistry.sol @@ -0,0 +1,99 @@ +pragma solidity ^0.4.18; + +import './ENS.sol'; + +/** + * The ENS registry contract. + */ +contract ENSRegistry is ENS { + struct Record { + address owner; + address resolver; + uint64 ttl; + } + + mapping (bytes32 => Record) records; + + // Permits modifications only by the owner of the specified node. + modifier only_owner(bytes32 node, address owner) { + require(records[node].owner == 0 || records[node].owner == msg.sender || records[node].owner == owner); + _; + } + + /** + * @dev Constructs a new ENS registrar. + */ + function ENSRegistry() public { + records[0x0].owner = msg.sender; + } + + /** + * @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 only_owner(node, owner) { + Transfer(node, owner); + records[node].owner = owner; + } + + /** + * @dev Transfers ownership of a subnode sha3(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 only_owner(node, owner) { + var subnode = keccak256(node, label); + NewOwner(node, label, owner); + records[subnode].owner = owner; + } + + /** + * @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 only_owner(node, 0x0) { + 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 only_owner(node, 0x0) { + NewTTL(node, ttl); + records[node].ttl = ttl; + } + + /** + * @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) { + return records[node].owner; + } + + /** + * @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; + } + +} diff --git a/test_apps/test_app/app/contracts/FIFSRegistrar.sol b/test_apps/test_app/app/contracts/FIFSRegistrar.sol new file mode 100644 index 00000000..d1d4924a --- /dev/null +++ b/test_apps/test_app/app/contracts/FIFSRegistrar.sol @@ -0,0 +1,38 @@ +pragma solidity ^0.4.18; + +import './ENS.sol'; +import './Resolver.sol'; + +/** + * A registrar that allocates subdomains to the first person to claim them. + */ +contract FIFSRegistrar { + ENS ens; + bytes32 rootNode; + + modifier only_owner(bytes32 subnode) { + bytes32 node = sha3(rootNode, subnode); + address currentOwner = ens.owner(node); + require(currentOwner == 0 || currentOwner == msg.sender); + _; + } + + /** + * Constructor. + * @param ensAddr The address of the ENS registry. + * @param node The node that this registrar administers. + */ + function FIFSRegistrar(ENS ensAddr, bytes32 node) public { + ens = ensAddr; + rootNode = node; + } + + /** + * Register a name, or change the owner of an existing registration. + * @param subnode The hash of the label to register. + * @param owner The address of the new owner. + */ + function register(bytes32 subnode, address owner) public only_owner(subnode) { + ens.setSubnodeOwner(rootNode, subnode, owner); + } +} diff --git a/test_apps/test_app/app/contracts/Resolver.sol b/test_apps/test_app/app/contracts/Resolver.sol new file mode 100644 index 00000000..1d339cbd --- /dev/null +++ b/test_apps/test_app/app/contracts/Resolver.sol @@ -0,0 +1,191 @@ +pragma solidity ^0.4.23; + +import "./ENS.sol"; + +/** + * A simple resolver anyone can use; only allows the owner of a node to set its + * address. + */ +contract Resolver { + 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); + + struct PublicKey { + bytes32 x; + bytes32 y; + } + + struct Record { + address addr; + bytes32 content; + string name; + PublicKey pubkey; + mapping(string=>string) text; + mapping(uint256=>bytes) abis; + } + + ENS ens; + + mapping (bytes32 => Record) records; + + modifier only_owner(bytes32 node) { + address currentOwner = ens.owner(node); + require(currentOwner == 0 || currentOwner == 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 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 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; + } +} diff --git a/test_apps/test_app/config/contracts.js b/test_apps/test_app/config/contracts.js index be823793..4fb5aa40 100644 --- a/test_apps/test_app/config/contracts.js +++ b/test_apps/test_app/config/contracts.js @@ -19,26 +19,18 @@ module.exports = { }, SimpleStorage: { fromIndex: 0, - args: [ - 100 - ], - onDeploy: [ - "SimpleStorage.methods.setRegistar(web3.eth.defaultAccount).send()" - ] + args: [100], + onDeploy: ["SimpleStorage.methods.setRegistar(web3.eth.defaultAccount).send()"] }, AnotherStorage: { - args: [ - "$SimpleStorage" - ] + args: ["$SimpleStorage"] }, Token: { deploy: false, args: [1000] }, Test: { - onDeploy: [ - "Test.methods.changeAddress('$MyToken')" - ] + onDeploy: ["Test.methods.changeAddress('$MyToken')"] }, MyToken: { instanceOf: "Token" @@ -71,18 +63,14 @@ module.exports = { }, SimpleStorageTest: { file: "./some_folder/test_contract.sol", - args: [ - 1000 - ] + args: [1000] }, Identity: { file: "https://github.com/status-im/contracts/blob/master/contracts/identity/Identity.sol" }, SimpleStorageWithHttpImport: { fromIndex: 0, - args: [ - 100 - ] + args: [100] } }, afterDeploy: [ diff --git a/test_apps/test_app/config/namesystem.json b/test_apps/test_app/config/namesystem.json index 3995055a..fdaee6c5 100644 --- a/test_apps/test_app/config/namesystem.json +++ b/test_apps/test_app/config/namesystem.json @@ -1,10 +1,15 @@ { "default": { "enabled": true, - "available_providers": ["ens"], + "available_providers": [ + "ens" + ], "provider": "ens", "register": { - "rootDomain": "embark" + "rootDomain": "embark.eth", + "subdomains": { + "status": "0x4a17f35f0a9927fb4141aa91cbbc72c1b31598de" + } } } -} \ No newline at end of file +} diff --git a/test_apps/test_app/test/ens_spec.js b/test_apps/test_app/test/ens_spec.js new file mode 100644 index 00000000..c231e0ae --- /dev/null +++ b/test_apps/test_app/test/ens_spec.js @@ -0,0 +1,42 @@ +/*global contract, config, it, assert, before*/ +const Resolver = require('Embark/contracts/Resolver'); + +const namehash = require('eth-ens-namehash'); +const address = '0x38ac14a9B6a7c8F9C46e4804074186c9F201D0A0'; +const rootNode = namehash.hash('embark.eth'); + +config({ + contracts: { + "ENSRegistry": { + "args": [] + }, + "Resolver": { + "args": ["$ENSRegistry"] + }, + "FIFSRegistrar": { + "args": ["$ENSRegistry", rootNode], + "onDeploy": [ + `ENSRegistry.methods.setOwner('${rootNode}', web3.eth.defaultAccount).send().then(() => { + ENSRegistry.methods.setResolver('${rootNode}', "$Resolver").send(); + Resolver.methods.setAddr('${rootNode}', '${address}').send(); + })` + ] + } + } +}); + +contract("ENS", function () { + this.timeout(0); + + before(function (done) { + // Wait for onDeploy to finish + setTimeout(function () { + done(); + }, 50); + }); + + it("should have registered embark.eth", async function () { + const domainAddress = await Resolver.methods.addr(rootNode).call(); + assert.strictEqual(domainAddress, address); + }); +});