Giter Site home page Giter Site logo

blockful-io / external-resolver Goto Github PK

View Code? Open in Web Editor NEW
7.0 7.0 2.0 2.66 MB

This project aims to scale the Ethereum Name Service (ENS) by consolidating existing patterns and proofs of concept into a unified and production-ready codebase.

License: MIT License

TypeScript 56.52% Solidity 42.73% JavaScript 0.02% Dockerfile 0.73%
arbitrum ens ethereum solidity typeorm viem

external-resolver's Issues

Integration tests (core)

Feature Request

Implement an integrated test for the project, aiming to integrate the existing components: Client, Gateway, Registry Contract on layer 1 (L1), and the Offchain-Resolver Contract, also from layer 1. This test is designed to establish the complete flow of interactions between these components, serving as a solid foundation for future integrated tests.

Describe Preferred Solution

Develop a comprehensive integrated test script, preferably written in TypeScript, that will act as a client and perform the following actions:

  • Start the server (gateway).
  • Deploy the contracts (registry and resolver).
  • Make a call to the registry and receive the callback with the resolver's address.
  • Make a call to the resolver and receive the callback with the offchain address of the gateway.
  • Make a call to the gateway.

This script should effectively simulate the end-to-end flow of interactions between the specified components, validating their integration and ensuring a comprehensive understanding of the system's functionality.

Related Code

  • External Resolver Repository: This repository contains the structure of the Resolver that should be utilized for integration.
  • Example E2E Test: Here, you can find an example of an end-to-end test using the specified contract. This can serve as a reference for implementing the integrated test in your project.
  • EVM Gateway Repository: This repository contains the implementation of a gateway and some tests that can serve as inspiration for your project.

Implement Client logic for offchain writing on layer 2

Feature Request

The client should be able to call the writing methods of a contract deployed on a layer 2 protocol to be used as an external source which will be used on the redirecting phase of the L1 flow.

sequenceDiagram
  Client->>Registry :resolver("test.eth")
  Registry->>Client :0x....
  Client->>Resolver :text("test.eth", "avatar", "avatar.png")
  Resolver->>Client :revert `StorageHandledByL2(chainId, contractAddress)`
  Client->> L2 Contract :text("test.eth", "avatar", "avatar.png")

Describe Preferred Solution

The client should be able to:

  1. Call any writing method on the L1 contract
  2. Handle the L1's StorageHandledByL2 revert
  3. Call the given method on the L2 contract

Related Code

Public Resolver contract implementation
Offchain Resolving
Address reading implementation
Read data validation

Additional context

No implementation of the CCIP-Read on Layer 2 has been found so far. The most popular JS libraries (Viem and Ethers) currently only support the Gateway reading.

No validation is required on the writing phase according to EIP-5559.

Implement the monorepo structure

Feature Request

Describe the Feature Request

We need to have a way to coordinate multiple entities of the project in a smooth way, which means being able to deploy them locally, test and improve each component seamlessly.

Describe Preferred Solution

Create a monorepo by using Turborepo where each different entity is a subpackage which their own set of dependencies and scripts.

We should be able to perform the following tasks seamlessly:

  • Build all packages
  • Run tests on all packages
  • Run tests on a specific package

Describe Alternatives

  1. to have different repositories for each different entity (contracts, client, gateway)
  2. to find an alternative for Turborepo such as yarn workspaces

[DB] Implement `transfer` function on gateway for transferring subdomains ownership

Feature Request

The user should be able to transfer its domains to any given address as long as the ownership is validated.

Describe Preferred Solution

The function signature looks like this:

function transfer(signature: string, address: string, domain: string) : Promise<void>;

This function follows the ERC-712 NFT standard and the underlying implementation should change the domain's owner address to the given one.

The signature is required to validate the ownership of the address before transferring it.

[DB] Implement `mint` function on gateway for creating new subdomains

Feature Request

Implement the mint function that enables the creation of new subdomains stored off-chain.

Describe Preferred Solution

The function signature looks like this:

function mint(address: string, node: string) : Promise<void>;

It follows the ERC-712 NFT standard. The underlying implementation should create a new domain on the domains table with the given address as owner.

Implement Offchain Resolver

Feature Request

Currently, our contracts consist in the Public Resolver and Registry. However, to enable the redirection of users to an offchain address, it is imperative to implement the Offchain Resolver contract. The Offchain Resolver will allow offchain interactions what is very important to our project's goal.

Describe Preferred Solution

The preferred solution involves implementing the Offchain Resolver contracts, as outlined in the ensdomains/offchain-resolver repository. These contracts are designed to provide offchain resolution of ENS names and are in compliance with ENSIP 10 (wildcard resolution support) and EIP 3668 (CCIP Read).

  • Deploy and Offchain Resolver.
  • Test the contracts by getting an offchain url as a call response.

Related Code

  • Offchain Resolver Contracts: In this directory, you will find the Solidity contracts defining the Offchain Resolver. These contracts include IExtendedResolver.sol, SignatureVerifier.sol, and OffchainResolver.sol.

  • TestOffchainResolver.js: This JavaScript file contains a test suite for the Offchain Resolver. Reviewing this test can provide valuable insights into how the resolver contracts are intended to be used and tested.

Gateway sign read data (Part.2)

