diff --git a/README.md b/README.md index 7a911c4..7a2cf82 100644 --- a/README.md +++ b/README.md @@ -27,38 +27,28 @@ Referrer is set in register() instead of verify() because any person or register ## quickstart +Clone the repo and install dependencies: ```bash git clone https://github.com/hnsfund/xnhns.git - cd xnhns -``` - -```bash - yarn install - ``` +Start the frontend dev server with: ```bash - yarn start - ``` -> in a second terminal window: - +In a second terminal window, start a local hardhat network (this will deploy all contracts on a fresh chain): ```bash - +cd packages/hardhat yarn chain --network hardhat - ``` -> in a third terminal window: - +In a third terminal window, run this to compile and publish to frontend (required after every contract modification): ```bash - +cd packages/hardhat yarn deploy:test - ``` diff --git a/packages/hardhat/.gitignore b/packages/hardhat/.gitignore new file mode 100644 index 0000000..c5435e3 --- /dev/null +++ b/packages/hardhat/.gitignore @@ -0,0 +1 @@ +deployments/ diff --git a/packages/hardhat/contracts/resolvers/PublicResolver.sol b/packages/hardhat/contracts/resolvers/PublicResolver.sol index 3943626..39077f0 100644 --- a/packages/hardhat/contracts/resolvers/PublicResolver.sol +++ b/packages/hardhat/contracts/resolvers/PublicResolver.sol @@ -5,7 +5,7 @@ import "../../interfaces/IENS.sol"; import "./ABIResolver.sol"; import "./AddrResolver.sol"; import "./ContentHashResolver.sol"; -// import "./DNSResolver.sol"; +import "./DNSResolver.sol"; import "./InterfaceResolver.sol"; import "./NameResolver.sol"; import "./PubkeyResolver.sol"; @@ -16,7 +16,7 @@ import "./TextResolver.sol"; * A simple resolver anyone can use; only allows the owner of a node to set its * address. */ -contract PublicResolver is ABIResolver, AddrResolver, ContentHashResolver, InterfaceResolver, PubkeyResolver, TextResolver, NameResolver, Ownable { +contract PublicResolver is ABIResolver, AddrResolver, ContentHashResolver, InterfaceResolver, PubkeyResolver, TextResolver, NameResolver, DNSResolver, Ownable { ENS ens; /** @@ -65,7 +65,7 @@ contract PublicResolver is ABIResolver, AddrResolver, ContentHashResolver, Inter return results; } - function supportsInterface(bytes4 interfaceID) virtual override(ABIResolver, AddrResolver, ContentHashResolver, InterfaceResolver, NameResolver, PubkeyResolver, TextResolver) public pure returns(bool) { + function supportsInterface(bytes4 interfaceID) virtual override(ABIResolver, AddrResolver, ContentHashResolver, DNSResolver, InterfaceResolver, NameResolver, PubkeyResolver, TextResolver) public pure returns(bool) { return super.supportsInterface(interfaceID); } } diff --git a/packages/hardhat/deploy/00_deploy_all.js b/packages/hardhat/deploy/00_deploy_all.js new file mode 100644 index 0000000..6edfe41 --- /dev/null +++ b/packages/hardhat/deploy/00_deploy_all.js @@ -0,0 +1,88 @@ +const fs = require('fs'); + +const namespace = 'matic' + +module.exports = async ({getNamedAccounts, deployments}) => { + const {deploy, execute} = deployments; + const {deployer} = await getNamedAccounts(); + + const ENSRegistry = await deploy('ENSRegistry', { + from: deployer, + args: [], + log: true, + }); + + const Root = await deploy('Root', { + from: deployer, + args: [ENSRegistry.address], + log: true, + }); + + // Set ENSRegistry's owner to Root + if (ENSRegistry.newlyDeployed) { + await execute( + 'ENSRegistry', + { + from: deployer, + log: true + }, + 'setOwner', + // args: + '0x0000000000000000000000000000000000000000000000000000000000000000', Root.address + ); + } + + const TrustedXNHNSOracle = await deploy('TrustedXNHNSOracle', { + from: deployer, + args: [namespace], + log: true, + }); + + const HNSRegistrar = await deploy('HNSRegistrar', { + from: deployer, + args: [ENSRegistry.address, namespace, TrustedXNHNSOracle.address], + log: true, + }); + + // Set Root's controller to HNSRegistrar + if (Root.newlyDeployed) { + await execute( + 'Root', + { + from: deployer, + log: true + }, + 'setController', + // args: + HNSRegistrar.address, true + ); + } + + // Allow HNSRegistrar to call XNHNSOracle + if (TrustedXNHNSOracle.newlyDeployed || Root.newlyDeployed) { + await execute( + 'TrustedXNHNSOracle', + { + from: deployer, + log: true + }, + 'setCallerPermission', + // args: + HNSRegistrar.address, true + ); + } + + const PublicResolver = await deploy('PublicResolver', { + from: deployer, + args: [ENSRegistry.address], + log: true, + }); + + fs.writeFileSync('artifacts/ENSRegistry.address', ENSRegistry.address); + fs.writeFileSync('artifacts/Root.address', Root.address); + fs.writeFileSync('artifacts/TrustedXNHNSOracle.address', TrustedXNHNSOracle.address); + fs.writeFileSync('artifacts/HNSRegistrar.address', HNSRegistrar.address); + fs.writeFileSync('artifacts/PublicResolver.address', PublicResolver.address); +}; + +module.exports.tags = ['all']; diff --git a/packages/hardhat/hardhat.config.js b/packages/hardhat/hardhat.config.js index 7367260..ca50974 100644 --- a/packages/hardhat/hardhat.config.js +++ b/packages/hardhat/hardhat.config.js @@ -141,6 +141,9 @@ module.exports = { }, }, }, + // Set API key for etherscan/polygonscan/mumbai.polygonscan + // Endpoint will be chosen based on default network + // TODO: Replace this hardcode with a switch on defaultNetwork etherscan: { apiKey: '43JVMCI3EJCZSECDWX99KRZRYWDX3H7MCH' } diff --git a/packages/hardhat/package.json b/packages/hardhat/package.json index 6bf1b30..1420aa3 100644 --- a/packages/hardhat/package.json +++ b/packages/hardhat/package.json @@ -22,21 +22,21 @@ "chalk": "^4.1.0", "ethereum-waffle": "^3.1.1", "ethers": "^5.0.17", - "hardhat": "^2.0.8", + "hardhat": "^2.8.0", "hardhat-deploy": "^0.7.0-beta.45", "node-watch": "^0.7.0", "qrcode-terminal": "^0.12.0", "ramda": "^0.27.1" }, "scripts": { - "chain": "hardhat node", + "chain": "hardhat node --write true", "console": "hardhat console", "test": "hardhat test", "compile": "hardhat compile", "deploy": "yarn run deploy-xnhns", "deploy:test": "yarn run deploy-xnhns && hardhat run scripts/publish.js", "deploy:singleton": "hardhat run scripts/deploy-single-tld-ens.js && hardhat run scripts/publish.js", - "deploy-xnhns": "hardhat run scripts/deploy-xnhns.js", + "deploy-xnhns": "hardhat deploy", "postdeploy": " hardhat run scripts/publish.js", "watch": "node scripts/watch.js", "accounts": "hardhat accounts", diff --git a/packages/hardhat/scripts/verify-contracts.js b/packages/hardhat/scripts/verify-contracts.js new file mode 100644 index 0000000..e4ab78c --- /dev/null +++ b/packages/hardhat/scripts/verify-contracts.js @@ -0,0 +1,70 @@ +const fs = require('fs'); +const hre = require('hardhat'); + +const namespace = 'matic'; + +// Comment ones you don't want to verify (lazy peeps customizable way) +const contractNames = [ + 'ENSRegistry', + 'Root', + 'TrustedXNHNSOracle', + 'HNSRegistrar', + 'PublicResolver' +]; + +// Gather all addresses from artifacts directory +const addresses = {}; +fs.readdirSync(`${hre.config.paths.artifacts}`, {withFileTypes: true}).map(file => { + if (!file.isFile() || !file.name.endsWith('.address')) return; + addresses[file.name.replace(/\.address$/, '')] = fs.readFileSync(`${hre.config.paths.artifacts}/${file.name}`)?.toString() || null; +}) +console.log('Addresses:', addresses); + +// Constructor arguments needed to verify +const constructorArguments = { + 'ENSRegistry': [], + 'Root': [addresses.ENSRegistry], + 'TrustedXNHNSOracle': [namespace], + 'HNSRegistrar': [addresses.ENSRegistry, namespace, addresses.TrustedXNHNSOracle], + 'PublicResolver': [addresses.ENSRegistry], +}; +console.log('Constructor Arguments:', constructorArguments); + + +// Verify all (uncommented) contracts +async function main() { + for (const name of contractNames) { + console.log(`Verifying contract ${name}...`); + + const address = addresses[name]; + + if (!address || !address.length) { + console.log('No address found, skipping.'); + continue; + } + + try { + await hre.run('verify:verify', { + address: address, + constructorArguments: constructorArguments[name], + }); + } catch (error) { + const alreadyVerified = error.message.includes('Contract source code already verified') || + error.message.includes('Already Verified') + if (alreadyVerified) { + console.log('Already verified, skipping.'); + } else { + throw error; + } + } + + console.log('Done!') + } +} + +main() + .then(() => process.exit(0)) + .catch((error) => { + console.error(error) + process.exit(1) + }) diff --git a/packages/react-app/.gitignore b/packages/react-app/.gitignore new file mode 100644 index 0000000..709ae34 --- /dev/null +++ b/packages/react-app/.gitignore @@ -0,0 +1 @@ +src/contracts/ diff --git a/packages/react-app/src/constants.js b/packages/react-app/src/constants.js index 5afddc5..15be024 100644 --- a/packages/react-app/src/constants.js +++ b/packages/react-app/src/constants.js @@ -103,6 +103,8 @@ export const NETWORKS = { }, mumbai: { name: "mumbai", + namespace: 'matic', + xnhnsRegistry: '0x0', color: '#92D9FA', chainId: 80001, price: 1, diff --git a/packages/subgraph/abis/PublicResolver.json b/packages/subgraph/abis/PublicResolver.json index 065e9d2..5a1c21f 100644 --- a/packages/subgraph/abis/PublicResolver.json +++ b/packages/subgraph/abis/PublicResolver.json @@ -123,6 +123,75 @@ "name": "ContenthashChanged", "type": "event" }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "bytes32", + "name": "node", + "type": "bytes32" + }, + { + "indexed": false, + "internalType": "bytes", + "name": "name", + "type": "bytes" + }, + { + "indexed": false, + "internalType": "uint16", + "name": "resource", + "type": "uint16" + }, + { + "indexed": false, + "internalType": "bytes", + "name": "record", + "type": "bytes" + } + ], + "name": "DNSRecordChanged", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "bytes32", + "name": "node", + "type": "bytes32" + }, + { + "indexed": false, + "internalType": "bytes", + "name": "name", + "type": "bytes" + }, + { + "indexed": false, + "internalType": "uint16", + "name": "resource", + "type": "uint16" + } + ], + "name": "DNSRecordDeleted", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "bytes32", + "name": "node", + "type": "bytes32" + } + ], + "name": "DNSZoneCleared", + "type": "event" + }, { "anonymous": false, "inputs": [ @@ -337,6 +406,19 @@ "stateMutability": "view", "type": "function" }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "node", + "type": "bytes32" + } + ], + "name": "clearDNSZone", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, { "inputs": [ { @@ -356,6 +438,59 @@ "stateMutability": "view", "type": "function" }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "node", + "type": "bytes32" + }, + { + "internalType": "bytes32", + "name": "name", + "type": "bytes32" + }, + { + "internalType": "uint16", + "name": "resource", + "type": "uint16" + } + ], + "name": "dnsRecord", + "outputs": [ + { + "internalType": "bytes", + "name": "", + "type": "bytes" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "node", + "type": "bytes32" + }, + { + "internalType": "bytes32", + "name": "name", + "type": "bytes32" + } + ], + "name": "hasDNSRecords", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, { "inputs": [ { @@ -567,6 +702,24 @@ "stateMutability": "nonpayable", "type": "function" }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "node", + "type": "bytes32" + }, + { + "internalType": "bytes", + "name": "data", + "type": "bytes" + } + ], + "name": "setDNSRecords", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, { "inputs": [ { diff --git a/yarn.lock b/yarn.lock index 7dc429f..ed48d75 100644 --- a/yarn.lock +++ b/yarn.lock @@ -10602,7 +10602,7 @@ hardhat-deploy@^0.7.0-beta.45: murmur-128 "^0.2.1" qs "^6.9.4" -hardhat@^2.0.8: +hardhat@^2.8.0: version "2.8.0" resolved "https://registry.yarnpkg.com/hardhat/-/hardhat-2.8.0.tgz#4701fafe2de4fe88bed328561e554df85455385b" integrity sha512-A2L5F+B7HgdvfcuEWBXyokzP3biSlu4UeIvNR/lgSC0Og/2kbP9cjMMkIH42V1W8nQEZk70VuryhVKX2uHwSYw==