Skip to content

Commit

Permalink
Crosschain resolver with reverse registrar with deploy script (#18)
Browse files Browse the repository at this point in the history
* Update deploy scripts

* Remove console.log

* Remove blank space from the file

* Add deployment script

* Update deployment readme

* Add getname

* Add setname

* Add setup script

* Add getaddr

* Add approve

* Fix wildcard for subname

* Update bun

* Deploy l2 reverse registrar to base goerli

* Add base deployment

* Added instruction on subname registrar

* Added instruction on how to register names
  • Loading branch information
makoto committed Nov 27, 2023
1 parent e142a7b commit c52f671
Show file tree
Hide file tree
Showing 20 changed files with 532 additions and 39 deletions.
Binary file modified bun.lockb
Binary file not shown.
160 changes: 156 additions & 4 deletions crosschain-resolver/README.md
Expand Up @@ -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
```
Expand All @@ -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)
```
12 changes: 8 additions & 4 deletions crosschain-resolver/deploy_l1/10_l1resolver.ts
Expand Up @@ -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,
});
};
Expand Down
8 changes: 0 additions & 8 deletions crosschain-resolver/deploy_l2/01_l2resolver.ts
Expand Up @@ -6,21 +6,13 @@ 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: [],
log: true,
});
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'];
6 changes: 3 additions & 3 deletions crosschain-resolver/deploy_l2/02_l2resolver_factory.ts
Expand Up @@ -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],
Expand Down
21 changes: 16 additions & 5 deletions crosschain-resolver/hardhat.config.ts
Expand Up @@ -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",
Expand All @@ -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: [
{
Expand All @@ -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",
}
}
]
},
Expand Down
4 changes: 4 additions & 0 deletions crosschain-resolver/package.json
Expand Up @@ -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": {
Expand Down
35 changes: 35 additions & 0 deletions 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();
51 changes: 51 additions & 0 deletions 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();

0 comments on commit c52f671

Please sign in to comment.