The gateway should sign messages read from any external datasource to prove its authenticity on the smart OffchainResolver smart contract.

  • It signs the read data with its private key so the contract can prove that it came from the right place

Integration tests for writing (Database)

Feature Request

After implementing the write information on a Database feature, it will be necessary to create some integration tests. The tests will be responsibly to ensure that the flow of interaction between each part of the project is working properly and test bad scenarios to see how the code handles it.

Describe Preferred Solution

To complete this issue, it is be necessary the following tests:

  • Main flow of writing information on the Database as specified in the image at the additional context;
  • Contract's address not exist;
  • Wrong calls in the resolver contract;

Additional Context

The right expected flow of interactions is the following one:

Captura de Tela 2024-02-23 às 15 20 39

Onchain writing on layer 1

Feature Request

We should be able to write data from the native layer 1 ENS solution. This will be the foundation for the future offchain implementations.

Describe Preferred Solution

The client needs to be able to call the Registry for getting the given domain's Resolver for then to set properties stored onchain.

sequenceDiagram
    Client->>Registry: resolver("test.eth")
    Registry->>Client: L1 Contract address
    Client->>L1 Contract: setText("test.eth", "key", "value")
import { createWalletClient, http } from 'viem'
import { mainnet } from 'viem/chains'
 
const client = createWalletClient({
  chain: mainnet,
  transport: http()
})

const hash = await walletClient.sendTransaction({ 
  account,
  to: '0x70997970c51812dc3a010c7d01b50e0d17dc79c8',
  value: 1000000000000000000n
})

Additional Context

The writing isn't yet supported by the most popular ENS SDK's, therefore it will require the requests to be made using the JSON-RPC API

Blocked by #11

Implement resolver writing methods on Gateway

Feature Request

Describe the Feature Request

As specified by the ERC-5559, the gateway should implement the methods we plan to support (such as setAddr(bytes32 node, address addr) and setText(bytes32 node, string calldata key, string calldata value)).

Describe Preferred Solution

The gateway must be able to receive writing functions as stablished by the ENS documentation by following the CCIP-Read-Server implementation. These are the arguments provided by the L1 smart contract when a writing call is supposed to be handled offchain (see EIP-5559):

  • sender: the address of the smart contract that redirected the request to the gateway
  • data: the calldata containing (see EIP-5559)
    • contract version

       /**
       * @notice Struct used to define the domain of the typed data signature, defined in EIP-712.
       * @param name The user friendly name of the contract that the signature corresponds to.
       * @param version The version of domain object being used.
       * @param chainId The ID of the chain that the signature corresponds to (ie Ethereum mainnet: 1, Goerli testnet: 5, ...). 
       * @param verifyingContract The address of the contract that the signature pertains to.
       */
       struct domainData {
           string name;
           string version;
           uint64 chainId;
           address verifyingContract;
       }   
    • url: Gateway's URL (e.g.: https://localhost:8080/{sender}/{data}.json

    • sender

        /**
        * @notice Struct used to define the message context used to construct a typed data signature, defined in EIP-712, 
        * to authorize and define the deferred mutation being performed.
        * @param functionSelector The function selector of the corresponding mutation.
        * @param sender The address of the user performing the mutation (msg.sender).
        * @param parameter[] A list of <key, value> pairs defining the inputs used to perform the deferred mutation.
        */
        struct messageData {
            bytes4 functionSelector;
            address sender;
            parameter[] parameters;
            uint256 expirationTimestamp;
        }
      
        /**
        * @notice Struct used to define a parameter for Off-Chain Database Handler deferral.
        * @param name The variable name of the parameter.
        * @param value The string encoded value representation of the parameter.
        */
        struct parameter {
            string name;
            string value;
        }

Related Code

Additional Context

No writing gateway implementation was found so far.

Architecture diagram:
Image

[L2] Create contract for Arbitrum reading (Part.2)

Feature Request

We need to have an smart contract deployed on any given layer 2 protocol to be used as an external source which will be used on the redirecting phase of the L1 flow.

sequenceDiagram
  Client->>Registry :resolver("test.eth")
  Registry->>Client :0x....
  Client->>Resolver :setText("test.eth", "avatar", "avatar.png")
  Resolver->>Client :revert `StorageHandledByL2(chainId, contractAddress)`
  Client->>⭐ L2 Contract :setText("test.eth", "avatar", "avatar.png")

Describe Preferred Solution

The L2 contract should behave exactly the same way as Public Resolver would in the Layer 1.

Address reading implementation

/**
    * Returns the address associated with an ENS node.
    * @param node The ENS node to query.
    * @return The associated address.
    */
function addr(
    bytes32 node
) public view virtual override returns (address payable) {
    bytes memory a = addr(node, COIN_TYPE_ETH);
    if (a.length == 0) {
        return payable(0);
    }
    return bytesToAddress(a);
}

Checklist

  • Deploy offchain resolver contract on the Layer 2.

Related Code

Public Resolver contract implementation
Offchain Resolving
Address reading implementation

Additional Context

After being deployed to the given layer 2, the contract address should be stored in the layer 1 contract in order to redirect the requests by using StorageHandledByL2(chainID, contractAddress).

Implement Husky git hooks

Feature Request

Automate usual tasks to enforce repo standards.

Describe Preferred Solution

