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

CCIP: Foundry example repo that shows how to transfer non ccip supported tokens using arbitry messaging #63

Merged
merged 1 commit into from
May 23, 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
12 changes: 12 additions & 0 deletions .gitmodules
Original file line number Diff line number Diff line change
Expand Up @@ -25,3 +25,15 @@
[submodule "ccip/estimate-gas/foundry/lib/ccip"]
path = ccip/estimate-gas/foundry/lib/ccip
url = https://github.com/smartcontractkit/ccip
[submodule "ccip/token-transfer-using-arbitrary-messaging/lib/forge-std"]
path = ccip/token-transfer-using-arbitrary-messaging/lib/forge-std
url = https://github.com/foundry-rs/forge-std
[submodule "ccip/token-transfer-using-arbitrary-messaging/lib/chainlink-local"]
path = ccip/token-transfer-using-arbitrary-messaging/lib/chainlink-local
url = https://github.com/smartcontractkit/chainlink-local
[submodule "ccip/token-transfer-using-arbitrary-messaging/lib/ccip"]
path = ccip/token-transfer-using-arbitrary-messaging/lib/ccip
url = https://github.com/smartcontractkit/ccip
[submodule "ccip/token-transfer-using-arbitrary-messaging/lib/openzeppelin-contracts"]
path = ccip/token-transfer-using-arbitrary-messaging/lib/openzeppelin-contracts
url = https://github.com/OpenZeppelin/openzeppelin-contracts
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
name: test

on: workflow_dispatch

env:
FOUNDRY_PROFILE: ci

jobs:
check:
strategy:
fail-fast: true

name: Foundry project
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
submodules: recursive

- name: Install Foundry
uses: foundry-rs/foundry-toolchain@v1
with:
version: nightly

- name: Run Forge build
run: |
forge --version
forge build --sizes
id: build

- name: Run Forge tests
run: |
forge test -vvv
id: test
16 changes: 16 additions & 0 deletions ccip/token-transfer-using-arbitrary-messaging/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
# Compiler files
cache/
out/

# Ignores development broadcast logs
!/broadcast
/broadcast/*/31337/
/broadcast/**/dry-run/

# Docs
docs/

# Dotenv file
.env

.DS_Store
178 changes: 178 additions & 0 deletions ccip/token-transfer-using-arbitrary-messaging/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,178 @@
# CCIP: Token transfers using arbitrary data

> **Note**
>
> _This repository represents an example of using a Chainlink product or service. It is provided to help you understand how to interact with Chainlink’s systems so that you can integrate them into your own. This template is provided "AS IS" without warranties of any kind, has not been audited, and may be missing key checks or error handling to make the usage of the product more clear. Take everything in this repository as an example and not something to be copy pasted into a production ready service._

This project demonstrates how to use Chainlink CCIP to send tokens to a receiver using arbitrary data. This example is interesting for projects that want to send tokens crosschain that are not supported by Chainlink CCIP.

## Overview

This project showcases three main scenarios:

### Burn and Mint

![burn-and-mint](./img/tokens-messaing-burn-and-mint.jpg)

In this scenario, tokens are burned on the source chain and minted on the destination chain. When the sender initiates a transfer, the Bridge contract on the source chain transfers the tokens to the token pool then calls the `lockOrBurn` function of the token pool to burn the tokens. On the destination chain, the bridge contract receives the CCIP message and calls the `releaseOrMint` function of the token pool to mint the tokens for the receiver.

### Lock and Mint / Burn and Release

![lock-and-mint](./img/tokens-messaing-lock-and-mint.jpg)

In this scenario, tokens are locked on the source chain and minted on the destination chain. When the sender initiates a transfer, the Bridge contract on the source chain transfers the tokens to the token pool then calls the `lockOrBurn` function of the token pool to lock the tokens. On the destination chain, the bridge contract receives the CCIP message and calls the `releaseOrMint` function of the token pool to mint the tokens for the receiver.

The reverse scenario works as follows: When the sender initiates a transfer, the Bridge contract on the source chain transfers the tokens to the token pool then calls the `lockOrBurn` function of the token pool to burn the tokens. On the destination chain, the bridge contract receives the CCIP message and calls the `releaseOrMint` function of the token pool to release the tokens for the receiver. **Note**: This scenario requires active liquidity management to ensure that there are enough tokens on the destination chain to release tokens for the receivers.

### Lock and Release

![lock-and-release](./img/tokens-messaging-lock-and-release.jpg)

In this scenario, tokens are locked on the source chain and released on the destination chain. When the sender initiates a transfer, the Bridge contract on the source chain transfers the tokens to the token pool then calls the `lockOrBurn` function of the token pool to lock the tokens. On the destination chain, the bridge contract receives the CCIP message and calls the `releaseOrMint` function of the token pool to release the tokens for the receiver. **Note**: This scenario requires active liquidity management to ensure that there are enough tokens on the destination chain to release tokens for the receivers.

