diff --git a/contracts-review-prep.md b/contracts-review-prep.md index c4e0ab196..66fe006f5 100644 --- a/contracts-review-prep.md +++ b/contracts-review-prep.md @@ -4,7 +4,7 @@ ### Reason for changes -The goal was to be build a foundation to be able to support bridging of tokens with custom logic on receiving chain (not wrapped), as well as custom bridging logic (assets, which accrue value over time, like LRTs). +The goal was to be build a foundation to be able to support token bridging with custom logic on receiving chain (not wrapped), as well as custom bridging logic (assets, which accrue value over time, like LRTs). For clarity, we only developed a framework, the exact logic for custom tokens and custom bridging will follow. ### Major changes @@ -20,7 +20,7 @@ In order to achieve it, we separated the liquidity managing logic from the Share ### storage layout -L2SharedBridge will be a system contract, L2NativeTokenVault will replace it ( the storage layout is still not yet backwards compatible) +L2SharedBridge will be a system contract, L2NativeTokenVault will replace it (the storage layout is still not yet backwards compatible) ### bridgehubDeposit API change diff --git a/l1-contracts/contracts/bridge/L1AssetRouter.sol b/l1-contracts/contracts/bridge/L1AssetRouter.sol index 173906b76..8f76b6dff 100644 --- a/l1-contracts/contracts/bridge/L1AssetRouter.sol +++ b/l1-contracts/contracts/bridge/L1AssetRouter.sol @@ -7,6 +7,7 @@ pragma solidity 0.8.24; import {Ownable2StepUpgradeable} from "@openzeppelin/contracts-upgradeable/access/Ownable2StepUpgradeable.sol"; import {PausableUpgradeable} from "@openzeppelin/contracts-upgradeable/security/PausableUpgradeable.sol"; +import {IERC20Metadata} from "@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol"; import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; @@ -30,7 +31,7 @@ import {L2_BASE_TOKEN_SYSTEM_CONTRACT_ADDR, L2_ASSET_ROUTER_ADDR} from "../commo /// @author Matter Labs /// @custom:security-contact security@matterlabs.dev -/// @dev Bridges assets between L1 and hyperchains, supporting both ETH and ERC20 tokens. +/// @dev Bridges assets between L1 and ZK chain, supporting both ETH and ERC20 tokens. /// @dev Designed for use with a proxy for upgradability. contract L1AssetRouter is IL1AssetRouter, ReentrancyGuard, Ownable2StepUpgradeable, PausableUpgradeable { using SafeERC20 for IERC20; @@ -89,7 +90,7 @@ contract L1AssetRouter is IL1AssetRouter, ReentrancyGuard, Ownable2StepUpgradeab // slither-disable-next-line uninitialized-state mapping(uint256 chainId => bool enabled) public hyperbridgingEnabled; - /// @dev Maps token balances for each chain to prevent unauthorized spending across hyperchains. + /// @dev Maps token balances for each chain to prevent unauthorized spending across ZK chain. /// This serves as a security measure until hyperbridging is implemented. /// NOTE: this function may be removed in the future, don't rely on it! mapping(uint256 chainId => mapping(address l1Token => uint256 balance)) public chainBalance; @@ -100,7 +101,7 @@ contract L1AssetRouter is IL1AssetRouter, ReentrancyGuard, Ownable2StepUpgradeab mapping(bytes32 assetId => address assetHandlerAddress) public assetHandlerAddress; /// @dev A mapping assetId => the asset deployment tracker address - /// @dev Tracks the address of Deployment Tracker contract on L1, which sets Asset Handlers on L2s (hyperchains) + /// @dev Tracks the address of Deployment Tracker contract on L1, which sets Asset Handlers on L2s (ZK chain) /// @dev for the asset and stores respective addresses mapping(bytes32 assetId => address assetDeploymentTracker) public assetDeploymentTracker; @@ -171,21 +172,26 @@ contract L1AssetRouter is IL1AssetRouter, ReentrancyGuard, Ownable2StepUpgradeab } /// @dev transfer token to shared bridge as part of upgrade - function transferEthToNTV() external { - address ntvAddress = address(nativeTokenVault); - require(msg.sender == ntvAddress, "ShB: not NTV"); - uint256 amount = address(this).balance; - bool callSuccess; - // Low-level assembly call, to avoid any memory copying (save gas) - assembly { - callSuccess := call(gas(), ntvAddress, amount, 0, 0, 0, 0) + function transferTokenToNTV(address _token) external { + require(msg.sender == address(nativeTokenVault), "ShB: not NTV"); + if (ETH_TOKEN_ADDRESS == _token) { + address ntvAddress = address(nativeTokenVault); + uint256 amount = address(this).balance; + bool callSuccess; + // Low-level assembly call, to avoid any memory copying (save gas) + assembly { + callSuccess := call(gas(), ntvAddress, amount, 0, 0, 0, 0) + } + require(callSuccess, "ShB: eth transfer failed"); + } else { + IERC20(_token).safeTransfer(address(nativeTokenVault), IERC20(_token).balanceOf(address(this))); } } - /// @dev transfer token to shared bridge as part of upgrade - function transferTokenToNTV(address _token) external { + /// @dev transfer balance to native token vault as part of upgrade + function transferBalanceToNTV(uint256 _chainId, address _token) external { require(msg.sender == address(nativeTokenVault), "ShB: not NTV"); - IERC20(_token).safeTransfer(address(nativeTokenVault), IERC20(_token).balanceOf(address(this))); + chainBalance[_chainId][_token] = 0; } /// @dev Sets the L1ERC20Bridge contract address. Should be called only once. @@ -243,7 +249,7 @@ contract L1AssetRouter is IL1AssetRouter, ReentrancyGuard, Ownable2StepUpgradeab /// @notice Allows bridgehub to acquire mintValue for L1->L2 transactions. /// @dev If the corresponding L2 transaction fails, refunds are issued to a refund recipient on L2. - /// @param _chainId The chain ID of the hyperchain to which deposit. + /// @param _chainId The chain ID of the ZK chain to which deposit. /// @param _assetId The deposited asset ID. /// @param _prevMsgSender The `msg.sender` address from the external call that initiated current one. /// @param _amount The total amount of tokens to be bridged. @@ -332,7 +338,7 @@ contract L1AssetRouter is IL1AssetRouter, ReentrancyGuard, Ownable2StepUpgradeab } /// @notice Initiates a deposit transaction within Bridgehub, used by `requestL2TransactionTwoBridges`. - /// @param _chainId The chain ID of the hyperchain to which deposit. + /// @param _chainId The chain ID of the ZK chain to which deposit. /// @param _prevMsgSender The `msg.sender` address from the external call that initiated current one. /// @param _l2Value The L2 `msg.value` from the L1 -> L2 deposit transaction. /// @param _data The calldata for the second bridge deposit. @@ -349,41 +355,41 @@ contract L1AssetRouter is IL1AssetRouter, ReentrancyGuard, Ownable2StepUpgradeab whenNotPaused returns (L2TransactionRequestTwoBridgesInner memory request) { - bytes32 _assetId; - bytes memory _transferData; + bytes32 assetId; + bytes memory transferData; bool legacyDeposit = false; try this.handleLegacyData(_data, _prevMsgSender) returns ( - bytes32 _assetIdDecoded, - bytes memory _transferDataDecoded + bytes32 assetIdDecoded, + bytes memory transferDataDecoded ) { - (_assetId, _transferData) = (_assetIdDecoded, _transferDataDecoded); + (assetId, transferData) = (assetIdDecoded, transferDataDecoded); legacyDeposit = true; } catch { - (_assetId, _transferData) = abi.decode(_data, (bytes32, bytes)); + (assetId, transferData) = abi.decode(_data, (bytes32, bytes)); } - require(BRIDGE_HUB.baseTokenAssetId(_chainId) != _assetId, "ShB: baseToken deposit not supported"); + require(BRIDGE_HUB.baseTokenAssetId(_chainId) != assetId, "ShB: baseToken deposit not supported"); bytes memory bridgeMintCalldata = _burn({ _chainId: _chainId, _l2Value: _l2Value, - _assetId: _assetId, + _assetId: assetId, _prevMsgSender: _prevMsgSender, - _transferData: _transferData + _transferData: transferData }); bytes32 txDataHash; if (legacyDeposit) { - (uint256 _depositAmount, ) = abi.decode(_transferData, (uint256, address)); - txDataHash = keccak256(abi.encode(_prevMsgSender, nativeTokenVault.tokenAddress(_assetId), _depositAmount)); + (uint256 _depositAmount, ) = abi.decode(transferData, (uint256, address)); + txDataHash = keccak256(abi.encode(_prevMsgSender, nativeTokenVault.tokenAddress(assetId), _depositAmount)); } else { - txDataHash = keccak256(abi.encode(_prevMsgSender, _assetId, _transferData)); + txDataHash = keccak256(abi.encode(_prevMsgSender, assetId, transferData)); } request = _requestToBridge({ _chainId: _chainId, _prevMsgSender: _prevMsgSender, - _assetId: _assetId, + _assetId: assetId, _bridgeMintCalldata: bridgeMintCalldata, _txDataHash: txDataHash }); @@ -392,7 +398,7 @@ contract L1AssetRouter is IL1AssetRouter, ReentrancyGuard, Ownable2StepUpgradeab chainId: _chainId, txDataHash: txDataHash, from: _prevMsgSender, - assetId: _assetId, + assetId: assetId, bridgeMintCalldata: bridgeMintCalldata }); } @@ -438,7 +444,7 @@ contract L1AssetRouter is IL1AssetRouter, ReentrancyGuard, Ownable2StepUpgradeab /// @notice Confirms the acceptance of a transaction by the Mailbox, as part of the L2 transaction process within Bridgehub. /// This function is utilized by `requestL2TransactionTwoBridges` to validate the execution of a transaction. - /// @param _chainId The chain ID of the hyperchain to which confirm the deposit. + /// @param _chainId The chain ID of the ZK chain to which confirm the deposit. /// @param _txDataHash The keccak256 hash of abi.encode(msgSender, l1Token, amount) /// @param _txHash The hash of the L1->L2 transaction to confirm the deposit. function bridgehubConfirmL2Transaction( @@ -518,14 +524,9 @@ contract L1AssetRouter is IL1AssetRouter, ReentrancyGuard, Ownable2StepUpgradeab } delete depositHappened[_chainId][_l2TxHash]; - IL1AssetHandler(assetHandlerAddress[_assetId]).bridgeRecoverFailedTransfer( - _chainId, - _assetId, - _depositSender, - _assetData - ); + IL1AssetHandler(assetHandlerAddress[_assetId]).bridgeRecoverFailedTransfer(_chainId, _assetId, _assetData); - emit ClaimedFailedDepositSharedBridge(_chainId, _depositSender, _assetId, keccak256(_assetData)); + emit ClaimedFailedDepositSharedBridge(_chainId, _depositSender, _assetId, _assetData); } /// @dev Determines if an eth withdrawal was initiated on zkSync Era before the upgrade to the Shared Bridge. @@ -713,6 +714,21 @@ contract L1AssetRouter is IL1AssetRouter, ReentrancyGuard, Ownable2StepUpgradeab } } + /// @dev Receives and parses (name, symbol, decimals) from the token contract + function getERC20Getters(address _token) public view returns (bytes memory) { + if (_token == ETH_TOKEN_ADDRESS) { + bytes memory name = bytes("Ether"); + bytes memory symbol = bytes("ETH"); + bytes memory decimals = abi.encode(uint8(18)); + return abi.encode(name, symbol, decimals); // when depositing eth to a non-eth based chain it is an ERC20 + } + + (, bytes memory data1) = _token.staticcall(abi.encodeCall(IERC20Metadata.name, ())); + (, bytes memory data2) = _token.staticcall(abi.encodeCall(IERC20Metadata.symbol, ())); + (, bytes memory data3) = _token.staticcall(abi.encodeCall(IERC20Metadata.decimals, ())); + return abi.encode(data1, data2, data3); + } + /*////////////////////////////////////////////////////////////// SHARED BRIDGE TOKEN BRIDGING LEGACY FUNCTIONS //////////////////////////////////////////////////////////////*/ @@ -799,13 +815,7 @@ contract L1AssetRouter is IL1AssetRouter, ReentrancyGuard, Ownable2StepUpgradeab _assetId = _ensureTokenRegisteredWithNTV(_l1Token); // solhint-disable-next-line func-named-parameters - bridgeMintCalldata = abi.encode( - _amount, - _prevMsgSender, - _l2Receiver, - nativeTokenVault.getERC20Getters(_l1Token), - _l1Token - ); + bridgeMintCalldata = abi.encode(_amount, _prevMsgSender, _l2Receiver, getERC20Getters(_l1Token), _l1Token); } { diff --git a/l1-contracts/contracts/bridge/L1ERC20Bridge.sol b/l1-contracts/contracts/bridge/L1ERC20Bridge.sol index 0eae221ab..2200d358a 100644 --- a/l1-contracts/contracts/bridge/L1ERC20Bridge.sol +++ b/l1-contracts/contracts/bridge/L1ERC20Bridge.sol @@ -16,7 +16,7 @@ import {ReentrancyGuard} from "../common/ReentrancyGuard.sol"; /// @author Matter Labs /// @custom:security-contact security@matterlabs.dev -/// @notice Smart contract that allows depositing ERC20 tokens from Ethereum to hyperchains +/// @notice Smart contract that allows depositing ERC20 tokens from Ethereum to ZK chains /// @dev It is a legacy bridge from zkSync Era, that was deprecated in favour of shared bridge. /// It is needed for backward compatibility with already integrated projects. contract L1ERC20Bridge is IL1ERC20Bridge, ReentrancyGuard { @@ -25,7 +25,7 @@ contract L1ERC20Bridge is IL1ERC20Bridge, ReentrancyGuard { /// @dev The shared bridge that is now used for all bridging, replacing the legacy contract. IL1AssetRouter public immutable override SHARED_BRIDGE; - /// @dev The native token vault, which handles the token transfers. We should deposit to it + /// @dev The native token vault, which holds deposited tokens. IL1NativeTokenVault public immutable override NATIVE_TOKEN_VAULT; /// @dev A mapping L2 batch number => message number => flag. @@ -70,13 +70,6 @@ contract L1ERC20Bridge is IL1ERC20Bridge, ReentrancyGuard { /// @dev Initializes the reentrancy guard. Expected to be used in the proxy. function initialize() external reentrancyGuardInitializer {} - /// @dev transfer token to shared bridge as part of upgrade - function transferTokenToSharedBridge(address _token) external { - require(msg.sender == address(SHARED_BRIDGE), "Not shared bridge"); - uint256 amount = IERC20(_token).balanceOf(address(this)); - IERC20(_token).safeTransfer(address(SHARED_BRIDGE), amount); - } - /*////////////////////////////////////////////////////////////// ERA LEGACY GETTERS //////////////////////////////////////////////////////////////*/ @@ -248,4 +241,9 @@ contract L1ERC20Bridge is IL1ERC20Bridge, ReentrancyGuard { }); emit WithdrawalFinalized(l1Receiver, l1Token, amount); } + + /// @notice View-only function for backward compatibility + function l2Bridge() external view returns (address) { + return l2NativeTokenVault; + } } diff --git a/l1-contracts/contracts/bridge/L1NativeTokenVault.sol b/l1-contracts/contracts/bridge/L1NativeTokenVault.sol index ed6b46aba..d052f4dbf 100644 --- a/l1-contracts/contracts/bridge/L1NativeTokenVault.sol +++ b/l1-contracts/contracts/bridge/L1NativeTokenVault.sol @@ -12,7 +12,6 @@ import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; import {IL1NativeTokenVault} from "./interfaces/IL1NativeTokenVault.sol"; -import {ReentrancyGuard} from "../common/ReentrancyGuard.sol"; import {IL1AssetHandler} from "./interfaces/IL1AssetHandler.sol"; import {IL1AssetRouter} from "./interfaces/IL1AssetRouter.sol"; @@ -21,15 +20,9 @@ import {L2_NATIVE_TOKEN_VAULT_ADDRESS} from "../common/L2ContractAddresses.sol"; /// @author Matter Labs /// @custom:security-contact security@matterlabs.dev -/// @dev Vault holding L1 native ETH and ERC20 tokens bridged into the hyperchains. +/// @dev Vault holding L1 native ETH and ERC20 tokens bridged into the ZK chains. /// @dev Designed for use with a proxy for upgradability. -contract L1NativeTokenVault is - IL1NativeTokenVault, - IL1AssetHandler, - ReentrancyGuard, - Ownable2StepUpgradeable, - PausableUpgradeable -{ +contract L1NativeTokenVault is IL1NativeTokenVault, IL1AssetHandler, Ownable2StepUpgradeable, PausableUpgradeable { using SafeERC20 for IERC20; /// @dev The address of the WETH token on L1. @@ -41,19 +34,11 @@ contract L1NativeTokenVault is /// @dev Era's chainID uint256 public immutable ERA_CHAIN_ID; - /// @dev Indicates whether the hyperbridging is enabled for a given chain. - // slither-disable-next-line uninitialized-state - mapping(uint256 chainId => bool enabled) public hyperbridgingEnabled; - - /// @dev Maps token balances for each chain to prevent unauthorized spending across hyperchains. + /// @dev Maps token balances for each chain to prevent unauthorized spending across ZK chains. /// This serves as a security measure until hyperbridging is implemented. /// NOTE: this function may be removed in the future, don't rely on it! mapping(uint256 chainId => mapping(address l1Token => uint256 balance)) public chainBalance; - /// @dev Maps token balances for each chain to verify that chain balance was migrated from shared bridge. - /// This serves only for migration purposes. - mapping(uint256 chainId => mapping(address l1Token => bool migrated)) public chainBalanceMigrated; - /// @dev A mapping assetId => tokenAddress mapping(bytes32 assetId => address tokenAddress) public tokenAddress; @@ -63,12 +48,6 @@ contract L1NativeTokenVault is _; } - /// @notice Checks that the message sender is the bridgehub. - modifier onlyOwnerOrBridge() { - require((msg.sender == address(L1_SHARED_BRIDGE) || (msg.sender == owner())), "NTV not ShB or o"); - _; - } - /// @notice Checks that the message sender is the shared bridge itself. modifier onlySelf() { require(msg.sender == address(this), "NTV only"); @@ -77,11 +56,7 @@ contract L1NativeTokenVault is /// @dev Contract is expected to be used as proxy implementation. /// @dev Initialize the implementation to prevent Parity hack. - constructor( - address _l1WethAddress, - IL1AssetRouter _l1SharedBridge, - uint256 _eraChainId - ) reentrancyGuardInitializer { + constructor(address _l1WethAddress, IL1AssetRouter _l1SharedBridge, uint256 _eraChainId) { _disableInitializers(); L1_WETH_TOKEN = _l1WethAddress; ERA_CHAIN_ID = _eraChainId; @@ -89,9 +64,9 @@ contract L1NativeTokenVault is } /// @dev Initializes a contract for later use. Expected to be used in the proxy - /// @param _owner Address which can change L2 token implementation and upgrade the bridge + /// @param _owner Address which can change pause / unpause the NTV /// implementation. The owner is the Governor and separate from the ProxyAdmin from now on, so that the Governor can call the bridge. - function initialize(address _owner) external reentrancyGuardInitializer initializer { + function initialize(address _owner) external initializer { require(_owner != address(0), "NTV owner 0"); _transferOwnership(_owner); } @@ -103,10 +78,10 @@ contract L1NativeTokenVault is /// @dev Transfer tokens from shared bridge as part of migration process. /// @param _token The address of token to be transferred (address(1) for ether and contract address for ERC20). - function transferFundsFromSharedBridge(address _token) external onlySelf { + function transferFundsFromSharedBridge(address _token) external { if (_token == ETH_TOKEN_ADDRESS) { uint256 balanceBefore = address(this).balance; - L1_SHARED_BRIDGE.transferEthToNTV(); + L1_SHARED_BRIDGE.transferTokenToNTV(_token); uint256 balanceAfter = address(this).balance; require(balanceAfter > balanceBefore, "NTV: 0 eth transferred"); } else { @@ -121,37 +96,11 @@ contract L1NativeTokenVault is /// @dev Set chain token balance as part of migration process. /// @param _token The address of token to be transferred (address(1) for ether and contract address for ERC20). - /// @param _targetChainId The chain ID of the corresponding hyperchain. - function transferBalancesFromSharedBridge(address _token, uint256 _targetChainId) external onlySelf { - require( - chainBalanceMigrated[_targetChainId][_token] == false, - "NTV: chain balance for the token already migrated" - ); + /// @param _targetChainId The chain ID of the corresponding ZK chain. + function transferBalancesFromSharedBridge(address _token, uint256 _targetChainId) external { uint256 sharedBridgeChainBalance = L1_SHARED_BRIDGE.chainBalance(_targetChainId, _token); chainBalance[_targetChainId][_token] = chainBalance[_targetChainId][_token] + sharedBridgeChainBalance; - chainBalanceMigrated[_targetChainId][_token] = true; - } - - /// @dev Transfer tokens from shared bridge as part of migration process. - /// @dev Unlike `transferFundsFromSharedBridge` is provides a concrete limit on the gas used for the transfer and even if it will fail, it will not revert the whole transaction. - function safeTransferFundsFromSharedBridge(address _token, uint256 _gasPerToken) external { - try this.transferFundsFromSharedBridge{gas: _gasPerToken}(_token) {} catch { - // A reasonable amount of gas will be provided to transfer the token. - // If the transfer fails, we don't want to revert the whole transaction. - } - } - - /// @dev Set chain token balance as part of migration process. - /// @dev Unlike `transferBalancesFromSharedBridge` is provides a concrete limit on the gas used for the transfer and even if it will fail, it will not revert the whole transaction. - function safeTransferBalancesFromSharedBridge( - address _token, - uint256 _targetChainId, - uint256 _gasPerToken - ) external { - try this.transferBalancesFromSharedBridge{gas: _gasPerToken}(_token, _targetChainId) {} catch { - // A reasonable amount of gas will be provided to transfer the token. - // If the transfer fails, we don't want to revert the whole transaction. - } + L1_SHARED_BRIDGE.transferBalanceToNTV(_targetChainId, _token); } /// @dev We want to be able to bridge native tokens automatically, this means registering them on the fly @@ -199,9 +148,7 @@ contract L1NativeTokenVault is } require(amount != 0, "6T"); // empty deposit amount - if (!L1_SHARED_BRIDGE.hyperbridgingEnabled(_chainId)) { - chainBalance[_chainId][l1Token] += amount; - } + chainBalance[_chainId][l1Token] += amount; // solhint-disable-next-line func-named-parameters _bridgeMintData = abi.encode(amount, _prevMsgSender, _l2Receiver, getERC20Getters(l1Token), l1Token); // to do add l2Receiver in here @@ -248,42 +195,44 @@ contract L1NativeTokenVault is uint256 _chainId, bytes32 _assetId, bytes calldata _data - ) external payable override onlyBridge whenNotPaused returns (address _l1Receiver) { + ) external payable override onlyBridge whenNotPaused returns (address l1Receiver) { // here we are minting the tokens after the bridgeBurn has happened on an L2, so we can assume the l1Token is not zero address l1Token = tokenAddress[_assetId]; uint256 amount; - (amount, _l1Receiver) = abi.decode(_data, (uint256, address)); - if (!L1_SHARED_BRIDGE.hyperbridgingEnabled(_chainId)) { - // Check that the chain has sufficient balance - require(chainBalance[_chainId][l1Token] >= amount, "NTV not enough funds 2"); // not enough funds - chainBalance[_chainId][l1Token] -= amount; - } + (amount, l1Receiver) = abi.decode(_data, (uint256, address)); + // Check that the chain has sufficient balance + require(chainBalance[_chainId][l1Token] >= amount, "NTV not enough funds 2"); // not enough funds + chainBalance[_chainId][l1Token] -= amount; + if (l1Token == ETH_TOKEN_ADDRESS) { bool callSuccess; // Low-level assembly call, to avoid any memory copying (save gas) assembly { - callSuccess := call(gas(), _l1Receiver, amount, 0, 0, 0, 0) + callSuccess := call(gas(), l1Receiver, amount, 0, 0, 0, 0) } require(callSuccess, "NTV: withdrawal failed, no funds or cannot transfer to receiver"); } else { // Withdraw funds - IERC20(l1Token).safeTransfer(_l1Receiver, amount); + IERC20(l1Token).safeTransfer(l1Receiver, amount); } // solhint-disable-next-line func-named-parameters - emit BridgeMint(_chainId, _assetId, _l1Receiver, amount); + emit BridgeMint(_chainId, _assetId, l1Receiver, amount); } /// @inheritdoc IL1AssetHandler function bridgeRecoverFailedTransfer( uint256 _chainId, bytes32 _assetId, - address, bytes calldata _data ) external payable override onlyBridge whenNotPaused { (uint256 _amount, address _depositSender) = abi.decode(_data, (uint256, address)); address l1Token = tokenAddress[_assetId]; require(_amount > 0, "y1"); + // check that the chain has sufficient balance + require(chainBalance[_chainId][l1Token] >= _amount, "NTV n funds"); + chainBalance[_chainId][l1Token] -= _amount; + if (l1Token == ETH_TOKEN_ADDRESS) { bool callSuccess; // Low-level assembly call, to avoid any memory copying (save gas) @@ -296,18 +245,23 @@ contract L1NativeTokenVault is // Note we don't allow weth deposits anymore, but there might be legacy weth deposits. // until we add Weth bridging capabilities, we don't wrap/unwrap weth to ether. } - - if (!L1_SHARED_BRIDGE.hyperbridgingEnabled(_chainId)) { - // check that the chain has sufficient balance - require(chainBalance[_chainId][l1Token] >= _amount, "NTV n funds"); - chainBalance[_chainId][l1Token] -= _amount; - } } function getAssetId(address _l1TokenAddress) public view override returns (bytes32) { - return - keccak256( - abi.encode(block.chainid, L2_NATIVE_TOKEN_VAULT_ADDRESS, bytes32(uint256(uint160(_l1TokenAddress)))) - ); + return keccak256(abi.encode(block.chainid, L2_NATIVE_TOKEN_VAULT_ADDRESS, _l1TokenAddress)); + } + + /*////////////////////////////////////////////////////////////// + PAUSE + //////////////////////////////////////////////////////////////*/ + + /// @notice Pauses all functions marked with the `whenNotPaused` modifier. + function pause() external onlyOwner { + _pause(); + } + + /// @notice Unpauses the contract, allowing all functions marked with the `whenNotPaused` modifier to be called again. + function unpause() external onlyOwner { + _unpause(); } } diff --git a/l1-contracts/contracts/bridge/interfaces/IL1AssetHandler.sol b/l1-contracts/contracts/bridge/interfaces/IL1AssetHandler.sol index 351cbfe29..9df9a3b7a 100644 --- a/l1-contracts/contracts/bridge/interfaces/IL1AssetHandler.sol +++ b/l1-contracts/contracts/bridge/interfaces/IL1AssetHandler.sol @@ -46,11 +46,6 @@ interface IL1AssetHandler { /// @param _chainId the chainId that the message will be sent to /// @param _assetId the assetId of the asset being bridged - /// @param _prevMsgSender the original caller of the Bridgehub/// @param _data the actual data specified for the function - function bridgeRecoverFailedTransfer( - uint256 _chainId, - bytes32 _assetId, - address _prevMsgSender, - bytes calldata _data - ) external payable; + /// @param _data the actual data specified for the function + function bridgeRecoverFailedTransfer(uint256 _chainId, bytes32 _assetId, bytes calldata _data) external payable; } diff --git a/l1-contracts/contracts/bridge/interfaces/IL1AssetRouter.sol b/l1-contracts/contracts/bridge/interfaces/IL1AssetRouter.sol index ab52cb072..5f94c826d 100644 --- a/l1-contracts/contracts/bridge/interfaces/IL1AssetRouter.sol +++ b/l1-contracts/contracts/bridge/interfaces/IL1AssetRouter.sol @@ -54,7 +54,7 @@ interface IL1AssetRouter { uint256 indexed chainId, address indexed to, bytes32 indexed assetId, - bytes32 assetDataHash + bytes assetData ); event AssetHandlerRegisteredInitial( @@ -184,7 +184,7 @@ interface IL1AssetRouter { function chainBalance(uint256 _chainId, address _token) external view returns (uint256); - function transferEthToNTV() external; - function transferTokenToNTV(address _token) external; + + function transferBalanceToNTV(uint256 _chainId, address _token) external; } diff --git a/l1-contracts/contracts/bridge/interfaces/IL1ERC20Bridge.sol b/l1-contracts/contracts/bridge/interfaces/IL1ERC20Bridge.sol index 220ac07d9..1ecb95165 100644 --- a/l1-contracts/contracts/bridge/interfaces/IL1ERC20Bridge.sol +++ b/l1-contracts/contracts/bridge/interfaces/IL1ERC20Bridge.sol @@ -8,7 +8,7 @@ import {IL1NativeTokenVault} from "./IL1NativeTokenVault.sol"; /// @title L1 Bridge contract legacy interface /// @author Matter Labs /// @custom:security-contact security@matterlabs.dev -/// @notice Legacy Bridge interface before hyperchain migration, used for backward compatibility with zkSync Era +/// @notice Legacy Bridge interface before ZK chain migration, used for backward compatibility with zkSync Era interface IL1ERC20Bridge { event DepositInitiated( bytes32 indexed l2DepositTxHash, @@ -69,11 +69,11 @@ interface IL1ERC20Bridge { function l2NativeTokenVault() external view returns (address); + function l2Bridge() external view returns (address); + function depositAmount( address _account, address _l1Token, bytes32 _depositL2TxHash ) external returns (uint256 amount); - - function transferTokenToSharedBridge(address _token) external; } diff --git a/l1-contracts/contracts/bridgehub/Bridgehub.sol b/l1-contracts/contracts/bridgehub/Bridgehub.sol index 5ad29bf95..08eab808e 100644 --- a/l1-contracts/contracts/bridgehub/Bridgehub.sol +++ b/l1-contracts/contracts/bridgehub/Bridgehub.sol @@ -69,12 +69,13 @@ contract Bridgehub is IBridgehub, ReentrancyGuard, Ownable2StepUpgradeable, Paus mapping(uint256 chainId => bool isWhitelistedSyncLayer) public whitelistedSettlementLayers; /// @notice to avoid parity hack - constructor(uint256 _l1ChainId) reentrancyGuardInitializer { + constructor(uint256 _l1ChainId, address _owner) reentrancyGuardInitializer { _disableInitializers(); L1_CHAIN_ID = _l1ChainId; ETH_TOKEN_ASSET_ID = keccak256( abi.encode(block.chainid, L2_NATIVE_TOKEN_VAULT_ADDRESS, bytes32(uint256(uint160(ETH_TOKEN_ADDRESS)))) ); + _transferOwnership(_owner); } /// @notice used to initialize the contract @@ -525,7 +526,6 @@ contract Bridgehub is IBridgehub, ReentrancyGuard, Ownable2StepUpgradeable, Paus function bridgeRecoverFailedTransfer( uint256 _chainId, bytes32 _assetId, - address _prevMsgSender, bytes calldata _data ) external payable override {} diff --git a/l1-contracts/contracts/bridgehub/MessageRoot.sol b/l1-contracts/contracts/bridgehub/MessageRoot.sol index f5e780391..a3f7e4513 100644 --- a/l1-contracts/contracts/bridgehub/MessageRoot.sol +++ b/l1-contracts/contracts/bridgehub/MessageRoot.sol @@ -2,7 +2,6 @@ pragma solidity 0.8.24; -// slither-disable-next-line unused-return // solhint-disable reason-string, gas-custom-errors import {DynamicIncrementalMerkle} from "../common/libraries/openzeppelin/IncrementalMerkle.sol"; // todo figure out how to import from OZ @@ -130,7 +129,7 @@ contract MessageRoot is IMessageRoot, ReentrancyGuard { // Note that it *does not* delete any storage slots, so in terms of pubdata savings, it is useless. // However, the chains paid for these changes anyway, so it is considered acceptable. // In the future, further optimizations will be available. - // slither-disable-next-line usused-return + // slither-disable-next-line unused-return chainTree[chainIndexToId[i]].setup(EMPTY_LOG_ROOT); } diff --git a/l1-contracts/contracts/bridgehub/STMDeploymentTracker.sol b/l1-contracts/contracts/bridgehub/STMDeploymentTracker.sol index 3caaf162f..926abd7b4 100644 --- a/l1-contracts/contracts/bridgehub/STMDeploymentTracker.sol +++ b/l1-contracts/contracts/bridgehub/STMDeploymentTracker.sol @@ -94,7 +94,7 @@ contract STMDeploymentTracker is ISTMDeploymentTracker, ReentrancyGuard, Ownable { assetId = getAssetId(_stmL1Address); } - // slither-disable-next-line usused-return + // slither-disable-next-line unused-return SHARED_BRIDGE.setAssetHandlerAddressOnCounterPart{value: msg.value}({ _chainId: _chainId, _mintValue: _mintValue, diff --git a/l1-contracts/contracts/upgrades/L1GenesisUpgrade.sol b/l1-contracts/contracts/upgrades/L1GenesisUpgrade.sol index af0e671a3..d4220bcff 100644 --- a/l1-contracts/contracts/upgrades/L1GenesisUpgrade.sol +++ b/l1-contracts/contracts/upgrades/L1GenesisUpgrade.sol @@ -34,24 +34,21 @@ contract L1GenesisUpgrade is IL1GenesisUpgrade, BaseZkSyncUpgradeGenesis { { bytes memory complexUpgraderCalldata; { - // _deploymentAddresses, _deploymentBool, _deploymentByteCodeHashes, _deploymentConstructorInputs); bytes memory l2GenesisUpgradeCalldata = abi.encodeCall( IL2GenesisUpgrade.genesisUpgrade, (_chainId, _forceDeploymentsData) - ); //todo + ); complexUpgraderCalldata = abi.encodeCall( IComplexUpgrader.upgrade, (L2_GENESIS_UPGRADE_ADDR, l2GenesisUpgradeCalldata) ); } - // complexUpgraderCalldata = abi.encodeCall(ISystemContext.setChainId, (_chainId)); // slither-disable-next-line unused-return (, uint32 minorVersion, ) = SemVer.unpackSemVer(SafeCast.toUint96(_protocolVersion)); l2ProtocolUpgradeTx = L2CanonicalTransaction({ txType: SYSTEM_UPGRADE_L2_TX_TYPE, from: uint256(uint160(L2_FORCE_DEPLOYER_ADDR)), - // to: uint256(uint160(L2_SYSTEM_CONTEXT_SYSTEM_CONTRACT_ADDR)), to: uint256(uint160(L2_COMPLEX_UPGRADER_ADDR)), gasLimit: PRIORITY_TX_MAX_GAS_LIMIT, gasPerPubdataByteLimit: REQUIRED_L2_GAS_PRICE_PER_PUBDATA, diff --git a/l1-contracts/hardhat.config.ts b/l1-contracts/hardhat.config.ts index 38bd5f478..fc8b7b1d7 100644 --- a/l1-contracts/hardhat.config.ts +++ b/l1-contracts/hardhat.config.ts @@ -45,7 +45,7 @@ export default { settings: { optimizer: { enabled: true, - runs: 9999999, + runs: 200, }, outputSelection: { "*": { diff --git a/l1-contracts/package.json b/l1-contracts/package.json index 01da16fd2..ff21925f5 100644 --- a/l1-contracts/package.json +++ b/l1-contracts/package.json @@ -53,7 +53,8 @@ "zksync-ethers": "5.8.0-beta.5" }, "scripts": { - "build": "hardhat compile", + "build": "hardhat compile && CONTRACTS_BASE_NETWORK_ZKSYNC=true hardhat compile ", + "build-l1": "harhdat compile", "clean": "hardhat clean", "clean:foundry": "forge clean", "test": "hardhat test test/unit_tests/*.spec.ts --network hardhat", diff --git a/l1-contracts/scripts/sync-layer.ts b/l1-contracts/scripts/sync-layer.ts index 943209caa..3043c33e6 100644 --- a/l1-contracts/scripts/sync-layer.ts +++ b/l1-contracts/scripts/sync-layer.ts @@ -80,14 +80,6 @@ async function main() { await initialBridgehubDeployment(deployer, [], gasPrice, true, create2Salt); await initialBridgehubDeployment(deployer, [], gasPrice, false, create2Salt); - const bridgehub = deployer.bridgehubContract(deployer.deployWallet); - const l1ChainId = getNumberFromEnv("ETH_CLIENT_CHAIN_ID"); - const l1BridgehubAddress = getAddressFromEnv("CONTRACTS_BRIDGEHUB_PROXY_ADDR"); - await deployer.executeUpgrade( - bridgehub.address, - 0, - bridgehub.interface.encodeFunctionData("registerCounterpart", [l1ChainId, l1BridgehubAddress]) - ); }); program @@ -332,19 +324,19 @@ async function registerSLContractsOnL1(deployer: Deployer) { const syncLayerAddress = await l1STM.getHyperchain(chainId); // this script only works when owner is the deployer console.log("Registering SyncLayer chain id on the STM"); - await deployer.executeUpgrade( + const receipt1 = await deployer.executeUpgrade( l1STM.address, 0, l1Bridgehub.interface.encodeFunctionData("registerSyncLayer", [chainId, true]) ); - console.log("Registering Bridgehub counter part on the SyncLayer"); - await deployer.executeUpgrade( - l1Bridgehub.address, // kl todo fix. The BH has the counterpart, the BH needs to be deployed on L2, and the STM needs to be registered in the L2 BH. - 0, - l1Bridgehub.interface.encodeFunctionData("registerCounterpart", [chainId, bridgehubOnSyncLayer]) - ); - console.log("SyncLayer registration completed in L1 Bridgehub"); + console.log("Registering Bridgehub counter part on the SyncLayer", receipt1.transactionHash); + // await deployer.executeUpgrade( + // l1Bridgehub.address, // kl todo fix. The BH has the counterpart, the BH needs to be deployed on L2, and the STM needs to be registered in the L2 BH. + // 0, + // l1Bridgehub.interface.encodeFunctionData("registerCounterpart", [chainId, bridgehubOnSyncLayer]) + // ); + // console.log("SyncLayer registration completed in L1 Bridgehub"); const gasPrice = (await deployer.deployWallet.provider.getGasPrice()).mul(GAS_MULTIPLIER); const value = ( @@ -363,7 +355,7 @@ async function registerSLContractsOnL1(deployer: Deployer) { } const stmDeploymentTracker = deployer.stmDeploymentTracker(deployer.deployWallet); - const receipt = await ( + const receipt2 = await ( await stmDeploymentTracker.registerSTMAssetOnL2SharedBridge( chainId, l1STM.address, @@ -374,9 +366,9 @@ async function registerSLContractsOnL1(deployer: Deployer) { { value } ) ).wait(); - const l2TxHash = zkUtils.getL2HashFromPriorityOp(receipt, syncLayerAddress); + const l2TxHash = zkUtils.getL2HashFromPriorityOp(receipt2, syncLayerAddress); console.log("STM asset registered in L2SharedBridge on SL l2 tx hash: ", l2TxHash); - const receipt2 = await deployer.executeUpgrade( + const receipt3 = await deployer.executeUpgrade( l1Bridgehub.address, value, l1Bridgehub.interface.encodeFunctionData("requestL2TransactionTwoBridges", [ @@ -396,8 +388,20 @@ async function registerSLContractsOnL1(deployer: Deployer) { }, ]) ); - const l2TxHash2 = zkUtils.getL2HashFromPriorityOp(receipt2, syncLayerAddress); + const l2TxHash2 = zkUtils.getL2HashFromPriorityOp(receipt3, syncLayerAddress); console.log("STM asset registered in L2 Bridgehub on SL", l2TxHash2); + + const upgradeData = l1Bridgehub.interface.encodeFunctionData("addStateTransitionManager", [ + deployer.addresses.StateTransition.StateTransitionProxy, + ]); + const receipt4 = await deployer.executeUpgradeOnL2( + chainId, + getAddressFromEnv("SYNC_LAYER_BRIDGEHUB_PROXY_ADDR"), + gasPrice, + upgradeData, + priorityTxMaxGasLimit + ); + console.log(`StateTransition System registered, txHash: ${receipt4.transactionHash}`); } // TODO: maybe move it to SDK diff --git a/l1-contracts/src.ts/deploy-process.ts b/l1-contracts/src.ts/deploy-process.ts index 95aa86695..c9a11c4df 100644 --- a/l1-contracts/src.ts/deploy-process.ts +++ b/l1-contracts/src.ts/deploy-process.ts @@ -12,7 +12,7 @@ import type { FacetCut } from "./diamondCut"; import type { Deployer } from "./deploy"; import { getTokens } from "./deploy-token"; -import { ADDRESS_ONE, isCurrentNetworkLocal } from "../src.ts/utils"; +import { ADDRESS_ONE, L2_BRIDGEHUB_ADDRESS, L2_MESSAGE_ROOT_ADDRESS, isCurrentNetworkLocal } from "../src.ts/utils"; export const L2_BOOTLOADER_BYTECODE_HASH = "0x1000100000000000000000000000000000000000000000000000000000000000"; export const L2_DEFAULT_ACCOUNT_BYTECODE_HASH = "0x1001000000000000000000000000000000000000000000000000000000000000"; @@ -63,13 +63,19 @@ export async function initialBridgehubDeployment( if (!deployer.isZkMode()) { // proxy admin is already deployed when SL's L2SharedBridge is registered await deployer.deployTransparentProxyAdmin(create2Salt, { gasPrice }); + await deployer.deployBridgehubContract(create2Salt, gasPrice); + } else { + deployer.addresses.Bridgehub.BridgehubProxy = L2_BRIDGEHUB_ADDRESS; + deployer.addresses.Bridgehub.MessageRootProxy = L2_MESSAGE_ROOT_ADDRESS; + + console.log(`CONTRACTS_BRIDGEHUB_IMPL_ADDR=${L2_BRIDGEHUB_ADDRESS}`); + console.log(`CONTRACTS_BRIDGEHUB_PROXY_ADDR=${L2_BRIDGEHUB_ADDRESS}`); + console.log(`CONTRACTS_MESSAGE_ROOT_IMPL_ADDR=${L2_MESSAGE_ROOT_ADDRESS}`); + console.log(`CONTRACTS_MESSAGE_ROOT_PROXY_ADDR=${L2_MESSAGE_ROOT_ADDRESS}`); } - await deployer.deployBridgehubContract(create2Salt, gasPrice); // L2 Asset Router Bridge already deployed - if (deployer.isZkMode()) { - await deployer.registerAddresses(); - } else { + if (!deployer.isZkMode()) { await deployer.deploySharedBridgeContracts(create2Salt, gasPrice); await deployer.deployERC20BridgeImplementation(create2Salt, { gasPrice }); await deployer.deployERC20BridgeProxy(create2Salt, { gasPrice }); diff --git a/l1-contracts/src.ts/deploy.ts b/l1-contracts/src.ts/deploy.ts index fd03b36bc..df7aa6f95 100644 --- a/l1-contracts/src.ts/deploy.ts +++ b/l1-contracts/src.ts/deploy.ts @@ -45,6 +45,8 @@ import { REQUIRED_L2_GAS_PRICE_PER_PUBDATA, compileInitialCutHash, readBytecode, + applyL1ToL2Alias, + // priorityTxMaxGasLimit, } from "./utils"; import { IBridgehubFactory } from "../typechain/IBridgehubFactory"; import { IGovernanceFactory } from "../typechain/IGovernanceFactory"; @@ -81,6 +83,7 @@ export interface DeployerConfig { bootloaderBytecodeHash?: string; defaultAccountBytecodeHash?: string; deployedLogPrefix?: string; + l1Deployer?: Deployer; } export interface Operation { @@ -173,11 +176,17 @@ export class Deployer { let messageRootZKBytecode = ethers.constants.HashZero; let assetRouterZKBytecode = ethers.constants.HashZero; let nativeTokenVaultZKBytecode = ethers.constants.HashZero; + let l2TokenProxyBytecodeHash = ethers.constants.HashZero; if (process.env.CHAIN_ETH_NETWORK != "hardhat") { bridgehubZKBytecode = readBytecode("./artifacts-zk/contracts/bridgehub", "Bridgehub"); messageRootZKBytecode = readBytecode("./artifacts-zk/contracts/bridgehub", "MessageRoot"); assetRouterZKBytecode = readBytecode("../l2-contracts/artifacts-zk/contracts/bridge", "L2AssetRouter"); nativeTokenVaultZKBytecode = readBytecode("../l2-contracts/artifacts-zk/contracts/bridge", "L2NativeTokenVault"); + const l2TokenProxyBytecode = readBytecode( + "../l2-contracts/artifacts-zk/@openzeppelin/contracts/proxy/beacon", + "BeaconProxy" + ); + l2TokenProxyBytecodeHash = ethers.utils.hexlify(hashL2Bytecode(l2TokenProxyBytecode)); } const bridgehubDeployment = { @@ -185,7 +194,10 @@ export class Deployer { newAddress: L2_BRIDGEHUB_ADDRESS, callConstructor: true, value: 0, - input: "0x", + input: ethers.utils.defaultAbiCoder.encode( + ["uint256", "address"], + [getNumberFromEnv("ETH_CLIENT_CHAIN_ID"), applyL1ToL2Alias(this.addresses.Governance)] + ), }; const messageRootDeployment = { bytecodeHash: ethers.utils.hexlify(hashL2Bytecode(messageRootZKBytecode)), @@ -201,8 +213,8 @@ export class Deployer { callConstructor: true, value: 0, input: ethers.utils.defaultAbiCoder.encode( - ["uint256", "uint256", "address"], - [eraChainId, getNumberFromEnv("ETH_CLIENT_CHAIN_ID"), this.addresses.Bridges.SharedBridgeProxy] + ["uint256", "uint256", "address", "address"], + [eraChainId, getNumberFromEnv("ETH_CLIENT_CHAIN_ID"), this.addresses.Bridges.SharedBridgeProxy, ADDRESS_ONE] ), }; const ntvDeployment = { @@ -210,10 +222,13 @@ export class Deployer { newAddress: L2_NATIVE_TOKEN_VAULT_ADDRESS, callConstructor: true, value: 0, - input: "0x", + input: ethers.utils.defaultAbiCoder.encode( + ["bytes32", "address", "bool"], + [l2TokenProxyBytecodeHash, this.addresses.Governance, false] + ), }; - const forceDeployments = [bridgehubDeployment, messageRootDeployment, assetRouterDeployment, ntvDeployment]; + const forceDeployments = [bridgehubDeployment, assetRouterDeployment, ntvDeployment, messageRootDeployment]; return ethers.utils.defaultAbiCoder.encode([FORCE_DEPLOYMENT_ABI_STRING], [forceDeployments]); } @@ -378,7 +393,12 @@ export class Deployer { public async deployBridgehubImplementation(create2Salt: string, ethTxOptions: ethers.providers.TransactionRequest) { const l1ChainId = this.isZkMode() ? getNumberFromEnv("ETH_CLIENT_CHAIN_ID") : await this.deployWallet.getChainId(); - const contractAddress = await this.deployViaCreate2("Bridgehub", [l1ChainId], create2Salt, ethTxOptions); + const contractAddress = await this.deployViaCreate2( + "Bridgehub", + [l1ChainId, this.addresses.Governance], + create2Salt, + ethTxOptions + ); if (this.verbose) { console.log(`CONTRACTS_BRIDGEHUB_IMPL_ADDR=${contractAddress}`); @@ -895,7 +915,7 @@ export class Deployer { console.log("ETH token registered in Bridgehub"); } - const upgradeData2 = await bridgehub.interface.encodeFunctionData("setAddresses", [ + const upgradeData2 = bridgehub.interface.encodeFunctionData("setAddresses", [ this.addresses.Bridges.SharedBridgeProxy, this.addresses.Bridgehub.STMDeploymentTrackerProxy, this.addresses.Bridgehub.MessageRootProxy, @@ -1000,10 +1020,12 @@ export class Deployer { this.addresses.StateTransition.StateTransitionProxy, ]); - const receipt1 = await this.executeUpgrade(this.addresses.Bridgehub.BridgehubProxy, 0, upgradeData); - - if (this.verbose) { - console.log(`StateTransition System registered, gas used: ${receipt1.gasUsed.toString()}`); + let receipt1; + if (!this.isZkMode()) { + receipt1 = await this.executeUpgrade(this.addresses.Bridgehub.BridgehubProxy, 0, upgradeData); + if (this.verbose) { + console.log(`StateTransition System registered, gas used: ${receipt1.gasUsed.toString()}`); + } } const stmDeploymentTracker = this.stmDeploymentTracker(this.deployWallet); diff --git a/l1-contracts/test/foundry/unit/concrete/Bridges/L1SharedBridge/L1SharedBridgeBase.t.sol b/l1-contracts/test/foundry/unit/concrete/Bridges/L1SharedBridge/L1SharedBridgeBase.t.sol index 0e84d5723..4f8098b61 100644 --- a/l1-contracts/test/foundry/unit/concrete/Bridges/L1SharedBridge/L1SharedBridgeBase.t.sol +++ b/l1-contracts/test/foundry/unit/concrete/Bridges/L1SharedBridge/L1SharedBridgeBase.t.sol @@ -205,7 +205,7 @@ contract L1AssetRouterTestBase is L1AssetRouterTest { chainId: chainId, to: alice, assetId: tokenAssetId, - assetDataHash: bytes32(0) + assetData: abi.encode(bytes32(0)) }); sharedBridge.claimFailedDeposit({ _chainId: chainId, @@ -247,7 +247,7 @@ contract L1AssetRouterTestBase is L1AssetRouterTest { chainId: chainId, to: alice, assetId: ETH_TOKEN_ASSET_ID, - assetDataHash: bytes32(0) + assetData: abi.encode(bytes32(0)) }); sharedBridge.claimFailedDeposit({ _chainId: chainId, @@ -290,7 +290,7 @@ contract L1AssetRouterTestBase is L1AssetRouterTest { chainId: chainId, to: alice, assetId: ETH_TOKEN_ASSET_ID, - assetDataHash: bytes32(0) + assetData: abi.encode(bytes32(0)) }); sharedBridge.bridgeRecoverFailedTransfer({ _chainId: chainId, @@ -517,8 +517,8 @@ contract L1AssetRouterTestBase is L1AssetRouterTest { // solhint-disable-next-line func-named-parameters vm.expectEmit(true, true, false, true, address(token)); emit IERC20.Transfer(address(sharedBridge), address(nativeTokenVault), amount); - nativeTokenVault.safeTransferFundsFromSharedBridge{gas: maxGas}(address(token), maxGas); - nativeTokenVault.safeTransferBalancesFromSharedBridge{gas: maxGas}(address(token), chainId, maxGas); + nativeTokenVault.transferFundsFromSharedBridge(address(token)); + nativeTokenVault.transferBalancesFromSharedBridge(address(token), chainId); uint256 endBalanceNtv = nativeTokenVault.chainBalance(chainId, address(token)); assertEq(endBalanceNtv - startBalanceNtv, amount); } @@ -526,8 +526,8 @@ contract L1AssetRouterTestBase is L1AssetRouterTest { function test_safeTransferFundsFromSharedBridge_Eth() public { uint256 startEthBalanceNtv = address(nativeTokenVault).balance; uint256 startBalanceNtv = nativeTokenVault.chainBalance(chainId, ETH_TOKEN_ADDRESS); - nativeTokenVault.safeTransferFundsFromSharedBridge{gas: maxGas}(ETH_TOKEN_ADDRESS, maxGas); - nativeTokenVault.safeTransferBalancesFromSharedBridge{gas: maxGas}(ETH_TOKEN_ADDRESS, chainId, maxGas); + nativeTokenVault.transferFundsFromSharedBridge(ETH_TOKEN_ADDRESS); + nativeTokenVault.transferBalancesFromSharedBridge(ETH_TOKEN_ADDRESS, chainId); uint256 endBalanceNtv = nativeTokenVault.chainBalance(chainId, ETH_TOKEN_ADDRESS); uint256 endEthBalanceNtv = address(nativeTokenVault).balance; assertEq(endBalanceNtv - startBalanceNtv, amount); diff --git a/l1-contracts/test/foundry/unit/concrete/Bridges/L1SharedBridge/L1SharedBridgeFails.t.sol b/l1-contracts/test/foundry/unit/concrete/Bridges/L1SharedBridge/L1SharedBridgeFails.t.sol index 3fecb1fd2..75a89d077 100644 --- a/l1-contracts/test/foundry/unit/concrete/Bridges/L1SharedBridge/L1SharedBridgeFails.t.sol +++ b/l1-contracts/test/foundry/unit/concrete/Bridges/L1SharedBridge/L1SharedBridgeFails.t.sol @@ -16,6 +16,7 @@ import {L2Message, TxStatus} from "contracts/common/Messaging.sol"; import {IMailbox} from "contracts/state-transition/chain-interfaces/IMailbox.sol"; import {IL1ERC20Bridge} from "contracts/bridge/interfaces/IL1ERC20Bridge.sol"; import {IL1NativeTokenVault} from "contracts/bridge/interfaces/IL1NativeTokenVault.sol"; +import {L1NativeTokenVault} from "contracts/bridge/L1NativeTokenVault.sol"; import {L2_BASE_TOKEN_SYSTEM_CONTRACT_ADDR} from "contracts/common/L2ContractAddresses.sol"; import {IGetters} from "contracts/state-transition/chain-interfaces/IGetters.sol"; import {StdStorage, stdStorage} from "forge-std/Test.sol"; @@ -51,14 +52,14 @@ contract L1AssetRouterFailTest is L1AssetRouterTest { ); } - function test_transferEthToNTV_wrongCaller() public { + function test_transferTokenToNTV_wrongCaller() public { vm.expectRevert("ShB: not NTV"); - sharedBridge.transferEthToNTV(); + sharedBridge.transferTokenToNTV(address(token)); } - function test_transferTokenToNTV_wrongCaller() public { + function test_transferBalanceToNTV_wrongCaller() public { vm.expectRevert("ShB: not NTV"); - sharedBridge.transferTokenToNTV(address(token)); + sharedBridge.transferBalanceToNTV(chainId, address(token)); } function test_registerToken_noCode() public { @@ -110,8 +111,21 @@ contract L1AssetRouterFailTest is L1AssetRouterTest { ); } + // function test_transferFundsToSharedBridge_Eth_CallFailed() public { + // vm.mockCall(address(nativeTokenVault), "0x", abi.encode("")); + // vm.prank(address(nativeTokenVault)); + // vm.expectRevert("ShB: eth transfer failed"); + // nativeTokenVault.transferFundsFromSharedBridge(ETH_TOKEN_ADDRESS); + // } + + // function test_transferFundsToSharedBridge_Eth_CallFailed() public { + // vm.mockCall(address(nativeTokenVault), "0x", abi.encode("")); + // vm.prank(address(nativeTokenVault)); + // vm.expectRevert("ShB: eth transfer failed"); + // nativeTokenVault.transferFundsFromSharedBridge(ETH_TOKEN_ADDRESS); + // } + function test_transferFundsToSharedBridge_Eth_0_AmountTransferred() public { - uint256 gas = 1_000_000; vm.deal(address(sharedBridge), 0); vm.prank(address(nativeTokenVault)); vm.expectRevert("NTV: 0 eth transferred"); @@ -133,14 +147,6 @@ contract L1AssetRouterFailTest is L1AssetRouterTest { nativeTokenVault.transferFundsFromSharedBridge(address(token)); } - function test_safeTransferFundsFromSharedBridge_ShouldNotBeCalledTwice() public { - nativeTokenVault.safeTransferBalancesFromSharedBridge{gas: maxGas}(ETH_TOKEN_ADDRESS, chainId, maxGas); - assertEq(nativeTokenVault.chainBalanceMigrated(chainId, ETH_TOKEN_ADDRESS), true); - vm.prank(address(nativeTokenVault)); - vm.expectRevert("NTV: chain balance for the token already migrated"); - nativeTokenVault.transferBalancesFromSharedBridge{gas: maxGas}(ETH_TOKEN_ADDRESS, chainId); - } - function test_bridgehubDepositBaseToken_Eth_Token_notRegisteredTokenID() public { // ToDo: Shall we do it properly instead of mocking? stdstore diff --git a/l1-contracts/test/foundry/unit/concrete/Bridges/L1SharedBridge/L1SharedBridgeHyperEnabled.t.sol b/l1-contracts/test/foundry/unit/concrete/Bridges/L1SharedBridge/L1SharedBridgeHyperEnabled.t.sol index 14a9a1ad1..f2a08bdd6 100644 --- a/l1-contracts/test/foundry/unit/concrete/Bridges/L1SharedBridge/L1SharedBridgeHyperEnabled.t.sol +++ b/l1-contracts/test/foundry/unit/concrete/Bridges/L1SharedBridge/L1SharedBridgeHyperEnabled.t.sol @@ -118,7 +118,7 @@ contract L1AssetRouterHyperEnabledTest is L1AssetRouterTest { // solhint-disable-next-line func-named-parameters vm.expectEmit(true, true, true, false, address(sharedBridge)); - emit ClaimedFailedDepositSharedBridge(chainId, alice, tokenAssetId, bytes32(0)); + emit ClaimedFailedDepositSharedBridge(chainId, alice, tokenAssetId, abi.encode(bytes32(0))); vm.prank(bridgehubAddress); sharedBridge.claimFailedDeposit({ _chainId: chainId, @@ -161,7 +161,7 @@ contract L1AssetRouterHyperEnabledTest is L1AssetRouterTest { // solhint-disable-next-line func-named-parameters vm.expectEmit(true, true, true, false, address(sharedBridge)); - emit ClaimedFailedDepositSharedBridge(chainId, alice, ETH_TOKEN_ASSET_ID, bytes32(0)); + emit ClaimedFailedDepositSharedBridge(chainId, alice, ETH_TOKEN_ASSET_ID, abi.encode(bytes32(0))); vm.prank(bridgehubAddress); sharedBridge.claimFailedDeposit({ _chainId: chainId, diff --git a/l1-contracts/test/foundry/unit/concrete/Bridges/L1SharedBridge/L1SharedBridgeLegacy.t.sol b/l1-contracts/test/foundry/unit/concrete/Bridges/L1SharedBridge/L1SharedBridgeLegacy.t.sol index 51ae1365d..fac3ca74d 100644 --- a/l1-contracts/test/foundry/unit/concrete/Bridges/L1SharedBridge/L1SharedBridgeLegacy.t.sol +++ b/l1-contracts/test/foundry/unit/concrete/Bridges/L1SharedBridge/L1SharedBridgeLegacy.t.sol @@ -184,7 +184,7 @@ contract L1AssetRouterLegacyTest is L1AssetRouterTest { // solhint-disable-next-line func-named-parameters vm.expectEmit(true, true, true, false, address(sharedBridge)); - emit ClaimedFailedDepositSharedBridge(eraChainId, alice, (tokenAssetId), bytes32(0)); + emit ClaimedFailedDepositSharedBridge(eraChainId, alice, (tokenAssetId), abi.encode(bytes32(0))); vm.prank(l1ERC20BridgeAddress); sharedBridge.claimFailedDepositLegacyErc20Bridge({ diff --git a/l1-contracts/test/foundry/unit/concrete/Bridges/L1SharedBridge/_L1SharedBridge_Shared.t.sol b/l1-contracts/test/foundry/unit/concrete/Bridges/L1SharedBridge/_L1SharedBridge_Shared.t.sol index bd8bc5c6b..6f516a2d4 100644 --- a/l1-contracts/test/foundry/unit/concrete/Bridges/L1SharedBridge/_L1SharedBridge_Shared.t.sol +++ b/l1-contracts/test/foundry/unit/concrete/Bridges/L1SharedBridge/_L1SharedBridge_Shared.t.sol @@ -51,7 +51,7 @@ contract L1AssetRouterTest is Test { uint256 indexed chainId, address indexed to, bytes32 indexed assetId, - bytes32 assetDataHash + bytes assetData ); event LegacyDepositInitiated( @@ -85,7 +85,6 @@ contract L1AssetRouterTest is Test { uint256 mintValue = 1; bytes32 txHash; uint256 gas = 1_000_000; - uint256 maxGas = 30_000_000; uint256 eraChainId; uint256 randomChainId; diff --git a/l2-contracts/contracts/bridge/L2NativeTokenVault.sol b/l2-contracts/contracts/bridge/L2NativeTokenVault.sol index 45925ea4b..aef3f3fdd 100644 --- a/l2-contracts/contracts/bridge/L2NativeTokenVault.sol +++ b/l2-contracts/contracts/bridge/L2NativeTokenVault.sol @@ -39,19 +39,11 @@ contract L2NativeTokenVault is IL2NativeTokenVault, Ownable2StepUpgradeable { /// @dev Contract is expected to be used as proxy implementation. /// @dev Disable the initialization to prevent Parity hack. - constructor() { - _disableInitializers(); - } - - /// @notice Initializes the bridge contract for later use. Expected to be used in the proxy. /// @param _l2TokenProxyBytecodeHash The bytecode hash of the proxy for tokens deployed by the bridge. /// @param _aliasedOwner The address of the governor contract. /// @param _contractsDeployedAlready Ensures beacon proxy for standard ERC20 has not been deployed - function initialize( - bytes32 _l2TokenProxyBytecodeHash, - address _aliasedOwner, - bool _contractsDeployedAlready - ) external reinitializer(2) { + constructor(bytes32 _l2TokenProxyBytecodeHash, address _aliasedOwner, bool _contractsDeployedAlready) { + _disableInitializers(); if (_l2TokenProxyBytecodeHash == bytes32(0)) { revert EmptyBytes32(); } @@ -60,19 +52,20 @@ contract L2NativeTokenVault is IL2NativeTokenVault, Ownable2StepUpgradeable { } if (!_contractsDeployedAlready) { - address l2StandardToken = address(new L2StandardERC20{salt: bytes32(0)}()); - l2TokenBeacon = new UpgradeableBeacon{salt: bytes32(0)}(l2StandardToken); l2TokenProxyBytecodeHash = _l2TokenProxyBytecodeHash; - l2TokenBeacon.transferOwnership(_aliasedOwner); } _transferOwnership(_aliasedOwner); } - function setL2TokenBeacon(address _l2TokenBeacon, bytes32 _l2TokenProxyBytecodeHash) external onlyOwner { - l2TokenBeacon = UpgradeableBeacon(_l2TokenBeacon); - l2TokenProxyBytecodeHash = _l2TokenProxyBytecodeHash; - emit L2TokenBeaconUpdated(_l2TokenBeacon, _l2TokenProxyBytecodeHash); + /// @dev we don't call this in the constructor, as we need to provide factory deps + function setL2TokenBeacon() external { + if (address(l2TokenBeacon) != address(0)) { + revert AddressMismatch(address(l2TokenBeacon), address(0)); + } + address l2StandardToken = address(new L2StandardERC20{salt: bytes32(0)}()); + l2TokenBeacon = new UpgradeableBeacon{salt: bytes32(0)}(l2StandardToken); + l2TokenBeacon.transferOwnership(owner()); } function bridgeMint(uint256 _chainId, bytes32 _assetId, bytes calldata _data) external payable override { diff --git a/l2-contracts/contracts/bridge/L2SharedBridgeLegacy.sol b/l2-contracts/contracts/bridge/L2SharedBridgeLegacy.sol index 944d26b6c..c4535bc24 100644 --- a/l2-contracts/contracts/bridge/L2SharedBridgeLegacy.sol +++ b/l2-contracts/contracts/bridge/L2SharedBridgeLegacy.sol @@ -2,183 +2,26 @@ pragma solidity 0.8.20; -import {Initializable} from "@openzeppelin/contracts/proxy/utils/Initializable.sol"; -import {UpgradeableBeacon} from "@openzeppelin/contracts/proxy/beacon/UpgradeableBeacon.sol"; +// import {Initializable} from "@openzeppelin/contracts/proxy/utils/Initializable.sol"; +// import {UpgradeableBeacon} from "@openzeppelin/contracts/proxy/beacon/UpgradeableBeacon.sol"; -import {IL1ERC20Bridge} from "./interfaces/IL1ERC20Bridge.sol"; -import {IL2AssetRouter} from "./interfaces/IL2AssetRouter.sol"; -import {ILegacyL2SharedBridge} from "./interfaces/ILegacyL2SharedBridge.sol"; -import {IL2AssetHandler} from "./interfaces/IL2AssetHandler.sol"; -import {ILegacyL2SharedBridge} from "./interfaces/ILegacyL2SharedBridge.sol"; -import {IL2StandardToken} from "./interfaces/IL2StandardToken.sol"; +// import {IL1ERC20Bridge} from "./interfaces/IL1ERC20Bridge.sol"; +// import {IL2AssetRouter} from "./interfaces/IL2AssetRouter.sol"; +// import {ILegacyL2SharedBridge} from "./interfaces/ILegacyL2SharedBridge.sol"; +// import {IL2AssetHandler} from "./interfaces/IL2AssetHandler.sol"; +// import {ILegacyL2SharedBridge} from "./interfaces/ILegacyL2SharedBridge.sol"; +// import {IL2StandardToken} from "./interfaces/IL2StandardToken.sol"; -import {AddressAliasHelper} from "../vendor/AddressAliasHelper.sol"; -import {L2ContractHelper, L2_NATIVE_TOKEN_VAULT} from "../L2ContractHelper.sol"; +// import {AddressAliasHelper} from "../vendor/AddressAliasHelper.sol"; +// import {L2ContractHelper, L2_NATIVE_TOKEN_VAULT} from "../L2ContractHelper.sol"; -import {EmptyAddress, InvalidCaller} from "../L2ContractErrors.sol"; +// import {EmptyAddress, InvalidCaller} from "../L2ContractErrors.sol"; /// @author Matter Labs /// @custom:security-contact security@matterlabs.dev /// @notice The "default" bridge implementation for the ERC20 tokens. Note, that it does not /// support any custom token logic, i.e. rebase tokens' functionality is not supported. -contract L2SharedBridge is IL2AssetRouter, ILegacyL2SharedBridge, Initializable { - /// @dev The address of the L1 shared bridge counterpart. - address public override l1SharedBridge; - - /// @dev Contract that stores the implementation address for token. - /// @dev For more details see https://docs.openzeppelin.com/contracts/3.x/api/proxy#UpgradeableBeacon. - UpgradeableBeacon public DEPRECATED_l2TokenBeacon; - - /// @dev Bytecode hash of the proxy for tokens deployed by the bridge. - bytes32 internal DEPRECATED_l2TokenProxyBytecodeHash; - - /// @dev A mapping l2 token address => l1 token address - mapping(address l2TokenAddress => address l1TokenAddress) public override l1TokenAddress; - - /// @dev The address of the legacy L1 erc20 bridge counterpart. - /// This is non-zero only on Era, and should not be renamed for backward compatibility with the SDKs. - address public override l1Bridge; - - /// @dev Chain ID of Era for legacy reasons - uint256 public immutable ERA_CHAIN_ID; - - /// @dev Chain ID of L1 for bridging reasons - uint256 public immutable L1_CHAIN_ID; - - /// @dev A mapping l2 token address => l1 token address - mapping(bytes32 assetId => address assetHandlerAddress) public override assetHandlerAddress; - - /// @notice Checks that the message sender is the legacy bridge. - modifier onlyL1Bridge() { - // Only the L1 bridge counterpart can initiate and finalize the deposit. - if ( - AddressAliasHelper.undoL1ToL2Alias(msg.sender) != l1Bridge && - AddressAliasHelper.undoL1ToL2Alias(msg.sender) != l1SharedBridge - ) { - revert InvalidCaller(msg.sender); - } - _; - } - - /// @dev Contract is expected to be used as proxy implementation. - /// @dev Disable the initialization to prevent Parity hack. - constructor(uint256 _eraChainId, uint256 _l1ChainId, address _l1SharedBridge, address _l1Bridge) { - ERA_CHAIN_ID = _eraChainId; - L1_CHAIN_ID = _l1ChainId; - _initialize(_l1SharedBridge, _l1Bridge); - _disableInitializers(); - } - - /// @notice Initializes the bridge contract for later use. Expected to be used in the proxy. - /// @param _l1SharedBridge The address of the L1 Bridge contract. - /// @param _l1Bridge The address of the legacy L1 Bridge contract. - function _initialize(address _l1SharedBridge, address _l1Bridge) internal reinitializer(3) { - if (_l1SharedBridge == address(0)) { - revert EmptyAddress(); - } - - l1SharedBridge = _l1SharedBridge; - if (block.chainid == ERA_CHAIN_ID) { - if (_l1Bridge == address(0)) { - revert EmptyAddress(); - } - if (l1Bridge == address(0)) { - l1Bridge = _l1Bridge; - } - } - } - - /// @notice Finalize the deposit and mint funds - /// @param _assetId The encoding of the asset on L2 - /// @param _transferData The encoded data required for deposit (address _l1Sender, uint256 _amount, address _l2Receiver, bytes memory erc20Data, address originToken) - function finalizeDeposit(bytes32 _assetId, bytes memory _transferData) public override onlyL1Bridge { - address assetHandler = assetHandlerAddress[_assetId]; - if (assetHandler != address(0)) { - IL2AssetHandler(assetHandler).bridgeMint(L1_CHAIN_ID, _assetId, _transferData); - } else { - L2_NATIVE_TOKEN_VAULT.bridgeMint(L1_CHAIN_ID, _assetId, _transferData); - assetHandlerAddress[_assetId] = address(L2_NATIVE_TOKEN_VAULT); - } - - emit FinalizeDepositSharedBridge(L1_CHAIN_ID, _assetId, keccak256(_transferData)); - } - - /// @notice Initiates a withdrawal by burning funds on the contract and sending the message to L1 - /// where tokens would be unlocked - /// @param _assetId The L2 token address which is withdrawn - /// @param _assetData The data that is passed to the asset handler contract - function withdraw(bytes32 _assetId, bytes memory _assetData) public override { - address assetHandler = assetHandlerAddress[_assetId]; - bytes memory _bridgeMintData = IL2AssetHandler(assetHandler).bridgeBurn({ - _chainId: L1_CHAIN_ID, - _mintValue: 0, - _assetId: _assetId, - _prevMsgSender: msg.sender, - _data: _assetData - }); - - bytes memory message = _getL1WithdrawMessage(_assetId, _bridgeMintData); - L2ContractHelper.sendMessageToL1(message); - - emit WithdrawalInitiatedSharedBridge(L1_CHAIN_ID, msg.sender, _assetId, keccak256(_assetData)); - } - - /// @dev Encode the message for l2ToL1log sent with withdraw initialization - function _getL1WithdrawMessage( - bytes32 _assetId, - bytes memory _bridgeMintData - ) internal pure returns (bytes memory) { - // note we use the IL1ERC20Bridge.finalizeWithdrawal function selector to specify the selector for L1<>L2 messages, - // and we use this interface so that when the switch happened the old messages could be processed - // solhint-disable-next-line func-named-parameters - return abi.encodePacked(IL1ERC20Bridge.finalizeWithdrawal.selector, _assetId, _bridgeMintData); - } - - /// @dev Used to set the assedAddress for a given assetId. - /// @dev Will be used by ZK Gateway - function setAssetHandlerAddress(bytes32 _assetId, address _assetAddress) external onlyL1Bridge { - assetHandlerAddress[_assetId] = _assetAddress; - emit AssetHandlerRegistered(_assetId, _assetAddress); - } - - /*////////////////////////////////////////////////////////////// - LEGACY FUNCTIONS - //////////////////////////////////////////////////////////////*/ - - function finalizeDeposit( - address _l1Sender, - address _l2Receiver, - address _l1Token, - uint256 _amount, - bytes calldata _data - ) external override { - // onlyBridge { - bytes32 assetId = keccak256( - abi.encode(L1_CHAIN_ID, address(L2_NATIVE_TOKEN_VAULT), bytes32(uint256(uint160(_l1Token)))) - ); - // solhint-disable-next-line func-named-parameters - bytes memory data = abi.encode(_l1Sender, _amount, _l2Receiver, _data, _l1Token); - finalizeDeposit(assetId, data); - } - - function withdraw(address _l1Receiver, address _l2Token, uint256 _amount) external { - bytes32 assetId = keccak256( - abi.encode( - L1_CHAIN_ID, - address(L2_NATIVE_TOKEN_VAULT), - bytes32(uint256(uint160(getL1TokenAddress(_l2Token)))) - ) - ); - bytes memory data = abi.encode(_amount, _l1Receiver); - withdraw(assetId, data); - } - - function getL1TokenAddress(address _l2Token) public view returns (address) { - return IL2StandardToken(_l2Token).l1Address(); - } - - /// @return Address of an L2 token counterpart - function l2TokenAddress(address _l1Token) public view returns (address) { - return L2_NATIVE_TOKEN_VAULT.l2TokenAddress(_l1Token); - } +contract L2SharedBridge { + // is IL2AssetRouter, ILegacyL2SharedBridge, Initializable { + // todo } diff --git a/l2-contracts/contracts/bridge/interfaces/IL2NativeTokenVault.sol b/l2-contracts/contracts/bridge/interfaces/IL2NativeTokenVault.sol index 95eeaacfb..0c4e61e93 100644 --- a/l2-contracts/contracts/bridge/interfaces/IL2NativeTokenVault.sol +++ b/l2-contracts/contracts/bridge/interfaces/IL2NativeTokenVault.sol @@ -27,5 +27,5 @@ interface IL2NativeTokenVault is IL2AssetHandler { function l2TokenAddress(address _l1Token) external view returns (address); - function setL2TokenBeacon(address _l2TokenBeacon, bytes32 _l2TokenProxyBytecodeHash) external; + function setL2TokenBeacon() external; } diff --git a/l2-contracts/src/deploy-l2-da-validator-on-l2-through-l1.ts b/l2-contracts/src/deploy-l2-da-validator-on-l2-through-l1.ts index ac90b805d..70489f117 100644 --- a/l2-contracts/src/deploy-l2-da-validator-on-l2-through-l1.ts +++ b/l2-contracts/src/deploy-l2-da-validator-on-l2-through-l1.ts @@ -41,26 +41,6 @@ async function deployContractOnL2ThroughL1( return address; } -export async function initializeChainGovernance(deployer: Deployer, chainId: string) { - const l1SharedBridge = deployer.defaultSharedBridge(deployer.deployWallet); - - if (deployer.verbose) { - console.log("Initializing chain governance"); - } - await deployer.executeUpgrade( - l1SharedBridge.address, - 0, - l1SharedBridge.interface.encodeFunctionData("initializeChainGovernance", [ - chainId, - deployer.addresses.Bridges.L2SharedBridgeProxy, - ]) - ); - - if (deployer.verbose) { - console.log("L2 shared bridge address registered on L1 via governance"); - } -} - async function main() { const program = new Command(); diff --git a/l2-contracts/src/deploy-shared-bridge-on-l2-through-l1.ts b/l2-contracts/src/deploy-shared-bridge-on-l2-through-l1.ts index 84c26bdbd..09c7d1807 100644 --- a/l2-contracts/src/deploy-shared-bridge-on-l2-through-l1.ts +++ b/l2-contracts/src/deploy-shared-bridge-on-l2-through-l1.ts @@ -2,13 +2,23 @@ import { Command } from "commander"; import type { BigNumberish } from "ethers"; import { Wallet } from "ethers"; import { formatUnits, parseUnits } from "ethers/lib/utils"; -import { provider, publishBytecodeFromL1 } from "./utils"; +import { provider, publishBytecodeFromL1, priorityTxMaxGasLimit } from "./utils"; import { ethTestConfig } from "./deploy-utils"; import { Deployer } from "../../l1-contracts/src.ts/deploy"; import { GAS_MULTIPLIER } from "../../l1-contracts/scripts/utils"; import * as hre from "hardhat"; +import { + ADDRESS_ONE, + L2_ASSET_ROUTER_ADDRESS, + L2_BRIDGEHUB_ADDRESS, + L2_MESSAGE_ROOT_ADDRESS, + L2_NATIVE_TOKEN_VAULT_ADDRESS, +} from "../../l1-contracts/src.ts/utils"; + +import { L2NativeTokenVaultFactory } from "../typechain"; +import { BridgehubFactory } from "../../l1-contracts/typechain"; export const L2_SHARED_BRIDGE_ABI = hre.artifacts.readArtifactSync("L2SharedBridge").abi; export const L2_STANDARD_TOKEN_PROXY_BYTECODE = hre.artifacts.readArtifactSync("BeaconProxy").bytecode; @@ -43,8 +53,48 @@ export async function publishL2NativeTokenVaultDependencyBytecodesOnL2( } } +async function setL2TokenBeacon(deployer: Deployer, chainId: string, gasPrice: BigNumberish) { + if (deployer.verbose) { + console.log("Setting L2 token beacon"); + } + const l2NTV = L2NativeTokenVaultFactory.connect(L2_NATIVE_TOKEN_VAULT_ADDRESS, deployer.deployWallet); + + const receipt = await deployer.executeUpgradeOnL2( + chainId, + L2_NATIVE_TOKEN_VAULT_ADDRESS, + gasPrice, + l2NTV.interface.encodeFunctionData("setL2TokenBeacon"), + priorityTxMaxGasLimit + ); + if (deployer.verbose) { + console.log("Set L2Token Beacon, upgrade hash", receipt.transactionHash); + } + const bridgehub = BridgehubFactory.connect(L2_BRIDGEHUB_ADDRESS, deployer.deployWallet); + const receipt2 = await deployer.executeUpgradeOnL2( + chainId, + L2_BRIDGEHUB_ADDRESS, + gasPrice, + bridgehub.interface.encodeFunctionData("setAddresses", [ + L2_ASSET_ROUTER_ADDRESS, + ADDRESS_ONE, + L2_MESSAGE_ROOT_ADDRESS, + ]), + priorityTxMaxGasLimit + ); + if (deployer.verbose) { + console.log("Set addresses in BH, upgrade hash", receipt2.transactionHash); + } +} + export async function deploySharedBridgeOnL2ThroughL1(deployer: Deployer, chainId: string, gasPrice: BigNumberish) { await publishL2NativeTokenVaultDependencyBytecodesOnL2(deployer, chainId, gasPrice); + await setL2TokenBeacon(deployer, chainId, gasPrice); + if (deployer.verbose) { + console.log(`CONTRACTS_L2_NATIVE_TOKEN_VAULT_IMPL_ADDR=${L2_NATIVE_TOKEN_VAULT_ADDRESS}`); + console.log(`CONTRACTS_L2_NATIVE_TOKEN_VAULT_PROXY_ADDR=${L2_NATIVE_TOKEN_VAULT_ADDRESS}`); + console.log(`CONTRACTS_L2_SHARED_BRIDGE_IMPL_ADDR=${L2_ASSET_ROUTER_ADDRESS}`); + console.log(`CONTRACTS_L2_SHARED_BRIDGE_ADDR=${L2_ASSET_ROUTER_ADDRESS}`); + } } async function main() { diff --git a/system-contracts/scripts/constants.ts b/system-contracts/scripts/constants.ts index 31faeeb49..c01f0a0ed 100644 --- a/system-contracts/scripts/constants.ts +++ b/system-contracts/scripts/constants.ts @@ -182,6 +182,38 @@ export const SYSTEM_CONTRACTS: ISystemContracts = { codeName: "L2GenesisUpgrade", lang: Language.Solidity, }, + L2BridgeHub: { + // This is explicitly a non-system-contract address. + // We do not use the same address as create2 factories on EVM, since + // this is a zkEVM create2 factory. + address: "0x0000000000000000000000000000000000010002", + codeName: "Bridgehub", + lang: Language.Solidity, + }, + L2AssetRouter: { + // This is explicitly a non-system-contract address. + // We do not use the same address as create2 factories on EVM, since + // this is a zkEVM create2 factory. + address: "0x0000000000000000000000000000000000010003", + codeName: "L2AssetRouter", + lang: Language.Solidity, + }, + L2NativeTokenVault: { + // This is explicitly a non-system-contract address. + // We do not use the same address as create2 factories on EVM, since + // this is a zkEVM create2 factory. + address: "0x0000000000000000000000000000000000010004", + codeName: "L2NativeTokenVault", + lang: Language.Solidity, + }, + L2MessageRouter: { + // This is explicitly a non-system-contract address. + // We do not use the same address as create2 factories on EVM, since + // this is a zkEVM create2 factory. + address: "0x0000000000000000000000000000000000010005", + codeName: "L2MessageRouter", + lang: Language.Solidity, + }, } as const; export const EIP712_TX_ID = 113;