Use Husky to implement git hooks.
Some useful tasks to be run on a git hook:

  • Run tests on pre-push
  • Looks for leaked private keys on changed files
  • Generate commit message based on changed files
  • Run linter

Describe Alternatives

Rely only on Github Actions.

Plan workshop structure and content

Feature Request

Plan ENS workshop that will be done in ETHSamba at march 22 to 24. The goal of the workshop is to onboard new developers to the ENS.

Describe Preferred Solution

To achieve the our goal, we will talk about the following subjects related to the ENS.

Theoric: #45

  • Introduction to Ethereum addresses and concepts
  • What's ENS, and what problem is it solving? Who is using it?
  • ENS and DNS
  • How to buy a domain? Why are there 2 transactions?
  • Explain primary name, subdomains, records
  • Contracts architecture (controller, registry, resolver, off-chain resolver)

Practical: #43

  • Buy domain in testnet.
  • Deploy public resolver // Deploy offchainResolver
  • Explain what we are doing and how can they contribute.

Additional Context

[DB] Implement Layer 1 contract that redirects to a Gateway

Feature Request

Describe the Feature Request

In order to support offchain writing, the layer 1 smart contract should redirect the writing call to an external URL by relying on the StorageHandleByOffchainDatabase error as defined in EIP-5559.

Describe Preferred Solution

The highlighted step is responsible for informing the client that the storage of the given ENS entity is held by an off-chain data source (a.k.a. Gateway).

┌──────┐                                           ┌───────────┐  ┌────────────────────┐
│Client│                                           │L1 Contract│  │ Off-Chain Database │
└──┬───┘                                           └─────┬─────┘  └──────────┬─────────┘
   │                                                     │                   │ 
   │ somefunc(...)                                       │                   │ 
   ├────────────────────────────────────────────────────►│                   │ 
   │                                                     │                   │ 
   │ ⭐ revert StorageHandledByOffChainDatabase(sender,  |                   │ 
   │                               urls, requestParams)  │                   │ 
   │◄────────────────────────────────────────────────────┤                   │ 
   │                                                     │                   │ 
   │ HTTP Request [requestParams, signature]             │                   │ 
   ├─────────────────────────────────────────────────────┼──────────────────►│ 
   │                                                     │                   │ 
   │ response                                            │                   │ 
   │◄────────────────────────────────────────────────────┼───────────────────┤ 
   │                                                     │                   │ 

The redirecting is done by reverting the contract call with the arguments specified by EIP-5559. In order to have access to such entities within the layer 1, the contract should implement the IWriteDeferral interface defined in here.

  • sender: the address of the smart contract that redirected the request to the gateway
  • data: the calldata
    • contract version

       /**
       * @notice Struct used to define the domain of the typed data signature, defined in EIP-712.
       * @param name The user friendly name of the contract that the signature corresponds to.
       * @param version The version of domain object being used.
       * @param chainId The ID of the chain that the signature corresponds to (ie Ethereum mainnet: 1, Goerli testnet: 5, ...). 
       * @param verifyingContract The address of the contract that the signature pertains to.
       */
       struct domainData {
           string name;
           string version;
           uint64 chainId;
           address verifyingContract;
       }   
    • url: Gateway's URL (e.g.: https://localhost:8080/{sender}/{data}.json

    • sender

        /**
        * @notice Struct used to define the message context used to construct a typed data signature, defined in EIP-712, 
        * to authorize and define the deferred mutation being performed.
        * @param functionSelector The function selector of the corresponding mutation.
        * @param sender The address of the user performing the mutation (msg.sender).
        * @param parameter[] A list of <key, value> pairs defining the inputs used to perform the deferred mutation.
        */
        struct messageData {
            bytes4 functionSelector;
            address sender;
            parameter[] parameters;
            uint256 expirationTimestamp;
        }
      
        /**
        * @notice Struct used to define a parameter for Off-Chain Database Handler deferral.
        * @param name The variable name of the parameter.
        * @param value The string encoded value representation of the parameter.
        */
        struct parameter {
            string name;
            string value;
        }

Related Code

Additional Context

It is important to notice that the EIP-5559 still under discussion on the Ethereum community and isn't yet an widely adopted standard.

[DB] Read ENS properties from database on gateway

Feature Request

After implementing the read information from a Database feature, it will be necessary to create some integration tests. The tests will be responsibly to ensure that the flow of interaction between each part of the project is working properly and test bad scenarios to see how the code handles it.

Describe Preferred Solution

To complete this issue, it is be necessary the following tests:

  • Main flow of reading information from Database as specified in the image at the additional context;
  • Flow with a error in the final validation step;
  • Contract's address not exist;
  • Wrong calls in the resolver contract;

Additional Context

The right expected flow of interactions is the following one:

Captura de Tela 2024-02-23 às 15 08 30

Blockful's demo day

Feature Request

To resume the progress of the ENS team, the roadmap and the tasks that we will be working on next, it is necessary to make a demonstration for all blockful's members.

Describe Preferred Solution

To finish the issue, it is important to complete the following steps:

  • Overview about what is ENS.
  • What kind of problem Blockful is working on to solve.
  • How this work contributes with layers two.
  • What is our planned code architecture.
  • Overview about the code.

Additional Context

Blockful's ENS repository: https://github.com/blockful-io/external-resolver

ENS Team's board: https://github.com/orgs/blockful-io/projects/6

