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

Access Chainlink VRF services on a L1 via ICM #208

Merged
merged 2 commits into from
Oct 30, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
---
title: Introduction
description: Learn how to use ICM to access Chainlink VRF services on L1 networks that do not have direct Chainlink support.
updated: 2024-10-21
authors: [0xstt]
icon: Book
---

As decentralized applications (dApps) expand across multiple blockchains, some Layer 1 (L1) networks lack direct support from essential services like **Chainlink VRF (Verifiable Random Functions)**. This presents a significant challenge for developers who rely on verifiable randomness for use cases such as gaming, lotteries, NFT minting, and other decentralized functions that require unbiased, unpredictable random numbers.

The challenge arises because not every L1 network has integrated with Chainlink, meaning developers on those chains are left without native access to VRF services. Without verifiable randomness, critical aspects of dApps, such as fairness and security, can be compromised.

### Why ICM is Necessary

To address this gap, **Interchain Messaging (ICM)** provides a solution by allowing L1 networks that don’t have direct Chainlink support to still access these services. Through ICM, any blockchain can request VRF outputs from a Chainlink-supported network (e.g., Fuji) and receive the results securely on their own L1.

This cross-chain solution unlocks the ability to use Chainlink VRF on unsupported networks, bypassing the need for native integration and ensuring that dApp developers can continue building secure and fair decentralized applications.

In the following sections, we will explore how to use ICM to access Chainlink VRF services across different chains by deploying two key smart contracts: one on the Chainlink-supported network and another on the target L1.
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
---
title: Understanding the Flow
description: Learn how requests for random words flow across chains using ICM.
updated: 2024-10-21
authors: [0xstt]
icon: BookOpen
---

import { Step, Steps } from 'fumadocs-ui/components/steps';

This section explains how random words are requested and fulfilled using Chainlink VRF across two different blockchains through **Interchain Messaging (ICM)**. The entire process involves several key components: the decentralized application (DApp), `CrossChainVRFConsumer`, `CrossChainVRFWrapper`, and `TeleporterMessenger`.

Let’s walk through the flow step by step:

<Steps>
<Step>

### DApp Submits Request for Random Words

The DApp, running on an L1 without direct Chainlink support, initiates a request for verifiable randomness by interacting with the `CrossChainVRFConsumer` contract deployed on its chain.

</Step>
<Step>

### `CrossChainVRFConsumer` Sends Cross-Chain Message

Once the DApp submits the request, the `CrossChainVRFConsumer` prepares a message containing the necessary VRF request parameters (such as `keyHash`, `request confirmations`, `gas limit`, etc.). This message is sent across chains via the `TeleporterMessenger` to the `CrossChainVRFWrapper` on the Chainlink-supported network (e.g., Fuji).

</Step>
<Step>

### `TeleporterMessenger` Receives Message & Calls `CrossChainVRFWrapper`
1. On the Chainlink-supported network, the `TeleporterMessenger` receives the cross-chain message sent by the `CrossChainVRFConsumer`. It passes the message to the `CrossChainVRFWrapper`, which is authorized to handle VRF requests.
2. The `CrossChainVRFWrapper` contract, deployed on the supported network, sends the request to **Chainlink VRF** for random words, using the parameters received in the message (e.g., `subscription ID`, `callback gas limit`, etc.).

</Step>

<Step>

### Chainlink VRF Fulfills Random Words Request

1. The **Chainlink VRF** fulfills the request and returns the random words to the `CrossChainVRFWrapper` contract by invoking its callback function.
2. Once the random words are received, the `CrossChainVRFWrapper` encodes the fulfilled random words and sends them back as a cross-chain message to the `CrossChainVRFConsumer` on the original L1.

</Step>

<Step>

### `TeleporterMessenger` Returns Random Words to `CrossChainVRFConsumer`

The `TeleporterMessenger` on the original L1 receives the message containing the random words and passes it to the `CrossChainVRFConsumer`.

</Step>

<Step>

