From 4fa79bad031eb6202796d77eb492ac82972ec04c Mon Sep 17 00:00:00 2001 From: hexshire Date: Mon, 11 Aug 2025 09:50:07 -0300 Subject: [PATCH 1/7] feat(cgt): cgt bridges --- specs/protocol/jovian/bridges.md | 288 ++++++++++++++++++++++++++++ specs/protocol/jovian/predeploys.md | 95 +++++++++ 2 files changed, 383 insertions(+) diff --git a/specs/protocol/jovian/bridges.md b/specs/protocol/jovian/bridges.md index 87b070697..be8dcc22c 100644 --- a/specs/protocol/jovian/bridges.md +++ b/specs/protocol/jovian/bridges.md @@ -5,9 +5,297 @@ **Table of Contents** - [Overview](#overview) +- [Custom Gas Token Bridges](#custom-gas-token-bridges) +- [Token Flow](#token-flow) +- [L1CGTBridge](#l1cgtbridge) + - [Interface](#interface) + - [Functions](#functions) + - [Events](#events) +- [L2CGTBridge](#l2cgtbridge) +- [Bridge Communication](#bridge-communication) +- [Security Considerations](#security-considerations) ## Overview ETH bridging functions MUST revert when Custom Gas Token mode is enabled and the function involves ETH transfers. This revert behavior is necessary because when a chain operates in Custom Gas Token mode, ETH is no longer the native asset used for gas fees and transactions. The chain has shifted to using a different native asset entirely. Allowing ETH transfers could create confusion about which asset serves as the native currency, potentially leading to user errors and lost funds. Additionally, the custom gas token's supply is managed independently through dedicated contracts (`NativeAssetLiquidity` and `LiquidityController`), and mixing ETH bridging with custom gas token operations would complicate supply management and accounting. + +## Custom Gas Token Bridges + +The Custom Gas Token (CGT) bridges enable bidirectional transfers between L1 ERC20 tokens and L2 native assets on chains using Custom Gas Token mode. Unlike standard bridges that handle ETH and generic ERC20 tokens, CGT bridges are specifically designed to convert between a designated L1 ERC20 token and the L2's native gas-paying asset. + +The CGT bridge system consists of: +- **L1CGTBridge**: Deployed on L1, manages locking/unlocking of the designated ERC20 token +- **L2CGTBridge**: Predeploy on L2 at `0x420000000000000000000000000000000000002B`, manages minting/burning of native assets + +Both bridges communicate exclusively through their respective CrossDomainMessenger contracts and maintain a trusted relationship where only messages from the counterpart bridge are accepted. + +## Token Flow + +**L1 → L2 Flow (ERC20 to Native Asset):** +1. User calls `bridgeCGTTo()` on L1CGTBridge with ERC20 tokens +2. L1CGTBridge locks ERC20 tokens and sends message to L2CGTBridge via L1CrossDomainMessenger +3. L2CGTBridge receives message, calls `LiquidityController.mint()` to unlock native assets +4. Native assets are sent to recipient address on L2 + +**L2 → L1 Flow (Native Asset to ERC20):** +1. User calls `bridgeCGTTo()` on L2CGTBridge with native assets (msg.value) +2. L2CGTBridge calls `LiquidityController.burn()` to deposit native assets into NativeAssetLiquidity +3. L2CGTBridge sends message to L1CGTBridge via L2CrossDomainMessenger +4. L1CGTBridge receives message and unlocks ERC20 tokens to recipient address on L1 + +## Legacy Withdrawal Support + +The L1CGTBridge provides functionality to handle legacy withdrawals initiated before CGT migration. These functions use a trusted storage root to prove and finalize withdrawals containing native asset transfers (`_tx.value > 0`) that cannot be processed through the standard OptimismPortal after migration. + +## L1CGTBridge + +The L1CGTBridge contract is deployed individually for each L2 chain using Custom Gas Token mode. It manages the L1 side of CGT bridging operations. + +### Interface + +```solidity +interface IL1CGTBridge { + // Events + event CGTBridgeInitiated(address indexed from, address indexed to, uint256 amount, bytes extraData); + event CGTBridgeFinalized(address indexed from, address indexed to, uint256 amount, bytes extraData); + event WithdrawalProven(bytes32 indexed withdrawalHash, address indexed from, address indexed to); + event WithdrawalProvenExtension1(bytes32 indexed withdrawalHash, address indexed proofSubmitter); + event WithdrawalFinalized(bytes32 indexed withdrawalHash, bool success); + + // Core bridge functions + function bridgeCGT(uint256 _amount, uint32 _minGasLimit, bytes calldata _extraData) external; + function bridgeCGTTo(address _to, uint256 _amount, uint32 _minGasLimit, bytes calldata _extraData) external; + function finalizeBridgeCGT(address _from, address _to, uint256 _amount, bytes calldata _extraData) external; + + // Legacy withdrawal functions (for CGT migration) + function legacyProveWithdrawalTransaction(Types.WithdrawalTransaction memory _tx, bytes[] calldata _withdrawalProof) external; + function legacyFinalizeWithdrawalTransaction(Types.WithdrawalTransaction memory _tx) external; + + // Trusted state management (migration only) + function setTrustedStateOnce(bytes32 trustedMessagePasserStorageRoot) external; + + // Bridge management + function enableDeposits() external; + function disableDeposits() external; + function enableWithdrawals() external; + function disableWithdrawals() external; + + // Configuration getters + function cgtToken() external view returns (address); + function otherBridge() external view returns (address); + function messenger() external view returns (address); + function trustedMessagePasserStorageRoot() external view returns (bytes32); +} +``` + +### Functions + +#### `bridgeCGT` + +Initiates a CGT transfer from L1 to L2 for the caller. + +```solidity +function bridgeCGT(uint256 _amount, uint32 _minGasLimit, bytes calldata _extraData) external +``` + +- MUST transfer `_amount` of CGT ERC20 tokens from `msg.sender` to the bridge contract +- MUST send a message to L2CGTBridge via CrossDomainMessenger to mint equivalent native assets to `msg.sender` +- MUST emit `CGTBridgeInitiated` event +- MUST revert if `_amount` is zero +- MUST revert if caller has insufficient token balance or allowance + +#### `bridgeCGTTo` + +Initiates a CGT transfer from L1 to L2 to a specified recipient. + +```solidity +function bridgeCGTTo(address _to, uint256 _amount, uint32 _minGasLimit, bytes calldata _extraData) external +``` + +- MUST transfer `_amount` of CGT ERC20 tokens from `msg.sender` to the bridge contract +- MUST send a message to L2CGTBridge via CrossDomainMessenger to mint equivalent native assets to `_to` +- MUST emit `CGTBridgeInitiated` event +- MUST revert if `_to` is zero address +- MUST revert if `_amount` is zero +- MUST revert if caller has insufficient token balance or allowance + +#### `finalizeBridgeCGT` + +Finalizes a CGT transfer from L2 to L1 by unlocking tokens. + +```solidity +function finalizeBridgeCGT(address _from, address _to, uint256 _amount, bytes calldata _extraData) external +``` + +- MUST only be callable by the CrossDomainMessenger when relaying a message from L2CGTBridge +- MUST transfer `_amount` of CGT ERC20 tokens from bridge contract to `_to` address +- MUST emit `CGTBridgeFinalized` event +- MUST revert if called by any address other than the CrossDomainMessenger +- MUST revert if the CrossDomainMessenger's `xDomainMessageSender()` is not the L2CGTBridge address +- MUST revert if bridge has insufficient token balance + +#### `setTrustedStateOnce` + +Sets the trusted L2ToL1MessagePasser storage root for legacy withdrawal verification (migration only). + +```solidity +function setTrustedStateOnce(bytes32 trustedMessagePasserStorageRoot) external +``` + +- MUST only be callable by the contract owner +- MUST only be callable once (subsequent calls MUST revert) +- MUST store the provided storage root for legacy withdrawal proofs +- MUST be set to a valid L2ToL1MessagePasser storage root from after the CGT migration +- MUST emit appropriate event when set + +#### `legacyProveWithdrawalTransaction` + +Proves a legacy withdrawal transaction using the trusted storage root (migration only). + +```solidity +function legacyProveWithdrawalTransaction(Types.WithdrawalTransaction memory _tx, bytes[] calldata _withdrawalProof) external +``` + +- MUST only be callable when `trustedMessagePasserStorageRoot` has been set +- MUST compute withdrawal hash using `Hashing.hashWithdrawal(_tx)` +- MUST verify inclusion proof using `SecureMerkleTrie.verifyInclusionProof()` against trusted root +- MUST store proven withdrawal in `provenLegacyWithdrawals[withdrawalHash][msg.sender]` +- MUST emit `WithdrawalProven` and `WithdrawalProvenExtension1` events +- MUST revert if proof is invalid + +#### `legacyFinalizeWithdrawalTransaction` + +Finalizes a proven legacy withdrawal transaction (migration only). + +```solidity +function legacyFinalizeWithdrawalTransaction(Types.WithdrawalTransaction memory _tx) external +``` + +- MUST only be callable for previously proven withdrawals +- MUST verify withdrawal has not been finalized in OptimismPortal or L1CGTBridge +- MUST only finalize withdrawals with `_tx.value > 0` (native asset withdrawals) +- MUST transfer ERC20 tokens equivalent to `_tx.value` to `_tx.target` +- MUST execute `_tx.data` if present (with gas limit checks) +- MUST mark withdrawal as finalized to prevent replay +- MUST revert if `_tx.target` is the CGT token contract address +- MUST emit `WithdrawalFinalized` event + +#### `enableDeposits` / `disableDeposits` + +Enables or disables deposit functionality for emergency control. + +```solidity +function enableDeposits() external +function disableDeposits() external +``` + +- MUST only be callable by the contract owner +- MUST pause/unpause deposit functions (`bridgeCGT`, `bridgeCGTTo`) +- MUST emit appropriate events when state changes + +#### `enableWithdrawals` / `disableWithdrawals` + +Enables or disables withdrawal functionality for emergency control. + +```solidity +function enableWithdrawals() external +function disableWithdrawals() external +``` + +- MUST only be callable by the contract owner +- MUST pause/unpause withdrawal functions (`finalizeBridgeCGT`, legacy functions) +- MUST emit appropriate events when state changes + +### Events + +#### `CGTBridgeInitiated` + +Emitted when a CGT bridge transfer is initiated from L1 to L2. + +```solidity +event CGTBridgeInitiated(address indexed from, address indexed to, uint256 amount, bytes extraData) +``` + +#### `CGTBridgeFinalized` + +Emitted when a CGT bridge transfer from L2 to L1 is finalized on L1. + +```solidity +event CGTBridgeFinalized(address indexed from, address indexed to, uint256 amount, bytes extraData) +``` + +#### `WithdrawalProven` + +Emitted when a legacy withdrawal is successfully proven. + +```solidity +event WithdrawalProven(bytes32 indexed withdrawalHash, address indexed from, address indexed to) +``` + +#### `WithdrawalProvenExtension1` + +Emitted when a legacy withdrawal is proven, includes proof submitter information. + +```solidity +event WithdrawalProvenExtension1(bytes32 indexed withdrawalHash, address indexed proofSubmitter) +``` + +#### `WithdrawalFinalized` + +Emitted when a legacy withdrawal is finalized. + +```solidity +event WithdrawalFinalized(bytes32 indexed withdrawalHash, bool success) +``` + +## L2CGTBridge + +The L2CGTBridge is a predeploy contract that handles the L2 side of CGT bridging operations. See the [L2CGTBridge predeploy specification](predeploys.md#l2cgtbridge) for complete interface and function definitions. + +Address: `0x420000000000000000000000000000000000002B` + +## Bridge Communication + +Both bridges maintain a trusted relationship and can only accept finalization messages from their designated counterpart: + +**L1CGTBridge Configuration:** +- `otherBridge`: Address of the L2CGTBridge predeploy (`0x420000000000000000000000000000000000002B`) +- `messenger`: Address of the L1CrossDomainMessenger +- `cgtToken`: Address of the ERC20 token being bridged (immutable, set at deployment) + +**L2CGTBridge Configuration:** +- `otherBridge`: Address of the corresponding L1CGTBridge (set during initialization) +- `messenger`: Address of the L2CrossDomainMessenger predeploy +- `liquidityController`: Address of the LiquidityController predeploy + +The bridges enforce cross-domain message authentication by: +1. Verifying calls to `finalizeBridgeCGT()` come from the CrossDomainMessenger +2. Verifying the `xDomainMessageSender()` matches the expected counterpart bridge address +3. Only processing messages that originate from the trusted counterpart bridge + +## Security Considerations + +**Access Control:** +- L2CGTBridge MUST be authorized as a minter in the LiquidityController before any L1→L2 transfers can be completed +- Only the designated L1CGTBridge can send finalization messages to L2CGTBridge +- Only the L2CGTBridge can send finalization messages to L1CGTBridge + +**Token Safety:** +- L1CGTBridge holds locked ERC20 tokens as collateral for minted L2 native assets +- Native assets on L2 are backed 1:1 by locked ERC20 tokens on L1 +- Burns on L2 immediately deposit assets into NativeAssetLiquidity, reducing circulating supply + +**Bridge Integrity:** +- Failed cross-domain messages can be replayed through the standard message relay mechanisms +- Bridge contracts SHOULD implement pausability for emergency situations +- Both bridges MUST validate all message parameters to prevent invalid minting or unlocking operations + +**Legacy Withdrawal Security:** +- `setTrustedStateOnce()` MUST only be callable once by contract owner +- `trustedMessagePasserStorageRoot` MUST represent valid post-migration L2ToL1MessagePasser state +- Legacy finalization MUST check both OptimismPortal and L1CGTBridge to prevent replays +- Legacy finalization MUST reject `_tx.target == cgtToken` to prevent approval attacks +- Legacy withdrawals MUST only process transactions with `_tx.value > 0` +- Proof verification MUST use SecureMerkleTrie with same standards as OptimismPortal diff --git a/specs/protocol/jovian/predeploys.md b/specs/protocol/jovian/predeploys.md index 047fa6f23..70bcfeb8f 100644 --- a/specs/protocol/jovian/predeploys.md +++ b/specs/protocol/jovian/predeploys.md @@ -34,6 +34,15 @@ - [`AssetsMinted`](#assetsminted) - [`AssetsBurned`](#assetsburned) - [Invariants](#invariants-1) +- [L2CGTBridge](#l2cgtbridge) + - [Functions](#functions-2) + - [`bridgeCGT`](#bridgecgt) + - [`bridgeCGTTo`](#bridgecgtto) + - [`finalizeBridgeCGT`](#finalizebridgecgt) + - [Events](#events-2) + - [`CGTBridgeInitiated`](#cgtbridgeinitiated) + - [`CGTBridgeFinalized`](#cgtbridgefinalized) + - [Invariants](#invariants-2) @@ -43,6 +52,7 @@ | -------------------- | ------------------------------------------ | ---------- | ---------- | ------- | | NativeAssetLiquidity | 0x4200000000000000000000000000000000000029 | Jovian | No | Yes | | LiquidityController | 0x420000000000000000000000000000000000002A | Jovian | No | Yes | +| L2CGTBridge | 0x420000000000000000000000000000000000002B | Jovian | No | Yes | ## WETH9 @@ -338,3 +348,88 @@ Where `burner` is the `msg.sender` who burned the assets and `amount` is the amo - The contract acts as the sole interface between governance and `NativeAssetLiquidity` - `burn()` operations always increase locked supply by calling `NativeAssetLiquidity.deposit()` - `mint()` operations always decrease locked supply by calling `NativeAssetLiquidity.withdraw()` + +## L2CGTBridge + +Address: `0x420000000000000000000000000000000000002B` + +The `L2CGTBridge` predeploy handles the L2 side of Custom Gas Token bridging operations, enabling bidirectional transfers between L1 ERC20 tokens and L2 native assets. This contract is only deployed on chains using Custom Gas Token mode and has minting authorization from the `LiquidityController` to manage native asset supply. + +The bridge maintains a trusted relationship with its L1 counterpart (`L1CGTBridge`) and only accepts cross-domain messages from the designated L1 bridge address through the `L2CrossDomainMessenger`. + +### Functions + +#### `bridgeCGT` + +Initiates a CGT transfer from L2 to L1 for the caller. + +```solidity +function bridgeCGT(uint32 _minGasLimit, bytes calldata _extraData) external payable +``` + +- MUST accept native assets via `msg.value` +- MUST call `LiquidityController.burn{value: msg.value}()` to deposit native assets into NativeAssetLiquidity contract +- MUST send cross-domain message to L1CGTBridge via `L2CrossDomainMessenger` to unlock equivalent ERC20 tokens to `msg.sender` +- MUST emit `CGTBridgeInitiated` event +- MUST revert if `msg.value` is zero + +#### `bridgeCGTTo` + +Initiates a CGT transfer from L2 to L1 to a specified recipient address. + +```solidity +function bridgeCGTTo(address _to, uint32 _minGasLimit, bytes calldata _extraData) external payable +``` + +- MUST accept native assets via `msg.value` +- MUST call `LiquidityController.burn{value: msg.value}()` to deposit native assets into NativeAssetLiquidity contract +- MUST send cross-domain message to L1CGTBridge via `L2CrossDomainMessenger` to unlock equivalent ERC20 tokens to `_to` +- MUST emit `CGTBridgeInitiated` event +- MUST revert if `_to` is zero address +- MUST revert if `msg.value` is zero + +#### `finalizeBridgeCGT` + +Finalizes a CGT transfer from L1 to L2 by minting native assets to the recipient. + +```solidity +function finalizeBridgeCGT(address _from, address _to, uint256 _amount, bytes calldata _extraData) external +``` + +- MUST only be callable by the `L2CrossDomainMessenger` when relaying a message from the authorized L1CGTBridge +- MUST call `LiquidityController.mint(_to, _amount)` to mint native assets to recipient +- MUST emit `CGTBridgeFinalized` event +- MUST revert if called by any address other than the `L2CrossDomainMessenger` +- MUST revert if the `CrossDomainMessenger.xDomainMessageSender()` is not the authorized L1CGTBridge address +- MUST revert if `LiquidityController.mint()` operation fails + +### Events + +#### `CGTBridgeInitiated` + +Emitted when a CGT bridge transfer is initiated from L2 to L1. + +```solidity +event CGTBridgeInitiated(address indexed from, address indexed to, uint256 amount, bytes extraData) +``` + +Where `from` is the L2 sender, `to` is the L1 recipient, `amount` is the native asset amount being bridged, and `extraData` contains additional bridging parameters. + +#### `CGTBridgeFinalized` + +Emitted when a CGT bridge transfer from L1 to L2 is finalized on L2. + +```solidity +event CGTBridgeFinalized(address indexed from, address indexed to, uint256 amount, bytes extraData) +``` + +Where `from` is the L1 sender, `to` is the L2 recipient, `amount` is the native asset amount minted, and `extraData` contains additional bridging parameters. + +### Invariants + +- Only the `L2CrossDomainMessenger` can call `finalizeBridgeCGT()` when relaying messages from the authorized L1CGTBridge +- The L2CGTBridge MUST be authorized as a minter in the `LiquidityController` before any bridging operations +- All L2→L1 transfers immediately burn native assets by depositing them into `NativeAssetLiquidity` +- All L1→L2 transfers mint native assets by withdrawing them from `NativeAssetLiquidity` via `LiquidityController` +- Native asset supply on L2 is backed 1:1 by locked ERC20 tokens on L1 +- Cross-domain message authentication ensures only trusted L1CGTBridge can trigger finalization From 64141140b0fd62e603ee36d825c26e44cada7482 Mon Sep 17 00:00:00 2001 From: hexshire Date: Tue, 12 Aug 2025 18:12:09 -0300 Subject: [PATCH 2/7] chore(cgt): address review comments --- specs/protocol/jovian/overview.md | 12 ++++++------ specs/protocol/jovian/predeploys.md | 4 +--- 2 files changed, 7 insertions(+), 9 deletions(-) diff --git a/specs/protocol/jovian/overview.md b/specs/protocol/jovian/overview.md index 923eafed6..747799f58 100644 --- a/specs/protocol/jovian/overview.md +++ b/specs/protocol/jovian/overview.md @@ -34,12 +34,12 @@ The Custom Gas Token (CGT) feature allows OP Stack chains to use a native asset Key components: -- **NativeAssetLiquidity**: A predeploy contract containing pre-minted native assets, deployed only for CGT-enabled chains -- **LiquidityController**: An owner-governed mint/burn router that manages supply control, deployed only for CGT-enabled chains -- **ETH Transfer Blocking**: When CGT is enabled, all ETH transfer flows in bridging methods are disabled via the `isCustomGasToken()` flag -- **ETH Bridging Disabled**: ETH bridging functions in `L2StandardBridge` and `OptimismPortal` MUST revert when CGT mode is enabled to prevent confusion about which asset is the native currency -- **Native Asset Bridging**: Custom Gas Token chains use dedicated CGT bridges (`L1CGTBridge` and `L2CGTBridge`) for native asset transfers between L1 ERC20 tokens and L2 native assets -- **WETH as ERC20**: ETH can still be bridged as WETH using the standard `OptimismMintableERC20` bridging path through `L2StandardBridge` +- **NativeAssetLiquidity**: A predeploy contract containing pre-minted native assets, deployed only for CGT-enabled chains. +- **LiquidityController**: An owner-governed mint/burn router that manages supply control, deployed only for CGT-enabled chains. +- **ETH Transfer Blocking**: When CGT is enabled, all ETH transfer flows in bridging methods are disabled via the `isCustomGasToken()` flag. +- **ETH Bridging Disabled**: ETH bridging functions in `L2ToL1MessagePasser` and `OptimismPortal` MUST revert when CGT mode is enabled to prevent confusion about which asset is the native currency. +- **Native Asset Bridging**: Custom Gas Token chains use dedicated CGT bridges (`L1CGTBridge` and `L2CGTBridge`) for native asset transfers between L1 ERC20 tokens and L2 native assets. +- **WETH as ERC20**: ETH can still be bridged as WETH using the standard `OptimismMintableERC20` bridging path through `L2StandardBridge`. OP Stack chains that use a native asset other than ETH (or the native asset of the settlement layer) introduce custom requirements that go beyond the current supply management model based on deposits and withdrawals. This architecture decouples and winds down the native bridging for the native asset, shifting the responsibility for supply management to the application layer. The chain operator becomes responsible for defining and assigning meaning to the native asset, which is managed through a new set of predeployed contracts. diff --git a/specs/protocol/jovian/predeploys.md b/specs/protocol/jovian/predeploys.md index 70bcfeb8f..40823ac07 100644 --- a/specs/protocol/jovian/predeploys.md +++ b/specs/protocol/jovian/predeploys.md @@ -158,13 +158,12 @@ function withdraw(uint256 _amount) external - MUST only be callable by the `LiquidityController` predeploy - MUST send exactly `_amount` of native asset to the caller -- MUST revert if called by any address other than `LiquidityController` - MUST revert if the contract balance is insufficient - MUST emit `LiquidityWithdrawn` event #### `fund` -Allows funding the contract with native assets, typically used during genesis deployment. +This function is used to initialize the contract with a large liquidity pool. ```solidity function fund() external payable @@ -265,7 +264,6 @@ function mint(address _to, uint256 _amount) external - MUST only be callable by authorized minters - MUST call `NativeAssetLiquidity.withdraw(_amount)` to unlock assets - MUST send exactly `_amount` of native asset to `_to` address -- MUST revert if caller is not an authorized minter - MUST revert if `NativeAssetLiquidity` has insufficient balance - MUST emit `AssetsMinted` event From ead6d1cadc5f622061232eab645f9953648c1aab Mon Sep 17 00:00:00 2001 From: hexshire Date: Tue, 12 Aug 2025 18:12:22 -0300 Subject: [PATCH 3/7] chore(cgt): address review comments --- specs/protocol/jovian/predeploys.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/specs/protocol/jovian/predeploys.md b/specs/protocol/jovian/predeploys.md index 40823ac07..970953682 100644 --- a/specs/protocol/jovian/predeploys.md +++ b/specs/protocol/jovian/predeploys.md @@ -163,7 +163,7 @@ function withdraw(uint256 _amount) external #### `fund` -This function is used to initialize the contract with a large liquidity pool. +Allows funding the contract with native assets, typically used during genesis deployment. ```solidity function fund() external payable From d70e4c78d68c0a40334c3cb09433239546b117ae Mon Sep 17 00:00:00 2001 From: hexshire Date: Wed, 13 Aug 2025 09:57:01 -0300 Subject: [PATCH 4/7] chore(cgt): remove cgt bridges spec --- specs/protocol/jovian/bridges.md | 297 ---------------------------- specs/protocol/jovian/predeploys.md | 95 --------- 2 files changed, 392 deletions(-) diff --git a/specs/protocol/jovian/bridges.md b/specs/protocol/jovian/bridges.md index 83c724b41..9ffb22008 100644 --- a/specs/protocol/jovian/bridges.md +++ b/specs/protocol/jovian/bridges.md @@ -6,15 +6,6 @@ **Table of Contents** - [Overview](#overview) -- [Custom Gas Token Bridges](#custom-gas-token-bridges) -- [Token Flow](#token-flow) -- [L1CGTBridge](#l1cgtbridge) - - [Interface](#interface) - - [Functions](#functions) - - [Events](#events) -- [L2CGTBridge](#l2cgtbridge) -- [Bridge Communication](#bridge-communication) -- [Security Considerations](#security-considerations) @@ -22,291 +13,3 @@ ETH bridging functions MUST revert when Custom Gas Token mode is enabled and the function involves ETH transfers. This revert behavior is necessary because when a chain operates in Custom Gas Token mode, ETH is no longer the native asset used for gas fees and transactions. The chain has shifted to using a different native asset entirely. Allowing ETH transfers could create confusion about which asset serves as the native currency, potentially leading to user errors and lost funds. Additionally, the custom gas token's supply is managed independently through dedicated contracts (`NativeAssetLiquidity` and `LiquidityController`), and combining ETH bridging with custom gas token operations introduces additional complexity to supply management and accounting. -## Custom Gas Token Bridges - -The Custom Gas Token (CGT) bridges enable bidirectional transfers between L1 ERC20 tokens and L2 native assets on chains using Custom Gas Token mode. Unlike standard bridges that handle ETH and generic ERC20 tokens, CGT bridges are specifically designed to convert between a designated L1 ERC20 token and the L2's native gas-paying asset. - -The CGT bridge system consists of: - -- **L1CGTBridge**: Deployed on L1, manages locking/unlocking of the designated ERC20 token -- **L2CGTBridge**: Predeploy on L2 at `0x420000000000000000000000000000000000002B`, manages minting/burning of native assets - -Both bridges communicate exclusively through their respective CrossDomainMessenger contracts and maintain a trusted relationship where only messages from the counterpart bridge are accepted. - -## Token Flow - -**L1 → L2 Flow (ERC20 to Native Asset):** - -1. User calls `bridgeCGTTo()` on L1CGTBridge with ERC20 tokens -2. L1CGTBridge locks ERC20 tokens and sends message to L2CGTBridge via L1CrossDomainMessenger -3. L2CGTBridge receives message, calls `LiquidityController.mint()` to unlock native assets -4. Native assets are sent to recipient address on L2 - -**L2 → L1 Flow (Native Asset to ERC20):** - -1. User calls `bridgeCGTTo()` on L2CGTBridge with native assets (msg.value) -2. L2CGTBridge calls `LiquidityController.burn()` to deposit native assets into NativeAssetLiquidity -3. L2CGTBridge sends message to L1CGTBridge via L2CrossDomainMessenger -4. L1CGTBridge receives message and unlocks ERC20 tokens to recipient address on L1 - -## Legacy Withdrawal Support - -The L1CGTBridge provides functionality to handle legacy withdrawals initiated before CGT migration. These functions use a trusted storage root to prove and finalize withdrawals containing native asset transfers (`_tx.value > 0`) that cannot be processed through the standard OptimismPortal after migration. - -## L1CGTBridge - -The L1CGTBridge contract is deployed individually for each L2 chain using Custom Gas Token mode. It manages the L1 side of CGT bridging operations. - -### Interface - -```solidity -interface IL1CGTBridge { - // Events - event CGTBridgeInitiated(address indexed from, address indexed to, uint256 amount, bytes extraData); - event CGTBridgeFinalized(address indexed from, address indexed to, uint256 amount, bytes extraData); - event WithdrawalProven(bytes32 indexed withdrawalHash, address indexed from, address indexed to); - event WithdrawalProvenExtension1(bytes32 indexed withdrawalHash, address indexed proofSubmitter); - event WithdrawalFinalized(bytes32 indexed withdrawalHash, bool success); - - // Core bridge functions - function bridgeCGT(uint256 _amount, uint32 _minGasLimit, bytes calldata _extraData) external; - function bridgeCGTTo(address _to, uint256 _amount, uint32 _minGasLimit, bytes calldata _extraData) external; - function finalizeBridgeCGT(address _from, address _to, uint256 _amount, bytes calldata _extraData) external; - - // Legacy withdrawal functions (for CGT migration) - function legacyProveWithdrawalTransaction(Types.WithdrawalTransaction memory _tx, bytes[] calldata _withdrawalProof) external; - function legacyFinalizeWithdrawalTransaction(Types.WithdrawalTransaction memory _tx) external; - - // Trusted state management (migration only) - function setTrustedStateOnce(bytes32 trustedMessagePasserStorageRoot) external; - - // Bridge management - function enableDeposits() external; - function disableDeposits() external; - function enableWithdrawals() external; - function disableWithdrawals() external; - - // Configuration getters - function cgtToken() external view returns (address); - function otherBridge() external view returns (address); - function messenger() external view returns (address); - function trustedMessagePasserStorageRoot() external view returns (bytes32); -} -``` - -### Functions - -#### `bridgeCGT` - -Initiates a CGT transfer from L1 to L2 for the caller. - -```solidity -function bridgeCGT(uint256 _amount, uint32 _minGasLimit, bytes calldata _extraData) external -``` - -- MUST transfer `_amount` of CGT ERC20 tokens from `msg.sender` to the bridge contract -- MUST send a message to L2CGTBridge via CrossDomainMessenger to mint equivalent native assets to `msg.sender` -- MUST emit `CGTBridgeInitiated` event -- MUST revert if `_amount` is zero -- MUST revert if caller has insufficient token balance or allowance - -#### `bridgeCGTTo` - -Initiates a CGT transfer from L1 to L2 to a specified recipient. - -```solidity -function bridgeCGTTo(address _to, uint256 _amount, uint32 _minGasLimit, bytes calldata _extraData) external -``` - -- MUST transfer `_amount` of CGT ERC20 tokens from `msg.sender` to the bridge contract -- MUST send a message to L2CGTBridge via CrossDomainMessenger to mint equivalent native assets to `_to` -- MUST emit `CGTBridgeInitiated` event -- MUST revert if `_to` is zero address -- MUST revert if `_amount` is zero -- MUST revert if caller has insufficient token balance or allowance - -#### `finalizeBridgeCGT` - -Finalizes a CGT transfer from L2 to L1 by unlocking tokens. - -```solidity -function finalizeBridgeCGT(address _from, address _to, uint256 _amount, bytes calldata _extraData) external -``` - -- MUST only be callable by the CrossDomainMessenger when relaying a message from L2CGTBridge -- MUST transfer `_amount` of CGT ERC20 tokens from bridge contract to `_to` address -- MUST emit `CGTBridgeFinalized` event -- MUST revert if called by any address other than the CrossDomainMessenger -- MUST revert if the CrossDomainMessenger's `xDomainMessageSender()` is not the L2CGTBridge address -- MUST revert if bridge has insufficient token balance - -#### `setTrustedStateOnce` - -Sets the trusted L2ToL1MessagePasser storage root for legacy withdrawal verification (migration only). - -```solidity -function setTrustedStateOnce(bytes32 trustedMessagePasserStorageRoot) external -``` - -- MUST only be callable by the contract owner -- MUST only be callable once (subsequent calls MUST revert) -- MUST store the provided storage root for legacy withdrawal proofs -- MUST be set to a valid L2ToL1MessagePasser storage root from after the CGT migration -- MUST emit appropriate event when set - -#### `legacyProveWithdrawalTransaction` - -Proves a legacy withdrawal transaction using the trusted storage root (migration only). - -```solidity -function legacyProveWithdrawalTransaction(Types.WithdrawalTransaction memory _tx, bytes[] calldata _withdrawalProof) external -``` - -- MUST only be callable when `trustedMessagePasserStorageRoot` has been set -- MUST compute withdrawal hash using `Hashing.hashWithdrawal(_tx)` -- MUST verify inclusion proof using `SecureMerkleTrie.verifyInclusionProof()` against trusted root -- MUST store proven withdrawal in `provenLegacyWithdrawals[withdrawalHash][msg.sender]` -- MUST emit `WithdrawalProven` and `WithdrawalProvenExtension1` events -- MUST revert if proof is invalid - -#### `legacyFinalizeWithdrawalTransaction` - -Finalizes a proven legacy withdrawal transaction (migration only). - -```solidity -function legacyFinalizeWithdrawalTransaction(Types.WithdrawalTransaction memory _tx) external -``` - -- MUST only be callable for previously proven withdrawals -- MUST verify withdrawal has not been finalized in OptimismPortal or L1CGTBridge -- MUST only finalize withdrawals with `_tx.value > 0` (native asset withdrawals) -- MUST transfer ERC20 tokens equivalent to `_tx.value` to `_tx.target` -- MUST execute `_tx.data` if present (with gas limit checks) -- MUST mark withdrawal as finalized to prevent replay -- MUST revert if `_tx.target` is the CGT token contract address -- MUST emit `WithdrawalFinalized` event - -#### `enableDeposits` / `disableDeposits` - -Enables or disables deposit functionality for emergency control. - -```solidity -function enableDeposits() external -function disableDeposits() external -``` - -- MUST only be callable by the contract owner -- MUST pause/unpause deposit functions (`bridgeCGT`, `bridgeCGTTo`) -- MUST emit appropriate events when state changes - -#### `enableWithdrawals` / `disableWithdrawals` - -Enables or disables withdrawal functionality for emergency control. - -```solidity -function enableWithdrawals() external -function disableWithdrawals() external -``` - -- MUST only be callable by the contract owner -- MUST pause/unpause withdrawal functions (`finalizeBridgeCGT`, legacy functions) -- MUST emit appropriate events when state changes - -### Events - -#### `CGTBridgeInitiated` - -Emitted when a CGT bridge transfer is initiated from L1 to L2. - -```solidity -event CGTBridgeInitiated(address indexed from, address indexed to, uint256 amount, bytes extraData) -``` - -#### `CGTBridgeFinalized` - -Emitted when a CGT bridge transfer from L2 to L1 is finalized on L1. - -```solidity -event CGTBridgeFinalized(address indexed from, address indexed to, uint256 amount, bytes extraData) -``` - -#### `WithdrawalProven` - -Emitted when a legacy withdrawal is successfully proven. - -```solidity -event WithdrawalProven(bytes32 indexed withdrawalHash, address indexed from, address indexed to) -``` - -#### `WithdrawalProvenExtension1` - -Emitted when a legacy withdrawal is proven, includes proof submitter information. - -```solidity -event WithdrawalProvenExtension1(bytes32 indexed withdrawalHash, address indexed proofSubmitter) -``` - -#### `WithdrawalFinalized` - -Emitted when a legacy withdrawal is finalized. - -```solidity -event WithdrawalFinalized(bytes32 indexed withdrawalHash, bool success) -``` - -## L2CGTBridge - -The L2CGTBridge is a predeploy contract that handles the L2 side of CGT bridging operations. See the [L2CGTBridge predeploy specification](predeploys.md#l2cgtbridge) for complete interface and function definitions. - -Address: `0x420000000000000000000000000000000000002B` - -## Bridge Communication - -Both bridges maintain a trusted relationship and can only accept finalization messages from their designated counterpart: - -**L1CGTBridge Configuration:** - -- `otherBridge`: Address of the L2CGTBridge predeploy (`0x420000000000000000000000000000000000002B`) -- `messenger`: Address of the L1CrossDomainMessenger -- `cgtToken`: Address of the ERC20 token being bridged (immutable, set at deployment) - -**L2CGTBridge Configuration:** - -- `otherBridge`: Address of the corresponding L1CGTBridge (set during initialization) -- `messenger`: Address of the L2CrossDomainMessenger predeploy -- `liquidityController`: Address of the LiquidityController predeploy - -The bridges enforce cross-domain message authentication by: - -1. Verifying calls to `finalizeBridgeCGT()` come from the CrossDomainMessenger -2. Verifying the `xDomainMessageSender()` matches the expected counterpart bridge address -3. Only processing messages that originate from the trusted counterpart bridge - -## Security Considerations - -**Access Control:** - -- L2CGTBridge MUST be authorized as a minter in the LiquidityController before any L1→L2 transfers can be completed -- Only the designated L1CGTBridge can send finalization messages to L2CGTBridge -- Only the L2CGTBridge can send finalization messages to L1CGTBridge - -**Token Safety:** - -- L1CGTBridge holds locked ERC20 tokens as collateral for minted L2 native assets -- Native assets on L2 are backed 1:1 by locked ERC20 tokens on L1 -- Burns on L2 immediately deposit assets into NativeAssetLiquidity, reducing circulating supply - -**Bridge Integrity:** - -- Failed cross-domain messages can be replayed through the standard message relay mechanisms -- Bridge contracts SHOULD implement pausability for emergency situations -- Both bridges MUST validate all message parameters to prevent invalid minting or unlocking operations - -**Legacy Withdrawal Security:** - -- `setTrustedStateOnce()` MUST only be callable once by contract owner -- `trustedMessagePasserStorageRoot` MUST represent valid post-migration L2ToL1MessagePasser state -- Legacy finalization MUST check both OptimismPortal and L1CGTBridge to prevent replays -- Legacy finalization MUST reject `_tx.target == cgtToken` to prevent approval attacks -- Legacy withdrawals MUST only process transactions with `_tx.value > 0` -- Proof verification MUST use SecureMerkleTrie with same standards as OptimismPortal diff --git a/specs/protocol/jovian/predeploys.md b/specs/protocol/jovian/predeploys.md index b9d430062..cdeab2bb1 100644 --- a/specs/protocol/jovian/predeploys.md +++ b/specs/protocol/jovian/predeploys.md @@ -34,15 +34,6 @@ - [`AssetsMinted`](#assetsminted) - [`AssetsBurned`](#assetsburned) - [Invariants](#invariants-1) -- [L2CGTBridge](#l2cgtbridge) - - [Functions](#functions-2) - - [`bridgeCGT`](#bridgecgt) - - [`bridgeCGTTo`](#bridgecgtto) - - [`finalizeBridgeCGT`](#finalizebridgecgt) - - [Events](#events-2) - - [`CGTBridgeInitiated`](#cgtbridgeinitiated) - - [`CGTBridgeFinalized`](#cgtbridgefinalized) - - [Invariants](#invariants-2) @@ -52,7 +43,6 @@ | -------------------- | ------------------------------------------ | ---------- | ---------- | ------- | | NativeAssetLiquidity | 0x4200000000000000000000000000000000000029 | Jovian | No | Yes | | LiquidityController | 0x420000000000000000000000000000000000002A | Jovian | No | Yes | -| L2CGTBridge | 0x420000000000000000000000000000000000002B | Jovian | No | Yes | ## WETH9 @@ -346,88 +336,3 @@ Where `burner` is the `msg.sender` who burned the assets and `amount` is the amo - The contract acts as the sole interface between governance and `NativeAssetLiquidity` - `burn()` operations always increase locked supply by calling `NativeAssetLiquidity.deposit()` - `mint()` operations always decrease locked supply by calling `NativeAssetLiquidity.withdraw()` - -## L2CGTBridge - -Address: `0x420000000000000000000000000000000000002B` - -The `L2CGTBridge` predeploy handles the L2 side of Custom Gas Token bridging operations, enabling bidirectional transfers between L1 ERC20 tokens and L2 native assets. This contract is only deployed on chains using Custom Gas Token mode and has minting authorization from the `LiquidityController` to manage native asset supply. - -The bridge maintains a trusted relationship with its L1 counterpart (`L1CGTBridge`) and only accepts cross-domain messages from the designated L1 bridge address through the `L2CrossDomainMessenger`. - -### Functions - -#### `bridgeCGT` - -Initiates a CGT transfer from L2 to L1 for the caller. - -```solidity -function bridgeCGT(uint32 _minGasLimit, bytes calldata _extraData) external payable -``` - -- MUST accept native assets via `msg.value` -- MUST call `LiquidityController.burn{value: msg.value}()` to deposit native assets into NativeAssetLiquidity contract -- MUST send cross-domain message to L1CGTBridge via `L2CrossDomainMessenger` to unlock equivalent ERC20 tokens to `msg.sender` -- MUST emit `CGTBridgeInitiated` event -- MUST revert if `msg.value` is zero - -#### `bridgeCGTTo` - -Initiates a CGT transfer from L2 to L1 to a specified recipient address. - -```solidity -function bridgeCGTTo(address _to, uint32 _minGasLimit, bytes calldata _extraData) external payable -``` - -- MUST accept native assets via `msg.value` -- MUST call `LiquidityController.burn{value: msg.value}()` to deposit native assets into NativeAssetLiquidity contract -- MUST send cross-domain message to L1CGTBridge via `L2CrossDomainMessenger` to unlock equivalent ERC20 tokens to `_to` -- MUST emit `CGTBridgeInitiated` event -- MUST revert if `_to` is zero address -- MUST revert if `msg.value` is zero - -#### `finalizeBridgeCGT` - -Finalizes a CGT transfer from L1 to L2 by minting native assets to the recipient. - -```solidity -function finalizeBridgeCGT(address _from, address _to, uint256 _amount, bytes calldata _extraData) external -``` - -- MUST only be callable by the `L2CrossDomainMessenger` when relaying a message from the authorized L1CGTBridge -- MUST call `LiquidityController.mint(_to, _amount)` to mint native assets to recipient -- MUST emit `CGTBridgeFinalized` event -- MUST revert if called by any address other than the `L2CrossDomainMessenger` -- MUST revert if the `CrossDomainMessenger.xDomainMessageSender()` is not the authorized L1CGTBridge address -- MUST revert if `LiquidityController.mint()` operation fails - -### Events - -#### `CGTBridgeInitiated` - -Emitted when a CGT bridge transfer is initiated from L2 to L1. - -```solidity -event CGTBridgeInitiated(address indexed from, address indexed to, uint256 amount, bytes extraData) -``` - -Where `from` is the L2 sender, `to` is the L1 recipient, `amount` is the native asset amount being bridged, and `extraData` contains additional bridging parameters. - -#### `CGTBridgeFinalized` - -Emitted when a CGT bridge transfer from L1 to L2 is finalized on L2. - -```solidity -event CGTBridgeFinalized(address indexed from, address indexed to, uint256 amount, bytes extraData) -``` - -Where `from` is the L1 sender, `to` is the L2 recipient, `amount` is the native asset amount minted, and `extraData` contains additional bridging parameters. - -### Invariants - -- Only the `L2CrossDomainMessenger` can call `finalizeBridgeCGT()` when relaying messages from the authorized L1CGTBridge -- The L2CGTBridge MUST be authorized as a minter in the `LiquidityController` before any bridging operations -- All L2→L1 transfers immediately burn native assets by depositing them into `NativeAssetLiquidity` -- All L1→L2 transfers mint native assets by withdrawing them from `NativeAssetLiquidity` via `LiquidityController` -- Native asset supply on L2 is backed 1:1 by locked ERC20 tokens on L1 -- Cross-domain message authentication ensures only trusted L1CGTBridge can trigger finalization From df4850367f24d98a7f8e16c8b08b66de81673ccc Mon Sep 17 00:00:00 2001 From: hexshire Date: Wed, 13 Aug 2025 10:30:08 -0300 Subject: [PATCH 5/7] chore(cgt): wip new cgt bridges spec --- .../experimental/custom-gas-token-bridges.md | 384 ++++++++++++++++++ 1 file changed, 384 insertions(+) create mode 100644 specs/experimental/custom-gas-token-bridges.md diff --git a/specs/experimental/custom-gas-token-bridges.md b/specs/experimental/custom-gas-token-bridges.md new file mode 100644 index 000000000..1cad10d47 --- /dev/null +++ b/specs/experimental/custom-gas-token-bridges.md @@ -0,0 +1,384 @@ +# Custom Gas Token Bridges + + + + +**Table of Contents** + +- [Overview](#overview) +- [Token Flow](#token-flow) +- [L1CGTBridge](#l1cgtbridge) + - [Interface](#interface) + - [Functions](#functions) + - [Events](#events) +- [L1CGTBridgeWithLegacyWithdrawal](#l1cgtbridgewithlegacywithdrawal) + - [Additional Interface](#additional-interface) + - [Legacy Withdrawal Support](#legacy-withdrawal-support) + - [Additional Functions](#additional-functions) +- [L2CGTBridge](#l2cgtbridge) + - [Functions](#functions-1) + - [Events](#events-1) + - [Invariants](#invariants) +- [Bridge Communication](#bridge-communication) +- [Security Considerations](#security-considerations) + + + +## Overview + +The Custom Gas Token (CGT) bridges enable bidirectional transfers between a specific L1 ERC20 token (designated as the chain's custom gas token) and L2 native assets on chains using Custom Gas Token mode. These bridges are purpose-built to handle the conversion between the chain's designated L1 ERC20 gas token and the L2's native gas-paying asset, providing seamless bridging functionality for custom gas token chains. + +The CGT bridge system consists of: + +- **L1CGTBridge**: Deployed on L1, manages locking/unlocking of the designated ERC20 token +- **L2CGTBridge**: Deployed on L2 via factory deployment from L1, manages minting/burning of native assets + +Both bridges are deployed from a factory on L1 and communicate exclusively through their respective CrossDomainMessenger contracts, maintaining a trusted relationship where only messages from the counterpart bridge are accepted. + +## Token Flow + +**L1 → L2 Flow (ERC20 to Native Asset):** + +1. User calls `bridgeCGT()` on L1CGTBridge to transfer the specified amount of the designated ERC20 for bridging +2. L1CGTBridge locks ERC20 tokens and sends a cross-domain message to L2CGTBridge via L1CrossDomainMessenger +3. L2CGTBridge receives the cross-domain message, calls `LiquidityController.mint()` to unlock native assets +4. Native assets are sent to recipient address on L2 + +**L2 → L1 Flow (Native Asset to ERC20):** + +1. User calls `bridgeCGT()` on L2CGTBridge with native assets (msg.value) +2. L2CGTBridge calls `LiquidityController.burn()` to deposit native assets into NativeAssetLiquidity +3. L2CGTBridge sends a cross-domain message to L1CGTBridge via L2CrossDomainMessenger +4. L1CGTBridge receives the cross-domain message and unlocks ERC20 tokens to recipient address on L1 + +## L1CGTBridge + +The L1CGTBridge contract is deployed individually for each L2 chain using Custom Gas Token mode. It manages the L1 side of CGT bridging operations. + +### Interface + +```solidity +interface IL1CGTBridge { + // Events + event CGTBridgeInitiated(address indexed from, address indexed to, uint256 amount); + event CGTBridgeFinalized(address indexed from, address indexed to, uint256 amount); + + // Core bridge functions + function bridgeCGT(address _to, uint256 _amount, uint32 _minGasLimit) external; + function finalizeBridgeCGT(address _from, address _to, uint256 _amount) external; + + // Configuration getters + function cgtToken() external view returns (address); + function otherBridge() external view returns (address); + function messenger() external view returns (address); +} +``` + +### Functions + +#### `bridgeCGT` + +Initiates a CGT transfer from L1 to L2 to a specified recipient. + +```solidity +function bridgeCGT(address _to, uint256 _amount, uint32 _minGasLimit) external +``` + +- MUST transfer `_amount` of CGT ERC20 tokens from `msg.sender` to the bridge contract +- MUST send a message to L2CGTBridge via CrossDomainMessenger to mint equivalent native assets to `_to` +- MUST emit `CGTBridgeInitiated` event +- MUST revert if `_to` is zero address +- MUST revert if `_amount` is zero +- MUST revert if caller has insufficient token balance or allowance + +#### `finalizeBridgeCGT` + +Finalizes a CGT transfer from L2 to L1 by unlocking tokens. + +```solidity +function finalizeBridgeCGT(address _from, address _to, uint256 _amount) external +``` + +- MUST transfer `_amount` of CGT ERC20 tokens from bridge contract to `_to` address +- MUST emit `CGTBridgeFinalized` event +- MUST revert if called by any address other than the CrossDomainMessenger +- MUST revert if the CrossDomainMessenger's `xDomainMessageSender()` is not the L2CGTBridge address +- MUST revert if bridge has insufficient token balance + +### Events + +#### `CGTBridgeInitiated` + +Emitted when a CGT bridge transfer is initiated from L1 to L2. + +```solidity +event CGTBridgeInitiated(address indexed from, address indexed to, uint256 amount) +``` + +#### `CGTBridgeFinalized` + +Emitted when a CGT bridge transfer from L2 to L1 is finalized on L1. + +```solidity +event CGTBridgeFinalized(address indexed from, address indexed to, uint256 amount) +``` + + +## L1CGTBridgeWithLegacyWithdrawal + +The `L1CGTBridgeWithLegacyWithdrawal` is an extension of the `L1CGTBridge` contract that includes additional functionality for handling legacy withdrawals from before CGT migration, bridge management controls, and trusted state management. This extension is deployed when legacy withdrawal support is required. + +### Additional Interface + +```solidity +interface IL1CGTBridgeWithLegacyWithdrawal is IL1CGTBridge { + // Additional events + event WithdrawalProven(bytes32 indexed withdrawalHash, address indexed from, address indexed to); + event WithdrawalProvenExtension1(bytes32 indexed withdrawalHash, address indexed proofSubmitter); + event WithdrawalFinalized(bytes32 indexed withdrawalHash, bool success); + + // Legacy withdrawal functions (for CGT migration) + function legacyProveWithdrawalTransaction(Types.WithdrawalTransaction memory _tx, bytes[] calldata _withdrawalProof) external; + function legacyFinalizeWithdrawalTransaction(Types.WithdrawalTransaction memory _tx) external; + + // Trusted state management (migration only) + function setTrustedStateOnce(bytes32 trustedMessagePasserStorageRoot) external; + + // Bridge management + function enableDeposits() external; + function disableDeposits() external; + function enableWithdrawals() external; + function disableWithdrawals() external; + + // Configuration getter + function trustedMessagePasserStorageRoot() external view returns (bytes32); +} +``` + +### Legacy Withdrawal Support + +The L1CGTBridgeWithLegacyWithdrawal provides functionality to handle legacy withdrawals initiated before CGT migration. These functions use a trusted storage root to prove and finalize withdrawals containing native asset transfers (`_tx.value > 0`) that cannot be processed through the standard OptimismPortal after migration. + +### Additional Functions + +#### `setTrustedStateOnce` + +Sets the trusted L2ToL1MessagePasser storage root for legacy withdrawal verification (migration only). + +```solidity +function setTrustedStateOnce(bytes32 trustedMessagePasserStorageRoot) external +``` + +- MUST revert if called by any address other than the contract owner +- MUST only be callable once (subsequent calls MUST revert) +- MUST store the provided storage root for legacy withdrawal proofs +- MUST be set to a valid L2ToL1MessagePasser storage root from after the CGT migration +- MUST emit appropriate event when set + +#### `legacyProveWithdrawalTransaction` + +Proves a legacy withdrawal transaction using the trusted storage root (migration only). + +```solidity +function legacyProveWithdrawalTransaction(Types.WithdrawalTransaction memory _tx, bytes[] calldata _withdrawalProof) external +``` + +- MUST revert if `trustedMessagePasserStorageRoot` has not been set +- MUST compute withdrawal hash using `Hashing.hashWithdrawal(_tx)` +- MUST verify inclusion proof using `SecureMerkleTrie.verifyInclusionProof()` against trusted root +- MUST store proven withdrawal in `provenLegacyWithdrawals[withdrawalHash][msg.sender]` +- MUST emit `WithdrawalProven` and `WithdrawalProvenExtension1` events +- MUST revert if proof is invalid + +#### `legacyFinalizeWithdrawalTransaction` + +Finalizes a proven legacy withdrawal transaction (migration only). + +```solidity +function legacyFinalizeWithdrawalTransaction(Types.WithdrawalTransaction memory _tx) external +``` + +- MUST revert if withdrawal has not been previously proven +- MUST verify withdrawal has not been finalized in OptimismPortal or L1CGTBridge +- MUST only finalize withdrawals with `_tx.value > 0` (native asset withdrawals) +- MUST transfer ERC20 tokens equivalent to `_tx.value` to `_tx.target` +- MUST execute `_tx.data` if present (with gas limit checks) +- MUST mark withdrawal as finalized to prevent replay +- MUST revert if `_tx.target` is the CGT token contract address +- MUST emit `WithdrawalFinalized` event + +#### `enableDeposits` / `disableDeposits` + +Enables or disables deposit functionality for emergency control. + +```solidity +function enableDeposits() external +function disableDeposits() external +``` + +- MUST revert if called by any address other than the contract owner +- MUST pause/unpause deposit functions (`bridgeCGT`) +- MUST emit appropriate events when state changes + +#### `enableWithdrawals` / `disableWithdrawals` + +Enables or disables withdrawal functionality for emergency control. + +```solidity +function enableWithdrawals() external +function disableWithdrawals() external +``` + +- MUST revert if called by any address other than the contract owner +- MUST pause/unpause withdrawal functions (`finalizeBridgeCGT`, legacy functions) +- MUST emit appropriate events when state changes + +#### `trustedMessagePasserStorageRoot` + +Returns the trusted L2ToL1MessagePasser storage root used for legacy withdrawal verification. + +```solidity +function trustedMessagePasserStorageRoot() external view returns (bytes32) +``` + +- MUST return the stored trusted storage root +- MUST return zero if not set + +## L2CGTBridge + +The `L2CGTBridge` contract is deployed on L2 via a factory deployment initiated from L1. It handles the L2 side of Custom Gas Token bridging operations, enabling bidirectional transfers between L1 ERC20 tokens and L2 native assets. This contract is deployed on chains using Custom Gas Token mode and has minting authorization from the `LiquidityController` to manage native asset supply. + +The bridge maintains a trusted relationship with its L1 counterpart (`L1CGTBridge`) and only accepts cross-domain messages from the designated L1 bridge address through the `L2CrossDomainMessenger`. + +### Interface + +```solidity +interface IL2CGTBridge { + // Events + event CGTBridgeInitiated(address indexed from, address indexed to, uint256 amount); + event CGTBridgeFinalized(address indexed from, address indexed to, uint256 amount); + + // Core bridge functions + function bridgeCGT(address _to, uint32 _minGasLimit) external payable; + function finalizeBridgeCGT(address _from, address _to, uint256 _amount) external; + + // Configuration getters + function otherBridge() external view returns (address); + function messenger() external view returns (address); + function liquidityController() external view returns (address); +} +``` + +### Functions + +#### `bridgeCGT` + +Initiates a CGT transfer from L2 to L1 to a specified recipient address. + +```solidity +function bridgeCGT(address _to, uint32 _minGasLimit) external payable +``` + +- MUST accept native assets via `msg.value` (the amount being bridged) +- MUST call `LiquidityController.burn{value: msg.value}()` to deposit native assets into NativeAssetLiquidity contract +- MUST send cross-domain message to L1CGTBridge via `L2CrossDomainMessenger` to unlock equivalent ERC20 tokens to `_to` +- MUST pass `msg.value` as the `_amount` parameter in the cross-domain message for L1 finalization +- MUST emit `CGTBridgeInitiated` event with `msg.sender`, `_to`, and `msg.value` +- MUST revert if `_to` is zero address +- MUST revert if `msg.value` is zero + +#### `finalizeBridgeCGT` + +Finalizes a CGT transfer from L1 to L2 by minting native assets to the recipient. + +```solidity +function finalizeBridgeCGT(address _from, address _to, uint256 _amount) external +``` + +- MUST call `LiquidityController.mint(_to, _amount)` to mint native assets to recipient +- MUST emit `CGTBridgeFinalized` event +- MUST revert if called by any address other than the `L2CrossDomainMessenger` +- MUST revert if the `CrossDomainMessenger.xDomainMessageSender()` is not the authorized L1CGTBridge address +- MUST revert if `LiquidityController.mint()` operation fails + +### Events + +#### `CGTBridgeInitiated` + +Emitted when a CGT bridge transfer is initiated from L2 to L1. + +```solidity +event CGTBridgeInitiated(address indexed from, address indexed to, uint256 amount) +``` + +Where `from` is the L2 sender, `to` is the L1 recipient, and `amount` is the native asset amount being bridged. + +#### `CGTBridgeFinalized` + +Emitted when a CGT bridge transfer from L1 to L2 is finalized on L2. + +```solidity +event CGTBridgeFinalized(address indexed from, address indexed to, uint256 amount) +``` + +Where `from` is the L1 sender, `to` is the L2 recipient, and `amount` is the native asset amount minted. + +### Invariants + +- Only the `L2CrossDomainMessenger` can call `finalizeBridgeCGT()` when relaying messages from the authorized L1CGTBridge +- The L2CGTBridge MUST be authorized as a minter in the `LiquidityController` before any bridging operations +- All L2→L1 transfers immediately burn native assets by depositing them into `NativeAssetLiquidity` +- All L1→L2 transfers mint native assets by withdrawing them from `NativeAssetLiquidity` via `LiquidityController` +- Native asset supply on L2 is backed 1:1 by locked ERC20 tokens on L1 +- Cross-domain message authentication ensures only trusted L1CGTBridge can trigger finalization + +## Bridge Communication + +Both bridges maintain a trusted relationship and can only accept finalization messages from their designated counterpart: + +**L1CGTBridge Configuration:** + +- `otherBridge`: Address of the corresponding L2CGTBridge (deployed via factory with deterministic addressing) +- `messenger`: Address of the L1CrossDomainMessenger +- `cgtToken`: Address of the ERC20 token being bridged (immutable, set at deployment) + +**L2CGTBridge Configuration:** + +- `otherBridge`: Address of the corresponding L1CGTBridge (deployed via factory with deterministic addressing) +- `messenger`: Address of the L2CrossDomainMessenger +- `liquidityController`: Address of the LiquidityController predeploy + +The bridges enforce cross-domain message authentication by: + +1. Verifying calls to `finalizeBridgeCGT()` come from the CrossDomainMessenger +2. Verifying the `xDomainMessageSender()` matches the expected counterpart bridge address +3. Only processing messages that originate from the trusted counterpart bridge + +## Security Considerations + +**Access Control:** + +- L2CGTBridge MUST be authorized as a minter in the LiquidityController before any L1→L2 transfers can be completed +- Only the designated L1CGTBridge can send finalization messages to L2CGTBridge +- Only the L2CGTBridge can send finalization messages to L1CGTBridge + +**Token Safety:** + +- L1CGTBridge holds locked ERC20 tokens as collateral for minted L2 native assets +- Native assets on L2 are backed 1:1 by locked ERC20 tokens on L1 +- Burns on L2 immediately deposit assets into NativeAssetLiquidity, reducing circulating supply + +**Bridge Integrity:** + +- Failed cross-domain messages can be replayed through the standard message relay mechanisms +- Bridge contracts SHOULD implement pausability for emergency situations +- Both bridges MUST validate all message parameters to prevent invalid minting or unlocking operations + +**Legacy Withdrawal Security (L1CGTBridgeWithLegacyWithdrawal only):** + +- `setTrustedStateOnce()` MUST revert if called by any address other than contract owner and MUST only be callable once +- `trustedMessagePasserStorageRoot` MUST represent valid post-migration L2ToL1MessagePasser state +- Legacy finalization MUST check both OptimismPortal and L1CGTBridge to prevent replays +- Legacy finalization MUST reject `_tx.target == cgtToken` to prevent approval attacks +- Legacy withdrawals MUST only process transactions with `_tx.value > 0` +- Proof verification MUST use SecureMerkleTrie with same standards as OptimismPortal \ No newline at end of file From 95541dfc31acfebf9d9755dee8769ddc094e9524 Mon Sep 17 00:00:00 2001 From: hexshire Date: Wed, 13 Aug 2025 10:32:05 -0300 Subject: [PATCH 6/7] chore(cgt): wip new cgt bridges spec --- specs/experimental/custom-gas-token-bridges.md | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/specs/experimental/custom-gas-token-bridges.md b/specs/experimental/custom-gas-token-bridges.md index 1cad10d47..13e6b23b0 100644 --- a/specs/experimental/custom-gas-token-bridges.md +++ b/specs/experimental/custom-gas-token-bridges.md @@ -2,7 +2,6 @@ - **Table of Contents** - [Overview](#overview) @@ -10,14 +9,29 @@ - [L1CGTBridge](#l1cgtbridge) - [Interface](#interface) - [Functions](#functions) + - [`bridgeCGT`](#bridgecgt) + - [`finalizeBridgeCGT`](#finalizebridgecgt) - [Events](#events) + - [`CGTBridgeInitiated`](#cgtbridgeinitiated) + - [`CGTBridgeFinalized`](#cgtbridgefinalized) - [L1CGTBridgeWithLegacyWithdrawal](#l1cgtbridgewithlegacywithdrawal) - [Additional Interface](#additional-interface) - [Legacy Withdrawal Support](#legacy-withdrawal-support) - [Additional Functions](#additional-functions) + - [`setTrustedStateOnce`](#settrustedstateonce) + - [`legacyProveWithdrawalTransaction`](#legacyprovewithdrawaltransaction) + - [`legacyFinalizeWithdrawalTransaction`](#legacyfinalizewithdrawaltransaction) + - [`enableDeposits` / `disableDeposits`](#enabledeposits--disabledeposits) + - [`enableWithdrawals` / `disableWithdrawals`](#enablewithdrawals--disablewithdrawals) + - [`trustedMessagePasserStorageRoot`](#trustedmessagepasserstorageroot) - [L2CGTBridge](#l2cgtbridge) + - [Interface](#interface-1) - [Functions](#functions-1) + - [`bridgeCGT`](#bridgecgt-1) + - [`finalizeBridgeCGT`](#finalizebridgecgt-1) - [Events](#events-1) + - [`CGTBridgeInitiated`](#cgtbridgeinitiated-1) + - [`CGTBridgeFinalized`](#cgtbridgefinalized-1) - [Invariants](#invariants) - [Bridge Communication](#bridge-communication) - [Security Considerations](#security-considerations) From 1b6beff1171371cebedba2c2ee4124b6860ca819 Mon Sep 17 00:00:00 2001 From: hexshire Date: Wed, 13 Aug 2025 15:20:30 -0300 Subject: [PATCH 7/7] chore(cgt): new cgt bridges spec --- .../experimental/custom-gas-token-bridges.md | 234 ++++++++++++++++-- specs/protocol/jovian/bridges.md | 2 - specs/protocol/jovian/optimism-portal.md | 1 + specs/protocol/jovian/overview.md | 3 +- specs/protocol/jovian/predeploys.md | 5 +- 5 files changed, 213 insertions(+), 32 deletions(-) diff --git a/specs/experimental/custom-gas-token-bridges.md b/specs/experimental/custom-gas-token-bridges.md index 13e6b23b0..728f44fb6 100644 --- a/specs/experimental/custom-gas-token-bridges.md +++ b/specs/experimental/custom-gas-token-bridges.md @@ -33,41 +33,68 @@ - [`CGTBridgeInitiated`](#cgtbridgeinitiated-1) - [`CGTBridgeFinalized`](#cgtbridgefinalized-1) - [Invariants](#invariants) +- [L1CGTBridgeFactory](#l1cgtbridgefactory) + - [Interface](#interface-2) + - [Deployment Process](#deployment-process) + - [Functions](#functions-2) + - [`deploy`](#deploy) + - [Deterministic Addressing](#deterministic-addressing) + - [Security Considerations](#security-considerations) +- [L2CGTBridgeFactory](#l2cgtbridgefactory) + - [Interface](#interface-3) + - [Deployment Process](#deployment-process-1) + - [Post-Deployment Requirements](#post-deployment-requirements) - [Bridge Communication](#bridge-communication) -- [Security Considerations](#security-considerations) +- [Security Considerations](#security-considerations-1) ## Overview -The Custom Gas Token (CGT) bridges enable bidirectional transfers between a specific L1 ERC20 token (designated as the chain's custom gas token) and L2 native assets on chains using Custom Gas Token mode. These bridges are purpose-built to handle the conversion between the chain's designated L1 ERC20 gas token and the L2's native gas-paying asset, providing seamless bridging functionality for custom gas token chains. +The Custom Gas Token (CGT) bridges enable bidirectional transfers between a specific L1 ERC20 token (designated as +the chain's custom gas token) and L2 native assets on chains using Custom Gas Token mode. These bridges are purpose-built +to handle the conversion between the chain's designated L1 ERC20 gas token and the L2's native gas-paying asset, +providing seamless bridging functionality for custom gas token chains. The CGT bridge system consists of: -- **L1CGTBridge**: Deployed on L1, manages locking/unlocking of the designated ERC20 token -- **L2CGTBridge**: Deployed on L2 via factory deployment from L1, manages minting/burning of native assets +- **L1CGTBridge**: Deployed on L1 by the L1CGTBridgeFactory, manages locking/unlocking of the designated ERC20 token +- **L2CGTBridge**: Deployed on L2 via L2CGTBridgeFactory triggered by L1CGTBridgeFactory, manages minting/burning of + native assets +- **L1CGTBridgeFactory**: Factory contract on L1 that orchestrates deployment of both bridges using deterministic + addressing +- **L2CGTBridgeFactory**: Factory contract deployed on L2 via cross-chain message to deploy the L2CGTBridge -Both bridges are deployed from a factory on L1 and communicate exclusively through their respective CrossDomainMessenger contracts, maintaining a trusted relationship where only messages from the counterpart bridge are accepted. +Both bridges are deployed through factory contracts that ensure deterministic addressing and proper configuration, +communicating exclusively through their respective CrossDomainMessenger contracts while maintaining a trusted +relationship where only messages from the counterpart bridge are accepted. ## Token Flow **L1 → L2 Flow (ERC20 to Native Asset):** -1. User calls `bridgeCGT()` on L1CGTBridge to transfer the specified amount of the designated ERC20 for bridging -2. L1CGTBridge locks ERC20 tokens and sends a cross-domain message to L2CGTBridge via L1CrossDomainMessenger -3. L2CGTBridge receives the cross-domain message, calls `LiquidityController.mint()` to unlock native assets +1. User calls `bridgeCGT()` on L1CGTBridge to transfer the specified amount of the designated ERC20 for + bridging +2. L1CGTBridge locks ERC20 tokens and sends a cross-domain message to L2CGTBridge via + L1CrossDomainMessenger +3. L2CGTBridge receives the cross-domain message, calls `LiquidityController.mint()` to unlock native + assets 4. Native assets are sent to recipient address on L2 **L2 → L1 Flow (Native Asset to ERC20):** 1. User calls `bridgeCGT()` on L2CGTBridge with native assets (msg.value) -2. L2CGTBridge calls `LiquidityController.burn()` to deposit native assets into NativeAssetLiquidity -3. L2CGTBridge sends a cross-domain message to L1CGTBridge via L2CrossDomainMessenger -4. L1CGTBridge receives the cross-domain message and unlocks ERC20 tokens to recipient address on L1 +2. L2CGTBridge calls `LiquidityController.burn()` to deposit native assets into + NativeAssetLiquidity +3. L2CGTBridge sends a cross-domain message to L1CGTBridge via + L2CrossDomainMessenger +4. L1CGTBridge receives the cross-domain message and unlocks ERC20 tokens to recipient + address on L1 ## L1CGTBridge -The L1CGTBridge contract is deployed individually for each L2 chain using Custom Gas Token mode. It manages the L1 side of CGT bridging operations. +The L1CGTBridge contract is deployed individually for each L2 chain using Custom Gas Token mode. It manages the L1 +side of CGT bridging operations. ### Interface @@ -137,10 +164,11 @@ Emitted when a CGT bridge transfer from L2 to L1 is finalized on L1. event CGTBridgeFinalized(address indexed from, address indexed to, uint256 amount) ``` - ## L1CGTBridgeWithLegacyWithdrawal -The `L1CGTBridgeWithLegacyWithdrawal` is an extension of the `L1CGTBridge` contract that includes additional functionality for handling legacy withdrawals from before CGT migration, bridge management controls, and trusted state management. This extension is deployed when legacy withdrawal support is required. +The `L1CGTBridgeWithLegacyWithdrawal` is an extension of the `L1CGTBridge` contract that includes additional +functionality for handling legacy withdrawals from before CGT migration, bridge management controls, and trusted state +management. This extension is deployed when legacy withdrawal support is required. ### Additional Interface @@ -171,13 +199,16 @@ interface IL1CGTBridgeWithLegacyWithdrawal is IL1CGTBridge { ### Legacy Withdrawal Support -The L1CGTBridgeWithLegacyWithdrawal provides functionality to handle legacy withdrawals initiated before CGT migration. These functions use a trusted storage root to prove and finalize withdrawals containing native asset transfers (`_tx.value > 0`) that cannot be processed through the standard OptimismPortal after migration. +The L1CGTBridgeWithLegacyWithdrawal provides functionality to handle legacy withdrawals initiated before CGT +migration. These functions use a trusted storage root to prove and finalize withdrawals containing native asset +transfers (`_tx.value > 0`) that cannot be processed through the standard OptimismPortal after migration. ### Additional Functions #### `setTrustedStateOnce` -Sets the trusted L2ToL1MessagePasser storage root for legacy withdrawal verification (migration only). +Sets the trusted L2ToL1MessagePasser storage root for legacy withdrawal verification (migration +only). ```solidity function setTrustedStateOnce(bytes32 trustedMessagePasserStorageRoot) external @@ -260,9 +291,13 @@ function trustedMessagePasserStorageRoot() external view returns (bytes32) ## L2CGTBridge -The `L2CGTBridge` contract is deployed on L2 via a factory deployment initiated from L1. It handles the L2 side of Custom Gas Token bridging operations, enabling bidirectional transfers between L1 ERC20 tokens and L2 native assets. This contract is deployed on chains using Custom Gas Token mode and has minting authorization from the `LiquidityController` to manage native asset supply. +The `L2CGTBridge` contract is deployed on L2 via a factory deployment initiated from L1. It handles the L2 side of +Custom Gas Token bridging operations, enabling bidirectional transfers between L1 ERC20 tokens and L2 native assets. +This contract is deployed on chains using Custom Gas Token mode and has minting authorization from the +`LiquidityController` to manage native asset supply. -The bridge maintains a trusted relationship with its L1 counterpart (`L1CGTBridge`) and only accepts cross-domain messages from the designated L1 bridge address through the `L2CrossDomainMessenger`. +The bridge maintains a trusted relationship with its L1 counterpart (`L1CGTBridge`) and only accepts cross-domain +messages from the designated L1 bridge address through the `L2CrossDomainMessenger`. ### Interface @@ -313,7 +348,7 @@ function finalizeBridgeCGT(address _from, address _to, uint256 _amount) external - MUST emit `CGTBridgeFinalized` event - MUST revert if called by any address other than the `L2CrossDomainMessenger` - MUST revert if the `CrossDomainMessenger.xDomainMessageSender()` is not the authorized L1CGTBridge address -- MUST revert if `LiquidityController.mint()` operation fails +- MUST revert if `LiquidityController.mint()` operation fails (this will occur if L2CGTBridge is not authorized as minter) ### Events @@ -340,39 +375,183 @@ Where `from` is the L1 sender, `to` is the L2 recipient, and `amount` is the nat ### Invariants - Only the `L2CrossDomainMessenger` can call `finalizeBridgeCGT()` when relaying messages from the authorized L1CGTBridge -- The L2CGTBridge MUST be authorized as a minter in the `LiquidityController` before any bridging operations +- The L2CGTBridge MUST be authorized as a minter in the `LiquidityController` before any L1→L2 bridging operations can succeed +- Authorization is granted by the LiquidityController owner via `LiquidityController.authorizeMinter(l2BridgeAddress)` - All L2→L1 transfers immediately burn native assets by depositing them into `NativeAssetLiquidity` - All L1→L2 transfers mint native assets by withdrawing them from `NativeAssetLiquidity` via `LiquidityController` - Native asset supply on L2 is backed 1:1 by locked ERC20 tokens on L1 - Cross-domain message authentication ensures only trusted L1CGTBridge can trigger finalization +## L1CGTBridgeFactory + +The `L1CGTBridgeFactory` is a factory contract deployed on L1 that handles the deployment of both L1CGTBridge and +L2CGTBridge contracts across L1 and L2. It uses Optimism's cross-chain deployment infrastructure to ensure +deterministic addressing and proper configuration of both bridges. + +### Interface + +```solidity +interface IL1CGTBridgeFactory { + /// @notice Struct containing L2 deployment parameters + struct L2Deployments { + address l2BridgeOwner; // Owner of the L2CGTBridge + uint32 minGasLimitDeploy; // Minimum gas limit for L2 factory deployment + } + + /// @notice The L2 Create2Deployer address used by Optimism + function L2_CREATE2_DEPLOYER() external view returns (address); + + /// @notice Counter to ensure unique salts for each deployment + function deploymentsSaltCounter() external view returns (uint256); + + /// @notice Deploys L1CGTBridge and triggers L2CGTBridge deployment + /// @param _l1Messenger The L1CrossDomainMessenger address + /// @param _cgtToken The ERC20 token address for custom gas token + /// @param _l1BridgeOwner The owner of the L1CGTBridge + /// @param _l2Deployments Parameters for L2 deployment + /// @return _l1Bridge The deployed L1CGTBridge address + /// @return _l2Factory The L2 factory address + /// @return _l2Bridge The precalculated L2CGTBridge address + function deploy( + address _l1Messenger, + address _cgtToken, + address _l1BridgeOwner, + L2Deployments calldata _l2Deployments + ) external returns (address _l1Bridge, address _l2Factory, address _l2Bridge); + + /// @notice Emitted when bridges are deployed + event BridgesDeployed(address indexed l1Bridge, address indexed l2Factory, address indexed l2Bridge); +} +``` + +### Deployment Process + +The factory follows this deployment sequence: + +1. **Salt Generation**: Increments `deploymentsSaltCounter` to ensure unique deployment addresses +2. **Address Precalculation**: Uses CREATE nonces to precalculate L1 and L2 bridge addresses +3. **L1 Bridge Deployment**: Deploys L1CGTBridge implementation and proxy with precalculated L2 bridge address +4. **L2 Factory Deployment**: Sends cross-domain message to deploy L2CGTBridgeFactory on L2 +5. **L2 Bridge Deployment**: L2 factory automatically deploys L2CGTBridge with precalculated L1 bridge address + +### Functions + +#### `deploy` + +Deploys both L1CGTBridge and triggers L2CGTBridge deployment via cross-chain message. + +```solidity +function deploy( + address _l1Messenger, + address _cgtToken, + address _l1BridgeOwner, + L2Deployments calldata _l2Deployments +) external returns (address _l1Bridge, address _l2Factory, address _l2Bridge) +``` + +- MUST increment `deploymentsSaltCounter` by 2 to ensure unique salt +- MUST precalculate L1CGTBridge address using CREATE nonce +- MUST precalculate L2CGTBridge address using L2 factory nonce +- MUST deploy L1CGTBridge implementation and proxy with correct configuration +- MUST send cross-domain message to deploy L2CGTBridgeFactory via L1CrossDomainMessenger +- MUST emit `BridgesDeployed` event +- MUST revert if `_cgtToken` is zero address +- MUST revert if `_l1Messenger` is zero address + +### Deterministic Addressing + +The factory ensures deterministic addressing through: + +**L1CGTBridge Address**: Calculated using `CREATE` with factory address and current nonce +**L2CGTBridge Address**: Calculated using `CREATE` with L2 factory address and deployment nonce +**Salt Uniqueness**: Each deployment uses incremented counter as salt to ensure unique L2 factory addresses + +### Security Considerations + +- Factory contract SHOULD be deployed with proper access controls if needed +- Salt counter prevents address collisions across different deployments +- Cross-domain message failure can be replayed through standard Optimism retry mechanisms +- L1CGTBridge configuration includes precalculated L2CGTBridge address for trusted communication + +## L2CGTBridgeFactory + +The `L2CGTBridgeFactory` is deployed on L2 by the L1CGTBridgeFactory via cross-chain message. It handles the +deployment of the L2CGTBridge contract and ensures proper initialization. + +### Interface + +```solidity +interface IL2CGTBridgeFactory { + /// @notice Deploys the L2CGTBridge contract + /// @param _l1Bridge The L1CGTBridge address + /// @param _l2BridgeOwner The owner of the L2CGTBridge + /// @param _liquidityController The LiquidityController predeploy address + /// @return _l2Bridge The deployed L2CGTBridge address + function deploy( + address _l1Bridge, + address _l2BridgeOwner, + address _liquidityController + ) external returns (address _l2Bridge); + + /// @notice Emitted when L2CGTBridge is deployed + event L2BridgeDeployed(address indexed l2Bridge, address indexed l1Bridge); +} +``` + +### Deployment Process + +1. **Automatic Execution**: Deployed and executed automatically by L1CGTBridgeFactory cross-chain + message +2. **L2CGTBridge Deployment**: Creates L2CGTBridge implementation and proxy +3. **Configuration**: Initializes L2CGTBridge with L1 bridge address and owner +4. **Authorization Required**: L2CGTBridge MUST be separately authorized as minter in LiquidityController via + `authorizeMinter()` before bridging operations can function + +### Post-Deployment Requirements + +After the L2CGTBridge is deployed, the following authorization step is +required: + +- The LiquidityController owner (L1 ProxyAdmin owner) MUST call + `LiquidityController.authorizeMinter(l2BridgeAddress)` to grant minting permissions +- Without this authorization, all L1→L2 bridging operations will fail when `finalizeBridgeCGT()` attempts to + call `LiquidityController.mint()` +- The L2CGTBridge address can be obtained from the factory deployment event or precalculated using the factory + deployment parameters + ## Bridge Communication Both bridges maintain a trusted relationship and can only accept finalization messages from their designated counterpart: **L1CGTBridge Configuration:** -- `otherBridge`: Address of the corresponding L2CGTBridge (deployed via factory with deterministic addressing) +- `otherBridge`: Address of the corresponding L2CGTBridge (precalculated by L1CGTBridgeFactory during deployment) - `messenger`: Address of the L1CrossDomainMessenger -- `cgtToken`: Address of the ERC20 token being bridged (immutable, set at deployment) +- `cgtToken`: Address of the ERC20 token being bridged (immutable, set at factory deployment) **L2CGTBridge Configuration:** -- `otherBridge`: Address of the corresponding L1CGTBridge (deployed via factory with deterministic addressing) +- `otherBridge`: Address of the corresponding L1CGTBridge (provided by L2CGTBridgeFactory during deployment) - `messenger`: Address of the L2CrossDomainMessenger - `liquidityController`: Address of the LiquidityController predeploy The bridges enforce cross-domain message authentication by: 1. Verifying calls to `finalizeBridgeCGT()` come from the CrossDomainMessenger -2. Verifying the `xDomainMessageSender()` matches the expected counterpart bridge address +2. Verifying the `xDomainMessageSender()` matches the expected counterpart bridge + address 3. Only processing messages that originate from the trusted counterpart bridge ## Security Considerations **Access Control:** -- L2CGTBridge MUST be authorized as a minter in the LiquidityController before any L1→L2 transfers can be completed +- L2CGTBridge MUST be authorized as a minter in the LiquidityController via `authorizeMinter()` before any L1→L2 + transfers can be completed +- This authorization must be performed by the LiquidityController owner (L1 ProxyAdmin owner) after bridge + deployment +- Failure to authorize the L2CGTBridge will cause all `finalizeBridgeCGT()` calls to revert when attempting to + mint native assets - Only the designated L1CGTBridge can send finalization messages to L2CGTBridge - Only the L2CGTBridge can send finalization messages to L1CGTBridge @@ -390,9 +569,10 @@ The bridges enforce cross-domain message authentication by: **Legacy Withdrawal Security (L1CGTBridgeWithLegacyWithdrawal only):** -- `setTrustedStateOnce()` MUST revert if called by any address other than contract owner and MUST only be callable once +- `setTrustedStateOnce()` MUST revert if called by any address other than contract owner and MUST only be callable + once - `trustedMessagePasserStorageRoot` MUST represent valid post-migration L2ToL1MessagePasser state - Legacy finalization MUST check both OptimismPortal and L1CGTBridge to prevent replays - Legacy finalization MUST reject `_tx.target == cgtToken` to prevent approval attacks - Legacy withdrawals MUST only process transactions with `_tx.value > 0` -- Proof verification MUST use SecureMerkleTrie with same standards as OptimismPortal \ No newline at end of file +- Proof verification MUST use SecureMerkleTrie with same standards as OptimismPortal diff --git a/specs/protocol/jovian/bridges.md b/specs/protocol/jovian/bridges.md index 9ffb22008..8553bd220 100644 --- a/specs/protocol/jovian/bridges.md +++ b/specs/protocol/jovian/bridges.md @@ -2,7 +2,6 @@ - **Table of Contents** - [Overview](#overview) @@ -12,4 +11,3 @@ ## Overview ETH bridging functions MUST revert when Custom Gas Token mode is enabled and the function involves ETH transfers. This revert behavior is necessary because when a chain operates in Custom Gas Token mode, ETH is no longer the native asset used for gas fees and transactions. The chain has shifted to using a different native asset entirely. Allowing ETH transfers could create confusion about which asset serves as the native currency, potentially leading to user errors and lost funds. Additionally, the custom gas token's supply is managed independently through dedicated contracts (`NativeAssetLiquidity` and `LiquidityController`), and combining ETH bridging with custom gas token operations introduces additional complexity to supply management and accounting. - diff --git a/specs/protocol/jovian/optimism-portal.md b/specs/protocol/jovian/optimism-portal.md index 986dffd6b..3269fcb52 100644 --- a/specs/protocol/jovian/optimism-portal.md +++ b/specs/protocol/jovian/optimism-portal.md @@ -4,6 +4,7 @@ **Table of Contents** +- [Rationale](#rationale) - [Function Specification](#function-specification) - [donateETH](#donateeth) - [depositTransaction](#deposittransaction) diff --git a/specs/protocol/jovian/overview.md b/specs/protocol/jovian/overview.md index 747799f58..a0457608f 100644 --- a/specs/protocol/jovian/overview.md +++ b/specs/protocol/jovian/overview.md @@ -2,12 +2,13 @@ - **Table of Contents** - [Execution Layer](#execution-layer) - [Consensus Layer](#consensus-layer) - [Smart Contracts](#smart-contracts) + - [Core L2 Smart Contracts](#core-l2-smart-contracts) + - [Custom Gas Token](#custom-gas-token) diff --git a/specs/protocol/jovian/predeploys.md b/specs/protocol/jovian/predeploys.md index cdeab2bb1..405ee94f5 100644 --- a/specs/protocol/jovian/predeploys.md +++ b/specs/protocol/jovian/predeploys.md @@ -2,7 +2,6 @@ - **Table of Contents** - [Overview](#overview) @@ -17,16 +16,18 @@ - [`deposit`](#deposit) - [`withdraw`](#withdraw) - [`fund`](#fund) + - [`burn`](#burn) - [Events](#events) - [`LiquidityDeposited`](#liquiditydeposited) - [`LiquidityWithdrawn`](#liquiditywithdrawn) - [`LiquidityFunded`](#liquidityfunded) + - [`LiquidityBurned`](#liquidityburned) - [Invariants](#invariants) - [Liquidity Controller](#liquidity-controller) - [Functions](#functions-1) - [`authorizeMinter`](#authorizeminter) - [`mint`](#mint) - - [`burn`](#burn) + - [`burn`](#burn-1) - [`gasPayingAssetName`](#gaspayingassetname) - [`gasPayingAssetSymbol`](#gaspayingassetsymbol) - [Events](#events-1)