[L2] Call l2 on gateway

Feature Request

We need to have an smart contract deployed on any given layer 2 protocol to be used as an external source which will be used on the redirecting phase of the L1 flow.

sequenceDiagram
  Client->>Registry :resolver("test.eth")
  Registry->>Client :0x....
  Client->>Resolver :text("test.eth", "avatar", "avatar.png")
  Resolver->>Client :revert `StorageHandledByL2(chainId, contractAddress)`
  Client->> L2 Contract :text("test.eth", "avatar", "avatar.png")

Describe Preferred Solution

The client should be able to:

  1. Connect to L2
  2. Call the given method on the L2 contract
  3. Return response

To complete this issue, it is be necessary the following steps:

  • L2 gateway file.
  • L2 repository class.
  • Script to deploy on arbitrum L1.
  • Script to deploy on arbitrum L2.
  • Resolver contract for arbitrum on layer 2.
  • Get functions implemented.

Related Code

Public Resolver contract implementation
Offchain Resolving
Address reading implementation
Read data validation

Additional context

No implementation of the CCIP-Read on Layer 2 has been found so far. The most popular JS libraries (Viem and Ethers) currently only support the Gateway reading.

We're not sure about whether the L2 response validation on L1 will be made through the callback (e.g. resolveWithProof) or via Merkle Proof.

Workshop ENS practical part

Feature Request

One core part of the workshop that will be done in 20/03/2024 at the ETHSamba, is the practical one. In this part, it will be taught the following aspects:

  • Buy domain in testnet.
  • Deploy a Public Resolver contract on the testnet.
  • Use ENS front end to point the deployed resolver and fetch information.
  • Explain what we are doing at Blockful and how can they contribute.

Describe Preferred Solution

1 - Setup local envirolment:

  • Download necessary libs, code editor and frameworks.
  • Get eth by some faucet.
  • Buy a testnet ENS domain.

2 - Deploy contracts:

  • Clone github repository's project.
  • Deploy Public Resolver with a deploy script.

3 - Fetch information with the ENS's frontend:

  • Get deployed contract's address.
  • Set the resolver's address in the frontend.
  • Confirm that the front end is reaching the contract and getting the information.

4 - What are we doing at Blockful?

  • Explain what we are working on and how can they contribute.

Additional Context

It still is some parts of the task that we needed to think about. Like:

  • What kind of libs, frameworks and code editors will be used?
  • What faucet will be used?
  • We will deploy the front end, or use a deployed one?

Also, here it is some important links to complete the task:

Faucet exemple: https://www.alchemy.com/faucets/ethereum-sepolia

how to use ENS testnet: https://phi-xyz.notion.site/Get-a-ENS-domain-on-Goerli-Testnet-6563298f8abc445d9a20472db05aef79

ENS frontend: https://app.ens.domains

ENS contracts: https://github.com/ensdomains/ens-contracts

Public resolver on Arbitrum testnet

Feature Request

In this feature, we aim to implement a public resolver on the Arbitrum testnet. Additionally, our gateway, which is already capable of communicating with a layer two like Arbitrum Sepolia, should be invoked through the redirection of a call from the client to the contracts on the L1. Subsequently, the gateway will call the Resolver contract deployed on the L2 and retrieve the offchain-stored information.

Related Code

https://docs.arbitrum.io/build-decentralized-apps/quickstart-solidity-hardhat

Gateway signing messages on reading (Part.1)

The gateway should sign messages read from any external datasource to proof its authenticity on the smart OffchainResolver smart contract.

  • Gateway has its own private key
  • It read data from an external datasource (DB, L2)
  • It signs the read data with it's private key so the contract can proof that it came from the right place

Integration test for reading from L1 through Merkle Proofs

Feature Request

The task objective is to finish the integration tests, and make the client able to read data from layer two. All this flow needs to be done in a test script.

Describe Preferred Solution

To do so, it is necessary to implement the gateway connection to l2 and to deploy the Public resolver in the Layer 2.

Related Code

#46
#47

[L2] Create contract for Optimism reading

Feature Request

We need to have an smart contract deployed on any given layer 2 protocol to be used as an external source which will be used on the redirecting phase of the L1 flow.

sequenceDiagram
  Client->>Registry :resolver("test.eth")
  Registry->>Client :0x....
  Client->>Resolver :setText("test.eth", "avatar", "avatar.png")
  Resolver->>Client :revert `StorageHandledByL2(chainId, contractAddress)`
  Client->>⭐ L2 Contract :setText("test.eth", "avatar", "avatar.png")

Describe Preferred Solution

The L2 contract should behave exactly the same way as Public Resolver would in the Layer 1.

Address reading implementation

/**
    * Returns the address associated with an ENS node.
    * @param node The ENS node to query.
    * @return The associated address.
    */
function addr(
    bytes32 node
) public view virtual override returns (address payable) {
    bytes memory a = addr(node, COIN_TYPE_ETH);
    if (a.length == 0) {
        return payable(0);
    }
    return bytesToAddress(a);
}

Checklist

  • Deploy contract on L2
  • Set L2 contract address on L1 contract

Related Code

Public Resolver contract implementation
Offchain Resolving
Address reading implementation

Additional Context

After being deployed to the given layer 2, the contract address should be stored in the layer 1 contract in order to redirect the requests by using StorageHandledByL2(chainID, contractAddress).

