Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
53 commits
Select commit Hold shift + click to select a range
1c3d56a
test: Implement TODO in test
zguesmi Aug 6, 2025
3bf8d10
test: Rename test files
zguesmi Aug 6, 2025
237ce9c
feat: Configure peer and options only when needed
zguesmi Aug 6, 2025
3434427
test: Deploy bridge using deploy script
zguesmi Aug 6, 2025
bb2aaf2
Revert "test: Deploy bridge using deploy script"
zguesmi Aug 6, 2025
ba67781
Add comment
zguesmi Aug 6, 2025
8df537d
chore: Add comment
zguesmi Aug 6, 2025
1d10b7e
chore: Add commnets
zguesmi Aug 6, 2025
72f068c
test: Label addresses for better debugging
zguesmi Aug 7, 2025
4b30140
test: Split configure function from run function
zguesmi Aug 7, 2025
a3e2792
test: Init bridge configure tests
zguesmi Aug 7, 2025
cfc3075
refactor: Create LayerZeroUtils
zguesmi Aug 7, 2025
058e92f
style: Format
zguesmi Aug 7, 2025
4c1d657
feat: Split configure script to smaller functions
zguesmi Aug 7, 2025
b7eb0f3
fix: Clean
zguesmi Aug 7, 2025
c7b01b0
test: Add tests
zguesmi Aug 7, 2025
4030b22
test: Add tests
zguesmi Aug 7, 2025
eaf4d75
style:Format
zguesmi Aug 7, 2025
af617a9
trest: Clean
zguesmi Aug 8, 2025
72b11ad
chore: Add TODO
zguesmi Aug 8, 2025
c1a7099
test: Clezan
zguesmi Aug 8, 2025
6b1dfca
test: Add configure function tests
zguesmi Aug 8, 2025
c87febb
Merge branch 'main' into feat/dvn-config-script
zguesmi Aug 8, 2025
b647d03
docs: Add links to LayerZero relevant guides.
zguesmi Aug 8, 2025
9356308
docs: Clean
zguesmi Aug 8, 2025
2e01a2e
docs:Update
zguesmi Aug 8, 2025
2ad2924
feat: Get onchain bridge config
zguesmi Aug 11, 2025
f3c6bb2
feat: Save LZ config in json file
zguesmi Aug 11, 2025
75ef85b
feat: Add helper functions
zguesmi Aug 11, 2025
3d1d739
feat: Finalize config script
zguesmi Aug 12, 2025
09ce2ec
test: Init ULN and executor config
zguesmi Aug 12, 2025
f751626
fix: Fix send scripts
zguesmi Aug 12, 2025
fa4582e
chore: WIP
zguesmi Aug 14, 2025
d643e82
Merge branch 'main' into feat/dvn-config-script
zguesmi Sep 8, 2025
ef62833
chore: Merge branch 'main' into feat/dvn-config-script
zguesmi Oct 6, 2025
8493fa8
chore: Merge branch 'main' into feat/dvn-config-script
zguesmi Nov 7, 2025
aaec2b7
style: Format
zguesmi Nov 7, 2025
70687dc
chore: Move function to utility file
zguesmi Nov 7, 2025
e850c1c
refactor: Rename function
zguesmi Nov 7, 2025
153c47b
chore: Clean
zguesmi Nov 7, 2025
4421cc3
chore: Clezan
zguesmi Nov 7, 2025
bf10a68
chore: Cleazn
zguesmi Nov 7, 2025
c0acaec
style: Format
zguesmi Nov 7, 2025
5789cc8
chore: Rename
zguesmi Nov 7, 2025
b06e23c
fix: Remove unused import
zguesmi Nov 7, 2025
e3ecc70
chore: Make lib functions internal
zguesmi Nov 7, 2025
18ceca2
test: Fix tests
zguesmi Nov 7, 2025
327a29c
Clean
zguesmi Nov 7, 2025
6302a9c
chore: Merge branch 'main' into feat/dvn-config-script
zguesmi Nov 7, 2025
337a3f2
chore: Add codeowner file
zguesmi Nov 7, 2025
40a9f99
chore: Cleazn
zguesmi Nov 7, 2025
5940cd2
style: Format
zguesmi Nov 7, 2025
e5bce70
chore: Cleazn
zguesmi Nov 7, 2025
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
4 changes: 4 additions & 0 deletions .github/CODEOWNERS
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
# Code owners are automatically requested to review when a pull request
# is opened (ready for review).

