Skip to content

Commit 67f1f54

Browse files
authored
feat: prepare mainnet (#84)
1 parent 4974fa7 commit 67f1f54

File tree

8 files changed

+300
-140
lines changed

8 files changed

+300
-140
lines changed

.env.template

Lines changed: 31 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,39 @@
1-
CI=false # Set to true if you want to run the make file commands in CI mode (will use --private-key instead of --account option)
1+
# ===========================================
2+
# CI/CD CONFIGURATION
3+
# ===========================================
4+
# Set to true in CI environments
5+
CI=false
26

7+
# ===========================================
8+
# DEPLOYMENT CONFIGURATION
9+
# ===========================================
10+
# Account name in your Foundry wallet for deployment
11+
ACCOUNT=default
12+
13+
# ===========================================
14+
# RPC_URL CONFIGURATION
15+
# ===========================================
16+
ETHEREUM_RPC_URL="https://gateway.tenderly.co/public/mainnet"
17+
ARBITRUM_RPC_URL="https://arbitrum.gateway.tenderly.co"
318
SEPOLIA_RPC_URL="https://gateway.tenderly.co/public/sepolia"
419
ARBITRUM_SEPOLIA_RPC_URL="https://arbitrum-sepolia.gateway.tenderly.co"
520

21+
# ===========================================
22+
# ANVIL LOCAL TESTING CONFIGURATION
23+
# ===========================================
24+
ANVIL_SEPOLIA_RPC_URL=http://localhost:8545
25+
ANVIL_ARBITRUM_SEPOLIA_RPC_URL=http://localhost:8546
26+
27+
# ===========================================
28+
# VERIFICATION CONFIGURATION
29+
# ===========================================
630
# Etherscan v2 uses a single API key for all networks.
731
ETHERSCAN_API_URL=https://api.etherscan.io/v2/api
32+
# Etherscan API key for contract verification
833
ETHERSCAN_API_KEY=
934

10-
# Local dev config.
11-
ANVIL_SEPOLIA_RPC_URL=http://localhost:8545
12-
ANVIL_ARBITRUM_SEPOLIA_RPC_URL=http://localhost:8546
13-
14-
# Account to be used for script execution.
15-
# Account name in Foundry keystore
16-
ACCOUNT=<your-account-name>
17-
RECIPIENT_ADDRESS=<recipient-address> # for cross-chain transfers scripts
35+
# ===========================================
36+
# TRANSFER CONFIGURATION
37+
# ===========================================
38+
# Recipient address for cross-chain transfers
39+
RECIPIENT_ADDRESS=

.github/workflows/deploy.yml

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -85,5 +85,9 @@ jobs:
8585

8686
- name: Verify contracts
8787
if: inputs.network != 'anvil'
88+
continue-on-error: true
89+
env:
90+
ETHERSCAN_API_KEY: ${{ secrets.ETHERSCAN_API_KEY }}
91+
RPC_URL: ${{ secrets.RPC_URL }}
8892
run: |
89-
echo "TODO: Implement contract verification for ${{ inputs.network }}."
93+
make verify-all-${{ inputs.network }}

Makefile

Lines changed: 35 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -52,31 +52,28 @@ deploy-on-anvil:
5252
$(MAKE) deploy-all \
5353
SOURCE_CHAIN=sepolia SOURCE_RPC=$(ANVIL_SEPOLIA_RPC_URL) \
5454
TARGET_CHAIN=arbitrum_sepolia TARGET_RPC=$(ANVIL_ARBITRUM_SEPOLIA_RPC_URL) \
55-
OPTIONS=
5655

5756
deploy-on-mainnets:
5857
$(MAKE) deploy-all \
5958
SOURCE_CHAIN=ethereum SOURCE_RPC=$(ETHEREUM_RPC_URL) \
6059
TARGET_CHAIN=arbitrum TARGET_RPC=$(ARBITRUM_RPC_URL) \
61-
OPTIONS=--verify
6260

6361
deploy-on-testnets:
6462
$(MAKE) deploy-all \
6563
SOURCE_CHAIN=sepolia SOURCE_RPC=$(SEPOLIA_RPC_URL) \
6664
TARGET_CHAIN=arbitrum_sepolia TARGET_RPC=$(ARBITRUM_SEPOLIA_RPC_URL) \
67-
OPTIONS="--verify --verifier etherscan --verifier-api-key $(ETHERSCAN_API_KEY) --verifier-url $(ETHERSCAN_API_URL)"
6865

6966
deploy-liquidity-unifier-and-bridge:
70-
$(MAKE) deploy-contract CONTRACT=RLCLiquidityUnifier CHAIN=$(CHAIN) RPC_URL=$(RPC_URL) OPTIONS="$(OPTIONS)"
71-
$(MAKE) deploy-contract CONTRACT=bridges/layerZero/IexecLayerZeroBridge CHAIN=$(CHAIN) RPC_URL=$(RPC_URL) OPTIONS="$(OPTIONS)"
67+
$(MAKE) deploy-contract CONTRACT=RLCLiquidityUnifier CHAIN=$(CHAIN) RPC_URL=$(RPC_URL)
68+
$(MAKE) deploy-contract CONTRACT=bridges/layerZero/IexecLayerZeroBridge CHAIN=$(CHAIN) RPC_URL=$(RPC_URL)
7269

7370
deploy-crosschain-token-and-bridge:
74-
$(MAKE) deploy-contract CONTRACT=RLCCrosschainToken CHAIN=$(CHAIN) RPC_URL=$(RPC_URL) OPTIONS="$(OPTIONS)"
75-
$(MAKE) deploy-contract CONTRACT=bridges/layerZero/IexecLayerZeroBridge CHAIN=$(CHAIN) RPC_URL=$(RPC_URL) OPTIONS="$(OPTIONS)"
71+
$(MAKE) deploy-contract CONTRACT=RLCCrosschainToken CHAIN=$(CHAIN) RPC_URL=$(RPC_URL)
72+
$(MAKE) deploy-contract CONTRACT=bridges/layerZero/IexecLayerZeroBridge CHAIN=$(CHAIN) RPC_URL=$(RPC_URL)
7673

77-
deploy-all: # SOURCE_CHAIN, SOURCE_RPC, TARGET_CHAIN, TARGET_RPC, OPTIONS
78-
$(MAKE) deploy-liquidity-unifier-and-bridge CHAIN=$(SOURCE_CHAIN) RPC_URL=$(SOURCE_RPC) OPTIONS=$(OPTIONS)
79-
$(MAKE) deploy-crosschain-token-and-bridge CHAIN=$(TARGET_CHAIN) RPC_URL=$(TARGET_RPC) OPTIONS=$(OPTIONS)
74+
deploy-all: # SOURCE_CHAIN, SOURCE_RPC, TARGET_CHAIN, TARGET_RPC
75+
$(MAKE) deploy-liquidity-unifier-and-bridge CHAIN=$(SOURCE_CHAIN) RPC_URL=$(SOURCE_RPC)
76+
$(MAKE) deploy-crosschain-token-and-bridge CHAIN=$(TARGET_CHAIN) RPC_URL=$(TARGET_RPC)
8077
@echo "Contracts deployment completed."
8178
@echo "⚠️ Run 'make configure-all' to configure bridges."
8279
@echo "⚠️ Please configure the bridges. Do not forget to authorize the RLCLiquidityUnifier and RLCCrosschainToken contracts on the bridges."
@@ -99,43 +96,39 @@ upgrade-on-mainnets:
9996
$(MAKE) upgrade-all \
10097
SOURCE_CHAIN=ethereum SOURCE_RPC=$(ETHEREUM_RPC_URL) \
10198
TARGET_CHAIN=arbitrum TARGET_RPC=$(ARBITRUM_RPC_URL) \
102-
OPTIONS=--verify
10399

104100
# TODO : RLCMultichain and RLCLiquidityUnifier upgrades
105101
upgrade-on-testnets:
106102
$(MAKE) upgrade-all \
107103
SOURCE_CHAIN=sepolia SOURCE_RPC=$(SEPOLIA_RPC_URL) \
108104
TARGET_CHAIN=arbitrum_sepolia TARGET_RPC=$(ARBITRUM_SEPOLIA_RPC_URL) \
109-
OPTIONS=--verify
110105

111-
upgrade-all: # SOURCE_CHAIN, SOURCE_RPC, TARGET_CHAIN, TARGET_RPC, OPTIONS
112-
$(MAKE) upgrade-contract CONTRACT=bridges/layerZero/IexecLayerZeroBridge CHAIN=$(SOURCE_CHAIN) RPC_URL=$(SOURCE_RPC) OPTIONS=$(OPTIONS)
113-
$(MAKE) upgrade-contract CONTRACT=bridges/layerZero/IexecLayerZeroBridge CHAIN=$(TARGET_CHAIN) RPC_URL=$(TARGET_RPC) OPTIONS=$(OPTIONS)
106+
upgrade-all: # SOURCE_CHAIN, SOURCE_RPC, TARGET_CHAIN, TARGET_RPC
107+
$(MAKE) upgrade-contract CONTRACT=bridges/layerZero/IexecLayerZeroBridge CHAIN=$(SOURCE_CHAIN) RPC_URL=$(SOURCE_RPC)
108+
$(MAKE) upgrade-contract CONTRACT=bridges/layerZero/IexecLayerZeroBridge CHAIN=$(TARGET_CHAIN) RPC_URL=$(TARGET_RPC)
114109

115110
#
116111
# Generic deployment targets
117112
#
118113

119-
deploy-contract: # CONTRACT, CHAIN, RPC_URL, OPTIONS
120-
@echo "Deploying $(CONTRACT) on $(CHAIN) with options: $(OPTIONS)"
114+
deploy-contract: # CONTRACT, CHAIN, RPC_URL
115+
@echo "Deploying $(CONTRACT) on $(CHAIN)"
121116
CHAIN=$(CHAIN) forge script script/$(CONTRACT).s.sol:Deploy \
122117
--rpc-url $(RPC_URL) \
123118
$$(if [ "$(CI)" = "true" ]; then echo "--private-key $(DEPLOYER_PRIVATE_KEY)"; else echo "--account $(ACCOUNT)"; fi) \
124-
$(OPTIONS) \
125119
--broadcast \
126120
-vvv
127121

128122
#
129123
# Generic upgrade targets
130124
#
131125

132-
upgrade-contract: # CONTRACT, CHAIN, RPC_URL, OPTIONS
133-
@echo "Upgrading $(CONTRACT) on $(CHAIN) with options: $(OPTIONS)"
126+
upgrade-contract: # CONTRACT, CHAIN, RPC_URL
127+
@echo "Upgrading $(CONTRACT) on $(CHAIN)"
134128
CHAIN=$(CHAIN) forge script script/$(CONTRACT).s.sol:Upgrade \
135129
--rpc-url $(RPC_URL) \
136130
--account $(ACCOUNT) \
137131
--broadcast \
138-
$(OPTIONS) \
139132
-vvv
140133

141134
#
@@ -162,6 +155,8 @@ upgrade-layerzero-bridge: # CHAIN, RPC_URL
162155
# Bridge operations.
163156
#
164157

158+
# Testnet bridge operations
159+
165160
send-tokens-to-arbitrum-sepolia:
166161
@echo "Sending tokens cross-chain... from SEPOLIA to Arbitrum SEPOLIA"
167162
SOURCE_CHAIN=sepolia TARGET_CHAIN=arbitrum_sepolia \
@@ -179,3 +174,22 @@ send-tokens-to-sepolia:
179174
--account $(ACCOUNT) \
180175
--broadcast \
181176
-vvv
177+
178+
# Mainnet bridge operations
179+
send-tokens-to-arbitrum-mainnet:
180+
@echo "Sending tokens cross-chain... from ETHEREUM to Arbitrum MAINNET"
181+
SOURCE_CHAIN=ethereum TARGET_CHAIN=arbitrum \
182+
forge script script/SendFromEthereumToArbitrum.s.sol:SendFromEthereumToArbitrum \
183+
--rpc-url $(ETHEREUM_RPC_URL) \
184+
--account $(ACCOUNT) \
185+
--broadcast \
186+
-vvv
187+
188+
send-tokens-to-ethereum-mainnet:
189+
@echo "Sending tokens cross-chain... from Arbitrum MAINNET to ETHEREUM"
190+
SOURCE_CHAIN=arbitrum TARGET_CHAIN=ethereum \
191+
forge script script/SendFromArbitrumToEthereum.s.sol:SendFromArbitrumToEthereum \
192+
--rpc-url $(ARBITRUM_RPC_URL) \
193+
--account $(ACCOUNT) \
194+
--broadcast \
195+
-vvv

README.md

Lines changed: 31 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -116,7 +116,17 @@ The core contracts of the multichain bridge system:
116116

117117
## Usage
118118

119-
### Bridge RLC
119+
### Network Support
120+
121+
The bridge currently supports:
122+
123+
#### **Testnets**
124+
- **Ethereum Sepolia****Arbitrum Sepolia**
125+
126+
#### **Mainnets**
127+
- **Ethereum Mainnet****Arbitrum Mainnet**
128+
129+
### Bridge RLC on Testnets
120130

121131
A. To send RLC tokens from Ethereum Sepolia to Arbitrum Sepolia:
122132

@@ -144,6 +154,20 @@ This will:
144154
2. Send a cross-chain message via LayerZero to Ethereum
145155
3. Release the original RLC tokens from the RLCLiquidityUnifier on Ethereum
146156
157+
### Bridge RLC on Mainnets
158+
159+
A. To send RLC tokens from Ethereum Mainnet to Arbitrum Mainnet:
160+
161+
```bash
162+
make send-tokens-to-arbitrum-mainnet
163+
```
164+
165+
B. To send RLC tokens from Arbitrum Mainnet back to Ethereum Mainnet:
166+
167+
```bash
168+
make send-tokens-to-ethereum-mainnet
169+
```
170+
147171
## 📊 Code Coverage Analysis
148172
149173
### Generating Coverage Reports
@@ -314,3 +338,9 @@ The scripts automatically calculate these fees and include them in the transacti
314338
- Use an enterprise RPC URL for `secrets.SEPOLIA_RPC_URL` in Github environment `ci`.
315339
- Add git pre-commit hook to format code locally.
316340
- Testing Documentation
341+
- Parametrize config.json to not overide btw mainnet and testnets
342+
```
343+
"initialAdmin": "0x111165a109feca14e4ad4d805f6460c7d206ead1",
344+
"initialUpgrader": "0x111121e2ec2557f484f65d5b1ad2b6b07b8acd23",
345+
"initialPauser": "0x11113fe3513787f5a4f5f19690700e2736b3056e",
346+
```

config/config.json

Lines changed: 22 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
{
22
"createxFactory": "0xba5Ed099633D3B313e4D5F7bdc1305d3c28ba5Ed",
3-
"initialAdmin": "0x9990cfb1Feb7f47297F54bef4d4EbeDf6c5463a3",
4-
"initialUpgrader": "0x9990cfb1Feb7f47297F54bef4d4EbeDf6c5463a3",
5-
"initialPauser": "0x9990cfb1Feb7f47297F54bef4d4EbeDf6c5463a3",
3+
"initialAdmin": "0x111165a109feca14e4ad4d805f6460c7d206ead1",
4+
"initialUpgrader": "0x111121e2ec2557f484f65d5b1ad2b6b07b8acd23",
5+
"initialPauser": "0x11113fe3513787f5a4f5f19690700e2736b3056e",
66
"chains": {
77
"sepolia": {
88
"approvalRequired": true,
@@ -22,6 +22,25 @@
2222
"iexecLayerZeroBridgeCreatexSalt": "0x4f2784ad07b2be2a5c5e466c91d758133f4aa33bd4cf09ddba1a1e1035e57875",
2323
"lzEndpointAddress": "0x6EDCE65403992e310A62460808c4b910D972f10f",
2424
"lzChainId": 40231
25+
},
26+
"ethereum": {
27+
"approvalRequired": true,
28+
"rlcAddress": "0x607F4C5BB672230e8672085532f7e901544a7375",
29+
"rlcLiquidityUnifierAddress": "0x0000000000000000000000000000000000000000",
30+
"rlcLiquidityUnifierCreatexSalt": "0x0000000000000000000000000000000000000000000000000000000000000000",
31+
"iexecLayerZeroBridgeAddress": "0x0000000000000000000000000000000000000000",
32+
"iexecLayerZeroBridgeCreatexSalt": "0x0000000000000000000000000000000000000000000000000000000000000000",
33+
"lzEndpointAddress": "0x1a44076050125825900e736c501f859c50fE728c",
34+
"lzChainId": 30101
35+
},
36+
"arbitrum": {
37+
"approvalRequired": false,
38+
"rlcCrosschainTokenAddress": "0x0000000000000000000000000000000000000000",
39+
"rlcCrosschainTokenCreatexSalt": "0x0000000000000000000000000000000000000000000000000000000000000000",
40+
"iexecLayerZeroBridgeAddress": "0x0000000000000000000000000000000000000000",
41+
"iexecLayerZeroBridgeCreatexSalt": "0x0000000000000000000000000000000000000000000000000000000000000000",
42+
"lzEndpointAddress": "0x1a44076050125825900e736c501f859c50fE728c",
43+
"lzChainId": 30110
2544
}
2645
}
2746
}

script/SendFromArbitrumToEthereum.s.sol

Lines changed: 48 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,20 @@
33
pragma solidity ^0.8.22;
44

55
import {Script, console} from "forge-std/Script.sol";
6+
import {ConfigLib} from "./lib/ConfigLib.sol";
7+
import {IexecLayerZeroBridge} from "../src/bridges/layerZero/IexecLayerZeroBridge.sol";
8+
import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
69
import {SendParam} from "@layerzerolabs/oft-evm/contracts/interfaces/IOFT.sol";
710
import {MessagingFee} from "@layerzerolabs/oapp-evm/contracts/oapp/OApp.sol";
8-
import {IexecLayerZeroBridge} from "../src/bridges/layerZero/IexecLayerZeroBridge.sol";
9-
import {ConfigLib} from "./lib/ConfigLib.sol";
1011

12+
/**
13+
* Script to send tokens from Arbitrum Mainnet to Ethereum Mainnet.
14+
* This script demonstrates cross-chain token transfers using LayerZero bridge.
15+
*/
16+
// TODO: fusion SendTokensFromArbitrumToEthereum and SendTokensFromEthereumToArbitrum into a single script with dynamic chain handling
1117
contract SendTokensFromArbitrumToEthereum is Script {
18+
uint256 private constant TRANSFER_AMOUNT = 10 * 10 ** 9; // 10 RLC tokens with 9 decimals
19+
1220
/**
1321
* @dev Converts an address to bytes32.
1422
* @param _addr The address to convert.
@@ -18,45 +26,59 @@ contract SendTokensFromArbitrumToEthereum is Script {
1826
return bytes32(uint256(uint160(_addr)));
1927
}
2028

29+
/**
30+
* Main function to execute the cross-chain transfer.
31+
* Reads configuration and sends tokens from Arbitrum to Ethereum.
32+
*/
2133
function run() external {
2234
string memory sourceChain = vm.envString("SOURCE_CHAIN");
2335
string memory targetChain = vm.envString("TARGET_CHAIN");
2436

2537
ConfigLib.CommonConfigParams memory sourceParams = ConfigLib.readCommonConfig(sourceChain);
2638
ConfigLib.CommonConfigParams memory targetParams = ConfigLib.readCommonConfig(targetChain);
2739

28-
// Contract addresses
29-
address iexecLayerZeroBridgeAddress = sourceParams.iexecLayerZeroBridgeAddress;
30-
31-
// Transfer parameters
32-
uint16 destinationChainId = uint16(targetParams.lzChainId); // LayerZero chain ID for Ethereum Sepolia
33-
address recipientAddress = vm.envAddress("RECIPIENT_ADDRESS");
34-
console.log("Recipient: %s", recipientAddress);
40+
IexecLayerZeroBridge sourceBridge = IexecLayerZeroBridge(sourceParams.iexecLayerZeroBridgeAddress);
41+
IERC20 rlcToken = IERC20(sourceParams.rlcCrosschainTokenAddress);
3542

36-
uint256 amount = 5 * 10 ** 9; // RLC tokens (adjust the amount as needed)
43+
address sender = vm.envAddress("RECIPIENT_ADDRESS");
44+
address recipient = vm.envAddress("RECIPIENT_ADDRESS");
3745

38-
// Send tokens cross-chain
39-
IexecLayerZeroBridge iexecLayerZeroBridge = IexecLayerZeroBridge(iexecLayerZeroBridgeAddress);
40-
console.log("Sending %s RLC to Ethereum Sepolia", amount / 10 ** 9);
46+
// Check sender's balance
47+
uint256 senderBalance = rlcToken.balanceOf(sender);
48+
require(senderBalance >= TRANSFER_AMOUNT, "Insufficient RLC balance");
4149

50+
// Prepare send parameters
4251
SendParam memory sendParam = SendParam(
43-
destinationChainId, // Destination endpoint ID.
44-
addressToBytes32(recipientAddress), // Recipient address.
45-
amount, // amount (in local decimals, e.g., 5 RLC = 5 * 10 ** 9)
46-
amount * 99 / 100, // minAmount (allowing 1% slippage)
47-
"", // Extra options, not used in this case, already setup using `setEnforcedOptions`
48-
"", // Composed message, not used in this case
49-
"" // OFT command to be executed, unused in default OFT implementations.
52+
uint16(targetParams.lzChainId), // Destination endpoint ID
53+
addressToBytes32(recipient), // Recipient address
54+
TRANSFER_AMOUNT, // Amount to send in local decimals
55+
TRANSFER_AMOUNT * 99 / 100, // Minimum amount to send (allowing 1% slippage)
56+
"", // Extra options, already set via setEnforcedOptions
57+
"", // Composed message for send() operation (unused)
58+
"" // OFT command to be executed (unused in default OFT)
5059
);
5160

52-
// Get the fee for the transfer
53-
MessagingFee memory fee = iexecLayerZeroBridge.quoteSend(sendParam, false);
54-
console.log("Fee amount: ", fee.nativeFee);
61+
// Get quote for the transfer
62+
MessagingFee memory fee = sourceBridge.quoteSend(sendParam, false);
63+
64+
console.log("=== Cross-Chain Transfer Details ===");
65+
console.log("From: Arbitrum Mainnet");
66+
console.log("To: Ethereum Mainnet");
67+
console.log("Amount: %d RLC", TRANSFER_AMOUNT / 10 ** 9);
68+
console.log("Fee: %d wei", fee.nativeFee);
69+
console.log("Sender: %s", sender);
70+
console.log("Recipient: %s", recipient);
71+
console.log("Sender Balance: %d RLC", senderBalance / 10 ** 9);
72+
5573
vm.startBroadcast();
56-
// Execute the cross-chain transfer
57-
iexecLayerZeroBridge.send{value: fee.nativeFee}(sendParam, fee, msg.sender);
5874

59-
console.log("Cross-chain transfer from Arbitrum to Ethereum initiated!");
75+
// Note: For crosschain tokens, no approval is needed as the bridge can burn directly
76+
console.log("Initiating cross-chain transfer...");
77+
sourceBridge.send{value: fee.nativeFee}(sendParam, fee, payable(sender));
78+
6079
vm.stopBroadcast();
80+
81+
console.log("Transfer initiated successfully!");
82+
console.log("Monitor the destination chain for token receipt.");
6183
}
6284
}

0 commit comments

Comments
 (0)