[L2] Read ENS properties from L2 on gateway

Feature Request

After implementing the read information from a layer 2 feature, it will be necessary to create some integration tests. The tests will be responsibly to ensure that the flow of interaction between each part of the project is working properly and test bad scenarios to see how the code handles it.

Describe Preferred Solution

To complete this issue, it is be necessary the following tests:

  • Main flow of reading information from layer 2 as specified in the image at the additional context;
  • Flow with a error in the final validation step;
  • Contract's address not exist;
  • Wrong calls in the resolver contract;

Additional Context

The right expected flow of interactions is the following one:

Captura de Tela 2024-02-23 às 14 50 21

Create architecture diagrams

Feature Request

Describe the Feature Request

To start building the project, it will be needed to plan all the flow of interactions between each part. So, the ideia of this task is to create some diagrams that represents the code structure, architecture and flow.

Describe Preferred Solution

To achieve the mentioned goal, the task will be broke into 4 pieces:

  • Project architecture for Layer 2 communication
  • Project architecture for Database communication
  • Interaction flow between project parts for Layer 2 communication
  • Interaction flow between project parts for Database communication

Onchain reading from layer 1

Feature Request

We should be able to read data from the native layer 1 ENS solution. This will be the foundation for the future offchain implementations.

Describe Preferred Solution

The client needs to be able to call the Registry for getting the given domain's Resolver for then to read its properties stored onchain.

sequenceDiagram
    Client->>Registry: resolver("test.eth")
    Registry->>Client: L1 Contract address
    Client->>L1 Contract: resolve("test.eth")
    L1 Contract->>Client: 0x....

Additional Context

The reading should be pretty straightforward giving that the SDKs support the layer 1 reading and writing by default.

Related Code

Blocked by #11

Implement `getStorageSlot` on Gateway

Feature Request

Implement the getStorageSlots function that is required for the L2 storage validation based on Merkle Proof.

Describe Preferred Solution

There is an implementation inside the ENS' EVM Gateway that handles this kind of validation. However, it relies on Ethers v6 and the Chainlink CCIP-Read-Server which currently isn't interchangeable with our implementation of the CCIP-Server.

In order to accomplish this task the following tasks should be completed:

  • Implement required functions using Viem instead of Ethers
    • async createProofs( address: string, commands: string[], constants: string[]): Promise<string>
    • async executeOperation(operation: number, constants: string[], requests: Promise<StorageElement>[]): Promise<string>
    • async computeFirstSlot(command: string,constants: string[],requests: Promise<StorageElement>[]): Promise<{ slot: bigint; isDynamic: boolean }>
    • async getDynamicValue(block: T,address: string,slot: bigint): Promise<StorageElement>
    • async getValueFromPath(block: T,address: string,command: string,constants: string[],requests: Promise<StorageElement>[]): Promise<StorageElement>
interface StorageElement {
  slots: bigint[];
  value: () => Promise<string>;
  isDynamic: boolean;
}
  • Implement the withGetStorageSlots handler following this interface
function getStorageSlots(address addr, bytes32[] memory commands, bytes[] memory constants) external view returns(bytes memory witness)
export function withGetStorageSlots(proofService: ProofService): ccip.HandlerDescription {
  return {
    type: 'getStorageSlots',
    func: async (args): Promise<ccip.HandlerResponse> => {
            const [addr, commands, constants] = args;
            const proofs = await this.createProofs(addr, commands, constants);
            return { data: [proofs] };
    },
  }
}

Describe Alternatives

We could make our CCIP-Server compliant to the Chainlink's CCIP-Read-Server by changing the handler interface, and then injecting into the EVM Gateway through the server.add function.

Related Code

Chainlink's CCIP-Read-Server

Additional Context

Make the repository compliant to the open source standards

Proposed solution

We need to have a repository that follows all the community standards as an open source project which means:

  • Add repository description
  • Add README.md
  • Implement code of conduct
  • Implement CONTRIBUITING.md
  • Add license file
  • Implement security policy
  • Implement pull request template
  • Set code owners
  • Create milestones and subtasks

Implement Gateway subpackage

Feature Request

Describe the Feature Request

The Gateway logic should be placed in a subpackage on the monorepo structure, allowing commands to be run from the root package in order automate local development environment setup.

The gateway is a part of our project architecture that aims to obtain state proofs of information coming from an EVM. It is through this system that the status check is currently carried out.

An important thing to note is that it will be necessary to have a gateway for each chain that we want to communicate with. In the case of this first task, we will communicate with layer 1.

  • Create a gateway that enhance the EVMgateway class.
  • Create gateway to handle ens methods for writing and reading.
  • Server to run the component locally.
  • Script to test the gateway.

Describe Preferred Solution

Knowing that we already have the standard gateway structure, we must import it and create a legacy gateway that communicates and validates with layer 1. This same type of structure that communicates with layer 1 will be used as the basis for our offchain implementations.

To be considered a valid external data source on the offchain resolving flow, there are requirements that need to be met that will enable off chain resolution as well as storage of additional properties of ENS' domains, such as avatar and com.twitter.

The gateway must implement the following methods:

  • Any methods from the ENS' Interface Standard
    • setAddr(bytes32 node, address addr): setting Ethereum address
    • addr(bytes32 node, uint coinType) view returns (byte memory): reading a multicoin address
    • setText(bytes32 node, string calldata key, string calldata value): setting a text record
    • text(bytes32 node, string key) view returns (string memory): reading a text record

