Skip to content

Commit

Permalink
Eip 5559 support (#34)
Browse files Browse the repository at this point in the history
* Add IResolverSetter

* Simplify metadata function

* Fix failing tests

* Add test for EIP 5559

* Added wait

* Rename from IResolverSetter to IAddrSetter

* Add resolveDeferral

* Store chainId directly

* Rename from resolveDeferral to setAddr
  • Loading branch information
makoto authored Apr 11, 2024
1 parent 75cb80c commit fe7df7c
Show file tree
Hide file tree
Showing 5 changed files with 65 additions and 61 deletions.
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);
});
});
});

0 comments on commit fe7df7c

Please sign in to comment.