Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Eip 5559 support #34

Merged
merged 13 commits into from
Apr 11, 2024
7 changes: 7 additions & 0 deletions crosschain-resolver/contracts/IAddrSetter.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.17;

interface IAddrSetter{
function setAddr(bytes calldata name, address _addr) external view returns (bytes memory result);
}

16 changes: 2 additions & 14 deletions crosschain-resolver/contracts/IMetadataResolver.sol
Original file line number Diff line number Diff line change
Expand Up @@ -6,27 +6,15 @@ interface IMetadataResolver {
* @notice Get metadata about the CCIP Resolver ENSIP 16 https://docs.ens.domains/ens-improvement-proposals/ensip-16-offchain-metadata
* @dev This function provides metadata about the CCIP Resolver, including its name, coin type, GraphQL URL, storage type, and encoded information.
* @param name The domain name in format (dnsEncoded)
* @return coinType The cointype of the chain the target contract locates such as Optimism, Base, Arb, etc
* @return graphqlUrl The GraphQL URL used by the resolver
* @return storageType 0 = EVM, 1 = Non blockchain, 2 = Starknet
* @return storageLocation The storage identifier. For EVM chains, this is the address of the resolver contract.
* @return context. An identifier used by l2 graph indexer for Domain schema id (`context-namehash`) allowing multiple resolver contracts to have own namespace.
*
*/
function metadata(bytes calldata name) external view returns (
uint256 coinType,
string memory graphqlUrl,
uint8 storageType,
bytes memory storageLocation,
bytes memory context
string memory graphqlUrl
);

event MetadataChanged(
bytes name,
uint256 coinType,
string graphqlUrl,
uint8 storageType,
bytes storageLocation,
bytes context
string graphqlUrl
);
}
66 changes: 38 additions & 28 deletions crosschain-resolver/contracts/L1Resolver.sol
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,9 @@ import "@ensdomains/ens-contracts/contracts/resolvers/profiles/IExtendedResolver
import {ITargetResolver} from './ITargetResolver.sol';
import {IMetadataResolver} from './IMetadataResolver.sol';
import "@openzeppelin/contracts/utils/introspection/ERC165.sol";
import { IAddrSetter } from './IAddrSetter.sol';