Related Code

Additional Context

There are no need for the Gateway to implement all the methods specified by ENS' Interface Standard, although each of them enables a new feature that is natively support by the ENS onchain resolution flow.

Still unclear whether the client should call setText() straight from the Gateway after the first resolve() call, or call the smart contract expecting for a StorageHandleByOffchainDatabase revert.

Implement Client subpackage

Feature Request

The client is responsible for linking different components that compose the ENS Offchain architecture. Therefore, a package named client will be required to gather all its specific logic, as well as its tests and scripts if needed.

┌──────┐                                           ┌───────────┐  ┌────────────────────┐
│Client│                                           │L1 Contract│  │ Off-Chain Database │
└──┬───┘                                           └─────┬─────┘  └──────────┬─────────┘
   │                                                     │                   │ 
   │ somefunc(...)                                       │                   │ 
   ├────────────────────────────────────────────────────►│                   │ 
   │                                                     │                   │ 
   │  revert StorageHandledByOffChainDatabase(sender,    |                   │ 
   │                               urls, requestParams)  │                   │ 
   │◄────────────────────────────────────────────────────┤                   │ 
   │                                                     │                   │ 
   │ HTTP Request [requestParams, signature]             │                   │ 
   ├─────────────────────────────────────────────────────┼──────────────────►│ 
   │                                                     │                   │ 
   │ response                                            │                   │ 
   │◄────────────────────────────────────────────────────┼───────────────────┤ 
   │                                                     │                   │ 

Describe Preferred Solution

The Client logic should be placed in a subpackage on the monorepo structure, allowing commands to be run from the root package in order automate local development environment setup. This subpackage must be able to:

  • Connect to a given Blockchain network through an JSON-RPC client
  • Call specific methods of an smart contract

Related Code

Gateway validate ownership of writing calls

Feature Request

The gateway should be able to receive encoded data from the Client with a signature for proofing it came from a given address in order to ensure ownership allowing changes to be made only by the property owner.

sequenceDiagram
User --> Client: Asks to set a text for test.eth
Client --> Client: normalize name
Client --> Client: namehash(name) = node
Client --> UniversalResolver: resolve(node, calldata)
UniversalResolver --> Resolver: setText(node, key, value)
Resolver --> Client: revert StorageHandledByOffChainDatabase(sender, url, calldata)
Note over Client,Gateway: Scope of this task
Client --> User: Request signature (EIP-712)
Client --> Gateway: setText(calldata, signature)
Gateway --> Gateway: Validate signature
Note over Client,Gateway: End of scope of this task
Gateway --> Database: Update data
Gateway --> Client: Response
Client --> User: Response

Create contracts subpackage

Feature Request

Describe the Feature Request

All the smart contracts should be placed in a single subpackage on the monorepo structure, allowing commands to be run from the root in order to automate local dev environment setup and mainnet/testnet deploy.

The ENS architecture is composed by multiple components, however the ones that are most important for the name resolution and data storage are the Registry, Universal Resolver and Public Resolver.

Describe Preferred Solution

Use Foundry as the Solidity framework due its simplicity and performance.

The contracts package must implement the following features:

  • Testing all contracts with filtering option
  • Deploying locally and to mainnet/testnet
  • Run local node for development

The following contracts are required to be present on the environment in order to resolve a name and to read/write its properties.

Describe Alternatives

Hardhat is an alternative giving its community adoption and TS native support.

Related Code

Foundry Book
Registry deployment script
Offchain Resolver
Setting resolver on a given domain

Implement Github Actions

Feature Request

Describe the Feature Request

The repository should automatically enforce the specified requirements in order to accept the addition of a change into the codebase. This will avoid repetitive manual tasks, leaking of private information, crashing of the master branch and merging of untested code.

Describe Preferred Solution

Implement Github Actions for:

  • running tests
  • enforce test coverage

Describe Alternatives

Some of these requirements could be enforced by using Husky git hooks.

Implement the contracts required for L2 storage and validation

Feature Request

Further investigation of the ENS' EVM Gateway led to a list of contracts that need to be deployed on Ethereum to support the L2 storage and validation based on Merkle Proofs.

Describe Preferred Solution

These are the required contracts gathered from the ENS' EVM Gateway repository.

  • EVMFetchTarget.sol
  • EVMFetcher.sol
  • EVMProofHelper.sol
  • IEVMVerifier.sol
  • L1Resolver.sol
  • L1Verifier.sol
  • MerkleTrie.sol
  • SecureMerkleTrie.sol
  • OffchainResolver.sol

We need a deployments directory with all the deployed contracts addresses by network,
as well as their ABI (e.g. ENS deployments) so that anyone who wants to implement the L2 Offchain reading could use it as a dependency.

Implement offchain reading from Gateway on Client

Feature Request

We need to have an API that is compliant to the ENS' Gateway Interface to be used as an external source which will be used on the redirecting phase of the L1 flow.

sequenceDiagram
  Client->>Registry :resolver("test.eth")
  Registry->>Client :0x....
  Client->>Resolver :text("test.eth", "avatar")
  Resolver->>Client :revert StorageHandledByOffchainDatabase(sender, url, message)
  Client->>Gateway :GET /{sender}/{encode(text("test.eth", "avatar"))}.json
  Gateway->>Client :200 ok {response}
  Client->>Resolver :callback(response, message.callbackData)