### `CrossChainVRFConsumer` Returns Random Words to the DApp

Finally, the `CrossChainVRFConsumer` processes the random words and sends them to the DApp that originally requested them, completing the flow.

</Step>

</Steps>

![](/course-images/interchain-messaging/cross-chain-vrf.png)

This end-to-end process demonstrates how decentralized applications on unsupported L1 networks can request verifiable randomness from Chainlink VRF, leveraging **ICM** to handle cross-chain communication.

Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
---
title: Orchestrating VRF Requests Over Multiple Chains (Wrapper)
description: Learn how the CrossChainVRFWrapper contract on a Chainlink-supported L1 handles cross-chain requests for VRF.
updated: 2024-10-21
authors: [0xstt]
icon: BookOpen
---

import { Step, Steps } from 'fumadocs-ui/components/steps';

The `CrossChainVRFWrapper` contract plays a critical role in handling requests for randomness from an unsupported L1. It is deployed on a **Chainlink-supported network** (like Avalanche Fuji) and serves as the intermediary that interacts with Chainlink VRF to request random words.

Here’s how it functions as the provider for VRF services:

## Receives Cross-Chain Messages

<Steps>
<Step>

When the `CrossChainVRFConsumer` on the unsupported L1 initiates a request for randomness, it sends a cross-chain message via the `TeleporterMessenger` to the `CrossChainVRFWrapper` on the supported network.

</Step>
<Step>

The `CrossChainVRFWrapper` verifies that the request came from an authorized address. This is essential to ensure that only verified consumers can request randomness.

</Step>
<Step>

Upon receiving a valid request, the **VRFWrapper** calls **Chainlink VRF** and sends the request with the required parameters, such as the number of random words, request confirmations, and gas limits.

</Step>
<Step>

The `CrossChainVRFWrapper` keeps track of all pending requests using a mapping, associating each request ID with its destination (the L1 where the `CrossChainVRFConsumer` resides). This ensures that the random words are returned to the correct destination once fulfilled.

</Step>
</Steps>

<Accordions>
<Accordion title="Implementation">
```solidity
function receiveTeleporterMessage(
bytes32 originChainID,
address originSenderAddress,
bytes calldata message
) external {
require(msg.sender == address(teleporterMessenger), "Caller is not the TeleporterMessenger");
// Verify that the origin sender address is authorized
require(authorizedSubscriptions[originSenderAddress].isAuthorized, "Origin sender is not authorized");
uint256 subscriptionId = authorizedSubscriptions[originSenderAddress].subscriptionId;
// Verify that the subscription ID belongs to the correct owner
(,,,, address[] memory consumers) = s_vrfCoordinator.getSubscription(subscriptionId);
// Check wrapper contract is a consumer of the subscription
bool isConsumer = false;
for (uint256 i = 0; i < consumers.length; i++) {
if (consumers[i] == address(this)) {
isConsumer = true;
break;
}
}
require(isConsumer, "Contract is not a consumer of this subscription");
// Decode message to get the VRF parameters
CrossChainRequest memory vrfMessage = abi.decode(message, (CrossChainRequest));
// Request random words
VRFV2PlusClient.RandomWordsRequest memory req = VRFV2PlusClient.RandomWordsRequest({
keyHash: vrfMessage.keyHash,
subId: subscriptionId,
requestConfirmations: vrfMessage.requestConfirmations,
callbackGasLimit: vrfMessage.callbackGasLimit,
numWords: vrfMessage.numWords,
extraArgs: VRFV2PlusClient._argsToBytes(VRFV2PlusClient.ExtraArgsV1({nativePayment: vrfMessage.nativePayment}))
});
uint256 requestId = s_vrfCoordinator.requestRandomWords(req);
pendingRequests[requestId] = CrossChainReceiver({
destinationBlockchainId: originChainID,
destinationAddress: originSenderAddress
});
}
```
</Accordion>
</Accordions>

## Handles Callback from Chainlink VRF

<Steps>
<Step>