### Contracts

#### Bridge

The `Bridge.sol` contract serves as a central component for enabling the transfer of tokens across different blockchain networks. This contract utilizes the Chainlink CCIP (Cross-Chain Interoperability Protocol) for passing arbitrary data. The arbitrary data contains information such as the receiver address, the amount, and the token address on the destination chain.

Key Functions and Features:

- On the source chain:

- Interacts with the Configuration contract to fetch the necessary configuration, such as the token pool, destination token address, and the CCIP extra args (the gas limit for the execution of the Bridge's ccipReceive function on the destination chain).
- Interacts with the relevant token pools to lock or burn tokens.
- Interacts with CCIP Router for fee calculation and routing of arbitrary data to the bridge on the destination chain.

- On the destination chain:
- Interacts with the Configuration contract to fetch the necessary configuration, such as the token pool.
- Interacts with the relevant token pools to release or mint tokens for the receiver.

#### Configuration

The `Configuration.sol` contract is a centralized repository designed to manage and store the configuration details required for cross-chain operations facilitated by the `Bridge.sol` contract. This contract holds important information such as bridge addresses, token pool mappings, and specific chain settings (extraArgs).

Key Functions and Features:

- Bridge and Token Settings: Stores and updates the addresses of sender and receiver bridges that facilitate the cross-chain communication. It also manages mappings between source tokens on one chain and their corresponding destination tokens on another.
- Token Pool Management: Links ERC20 tokens to their respective token pools, which handle the actual locking, burning, releasing, and minting of tokens.
- Extra Arguments Storage: Maintains additional parameters needed for transaction execution on remote chains, such as gas limits, ensuring that each cross-chain message is tailored to meet the destination chain’s operational requirements.

#### Token pools

Token pools handle essential tasks such as locking, releasing, minting, and burning of tokens.

Types of Token Pools:

- BurnMint Token Pools: These pools refer to crosschain tokens that can be minted or burned. In this model, tokens are burned on the source chain and minted on the destination chain.
- LockRelease Token Pools: These are designed for scenarios where tokens cannot be directly minted or burned across chains. Instead, tokens are "locked" in the pool on the source chain and an equivalent amount of tokens is "released" from a similar pool on the destination chain. This method requires active liquidity management to ensure sufficient tokens on the destination chain to release tokens for the receivers.

### Transfer tokens using arbitrary data

Here’s a step-by-step breakdown for transferring tokens using arbitrary data:

1. **Token Approval**:

- The sender on the source chain approves the Bridge contract to spend tokens on their behalf, enabling the Bridge to handle tokens during the transfer process.

2. **Initiating Transfer**:

- The sender initiates the transfer by calling the `transferTokensToDestinationChain` function on the Bridge contract on the source chain, specifying the amount of tokens, the destination chain selector, the receiver's address on the destination chain, and any applicable fee tokens.

3. **Configuration Fetch**:

- The Bridge contract interacts with the Configuration contract to fetch necessary details such as the associated token pool, the destination token address on the destination chain, and extra arguments needed for cross-chain communication.

4. **Token Handling (Lock or Burn)**:

- Depending on the type of token pool:
- **LockRelease Token Pool**: Tokens are locked in the pool on the source chain, withheld from circulation but not destroyed.
- **BurnMint Token Pool**: Tokens are burned on the source chain, reducing the total supply on that chain.

5. **CCIP Message Preparation and Sending**:

- A CCIP message containing details about the receiver, amount, and destination token is sent to the Bridge on the destination chain via the CCIP Router. Fees are calculated and handled accordingly.

6. **Receiving on the destination chain**:
- The Bridge on the destination chain processes the message, validates the data against its Configuration contract, and performs token operations:
- **LockRelease Token Pool**: Tokens equivalent to the locked amount on the source chain are released and transferred to the receiver.
- **BurnMint Token Pool**: New tokens are minted equivalent to the burned amount on the source chain and transferred to the receiver.

## Test locally

The tests are written in Solidity and can be found in the `test` directory. The tests cover the core functionalities of the Bridge, Configuration, and Token Pool contracts.
`BridgeWithSimulator.t.sol` uses the Chainlink CCIP simulator locally. While the `BridgeWithSimulatorFork.t.sol` uses the Chainlink CCIP simulator with forked Ethereum Sepolia (as source chain) and Avalanche Fuji (as destination chain).

To run the tests:

- Set three environment variables that will be used to fork the source and destination chains:

- `ETHEREUM_SEPOLIA_RPC_URL`: The RPC URL of Ethereum Sepolia.

```text
export ETHEREUM_SEPOLIA_RPC_URL=YOUR_SEPOLIA_RPC_URL
```

- `AVALANCHE_FUJI_RPC_URL`: The RPC URL of Avalanche Fuji.

```text
export AVALANCHE_FUJI_RPC_URL=YOUR_FUJI_RPC_URL
```

- `ARBITRUM_SEPOLIA_RPC_URL`: The RPC URL of Arbitrum Sepolia.

```text
export ARBITRUM_SEPOLIA_RPC_URL=YOUR_ARBITRUM_SEPOLIA_RPC_URL
```

- Run the tests:

```bash
forge test
```

## Test on testnets

### Prerequisites

- To test on testnets, you need to set the private key environment variable for the sender account:

- `PRIVATE_KEY`: Your EOA private key.

```text
export PRIVATE_KEY=YOUR_PRIVATE_KEY
```

- Your EOA must have enough LINK and native gas tokens on Avalanche Fuji, Ethereum Sepolia, and Arbitrum Sepolia. You can use the [Chainlink Faucet](https://faucets.chain.link/) to get testnet tokens.

### Deploy Contracts

First, deploy the contracts to the specified networks. This script reads from the `addresses.json` file to get the necessary contract addresses.

```sh
forge script script/Deploy.s.sol --broadcast --legacy --with-gas-price 100000000000
```

### Burn and Mint from Fuji to Sepolia

This script tests the burn and mint functionality, transferring tokens from Fuji to Sepolia.

```sh
forge script script/BurnAndMint.s.sol --broadcast --legacy --with-gas-price 100000000000 -vvvvv
```

### Lock and Mint from Sepolia to Arbitrum

This script tests the lock and mint functionality, transferring tokens from Sepolia to Arbitrum.

```sh
forge script script/LockAndMint.s.sol --broadcast --legacy --with-gas-price 100000000000 -vvvvv
```

### Burn and Release from Arbitrum to Sepolia

This script tests the burn and release functionality, transferring tokens from Arbitrum to Sepolia.

```sh
forge script script/BurnAndRelease.s.sol --broadcast --legacy --with-gas-price 100000000000 -vvvvv
```
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
{
"transactions": [
{
"hash": "0xbb816971e2ab5ece53f2ba9662dc56542c2998b23b7c10bacce7ecaedae47539",
"transactionType": "CALL",
"contractName": null,
"contractAddress": "0x59cc0a2f462aca8b8d2363032b0bbb719c6f0b93",
"function": "mint(address,uint256)",
"arguments": [
"0x9d087fC03ae39b088326b67fA3C788236645b717",
"1000"
],
"transaction": {
"from": "0x9d087fc03ae39b088326b67fa3c788236645b717",
"to": "0x59cc0a2f462aca8b8d2363032b0bbb719c6f0b93",
"gas": "0x170e7",
"value": "0x0",
"input": "0x40c10f190000000000000000000000009d087fc03ae39b088326b67fa3c788236645b71700000000000000000000000000000000000000000000000000000000000003e8",
"nonce": "0x1ea",
"chainId": "0xa869"
},
"additionalContracts": [],
"isFixedGasLimit": false
}
],
"receipts": [
{
"status": "0x1",
"cumulativeGasUsed": "0x10b15",
"logs": [
{
"address": "0x59cc0a2f462aca8b8d2363032b0bbb719c6f0b93",
"topics": [
"0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef",
"0x0000000000000000000000000000000000000000000000000000000000000000",
"0x0000000000000000000000009d087fc03ae39b088326b67fa3c788236645b717"
],
"data": "0x00000000000000000000000000000000000000000000000000000000000003e8",
"blockHash": "0x6a296fac4582c2522142f53d13521d4631cf4185a0fad5c081545234cda97c8b",
"blockNumber": "0x1fa07a3",
"transactionHash": "0xbb816971e2ab5ece53f2ba9662dc56542c2998b23b7c10bacce7ecaedae47539",
"transactionIndex": "0x0",
"logIndex": "0x0",
"removed": false
}
],
"logsBloom": "0x00000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008000000000010000000000000000000000000000000000000020000000010000000000800000000000000000000000010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008000000000000000000000002000000000000000000000000000000000000080000000000000020000000000000000000000000000000000010000000000000000000000000000000",
"type": "0x0",
"transactionHash": "0xbb816971e2ab5ece53f2ba9662dc56542c2998b23b7c10bacce7ecaedae47539",
"transactionIndex": "0x0",
"blockHash": "0x6a296fac4582c2522142f53d13521d4631cf4185a0fad5c081545234cda97c8b",
"blockNumber": "0x1fa07a3",
"gasUsed": "0x10b15",
"effectiveGasPrice": "0x174876e800",
"from": "0x9d087fc03ae39b088326b67fa3c788236645b717",
"to": "0x59cc0a2f462aca8b8d2363032b0bbb719c6f0b93",
"contractAddress": null
}
],
"libraries": [],
"pending": [],
"returns": {},
"timestamp": 1716160599,
"chain": 43113,
"commit": "d24fcda"
}
Loading
Loading