From 9be51920aed43d96835e644ef60d24d05de86388 Mon Sep 17 00:00:00 2001 From: Makoto Inoue <2630+makoto@users.noreply.github.com> Date: Thu, 30 Nov 2023 17:54:20 +0300 Subject: [PATCH] Add support interface (#21) * Add support interface * Use ens-contracts IExtendedResolver * Add support interface to reverse registrar * Change test name * Update L1 deployment contract address for base and OP * Remove offset from getTarget * Inherit Interfaces * Inherit interfaces for ReverseResolver * Add ERC165 signature --- crosschain-resolver/README.md | 6 ++-- .../contracts/ITargetResolver.sol | 8 +++++ crosschain-resolver/contracts/L1Resolver.sol | 34 ++++++++++++++----- crosschain-resolver/scripts/getaddr.ts | 4 +-- crosschain-resolver/test/testResolver.ts | 14 +++++--- .../contracts/L1ReverseResolver.sol | 14 +++++++- .../test/testReverseResolver.ts | 5 +++ 7 files changed, 66 insertions(+), 19 deletions(-) create mode 100644 crosschain-resolver/contracts/ITargetResolver.sol diff --git a/crosschain-resolver/README.md b/crosschain-resolver/README.md index 39a317da..0d3bdcbb 100644 --- a/crosschain-resolver/README.md +++ b/crosschain-resolver/README.md @@ -110,7 +110,7 @@ const l2resolverAddress = await l2Factory.predictAddress(ETH_ADDRESS) - 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 +- L1Resolver = [0x7Bf57B0a683CC964B0fEe30633A72F5c05464a0f](https://goerli.etherscan.io/address/0x7Bf57B0a683CC964B0fEe30633A72F5c05464a0f) = Currently `op.evmgateway.eth` is set to the resolver ### Base @@ -121,7 +121,7 @@ const l2resolverAddress = await l2Factory.predictAddress(ETH_ADDRESS) - 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 +- L1Resolver = [0x3Ac25843A1F696fe2166C5dE127FD4f2832F4d42](https://goerli.etherscan.io/address/0x3Ac25843A1F696fe2166C5dE127FD4f2832F4d42) = Currently `base.evmgateway.eth` is set to the resolver ## Usage @@ -214,7 +214,7 @@ Once done, set addrss of the subname from the operator, wait 10~20 min, then que Go to [Base L1 resolver](https://goerli.etherscan.io/address/0x052D7E10D55Ae12b4F62cdE18dBb7E938efa230D#readContract) ``` -[node, target] = l1resolver.getTarget(encodedname, 0) +[node, target] = l1resolver.getTarget(encodedname) ``` #### Step 2: Deploy the registrar contract diff --git a/crosschain-resolver/contracts/ITargetResolver.sol b/crosschain-resolver/contracts/ITargetResolver.sol new file mode 100644 index 00000000..b7f0b797 --- /dev/null +++ b/crosschain-resolver/contracts/ITargetResolver.sol @@ -0,0 +1,8 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.17; + +interface ITargetResolver{ + function getTarget( + bytes memory name + ) external view returns (bytes32 node, address target); +} diff --git a/crosschain-resolver/contracts/L1Resolver.sol b/crosschain-resolver/contracts/L1Resolver.sol index d9cb25c3..4a1ba3bb 100644 --- a/crosschain-resolver/contracts/L1Resolver.sol +++ b/crosschain-resolver/contracts/L1Resolver.sol @@ -11,14 +11,16 @@ import {IAddrResolver} from "@ensdomains/ens-contracts/contracts/resolvers/profi import {IAddressResolver} from "@ensdomains/ens-contracts/contracts/resolvers/profiles/IAddressResolver.sol"; import {ITextResolver} from "@ensdomains/ens-contracts/contracts/resolvers/profiles/ITextResolver.sol"; import {IContentHashResolver} from "@ensdomains/ens-contracts/contracts/resolvers/profiles/IContentHashResolver.sol"; +import "@ensdomains/ens-contracts/contracts/resolvers/profiles/IExtendedResolver.sol"; +import {ITargetResolver} from './ITargetResolver.sol'; +import "@openzeppelin/contracts/utils/introspection/ERC165.sol"; - -contract L1Resolver is EVMFetchTarget { +contract L1Resolver is EVMFetchTarget, ITargetResolver, IExtendedResolver, ERC165 { using EVMFetcher for EVMFetcher.EVMFetchRequest; using BytesUtils for bytes; - IEVMVerifier immutable verifier; - ENS immutable ens; - INameWrapper immutable nameWrapper; + IEVMVerifier public immutable verifier; + ENS public immutable ens; + INameWrapper public immutable nameWrapper; mapping(bytes32 => address) targets; uint256 constant COIN_TYPE_ETH = 60; uint256 constant RECORD_VERSIONS_SLOT = 0; @@ -72,19 +74,24 @@ contract L1Resolver is EVMFetchTarget { /** * @dev Returns the L2 target address that can answer queries for `name`. * @param name DNS encoded ENS name to query - * @param offset The offset of the label to query recursively. * @return node The node of the name * @return target The L2 resolver address to verify against. */ function getTarget( + bytes memory name + ) public view returns (bytes32 node, address target) { + return _getTarget(name, 0); + } + + function _getTarget( bytes memory name, uint256 offset - ) public view returns (bytes32 node, address target) { + ) private view returns (bytes32 node, address target) { uint256 len = name.readUint8(offset); node = bytes32(0); if (len > 0) { bytes32 label = name.keccak(offset + 1, len); - (node, target) = getTarget( + (node, target) = _getTarget( name, offset + len + 1 ); @@ -111,7 +118,7 @@ contract L1Resolver is EVMFetchTarget { * @return result result of the call */ function resolve(bytes calldata name, bytes calldata data) external view returns (bytes memory result) { - (, address target) = getTarget(name, 0); + (, address target) = _getTarget(name, 0); bytes4 selector = bytes4(data); if (selector == IAddrResolver.addr.selector) { @@ -210,4 +217,13 @@ contract L1Resolver is EVMFetchTarget { ) public pure returns (bytes memory) { return abi.encode(values[1]); } + + function supportsInterface( + bytes4 interfaceId + ) public override view returns (bool) { + return + interfaceId == type(IExtendedResolver).interfaceId || + interfaceId == type(ITargetResolver).interfaceId || + super.supportsInterface(interfaceId); + } } diff --git a/crosschain-resolver/scripts/getaddr.ts b/crosschain-resolver/scripts/getaddr.ts index a018ba65..fd0008d6 100644 --- a/crosschain-resolver/scripts/getaddr.ts +++ b/crosschain-resolver/scripts/getaddr.ts @@ -1,7 +1,7 @@ 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 getTarget(bytes) view returns (bytes32, address)", "function addr(bytes32) view returns (address)", "function resolve(bytes,bytes) view returns (bytes)", ] @@ -32,7 +32,7 @@ export const main = async () => { } console.log({ENS_NAME, resolver, encodedname, node}) const l1resolver = new ethers.Contract(resolver.address, l1abi, l1provider); - const target = await l1resolver.getTarget(encodedname, 0) + const target = await l1resolver.getTarget(encodedname) const l2resolverAddress = target[1] console.log('Target is set to ' + l2resolverAddress); const l2resolver = new ethers.Contract(l2resolverAddress, l2abi, l2provider); diff --git a/crosschain-resolver/test/testResolver.ts b/crosschain-resolver/test/testResolver.ts index d3f7d443..ece5e399 100644 --- a/crosschain-resolver/test/testResolver.ts +++ b/crosschain-resolver/test/testResolver.ts @@ -159,13 +159,13 @@ describe('Crosschain Resolver', () => { }catch(e){ } - const result = await target.getTarget(incorrectname, 0) + const result = await target.getTarget(incorrectname) expect(result[1]).to.equal(EMPTY_ADDRESS); }) it("should allow owner to set target", async() => { await target.setTarget(node, signerAddress) - const result = await target.getTarget(encodeName(name), 0) + const result = await target.getTarget(encodeName(name)) expect(result[1]).to.equal(signerAddress); }) @@ -174,7 +174,7 @@ describe('Crosschain Resolver', () => { const encodedsubname = encodeName(subname) const subnode = ethers.namehash(subname) await target.setTarget(node, signerAddress) - const result = await target.getTarget(encodedsubname, 0) + const result = await target.getTarget(encodedsubname) expect(result[0]).to.equal(subnode); expect(result[1]).to.equal(signerAddress); }) @@ -193,7 +193,7 @@ describe('Crosschain Resolver', () => { const wrappedtnode = ethers.namehash(`${label}.eth`) await target.setTarget(wrappedtnode, resolverAddress) const encodedname = encodeName(`${label}.eth`) - const result = await target.getTarget(encodedname, 0) + const result = await target.getTarget(encodedname) expect(result[1]).to.equal(resolverAddress); }) @@ -290,4 +290,10 @@ describe('Crosschain Resolver', () => { const decoded = i.decodeFunctionResult("contenthash", result2) expect(decoded[0]).to.equal(contenthash); }) + + it("should support interface", async() => { + expect(await target.supportsInterface('0x15f64386')).to.equal(true) // ITargetResolver + expect(await target.supportsInterface('0x9061b923')).to.equal(true) // IExtendedResolver + expect(await target.supportsInterface('0x01ffc9a7')).to.equal(true) // ERC-165 support + }) }); \ No newline at end of file diff --git a/crosschain-reverse-resolver/contracts/L1ReverseResolver.sol b/crosschain-reverse-resolver/contracts/L1ReverseResolver.sol index 7f038a30..c5cdf910 100644 --- a/crosschain-reverse-resolver/contracts/L1ReverseResolver.sol +++ b/crosschain-reverse-resolver/contracts/L1ReverseResolver.sol @@ -4,8 +4,11 @@ pragma solidity ^0.8.17; import {EVMFetcher} from '@ensdomains/evm-verifier/contracts/EVMFetcher.sol'; import {EVMFetchTarget} from '@ensdomains/evm-verifier/contracts/EVMFetchTarget.sol'; import {IEVMVerifier} from '@ensdomains/evm-verifier/contracts/IEVMVerifier.sol'; +import "@ensdomains/ens-contracts/contracts/resolvers/profiles/INameResolver.sol"; +import "@ensdomains/ens-contracts/contracts/resolvers/profiles/ITextResolver.sol"; +import "@openzeppelin/contracts/utils/introspection/ERC165.sol"; -contract L1ReverseResolver is EVMFetchTarget { +contract L1ReverseResolver is EVMFetchTarget, INameResolver, ITextResolver, ERC165 { using EVMFetcher for EVMFetcher.EVMFetchRequest; IEVMVerifier immutable verifier; address immutable target; @@ -67,4 +70,13 @@ contract L1ReverseResolver is EVMFetchTarget { ) public pure returns (string memory) { return string(values[1]); } + + function supportsInterface( + bytes4 interfaceId + ) public override view returns (bool) { + return + interfaceId == type(ITextResolver).interfaceId || + interfaceId == type(INameResolver).interfaceId || + super.supportsInterface(interfaceId); + } } diff --git a/crosschain-reverse-resolver/test/testReverseResolver.ts b/crosschain-reverse-resolver/test/testReverseResolver.ts index 9c362bb3..146f13c9 100644 --- a/crosschain-reverse-resolver/test/testReverseResolver.ts +++ b/crosschain-reverse-resolver/test/testReverseResolver.ts @@ -114,4 +114,9 @@ describe('Crosschain Reverse Resolver', () => { expect(result2).to.equal(value); }) + it("should support interface", async() => { + expect(await target.supportsInterface('0x59d1d43c')).to.equal(true) // ITextResolver + expect(await target.supportsInterface('0x691f3431')).to.equal(true) // INameResolver + expect(await target.supportsInterface('0x01ffc9a7')).to.equal(true) // ERC-165 support + }) });