When **Chainlink VRF** fulfills the randomness request, the `CrossChainVRFWrapper` receives the random words through a callback function. This ensures that the request has been successfully processed.

</Step>
<Step>

After receiving the random words, the `CrossChainVRFWrapper` encodes the result and sends it back as a cross-chain message to the `CrossChainVRFConsumer` on the unsupported L1. This is done using the `TeleporterMessenger`.

</Step>
</Steps>

<Accordions>
<Accordion title="Implementation">
```solidity
function fulfillRandomWords(uint256 requestId, uint256[] calldata randomWords) internal override {
require(pendingRequests[requestId].destinationAddress != address(0), "Invalid request ID");
// Create CrossChainResponse struct
CrossChainResponse memory crossChainResponse = CrossChainResponse({
requestId: requestId,
randomWords: randomWords
});
bytes memory encodedMessage = abi.encode(crossChainResponse);
// Send cross chain message using ITeleporterMessenger interface
TeleporterMessageInput memory messageInput = TeleporterMessageInput({
destinationBlockchainID: pendingRequests[requestId].destinationBlockchainId,
destinationAddress: pendingRequests[requestId].destinationAddress,
feeInfo: TeleporterFeeInfo({ feeTokenAddress: address(0), amount: 0 }),
requiredGasLimit: 100000,
allowedRelayerAddresses: new address[](0),
message: encodedMessage
});
teleporterMessenger.sendCrossChainMessage(messageInput);
delete pendingRequests[requestId];
}
```
</Accordion>
</Accordions>

In summary, the `CrossChainVRFWrapper` contract acts as the **bridge** between the unsupported L1 and Chainlink’s VRF services, ensuring that random words are requested, fulfilled, and delivered back across chains efficiently.
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
---
title: Bringing Chainlink VRF to Unsupported L1s (Consumer)
description: Learn how to request VRF from an unsupported L1 using CrossChainVRFConsumer.
updated: 2024-10-21
authors: [0xstt]
icon: BookOpen
---

import { Step, Steps } from 'fumadocs-ui/components/steps';

The `CrossChainVRFConsumer` contract enables DApps on an unsupported L1 to request random words from Chainlink VRF using a cross-chain communication mechanism. Since Chainlink does not natively support all blockchains, this setup allows developers to access Chainlink's VRF service even on networks that don’t have direct support.

<Steps>
<Step>

## Requesting Random Words

The `CrossChainVRFConsumer` contract sends a cross-chain message to the `CrossChainVRFWrapper` on a Chainlink-supported L1, requesting random words. This request is sent using `TeleporterMessenger`, which handles cross-chain communication.

<Accordions>
<Accordion title="Implementation">
```solidity
function requestRandomWords(
bytes32 keyHash,
uint16 requestConfirmations,
uint32 callbackGasLimit,
uint32 numWords,
bool nativePayment,
uint32 requiredGasLimit
) external {
// Create CrossChainRequest struct
CrossChainRequest memory crossChainRequest = CrossChainRequest({
keyHash: keyHash,
requestConfirmations: requestConfirmations,
callbackGasLimit: callbackGasLimit,
numWords: numWords,
nativePayment: nativePayment
});
// Send Teleporter message
bytes memory encodedMessage = abi.encode(crossChainRequest);
TeleporterMessageInput memory messageInput = TeleporterMessageInput({
destinationBlockchainID: DATASOURCE_BLOCKCHAIN_ID,
destinationAddress: vrfRequesterContract,
feeInfo: TeleporterFeeInfo({ feeTokenAddress: address(0), amount: 0 }),
requiredGasLimit: requiredGasLimit,
allowedRelayerAddresses: new address[](0),
message: encodedMessage
});
teleporterMessenger.sendCrossChainMessage(messageInput);
}
```
</Accordion>
</Accordions>

</Step>

<Step>

## Processing the Request
Once the request is received by the `CrossChainVRFWrapper`, it interacts with the Chainlink VRF Coordinator to request the random words on behalf of the consumer on the unsupported L1.