* @zguesmi @gfournieriExec @Le-Caignec
4 changes: 4 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,10 @@ The architecture is designed to support additional networks in the future with m
- RLC tokens for bridge testing
- [LCOV](https://wiki.documentfoundation.org/Development/Lcov) for coverage report generation (install via `brew install lcov` on macOS)

## Go to production

Read [how to make the bridge production-ready](./docs/LayerZeroSetup.md).

## Installation

1. Clone the repository
Expand Down
72 changes: 68 additions & 4 deletions config/config.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,21 @@
"iexecLayerZeroBridgeAddress": "0xA18e571f91ab58889C348E1764fBaBF622ab89b5",
"iexecLayerZeroBridgeCreatexSalt": "0x4f2784ad07b2be2a5c5e466c91d758133f4aa33bd4cf09ddba1a1e1035e57875",
"lzEndpointAddress": "0x6EDCE65403992e310A62460808c4b910D972f10f",
"lzEndpointId": 40161
"lzEndpointId": 40161,
"lzSendLibraryAddress": "0xcc1ae8Cf5D3904Cef3360A9532B477529b177cCE",
"lzReceiveLibraryAddress": "0xdAf00F5eE2158dD58E0d3857851c432E34A3A851",
"lzExecutorConfig": {
"maxMessageSize": 10000,
"executor": "0x718B92b5CB0a5552039B593faF724D182A881eDA"
},
"lzUlnConfig": {
"confirmations": 2,
"requiredDVNCount": 1,
"requiredDVNs": ["0x8eebf8b423B73bFCa51a1Db4B7354AA0bFCA9193"],
"optionalDVNCount": 0,
"optionalDVNs": [],
"optionalDVNThreshold": 0
}
},
"arbitrum_sepolia": {
"approvalRequired": false,
Expand All @@ -21,26 +35,76 @@
"iexecLayerZeroBridgeAddress": "0xB560ae1dD7FdF011Ead2189510ae08f2dbD168a5",
"iexecLayerZeroBridgeCreatexSalt": "0x4f2784ad07b2be2a5c5e466c91d758133f4aa33bd4cf09ddba1a1e1035e57875",
"lzEndpointAddress": "0x6EDCE65403992e310A62460808c4b910D972f10f",
"lzEndpointId": 40231
"lzEndpointId": 40231,
"lzSendLibraryAddress": "0x4f7cd4DA19ABB31b0eC98b9066B9e857B1bf9C0E",
"lzReceiveLibraryAddress": "0x75Db67CDab2824970131D5aa9CECfC9F69c69636",
"lzExecutorConfig": {
"maxMessageSize": 10000,
"executor": "0x5Df3a1cEbBD9c8BA7F8dF51Fd632A9aef8308897"
},
"lzDvnConfig": {
"confirmations": 1,
"requiredDvnCount": 1,
"requiredDvnList": ["0x53f488E93b4f1b60E8E83aa374dBe1780A1EE8a8"],
"optionalDvnCount": 0,
"optionalDvnList": [],
"optionalDvnThreshold": 0
}
},
"ethereum": {
"__dvnConfigSourceUrl": "https://layerzeroscan.com/tools/defaults?version=V2&srcChainKey[0]=ethereum&dstChainKey[0]=arbitrum",
"approvalRequired": true,
"rlcAddress": "0x607F4C5BB672230e8672085532f7e901544a7375",
"rlcLiquidityUnifierAddress": "0x329F9f984B1364d4E01c6F9f63a1326b7E420b38",
"rlcLiquidityUnifierCreatexSalt": "0x0000000000000000000000000000000000000000000000000000000000000000",
"iexecLayerZeroBridgeAddress": "0xA8Bc0B19b955c8AC38C8Cbc9383866b834Cd33d8",
"iexecLayerZeroBridgeCreatexSalt": "0x0000000000000000000000000000000000000000000000000000000000000000",
"lzEndpointAddress": "0x1a44076050125825900e736c501f859c50fE728c",
"lzEndpointId": 30101
"lzEndpointId": 30101,
"lzSendLibraryAddress": "0xbB2Ea70C9E858123480642Cf96acbcCE1372dCe1",
"lzReceiveLibraryAddress": "0xc02Ab410f0734EFa3F14628780e6e695156024C2",
"lzExecutorConfig": {
"maxMessageSize": 10000,
"executor": "0x173272739Bd7Aa6e4e214714048a9fE699453059"
},
"lzDvnConfig": {
"confirmations": 15,
"requiredDvnCount": 2,
"requiredDvnList": [
"0x589dEDbD617e0CBcB916A9223F4d1300c294236b",
"0xD56e4eAb23cb81f43168F9F45211Eb027b9aC7cc"
],
"optionalDvnCount": 0,
"optionalDvnList": [],
"optionalDvnThreshold": 0
}
},
"arbitrum": {
"__dvnConfigSourceUrl": "https://layerzeroscan.com/tools/defaults?version=V2&srcChainKey[0]=arbitrum&dstChainKey[0]=ethereum",
"approvalRequired": false,
"rlcCrosschainTokenAddress": "0xe649e6a1F2afc63ca268C2363691ceCAF75CF47C",
"rlcCrosschainTokenCreatexSalt": "0x0000000000000000000000000000000000000000000000000000000000000001",
"iexecLayerZeroBridgeAddress": "0x7000e96D0Bfc74cf5259aEDaE6065B39B3888699",
"iexecLayerZeroBridgeCreatexSalt": "0x0000000000000000000000000000000000000000000000000000000000000001",
"lzEndpointAddress": "0x1a44076050125825900e736c501f859c50fE728c",
"lzEndpointId": 30110
"lzEndpointId": 30110,
"lzSendLibraryAddress": "0x975bcD720be66659e3EB3C0e4F1866a3020E493A",
"lzReceiveLibraryAddress": "0x7B9E184e07a6EE1aC23eAe0fe8D6Be2f663f05e6",
"lzExecutorConfig": {
"maxMessageSize": 10000,
"executor": "0x31CAe3B7fB82d847621859fb1585353c5720660D"
},
"lzDvnConfig": {
"confirmations": 20,
"requiredDvnCount": 2,
"requiredDvnList": [
"0x2f55C492897526677C5B68fb199ea31E2c126416",
"0xD56e4eAb23cb81f43168F9F45211Eb027b9aC7cc"
],
"optionalDvnCount": 0,
"optionalDvnList": [],
"optionalDvnThreshold": 0
}
}
}
}
8 changes: 8 additions & 0 deletions docs/LayerZeroSetup.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@

