@@ -5,13 +5,15 @@ pragma solidity ^0.8.22;
55
66import {OFTCoreUpgradeable} from "@layerzerolabs/oft-evm-upgradeable/contracts/oft/OFTCoreUpgradeable.sol " ;
77import {IERC20Metadata } from "@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol " ;
8- import {IERC7802 } from "@openzeppelin/contracts/interfaces/draft-IERC7802.sol " ;
98import {OwnableUpgradeable} from "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol " ;
109import {UUPSUpgradeable} from "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol " ;
1110import {AccessControlDefaultAdminRulesUpgradeable} from
1211 "@openzeppelin/contracts-upgradeable/access/extensions/AccessControlDefaultAdminRulesUpgradeable.sol " ;
12+ import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol " ;
1313import {DualPausableUpgradeable} from "../utils/DualPausableUpgradeable.sol " ;
1414import {IIexecLayerZeroBridge} from "../../interfaces/IIexecLayerZeroBridge.sol " ;
15+ import {IERC7802 } from "@openzeppelin/contracts/interfaces/draft-IERC7802.sol " ;
16+ import {IRLCLiquidityUnifier} from "../../interfaces/IRLCLiquidityUnifier.sol " ;
1517
1618/**
1719 * @title IexecLayerZeroBridge
@@ -31,6 +33,22 @@ import {IIexecLayerZeroBridge} from "../../interfaces/IIexecLayerZeroBridge.sol"
3133 * Dual-Pause Emergency System:
3234 * 1. Complete Pause: Blocks all bridge operations (incoming and outgoing transfers)
3335 * 2. Send Pause: Blocks only outgoing transfers, allows users to receive/withdraw funds
36+ *
37+ * Architecture Overview:
38+ * This bridge supports two distinct deployment scenarios:
39+ *
40+ * 1. Non-Mainnet Chains (L2s, sidechains, etc.):
41+ * - BRIDGEABLE_TOKEN: Points to RLCCrosschain contract (mintable/burnable tokens)
42+ * - APPROVAL_REQUIRED: false (bridge can mint/burn directly)
43+ * - Mechanism: Mint tokens on transfer-in, burn tokens on transfer-out
44+ *
45+ * 2. Ethereum Mainnet:
46+ * - BRIDGEABLE_TOKEN: Points to LiquidityUnifier contract (manages original RLC tokens)
47+ * - APPROVAL_REQUIRED: true (requires user approval for token transfers)
48+ * - Mechanism: Lock tokens on transfer-out, unlock tokens on transfer-in
49+ * The LiquidityUnifier contract acts as a relayer, implementing ERC-7802 interface
50+ * to provide consistent lock/unlock operations for the original RLC token contract
51+ * that may not natively support the crosschain standard.
3452 */
3553contract IexecLayerZeroBridge is
3654 UUPSUpgradeable ,
@@ -39,20 +57,32 @@ contract IexecLayerZeroBridge is
3957 DualPausableUpgradeable ,
4058 IIexecLayerZeroBridge
4159{
60+ using SafeERC20 for IERC20Metadata ;
61+
4262 /// @dev Role identifier for accounts authorized to upgrade the contract
4363 bytes32 public constant UPGRADER_ROLE = keccak256 ("UPGRADER_ROLE " );
4464
4565 /// @dev Role identifier for accounts authorized to pause/unpause the contract
4666 bytes32 public constant PAUSER_ROLE = keccak256 ("PAUSER_ROLE " );
4767
4868 /**
49- * @dev The RLC token contract that this bridge operates on
50- * Must implement the [ERC-7802](https://eips.ethereum.org/EIPS/eip-7802) interface.
69+ * @custom:oz-upgrades-unsafe-allow state-variable-immutable
70+ */
71+ // slither-disable-next-line naming-convention
72+ address public immutable BRIDGEABLE_TOKEN;
73+
74+ /**
75+ * @dev Indicates the token transfer mechanism required for this deployment.
76+ *
77+ * - true: Ethereum Mainnet deployment requiring user approval (lock/unlock mechanism)
78+ * - false: Non Ethereum Mainnet deployment with direct mint/burn capabilities
79+ *
80+ * This flag indicates on which chain the bridge is deployed.
5181 *
5282 * @custom:oz-upgrades-unsafe-allow state-variable-immutable
5383 */
5484 // slither-disable-next-line naming-convention
55- IERC7802 public immutable BRIDGEABLE_TOKEN ;
85+ bool private immutable APPROVAL_REQUIRED ;
5686
5787 /**
5888 * @dev Constructor for the LayerZero bridge contract
@@ -61,11 +91,12 @@ contract IexecLayerZeroBridge is
6191 *
6292 * @custom:oz-upgrades-unsafe-allow constructor
6393 */
64- constructor (address bridgeableToken , address lzEndpoint )
94+ constructor (bool approvalRequired_ , address bridgeableToken , address lzEndpoint )
6595 OFTCoreUpgradeable (IERC20Metadata (bridgeableToken).decimals (), lzEndpoint)
6696 {
6797 _disableInitializers ();
68- BRIDGEABLE_TOKEN = IERC7802 (bridgeableToken);
98+ BRIDGEABLE_TOKEN = bridgeableToken;
99+ APPROVAL_REQUIRED = approvalRequired_;
69100 }
70101
71102 // ============ INITIALIZATION ============
@@ -143,15 +174,15 @@ contract IexecLayerZeroBridge is
143174 * @return requiresApproval Returns true if deployed on Ethereum Mainnet, false otherwise
144175 */
145176 function approvalRequired () external view virtual returns (bool ) {
146- return block . chainid == 1 ;
177+ return APPROVAL_REQUIRED ;
147178 }
148179
149180 /**
150181 * @notice Returns the address of the underlying token being bridged
151182 * @return The address of the RLC token contract
152183 */
153184 function token () external view returns (address ) {
154- return address (BRIDGEABLE_TOKEN);
185+ return APPROVAL_REQUIRED ? address (IRLCLiquidityUnifier ( BRIDGEABLE_TOKEN). RLC_TOKEN ()) : BRIDGEABLE_TOKEN ;
155186 }
156187
157188 // ============ ACCESS CONTROL OVERRIDES ============
@@ -183,6 +214,9 @@ contract IexecLayerZeroBridge is
183214 * It overrides the `_debit` function
184215 * https://github.com/LayerZero-Labs/devtools/blob/a2e444f4c3a6cb7ae88166d785bd7cf2d9609c7f/packages/oft-evm/contracts/OFT.sol#L56-L69
185216 *
217+ * This function behavior is chain specific and works differently
218+ * depending on whether the bridge is deployed on Ethereum Mainnet or a non-mainnet chain.
219+ *
186220 * IMPORTANT ASSUMPTIONS:
187221 * - This implementation assumes LOSSLESS transfers (1 token burned = 1 token minted)
188222 * - If BRIDGEABLE_TOKEN implements transfer fees, burn fees, or any other fee mechanism,
@@ -213,8 +247,14 @@ contract IexecLayerZeroBridge is
213247 // Calculate the amounts using the parent's logic (handles slippage protection)
214248 (amountSentLD, amountReceivedLD) = _debitView (amountLD, minAmountLD, dstEid);
215249
216- // Burn the tokens from the sender's balance
217- BRIDGEABLE_TOKEN.crosschainBurn (from, amountSentLD);
250+ if (APPROVAL_REQUIRED) {
251+ // Transfer RLC tokens from the user's account to the LiquidityUnifier contract.
252+ // The normal workflow would be to call `LiquidityUnifier#crosschainBurn()` but this workflow is not compatible with Stargate UI.
253+ // Stargate UI does not support approving a contract other than the bridge itself, so here the LiquidityUnifier will not be able to send the `transferFrom` transaction.
254+ IRLCLiquidityUnifier (BRIDGEABLE_TOKEN).RLC_TOKEN ().safeTransferFrom (from, BRIDGEABLE_TOKEN, amountSentLD);
255+ } else {
256+ IERC7802 (BRIDGEABLE_TOKEN).crosschainBurn (from, amountSentLD);
257+ }
218258 }
219259
220260 /**
@@ -225,6 +265,7 @@ contract IexecLayerZeroBridge is
225265 * It overrides the `_credit` function
226266 * https://github.com/LayerZero-Labs/devtools/blob/a2e444f4c3a6cb7ae88166d785bd7cf2d9609c7f/packages/oft-evm/contracts/OFT.sol#L78-L88
227267 *
268+ * This function behavior is chain agnostic and works the same for both chains that does or doesn't require approval.
228269 *
229270 * IMPORTANT ASSUMPTIONS:
230271 * - This implementation assumes LOSSLESS transfers (1 token received = 1 token minted)
@@ -257,7 +298,7 @@ contract IexecLayerZeroBridge is
257298
258299 // Mint the tokens to the recipient
259300 // This assumes crosschainMint doesn't apply any fees
260- BRIDGEABLE_TOKEN.crosschainMint (to, amountLD);
301+ IERC7802 ( BRIDGEABLE_TOKEN) .crosschainMint (to, amountLD);
261302
262303 // Return the amount minted (assuming no fees)
263304 return amountLD;
0 commit comments