diff --git a/.env.template b/.env.template index 6c7e5927..ac8d8a98 100644 --- a/.env.template +++ b/.env.template @@ -1,17 +1,39 @@ -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) +# =========================================== +# CI/CD CONFIGURATION +# =========================================== +# Set to true in CI environments +CI=false +# =========================================== +# DEPLOYMENT CONFIGURATION +# =========================================== +# Account name in your Foundry wallet for deployment +ACCOUNT=default + +# =========================================== +# RPC_URL CONFIGURATION +# =========================================== +ETHEREUM_RPC_URL="https://gateway.tenderly.co/public/mainnet" +ARBITRUM_RPC_URL="https://arbitrum.gateway.tenderly.co" SEPOLIA_RPC_URL="https://gateway.tenderly.co/public/sepolia" ARBITRUM_SEPOLIA_RPC_URL="https://arbitrum-sepolia.gateway.tenderly.co" +# =========================================== +# ANVIL LOCAL TESTING CONFIGURATION +# =========================================== +ANVIL_SEPOLIA_RPC_URL=http://localhost:8545 +ANVIL_ARBITRUM_SEPOLIA_RPC_URL=http://localhost:8546 + +# =========================================== +# VERIFICATION CONFIGURATION +# =========================================== # Etherscan v2 uses a single API key for all networks. ETHERSCAN_API_URL=https://api.etherscan.io/v2/api +# Etherscan API key for contract verification ETHERSCAN_API_KEY= -# Local dev config. -ANVIL_SEPOLIA_RPC_URL=http://localhost:8545 -ANVIL_ARBITRUM_SEPOLIA_RPC_URL=http://localhost:8546 - -# Account to be used for script execution. -# Account name in Foundry keystore -ACCOUNT= -RECIPIENT_ADDRESS= # for cross-chain transfers scripts +# =========================================== +# TRANSFER CONFIGURATION +# =========================================== +# Recipient address for cross-chain transfers +RECIPIENT_ADDRESS= diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index cdaf4780..40415952 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -85,5 +85,9 @@ jobs: - name: Verify contracts if: inputs.network != 'anvil' + continue-on-error: true + env: + ETHERSCAN_API_KEY: ${{ secrets.ETHERSCAN_API_KEY }} + RPC_URL: ${{ secrets.RPC_URL }} run: | - echo "TODO: Implement contract verification for ${{ inputs.network }}." + make verify-all-${{ inputs.network }} diff --git a/Makefile b/Makefile index 798b8fbb..824e345a 100644 --- a/Makefile +++ b/Makefile @@ -52,31 +52,28 @@ deploy-on-anvil: $(MAKE) deploy-all \ SOURCE_CHAIN=sepolia SOURCE_RPC=$(ANVIL_SEPOLIA_RPC_URL) \ TARGET_CHAIN=arbitrum_sepolia TARGET_RPC=$(ANVIL_ARBITRUM_SEPOLIA_RPC_URL) \ - OPTIONS= deploy-on-mainnets: $(MAKE) deploy-all \ SOURCE_CHAIN=ethereum SOURCE_RPC=$(ETHEREUM_RPC_URL) \ TARGET_CHAIN=arbitrum TARGET_RPC=$(ARBITRUM_RPC_URL) \ - OPTIONS=--verify deploy-on-testnets: $(MAKE) deploy-all \ SOURCE_CHAIN=sepolia SOURCE_RPC=$(SEPOLIA_RPC_URL) \ TARGET_CHAIN=arbitrum_sepolia TARGET_RPC=$(ARBITRUM_SEPOLIA_RPC_URL) \ - OPTIONS="--verify --verifier etherscan --verifier-api-key $(ETHERSCAN_API_KEY) --verifier-url $(ETHERSCAN_API_URL)" deploy-liquidity-unifier-and-bridge: - $(MAKE) deploy-contract CONTRACT=RLCLiquidityUnifier CHAIN=$(CHAIN) RPC_URL=$(RPC_URL) OPTIONS="$(OPTIONS)" - $(MAKE) deploy-contract CONTRACT=bridges/layerZero/IexecLayerZeroBridge CHAIN=$(CHAIN) RPC_URL=$(RPC_URL) OPTIONS="$(OPTIONS)" + $(MAKE) deploy-contract CONTRACT=RLCLiquidityUnifier CHAIN=$(CHAIN) RPC_URL=$(RPC_URL) + $(MAKE) deploy-contract CONTRACT=bridges/layerZero/IexecLayerZeroBridge CHAIN=$(CHAIN) RPC_URL=$(RPC_URL) deploy-crosschain-token-and-bridge: - $(MAKE) deploy-contract CONTRACT=RLCCrosschainToken CHAIN=$(CHAIN) RPC_URL=$(RPC_URL) OPTIONS="$(OPTIONS)" - $(MAKE) deploy-contract CONTRACT=bridges/layerZero/IexecLayerZeroBridge CHAIN=$(CHAIN) RPC_URL=$(RPC_URL) OPTIONS="$(OPTIONS)" + $(MAKE) deploy-contract CONTRACT=RLCCrosschainToken CHAIN=$(CHAIN) RPC_URL=$(RPC_URL) + $(MAKE) deploy-contract CONTRACT=bridges/layerZero/IexecLayerZeroBridge CHAIN=$(CHAIN) RPC_URL=$(RPC_URL) -deploy-all: # SOURCE_CHAIN, SOURCE_RPC, TARGET_CHAIN, TARGET_RPC, OPTIONS - $(MAKE) deploy-liquidity-unifier-and-bridge CHAIN=$(SOURCE_CHAIN) RPC_URL=$(SOURCE_RPC) OPTIONS=$(OPTIONS) - $(MAKE) deploy-crosschain-token-and-bridge CHAIN=$(TARGET_CHAIN) RPC_URL=$(TARGET_RPC) OPTIONS=$(OPTIONS) +deploy-all: # SOURCE_CHAIN, SOURCE_RPC, TARGET_CHAIN, TARGET_RPC + $(MAKE) deploy-liquidity-unifier-and-bridge CHAIN=$(SOURCE_CHAIN) RPC_URL=$(SOURCE_RPC) + $(MAKE) deploy-crosschain-token-and-bridge CHAIN=$(TARGET_CHAIN) RPC_URL=$(TARGET_RPC) @echo "Contracts deployment completed." @echo "⚠️ Run 'make configure-all' to configure bridges." @echo "⚠️ Please configure the bridges. Do not forget to authorize the RLCLiquidityUnifier and RLCCrosschainToken contracts on the bridges." @@ -99,29 +96,26 @@ upgrade-on-mainnets: $(MAKE) upgrade-all \ SOURCE_CHAIN=ethereum SOURCE_RPC=$(ETHEREUM_RPC_URL) \ TARGET_CHAIN=arbitrum TARGET_RPC=$(ARBITRUM_RPC_URL) \ - OPTIONS=--verify # TODO : RLCMultichain and RLCLiquidityUnifier upgrades upgrade-on-testnets: $(MAKE) upgrade-all \ SOURCE_CHAIN=sepolia SOURCE_RPC=$(SEPOLIA_RPC_URL) \ TARGET_CHAIN=arbitrum_sepolia TARGET_RPC=$(ARBITRUM_SEPOLIA_RPC_URL) \ - OPTIONS=--verify -upgrade-all: # SOURCE_CHAIN, SOURCE_RPC, TARGET_CHAIN, TARGET_RPC, OPTIONS - $(MAKE) upgrade-contract CONTRACT=bridges/layerZero/IexecLayerZeroBridge CHAIN=$(SOURCE_CHAIN) RPC_URL=$(SOURCE_RPC) OPTIONS=$(OPTIONS) - $(MAKE) upgrade-contract CONTRACT=bridges/layerZero/IexecLayerZeroBridge CHAIN=$(TARGET_CHAIN) RPC_URL=$(TARGET_RPC) OPTIONS=$(OPTIONS) +upgrade-all: # SOURCE_CHAIN, SOURCE_RPC, TARGET_CHAIN, TARGET_RPC + $(MAKE) upgrade-contract CONTRACT=bridges/layerZero/IexecLayerZeroBridge CHAIN=$(SOURCE_CHAIN) RPC_URL=$(SOURCE_RPC) + $(MAKE) upgrade-contract CONTRACT=bridges/layerZero/IexecLayerZeroBridge CHAIN=$(TARGET_CHAIN) RPC_URL=$(TARGET_RPC) # # Generic deployment targets # -deploy-contract: # CONTRACT, CHAIN, RPC_URL, OPTIONS - @echo "Deploying $(CONTRACT) on $(CHAIN) with options: $(OPTIONS)" +deploy-contract: # CONTRACT, CHAIN, RPC_URL + @echo "Deploying $(CONTRACT) on $(CHAIN)" CHAIN=$(CHAIN) forge script script/$(CONTRACT).s.sol:Deploy \ --rpc-url $(RPC_URL) \ $$(if [ "$(CI)" = "true" ]; then echo "--private-key $(DEPLOYER_PRIVATE_KEY)"; else echo "--account $(ACCOUNT)"; fi) \ - $(OPTIONS) \ --broadcast \ -vvv @@ -129,13 +123,12 @@ deploy-contract: # CONTRACT, CHAIN, RPC_URL, OPTIONS # Generic upgrade targets # -upgrade-contract: # CONTRACT, CHAIN, RPC_URL, OPTIONS - @echo "Upgrading $(CONTRACT) on $(CHAIN) with options: $(OPTIONS)" +upgrade-contract: # CONTRACT, CHAIN, RPC_URL + @echo "Upgrading $(CONTRACT) on $(CHAIN)" CHAIN=$(CHAIN) forge script script/$(CONTRACT).s.sol:Upgrade \ --rpc-url $(RPC_URL) \ --account $(ACCOUNT) \ --broadcast \ - $(OPTIONS) \ -vvv # @@ -162,6 +155,8 @@ upgrade-layerzero-bridge: # CHAIN, RPC_URL # Bridge operations. # +# Testnet bridge operations + send-tokens-to-arbitrum-sepolia: @echo "Sending tokens cross-chain... from SEPOLIA to Arbitrum SEPOLIA" SOURCE_CHAIN=sepolia TARGET_CHAIN=arbitrum_sepolia \ @@ -179,3 +174,22 @@ send-tokens-to-sepolia: --account $(ACCOUNT) \ --broadcast \ -vvv + +# Mainnet bridge operations +send-tokens-to-arbitrum-mainnet: + @echo "Sending tokens cross-chain... from ETHEREUM to Arbitrum MAINNET" + SOURCE_CHAIN=ethereum TARGET_CHAIN=arbitrum \ + forge script script/SendFromEthereumToArbitrum.s.sol:SendFromEthereumToArbitrum \ + --rpc-url $(ETHEREUM_RPC_URL) \ + --account $(ACCOUNT) \ + --broadcast \ + -vvv + +send-tokens-to-ethereum-mainnet: + @echo "Sending tokens cross-chain... from Arbitrum MAINNET to ETHEREUM" + SOURCE_CHAIN=arbitrum TARGET_CHAIN=ethereum \ + forge script script/SendFromArbitrumToEthereum.s.sol:SendFromArbitrumToEthereum \ + --rpc-url $(ARBITRUM_RPC_URL) \ + --account $(ACCOUNT) \ + --broadcast \ + -vvv diff --git a/README.md b/README.md index 8f0110db..62145900 100644 --- a/README.md +++ b/README.md @@ -116,7 +116,17 @@ The core contracts of the multichain bridge system: ## Usage -### Bridge RLC +### Network Support + +The bridge currently supports: + +#### **Testnets** +- **Ethereum Sepolia** ↔ **Arbitrum Sepolia** + +#### **Mainnets** +- **Ethereum Mainnet** ↔ **Arbitrum Mainnet** + +### Bridge RLC on Testnets A. To send RLC tokens from Ethereum Sepolia to Arbitrum Sepolia: @@ -144,6 +154,20 @@ This will: 2. Send a cross-chain message via LayerZero to Ethereum 3. Release the original RLC tokens from the RLCLiquidityUnifier on Ethereum +### Bridge RLC on Mainnets + +A. To send RLC tokens from Ethereum Mainnet to Arbitrum Mainnet: + +```bash +make send-tokens-to-arbitrum-mainnet +``` + +B. To send RLC tokens from Arbitrum Mainnet back to Ethereum Mainnet: + +```bash +make send-tokens-to-ethereum-mainnet +``` + ## 📊 Code Coverage Analysis ### Generating Coverage Reports @@ -314,3 +338,9 @@ The scripts automatically calculate these fees and include them in the transacti - Use an enterprise RPC URL for `secrets.SEPOLIA_RPC_URL` in Github environment `ci`. - Add git pre-commit hook to format code locally. - Testing Documentation +- Parametrize config.json to not overide btw mainnet and testnets +``` + "initialAdmin": "0x111165a109feca14e4ad4d805f6460c7d206ead1", + "initialUpgrader": "0x111121e2ec2557f484f65d5b1ad2b6b07b8acd23", + "initialPauser": "0x11113fe3513787f5a4f5f19690700e2736b3056e", +``` \ No newline at end of file diff --git a/config/config.json b/config/config.json index c2845261..07cb9ae8 100644 --- a/config/config.json +++ b/config/config.json @@ -1,8 +1,8 @@ { "createxFactory": "0xba5Ed099633D3B313e4D5F7bdc1305d3c28ba5Ed", - "initialAdmin": "0x9990cfb1Feb7f47297F54bef4d4EbeDf6c5463a3", - "initialUpgrader": "0x9990cfb1Feb7f47297F54bef4d4EbeDf6c5463a3", - "initialPauser": "0x9990cfb1Feb7f47297F54bef4d4EbeDf6c5463a3", + "initialAdmin": "0x111165a109feca14e4ad4d805f6460c7d206ead1", + "initialUpgrader": "0x111121e2ec2557f484f65d5b1ad2b6b07b8acd23", + "initialPauser": "0x11113fe3513787f5a4f5f19690700e2736b3056e", "chains": { "sepolia": { "approvalRequired": true, @@ -22,6 +22,25 @@ "iexecLayerZeroBridgeCreatexSalt": "0x4f2784ad07b2be2a5c5e466c91d758133f4aa33bd4cf09ddba1a1e1035e57875", "lzEndpointAddress": "0x6EDCE65403992e310A62460808c4b910D972f10f", "lzChainId": 40231 + }, + "ethereum": { + "approvalRequired": true, + "rlcAddress": "0x607F4C5BB672230e8672085532f7e901544a7375", + "rlcLiquidityUnifierAddress": "0x0000000000000000000000000000000000000000", + "rlcLiquidityUnifierCreatexSalt": "0x0000000000000000000000000000000000000000000000000000000000000000", + "iexecLayerZeroBridgeAddress": "0x0000000000000000000000000000000000000000", + "iexecLayerZeroBridgeCreatexSalt": "0x0000000000000000000000000000000000000000000000000000000000000000", + "lzEndpointAddress": "0x1a44076050125825900e736c501f859c50fE728c", + "lzChainId": 30101 + }, + "arbitrum": { + "approvalRequired": false, + "rlcCrosschainTokenAddress": "0x0000000000000000000000000000000000000000", + "rlcCrosschainTokenCreatexSalt": "0x0000000000000000000000000000000000000000000000000000000000000000", + "iexecLayerZeroBridgeAddress": "0x0000000000000000000000000000000000000000", + "iexecLayerZeroBridgeCreatexSalt": "0x0000000000000000000000000000000000000000000000000000000000000000", + "lzEndpointAddress": "0x1a44076050125825900e736c501f859c50fE728c", + "lzChainId": 30110 } } } diff --git a/script/SendFromArbitrumToEthereum.s.sol b/script/SendFromArbitrumToEthereum.s.sol index 92daa76b..73b4a0e5 100644 --- a/script/SendFromArbitrumToEthereum.s.sol +++ b/script/SendFromArbitrumToEthereum.s.sol @@ -3,12 +3,20 @@ pragma solidity ^0.8.22; import {Script, console} from "forge-std/Script.sol"; +import {ConfigLib} from "./lib/ConfigLib.sol"; +import {IexecLayerZeroBridge} from "../src/bridges/layerZero/IexecLayerZeroBridge.sol"; +import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; import {SendParam} from "@layerzerolabs/oft-evm/contracts/interfaces/IOFT.sol"; import {MessagingFee} from "@layerzerolabs/oapp-evm/contracts/oapp/OApp.sol"; -import {IexecLayerZeroBridge} from "../src/bridges/layerZero/IexecLayerZeroBridge.sol"; -import {ConfigLib} from "./lib/ConfigLib.sol"; +/** + * Script to send tokens from Arbitrum Mainnet to Ethereum Mainnet. + * This script demonstrates cross-chain token transfers using LayerZero bridge. + */ +// TODO: fusion SendTokensFromArbitrumToEthereum and SendTokensFromEthereumToArbitrum into a single script with dynamic chain handling contract SendTokensFromArbitrumToEthereum is Script { + uint256 private constant TRANSFER_AMOUNT = 10 * 10 ** 9; // 10 RLC tokens with 9 decimals + /** * @dev Converts an address to bytes32. * @param _addr The address to convert. @@ -18,6 +26,10 @@ contract SendTokensFromArbitrumToEthereum is Script { return bytes32(uint256(uint160(_addr))); } + /** + * Main function to execute the cross-chain transfer. + * Reads configuration and sends tokens from Arbitrum to Ethereum. + */ function run() external { string memory sourceChain = vm.envString("SOURCE_CHAIN"); string memory targetChain = vm.envString("TARGET_CHAIN"); @@ -25,38 +37,48 @@ contract SendTokensFromArbitrumToEthereum is Script { ConfigLib.CommonConfigParams memory sourceParams = ConfigLib.readCommonConfig(sourceChain); ConfigLib.CommonConfigParams memory targetParams = ConfigLib.readCommonConfig(targetChain); - // Contract addresses - address iexecLayerZeroBridgeAddress = sourceParams.iexecLayerZeroBridgeAddress; - - // Transfer parameters - uint16 destinationChainId = uint16(targetParams.lzChainId); // LayerZero chain ID for Ethereum Sepolia - address recipientAddress = vm.envAddress("RECIPIENT_ADDRESS"); - console.log("Recipient: %s", recipientAddress); + IexecLayerZeroBridge sourceBridge = IexecLayerZeroBridge(sourceParams.iexecLayerZeroBridgeAddress); + IERC20 rlcToken = IERC20(sourceParams.rlcCrosschainTokenAddress); - uint256 amount = 5 * 10 ** 9; // RLC tokens (adjust the amount as needed) + address sender = vm.envAddress("RECIPIENT_ADDRESS"); + address recipient = vm.envAddress("RECIPIENT_ADDRESS"); - // Send tokens cross-chain - IexecLayerZeroBridge iexecLayerZeroBridge = IexecLayerZeroBridge(iexecLayerZeroBridgeAddress); - console.log("Sending %s RLC to Ethereum Sepolia", amount / 10 ** 9); + // Check sender's balance + uint256 senderBalance = rlcToken.balanceOf(sender); + require(senderBalance >= TRANSFER_AMOUNT, "Insufficient RLC balance"); + // Prepare send parameters SendParam memory sendParam = SendParam( - destinationChainId, // Destination endpoint ID. - addressToBytes32(recipientAddress), // Recipient address. - amount, // amount (in local decimals, e.g., 5 RLC = 5 * 10 ** 9) - amount * 99 / 100, // minAmount (allowing 1% slippage) - "", // Extra options, not used in this case, already setup using `setEnforcedOptions` - "", // Composed message, not used in this case - "" // OFT command to be executed, unused in default OFT implementations. + uint16(targetParams.lzChainId), // Destination endpoint ID + addressToBytes32(recipient), // Recipient address + TRANSFER_AMOUNT, // Amount to send in local decimals + TRANSFER_AMOUNT * 99 / 100, // Minimum amount to send (allowing 1% slippage) + "", // Extra options, already set via setEnforcedOptions + "", // Composed message for send() operation (unused) + "" // OFT command to be executed (unused in default OFT) ); - // Get the fee for the transfer - MessagingFee memory fee = iexecLayerZeroBridge.quoteSend(sendParam, false); - console.log("Fee amount: ", fee.nativeFee); + // Get quote for the transfer + MessagingFee memory fee = sourceBridge.quoteSend(sendParam, false); + + console.log("=== Cross-Chain Transfer Details ==="); + console.log("From: Arbitrum Mainnet"); + console.log("To: Ethereum Mainnet"); + console.log("Amount: %d RLC", TRANSFER_AMOUNT / 10 ** 9); + console.log("Fee: %d wei", fee.nativeFee); + console.log("Sender: %s", sender); + console.log("Recipient: %s", recipient); + console.log("Sender Balance: %d RLC", senderBalance / 10 ** 9); + vm.startBroadcast(); - // Execute the cross-chain transfer - iexecLayerZeroBridge.send{value: fee.nativeFee}(sendParam, fee, msg.sender); - console.log("Cross-chain transfer from Arbitrum to Ethereum initiated!"); + // Note: For crosschain tokens, no approval is needed as the bridge can burn directly + console.log("Initiating cross-chain transfer..."); + sourceBridge.send{value: fee.nativeFee}(sendParam, fee, payable(sender)); + vm.stopBroadcast(); + + console.log("Transfer initiated successfully!"); + console.log("Monitor the destination chain for token receipt."); } } diff --git a/script/SendFromEthereumToArbitrum.s.sol b/script/SendFromEthereumToArbitrum.s.sol index a08ad71f..c25afa17 100644 --- a/script/SendFromEthereumToArbitrum.s.sol +++ b/script/SendFromEthereumToArbitrum.s.sol @@ -3,19 +3,25 @@ pragma solidity ^0.8.22; import {Script, console} from "forge-std/Script.sol"; +import {ConfigLib} from "./lib/ConfigLib.sol"; import {IexecLayerZeroBridge} from "../src/bridges/layerZero/IexecLayerZeroBridge.sol"; import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; import {SendParam} from "@layerzerolabs/oft-evm/contracts/interfaces/IOFT.sol"; -// import { OptionsBuilder } from "@layerzerolabs/oapp-evm/contracts/oapp/libs/OptionsBuilder.sol"; import {MessagingFee} from "@layerzerolabs/oapp-evm/contracts/oapp/OApp.sol"; -import {ConfigLib} from "./lib/ConfigLib.sol"; +/** + * Script to send tokens from Ethereum Mainnet to Arbitrum Mainnet. + * This script demonstrates cross-chain token transfers using LayerZero bridge. + */ +// TODO: fusion SendTokensFromArbitrumToEthereum and SendTokensFromEthereumToArbitrum into a single script with dynamic chain handling contract SendTokensFromEthereumToArbitrum is Script { + uint256 private constant TRANSFER_AMOUNT = 10 * 10 ** 9; // 10 RLC tokens with 9 decimals /** * @dev Converts an address to bytes32. * @param _addr The address to convert. * @return The bytes32 representation of the address. */ + function addressToBytes32(address _addr) internal pure returns (bytes32) { return bytes32(uint256(uint160(_addr))); } @@ -27,43 +33,55 @@ contract SendTokensFromEthereumToArbitrum is Script { ConfigLib.CommonConfigParams memory sourceParams = ConfigLib.readCommonConfig(sourceChain); ConfigLib.CommonConfigParams memory targetParams = ConfigLib.readCommonConfig(targetChain); - // Contract addresses - address iexecLayerZeroBridgeAddress = sourceParams.iexecLayerZeroBridgeAddress; - address rlcMainnetTokenAddress = sourceParams.rlcToken; + IexecLayerZeroBridge sourceBridge = IexecLayerZeroBridge(sourceParams.iexecLayerZeroBridgeAddress); + IERC20 rlcToken = IERC20(sourceParams.rlcToken); - // Transfer parameters - uint16 destinationChainId = uint16(targetParams.lzChainId); - address recipientAddress = vm.envAddress("RECIPIENT_ADDRESS"); - uint256 amount = 5 * 10 ** 9; // RLC tokens (adjust the amount as needed) - - vm.startBroadcast(); - // First, approve the adapter to spend your tokens - IERC20 rlcToken = IERC20(rlcMainnetTokenAddress); - console.log("Approving IexecLayerZeroBridge contract to spend %s RLC", amount / 10 ** 9); - rlcToken.approve(iexecLayerZeroBridgeAddress, amount); + address sender = vm.envAddress("RECIPIENT_ADDRESS"); + address recipient = vm.envAddress("RECIPIENT_ADDRESS"); - // Then, send tokens cross-chain - IexecLayerZeroBridge adapter = IexecLayerZeroBridge(iexecLayerZeroBridgeAddress); - console.log("Sending %s RLC to Arbitrum Sepolia", amount / 10 ** 9); - console.log("Recipient: %s", recipientAddress); + // Check sender's balance + uint256 senderBalance = rlcToken.balanceOf(sender); + require(senderBalance >= TRANSFER_AMOUNT, "Insufficient RLC balance"); + // Prepare send parameters SendParam memory sendParam = SendParam( - destinationChainId, // Destination endpoint ID. - addressToBytes32(recipientAddress), // Recipient address. - amount, // Amount to send in local decimals. - amount * 99 / 100, // Minimum amount to send in local decimals (allowing 1% slippage). - "", // Extra options, not used in this case, already setup using `setEnforcedOptions` - "", // Composed message for the send() operation, unused in this context. - "" // OFT command to be executed, unused in default OFT implementations. + uint16(targetParams.lzChainId), // Destination endpoint ID + addressToBytes32(recipient), // Recipient address + TRANSFER_AMOUNT, // Amount to send in local decimals + TRANSFER_AMOUNT * 99 / 100, // Minimum amount to send (allowing 1% slippage) + "", // Extra options, already set via setEnforcedOptions + "", // Composed message for send() operation (unused) + "" // OFT command to be executed (unused in default OFT) ); - MessagingFee memory fee = adapter.quoteSend(sendParam, false); + // Get quote for the transfer + MessagingFee memory fee = sourceBridge.quoteSend(sendParam, false); - console.log("Fee amount: ", fee.nativeFee); + console.log("=== Cross-Chain Transfer Details ==="); + console.log("From: Ethereum Mainnet"); + console.log("To: Arbitrum Mainnet"); + console.log("Amount: %d RLC", TRANSFER_AMOUNT / 10 ** 9); + console.log("Fee: %d wei", fee.nativeFee); + console.log("Sender: %s", sender); + console.log("Recipient: %s", recipient); + console.log("Sender Balance: %d RLC", senderBalance / 10 ** 9); - adapter.send{value: fee.nativeFee}(sendParam, fee, msg.sender); + vm.startBroadcast(); + + // Approve bridge to spend tokens if needed + uint256 currentAllowance = rlcToken.allowance(sender, address(sourceBridge)); + if (currentAllowance < TRANSFER_AMOUNT) { + console.log("Approving bridge to spend %d RLC", TRANSFER_AMOUNT / 10 ** 9); + rlcToken.approve(address(sourceBridge), TRANSFER_AMOUNT); + } + + // Execute cross-chain transfer + console.log("Initiating cross-chain transfer..."); + sourceBridge.send{value: fee.nativeFee}(sendParam, fee, payable(sender)); - console.log("Cross-chain transfer initiated!"); vm.stopBroadcast(); + + console.log("Transfer initiated successfully!"); + console.log("Monitor the destination chain for token receipt."); } } diff --git a/tools/verification.mk b/tools/verification.mk index c9638473..791904c0 100644 --- a/tools/verification.mk +++ b/tools/verification.mk @@ -3,9 +3,12 @@ # SMART CONTRACT VERIFICATION # ======================================================================== # Chain IDs for supported networks +MAINNET_CHAIN_ID := 1 +ARBITRUM_CHAIN_ID := 42161 SEPOLIA_CHAIN_ID := 11155111 ARBITRUM_SEPOLIA_CHAIN_ID := 421614 + # ======================================================================== # VERIFICATION FUNCTIONS # ======================================================================== @@ -43,74 +46,98 @@ define verify-impl endef # ======================================================================== -# SEPOLIA NETWORK VERIFICATION +# ETHEREUM-TYPE VERIFICATION (Ethereum & Sepolia) # ======================================================================== -# Proxy Verifications - Sepolia -# ----------------------------- -verify-rlc-liquidity-unifier-proxy-sepolia: - $(call verify-proxy,RLCLiquidityUnifier,sepolia,rlcLiquidityUnifierAddress,$(SEPOLIA_CHAIN_ID),Sepolia Etherscan) +# Ethereum-type networks use RLC Liquidity Unifier + LayerZero Bridge +# Parameters: NETWORK, CHAIN_ID, DISPLAY_NAME +define verify-ethereum-type-proxies +verify-rlc-liquidity-unifier-proxy-$(1): + $$(call verify-proxy,RLCLiquidityUnifier,$(1),rlcLiquidityUnifierAddress,$(2),$(3)) -verify-layerzero-bridge-proxy-sepolia: - $(call verify-proxy,IexecLayerZeroBridge,sepolia,iexecLayerZeroBridgeAddress,$(SEPOLIA_CHAIN_ID),Sepolia Etherscan) +verify-layerzero-bridge-proxy-$(1): + $$(call verify-proxy,IexecLayerZeroBridge,$(1),iexecLayerZeroBridgeAddress,$(2),$(3)) +endef -# Implementation Verifications - Sepolia -# -------------------------------------- -verify-rlc-liquidity-unifier-impl-sepolia: - @rlc_address=$$(forge script script/GetConfigInfo.s.sol --sig "getConfigField(string)" ".chains.sepolia.rlcAddress" 2>/dev/null | grep "0x" | tail -n1); \ - constructor_args=$$(cast abi-encode "constructor(address)" $$rlc_address); \ - $(MAKE) _verify-rlc-liquidity-unifier-impl-sepolia CONSTRUCTOR_ARGS="--constructor-args $$constructor_args" +define verify-ethereum-type-implementations +verify-rlc-liquidity-unifier-impl-$(1): + @rlc_address=$$$$(forge script script/GetConfigInfo.s.sol --sig "getConfigField(string)" ".chains.$(1).rlcAddress" 2>/dev/null | grep "0x" | tail -n1); \ + constructor_args=$$$$(cast abi-encode "constructor(address)" $$$$rlc_address); \ + $$(MAKE) _verify-rlc-liquidity-unifier-impl-$(1) CONSTRUCTOR_ARGS="--constructor-args $$$$constructor_args" -_verify-rlc-liquidity-unifier-impl-sepolia: - $(call verify-impl,RLCLiquidityUnifier,sepolia,rlcLiquidityUnifierAddress,$(SEPOLIA_CHAIN_ID),Sepolia Etherscan,src/RLCLiquidityUnifier.sol:RLCLiquidityUnifier,$(SEPOLIA_RPC_URL),$(CONSTRUCTOR_ARGS)) +_verify-rlc-liquidity-unifier-impl-$(1): + $$(call verify-impl,RLCLiquidityUnifier,$(1),rlcLiquidityUnifierAddress,$(2),$(3),src/RLCLiquidityUnifier.sol:RLCLiquidityUnifier,$$(RPC_URL),$$(CONSTRUCTOR_ARGS)) -verify-layerzero-bridge-impl-sepolia: +verify-layerzero-bridge-impl-$(1): @echo "Building constructor arguments for IexecLayerZeroBridge..." - @rlc_liquidity_unifier_address=$$(forge script script/GetConfigInfo.s.sol --sig "getConfigField(string)" ".chains.sepolia.rlcLiquidityUnifierAddress" 2>/dev/null | grep "0x" | tail -n1); \ - lz_endpoint_address=$$(forge script script/GetConfigInfo.s.sol --sig "getConfigField(string)" ".chains.sepolia.lzEndpointAddress" 2>/dev/null | grep "0x" | tail -n1); \ - constructor_args=$$(cast abi-encode "constructor(bool,address,address)" true $$rlc_liquidity_unifier_address $$lz_endpoint_address); \ - $(MAKE) _verify-layerzero-bridge-impl-sepolia CONSTRUCTOR_ARGS="--constructor-args $$constructor_args" + @rlc_liquidity_unifier_address=$$$$(forge script script/GetConfigInfo.s.sol --sig "getConfigField(string)" ".chains.$(1).rlcLiquidityUnifierAddress" 2>/dev/null | grep "0x" | tail -n1); \ + lz_endpoint_address=$$$$(forge script script/GetConfigInfo.s.sol --sig "getConfigField(string)" ".chains.$(1).lzEndpointAddress" 2>/dev/null | grep "0x" | tail -n1); \ + constructor_args=$$$$(cast abi-encode "constructor(bool,address,address)" true $$$$rlc_liquidity_unifier_address $$$$lz_endpoint_address); \ + $$(MAKE) _verify-layerzero-bridge-impl-$(1) CONSTRUCTOR_ARGS="--constructor-args $$$$constructor_args" -_verify-layerzero-bridge-impl-sepolia: - $(call verify-impl,IexecLayerZeroBridge,sepolia,iexecLayerZeroBridgeAddress,$(SEPOLIA_CHAIN_ID),Sepolia Etherscan,src/bridges/layerZero/IexecLayerZeroBridge.sol:IexecLayerZeroBridge,$(SEPOLIA_RPC_URL),$(CONSTRUCTOR_ARGS)) +_verify-layerzero-bridge-impl-$(1): + $$(call verify-impl,IexecLayerZeroBridge,$(1),iexecLayerZeroBridgeAddress,$(2),$(3),src/bridges/layerZero/IexecLayerZeroBridge.sol:IexecLayerZeroBridge,$$(RPC_URL),$$(CONSTRUCTOR_ARGS)) +endef # ======================================================================== -# ARBITRUM SEPOLIA NETWORK VERIFICATION +# ARBITRUM-TYPE VERIFICATION (Arbitrum & Arbitrum Sepolia) # ======================================================================== -# Proxy Verifications - Arbitrum Sepolia -# --------------------------------------- -verify-rlc-crosschain-token-proxy-arbitrum-sepolia: - $(call verify-proxy,RLCCrosschainToken,arbitrum_sepolia,rlcCrosschainTokenAddress,$(ARBITRUM_SEPOLIA_CHAIN_ID),Arbitrum Sepolia) +# Arbitrum-type networks use RLC Crosschain Token + LayerZero Bridge +# Parameters: NETWORK, CHAIN_ID, DISPLAY_NAME +define verify-arbitrum-type-proxies +verify-rlc-crosschain-token-proxy-$(1): + $$(call verify-proxy,RLCCrosschainToken,$(1),rlcCrosschainTokenAddress,$(2),$(3)) -verify-layerzero-bridge-proxy-arbitrum-sepolia: - $(call verify-proxy,IexecLayerZeroBridge,arbitrum_sepolia,iexecLayerZeroBridgeAddress,$(ARBITRUM_SEPOLIA_CHAIN_ID),Arbitrum Sepolia) +verify-layerzero-bridge-proxy-$(1): + $$(call verify-proxy,IexecLayerZeroBridge,$(1),iexecLayerZeroBridgeAddress,$(2),$(3)) +endef -# Implementation Verifications - Arbitrum Sepolia -# ------------------------------------------------ -verify-rlc-crosschain-token-impl-arbitrum-sepolia: - $(call verify-impl,RLCCrosschainToken,arbitrum_sepolia,rlcCrosschainTokenAddress,$(ARBITRUM_SEPOLIA_CHAIN_ID),Arbitrum Sepolia,src/RLCCrosschainToken.sol:RLCCrosschainToken,$(ARBITRUM_SEPOLIA_RPC_URL),) +define verify-arbitrum-type-implementations +verify-rlc-crosschain-token-impl-$(1): + $$(call verify-impl,RLCCrosschainToken,$(1),rlcCrosschainTokenAddress,$(2),$(3),src/RLCCrosschainToken.sol:RLCCrosschainToken,$$(RPC_URL),) -verify-layerzero-bridge-impl-arbitrum-sepolia: +verify-layerzero-bridge-impl-$(1): @echo "Building constructor arguments for IexecLayerZeroBridge..." - @rlc_crosschain_token_address=$$(forge script script/GetConfigInfo.s.sol --sig "getConfigField(string)" ".chains.arbitrum_sepolia.rlcCrosschainTokenAddress" 2>/dev/null | grep "0x" | tail -n1); \ - lz_endpoint_address=$$(forge script script/GetConfigInfo.s.sol --sig "getConfigField(string)" ".chains.arbitrum_sepolia.lzEndpointAddress" 2>/dev/null | grep "0x" | tail -n1); \ - constructor_args=$$(cast abi-encode "constructor(bool,address,address)" false $$rlc_crosschain_token_address $$lz_endpoint_address); \ - $(MAKE) _verify-layerzero-bridge-impl-arbitrum-sepolia CONSTRUCTOR_ARGS="--constructor-args $$constructor_args" + @rlc_crosschain_token_address=$$$$(forge script script/GetConfigInfo.s.sol --sig "getConfigField(string)" ".chains.$(1).rlcCrosschainTokenAddress" 2>/dev/null | grep "0x" | tail -n1); \ + lz_endpoint_address=$$$$(forge script script/GetConfigInfo.s.sol --sig "getConfigField(string)" ".chains.$(1).lzEndpointAddress" 2>/dev/null | grep "0x" | tail -n1); \ + constructor_args=$$$$(cast abi-encode "constructor(bool,address,address)" false $$$$rlc_crosschain_token_address $$$$lz_endpoint_address); \ + $$(MAKE) _verify-layerzero-bridge-impl-$(1) CONSTRUCTOR_ARGS="--constructor-args $$$$constructor_args" + +_verify-layerzero-bridge-impl-$(1): + $$(call verify-impl,IexecLayerZeroBridge,$(1),iexecLayerZeroBridgeAddress,$(2),$(3),src/bridges/layerZero/IexecLayerZeroBridge.sol:IexecLayerZeroBridge,$$(RPC_URL),$$(CONSTRUCTOR_ARGS)) +endef + +# ======================================================================== +# NETWORK-SPECIFIC VERIFICATION TARGETS +# ======================================================================== + +# Generate verification targets for Ethereum-type networks (Ethereum & Sepolia) +$(eval $(call verify-ethereum-type-proxies,sepolia,$(SEPOLIA_CHAIN_ID),Sepolia)) +$(eval $(call verify-ethereum-type-implementations,sepolia,$(SEPOLIA_CHAIN_ID),Sepolia)) + +$(eval $(call verify-ethereum-type-proxies,ethereum,$(MAINNET_CHAIN_ID),Ethereum Mainnet)) +$(eval $(call verify-ethereum-type-implementations,ethereum,$(MAINNET_CHAIN_ID),Ethereum Mainnet)) + +# Generate verification targets for Arbitrum-type networks (Arbitrum & Arbitrum Sepolia) +$(eval $(call verify-arbitrum-type-proxies,arbitrum_sepolia,$(ARBITRUM_SEPOLIA_CHAIN_ID),Arbitrum Sepolia)) +$(eval $(call verify-arbitrum-type-implementations,arbitrum_sepolia,$(ARBITRUM_SEPOLIA_CHAIN_ID),Arbitrum Sepolia)) -_verify-layerzero-bridge-impl-arbitrum-sepolia: - $(call verify-impl,IexecLayerZeroBridge,arbitrum_sepolia,iexecLayerZeroBridgeAddress,$(ARBITRUM_SEPOLIA_CHAIN_ID),Arbitrum Sepolia,src/bridges/layerZero/IexecLayerZeroBridge.sol:IexecLayerZeroBridge,$(ARBITRUM_SEPOLIA_RPC_URL),$(CONSTRUCTOR_ARGS)) +$(eval $(call verify-arbitrum-type-proxies,arbitrum,$(ARBITRUM_CHAIN_ID),Arbitrum Mainnet)) +$(eval $(call verify-arbitrum-type-implementations,arbitrum,$(ARBITRUM_CHAIN_ID),Arbitrum Mainnet)) # ======================================================================== # PROXY AND IMPLEMENTATION VERIFICATION # ======================================================================== verify-proxies-sepolia: verify-rlc-liquidity-unifier-proxy-sepolia verify-layerzero-bridge-proxy-sepolia -verify-proxies-arbitrum-sepolia: verify-rlc-crosschain-token-proxy-arbitrum-sepolia verify-layerzero-bridge-proxy-arbitrum-sepolia -verify-proxies-testnets: verify-proxies-sepolia verify-proxies-arbitrum-sepolia +verify-proxies-arbitrum_sepolia: verify-rlc-crosschain-token-proxy-arbitrum_sepolia verify-layerzero-bridge-proxy-arbitrum_sepolia +verify-proxies-ethereum: verify-rlc-liquidity-unifier-proxy-ethereum verify-layerzero-bridge-proxy-ethereum +verify-proxies-arbitrum: verify-rlc-crosschain-token-proxy-arbitrum verify-layerzero-bridge-proxy-arbitrum verify-implementations-sepolia: verify-rlc-liquidity-unifier-impl-sepolia verify-layerzero-bridge-impl-sepolia -verify-implementations-arbitrum-sepolia: verify-rlc-crosschain-token-impl-arbitrum-sepolia verify-layerzero-bridge-impl-arbitrum-sepolia -verify-implementations-testnets: verify-implementations-sepolia verify-implementations-arbitrum-sepolia +verify-implementations-arbitrum_sepolia: verify-rlc-crosschain-token-impl-arbitrum_sepolia verify-layerzero-bridge-impl-arbitrum_sepolia +verify-implementations-ethereum: verify-rlc-liquidity-unifier-impl-ethereum verify-layerzero-bridge-impl-ethereum +verify-implementations-arbitrum: verify-rlc-crosschain-token-impl-arbitrum verify-layerzero-bridge-impl-arbitrum # ======================================================================== @@ -118,10 +145,14 @@ verify-implementations-testnets: verify-implementations-sepolia verify-implement # ======================================================================== verify-all-sepolia: verify-proxies-sepolia verify-implementations-sepolia -verify-all-arbitrum-sepolia: verify-proxies-arbitrum-sepolia verify-implementations-arbitrum-sepolia -verify-all-testnets: verify-all-sepolia verify-all-arbitrum-sepolia +verify-all-arbitrum_sepolia: verify-proxies-arbitrum_sepolia verify-implementations-arbitrum_sepolia +verify-all-ethereum: verify-proxies-ethereum verify-implementations-ethereum +verify-all-arbitrum: verify-proxies-arbitrum verify-implementations-arbitrum +verify-all-testnets: verify-all-sepolia verify-all-arbitrum_sepolia +verify-all-mainnets: verify-all-ethereum verify-all-arbitrum +verify-all: verify-all-testnets verify-all-mainnets -.PHONY: verify-all-testnets verify-all-sepolia verify-all-arbitrum-sepolia \ - verify-proxies-sepolia verify-proxies-arbitrum-sepolia verify-proxies-testnets \ - verify-implementations-sepolia verify-implementations-arbitrum-sepolia verify-implementations-testnets +.PHONY: verify-all verify-all-testnets verify-all-mainnets verify-all-sepolia verify-all-arbitrum_sepolia verify-all-ethereum verify-all-arbitrum \ + verify-proxies-sepolia verify-proxies-arbitrum_sepolia verify-proxies-ethereum verify-proxies-arbitrum \ + verify-implementations-sepolia verify-implementations-arbitrum_sepolia verify-implementations-ethereum verify-implementations-arbitrum