contract L1Resolver is EVMFetchTarget, ITargetResolver, IMetadataResolver, IExtendedResolver, ERC165 {
contract L1Resolver is EVMFetchTarget, ITargetResolver, IMetadataResolver, IExtendedResolver, IAddrSetter, ERC165 {
using EVMFetcher for EVMFetcher.EVMFetchRequest;
using BytesUtils for bytes;
IEVMVerifier public immutable verifier;
Expand All @@ -29,7 +30,7 @@ contract L1Resolver is EVMFetchTarget, ITargetResolver, IMetadataResolver, IExte
uint256 constant VERSIONABLE_HASHES_SLOT = 3;
uint256 constant VERSIONABLE_TEXTS_SLOT = 10;
string public graphqlUrl;
uint256 public l2ResolverCoinType;
uint256 public l2ChainId;

event TargetSet(bytes name, address target);
function isAuthorised(bytes32 node) internal view returns (bool) {
Expand All @@ -45,19 +46,29 @@ contract L1Resolver is EVMFetchTarget, ITargetResolver, IMetadataResolver, IExte
return owner == msg.sender;
}

/**
* @dev EIP-5559 - Error to raise when mutations are being deferred to an L2.
* @param chainId Chain ID to perform the deferred mutation to.
* @param contractAddress Contract Address at which the deferred mutation should transact with.
*/
error StorageHandledByL2(
uint256 chainId,
address contractAddress
);

/**
* @param _verifier The chain verifier address
* @param _ens The ENS registry address
* @param _nameWrapper The ENS name wrapper address
* @param _graphqlUrl The offchain/l2 graphql endpoint url
* @param _l2ResolverCoinType The chainId at which the resolver resolves data from. 0 if storageLocation is offChain
* @param _l2ChainId The chainId at which the resolver resolves data from
*/
constructor(
IEVMVerifier _verifier,
ENS _ens,
INameWrapper _nameWrapper,
string memory _graphqlUrl,
uint256 _l2ResolverCoinType
uint256 _l2ChainId
){
require(address(_nameWrapper) != address(0), "Name Wrapper address must be set");
require(address(_verifier) != address(0), "Verifier address must be set");
Expand All @@ -66,7 +77,7 @@ contract L1Resolver is EVMFetchTarget, ITargetResolver, IMetadataResolver, IExte
ens = _ens;
nameWrapper = _nameWrapper;
graphqlUrl = _graphqlUrl;
l2ResolverCoinType = _l2ResolverCoinType;
l2ChainId = _l2ChainId;
}

/**
Expand All @@ -79,19 +90,9 @@ contract L1Resolver is EVMFetchTarget, ITargetResolver, IMetadataResolver, IExte
require(isAuthorised(node));
targets[node] = target;
emit TargetSet(name, target);
(
,,
uint8 storageType,
bytes memory storageLocation,
bytes memory context
) = metadata(name);
emit MetadataChanged(
name,
l2ResolverCoinType,
graphqlUrl,
storageType,
storageLocation,
context
graphqlUrl
);
}

Expand Down Expand Up @@ -163,6 +164,17 @@ contract L1Resolver is EVMFetchTarget, ITargetResolver, IMetadataResolver, IExte
}
}

/**
* @dev Resolve and throws an EIP 3559 compliant error
* @param name DNS encoded ENS name to query
* @param _addr The actual calldata
* @return result result of the call
*/
function setAddr(bytes calldata name, address _addr) external view returns (bytes memory result) {
(, address target) = _getTarget(name, 0);
_writeDeferral(target);
}

function _addr(bytes32 node, address target) private view returns (bytes memory) {
EVMFetcher.newFetchRequest(verifier, target)
.getStatic(RECORD_VERSIONS_SLOT)
Expand Down Expand Up @@ -246,23 +258,13 @@ contract L1Resolver is EVMFetchTarget, ITargetResolver, IMetadataResolver, IExte
* @notice Get metadata about the L1 Resolver
* @dev This function provides metadata about the L1 Resolver, including its name, coin type, GraphQL URL, storage type, and encoded information.
* @param name The domain name in format (dnsEncoded)
* @return coinType The cointype of the chain the target contract locates such as Optimism, Base, Arb, etc
* @return graphqlUrl The GraphQL URL used by the resolver
* @return storageType Storage Type (0 for EVM)
* @return storageLocation The storage identifier. For EVM chains, this is the address of the resolver contract.
* @return context. An identifier used by l2 graph indexer for Domain schema id (`context-namehash`) allowing multiple resolver contracts to have own namespace.
*/
function metadata(
bytes calldata name
) public view returns (uint256, string memory, uint8, bytes memory, bytes memory) {
(, address target) = getTarget(name);

) public view returns (string memory) {
return (
l2ResolverCoinType,
graphqlUrl,
uint8(0), // storage Type 0 => EVM
abi.encodePacked(address(target)), // storage location => l2 resolver address
abi.encodePacked(address(target)) // context => l2 resolver address
graphqlUrl
);
}

Expand All @@ -273,6 +275,14 @@ contract L1Resolver is EVMFetchTarget, ITargetResolver, IMetadataResolver, IExte
interfaceId == type(IExtendedResolver).interfaceId ||
interfaceId == type(ITargetResolver).interfaceId ||
interfaceId == type(IMetadataResolver).interfaceId ||
interfaceId == type(IAddrSetter).interfaceId ||
super.supportsInterface(interfaceId);
}

function _writeDeferral(address target) internal view {
revert StorageHandledByL2(
l2ChainId,
target
);
}
}
5 changes: 2 additions & 3 deletions crosschain-resolver/deploy_l1/10_l1resolver.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,12 +22,11 @@ const func: DeployFunction = async function (hre: HardhatRuntimeEnvironment) {
const CHAIN_NAME = process.env.CHAIN_NAME

if(!['Op', 'Base', 'Arb'].includes(CHAIN_NAME)) throw ('Set $CHAIN_NAME to Op, Base, or Arb')
const L2_COINTYPE = convertEVMChainIdToCoinType(parseInt(L2_CHAIN_ID))
console.log({VERIFIER_ADDRESS,ENS_ADDRESS, WRAPPER_ADDRESS,L2_GRAPHQL_URL,L2_COINTYPE})
console.log({VERIFIER_ADDRESS,ENS_ADDRESS, WRAPPER_ADDRESS,L2_GRAPHQL_URL,L2_CHAIN_ID})
await deploy(`${CHAIN_NAME}L1Resolver`, {
from: deployer,
contract: 'L1Resolver',
args: [VERIFIER_ADDRESS,ENS_ADDRESS,WRAPPER_ADDRESS,L2_GRAPHQL_URL,L2_COINTYPE],
args: [VERIFIER_ADDRESS,ENS_ADDRESS,WRAPPER_ADDRESS,L2_GRAPHQL_URL,parseInt(L2_CHAIN_ID)],
log: true,
});
};
Expand Down
32 changes: 16 additions & 16 deletions crosschain-resolver/test/testResolver.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,12 +15,12 @@ import { ethers } from 'hardhat';
import { EthereumProvider } from 'hardhat/types';
import request from 'supertest';
import packet from 'dns-packet';
import {convertEVMChainIdToCoinType} from '@ensdomains/address-encoder'
import { L2ChainID } from '@eth-optimism/sdk';
const labelhash = (label) => ethers.keccak256(ethers.toUtf8Bytes(label))
const encodeName = (name) => '0x' + packet.name.encode(name).toString('hex')

const l2graphqlUrl = 'http://graphql'
const l2ResolverCoinType = convertEVMChainIdToCoinType(420) // Optimism Goerli
const chainId = L2ChainID.OPTIMISM_SEPOLIA

const name = 'foo.eth'
const node = ethers.namehash(name)
Expand Down Expand Up @@ -154,7 +154,7 @@ describe('Crosschain Resolver', () => {
signer
);
const verifierAddress = await verifier.getAddress()
target = await testL1Factory.deploy(verifierAddress, ensAddress, wrapperAddress, l2graphqlUrl, l2ResolverCoinType);
target = await testL1Factory.deploy(verifierAddress, ensAddress, wrapperAddress, l2graphqlUrl, chainId);

// Mine an empty block so we have something to prove against
await provider.send('evm_mine', []);
Expand Down Expand Up @@ -268,6 +268,7 @@ describe('Crosschain Resolver', () => {
})

it("should resolve non ETH Address", async() => {

await target.setTarget(encodedname, resolverAddress)
const addr = '0x76a91462e907b15cbf27d5425399ebf6f0fb50ebb88f1888ac'
const coinType = 0 // BTC
Expand Down Expand Up @@ -316,31 +317,30 @@ describe('Crosschain Resolver', () => {
expect(await target.supportsInterface('0x9061b923')).to.equal(true) // IExtendedResolver
expect(await target.supportsInterface('0x8a596ebe')).to.equal(true) // IMetadataResolver
expect(await target.supportsInterface('0x01ffc9a7')).to.equal(true) // ERC-165 support
expect(await target.supportsInterface('0xf00eebf4')).to.equal(true) // IAddrSetter support
})

describe('EIP 5559', () => {
it('throws StorageHandledByL2 error', async () => {
await target.setTarget(encodedname, resolverAddress)
await expect(target.setAddr(encodedname, EMPTY_ADDRESS)).to.be
.revertedWithCustomError(target, 'StorageHandledByL2')
.withArgs(chainId, resolverAddress)
});
});

describe('Metadata', () => {
it('returns metadata', async () => {
await target.setTarget(encodedname, signerAddress)

const [coinType, graphqlUrl, storageType, storageLocation, context] = await target.metadata(encodedname);
expect(parseInt(coinType)).to.equal(l2ResolverCoinType);
expect(graphqlUrl).to.equal(l2graphqlUrl);
expect(storageType).to.equal(storageType);
expect(ethers.getAddress(storageLocation)).to.equal(signerAddress);
expect(ethers.getAddress(context)).to.equal(signerAddress);
expect(await target.metadata(encodedname)).to.equal(l2graphqlUrl);
});

it('emits a MetadataChanged event', async () => {
const tx = await target.setTarget(encodedname, signerAddress)
await tx.wait()
const logs = await target.queryFilter("MetadataChanged")
const [name, coinType, graphqlUrl, storageType, storageLocation, context] = logs[0].args
const [name, graphqlUrl] = logs[0].args
expect(name).to.equal(encodedname);
expect(parseInt(coinType)).to.equal(l2ResolverCoinType);
expect(graphqlUrl).to.equal(l2graphqlUrl);
expect(storageType).to.equal(storageType);
expect(ethers.getAddress(storageLocation)).to.equal(signerAddress);
expect(ethers.getAddress(context)).to.equal(signerAddress);
});
});
});
Loading