Skip to content

From multi to uni #110

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

Merged
merged 2 commits into from
Aug 22, 2025
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
149 changes: 132 additions & 17 deletions ccip/token-transfer-using-arbitrary-messaging/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -211,46 +211,161 @@ forge test

### Deploy Contracts

First, deploy the contracts to the specified networks. This script will:
This project uses a reliable split deployment architecture that eliminates RPC reliability issues. Deployment consists of two phases:

1. Deploy Bridge, Configuration, Token Pool, and Token contracts to all three networks
1. Configure cross-chain relationships between the contracts
1. Save all deployed contract addresses to `addresses.json` for use by the test scripts
#### Phase 1: Deploy Contracts (Independent)

Deploy contracts to each chain independently. These commands use separate RPC endpoints and can run in parallel:

```sh
forge script script/Deploy.s.sol --broadcast --legacy --with-gas-price 100000000000
# Deploy to Sepolia
forge script script/deploy/DeploySepolia.s.sol --broadcast

# Deploy to Arbitrum Sepolia
forge script script/deploy/DeployArbitrumSepolia.s.sol --broadcast

# Deploy to Fuji
forge script script/deploy/DeployFuji.s.sol --broadcast
```

**Note**: The `addresses.json` file in this repository contains pre-deployed contract addresses from a previous deployment. When you run the deployment script above, it will overwrite this file with your new contract addresses.
#### Phase 2: Configure Cross-Chain Relationships (Independent)

After ALL deployments complete successfully, configure cross-chain relationships:

```sh
# Configure Sepolia
forge script script/configure/ConfigureSepolia.s.sol --broadcast

# Configure Arbitrum Sepolia
forge script script/configure/ConfigureArbitrumSepolia.s.sol --broadcast

# Configure Fuji
forge script script/configure/ConfigureFuji.s.sol --broadcast
```

#### Benefits of Split Deployment

- **RPC Reliability**: Each script uses its own RPC endpoint, eliminating multi-RPC failures
- **Error Recovery**: Retry individual failed deployments without affecting successful ones
- **Parallel Execution**: Deploy to multiple chains simultaneously
- **Better Debugging**: Smaller, focused scripts are easier to troubleshoot

### Test Cross-Chain Token Transfers
**Note**: Each deployment creates a network-specific address file (`script/addresses-Sepolia.json`, `script/addresses-ArbitrumSepolia.json`, `script/addresses-Fuji.json`) with the deployed contract addresses. This ensures deployments don't overwrite each other and provides better error isolation.

After deployment, you can test different token transfer scenarios. Each test script reads the contract addresses from the `addresses.json` file created by the deployment.
## Test Cross-Chain Token Transfers

### Burn and Mint from Avalanche Fuji to Ethereum Sepolia
After deployment, test the complete token transfer ecosystem. Each script demonstrates a different mechanism and automatically reads contract addresses from network-specific files.

This script tests the burn and mint functionality, transferring tokens from Fuji to Sepolia.
### Overview of Transfer Mechanisms

| Script | Source → Destination | Mechanism | Purpose |
| ---------------------- | -------------------- | -------------- | ----------------------- |
| `BurnAndMint.s.sol` | Fuji → Sepolia | Burn → Mint | Independent token pools |
| `LockAndMint.s.sol` | Sepolia → Arbitrum | Lock → Mint | Token bridging (step 1) |
| `BurnAndRelease.s.sol` | Arbitrum → Sepolia | Burn → Release | Token bridging (step 2) |

---

### 1. Burn and Mint: Fuji → Sepolia

This script demonstrates the burn and mint functionality by transferring tokens from Avalanche Fuji to Ethereum Sepolia. The script automatically reads deployed contract addresses from `addresses-Fuji.json`.

**Command:**

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

### Lock and Mint from Ethereum Sepolia to Arbitrum Sepolia
**Execution Flow:**

