Using CCIP Local Simulator in Your Hardhat Project - Forked Environments

You can use Chainlink Local to run CCIP in forked environments within your Hardhat project. To get started quickly, you will use the CCIP Hardhat Starter Kit. This project includes the Example1.spec.tst file located in the ./test/fork directory, demonstrating how to set up and run token transfer tests between two accounts using CCIP in forked environments.

Forked environments allow you to simulate real-world blockchain networks by forking the state of existing chains. In this example, we will fork Arbitrum Sepolia and Ethereum Sepolia.

Prerequisites

This guide assumes that you are familiar with the guide Using CCIP Local Simulator in Your Hardhat Project. If not, please get familiar with it and run all the prerequisites.

Set an environment variable file. For higher security, the examples repository imports @chainlink/env-enc. Use this tool to encrypt your environment variables at rest.

  1. Set an encryption password for your environment variables.

    npx env-enc set-pw
    
  2. Run npx env-enc set to configure a .env.enc file with:

    • ARBITRUM_SEPOLIA_RPC_URL: The Remote Procedure Call (RPC) URL for the Arbitrum Sepolia network. You can obtain one by creating an account on Alchemy or Infura and setting up an Arbitrum Sepolia project.
    • ETHEREUM_SEPOLIA_RPC_URL: The RPC URL for the Ethereum Sepolia testnet. You can sign up for a personal endpoint from Alchemy, Infura, or another node provider service.

Test tokens transfers

You will run a test to transfer tokens between two accounts in a forked environment. The test file Example1.spec.ts is located in the ./test/fork directory. This file contains one test case:

Transfer with LINK fees: This test case transfers tokens from the sender account to the receiver account, paying fees in LINK. At the end of the test, it verifies that the sender account was debited and the receiver account was credited.

In this test, we simulate a transfer of tokens from an Externally Owned Account (EOA) on a source blockchain (which is a fork of Arbitrum Sepolia) to an EOA on a destination blockchain (which is a fork of Ethereum Sepolia). Forked environments allow you to simulate real-world blockchain networks by forking the state of existing chains, providing a realistic testing scenario.

For a detailed explanation of the test file, refer to the Examine the code section.

In your terminal, run the following command to execute the test:

npx hardhat test test/fork/Example1.spec.ts

Example output:

$ npx hardhat test test/fork/Example1.spec.ts


Example 1 - Fork
   ✔ Should transfer CCIP test tokens from EOA to EOA (10889ms)


1 passing (11s)

Examine the code

To transfer tokens using CCIP in a forked environment, we need the following:

  • Destination chain selector
  • Source CCIP router
  • LINK token for paying CCIP fees
  • A test token contract (such as CCIP-BnM) on both source and destination chains
  • A sender account (Alice)
  • A receiver account (Bob)

The it("Should transfer CCIP test tokens from EOA to EOA") function sets up the necessary environment and runs the test. Here are the steps involved:

  1. Initialize the sender and receiver accounts:

    const [alice, bob] = await hre.ethers.getSigners()
    
  2. Set the source and destination chains:

    const [source, destination] = ["ethereumSepolia", "arbitrumSepolia"]
    
  3. Retrieve the necessary addresses and configurations (such as the LINK token address, source router address, and so on).

  4. Fork the source network:

    await hre.network.provider.request({
      method: "hardhat_reset",
      params: [
        {
          forking: {
            jsonRpcUrl: getProviderRpcUrl(source),
          },
        },
      ],
    })
    
  5. Connect to the source router and CCIP-BnM contracts.

  6. Call sourceCCIPBnM.drip to request CCIP-BnM tokens for Alice (sender).

  7. Approve the source router to spend tokens on behalf of Alice (sender).

  8. Construct the Client.EVM2AnyMessage structure. This step is similar to the non fork example.

  9. Call the source router to estimate the fees.

  10. Call the requestLinkFromTheFaucet function to request LINK tokens for Alice (sender).

  11. Connect to the LINK contract and approve the LINK token for paying CCIP fees.

  12. Estimate Alice (sender) balance before the transfer.

  13. Call the source router to send the CCIP request.

  14. Wait for the transaction to be included in a block.

  15. Call the getEvm2EvmMessage function to parse the transaction receipt and extract the CCIPSendRequested event and then decodes it to an object.

  16. Verify that Alice's balance has decreased by the amount sent.

  17. Fork and switch to the destination network:

    await hre.network.provider.request({
      method: "hardhat_reset",
      params: [
        {
          forking: {
            jsonRpcUrl: getProviderRpcUrl(destination),
          },
        },
      ],
    })
    
  18. Call the routeMessage function to route the message to the destination router.

  19. Connect to the CCIP-BnM contract using the CCIP-BnM destination address.

  20. Verify that Bob's balance has increased by the amount sent.

Get the latest Chainlink content straight to your inbox.