Skip to content

Commit a1fd3ad

Browse files
authored
From multi to uni (#110)
* multi deployment scripts to mono scripts * update
1 parent 678190c commit a1fd3ad

18 files changed

+827
-336
lines changed

ccip/token-transfer-using-arbitrary-messaging/README.md

Lines changed: 132 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -211,46 +211,161 @@ forge test
211211

212212
### Deploy Contracts
213213

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

216-
1. Deploy Bridge, Configuration, Token Pool, and Token contracts to all three networks
217-
1. Configure cross-chain relationships between the contracts
218-
1. Save all deployed contract addresses to `addresses.json` for use by the test scripts
216+
#### Phase 1: Deploy Contracts (Independent)
217+
218+
Deploy contracts to each chain independently. These commands use separate RPC endpoints and can run in parallel:
219219

220220
```sh
221-
forge script script/Deploy.s.sol --broadcast --legacy --with-gas-price 100000000000
221+
# Deploy to Sepolia
222+
forge script script/deploy/DeploySepolia.s.sol --broadcast
223+
224+
# Deploy to Arbitrum Sepolia
225+
forge script script/deploy/DeployArbitrumSepolia.s.sol --broadcast
226+
227+
# Deploy to Fuji
228+
forge script script/deploy/DeployFuji.s.sol --broadcast
222229
```
223230

224-
**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.
231+
#### Phase 2: Configure Cross-Chain Relationships (Independent)
232+
233+
After ALL deployments complete successfully, configure cross-chain relationships:
234+
235+
```sh
236+
# Configure Sepolia
237+
forge script script/configure/ConfigureSepolia.s.sol --broadcast
238+
239+
# Configure Arbitrum Sepolia
240+
forge script script/configure/ConfigureArbitrumSepolia.s.sol --broadcast
241+
242+
# Configure Fuji
243+
forge script script/configure/ConfigureFuji.s.sol --broadcast
244+
```
245+
246+
#### Benefits of Split Deployment
247+
248+
- **RPC Reliability**: Each script uses its own RPC endpoint, eliminating multi-RPC failures
249+
- **Error Recovery**: Retry individual failed deployments without affecting successful ones
250+
- **Parallel Execution**: Deploy to multiple chains simultaneously
251+
- **Better Debugging**: Smaller, focused scripts are easier to troubleshoot
225252

226-
### Test Cross-Chain Token Transfers
253+
**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.
227254

228-
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.
255+
## Test Cross-Chain Token Transfers
229256

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

232-
This script tests the burn and mint functionality, transferring tokens from Fuji to Sepolia.
259+
### Overview of Transfer Mechanisms
260+
261+
| Script | Source → Destination | Mechanism | Purpose |
262+
| ---------------------- | -------------------- | -------------- | ----------------------- |
263+
| `BurnAndMint.s.sol` | Fuji → Sepolia | Burn → Mint | Independent token pools |
264+
| `LockAndMint.s.sol` | Sepolia → Arbitrum | Lock → Mint | Token bridging (step 1) |
265+
| `BurnAndRelease.s.sol` | Arbitrum → Sepolia | Burn → Release | Token bridging (step 2) |
266+
267+
---
268+
269+
### 1. Burn and Mint: Fuji → Sepolia
270+
271+
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`.
272+
273+
**Command:**
233274

234275
```sh
235-
forge script script/BurnAndMint.s.sol --broadcast --legacy --with-gas-price 100000000000 -vvvvv
276+
forge script script/BurnAndMint.s.sol --broadcast --with-gas-price 100000000000 -vvvvv
236277
```
237278

238-
### Lock and Mint from Ethereum Sepolia to Arbitrum Sepolia
279+
**Execution Flow:**
280+
281+
1. **Setup**: Reads `addresses-Fuji.json` → Grants minter role → Mints 1000 test tokens
282+
2. **Transfer**: Calculates fees → Approves tokens → Calls bridge transfer
283+
3. **Cross-Chain**: Burns tokens on Fuji → Sends CCIP message → Mints on Sepolia
284+
285+
**Key Details:**
286+
287+
- **Networks**: Avalanche Fuji → Ethereum Sepolia
288+
- **Amount**: 1000 burn/mint tokens
289+
- **CCIP Fees**: ~0.023 LINK
290+
- **Gas Used**: ~360,104
291+
292+
**Success Indicators:**
293+
294+
- ✅ Message ID: `0x02f9b78cec831e4c548de955aa447057e8c93d635dac3eb96f2e8e21a03e4335`
295+
- ✅ Events: `Burned`, `TokensTransferred`, `CrossChainMessageSent`
296+
-[Track on CCIP Explorer](https://ccip.chain.link/#/side-drawer/msg/0x02f9b78cec831e4c548de955aa447057e8c93d635dac3eb96f2e8e21a03e4335)
239297

240-
This script tests the lock and mint functionality, transferring tokens from Sepolia to Arbitrum.
298+
### 2. Lock and Mint: Sepolia → Arbitrum
299+
300+
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`.
301+
302+
**Command:**
241303

242304
```sh
243-
forge script script/LockAndMint.s.sol --broadcast --legacy --with-gas-price 100000000000 -vvvvv
305+
forge script script/LockAndMint.s.sol --broadcast --with-gas-price 100000000000 -vvvvv
244306
```
245307

246-
### Burn and Release from Arbitrum Sepolia to Ethereum Sepolia
308+
**Execution Flow:**
309+
310+
1. **Setup**: Reads `addresses-Sepolia.json` → Calculates fees → Approves tokens
311+
2. **Transfer**: Transfers tokens to pool → Locks tokens → Calls bridge transfer
312+
3. **Cross-Chain**: Sends CCIP message → Arbitrum receives → Mints equivalent tokens
313+
314+
**Key Details:**
315+
316+
- **Networks**: Ethereum Sepolia → Arbitrum Sepolia
317+
- **Amount**: 1000 lockable tokens
318+
- **CCIP Fees**: ~0.041 LINK
319+
- **Gas Used**: ~325,384
247320

248-
This script tests the burn and release functionality, transferring tokens from Arbitrum to Sepolia.
321+
**Success Indicators:**
322+
323+
- ✅ Message ID: `0xab14c3e93c2370e736c892d640dad06cc550ce4e277afe3c17af5c631a7491bf`
324+
- ✅ Events: `Locked`, `TokensTransferred`, `CrossChainMessageSent`
325+
-[Track on CCIP Explorer](https://ccip.chain.link/#/side-drawer/msg/0xab14c3e93c2370e736c892d640dad06cc550ce4e277afe3c17af5c631a7491bf)
326+
327+
### 3. Burn and Release: Arbitrum → Sepolia
328+
329+
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`.
330+
331+
**Command:**
249332

250333
```sh
251-
forge script script/BurnAndRelease.s.sol --broadcast --legacy --with-gas-price 100000000000 -vvvvv
334+
forge script script/BurnAndRelease.s.sol --broadcast --with-gas-price 100000000000 -vvvvv
252335
```
253336

337+
**Execution Flow:**
338+
339+
1. **Setup**: Reads `addresses-ArbitrumSepolia.json` → Grants minter role → Mints 1000 test tokens
340+
2. **Transfer**: Calculates fees → Approves tokens → Calls bridge transfer
341+
3. **Cross-Chain**: Burns tokens on Arbitrum → Sends CCIP message → Releases locked tokens on Sepolia
342+
343+
**Key Details:**
344+
345+
- **Networks**: Arbitrum Sepolia → Ethereum Sepolia
346+
- **Amount**: 1000 burn/mint tokens
347+
- **CCIP Fees**: ~0.024 LINK
348+
- **Gas Used**: ~364,682
349+
350+
**Success Indicators:**
351+
352+
- ✅ Message ID: `0x8679ffccc5f4f47bea12666d7e10fe514ea0e28de50dc5b978da1fabbbb0fa42`
353+
- ✅ Events: `Burned`, `TokensTransferred`, `CrossChainMessageSent`
354+
-[Track on CCIP Explorer](https://ccip.chain.link/#/side-drawer/msg/0x8679ffccc5f4f47bea12666d7e10fe514ea0e28de50dc5b978da1fabbbb0fa42)
355+
356+
---
357+
358+
### Token Bridging Cycle
359+
360+
The **Lock and Mint** + **Burn and Release** scripts demonstrate a complete token bridging cycle:
361+
362+
| Step | Action | Result |
363+
| ---- | ----------------------------------------- | ------------------------------------------------------------ |
364+
| 1️⃣ | **Lock and Mint** (Sepolia → Arbitrum) | Tokens locked on Sepolia, equivalent minted on Arbitrum |
365+
| 2️⃣ | **Burn and Release** (Arbitrum → Sepolia) | Tokens burned on Arbitrum, locked tokens released on Sepolia |
366+
367+
This creates a **round-trip token bridging system** where the original tokens can be recovered.
368+
254369
## Tracking Cross-Chain Transactions
255370

256371
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/).

ccip/token-transfer-using-arbitrary-messaging/foundry.toml

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,11 @@
22
src = "src"
33
out = "out"
44
libs = ["lib", "node_modules"]
5-
fs_permissions = [{ access = "read-write", path = "./script/addresses.json"}]
5+
fs_permissions = [
6+
{ access = "read-write", path = "./script/addresses-Sepolia.json"},
7+
{ access = "read-write", path = "./script/addresses-ArbitrumSepolia.json"},
8+
{ access = "read-write", path = "./script/addresses-Fuji.json"}
9+
]
610
remappings = [
711
"@chainlink/contracts/=node_modules/@chainlink/contracts/",
812
"@chainlink/contracts-ccip/contracts/=node_modules/@chainlink/contracts-ccip/contracts/",
Lines changed: 180 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,180 @@
1+
// SPDX-License-Identifier: MIT
2+
pragma solidity 0.8.24;
3+
4+
/**
5+
* @notice This contract is provided "AS IS" without warranties of any kind, as an example and has not been audited.
6+
* Users are advised to thoroughly test and audit their own implementations
7+
* before deploying to mainnet or any production environment.
8+
*
9+
* @dev This code is intended for educational and illustrative purposes only.
10+
* Use it at your own risk. The authors are not responsible for any loss of
11+
* funds or other damages caused by the use of this code.
12+
*/
13+
14+
import {Script, console, stdJson} from "forge-std/Script.sol";
15+
import {Configuration} from "../src/bridge/Configuration.sol";
16+
import {MockERC20} from "../test/mocks/MockERC20.sol";
17+
import {BurnMintERC20} from "@chainlink/contracts/src/v0.8/shared/token/ERC20/BurnMintERC20.sol";
18+
import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
19+
import {Client} from "@chainlink/contracts-ccip/contracts/libraries/Client.sol";
20+
import {BaseDeployment} from "./BaseDeployment.s.sol";
21+
22+
abstract contract BaseConfiguration is BaseDeployment {
23+
using stdJson for string;
24+
25+
struct NetworkAddresses {
26+
address bridge;
27+
address configuration;
28+
address lockReleasePool;
29+
address burnMintPool;
30+
address lockableToken;
31+
address burnMintToken;
32+
}
33+
34+
struct ChainInfo {
35+
uint64 chainSelector;
36+
NetworkAddresses addresses;
37+
}
38+
39+
function loadNetworkAddresses(string memory networkName) internal view returns (NetworkAddresses memory) {
40+
string memory fileName = string(abi.encodePacked("./script/addresses-", networkName, ".json"));
41+
string memory json = vm.readFile(fileName);
42+
43+
return NetworkAddresses({
44+
bridge: json.readAddress(".bridge"),
45+
configuration: json.readAddress(".configuration"),
46+
lockReleasePool: json.readAddress(".lockReleasePool"),
47+
burnMintPool: json.readAddress(".burnMintPool"),
48+
lockableToken: json.readAddress(".lockableToken"),
49+
burnMintToken: json.readAddress(".burnMintToken")
50+
});
51+
}
52+
53+
function getAllChainInfo() internal view returns (ChainInfo[3] memory) {
54+
ChainInfo[3] memory chains;
55+
56+
// Sepolia - use getNetworkConfig for chain selector
57+
chains[0] = ChainInfo({
58+
chainSelector: getNetworkConfig("Sepolia").chainSelector,
59+
addresses: loadNetworkAddresses("Sepolia")
60+
});
61+
62+
// ArbitrumSepolia - use getNetworkConfig for chain selector
63+
chains[1] = ChainInfo({
64+
chainSelector: getNetworkConfig("ArbitrumSepolia").chainSelector,
65+
addresses: loadNetworkAddresses("ArbitrumSepolia")
66+
});
67+
68+
// Fuji - use getNetworkConfig for chain selector
69+
chains[2] = ChainInfo({
70+
chainSelector: getNetworkConfig("Fuji").chainSelector,
71+
addresses: loadNetworkAddresses("Fuji")
72+
});
73+
74+
return chains;
75+
}
76+
77+
function setRemoteBridges(
78+
Configuration configuration,
79+
string memory currentNetworkName,
80+
ChainInfo[3] memory allChains
81+
) internal {
82+
// Get current chain selector from network config (single source of truth)
83+
uint64 currentChainSelector = getNetworkConfig(currentNetworkName).chainSelector;
84+
for (uint256 i = 0; i < allChains.length; i++) {
85+
// Skip self
86+
if (allChains[i].chainSelector != currentChainSelector) {
87+
configuration.setRemoteBridge(
88+
allChains[i].chainSelector,
89+
allChains[i].addresses.bridge
90+
);
91+
92+
configuration.setExtraArgs(
93+
allChains[i].chainSelector,
94+
Client._argsToBytes(
95+
Client.GenericExtraArgsV2({
96+
gasLimit: 300_000,
97+
allowOutOfOrderExecution: true
98+
})
99+
)
100+
);
101+
}
102+
}
103+
}
104+
105+
function configureSepoliaDestinationTokens(
106+
Configuration sepoliaConfig,
107+
ChainInfo[3] memory allChains
108+
) internal {
109+
console.log("Setting destination tokens for Sepolia");
110+
111+
// Find indices for each chain
112+
uint256 sepoliaIndex = 0;
113+
uint256 arbitrumIndex = 1;
114+
uint256 fujiIndex = 2;
115+
116+
// Lock and Release with Fuji
117+
sepoliaConfig.setDestinationToken(
118+
MockERC20(allChains[sepoliaIndex].addresses.lockableToken),
119+
allChains[fujiIndex].chainSelector,
120+
MockERC20(allChains[fujiIndex].addresses.lockableToken)
121+
);
122+
123+
// Burn and Mint with Fuji
124+
sepoliaConfig.setDestinationToken(
125+
IERC20(address(BurnMintERC20(allChains[sepoliaIndex].addresses.burnMintToken))),
126+
allChains[fujiIndex].chainSelector,
127+
IERC20(address(BurnMintERC20(allChains[fujiIndex].addresses.burnMintToken)))
128+
);
129+
130+
// Lock and Mint with ArbitrumSepolia
131+
sepoliaConfig.setDestinationToken(
132+
IERC20(address(MockERC20(allChains[sepoliaIndex].addresses.lockableToken))),
133+
allChains[arbitrumIndex].chainSelector,
134+
IERC20(address(BurnMintERC20(allChains[arbitrumIndex].addresses.burnMintToken)))
135+
);
136+
}
137+
138+
function configureArbitrumSepoliaDestinationTokens(
139+
Configuration arbitrumConfig,
140+
ChainInfo[3] memory allChains
141+
) internal {
142+
console.log("Setting destination tokens for ArbitrumSepolia");
143+
144+
// Find indices for each chain
145+
uint256 sepoliaIndex = 0;
146+
uint256 arbitrumIndex = 1;
147+
148+
// Burn and Release with Sepolia
149+
arbitrumConfig.setDestinationToken(
150+
IERC20(address(BurnMintERC20(allChains[arbitrumIndex].addresses.burnMintToken))),
151+
allChains[sepoliaIndex].chainSelector,
152+
IERC20(address(MockERC20(allChains[sepoliaIndex].addresses.lockableToken)))
153+
);
154+
}
155+
156+
function configureFujiDestinationTokens(
157+
Configuration fujiConfig,
158+
ChainInfo[3] memory allChains
159+
) internal {
160+
console.log("Setting destination tokens for Fuji");
161+
162+
// Find indices for each chain
163+
uint256 sepoliaIndex = 0;
164+
uint256 fujiIndex = 2;
165+
166+
// Lock and Release with Sepolia
167+
fujiConfig.setDestinationToken(
168+
MockERC20(allChains[fujiIndex].addresses.lockableToken),
169+
allChains[sepoliaIndex].chainSelector,
170+
MockERC20(allChains[sepoliaIndex].addresses.lockableToken)
171+
);
172+
173+
// Burn and Mint with Sepolia
174+
fujiConfig.setDestinationToken(
175+
IERC20(address(BurnMintERC20(allChains[fujiIndex].addresses.burnMintToken))),
176+
allChains[sepoliaIndex].chainSelector,
177+
IERC20(address(BurnMintERC20(allChains[sepoliaIndex].addresses.burnMintToken)))
178+
);
179+
}
180+
}

0 commit comments

Comments
 (0)