diff --git a/bun.lockb b/bun.lockb index 162ca51c..f23ca6d0 100755 Binary files a/bun.lockb and b/bun.lockb differ diff --git a/crosschain-resolver/README.md b/crosschain-resolver/README.md index e94b6b1a..39a317da 100644 --- a/crosschain-resolver/README.md +++ b/crosschain-resolver/README.md @@ -64,14 +64,14 @@ Storage verificaton can only verify the data of l2. When the function result nee Create `.env` and set the following variables - - DEPLOYER_PRIVATE_KEY - L1_PROVIDER_URL - L2_PROVIDER_URL - L1_ETHERSCAN_API_KEY - L2_ETHERSCAN_API_KEY -- OP_VERIFIER_ADDRESS=0x0c2746F20C9c97DBf718de10c04943cf408230A3 - +- VERIFIER_ADDRESS +- ENS_ADDRESS +- WRAPPER_ADDRESS ``` bun run hardhat deploy --network optimismGoerli ``` @@ -82,5 +82,157 @@ Followed by the L1 contract: bun run hardhat deploy --network goerli ``` - ## Deployments + +NOTE: Each name owner will be deploying a dedicated resolver for the name and their subnames. +You can predict the resolver address by calling the predictAddress + +``` +DelegatableResolverFactory.predictAddress(ownerAddress) +``` + +The function is an external function and you cannot call read function from etherscan. +To work around, you may want to define the abi function as view function + +``` +const abi = [ + "function predictAddress(address) view returns (address)" +] +const l2Factory = new ethers.Contract(L2_RESOLVER_FACTORY_ADDRESS, abi, l2provider); +const l2resolverAddress = await l2Factory.predictAddress(ETH_ADDRESS) +``` + +### OP +#### L2 +- DelegatableResolver = [0xE00739Fc93e27aBf44343fD5FAA151c67C0A0Aa3](https://goerli-optimism.etherscan.io/address/0xE00739Fc93e27aBf44343fD5FAA151c67C0A0Aa3) = this is used as a template so cannot interact directly +- DelegatableResolverFactory = [0xacB9771923873614d77C914D716d8E25dAF09b8d](https://goerli-optimism.etherscan.io/address/0xacB9771923873614d77C914D716d8E25dAF09b8d) + +- DelegatableResolverRegistrar = [DelegatableResolverRegistrar](https://goerli-optimism.etherscan.io/address/0x2b07cf3ef421a5ff1cb6f437036bdef132eea37b#writeContract) = Demo contract that allow anyone to register subname under `op.evmgateway.eth` on [L2 resolver on `op.evmgateway.eth`](https://goerli-optimism.etherscan.io/address/0x96753bd0D9bdd98d3a84837B5933078AF49aF12d#writeContract) + +#### L1 +- L1Resolver = [0x65a0963A2941A13a96FcDCfE36c94c6a341f26E5](https://goerli.etherscan.io/address/0x65a0963A2941A13a96FcDCfE36c94c6a341f26E5) = Currently `op.evmgateway.eth` is set to the resolver + +### Base + +#### L2 +- DelegatableResolver = [0x60BDFeF9ff7bB47d95d1658Be925587F046AE2C7](https://goerli.basescan.org/address/0x7d56Bc48F0802319CB7C79B421Fa5661De905AF7) = this is used as a template so cannot interact directly +- DelegatableResolverFactory = [0x7d56Bc48F0802319CB7C79B421Fa5661De905AF7](https://goerli.basescan.org/address/0x7d56Bc48F0802319CB7C79B421Fa5661De905AF7) + +- DelegatableResolverRegistrar = [DelegatableResolverRegistrar](https://goerli.basescan.org/address/0xe0356133c3c43cbb623543488e607e4e349eaa10#code) = Demo contract that allow anyone to register subname under `base.evmgateway.eth` on [L2 resolver on `base.evmgateway.eth`](https://goerli.basescan.org/address/0xE4B18eFbF71d516046514598FD7FcFbad4beC742) + +#### L1 +- L1Resolver = [0x052D7E10D55Ae12b4F62cdE18dBb7E938efa230D](https://goerli.etherscan.io/address/0x052D7E10D55Ae12b4F62cdE18dBb7E938efa230D) = Currently `base.evmgateway.eth` is set to the resolver + + +## Usage + +### Move resolver to L2 + +On L1 + +```js +// On L1 +await ENS.setResolver(l1lresolver) +const l2resolverAddress = await DelegatableResolverFactory.predictAddress(OWNER_ADDRESS) +await L1Resolver.setTarget(node, l2resolverAddress) +// On L2 +const l2resolverAddress = await DelegatableResolverFactory.predictAddress(OWNER_ADDRESS) +await DelegatableResolverFactory.create(OWNER_ADDRESS) +await DelegatableResolver['setAddr(bytes32,address)'](node, OWNER_ADDRESS) +// On L1 +const abi = [ + "function addr(bytes32) view returns (address)", + "function resolve(bytes,bytes) view returns (bytes)", +] +const i = new ethers.Interface(abi) +const calldata = i.encodeFunctionData("addr", [node]) +const result2 = await l1resolver.resolve(encodedname, calldata, { enableCcipRead: true }) +const address = i.decodeFunctionResult("addr", result2)[0] +``` + +NOTE: The l1 resolver must be queried through `resolve` function to handle subnames + +Using the scripts + +``` +DEPLOYER_PRIVATE_KEY=$DEPLOYER_PRIVATE_KEY L1_PROVIDER_URL=$L1_PROVIDER_URL L2_PROVIDER_URL=$L2_PROVIDER_URL L1_ETHERSCAN_API_KEY=$L1_ETHERSCAN_API_KEY L2_ETHERSCAN_API_KEY=$L2_ETHERSCAN_API_KEY L2_PROVIDER_URL=$L2_PROVIDER_URL L2_RESOLVER_FACTORY_ADDRESS=$L2_RESOLVER_FACTORY_ADDRESS L1_RESOLVER_ADDRESS=$L1_RESOLVER_ADDRESS ENS_NAME=$ENS_NAME yarn setupl1 +``` + +``` +DEPLOYER_PRIVATE_KEY=$DEPLOYER_PRIVATE_KEY L1_PROVIDER_URL=$L1_PROVIDER_URL L2_PROVIDER_URL=$L2_PROVIDER_URL L1_ETHERSCAN_API_KEY=$L1_ETHERSCAN_API_KEY L2_ETHERSCAN_API_KEY=$L2_ETHERSCAN_API_KEY L2_PROVIDER_URL=$L2_PROVIDER_URL L2_RESOLVER_FACTORY_ADDRESS=$L2_RESOLVER_FACTORY_ADDRESS ENS_NAME=$ENS_NAME yarn setupl2 +``` + +``` +L1_PROVIDER_URL=$L1_PROVIDER_URL L1_ETHERSCAN_API_KEY=$L1_ETHERSCAN_API_KEY L2_ETHERSCAN_API_KEY=$L2_ETHERSCAN_API_KEY L2_PROVIDER_URL=$L2_PROVIDER_URL ENS_NAME=$ENS_NAME yarn getaddr +``` + +### Issue subname to L2 + +Assuming you have already moved the parent name to a l2, + + +```js +// On L2 +const OPERATOR_ADDRESS = '' +const PARENT_NAME = 'op.evmgateway.eth' +const SUBNAME = `${SUBNAME}.${PARENT_NAME}` +const l2resolverAddress = await DelegatableResolverFactory.predictAddress(OWNER_ADDRESS) +const DelegatableResolver = new ethers.Contract(l2resolverAddress, abi, l2provider); +await DelegatableResolver.approve(encodedname, OWNER_ADDRESS, true) +``` + +Using the script + +``` +OPERATOR_ADDRESS=0x5A384227B65FA093DEC03Ec34e111Db80A040615 +DEPLOYER_PRIVATE_KEY=$DEPLOYER_PRIVATE_KEY L1_PROVIDER_URL=$L1_PROVIDER_URL L2_PROVIDER_URL=$L2_PROVIDER_URL L1_ETHERSCAN_API_KEY=$L1_ETHERSCAN_API_KEY L2_ETHERSCAN_API_KEY=$L2_ETHERSCAN_API_KEY L2_PROVIDER_URL=$L2_PROVIDER_URL L2_RESOLVER_FACTORY_ADDRESS=$L2_RESOLVER_FACTORY_ADDRESS ENS_SUBNAME=$ENS_SUBNAME yarn approve +``` + +Once done, set addrss of the subname from the operator, wait 10~20 min, then query the subname on L1 + + +### Create subname registrar + +#### Step 1: Find out the corresponding L2 resolvers on L1 + +``` +> const packet = require('dns-packet'); +> const ethers = require('ethers'); +> const encodeName = (name) => '0x' + packet.name.encode(name).toString('hex') +> encodedbasename = encodeName('') +'0x0000' +> node = ethers.namehash('base.evmgateway.eth') +'0xb164280377eb563e73caf38ac5693328813c1ed18f9bd925d17d5f59b22421f0' +> encodedname = encodeName('base.evmgateway.eth') +'0x04626173650a65766d676174657761790365746800' +> subnode = ethers.namehash('makoto.base.evmgateway.eth') +'0xe399336bd17b7d9660834201b6b166196290ceca5a41b402c033e3026d4e17d1' +> encodedsubname = encodeName('makoto.base.evmgateway.eth') +'0x066d616b6f746f04626173650a65766d676174657761790365746800' +``` + +Go to [Base L1 resolver](https://goerli.etherscan.io/address/0x052D7E10D55Ae12b4F62cdE18dBb7E938efa230D#readContract) + +``` +[node, target] = l1resolver.getTarget(encodedname, 0) +``` + +#### Step 2: Deploy the registrar contract + +Deploy [DelegatableResolverRegistrar.sol](https://gist.github.com/makoto/7d83ca6530adc69fea27923ee8ae8986) to L2 with resolver address as `target` which you obtained at step 1. +Take notes of the deployed registrar address + +#### Step 3: Delegate the registrar contract to root + +Go to the target address and approve the newly created registrar address as the operator ob the base node + +``` +l2resolver.approve(baseencodedname,registrarAddress, true) +``` + +Once done, anyone can register subnames on "base.evmgateway.eth" (NOTE: currently there is no notion of ownership so other people can overtake the names) from the L2 Registrar + +``` +l2registrar.register(encodedsubname, subnameuser) +l2resolver['setAddr(bytes32,address)](subnode, subnameuser) +``` \ No newline at end of file diff --git a/crosschain-resolver/deploy_l1/10_l1resolver.ts b/crosschain-resolver/deploy_l1/10_l1resolver.ts index 8de02b18..c010d234 100644 --- a/crosschain-resolver/deploy_l1/10_l1resolver.ts +++ b/crosschain-resolver/deploy_l1/10_l1resolver.ts @@ -8,12 +8,16 @@ const func: DeployFunction = async function (hre: HardhatRuntimeEnvironment) { const {deployer} = await getNamedAccounts(); - const OP_VERIFIER_ADDRESS = process.env.OP_VERIFIER_ADDRESS - if(!OP_VERIFIER_ADDRESS) throw ('Set $OP_VERIFIER_ADDRESS') - console.log({OP_VERIFIER_ADDRESS}) + const VERIFIER_ADDRESS = process.env.VERIFIER_ADDRESS + const ENS_ADDRESS = process.env.ENS_ADDRESS + const WRAPPER_ADDRESS = process.env.WRAPPER_ADDRESS + if(!VERIFIER_ADDRESS) throw ('Set $VERIFIER_ADDRESS') + if(!ENS_ADDRESS) throw ('Set $ENS_ADDRESS') + if(!WRAPPER_ADDRESS) throw ('Set $WRAPPER_ADDRESS') + console.log({VERIFIER_ADDRESS,ENS_ADDRESS, WRAPPER_ADDRESS}) await deploy('L1Resolver', { from: deployer, - args: [OP_VERIFIER_ADDRESS], + args: [VERIFIER_ADDRESS,ENS_ADDRESS,WRAPPER_ADDRESS], log: true, }); }; diff --git a/crosschain-resolver/deploy_l2/01_l2resolver.ts b/crosschain-resolver/deploy_l2/01_l2resolver.ts index 67d28a1a..060695e1 100644 --- a/crosschain-resolver/deploy_l2/01_l2resolver.ts +++ b/crosschain-resolver/deploy_l2/01_l2resolver.ts @@ -6,7 +6,6 @@ const func: DeployFunction = async function (hre: HardhatRuntimeEnvironment) { const {deploy} = deployments; const {deployer} = await getNamedAccounts(); - console.log({deployer}) const impl = await deploy('DelegatableResolver', { from: deployer, args: [], @@ -14,13 +13,6 @@ const func: DeployFunction = async function (hre: HardhatRuntimeEnvironment) { }); const implAddress = impl.address console.log(`DelegatableResolver is deployed at ${implAddress}`) - const factory = await deploy('DelegatableResolverFactory', { - from: deployer, - args: [impl.address], - log: true, - }); - await factory.wait() - console.log(`DelegatableResolverFactory is deployed at ${factory.address}`) }; export default func; func.tags = ['DelegatableResolver']; diff --git a/crosschain-resolver/deploy_l2/02_l2resolver_factory.ts b/crosschain-resolver/deploy_l2/02_l2resolver_factory.ts index c58029a5..3f51588e 100644 --- a/crosschain-resolver/deploy_l2/02_l2resolver_factory.ts +++ b/crosschain-resolver/deploy_l2/02_l2resolver_factory.ts @@ -2,10 +2,10 @@ import {HardhatRuntimeEnvironment} from 'hardhat/types'; import {DeployFunction} from 'hardhat-deploy/types'; const func: DeployFunction = async function (hre: HardhatRuntimeEnvironment) { - const {deployments} = hre; + const {deployments, getNamedAccounts} = hre; const {deploy} = deployments; - const DelegatableResolver = await hre.companionNetworks['l2'].deployments.get('DelegatableResolver'); - console.log({DelegatableResolver}) + const {deployer} = await getNamedAccounts(); + const DelegatableResolver = await deployments.get('DelegatableResolver'); const factory = await deploy('DelegatableResolverFactory', { from: deployer, args: [DelegatableResolver.address], diff --git a/crosschain-resolver/hardhat.config.ts b/crosschain-resolver/hardhat.config.ts index 9e54c70e..3b49e64e 100644 --- a/crosschain-resolver/hardhat.config.ts +++ b/crosschain-resolver/hardhat.config.ts @@ -7,7 +7,7 @@ const DEPLOYER_PRIVATE_KEY = process.env.DEPLOYER_PRIVATE_KEY ?? "ac0974bec39a17 const L1_PROVIDER_URL = process.env.L1_PROVIDER_URL || ''; const L1_ETHERSCAN_API_KEY = process.env.L1_ETHERSCAN_API_KEY || ''; const L2_ETHERSCAN_API_KEY = process.env.L2_ETHERSCAN_API_KEY || ''; - +console.log({L1_PROVIDER_URL,L1_ETHERSCAN_API_KEY,L2_ETHERSCAN_API_KEY}) const config: HardhatUserConfig = { solidity: { version: "0.8.19", @@ -27,20 +27,23 @@ const config: HardhatUserConfig = { url: L1_PROVIDER_URL, accounts: [DEPLOYER_PRIVATE_KEY], deploy: [ "deploy_l1/" ], - companionNetworks: { - l2: "optimismGoerli", - }, }, optimismGoerli: { url: "https://goerli.optimism.io", accounts: [DEPLOYER_PRIVATE_KEY], deploy: [ "deploy_l2/" ], + }, + baseGoerli: { + url: "https://goerli.base.org", + accounts: [DEPLOYER_PRIVATE_KEY], + deploy: [ "deploy_l2/" ], } }, etherscan: { apiKey: { goerli: L1_ETHERSCAN_API_KEY, - optimismGoerli: L2_ETHERSCAN_API_KEY + optimismGoerli: L2_ETHERSCAN_API_KEY, + baseGoerli: L2_ETHERSCAN_API_KEY }, customChains: [ { @@ -50,6 +53,14 @@ const config: HardhatUserConfig = { apiURL: "https://api-goerli-optimism.etherscan.io/api", browserURL: "https://goerli-optimism.etherscan.io" } + }, + { + network: "baseGoerli", + chainId: 84531, + urls: { + browserURL: "https://goerli.basescan.org", + apiURL: "https://api-goerli.basescan.org/api", + } } ] }, diff --git a/crosschain-resolver/package.json b/crosschain-resolver/package.json index eb816efc..054e5a26 100644 --- a/crosschain-resolver/package.json +++ b/crosschain-resolver/package.json @@ -7,6 +7,10 @@ "test": "node scripts/test.js", "storage": "hardhat check", "clean": "rm -fr artifacts cache node_modules typechain-types", + "setupl1": "hardhat run scripts/setupl1.ts --network goerli", + "setupl2": "hardhat run scripts/setupl2.ts", + "getaddr": "hardhat run scripts/getaddr.ts --network goerli", + "approve": "hardhat run scripts/approve.ts", "lint": "exit 0" }, "devDependencies": { diff --git a/crosschain-resolver/scripts/approve.ts b/crosschain-resolver/scripts/approve.ts new file mode 100644 index 00000000..a2a0390e --- /dev/null +++ b/crosschain-resolver/scripts/approve.ts @@ -0,0 +1,35 @@ +import hre from 'hardhat'; +import packet from 'dns-packet'; +const ethers = hre.ethers; +const encodeName = (name) => '0x' + packet.name.encode(name).toString('hex') +const abi = [ + "function predictAddress(address) view returns (address)" +] + +export const main = async () => { + const [signer] = await hre.ethers.getSigners(); + if (!process.env.OPERATOR_ADDRESS || !process.env.L2_RESOLVER_FACTORY_ADDRESS || !process.env.L1_PROVIDER_URL || !process.env.L2_PROVIDER_URL || !process.env.ENS_SUBNAME) + throw 'Set OPERATOR_ADDRESS L1_PROVIDER_URL, L2_PROVIDER_URL, L2_RESOLVER_FACTORY_ADDRESS, and ENS_SUBNAME'; + + const OPERATOR_ADDRESS = process.env.OPERATOR_ADDRESS + const L2_RESOLVER_FACTORY_ADDRESS = process.env.L2_RESOLVER_FACTORY_ADDRESS + const L1_PROVIDER_URL = process.env.L1_PROVIDER_URL; + const L2_PROVIDER_URL = process.env.L2_PROVIDER_URL; + const ENS_SUBNAME = process.env.ENS_SUBNAME; + const ETH_ADDRESS = signer.address; + const node = ethers.namehash(ENS_SUBNAME) + const encodedName = encodeName(ENS_SUBNAME) + console.log({L1_PROVIDER_URL, L2_PROVIDER_URL, L2_RESOLVER_FACTORY_ADDRESS, OPERATOR_ADDRESS, ENS_SUBNAME, encodedName,node}) + const l2provider = new ethers.JsonRpcProvider(L2_PROVIDER_URL); + const l2FactoryRead = new ethers.Contract(L2_RESOLVER_FACTORY_ADDRESS, abi, l2provider); + const l2resolverAddress = await l2FactoryRead.predictAddress(ETH_ADDRESS) + console.log(`l2 resolver address for ${ETH_ADDRESS} is ${l2resolverAddress}`) + const l2Resolver = (await ethers.getContractFactory('DelegatableResolver', signer)).attach(l2resolverAddress); + const tx2 = await l2Resolver.approve(encodedName, OPERATOR_ADDRESS, true, { + gasPrice: "900000", + gasLimit: 500000, + }); + console.log(`Approving ${OPERATOR_ADDRESS} to update ${ENS_SUBNAME}`, (await tx2.wait()).hash) +}; + +main(); \ No newline at end of file diff --git a/crosschain-resolver/scripts/getaddr.ts b/crosschain-resolver/scripts/getaddr.ts new file mode 100644 index 00000000..a018ba65 --- /dev/null +++ b/crosschain-resolver/scripts/getaddr.ts @@ -0,0 +1,51 @@ +import packet from 'dns-packet'; +const encodeName = (name) => '0x' + packet.name.encode(name).toString('hex') +const l1abi = [ + "function getTarget(bytes,uint256) view returns (bytes32, address)", + "function addr(bytes32) view returns (address)", + "function resolve(bytes,bytes) view returns (bytes)", +] + +const l2abi = [ + "function addr(bytes32) view returns (address)" +] + +import {ethers} from 'ethers'; +export const main = async () => { + if (!process.env.L1_PROVIDER_URL || !process.env.ENS_NAME) + throw 'Set L1_PROVIDER_URL and ENS_NAME'; + + const L1_PROVIDER_URL = process.env.L1_PROVIDER_URL; + const L2_PROVIDER_URL = process.env.L2_PROVIDER_URL; + const ENS_NAME = process.env.ENS_NAME; + const encodedname = encodeName(ENS_NAME) + const node = ethers.namehash(ENS_NAME) + + const l1provider = new ethers.JsonRpcProvider(L1_PROVIDER_URL); + const l2provider = new ethers.JsonRpcProvider(L2_PROVIDER_URL); + let resolver = await l1provider.getResolver(ENS_NAME) + // Wildcard seems not working. A workaround for now + if(!resolver){ + const parentName = ENS_NAME.split('.').slice(1).join('.') + console.log(`Resolver not found on ${ENS_NAME}. Looking up ${parentName}`) + resolver = await l1provider.getResolver(parentName) + } + console.log({ENS_NAME, resolver, encodedname, node}) + const l1resolver = new ethers.Contract(resolver.address, l1abi, l1provider); + const target = await l1resolver.getTarget(encodedname, 0) + const l2resolverAddress = target[1] + console.log('Target is set to ' + l2resolverAddress); + const l2resolver = new ethers.Contract(l2resolverAddress, l2abi, l2provider); + const l2address = await l2resolver['addr(bytes32)'](node) + console.log('L2 query result ' + l2address); + const i = new ethers.Interface(l1abi) + const calldata = i.encodeFunctionData("addr", [node]) + const result2 = await l1resolver.resolve(encodedname, calldata, { enableCcipRead: true }) + const decoded = i.decodeFunctionResult("addr", result2) + console.log('L1 query result ' + decoded[0]); + // These should also work but somehow not working. Mabye some inconsistent resolver interface? + console.log(await l1provider.resolveName(ENS_NAME)); + console.log(await resolver.getAddress()); +}; + +main(); \ No newline at end of file diff --git a/crosschain-resolver/scripts/setupl1.ts b/crosschain-resolver/scripts/setupl1.ts new file mode 100644 index 00000000..90247ab2 --- /dev/null +++ b/crosschain-resolver/scripts/setupl1.ts @@ -0,0 +1,44 @@ +import hre from 'hardhat'; +const ethers = hre.ethers; +const abi = [ + "function predictAddress(address) view returns (address)" +] + +export const main = async () => { + const [signer] = await hre.ethers.getSigners(); + + const L2_RESOLVER_FACTORY_ADDRESS = process.env.L2_RESOLVER_FACTORY_ADDRESS + const L1_PROVIDER_URL = process.env.L1_PROVIDER_URL; + const L2_PROVIDER_URL = process.env.L2_PROVIDER_URL; + const L1_RESOLVER_ADDRESS = process.env.L1_RESOLVER_ADDRESS; + const ENS_NAME = process.env.ENS_NAME; + const ETH_ADDRESS = signer.address + const node = ethers.namehash(ENS_NAME); + console.log({L2_RESOLVER_FACTORY_ADDRESS, L1_RESOLVER_ADDRESS, ENS_NAME, node, ETH_ADDRESS}) + + + if (!process.env.L2_RESOLVER_FACTORY_ADDRESS || !process.env.L1_PROVIDER_URL || !process.env.L2_PROVIDER_URL || !process.env.ENS_NAME) + throw 'Set L1_PROVIDER_URL, L2_PROVIDER_URL, L2_RESOLVER_FACTORY_ADDRESS, L1_RESOLVER_ADDRESS, and ENS_NAME'; + + const provider = new ethers.JsonRpcProvider(L1_PROVIDER_URL); + const currentResolver = await provider.getResolver(ENS_NAME) + if(currentResolver.address !== L1_RESOLVER_ADDRESS){ + console.log({ENS_NAME, node, CURRENT_RESOLVER_ADDRESS:currentResolver.address, L1_RESOLVER_ADDRESS}) + throw(`Set the resolver of the parent name to ${L1_RESOLVER_ADDRESS}`) + }else{ + console.log(`The resolver of ${ENS_NAME} is set to ${L1_RESOLVER_ADDRESS}`) + } + + const l2provider = new ethers.JsonRpcProvider(L2_PROVIDER_URL); + const l2Factory = new ethers.Contract(L2_RESOLVER_FACTORY_ADDRESS, abi, l2provider); + console.log({l2Factory}) + const l2resolverAddress = await l2Factory.predictAddress(ETH_ADDRESS) + console.log({l2resolverAddress}) + const l1resolver = (await ethers.getContractFactory('L1Resolver', signer)).attach(L1_RESOLVER_ADDRESS); + const tx2 = await l1resolver.setTarget(node, l2resolverAddress) + + console.log(`Setting l2 resolver ${l2resolverAddress} as a target`, (await tx2.wait()).hash) + console.log(`Set export L2_RESOLVER_ADDRESS=${l2resolverAddress}`) +}; + +main(); \ No newline at end of file diff --git a/crosschain-resolver/scripts/setupl2.ts b/crosschain-resolver/scripts/setupl2.ts new file mode 100644 index 00000000..281dd277 --- /dev/null +++ b/crosschain-resolver/scripts/setupl2.ts @@ -0,0 +1,37 @@ +import hre from 'hardhat'; +const ethers = hre.ethers; +const abi = [ + "function predictAddress(address) view returns (address)" +] + +export const main = async () => { + const [signer] = await hre.ethers.getSigners(); + if (!process.env.L2_RESOLVER_FACTORY_ADDRESS || !process.env.L1_PROVIDER_URL || !process.env.L2_PROVIDER_URL || !process.env.ENS_NAME) + throw 'Set L1_PROVIDER_URL, L2_PROVIDER_URL, L2_RESOLVER_FACTORY_ADDRESS, and ENS_NAME'; + + const L2_RESOLVER_FACTORY_ADDRESS = process.env.L2_RESOLVER_FACTORY_ADDRESS + const L1_PROVIDER_URL = process.env.L1_PROVIDER_URL; + const L2_PROVIDER_URL = process.env.L2_PROVIDER_URL; + const ENS_NAME = process.env.ENS_NAME; + const ETH_ADDRESS = signer.address + const node = ethers.namehash(ENS_NAME) + console.log({L1_PROVIDER_URL, L2_PROVIDER_URL, L2_RESOLVER_FACTORY_ADDRESS, ENS_NAME, node, ETH_ADDRESS}) + const l2Factory = (await ethers.getContractFactory('DelegatableResolverFactory', signer)).attach(L2_RESOLVER_FACTORY_ADDRESS); + // Calling predictAddress via the factory above is throwing an error so creating another connection as a workaround. + const l2provider = new ethers.JsonRpcProvider(L2_PROVIDER_URL); + const l2FactoryRead = new ethers.Contract(L2_RESOLVER_FACTORY_ADDRESS, abi, l2provider); + const l2resolverAddress = await l2FactoryRead.predictAddress(ETH_ADDRESS) + const tx1 = await l2Factory.create(ETH_ADDRESS, { + gasPrice: "900000", + gasLimit: 500000, + }) + console.log(`4, Creating l2 resolver ${l2resolverAddress} as a target`, (await tx1.wait()).hash) + const l2Resolver = (await ethers.getContractFactory('DelegatableResolver', signer)).attach(l2resolverAddress); + const tx2 = await l2Resolver['setAddr(bytes32,address)'](node, ETH_ADDRESS, { + gasPrice: "900000", + gasLimit: 500000, + }); + console.log(`5, Setting l2 ETH address of ${ENS_NAME} to ${ETH_ADDRESS} as a target`, (await tx2.wait()).hash) +}; + +main(); \ No newline at end of file diff --git a/crosschain-reverse-resolver/README.md b/crosschain-reverse-resolver/README.md index 3e785eb9..1deff513 100644 --- a/crosschain-reverse-resolver/README.md +++ b/crosschain-reverse-resolver/README.md @@ -3,3 +3,88 @@ A reverse resolver contract that is built on top of evm-verifier. For a detailed readme and usage instructions, see the [monorepo readme](https://github.com/ensdomains/evmgateway/tree/main). + + +## Deploying (Goerli) + +Create `.env` and set the following variables + +- DEPLOYER_PRIVATE_KEY +- L1_PROVIDER_URL +- L2_PROVIDER_URL +- L1_ETHERSCAN_API_KEY +- L2_ETHERSCAN_API_KEY +- VERIFIER_ADDRESS +- REVERSE_NAMESPACE + +NOTE: Use https://github.com/ethereum-optimism/evmgateway-starter#op-gateway-and-op-verifier-deployments for VERIFIER_ADDRESS + +``` +bun run hardhat deploy --network optimismGoerli +``` + +Followed by the L1 contract: + +``` +bun run hardhat deploy --network goerli +``` + +After deployment is complete, set the rersolver of $REVERSE_NAMESPACE to L1ReverseResolver contract address + +## Deployments + +### OP +#### L2 +- L2ReverseRegistrar = [0x7D006EFd21eb282C8B0a425BAB546517bfEC2cc2](https://goerli-optimism.etherscan.io/address/0x7D006EFd21eb282C8B0a425BAB546517bfEC2cc2) = REVERSE_NAMESPACE is set to `op.reverse.evmgateway.eth` +#### L1 +- L1ReverseResolver = [0xeEB5832Ea8732f7EF06d468E40F562c9D7347795](https://goerli.etherscan.io/address/0xeEB5832Ea8732f7EF06d468E40F562c9D7347795) + +### Base + +#### L2 +- L2ReverseRegistrar = [0xDC317ef697b3A9903a24abcC325d9C1C80B19D87](https://goerli.basescan.org/address/0xDC317ef697b3A9903a24abcC325d9C1C80B19D87) = REVERSE_NAMESPACE is set to `base.reverse.evmgateway.eth` +#### L1 +- L1ReverseResolver = [0x3c332a23a6052afE947F47656d1fD0f450F4C237](https://goerli.etherscan.io/address/0x3c332a23a6052afE947F47656d1fD0f450F4C237) + + +## Usage + +### Set Primary name on L2 + +``` +const name = 'foo.op.evmgateway.eth' +const registrar = registrar.setName(name) +``` + +, try it directly from [etherscan](https://goerli.etherscan.io/address/0xeEB5832Ea8732f7EF06d468E40F562c9D7347795), or run the script +``` +DEPLOYER_PRIVATE_KEY=$DEPLOYER_PRIVATE_KEY REVERSE_NAMESPACE=REVERSE_NAMESPACE L2_PROVIDER_URL=$L2_PROVIDER_URL L2_REVERSE_REGISTRAR_ADDRESS=$L2_REVERSE_REGISTRAR_ADDRESS ENS_NAME='foo.op.evmgateway.eth' yarn setname --network optimismGoerli +``` + +### Query Primary name on L1 + +The current goerli primary namespace is set at `op.reverse.evmgateway.eth` for Optimism Goerli. Once the ENS DAO approves it, it will be put under `${cointype}.ververse` + +- 2147484068 is the coin type of Optimism Goerli (420) +- 2147568179 is the coin type of Base Goerli (84531) + +```js +import packet from 'dns-packet'; +import {ethers} from 'ethers'; +const abi = ['function name(bytes32) view returns(string)']; +const encodeName = (name) => '0x' + packet.name.encode(name).toString('hex') +const namespace = 'op.reverse.evmgateway.eth' // 2147484068. is the coinType of Optimism Goerli (420) +const name = ETH_ADDRESS.substring(2).toLowerCase() + "." + namespace +const encodedname = encodeName(name); +const reversenode = ethers.namehash(name); +const reverseresolver = await provider.getResolver(namespace); +const provider = new ethers.JsonRpcProvider(L1_PROVIDER_URL); +const l1resolver = new ethers.Contract(reverseresolver.address, abi, provider); +console.log(await l1resolver.name(reversenode, {enableCcipRead:true})) +``` + +Using the script + +``` +L1_PROVIDER_URL=$L1_PROVIDER_URL L2_REVERSE_REGISTRAR_ADDRESS=$L2_REVERSE_REGISTRAR_ADDRESS ETH_ADDRESS=$ETH_ADDRESS yarn getname +``` diff --git a/crosschain-reverse-resolver/contracts/deps.sol b/crosschain-reverse-resolver/contracts/deps.sol index 8de74fc4..fc4d9ccf 100644 --- a/crosschain-reverse-resolver/contracts/deps.sol +++ b/crosschain-reverse-resolver/contracts/deps.sol @@ -1,5 +1,4 @@ import {L1Verifier} from '@ensdomains/l1-verifier/contracts/L1Verifier.sol'; -import '@ensdomains/ens-contracts/contracts/resolvers/OwnedResolver.sol'; import '@ensdomains/ens-contracts/contracts/reverseRegistrar/L2ReverseRegistrar.sol'; // Storage slot diff --git a/crosschain-reverse-resolver/deploy_l1/10_l1resolver.ts b/crosschain-reverse-resolver/deploy_l1/10_l1resolver.ts index 767bcf81..a0db1637 100644 --- a/crosschain-reverse-resolver/deploy_l1/10_l1resolver.ts +++ b/crosschain-reverse-resolver/deploy_l1/10_l1resolver.ts @@ -8,13 +8,14 @@ const func: DeployFunction = async function (hre: HardhatRuntimeEnvironment) { const {deployer} = await getNamedAccounts(); - const OP_VERIFIER_ADDRESS = process.env.OP_VERIFIER_ADDRESS - const OwnedResolver = await hre.companionNetworks['l2'].deployments.get('OwnedResolver'); - if(!OP_VERIFIER_ADDRESS) throw ('Set $OP_VERIFIER_ADDRESS') - console.log({OP_VERIFIER_ADDRESS, OWNED_RESOLVER_ADDRESS:OwnedResolver.address}) + const VERIFIER_ADDRESS = process.env.VERIFIER_ADDRESS + const L2_REVERSE_REGISTRAR_ADDRESS = process.env.L2_REVERSE_REGISTRAR_ADDRESS + if(!VERIFIER_ADDRESS) throw ('Set $VERIFIER_ADDRESS') + if(!L2_REVERSE_REGISTRAR_ADDRESS) throw ('Set $L2_REVERSE_REGISTRAR_ADDRESS') + console.log({VERIFIER_ADDRESS, L2_REVERSE_REGISTRAR_ADDRESS}) await deploy('L1ReverseResolver', { from: deployer, - args: [OP_VERIFIER_ADDRESS, OwnedResolver.address], + args: [VERIFIER_ADDRESS, L2_REVERSE_REGISTRAR_ADDRESS], log: true, }); }; diff --git a/crosschain-reverse-resolver/deploy_l2/01_l2resolver .ts b/crosschain-reverse-resolver/deploy_l2/01_l2resolver .ts index 6c685678..6e341fa0 100644 --- a/crosschain-reverse-resolver/deploy_l2/01_l2resolver .ts +++ b/crosschain-reverse-resolver/deploy_l2/01_l2resolver .ts @@ -7,11 +7,19 @@ const func: DeployFunction = async function (hre: HardhatRuntimeEnvironment) { const {deployer} = await getNamedAccounts(); console.log({deployer}) - await deploy('OwnedResolver', { + const REVERSE_NAMESPACE = process.env.REVERSE_NAMESPACE + if(!REVERSE_NAMESPACE) throw ('Set $REVERSE_NAMESPACE') + + // eg: `${opcointype}.reverse` + const reversenode = ethers.namehash(REVERSE_NAMESPACE) + console.log({REVERSE_NAMESPACE, reversenode}) + await deploy('L2ReverseRegistrar', { from: deployer, - args: [], + args: [reversenode], log: true, + gasPrice:20000007, + gas:20000007 }); }; export default func; -func.tags = ['OwnedResolver']; +func.tags = ['L2ReverseRegistrar']; diff --git a/crosschain-reverse-resolver/hardhat.config.ts b/crosschain-reverse-resolver/hardhat.config.ts index 1989c071..a60283e7 100644 --- a/crosschain-reverse-resolver/hardhat.config.ts +++ b/crosschain-reverse-resolver/hardhat.config.ts @@ -7,7 +7,7 @@ const DEPLOYER_PRIVATE_KEY = process.env.DEPLOYER_PRIVATE_KEY ?? "ac0974bec39a17 const L1_PROVIDER_URL = process.env.L1_PROVIDER_URL || ''; const L1_ETHERSCAN_API_KEY = process.env.L1_ETHERSCAN_API_KEY || ''; const L2_ETHERSCAN_API_KEY = process.env.L2_ETHERSCAN_API_KEY || ''; - +console.log({L1_PROVIDER_URL,L1_ETHERSCAN_API_KEY,L2_ETHERSCAN_API_KEY}) const config: HardhatUserConfig = { solidity: '0.8.19', networks: { @@ -18,20 +18,23 @@ const config: HardhatUserConfig = { url: L1_PROVIDER_URL, accounts: [DEPLOYER_PRIVATE_KEY], deploy: [ "deploy_l1/" ], - companionNetworks: { - l2: "optimismGoerli", - }, }, optimismGoerli: { url: "https://goerli.optimism.io", accounts: [DEPLOYER_PRIVATE_KEY], deploy: [ "deploy_l2/" ], + }, + baseGoerli: { + url: "https://goerli.base.org", + accounts: [DEPLOYER_PRIVATE_KEY], + deploy: [ "deploy_l2/" ], } }, etherscan: { apiKey: { goerli: L1_ETHERSCAN_API_KEY, - optimismGoerli: L2_ETHERSCAN_API_KEY + optimismGoerli: L2_ETHERSCAN_API_KEY, + baseGoerli: L2_ETHERSCAN_API_KEY }, customChains: [ { @@ -41,6 +44,14 @@ const config: HardhatUserConfig = { apiURL: "https://api-goerli-optimism.etherscan.io/api", browserURL: "https://goerli-optimism.etherscan.io" } + }, + { + network: "baseGoerli", + chainId: 84531, + urls: { + browserURL: "https://goerli.basescan.org", + apiURL: "https://api-goerli.basescan.org/api", + } } ] }, diff --git a/crosschain-reverse-resolver/package.json b/crosschain-reverse-resolver/package.json index b11c3c87..cb67ff41 100644 --- a/crosschain-reverse-resolver/package.json +++ b/crosschain-reverse-resolver/package.json @@ -6,6 +6,8 @@ "build": "echo 'building crosschain-reverse-resolver' && hardhat compile", "test": "node scripts/test.js", "storage": "hardhat check", + "setname": "hardhat run scripts/setname.ts", + "getname": "hardhat run scripts/getname.ts --network goerli", "clean": "rm -fr artifacts cache node_modules typechain-types", "lint": "exit 0" }, diff --git a/crosschain-reverse-resolver/scripts/getname.ts b/crosschain-reverse-resolver/scripts/getname.ts new file mode 100644 index 00000000..ef5a2d4a --- /dev/null +++ b/crosschain-reverse-resolver/scripts/getname.ts @@ -0,0 +1,25 @@ +import hre from 'hardhat'; +import packet from 'dns-packet'; +const abi = ['function name(bytes32) view returns(string)']; +const encodeName = (name) => '0x' + packet.name.encode(name).toString('hex') +import {ethers} from 'ethers'; +export const main = async () => { + if (!process.env.REVERSE_NAMESPACE || !process.env.L1_PROVIDER_URL || !process.env.ETH_ADDRESS) + throw 'Set REVERSE_NAMESPACE, L1_PROVIDER_URL and ETH_ADDRESS'; + + const namespace = process.env.REVERSE_NAMESPACE; + const L1_PROVIDER_URL = process.env.L1_PROVIDER_URL; + const ETH_ADDRESS = process.env.ETH_ADDRESS; + const provider = new ethers.JsonRpcProvider(L1_PROVIDER_URL); + const name = ETH_ADDRESS.substring(2).toLowerCase() + "." + namespace + const encodedname = encodeName(name) + const reversenode = ethers.namehash(name) + + console.log({namespace, ETH_ADDRESS, name, encodedname,reversenode}) + const reverseresolver = await provider.getResolver(namespace); + console.log({reverseresolver}) + const l1resolver = new ethers.Contract(reverseresolver.address, abi, provider); + console.log(`Reverse node is set to `, await l1resolver.name(reversenode, {enableCcipRead:true})) +}; + +main(); \ No newline at end of file diff --git a/crosschain-reverse-resolver/scripts/setname.ts b/crosschain-reverse-resolver/scripts/setname.ts new file mode 100644 index 00000000..9dc386fe --- /dev/null +++ b/crosschain-reverse-resolver/scripts/setname.ts @@ -0,0 +1,32 @@ +import hre from 'hardhat'; +const ethers = hre.ethers; + +export const main = async () => { + const [signer] = await hre.ethers.getSigners(); + if (!process.env.REVERSE_NAMESPACE || !process.env.L2_PROVIDER_URL || !process.env.L2_REVERSE_REGISTRAR_ADDRESS || !process.env.ENS_NAME) + throw 'Set REVERSE_NAMESPACE, L2_PROVIDER_URL, L2_REVERSE_REGISTRAR_ADDRESS, and ENS_NAME'; + + const L2_PROVIDER_URL = process.env.L2_PROVIDER_URL; + const L2_REVERSE_REGISTRAR_ADDRESS = process.env.L2_REVERSE_REGISTRAR_ADDRESS; + const namespace = process.env.REVERSE_NAMESPACE; + const ENS_NAME = process.env.ENS_NAME; + const ETH_ADDRESS = signer.address + const name = ETH_ADDRESS.substring(2).toLowerCase() + "." + namespace + const reversenode = ethers.namehash(name) + + const L2ReverseRegistrarFactory = (await hre.ethers.getContractFactory("L2ReverseRegistrar")) as L2ReverseRegistrarFactory__factory; + const l2ReverseRegistrar = L2ReverseRegistrarFactory + .connect(signer) + .attach(L2_REVERSE_REGISTRAR_ADDRESS); + + console.log({ L2_REVERSE_REGISTRAR_ADDRESS, L2_PROVIDER_URL,ENS_NAME, ETH_ADDRESS, namespace, name, reversenode}) + const tx = await l2ReverseRegistrar.setName(ENS_NAME, { + gasPrice: "900000", + gasLimit: 500000, + }); + const rec = await tx.wait(); + console.log({txhash:rec.hash}); + console.log(await l2ReverseRegistrar.name(reversenode, {enableCcipRead:true})) +}; + +main(); \ No newline at end of file diff --git a/op-verifier/deploy_l2/01_testl2 .ts b/op-verifier/deploy_l1/01_testl2.ts similarity index 94% rename from op-verifier/deploy_l2/01_testl2 .ts rename to op-verifier/deploy_l1/01_testl2.ts index 369e8f47..f781c1b2 100644 --- a/op-verifier/deploy_l2/01_testl2 .ts +++ b/op-verifier/deploy_l1/01_testl2.ts @@ -14,4 +14,4 @@ const func: DeployFunction = async function (hre: HardhatRuntimeEnvironment) { }); }; export default func; -func.tags = ['TestL2']; +func.tags = ['TestL2']; \ No newline at end of file