1. **Setup**: Reads `addresses-Fuji.json` → Grants minter role → Mints 1000 test tokens
2. **Transfer**: Calculates fees → Approves tokens → Calls bridge transfer
3. **Cross-Chain**: Burns tokens on Fuji → Sends CCIP message → Mints on Sepolia

**Key Details:**

- **Networks**: Avalanche Fuji → Ethereum Sepolia
- **Amount**: 1000 burn/mint tokens
- **CCIP Fees**: ~0.023 LINK
- **Gas Used**: ~360,104

**Success Indicators:**

- ✅ Message ID: `0x02f9b78cec831e4c548de955aa447057e8c93d635dac3eb96f2e8e21a03e4335`
- ✅ Events: `Burned`, `TokensTransferred`, `CrossChainMessageSent`
- ✅ [Track on CCIP Explorer](https://ccip.chain.link/#/side-drawer/msg/0x02f9b78cec831e4c548de955aa447057e8c93d635dac3eb96f2e8e21a03e4335)

This script tests the lock and mint functionality, transferring tokens from Sepolia to Arbitrum.
### 2. Lock and Mint: Sepolia → Arbitrum

This script demonstrates the lock and mint functionality by transferring tokens from Ethereum Sepolia to Arbitrum Sepolia. The script automatically reads deployed contract addresses from `addresses-Sepolia.json`.

**Command:**

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

### Burn and Release from Arbitrum Sepolia to Ethereum Sepolia
**Execution Flow:**

1. **Setup**: Reads `addresses-Sepolia.json` → Calculates fees → Approves tokens
2. **Transfer**: Transfers tokens to pool → Locks tokens → Calls bridge transfer
3. **Cross-Chain**: Sends CCIP message → Arbitrum receives → Mints equivalent tokens

**Key Details:**

- **Networks**: Ethereum Sepolia → Arbitrum Sepolia
- **Amount**: 1000 lockable tokens
- **CCIP Fees**: ~0.041 LINK
- **Gas Used**: ~325,384

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

- ✅ Message ID: `0xab14c3e93c2370e736c892d640dad06cc550ce4e277afe3c17af5c631a7491bf`
- ✅ Events: `Locked`, `TokensTransferred`, `CrossChainMessageSent`
- ✅ [Track on CCIP Explorer](https://ccip.chain.link/#/side-drawer/msg/0xab14c3e93c2370e736c892d640dad06cc550ce4e277afe3c17af5c631a7491bf)

### 3. Burn and Release: Arbitrum → Sepolia

This script demonstrates the burn and release functionality by transferring tokens from Arbitrum Sepolia back to Ethereum Sepolia. The script automatically reads deployed contract addresses from `addresses-ArbitrumSepolia.json`.

**Command:**

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

**Execution Flow:**

1. **Setup**: Reads `addresses-ArbitrumSepolia.json` → Grants minter role → Mints 1000 test tokens
2. **Transfer**: Calculates fees → Approves tokens → Calls bridge transfer
3. **Cross-Chain**: Burns tokens on Arbitrum → Sends CCIP message → Releases locked tokens on Sepolia

**Key Details:**

- **Networks**: Arbitrum Sepolia → Ethereum Sepolia
- **Amount**: 1000 burn/mint tokens
- **CCIP Fees**: ~0.024 LINK
- **Gas Used**: ~364,682

**Success Indicators:**

- ✅ Message ID: `0x8679ffccc5f4f47bea12666d7e10fe514ea0e28de50dc5b978da1fabbbb0fa42`
- ✅ Events: `Burned`, `TokensTransferred`, `CrossChainMessageSent`
- ✅ [Track on CCIP Explorer](https://ccip.chain.link/#/side-drawer/msg/0x8679ffccc5f4f47bea12666d7e10fe514ea0e28de50dc5b978da1fabbbb0fa42)

---

### Token Bridging Cycle

The **Lock and Mint** + **Burn and Release** scripts demonstrate a complete token bridging cycle:

| Step | Action | Result |
| ---- | ----------------------------------------- | ------------------------------------------------------------ |
| 1️⃣ | **Lock and Mint** (Sepolia → Arbitrum) | Tokens locked on Sepolia, equivalent minted on Arbitrum |
| 2️⃣ | **Burn and Release** (Arbitrum → Sepolia) | Tokens burned on Arbitrum, locked tokens released on Sepolia |

This creates a **round-trip token bridging system** where the original tokens can be recovered.

## Tracking Cross-Chain Transactions

After running any of the test scripts, you can track the progress of your cross-chain token transfer using the transaction logs and the [Chainlink CCIP Explorer](https://ccip.chain.link/).
Expand Down
6 changes: 5 additions & 1 deletion ccip/token-transfer-using-arbitrary-messaging/foundry.toml
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,11 @@
src = "src"
out = "out"
libs = ["lib", "node_modules"]
fs_permissions = [{ access = "read-write", path = "./script/addresses.json"}]
fs_permissions = [
{ access = "read-write", path = "./script/addresses-Sepolia.json"},
{ access = "read-write", path = "./script/addresses-ArbitrumSepolia.json"},
{ access = "read-write", path = "./script/addresses-Fuji.json"}
]
remappings = [
"@chainlink/contracts/=node_modules/@chainlink/contracts/",
"@chainlink/contracts-ccip/contracts/=node_modules/@chainlink/contracts-ccip/contracts/",
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,180 @@
// SPDX-License-Identifier: MIT
pragma solidity 0.8.24;

/**
* @notice This contract is provided "AS IS" without warranties of any kind, as an example and has not been audited.
* Users are advised to thoroughly test and audit their own implementations
* before deploying to mainnet or any production environment.
*
* @dev This code is intended for educational and illustrative purposes only.
* Use it at your own risk. The authors are not responsible for any loss of
* funds or other damages caused by the use of this code.
*/

import {Script, console, stdJson} from "forge-std/Script.sol";
import {Configuration} from "../src/bridge/Configuration.sol";
import {MockERC20} from "../test/mocks/MockERC20.sol";
import {BurnMintERC20} from "@chainlink/contracts/src/v0.8/shared/token/ERC20/BurnMintERC20.sol";
import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import {Client} from "@chainlink/contracts-ccip/contracts/libraries/Client.sol";
import {BaseDeployment} from "./BaseDeployment.s.sol";

abstract contract BaseConfiguration is BaseDeployment {
using stdJson for string;

struct NetworkAddresses {
address bridge;
address configuration;
address lockReleasePool;
address burnMintPool;
address lockableToken;
address burnMintToken;
}

struct ChainInfo {
uint64 chainSelector;
NetworkAddresses addresses;
}

function loadNetworkAddresses(string memory networkName) internal view returns (NetworkAddresses memory) {
string memory fileName = string(abi.encodePacked("./script/addresses-", networkName, ".json"));
string memory json = vm.readFile(fileName);

return NetworkAddresses({
bridge: json.readAddress(".bridge"),
configuration: json.readAddress(".configuration"),
lockReleasePool: json.readAddress(".lockReleasePool"),
burnMintPool: json.readAddress(".burnMintPool"),
lockableToken: json.readAddress(".lockableToken"),
burnMintToken: json.readAddress(".burnMintToken")
});
}

function getAllChainInfo() internal view returns (ChainInfo[3] memory) {
ChainInfo[3] memory chains;

// Sepolia - use getNetworkConfig for chain selector
chains[0] = ChainInfo({
chainSelector: getNetworkConfig("Sepolia").chainSelector,
addresses: loadNetworkAddresses("Sepolia")
});

// ArbitrumSepolia - use getNetworkConfig for chain selector
chains[1] = ChainInfo({
chainSelector: getNetworkConfig("ArbitrumSepolia").chainSelector,
addresses: loadNetworkAddresses("ArbitrumSepolia")
});

// Fuji - use getNetworkConfig for chain selector
chains[2] = ChainInfo({
chainSelector: getNetworkConfig("Fuji").chainSelector,
addresses: loadNetworkAddresses("Fuji")
});

return chains;
}

function setRemoteBridges(
Configuration configuration,
string memory currentNetworkName,
ChainInfo[3] memory allChains
) internal {
// Get current chain selector from network config (single source of truth)
uint64 currentChainSelector = getNetworkConfig(currentNetworkName).chainSelector;
for (uint256 i = 0; i < allChains.length; i++) {
// Skip self
if (allChains[i].chainSelector != currentChainSelector) {
configuration.setRemoteBridge(
allChains[i].chainSelector,
allChains[i].addresses.bridge
);

configuration.setExtraArgs(
allChains[i].chainSelector,
Client._argsToBytes(
Client.GenericExtraArgsV2({
gasLimit: 300_000,
allowOutOfOrderExecution: true
})
)
);
}
}
}

function configureSepoliaDestinationTokens(
Configuration sepoliaConfig,
ChainInfo[3] memory allChains
) internal {
console.log("Setting destination tokens for Sepolia");

// Find indices for each chain
uint256 sepoliaIndex = 0;
uint256 arbitrumIndex = 1;
uint256 fujiIndex = 2;

// Lock and Release with Fuji
sepoliaConfig.setDestinationToken(
MockERC20(allChains[sepoliaIndex].addresses.lockableToken),
allChains[fujiIndex].chainSelector,
MockERC20(allChains[fujiIndex].addresses.lockableToken)
);

// Burn and Mint with Fuji
sepoliaConfig.setDestinationToken(
IERC20(address(BurnMintERC20(allChains[sepoliaIndex].addresses.burnMintToken))),
allChains[fujiIndex].chainSelector,
IERC20(address(BurnMintERC20(allChains[fujiIndex].addresses.burnMintToken)))
);

// Lock and Mint with ArbitrumSepolia
sepoliaConfig.setDestinationToken(
IERC20(address(MockERC20(allChains[sepoliaIndex].addresses.lockableToken))),
allChains[arbitrumIndex].chainSelector,
IERC20(address(BurnMintERC20(allChains[arbitrumIndex].addresses.burnMintToken)))
);
}

function configureArbitrumSepoliaDestinationTokens(
Configuration arbitrumConfig,
ChainInfo[3] memory allChains
) internal {
console.log("Setting destination tokens for ArbitrumSepolia");

// Find indices for each chain
uint256 sepoliaIndex = 0;
uint256 arbitrumIndex = 1;

// Burn and Release with Sepolia
arbitrumConfig.setDestinationToken(
IERC20(address(BurnMintERC20(allChains[arbitrumIndex].addresses.burnMintToken))),
allChains[sepoliaIndex].chainSelector,
IERC20(address(MockERC20(allChains[sepoliaIndex].addresses.lockableToken)))
);
}

function configureFujiDestinationTokens(
Configuration fujiConfig,
ChainInfo[3] memory allChains
) internal {
console.log("Setting destination tokens for Fuji");

// Find indices for each chain
uint256 sepoliaIndex = 0;
uint256 fujiIndex = 2;

// Lock and Release with Sepolia
fujiConfig.setDestinationToken(
MockERC20(allChains[fujiIndex].addresses.lockableToken),
allChains[sepoliaIndex].chainSelector,
MockERC20(allChains[sepoliaIndex].addresses.lockableToken)
);

// Burn and Mint with Sepolia
fujiConfig.setDestinationToken(
IERC20(address(BurnMintERC20(allChains[fujiIndex].addresses.burnMintToken))),
allChains[sepoliaIndex].chainSelector,
IERC20(address(BurnMintERC20(allChains[sepoliaIndex].addresses.burnMintToken)))
);
}
}
Loading
Loading