<!-- TODO -->
Before going to production, follow this checklist to correctly and securely configure the bridge:
* [LayerZero V2 Integration Checklist](https://docs.layerzero.network/v2/tools/integration-checklist)
* [Steps to set up required config](https://docs.layerzero.network/v2/developers/evm/configuration/dvn-executor-config)
* https://docs.layerzero.network/v2/concepts/modular-security/security-stack-dvns
* https://docs.layerzero.network/v2/deployments/deployed-contracts
* https://docs.layerzero.network/v2/get-started/create-lz-oapp/configuring-pathways
31 changes: 27 additions & 4 deletions script/bridges/layerZero/IexecLayerZeroBridge.s.sol
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,18 @@ import {Upgrades} from "openzeppelin-foundry-upgrades/Upgrades.sol";
import {IAccessControl} from "@openzeppelin/contracts/access/IAccessControl.sol";
import {EnforcedOptionParam} from "@layerzerolabs/oapp-evm/contracts/oapp/libs/OAppOptionsType3.sol";
import {OptionsBuilder} from "@layerzerolabs/oapp-evm/contracts/oapp/libs/OptionsBuilder.sol";
import {UlnConfig} from "@layerzerolabs/lz-evm-messagelib-v2/contracts/uln/UlnBase.sol";
import {ExecutorConfig} from "@layerzerolabs/lz-evm-messagelib-v2/contracts/SendLibBase.sol";
import {SetConfigParam} from "@layerzerolabs/lz-evm-protocol-v2/contracts/interfaces/IMessageLibManager.sol";
import {ILayerZeroEndpointV2} from "@layerzerolabs/lz-evm-protocol-v2/contracts/interfaces/ILayerZeroEndpointV2.sol";
import {ConfigLib} from "./../../lib/ConfigLib.sol";
import {IexecLayerZeroBridge} from "../../../src/bridges/layerZero/IexecLayerZeroBridge.sol";
import {RLCLiquidityUnifier} from "../../../src/RLCLiquidityUnifier.sol";
import {RLCCrosschainToken} from "../../../src/RLCCrosschainToken.sol";
import {UUPSProxyDeployer} from "../../lib/UUPSProxyDeployer.sol";
import {UpgradeUtils} from "../../lib/UpgradeUtils.sol";
import {LayerZeroUtils} from "../../utils/LayerZeroUtils.sol";
import {LzConfig} from "../../lib/ConfigLib.sol";

/**
* A script to deploy and initialize the IexecLayerZeroBridge contract.
Expand Down Expand Up @@ -76,6 +81,8 @@ contract Deploy is Script {
* - Authorize the bridge in RLCLiquidityUnifier or RLCCrosschainToken contract (`grantRole`).
* The script should be called at least once for each chain where the bridge is configured.
*/
// TODO configure `delegate` role for bridges to manage config in endpoints.
// See tests and https://docs.layerzero.network/v2/concepts/glossary#delegate
contract Configure is Script {
using OptionsBuilder for bytes;

Expand All @@ -88,9 +95,11 @@ contract Configure is Script {
string memory targetChain = vm.envString("TARGET_CHAIN");
ConfigLib.CommonConfigParams memory sourceParams = ConfigLib.readCommonConfig(sourceChain);
ConfigLib.CommonConfigParams memory targetParams = ConfigLib.readCommonConfig(targetChain);
LzConfig memory srcChainLzConfig = ConfigLib.readLzConfig(sourceChain);
LzConfig memory dstChainLzConfig = ConfigLib.readLzConfig(targetChain);
console.log("Configuring bridge [chain:%s, address:%s]", sourceChain, sourceParams.iexecLayerZeroBridgeAddress);
vm.startBroadcast();
configure(sourceParams, targetParams);
configure(sourceParams, targetParams, srcChainLzConfig, dstChainLzConfig);
vm.stopBroadcast();
}

Expand All @@ -102,21 +111,24 @@ contract Configure is Script {
*/
function configure(
ConfigLib.CommonConfigParams memory sourceParams,
ConfigLib.CommonConfigParams memory targetParams
ConfigLib.CommonConfigParams memory targetParams,
LzConfig memory srcChainLzConfig,
LzConfig memory dstChainLzConfig
) public returns (bool) {
address bridge = sourceParams.iexecLayerZeroBridgeAddress;
RLCLiquidityUnifier rlcLiquidityUnifier = RLCLiquidityUnifier(sourceParams.rlcLiquidityUnifierAddress);
RLCCrosschainToken rlcCrosschainToken = RLCCrosschainToken(sourceParams.rlcCrosschainTokenAddress);
bool bool1 = setBridgePeerIfNeeded(bridge, targetParams.lzEndpointId, targetParams.iexecLayerZeroBridgeAddress);
bool bool2 = setEnforcedOptionsIfNeeded(bridge, targetParams.lzEndpointId);
bool bool3 = authorizeBridgeIfNeeded(
bool bool3 = setExecutorAndUlnConfigIfNeeded(srcChainLzConfig, dstChainLzConfig);
bool bool4 = authorizeBridgeIfNeeded(
bridge,
sourceParams.approvalRequired ? address(rlcLiquidityUnifier) : address(rlcCrosschainToken),
sourceParams.approvalRequired
? rlcLiquidityUnifier.TOKEN_BRIDGE_ROLE()
: rlcCrosschainToken.TOKEN_BRIDGE_ROLE()
);
return bool1 || bool2 || bool3;
return bool1 || bool2 || bool3 || bool4;
}

/**
Expand Down Expand Up @@ -171,6 +183,17 @@ contract Configure is Script {
return true;
}

// TODO use LayerZero CLI:
// https://docs.layerzero.network/v2/get-started/create-lz-oapp/configuring-pathways
// More on DVNs https://docs.layerzero.network/v2/concepts/modular-security/security-stack-dvns
function setExecutorAndUlnConfigIfNeeded(LzConfig memory srcChainLzConfig, LzConfig memory dstChainLzConfig)
public
returns (bool)
{
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

TODO check if the config is needed.

LayerZeroUtils.setBridgeLzConfig(srcChainLzConfig, dstChainLzConfig);
return true;
}

/**
* Authorizes the bridge in the RLCLiquidityUnifier or RLCCrosschainToken contract if it
* is not already authorized. Otherwise, do nothing.
Expand Down
49 changes: 46 additions & 3 deletions script/lib/ConfigLib.sol
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,19 @@ pragma solidity ^0.8.22;
import {Vm} from "forge-std/Vm.sol";
import {console} from "forge-std/console.sol";
import {stdJson} from "forge-std/StdJson.sol";
import {StdConstants} from "forge-std/StdConstants.sol";
import {UlnConfig} from "@layerzerolabs/lz-evm-messagelib-v2/contracts/uln/UlnBase.sol";
import {ExecutorConfig} from "@layerzerolabs/lz-evm-messagelib-v2/contracts/SendLibBase.sol";

struct LzConfig {
address endpoint;
uint32 endpointId;
address bridge;
address sendLibrary;
address receiveLibrary;
ExecutorConfig executorConfig;
UlnConfig ulnConfig;
}

/**
* @title ConfigLib
Expand All @@ -14,7 +27,8 @@ import {stdJson} from "forge-std/StdJson.sol";
library ConfigLib {
using stdJson for string;

Vm constant vm = Vm(address(uint160(uint256(keccak256("hevm cheat code")))));
Vm private constant vm = StdConstants.VM;
string constant CONFIG_FILE_PATH = "config/config.json";

/**
* @dev Common configuration parameters structure
Expand Down Expand Up @@ -101,7 +115,7 @@ library ConfigLib {
* @return params Common configuration parameters
*/
function readCommonConfig(string memory chain) internal view returns (CommonConfigParams memory params) {
string memory config = vm.readFile("config/config.json");
string memory config = vm.readFile(CONFIG_FILE_PATH);
string memory prefix = string.concat(".chains.", chain);
params.initialAdmin = config.readAddress(".initialAdmin");
params.initialPauser = config.readAddress(".initialPauser");
Expand All @@ -121,14 +135,43 @@ library ConfigLib {
params.lzEndpointId = uint32(config.readUint(string.concat(prefix, ".lzEndpointId")));
}

/**
* @dev Reads the LayerZero configuration from the config.json file for the specified chain.
* @param chain The chain identifier (e.g., "sepolia", "arbitrum_sepolia")
* @return lzConfig The LayerZero configuration parameters for the specified chain
*/
function readLzConfig(string memory chain) internal view returns (LzConfig memory) {
string memory json = vm.readFile(CONFIG_FILE_PATH);
string memory prefix = string.concat(".chains.", chain);
LzConfig memory lzConfig;
lzConfig.endpoint = json.readAddress(string.concat(prefix, ".lzEndpointAddress"));
lzConfig.endpointId = uint32(json.readUint(string.concat(prefix, ".lzEndpointId")));
lzConfig.bridge = json.readAddress(string.concat(prefix, ".iexecLayerZeroBridgeAddress"));
lzConfig.sendLibrary = json.readAddress(string.concat(prefix, ".lzSendLibraryAddress"));
lzConfig.receiveLibrary = json.readAddress(string.concat(prefix, ".lzReceiveLibraryAddress"));
lzConfig.executorConfig = ExecutorConfig({
executor: json.readAddress(string.concat(prefix, ".lzExecutorConfig.executor")),
maxMessageSize: uint32(json.readUint(string.concat(prefix, ".lzExecutorConfig.maxMessageSize")))
});
lzConfig.ulnConfig = UlnConfig({
confirmations: uint64(json.readUint(string.concat(prefix, ".lzUlnConfig.confirmations"))),
requiredDVNCount: uint8(json.readUint(string.concat(prefix, ".lzUlnConfig.requiredDvnCount"))),
requiredDVNs: json.readAddressArray(string.concat(prefix, ".lzUlnConfig.requiredDVNs")),
optionalDVNCount: uint8(json.readUint(string.concat(prefix, ".lzUlnConfig.optionalDVNCount"))),
optionalDVNs: json.readAddressArray(string.concat(prefix, ".lzUlnConfig.optionalDVNs")),
optionalDVNThreshold: uint8(json.readUint(string.concat(prefix, ".lzUlnConfig.optionalDVNThreshold")))
});
return lzConfig;
}

/**
* @dev Updates the config file with a new address for a specific chain
* @param chain The chain identifier (e.g., "sepolia", "arbitrum_sepolia")
* @param fieldName The field name to update (e.g., "iexecLayerZeroBridgeAddress")
* @param value The address value to set
*/
function updateConfigAddress(string memory chain, string memory fieldName, address value) internal {
string memory configPath = "config/config.json";
string memory configPath = CONFIG_FILE_PATH;
// Check if file exists
if (!vm.exists(configPath)) {
console.log("Config file not found at:", configPath);
Expand Down
Loading