Skip to content

Latest commit

 

History

History
238 lines (177 loc) · 9.65 KB

README.md

File metadata and controls

238 lines (177 loc) · 9.65 KB

@ensdomains/crosschain-resolver

A resolver contract that is built on top of evm-verifier.

For a detailed readme and usage instructions, see the monorepo readme.

How it is defined

When the resolver has the following storage layout,

 contract Resolver {
    // node => version
    mapping(bytes32 => uint64) public recordVersions;
    // versionable_addresses[recordVersions[node]][node][coinType]
    // version => node => cointype
    mapping(uint64 => mapping(bytes32 => mapping(uint256 => bytes))) versionable_addresses;

Run yarn storage to find out storage slot for each variable

// Storage slot
// ┌────────────────────────────┬──────────────────────────────┬──────────────┬
// │      contract              │        state_variable        │ storage_slot │ 
// ├────────────────────────────┼──────────────────────────────┼──────────────┼
// │    DelegatableResolver     │        recordVersions        │      0       │
// │    DelegatableResolver     │       versionable_abis       │      1       │
// │    DelegatableResolver     │    versionable_addresses     │      2       │

Then define the l1 function

    function addr(
        bytes32 node,
        uint256 coinType
    ) public view returns (bytes memory) {
        EVMFetcher.newFetchRequest(verifier, target)
            .getStatic(0)  // storage_slot of recordVersions
              .element(node)
            .getDynamic(2) // storage_slot of versionable_addresses
              .ref(0)        // Referencing the result of `.getStatic(0)`
              .element(node)
              .element(coinType)
            .fetch(this.addrCoinTypeCallback.selector, ''); // recordVersions

Storage verificaton can only verify the data of l2. When the function result needs some transformation, transform inside the callback function as follows.

    function addrCallback(
        bytes[] memory values,
        bytes memory
    ) public pure returns (address) {
        return bytesToAddress(values[1]);
    }

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
  • ENS_ADDRESS
  • WRAPPER_ADDRESS
bun run hardhat deploy --network optimismGoerli

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

L1

Base

L2

L1

Usage

Move resolver to L2

On L1

// 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,

// 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

[node, target] = l1resolver.getTarget(encodedname, 0)

Step 2: Deploy the registrar contract

Deploy DelegatableResolverRegistrar.sol 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)