</Step>

<Step>

## Receiving Random Words

Once Chainlink fulfills the request, the `CrossChainVRFWrapper` sends the random words back to the `CrossChainVRFConsumer` via a cross-chain message, enabling the DApp on the unsupported L1 to use them.

<Accordions>
<Accordion title="Implementation">
```solidity
function receiveTeleporterMessage(
bytes32 originChainID,
address originSenderAddress,
bytes calldata message
) external {
require(originChainID == DATASOURCE_BLOCKCHAIN_ID, "Invalid originChainID");
require(msg.sender == address(teleporterMessenger), "Caller is not the TeleporterMessenger");
require(originSenderAddress == vrfRequesterContract, "Invalid sender");

// Decode the message to get the request ID and random words
CrossChainResponse memory response = abi.decode(message, (CrossChainResponse));

// Fulfill the request by calling the internal function
fulfillRandomWords(response.requestId, response.randomWords);
}

function fulfillRandomWords(uint256 requestId, uint256[] memory randomWords) internal {
// Logic to handle the fulfillment of random words
// Implement your custom logic here

// Emit event for received random words
emit RandomWordsReceived(requestId);
}
```
</Accordion>
</Accordions>

</Step>
</Steps>
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
---
title: Deploy Wrapper
description: Learn how to deploy the CrossChainVRFWrapper contract to handle cross-chain VRF requests.
updated: 2024-10-21
authors: [0xstt]
icon: Terminal
---

import { Step, Steps } from 'fumadocs-ui/components/steps';

Once you have set up your Chainlink VRF subscription and have your LINK tokens ready, the next step is to deploy the **CrossChainVRFWrapper** contract. This contract will act as the bridge between your unsupported L1 and the Chainlink VRF network on a supported L1, enabling cross-chain requests for random words.

<Steps>
<Step>

## Prerequisites
Before deployment, make sure you have:
- A valid **Chainlink VRF Subscription ID** (see previous section for details).
- The `TeleporterMessenger` contract address on your supported L1 (e.g., Avalanche Fuji).
</Step>
<Step>

## Deploy the Contract
Using the `forge create` command, deploy the `CrossChainVRFWrapper` contract to the supported L1 (e.g., Avalanche Fuji).

```bash
forge create --rpc-url <RPC_URL> --private-key <PRIVATE_KEY> src/CrossChainVRFWrapper.sol:CrossChainVRFWrapper --constructor-args <TELEPORTER_MESSENGER_ADDRESS>
```

Replace the following:

- `<RPC_URL>`: The RPC URL for the L1.
- `<PRIVATE_KEY>`: The private key for the account used to deploy the contract.
- `<TELEPORTER_MESSENGER_ADDRESS>`: The address of the deployed `TeleporterMessenger` contract.

After deployment, save the `Deployed to` address in an environment variable for future use.

```bash
export VRF_WRAPPER=<address>
```

</Step>
<Step>

## Verify the Deployment
After deploying the contract, verify that the `CrossChainVRFWrapper` has been successfully deployed by checking its address on a block explorer.

</Step>
<Step>

## Configure Authorized Subscriptions
Once deployed, the `CrossChainVRFWrapper` contract needs to be configured with authorized subscriptions to process requests for random words.

- Call the `addAuthorizedAddress` function to authorize a specific address with a given subscription ID.
- This ensures that only authorized addresses can request random words via the wrapper.

```bash
cast send --rpc-url <RPC_URL> --private-key <PRIVATE_KEY> $VRF_WRAPPER "addAuthorizedAddress(address caller, uint256 subscriptionId)" <CALLER_ADDRESS> <SUBSCRIPTION_ID>
```

Replace the following:

- `<CALLER_ADDRESS>`: The address that will be authorized to request random words.
- `<SUBSCRIPTION_ID>`: The ID of your Chainlink VRF subscription.
</Step>
</Steps>
Loading
Loading