CCIP Read
CCIP Read is a protocol developed by ENS that introduces functionality for cross-chain data retrieval through a gateway server. It extends the Cross Chain Interoperability Protocol (CCIP) developed by Chainlink, who funded its development through a Chainlink Community Grant.
Ethereum Name Service (ENS) implements CCIP Read so that it can resolve domains on Ethereum even if the relevant data and records are stored on an L2 chain—an offchain resolver framework. The process is trust-minimal, as the gateway returns a storage proof that can be verified on L1, and which is immune from any intervention or tampering.
Linea adapted the functionality of the relevant ENS contract, evm-gateway
,
so that it would function correctly with Linea's sparse Merkle tree design. The
Linea ENS repository therefore adds support for CCIP Read to Linea.
Read more about sparse Merkle trees in our architecture documentation.
CCIP
The Chainlink CCIP is middleware designed to enable traditional backends to securely interact with blockchains, preventing the need for organizations to devote resources to developing case-by-case solutions to this problem. This enables applications to transfer assets and data between chains, allowing developers to access different chains for their relative strengths, and also access a larger audience.
Read more about CCIP in the Chainlink documentation.
Linea ENS
Linea ENS allows Linea users to register human-readable domains for considerably lower fees than on Ethereum Mainnet, it also leverages smart contracts with considerable utility for developers by enabling CCIP Read. See the user guide here.
The Linea ENS repository contains a frontend and associated contracts for ENS to work on Linea, including functionality that enables any L1 application to query L2 data using CCIP Read.
However, Linea ENS is just one example use case; CCIP Read is beneficial in any scenario where it's advantageous to store data on L2. In addition to ENS-like systems, the library enables L1 applications to create allowlists that use cross-chain Verax attestations to determine eligibility for NFT mints or governance voting, for example.
You can use the building blocks of Linea ENS to deploy your own subdomain on Linea.
Use CCIP Read on Linea
While the repository was created to bring ENS to Linea, its component libraries can be applied in other contexts.
The two main contracts are:
- The Linea CCIP Gateway, which implements the gateway required for L1 protocols to query data on Linea,
- The Linea State Verifier, which verifies the state proofs generated by the gateway.
Together, they can be used to retrieve data on Linea in a trustless way. They are 'generic' in the sense that they can be applied to any use case.
The basic outline for using these contracts in a read capacity is as follows:
-
Your L1 contract, which we'll refer to as the client contract, requires data from L2. To do this, it builds a request to the
EVMFetcher
contract (part of the Linea State Verifier). -
The
EVMFetcher
contract uses CCIP Read to call the offchain gateway. -
The gateway retrieves the requested information from your associated L2 contract.
-
The gateway returns data and proofs to the
IEVMVerifier
contract. -
IEVMVerifier
andLineaSparseProofVerifier
verify the proofs and return the data to the L1 client contract.
Example
The below contract fetches another contract's storage value, testUint
.
// SPDX-License-Identifier: MIT
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';
contract TestL2 {
uint256 testUint; // Slot 0
constructor() {
testUint = 42;
}
}
contract TestL1 is EVMFetchTarget {
using EVMFetcher for EVMFetcher.EVMFetchRequest;
IEVMVerifier verifier;
address target;
constructor(IEVMVerifier _verifier, address _target) {
verifier = _verifier;
target = _target;
}
function getTestUint() public view returns(uint256) {
EVMFetcher.newFetchRequest(verifier, target)
.getStatic(0)
.fetch(this.getSingleStorageSlotCallback.selector, "");
}
function getSingleStorageSlotCallback(bytes[] memory values, bytes memory) public pure returns(uint256) {
return uint256(bytes32(values[0]));
}
}