11// SPDX-FileCopyrightText: 2025 IEXEC BLOCKCHAIN TECH <[email protected] > 22// SPDX-License-Identifier: Apache-2.0
3+
34pragma solidity ^ 0.8.22 ;
45
6+ import {console} from "forge-std/console.sol " ;
57import {Script} from "forge-std/Script.sol " ;
68import {Upgrades} from "openzeppelin-foundry-upgrades/Upgrades.sol " ;
9+ import {IAccessControl} from "@openzeppelin/contracts/access/IAccessControl.sol " ;
710import {EnforcedOptionParam} from "@layerzerolabs/oapp-evm/contracts/oapp/libs/OAppOptionsType3.sol " ;
811import {OptionsBuilder} from "@layerzerolabs/oapp-evm/contracts/oapp/libs/OptionsBuilder.sol " ;
912import {ConfigLib} from "./../../lib/ConfigLib.sol " ;
@@ -12,11 +15,17 @@ import {RLCLiquidityUnifier} from "../../../src/RLCLiquidityUnifier.sol";
1215import {RLCCrosschainToken} from "../../../src/RLCCrosschainToken.sol " ;
1316import {UUPSProxyDeployer} from "../../lib/UUPSProxyDeployer.sol " ;
1417import {UpgradeUtils} from "../../lib/UpgradeUtils.sol " ;
18+ import {LayerZeroUtils} from "../../utils/LayerZeroUtils.sol " ;
1519
20+ /**
21+ * A script to deploy and initialize the IexecLayerZeroBridge contract.
22+ * It uses CreateX to deploy the contract as a UUPS proxy.
23+ */
1624contract Deploy is Script {
1725 /**
18- * Reads configuration from config file and deploys IexecLayerZeroBridge contract.
19- * @return address of the deployed IexecLayerZeroBridge proxy contract.
26+ * Reads configuration from config file and deploys `IexecLayerZeroBridge` contract.
27+ * @dev This function is called by `forge script run`.
28+ * @return address of the deployed `IexecLayerZeroBridge` proxy contract.
2029 */
2130 function run () external returns (address ) {
2231 string memory chain = vm.envString ("CHAIN " );
@@ -59,39 +68,131 @@ contract Deploy is Script {
5968 }
6069}
6170
71+ /**
72+ * A script to configure the IexecLayerZeroBridge contract:
73+ * - Set the peer for the bridge (`setPeer`).
74+ * - Set enforced options for the bridge (`setEnforcedOptions`).
75+ * - Set DVNs config for the bridge (`setDvnConfig`). TODO
76+ * - Authorize the bridge in RLCLiquidityUnifier or RLCCrosschainToken contract (`grantRole`).
77+ * The script should be called at least once for each chain where the bridge is configured.
78+ */
6279contract Configure is Script {
6380 using OptionsBuilder for bytes ;
6481
82+ /**
83+ * Reads configuration from config file and configures `IexecLayerZeroBridge` contract.
84+ * @dev This function is called by `forge script run`.
85+ */
6586 function run () external {
6687 string memory sourceChain = vm.envString ("SOURCE_CHAIN " );
6788 string memory targetChain = vm.envString ("TARGET_CHAIN " );
6889 ConfigLib.CommonConfigParams memory sourceParams = ConfigLib.readCommonConfig (sourceChain);
6990 ConfigLib.CommonConfigParams memory targetParams = ConfigLib.readCommonConfig (targetChain);
70- IexecLayerZeroBridge sourceBridge = IexecLayerZeroBridge ( sourceParams.iexecLayerZeroBridgeAddress);
91+ console. log ( " Configuring bridge [chain:%s, address:%s] " , sourceChain, sourceParams.iexecLayerZeroBridgeAddress);
7192 vm.startBroadcast ();
72- sourceBridge.setPeer (
73- targetParams.lzEndpointId, bytes32 (uint256 (uint160 (targetParams.iexecLayerZeroBridgeAddress)))
93+ configure (sourceParams, targetParams);
94+ vm.stopBroadcast ();
95+ }
96+
97+ /**
98+ * Setup bridge configuration.
99+ * @param sourceParams Configuration parameters for the source chain.
100+ * @param targetParams Configuration parameters for the target chain.
101+ * @return true if at least one configuration was changed, false otherwise.
102+ */
103+ function configure (
104+ ConfigLib.CommonConfigParams memory sourceParams ,
105+ ConfigLib.CommonConfigParams memory targetParams
106+ ) public returns (bool ) {
107+ address bridge = sourceParams.iexecLayerZeroBridgeAddress;
108+ RLCLiquidityUnifier rlcLiquidityUnifier = RLCLiquidityUnifier (sourceParams.rlcLiquidityUnifierAddress);
109+ RLCCrosschainToken rlcCrosschainToken = RLCCrosschainToken (sourceParams.rlcCrosschainTokenAddress);
110+ bool bool1 = setBridgePeerIfNeeded (bridge, targetParams.lzEndpointId, targetParams.iexecLayerZeroBridgeAddress);
111+ bool bool2 = setEnforcedOptionsIfNeeded (bridge, targetParams.lzEndpointId);
112+ bool bool3 = authorizeBridgeIfNeeded (
113+ bridge,
114+ sourceParams.approvalRequired ? address (rlcLiquidityUnifier) : address (rlcCrosschainToken),
115+ sourceParams.approvalRequired
116+ ? rlcLiquidityUnifier.TOKEN_BRIDGE_ROLE ()
117+ : rlcCrosschainToken.TOKEN_BRIDGE_ROLE ()
74118 );
75- EnforcedOptionParam[] memory enforcedOptions = new EnforcedOptionParam [](2 );
76- bytes memory _extraOptions = OptionsBuilder.newOptions ().addExecutorLzReceiveOption (90_000 , 0 ); // 90_000 gas limit for the receiving executor and 0 for the executor's value
77- enforcedOptions[0 ] = EnforcedOptionParam (targetParams.lzEndpointId, 1 , _extraOptions); // lzReceive
78- enforcedOptions[1 ] = EnforcedOptionParam (targetParams.lzEndpointId, 2 , _extraOptions); // lzCompose
79- sourceBridge.setEnforcedOptions (enforcedOptions);
80- // Authorize bridge in the relevant contract.
81- if (sourceParams.approvalRequired) {
82- RLCLiquidityUnifier rlcLiquidityUnifier = RLCLiquidityUnifier (sourceParams.rlcLiquidityUnifierAddress);
83- bytes32 bridgeTokenRoleId = rlcLiquidityUnifier.TOKEN_BRIDGE_ROLE ();
84- rlcLiquidityUnifier.grantRole (bridgeTokenRoleId, address (sourceBridge));
85- } else {
86- RLCCrosschainToken rlcCrosschainToken = RLCCrosschainToken (sourceParams.rlcCrosschainTokenAddress);
87- bytes32 bridgeTokenRoleId = rlcCrosschainToken.TOKEN_BRIDGE_ROLE ();
88- rlcCrosschainToken.grantRole (bridgeTokenRoleId, address (sourceBridge));
119+ return bool1 || bool2 || bool3;
120+ }
121+
122+ /**
123+ * Sets the bridge peer if it is not already set. Otherwise, do nothing.
124+ * @dev see https://docs.layerzero.network/v2/developers/evm/technical-reference/integration-checklist#call-setpeer-on-every-oapp-deployment
125+ * @param bridgeAddress The address of the LayerZero bridge contract.
126+ * @param targetEndpointId The ID of the target LayerZero endpoint.
127+ * @param targetBridgeAddress The address of the target LayerZero bridge contract.
128+ */
129+ function setBridgePeerIfNeeded (address bridgeAddress , uint32 targetEndpointId , address targetBridgeAddress )
130+ public
131+ returns (bool )
132+ {
133+ IexecLayerZeroBridge bridge = IexecLayerZeroBridge (bridgeAddress);
134+ bytes32 peer = bytes32 (uint256 (uint160 (targetBridgeAddress)));
135+ if (bridge.isPeer (targetEndpointId, peer)) {
136+ console.log (
137+ "Bridge peer already set [endpointId:%s, peer:%s] " , vm.toString (targetEndpointId), vm.toString (peer)
138+ );
139+ return false ;
89140 }
141+ console.log ("Setting bridge peer [endpointId:%s, peer:%s] " , vm.toString (targetEndpointId), vm.toString (peer));
142+ bridge.setPeer (targetEndpointId, peer);
143+ return true ;
144+ }
90145
91- vm.stopBroadcast ();
146+ /**
147+ * Sets the enforced options for the LayerZero bridge if they are not already set.
148+ * If the same options are already enforced on-chain, do nothing.
149+ * @dev see https://docs.layerzero.network/v2/developers/evm/technical-reference/integration-checklist#implement-enforced-options
150+ * @param bridgeAddress The address of the LayerZero bridge contract.
151+ * @param targetEndpointId The ID of the target LayerZero endpoint.
152+ */
153+ function setEnforcedOptionsIfNeeded (address bridgeAddress , uint32 targetEndpointId ) public returns (bool ) {
154+ IexecLayerZeroBridge bridge = IexecLayerZeroBridge (bridgeAddress);
155+ bytes memory options = LayerZeroUtils.buildLzReceiveExecutorConfig (90_000 , 0 );
156+ if (LayerZeroUtils.matchesOnchainEnforcedOptions (bridge, targetEndpointId, options)) {
157+ console.log (
158+ "Bridge enforced options already set [endpointId:%s, options:%s] " ,
159+ vm.toString (targetEndpointId),
160+ vm.toString (options)
161+ );
162+ return false ;
163+ }
164+ EnforcedOptionParam[] memory enforcedOptions = LayerZeroUtils.buildEnforcedOptions (targetEndpointId, options);
165+ console.log (
166+ "Setting bridge enforced options [endpointId:%s, options:%s] " ,
167+ vm.toString (targetEndpointId),
168+ vm.toString (options)
169+ );
170+ bridge.setEnforcedOptions (enforcedOptions);
171+ return true ;
172+ }
173+
174+ /**
175+ * Authorizes the bridge in the RLCLiquidityUnifier or RLCCrosschainToken contract if it
176+ * is not already authorized. Otherwise, do nothing.
177+ * @param bridge The address of the LayerZero bridge contract.
178+ * @param authorizerAddress The address of the authorizer contract.
179+ * @param roleId The role ID to grant to the bridge.
180+ */
181+ function authorizeBridgeIfNeeded (address bridge , address authorizerAddress , bytes32 roleId ) public returns (bool ) {
182+ IAccessControl authorizer = IAccessControl (authorizerAddress);
183+ if (authorizer.hasRole (roleId, bridge)) {
184+ console.log ("Bridge already authorized " );
185+ return false ;
186+ }
187+ console.log ("Granting bridge role in contract " , authorizerAddress);
188+ authorizer.grantRole (roleId, bridge);
189+ return true ;
92190 }
93191}
94192
193+ /**
194+ * A script to upgrade the IexecLayerZeroBridge contract.
195+ */
95196contract Upgrade is Script {
96197 function run () external {
97198 string memory chain = vm.envString ("CHAIN " );
0 commit comments