Describe Preferred Solution

The ERC-3668 specification requires the read data from any offchain data source to be validated on the layer 1 by calling a callback function with the Gateway's response and the callbackData.

Client read and callback validation:

const result = await ccipFetch({ data: callData, sender, urls }) // Gateway call

const { data: data_ } = await call(client, { // L1 data validation
  blockNumber,
  blockTag,
  data: concat([
    callbackSelector,
    encodeAbiParameters(
      [{ type: 'bytes' }, { type: 'bytes' }],
      [result, extraData],
    ),
  ]),
  to,
} as CallParameters)

return data_!

Layer 1 data validation:

/**
* Callback used by CCIP read compatible clients to verify and parse the response.
*/
function resolveWithProof(bytes calldata response, bytes calldata extraData) external view returns(bytes memory) {
    (address signer, bytes memory result) = SignatureVerifier.verify(extraData, response);
    require(
        signers[signer],
        "SignatureVerifier: Invalid sigature");
    return result;
}

Checklist

  • Call L1 Registry: resolver("test.eth")
  • Call L1 Resolver: text("test.eth", "com.twitter")
  • Handle StorageHandledByOffchainDatabase(sender, url, message) error
  • Call Gateway: GET /{sender}/{encode(text("test.eth", "com.twitter"))}.json
  • Validate response on L1 Resolver: callback(response, callbackData)

Related Code

Public Resolver contract implementation
Offchain Resolving
Address reading implementation
Read data validation

Create contract for Gateway reading and writing

Feature Request

The ENS storage of properties related to a domain or subdomains can be handled by Offchain gateway, an API that exposes a REST endpoint (see below) and saves its content into a data source that can be pretty much anything.

Describe Preferred Solution

The Gateway should expose a REST endpoint that follows either of the following patterns as defined in ERC-3668:

  • GET /{sender}/{data}.json
  • POST /{sender}.json body: {data}
sequenceDiagram
  Client->>Registry :resolver("test.eth")
  Registry->>Client :0x....
  Client->>Resolver :resolve("test.eth")
  Resolver->>Client :revert `StorageHandledByOffchainDatabase(sender, url, calldata)`
  Client->>Gateway :GET `/{sender}/{data}.json`
  Gateway->>Client :200 OK
  • The sender is the smart contract address and its version.
  • The url is the Gateway's URL so the Client can redirect the request
  • The calldata is the encoded information of the domain such as owner address, namehash, and parameters

Smart contract implementation

function setAddr(bytes32 node, address a) external {
    IWriteDeferral.parameter[] memory params = new IWriteDeferral.parameter[](3);

    params[0].name = "node";
    params[0].value = BytesToString.bytes32ToString(node);

    params[1].name = "coin_type";
    params[1].value = Strings.toString(coinType);

    params[2].name = "address";
    params[2].value = BytesToString.bytesToString(a);

    revert StorageHandledByOffChainDatabase(
        IWriteDeferral.domainData(
            {
                name: WRITE_DEFERRAL_DOMAIN_NAME,
                version: WRITE_DEFERRAL_DOMAIN_VERSION,
                chainId: 1,
                verifyingContract: address(this)
            }
        ),
        _offChainDatabaseUrl,
        IWriteDeferral.messageData(
            {
                functionSelector: msg.sig,
                sender: msg.sender,
                parameters: params,
                expirationTimestamp: block.timestamp + _offChainDatabaseTimeoutDuration
            }
        )
    );
}

Describe Alternatives

The /{sender}/{data}.json is a pattern created for reading data offchain, which doesn't necessarily attend to the writing requirements. If so, a new pattern could be created and applied as a new EIP, giving that the EIP-5559 is still under discussion in the community.

Related Code

Smart contract implementation

Additional Context

No offchain writing implementation has been found so far.

[L2] Implement EIP-5559 on the offchain resolver redirecting request to the L2

Feature Request

The ENS storage of properties related to a domain or subdomains can be handled by a different network than Ethereum.

Describe Preferred Solution

Revert writing calls with the StorageHandledByL2(chainID, contractAddress) error specified by the EIP-5559.

sequenceDiagram
User --> Client: Asks to set a text for test.eth
Client --> Client: normalize name
Client --> Client: namehash(name) = node
Client --> UniversalResolver: resolve(node, calldata)
UniversalResolver --> Resolver: setText(node, key, value)
Note over Resolver,Client: Scope of this task
Resolver --> Client: revert StorageHandledByL2(chainID, contractAddress)
Note over Resolver,Client: End of scope of this task
Client --> User: Request signature (EIP-712)
User --> Client: signature
Client --> Gateway: setText(calldata, signature)
Gateway --> L2: Validate signature
Gateway --> L2: Update data
Gateway --> Client: Response
Client --> User: Action's Response

Related Code

ENS Public Resolver
L2 contract implementation

[L2] Create contract for Arbitrum reading (Part.1)

Feature Request

We need to have an smart contract deployed on any given layer 2 protocol to be used as an external source which will be used on the redirecting phase of the L1 flow.

