Merge pull request #642 from embark-framework/features/ens-register

ENS pre-register domains in development
This commit is contained in:
Iuri Matias 2018-07-27 14:51:47 -04:00 committed by GitHub
commit 2192347631
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
20 changed files with 4903 additions and 2888 deletions

View File

@ -183,7 +183,7 @@ class Blockchain {
const self = this;
this.events.setCommandHandler("blockchain:defaultAccount:get", function(cb) {
cb(self.defaultAccount);
cb(self.defaultAccount());
});
this.events.setCommandHandler("blockchain:defaultAccount:set", function(account, cb) {
@ -199,6 +199,9 @@ class Blockchain {
self.getGasPrice(cb);
});
this.events.setCommandHandler("blockchain:contract:create", function(params, cb) {
cb(self.ContractObject(params));
});
}
defaultAccount() {
@ -226,7 +229,7 @@ class Blockchain {
}
ContractObject(params) {
return new this.web3.eth.Contract(params.abi);
return new this.web3.eth.Contract(params.abi, params.address);
}
deployContractObject(contractObject, params) {

View File

@ -282,10 +282,7 @@ Config.prototype.loadNameSystemConfigFile = function() {
"default": {
"available_providers": ["ens"],
"provider": "ens",
"enabled": true,
"register": {
"rootDomain": "embark"
}
"enabled": true
}
};

View File

@ -15,8 +15,8 @@ contract ENSRegistry is ENS {
mapping (bytes32 => Record) records;
// Permits modifications only by the owner of the specified node.
modifier only_owner(bytes32 node) {
require(records[node].owner == msg.sender);
modifier only_owner(bytes32 node, address owner) {
require(records[node].owner == 0 || records[node].owner == msg.sender || records[node].owner == owner);
_;
}
@ -32,19 +32,19 @@ contract ENSRegistry is ENS {
* @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) {
function setOwner(bytes32 node, address owner) public only_owner(node, owner) {
Transfer(node, owner);
records[node].owner = 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.
* @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) {
var subnode = keccak256(node, label);
function setSubnodeOwner(bytes32 node, bytes32 label, address owner) public only_owner(node, owner) {
var subnode = sha3(node, label);
NewOwner(node, label, owner);
records[subnode].owner = owner;
}
@ -54,7 +54,7 @@ contract ENSRegistry is ENS {
* @param node The node to update.
* @param resolver The address of the resolver.
*/
function setResolver(bytes32 node, address resolver) public only_owner(node) {
function setResolver(bytes32 node, address resolver) public only_owner(node, 0x0) {
NewResolver(node, resolver);
records[node].resolver = resolver;
}
@ -64,7 +64,7 @@ contract ENSRegistry is ENS {
* @param node The node to update.
* @param ttl The TTL in seconds.
*/
function setTTL(bytes32 node, uint64 ttl) public only_owner(node) {
function setTTL(bytes32 node, uint64 ttl) public only_owner(node, 0x0) {
NewTTL(node, ttl);
records[node].ttl = ttl;
}

View File

@ -1,6 +1,7 @@
pragma solidity ^0.4.18;
import './ENS.sol';
import './Resolver.sol';
/**
* A registrar that allocates subdomains to the first person to claim them.
@ -10,7 +11,8 @@ contract FIFSRegistrar {
bytes32 rootNode;
modifier only_owner(bytes32 subnode) {
address currentOwner = ens.owner(keccak256(rootNode, subnode));
bytes32 node = sha3(rootNode, subnode);
address currentOwner = ens.owner(node);
require(currentOwner == 0 || currentOwner == msg.sender);
_;
}

View File

@ -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;
}
}

View File

@ -1,6 +1,4 @@
/*global EmbarkJS, web3*/
import namehash from 'eth-ens-namehash';
/*global EmbarkJS, web3, registerSubDomain, namehash*/
let __embarkENS = {};
@ -57,27 +55,6 @@ __embarkENS.resolverInterface = [
],
"type": "function"
},
{
"constant": true,
"inputs": [
{
"name": "node",
"type": "bytes32"
},
{
"name": "kind",
"type": "bytes32"
}
],
"name": "has",
"outputs": [
{
"name": "",
"type": "bool"
}
],
"type": "function"
},
{
"constant": false,
"inputs": [
@ -157,6 +134,8 @@ __embarkENS.resolverInterface = [
const providerNotSetError = 'ENS provider not set';
const NoDecodeAddrError = 'Error: Couldn\'t decode address from ABI: 0x';
const NoDecodeStringError = 'ERROR: The returned value is not a convertible string: 0x0';
const reverseAddrSuffix = '.addr.reverse';
const voidAddress = '0x0000000000000000000000000000000000000000';
__embarkENS.registryAddresses = {
// Mainnet
@ -170,23 +149,25 @@ __embarkENS.registryAddresses = {
__embarkENS.setProvider = function (config) {
const self = this;
const ERROR_MESSAGE = 'ENS is not available in this chain';
self.registration = config.registration;
self.env = config.env;
EmbarkJS.onReady(() => {
web3.eth.net.getId((err, id) => {
if (err) {
web3.eth.net.getId()
.then((id) => {
const registryAddress = self.registryAddresses[id] || config.registryAddress;
self.isAvailable = true;
self.ens = new EmbarkJS.Contract({abi: config.registryAbi, address: registryAddress});
self.registrar = new EmbarkJS.Contract({abi: config.registrarAbi, address: config.registrarAddress});
self.resolver = new EmbarkJS.Contract({abi: config.resolverAbi, address: config.resolverAddress});
})
.catch(err => {
if (err.message.indexOf('Provider not set or invalid') > -1) {
console.warn(ERROR_MESSAGE);
return;
}
console.error(err);
return;
}
if (!self.registryAddresses[id]) {
console.warn(ERROR_MESSAGE);
return;
}
self.isAvailable = true;
self.ens = new EmbarkJS.Contract({abi: config.abi, address: self.registryAddresses[id]});
});
});
});
};
@ -208,6 +189,9 @@ __embarkENS.resolve = function (name, callback) {
if (err) {
return cb(err);
}
if (resolverAddress === voidAddress) {
return cb('Name not yet registered');
}
let resolverContract = new EmbarkJS.Contract({abi: this.resolverInterface, address: resolverAddress});
resolverContract.methods.addr(node).call(cb);
});
@ -221,7 +205,7 @@ __embarkENS.lookup = function (address, callback) {
if (address.startsWith("0x")) {
address = address.slice(2);
}
let node = namehash.hash(address.toLowerCase() + ".addr.reverse");
let node = web3.utils.soliditySha3(address.toLowerCase() + reverseAddrSuffix);
function cb(err, name) {
if (err === NoDecodeStringError || err === NoDecodeAddrError) {
@ -234,11 +218,32 @@ __embarkENS.lookup = function (address, callback) {
if (err) {
return cb(err);
}
if (resolverAddress === voidAddress) {
return cb('Address not associated to a resolver');
}
let resolverContract = new EmbarkJS.Contract({abi: this.resolverInterface, address: resolverAddress});
resolverContract.methods.name(node).call(cb);
});
};
__embarkENS.registerSubDomain = function (name, address, callback) {
callback = callback || function () {};
if (this.env !== 'development') {
return callback('Sub-domain registration is only available in development mode');
}
if (!this.registration || !this.registration.rootDomain) {
return callback('No rootDomain is declared in config/namesystem.js (register.rootDomain). Unable to register a subdomain until then.');
}
if (!address || !web3.utils.isAddress(address)) {
return callback('You need to specify a valid address for the subdomain');
}
// Register function generated by the index
registerSubDomain(this.ens, this.registrar, this.resolver, web3.eth.defaultAccount, name, this.registration.rootDomain,
web3.utils.soliditySha3(address.toLowerCase().substr(2) + reverseAddrSuffix), address, console, callback);
};
__embarkENS.isAvailable = function () {
return Boolean(this.isAvailable);
};

View File

@ -1,28 +1,104 @@
const fs = require('../../core/fs.js');
const utils = require('../../utils/utils.js');
const namehash = require('eth-ens-namehash');
const async = require('async');
const reverseAddrSuffix = '.addr.reverse';
class ENS {
constructor(embark, _options) {
const self = this;
this.env = embark.env;
this.logger = embark.logger;
this.events = embark.events;
this.namesConfig = embark.config.namesystemConfig;
this.registration = this.namesConfig.register;
this.registration = this.namesConfig.register || {};
this.embark = embark;
this.addENSToEmbarkJS();
this.configureENSRegistry();
this.configureRootRegistrar();
this.configureContracts();
this.registerEvents();
}
self.embark.registerActionForEvent("contracts:deploy:afterAll", (cb) => {
self.events.request('contracts:contract', "ENSRegistry", (contract) => {
registerEvents() {
const self = this;
self.embark.registerActionForEvent("contracts:deploy:afterAll", (cb) => {
async.parallel([
function getENSRegistry(paraCb) {
self.events.request('contracts:contract', "ENSRegistry", (contract) => {
paraCb(null, contract);
});
},
function getRegistrar(paraCb) {
self.events.request('contracts:contract', "FIFSRegistrar", (contract) => {
paraCb(null, contract);
});
},
function getResolver(paraCb) {
self.events.request('contracts:contract', "Resolver", (contract) => {
paraCb(null, contract);
});
}
], (err, results) => {
// result[0] => ENSRegistry; result[1] => FIFSRegistrar; result[2] => FIFSRegistrar
let config = {
abi: contract.abiDefinition,
address: contract.deployedAddress
env: self.env,
registration: self.registration,
registryAbi: results[0].abiDefinition,
registryAddress: results[0].deployedAddress,
registrarAbi: results[1].abiDefinition,
registrarAddress: results[1].deployedAddress,
resolverAbi: results[2].abiDefinition,
resolverAddress: results[2].deployedAddress
};
self.addSetProvider(config);
cb();
if (!self.env === 'development' || !self.registration || !self.registration.subdomains || !Object.keys(self.registration.subdomains).length) {
return cb();
}
self.registerConfigDomains(config, cb);
});
});
}
registerConfigDomains(config, cb) {
const self = this;
const register = require('./register');
self.events.request("blockchain:defaultAccount:get", (defaultAccount) => {
async.parallel([
function createRegistryContract(paraCb) {
self.events.request("blockchain:contract:create",
{abi: config.registryAbi, address: config.registryAddress},
(registry) => {
paraCb(null, registry);
});
},
function createRegistrarContract(paraCb) {
self.events.request("blockchain:contract:create",
{abi: config.registrarAbi, address: config.registrarAddress},
(registrar) => {
paraCb(null, registrar);
});
},
function createResolverContract(paraCb) {
self.events.request("blockchain:contract:create",
{abi: config.resolverAbi, address: config.resolverAddress},
(resolver) => {
paraCb(null, resolver);
});
}
], function (err, contracts) {
if (err) {
return cb(err);
}
const [ens, registrar, resolver] = contracts;
async.each(Object.keys(self.registration.subdomains), (subDomainName, eachCb) => {
const address = self.registration.subdomains[subDomainName];
const reverseNode = utils.soliditySha3(address.toLowerCase().substr(2) + reverseAddrSuffix);
register(ens, registrar, resolver, defaultAccount, subDomainName, self.registration.rootDomain,
reverseNode, address, self.logger, eachCb);
}, cb);
});
});
}
@ -39,32 +115,39 @@ class ENS {
}
// get namehash, import it into file
self.events.request("version:get:eth-ens-namehash", function(EnsNamehashVersion) {
self.events.request("version:get:eth-ens-namehash", function (EnsNamehashVersion) {
let currentEnsNamehashVersion = require('../../../package.json').dependencies["eth-ens-namehash"];
if (EnsNamehashVersion !== currentEnsNamehashVersion) {
self.events.request("version:getPackageLocation", "eth-ens-namehash", EnsNamehashVersion, function(err, location) {
self.events.request("version:getPackageLocation", "eth-ens-namehash", EnsNamehashVersion, function (err, location) {
self.embark.registerImportFile("eth-ens-namehash", fs.dappPath(location));
});
}
});
let code = "";
let code = fs.readFileSync(utils.joinPath(__dirname, 'register.js')).toString();
code += "\n" + fs.readFileSync(utils.joinPath(__dirname, 'embarkjs.js')).toString();
code += "\nEmbarkJS.Names.registerProvider('ens', __embarkENS);";
this.embark.addCodeToEmbarkJS(code);
}
configureENSRegistry() {
const self = this;
self.embark.registerContractConfiguration({
configureContracts() {
const config = {
"default": {
"gas": "auto",
"gas": "auto"
},
"development": {
"contracts": {
"ENSRegistry": {
"deploy": true,
"args": []
},
"Resolver": {
"deploy": true,
"args": ["$ENSRegistry"]
},
"FIFSRegistrar": {
"deploy": false
}
}
},
@ -72,6 +155,12 @@ class ENS {
"contracts": {
"ENSRegistry": {
"address": "0x112234455c3a32fd11230c42e7bccd4a84e02010"
},
"Resolver": {
"deploy": false
},
"FIFSRegistrar": {
"deploy": false
}
}
},
@ -79,6 +168,12 @@ class ENS {
"contracts": {
"ENSRegistry": {
"address": "0xe7410170f87102DF0055eB195163A03B7F2Bff4A"
},
"Resolver": {
"deploy": false
},
"FIFSRegistrar": {
"deploy": false
}
}
},
@ -86,51 +181,40 @@ class ENS {
"contracts": {
"ENSRegistry": {
"address": "0x314159265dd8dbb310642f98f50c066173c1259b"
}
}
}
});
self.embark.events.request("config:contractsFiles:add", self.embark.pathToFile('./contracts/ENSRegistry.sol'));
}
configureRootRegistrar() {
const self = this;
let rootNode = namehash.hash(self.registration.rootDomain);
self.embark.registerContractConfiguration({
"default": {
"gas": "auto",
"contracts": {
"FIFSRegistrar": {
"deploy": true,
"args": ["$ENSRegistry", rootNode],
"onDeploy": ["ENSRegistry.methods.setOwner(0, FIFSRegistrar.options.address).send()"]
}
}
},
"ropsten": {
"contracts": {
"FIFSRegistrar": {
},
"Resolver": {
"deploy": false
}
}
},
"rinkeby": {
"contracts": {
"FIFSRegistrar": {
"deploy": false
}
}
},
"livenet": {
"contracts": {
},
"FIFSRegistrar": {
"deploy": false
}
}
}
});
self.embark.events.request("config:contractsFiles:add", self.embark.pathToFile('./contracts/FIFSRegistrar.sol'));
};
if (this.registration && this.registration.rootDomain) {
// Register root domain if it is defined
const rootNode = namehash.hash(this.registration.rootDomain);
config.development.contracts['FIFSRegistrar'] = {
"deploy": true,
"args": ["$ENSRegistry", rootNode],
"onDeploy": [
`ENSRegistry.methods.setOwner('${rootNode}', web3.eth.defaultAccount).send().then(() => {
ENSRegistry.methods.setResolver('${rootNode}', "$Resolver").send();
var reverseNode = web3.utils.soliditySha3(web3.eth.defaultAccount.toLowerCase().substr(2) + '${reverseAddrSuffix}');
ENSRegistry.methods.setResolver(reverseNode, "$Resolver").send();
Resolver.methods.setAddr('${rootNode}', web3.eth.defaultAccount).send();
Resolver.methods.setName(reverseNode, '${this.registration.rootDomain}').send();
})`
]
};
}
this.embark.registerContractConfiguration(config);
this.embark.events.request("config:contractsFiles:add", this.embark.pathToFile('./contracts/ENSRegistry.sol'));
this.embark.events.request("config:contractsFiles:add", this.embark.pathToFile('./contracts/FIFSRegistrar.sol'));
this.embark.events.request("config:contractsFiles:add", this.embark.pathToFile('./contracts/Resolver.sol'));
}
addSetProvider(config) {

View File

@ -0,0 +1,46 @@
const namehash = require('eth-ens-namehash');
function registerSubDomain(ens, registrar, resolver, defaultAccount, subdomain, rootDomain, reverseNode, address, logger, callback) {
const subnode = namehash.hash(subdomain);
const node = namehash.hash(`${subdomain}.${rootDomain}`);
const toSend = registrar.methods.register(subnode, defaultAccount);
let transaction;
toSend.estimateGas()
// Register domain
.then(gasEstimated => {
return toSend.send({gas: gasEstimated + 1000, from: defaultAccount});
})
// Set resolver for the node
.then(transac => {
if (transac.status !== "0x1" && transac.status !== "0x01" && transac.status !== true) {
logger.warn('Failed transaction', transac);
return callback('Failed to register. Check gas cost.');
}
transaction = transac;
return ens.methods.setResolver(node, resolver.options.address).send({from: defaultAccount});
})
// Set address for node
.then(_result => {
return resolver.methods.setAddr(node, address).send({from: defaultAccount});
})
// Set resolver for the reverse node
.then(_result => {
return ens.methods.setResolver(reverseNode, resolver.options.address).send({from: defaultAccount});
})
// Set name for reverse node
.then(_result => {
return resolver.methods.setName(reverseNode, subdomain + '.embark.eth').send({from: defaultAccount});
})
.then(_result => {
callback(null, transaction);
})
.catch(err => {
logger.error(err);
callback('Failed to register with error: ' + (err.message || err));
});
}
if (typeof module !== 'undefined' && module.exports) {
module.exports = registerSubDomain;
}

View File

@ -268,6 +268,11 @@ function sha3(arg) {
return Web3.utils.sha3(arg);
}
function soliditySha3(arg) {
const Web3 = require('web3');
return Web3.utils.soliditySha3(arg);
}
function normalizeInput(input) {
let args = Object.values(input);
if (args.length === 0) {
@ -364,6 +369,7 @@ module.exports = {
getExternalContractUrl,
toChecksumAddress,
sha3,
soliditySha3,
normalizeInput,
buildUrl,
buildUrlFromConfig,

6718
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -38,7 +38,7 @@
"decompress": "^4.2.0",
"deep-equal": "^1.0.1",
"ejs": "^2.5.8",
"embarkjs": "^0.2.0",
"embarkjs": "^0.3.0",
"eth-ens-namehash": "^2.0.8",
"eth-lib": "^0.2.8",
"ethereumjs-wallet": "^0.6.0",

View File

@ -1,6 +1,7 @@
/*global web3*/
import EmbarkJS from 'Embark/EmbarkJS';
import React from 'react';
import {Alert, Form, FormGroup, FormControl, Button} from 'react-bootstrap';
import { Alert, Form, FormGroup, FormControl, Button } from 'react-bootstrap';
window.EmbarkJS = EmbarkJS;
@ -10,18 +11,49 @@ class ENS extends React.Component {
super(props);
this.state = {
valueResolve: 'ethereumfoundation.eth',
valueResolve: 'embark.eth',
responseResolve: null,
isResolveError: false,
valueLookup: '0xfB6916095ca1df60bB79Ce92cE3Ea74c37c5d359',
valueLookup: '',
responseLookup: null,
isLookupError: false,
valueRegister: '',
addressRegister: '',
responseRegister: null,
isRegisterError: false,
embarkLogs: []
};
}
componentWillMount() {
EmbarkJS.onReady(() => {
this.setState({
addressRegister: web3.eth.defaultAccount,
valueLookup: web3.eth.defaultAccount
})
});
}
handleChange(stateName, e) {
this.setState({[stateName]: e.target.value});
this.setState({ [stateName]: e.target.value });
}
registerSubDomain(e) {
e.preventDefault();
const self = this;
const embarkLogs = this.state.embarkLogs;
embarkLogs.push(`EmbarkJS.Names.registerSubDomain('${this.state.valueRegister}', '${this.state.addressRegister}', console.log)`);
this.setState({
embarkLogs: embarkLogs
});
EmbarkJS.Names.registerSubDomain(this.state.valueRegister, this.state.addressRegister, (err, transaction) => {
const message = err ? err : `Successfully registered "${this.state.valueRegister}" with ${transaction.gasUsed} gas`;
self.setState({
responseRegister: message,
isRegisterError: !!err
});
});
}
resolveName(e) {
@ -35,7 +67,7 @@ class ENS extends React.Component {
EmbarkJS.Names.resolve(this.state.valueResolve, (err, result) => {
if (err) {
return this.setState({
responseResolve: err,
responseResolve: err.message || err,
isResolveError: true
});
}
@ -57,7 +89,7 @@ class ENS extends React.Component {
EmbarkJS.Names.lookup(this.state.valueLookup, (err, result) => {
if (err) {
return this.setState({
responseLookup: err,
responseLookup: err.message || err,
isLookupError: true
});
}
@ -70,12 +102,6 @@ class ENS extends React.Component {
render() {
return (<React.Fragment>
{
!this.props.enabled ?
<React.Fragment>
<Alert bsStyle="warning">ENS provider might not be set</Alert>
</React.Fragment> : ''
}
<h3>Resolve a name</h3>
<Form inline>
<FormGroup>
@ -106,6 +132,25 @@ class ENS extends React.Component {
</FormGroup>
</Form>
<h3>Register subdomain for embark</h3>
<Form inline>
<FormGroup>
{this.state.responseRegister &&
<Alert className="alert-result" bsStyle={this.state.isRegisterError ? 'danger' : 'success'}>
<span className="value">{this.state.responseRegister}</span>
</Alert>}
<FormControl
type="text"
defaultValue={this.state.valueRegister}
onChange={(e) => this.handleChange('valueRegister', e)}/>
<FormControl
type="text"
defaultValue={this.state.addressRegister}
onChange={(e) => this.handleChange('addressRegister', e)}/>
<Button bsStyle="primary" onClick={(e) => this.registerSubDomain(e)}>Register subdomain</Button>
</FormGroup>
</Form>
<h3>Embark Calls </h3>
<p>Javascript calls being made: </p>
<div className="logs">

View File

@ -1,6 +1,12 @@
module.exports = {
default: {
available_providers: ["ens"],
provider: "ens"
provider: "ens",
register: {
rootDomain: "embark.eth",
subdomains: {
'status': '0x1a2f3b98e434c02363f3dac3174af93c1d690914'
}
}
}
};

View File

@ -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);
}

View File

@ -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;
}
}

View File

@ -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);
}
}

View File

@ -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;
}
}

View File

@ -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,23 @@ 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]
},
ENSRegistry: {
"deploy": false
},
Resolver: {
"deploy": false
},
FIFSRegistrar: {
"deploy": false
}
},
afterDeploy: [

View File

@ -1,10 +1,15 @@
{
"default": {
"enabled": true,
"available_providers": ["ens"],
"available_providers": [
"ens"
],
"provider": "ens",
"register": {
"rootDomain": "embark"
"rootDomain": "embark.eth",
"subdomains": {
"status": "0x4a17f35f0a9927fb4141aa91cbbc72c1b31598de"
}
}
}
}
}

View File

@ -0,0 +1,48 @@
/*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().then(() => {
global.ensTestReady = true;
});
});`
]
}
}
});
contract("ENS", function () {
this.timeout(1000);
before(function (done) {
// Wait for onDeploy to finish
const wait = setInterval(() => {
if (!global.ensTestReady) {
return;
}
clearInterval(wait);
done();
});
});
it("should have registered embark.eth", async function () {
const domainAddress = await Resolver.methods.addr(rootNode).call();
assert.strictEqual(domainAddress, address);
});
});