sequenceDiagram
  Client->>Registry :resolver("test.eth")
  Registry->>Client :0x....
  Client->>Resolver :setText("test.eth", "avatar", "avatar.png")
  Resolver->>Client :revert `StorageHandledByL2(chainId, contractAddress)`
  Client->>⭐ L2 Contract :setText("test.eth", "avatar", "avatar.png")

Describe Preferred Solution

The L2 contract should behave exactly the same way as Public Resolver would in the Layer 1.

Address reading implementation

/**
    * Returns the address associated with an ENS node.
    * @param node The ENS node to query.
    * @return The associated address.
    */
function addr(
    bytes32 node
) public view virtual override returns (address payable) {
    bytes memory a = addr(node, COIN_TYPE_ETH);
    if (a.length == 0) {
        return payable(0);
    }
    return bytesToAddress(a);
}

Checklist

  • Run local node with Layer 2.

Related Code

Public Resolver contract implementation
Offchain Resolving
Address reading implementation

Additional Context

After being deployed to the given layer 2, the contract address should be stored in the layer 1 contract in order to redirect the requests by using StorageHandledByL2(chainID, contractAddress).

Integration tests for writing (Layer 2)

Feature Request

After implementing the write information on a Layer 2 feature, it will be necessary to create some integration tests. The tests will be responsibly to ensure that the flow of interaction between each part of the project is working properly and test bad scenarios to see how the code handles it.

Describe Preferred Solution

To complete this issue, it is be necessary the following tests:

  • Main flow of writing information on the Layer 2 as specified in the image at the additional context;
  • Contract's address not exist;
  • Wrong calls in the resolver contract;

Additional Context

The right expected flow of interactions is the following one:

Captura de Tela 2024-02-23 às 15 24 17

Integration test: reading from database

Feature Request

After implementing the read and signing information on a Database feature, it will be necessary to create some integration tests. The tests will be responsible for ensuring that the flow of interaction between each part of the project is working properly and testing bad scenarios to see how the code handles it.

Describe Preferred Solution

To complete this issue, it is necessary the following tests:

  • Setup local environment for testing
    • Run gateway
    • Run local node
      • Deploy ENS Registry
      • Deploy Offchain Resolver
    • Seed test database with mock data

Test Scenarios

  • Read avatar URL
  • Read arbitrary text record
  • Read contenthash
  • Read address Ethereum
  • Read address Bitcoin

Additional Context

The right expected flow of interactions is the following:

Image

Workshop ENS theoretical part

Feature Request

One core part of the workshop that will be done in 20/03/2024 at the ETHSamba, is the theoretical one. In this part, it will be taught the following aspects:

  • Introduction to Ethereum addresses and concepts
  • What's ENS, and what problem is it solving? Who is using it?
  • ENS and DNS
  • How to buy a domain? Why are there 2 transactions?
  • Explain primary name, subdomains, records
  • Contracts architecture (controller, registry, resolver, off-chain resolver)

Additional Context

ENS website: https://app.ens.domains

Validate Gateway inheritance possibility

Feature Request

It is needed to implement a proof generation and validation in the gateway that communicates with the layer two. To do so, we will test the viability of inherit the EVM Gateway properties and functions on our own gateway.
This task aim to answer if it is possible or not to do this process.

Describe Preferred Solution

We want to instantiate the gateway in the format we are currently doing. In other words, preferably we only want to modify the newApp function than it can import the EVMGateway and uses its structure. The function code should look something like this:

export function NewApp(
  handlers: ccip.HandlerDescription[],
  middlewares: RequestHandler[],
) {
  const server = NewServer(...handlers, withQuery())
  const l1Provider = process.env.LAYER_ONE_RPC
  const l2Provider = process.env.LAYER2_RPC
  const l2Rollup = '0xd80810638dbDF9081b72C1B33c65375e807281C8'

  const evmGateway = new EVMGateway(
    new ArbProofService(
      new JsonRpcProvider(l1Provider),
      new JsonRpcProvider(l2Provider),
      l2Rollup,
      new InMemoryBlockCache(),
    ),
  )
  evmGateway.add(server)

  return server.makeApp('/', ...middlewares)
}

Describe Alternatives

There are two possible Alternatives.

  • Include EVM Gateway repository as a git submodule in our repository. Then, import the gateway structure and use it with our gateway.
git submodule add https://github.com/ensdomains/evmgateway
  • Clone the EVM Gateway repository in our repository. This will not allow us to be update to the evmGateway modifications, but allow us to modificate their code and make the files more custom.
cd packages && git clone https://github.com/ensdomains/evmgateway

Related Code

For the task, it is important to undrustand and take a look at EVM Gateway code:
https://github.com/ensdomains/evmgateway

refactor: integration tests to use vitest instead jest

Refactor Request

Describe the Refactor Request

In the integration tests branch, it was used jest. Otherwise, in some parts of the project, it is being used vitest, that is newer and more performative.
So, to make a more concise and more performative code, the integration tests need to be refactored.

Describe Preferred Solution

To complete the issue, the integration test needs to import vitest functions and definitions, like beforeAll, it and describe. None of jest definitions will be used after the modification.

Related Code

An example of test the is using the vitest already can be seen at: https://github.com/blockful-io/external-resolver/blob/integrationTests/packages/client/test/public.test.ts

Additional Context

vitest docs: https://vitest.dev/guide/

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.