From 9615d90278dc71201e17254e408d9a172b96b40a Mon Sep 17 00:00:00 2001 From: Stanislav Breadless Date: Tue, 10 Sep 2024 18:41:32 +0200 Subject: [PATCH 01/18] resolve most of the conflicts --- .../contracts/bridge/L1AssetRouter.sol | 8 +- .../contracts/bridge/L1ERC20Bridge.sol | 128 +-- .../contracts/bridge/L1NativeTokenVault.sol | 8 +- .../contracts/bridge/L1SharedBridge.sol | 918 ------------------ .../bridge/interfaces/IL1ERC20Bridge.sol | 4 - .../contracts/bridgehub/Bridgehub.sol | 171 +--- .../dev-contracts/test/DummyBridgehub.sol | 23 +- .../dev-contracts/test/DummySharedBridge.sol | 24 +- .../contracts/governance/ChainAdmin.sol | 27 - .../contracts/governance/IChainAdmin.sol | 7 - .../StateTransitionManager.sol | 32 - .../state-transition/ValidatorTimelock.sol | 3 - .../chain-deps/DiamondInit.sol | 3 - .../chain-deps/facets/Admin.sol | 3 - .../chain-deps/facets/Executor.sol | 229 +---- .../chain-deps/facets/Mailbox.sol | 67 +- .../IZkSyncHyperchainBase.sol | 4 - l1-contracts/deploy-scripts/AcceptAdmin.s.sol | 24 - l1-contracts/deploy-scripts/DeployErc20.s.sol | 12 - l1-contracts/deploy-scripts/DeployL1.s.sol | 18 - .../deploy-scripts/DeployL2Contracts.sol | 24 - .../deploy-scripts/RegisterHyperchain.s.sol | 10 - l1-contracts/deploy-scripts/Utils.sol | 6 +- l1-contracts/foundry.toml | 17 +- l1-contracts/scripts/register-hyperchain.ts | 8 - l1-contracts/src.ts/deploy-process.ts | 10 - l1-contracts/src.ts/deploy.ts | 45 +- l2-contracts/contracts/L2ContractHelper.sol | 2 +- .../contracts/bridge/L2AssetRouter.sol | 2 +- .../contracts/bridge/L2NativeTokenVault.sol | 2 +- .../contracts/bridge/L2StandardERC20.sol | 19 - .../contracts/bridge/L2WrappedBaseToken.sol | 10 - .../contracts/errors/L2ContractErrors.sol | 6 - l2-contracts/test/erc20.test.ts | 25 - system-contracts/SystemContractsHashes.json | 100 -- system-contracts/contracts/L1Messenger.sol | 43 - .../contracts/PubdataChunkPublisher.sol | 7 - .../contracts/interfaces/IComplexUpgrader.sol | 4 - system-contracts/package.json | 4 - system-contracts/scripts/utils.ts | 4 +- yarn.lock | 18 +- 41 files changed, 78 insertions(+), 2001 deletions(-) delete mode 100644 l1-contracts/contracts/bridge/L1SharedBridge.sol diff --git a/l1-contracts/contracts/bridge/L1AssetRouter.sol b/l1-contracts/contracts/bridge/L1AssetRouter.sol index b6845cba0..9a4a16bce 100644 --- a/l1-contracts/contracts/bridge/L1AssetRouter.sol +++ b/l1-contracts/contracts/bridge/L1AssetRouter.sol @@ -4,11 +4,11 @@ pragma solidity 0.8.24; // solhint-disable reason-string, gas-custom-errors -import {Ownable2StepUpgradeable} from "@openzeppelin/contracts-upgradeable/access/Ownable2StepUpgradeable.sol"; -import {PausableUpgradeable} from "@openzeppelin/contracts-upgradeable/security/PausableUpgradeable.sol"; +import {Ownable2StepUpgradeable} from "@openzeppelin/contracts-upgradeable-v4/access/Ownable2StepUpgradeable.sol"; +import {PausableUpgradeable} from "@openzeppelin/contracts-upgradeable-v4/security/PausableUpgradeable.sol"; -import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; -import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; +import {IERC20} from "@openzeppelin/contracts-v4/token/ERC20/IERC20.sol"; +import {SafeERC20} from "@openzeppelin/contracts-v4/token/ERC20/utils/SafeERC20.sol"; import {IL1ERC20Bridge} from "./interfaces/IL1ERC20Bridge.sol"; import {IL1AssetRouter} from "./interfaces/IL1AssetRouter.sol"; diff --git a/l1-contracts/contracts/bridge/L1ERC20Bridge.sol b/l1-contracts/contracts/bridge/L1ERC20Bridge.sol index fdc00acb4..39c8879b2 100644 --- a/l1-contracts/contracts/bridge/L1ERC20Bridge.sol +++ b/l1-contracts/contracts/bridge/L1ERC20Bridge.sol @@ -16,11 +16,7 @@ import {Unauthorized, EmptyDeposit, TokensWithFeesNotSupported, WithdrawalAlread /// @author Matter Labs /// @custom:security-contact security@matterlabs.dev -<<<<<<< HEAD /// @notice Smart contract that allows depositing ERC20 tokens from Ethereum to ZK chains -======= -/// @notice Smart contract that allows depositing ERC20 tokens from Ethereum to hyperchains ->>>>>>> 874bc6ba940de9d37b474d1e3dda2fe4e869dfbe /// @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 { @@ -46,11 +42,7 @@ contract L1ERC20Bridge is IL1ERC20Bridge, ReentrancyGuard { mapping(address account => mapping(address l1Token => mapping(bytes32 depositL2TxHash => uint256 amount))) public depositAmount; -<<<<<<< HEAD - /// @dev The address that is used as a L2 Shared Bridge in ZKsync Era. -======= /// @dev The address that is used as a L2 bridge counterpart in ZKsync Era. ->>>>>>> 874bc6ba940de9d37b474d1e3dda2fe4e869dfbe // slither-disable-next-line uninitialized-state address public l2Bridge; @@ -86,17 +78,6 @@ contract L1ERC20Bridge is IL1ERC20Bridge, ReentrancyGuard { /// @dev Initializes the reentrancy guard. Expected to be used in the proxy. function initialize() external reentrancyGuardInitializer {} -<<<<<<< HEAD -======= - /// @dev transfer token to shared bridge as part of upgrade - function transferTokenToSharedBridge(address _token) external { - if (msg.sender != address(SHARED_BRIDGE)) { - revert Unauthorized(msg.sender); - } - uint256 amount = IERC20(_token).balanceOf(address(this)); - IERC20(_token).safeTransfer(address(SHARED_BRIDGE), amount); - } - /*////////////////////////////////////////////////////////////// ERA LEGACY GETTERS //////////////////////////////////////////////////////////////*/ @@ -186,7 +167,7 @@ contract L1ERC20Bridge is IL1ERC20Bridge, ReentrancyGuard { } l2TxHash = SHARED_BRIDGE.depositLegacyErc20Bridge{value: msg.value}({ - _msgSender: msg.sender, + _prevMsgSender: msg.sender, _l2Receiver: _l2Receiver, _l1Token: _l1Token, _amount: _amount, @@ -214,7 +195,6 @@ contract L1ERC20Bridge is IL1ERC20Bridge, ReentrancyGuard { return balanceAfter - balanceBefore; } ->>>>>>> 874bc6ba940de9d37b474d1e3dda2fe4e869dfbe /// @dev Withdraw funds from the initiated deposit, that failed when finalizing on L2. /// @param _depositSender The address of the deposit initiator /// @param _l1Token The address of the deposited L1 ERC20 token @@ -257,35 +237,6 @@ contract L1ERC20Bridge is IL1ERC20Bridge, ReentrancyGuard { ERA LEGACY FUNCTIONS //////////////////////////////////////////////////////////////*/ - /// @notice Legacy deposit method with refunding the fee to the caller, use another `deposit` method instead. - /// @dev Initiates a deposit by locking funds on the contract and sending the request - /// of processing an L2 transaction where tokens would be minted. - /// @dev If the token is bridged for the first time, the L2 token contract will be deployed. Note however, that the - /// newly-deployed token does not support any custom logic, i.e. rebase tokens' functionality is not supported. - /// @param _l2Receiver The account address that should receive funds on L2 - /// @param _l1Token The L1 token address which is deposited - /// @param _amount The total amount of tokens to be bridged - /// @param _l2TxGasLimit The L2 gas limit to be used in the corresponding L2 transaction - /// @param _l2TxGasPerPubdataByte The gasPerPubdataByteLimit to be used in the corresponding L2 transaction - /// @return txHash The L2 transaction hash of deposit finalization - /// NOTE: the function doesn't use `nonreentrant` modifier, because the inner method does. - function deposit( - address _l2Receiver, - address _l1Token, - uint256 _amount, - uint256 _l2TxGasLimit, - uint256 _l2TxGasPerPubdataByte - ) external payable returns (bytes32 txHash) { - txHash = deposit({ - _l2Receiver: _l2Receiver, - _l1Token: _l1Token, - _amount: _amount, - _l2TxGasLimit: _l2TxGasLimit, - _l2TxGasPerPubdataByte: _l2TxGasPerPubdataByte, - _refundRecipient: address(0) - }); - } - /// @notice Finalize the withdrawal and release funds /// @param _l2BatchNumber The L2 batch number where the withdrawal was processed /// @param _l2MessageIndex The position in the L2 logs Merkle tree of the l2Log that was sent with the message @@ -313,81 +264,4 @@ contract L1ERC20Bridge is IL1ERC20Bridge, ReentrancyGuard { }); emit WithdrawalFinalized(l1Receiver, l1Token, amount); } - - /// @notice Initiates a deposit by locking funds on the contract and sending the request - /// @dev Initiates a deposit by locking funds on the contract and sending the request - /// of processing an L2 transaction where tokens would be minted - /// @dev If the token is bridged for the first time, the L2 token contract will be deployed. Note however, that the - /// newly-deployed token does not support any custom logic, i.e. rebase tokens' functionality is not supported. - /// @param _l2Receiver The account address that should receive funds on L2 - /// @param _l1Token The L1 token address which is deposited - /// @param _amount The total amount of tokens to be bridged - /// @param _l2TxGasLimit The L2 gas limit to be used in the corresponding L2 transaction - /// @param _l2TxGasPerPubdataByte The gasPerPubdataByteLimit to be used in the corresponding L2 transaction - /// @param _refundRecipient The address on L2 that will receive the refund for the transaction. - /// @dev If the L2 deposit finalization transaction fails, the `_refundRecipient` will receive the `_l2Value`. - /// Please note, the contract may change the refund recipient's address to eliminate sending funds to addresses - /// out of control. - /// - If `_refundRecipient` is a contract on L1, the refund will be sent to the aliased `_refundRecipient`. - /// - If `_refundRecipient` is set to `address(0)` and the sender has NO deployed bytecode on L1, the refund will - /// be sent to the `msg.sender` address. - /// - If `_refundRecipient` is set to `address(0)` and the sender has deployed bytecode on L1, the refund will be - /// sent to the aliased `msg.sender` address. - /// @dev The address aliasing of L1 contracts as refund recipient on L2 is necessary to guarantee that the funds - /// are controllable through the Mailbox, since the Mailbox applies address aliasing to the from address for the - /// L2 tx if the L1 msg.sender is a contract. Without address aliasing for L1 contracts as refund recipients they - /// would not be able to make proper L2 tx requests through the Mailbox to use or withdraw the funds from L2, and - /// the funds would be lost. - /// @return txHash The L2 transaction hash of deposit finalization - function deposit( - address _l2Receiver, - address _l1Token, - uint256 _amount, - uint256 _l2TxGasLimit, - uint256 _l2TxGasPerPubdataByte, - address _refundRecipient - ) public payable nonReentrant returns (bytes32 txHash) { - require(_amount != 0, "0T"); // empty deposit - uint256 amount = _depositFundsToSharedBridge(msg.sender, IERC20(_l1Token), _amount); - require(amount == _amount, "3T"); // The token has non-standard transfer logic - - txHash = SHARED_BRIDGE.depositLegacyErc20Bridge{value: msg.value}({ - _prevMsgSender: msg.sender, - _l2Receiver: _l2Receiver, - _l1Token: _l1Token, - _amount: _amount, - _l2TxGasLimit: _l2TxGasLimit, - _l2TxGasPerPubdataByte: _l2TxGasPerPubdataByte, - _refundRecipient: _refundRecipient - }); - depositAmount[msg.sender][_l1Token][txHash] = _amount; - emit DepositInitiated({ - l2DepositTxHash: txHash, - from: msg.sender, - to: _l2Receiver, - l1Token: _l1Token, - amount: _amount - }); - } - - /// @dev Transfers tokens from the depositor address to the shared bridge address. - /// @return The difference between the contract balance before and after the transferring of funds. - function _depositFundsToSharedBridge(address _from, IERC20 _token, uint256 _amount) internal returns (uint256) { - uint256 balanceBefore = _token.balanceOf(address(SHARED_BRIDGE)); - _token.safeTransferFrom(_from, address(SHARED_BRIDGE), _amount); - uint256 balanceAfter = _token.balanceOf(address(SHARED_BRIDGE)); - return balanceAfter - balanceBefore; - } - - /*////////////////////////////////////////////////////////////// - ERA LEGACY GETTERS - //////////////////////////////////////////////////////////////*/ - - /// @return The L2 token address that would be minted for deposit of the given L1 token on zkSync Era. - function l2TokenAddress(address _l1Token) external view returns (address) { - bytes32 constructorInputHash = keccak256(abi.encode(l2TokenBeacon, "")); - bytes32 salt = bytes32(uint256(uint160(_l1Token))); - - return L2ContractHelper.computeCreate2Address(l2Bridge, salt, l2TokenProxyBytecodeHash, constructorInputHash); - } } diff --git a/l1-contracts/contracts/bridge/L1NativeTokenVault.sol b/l1-contracts/contracts/bridge/L1NativeTokenVault.sol index 1d8cd0973..fba532597 100644 --- a/l1-contracts/contracts/bridge/L1NativeTokenVault.sol +++ b/l1-contracts/contracts/bridge/L1NativeTokenVault.sol @@ -4,11 +4,11 @@ pragma solidity 0.8.24; // solhint-disable reason-string, gas-custom-errors -import {Ownable2StepUpgradeable} from "@openzeppelin/contracts-upgradeable/access/Ownable2StepUpgradeable.sol"; -import {PausableUpgradeable} from "@openzeppelin/contracts-upgradeable/security/PausableUpgradeable.sol"; +import {Ownable2StepUpgradeable} from "@openzeppelin/contracts-upgradeable-v4/access/Ownable2StepUpgradeable.sol"; +import {PausableUpgradeable} from "@openzeppelin/contracts-upgradeable-v4/security/PausableUpgradeable.sol"; -import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; -import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; +import {IERC20} from "@openzeppelin/contracts-v4/token/ERC20/IERC20.sol"; +import {SafeERC20} from "@openzeppelin/contracts-v4/token/ERC20/utils/SafeERC20.sol"; import {IL1NativeTokenVault} from "./interfaces/IL1NativeTokenVault.sol"; import {IL1AssetHandler} from "./interfaces/IL1AssetHandler.sol"; diff --git a/l1-contracts/contracts/bridge/L1SharedBridge.sol b/l1-contracts/contracts/bridge/L1SharedBridge.sol deleted file mode 100644 index eec3c2e69..000000000 --- a/l1-contracts/contracts/bridge/L1SharedBridge.sol +++ /dev/null @@ -1,918 +0,0 @@ -// SPDX-License-Identifier: MIT - -pragma solidity 0.8.24; - -import {Ownable2StepUpgradeable} from "@openzeppelin/contracts-upgradeable-v4/access/Ownable2StepUpgradeable.sol"; -import {PausableUpgradeable} from "@openzeppelin/contracts-upgradeable-v4/security/PausableUpgradeable.sol"; - -import {IERC20Metadata} from "@openzeppelin/contracts-v4/token/ERC20/extensions/IERC20Metadata.sol"; -import {IERC20} from "@openzeppelin/contracts-v4/token/ERC20/IERC20.sol"; -import {SafeERC20} from "@openzeppelin/contracts-v4/token/ERC20/utils/SafeERC20.sol"; - -import {IL1ERC20Bridge} from "./interfaces/IL1ERC20Bridge.sol"; -import {IL1SharedBridge} from "./interfaces/IL1SharedBridge.sol"; -import {IL2Bridge} from "./interfaces/IL2Bridge.sol"; - -import {IMailbox} from "../state-transition/chain-interfaces/IMailbox.sol"; -import {L2Message, TxStatus} from "../common/Messaging.sol"; -import {UnsafeBytes} from "../common/libraries/UnsafeBytes.sol"; -import {ReentrancyGuard} from "../common/ReentrancyGuard.sol"; -import {AddressAliasHelper} from "../vendor/AddressAliasHelper.sol"; -import {ETH_TOKEN_ADDRESS, TWO_BRIDGES_MAGIC_VALUE} from "../common/Config.sol"; -import {IBridgehub, L2TransactionRequestTwoBridgesInner, L2TransactionRequestDirect} from "../bridgehub/IBridgehub.sol"; -import {IGetters} from "../state-transition/chain-interfaces/IGetters.sol"; -import {L2_BASE_TOKEN_SYSTEM_CONTRACT_ADDR} from "../common/L2ContractAddresses.sol"; -import {Unauthorized, ZeroAddress, SharedBridgeValueAlreadySet, SharedBridgeKey, NoFundsTransferred, ZeroBalance, ValueMismatch, TokensWithFeesNotSupported, NonEmptyMsgValue, L2BridgeNotSet, TokenNotSupported, DepositIncorrectAmount, EmptyDeposit, DepositExists, AddressAlreadyUsed, InvalidProof, DepositDoesNotExist, InsufficientChainBalance, SharedBridgeValueNotSet, WithdrawalAlreadyFinalized, WithdrawFailed, L2WithdrawalMessageWrongLength, InvalidSelector, SharedBridgeBalanceMismatch, SharedBridgeValueNotSet} from "../common/L1ContractErrors.sol"; - -/// @author Matter Labs -/// @custom:security-contact security@matterlabs.dev -/// @dev Bridges assets between L1 and hyperchains, supporting both ETH and ERC20 tokens. -/// @dev Designed for use with a proxy for upgradability. -contract L1SharedBridge is IL1SharedBridge, ReentrancyGuard, Ownable2StepUpgradeable, PausableUpgradeable { - using SafeERC20 for IERC20; - - /// @dev The address of the WETH token on L1. - address public immutable override L1_WETH_TOKEN; - - /// @dev Bridgehub smart contract that is used to operate with L2 via asynchronous L2 <-> L1 communication. - IBridgehub public immutable override BRIDGE_HUB; - - /// @dev Era's chainID - uint256 internal immutable ERA_CHAIN_ID; - - /// @dev The address of ZKsync Era diamond proxy contract. - address internal immutable ERA_DIAMOND_PROXY; - - /// @dev Stores the first batch number on the ZKsync Era Diamond Proxy that was settled after Diamond proxy upgrade. - /// This variable is used to differentiate between pre-upgrade and post-upgrade Eth withdrawals. Withdrawals from batches older - /// than this value are considered to have been finalized prior to the upgrade and handled separately. - uint256 internal eraPostDiamondUpgradeFirstBatch; - - /// @dev Stores the first batch number on the ZKsync Era Diamond Proxy that was settled after L1ERC20 Bridge upgrade. - /// This variable is used to differentiate between pre-upgrade and post-upgrade ERC20 withdrawals. Withdrawals from batches older - /// than this value are considered to have been finalized prior to the upgrade and handled separately. - uint256 internal eraPostLegacyBridgeUpgradeFirstBatch; - - /// @dev Stores the ZKsync Era batch number that processes the last deposit tx initiated by the legacy bridge - /// This variable (together with eraLegacyBridgeLastDepositTxNumber) is used to differentiate between pre-upgrade and post-upgrade deposits. Deposits processed in older batches - /// than this value are considered to have been processed prior to the upgrade and handled separately. - /// We use this both for Eth and erc20 token deposits, so we need to update the diamond and bridge simultaneously. - uint256 internal eraLegacyBridgeLastDepositBatch; - - /// @dev The tx number in the _eraLegacyBridgeLastDepositBatch of the last deposit tx initiated by the legacy bridge - /// This variable (together with eraLegacyBridgeLastDepositBatch) is used to differentiate between pre-upgrade and post-upgrade deposits. Deposits processed in older txs - /// than this value are considered to have been processed prior to the upgrade and handled separately. - /// We use this both for Eth and erc20 token deposits, so we need to update the diamond and bridge simultaneously. - uint256 internal eraLegacyBridgeLastDepositTxNumber; - - /// @dev Legacy bridge smart contract that used to hold ERC20 tokens. - IL1ERC20Bridge public override legacyBridge; - - /// @dev A mapping chainId => bridgeProxy. Used to store the bridge proxy's address, and to see if it has been deployed yet. - mapping(uint256 chainId => address l2Bridge) public override l2BridgeAddress; - - /// @dev A mapping chainId => L2 deposit transaction hash => keccak256(abi.encode(account, tokenAddress, amount)) - /// @dev Tracks deposit transactions from L2 to enable users to claim their funds if a deposit fails. - mapping(uint256 chainId => mapping(bytes32 l2DepositTxHash => bytes32 depositDataHash)) - public - override depositHappened; - - /// @dev Tracks the processing status of L2 to L1 messages, indicating whether a message has already been finalized. - mapping(uint256 chainId => mapping(uint256 l2BatchNumber => mapping(uint256 l2ToL1MessageNumber => bool isFinalized))) - public isWithdrawalFinalized; - - /// @dev Indicates whether the hyperbridging is enabled for a given chain. - // slither-disable-next-line uninitialized-state - mapping(uint256 chainId => bool enabled) internal hyperbridgingEnabled; - - /// @dev Maps token balances for each chain to prevent unauthorized spending across hyperchains. - /// 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; - - /// @notice Checks that the message sender is the bridgehub. - modifier onlyBridgehub() { - if (msg.sender != address(BRIDGE_HUB)) { - revert Unauthorized(msg.sender); - } - _; - } - - /// @notice Checks that the message sender is the bridgehub or ZKsync Era Diamond Proxy. - modifier onlyBridgehubOrEra(uint256 _chainId) { - if (msg.sender != address(BRIDGE_HUB) && (_chainId != ERA_CHAIN_ID || msg.sender != ERA_DIAMOND_PROXY)) { - revert Unauthorized(msg.sender); - } - _; - } - - /// @notice Checks that the message sender is the legacy bridge. - modifier onlyLegacyBridge() { - if (msg.sender != address(legacyBridge)) { - revert Unauthorized(msg.sender); - } - _; - } - - /// @notice Checks that the message sender is the shared bridge itself. - modifier onlySelf() { - if (msg.sender != address(this)) { - revert Unauthorized(msg.sender); - } - _; - } - - /// @dev Contract is expected to be used as proxy implementation. - /// @dev Initialize the implementation to prevent Parity hack. - constructor( - address _l1WethAddress, - IBridgehub _bridgehub, - uint256 _eraChainId, - address _eraDiamondProxy - ) reentrancyGuardInitializer { - _disableInitializers(); - L1_WETH_TOKEN = _l1WethAddress; - BRIDGE_HUB = _bridgehub; - ERA_CHAIN_ID = _eraChainId; - ERA_DIAMOND_PROXY = _eraDiamondProxy; - } - - /// @dev Initializes a contract bridge for later use. Expected to be used in the proxy - /// @param _owner Address which can change L2 token implementation and upgrade the bridge - /// 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 { - if (_owner == address(0)) { - revert ZeroAddress(); - } - _transferOwnership(_owner); - } - - /// @dev This sets the first post diamond upgrade batch for era, used to check old eth withdrawals - /// @param _eraPostDiamondUpgradeFirstBatch The first batch number on the ZKsync Era Diamond Proxy that was settled after diamond proxy upgrade. - function setEraPostDiamondUpgradeFirstBatch(uint256 _eraPostDiamondUpgradeFirstBatch) external onlyOwner { - if (eraPostDiamondUpgradeFirstBatch != 0) { - revert SharedBridgeValueAlreadySet(SharedBridgeKey.PostUpgradeFirstBatch); - } - eraPostDiamondUpgradeFirstBatch = _eraPostDiamondUpgradeFirstBatch; - } - - /// @dev This sets the first post upgrade batch for era, used to check old token withdrawals - /// @param _eraPostLegacyBridgeUpgradeFirstBatch The first batch number on the ZKsync Era Diamond Proxy that was settled after legacy bridge upgrade. - function setEraPostLegacyBridgeUpgradeFirstBatch(uint256 _eraPostLegacyBridgeUpgradeFirstBatch) external onlyOwner { - if (eraPostLegacyBridgeUpgradeFirstBatch != 0) { - revert SharedBridgeValueAlreadySet(SharedBridgeKey.LegacyBridgeFirstBatch); - } - eraPostLegacyBridgeUpgradeFirstBatch = _eraPostLegacyBridgeUpgradeFirstBatch; - } - - /// @dev This sets the first post upgrade batch for era, used to check old withdrawals - /// @param _eraLegacyBridgeLastDepositBatch The the ZKsync Era batch number that processes the last deposit tx initiated by the legacy bridge - /// @param _eraLegacyBridgeLastDepositTxNumber The tx number in the _eraLegacyBridgeLastDepositBatch of the last deposit tx initiated by the legacy bridge - function setEraLegacyBridgeLastDepositTime( - uint256 _eraLegacyBridgeLastDepositBatch, - uint256 _eraLegacyBridgeLastDepositTxNumber - ) external onlyOwner { - if (eraLegacyBridgeLastDepositBatch != 0) { - revert SharedBridgeValueAlreadySet(SharedBridgeKey.LegacyBridgeLastDepositBatch); - } - if (eraLegacyBridgeLastDepositTxNumber != 0) { - revert SharedBridgeValueAlreadySet(SharedBridgeKey.LegacyBridgeLastDepositTxn); - } - eraLegacyBridgeLastDepositBatch = _eraLegacyBridgeLastDepositBatch; - eraLegacyBridgeLastDepositTxNumber = _eraLegacyBridgeLastDepositTxNumber; - } - - /// @dev Transfer tokens from legacy erc20 bridge or mailbox and set chainBalance as part of migration process. - /// @param _token The address of token to be transferred (address(1) for ether and contract address for ERC20). - /// @param _target The hyperchain or bridge contract address from where to transfer funds. - /// @param _targetChainId The chain ID of the corresponding hyperchain. - function transferFundsFromLegacy(address _token, address _target, uint256 _targetChainId) external onlySelf { - if (_token == ETH_TOKEN_ADDRESS) { - uint256 balanceBefore = address(this).balance; - IMailbox(_target).transferEthToSharedBridge(); - uint256 balanceAfter = address(this).balance; - if (balanceAfter <= balanceBefore) { - revert NoFundsTransferred(); - } - chainBalance[_targetChainId][ETH_TOKEN_ADDRESS] = - chainBalance[_targetChainId][ETH_TOKEN_ADDRESS] + - balanceAfter - - balanceBefore; - } else { - uint256 balanceBefore = IERC20(_token).balanceOf(address(this)); - uint256 legacyBridgeBalance = IERC20(_token).balanceOf(address(legacyBridge)); - if (legacyBridgeBalance == 0) { - revert ZeroBalance(); - } - IL1ERC20Bridge(_target).transferTokenToSharedBridge(_token); - uint256 balanceAfter = IERC20(_token).balanceOf(address(this)); - if (balanceAfter - balanceBefore < legacyBridgeBalance) { - revert SharedBridgeBalanceMismatch(); - } - chainBalance[_targetChainId][_token] = chainBalance[_targetChainId][_token] + legacyBridgeBalance; - } - } - - /// @dev transfer tokens from legacy erc20 bridge or mailbox and set chainBalance as part of migration process. - /// @dev Unlike `transferFundsFromLegacy` 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 safeTransferFundsFromLegacy( - address _token, - address _target, - uint256 _targetChainId, - uint256 _gasPerToken - ) external onlyOwner { - try this.transferFundsFromLegacy{gas: _gasPerToken}(_token, _target, _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. - } - } - - /// @dev Accepts ether only from the hyperchain associated with the specified chain ID. - /// @param _chainId The chain ID corresponding to the hyperchain allowed to send ether. - function receiveEth(uint256 _chainId) external payable { - if (BRIDGE_HUB.getHyperchain(_chainId) != msg.sender) { - revert Unauthorized(msg.sender); - } - } - - /// @dev Initializes the l2Bridge address by governance for a specific chain. - function initializeChainGovernance(uint256 _chainId, address _l2BridgeAddress) external onlyOwner { - l2BridgeAddress[_chainId] = _l2BridgeAddress; - } - - /// @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 _prevMsgSender The `msg.sender` address from the external call that initiated current one. - /// @param _l1Token The L1 token address which is deposited. - /// @param _amount The total amount of tokens to be bridged. - function bridgehubDepositBaseToken( - uint256 _chainId, - address _prevMsgSender, - address _l1Token, - uint256 _amount - ) external payable virtual onlyBridgehubOrEra(_chainId) whenNotPaused { - if (_l1Token == ETH_TOKEN_ADDRESS) { - if (msg.value != _amount) { - revert ValueMismatch(_amount, msg.value); - } - } else { - // The Bridgehub also checks this, but we want to be sure - if (msg.value != 0) { - revert NonEmptyMsgValue(); - } - - uint256 amount = _depositFunds(_prevMsgSender, IERC20(_l1Token), _amount); // note if _prevMsgSender is this contract, this will return 0. This does not happen. - // The token has non-standard transfer logic - if (amount != _amount) { - revert TokensWithFeesNotSupported(); - } - } - - if (!hyperbridgingEnabled[_chainId]) { - chainBalance[_chainId][_l1Token] += _amount; - } - // Note that we don't save the deposited amount, as this is for the base token, which gets sent to the refundRecipient if the tx fails - emit BridgehubDepositBaseTokenInitiated(_chainId, _prevMsgSender, _l1Token, _amount); - } - - /// @dev Transfers tokens from the depositor address to the smart contract address. - /// @return The difference between the contract balance before and after the transferring of funds. - function _depositFunds(address _from, IERC20 _token, uint256 _amount) internal returns (uint256) { - uint256 balanceBefore = _token.balanceOf(address(this)); - // slither-disable-next-line arbitrary-send-erc20 - _token.safeTransferFrom(_from, address(this), _amount); - uint256 balanceAfter = _token.balanceOf(address(this)); - - return balanceAfter - balanceBefore; - } - - /// @notice Initiates a deposit transaction within Bridgehub, used by `requestL2TransactionTwoBridges`. - /// @param _chainId The chain ID of the hyperchain 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. - function bridgehubDeposit( - uint256 _chainId, - address _prevMsgSender, - // solhint-disable-next-line no-unused-vars - uint256 _l2Value, - bytes calldata _data - ) - external - payable - override - onlyBridgehub - whenNotPaused - returns (L2TransactionRequestTwoBridgesInner memory request) - { - if (l2BridgeAddress[_chainId] == address(0)) { - revert L2BridgeNotSet(_chainId); - } - - (address _l1Token, uint256 _depositAmount, address _l2Receiver) = abi.decode( - _data, - (address, uint256, address) - ); - if (_l1Token == L1_WETH_TOKEN) { - revert TokenNotSupported(L1_WETH_TOKEN); - } - if (BRIDGE_HUB.baseToken(_chainId) == _l1Token) { - revert TokenNotSupported(_l1Token); - } - - uint256 amount; - if (_l1Token == ETH_TOKEN_ADDRESS) { - amount = msg.value; - if (_depositAmount != 0) { - revert DepositIncorrectAmount(0, _depositAmount); - } - } else { - if (msg.value != 0) { - revert NonEmptyMsgValue(); - } - amount = _depositAmount; - - uint256 depAmount = _depositFunds(_prevMsgSender, IERC20(_l1Token), _depositAmount); - // The token has non-standard transfer logic - if (depAmount != _depositAmount) { - revert DepositIncorrectAmount(depAmount, _depositAmount); - } - } - // empty deposit amount - if (amount == 0) { - revert EmptyDeposit(); - } - - bytes32 txDataHash = keccak256(abi.encode(_prevMsgSender, _l1Token, amount)); - if (!hyperbridgingEnabled[_chainId]) { - chainBalance[_chainId][_l1Token] += amount; - } - - { - // Request the finalization of the deposit on the L2 side - bytes memory l2TxCalldata = _getDepositL2Calldata(_prevMsgSender, _l2Receiver, _l1Token, amount); - - request = L2TransactionRequestTwoBridgesInner({ - magicValue: TWO_BRIDGES_MAGIC_VALUE, - l2Contract: l2BridgeAddress[_chainId], - l2Calldata: l2TxCalldata, - factoryDeps: new bytes[](0), - txDataHash: txDataHash - }); - } - emit BridgehubDepositInitiated({ - chainId: _chainId, - txDataHash: txDataHash, - from: _prevMsgSender, - to: _l2Receiver, - l1Token: _l1Token, - amount: amount - }); - } - - /// @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 _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( - uint256 _chainId, - bytes32 _txDataHash, - bytes32 _txHash - ) external override onlyBridgehub whenNotPaused { - if (depositHappened[_chainId][_txHash] != 0x00) { - revert DepositExists(); - } - depositHappened[_chainId][_txHash] = _txDataHash; - emit BridgehubDepositFinalized(_chainId, _txDataHash, _txHash); - } - - /// @dev Sets the L1ERC20Bridge contract address. Should be called only once. - function setL1Erc20Bridge(address _legacyBridge) external onlyOwner { - if (address(legacyBridge) != address(0)) { - revert AddressAlreadyUsed(address(legacyBridge)); - } - if (_legacyBridge == address(0)) { - revert ZeroAddress(); - } - legacyBridge = IL1ERC20Bridge(_legacyBridge); - } - - /// @dev Generate a calldata for calling the deposit finalization on the L2 bridge contract - function _getDepositL2Calldata( - address _l1Sender, - address _l2Receiver, - address _l1Token, - uint256 _amount - ) internal view returns (bytes memory) { - bytes memory gettersData = _getERC20Getters(_l1Token); - return abi.encodeCall(IL2Bridge.finalizeDeposit, (_l1Sender, _l2Receiver, _l1Token, _amount, gettersData)); - } - - /// @dev Receives and parses (name, symbol, decimals) from the token contract - function _getERC20Getters(address _token) internal 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); - } - - /// @dev Withdraw funds from the initiated deposit, that failed when finalizing on L2 - /// @param _depositSender The address of the deposit initiator - /// @param _l1Token The address of the deposited L1 ERC20 token - /// @param _amount The amount of the deposit that failed. - /// @param _l2TxHash The L2 transaction hash of the failed deposit finalization - /// @param _l2BatchNumber The L2 batch number where the deposit finalization was processed - /// @param _l2MessageIndex The position in the L2 logs Merkle tree of the l2Log that was sent with the message - /// @param _l2TxNumberInBatch The L2 transaction number in a batch, in which the log was sent - /// @param _merkleProof The Merkle proof of the processing L1 -> L2 transaction with deposit finalization - function claimFailedDeposit( - uint256 _chainId, - address _depositSender, - address _l1Token, - uint256 _amount, - bytes32 _l2TxHash, - uint256 _l2BatchNumber, - uint256 _l2MessageIndex, - uint16 _l2TxNumberInBatch, - bytes32[] calldata _merkleProof - ) external override { - _claimFailedDeposit({ - _checkedInLegacyBridge: false, - _chainId: _chainId, - _depositSender: _depositSender, - _l1Token: _l1Token, - _amount: _amount, - _l2TxHash: _l2TxHash, - _l2BatchNumber: _l2BatchNumber, - _l2MessageIndex: _l2MessageIndex, - _l2TxNumberInBatch: _l2TxNumberInBatch, - _merkleProof: _merkleProof - }); - } - - /// @dev Processes claims of failed deposit, whether they originated from the legacy bridge or the current system. - function _claimFailedDeposit( - bool _checkedInLegacyBridge, - uint256 _chainId, - address _depositSender, - address _l1Token, - uint256 _amount, - bytes32 _l2TxHash, - uint256 _l2BatchNumber, - uint256 _l2MessageIndex, - uint16 _l2TxNumberInBatch, - bytes32[] calldata _merkleProof - ) internal nonReentrant whenNotPaused { - { - bool proofValid = BRIDGE_HUB.proveL1ToL2TransactionStatus({ - _chainId: _chainId, - _l2TxHash: _l2TxHash, - _l2BatchNumber: _l2BatchNumber, - _l2MessageIndex: _l2MessageIndex, - _l2TxNumberInBatch: _l2TxNumberInBatch, - _merkleProof: _merkleProof, - _status: TxStatus.Failure - }); - if (!proofValid) { - revert InvalidProof(); - } - } - if (_amount == 0) { - revert NoFundsTransferred(); - } - - { - bool notCheckedInLegacyBridgeOrWeCanCheckDeposit; - { - // Deposits that happened before the upgrade cannot be checked here, they have to be claimed and checked in the legacyBridge - bool weCanCheckDepositHere = !_isEraLegacyDeposit(_chainId, _l2BatchNumber, _l2TxNumberInBatch); - // Double claims are not possible, as depositHappened is checked here for all except legacy deposits (which have to happen through the legacy bridge) - // Funds claimed before the update will still be recorded in the legacy bridge - // Note we double check NEW deposits if they are called from the legacy bridge - notCheckedInLegacyBridgeOrWeCanCheckDeposit = (!_checkedInLegacyBridge) || weCanCheckDepositHere; - } - if (notCheckedInLegacyBridgeOrWeCanCheckDeposit) { - bytes32 dataHash = depositHappened[_chainId][_l2TxHash]; - bytes32 txDataHash = keccak256(abi.encode(_depositSender, _l1Token, _amount)); - if (dataHash != txDataHash) { - revert DepositDoesNotExist(); - } - delete depositHappened[_chainId][_l2TxHash]; - } - } - - if (!hyperbridgingEnabled[_chainId]) { - // check that the chain has sufficient balance - if (chainBalance[_chainId][_l1Token] < _amount) { - revert InsufficientChainBalance(); - } - chainBalance[_chainId][_l1Token] -= _amount; - } - - // Withdraw funds - if (_l1Token == ETH_TOKEN_ADDRESS) { - bool callSuccess; - // Low-level assembly call, to avoid any memory copying (save gas) - assembly { - callSuccess := call(gas(), _depositSender, _amount, 0, 0, 0, 0) - } - if (!callSuccess) { - revert WithdrawFailed(); - } - } else { - IERC20(_l1Token).safeTransfer(_depositSender, _amount); - // 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. - } - - emit ClaimedFailedDepositSharedBridge(_chainId, _depositSender, _l1Token, _amount); - } - - /// @dev Determines if an eth withdrawal was initiated on ZKsync Era before the upgrade to the Shared Bridge. - /// @param _chainId The chain ID of the transaction to check. - /// @param _l2BatchNumber The L2 batch number for the withdrawal. - /// @return Whether withdrawal was initiated on ZKsync Era before diamond proxy upgrade. - function _isEraLegacyEthWithdrawal(uint256 _chainId, uint256 _l2BatchNumber) internal view returns (bool) { - if ((_chainId == ERA_CHAIN_ID) && eraPostDiamondUpgradeFirstBatch == 0) { - revert SharedBridgeValueNotSet(SharedBridgeKey.PostUpgradeFirstBatch); - } - return (_chainId == ERA_CHAIN_ID) && (_l2BatchNumber < eraPostDiamondUpgradeFirstBatch); - } - - /// @dev Determines if a token withdrawal was initiated on ZKsync Era before the upgrade to the Shared Bridge. - /// @param _chainId The chain ID of the transaction to check. - /// @param _l2BatchNumber The L2 batch number for the withdrawal. - /// @return Whether withdrawal was initiated on ZKsync Era before Legacy Bridge upgrade. - function _isEraLegacyTokenWithdrawal(uint256 _chainId, uint256 _l2BatchNumber) internal view returns (bool) { - if ((_chainId == ERA_CHAIN_ID) && eraPostLegacyBridgeUpgradeFirstBatch == 0) { - revert SharedBridgeValueNotSet(SharedBridgeKey.LegacyBridgeFirstBatch); - } - return (_chainId == ERA_CHAIN_ID) && (_l2BatchNumber < eraPostLegacyBridgeUpgradeFirstBatch); - } - - /// @dev Determines if a deposit was initiated on ZKsync Era before the upgrade to the Shared Bridge. - /// @param _chainId The chain ID of the transaction to check. - /// @param _l2BatchNumber The L2 batch number for the deposit where it was processed. - /// @param _l2TxNumberInBatch The L2 transaction number in the batch, in which the deposit was processed. - /// @return Whether deposit was initiated on ZKsync Era before Shared Bridge upgrade. - function _isEraLegacyDeposit( - uint256 _chainId, - uint256 _l2BatchNumber, - uint256 _l2TxNumberInBatch - ) internal view returns (bool) { - if ((_chainId == ERA_CHAIN_ID) && (eraLegacyBridgeLastDepositBatch == 0)) { - revert SharedBridgeValueNotSet(SharedBridgeKey.LegacyBridgeLastDepositBatch); - } - return - (_chainId == ERA_CHAIN_ID) && - (_l2BatchNumber < eraLegacyBridgeLastDepositBatch || - (_l2TxNumberInBatch < eraLegacyBridgeLastDepositTxNumber && - _l2BatchNumber == eraLegacyBridgeLastDepositBatch)); - } - - /// @notice Finalize the withdrawal and release funds - /// @param _chainId The chain ID of the transaction to check - /// @param _l2BatchNumber The L2 batch number where the withdrawal was processed - /// @param _l2MessageIndex The position in the L2 logs Merkle tree of the l2Log that was sent with the message - /// @param _l2TxNumberInBatch The L2 transaction number in the batch, in which the log was sent - /// @param _message The L2 withdraw data, stored in an L2 -> L1 message - /// @param _merkleProof The Merkle proof of the inclusion L2 -> L1 message about withdrawal initialization - function finalizeWithdrawal( - uint256 _chainId, - uint256 _l2BatchNumber, - uint256 _l2MessageIndex, - uint16 _l2TxNumberInBatch, - bytes calldata _message, - bytes32[] calldata _merkleProof - ) external override { - // To avoid rewithdrawing txs that have already happened on the legacy bridge. - // Note: new withdraws are all recorded here, so double withdrawing them is not possible. - if (_isEraLegacyTokenWithdrawal(_chainId, _l2BatchNumber)) { - if (legacyBridge.isWithdrawalFinalized(_l2BatchNumber, _l2MessageIndex)) { - revert WithdrawalAlreadyFinalized(); - } - } - _finalizeWithdrawal({ - _chainId: _chainId, - _l2BatchNumber: _l2BatchNumber, - _l2MessageIndex: _l2MessageIndex, - _l2TxNumberInBatch: _l2TxNumberInBatch, - _message: _message, - _merkleProof: _merkleProof - }); - } - - struct MessageParams { - uint256 l2BatchNumber; - uint256 l2MessageIndex; - uint16 l2TxNumberInBatch; - } - - /// @dev Internal function that handles the logic for finalizing withdrawals, - /// serving both the current bridge system and the legacy ERC20 bridge. - function _finalizeWithdrawal( - uint256 _chainId, - uint256 _l2BatchNumber, - uint256 _l2MessageIndex, - uint16 _l2TxNumberInBatch, - bytes calldata _message, - bytes32[] calldata _merkleProof - ) internal nonReentrant whenNotPaused returns (address l1Receiver, address l1Token, uint256 amount) { - if (isWithdrawalFinalized[_chainId][_l2BatchNumber][_l2MessageIndex]) { - revert WithdrawalAlreadyFinalized(); - } - isWithdrawalFinalized[_chainId][_l2BatchNumber][_l2MessageIndex] = true; - - // Handling special case for withdrawal from ZKsync Era initiated before Shared Bridge. - if (_isEraLegacyEthWithdrawal(_chainId, _l2BatchNumber)) { - // Checks that the withdrawal wasn't finalized already. - bool alreadyFinalized = IGetters(ERA_DIAMOND_PROXY).isEthWithdrawalFinalized( - _l2BatchNumber, - _l2MessageIndex - ); - if (alreadyFinalized) { - revert WithdrawalAlreadyFinalized(); - } - } - - MessageParams memory messageParams = MessageParams({ - l2BatchNumber: _l2BatchNumber, - l2MessageIndex: _l2MessageIndex, - l2TxNumberInBatch: _l2TxNumberInBatch - }); - (l1Receiver, l1Token, amount) = _checkWithdrawal(_chainId, messageParams, _message, _merkleProof); - - if (!hyperbridgingEnabled[_chainId]) { - // Check that the chain has sufficient balance - if (chainBalance[_chainId][l1Token] < amount) { - // not enough funds - revert InsufficientChainBalance(); - } - 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) - } - if (!callSuccess) { - revert WithdrawFailed(); - } - } else { - // Withdraw funds - IERC20(l1Token).safeTransfer(l1Receiver, amount); - } - emit WithdrawalFinalizedSharedBridge(_chainId, l1Receiver, l1Token, amount); - } - - /// @dev Verifies the validity of a withdrawal message from L2 and returns details of the withdrawal. - function _checkWithdrawal( - uint256 _chainId, - MessageParams memory _messageParams, - bytes calldata _message, - bytes32[] calldata _merkleProof - ) internal view returns (address l1Receiver, address l1Token, uint256 amount) { - (l1Receiver, l1Token, amount) = _parseL2WithdrawalMessage(_chainId, _message); - L2Message memory l2ToL1Message; - { - bool baseTokenWithdrawal = (l1Token == BRIDGE_HUB.baseToken(_chainId)); - address l2Sender = baseTokenWithdrawal ? L2_BASE_TOKEN_SYSTEM_CONTRACT_ADDR : l2BridgeAddress[_chainId]; - - l2ToL1Message = L2Message({ - txNumberInBatch: _messageParams.l2TxNumberInBatch, - sender: l2Sender, - data: _message - }); - } - - bool success = BRIDGE_HUB.proveL2MessageInclusion({ - _chainId: _chainId, - _batchNumber: _messageParams.l2BatchNumber, - _index: _messageParams.l2MessageIndex, - _message: l2ToL1Message, - _proof: _merkleProof - }); - // withdrawal wrong proof - if (!success) { - revert InvalidProof(); - } - } - - function _parseL2WithdrawalMessage( - uint256 _chainId, - bytes memory _l2ToL1message - ) internal view returns (address l1Receiver, address l1Token, uint256 amount) { - // We check that the message is long enough to read the data. - // Please note that there are two versions of the message: - // 1. The message that is sent by `withdraw(address _l1Receiver)` - // It should be equal to the length of the bytes4 function signature + address l1Receiver + uint256 amount = 4 + 20 + 32 = 56 (bytes). - // 2. The message that is sent by `withdrawWithMessage(address _l1Receiver, bytes calldata _additionalData)` - // It should be equal to the length of the following: - // bytes4 function signature + address l1Receiver + uint256 amount + address l2Sender + bytes _additionalData = - // = 4 + 20 + 32 + 32 + _additionalData.length >= 68 (bytes). - - // So the data is expected to be at least 56 bytes long. - // wrong message length - if (_l2ToL1message.length < 56) { - revert L2WithdrawalMessageWrongLength(_l2ToL1message.length); - } - - (uint32 functionSignature, uint256 offset) = UnsafeBytes.readUint32(_l2ToL1message, 0); - if (bytes4(functionSignature) == IMailbox.finalizeEthWithdrawal.selector) { - // this message is a base token withdrawal - (l1Receiver, offset) = UnsafeBytes.readAddress(_l2ToL1message, offset); - (amount, offset) = UnsafeBytes.readUint256(_l2ToL1message, offset); - l1Token = BRIDGE_HUB.baseToken(_chainId); - } else if (bytes4(functionSignature) == IL1ERC20Bridge.finalizeWithdrawal.selector) { - // We use the IL1ERC20Bridge for backward compatibility with old withdrawals. - - // this message is a token withdrawal - - // Check that the message length is correct. - // It should be equal to the length of the function signature + address + address + uint256 = 4 + 20 + 20 + 32 = - // 76 (bytes). - if (_l2ToL1message.length != 76) { - revert L2WithdrawalMessageWrongLength(_l2ToL1message.length); - } - (l1Receiver, offset) = UnsafeBytes.readAddress(_l2ToL1message, offset); - (l1Token, offset) = UnsafeBytes.readAddress(_l2ToL1message, offset); - (amount, offset) = UnsafeBytes.readUint256(_l2ToL1message, offset); - } else { - revert InvalidSelector(bytes4(functionSignature)); - } - } - - /*////////////////////////////////////////////////////////////// - ERA LEGACY FUNCTIONS - //////////////////////////////////////////////////////////////*/ - - /// @notice Initiates a deposit by locking funds on the contract and sending the request - /// of processing an L2 transaction where tokens would be minted. - /// @dev If the token is bridged for the first time, the L2 token contract will be deployed. Note however, that the - /// newly-deployed token does not support any custom logic, i.e. rebase tokens' functionality is not supported. - /// @param _prevMsgSender The `msg.sender` address from the external call that initiated current one. - /// @param _l2Receiver The account address that should receive funds on L2 - /// @param _l1Token The L1 token address which is deposited - /// @param _amount The total amount of tokens to be bridged - /// @param _l2TxGasLimit The L2 gas limit to be used in the corresponding L2 transaction - /// @param _l2TxGasPerPubdataByte The gasPerPubdataByteLimit to be used in the corresponding L2 transaction - /// @param _refundRecipient The address on L2 that will receive the refund for the transaction. - /// @dev If the L2 deposit finalization transaction fails, the `_refundRecipient` will receive the `_l2Value`. - /// Please note, the contract may change the refund recipient's address to eliminate sending funds to addresses - /// out of control. - /// - If `_refundRecipient` is a contract on L1, the refund will be sent to the aliased `_refundRecipient`. - /// - If `_refundRecipient` is set to `address(0)` and the sender has NO deployed bytecode on L1, the refund will - /// be sent to the `msg.sender` address. - /// - If `_refundRecipient` is set to `address(0)` and the sender has deployed bytecode on L1, the refund will be - /// sent to the aliased `msg.sender` address. - /// @dev The address aliasing of L1 contracts as refund recipient on L2 is necessary to guarantee that the funds - /// are controllable through the Mailbox, since the Mailbox applies address aliasing to the from address for the - /// L2 tx if the L1 msg.sender is a contract. Without address aliasing for L1 contracts as refund recipients they - /// would not be able to make proper L2 tx requests through the Mailbox to use or withdraw the funds from L2, and - /// the funds would be lost. - /// @return l2TxHash The L2 transaction hash of deposit finalization. - function depositLegacyErc20Bridge( - address _prevMsgSender, - address _l2Receiver, - address _l1Token, - uint256 _amount, - uint256 _l2TxGasLimit, - uint256 _l2TxGasPerPubdataByte, - address _refundRecipient - ) external payable override onlyLegacyBridge nonReentrant whenNotPaused returns (bytes32 l2TxHash) { - if (l2BridgeAddress[ERA_CHAIN_ID] == address(0)) { - revert L2BridgeNotSet(ERA_CHAIN_ID); - } - if (_l1Token == L1_WETH_TOKEN) { - revert TokenNotSupported(L1_WETH_TOKEN); - } - - // Note that funds have been transferred to this contract in the legacy ERC20 bridge. - if (!hyperbridgingEnabled[ERA_CHAIN_ID]) { - chainBalance[ERA_CHAIN_ID][_l1Token] += _amount; - } - - bytes memory l2TxCalldata = _getDepositL2Calldata(_prevMsgSender, _l2Receiver, _l1Token, _amount); - - { - // If the refund recipient is not specified, the refund will be sent to the sender of the transaction. - // Otherwise, the refund will be sent to the specified address. - // If the recipient is a contract on L1, the address alias will be applied. - address refundRecipient = AddressAliasHelper.actualRefundRecipient(_refundRecipient, _prevMsgSender); - - L2TransactionRequestDirect memory request = L2TransactionRequestDirect({ - chainId: ERA_CHAIN_ID, - l2Contract: l2BridgeAddress[ERA_CHAIN_ID], - mintValue: msg.value, // l2 gas + l2 msg.Value the bridgehub will withdraw the mintValue from the base token bridge for gas - l2Value: 0, // L2 msg.value, this contract doesn't support base token deposits or wrapping functionality, for direct deposits use bridgehub - l2Calldata: l2TxCalldata, - l2GasLimit: _l2TxGasLimit, - l2GasPerPubdataByteLimit: _l2TxGasPerPubdataByte, - factoryDeps: new bytes[](0), - refundRecipient: refundRecipient - }); - l2TxHash = BRIDGE_HUB.requestL2TransactionDirect{value: msg.value}(request); - } - - bytes32 txDataHash = keccak256(abi.encode(_prevMsgSender, _l1Token, _amount)); - // Save the deposited amount to claim funds on L1 if the deposit failed on L2 - depositHappened[ERA_CHAIN_ID][l2TxHash] = txDataHash; - - emit LegacyDepositInitiated({ - chainId: ERA_CHAIN_ID, - l2DepositTxHash: l2TxHash, - from: _prevMsgSender, - to: _l2Receiver, - l1Token: _l1Token, - amount: _amount - }); - } - - /// @notice Finalizes the withdrawal for transactions initiated via the legacy ERC20 bridge. - /// @param _l2BatchNumber The L2 batch number where the withdrawal was processed - /// @param _l2MessageIndex The position in the L2 logs Merkle tree of the l2Log that was sent with the message - /// @param _l2TxNumberInBatch The L2 transaction number in the batch, in which the log was sent - /// @param _message The L2 withdraw data, stored in an L2 -> L1 message - /// @param _merkleProof The Merkle proof of the inclusion L2 -> L1 message about withdrawal initialization - /// - /// @return l1Receiver The address on L1 that will receive the withdrawn funds - /// @return l1Token The address of the L1 token being withdrawn - /// @return amount The amount of the token being withdrawn - function finalizeWithdrawalLegacyErc20Bridge( - uint256 _l2BatchNumber, - uint256 _l2MessageIndex, - uint16 _l2TxNumberInBatch, - bytes calldata _message, - bytes32[] calldata _merkleProof - ) external override onlyLegacyBridge returns (address l1Receiver, address l1Token, uint256 amount) { - (l1Receiver, l1Token, amount) = _finalizeWithdrawal({ - _chainId: ERA_CHAIN_ID, - _l2BatchNumber: _l2BatchNumber, - _l2MessageIndex: _l2MessageIndex, - _l2TxNumberInBatch: _l2TxNumberInBatch, - _message: _message, - _merkleProof: _merkleProof - }); - } - - /// @notice Withdraw funds from the initiated deposit, that failed when finalizing on ZKsync Era chain. - /// This function is specifically designed for maintaining backward-compatibility with legacy `claimFailedDeposit` - /// method in `L1ERC20Bridge`. - /// - /// @param _depositSender The address of the deposit initiator - /// @param _l1Token The address of the deposited L1 ERC20 token - /// @param _amount The amount of the deposit that failed. - /// @param _l2TxHash The L2 transaction hash of the failed deposit finalization - /// @param _l2BatchNumber The L2 batch number where the deposit finalization was processed - /// @param _l2MessageIndex The position in the L2 logs Merkle tree of the l2Log that was sent with the message - /// @param _l2TxNumberInBatch The L2 transaction number in a batch, in which the log was sent - /// @param _merkleProof The Merkle proof of the processing L1 -> L2 transaction with deposit finalization - function claimFailedDepositLegacyErc20Bridge( - address _depositSender, - address _l1Token, - uint256 _amount, - bytes32 _l2TxHash, - uint256 _l2BatchNumber, - uint256 _l2MessageIndex, - uint16 _l2TxNumberInBatch, - bytes32[] calldata _merkleProof - ) external override onlyLegacyBridge { - _claimFailedDeposit({ - _checkedInLegacyBridge: true, - _chainId: ERA_CHAIN_ID, - _depositSender: _depositSender, - _l1Token: _l1Token, - _amount: _amount, - _l2TxHash: _l2TxHash, - _l2BatchNumber: _l2BatchNumber, - _l2MessageIndex: _l2MessageIndex, - _l2TxNumberInBatch: _l2TxNumberInBatch, - _merkleProof: _merkleProof - }); - } - - /*////////////////////////////////////////////////////////////// - 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/IL1ERC20Bridge.sol b/l1-contracts/contracts/bridge/interfaces/IL1ERC20Bridge.sol index d4c13af8e..b9426f3e1 100644 --- a/l1-contracts/contracts/bridge/interfaces/IL1ERC20Bridge.sol +++ b/l1-contracts/contracts/bridge/interfaces/IL1ERC20Bridge.sol @@ -8,11 +8,7 @@ import {IL1NativeTokenVault} from "./IL1NativeTokenVault.sol"; /// @title L1 Bridge contract legacy interface /// @author Matter Labs /// @custom:security-contact security@matterlabs.dev -<<<<<<< HEAD /// @notice Legacy Bridge interface before ZK chain migration, used for backward compatibility with ZKsync Era -======= -/// @notice Legacy Bridge interface before hyperchain migration, used for backward compatibility with ZKsync Era ->>>>>>> 874bc6ba940de9d37b474d1e3dda2fe4e869dfbe interface IL1ERC20Bridge { event DepositInitiated( bytes32 indexed l2DepositTxHash, diff --git a/l1-contracts/contracts/bridgehub/Bridgehub.sol b/l1-contracts/contracts/bridgehub/Bridgehub.sol index 12ff6cfc5..10495b489 100644 --- a/l1-contracts/contracts/bridgehub/Bridgehub.sol +++ b/l1-contracts/contracts/bridgehub/Bridgehub.sol @@ -15,13 +15,10 @@ import {IZkSyncHyperchain} from "../state-transition/chain-interfaces/IZkSyncHyp import {ETH_TOKEN_ADDRESS, TWO_BRIDGES_MAGIC_VALUE, BRIDGEHUB_MIN_SECOND_BRIDGE_ADDRESS, VIRTUAL_SENDER_ALIASED_ZERO_ADDRESS} from "../common/Config.sol"; import {BridgehubL2TransactionRequest, L2Message, L2Log, TxStatus} from "../common/Messaging.sol"; import {AddressAliasHelper} from "../vendor/AddressAliasHelper.sol"; -<<<<<<< HEAD import {IMessageRoot} from "./IMessageRoot.sol"; import {ISTMDeploymentTracker} from "./ISTMDeploymentTracker.sol"; import {L2CanonicalTransaction} from "../common/Messaging.sol"; -======= import {Unauthorized, STMAlreadyRegistered, STMNotRegistered, TokenAlreadyRegistered, TokenNotRegistered, ZeroChainId, ChainIdTooBig, SharedBridgeNotSet, BridgeHubAlreadyRegistered, AddressTooLow, MsgValueMismatch, WrongMagicValue, ZeroAddress} from "../common/L1ContractErrors.sol"; ->>>>>>> 874bc6ba940de9d37b474d1e3dda2fe4e869dfbe /// @author Matter Labs /// @custom:security-contact security@matterlabs.dev @@ -79,7 +76,9 @@ contract Bridgehub is IBridgehub, ReentrancyGuard, Ownable2StepUpgradeable, Paus mapping(uint256 chainId => bool isWhitelistedSettlementLayer) public whitelistedSettlementLayers; modifier onlyOwnerOrAdmin() { - require(msg.sender == admin || msg.sender == owner(), "BH: not owner or admin"); + if (msg.sender != admin && msg.sender != owner()) { + revert Unauthorized(msg.sender); + } _; } @@ -103,16 +102,9 @@ contract Bridgehub is IBridgehub, ReentrancyGuard, Ownable2StepUpgradeable, Paus _transferOwnership(_owner); } -<<<<<<< HEAD modifier onlyAliasedZero() { /// There is no sender for the wrapping, we use a virtual address. require(msg.sender == VIRTUAL_SENDER_ALIASED_ZERO_ADDRESS, "BH: not aliased zero"); -======= - modifier onlyOwnerOrAdmin() { - if (msg.sender != admin && msg.sender != owner()) { - revert Unauthorized(msg.sender); - } ->>>>>>> 874bc6ba940de9d37b474d1e3dda2fe4e869dfbe _; } @@ -173,19 +165,12 @@ contract Bridgehub is IBridgehub, ReentrancyGuard, Ownable2StepUpgradeable, Paus /// @notice State Transition can be any contract with the appropriate interface/functionality /// @param _stateTransitionManager the state transition manager address to be added function addStateTransitionManager(address _stateTransitionManager) external onlyOwner { -<<<<<<< HEAD - require( - !stateTransitionManagerIsRegistered[_stateTransitionManager], - "BH: state transition already registered" - ); -======= if (_stateTransitionManager == address(0)) { revert ZeroAddress(); } if (stateTransitionManagerIsRegistered[_stateTransitionManager]) { revert STMAlreadyRegistered(); } ->>>>>>> 874bc6ba940de9d37b474d1e3dda2fe4e869dfbe stateTransitionManagerIsRegistered[_stateTransitionManager] = true; emit StateTransitionManagerAdded(_stateTransitionManager); @@ -195,16 +180,12 @@ contract Bridgehub is IBridgehub, ReentrancyGuard, Ownable2StepUpgradeable, Paus /// @notice this stops new Chains from using the STF, old chains are not affected /// @param _stateTransitionManager the state transition manager address to be removed function removeStateTransitionManager(address _stateTransitionManager) external onlyOwner { -<<<<<<< HEAD - require(stateTransitionManagerIsRegistered[_stateTransitionManager], "BH: state transition not registered yet"); -======= if (_stateTransitionManager == address(0)) { revert ZeroAddress(); } if (!stateTransitionManagerIsRegistered[_stateTransitionManager]) { revert STMNotRegistered(); } ->>>>>>> 874bc6ba940de9d37b474d1e3dda2fe4e869dfbe stateTransitionManagerIsRegistered[_stateTransitionManager] = false; emit StateTransitionManagerRemoved(_stateTransitionManager); @@ -213,13 +194,9 @@ contract Bridgehub is IBridgehub, ReentrancyGuard, Ownable2StepUpgradeable, Paus /// @notice token can be any contract with the appropriate interface/functionality /// @param _token address of base token to be registered function addToken(address _token) external onlyOwner { -<<<<<<< HEAD - require(!tokenIsRegistered[_token], "BH: token already registered"); -======= if (tokenIsRegistered[_token]) { revert TokenAlreadyRegistered(_token); } ->>>>>>> 874bc6ba940de9d37b474d1e3dda2fe4e869dfbe tokenIsRegistered[_token] = true; emit TokenRegistered(_token); @@ -228,16 +205,11 @@ contract Bridgehub is IBridgehub, ReentrancyGuard, Ownable2StepUpgradeable, Paus /// @notice To set shared bridge, only Owner. Not done in initialize, as /// the order of deployment is Bridgehub, Shared bridge, and then we call this function setSharedBridge(address _sharedBridge) external onlyOwner { -<<<<<<< HEAD - sharedBridge = IL1AssetRouter(_sharedBridge); - - emit SharedBridgeUpdated(_sharedBridge); -======= if (_sharedBridge == address(0)) { revert ZeroAddress(); } - sharedBridge = IL1SharedBridge(_sharedBridge); ->>>>>>> 874bc6ba940de9d37b474d1e3dda2fe4e869dfbe + sharedBridge = IL1AssetRouter(_sharedBridge); + emit SharedBridgeUpdated(_sharedBridge); } /// @notice Used to register a chain as a settlement layer. @@ -285,16 +257,6 @@ contract Bridgehub is IBridgehub, ReentrancyGuard, Ownable2StepUpgradeable, Paus bytes calldata _initData, bytes[] calldata _factoryDeps ) external onlyOwnerOrAdmin nonReentrant whenNotPaused returns (uint256) { -<<<<<<< HEAD - require(_chainId != 0, "BH: chainId cannot be 0"); - require(_chainId <= type(uint48).max, "BH: chainId too large"); - - require(stateTransitionManagerIsRegistered[_stateTransitionManager], "BH: state transition not registered"); - require(tokenIsRegistered[_baseToken], "BH: token not registered"); - require(address(sharedBridge) != address(0), "BH: shared bridge not set"); - - require(stateTransitionManager[_chainId] == address(0), "BH: chainId already registered"); -======= if (_chainId == 0) { revert ZeroChainId(); } @@ -321,7 +283,6 @@ contract Bridgehub is IBridgehub, ReentrancyGuard, Ownable2StepUpgradeable, Paus if (stateTransitionManager[_chainId] != address(0)) { revert BridgeHubAlreadyRegistered(); } ->>>>>>> 874bc6ba940de9d37b474d1e3dda2fe4e869dfbe stateTransitionManager[_chainId] = _stateTransitionManager; baseToken[_chainId] = _baseToken; @@ -378,9 +339,13 @@ contract Bridgehub is IBridgehub, ReentrancyGuard, Ownable2StepUpgradeable, Paus { bytes32 tokenAssetId = baseTokenAssetId[_request.chainId]; if (tokenAssetId == ETH_TOKEN_ASSET_ID) { - require(msg.value == _request.mintValue, "BH: msg.value mismatch 1"); + if (msg.value != _request.mintValue) { + revert MsgValueMismatch(_request.mintValue, msg.value); + } } else { - require(msg.value == 0, "BH: non-eth bridge with msg.value"); + if (msg.value != 0) { + revert MsgValueMismatch(0, msg.value); + } } // slither-disable-next-line arbitrary-send-eth @@ -432,10 +397,14 @@ contract Bridgehub is IBridgehub, ReentrancyGuard, Ownable2StepUpgradeable, Paus bytes32 tokenAssetId = baseTokenAssetId[_request.chainId]; uint256 baseTokenMsgValue; if (tokenAssetId == ETH_TOKEN_ASSET_ID) { - require(msg.value == _request.mintValue + _request.secondBridgeValue, "BH: msg.value mismatch 2"); + if (msg.value != _request.mintValue + _request.secondBridgeValue) { + revert MsgValueMismatch(_request.mintValue + _request.secondBridgeValue, msg.value); + } baseTokenMsgValue = _request.mintValue; } else { - require(msg.value == _request.secondBridgeValue, "BH: msg.value mismatch 3"); + if (msg.value != _request.secondBridgeValue) { + revert MsgValueMismatch(_request.secondBridgeValue, msg.value); + } baseTokenMsgValue = 0; } // slither-disable-next-line arbitrary-send-eth @@ -458,10 +427,15 @@ contract Bridgehub is IBridgehub, ReentrancyGuard, Ownable2StepUpgradeable, Paus _request.secondBridgeCalldata ); - require(outputRequest.magicValue == TWO_BRIDGES_MAGIC_VALUE, "BH: magic value mismatch"); + if (outputRequest.magicValue != TWO_BRIDGES_MAGIC_VALUE) { + revert WrongMagicValue(uint256(TWO_BRIDGES_MAGIC_VALUE), uint256(outputRequest.magicValue)); + } address refundRecipient = AddressAliasHelper.actualRefundRecipient(_request.refundRecipient, msg.sender); + if (_request.secondBridgeAddress <= BRIDGEHUB_MIN_SECOND_BRIDGE_ADDRESS) { + revert AddressTooLow(_request.secondBridgeAddress); + } canonicalTxHash = IZkSyncHyperchain(hyperchain).bridgehubRequestL2Transaction( BridgehubL2TransactionRequest({ sender: _request.secondBridgeAddress, @@ -584,30 +558,9 @@ contract Bridgehub is IBridgehub, ReentrancyGuard, Ownable2StepUpgradeable, Paus return IZkSyncHyperchain(hyperchain).l2TransactionBaseCost(_gasPrice, _l2GasLimit, _l2GasPerPubdataByteLimit); } -<<<<<<< HEAD /*////////////////////////////////////////////////////////////// Chain migration //////////////////////////////////////////////////////////////*/ -======= - /// @notice the mailbox is called directly after the sharedBridge received the deposit - /// this assumes that either ether is the base token or - /// the msg.sender has approved mintValue allowance for the sharedBridge. - /// This means this is not ideal for contract calls, as the contract would have to handle token allowance of the base Token - function requestL2TransactionDirect( - L2TransactionRequestDirect calldata _request - ) external payable override nonReentrant whenNotPaused returns (bytes32 canonicalTxHash) { - { - address token = baseToken[_request.chainId]; - if (token == ETH_TOKEN_ADDRESS) { - if (msg.value != _request.mintValue) { - revert MsgValueMismatch(_request.mintValue, msg.value); - } - } else { - if (msg.value != 0) { - revert MsgValueMismatch(0, msg.value); - } - } ->>>>>>> 874bc6ba940de9d37b474d1e3dda2fe4e869dfbe /// @notice IL1AssetHandler interface, used to migrate (transfer) a chain to the settlement layer. /// @param _settlementChainId the chainId of the settlement chain, i.e. where the message and the migrating chain is sent. @@ -641,7 +594,6 @@ contract Bridgehub is IBridgehub, ReentrancyGuard, Ownable2StepUpgradeable, Paus // TODO: double check that get only returns when chain id is there. } -<<<<<<< HEAD /// @dev IL1AssetHandler interface, used to receive a chain on the settlement layer. /// @param _assetId the assetId of the chain's STM /// @param _bridgehubMintData the data for the mint @@ -668,83 +620,6 @@ contract Bridgehub is IBridgehub, ReentrancyGuard, Ownable2StepUpgradeable, Paus messageRoot.addNewChainIfNeeded(_chainId); IZkSyncHyperchain(hyperchain).forwardedBridgeMint(_chainMintData); return address(0); -======= - /// @notice After depositing funds to the sharedBridge, the secondBridge is called - /// to return the actual L2 message which is sent to the Mailbox. - /// This assumes that either ether is the base token or - /// the msg.sender has approved the sharedBridge with the mintValue, - /// and also the necessary approvals are given for the second bridge. - /// @notice The logic of this bridge is to allow easy depositing for bridges. - /// Each contract that handles the users ERC20 tokens needs approvals from the user, this contract allows - /// the user to approve for each token only its respective bridge - /// @notice This function is great for contract calls to L2, the secondBridge can be any contract. - function requestL2TransactionTwoBridges( - L2TransactionRequestTwoBridgesOuter calldata _request - ) external payable override nonReentrant whenNotPaused returns (bytes32 canonicalTxHash) { - { - address token = baseToken[_request.chainId]; - uint256 baseTokenMsgValue; - if (token == ETH_TOKEN_ADDRESS) { - if (msg.value != _request.mintValue + _request.secondBridgeValue) { - revert MsgValueMismatch(_request.mintValue + _request.secondBridgeValue, msg.value); - } - baseTokenMsgValue = _request.mintValue; - } else { - if (msg.value != _request.secondBridgeValue) { - revert MsgValueMismatch(_request.secondBridgeValue, msg.value); - } - baseTokenMsgValue = 0; - } - // slither-disable-next-line arbitrary-send-eth - sharedBridge.bridgehubDepositBaseToken{value: baseTokenMsgValue}( - _request.chainId, - msg.sender, - token, - _request.mintValue - ); - } - - address hyperchain = getHyperchain(_request.chainId); - - // slither-disable-next-line arbitrary-send-eth - L2TransactionRequestTwoBridgesInner memory outputRequest = IL1SharedBridge(_request.secondBridgeAddress) - .bridgehubDeposit{value: _request.secondBridgeValue}( - _request.chainId, - msg.sender, - _request.l2Value, - _request.secondBridgeCalldata - ); - - if (outputRequest.magicValue != TWO_BRIDGES_MAGIC_VALUE) { - revert WrongMagicValue(uint256(TWO_BRIDGES_MAGIC_VALUE), uint256(outputRequest.magicValue)); - } - - address refundRecipient = AddressAliasHelper.actualRefundRecipient(_request.refundRecipient, msg.sender); - - if (_request.secondBridgeAddress <= BRIDGEHUB_MIN_SECOND_BRIDGE_ADDRESS) { - revert AddressTooLow(_request.secondBridgeAddress); - } - // to avoid calls to precompiles - canonicalTxHash = IZkSyncHyperchain(hyperchain).bridgehubRequestL2Transaction( - BridgehubL2TransactionRequest({ - sender: _request.secondBridgeAddress, - contractL2: outputRequest.l2Contract, - mintValue: _request.mintValue, - l2Value: _request.l2Value, - l2Calldata: outputRequest.l2Calldata, - l2GasLimit: _request.l2GasLimit, - l2GasPerPubdataByteLimit: _request.l2GasPerPubdataByteLimit, - factoryDeps: outputRequest.factoryDeps, - refundRecipient: refundRecipient - }) - ); - - IL1SharedBridge(_request.secondBridgeAddress).bridgehubConfirmL2Transaction( - _request.chainId, - outputRequest.txDataHash, - canonicalTxHash - ); ->>>>>>> 874bc6ba940de9d37b474d1e3dda2fe4e869dfbe } /// @dev IL1AssetHandler interface, used to undo a failed migration of a chain. diff --git a/l1-contracts/contracts/dev-contracts/test/DummyBridgehub.sol b/l1-contracts/contracts/dev-contracts/test/DummyBridgehub.sol index c2524bbdf..fc092c801 100644 --- a/l1-contracts/contracts/dev-contracts/test/DummyBridgehub.sol +++ b/l1-contracts/contracts/dev-contracts/test/DummyBridgehub.sol @@ -2,12 +2,13 @@ pragma solidity 0.8.24; -<<<<<<< HEAD import {ETH_TOKEN_ADDRESS} from "../../common/Config.sol"; import {L2_NATIVE_TOKEN_VAULT_ADDRESS} from "../../common/L2ContractAddresses.sol"; import {IMessageRoot} from "../../bridgehub/IMessageRoot.sol"; import {IGetters} from "../../state-transition/chain-interfaces/IGetters.sol"; +import {Bridgehub} from "../../bridgehub/Bridgehub.sol"; + contract DummyBridgehub { IMessageRoot public messageRoot; @@ -15,6 +16,8 @@ contract DummyBridgehub { // add this to be excluded from coverage report function test() internal virtual {} + constructor() {} + function baseTokenAssetId(uint256) external view returns (bytes32) { return keccak256( @@ -29,19 +32,9 @@ contract DummyBridgehub { function setMessageRoot(address _messageRoot) public { messageRoot = IMessageRoot(_messageRoot); -======= -import {Bridgehub} from "../../bridgehub/Bridgehub.sol"; - -/// @title DummyBridgehub -/// @notice A test smart contract that allows to set State Transition Manager for a given chain -contract DummyBridgehub is Bridgehub { - // add this to be excluded from coverage report - function test() internal virtual {} - - constructor() Bridgehub() {} - - function setStateTransitionManager(uint256 _chainId, address _stm) external { - stateTransitionManager[_chainId] = _stm; ->>>>>>> 874bc6ba940de9d37b474d1e3dda2fe4e869dfbe } + + // function setStateTransitionManager(uint256 _chainId, address _stm) external { + // stateTransitionManager[_chainId] = _stm; + // } } diff --git a/l1-contracts/contracts/dev-contracts/test/DummySharedBridge.sol b/l1-contracts/contracts/dev-contracts/test/DummySharedBridge.sol index 6b49bae91..f0151b6df 100644 --- a/l1-contracts/contracts/dev-contracts/test/DummySharedBridge.sol +++ b/l1-contracts/contracts/dev-contracts/test/DummySharedBridge.sol @@ -5,24 +5,18 @@ pragma solidity 0.8.24; import {IERC20} from "@openzeppelin/contracts-v4/token/ERC20/IERC20.sol"; import {L2TransactionRequestTwoBridgesInner} from "../../bridgehub/IBridgehub.sol"; -<<<<<<< HEAD -import {TWO_BRIDGES_MAGIC_VALUE} from "../../common/Config.sol"; -import {IL1NativeTokenVault} from "../../bridge/L1NativeTokenVault.sol"; -import {L2_NATIVE_TOKEN_VAULT_ADDRESS} from "../../common/L2ContractAddresses.sol"; - -contract DummySharedBridge { - IL1NativeTokenVault public nativeTokenVault; - -======= import {TWO_BRIDGES_MAGIC_VALUE, ETH_TOKEN_ADDRESS} from "../../common/Config.sol"; +import {IL1NativeTokenVault} from "../../bridge/L1NativeTokenVault.sol"; import {PausableUpgradeable} from "@openzeppelin/contracts-upgradeable-v4/security/PausableUpgradeable.sol"; +import {L2_NATIVE_TOKEN_VAULT_ADDRESS} from "../../common/L2ContractAddresses.sol"; import {SafeERC20} from "@openzeppelin/contracts-v4/token/ERC20/utils/SafeERC20.sol"; import {IL2Bridge} from "../../bridge/interfaces/IL2Bridge.sol"; contract DummySharedBridge is PausableUpgradeable { using SafeERC20 for IERC20; ->>>>>>> 874bc6ba940de9d37b474d1e3dda2fe4e869dfbe + IL1NativeTokenVault public nativeTokenVault; + event BridgehubDepositBaseTokenInitiated( uint256 indexed chainId, address indexed from, @@ -203,10 +197,12 @@ contract DummySharedBridge is PausableUpgradeable { require(withdrawAmount == _depositAmount, "5T"); // The token has non-standard transfer logic } - bytes memory l2TxCalldata = abi.encodeCall( - IL2Bridge.finalizeDeposit, - (_prevMsgSender, _l2Receiver, _l1Token, amount, new bytes(0)) - ); + // TODO: restore + bytes memory l2TxCalldata = hex""; + // abi.encodeCall( + // IL2Bridge.finalizeDeposit, + // (_prevMsgSender, _l2Receiver, _l1Token, amount, new bytes(0)) + // ); bytes32 txDataHash = keccak256(abi.encode(_prevMsgSender, _l1Token, amount)); request = L2TransactionRequestTwoBridgesInner({ diff --git a/l1-contracts/contracts/governance/ChainAdmin.sol b/l1-contracts/contracts/governance/ChainAdmin.sol index ba30e71f1..4d9ff858f 100644 --- a/l1-contracts/contracts/governance/ChainAdmin.sol +++ b/l1-contracts/contracts/governance/ChainAdmin.sol @@ -2,40 +2,22 @@ pragma solidity 0.8.24; -<<<<<<< HEAD -import {Ownable2Step} from "@openzeppelin/contracts/access/Ownable2Step.sol"; -import {IChainAdmin} from "./IChainAdmin.sol"; -======= import {Ownable2Step} from "@openzeppelin/contracts-v4/access/Ownable2Step.sol"; import {IChainAdmin} from "./IChainAdmin.sol"; import {IAdmin} from "../state-transition/chain-interfaces/IAdmin.sol"; import {NoCallsProvided, Unauthorized, ZeroAddress} from "../common/L1ContractErrors.sol"; ->>>>>>> 874bc6ba940de9d37b474d1e3dda2fe4e869dfbe /// @author Matter Labs /// @custom:security-contact security@matterlabs.dev /// @notice The contract is designed to hold the `admin` role in ZKSync Chain (State Transition) contracts. /// The owner of the contract can perform any external calls and also save the information needed for -<<<<<<< HEAD -/// the blockchain node to accept the protocol upgrade. -contract ChainAdmin is IChainAdmin, Ownable2Step { - constructor(address _initialOwner) { - // solhint-disable-next-line gas-custom-errors, reason-string - require(_initialOwner != address(0), "Initial owner should be non zero address"); - _transferOwnership(_initialOwner); - } - -======= /// the blockchain node to accept the protocol upgrade. Another role - `tokenMultiplierSetter` can be used in the contract /// to change the base token gas price in the Chain contract. contract ChainAdmin is IChainAdmin, Ownable2Step { ->>>>>>> 874bc6ba940de9d37b474d1e3dda2fe4e869dfbe /// @notice Mapping of protocol versions to their expected upgrade timestamps. /// @dev Needed for the offchain node administration to know when to start building batches with the new protocol version. mapping(uint256 protocolVersion => uint256 upgradeTimestamp) public protocolVersionToUpgradeTimestamp; -<<<<<<< HEAD -======= /// @notice The address which can call `setTokenMultiplier` function to change the base token gas price in the Chain contract. /// @dev The token base price can be changed quite often, so the private key for this role is supposed to be stored in the node /// and used by the automated service in a way similar to the sequencer workflow. @@ -58,7 +40,6 @@ contract ChainAdmin is IChainAdmin, Ownable2Step { tokenMultiplierSetter = _tokenMultiplierSetter; } ->>>>>>> 874bc6ba940de9d37b474d1e3dda2fe4e869dfbe /// @notice Set the expected upgrade timestamp for a specific protocol version. /// @param _protocolVersion The ZKsync chain protocol version. /// @param _upgradeTimestamp The timestamp at which the chain node should expect the upgrade to happen. @@ -72,14 +53,9 @@ contract ChainAdmin is IChainAdmin, Ownable2Step { /// @param _requireSuccess If true, reverts transaction on any call failure. /// @dev Intended for batch processing of contract interactions, managing gas efficiency and atomicity of operations. function multicall(Call[] calldata _calls, bool _requireSuccess) external payable onlyOwner { -<<<<<<< HEAD - // solhint-disable-next-line gas-custom-errors - require(_calls.length > 0, "No calls provided"); -======= if (_calls.length == 0) { revert NoCallsProvided(); } ->>>>>>> 874bc6ba940de9d37b474d1e3dda2fe4e869dfbe // solhint-disable-next-line gas-length-in-loops for (uint256 i = 0; i < _calls.length; ++i) { // slither-disable-next-line arbitrary-send-eth @@ -94,8 +70,6 @@ contract ChainAdmin is IChainAdmin, Ownable2Step { } } -<<<<<<< HEAD -======= /// @notice Sets the token multiplier in the specified Chain contract. /// @param _chainContract The chain contract address where the token multiplier will be set. /// @param _nominator The numerator part of the token multiplier. @@ -107,7 +81,6 @@ contract ChainAdmin is IChainAdmin, Ownable2Step { _chainContract.setTokenMultiplier(_nominator, _denominator); } ->>>>>>> 874bc6ba940de9d37b474d1e3dda2fe4e869dfbe /// @dev Contract might receive/hold ETH as part of the maintenance process. receive() external payable {} } diff --git a/l1-contracts/contracts/governance/IChainAdmin.sol b/l1-contracts/contracts/governance/IChainAdmin.sol index cea8d7bcd..d5d8f117c 100644 --- a/l1-contracts/contracts/governance/IChainAdmin.sol +++ b/l1-contracts/contracts/governance/IChainAdmin.sol @@ -2,11 +2,8 @@ pragma solidity 0.8.24; -<<<<<<< HEAD -======= import {IAdmin} from "../state-transition/chain-interfaces/IAdmin.sol"; ->>>>>>> 874bc6ba940de9d37b474d1e3dda2fe4e869dfbe /// @title ChainAdmin contract interface /// @author Matter Labs /// @custom:security-contact security@matterlabs.dev @@ -25,9 +22,6 @@ interface IChainAdmin { event UpdateUpgradeTimestamp(uint256 indexed _protocolVersion, uint256 _upgradeTimestamp); /// @notice Emitted when the call is executed from the contract. -<<<<<<< HEAD - event CallExecuted(Call _call, bool success, bytes returnData); -======= event CallExecuted(Call _call, bool _success, bytes _returnData); /// @notice Emitted when the new token multiplier address is set. @@ -40,5 +34,4 @@ interface IChainAdmin { function multicall(Call[] calldata _calls, bool _requireSuccess) external payable; function setTokenMultiplier(IAdmin _chainContract, uint128 _nominator, uint128 _denominator) external; ->>>>>>> 874bc6ba940de9d37b474d1e3dda2fe4e869dfbe } diff --git a/l1-contracts/contracts/state-transition/StateTransitionManager.sol b/l1-contracts/contracts/state-transition/StateTransitionManager.sol index f305031e9..9396458ab 100644 --- a/l1-contracts/contracts/state-transition/StateTransitionManager.sol +++ b/l1-contracts/contracts/state-transition/StateTransitionManager.sol @@ -13,11 +13,6 @@ import {IExecutor} from "./chain-interfaces/IExecutor.sol"; import {IStateTransitionManager, StateTransitionManagerInitializeData, ChainCreationParams} from "./IStateTransitionManager.sol"; import {IZkSyncHyperchain} from "./chain-interfaces/IZkSyncHyperchain.sol"; import {FeeParams} from "./chain-deps/ZkSyncHyperchainStorage.sol"; -<<<<<<< HEAD -import {Ownable2StepUpgradeable} from "@openzeppelin/contracts-upgradeable/access/Ownable2StepUpgradeable.sol"; -import {ReentrancyGuard} from "../common/ReentrancyGuard.sol"; -import {L2_TO_L1_LOG_SERIALIZE_SIZE, DEFAULT_L2_LOGS_TREE_ROOT_HASH, EMPTY_STRING_KECCAK} from "../common/Config.sol"; -======= import {L2_SYSTEM_CONTEXT_SYSTEM_CONTRACT_ADDR, L2_FORCE_DEPLOYER_ADDR} from "../common/L2ContractAddresses.sol"; import {L2CanonicalTransaction} from "../common/Messaging.sol"; import {Ownable2StepUpgradeable} from "@openzeppelin/contracts-upgradeable-v4/access/Ownable2StepUpgradeable.sol"; @@ -26,7 +21,6 @@ import {ReentrancyGuard} from "../common/ReentrancyGuard.sol"; import {REQUIRED_L2_GAS_PRICE_PER_PUBDATA, L2_TO_L1_LOG_SERIALIZE_SIZE, DEFAULT_L2_LOGS_TREE_ROOT_HASH, EMPTY_STRING_KECCAK, SYSTEM_UPGRADE_L2_TX_TYPE, PRIORITY_TX_MAX_GAS_LIMIT} from "../common/Config.sol"; import {VerifierParams} from "./chain-interfaces/IVerifier.sol"; import {Unauthorized, ZeroAddress, HashMismatch, HyperchainLimitReached, GenesisUpgradeZero, GenesisBatchHashZero, GenesisIndexStorageZero, GenesisBatchCommitmentZero} from "../common/L1ContractErrors.sol"; ->>>>>>> 874bc6ba940de9d37b474d1e3dda2fe4e869dfbe import {SemVer} from "../common/libraries/SemVer.sol"; import {IBridgehub} from "../bridgehub/IBridgehub.sol"; @@ -393,41 +387,16 @@ contract StateTransitionManager is IStateTransitionManager, ReentrancyGuard, Own // check not registered Diamond.DiamondCutData memory diamondCut = abi.decode(_diamondCut, (Diamond.DiamondCutData)); -<<<<<<< HEAD - { - // check input - bytes32 cutHashInput = keccak256(_diamondCut); - require(cutHashInput == initialCutHash, "STM: initial cutHash mismatch"); - } - bytes memory mandatoryInitData; - { - // solhint-disable-next-line func-named-parameters - mandatoryInitData = bytes.concat( - bytes32(_chainId), - bytes32(uint256(uint160(BRIDGE_HUB))), - bytes32(uint256(uint160(address(this)))), - bytes32(protocolVersion), - bytes32(uint256(uint160(_admin))), - bytes32(uint256(uint160(validatorTimelock))), - bytes32(uint256(uint160(_baseToken))), - bytes32(uint256(uint160(_sharedBridge))), - storedBatchZero - ); -======= // check input bytes32 cutHashInput = keccak256(_diamondCut); if (cutHashInput != initialCutHash) { revert HashMismatch(initialCutHash, cutHashInput); ->>>>>>> 874bc6ba940de9d37b474d1e3dda2fe4e869dfbe } // construct init data bytes memory initData; /// all together 4+9*32=292 bytes for the selector + mandatory data // solhint-disable-next-line func-named-parameters -<<<<<<< HEAD - initData = bytes.concat(IDiamondInit.initialize.selector, mandatoryInitData, diamondCut.initCalldata); -======= initData = bytes.concat( IDiamondInit.initialize.selector, bytes32(_chainId), @@ -441,7 +410,6 @@ contract StateTransitionManager is IStateTransitionManager, ReentrancyGuard, Own storedBatchZero, diamondCut.initCalldata ); ->>>>>>> 874bc6ba940de9d37b474d1e3dda2fe4e869dfbe diamondCut.initCalldata = initData; // deploy hyperchainContract diff --git a/l1-contracts/contracts/state-transition/ValidatorTimelock.sol b/l1-contracts/contracts/state-transition/ValidatorTimelock.sol index b8d219111..710eaa870 100644 --- a/l1-contracts/contracts/state-transition/ValidatorTimelock.sol +++ b/l1-contracts/contracts/state-transition/ValidatorTimelock.sol @@ -6,11 +6,8 @@ import {Ownable2Step} from "@openzeppelin/contracts-v4/access/Ownable2Step.sol"; import {LibMap} from "./libraries/LibMap.sol"; import {IExecutor} from "./chain-interfaces/IExecutor.sol"; import {IStateTransitionManager} from "./IStateTransitionManager.sol"; -<<<<<<< HEAD import {PriorityOpsBatchInfo} from "./libraries/PriorityTree.sol"; -======= import {Unauthorized, TimeNotReached, ZeroAddress} from "../common/L1ContractErrors.sol"; ->>>>>>> 874bc6ba940de9d37b474d1e3dda2fe4e869dfbe /// @author Matter Labs /// @custom:security-contact security@matterlabs.dev diff --git a/l1-contracts/contracts/state-transition/chain-deps/DiamondInit.sol b/l1-contracts/contracts/state-transition/chain-deps/DiamondInit.sol index bf944e95c..a1812ca26 100644 --- a/l1-contracts/contracts/state-transition/chain-deps/DiamondInit.sol +++ b/l1-contracts/contracts/state-transition/chain-deps/DiamondInit.sol @@ -6,13 +6,10 @@ import {Diamond} from "../libraries/Diamond.sol"; import {ZkSyncHyperchainBase} from "./facets/ZkSyncHyperchainBase.sol"; import {L2_TO_L1_LOG_SERIALIZE_SIZE, MAX_GAS_PER_TRANSACTION} from "../../common/Config.sol"; import {InitializeData, IDiamondInit} from "../chain-interfaces/IDiamondInit.sol"; -<<<<<<< HEAD import {IBridgehub} from "../../bridgehub/IBridgehub.sol"; import {PriorityQueue} from "../libraries/PriorityQueue.sol"; import {PriorityTree} from "../libraries/PriorityTree.sol"; -======= import {ZeroAddress, TooMuchGas} from "../../common/L1ContractErrors.sol"; ->>>>>>> 874bc6ba940de9d37b474d1e3dda2fe4e869dfbe /// @author Matter Labs /// @dev The contract is used only once to initialize the diamond proxy. diff --git a/l1-contracts/contracts/state-transition/chain-deps/facets/Admin.sol b/l1-contracts/contracts/state-transition/chain-deps/facets/Admin.sol index cd472126d..20ce1f5c9 100644 --- a/l1-contracts/contracts/state-transition/chain-deps/facets/Admin.sol +++ b/l1-contracts/contracts/state-transition/chain-deps/facets/Admin.sol @@ -10,11 +10,8 @@ import {PriorityTree} from "../../../state-transition/libraries/PriorityTree.sol import {PriorityQueue} from "../../../state-transition/libraries/PriorityQueue.sol"; import {ZkSyncHyperchainBase} from "./ZkSyncHyperchainBase.sol"; import {IStateTransitionManager} from "../../IStateTransitionManager.sol"; -<<<<<<< HEAD import {IL1GenesisUpgrade} from "../../../upgrades/IL1GenesisUpgrade.sol"; -======= import {Unauthorized, TooMuchGas, PriorityTxPubdataExceedsMaxPubDataPerBatch, InvalidPubdataPricingMode, ProtocolIdMismatch, ChainAlreadyLive, HashMismatch, ProtocolIdNotGreater, DenominatorIsZero, DiamondAlreadyFrozen, DiamondNotFrozen} from "../../../common/L1ContractErrors.sol"; ->>>>>>> 874bc6ba940de9d37b474d1e3dda2fe4e869dfbe // While formally the following import is not used, it is needed to inherit documentation from it import {IZkSyncHyperchainBase} from "../../chain-interfaces/IZkSyncHyperchainBase.sol"; diff --git a/l1-contracts/contracts/state-transition/chain-deps/facets/Executor.sol b/l1-contracts/contracts/state-transition/chain-deps/facets/Executor.sol index 9406387e7..7fb021060 100644 --- a/l1-contracts/contracts/state-transition/chain-deps/facets/Executor.sol +++ b/l1-contracts/contracts/state-transition/chain-deps/facets/Executor.sol @@ -12,12 +12,9 @@ import {UncheckedMath} from "../../../common/libraries/UncheckedMath.sol"; import {UnsafeBytes} from "../../../common/libraries/UnsafeBytes.sol"; import {L2_BOOTLOADER_ADDRESS, L2_TO_L1_MESSENGER_SYSTEM_CONTRACT_ADDR, L2_SYSTEM_CONTEXT_SYSTEM_CONTRACT_ADDR} from "../../../common/L2ContractAddresses.sol"; import {IStateTransitionManager} from "../../IStateTransitionManager.sol"; -<<<<<<< HEAD import {PriorityTree, PriorityOpsBatchInfo} from "../../libraries/PriorityTree.sol"; import {IL1DAValidator, L1DAValidatorOutput} from "../../chain-interfaces/IL1DAValidator.sol"; -======= import {BatchNumberMismatch, TimeNotReached, TooManyBlobs, ValueMismatch, InvalidPubdataMode, InvalidPubdataLength, HashMismatch, NonIncreasingTimestamp, TimestampError, InvalidLogSender, TxHashMismatch, UnexpectedSystemLog, MissingSystemLogs, LogAlreadyProcessed, InvalidProtocolVersion, CanOnlyProcessOneBatch, BatchHashMismatch, UpgradeBatchNumberIsNotZero, NonSequentialBatch, CantExecuteUnprovenBatches, SystemLogsSizeTooBig, InvalidNumberOfBlobs, VerifiedBatchesExceedsCommittedBatches, InvalidProof, RevertedBatchNotAfterNewLastBatch, CantRevertExecutedBatch, PointEvalFailed, EmptyBlobVersionHash, NonEmptyBlobVersionHash, BlobHashCommitmentError, CalldataLengthTooBig, InvalidPubdataHash, L2TimestampTooBig, PriorityOperationsRollingHashMismatch, PubdataCommitmentsEmpty, PointEvalCallFailed, PubdataCommitmentsTooBig, InvalidPubdataCommitmentsSize} from "../../../common/L1ContractErrors.sol"; ->>>>>>> 874bc6ba940de9d37b474d1e3dda2fe4e869dfbe // While formally the following import is not used, it is needed to inherit documentation from it import {IZkSyncHyperchainBase} from "../../chain-interfaces/IZkSyncHyperchainBase.sol"; @@ -40,9 +37,11 @@ contract ExecutorFacet is ZkSyncHyperchainBase, IExecutor { StoredBatchInfo memory _previousBatch, CommitBatchInfo calldata _newBatch, bytes32 _expectedSystemContractUpgradeTxHash -<<<<<<< HEAD ) internal returns (StoredBatchInfo memory) { - require(_newBatch.batchNumber == _previousBatch.batchNumber + 1, "f"); // only commit next batch + // only commit next batch + if (_newBatch.batchNumber != _previousBatch.batchNumber + 1) { + revert BatchNumberMismatch(_previousBatch.batchNumber + 1, _newBatch.batchNumber); + } // Check that batch contains all meta information for L2 logs. // Get the chained hash of priority transaction hashes. @@ -54,55 +53,6 @@ contract ExecutorFacet is ZkSyncHyperchainBase, IExecutor { _newBatch.operatorDAInput, TOTAL_BLOBS_IN_COMMITMENT ); -======= - ) internal view returns (StoredBatchInfo memory) { - // only commit next batch - if (_newBatch.batchNumber != _previousBatch.batchNumber + 1) { - revert BatchNumberMismatch(_previousBatch.batchNumber + 1, _newBatch.batchNumber); - } - - uint8 pubdataSource = uint8(bytes1(_newBatch.pubdataCommitments[0])); - PubdataPricingMode pricingMode = s.feeParams.pubdataPricingMode; - if ( - pricingMode != PubdataPricingMode.Validium && - pubdataSource != uint8(PubdataSource.Calldata) && - pubdataSource != uint8(PubdataSource.Blob) - ) { - revert InvalidPubdataMode(); - } - - // Check that batch contain all meta information for L2 logs. - // Get the chained hash of priority transaction hashes. - LogProcessingOutput memory logOutput = _processL2Logs(_newBatch, _expectedSystemContractUpgradeTxHash); - - bytes32[] memory blobCommitments = new bytes32[](MAX_NUMBER_OF_BLOBS); - if (pricingMode == PubdataPricingMode.Validium) { - // skipping data validation for validium, we just check that the data is empty - if (_newBatch.pubdataCommitments.length != 1) { - revert CalldataLengthTooBig(); - } - for (uint8 i = uint8(SystemLogKey.BLOB_ONE_HASH_KEY); i <= uint8(SystemLogKey.BLOB_SIX_HASH_KEY); ++i) { - logOutput.blobHashes[i - uint8(SystemLogKey.BLOB_ONE_HASH_KEY)] = bytes32(0); - } - } else if (pubdataSource == uint8(PubdataSource.Blob)) { - // In this scenario, pubdataCommitments is a list of: opening point (16 bytes) || claimed value (32 bytes) || commitment (48 bytes) || proof (48 bytes)) = 144 bytes - blobCommitments = _verifyBlobInformation(_newBatch.pubdataCommitments[1:], logOutput.blobHashes); - } else if (pubdataSource == uint8(PubdataSource.Calldata)) { - // In this scenario pubdataCommitments is actual pubdata consisting of l2 to l1 logs, l2 to l1 message, compressed smart contract bytecode, and compressed state diffs - if (_newBatch.pubdataCommitments.length > BLOB_SIZE_BYTES) { - revert InvalidPubdataLength(); - } - bytes32 pubdataHash = keccak256(_newBatch.pubdataCommitments[1:_newBatch.pubdataCommitments.length - 32]); - if (logOutput.pubdataHash != pubdataHash) { - revert InvalidPubdataHash(pubdataHash, logOutput.pubdataHash); - } - blobCommitments[0] = bytes32( - _newBatch.pubdataCommitments[_newBatch.pubdataCommitments.length - 32:_newBatch - .pubdataCommitments - .length] - ); - } ->>>>>>> 874bc6ba940de9d37b474d1e3dda2fe4e869dfbe if (_previousBatch.batchHash != logOutput.previousBatchHash) { revert HashMismatch(logOutput.previousBatchHash, _previousBatch.batchHash); @@ -215,19 +165,6 @@ contract ExecutorFacet is ZkSyncHyperchainBase, IExecutor { revert InvalidLogSender(logSender, logKey); } logOutput.l2LogsTreeRoot = logValue; -<<<<<<< HEAD -======= - } else if (logKey == uint256(SystemLogKey.TOTAL_L2_TO_L1_PUBDATA_KEY)) { - if (logSender != L2_TO_L1_MESSENGER_SYSTEM_CONTRACT_ADDR) { - revert InvalidLogSender(logSender, logKey); - } - logOutput.pubdataHash = logValue; - } else if (logKey == uint256(SystemLogKey.STATE_DIFF_HASH_KEY)) { - if (logSender != L2_TO_L1_MESSENGER_SYSTEM_CONTRACT_ADDR) { - revert InvalidLogSender(logSender, logKey); - } - logOutput.stateDiffHash = logValue; ->>>>>>> 874bc6ba940de9d37b474d1e3dda2fe4e869dfbe } else if (logKey == uint256(SystemLogKey.PACKED_BATCH_AND_L2_BLOCK_TIMESTAMP_KEY)) { if (logSender != L2_SYSTEM_CONTEXT_SYSTEM_CONTRACT_ADDR) { revert InvalidLogSender(logSender, logKey); @@ -248,28 +185,12 @@ contract ExecutorFacet is ZkSyncHyperchainBase, IExecutor { revert InvalidLogSender(logSender, logKey); } logOutput.numberOfLayer1Txs = uint256(logValue); -<<<<<<< HEAD } else if (logKey == uint256(SystemLogKey.USED_L2_DA_VALIDATOR_ADDRESS_KEY)) { require(logSender == L2_TO_L1_MESSENGER_SYSTEM_CONTRACT_ADDR, "vk"); require(s.l2DAValidator == address(uint160(uint256(logValue))), "lo"); } else if (logKey == uint256(SystemLogKey.L2_DA_VALIDATOR_OUTPUT_HASH_KEY)) { require(logSender == L2_TO_L1_MESSENGER_SYSTEM_CONTRACT_ADDR, "lp2"); logOutput.l2DAValidatorOutputHash = logValue; -======= - } else if ( - logKey >= uint256(SystemLogKey.BLOB_ONE_HASH_KEY) && logKey <= uint256(SystemLogKey.BLOB_SIX_HASH_KEY) - ) { - if (logSender != L2_PUBDATA_CHUNK_PUBLISHER_ADDR) { - revert InvalidLogSender(logSender, logKey); - } - uint8 blobNumber = uint8(logKey) - uint8(SystemLogKey.BLOB_ONE_HASH_KEY); - - if (blobNumber >= MAX_NUMBER_OF_BLOBS) { - revert TooManyBlobs(); - } - - logOutput.blobHashes[blobNumber] = logValue; ->>>>>>> 874bc6ba940de9d37b474d1e3dda2fe4e869dfbe } else if (logKey == uint256(SystemLogKey.EXPECTED_SYSTEM_CONTRACT_UPGRADE_TX_HASH_KEY)) { if (logSender != L2_BOOTLOADER_ADDRESS) { revert InvalidLogSender(logSender, logKey); @@ -288,20 +209,10 @@ contract ExecutorFacet is ZkSyncHyperchainBase, IExecutor { // Without the protocol upgrade we expect 8 logs: 2^8 - 1 = 255 // With the protocol upgrade we expect 9 logs: 2^9 - 1 = 511 if (_expectedSystemContractUpgradeTxHash == bytes32(0)) { -<<<<<<< HEAD // require(processedLogs == 255, "b7"); } else { // FIXME: do restore this code to the one that was before require(_checkBit(processedLogs, uint8(SystemLogKey.EXPECTED_SYSTEM_CONTRACT_UPGRADE_TX_HASH_KEY)), "b8"); -======= - if (processedLogs != 8191) { - revert MissingSystemLogs(8191, processedLogs); - } - } else { - if (processedLogs != 16383) { - revert MissingSystemLogs(16383, processedLogs); - } ->>>>>>> 874bc6ba940de9d37b474d1e3dda2fe4e869dfbe } } @@ -454,38 +365,26 @@ contract ExecutorFacet is ZkSyncHyperchainBase, IExecutor { bytes32 _priorityOperationsHash ) internal view { uint256 currentBatchNumber = _storedBatch.batchNumber; -<<<<<<< HEAD - require(currentBatchNumber == s.totalBatchesExecuted + _executedBatchIdx + 1, "k"); // Execute batches in order - require( - _hashStoredBatchInfo(_storedBatch) == s.storedBatchHashes[currentBatchNumber], - "exe10" // executing batch should be committed - ); - require(_priorityOperationsHash == _storedBatch.priorityOperationsHash, "x"); // priority operations hash does not match with expected - } -======= if (currentBatchNumber != s.totalBatchesExecuted + _executedBatchIdx + 1) { revert NonSequentialBatch(); } if (_hashStoredBatchInfo(_storedBatch) != s.storedBatchHashes[currentBatchNumber]) { revert BatchHashMismatch(s.storedBatchHashes[currentBatchNumber], _hashStoredBatchInfo(_storedBatch)); } ->>>>>>> 874bc6ba940de9d37b474d1e3dda2fe4e869dfbe - + if (_priorityOperationsHash != _storedBatch.priorityOperationsHash) { + revert PriorityOperationsRollingHashMismatch(); + } + } + /// @dev Executes one batch /// @dev 1. Processes all pending operations (Complete priority requests) /// @dev 2. Finalizes batch on Ethereum /// @dev _executedBatchIdx is an index in the array of the batches that we want to execute together function _executeOneBatch(StoredBatchInfo memory _storedBatch, uint256 _executedBatchIdx) internal { bytes32 priorityOperationsHash = _collectOperationsFromPriorityQueue(_storedBatch.numberOfLayer1Txs); -<<<<<<< HEAD _checkBatchData(_storedBatch, _executedBatchIdx, priorityOperationsHash); uint256 currentBatchNumber = _storedBatch.batchNumber; -======= - if (priorityOperationsHash != _storedBatch.priorityOperationsHash) { - revert PriorityOperationsRollingHashMismatch(); - } ->>>>>>> 874bc6ba940de9d37b474d1e3dda2fe4e869dfbe // Save root hash of L2 -> L1 logs tree s.l2LogsRootHashes[currentBatchNumber] = _storedBatch.l2LogsTreeRoot; @@ -764,14 +663,9 @@ contract ExecutorFacet is ZkSyncHyperchainBase, IExecutor { ) internal pure returns (bytes32[] memory blobAuxOutputWords) { // These invariants should be checked by the caller of this function, but we double check // just in case. -<<<<<<< HEAD - require(_blobCommitments.length == TOTAL_BLOBS_IN_COMMITMENT, "b10"); - require(_blobHashes.length == TOTAL_BLOBS_IN_COMMITMENT, "b11"); -======= - if (_blobCommitments.length != MAX_NUMBER_OF_BLOBS || _blobHashes.length != MAX_NUMBER_OF_BLOBS) { - revert InvalidNumberOfBlobs(MAX_NUMBER_OF_BLOBS, _blobCommitments.length, _blobHashes.length); + if (_blobCommitments.length != TOTAL_BLOBS_IN_COMMITMENT || _blobHashes.length != TOTAL_BLOBS_IN_COMMITMENT) { + revert InvalidNumberOfBlobs(TOTAL_BLOBS_IN_COMMITMENT, _blobCommitments.length, _blobHashes.length); } ->>>>>>> 874bc6ba940de9d37b474d1e3dda2fe4e869dfbe // for each blob we have: // linear hash (hash of preimage from system logs) and @@ -803,105 +697,4 @@ contract ExecutorFacet is ZkSyncHyperchainBase, IExecutor { function _setBit(uint256 _bitMap, uint8 _index) internal pure returns (uint256) { return _bitMap | (1 << _index); } -<<<<<<< HEAD -======= - - /// @notice Calls the point evaluation precompile and verifies the output - /// Verify p(z) = y given commitment that corresponds to the polynomial p(x) and a KZG proof. - /// Also verify that the provided commitment matches the provided versioned_hash. - /// - function _pointEvaluationPrecompile( - bytes32 _versionedHash, - bytes32 _openingPoint, - bytes calldata _openingValueCommitmentProof - ) internal view { - bytes memory precompileInput = abi.encodePacked(_versionedHash, _openingPoint, _openingValueCommitmentProof); - - (bool success, bytes memory data) = POINT_EVALUATION_PRECOMPILE_ADDR.staticcall(precompileInput); - - // We verify that the point evaluation precompile call was successful by testing the latter 32 bytes of the - // response is equal to BLS_MODULUS as defined in https://eips.ethereum.org/EIPS/eip-4844#point-evaluation-precompile - if (!success) { - revert PointEvalCallFailed(precompileInput); - } - (, uint256 result) = abi.decode(data, (uint256, uint256)); - if (result != BLS_MODULUS) { - revert PointEvalFailed(abi.encode(result)); - } - } - - /// @dev Verifies that the blobs contain the correct data by calling the point evaluation precompile. For the precompile we need: - /// versioned hash || opening point || opening value || commitment || proof - /// the _pubdataCommitments will contain the last 4 values, the versioned hash is pulled from the BLOBHASH opcode - /// pubdataCommitments is a list of: opening point (16 bytes) || claimed value (32 bytes) || commitment (48 bytes) || proof (48 bytes)) = 144 bytes - function _verifyBlobInformation( - bytes calldata _pubdataCommitments, - bytes32[] memory _blobHashes - ) internal view returns (bytes32[] memory blobCommitments) { - uint256 versionedHashIndex = 0; - - if (_pubdataCommitments.length == 0) { - revert PubdataCommitmentsEmpty(); - } - if (_pubdataCommitments.length > PUBDATA_COMMITMENT_SIZE * MAX_NUMBER_OF_BLOBS) { - revert PubdataCommitmentsTooBig(); - } - if (_pubdataCommitments.length % PUBDATA_COMMITMENT_SIZE != 0) { - revert InvalidPubdataCommitmentsSize(); - } - blobCommitments = new bytes32[](MAX_NUMBER_OF_BLOBS); - - // We disable this check because calldata array length is cheap. - // solhint-disable-next-line gas-length-in-loops - for (uint256 i = 0; i < _pubdataCommitments.length; i += PUBDATA_COMMITMENT_SIZE) { - bytes32 blobVersionedHash = _getBlobVersionedHash(versionedHashIndex); - - if (blobVersionedHash == bytes32(0)) { - revert EmptyBlobVersionHash(versionedHashIndex); - } - - // First 16 bytes is the opening point. While we get the point as 16 bytes, the point evaluation precompile - // requires it to be 32 bytes. The blob commitment must use the opening point as 16 bytes though. - bytes32 openingPoint = bytes32( - uint256(uint128(bytes16(_pubdataCommitments[i:i + PUBDATA_COMMITMENT_CLAIMED_VALUE_OFFSET]))) - ); - - _pointEvaluationPrecompile( - blobVersionedHash, - openingPoint, - _pubdataCommitments[i + PUBDATA_COMMITMENT_CLAIMED_VALUE_OFFSET:i + PUBDATA_COMMITMENT_SIZE] - ); - - // Take the hash of the versioned hash || opening point || claimed value - blobCommitments[versionedHashIndex] = keccak256( - abi.encodePacked(blobVersionedHash, _pubdataCommitments[i:i + PUBDATA_COMMITMENT_COMMITMENT_OFFSET]) - ); - ++versionedHashIndex; - } - - // This check is required because we want to ensure that there aren't any extra blobs trying to be published. - // Calling the BLOBHASH opcode with an index > # blobs - 1 yields bytes32(0) - bytes32 versionedHash = _getBlobVersionedHash(versionedHashIndex); - if (versionedHash != bytes32(0)) { - revert NonEmptyBlobVersionHash(versionedHashIndex); - } - - // We verify that for each set of blobHash/blobCommitment are either both empty - // or there are values for both. - for (uint256 i = 0; i < MAX_NUMBER_OF_BLOBS; ++i) { - if ( - (_blobHashes[i] == bytes32(0) && blobCommitments[i] != bytes32(0)) || - (_blobHashes[i] != bytes32(0) && blobCommitments[i] == bytes32(0)) - ) { - revert BlobHashCommitmentError(i, _blobHashes[i] == bytes32(0), blobCommitments[i] == bytes32(0)); - } - } - } - - function _getBlobVersionedHash(uint256 _index) internal view virtual returns (bytes32 versionedHash) { - assembly { - versionedHash := blobhash(_index) - } - } ->>>>>>> 874bc6ba940de9d37b474d1e3dda2fe4e869dfbe } diff --git a/l1-contracts/contracts/state-transition/chain-deps/facets/Mailbox.sol b/l1-contracts/contracts/state-transition/chain-deps/facets/Mailbox.sol index 3aa3bac79..18781078f 100644 --- a/l1-contracts/contracts/state-transition/chain-deps/facets/Mailbox.sol +++ b/l1-contracts/contracts/state-transition/chain-deps/facets/Mailbox.sol @@ -49,20 +49,6 @@ contract MailboxFacet is ZkSyncHyperchainBase, IMailbox { } /// @inheritdoc IMailbox -<<<<<<< HEAD -======= - function transferEthToSharedBridge() external onlyBaseTokenBridge { - if (s.chainId != ERA_CHAIN_ID) { - revert OnlyEraSupported(); - } - - uint256 amount = address(this).balance; - address baseTokenBridgeAddress = s.baseTokenBridge; - IL1SharedBridge(baseTokenBridgeAddress).receiveEth{value: amount}(ERA_CHAIN_ID); - } - - /// @notice when requesting transactions through the bridgehub ->>>>>>> 874bc6ba940de9d37b474d1e3dda2fe4e869dfbe function bridgehubRequestL2Transaction( BridgehubL2TransactionRequest calldata _request ) external onlyBridgehub returns (bytes32 canonicalTxHash) { @@ -235,13 +221,7 @@ contract MailboxFacet is ZkSyncHyperchainBase, IMailbox { L2Log memory _log, bytes32[] calldata _proof ) internal view returns (bool) { -<<<<<<< HEAD // require(_batchNumber <= s.totalBatchesExecuted, "xx"); -======= - if (_batchNumber > s.totalBatchesExecuted) { - revert BatchNotExecuted(_batchNumber); - } ->>>>>>> 874bc6ba940de9d37b474d1e3dda2fe4e869dfbe bytes32 hashedLog = keccak256( // solhint-disable-next-line func-named-parameters @@ -315,7 +295,6 @@ contract MailboxFacet is ZkSyncHyperchainBase, IMailbox { } /// @inheritdoc IMailbox -<<<<<<< HEAD function requestL2TransactionToGatewayMailbox( uint256 _chainId, L2CanonicalTransaction calldata _transaction, @@ -335,25 +314,6 @@ contract MailboxFacet is ZkSyncHyperchainBase, IMailbox { _factoryDeps: _factoryDeps, _canonicalTxHash: _canonicalTxHash, _expirationTimestamp: _expirationTimestamp -======= - function finalizeEthWithdrawal( - uint256 _l2BatchNumber, - uint256 _l2MessageIndex, - uint16 _l2TxNumberInBatch, - bytes calldata _message, - bytes32[] calldata _merkleProof - ) external nonReentrant { - if (s.chainId != ERA_CHAIN_ID) { - revert OnlyEraSupported(); - } - IL1SharedBridge(s.baseTokenBridge).finalizeWithdrawal({ - _chainId: ERA_CHAIN_ID, - _l2BatchNumber: _l2BatchNumber, - _l2MessageIndex: _l2MessageIndex, - _l2TxNumberInBatch: _l2TxNumberInBatch, - _message: _message, - _merkleProof: _merkleProof ->>>>>>> 874bc6ba940de9d37b474d1e3dda2fe4e869dfbe }); canonicalTxHash = _requestL2TransactionToGatewayFree(wrappedRequest); } @@ -362,7 +322,6 @@ contract MailboxFacet is ZkSyncHyperchainBase, IMailbox { function bridgehubRequestL2TransactionOnGateway( L2CanonicalTransaction calldata _transaction, bytes[] calldata _factoryDeps, -<<<<<<< HEAD bytes32 _canonicalTxHash, uint64 _expirationTimestamp ) external override onlyBridgehub { @@ -382,14 +341,6 @@ contract MailboxFacet is ZkSyncHyperchainBase, IMailbox { (_chainId, _transaction, _factoryDeps, _canonicalTxHash, _expirationTimestamp) ); return -======= - address _refundRecipient - ) external payable returns (bytes32 canonicalTxHash) { - if (s.chainId != ERA_CHAIN_ID) { - revert OnlyEraSupported(); - } - canonicalTxHash = _requestL2TransactionSender( ->>>>>>> 874bc6ba940de9d37b474d1e3dda2fe4e869dfbe BridgehubL2TransactionRequest({ /// There is no sender for the wrapping, we use a virtual address. sender: VIRTUAL_SENDER_ALIASED_ZERO_ADDRESS, @@ -443,15 +394,10 @@ contract MailboxFacet is ZkSyncHyperchainBase, IMailbox { function _requestL2Transaction(WritePriorityOpParams memory _params) internal returns (bytes32 canonicalTxHash) { BridgehubL2TransactionRequest memory request = _params.request; -<<<<<<< HEAD - require(request.factoryDeps.length <= MAX_NEW_FACTORY_DEPS, "uj"); - _params.txId = _nextPriorityTxId(); -======= if (request.factoryDeps.length > MAX_NEW_FACTORY_DEPS) { revert TooManyFactoryDeps(); } - _params.txId = s.priorityQueue.getTotalPriorityTxs(); ->>>>>>> 874bc6ba940de9d37b474d1e3dda2fe4e869dfbe + _params.txId = _nextPriorityTxId(); // Checking that the user provided enough ether to pay for the transaction. _params.l2GasPrice = _deriveL2GasPrice(tx.gasprice, request.l2GasPerPubdataByteLimit); @@ -463,10 +409,7 @@ contract MailboxFacet is ZkSyncHyperchainBase, IMailbox { request.refundRecipient = AddressAliasHelper.actualRefundRecipient(request.refundRecipient, request.sender); // Change the sender address if it is a smart contract to prevent address collision between L1 and L2. // Please note, currently ZKsync address derivation is different from Ethereum one, but it may be changed in the future. -<<<<<<< HEAD -======= // solhint-disable avoid-tx-origin ->>>>>>> 874bc6ba940de9d37b474d1e3dda2fe4e869dfbe // slither-disable-next-line tx-origin if (request.sender != tx.origin) { request.sender = AddressAliasHelper.applyL1ToL2Alias(request.sender); @@ -602,7 +545,9 @@ contract MailboxFacet is ZkSyncHyperchainBase, IMailbox { bytes calldata _message, bytes32[] calldata _merkleProof ) external nonReentrant { - require(s.chainId == ERA_CHAIN_ID, "Mailbox: finalizeEthWithdrawal only available for Era on mailbox"); + if (s.chainId != ERA_CHAIN_ID) { + revert OnlyEraSupported(); + } IL1AssetRouter(s.baseTokenBridge).finalizeWithdrawal({ _chainId: ERA_CHAIN_ID, _l2BatchNumber: _l2BatchNumber, @@ -623,7 +568,9 @@ contract MailboxFacet is ZkSyncHyperchainBase, IMailbox { bytes[] calldata _factoryDeps, address _refundRecipient ) external payable returns (bytes32 canonicalTxHash) { - require(s.chainId == ERA_CHAIN_ID, "Mailbox: legacy interface only available for Era"); + if (s.chainId != ERA_CHAIN_ID) { + revert OnlyEraSupported(); + } canonicalTxHash = _requestL2TransactionSender( BridgehubL2TransactionRequest({ sender: msg.sender, diff --git a/l1-contracts/contracts/state-transition/chain-interfaces/IZkSyncHyperchainBase.sol b/l1-contracts/contracts/state-transition/chain-interfaces/IZkSyncHyperchainBase.sol index 23466ce4a..3cd646cc9 100644 --- a/l1-contracts/contracts/state-transition/chain-interfaces/IZkSyncHyperchainBase.sol +++ b/l1-contracts/contracts/state-transition/chain-interfaces/IZkSyncHyperchainBase.sol @@ -1,10 +1,6 @@ // SPDX-License-Identifier: MIT -<<<<<<< HEAD -pragma solidity 0.8.24; -======= // We use a floating point pragma here so it can be used within other projects that interact with the zkSync ecosystem without using our exact pragma version. pragma solidity ^0.8.21; ->>>>>>> 874bc6ba940de9d37b474d1e3dda2fe4e869dfbe /// @title The interface of the ZKsync contract, responsible for the main ZKsync logic. /// @author Matter Labs diff --git a/l1-contracts/deploy-scripts/AcceptAdmin.s.sol b/l1-contracts/deploy-scripts/AcceptAdmin.s.sol index c4b9827cd..9262a5cd8 100644 --- a/l1-contracts/deploy-scripts/AcceptAdmin.s.sol +++ b/l1-contracts/deploy-scripts/AcceptAdmin.s.sol @@ -1,9 +1,4 @@ -<<<<<<< HEAD -// SPDX-License-Identifier: UNLICENSED -// We use a floating point pragma here so it can be used within other projects that interact with the zkSync ecosystem without using our exact pragma version. -======= // SPDX-License-Identifier: MIT ->>>>>>> 874bc6ba940de9d37b474d1e3dda2fe4e869dfbe pragma solidity ^0.8.21; import {Script} from "forge-std/Script.sol"; @@ -33,17 +28,11 @@ contract AcceptAdmin is Script { config.governor = toml.readAddress("$.governor"); } -<<<<<<< HEAD // This function should be called by the owner to accept the owner role function acceptOwner() public { initConfig(); Ownable2Step adminContract = Ownable2Step(config.admin); -======= - // This function should be called by the owner to accept the admin role - function governanceAcceptOwner(address governor, address target) public { - Ownable2Step adminContract = Ownable2Step(target); ->>>>>>> 874bc6ba940de9d37b474d1e3dda2fe4e869dfbe Utils.executeUpgrade({ _governor: governor, _salt: bytes32(0), @@ -55,7 +44,6 @@ contract AcceptAdmin is Script { } // This function should be called by the owner to accept the admin role -<<<<<<< HEAD function acceptAdmin(address payable _admin, address _target) public { IZkSyncHyperchain hyperchain = IZkSyncHyperchain(_target); ChainAdmin chainAdmin = ChainAdmin(_admin); @@ -66,18 +54,6 @@ contract AcceptAdmin is Script { vm.startBroadcast(); chainAdmin.multicall(calls, true); vm.stopBroadcast(); -======= - function governanceAcceptAdmin(address governor, address target) public { - IZkSyncHyperchain adminContract = IZkSyncHyperchain(target); - Utils.executeUpgrade({ - _governor: governor, - _salt: bytes32(0), - _target: target, - _data: abi.encodeCall(adminContract.acceptAdmin, ()), - _value: 0, - _delay: 0 - }); ->>>>>>> 874bc6ba940de9d37b474d1e3dda2fe4e869dfbe } // This function should be called by the owner to accept the admin role diff --git a/l1-contracts/deploy-scripts/DeployErc20.s.sol b/l1-contracts/deploy-scripts/DeployErc20.s.sol index 3ad89a8a4..6b58a2acb 100644 --- a/l1-contracts/deploy-scripts/DeployErc20.s.sol +++ b/l1-contracts/deploy-scripts/DeployErc20.s.sol @@ -128,7 +128,6 @@ contract DeployErc20Script is Script { if (mint > 0) { vm.broadcast(); additionalAddressesForMinting.push(config.deployerAddress); -<<<<<<< HEAD // solhint-disable-next-line gas-length-in-loops for (uint256 i = 0; i < additionalAddressesForMinting.length; ++i) { (bool success, ) = tokenAddress.call( @@ -138,17 +137,6 @@ contract DeployErc20Script is Script { if (!success) { revert MintFailed(); } -======= - uint256 addressMintListLength = additionalAddressesForMinting.length; - for (uint256 i = 0; i < addressMintListLength; ++i) { - (bool success, ) = tokenAddress.call( - abi.encodeWithSignature("mint(address,uint256)", additionalAddressesForMinting[i], mint) - ); - if (!success) { - revert MintFailed(); - } - console.log("Minting to:", additionalAddressesForMinting[i]); ->>>>>>> 874bc6ba940de9d37b474d1e3dda2fe4e869dfbe } } diff --git a/l1-contracts/deploy-scripts/DeployL1.s.sol b/l1-contracts/deploy-scripts/DeployL1.s.sol index 0adb606b4..71813c016 100644 --- a/l1-contracts/deploy-scripts/DeployL1.s.sol +++ b/l1-contracts/deploy-scripts/DeployL1.s.sol @@ -5,14 +5,8 @@ pragma solidity 0.8.24; import {Script, console2 as console} from "forge-std/Script.sol"; import {stdToml} from "forge-std/StdToml.sol"; -<<<<<<< HEAD -import {ProxyAdmin} from "@openzeppelin/contracts/proxy/transparent/ProxyAdmin.sol"; -import {TransparentUpgradeableProxy} from "@openzeppelin/contracts/proxy/transparent/TransparentUpgradeableProxy.sol"; -// import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol"; -======= import {ProxyAdmin} from "@openzeppelin/contracts-v4/proxy/transparent/ProxyAdmin.sol"; import {TransparentUpgradeableProxy} from "@openzeppelin/contracts-v4/proxy/transparent/TransparentUpgradeableProxy.sol"; ->>>>>>> 874bc6ba940de9d37b474d1e3dda2fe4e869dfbe import {Utils} from "./Utils.sol"; import {Multicall3} from "contracts/dev-contracts/Multicall3.sol"; @@ -21,13 +15,8 @@ import {TestnetVerifier} from "contracts/state-transition/TestnetVerifier.sol"; import {VerifierParams, IVerifier} from "contracts/state-transition/chain-interfaces/IVerifier.sol"; import {DefaultUpgrade} from "contracts/upgrades/DefaultUpgrade.sol"; import {Governance} from "contracts/governance/Governance.sol"; -<<<<<<< HEAD import {L1GenesisUpgrade} from "contracts/upgrades/L1GenesisUpgrade.sol"; import {ChainAdmin} from "contracts/governance/ChainAdmin.sol"; -======= -import {ChainAdmin} from "contracts/governance/ChainAdmin.sol"; -import {GenesisUpgrade} from "contracts/upgrades/GenesisUpgrade.sol"; ->>>>>>> 874bc6ba940de9d37b474d1e3dda2fe4e869dfbe import {ValidatorTimelock} from "contracts/state-transition/ValidatorTimelock.sol"; import {Bridgehub} from "contracts/bridgehub/Bridgehub.sol"; import {MessageRoot} from "contracts/bridgehub/MessageRoot.sol"; @@ -361,14 +350,7 @@ contract DeployL1Script is Script { } function deployChainAdmin() internal { -<<<<<<< HEAD bytes memory bytecode = abi.encodePacked(type(ChainAdmin).creationCode, abi.encode(config.ownerAddress)); -======= - bytes memory bytecode = abi.encodePacked( - type(ChainAdmin).creationCode, - abi.encode(config.ownerAddress, address(0)) - ); ->>>>>>> 874bc6ba940de9d37b474d1e3dda2fe4e869dfbe address contractAddress = deployViaCreate2(bytecode); console.log("ChainAdmin deployed at:", contractAddress); addresses.chainAdmin = contractAddress; diff --git a/l1-contracts/deploy-scripts/DeployL2Contracts.sol b/l1-contracts/deploy-scripts/DeployL2Contracts.sol index 9e3b3bdc6..cd647d7e9 100644 --- a/l1-contracts/deploy-scripts/DeployL2Contracts.sol +++ b/l1-contracts/deploy-scripts/DeployL2Contracts.sol @@ -45,10 +45,6 @@ contract DeployL2Script is Script { deployFactoryDeps(); deploySharedBridge(); deploySharedBridgeProxy(); -<<<<<<< HEAD -======= - initializeChain(); ->>>>>>> 874bc6ba940de9d37b474d1e3dda2fe4e869dfbe deployForceDeployer(); saveOutput(); @@ -61,10 +57,6 @@ contract DeployL2Script is Script { deployFactoryDeps(); deploySharedBridge(); deploySharedBridgeProxy(); -<<<<<<< HEAD -======= - initializeChain(); ->>>>>>> 874bc6ba940de9d37b474d1e3dda2fe4e869dfbe saveOutput(); } @@ -196,20 +188,4 @@ contract DeployL2Script is Script { l1SharedBridgeProxy: config.l1SharedBridgeProxy }); } -<<<<<<< HEAD -======= - - function initializeChain() internal { - L1SharedBridge bridge = L1SharedBridge(config.l1SharedBridgeProxy); - - Utils.executeUpgrade({ - _governor: bridge.owner(), - _salt: bytes32(0), - _target: config.l1SharedBridgeProxy, - _data: abi.encodeCall(bridge.initializeChainGovernance, (config.chainId, config.l2SharedBridgeProxy)), - _value: 0, - _delay: 0 - }); - } ->>>>>>> 874bc6ba940de9d37b474d1e3dda2fe4e869dfbe } diff --git a/l1-contracts/deploy-scripts/RegisterHyperchain.s.sol b/l1-contracts/deploy-scripts/RegisterHyperchain.s.sol index 2d8e7b854..6aca2fefe 100644 --- a/l1-contracts/deploy-scripts/RegisterHyperchain.s.sol +++ b/l1-contracts/deploy-scripts/RegisterHyperchain.s.sol @@ -174,11 +174,7 @@ contract RegisterHyperchainScript is Script { function deployChainAdmin() internal { vm.broadcast(); -<<<<<<< HEAD - ChainAdmin chainAdmin = new ChainAdmin(config.ownerAddress); -======= ChainAdmin chainAdmin = new ChainAdmin(config.ownerAddress, address(0)); ->>>>>>> 874bc6ba940de9d37b474d1e3dda2fe4e869dfbe console.log("ChainAdmin deployed at:", address(chainAdmin)); config.chainAdmin = address(chainAdmin); } @@ -258,14 +254,8 @@ contract RegisterHyperchainScript is Script { function setPendingAdmin() internal { IZkSyncHyperchain hyperchain = IZkSyncHyperchain(config.newDiamondProxy); -<<<<<<< HEAD - vm.startBroadcast(msg.sender); - hyperchain.setPendingAdmin(config.chainAdmin); - vm.stopBroadcast(); -======= vm.broadcast(); hyperchain.setPendingAdmin(config.chainAdmin); ->>>>>>> 874bc6ba940de9d37b474d1e3dda2fe4e869dfbe console.log("Owner for ", config.newDiamondProxy, "set to", config.chainAdmin); } diff --git a/l1-contracts/deploy-scripts/Utils.sol b/l1-contracts/deploy-scripts/Utils.sol index 780645b21..c3ab22594 100644 --- a/l1-contracts/deploy-scripts/Utils.sol +++ b/l1-contracts/deploy-scripts/Utils.sol @@ -8,12 +8,8 @@ import {Vm} from "forge-std/Vm.sol"; import {Bridgehub} from "contracts/bridgehub/Bridgehub.sol"; import {L2TransactionRequestDirect} from "contracts/bridgehub/IBridgehub.sol"; import {IGovernance} from "contracts/governance/IGovernance.sol"; -<<<<<<< HEAD -import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; -import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol"; -======= import {IERC20} from "@openzeppelin/contracts-v4/token/ERC20/IERC20.sol"; ->>>>>>> 874bc6ba940de9d37b474d1e3dda2fe4e869dfbe +import {Ownable} from "@openzeppelin/contracts-v4/access/Ownable.sol"; import {REQUIRED_L2_GAS_PRICE_PER_PUBDATA} from "contracts/common/Config.sol"; import {L2_DEPLOYER_SYSTEM_CONTRACT_ADDR} from "contracts/common/L2ContractAddresses.sol"; import {L2ContractHelper} from "contracts/common/libraries/L2ContractHelper.sol"; diff --git a/l1-contracts/foundry.toml b/l1-contracts/foundry.toml index b8389b8b9..c640a4b27 100644 --- a/l1-contracts/foundry.toml +++ b/l1-contracts/foundry.toml @@ -1,15 +1,4 @@ [profile.default] -<<<<<<< HEAD -src = 'contracts' -out = 'out' -libs = ['node_modules', 'lib', '../da-contracts/'] -remappings = [ - "@openzeppelin/contracts/=lib/openzeppelin-contracts/contracts/", - "@openzeppelin/contracts-upgradeable/=lib/openzeppelin-contracts-upgradeable/contracts/", - "l2-contracts/=../l2-contracts/contracts/", - "da-contracts/=../da-contracts/contracts/" -] -======= src = "contracts" out = "out" libs = ["lib"] @@ -17,18 +6,14 @@ cache_path = "cache-forge" test = "test/foundry" solc_version = "0.8.24" evm_version = "cancun" ->>>>>>> 874bc6ba940de9d37b474d1e3dda2fe4e869dfbe allow_paths = ["../l2-contracts/contracts"] fs_permissions = [ { access = "read", path = "../system-contracts/bootloader/build/artifacts" }, { access = "read", path = "../system-contracts/artifacts-zk/contracts-preprocessed" }, { access = "read", path = "../l2-contracts/artifacts-zk/" }, -<<<<<<< HEAD { access = "read", path = "../da-contracts/" }, -======= { access = "read", path = "../l2-contracts/zkout/" }, { access = "read", path = "../system-contracts/zkout/" }, ->>>>>>> 874bc6ba940de9d37b474d1e3dda2fe4e869dfbe { access = "read", path = "./script-config" }, { access = "read-write", path = "./script-out" }, { access = "read", path = "./out" }, @@ -43,4 +28,6 @@ remappings = [ "foundry-test/=test/foundry/", "@openzeppelin/contracts-v4/=lib/openzeppelin-contracts-v4/contracts/", "@openzeppelin/contracts-upgradeable-v4/=lib/openzeppelin-contracts-upgradeable-v4/contracts/", + "l2-contracts/=../l2-contracts/contracts/", + "da-contracts/=../da-contracts/contracts/" ] diff --git a/l1-contracts/scripts/register-hyperchain.ts b/l1-contracts/scripts/register-hyperchain.ts index 2d8bfa589..8a68c92df 100644 --- a/l1-contracts/scripts/register-hyperchain.ts +++ b/l1-contracts/scripts/register-hyperchain.ts @@ -66,12 +66,8 @@ async function main() { .option("--validium-mode") .option("--base-token-name ") .option("--base-token-address ") -<<<<<<< HEAD - .option("--use-governance") -======= .option("--use-governance ") .option("--token-multiplier-setter-address ") ->>>>>>> 874bc6ba940de9d37b474d1e3dda2fe4e869dfbe .action(async (cmd) => { const deployWallet = cmd.privateKey ? new Wallet(cmd.privateKey, provider) @@ -105,7 +101,6 @@ async function main() { if (!(await deployer.bridgehubContract(deployWallet).tokenIsRegistered(baseTokenAddress))) { await deployer.registerTokenBridgehub(baseTokenAddress, cmd.useGovernance); } -<<<<<<< HEAD await deployer.registerTokenInNativeTokenVault(baseTokenAddress); await deployer.registerHyperchain( baseTokenAddress, @@ -117,16 +112,13 @@ async function main() { null, cmd.useGovernance ); -======= const tokenMultiplierSetterAddress = cmd.tokenMultiplierSetterAddress || ""; - await deployer.registerHyperchain(baseTokenAddress, cmd.validiumMode, null, gasPrice, useGovernance); if (tokenMultiplierSetterAddress != "") { console.log(`Using token multiplier setter address: ${tokenMultiplierSetterAddress}`); await deployer.setTokenMultiplierSetterAddress(tokenMultiplierSetterAddress); } ->>>>>>> 874bc6ba940de9d37b474d1e3dda2fe4e869dfbe await deployer.transferAdminFromDeployerToChainAdmin(); }); diff --git a/l1-contracts/src.ts/deploy-process.ts b/l1-contracts/src.ts/deploy-process.ts index cb7ba5232..d197df699 100644 --- a/l1-contracts/src.ts/deploy-process.ts +++ b/l1-contracts/src.ts/deploy-process.ts @@ -61,7 +61,6 @@ export async function initialBridgehubDeployment( await deployer.deployChainAdmin(create2Salt, { gasPrice }); await deployer.deployValidatorTimelock(create2Salt, { gasPrice }); -<<<<<<< HEAD if (!deployer.isZkMode()) { // proxy admin is already deployed when SL's L2SharedBridge is registered await deployer.deployTransparentProxyAdmin(create2Salt, { gasPrice }); @@ -89,15 +88,6 @@ export async function initialBridgehubDeployment( } else { await deployer.deployBlobVersionedHashRetriever(create2Salt, { gasPrice }); } -======= - await deployer.deployGovernance(create2Salt, { gasPrice, nonce }); - nonce++; - - await deployer.deployChainAdmin(create2Salt, { gasPrice, nonce }); - await deployer.deployTransparentProxyAdmin(create2Salt, { gasPrice }); - await deployer.deployBridgehubContract(create2Salt, gasPrice); - await deployer.deployBlobVersionedHashRetriever(create2Salt, { gasPrice }); ->>>>>>> 874bc6ba940de9d37b474d1e3dda2fe4e869dfbe await deployer.deployStateTransitionManagerContract(create2Salt, extraFacets, gasPrice); await deployer.setStateTransitionManagerInValidatorTimelock({ gasPrice }); } diff --git a/l1-contracts/src.ts/deploy.ts b/l1-contracts/src.ts/deploy.ts index e334bd0cd..afae0dbc2 100644 --- a/l1-contracts/src.ts/deploy.ts +++ b/l1-contracts/src.ts/deploy.ts @@ -63,7 +63,6 @@ import type { FacetCut } from "./diamondCut"; import { getCurrentFacetCutsForAdd } from "./diamondCut"; import { ChainAdminFactory, ERC20Factory, StateTransitionManagerFactory } from "../typechain"; -<<<<<<< HEAD import { IL1AssetRouterFactory } from "../typechain/IL1AssetRouterFactory"; import { IL1NativeTokenVaultFactory } from "../typechain/IL1NativeTokenVaultFactory"; @@ -73,9 +72,6 @@ import { TestnetERC20TokenFactory } from "../typechain/TestnetERC20TokenFactory" import { RollupL1DAValidatorFactory } from "../../da-contracts/typechain/RollupL1DAValidatorFactory"; import { ValidiumL1DAValidatorFactory } from "../../da-contracts/typechain/ValidiumL1DAValidatorFactory"; -======= -import type { Contract, Overrides } from "@ethersproject/contracts"; ->>>>>>> 874bc6ba940de9d37b474d1e3dda2fe4e869dfbe let L2_BOOTLOADER_BYTECODE_HASH: string; let L2_DEFAULT_ACCOUNT_BYTECODE_HASH: string; @@ -356,8 +352,6 @@ export class Deployer { } public async deployChainAdmin(create2Salt: string, ethTxOptions: ethers.providers.TransactionRequest) { -<<<<<<< HEAD -======= ethTxOptions.gasLimit ??= 10_000_000; const contractAddress = await this.deployViaCreate2( "ChainAdmin", @@ -371,32 +365,13 @@ export class Deployer { this.addresses.ChainAdmin = contractAddress; } - public async deployBridgehubImplementation(create2Salt: string, ethTxOptions: ethers.providers.TransactionRequest) { ->>>>>>> 874bc6ba940de9d37b474d1e3dda2fe4e869dfbe - ethTxOptions.gasLimit ??= 10_000_000; - const contractAddress = await this.deployViaCreate2("ChainAdmin", [this.ownerAddress], create2Salt, ethTxOptions); - if (this.verbose) { - console.log(`CONTRACTS_CHAIN_ADMIN_ADDR=${contractAddress}`); - } - this.addresses.ChainAdmin = contractAddress; - } - public async deployTransparentProxyAdmin(create2Salt: string, ethTxOptions: ethers.providers.TransactionRequest) { if (this.verbose) { console.log("Deploying Proxy Admin"); } // Note: we cannot deploy using Create2, as the owner of the ProxyAdmin is msg.sender -<<<<<<< HEAD let proxyAdmin; let rec; -======= - const contractFactory = await hardhat.ethers.getContractFactory( - "@openzeppelin/contracts-v4/proxy/transparent/ProxyAdmin.sol:ProxyAdmin", - { - signer: this.deployWallet, - } - ); ->>>>>>> 874bc6ba940de9d37b474d1e3dda2fe4e869dfbe if (this.isZkMode()) { // @ts-ignore @@ -1327,7 +1302,6 @@ export class Deployer { } } -<<<<<<< HEAD public async executeChainAdminMulticall(calls: ChainAdminCall[], requireSuccess: boolean = true) { const chainAdmin = ChainAdminFactory.connect(this.addresses.ChainAdmin, this.deployWallet); @@ -1335,7 +1309,7 @@ export class Deployer { const multicallTx = await chainAdmin.multicall(calls, requireSuccess, { value: totalValue }); return await multicallTx.wait(); -======= + } public async setTokenMultiplierSetterAddress(tokenMultiplierSetterAddress: string) { const chainAdmin = ChainAdminFactory.connect(this.addresses.ChainAdmin, this.deployWallet); @@ -1345,7 +1319,6 @@ export class Deployer { `Token multiplier setter set as ${tokenMultiplierSetterAddress}, gas used: ${receipt.gasUsed.toString()}` ); } ->>>>>>> 874bc6ba940de9d37b474d1e3dda2fe4e869dfbe } public async transferAdminFromDeployerToChainAdmin() { @@ -1358,7 +1331,6 @@ export class Deployer { console.log(`ChainAdmin set as pending admin, gas used: ${receipt.gasUsed.toString()}`); } -<<<<<<< HEAD // await this.executeUpgrade( // hyperchain.address, // 0, @@ -1374,21 +1346,6 @@ export class Deployer { data: acceptAdminData, }, ]); -======= - const acceptAdminData = hyperchain.interface.encodeFunctionData("acceptAdmin"); - const chainAdmin = ChainAdminFactory.connect(this.addresses.ChainAdmin, this.deployWallet); - const multicallTx = await chainAdmin.multicall( - [ - { - target: hyperchain.address, - value: 0, - data: acceptAdminData, - }, - ], - true - ); - await multicallTx.wait(); ->>>>>>> 874bc6ba940de9d37b474d1e3dda2fe4e869dfbe if (this.verbose) { console.log("Pending admin successfully accepted"); diff --git a/l2-contracts/contracts/L2ContractHelper.sol b/l2-contracts/contracts/L2ContractHelper.sol index edeec071e..97bf6b55e 100644 --- a/l2-contracts/contracts/L2ContractHelper.sol +++ b/l2-contracts/contracts/L2ContractHelper.sol @@ -5,7 +5,7 @@ pragma solidity ^0.8.20; import {EfficientCall} from "@matterlabs/zksync-contracts/l2/system-contracts/libraries/EfficientCall.sol"; import {IL2AssetRouter} from "./bridge/interfaces/IL2AssetRouter.sol"; import {IL2NativeTokenVault} from "./bridge/interfaces/IL2NativeTokenVault.sol"; -import {MalformedBytecode, BytecodeError} from "./L2ContractErrors.sol"; +import {MalformedBytecode, BytecodeError} from "./errors/L2ContractErrors.sol"; /** * @author Matter Labs diff --git a/l2-contracts/contracts/bridge/L2AssetRouter.sol b/l2-contracts/contracts/bridge/L2AssetRouter.sol index 20be7d767..d143517b1 100644 --- a/l2-contracts/contracts/bridge/L2AssetRouter.sol +++ b/l2-contracts/contracts/bridge/L2AssetRouter.sol @@ -16,7 +16,7 @@ import {AddressAliasHelper} from "../vendor/AddressAliasHelper.sol"; import {L2ContractHelper, L2_NATIVE_TOKEN_VAULT} from "../L2ContractHelper.sol"; import {DataEncoding} from "../common/libraries/DataEncoding.sol"; -import {EmptyAddress, InvalidCaller} from "../L2ContractErrors.sol"; +import {EmptyAddress, InvalidCaller} from "../errors/L2ContractErrors.sol"; /// @author Matter Labs /// @custom:security-contact security@matterlabs.dev diff --git a/l2-contracts/contracts/bridge/L2NativeTokenVault.sol b/l2-contracts/contracts/bridge/L2NativeTokenVault.sol index 1cab56ae3..56c50d9a8 100644 --- a/l2-contracts/contracts/bridge/L2NativeTokenVault.sol +++ b/l2-contracts/contracts/bridge/L2NativeTokenVault.sol @@ -14,7 +14,7 @@ import {L2ContractHelper, DEPLOYER_SYSTEM_CONTRACT, L2_ASSET_ROUTER, IContractDe import {SystemContractsCaller} from "../SystemContractsCaller.sol"; import {DataEncoding} from "../common/libraries/DataEncoding.sol"; -import {EmptyAddress, EmptyBytes32, AddressMismatch, AssetIdMismatch, DeployFailed, AmountMustBeGreaterThanZero, InvalidCaller} from "../L2ContractErrors.sol"; +import {EmptyAddress, EmptyBytes32, AddressMismatch, AssetIdMismatch, DeployFailed, AmountMustBeGreaterThanZero, InvalidCaller} from "../errors/L2ContractErrors.sol"; /// @author Matter Labs /// @custom:security-contact security@matterlabs.dev diff --git a/l2-contracts/contracts/bridge/L2StandardERC20.sol b/l2-contracts/contracts/bridge/L2StandardERC20.sol index 60f68dde4..df81f542e 100644 --- a/l2-contracts/contracts/bridge/L2StandardERC20.sol +++ b/l2-contracts/contracts/bridge/L2StandardERC20.sol @@ -147,25 +147,6 @@ contract L2StandardERC20 is ERC20PermitUpgradeable, IL2StandardToken, ERC1967Upg emit BridgeInitialize(l1Address, _newName, _newSymbol, decimals_); } -<<<<<<< HEAD -======= - modifier onlyBridge() { - if (msg.sender != l2Bridge) { - revert Unauthorized(msg.sender); - } - _; - } - - modifier onlyNextVersion(uint8 _version) { - // The version should be incremented by 1. Otherwise, the governor risks disabling - // future reinitialization of the token by providing too large a version. - if (_version != _getInitializedVersion() + 1) { - revert NonSequentialVersion(); - } - _; - } - ->>>>>>> 874bc6ba940de9d37b474d1e3dda2fe4e869dfbe /// @dev Mint tokens to a given account. /// @param _to The account that will receive the created tokens. /// @param _amount The amount that will be created. diff --git a/l2-contracts/contracts/bridge/L2WrappedBaseToken.sol b/l2-contracts/contracts/bridge/L2WrappedBaseToken.sol index f8566f01d..f7bda32b5 100644 --- a/l2-contracts/contracts/bridge/L2WrappedBaseToken.sol +++ b/l2-contracts/contracts/bridge/L2WrappedBaseToken.sol @@ -80,16 +80,6 @@ contract L2WrappedBaseToken is ERC20PermitUpgradeable, IL2WrappedBaseToken, IL2S emit Initialize(name_, symbol_, 18); } -<<<<<<< HEAD -======= - modifier onlyBridge() { - if (msg.sender != l2Bridge) { - revert Unauthorized(msg.sender); - } - _; - } - ->>>>>>> 874bc6ba940de9d37b474d1e3dda2fe4e869dfbe /// @notice Function for minting tokens on L2, implemented only to be compatible with IL2StandardToken interface. /// Always reverts instead of minting anything! /// Note: Use `deposit`/`depositTo` methods instead. diff --git a/l2-contracts/contracts/errors/L2ContractErrors.sol b/l2-contracts/contracts/errors/L2ContractErrors.sol index 918d52d1d..c7d5deffe 100644 --- a/l2-contracts/contracts/errors/L2ContractErrors.sol +++ b/l2-contracts/contracts/errors/L2ContractErrors.sol @@ -4,11 +4,8 @@ pragma solidity ^0.8.20; // 0x1f73225f error AddressMismatch(address expected, address supplied); -<<<<<<< HEAD:l2-contracts/contracts/L2ContractErrors.sol error AssetIdMismatch(bytes32 expected, bytes32 supplied); -======= // 0x5e85ae73 ->>>>>>> 874bc6ba940de9d37b474d1e3dda2fe4e869dfbe:l2-contracts/contracts/errors/L2ContractErrors.sol error AmountMustBeGreaterThanZero(); // 0xb4f54111 error DeployFailed(); @@ -36,7 +33,6 @@ error UnimplementedMessage(string message); error UnsupportedPaymasterFlow(); // 0x750b219c error WithdrawFailed(); -<<<<<<< HEAD:l2-contracts/contracts/L2ContractErrors.sol error MalformedBytecode(BytecodeError); enum BytecodeError { @@ -46,9 +42,7 @@ enum BytecodeError { WordsMustBeOdd, DictionaryLength } -======= // 0xd92e233d error ZeroAddress(); ->>>>>>> 874bc6ba940de9d37b474d1e3dda2fe4e869dfbe:l2-contracts/contracts/errors/L2ContractErrors.sol string constant BRIDGE_MINT_NOT_IMPLEMENTED = "bridgeMint is not implemented! Use deposit/depositTo methods instead."; diff --git a/l2-contracts/test/erc20.test.ts b/l2-contracts/test/erc20.test.ts index e7ce20449..ec531f7aa 100644 --- a/l2-contracts/test/erc20.test.ts +++ b/l2-contracts/test/erc20.test.ts @@ -54,7 +54,6 @@ describe("ERC20Bridge", function () { // While we formally don't need to deploy the token and the beacon proxy, it is a neat way to have the bytecode published const l2TokenImplAddress = await deployer.deploy(await deployer.loadArtifact("L2StandardERC20")); -<<<<<<< HEAD const l2Erc20TokenBeacon = await deployer.deploy(await deployer.loadArtifact("UpgradeableBeacon"), [ l2TokenImplAddress.address, ]); @@ -71,19 +70,6 @@ describe("ERC20Bridge", function () { (await deployer.loadArtifact("L2AssetRouter")).bytecode, true, constructorArgs -======= - const l2Erc20TokenBeacon = await deployer.deploy( - await deployer.loadArtifact("@openzeppelin/contracts-v4/proxy/beacon/UpgradeableBeacon.sol:UpgradeableBeacon"), - [l2TokenImplAddress.address] - ); - await deployer.deploy( - await deployer.loadArtifact("@openzeppelin/contracts-v4/proxy/beacon/BeaconProxy.sol:BeaconProxy"), - [l2Erc20TokenBeacon.address, "0x"] - ); - - const beaconProxyBytecodeHash = hashBytecode( - (await deployer.loadArtifact("@openzeppelin/contracts-v4/proxy/beacon/BeaconProxy.sol:BeaconProxy")).bytecode ->>>>>>> 874bc6ba940de9d37b474d1e3dda2fe4e869dfbe ); erc20Bridge = L2AssetRouterFactory.connect(L2_ASSET_ROUTER_ADDRESS, deployerWallet); @@ -101,20 +87,9 @@ describe("ERC20Bridge", function () { constructorArgs ); -<<<<<<< HEAD erc20NativeTokenVault = L2NativeTokenVaultFactory.connect(L2_NATIVE_TOKEN_VAULT_ADDRESS, l1BridgeWallet); const governorNTV = L2NativeTokenVaultFactory.connect(L2_NATIVE_TOKEN_VAULT_ADDRESS, governorWallet); await governorNTV.configureL2TokenBeacon(false, ethers.constants.AddressZero); -======= - const erc20BridgeProxy = await deployer.deploy( - await deployer.loadArtifact( - "@openzeppelin/contracts-v4/proxy/transparent/TransparentUpgradeableProxy.sol:TransparentUpgradeableProxy" - ), - [erc20BridgeImpl.address, governorWallet.address, bridgeInitializeData] - ); - - erc20Bridge = L2SharedBridgeFactory.connect(erc20BridgeProxy.address, deployerWallet); ->>>>>>> 874bc6ba940de9d37b474d1e3dda2fe4e869dfbe }); it("Should finalize deposit ERC20 deposit", async function () { diff --git a/system-contracts/SystemContractsHashes.json b/system-contracts/SystemContractsHashes.json index 76598528b..abf7c87f1 100644 --- a/system-contracts/SystemContractsHashes.json +++ b/system-contracts/SystemContractsHashes.json @@ -3,85 +3,50 @@ "contractName": "AccountCodeStorage", "bytecodePath": "artifacts-zk/contracts-preprocessed/AccountCodeStorage.sol/AccountCodeStorage.json", "sourceCodePath": "contracts-preprocessed/AccountCodeStorage.sol", -<<<<<<< HEAD "bytecodeHash": "0x0100005d1aca0cbed0567a1c7e07a1da321386b1665e440da22053addd4639e1", "sourceCodeHash": "0xea3806fcaf7728463f559fe195d8acdc47a7659d58119e0a51efcf86a691b61b" -======= - "bytecodeHash": "0x0100005d05a277543946914759aa4a6c403604b828f80d00b900c669c3d224e1", - "sourceCodeHash": "0x2e0e09d57a04bd1e722d8bf8c6423fdf3f8bca44e5e8c4f6684f987794be066e" ->>>>>>> 874bc6ba940de9d37b474d1e3dda2fe4e869dfbe }, { "contractName": "BootloaderUtilities", "bytecodePath": "artifacts-zk/contracts-preprocessed/BootloaderUtilities.sol/BootloaderUtilities.json", "sourceCodePath": "contracts-preprocessed/BootloaderUtilities.sol", -<<<<<<< HEAD "bytecodeHash": "0x010007c7149301f5f29fa1757a600b5088354d9e280a6d8f69bcce4f2dbce660", "sourceCodeHash": "0x9d2b7376c4cd9b143ddd5dfe001a9faae99b9125ccd45f2915c3ce0099643ed9" -======= - "bytecodeHash": "0x010007c7bb63f64649098bf75f4baa588db20f445b4d20b7cca972d5d8f973ce", - "sourceCodeHash": "0x0f1213c4b95acb71f4ab5d4082cc1aeb2bd5017e1cccd46afc66e53268609d85" ->>>>>>> 874bc6ba940de9d37b474d1e3dda2fe4e869dfbe }, { "contractName": "ComplexUpgrader", "bytecodePath": "artifacts-zk/contracts-preprocessed/ComplexUpgrader.sol/ComplexUpgrader.json", "sourceCodePath": "contracts-preprocessed/ComplexUpgrader.sol", -<<<<<<< HEAD "bytecodeHash": "0x0100004deb11ca32277ab54e2d036f4d33b4a7e218ced1fee63b5b4713ff50ff", "sourceCodeHash": "0xdde7c49a94cc3cd34c3e7ced1b5ba45e4740df68d26243871edbe393e7298f7a" -======= - "bytecodeHash": "0x0100004da9f3aa5e4febcc53522cb7ee6949369fde25dd79e977752b82b9fd5d", - "sourceCodeHash": "0x796046a914fb676ba2bbd337b2924311ee2177ce54571c18a2c3945755c83614" ->>>>>>> 874bc6ba940de9d37b474d1e3dda2fe4e869dfbe }, { "contractName": "Compressor", "bytecodePath": "artifacts-zk/contracts-preprocessed/Compressor.sol/Compressor.json", "sourceCodePath": "contracts-preprocessed/Compressor.sol", -<<<<<<< HEAD "bytecodeHash": "0x0100013fc989177bcb6ec29d851cd01f845de31963ea5817ea7a684767c36368", "sourceCodeHash": "0xb0cec0016f481ce023478f71727fbc0d82e967ddc0508e4d47f5c52292a3f790" -======= - "bytecodeHash": "0x0100014fb4f05ae09288cbcf4fa7a09ca456910f6e69be5ac2c2dfc8d71d1576", - "sourceCodeHash": "0xc6f7cd8b21aae52ed3dd5083c09b438a7af142a4ecda6067c586770e8be745a5" ->>>>>>> 874bc6ba940de9d37b474d1e3dda2fe4e869dfbe }, { "contractName": "ContractDeployer", "bytecodePath": "artifacts-zk/contracts-preprocessed/ContractDeployer.sol/ContractDeployer.json", "sourceCodePath": "contracts-preprocessed/ContractDeployer.sol", -<<<<<<< HEAD "bytecodeHash": "0x010004e5711fff19f0048d745b0177b8b73952963b6de79ff4e16c902dbcc091", "sourceCodeHash": "0xea9627fd5e6e905c268ba801e87bf2d9022bea036982d2b54425f2388b27e6b1" -======= - "bytecodeHash": "0x010004e5d52d692822d5c54ac87de3297f39be0e4a6f72f2830ae5ac856684ee", - "sourceCodeHash": "0x82f81fbf5fb007a9cac97462d50907ca5d7a1af62d82d2645e093ed8647a5209" ->>>>>>> 874bc6ba940de9d37b474d1e3dda2fe4e869dfbe }, { "contractName": "Create2Factory", "bytecodePath": "artifacts-zk/contracts-preprocessed/Create2Factory.sol/Create2Factory.json", "sourceCodePath": "contracts-preprocessed/Create2Factory.sol", -<<<<<<< HEAD "bytecodeHash": "0x0100004937dba13ac3e393def7fe6cf01da88bbe9b087c397e950301fe14377d", "sourceCodeHash": "0x217e65f55c8add77982171da65e0db8cc10141ba75159af582973b332a4e098a" -======= - "bytecodeHash": "0x010000495bd172e90725e6bfafe73e36a288d616d4673f5347eeb819a78bf546", - "sourceCodeHash": "0x114d9322a9ca654989f3e0b3b21f1311dbc4db84f443d054cd414f6414d84de3" ->>>>>>> 874bc6ba940de9d37b474d1e3dda2fe4e869dfbe }, { "contractName": "DefaultAccount", "bytecodePath": "artifacts-zk/contracts-preprocessed/DefaultAccount.sol/DefaultAccount.json", "sourceCodePath": "contracts-preprocessed/DefaultAccount.sol", -<<<<<<< HEAD "bytecodeHash": "0x0100055d7adab6efac115df578d88bc113738dc6ad811329c7575c2af3d91756", "sourceCodeHash": "0xeb5ac8fc83e1c8619db058a9b6973958bd6ed1b6f4938f8f4541d702f12e085d" -======= - "bytecodeHash": "0x0100055dba11508480be023137563caec69debc85f826cb3a4b68246a7cabe30", - "sourceCodeHash": "0xebffe840ebbd9329edb1ebff8ca50f6935e7dabcc67194a896fcc2e968d46dfb" ->>>>>>> 874bc6ba940de9d37b474d1e3dda2fe4e869dfbe }, { "contractName": "EmptyContract", @@ -94,49 +59,29 @@ "contractName": "ImmutableSimulator", "bytecodePath": "artifacts-zk/contracts-preprocessed/ImmutableSimulator.sol/ImmutableSimulator.json", "sourceCodePath": "contracts-preprocessed/ImmutableSimulator.sol", -<<<<<<< HEAD "bytecodeHash": "0x0100003bf60f81cb3074170af6420e8d74b710fea0b1fa04e291a080ec17f98a", "sourceCodeHash": "0x4212e99cbc1722887cfb5b4cb967f278ac8642834786f0e3c6f3b324a9316815" -======= - "bytecodeHash": "0x01000039785a8e0d342a49b6b6c6e156801b816434d93bee85d33f56d56b4f9a", - "sourceCodeHash": "0x9659e69f7db09e8f60a8bb95314b1ed26afcc689851665cf27f5408122f60c98" ->>>>>>> 874bc6ba940de9d37b474d1e3dda2fe4e869dfbe }, { "contractName": "KnownCodesStorage", "bytecodePath": "artifacts-zk/contracts-preprocessed/KnownCodesStorage.sol/KnownCodesStorage.json", "sourceCodePath": "contracts-preprocessed/KnownCodesStorage.sol", -<<<<<<< HEAD "bytecodeHash": "0x0100006f5cf65a28234e0791927389664cced66dd3d600aefbe120d63e9debae", "sourceCodeHash": "0x8da495a9fc5aa0d7d20a165a4fc8bc77012bec29c472015ea5ecc0a2bd706137" -======= - "bytecodeHash": "0x0100006f0f209c9e6d06b1327db1257b15fa7a8b9864ee5ccd12cd3f8bc40ac9", - "sourceCodeHash": "0xb39b5b81168653e0c5062f7b8e1d6d15a4e186df3317f192f0cb2fc3a74f5448" ->>>>>>> 874bc6ba940de9d37b474d1e3dda2fe4e869dfbe }, { "contractName": "L1Messenger", "bytecodePath": "artifacts-zk/contracts-preprocessed/L1Messenger.sol/L1Messenger.json", "sourceCodePath": "contracts-preprocessed/L1Messenger.sol", -<<<<<<< HEAD "bytecodeHash": "0x010001e9765b885f7e6422722e36e6d375beffc916047f5cec419d11d178baea", "sourceCodeHash": "0xd83b345b8633affb0bba2296fec9424b4fe9483b60c20ca407d755857a385d8e" -======= - "bytecodeHash": "0x010002955993e8ff8190e388e94a6bb791fbe9c6388e5011c52cb587a4ebf05e", - "sourceCodeHash": "0xa8768fdaac6d8804782f14e2a51bbe2b6be31dee9103b6d02d149ea8dc46eb6a" ->>>>>>> 874bc6ba940de9d37b474d1e3dda2fe4e869dfbe }, { "contractName": "L2BaseToken", "bytecodePath": "artifacts-zk/contracts-preprocessed/L2BaseToken.sol/L2BaseToken.json", "sourceCodePath": "contracts-preprocessed/L2BaseToken.sol", -<<<<<<< HEAD "bytecodeHash": "0x0100010517992363aa510731a717db3c7740d1c31e69718090d07f73d47ba960", "sourceCodeHash": "0x4cdafafd4cfdf410b31641e14487ea657be3af25e5ec1754fcd7ad67ec23d8be" -======= - "bytecodeHash": "0x01000103174a90beadc2cffe3e81bdb7b8a576174e888809c1953175fd3015b4", - "sourceCodeHash": "0x8bdd2b4d0b53dba84c9f0af250bbaa2aad10b3de6747bba957f0bd3721090dfa" ->>>>>>> 874bc6ba940de9d37b474d1e3dda2fe4e869dfbe }, { "contractName": "L2GenesisUpgrade", @@ -149,49 +94,29 @@ "contractName": "MsgValueSimulator", "bytecodePath": "artifacts-zk/contracts-preprocessed/MsgValueSimulator.sol/MsgValueSimulator.json", "sourceCodePath": "contracts-preprocessed/MsgValueSimulator.sol", -<<<<<<< HEAD "bytecodeHash": "0x0100005d3182a51477d7ee3488aafab351bb5f0560412e4df7e5a5e21ca87cd5", "sourceCodeHash": "0x4834adf62dbaefa1a1c15d36b5ad1bf2826e7d888a17be495f7ed4e4ea381aa8" -======= - "bytecodeHash": "0x0100005da36075675b98f85fe90df11c1d526f6b12925da3a55a8b9c02aaac5f", - "sourceCodeHash": "0x082f3dcbc2fe4d93706c86aae85faa683387097d1b676e7ebd00f71ee0f13b71" ->>>>>>> 874bc6ba940de9d37b474d1e3dda2fe4e869dfbe }, { "contractName": "NonceHolder", "bytecodePath": "artifacts-zk/contracts-preprocessed/NonceHolder.sol/NonceHolder.json", "sourceCodePath": "contracts-preprocessed/NonceHolder.sol", -<<<<<<< HEAD "bytecodeHash": "0x010000db9e6c4608ca06cd3a69484b23f5e4ee196fa046cb2db8c0b56d3a2163", "sourceCodeHash": "0xaa2ed3a26af30032c00a612ac327e0cdf5288b7c932ae903462355f863f950cb" -======= - "bytecodeHash": "0x010000d97de8c14cd36b1ce06cd7f44a09f6093ec8eb4041629c0fc2116d0c73", - "sourceCodeHash": "0xcd0c0366effebf2c98c58cf96322cc242a2d1c675620ef5514b7ed1f0a869edc" ->>>>>>> 874bc6ba940de9d37b474d1e3dda2fe4e869dfbe }, { "contractName": "PubdataChunkPublisher", "bytecodePath": "artifacts-zk/contracts-preprocessed/PubdataChunkPublisher.sol/PubdataChunkPublisher.json", "sourceCodePath": "contracts-preprocessed/PubdataChunkPublisher.sol", -<<<<<<< HEAD "bytecodeHash": "0x01000049825a39e7057700666867a2b2be806c15d9b2addb60d335bb61b405d9", "sourceCodeHash": "0x0da0d1279f906147a40e278f52bf3e4d5d4f24225935e4611cc04f4b387b5286" -======= - "bytecodeHash": "0x010000470e396f376539289b7975b6866914a8a0994008a02987edac8be81db7", - "sourceCodeHash": "0xd7161e2c8092cf57b43c6220bc605c0e7e540bddcde1af24e2d90f75633b098e" ->>>>>>> 874bc6ba940de9d37b474d1e3dda2fe4e869dfbe }, { "contractName": "SystemContext", "bytecodePath": "artifacts-zk/contracts-preprocessed/SystemContext.sol/SystemContext.json", "sourceCodePath": "contracts-preprocessed/SystemContext.sol", -<<<<<<< HEAD "bytecodeHash": "0x010001a722bf92cd15264a662582a6806db24101493ad7a1f202428a2a10e7bc", "sourceCodeHash": "0x532a962209042f948e8a13e3f4cf12b6d53631e0fc5fa53083c7e2d8062771c0" -======= - "bytecodeHash": "0x010001a5eabf9e28288b7ab7e1db316148021347460cfb4314570956867d5af5", - "sourceCodeHash": "0xf308743981ef5cea2f7a3332b8e51695a5e47e811a63974437fc1cceee475e7a" ->>>>>>> 874bc6ba940de9d37b474d1e3dda2fe4e869dfbe }, { "contractName": "EventWriter", @@ -260,60 +185,35 @@ "contractName": "bootloader_test", "bytecodePath": "bootloader/build/artifacts/bootloader_test.yul.zbin", "sourceCodePath": "bootloader/build/bootloader_test.yul", -<<<<<<< HEAD "bytecodeHash": "0x010003cb983fb1cded326ff4429b8a637a7b045233c427dc498373be58312969", "sourceCodeHash": "0xa4f83a28bcc3d3a79c197a77de03dce464b2141c3aaf970ad3f3487f41ae5690" -======= - "bytecodeHash": "0x010003cbe67434b2848054322cbc311385851bbfbf70d17f92cd5f1f9836b25e", - "sourceCodeHash": "0x006fdf461899dec5fdb34301c23e6819eb93e275907cbfc67d73fccfb47cae68" ->>>>>>> 874bc6ba940de9d37b474d1e3dda2fe4e869dfbe }, { "contractName": "fee_estimate", "bytecodePath": "bootloader/build/artifacts/fee_estimate.yul.zbin", "sourceCodePath": "bootloader/build/fee_estimate.yul", -<<<<<<< HEAD "bytecodeHash": "0x010009559ef1268a8b83687828c5dfc804c58a018028694fc931df712fb67f58", "sourceCodeHash": "0xe7970c1738f2817b50bfcd16038227c5c059f12309407977df453bc6d365d31e" -======= - "bytecodeHash": "0x0100092d9b8ae575bca569f68fe60fbef1dc7d4aad6aa02cc4836f56afcf0a38", - "sourceCodeHash": "0x8a858319bac2924a3dee778218a7fe5e23898db0d87b02d7b783f94c5a02d257" ->>>>>>> 874bc6ba940de9d37b474d1e3dda2fe4e869dfbe }, { "contractName": "gas_test", "bytecodePath": "bootloader/build/artifacts/gas_test.yul.zbin", "sourceCodePath": "bootloader/build/gas_test.yul", -<<<<<<< HEAD "bytecodeHash": "0x010008db4c71130d6a96d77f2d3bf3573a5cddc72ecee030ecc9c3bc22764039", "sourceCodeHash": "0x1b6ef61d0dbbbaa049946b95dc6d12d9335baed03b8c3364a0cbdb404495c045" -======= - "bytecodeHash": "0x010008b3e1b8bdd393f2f8d6f5c994c8b9c287df7310dee75d0c52a245fc7cb1", - "sourceCodeHash": "0x89f5ad470f10e755fa57b82507518e571c24409a328bc33aeba26e9518ad1c3e" ->>>>>>> 874bc6ba940de9d37b474d1e3dda2fe4e869dfbe }, { "contractName": "playground_batch", "bytecodePath": "bootloader/build/artifacts/playground_batch.yul.zbin", "sourceCodePath": "bootloader/build/playground_batch.yul", -<<<<<<< HEAD "bytecodeHash": "0x0100095b01a95cb5caa633702442aeeac8ea64bf4dab31d534e0bb815b5c2820", "sourceCodeHash": "0xb5a2f9d7d8990f9a1296f699573a5dffe3a1d2ed53d9d3c60b313cd9443221ab" -======= - "bytecodeHash": "0x01000933061c23d700f3f647c45068e22f5506ff33bb516ac13f11069b163986", - "sourceCodeHash": "0x769448c4fd2b65c43d758ca5f34dd29d9b9dd3000fd0ec89cffcaf8d365a64fd" ->>>>>>> 874bc6ba940de9d37b474d1e3dda2fe4e869dfbe }, { "contractName": "proved_batch", "bytecodePath": "bootloader/build/artifacts/proved_batch.yul.zbin", "sourceCodePath": "bootloader/build/proved_batch.yul", -<<<<<<< HEAD "bytecodeHash": "0x010008eba57f69c88344eced34109c75d34b4bf84db66f47b47b4579b930355e", "sourceCodeHash": "0xb7697ee1c00b1b8af52c166e3a6deb862281616ca8861ab3aa0b51e34aed8715" -======= - "bytecodeHash": "0x010008c3be57ae5800e077b6c2056d9d75ad1a7b4f0ce583407961cc6fe0b678", - "sourceCodeHash": "0x908bc6ddb34ef89b125e9637239a1149deacacd91255781d82a65a542a39036e" ->>>>>>> 874bc6ba940de9d37b474d1e3dda2fe4e869dfbe } ] diff --git a/system-contracts/contracts/L1Messenger.sol b/system-contracts/contracts/L1Messenger.sol index f3deb0fa2..d79b129f7 100644 --- a/system-contracts/contracts/L1Messenger.sol +++ b/system-contracts/contracts/L1Messenger.sol @@ -2,14 +2,9 @@ pragma solidity 0.8.24; -<<<<<<< HEAD import {IL1Messenger, L2ToL1Log, L2_L1_LOGS_TREE_DEFAULT_LEAF_HASH, L2_TO_L1_LOG_SERIALIZE_SIZE} from "./interfaces/IL1Messenger.sol"; -import {ISystemContract} from "./interfaces/ISystemContract.sol"; -======= -import {IL1Messenger, L2ToL1Log, L2_L1_LOGS_TREE_DEFAULT_LEAF_HASH, L2_TO_L1_LOG_SERIALIZE_SIZE, STATE_DIFF_COMPRESSION_VERSION_NUMBER} from "./interfaces/IL1Messenger.sol"; import {SystemContractBase} from "./abstract/SystemContractBase.sol"; ->>>>>>> 874bc6ba940de9d37b474d1e3dda2fe4e869dfbe import {SystemContractHelper} from "./libraries/SystemContractHelper.sol"; import {EfficientCall} from "./libraries/EfficientCall.sol"; import {Utils} from "./libraries/Utils.sol"; @@ -291,7 +286,6 @@ contract L1Messenger is IL1Messenger, SystemContractBase { } bytes32 localLogsRootHash = l2ToL1LogsTreeArray[0]; -<<<<<<< HEAD bytes32 aggregatedRootHash = L2_MESSAGE_ROOT.getAggregatedRoot(); bytes32 fullRootHash = keccak256(bytes.concat(localLogsRootHash, aggregatedRootHash)); @@ -310,43 +304,6 @@ contract L1Messenger is IL1Messenger, SystemContractBase { }); l2DAValidatorOutputhash = abi.decode(returnData, (bytes32)); -======= - /// Check messages - uint32 numberOfMessages = uint32(bytes4(_totalL2ToL1PubdataAndStateDiffs[calldataPtr:calldataPtr + 4])); - calldataPtr += 4; - bytes32 reconstructedChainedMessagesHash = bytes32(0); - for (uint256 i = 0; i < numberOfMessages; ++i) { - uint32 currentMessageLength = uint32(bytes4(_totalL2ToL1PubdataAndStateDiffs[calldataPtr:calldataPtr + 4])); - calldataPtr += 4; - bytes32 hashedMessage = EfficientCall.keccak( - _totalL2ToL1PubdataAndStateDiffs[calldataPtr:calldataPtr + currentMessageLength] - ); - calldataPtr += currentMessageLength; - reconstructedChainedMessagesHash = keccak256(abi.encode(reconstructedChainedMessagesHash, hashedMessage)); - } - if (reconstructedChainedMessagesHash != chainedMessagesHash) { - revert ReconstructionMismatch(PubdataField.MsgHash, chainedMessagesHash, reconstructedChainedMessagesHash); - } - - /// Check bytecodes - uint32 numberOfBytecodes = uint32(bytes4(_totalL2ToL1PubdataAndStateDiffs[calldataPtr:calldataPtr + 4])); - calldataPtr += 4; - bytes32 reconstructedChainedL1BytecodesRevealDataHash = bytes32(0); - for (uint256 i = 0; i < numberOfBytecodes; ++i) { - uint32 currentBytecodeLength = uint32( - bytes4(_totalL2ToL1PubdataAndStateDiffs[calldataPtr:calldataPtr + 4]) - ); - calldataPtr += 4; - reconstructedChainedL1BytecodesRevealDataHash = keccak256( - abi.encode( - reconstructedChainedL1BytecodesRevealDataHash, - Utils.hashL2Bytecode( - _totalL2ToL1PubdataAndStateDiffs[calldataPtr:calldataPtr + currentBytecodeLength] - ) - ) - ); - calldataPtr += currentBytecodeLength; ->>>>>>> 874bc6ba940de9d37b474d1e3dda2fe4e869dfbe } /// Native (VM) L2 to L1 log diff --git a/system-contracts/contracts/PubdataChunkPublisher.sol b/system-contracts/contracts/PubdataChunkPublisher.sol index 535667d68..2f48a9626 100644 --- a/system-contracts/contracts/PubdataChunkPublisher.sol +++ b/system-contracts/contracts/PubdataChunkPublisher.sol @@ -2,14 +2,7 @@ pragma solidity 0.8.24; import {IPubdataChunkPublisher} from "./interfaces/IPubdataChunkPublisher.sol"; -<<<<<<< HEAD -import {ISystemContract} from "./interfaces/ISystemContract.sol"; import {BLOB_SIZE_BYTES, MAX_NUMBER_OF_BLOBS} from "./Constants.sol"; -======= -import {SystemContractBase} from "./abstract/SystemContractBase.sol"; -import {L1_MESSENGER_CONTRACT, BLOB_SIZE_BYTES, MAX_NUMBER_OF_BLOBS, SystemLogKey} from "./Constants.sol"; -import {SystemContractHelper} from "./libraries/SystemContractHelper.sol"; ->>>>>>> 874bc6ba940de9d37b474d1e3dda2fe4e869dfbe import {TooMuchPubdata} from "./SystemContractErrors.sol"; /** diff --git a/system-contracts/contracts/interfaces/IComplexUpgrader.sol b/system-contracts/contracts/interfaces/IComplexUpgrader.sol index 6e3624b89..3b1468417 100644 --- a/system-contracts/contracts/interfaces/IComplexUpgrader.sol +++ b/system-contracts/contracts/interfaces/IComplexUpgrader.sol @@ -1,9 +1,5 @@ // SPDX-License-Identifier: MIT -<<<<<<< HEAD -// We use a floating point pragma here so it can be used within other projects that interact with the zkSync ecosystem without using our exact pragma version. -======= // We use a floating point pragma here so it can be used within other projects that interact with the ZKsync ecosystem without using our exact pragma version. ->>>>>>> 874bc6ba940de9d37b474d1e3dda2fe4e869dfbe pragma solidity ^0.8.20; /** diff --git a/system-contracts/package.json b/system-contracts/package.json index 20d4aed40..70e7208b7 100644 --- a/system-contracts/package.json +++ b/system-contracts/package.json @@ -63,12 +63,8 @@ "deploy-preimages": "ts-node scripts/deploy-preimages.ts", "copy:typechain": "mkdir -p ../l2-contracts/typechain && cp ./typechain/ContractDeployerFactory.ts ../l2-contracts/typechain/", "preprocess:bootloader": "rm -rf ./bootloader/build && yarn ts-node scripts/preprocess-bootloader.ts", -<<<<<<< HEAD - "preprocess:system-contracts": "ts-node scripts/preprocess-system-contracts.ts", -======= "preprocess:system-contracts": "rm -rf ./contracts-preprocessed && ts-node scripts/preprocess-system-contracts.ts", "verify-on-explorer": "hardhat run scripts/verify-on-explorer.ts", ->>>>>>> 874bc6ba940de9d37b474d1e3dda2fe4e869dfbe "test": "yarn build:test-system-contracts && hardhat test --network zkSyncTestNode", "test-node": "hardhat node-zksync --tag v0.0.1-vm1.5.0", "test:bootloader": "cd bootloader/test_infra && cargo run" diff --git a/system-contracts/scripts/utils.ts b/system-contracts/scripts/utils.ts index 93ce6f515..8f63cd004 100644 --- a/system-contracts/scripts/utils.ts +++ b/system-contracts/scripts/utils.ts @@ -16,6 +16,7 @@ import path from "path"; import { spawn as _spawn } from "child_process"; import { createHash } from "crypto"; import { CompilerDownloader } from "hardhat/internal/solidity/compiler/downloader"; +import fetch from 'node-fetch'; export type HttpMethod = "POST" | "GET"; @@ -326,8 +327,6 @@ export async function isFolderEmpty(folderPath: string): Promise { } catch (error) { console.error("No target folder with artifacts."); return true; // Return true if an error, as folder doesn't exist. -<<<<<<< HEAD -======= } } /** @@ -371,6 +370,5 @@ export async function query( error: "Could not decode JSON in response", status: `${response.status} ${response.statusText}`, }; ->>>>>>> 874bc6ba940de9d37b474d1e3dda2fe4e869dfbe } } diff --git a/yarn.lock b/yarn.lock index a353889cb..c550f2d1e 100644 --- a/yarn.lock +++ b/yarn.lock @@ -763,7 +763,6 @@ sinon-chai "^3.7.0" undici "^5.14.0" -<<<<<<< HEAD "@matterlabs/hardhat-zksync-solc@^0.3.15": version "0.3.17" resolved "https://registry.yarnpkg.com/@matterlabs/hardhat-zksync-solc/-/hardhat-zksync-solc-0.3.17.tgz#72f199544dc89b268d7bfc06d022a311042752fd" @@ -773,8 +772,6 @@ chalk "4.1.2" dockerode "^3.3.4" -======= ->>>>>>> 874bc6ba940de9d37b474d1e3dda2fe4e869dfbe "@matterlabs/hardhat-zksync-verify@0.6.1": version "0.6.1" resolved "https://registry.yarnpkg.com/@matterlabs/hardhat-zksync-verify/-/hardhat-zksync-verify-0.6.1.tgz#3fd83f4177ac0b138656ed93d4756ec27f1d329d" @@ -1219,12 +1216,12 @@ resolved "https://registry.yarnpkg.com/@nomiclabs/hardhat-waffle/-/hardhat-waffle-2.0.6.tgz#d11cb063a5f61a77806053e54009c40ddee49a54" integrity sha512-+Wz0hwmJGSI17B+BhU/qFRZ1l6/xMW82QGXE/Gi+WTmwgJrQefuBs1lIf7hzQ1hLk6hpkvb/zwcNkpVKRYTQYg== -"@openzeppelin/contracts-upgradeable-v4@npm:@openzeppelin/contracts-upgradeable@4.9.5": +"@openzeppelin/contracts-upgradeable-v4@npm:@openzeppelin/contracts-upgradeable@4.9.5", "@openzeppelin/contracts-upgradeable@4.9.5": version "4.9.5" resolved "https://registry.yarnpkg.com/@openzeppelin/contracts-upgradeable/-/contracts-upgradeable-4.9.5.tgz#572b5da102fc9be1d73f34968e0ca56765969812" integrity sha512-f7L1//4sLlflAN7fVzJLoRedrf5Na3Oal5PZfIq55NFcVZ90EpV1q5xOvL4lFvg3MNICSDr2hH0JUBxwlxcoPg== -"@openzeppelin/contracts-v4@npm:@openzeppelin/contracts@4.9.5": +"@openzeppelin/contracts-v4@npm:@openzeppelin/contracts@4.9.5", "@openzeppelin/contracts@4.9.5": version "4.9.5" resolved "https://registry.yarnpkg.com/@openzeppelin/contracts/-/contracts-4.9.5.tgz#1eed23d4844c861a1835b5d33507c1017fa98de8" integrity sha512-ZK+W5mVhRppff9BE6YdR8CC52C8zAvsVAiWhEtQ5+oNxFE6h1WdeWo+FJSF8KKvtxxVYZ7MTP/5KoVpAU3aSWg== @@ -4444,9 +4441,6 @@ hardhat@=2.22.2: uuid "^8.3.2" ws "^7.4.6" -<<<<<<< HEAD -hardhat@^2.14.0, hardhat@^2.19.4: -======= hardhat@^2.14.0: version "2.22.7" resolved "https://registry.yarnpkg.com/hardhat/-/hardhat-2.22.7.tgz#3de0ce5074063cf468876c5e62f84c66d2408e8e" @@ -4497,7 +4491,6 @@ hardhat@^2.14.0: ws "^7.4.6" hardhat@^2.19.4: ->>>>>>> 874bc6ba940de9d37b474d1e3dda2fe4e869dfbe version "2.22.5" resolved "https://registry.yarnpkg.com/hardhat/-/hardhat-2.22.5.tgz#7e1a4311fa9e34a1cfe337784eae06706f6469a5" integrity sha512-9Zq+HonbXCSy6/a13GY1cgHglQRfh4qkzmj1tpPlhxJDwNVnhxlReV6K7hCWFKlOrV13EQwsdcD0rjcaQKWRZw== @@ -7925,7 +7918,6 @@ yocto-queue@^1.0.0: resolved "https://registry.yarnpkg.com/yocto-queue/-/yocto-queue-1.0.0.tgz#7f816433fb2cbc511ec8bf7d263c3b58a1a3c251" integrity sha512-9bnSc/HEW2uRy67wc+T8UwauLuPJVn28jb+GtJY16iiKWyvmYJRXVT4UamsAEGQfPohgr2q4Tq0sQbQlxTfi1g== -<<<<<<< HEAD zksync-ethers@5.8.0-beta.5: version "5.8.0-beta.5" resolved "https://registry.yarnpkg.com/zksync-ethers/-/zksync-ethers-5.8.0-beta.5.tgz#4f70193a86bd1e41b25b0aa5aa32f6d41d52f7c6" @@ -7933,11 +7925,6 @@ zksync-ethers@5.8.0-beta.5: dependencies: ethers "~5.7.0" -zksync-ethers@^5.0.0: - version "5.7.2" - resolved "https://registry.yarnpkg.com/zksync-ethers/-/zksync-ethers-5.7.2.tgz#e965a9926e6f8168963ab565dd6ad0d38c4f7f18" - integrity sha512-D+wn4nkGixUOek9ZsVvIZ/MHponQ5xvw74FSbDJDv6SLCI4LZALOAc8lF3b1ml8nOkpeE2pGV0VKmHTSquRNJg== -======= zksync-ethers@^5.0.0: version "5.8.0" resolved "https://registry.yarnpkg.com/zksync-ethers/-/zksync-ethers-5.8.0.tgz#ff054345048f851c33cb6efcf2094f40d4da6063" @@ -7949,6 +7936,5 @@ zksync-ethers@^5.9.0: version "5.9.0" resolved "https://registry.yarnpkg.com/zksync-ethers/-/zksync-ethers-5.9.0.tgz#96dc29e4eaaf0aa70d927886fd6e1e4c545786e3" integrity sha512-VnRUesrBcPBmiTYTAp+WreIazK2qCIJEHE7j8BiK+cDApHzjAfIXX+x8SXXJpG1npGJANxiJKnPwA5wjGZtCRg== ->>>>>>> 874bc6ba940de9d37b474d1e3dda2fe4e869dfbe dependencies: ethers "~5.7.0" From eb3583a28067a15e70c7076e3feb5686688f78bf Mon Sep 17 00:00:00 2001 From: Stanislav Breadless Date: Wed, 11 Sep 2024 17:50:23 +0200 Subject: [PATCH 02/18] delete unneeded files --- .../contracts/bridge/L1AssetRouter.sol | 1030 ----------------- .../contracts/bridge/L1NativeTokenVault.sol | 259 ----- .../state-transition/libraries/Merkle.sol | 54 - .../StateTransitionManager/FreezeChain.t.sol | 26 - .../Getters/PriorityQueueFrontOperation.t.sol | 30 - .../libraries/Merkle/Merkle.t.sol | 67 -- .../contracts/bridge/L2AssetRouter.sol | 198 ---- .../contracts/bridge/L2NativeTokenVault.sol | 234 ---- .../dev-contracts/DevL2SharedBridge.sol | 33 - .../deploy-shared-bridge-on-l2-through-l1.ts | 154 --- l2-contracts/test/erc20.test.ts | 169 --- 11 files changed, 2254 deletions(-) delete mode 100644 l1-contracts/contracts/bridge/L1AssetRouter.sol delete mode 100644 l1-contracts/contracts/bridge/L1NativeTokenVault.sol delete mode 100644 l1-contracts/contracts/state-transition/libraries/Merkle.sol delete mode 100644 l1-contracts/test/foundry/unit/concrete/state-transition/StateTransitionManager/FreezeChain.t.sol delete mode 100644 l1-contracts/test/foundry/unit/concrete/state-transition/chain-deps/facets/Getters/PriorityQueueFrontOperation.t.sol delete mode 100644 l1-contracts/test/foundry/unit/concrete/state-transition/libraries/Merkle/Merkle.t.sol delete mode 100644 l2-contracts/contracts/bridge/L2AssetRouter.sol delete mode 100644 l2-contracts/contracts/bridge/L2NativeTokenVault.sol delete mode 100644 l2-contracts/contracts/dev-contracts/DevL2SharedBridge.sol delete mode 100644 l2-contracts/src/deploy-shared-bridge-on-l2-through-l1.ts delete mode 100644 l2-contracts/test/erc20.test.ts diff --git a/l1-contracts/contracts/bridge/L1AssetRouter.sol b/l1-contracts/contracts/bridge/L1AssetRouter.sol deleted file mode 100644 index 9a4a16bce..000000000 --- a/l1-contracts/contracts/bridge/L1AssetRouter.sol +++ /dev/null @@ -1,1030 +0,0 @@ -// SPDX-License-Identifier: MIT - -pragma solidity 0.8.24; - -// solhint-disable reason-string, gas-custom-errors - -import {Ownable2StepUpgradeable} from "@openzeppelin/contracts-upgradeable-v4/access/Ownable2StepUpgradeable.sol"; -import {PausableUpgradeable} from "@openzeppelin/contracts-upgradeable-v4/security/PausableUpgradeable.sol"; - -import {IERC20} from "@openzeppelin/contracts-v4/token/ERC20/IERC20.sol"; -import {SafeERC20} from "@openzeppelin/contracts-v4/token/ERC20/utils/SafeERC20.sol"; - -import {IL1ERC20Bridge} from "./interfaces/IL1ERC20Bridge.sol"; -import {IL1AssetRouter} from "./interfaces/IL1AssetRouter.sol"; -import {IL2Bridge} from "./interfaces/IL2Bridge.sol"; -import {IL2BridgeLegacy} from "./interfaces/IL2BridgeLegacy.sol"; -import {IL1AssetHandler} from "./interfaces/IL1AssetHandler.sol"; -import {IL1NativeTokenVault} from "./interfaces/IL1NativeTokenVault.sol"; - -import {IMailbox} from "../state-transition/chain-interfaces/IMailbox.sol"; -import {L2Message, TxStatus} from "../common/Messaging.sol"; -import {UnsafeBytes} from "../common/libraries/UnsafeBytes.sol"; -import {ReentrancyGuard} from "../common/ReentrancyGuard.sol"; -import {DataEncoding} from "../common/libraries/DataEncoding.sol"; -import {AddressAliasHelper} from "../vendor/AddressAliasHelper.sol"; -import {TWO_BRIDGES_MAGIC_VALUE, ETH_TOKEN_ADDRESS} from "../common/Config.sol"; -import {L2_NATIVE_TOKEN_VAULT_ADDRESS} from "../common/L2ContractAddresses.sol"; - -import {IBridgehub, L2TransactionRequestTwoBridgesInner, L2TransactionRequestDirect} from "../bridgehub/IBridgehub.sol"; -import {L2_BASE_TOKEN_SYSTEM_CONTRACT_ADDR, L2_ASSET_ROUTER_ADDR} from "../common/L2ContractAddresses.sol"; - -import {BridgeHelper} from "./BridgeHelper.sol"; - -/// @author Matter Labs -/// @custom:security-contact security@matterlabs.dev -/// @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; - - /// @dev The address of the WETH token on L1. - address public immutable override L1_WETH_TOKEN; - - /// @dev Bridgehub smart contract that is used to operate with L2 via asynchronous L2 <-> L1 communication. - IBridgehub public immutable override BRIDGE_HUB; - - /// @dev Era's chainID - uint256 internal immutable ERA_CHAIN_ID; - - /// @dev The address of ZKsync Era diamond proxy contract. - address internal immutable ERA_DIAMOND_PROXY; - - /// @dev Stores the first batch number on the ZKsync Era Diamond Proxy that was settled after Diamond proxy upgrade. - /// This variable is used to differentiate between pre-upgrade and post-upgrade Eth withdrawals. Withdrawals from batches older - /// than this value are considered to have been finalized prior to the upgrade and handled separately. - uint256 internal eraPostDiamondUpgradeFirstBatch; - - /// @dev Stores the first batch number on the ZKsync Era Diamond Proxy that was settled after L1ERC20 Bridge upgrade. - /// This variable is used to differentiate between pre-upgrade and post-upgrade ERC20 withdrawals. Withdrawals from batches older - /// than this value are considered to have been finalized prior to the upgrade and handled separately. - uint256 internal eraPostLegacyBridgeUpgradeFirstBatch; - - /// @dev Stores the ZKsync Era batch number that processes the last deposit tx initiated by the legacy bridge - /// This variable (together with eraLegacyBridgeLastDepositTxNumber) is used to differentiate between pre-upgrade and post-upgrade deposits. Deposits processed in older batches - /// than this value are considered to have been processed prior to the upgrade and handled separately. - /// We use this both for Eth and erc20 token deposits, so we need to update the diamond and bridge simultaneously. - uint256 internal eraLegacyBridgeLastDepositBatch; - - /// @dev The tx number in the _eraLegacyBridgeLastDepositBatch of the last deposit tx initiated by the legacy bridge. - /// This variable (together with eraLegacyBridgeLastDepositBatch) is used to differentiate between pre-upgrade and post-upgrade deposits. Deposits processed in older txs - /// than this value are considered to have been processed prior to the upgrade and handled separately. - /// We use this both for Eth and erc20 token deposits, so we need to update the diamond and bridge simultaneously. - uint256 internal eraLegacyBridgeLastDepositTxNumber; - - /// @dev Legacy bridge smart contract that used to hold ERC20 tokens. - IL1ERC20Bridge public override legacyBridge; - - /// @dev A mapping chainId => bridgeProxy. Used to store the bridge proxy's address, and to see if it has been deployed yet. - mapping(uint256 chainId => address l2Bridge) public __DEPRECATED_l2BridgeAddress; - - /// @dev A mapping chainId => L2 deposit transaction hash => dataHash - // keccak256(abi.encode(account, tokenAddress, amount)) for legacy transfers - // keccak256(abi.encode(_prevMsgSender, assetId, transferData)) for new transfers - /// @dev Tracks deposit transactions to L2 to enable users to claim their funds if a deposit fails. - mapping(uint256 chainId => mapping(bytes32 l2DepositTxHash => bytes32 depositDataHash)) - public - override depositHappened; - - /// @dev Tracks the processing status of L2 to L1 messages, indicating whether a message has already been finalized. - mapping(uint256 chainId => mapping(uint256 l2BatchNumber => mapping(uint256 l2ToL1MessageNumber => bool isFinalized))) - public isWithdrawalFinalized; - - /// @notice Deprecated. Kept for backwards compatibility. - /// @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 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; - - /// @dev Maps asset ID to address of corresponding asset handler. - /// @dev Tracks the address of Asset Handler contracts, where bridged funds are locked for each asset. - /// @dev P.S. this liquidity was locked directly in SharedBridge before. - mapping(bytes32 assetId => address assetHandlerAddress) public assetHandlerAddress; - - /// @dev Maps asset ID to the asset deployment tracker address. - /// @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; - - /// @dev Address of native token vault. - IL1NativeTokenVault public nativeTokenVault; - - /// @notice Checks that the message sender is the bridgehub. - modifier onlyBridgehub() { - require(msg.sender == address(BRIDGE_HUB), "L1AR: not BH"); - _; - } - - /// @notice Checks that the message sender is the bridgehub or zkSync Era Diamond Proxy. - modifier onlyBridgehubOrEra(uint256 _chainId) { - require( - msg.sender == address(BRIDGE_HUB) || (_chainId == ERA_CHAIN_ID && msg.sender == ERA_DIAMOND_PROXY), - "L1AR: msg.sender not equal to bridgehub or era chain" - ); - _; - } - - /// @notice Checks that the message sender is the legacy bridge. - modifier onlyLegacyBridge() { - require(msg.sender == address(legacyBridge), "L1AR: not legacy bridge"); - _; - } - - /// @dev Contract is expected to be used as proxy implementation. - /// @dev Initialize the implementation to prevent Parity hack. - constructor( - address _l1WethAddress, - IBridgehub _bridgehub, - uint256 _eraChainId, - address _eraDiamondProxy - ) reentrancyGuardInitializer { - _disableInitializers(); - L1_WETH_TOKEN = _l1WethAddress; - BRIDGE_HUB = _bridgehub; - ERA_CHAIN_ID = _eraChainId; - ERA_DIAMOND_PROXY = _eraDiamondProxy; - } - - /// @dev Initializes a contract bridge for later use. Expected to be used in the proxy. - /// @dev Used for testing purposes only, as the contract has been initialized on mainnet. - /// @param _owner The address which can change L2 token implementation and upgrade the bridge implementation. - /// The owner is the Governor and separate from the ProxyAdmin from now on, so that the Governor can call the bridge. - /// @param _eraPostDiamondUpgradeFirstBatch The first batch number on the zkSync Era Diamond Proxy that was settled after diamond proxy upgrade. - /// @param _eraPostLegacyBridgeUpgradeFirstBatch The first batch number on the zkSync Era Diamond Proxy that was settled after legacy bridge upgrade. - /// @param _eraLegacyBridgeLastDepositBatch The the zkSync Era batch number that processes the last deposit tx initiated by the legacy bridge. - /// @param _eraLegacyBridgeLastDepositTxNumber The tx number in the _eraLegacyBridgeLastDepositBatch of the last deposit tx initiated by the legacy bridge. - function initialize( - address _owner, - uint256 _eraPostDiamondUpgradeFirstBatch, - uint256 _eraPostLegacyBridgeUpgradeFirstBatch, - uint256 _eraLegacyBridgeLastDepositBatch, - uint256 _eraLegacyBridgeLastDepositTxNumber - ) external reentrancyGuardInitializer initializer { - require(_owner != address(0), "L1AR: owner 0"); - _transferOwnership(_owner); - if (eraPostDiamondUpgradeFirstBatch == 0) { - eraPostDiamondUpgradeFirstBatch = _eraPostDiamondUpgradeFirstBatch; - eraPostLegacyBridgeUpgradeFirstBatch = _eraPostLegacyBridgeUpgradeFirstBatch; - eraLegacyBridgeLastDepositBatch = _eraLegacyBridgeLastDepositBatch; - eraLegacyBridgeLastDepositTxNumber = _eraLegacyBridgeLastDepositTxNumber; - } - } - - /// @notice Transfers tokens from shared bridge to native token vault. - /// @dev This function is part of the upgrade process used to transfer liquidity. - /// @param _token The address of the token to be transferred to NTV. - function transferTokenToNTV(address _token) external { - address ntvAddress = address(nativeTokenVault); - require(msg.sender == ntvAddress, "L1AR: not NTV"); - if (ETH_TOKEN_ADDRESS == _token) { - 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, "L1AR: eth transfer failed"); - } else { - IERC20(_token).safeTransfer(ntvAddress, IERC20(_token).balanceOf(address(this))); - } - } - - /// @notice Clears chain balance for specific token. - /// @dev This function is part of the upgrade process used to nullify chain balances once they are credited to NTV. - /// @param _chainId The ID of the ZK chain. - /// @param _token The address of the token which was previously deposit to shared bridge. - function nullifyChainBalanceByNTV(uint256 _chainId, address _token) external { - require(msg.sender == address(nativeTokenVault), "L1AR: not NTV"); - chainBalance[_chainId][_token] = 0; - } - - /// @notice Sets the L1ERC20Bridge contract address. - /// @dev Should be called only once by the owner. - /// @param _legacyBridge The address of the legacy bridge. - function setL1Erc20Bridge(address _legacyBridge) external onlyOwner { - require(address(legacyBridge) == address(0), "L1AR: legacy bridge already set"); - require(_legacyBridge != address(0), "L1AR: legacy bridge 0"); - legacyBridge = IL1ERC20Bridge(_legacyBridge); - } - - /// @notice Sets the nativeTokenVault contract address. - /// @dev Should be called only once by the owner. - /// @param _nativeTokenVault The address of the native token vault. - function setNativeTokenVault(IL1NativeTokenVault _nativeTokenVault) external onlyOwner { - require(address(nativeTokenVault) == address(0), "L1AR: native token vault already set"); - require(address(_nativeTokenVault) != address(0), "L1AR: native token vault 0"); - nativeTokenVault = _nativeTokenVault; - } - - /// @notice Used to set the assed deployment tracker address for given asset data. - /// @param _assetRegistrationData The asset data which may include the asset address and any additional required data or encodings. - /// @param _assetDeploymentTracker The whitelisted address of asset deployment tracker for provided asset. - function setAssetDeploymentTracker( - bytes32 _assetRegistrationData, - address _assetDeploymentTracker - ) external onlyOwner { - bytes32 assetId = keccak256( - abi.encode(uint256(block.chainid), _assetDeploymentTracker, _assetRegistrationData) - ); - assetDeploymentTracker[assetId] = _assetDeploymentTracker; - emit AssetDeploymentTrackerSet(assetId, _assetDeploymentTracker, _assetRegistrationData); - } - - /// @notice Sets the asset handler address for a specified asset ID on the chain of the asset deployment tracker. - /// @dev The caller of this function is encoded within the `assetId`, therefore, it should be invoked by the asset deployment tracker contract. - /// @dev Typically, for most tokens, ADT is the native token vault. However, custom tokens may have their own specific asset deployment trackers. - /// @dev `setAssetHandlerAddressOnCounterPart` should be called on L1 to set asset handlers on L2 chains for a specific asset ID. - /// @param _assetRegistrationData The asset data which may include the asset address and any additional required data or encodings. - /// @param _assetHandlerAddress The address of the asset handler to be set for the provided asset. - function setAssetHandlerAddressInitial(bytes32 _assetRegistrationData, address _assetHandlerAddress) external { - bool senderIsNTV = msg.sender == address(nativeTokenVault); - address sender = senderIsNTV ? L2_NATIVE_TOKEN_VAULT_ADDRESS : msg.sender; - bytes32 assetId = DataEncoding.encodeAssetId(block.chainid, _assetRegistrationData, sender); - require(senderIsNTV || msg.sender == assetDeploymentTracker[assetId], "ShB: not NTV or ADT"); - assetHandlerAddress[assetId] = _assetHandlerAddress; - if (senderIsNTV) { - assetDeploymentTracker[assetId] = msg.sender; - } - emit AssetHandlerRegisteredInitial(assetId, _assetHandlerAddress, _assetRegistrationData, sender); - } - - /// @notice Used to set the asset handler address for a given asset ID on a remote ZK chain - /// @dev No access control on the caller, as msg.sender is encoded in the assetId. - /// @param _chainId The ZK chain ID. - /// @param _mintValue The value withdrawn by base token bridge to cover for l2 gas and l2 msg.value costs. - /// @param _l2TxGasLimit The L2 gas limit to be used in the corresponding L2 transaction. - /// @param _l2TxGasPerPubdataByte The gasPerPubdataByteLimit to be used in the corresponding L2 transaction. - /// @param _refundRecipient The address on L2 that will receive the refund for the transaction. - /// @param _assetId The encoding of asset ID. - /// @param _assetHandlerAddressOnCounterPart The address of the asset handler, which will hold the token of interest. - /// @return txHash The L2 transaction hash of setting asset handler on remote chain. - function setAssetHandlerAddressOnCounterPart( - uint256 _chainId, - uint256 _mintValue, - uint256 _l2TxGasLimit, - uint256 _l2TxGasPerPubdataByte, - address _refundRecipient, - bytes32 _assetId, - address _assetHandlerAddressOnCounterPart - ) external payable returns (bytes32 txHash) { - require(msg.sender == assetDeploymentTracker[_assetId] || msg.sender == owner(), "L1AR: only ADT or owner"); - - bytes memory l2Calldata = abi.encodeCall( - IL2Bridge.setAssetHandlerAddress, - (_assetId, _assetHandlerAddressOnCounterPart) - ); - - L2TransactionRequestDirect memory request = L2TransactionRequestDirect({ - chainId: _chainId, - l2Contract: L2_ASSET_ROUTER_ADDR, - mintValue: _mintValue, // l2 gas + l2 msg.value the bridgehub will withdraw the mintValue from the base token bridge for gas - l2Value: 0, // For base token deposits, there is no msg.value during the call, as the base token is minted to the recipient address - l2Calldata: l2Calldata, - l2GasLimit: _l2TxGasLimit, - l2GasPerPubdataByteLimit: _l2TxGasPerPubdataByte, - factoryDeps: new bytes[](0), - refundRecipient: _refundRecipient - }); - txHash = BRIDGE_HUB.requestL2TransactionDirect{value: msg.value}(request); - } - - /// @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 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. - function bridgehubDepositBaseToken( - uint256 _chainId, - bytes32 _assetId, - address _prevMsgSender, - uint256 _amount - ) external payable onlyBridgehubOrEra(_chainId) whenNotPaused { - address l1AssetHandler = assetHandlerAddress[_assetId]; - require(l1AssetHandler != address(0), "ShB: asset handler not set"); - - _transferAllowanceToNTV(_assetId, _amount, _prevMsgSender); - // slither-disable-next-line unused-return - IL1AssetHandler(l1AssetHandler).bridgeBurn{value: msg.value}({ - _chainId: _chainId, - _l2Value: 0, - _assetId: _assetId, - _prevMsgSender: _prevMsgSender, - _data: abi.encode(_amount, address(0)) - }); - - // Note that we don't save the deposited amount, as this is for the base token, which gets sent to the refundRecipient if the tx fails - emit BridgehubDepositBaseTokenInitiated(_chainId, _prevMsgSender, _assetId, _amount); - } - - /// @notice Initiates a deposit transaction within Bridgehub, used by `requestL2TransactionTwoBridges`. - /// @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. - /// @return request The data used by the bridgehub to create L2 transaction request to specific ZK chain. - function bridgehubDeposit( - uint256 _chainId, - address _prevMsgSender, - uint256 _l2Value, - bytes calldata _data - ) - external - payable - override - onlyBridgehub - whenNotPaused - returns (L2TransactionRequestTwoBridgesInner memory request) - { - bytes32 assetId; - bytes memory transferData; - bool legacyDeposit = false; - bytes1 encodingVersion = _data[0]; - - // The new encoding ensures that the calldata is collision-resistant with respect to the legacy format. - // In the legacy calldata, the first input was the address, meaning the most significant byte was always `0x00`. - if (encodingVersion == 0x01) { - (assetId, transferData) = abi.decode(_data[1:], (bytes32, bytes)); - require( - assetHandlerAddress[assetId] != address(nativeTokenVault), - "ShB: new encoding format not yet supported for NTV" - ); - } else { - (assetId, transferData) = _handleLegacyData(_data, _prevMsgSender); - legacyDeposit = true; - } - - require(BRIDGE_HUB.baseTokenAssetId(_chainId) != assetId, "L1AR: baseToken deposit not supported"); - - bytes memory bridgeMintCalldata = _burn({ - _chainId: _chainId, - _l2Value: _l2Value, - _assetId: assetId, - _prevMsgSender: _prevMsgSender, - _transferData: transferData, - _passValue: true - }); - bytes32 txDataHash = this.encodeTxDataHash(legacyDeposit, _prevMsgSender, assetId, transferData); - - request = _requestToBridge({ - _prevMsgSender: _prevMsgSender, - _assetId: assetId, - _bridgeMintCalldata: bridgeMintCalldata, - _txDataHash: txDataHash - }); - - emit BridgehubDepositInitiated({ - chainId: _chainId, - txDataHash: txDataHash, - from: _prevMsgSender, - assetId: assetId, - bridgeMintCalldata: bridgeMintCalldata - }); - } - - /// @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 ZK chain to which confirm the deposit. - /// @param _txDataHash The keccak256 hash of 0x01 || abi.encode(bytes32, bytes) to identify deposits. - /// @param _txHash The hash of the L1->L2 transaction to confirm the deposit. - function bridgehubConfirmL2Transaction( - uint256 _chainId, - bytes32 _txDataHash, - bytes32 _txHash - ) external override onlyBridgehub whenNotPaused { - require(depositHappened[_chainId][_txHash] == 0x00, "L1AR: tx hap"); - depositHappened[_chainId][_txHash] = _txDataHash; - emit BridgehubDepositFinalized(_chainId, _txDataHash, _txHash); - } - - /// @notice Finalize the withdrawal and release funds - /// @param _chainId The chain ID of the transaction to check - /// @param _l2BatchNumber The L2 batch number where the withdrawal was processed - /// @param _l2MessageIndex The position in the L2 logs Merkle tree of the l2Log that was sent with the message - /// @param _l2TxNumberInBatch The L2 transaction number in the batch, in which the log was sent - /// @param _message The L2 withdraw data, stored in an L2 -> L1 message - /// @param _merkleProof The Merkle proof of the inclusion L2 -> L1 message about withdrawal initialization - function finalizeWithdrawal( - uint256 _chainId, - uint256 _l2BatchNumber, - uint256 _l2MessageIndex, - uint16 _l2TxNumberInBatch, - bytes calldata _message, - bytes32[] calldata _merkleProof - ) external override { - _finalizeWithdrawal({ - _chainId: _chainId, - _l2BatchNumber: _l2BatchNumber, - _l2MessageIndex: _l2MessageIndex, - _l2TxNumberInBatch: _l2TxNumberInBatch, - _message: _message, - _merkleProof: _merkleProof - }); - } - - /// @dev Calls the internal `_encodeTxDataHash`. Used as a wrapped for try / catch case. - /// @param _isLegacyEncoding Boolean flag indicating whether to use the legacy encoding standard (true) or the latest encoding standard (false). - /// @param _prevMsgSender The address of the entity that initiated the deposit. - /// @param _assetId The unique identifier of the deposited L1 token. - /// @param _transferData The encoded transfer data, which includes both the deposit amount and the address of the L2 receiver. - /// @return txDataHash The resulting encoded transaction data hash. - function encodeTxDataHash( - bool _isLegacyEncoding, - address _prevMsgSender, - bytes32 _assetId, - bytes calldata _transferData - ) external view returns (bytes32 txDataHash) { - return _encodeTxDataHash(_isLegacyEncoding, _prevMsgSender, _assetId, _transferData); - } - - /// @dev Withdraw funds from the initiated deposit, that failed when finalizing on L2. - /// @param _chainId The ZK chain id to which deposit was initiated. - /// @param _depositSender The address of the entity that initiated the deposit. - /// @param _assetId The unique identifier of the deposited L1 token. - /// @param _assetData The encoded transfer data, which includes both the deposit amount and the address of the L2 receiver. Might include extra information. - /// @param _l2TxHash The L2 transaction hash of the failed deposit finalization. - /// @param _l2BatchNumber The L2 batch number where the deposit finalization was processed. - /// @param _l2MessageIndex The position in the L2 logs Merkle tree of the l2Log that was sent with the message. - /// @param _l2TxNumberInBatch The L2 transaction number in a batch, in which the log was sent. - /// @param _merkleProof The Merkle proof of the processing L1 -> L2 transaction with deposit finalization. - /// @dev Processes claims of failed deposit, whether they originated from the legacy bridge or the current system. - function bridgeRecoverFailedTransfer( - uint256 _chainId, - address _depositSender, - bytes32 _assetId, - bytes memory _assetData, - bytes32 _l2TxHash, - uint256 _l2BatchNumber, - uint256 _l2MessageIndex, - uint16 _l2TxNumberInBatch, - bytes32[] calldata _merkleProof - ) public nonReentrant whenNotPaused { - { - bool proofValid = BRIDGE_HUB.proveL1ToL2TransactionStatus({ - _chainId: _chainId, - _l2TxHash: _l2TxHash, - _l2BatchNumber: _l2BatchNumber, - _l2MessageIndex: _l2MessageIndex, - _l2TxNumberInBatch: _l2TxNumberInBatch, - _merkleProof: _merkleProof, - _status: TxStatus.Failure - }); - require(proofValid, "yn"); - } - - require(!_isEraLegacyDeposit(_chainId, _l2BatchNumber, _l2TxNumberInBatch), "L1AR: legacy cFD"); - { - bytes32 dataHash = depositHappened[_chainId][_l2TxHash]; - // Determine if the given dataHash matches the calculated legacy transaction hash. - bool isLegacyTxDataHash = _isLegacyTxDataHash(_depositSender, _assetId, _assetData, dataHash); - // If the dataHash matches the legacy transaction hash, skip the next step. - // Otherwise, perform the check using the new transaction data hash encoding. - if (!isLegacyTxDataHash) { - bytes32 txDataHash = _encodeTxDataHash(false, _depositSender, _assetId, _assetData); - require(dataHash == txDataHash, "L1AR: d.it not hap"); - } - } - delete depositHappened[_chainId][_l2TxHash]; - - IL1AssetHandler(assetHandlerAddress[_assetId]).bridgeRecoverFailedTransfer( - _chainId, - _assetId, - _depositSender, - _assetData - ); - - emit ClaimedFailedDepositSharedBridge(_chainId, _depositSender, _assetId, _assetData); - } - - /// @dev Receives and parses (name, symbol, decimals) from the token contract - function getERC20Getters(address _token) public view returns (bytes memory) { - return BridgeHelper.getERC20Getters(_token, ETH_TOKEN_ADDRESS); - } - - /// @dev send the burn message to the asset - /// @notice Forwards the burn request for specific asset to respective asset handler - /// @param _chainId The chain ID of the ZK chain to which deposit. - /// @param _l2Value The L2 `msg.value` from the L1 -> L2 deposit transaction. - /// @param _assetId The deposited asset ID. - /// @param _prevMsgSender The `msg.sender` address from the external call that initiated current one. - /// @param _transferData The encoded data, which is used by the asset handler to determine L2 recipient and amount. Might include extra information. - /// @param _passValue Boolean indicating whether to pass msg.value in the call. - /// @return bridgeMintCalldata The calldata used by remote asset handler to mint tokens for recipient. - function _burn( - uint256 _chainId, - uint256 _l2Value, - bytes32 _assetId, - address _prevMsgSender, - bytes memory _transferData, - bool _passValue - ) internal returns (bytes memory bridgeMintCalldata) { - address l1AssetHandler = assetHandlerAddress[_assetId]; - require(l1AssetHandler != address(0), "ShB: asset handler does not exist for assetId"); - - uint256 msgValue = _passValue ? msg.value : 0; - bridgeMintCalldata = IL1AssetHandler(l1AssetHandler).bridgeBurn{value: msgValue}({ - _chainId: _chainId, - _l2Value: _l2Value, - _assetId: _assetId, - _prevMsgSender: _prevMsgSender, - _data: _transferData - }); - } - - struct MessageParams { - uint256 l2BatchNumber; - uint256 l2MessageIndex; - uint16 l2TxNumberInBatch; - } - - /// @notice Internal function that handles the logic for finalizing withdrawals, supporting both the current bridge system and the legacy ERC20 bridge. - /// @param _chainId The chain ID of the transaction to check. - /// @param _l2BatchNumber The L2 batch number where the withdrawal was processed. - /// @param _l2MessageIndex The position in the L2 logs Merkle tree of the l2Log that was sent with the message. - /// @param _l2TxNumberInBatch The L2 transaction number in the batch, in which the log was sent. - /// @param _message The L2 withdraw data, stored in an L2 -> L1 message. - /// @param _merkleProof The Merkle proof of the inclusion L2 -> L1 message about withdrawal initialization. - /// @return l1Receiver The address to receive bridged assets. - /// @return assetId The bridged asset ID. - /// @return amount The amount of asset bridged. - function _finalizeWithdrawal( - uint256 _chainId, - uint256 _l2BatchNumber, - uint256 _l2MessageIndex, - uint16 _l2TxNumberInBatch, - bytes calldata _message, - bytes32[] calldata _merkleProof - ) internal nonReentrant whenNotPaused returns (address l1Receiver, bytes32 assetId, uint256 amount) { - require( - !isWithdrawalFinalized[_chainId][_l2BatchNumber][_l2MessageIndex], - "L1AR: Withdrawal is already finalized" - ); - isWithdrawalFinalized[_chainId][_l2BatchNumber][_l2MessageIndex] = true; - - // Handling special case for withdrawal from ZKsync Era initiated before Shared Bridge. - require(!_isEraLegacyEthWithdrawal(_chainId, _l2BatchNumber), "L1AR: legacy eth withdrawal"); - require(!_isEraLegacyTokenWithdrawal(_chainId, _l2BatchNumber), "L1AR: legacy token withdrawal"); - - bytes memory transferData; - { - MessageParams memory messageParams = MessageParams({ - l2BatchNumber: _l2BatchNumber, - l2MessageIndex: _l2MessageIndex, - l2TxNumberInBatch: _l2TxNumberInBatch - }); - (assetId, transferData) = _checkWithdrawal(_chainId, messageParams, _message, _merkleProof); - } - address l1AssetHandler = assetHandlerAddress[assetId]; - // slither-disable-next-line unused-return - IL1AssetHandler(l1AssetHandler).bridgeMint(_chainId, assetId, transferData); - (amount, l1Receiver) = abi.decode(transferData, (uint256, address)); - - emit WithdrawalFinalizedSharedBridge(_chainId, l1Receiver, assetId, amount); - } - - /// @notice Decodes the transfer input for legacy data and transfers allowance to NTV - /// @dev Is not applicable for custom asset handlers - /// @param _data encoded transfer data (address _l1Token, uint256 _depositAmount, address _l2Receiver) - /// @param _prevMsgSender address of the deposit initiator - function _handleLegacyData(bytes calldata _data, address _prevMsgSender) internal returns (bytes32, bytes memory) { - (address _l1Token, uint256 _depositAmount, address _l2Receiver) = abi.decode( - _data, - (address, uint256, address) - ); - bytes32 assetId = _ensureTokenRegisteredWithNTV(_l1Token); - _transferAllowanceToNTV(assetId, _depositAmount, _prevMsgSender); - return (assetId, abi.encode(_depositAmount, _l2Receiver)); - } - - function _ensureTokenRegisteredWithNTV(address _l1Token) internal returns (bytes32 assetId) { - assetId = DataEncoding.encodeNTVAssetId(block.chainid, _l1Token); - if (nativeTokenVault.tokenAddress(assetId) == address(0)) { - nativeTokenVault.registerToken(_l1Token); - } - } - - /// @notice Transfers allowance to Native Token Vault, if the asset is registered with it. Does nothing for ETH or non-registered tokens. - /// @dev assetId is not the padded address, but the correct encoded id (NTV stores respective format for IDs) - function _transferAllowanceToNTV(bytes32 _assetId, uint256 _amount, address _prevMsgSender) internal { - address l1TokenAddress = nativeTokenVault.tokenAddress(_assetId); - if (l1TokenAddress == address(0) || l1TokenAddress == ETH_TOKEN_ADDRESS) { - return; - } - IERC20 l1Token = IERC20(l1TokenAddress); - - // Do the transfer if allowance to Shared bridge is bigger than amount - // And if there is not enough allowance for the NTV - if ( - l1Token.allowance(_prevMsgSender, address(this)) >= _amount && - l1Token.allowance(_prevMsgSender, address(nativeTokenVault)) < _amount - ) { - // slither-disable-next-line arbitrary-send-erc20 - l1Token.safeTransferFrom(_prevMsgSender, address(this), _amount); - l1Token.forceApprove(address(nativeTokenVault), _amount); - } - } - - /// @dev The request data that is passed to the bridgehub - function _requestToBridge( - address _prevMsgSender, - bytes32 _assetId, - bytes memory _bridgeMintCalldata, - bytes32 _txDataHash - ) internal view returns (L2TransactionRequestTwoBridgesInner memory request) { - // Request the finalization of the deposit on the L2 side - bytes memory l2TxCalldata = _getDepositL2Calldata(_prevMsgSender, _assetId, _bridgeMintCalldata); - - request = L2TransactionRequestTwoBridgesInner({ - magicValue: TWO_BRIDGES_MAGIC_VALUE, - l2Contract: L2_ASSET_ROUTER_ADDR, - l2Calldata: l2TxCalldata, - factoryDeps: new bytes[](0), - txDataHash: _txDataHash - }); - } - - /// @dev Generate a calldata for calling the deposit finalization on the L2 bridge contract - function _getDepositL2Calldata( - address _l1Sender, - bytes32 _assetId, - bytes memory _assetData - ) internal view returns (bytes memory) { - // First branch covers the case when asset is not registered with NTV (custom asset handler) - // Second branch handles tokens registered with NTV and uses legacy calldata encoding - if (nativeTokenVault.tokenAddress(_assetId) == address(0)) { - return abi.encodeCall(IL2Bridge.finalizeDeposit, (_assetId, _assetData)); - } else { - // slither-disable-next-line unused-return - (, address _l2Receiver, address _parsedL1Token, uint256 _amount, bytes memory _gettersData) = DataEncoding - .decodeBridgeMintData(_assetData); - return - abi.encodeCall( - IL2BridgeLegacy.finalizeDeposit, - (_l1Sender, _l2Receiver, _parsedL1Token, _amount, _gettersData) - ); - } - } - - /// @dev Determines if an eth withdrawal was initiated on zkSync Era before the upgrade to the Shared Bridge. - /// @param _chainId The chain ID of the transaction to check. - /// @param _l2BatchNumber The L2 batch number for the withdrawal. - /// @return Whether withdrawal was initiated on ZKsync Era before diamond proxy upgrade. - function _isEraLegacyEthWithdrawal(uint256 _chainId, uint256 _l2BatchNumber) internal view returns (bool) { - require((_chainId != ERA_CHAIN_ID) || eraPostDiamondUpgradeFirstBatch != 0, "L1AR: diamondUFB not set for Era"); - return (_chainId == ERA_CHAIN_ID) && (_l2BatchNumber < eraPostDiamondUpgradeFirstBatch); - } - - /// @dev Determines if a token withdrawal was initiated on ZKsync Era before the upgrade to the Shared Bridge. - /// @param _chainId The chain ID of the transaction to check. - /// @param _l2BatchNumber The L2 batch number for the withdrawal. - /// @return Whether withdrawal was initiated on ZKsync Era before Legacy Bridge upgrade. - function _isEraLegacyTokenWithdrawal(uint256 _chainId, uint256 _l2BatchNumber) internal view returns (bool) { - require( - (_chainId != ERA_CHAIN_ID) || eraPostLegacyBridgeUpgradeFirstBatch != 0, - "L1AR: LegacyUFB not set for Era" - ); - return (_chainId == ERA_CHAIN_ID) && (_l2BatchNumber < eraPostLegacyBridgeUpgradeFirstBatch); - } - - /// @dev Determines if the provided data for a failed deposit corresponds to a legacy failed deposit. - /// @param _prevMsgSender The address of the entity that initiated the deposit. - /// @param _assetId The unique identifier of the deposited L1 token. - /// @param _transferData The encoded transfer data, which includes both the deposit amount and the address of the L2 receiver. - /// @param _expectedTxDataHash The nullifier data hash stored for the failed deposit. - /// @return isLegacyTxDataHash True if the transaction is legacy, false otherwise. - function _isLegacyTxDataHash( - address _prevMsgSender, - bytes32 _assetId, - bytes memory _transferData, - bytes32 _expectedTxDataHash - ) internal view returns (bool isLegacyTxDataHash) { - try this.encodeTxDataHash(true, _prevMsgSender, _assetId, _transferData) returns (bytes32 txDataHash) { - return txDataHash == _expectedTxDataHash; - } catch { - return false; - } - } - - /// @dev Encodes the transaction data hash using either the latest encoding standard or the legacy standard. - /// @param _isLegacyEncoding Boolean flag indicating whether to use the legacy encoding standard (true) or the latest encoding standard (false). - /// @param _prevMsgSender The address of the entity that initiated the deposit. - /// @param _assetId The unique identifier of the deposited L1 token. - /// @param _transferData The encoded transfer data, which includes both the deposit amount and the address of the L2 receiver. - /// @return txDataHash The resulting encoded transaction data hash. - function _encodeTxDataHash( - bool _isLegacyEncoding, - address _prevMsgSender, - bytes32 _assetId, - bytes memory _transferData - ) internal view returns (bytes32 txDataHash) { - if (_isLegacyEncoding) { - (uint256 depositAmount, ) = abi.decode(_transferData, (uint256, address)); - txDataHash = keccak256(abi.encode(_prevMsgSender, nativeTokenVault.tokenAddress(_assetId), depositAmount)); - } else { - // Similarly to calldata, the txDataHash is collision-resistant. - // In the legacy data hash, the first encoded variable was the address, which is padded with zeros during `abi.encode`. - txDataHash = keccak256(bytes.concat(bytes1(0x01), abi.encode(_prevMsgSender, _assetId, _transferData))); - } - } - - /// @dev Determines if a deposit was initiated on zkSync Era before the upgrade to the Shared Bridge. - /// @param _chainId The chain ID of the transaction to check. - /// @param _l2BatchNumber The L2 batch number for the deposit where it was processed. - /// @param _l2TxNumberInBatch The L2 transaction number in the batch, in which the deposit was processed. - /// @return Whether deposit was initiated on ZKsync Era before Shared Bridge upgrade. - function _isEraLegacyDeposit( - uint256 _chainId, - uint256 _l2BatchNumber, - uint256 _l2TxNumberInBatch - ) internal view returns (bool) { - require( - (_chainId != ERA_CHAIN_ID) || (eraLegacyBridgeLastDepositBatch != 0), - "L1AR: last deposit time not set for Era" - ); - return - (_chainId == ERA_CHAIN_ID) && - (_l2BatchNumber < eraLegacyBridgeLastDepositBatch || - (_l2TxNumberInBatch <= eraLegacyBridgeLastDepositTxNumber && - _l2BatchNumber == eraLegacyBridgeLastDepositBatch)); - } - - /// @notice Verifies the validity of a withdrawal message from L2 and returns withdrawal details. - /// @param _chainId The chain ID of the transaction to check. - /// @param _messageParams The message params, which include batch number, message index, and L2 tx number in batch. - /// @param _message The L2 withdraw data, stored in an L2 -> L1 message. - /// @param _merkleProof The Merkle proof of the inclusion L2 -> L1 message about withdrawal initialization. - /// @return assetId The ID of the bridged asset. - /// @return transferData The transfer data used to finalize withdawal. - function _checkWithdrawal( - uint256 _chainId, - MessageParams memory _messageParams, - bytes calldata _message, - bytes32[] calldata _merkleProof - ) internal view returns (bytes32 assetId, bytes memory transferData) { - (assetId, transferData) = _parseL2WithdrawalMessage(_chainId, _message); - L2Message memory l2ToL1Message; - { - bool baseTokenWithdrawal = (assetId == BRIDGE_HUB.baseTokenAssetId(_chainId)); - address l2Sender = baseTokenWithdrawal ? L2_BASE_TOKEN_SYSTEM_CONTRACT_ADDR : L2_ASSET_ROUTER_ADDR; - - l2ToL1Message = L2Message({ - txNumberInBatch: _messageParams.l2TxNumberInBatch, - sender: l2Sender, - data: _message - }); - } - - bool success = BRIDGE_HUB.proveL2MessageInclusion({ - _chainId: _chainId, - _batchNumber: _messageParams.l2BatchNumber, - _index: _messageParams.l2MessageIndex, - _message: l2ToL1Message, - _proof: _merkleProof - }); - require(success, "L1AR: withd w proof"); // withdrawal wrong proof - } - - /// @notice Parses the withdrawal message and returns withdrawal details. - /// @dev Currently, 3 different encoding versions are supported: legacy mailbox withdrawal, ERC20 bridge withdrawal, - /// @dev and the latest version supported by shared bridge. Selectors are used for versioning. - /// @param _chainId The ZK chain ID. - /// @param _l2ToL1message The encoded L2 -> L1 message. - /// @return assetId The ID of the bridged asset. - /// @return transferData The transfer data used to finalize withdawal. - function _parseL2WithdrawalMessage( - uint256 _chainId, - bytes memory _l2ToL1message - ) internal view returns (bytes32 assetId, bytes memory transferData) { - // We check that the message is long enough to read the data. - // Please note that there are three versions of the message: - // 1. The message that is sent by `withdraw(address _l1Receiver)` or `withdrawWithMessage`. In the second case, this function ignores the extra data - // It should be equal to the length of the bytes4 function signature + address l1Receiver + uint256 amount = 4 + 20 + 32 = 56 (bytes). - // 2. The legacy `getL1WithdrawMessage`, the length of the data is known. - // 3. The message that is encoded by `getL1WithdrawMessage(bytes32 _assetId, bytes memory _bridgeMintData)` - // No length is assumed. The assetId is decoded and the mintData is passed to respective assetHandler - - (uint32 functionSignature, uint256 offset) = UnsafeBytes.readUint32(_l2ToL1message, 0); - if (bytes4(functionSignature) == IMailbox.finalizeEthWithdrawal.selector) { - uint256 amount; - address l1Receiver; - - // The data is expected to be at least 56 bytes long. - require(_l2ToL1message.length >= 56, "L1AR: wrong msg len"); // wrong message length - // this message is a base token withdrawal - (l1Receiver, offset) = UnsafeBytes.readAddress(_l2ToL1message, offset); - // slither-disable-next-line unused-return - (amount, ) = UnsafeBytes.readUint256(_l2ToL1message, offset); - assetId = BRIDGE_HUB.baseTokenAssetId(_chainId); - transferData = abi.encode(amount, l1Receiver); - } else if (bytes4(functionSignature) == IL1ERC20Bridge.finalizeWithdrawal.selector) { - address l1Token; - uint256 amount; - address l1Receiver; - // We use the IL1ERC20Bridge for backward compatibility with old withdrawals. - // This message is a token withdrawal - - // Check that the message length is correct. - // It should be equal to the length of the function signature + address + address + uint256 = 4 + 20 + 20 + 32 = - // 76 (bytes). - require(_l2ToL1message.length == 76, "L1AR: wrong msg len 2"); - (l1Receiver, offset) = UnsafeBytes.readAddress(_l2ToL1message, offset); - (l1Token, offset) = UnsafeBytes.readAddress(_l2ToL1message, offset); - // slither-disable-next-line unused-return - (amount, ) = UnsafeBytes.readUint256(_l2ToL1message, offset); - - assetId = DataEncoding.encodeNTVAssetId(block.chainid, l1Token); - transferData = abi.encode(amount, l1Receiver); - } else if (bytes4(functionSignature) == this.finalizeWithdrawal.selector) { - // The data is expected to be at least 36 bytes long to contain assetId. - require(_l2ToL1message.length >= 36, "L1AR: wrong msg len"); // wrong message length - (assetId, offset) = UnsafeBytes.readBytes32(_l2ToL1message, offset); - transferData = UnsafeBytes.readRemainingBytes(_l2ToL1message, offset); - } else { - revert("L1AR: Incorrect message function selector"); - } - } - - /*////////////////////////////////////////////////////////////// - SHARED BRIDGE TOKEN BRIDGING LEGACY FUNCTIONS - //////////////////////////////////////////////////////////////*/ - - /// @notice Withdraw funds from the initiated deposit, that failed when finalizing on L2. - /// @dev Cannot be used to claim deposits made with new encoding. - /// @param _chainId The ZK chain id to which deposit was initiated. - /// @param _depositSender The address of the deposit initiator. - /// @param _l1Asset The address of the deposited L1 ERC20 token. - /// @param _amount The amount of the deposit that failed. - /// @param _l2TxHash The L2 transaction hash of the failed deposit finalization. - /// @param _l2BatchNumber The L2 batch number where the deposit finalization was processed. - /// @param _l2MessageIndex The position in the L2 logs Merkle tree of the l2Log that was sent with the message. - /// @param _l2TxNumberInBatch The L2 transaction number in a batch, in which the log was sent. - /// @param _merkleProof The Merkle proof of the processing L1 -> L2 transaction with deposit finalization. - function claimFailedDeposit( - uint256 _chainId, - address _depositSender, - address _l1Asset, - uint256 _amount, - bytes32 _l2TxHash, - uint256 _l2BatchNumber, - uint256 _l2MessageIndex, - uint16 _l2TxNumberInBatch, - bytes32[] calldata _merkleProof - ) external override { - bytes32 assetId = DataEncoding.encodeNTVAssetId(block.chainid, _l1Asset); - // For legacy deposits, the l2 receiver is not required to check tx data hash - bytes memory transferData = abi.encode(_amount, address(0)); - bridgeRecoverFailedTransfer({ - _chainId: _chainId, - _depositSender: _depositSender, - _assetId: assetId, - _assetData: transferData, - _l2TxHash: _l2TxHash, - _l2BatchNumber: _l2BatchNumber, - _l2MessageIndex: _l2MessageIndex, - _l2TxNumberInBatch: _l2TxNumberInBatch, - _merkleProof: _merkleProof - }); - } - - /*////////////////////////////////////////////////////////////// - ERA ERC20 LEGACY FUNCTIONS - //////////////////////////////////////////////////////////////*/ - - /// @notice Initiates a deposit by locking funds on the contract and sending the request - /// of processing an L2 transaction where tokens would be minted. - /// @dev If the token is bridged for the first time, the L2 token contract will be deployed. Note however, that the - /// newly-deployed token does not support any custom logic, i.e. rebase tokens' functionality is not supported. - /// @param _prevMsgSender The `msg.sender` address from the external call that initiated current one. - /// @param _l2Receiver The account address that should receive funds on L2. - /// @param _l1Token The L1 token address which is deposited. - /// @param _amount The total amount of tokens to be bridged. - /// @param _l2TxGasLimit The L2 gas limit to be used in the corresponding L2 transaction. - /// @param _l2TxGasPerPubdataByte The gasPerPubdataByteLimit to be used in the corresponding L2 transaction. - /// @param _refundRecipient The address on L2 that will receive the refund for the transaction. - /// @dev If the L2 deposit finalization transaction fails, the `_refundRecipient` will receive the `_l2Value`. - /// Please note, the contract may change the refund recipient's address to eliminate sending funds to addresses - /// out of control. - /// - If `_refundRecipient` is a contract on L1, the refund will be sent to the aliased `_refundRecipient`. - /// - If `_refundRecipient` is set to `address(0)` and the sender has NO deployed bytecode on L1, the refund will - /// be sent to the `msg.sender` address. - /// - If `_refundRecipient` is set to `address(0)` and the sender has deployed bytecode on L1, the refund will be - /// sent to the aliased `msg.sender` address. - /// @dev The address aliasing of L1 contracts as refund recipient on L2 is necessary to guarantee that the funds - /// are controllable through the Mailbox, since the Mailbox applies address aliasing to the from address for the - /// L2 tx if the L1 msg.sender is a contract. Without address aliasing for L1 contracts as refund recipients they - /// would not be able to make proper L2 tx requests through the Mailbox to use or withdraw the funds from L2, and - /// the funds would be lost. - /// @return txHash The L2 transaction hash of deposit finalization. - function depositLegacyErc20Bridge( - address _prevMsgSender, - address _l2Receiver, - address _l1Token, - uint256 _amount, - uint256 _l2TxGasLimit, - uint256 _l2TxGasPerPubdataByte, - address _refundRecipient - ) external payable override onlyLegacyBridge nonReentrant whenNotPaused returns (bytes32 txHash) { - require(_l1Token != L1_WETH_TOKEN, "L1AR: WETH deposit not supported 2"); - - bytes32 _assetId; - bytes memory bridgeMintCalldata; - - { - // Inner call to encode data to decrease local var numbers - _assetId = _ensureTokenRegisteredWithNTV(_l1Token); - IERC20(_l1Token).forceApprove(address(nativeTokenVault), _amount); - } - - { - bridgeMintCalldata = _burn({ - _chainId: ERA_CHAIN_ID, - _l2Value: 0, - _assetId: _assetId, - _prevMsgSender: _prevMsgSender, - _transferData: abi.encode(_amount, _l2Receiver), - _passValue: false - }); - } - - { - bytes memory l2TxCalldata = _getDepositL2Calldata(_prevMsgSender, _assetId, bridgeMintCalldata); - - // If the refund recipient is not specified, the refund will be sent to the sender of the transaction. - // Otherwise, the refund will be sent to the specified address. - // If the recipient is a contract on L1, the address alias will be applied. - address refundRecipient = AddressAliasHelper.actualRefundRecipient(_refundRecipient, _prevMsgSender); - - L2TransactionRequestDirect memory request = L2TransactionRequestDirect({ - chainId: ERA_CHAIN_ID, - l2Contract: L2_ASSET_ROUTER_ADDR, - mintValue: msg.value, // l2 gas + l2 msg.Value the bridgehub will withdraw the mintValue from the shared bridge (base token bridge) for gas - l2Value: 0, // L2 msg.value, this contract doesn't support base token deposits or wrapping functionality, for direct deposits use bridgehub - l2Calldata: l2TxCalldata, - l2GasLimit: _l2TxGasLimit, - l2GasPerPubdataByteLimit: _l2TxGasPerPubdataByte, - factoryDeps: new bytes[](0), - refundRecipient: refundRecipient - }); - txHash = BRIDGE_HUB.requestL2TransactionDirect{value: msg.value}(request); - } - - // Save the deposited amount to claim funds on L1 if the deposit failed on L2 - depositHappened[ERA_CHAIN_ID][txHash] = keccak256(abi.encode(_prevMsgSender, _l1Token, _amount)); - - emit LegacyDepositInitiated({ - chainId: ERA_CHAIN_ID, - l2DepositTxHash: txHash, - from: _prevMsgSender, - to: _l2Receiver, - l1Asset: _l1Token, - amount: _amount - }); - } - - /// @notice Finalizes the withdrawal for transactions initiated via the legacy ERC20 bridge. - /// @param _l2BatchNumber The L2 batch number where the withdrawal was processed. - /// @param _l2MessageIndex The position in the L2 logs Merkle tree of the l2Log that was sent with the message. - /// @param _l2TxNumberInBatch The L2 transaction number in the batch, in which the log was sent. - /// @param _message The L2 withdraw data, stored in an L2 -> L1 message. - /// @param _merkleProof The Merkle proof of the inclusion L2 -> L1 message about withdrawal initialization. - /// - /// @return l1Receiver The address on L1 that will receive the withdrawn funds. - /// @return l1Asset The address of the L1 token being withdrawn. - /// @return amount The amount of the token being withdrawn. - function finalizeWithdrawalLegacyErc20Bridge( - uint256 _l2BatchNumber, - uint256 _l2MessageIndex, - uint16 _l2TxNumberInBatch, - bytes calldata _message, - bytes32[] calldata _merkleProof - ) external override onlyLegacyBridge returns (address l1Receiver, address l1Asset, uint256 amount) { - bytes32 assetId; - (l1Receiver, assetId, amount) = _finalizeWithdrawal({ - _chainId: ERA_CHAIN_ID, - _l2BatchNumber: _l2BatchNumber, - _l2MessageIndex: _l2MessageIndex, - _l2TxNumberInBatch: _l2TxNumberInBatch, - _message: _message, - _merkleProof: _merkleProof - }); - l1Asset = nativeTokenVault.tokenAddress(assetId); - } - - /*////////////////////////////////////////////////////////////// - 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/L1NativeTokenVault.sol b/l1-contracts/contracts/bridge/L1NativeTokenVault.sol deleted file mode 100644 index fba532597..000000000 --- a/l1-contracts/contracts/bridge/L1NativeTokenVault.sol +++ /dev/null @@ -1,259 +0,0 @@ -// SPDX-License-Identifier: MIT - -pragma solidity 0.8.24; - -// solhint-disable reason-string, gas-custom-errors - -import {Ownable2StepUpgradeable} from "@openzeppelin/contracts-upgradeable-v4/access/Ownable2StepUpgradeable.sol"; -import {PausableUpgradeable} from "@openzeppelin/contracts-upgradeable-v4/security/PausableUpgradeable.sol"; - -import {IERC20} from "@openzeppelin/contracts-v4/token/ERC20/IERC20.sol"; -import {SafeERC20} from "@openzeppelin/contracts-v4/token/ERC20/utils/SafeERC20.sol"; - -import {IL1NativeTokenVault} from "./interfaces/IL1NativeTokenVault.sol"; -import {IL1AssetHandler} from "./interfaces/IL1AssetHandler.sol"; - -import {IL1AssetRouter} from "./interfaces/IL1AssetRouter.sol"; -import {ETH_TOKEN_ADDRESS} from "../common/Config.sol"; -import {DataEncoding} from "../common/libraries/DataEncoding.sol"; - -import {BridgeHelper} from "./BridgeHelper.sol"; - -/// @author Matter Labs -/// @custom:security-contact security@matterlabs.dev -/// @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, Ownable2StepUpgradeable, PausableUpgradeable { - using SafeERC20 for IERC20; - - /// @dev The address of the WETH token on L1. - address public immutable override L1_WETH_TOKEN; - - /// @dev L1 Shared Bridge smart contract that handles communication with its counterparts on L2s - IL1AssetRouter public immutable override L1_SHARED_BRIDGE; - - /// @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 A mapping assetId => tokenAddress - mapping(bytes32 assetId => address tokenAddress) public tokenAddress; - - /// @notice Checks that the message sender is the bridge. - modifier onlyBridge() { - require(msg.sender == address(L1_SHARED_BRIDGE), "NTV not ShB"); - _; - } - - /// @dev Contract is expected to be used as proxy implementation. - /// @dev Initialize the implementation to prevent Parity hack. - constructor(address _l1WethAddress, IL1AssetRouter _l1SharedBridge) { - _disableInitializers(); - L1_WETH_TOKEN = _l1WethAddress; - L1_SHARED_BRIDGE = _l1SharedBridge; - } - - /// @dev Accepts ether only from the Shared Bridge. - receive() external payable { - require(address(L1_SHARED_BRIDGE) == msg.sender, "NTV: ETH only accepted from Shared Bridge"); - } - - /// @dev Initializes a contract for later use. Expected to be used in the proxy - /// @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 initializer { - require(_owner != address(0), "NTV owner 0"); - _transferOwnership(_owner); - } - - /// @notice Transfers tokens from shared bridge as part of the migration process. - /// @dev Both ETH and ERC20 tokens can be transferred. Exhausts balance of shared bridge after the first call. - /// @dev Calling second time for the same token will revert. - /// @param _token The address of token to be transferred (address(1) for ether and contract address for ERC20). - function transferFundsFromSharedBridge(address _token) external { - if (_token == ETH_TOKEN_ADDRESS) { - uint256 balanceBefore = address(this).balance; - L1_SHARED_BRIDGE.transferTokenToNTV(_token); - uint256 balanceAfter = address(this).balance; - require(balanceAfter > balanceBefore, "NTV: 0 eth transferred"); - } else { - uint256 balanceBefore = IERC20(_token).balanceOf(address(this)); - uint256 sharedBridgeChainBalance = IERC20(_token).balanceOf(address(L1_SHARED_BRIDGE)); - require(sharedBridgeChainBalance > 0, "NTV: 0 amount to transfer"); - L1_SHARED_BRIDGE.transferTokenToNTV(_token); - uint256 balanceAfter = IERC20(_token).balanceOf(address(this)); - require(balanceAfter - balanceBefore >= sharedBridgeChainBalance, "NTV: wrong amount transferred"); - } - } - - /// @notice Updates chain token balance within NTV to account for tokens transferred from the shared bridge (part of the migration process). - /// @dev Clears chain balance on the shared bridge after the first call. Subsequent calls will not affect the state. - /// @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 ZK chain. - function updateChainBalancesFromSharedBridge(address _token, uint256 _targetChainId) external { - uint256 sharedBridgeChainBalance = L1_SHARED_BRIDGE.chainBalance(_targetChainId, _token); - chainBalance[_targetChainId][_token] = chainBalance[_targetChainId][_token] + sharedBridgeChainBalance; - L1_SHARED_BRIDGE.nullifyChainBalanceByNTV(_targetChainId, _token); - } - - /// @notice Registers tokens within the NTV. - /// @dev The goal was to allow bridging L1 native tokens automatically, by registering them on the fly. - /// @notice Allows the bridge to register a token address for the vault. - /// @notice No access control is ok, since the bridging of tokens should be permissionless. This requires permissionless registration. - function registerToken(address _l1Token) external { - require(_l1Token != L1_WETH_TOKEN, "NTV: WETH deposit not supported"); - require(_l1Token == ETH_TOKEN_ADDRESS || _l1Token.code.length > 0, "NTV: empty token"); - bytes32 assetId = DataEncoding.encodeNTVAssetId(block.chainid, _l1Token); - L1_SHARED_BRIDGE.setAssetHandlerAddressInitial(bytes32(uint256(uint160(_l1Token))), address(this)); - tokenAddress[assetId] = _l1Token; - } - - /// @inheritdoc IL1AssetHandler - function bridgeMint( - uint256 _chainId, - bytes32 _assetId, - bytes calldata _data - ) 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)); - // Check that the chain has sufficient balance - require(chainBalance[_chainId][l1Token] >= amount, "NTV: not enough funds"); // 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) - } - require(callSuccess, "NTV: withdrawal failed, no funds or cannot transfer to receiver"); - } else { - // Withdraw funds - IERC20(l1Token).safeTransfer(l1Receiver, amount); - } - emit BridgeMint(_chainId, _assetId, l1Receiver, amount); - } - - /// @inheritdoc IL1AssetHandler - /// @notice Allows bridgehub to acquire mintValue for L1->L2 transactions. - /// @dev In case of native token vault _data is the tuple of _depositAmount and _l2Receiver. - function bridgeBurn( - uint256 _chainId, - uint256, - bytes32 _assetId, - address _prevMsgSender, - bytes calldata _data - ) external payable override onlyBridge whenNotPaused returns (bytes memory _bridgeMintData) { - (uint256 _depositAmount, address _l2Receiver) = abi.decode(_data, (uint256, address)); - - uint256 amount; - address l1Token = tokenAddress[_assetId]; - if (l1Token == ETH_TOKEN_ADDRESS) { - amount = msg.value; - - // In the old SDK/contracts the user had to always provide `0` as the deposit amount for ETH token, while - // ultimately the provided `msg.value` was used as the deposit amount. This check is needed for backwards compatibility. - if (_depositAmount == 0) { - _depositAmount = amount; - } - - require(_depositAmount == amount, "L1NTV: msg.value not equal to amount"); - } else { - // The Bridgehub also checks this, but we want to be sure - require(msg.value == 0, "NTV m.v > 0 b d.it"); - amount = _depositAmount; - - uint256 expectedDepositAmount = _depositFunds(_prevMsgSender, IERC20(l1Token), _depositAmount); // note if _prevMsgSender is this contract, this will return 0. This does not happen. - require(expectedDepositAmount == _depositAmount, "5T"); // The token has non-standard transfer logic - } - require(amount != 0, "6T"); // empty deposit amount - - chainBalance[_chainId][l1Token] += amount; - - _bridgeMintData = DataEncoding.encodeBridgeMintData({ - _prevMsgSender: _prevMsgSender, - _l2Receiver: _l2Receiver, - _l1Token: l1Token, - _amount: amount, - _erc20Metadata: getERC20Getters(l1Token) - }); - - emit BridgeBurn({ - chainId: _chainId, - assetId: _assetId, - l1Sender: _prevMsgSender, - l2receiver: _l2Receiver, - amount: amount - }); - } - - /// @inheritdoc IL1AssetHandler - function bridgeRecoverFailedTransfer( - uint256 _chainId, - bytes32 _assetId, - address _depositSender, - bytes calldata _data - ) external payable override onlyBridge whenNotPaused { - (uint256 _amount, ) = 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: not enough funds 2"); - 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(), _depositSender, _amount, 0, 0, 0, 0) - } - require(callSuccess, "NTV: claimFailedDeposit failed, no funds or cannot transfer to receiver"); - } else { - IERC20(l1Token).safeTransfer(_depositSender, _amount); - // 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. - } - } - - /// @dev Receives and parses (name, symbol, decimals) from the token contract - function getERC20Getters(address _token) public view returns (bytes memory) { - return BridgeHelper.getERC20Getters(_token, ETH_TOKEN_ADDRESS); - } - - /// @dev Transfers tokens from the depositor address to the smart contract address. - /// @return The difference between the contract balance before and after the transferring of funds. - function _depositFunds(address _from, IERC20 _token, uint256 _amount) internal returns (uint256) { - uint256 balanceBefore = _token.balanceOf(address(this)); - address from = _from; - // in the legacy scenario the SharedBridge was granting the allowance, we have to transfer from them instead of the user - if ( - _token.allowance(address(L1_SHARED_BRIDGE), address(this)) >= _amount && - _token.allowance(_from, address(this)) < _amount - ) { - from = address(L1_SHARED_BRIDGE); - } - // slither-disable-next-line arbitrary-send-erc20 - _token.safeTransferFrom(from, address(this), _amount); - uint256 balanceAfter = _token.balanceOf(address(this)); - - return balanceAfter - balanceBefore; - } - - /*////////////////////////////////////////////////////////////// - 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/state-transition/libraries/Merkle.sol b/l1-contracts/contracts/state-transition/libraries/Merkle.sol deleted file mode 100644 index 57701f338..000000000 --- a/l1-contracts/contracts/state-transition/libraries/Merkle.sol +++ /dev/null @@ -1,54 +0,0 @@ -// SPDX-License-Identifier: MIT -// We use a floating point pragma here so it can be used within other projects that interact with the ZKsync ecosystem without using our exact pragma version. -pragma solidity ^0.8.21; - -import {UncheckedMath} from "../../common/libraries/UncheckedMath.sol"; -import {MerklePathEmpty, MerklePathOutOfBounds, MerkleIndexOutOfBounds} from "../../common/L1ContractErrors.sol"; - -/// @author Matter Labs -/// @custom:security-contact security@matterlabs.dev -library Merkle { - using UncheckedMath for uint256; - - /// @dev Calculate Merkle root by the provided Merkle proof. - /// NOTE: When using this function, check that the _path length is equal to the tree height to prevent shorter/longer paths attack - /// @param _path Merkle path from the leaf to the root - /// @param _index Leaf index in the tree - /// @param _itemHash Hash of leaf content - /// @return The Merkle root - function calculateRoot( - bytes32[] calldata _path, - uint256 _index, - bytes32 _itemHash - ) internal pure returns (bytes32) { - uint256 pathLength = _path.length; - if (pathLength == 0) { - revert MerklePathEmpty(); - } - if (pathLength >= 256) { - revert MerklePathOutOfBounds(); - } - if (_index >= (1 << pathLength)) { - revert MerkleIndexOutOfBounds(); - } - - bytes32 currentHash = _itemHash; - for (uint256 i; i < pathLength; i = i.uncheckedInc()) { - currentHash = (_index % 2 == 0) - ? _efficientHash(currentHash, _path[i]) - : _efficientHash(_path[i], currentHash); - _index /= 2; - } - - return currentHash; - } - - /// @dev Keccak hash of the concatenation of two 32-byte words - function _efficientHash(bytes32 _lhs, bytes32 _rhs) private pure returns (bytes32 result) { - assembly { - mstore(0x00, _lhs) - mstore(0x20, _rhs) - result := keccak256(0x00, 0x40) - } - } -} diff --git a/l1-contracts/test/foundry/unit/concrete/state-transition/StateTransitionManager/FreezeChain.t.sol b/l1-contracts/test/foundry/unit/concrete/state-transition/StateTransitionManager/FreezeChain.t.sol deleted file mode 100644 index 20dd04e92..000000000 --- a/l1-contracts/test/foundry/unit/concrete/state-transition/StateTransitionManager/FreezeChain.t.sol +++ /dev/null @@ -1,26 +0,0 @@ -// // SPDX-License-Identifier: MIT -pragma solidity 0.8.24; - -import {StateTransitionManagerTest} from "./_StateTransitionManager_Shared.t.sol"; -import {GettersFacet} from "contracts/state-transition/chain-deps/facets/Getters.sol"; -import {IAdmin} from "contracts/state-transition/chain-interfaces/IAdmin.sol"; -import {FacetIsFrozen} from "contracts/common/L1ContractErrors.sol"; - -contract freezeChainTest is StateTransitionManagerTest { - // function test_FreezingChain() public { - // createNewChain(getDiamondCutData(diamondInit)); - // address newChainAddress = chainContractAddress.getHyperchain(chainId); - // GettersFacet gettersFacet = GettersFacet(newChainAddress); - // bool isChainFrozen = gettersFacet.isDiamondStorageFrozen(); - // assertEq(isChainFrozen, false); - // vm.stopPrank(); - // vm.startPrank(governor); - // chainContractAddress.freezeChain(block.chainid); - // // Repeated call should revert - // vm.expectRevert(bytes.concat("q1")); // storage frozen - // chainContractAddress.freezeChain(block.chainid); - // // Call fails as storage is frozen - // vm.expectRevert(bytes.concat("q1")); - // isChainFrozen = gettersFacet.isDiamondStorageFrozen(); - // } -} diff --git a/l1-contracts/test/foundry/unit/concrete/state-transition/chain-deps/facets/Getters/PriorityQueueFrontOperation.t.sol b/l1-contracts/test/foundry/unit/concrete/state-transition/chain-deps/facets/Getters/PriorityQueueFrontOperation.t.sol deleted file mode 100644 index ac8ccfeaa..000000000 --- a/l1-contracts/test/foundry/unit/concrete/state-transition/chain-deps/facets/Getters/PriorityQueueFrontOperation.t.sol +++ /dev/null @@ -1,30 +0,0 @@ -// SPDX-License-Identifier: MIT - -pragma solidity 0.8.24; - -import {GettersFacetTest} from "./_Getters_Shared.t.sol"; -import {PriorityOperation} from "contracts/state-transition/libraries/PriorityQueue.sol"; -import {QueueIsEmpty} from "contracts/common/L1ContractErrors.sol"; - -contract GetPriorityQueueFrontOperationTest is GettersFacetTest { - function test_revertWhen_queueIsEmpty() public { - vm.expectRevert(QueueIsEmpty.selector); - gettersFacet.priorityQueueFrontOperation(); - } - - function test() public { - PriorityOperation memory expected = PriorityOperation({ - canonicalTxHash: bytes32(uint256(1)), - expirationTimestamp: uint64(2), - layer2Tip: uint192(3) - }); - - gettersFacetWrapper.util_setPriorityQueueFrontOperation(expected); - - PriorityOperation memory received = gettersFacet.priorityQueueFrontOperation(); - - bytes32 expectedHash = keccak256(abi.encode(expected)); - bytes32 receivedHash = keccak256(abi.encode(received)); - assertEq(expectedHash, receivedHash, "Priority queue front operation is incorrect"); - } -} diff --git a/l1-contracts/test/foundry/unit/concrete/state-transition/libraries/Merkle/Merkle.t.sol b/l1-contracts/test/foundry/unit/concrete/state-transition/libraries/Merkle/Merkle.t.sol deleted file mode 100644 index 89514fc99..000000000 --- a/l1-contracts/test/foundry/unit/concrete/state-transition/libraries/Merkle/Merkle.t.sol +++ /dev/null @@ -1,67 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity 0.8.24; - -import {Test} from "forge-std/Test.sol"; -import {MerkleTest} from "contracts/dev-contracts/test/MerkleTest.sol"; -import {MerkleTreeNoSort} from "./MerkleTreeNoSort.sol"; -import {MerklePathEmpty, MerkleIndexOutOfBounds, MerklePathOutOfBounds} from "contracts/common/L1ContractErrors.sol"; - -contract MerkleTestTest is Test { - MerkleTreeNoSort merkleTree; - MerkleTest merkleTest; - bytes32[] elements; - bytes32 root; - - function setUp() public { - merkleTree = new MerkleTreeNoSort(); - merkleTest = new MerkleTest(); - - for (uint256 i = 0; i < 65; i++) { - elements.push(keccak256(abi.encodePacked(i))); - } - - root = merkleTree.getRoot(elements); - } - - function testElements(uint256 i) public { - vm.assume(i < elements.length); - bytes32 leaf = elements[i]; - bytes32[] memory proof = merkleTree.getProof(elements, i); - - bytes32 rootFromContract = merkleTest.calculateRoot(proof, i, leaf); - - assertEq(rootFromContract, root); - } - - function testFirstElement() public { - testElements(0); - } - - function testLastElement() public { - testElements(elements.length - 1); - } - - function testEmptyProof_shouldRevert() public { - bytes32 leaf = elements[0]; - bytes32[] memory proof; - - vm.expectRevert(MerklePathEmpty.selector); - merkleTest.calculateRoot(proof, 0, leaf); - } - - function testLeafIndexTooBig_shouldRevert() public { - bytes32 leaf = elements[0]; - bytes32[] memory proof = merkleTree.getProof(elements, 0); - - vm.expectRevert(MerkleIndexOutOfBounds.selector); - merkleTest.calculateRoot(proof, 2 ** 255, leaf); - } - - function testProofLengthTooLarge_shouldRevert() public { - bytes32 leaf = elements[0]; - bytes32[] memory proof = new bytes32[](256); - - vm.expectRevert(MerklePathOutOfBounds.selector); - merkleTest.calculateRoot(proof, 0, leaf); - } -} diff --git a/l2-contracts/contracts/bridge/L2AssetRouter.sol b/l2-contracts/contracts/bridge/L2AssetRouter.sol deleted file mode 100644 index d143517b1..000000000 --- a/l2-contracts/contracts/bridge/L2AssetRouter.sol +++ /dev/null @@ -1,198 +0,0 @@ -// SPDX-License-Identifier: MIT - -pragma solidity 0.8.20; - -import {Initializable} from "@openzeppelin/contracts/proxy/utils/Initializable.sol"; -import {UpgradeableBeacon} from "@openzeppelin/contracts/proxy/beacon/UpgradeableBeacon.sol"; - -import {IL2AssetRouter} from "./interfaces/IL2AssetRouter.sol"; -import {IL1AssetRouter} from "./interfaces/IL1AssetRouter.sol"; -import {ILegacyL2SharedBridge} from "./interfaces/ILegacyL2SharedBridge.sol"; -import {IL2AssetHandler} from "./interfaces/IL2AssetHandler.sol"; -import {IL2StandardToken} from "./interfaces/IL2StandardToken.sol"; -import {IL2NativeTokenVault} from "./interfaces/IL2NativeTokenVault.sol"; - -import {AddressAliasHelper} from "../vendor/AddressAliasHelper.sol"; -import {L2ContractHelper, L2_NATIVE_TOKEN_VAULT} from "../L2ContractHelper.sol"; -import {DataEncoding} from "../common/libraries/DataEncoding.sol"; - -import {EmptyAddress, InvalidCaller} from "../errors/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 L2AssetRouter is IL2AssetRouter, ILegacyL2SharedBridge, Initializable { - /// @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 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; - - /// @notice Deprecated. Kept for backwards compatibility. - /// @dev A mapping l2 token address => l1 token address - mapping(address l2Token => address l1Token) public override l1TokenAddress; - - /// @notice Obsolete, as all calls are performed via L1 Shared Bridge. Kept for backwards compatibility. - /// @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 The contract responsible for handling tokens native to a single chain. - IL2NativeTokenVault public nativeTokenVault; - - /// @dev A mapping of asset ID to asset handler 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. - /// @param _l1SharedBridge The address of the L1 Bridge contract. - /// @param _l1Bridge The address of the legacy L1 Bridge contract. - constructor(uint256 _eraChainId, uint256 _l1ChainId, address _l1SharedBridge, address _l1Bridge) { - ERA_CHAIN_ID = _eraChainId; - L1_CHAIN_ID = _l1ChainId; - 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; - } - } - _disableInitializers(); - } - - /// @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); - } - - /// @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, _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 asset id of the withdrawn asset - /// @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 _l1bridgeMintData = IL2AssetHandler(assetHandler).bridgeBurn({ - _chainId: L1_CHAIN_ID, - _mintValue: 0, - _assetId: _assetId, - _prevMsgSender: msg.sender, - _data: _assetData - }); - - bytes memory message = _getL1WithdrawMessage(_assetId, _l1bridgeMintData); - L2ContractHelper.sendMessageToL1(message); - - emit WithdrawalInitiatedSharedBridge(L1_CHAIN_ID, msg.sender, _assetId, _assetData); - } - - /// @notice Encodes the message for l2ToL1log sent during withdraw initialization. - /// @param _assetId The encoding of the asset on L2 which is withdrawn. - /// @param _l1bridgeMintData The calldata used by l1 asset handler to unlock tokens for recipient. - function _getL1WithdrawMessage( - bytes32 _assetId, - bytes memory _l1bridgeMintData - ) internal pure returns (bytes memory) { - // note we use the IL1SharedBridge.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(IL1AssetRouter.finalizeWithdrawal.selector, _assetId, _l1bridgeMintData); - } - - /*////////////////////////////////////////////////////////////// - LEGACY FUNCTIONS - //////////////////////////////////////////////////////////////*/ - - /// @notice Legacy finalizeDeposit. - /// @dev Finalizes the deposit and mint funds. - /// @param _l1Sender The address of token sender on L1. - /// @param _l2Receiver The address of token receiver on L2. - /// @param _l1Token The address of the token transferred. - /// @param _amount The amount of the token transferred. - /// @param _data The metadata of the token transferred. - function finalizeDeposit( - address _l1Sender, - address _l2Receiver, - address _l1Token, - uint256 _amount, - bytes calldata _data - ) external override { - bytes32 assetId = DataEncoding.encodeNTVAssetId(L1_CHAIN_ID, _l1Token); - // solhint-disable-next-line func-named-parameters - bytes memory data = DataEncoding.encodeBridgeMintData(_l1Sender, _l2Receiver, _l1Token, _amount, _data); - finalizeDeposit(assetId, data); - } - - /// @notice Initiates a withdrawal by burning funds on the contract and sending the message to L1 - /// where tokens would be unlocked. - /// @param _l1Receiver The address of token receiver on L1. - /// @param _l2Token The address of the token transferred. - /// @param _amount The amount of the token transferred. - function withdraw(address _l1Receiver, address _l2Token, uint256 _amount) external { - bytes32 assetId = DataEncoding.encodeNTVAssetId(L1_CHAIN_ID, getL1TokenAddress(_l2Token)); - bytes memory data = abi.encode(_amount, _l1Receiver); - withdraw(assetId, data); - } - - /// @notice Legacy getL1TokenAddress. - /// @param _l2Token The address of token on L2. - /// @return The address of token on L1. - function getL1TokenAddress(address _l2Token) public view returns (address) { - return IL2StandardToken(_l2Token).l1Address(); - } - - /// @notice Legacy function used for backward compatibility to return L2 wrapped token - /// @notice address corresponding to provided L1 token address and deployed through NTV. - /// @dev However, the shared bridge can use custom asset handlers such that L2 addresses differ, - /// @dev or an L1 token may not have an L2 counterpart. - /// @param _l1Token The address of token on L1. - /// @return Address of an L2 token counterpart - function l2TokenAddress(address _l1Token) public view returns (address) { - return L2_NATIVE_TOKEN_VAULT.l2TokenAddress(_l1Token); - } -} diff --git a/l2-contracts/contracts/bridge/L2NativeTokenVault.sol b/l2-contracts/contracts/bridge/L2NativeTokenVault.sol deleted file mode 100644 index 56c50d9a8..000000000 --- a/l2-contracts/contracts/bridge/L2NativeTokenVault.sol +++ /dev/null @@ -1,234 +0,0 @@ -// SPDX-License-Identifier: MIT - -pragma solidity 0.8.20; - -import {Ownable2StepUpgradeable} from "@openzeppelin/contracts-upgradeable/access/Ownable2StepUpgradeable.sol"; -import {BeaconProxy} from "@openzeppelin/contracts/proxy/beacon/BeaconProxy.sol"; -import {UpgradeableBeacon} from "@openzeppelin/contracts/proxy/beacon/UpgradeableBeacon.sol"; - -import {IL2StandardToken} from "./interfaces/IL2StandardToken.sol"; -import {IL2NativeTokenVault} from "./interfaces/IL2NativeTokenVault.sol"; - -import {L2StandardERC20} from "./L2StandardERC20.sol"; -import {L2ContractHelper, DEPLOYER_SYSTEM_CONTRACT, L2_ASSET_ROUTER, IContractDeployer} from "../L2ContractHelper.sol"; -import {SystemContractsCaller} from "../SystemContractsCaller.sol"; -import {DataEncoding} from "../common/libraries/DataEncoding.sol"; - -import {EmptyAddress, EmptyBytes32, AddressMismatch, AssetIdMismatch, DeployFailed, AmountMustBeGreaterThanZero, InvalidCaller} from "../errors/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 L2NativeTokenVault is IL2NativeTokenVault, Ownable2StepUpgradeable { - /// @dev Chain ID of L1 for bridging reasons. - uint256 public immutable L1_CHAIN_ID; - - bytes32 internal l2TokenProxyBytecodeHash; - - /// @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 l2TokenBeacon; - - mapping(bytes32 assetId => address tokenAddress) public override tokenAddress; - - /// @dev Bytecode hash of the proxy for tokens deployed by the bridge. - - modifier onlyBridge() { - if (msg.sender != address(L2_ASSET_ROUTER)) { - revert InvalidCaller(msg.sender); - // Only L2 bridge can call this method - } - _; - } - - /// @notice Initializes the bridge contract for later use. Expected to be used in the proxy. - /// @param _l1ChainId The L1 chain id differs between mainnet and testnets. - /// @param _l2TokenProxyBytecodeHash The bytecode hash of the proxy for tokens deployed by the bridge. - /// @param _aliasedOwner The address of the governor contract. - constructor(uint256 _l1ChainId, bytes32 _l2TokenProxyBytecodeHash, address _aliasedOwner) { - L1_CHAIN_ID = _l1ChainId; - - _disableInitializers(); - if (_l2TokenProxyBytecodeHash == bytes32(0)) { - revert EmptyBytes32(); - } - if (_aliasedOwner == address(0)) { - revert EmptyAddress(); - } - - l2TokenProxyBytecodeHash = _l2TokenProxyBytecodeHash; - _transferOwnership(_aliasedOwner); - } - - /// @notice Sets L2 token beacon used by wrapped ERC20 tokens deployed by NTV. - /// @dev Sets the l2TokenBeacon, called after initialize. - /// @param _l2TokenBeacon The address of L2 token beacon implementation. - /// @param _l2TokenProxyBytecodeHash The bytecode hash of the L2 token proxy that will be deployed for wrapped tokens. - function setL2TokenBeacon(address _l2TokenBeacon, bytes32 _l2TokenProxyBytecodeHash) external onlyOwner { - l2TokenBeacon = UpgradeableBeacon(_l2TokenBeacon); - l2TokenProxyBytecodeHash = _l2TokenProxyBytecodeHash; - emit L2TokenBeaconUpdated(_l2TokenBeacon, _l2TokenProxyBytecodeHash); - } - - /// @notice Configure L2 token beacon used by wrapped ERC20 tokens deployed by NTV. - /// @dev we don't call this in the constructor, as we need to provide factory deps. - /// @param _contractsDeployedAlready Ensures beacon proxy for standard ERC20 has not been deployed. - function configureL2TokenBeacon(bool _contractsDeployedAlready, address _l2TokenBeacon) external { - if (address(l2TokenBeacon) != address(0)) { - revert AddressMismatch(address(l2TokenBeacon), address(0)); - } - if (_contractsDeployedAlready) { - if (_l2TokenBeacon == address(0)) { - revert EmptyAddress(); - } - l2TokenBeacon = UpgradeableBeacon(_l2TokenBeacon); - } else { - address l2StandardToken = address(new L2StandardERC20{salt: bytes32(0)}()); - l2TokenBeacon = new UpgradeableBeacon{salt: bytes32(0)}(l2StandardToken); - l2TokenBeacon.transferOwnership(owner()); - } - } - - /// @notice Used when the chain receives a transfer from L1 Shared Bridge and correspondingly mints the asset. - /// @param _chainId The chainId that the message is from. - /// @param _assetId The assetId of the asset being bridged. - /// @param _data The abi.encoded transfer data. - function bridgeMint(uint256 _chainId, bytes32 _assetId, bytes calldata _data) external payable override onlyBridge { - address token = tokenAddress[_assetId]; - ( - address _l1Sender, - address _l2Receiver, - address originToken, - uint256 _amount, - bytes memory erc20Data - ) = DataEncoding.decodeBridgeMintData(_data); - - if (token == address(0)) { - address expectedToken = _calculateCreate2TokenAddress(originToken); - bytes32 expectedAssetId = DataEncoding.encodeNTVAssetId(L1_CHAIN_ID, originToken); - if (_assetId != expectedAssetId) { - // Make sure that a NativeTokenVault sent the message - revert AssetIdMismatch(expectedAssetId, _assetId); - } - address deployedToken = _deployL2Token(originToken, erc20Data); - if (deployedToken != expectedToken) { - revert AddressMismatch(expectedToken, deployedToken); - } - tokenAddress[_assetId] = expectedToken; - token = expectedToken; - } - - IL2StandardToken(token).bridgeMint(_l2Receiver, _amount); - /// backwards compatible event - emit FinalizeDeposit(_l1Sender, _l2Receiver, token, _amount); - emit BridgeMint({ - chainId: _chainId, - assetId: _assetId, - sender: _l1Sender, - l2Receiver: _l2Receiver, - amount: _amount - }); - } - - /// @notice Burns wrapped tokens and returns the calldata for L2 -> L1 message. - /// @dev In case of native token vault _data is the tuple of _depositAmount and _l2Receiver. - /// @param _chainId The chainId that the message will be sent to. - /// @param _mintValue The L1 base token value bridged. - /// @param _assetId The L2 assetId of the asset being bridged. - /// @param _prevMsgSender The original caller of the shared bridge. - /// @param _data The abi.encoded transfer data. - /// @return l1BridgeMintData The calldata used by l1 asset handler to unlock tokens for recipient. - function bridgeBurn( - uint256 _chainId, - uint256 _mintValue, - bytes32 _assetId, - address _prevMsgSender, - bytes calldata _data - ) external payable override onlyBridge returns (bytes memory l1BridgeMintData) { - (uint256 _amount, address _l1Receiver) = abi.decode(_data, (uint256, address)); - if (_amount == 0) { - // "Amount cannot be zero"); - revert AmountMustBeGreaterThanZero(); - } - - address l2Token = tokenAddress[_assetId]; - IL2StandardToken(l2Token).bridgeBurn(_prevMsgSender, _amount); - - /// backwards compatible event - emit WithdrawalInitiated(_prevMsgSender, _l1Receiver, l2Token, _amount); - emit BridgeBurn({ - chainId: _chainId, - assetId: _assetId, - l2Sender: _prevMsgSender, - receiver: _l1Receiver, - mintValue: _mintValue, - amount: _amount - }); - l1BridgeMintData = _data; - } - - /// @notice Calculates L2 wrapped token address corresponding to L1 token counterpart. - /// @param _l1Token The address of token on L1. - /// @return expectedToken The address of token on L2. - function l2TokenAddress(address _l1Token) public view override returns (address expectedToken) { - bytes32 expectedAssetId = DataEncoding.encodeNTVAssetId(L1_CHAIN_ID, _l1Token); - expectedToken = tokenAddress[expectedAssetId]; - if (expectedToken == address(0)) { - expectedToken = _calculateCreate2TokenAddress(_l1Token); - } - } - - /// @notice Deploys and initializes the L2 token for the L1 counterpart. - /// @param _l1Token The address of token on L1. - /// @param _erc20Data The ERC20 metadata of the token deployed. - /// @return The address of the beacon proxy (L2 wrapped / bridged token). - function _deployL2Token(address _l1Token, bytes memory _erc20Data) internal returns (address) { - bytes32 salt = _getCreate2Salt(_l1Token); - - BeaconProxy l2Token = _deployBeaconProxy(salt); - L2StandardERC20(address(l2Token)).bridgeInitialize(_l1Token, _erc20Data); - - return address(l2Token); - } - - /// @notice Deploys the beacon proxy for the L2 token, while using ContractDeployer system contract. - /// @dev This function uses raw call to ContractDeployer to make sure that exactly `l2TokenProxyBytecodeHash` is used - /// for the code of the proxy. - /// @param salt The salt used for beacon proxy deployment of L2 wrapped token. - /// @return proxy The beacon proxy, i.e. L2 wrapped / bridged token. - function _deployBeaconProxy(bytes32 salt) internal returns (BeaconProxy proxy) { - (bool success, bytes memory returndata) = SystemContractsCaller.systemCallWithReturndata( - uint32(gasleft()), - DEPLOYER_SYSTEM_CONTRACT, - 0, - abi.encodeCall( - IContractDeployer.create2, - (salt, l2TokenProxyBytecodeHash, abi.encode(address(l2TokenBeacon), "")) - ) - ); - - // The deployment should be successful and return the address of the proxy - if (!success) { - revert DeployFailed(); - } - proxy = BeaconProxy(abi.decode(returndata, (address))); - } - - /// @notice Calculates L2 wrapped token address given the currently stored beacon proxy bytecode hash and beacon address. - /// @param _l1Token The address of token on L1. - /// @return Address of an L2 token counterpart. - function _calculateCreate2TokenAddress(address _l1Token) internal view returns (address) { - bytes32 constructorInputHash = keccak256(abi.encode(address(l2TokenBeacon), "")); - bytes32 salt = _getCreate2Salt(_l1Token); - return - L2ContractHelper.computeCreate2Address(address(this), salt, l2TokenProxyBytecodeHash, constructorInputHash); - } - - /// @notice Converts the L1 token address to the create2 salt of deployed L2 token. - /// @param _l1Token The address of token on L1. - /// @return salt The salt used to compute address of wrapped token on L2 and for beacon proxy deployment. - function _getCreate2Salt(address _l1Token) internal pure returns (bytes32 salt) { - salt = bytes32(uint256(uint160(_l1Token))); - } -} diff --git a/l2-contracts/contracts/dev-contracts/DevL2SharedBridge.sol b/l2-contracts/contracts/dev-contracts/DevL2SharedBridge.sol deleted file mode 100644 index e93d5c987..000000000 --- a/l2-contracts/contracts/dev-contracts/DevL2SharedBridge.sol +++ /dev/null @@ -1,33 +0,0 @@ -// SPDX-License-Identifier: MIT - -pragma solidity 0.8.24; - -import {L2SharedBridge} from "../bridge/L2SharedBridge.sol"; -import {L2StandardERC20} from "../bridge/L2StandardERC20.sol"; -import {UpgradeableBeacon} from "@openzeppelin/contracts-v4/proxy/beacon/UpgradeableBeacon.sol"; - -/// @author Matter Labs -/// @notice The implementation of the shared bridge that allows setting legacy bridge. Must only be used in local testing environments. -contract DevL2SharedBridge is L2SharedBridge { - constructor(uint256 _eraChainId) L2SharedBridge(_eraChainId) {} - - function initializeDevBridge( - address _l1SharedBridge, - address _l1Bridge, - bytes32 _l2TokenProxyBytecodeHash, - address _aliasedOwner - ) external reinitializer(2) { - l1SharedBridge = _l1SharedBridge; - - address l2StandardToken = address(new L2StandardERC20{salt: bytes32(0)}()); - l2TokenBeacon = new UpgradeableBeacon{salt: bytes32(0)}(l2StandardToken); - l2TokenProxyBytecodeHash = _l2TokenProxyBytecodeHash; - l2TokenBeacon.transferOwnership(_aliasedOwner); - - // Unfortunately the `l1Bridge` is not an internal variable in the parent contract. - // To keep the changes to the production code minimal, we'll just manually set the variable here. - assembly { - sstore(4, _l1Bridge) - } - } -} 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 deleted file mode 100644 index 433365064..000000000 --- a/l2-contracts/src/deploy-shared-bridge-on-l2-through-l1.ts +++ /dev/null @@ -1,154 +0,0 @@ -import { Command } from "commander"; -import type { BigNumberish } from "ethers"; -import { Wallet, ethers } from "ethers"; -import { formatUnits, parseUnits } from "ethers/lib/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; - -export async function publishL2NativeTokenVaultDependencyBytecodesOnL2( - deployer: Deployer, - chainId: string, - gasPrice: BigNumberish -) { - if (deployer.verbose) { - console.log("Providing necessary L2 bytecodes"); - } - - const L2_STANDARD_ERC20_PROXY_FACTORY_BYTECODE = hre.artifacts.readArtifactSync("UpgradeableBeacon").bytecode; - const L2_STANDARD_ERC20_IMPLEMENTATION_BYTECODE = hre.artifacts.readArtifactSync("L2StandardERC20").bytecode; - - const receipt = await ( - await publishBytecodeFromL1( - chainId, - deployer.deployWallet, - [ - L2_STANDARD_ERC20_PROXY_FACTORY_BYTECODE, - L2_STANDARD_ERC20_IMPLEMENTATION_BYTECODE, - L2_STANDARD_TOKEN_PROXY_BYTECODE, - ], - gasPrice - ) - ).wait(); - - if (deployer.verbose) { - console.log("Bytecodes published on L2, hash: ", receipt.transactionHash); - } -} - -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", [false, ethers.constants.AddressZero]), - 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() { - const program = new Command(); - - program.version("0.1.0").name("deploy-shared-bridge-on-l2-through-l1"); - - program - .option("--private-key ") - .option("--chain-id ") - .option("--local-legacy-bridge-testing") - .option("--gas-price ") - .option("--nonce ") - .option("--erc20-bridge ") - .option("--skip-initialize-chain-governance ") - .action(async (cmd) => { - const chainId: string = cmd.chainId ? cmd.chainId : process.env.CHAIN_ETH_ZKSYNC_NETWORK_ID; - const deployWallet = cmd.privateKey - ? new Wallet(cmd.privateKey, provider) - : Wallet.fromMnemonic( - process.env.MNEMONIC ? process.env.MNEMONIC : ethTestConfig.mnemonic, - "m/44'/60'/0'/0/1" - ).connect(provider); - console.log(`Using deployer wallet: ${deployWallet.address}`); - - const deployer = new Deployer({ - deployWallet, - ownerAddress: deployWallet.address, - verbose: true, - }); - - const nonce = cmd.nonce ? parseInt(cmd.nonce) : await deployer.deployWallet.getTransactionCount(); - console.log(`Using nonce: ${nonce}`); - - const gasPrice = cmd.gasPrice - ? parseUnits(cmd.gasPrice, "gwei") - : (await provider.getGasPrice()).mul(GAS_MULTIPLIER); - console.log(`Using gas price: ${formatUnits(gasPrice, "gwei")} gwei`); - - const skipInitializeChainGovernance = - !!cmd.skipInitializeChainGovernance && cmd.skipInitializeChainGovernance === "true"; - if (skipInitializeChainGovernance) { - console.log("Initialization of the chain governance will be skipped"); - } - - await deploySharedBridgeOnL2ThroughL1(deployer, chainId, gasPrice); - }); - - await program.parseAsync(process.argv); -} - -main() - .then(() => process.exit(0)) - .catch((err) => { - console.error("Error:", err); - process.exit(1); - }); diff --git a/l2-contracts/test/erc20.test.ts b/l2-contracts/test/erc20.test.ts deleted file mode 100644 index ec531f7aa..000000000 --- a/l2-contracts/test/erc20.test.ts +++ /dev/null @@ -1,169 +0,0 @@ -import { Deployer } from "@matterlabs/hardhat-zksync-deploy"; -import { expect } from "chai"; -import { ethers } from "ethers"; -import * as hre from "hardhat"; -import { Provider, Wallet } from "zksync-ethers"; -import { hashBytecode } from "zksync-ethers/build/utils"; -import { unapplyL1ToL2Alias, setCode } from "./test-utils"; -import type { L2AssetRouter, L2NativeTokenVault, L2StandardERC20 } from "../typechain"; -import { L2AssetRouterFactory, L2NativeTokenVaultFactory, L2StandardERC20Factory } from "../typechain"; - -const richAccount = [ - { - address: "0x36615Cf349d7F6344891B1e7CA7C72883F5dc049", - privateKey: "0x7726827caac94a7f9e1b160f7ea819f172f7b6f9d2a97f992c38edeab82d4110", - }, - { - address: "0xa61464658AfeAf65CccaaFD3a512b69A83B77618", - privateKey: "0xac1e735be8536c6534bb4f17f06f6afc73b2b5ba84ac2cfb12f7461b20c0bbe3", - }, - { - address: "0x0D43eB5B8a47bA8900d84AA36656c92024e9772e", - privateKey: "0xd293c684d884d56f8d6abd64fc76757d3664904e309a0645baf8522ab6366d9e", - }, - { - address: "0xA13c10C0D5bd6f79041B9835c63f91de35A15883", - privateKey: "0x850683b40d4a740aa6e745f889a6fdc8327be76e122f5aba645a5b02d0248db8", - }, -]; - -describe("ERC20Bridge", function () { - const provider = new Provider(hre.config.networks.localhost.url); - const deployerWallet = new Wallet(richAccount[0].privateKey, provider); - const governorWallet = new Wallet(richAccount[1].privateKey, provider); - const proxyAdminWallet = new Wallet(richAccount[3].privateKey, provider); - - // We need to emulate a L1->L2 transaction from the L1 bridge to L2 counterpart. - // It is a bit easier to use EOA and it is sufficient for the tests. - const l1BridgeWallet = new Wallet(richAccount[2].privateKey, provider); - - // We won't actually deploy an L1 token in these tests, but we need some address for it. - const L1_TOKEN_ADDRESS = "0x1111000000000000000000000000000000001111"; - const L2_ASSET_ROUTER_ADDRESS = "0x0000000000000000000000000000000000010003"; - const L2_NATIVE_TOKEN_VAULT_ADDRESS = "0x0000000000000000000000000000000000010004"; - - const testChainId = 9; - - let erc20Bridge: L2AssetRouter; - let erc20NativeTokenVault: L2NativeTokenVault; - let erc20Token: L2StandardERC20; - const contractsDeployedAlready: boolean = false; - - before("Deploy token and bridge", async function () { - const deployer = new Deployer(hre, deployerWallet); - - // While we formally don't need to deploy the token and the beacon proxy, it is a neat way to have the bytecode published - const l2TokenImplAddress = await deployer.deploy(await deployer.loadArtifact("L2StandardERC20")); - const l2Erc20TokenBeacon = await deployer.deploy(await deployer.loadArtifact("UpgradeableBeacon"), [ - l2TokenImplAddress.address, - ]); - await deployer.deploy(await deployer.loadArtifact("BeaconProxy"), [l2Erc20TokenBeacon.address, "0x"]); - const beaconProxyBytecodeHash = hashBytecode((await deployer.loadArtifact("BeaconProxy")).bytecode); - let constructorArgs = ethers.utils.defaultAbiCoder.encode( - ["uint256", "uint256", "address", "address"], - /// note in real deployment we have to transfer ownership of standard deployer here - [testChainId, 1, unapplyL1ToL2Alias(l1BridgeWallet.address), unapplyL1ToL2Alias(l1BridgeWallet.address)] - ); - await setCode( - deployerWallet, - L2_ASSET_ROUTER_ADDRESS, - (await deployer.loadArtifact("L2AssetRouter")).bytecode, - true, - constructorArgs - ); - - erc20Bridge = L2AssetRouterFactory.connect(L2_ASSET_ROUTER_ADDRESS, deployerWallet); - const l2NativeTokenVaultArtifact = await deployer.loadArtifact("L2NativeTokenVault"); - constructorArgs = ethers.utils.defaultAbiCoder.encode( - ["uint256", "bytes32", "address", "bool"], - /// note in real deployment we have to transfer ownership of standard deployer here - [1, beaconProxyBytecodeHash, governorWallet.address, contractsDeployedAlready] - ); - await setCode( - deployerWallet, - L2_NATIVE_TOKEN_VAULT_ADDRESS, - l2NativeTokenVaultArtifact.bytecode, - true, - constructorArgs - ); - - erc20NativeTokenVault = L2NativeTokenVaultFactory.connect(L2_NATIVE_TOKEN_VAULT_ADDRESS, l1BridgeWallet); - const governorNTV = L2NativeTokenVaultFactory.connect(L2_NATIVE_TOKEN_VAULT_ADDRESS, governorWallet); - await governorNTV.configureL2TokenBeacon(false, ethers.constants.AddressZero); - }); - - it("Should finalize deposit ERC20 deposit", async function () { - const erc20BridgeWithL1BridgeWallet = L2AssetRouterFactory.connect(erc20Bridge.address, proxyAdminWallet); - const l1Depositor = ethers.Wallet.createRandom(); - const l2Receiver = ethers.Wallet.createRandom(); - const l1Bridge = await hre.ethers.getImpersonatedSigner(l1BridgeWallet.address); - const tx = await ( - await erc20BridgeWithL1BridgeWallet.connect(l1Bridge)["finalizeDeposit(address,address,address,uint256,bytes)"]( - // Depositor and l2Receiver can be any here - l1Depositor.address, - l2Receiver.address, - L1_TOKEN_ADDRESS, - 100, - encodedTokenData("TestToken", "TT", 18) - ) - ).wait(); - const l2TokenInfo = tx.events.find((event) => event.event === "FinalizeDepositSharedBridge").args.assetId; - const l2TokenAddress = await erc20NativeTokenVault.tokenAddress(l2TokenInfo); - // Checking the correctness of the balance: - erc20Token = L2StandardERC20Factory.connect(l2TokenAddress, deployerWallet); - expect(await erc20Token.balanceOf(l2Receiver.address)).to.equal(100); - expect(await erc20Token.totalSupply()).to.equal(100); - expect(await erc20Token.name()).to.equal("TestToken"); - expect(await erc20Token.symbol()).to.equal("TT"); - expect(await erc20Token.decimals()).to.equal(18); - }); - - it("Governance should be able to reinitialize the token", async () => { - const erc20TokenWithGovernor = L2StandardERC20Factory.connect(erc20Token.address, governorWallet); - - await ( - await erc20TokenWithGovernor.reinitializeToken( - { - ignoreName: false, - ignoreSymbol: false, - ignoreDecimals: false, - }, - "TestTokenNewName", - "TTN", - 2 - ) - ).wait(); - - expect(await erc20Token.name()).to.equal("TestTokenNewName"); - expect(await erc20Token.symbol()).to.equal("TTN"); - // The decimals should stay the same - expect(await erc20Token.decimals()).to.equal(18); - }); - - it("Governance should not be able to skip initializer versions", async () => { - const erc20TokenWithGovernor = L2StandardERC20Factory.connect(erc20Token.address, governorWallet); - - await expect( - erc20TokenWithGovernor.reinitializeToken( - { - ignoreName: false, - ignoreSymbol: false, - ignoreDecimals: false, - }, - "TestTokenNewName", - "TTN", - 20, - { gasLimit: 10000000 } - ) - ).to.be.reverted; - }); -}); - -function encodedTokenData(name: string, symbol: string, decimals: number) { - const abiCoder = ethers.utils.defaultAbiCoder; - const encodedName = abiCoder.encode(["string"], [name]); - const encodedSymbol = abiCoder.encode(["string"], [symbol]); - const encodedDecimals = abiCoder.encode(["uint8"], [decimals]); - - return abiCoder.encode(["bytes", "bytes", "bytes"], [encodedName, encodedSymbol, encodedDecimals]); -} From e5727a01e6db06743d77f85521afa12f07e3d257 Mon Sep 17 00:00:00 2001 From: Stanislav Bezkorovainyi Date: Wed, 11 Sep 2024 17:52:01 +0200 Subject: [PATCH 03/18] Sync audit head with base (#797) From 6a8dc33be18ce8c75518a2494410289afbfba263 Mon Sep 17 00:00:00 2001 From: Stanislav Breadless Date: Wed, 25 Sep 2024 12:28:16 +0200 Subject: [PATCH 04/18] better governance protection --- .../contracts/common/L1ContractErrors.sol | 4 + .../governance/IPermanentRestriction.sol | 3 + .../contracts/governance/L2AdminFactory.sol | 43 +++++++ .../governance/PermanentRestriction.sol | 113 +++++++++++++++++- 4 files changed, 159 insertions(+), 4 deletions(-) create mode 100644 l1-contracts/contracts/governance/L2AdminFactory.sol diff --git a/l1-contracts/contracts/common/L1ContractErrors.sol b/l1-contracts/contracts/common/L1ContractErrors.sol index 18766497f..4e4a71ee7 100644 --- a/l1-contracts/contracts/common/L1ContractErrors.sol +++ b/l1-contracts/contracts/common/L1ContractErrors.sol @@ -407,6 +407,10 @@ error IncorrectBatchBounds( ); // 0x64107968 error AssetHandlerNotRegistered(bytes32 assetId); +// 0x10f30e75 +error NotBridgehub(address addr); +// 0x2554babc +error InvalidAddress(address expected, address actual); enum SharedBridgeKey { PostUpgradeFirstBatch, diff --git a/l1-contracts/contracts/governance/IPermanentRestriction.sol b/l1-contracts/contracts/governance/IPermanentRestriction.sol index 548866b9f..7169ddf14 100644 --- a/l1-contracts/contracts/governance/IPermanentRestriction.sol +++ b/l1-contracts/contracts/governance/IPermanentRestriction.sol @@ -14,4 +14,7 @@ interface IPermanentRestriction { /// @notice Emitted when the selector is labeled as validated or not. event SelectorValidationChanged(bytes4 indexed selector, bool isValidated); + + /// @notice Emitted when the L2 admin is whitelisted or not. + event WhitelistL2Admin(address indexed adminAddress, bool isWhitelisted); } diff --git a/l1-contracts/contracts/governance/L2AdminFactory.sol b/l1-contracts/contracts/governance/L2AdminFactory.sol new file mode 100644 index 000000000..f3dba098e --- /dev/null +++ b/l1-contracts/contracts/governance/L2AdminFactory.sol @@ -0,0 +1,43 @@ +// SPDX-License-Identifier: MIT + +pragma solidity 0.8.24; + +import {ChainAdmin} from "./ChainAdmin.sol"; + +/// @author Matter Labs +/// @custom:security-contact security@matterlabs.dev +/// @dev Contract used to deploy ChainAdmin contracts on L2. +/// @dev It can be used to ensure that certain L2 admins are deployed with +/// predefined restrictions. E.g. it can be used to deploy admins that ensure that +/// a chain is a permanent rollup. +/// @dev This contract is expected to be deployed in zkEVM (L2) environment. +/// @dev The contract is immutable, in case the restrictions need to be changed, +/// a new contract should be deployed. +contract L2AdminFactory { + event AdminDeployed(address admin); + + /// @dev We use storage instead of immutable variables due to the + /// specifics of the zkEVM environment, where storage is actually cheaper. + address[] public requiredRestrictions; + + constructor(address[] memory _requiredRestrictions) { + requiredRestrictions = _requiredRestrictions; + } + + /// @notice Deploys a new L2 admin contract. + /// @return admin The address of the deployed admin contract. + function deployAdmin( + address[] memory _additionalRestrictions, + bytes32 _salt + ) external returns (address admin) { + address[] memory restrictions = new address[](requiredRestrictions.length + _additionalRestrictions.length); + for (uint256 i = 0; i < requiredRestrictions.length; i++) { + restrictions[i] = requiredRestrictions[i]; + } + for (uint256 i = 0; i < _additionalRestrictions.length; i++) { + restrictions[requiredRestrictions.length + i] = _additionalRestrictions[i]; + } + + admin = address(new ChainAdmin{salt: _salt}(restrictions)); + } +} diff --git a/l1-contracts/contracts/governance/PermanentRestriction.sol b/l1-contracts/contracts/governance/PermanentRestriction.sol index d013a4de6..650a289a8 100644 --- a/l1-contracts/contracts/governance/PermanentRestriction.sol +++ b/l1-contracts/contracts/governance/PermanentRestriction.sol @@ -2,9 +2,12 @@ pragma solidity 0.8.24; -import {CallNotAllowed, ChainZeroAddress, NotAHyperchain, NotAnAdmin, RemovingPermanentRestriction, ZeroAddress, UnallowedImplementation} from "../common/L1ContractErrors.sol"; +import {UnsupportedEncodingVersion, CallNotAllowed, ChainZeroAddress, NotAHyperchain, NotAnAdmin, RemovingPermanentRestriction, ZeroAddress, UnallowedImplementation, AlreadyWhitelisted, NotWhitelisted, NotBridgehub, InvalidSelector, InvalidAddress} from "../common/L1ContractErrors.sol"; -import {Ownable2Step} from "@openzeppelin/contracts-v4/access/Ownable2Step.sol"; +import {L2TransactionRequestTwoBridgesOuter, BridgehubBurnCTMAssetData} from "../bridgehub/IBridgehub.sol"; +import {Ownable2StepUpgradeable} from "@openzeppelin/contracts-upgradeable-v4/access/Ownable2StepUpgradeable.sol"; +import {L2ContractHelper} from "../common/libraries/L2ContractHelper.sol"; +import {NEW_ENCODING_VERSION} from "../bridge/asset-router/IAssetRouterBase.sol"; import {Call} from "./Common.sol"; import {IRestriction} from "./IRestriction.sol"; @@ -22,10 +25,19 @@ import {IPermanentRestriction} from "./IPermanentRestriction.sol"; /// properties are preserved forever. /// @dev To be deployed as a transparent upgradable proxy, owned by a trusted decentralized governance. /// @dev Once of the instances of such contract is to ensure that a ZkSyncHyperchain is a rollup forever. -contract PermanentRestriction is IRestriction, IPermanentRestriction, Ownable2Step { +contract PermanentRestriction is IRestriction, IPermanentRestriction, Ownable2StepUpgradeable { /// @notice The address of the Bridgehub contract. IBridgehub public immutable BRIDGE_HUB; + /// @notice The address of the L2 admin factory that should be used to deploy the chain admins + /// for chains that migrated on top of an L2 settlement layer. + /// @dev If this contract is deployed on L2, this address is 0. + /// @dev This address is expected to be the same on all L2 chains. + address public immutable L2_ADMIN_FACTORY; + + /// @notice The bytecode hash of the L2 chain admin contract. + bytes32 public immutable L2_CHAIN_ADMIN_BYTECODE_HASH; + /// @notice The mapping of the allowed admin implementations. mapping(bytes32 implementationCodeHash => bool isAllowed) public allowedAdminImplementations; @@ -35,9 +47,20 @@ contract PermanentRestriction is IRestriction, IPermanentRestriction, Ownable2St /// @notice The mapping of the validated selectors. mapping(bytes4 selector => bool isValidated) public validatedSelectors; - constructor(address _initialOwner, IBridgehub _bridgehub) { + /// @notice The mapping of whitelisted L2 admins. + mapping(address adminAddress => bool isWhitelisted) public whitelistedL2Admins; + + constructor( + IBridgehub _bridgehub, + address _l2AdminFactory, + bytes32 _l2ChainAdminBytecodeHash + ) { BRIDGE_HUB = _bridgehub; + L2_ADMIN_FACTORY = _l2AdminFactory; + L2_CHAIN_ADMIN_BYTECODE_HASH = _l2ChainAdminBytecodeHash; + } + function initialize(address _initialOwner) initializer external { // solhint-disable-next-line gas-custom-errors, reason-string if (_initialOwner == address(0)) { revert ZeroAddress(); @@ -71,6 +94,27 @@ contract PermanentRestriction is IRestriction, IPermanentRestriction, Ownable2St emit SelectorValidationChanged(_selector, _isValidated); } + + /// @notice Whitelists a certain L2 admin. + /// @param deploymentSalt The salt for the deployment. + /// @param constructorInputHash The hash of the constructor data for the deployment. + function whitelistL2Admin(bytes32 deploymentSalt, bytes32 constructorInputHash) external { + // We do not do any additional validations for constructor data, + // we expect that only admins of the allowed format are to be deployed. + address expectedAddress = L2ContractHelper.computeCreate2Address( + L2_ADMIN_FACTORY, + deploymentSalt, + L2_CHAIN_ADMIN_BYTECODE_HASH, + constructorInputHash + ); + + if (whitelistedL2Admins[expectedAddress]) { + revert AlreadyWhitelisted(expectedAddress); + } + + whitelistedL2Admins[expectedAddress] = true; + emit WhitelistL2Admin(expectedAddress, true); + } /// @inheritdoc IRestriction function validateCall( @@ -78,9 +122,26 @@ contract PermanentRestriction is IRestriction, IPermanentRestriction, Ownable2St address // _invoker ) external view override { _validateAsChainAdmin(_call); + _validateMigrationToL2(_call); _validateRemoveRestriction(_call); } + /// @notice Validates the migration to an L2 settlement layer. + /// @param _call The call data. + /// @dev Note that we do not need to validate the migration to the L1 layer as the admin + /// is not changed in this case. + function _validateMigrationToL2( + Call calldata _call + ) internal view { + try this.tryGetNewAdminFromMigration(_call) returns (address admin) { + if(!whitelistedL2Admins[admin]) { + revert NotWhitelisted(admin); + } + } catch { + // It was not the migration call, so we do nothing + } + } + /// @notice Validates the call as the chain admin /// @param _call The call data. function _validateAsChainAdmin(Call calldata _call) internal view { @@ -183,4 +244,48 @@ contract PermanentRestriction is IRestriction, IPermanentRestriction, Ownable2St revert NotAnAdmin(admin, _potentialAdmin); } } + + /// @notice Tries to get the new admin from the migration. + /// @param _call The call data. + /// @dev This function reverts if the provided call was not a migration call. + function tryGetNewAdminFromMigration(Call calldata _call) external view returns (address) { + if (_call.target != address(BRIDGE_HUB)) { + revert NotBridgehub(_call.target); + } + + if (bytes4(_call.data[:4]) != IBridgehub.requestL2TransactionTwoBridges.selector) { + revert InvalidSelector(bytes4(_call.data[:4])); + } + + address sharedBridge = BRIDGE_HUB.sharedBridge(); + + L2TransactionRequestTwoBridgesOuter memory request = abi.decode(_call.data[4:], (L2TransactionRequestTwoBridgesOuter)); + + if (request.secondBridgeAddress != sharedBridge) { + revert InvalidAddress(request.secondBridgeAddress, sharedBridge); + } + + bytes memory secondBridgeData = request.secondBridgeCalldata; + if (secondBridgeData[0] != NEW_ENCODING_VERSION) { + revert UnsupportedEncodingVersion(); + } + bytes memory encodedData = new bytes(secondBridgeData.length - 1); + assembly { + mcopy(add(encodedData, 0x20), add(secondBridgeData, 0x21), mload(encodedData)) + } + + (bytes32 chainAssetId, bytes memory bridgehubData) = abi.decode(encodedData, (bytes32, bytes)); + // We will just check that the chainAssetId is a valid chainAssetId. + // For now, for simplicity, we do not check that the admin is exactly the admin + // of this chain. + address ctmAddress = BRIDGE_HUB.ctmAssetIdToAddress(chainAssetId); + if (ctmAddress == address(0)) { + revert ZeroAddress(); + } + + BridgehubBurnCTMAssetData memory burnData = abi.decode(bridgehubData, (BridgehubBurnCTMAssetData)); + (address l2Admin, ) = abi.decode(burnData.ctmData, (address, bytes)); + + return l2Admin; + } } From 82a2639c1500ad2dafccd3efc04902f2b7df409c Mon Sep 17 00:00:00 2001 From: Stanislav Breadless Date: Wed, 25 Sep 2024 12:43:01 +0200 Subject: [PATCH 05/18] fix old tests --- .../governance/PermanentRestriction.sol | 18 +++++++------- .../Governance/PermanentRestriction.t.sol | 24 +++++++++++++++++-- 2 files changed, 31 insertions(+), 11 deletions(-) diff --git a/l1-contracts/contracts/governance/PermanentRestriction.sol b/l1-contracts/contracts/governance/PermanentRestriction.sol index 650a289a8..12e2e5d4c 100644 --- a/l1-contracts/contracts/governance/PermanentRestriction.sol +++ b/l1-contracts/contracts/governance/PermanentRestriction.sol @@ -35,9 +35,6 @@ contract PermanentRestriction is IRestriction, IPermanentRestriction, Ownable2St /// @dev This address is expected to be the same on all L2 chains. address public immutable L2_ADMIN_FACTORY; - /// @notice The bytecode hash of the L2 chain admin contract. - bytes32 public immutable L2_CHAIN_ADMIN_BYTECODE_HASH; - /// @notice The mapping of the allowed admin implementations. mapping(bytes32 implementationCodeHash => bool isAllowed) public allowedAdminImplementations; @@ -52,12 +49,10 @@ contract PermanentRestriction is IRestriction, IPermanentRestriction, Ownable2St constructor( IBridgehub _bridgehub, - address _l2AdminFactory, - bytes32 _l2ChainAdminBytecodeHash + address _l2AdminFactory ) { BRIDGE_HUB = _bridgehub; L2_ADMIN_FACTORY = _l2AdminFactory; - L2_CHAIN_ADMIN_BYTECODE_HASH = _l2ChainAdminBytecodeHash; } function initialize(address _initialOwner) initializer external { @@ -97,14 +92,19 @@ contract PermanentRestriction is IRestriction, IPermanentRestriction, Ownable2St /// @notice Whitelists a certain L2 admin. /// @param deploymentSalt The salt for the deployment. + /// @param l2BytecodeHash The hash of the L2 bytecode. /// @param constructorInputHash The hash of the constructor data for the deployment. - function whitelistL2Admin(bytes32 deploymentSalt, bytes32 constructorInputHash) external { - // We do not do any additional validations for constructor data, + function whitelistL2Admin( + bytes32 deploymentSalt, + bytes32 l2BytecodeHash, + bytes32 constructorInputHash + ) external { + // We do not do any additional validations for constructor data or the bytecode, // we expect that only admins of the allowed format are to be deployed. address expectedAddress = L2ContractHelper.computeCreate2Address( L2_ADMIN_FACTORY, deploymentSalt, - L2_CHAIN_ADMIN_BYTECODE_HASH, + l2BytecodeHash, constructorInputHash ); diff --git a/l1-contracts/test/foundry/l1/unit/concrete/Governance/PermanentRestriction.t.sol b/l1-contracts/test/foundry/l1/unit/concrete/Governance/PermanentRestriction.t.sol index f97ecc08a..c8da87080 100644 --- a/l1-contracts/test/foundry/l1/unit/concrete/Governance/PermanentRestriction.t.sol +++ b/l1-contracts/test/foundry/l1/unit/concrete/Governance/PermanentRestriction.t.sol @@ -1,6 +1,7 @@ pragma solidity 0.8.24; import "@openzeppelin/contracts-v4/utils/Strings.sol"; +import {TransparentUpgradeableProxy} from "@openzeppelin/contracts-v4/proxy/transparent/TransparentUpgradeableProxy.sol"; import {Bridgehub} from "contracts/bridgehub/Bridgehub.sol"; import {Diamond} from "contracts/state-transition/libraries/Diamond.sol"; import {ChainTypeManager} from "contracts/state-transition/ChainTypeManager.sol"; @@ -22,12 +23,15 @@ import {IMessageRoot} from "contracts/bridgehub/IMessageRoot.sol"; import {MessageRoot} from "contracts/bridgehub/MessageRoot.sol"; import {IL1AssetRouter} from "contracts/bridge/asset-router/IL1AssetRouter.sol"; import {IL1Nullifier} from "contracts/bridge/interfaces/IL1Nullifier.sol"; +import {IBridgehub} from "contracts/bridgehub/IBridgehub.sol"; contract PermanentRestrictionTest is ChainTypeManagerTest { ChainAdmin internal chainAdmin; AccessControlRestriction internal restriction; PermanentRestriction internal permRestriction; + address constant L2_FACTORY_ADDR = address(0); + address internal owner; address internal hyperchain; @@ -40,16 +44,32 @@ contract PermanentRestrictionTest is ChainTypeManagerTest { owner = makeAddr("owner"); hyperchain = chainContractAddress.getHyperchain(chainId); - permRestriction = new PermanentRestriction(owner, bridgehub); + (permRestriction, ) = _deployPermRestriction( + bridgehub, + L2_FACTORY_ADDR, + owner + ); restriction = new AccessControlRestriction(0, owner); address[] memory restrictions = new address[](1); restrictions[0] = address(restriction); chainAdmin = new ChainAdmin(restrictions); } + function _deployPermRestriction( + IBridgehub _bridgehub, + address _l2AdminFactory, + address _owner + ) internal returns (PermanentRestriction proxy, PermanentRestriction impl) { + impl = new PermanentRestriction(_bridgehub, _l2AdminFactory); + TransparentUpgradeableProxy tup = new TransparentUpgradeableProxy(address(impl), address(uint160(1)), abi.encodeCall(PermanentRestriction.initialize, (_owner))); + + proxy = PermanentRestriction(address(tup)); + } + function test_ownerAsAddressZero() public { + PermanentRestriction impl = new PermanentRestriction(bridgehub, L2_FACTORY_ADDR); vm.expectRevert(ZeroAddress.selector); - permRestriction = new PermanentRestriction(address(0), bridgehub); + new TransparentUpgradeableProxy(address(impl), address(uint160(1)), abi.encodeCall(PermanentRestriction.initialize, (address(0)))); } function test_allowAdminImplementation(bytes32 implementationHash) public { From 54f3c8b626cff87570b718c2a99af03fe1cdf433 Mon Sep 17 00:00:00 2001 From: Stanislav Breadless Date: Wed, 25 Sep 2024 12:45:48 +0200 Subject: [PATCH 06/18] fix lint --- .../contracts/governance/L2AdminFactory.sol | 19 ++++---- .../governance/PermanentRestriction.sol | 44 ++++++++----------- .../Governance/PermanentRestriction.t.sol | 18 +++++--- 3 files changed, 39 insertions(+), 42 deletions(-) diff --git a/l1-contracts/contracts/governance/L2AdminFactory.sol b/l1-contracts/contracts/governance/L2AdminFactory.sol index f3dba098e..d4fe4637c 100644 --- a/l1-contracts/contracts/governance/L2AdminFactory.sol +++ b/l1-contracts/contracts/governance/L2AdminFactory.sol @@ -7,16 +7,16 @@ import {ChainAdmin} from "./ChainAdmin.sol"; /// @author Matter Labs /// @custom:security-contact security@matterlabs.dev /// @dev Contract used to deploy ChainAdmin contracts on L2. -/// @dev It can be used to ensure that certain L2 admins are deployed with -/// predefined restrictions. E.g. it can be used to deploy admins that ensure that -/// a chain is a permanent rollup. +/// @dev It can be used to ensure that certain L2 admins are deployed with +/// predefined restrictions. E.g. it can be used to deploy admins that ensure that +/// a chain is a permanent rollup. /// @dev This contract is expected to be deployed in zkEVM (L2) environment. /// @dev The contract is immutable, in case the restrictions need to be changed, /// a new contract should be deployed. contract L2AdminFactory { event AdminDeployed(address admin); - /// @dev We use storage instead of immutable variables due to the + /// @dev We use storage instead of immutable variables due to the /// specifics of the zkEVM environment, where storage is actually cheaper. address[] public requiredRestrictions; @@ -26,15 +26,14 @@ contract L2AdminFactory { /// @notice Deploys a new L2 admin contract. /// @return admin The address of the deployed admin contract. - function deployAdmin( - address[] memory _additionalRestrictions, - bytes32 _salt - ) external returns (address admin) { + function deployAdmin(address[] calldata _additionalRestrictions, bytes32 _salt) external returns (address admin) { address[] memory restrictions = new address[](requiredRestrictions.length + _additionalRestrictions.length); - for (uint256 i = 0; i < requiredRestrictions.length; i++) { + uint256 cachedRequired = requiredRestrictions.length; + for (uint256 i = 0; i < cachedRequired; ++i) { restrictions[i] = requiredRestrictions[i]; } - for (uint256 i = 0; i < _additionalRestrictions.length; i++) { + uint256 cachedAdditional = _additionalRestrictions.length; + for (uint256 i = 0; i < cachedAdditional; ++i) { restrictions[requiredRestrictions.length + i] = _additionalRestrictions[i]; } diff --git a/l1-contracts/contracts/governance/PermanentRestriction.sol b/l1-contracts/contracts/governance/PermanentRestriction.sol index 12e2e5d4c..83745de5e 100644 --- a/l1-contracts/contracts/governance/PermanentRestriction.sol +++ b/l1-contracts/contracts/governance/PermanentRestriction.sol @@ -29,7 +29,7 @@ contract PermanentRestriction is IRestriction, IPermanentRestriction, Ownable2St /// @notice The address of the Bridgehub contract. IBridgehub public immutable BRIDGE_HUB; - /// @notice The address of the L2 admin factory that should be used to deploy the chain admins + /// @notice The address of the L2 admin factory that should be used to deploy the chain admins /// for chains that migrated on top of an L2 settlement layer. /// @dev If this contract is deployed on L2, this address is 0. /// @dev This address is expected to be the same on all L2 chains. @@ -47,15 +47,12 @@ contract PermanentRestriction is IRestriction, IPermanentRestriction, Ownable2St /// @notice The mapping of whitelisted L2 admins. mapping(address adminAddress => bool isWhitelisted) public whitelistedL2Admins; - constructor( - IBridgehub _bridgehub, - address _l2AdminFactory - ) { + constructor(IBridgehub _bridgehub, address _l2AdminFactory) { BRIDGE_HUB = _bridgehub; L2_ADMIN_FACTORY = _l2AdminFactory; } - function initialize(address _initialOwner) initializer external { + function initialize(address _initialOwner) external initializer { // solhint-disable-next-line gas-custom-errors, reason-string if (_initialOwner == address(0)) { revert ZeroAddress(); @@ -89,25 +86,21 @@ contract PermanentRestriction is IRestriction, IPermanentRestriction, Ownable2St emit SelectorValidationChanged(_selector, _isValidated); } - + /// @notice Whitelists a certain L2 admin. /// @param deploymentSalt The salt for the deployment. /// @param l2BytecodeHash The hash of the L2 bytecode. /// @param constructorInputHash The hash of the constructor data for the deployment. - function whitelistL2Admin( - bytes32 deploymentSalt, - bytes32 l2BytecodeHash, - bytes32 constructorInputHash - ) external { - // We do not do any additional validations for constructor data or the bytecode, + function whitelistL2Admin(bytes32 deploymentSalt, bytes32 l2BytecodeHash, bytes32 constructorInputHash) external { + // We do not do any additional validations for constructor data or the bytecode, // we expect that only admins of the allowed format are to be deployed. address expectedAddress = L2ContractHelper.computeCreate2Address( - L2_ADMIN_FACTORY, - deploymentSalt, - l2BytecodeHash, + L2_ADMIN_FACTORY, + deploymentSalt, + l2BytecodeHash, constructorInputHash ); - + if (whitelistedL2Admins[expectedAddress]) { revert AlreadyWhitelisted(expectedAddress); } @@ -128,13 +121,11 @@ contract PermanentRestriction is IRestriction, IPermanentRestriction, Ownable2St /// @notice Validates the migration to an L2 settlement layer. /// @param _call The call data. - /// @dev Note that we do not need to validate the migration to the L1 layer as the admin + /// @dev Note that we do not need to validate the migration to the L1 layer as the admin /// is not changed in this case. - function _validateMigrationToL2( - Call calldata _call - ) internal view { + function _validateMigrationToL2(Call calldata _call) internal view { try this.tryGetNewAdminFromMigration(_call) returns (address admin) { - if(!whitelistedL2Admins[admin]) { + if (!whitelistedL2Admins[admin]) { revert NotWhitelisted(admin); } } catch { @@ -259,7 +250,10 @@ contract PermanentRestriction is IRestriction, IPermanentRestriction, Ownable2St address sharedBridge = BRIDGE_HUB.sharedBridge(); - L2TransactionRequestTwoBridgesOuter memory request = abi.decode(_call.data[4:], (L2TransactionRequestTwoBridgesOuter)); + L2TransactionRequestTwoBridgesOuter memory request = abi.decode( + _call.data[4:], + (L2TransactionRequestTwoBridgesOuter) + ); if (request.secondBridgeAddress != sharedBridge) { revert InvalidAddress(request.secondBridgeAddress, sharedBridge); @@ -285,7 +279,7 @@ contract PermanentRestriction is IRestriction, IPermanentRestriction, Ownable2St BridgehubBurnCTMAssetData memory burnData = abi.decode(bridgehubData, (BridgehubBurnCTMAssetData)); (address l2Admin, ) = abi.decode(burnData.ctmData, (address, bytes)); - + return l2Admin; - } + } } diff --git a/l1-contracts/test/foundry/l1/unit/concrete/Governance/PermanentRestriction.t.sol b/l1-contracts/test/foundry/l1/unit/concrete/Governance/PermanentRestriction.t.sol index c8da87080..245028235 100644 --- a/l1-contracts/test/foundry/l1/unit/concrete/Governance/PermanentRestriction.t.sol +++ b/l1-contracts/test/foundry/l1/unit/concrete/Governance/PermanentRestriction.t.sol @@ -44,11 +44,7 @@ contract PermanentRestrictionTest is ChainTypeManagerTest { owner = makeAddr("owner"); hyperchain = chainContractAddress.getHyperchain(chainId); - (permRestriction, ) = _deployPermRestriction( - bridgehub, - L2_FACTORY_ADDR, - owner - ); + (permRestriction, ) = _deployPermRestriction(bridgehub, L2_FACTORY_ADDR, owner); restriction = new AccessControlRestriction(0, owner); address[] memory restrictions = new address[](1); restrictions[0] = address(restriction); @@ -61,7 +57,11 @@ contract PermanentRestrictionTest is ChainTypeManagerTest { address _owner ) internal returns (PermanentRestriction proxy, PermanentRestriction impl) { impl = new PermanentRestriction(_bridgehub, _l2AdminFactory); - TransparentUpgradeableProxy tup = new TransparentUpgradeableProxy(address(impl), address(uint160(1)), abi.encodeCall(PermanentRestriction.initialize, (_owner))); + TransparentUpgradeableProxy tup = new TransparentUpgradeableProxy( + address(impl), + address(uint160(1)), + abi.encodeCall(PermanentRestriction.initialize, (_owner)) + ); proxy = PermanentRestriction(address(tup)); } @@ -69,7 +69,11 @@ contract PermanentRestrictionTest is ChainTypeManagerTest { function test_ownerAsAddressZero() public { PermanentRestriction impl = new PermanentRestriction(bridgehub, L2_FACTORY_ADDR); vm.expectRevert(ZeroAddress.selector); - new TransparentUpgradeableProxy(address(impl), address(uint160(1)), abi.encodeCall(PermanentRestriction.initialize, (address(0)))); + new TransparentUpgradeableProxy( + address(impl), + address(uint160(1)), + abi.encodeCall(PermanentRestriction.initialize, (address(0))) + ); } function test_allowAdminImplementation(bytes32 implementationHash) public { From c2c3dbe61f17675acf6ff755499c6c6ddeb84c1c Mon Sep 17 00:00:00 2001 From: Stanislav Breadless Date: Wed, 25 Sep 2024 13:50:33 +0200 Subject: [PATCH 07/18] tests wip --- .../governance/PermanentRestriction.sol | 2 +- .../Governance/PermanentRestriction.t.sol | 116 +++++++++++++++++- 2 files changed, 116 insertions(+), 2 deletions(-) diff --git a/l1-contracts/contracts/governance/PermanentRestriction.sol b/l1-contracts/contracts/governance/PermanentRestriction.sol index 83745de5e..976036b91 100644 --- a/l1-contracts/contracts/governance/PermanentRestriction.sol +++ b/l1-contracts/contracts/governance/PermanentRestriction.sol @@ -256,7 +256,7 @@ contract PermanentRestriction is IRestriction, IPermanentRestriction, Ownable2St ); if (request.secondBridgeAddress != sharedBridge) { - revert InvalidAddress(request.secondBridgeAddress, sharedBridge); + revert InvalidAddress(sharedBridge, request.secondBridgeAddress); } bytes memory secondBridgeData = request.secondBridgeCalldata; diff --git a/l1-contracts/test/foundry/l1/unit/concrete/Governance/PermanentRestriction.t.sol b/l1-contracts/test/foundry/l1/unit/concrete/Governance/PermanentRestriction.t.sol index 245028235..99c03cd27 100644 --- a/l1-contracts/test/foundry/l1/unit/concrete/Governance/PermanentRestriction.t.sol +++ b/l1-contracts/test/foundry/l1/unit/concrete/Governance/PermanentRestriction.t.sol @@ -3,12 +3,13 @@ pragma solidity 0.8.24; import "@openzeppelin/contracts-v4/utils/Strings.sol"; import {TransparentUpgradeableProxy} from "@openzeppelin/contracts-v4/proxy/transparent/TransparentUpgradeableProxy.sol"; import {Bridgehub} from "contracts/bridgehub/Bridgehub.sol"; +import {L2TransactionRequestTwoBridgesOuter, BridgehubBurnCTMAssetData} from "contracts/bridgehub/IBridgehub.sol"; import {Diamond} from "contracts/state-transition/libraries/Diamond.sol"; import {ChainTypeManager} from "contracts/state-transition/ChainTypeManager.sol"; import {DiamondInit} from "contracts/state-transition/chain-deps/DiamondInit.sol"; import {PermanentRestriction} from "contracts/governance/PermanentRestriction.sol"; import {IPermanentRestriction} from "contracts/governance/IPermanentRestriction.sol"; -import {ZeroAddress, ChainZeroAddress, NotAnAdmin, UnallowedImplementation, RemovingPermanentRestriction, CallNotAllowed} from "contracts/common/L1ContractErrors.sol"; +import {InvalidAddress, UnsupportedEncodingVersion, InvalidSelector, NotBridgehub, ZeroAddress, ChainZeroAddress, NotAnAdmin, UnallowedImplementation, RemovingPermanentRestriction, CallNotAllowed} from "contracts/common/L1ContractErrors.sol"; import {Call} from "contracts/governance/Common.sol"; import {IZKChain} from "contracts/state-transition/chain-interfaces/IZKChain.sol"; import {VerifierParams, FeeParams, PubdataPricingMode} from "contracts/state-transition/chain-deps/ZKChainStorage.sol"; @@ -221,6 +222,119 @@ contract PermanentRestrictionTest is ChainTypeManagerTest { vm.stopPrank(); } + function _encodeMigraationCall( + bool correctTarget, + bool correctSelector, + bool correctSecondBridge, + bool correctEncodingVersion, + bool correctAssetId, + address l2Admin + ) internal returns (Call memory call) { + if (!correctTarget) { + call.target = address(0); + return call; + } + call.target = address(bridgehub); + + if (!correctSelector) { + call.data = hex"00000000"; + return call; + } + + L2TransactionRequestTwoBridgesOuter memory outer = L2TransactionRequestTwoBridgesOuter({ + chainId: chainId, + mintValue: 0, + l2Value: 0, + l2GasLimit: 0, + l2GasPerPubdataByteLimit: 0, + refundRecipient: address(0), + secondBridgeAddress: address(0), + secondBridgeValue: 0, + secondBridgeCalldata: hex"" + }); + if (!correctSecondBridge) { + call.data = abi.encodeCall(Bridgehub.requestL2TransactionTwoBridges, (outer)); + // 0 is not correct second bridge + return call; + } + outer.secondBridgeAddress = sharedBridge; + + + uint8 encoding = correctEncodingVersion ? 1 : 12; + + bytes32 chainAssetId = correctAssetId ? bridgehub.ctmAssetIdFromChainId(chainId) : bytes32(0); + + bytes memory bridgehubData = abi.encode( + BridgehubBurnCTMAssetData({ + // Gateway chain id, we do not need it + chainId: 0, + ctmData: abi.encode(l2Admin, hex""), + chainData: abi.encode(IZKChain(IBridgehub(bridgehub).getZKChain(chainId)).getProtocolVersion()) + }) + ); + outer.secondBridgeCalldata = abi.encodePacked(bytes1(encoding), abi.encode(chainAssetId, bridgehubData)); + + call.data = abi.encodeCall(Bridgehub.requestL2TransactionTwoBridges, (outer)); + } + + function test_tryGetNewAdminFromMigrationRevertWhenInvalidSelector() public { + Call memory call = _encodeMigraationCall( + false, + true, + true, + true, + true, + address(0) + ); + + vm.expectRevert(abi.encodeWithSelector(NotBridgehub.selector, address(0))); + permRestriction.tryGetNewAdminFromMigration(call); + } + + function test_tryGetNewAdminFromMigrationRevertWhenNotBridgehub() public { + Call memory call = _encodeMigraationCall( + true, + false, + true, + true, + true, + address(0) + ); + + vm.expectRevert(abi.encodeWithSelector(InvalidSelector.selector, bytes4(0))); + permRestriction.tryGetNewAdminFromMigration(call); + } + + + function test_tryGetNewAdminFromMigrationRevertWhenNotSharedBridge() public { + Call memory call = _encodeMigraationCall( + true, + true, + false, + true, + true, + address(0) + ); + + vm.expectRevert(abi.encodeWithSelector(InvalidAddress.selector, address(sharedBridge), address(0))); + permRestriction.tryGetNewAdminFromMigration(call); + } + + function test_tryGetNewAdminFromMigrationRevertWhenIncorrectEncoding() public { + Call memory call = _encodeMigraationCall( + true, + true, + true, + false, + true, + address(0) + ); + + vm.expectRevert(abi.encodeWithSelector(UnsupportedEncodingVersion.selector)); + permRestriction.tryGetNewAdminFromMigration(call); + } + + function createNewChainBridgehub() internal { bytes[] memory factoryDeps = new bytes[](0); vm.stopPrank(); From 6ea2aed59df00eb76008c9fb71cd9605b1270c29 Mon Sep 17 00:00:00 2001 From: Stanislav Breadless Date: Wed, 25 Sep 2024 14:04:23 +0200 Subject: [PATCH 08/18] min gas for fallable call --- .../contracts/governance/PermanentRestriction.sol | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/l1-contracts/contracts/governance/PermanentRestriction.sol b/l1-contracts/contracts/governance/PermanentRestriction.sol index 976036b91..748ae675a 100644 --- a/l1-contracts/contracts/governance/PermanentRestriction.sol +++ b/l1-contracts/contracts/governance/PermanentRestriction.sol @@ -2,7 +2,7 @@ pragma solidity 0.8.24; -import {UnsupportedEncodingVersion, CallNotAllowed, ChainZeroAddress, NotAHyperchain, NotAnAdmin, RemovingPermanentRestriction, ZeroAddress, UnallowedImplementation, AlreadyWhitelisted, NotWhitelisted, NotBridgehub, InvalidSelector, InvalidAddress} from "../common/L1ContractErrors.sol"; +import {UnsupportedEncodingVersion, CallNotAllowed, ChainZeroAddress, NotAHyperchain, NotAnAdmin, RemovingPermanentRestriction, ZeroAddress, UnallowedImplementation, AlreadyWhitelisted, NotWhitelisted, NotBridgehub, InvalidSelector, InvalidAddress, NotEnoughGas} from "../common/L1ContractErrors.sol"; import {L2TransactionRequestTwoBridgesOuter, BridgehubBurnCTMAssetData} from "../bridgehub/IBridgehub.sol"; import {Ownable2StepUpgradeable} from "@openzeppelin/contracts-upgradeable-v4/access/Ownable2StepUpgradeable.sol"; @@ -26,6 +26,11 @@ import {IPermanentRestriction} from "./IPermanentRestriction.sol"; /// @dev To be deployed as a transparent upgradable proxy, owned by a trusted decentralized governance. /// @dev Once of the instances of such contract is to ensure that a ZkSyncHyperchain is a rollup forever. contract PermanentRestriction is IRestriction, IPermanentRestriction, Ownable2StepUpgradeable { + /// @notice We use try-catch to test whether some of the conditions should be checked. + /// To avoid attacks based on teh 63/64 gas limitations, we ensure that each such call + /// has at least this amount. + uint256 constant MIN_GAS_FOR_FALLABLE_CALL = 5_000_000; + /// @notice The address of the Bridgehub contract. IBridgehub public immutable BRIDGE_HUB; @@ -124,6 +129,7 @@ contract PermanentRestriction is IRestriction, IPermanentRestriction, Ownable2St /// @dev Note that we do not need to validate the migration to the L1 layer as the admin /// is not changed in this case. function _validateMigrationToL2(Call calldata _call) internal view { + _ensureEnoughGas(); try this.tryGetNewAdminFromMigration(_call) returns (address admin) { if (!whitelistedL2Admins[admin]) { revert NotWhitelisted(admin); @@ -205,6 +211,7 @@ contract PermanentRestriction is IRestriction, IPermanentRestriction, Ownable2St /// @notice Checks if the `msg.sender` is an admin of a certain ZkSyncHyperchain. /// @param _chain The address of the chain. function _isAdminOfAChain(address _chain) internal view returns (bool) { + _ensureEnoughGas(); (bool success, ) = address(this).staticcall(abi.encodeCall(this.tryCompareAdminOfAChain, (_chain, msg.sender))); return success; } @@ -282,4 +289,10 @@ contract PermanentRestriction is IRestriction, IPermanentRestriction, Ownable2St return l2Admin; } + + function _ensureEnoughGas() internal view { + if (gasleft() < MIN_GAS_FOR_FALLABLE_CALL) { + revert NotEnoughGas(); + } + } } From 4be4c29174e8a541e3769d1a54fdab359398bfb8 Mon Sep 17 00:00:00 2001 From: Stanislav Breadless Date: Wed, 25 Sep 2024 15:09:49 +0200 Subject: [PATCH 09/18] full test cover for tryGetNewAdminFromMigration --- .../Governance/PermanentRestriction.t.sol | 39 ++++++++++++++++++- 1 file changed, 37 insertions(+), 2 deletions(-) diff --git a/l1-contracts/test/foundry/l1/unit/concrete/Governance/PermanentRestriction.t.sol b/l1-contracts/test/foundry/l1/unit/concrete/Governance/PermanentRestriction.t.sol index 99c03cd27..59899da0b 100644 --- a/l1-contracts/test/foundry/l1/unit/concrete/Governance/PermanentRestriction.t.sol +++ b/l1-contracts/test/foundry/l1/unit/concrete/Governance/PermanentRestriction.t.sol @@ -41,8 +41,6 @@ contract PermanentRestrictionTest is ChainTypeManagerTest { createNewChainBridgehub(); - vm.stopPrank(); - owner = makeAddr("owner"); hyperchain = chainContractAddress.getHyperchain(chainId); (permRestriction, ) = _deployPermRestriction(bridgehub, L2_FACTORY_ADDR, owner); @@ -334,6 +332,34 @@ contract PermanentRestrictionTest is ChainTypeManagerTest { permRestriction.tryGetNewAdminFromMigration(call); } + function test_tryGetNewAdminFromMigrationRevertWhenIncorrectAssetId() public { + Call memory call = _encodeMigraationCall( + true, + true, + true, + true, + false, + address(0) + ); + + vm.expectRevert(abi.encodeWithSelector(ZeroAddress.selector)); + permRestriction.tryGetNewAdminFromMigration(call); + } + + function test_tryGetNewAdminFromMigrationShouldWorkCorrectly() public { + address l2Addr = makeAddr("l2Addr"); + Call memory call = _encodeMigraationCall( + true, + true, + true, + true, + true, + l2Addr + ); + + address result = permRestriction.tryGetNewAdminFromMigration(call); + assertEq(result, l2Addr); + } function createNewChainBridgehub() internal { bytes[] memory factoryDeps = new bytes[](0); @@ -342,6 +368,13 @@ contract PermanentRestrictionTest is ChainTypeManagerTest { bridgehub.addChainTypeManager(address(chainContractAddress)); bridgehub.addTokenAssetId(DataEncoding.encodeNTVAssetId(block.chainid, baseToken)); bridgehub.setAddresses(sharedBridge, ICTMDeploymentTracker(address(0)), new MessageRoot(bridgehub)); + vm.stopPrank(); + + // ctm deployer address is 0 in this test + vm.startPrank(address(0)); + bridgehub.setAssetHandlerAddress(bytes32(uint256(uint160(address(chainContractAddress)))), address(chainContractAddress)); + vm.stopPrank(); + address l1Nullifier = makeAddr("l1Nullifier"); address l2LegacySharedBridge = makeAddr("l2LegacySharedBridge"); vm.mockCall( @@ -354,6 +387,7 @@ contract PermanentRestrictionTest is ChainTypeManagerTest { abi.encodeWithSelector(IL1Nullifier.l2BridgeAddress.selector), abi.encode(l2LegacySharedBridge) ); + vm.startPrank(governor); bridgehub.createNewChain({ _chainId: chainId, _chainTypeManager: address(chainContractAddress), @@ -363,5 +397,6 @@ contract PermanentRestrictionTest is ChainTypeManagerTest { _initData: getCTMInitData(), _factoryDeps: factoryDeps }); + vm.stopPrank(); } } From c77fd57b3d974b80d3cbe33961e5b95e1d766b91 Mon Sep 17 00:00:00 2001 From: Stanislav Breadless Date: Wed, 25 Sep 2024 15:31:25 +0200 Subject: [PATCH 10/18] some rename + better coverage --- .../contracts/common/L1ContractErrors.sol | 2 + .../governance/IPermanentRestriction.sol | 2 +- .../governance/PermanentRestriction.sol | 26 ++++---- .../Governance/PermanentRestriction.t.sol | 63 ++++++++++++++++++- 4 files changed, 77 insertions(+), 16 deletions(-) diff --git a/l1-contracts/contracts/common/L1ContractErrors.sol b/l1-contracts/contracts/common/L1ContractErrors.sol index 4e4a71ee7..a0c470a67 100644 --- a/l1-contracts/contracts/common/L1ContractErrors.sol +++ b/l1-contracts/contracts/common/L1ContractErrors.sol @@ -411,6 +411,8 @@ error AssetHandlerNotRegistered(bytes32 assetId); error NotBridgehub(address addr); // 0x2554babc error InvalidAddress(address expected, address actual); +// 0xfa5cd00f +error NotAllowed(address addr); enum SharedBridgeKey { PostUpgradeFirstBatch, diff --git a/l1-contracts/contracts/governance/IPermanentRestriction.sol b/l1-contracts/contracts/governance/IPermanentRestriction.sol index 7169ddf14..5fb015e33 100644 --- a/l1-contracts/contracts/governance/IPermanentRestriction.sol +++ b/l1-contracts/contracts/governance/IPermanentRestriction.sol @@ -16,5 +16,5 @@ interface IPermanentRestriction { event SelectorValidationChanged(bytes4 indexed selector, bool isValidated); /// @notice Emitted when the L2 admin is whitelisted or not. - event WhitelistL2Admin(address indexed adminAddress, bool isWhitelisted); + event AllowL2Admin(address indexed adminAddress); } diff --git a/l1-contracts/contracts/governance/PermanentRestriction.sol b/l1-contracts/contracts/governance/PermanentRestriction.sol index 748ae675a..5dda4f071 100644 --- a/l1-contracts/contracts/governance/PermanentRestriction.sol +++ b/l1-contracts/contracts/governance/PermanentRestriction.sol @@ -2,7 +2,7 @@ pragma solidity 0.8.24; -import {UnsupportedEncodingVersion, CallNotAllowed, ChainZeroAddress, NotAHyperchain, NotAnAdmin, RemovingPermanentRestriction, ZeroAddress, UnallowedImplementation, AlreadyWhitelisted, NotWhitelisted, NotBridgehub, InvalidSelector, InvalidAddress, NotEnoughGas} from "../common/L1ContractErrors.sol"; +import {UnsupportedEncodingVersion, CallNotAllowed, ChainZeroAddress, NotAHyperchain, NotAnAdmin, RemovingPermanentRestriction, ZeroAddress, UnallowedImplementation, AlreadyWhitelisted, NotAllowed, NotBridgehub, InvalidSelector, InvalidAddress, NotEnoughGas} from "../common/L1ContractErrors.sol"; import {L2TransactionRequestTwoBridgesOuter, BridgehubBurnCTMAssetData} from "../bridgehub/IBridgehub.sol"; import {Ownable2StepUpgradeable} from "@openzeppelin/contracts-upgradeable-v4/access/Ownable2StepUpgradeable.sol"; @@ -18,6 +18,11 @@ import {IAdmin} from "../state-transition/chain-interfaces/IAdmin.sol"; import {IPermanentRestriction} from "./IPermanentRestriction.sol"; +/// @dev We use try-catch to test whether some of the conditions should be checked. +/// To avoid attacks based on teh 63/64 gas limitations, we ensure that each such call +/// has at least this amount. +uint256 constant MIN_GAS_FOR_FALLABLE_CALL = 5_000_000; + /// @title PermanentRestriction contract /// @author Matter Labs /// @custom:security-contact security@matterlabs.dev @@ -26,11 +31,6 @@ import {IPermanentRestriction} from "./IPermanentRestriction.sol"; /// @dev To be deployed as a transparent upgradable proxy, owned by a trusted decentralized governance. /// @dev Once of the instances of such contract is to ensure that a ZkSyncHyperchain is a rollup forever. contract PermanentRestriction is IRestriction, IPermanentRestriction, Ownable2StepUpgradeable { - /// @notice We use try-catch to test whether some of the conditions should be checked. - /// To avoid attacks based on teh 63/64 gas limitations, we ensure that each such call - /// has at least this amount. - uint256 constant MIN_GAS_FOR_FALLABLE_CALL = 5_000_000; - /// @notice The address of the Bridgehub contract. IBridgehub public immutable BRIDGE_HUB; @@ -50,7 +50,7 @@ contract PermanentRestriction is IRestriction, IPermanentRestriction, Ownable2St mapping(bytes4 selector => bool isValidated) public validatedSelectors; /// @notice The mapping of whitelisted L2 admins. - mapping(address adminAddress => bool isWhitelisted) public whitelistedL2Admins; + mapping(address adminAddress => bool isWhitelisted) public allowedL2Admins; constructor(IBridgehub _bridgehub, address _l2AdminFactory) { BRIDGE_HUB = _bridgehub; @@ -96,7 +96,7 @@ contract PermanentRestriction is IRestriction, IPermanentRestriction, Ownable2St /// @param deploymentSalt The salt for the deployment. /// @param l2BytecodeHash The hash of the L2 bytecode. /// @param constructorInputHash The hash of the constructor data for the deployment. - function whitelistL2Admin(bytes32 deploymentSalt, bytes32 l2BytecodeHash, bytes32 constructorInputHash) external { + function allowL2Admin(bytes32 deploymentSalt, bytes32 l2BytecodeHash, bytes32 constructorInputHash) external { // We do not do any additional validations for constructor data or the bytecode, // we expect that only admins of the allowed format are to be deployed. address expectedAddress = L2ContractHelper.computeCreate2Address( @@ -106,12 +106,12 @@ contract PermanentRestriction is IRestriction, IPermanentRestriction, Ownable2St constructorInputHash ); - if (whitelistedL2Admins[expectedAddress]) { + if (allowedL2Admins[expectedAddress]) { revert AlreadyWhitelisted(expectedAddress); } - whitelistedL2Admins[expectedAddress] = true; - emit WhitelistL2Admin(expectedAddress, true); + allowedL2Admins[expectedAddress] = true; + emit AllowL2Admin(expectedAddress); } /// @inheritdoc IRestriction @@ -131,8 +131,8 @@ contract PermanentRestriction is IRestriction, IPermanentRestriction, Ownable2St function _validateMigrationToL2(Call calldata _call) internal view { _ensureEnoughGas(); try this.tryGetNewAdminFromMigration(_call) returns (address admin) { - if (!whitelistedL2Admins[admin]) { - revert NotWhitelisted(admin); + if (!allowedL2Admins[admin]) { + revert NotAllowed(admin); } } catch { // It was not the migration call, so we do nothing diff --git a/l1-contracts/test/foundry/l1/unit/concrete/Governance/PermanentRestriction.t.sol b/l1-contracts/test/foundry/l1/unit/concrete/Governance/PermanentRestriction.t.sol index 59899da0b..84e4d4793 100644 --- a/l1-contracts/test/foundry/l1/unit/concrete/Governance/PermanentRestriction.t.sol +++ b/l1-contracts/test/foundry/l1/unit/concrete/Governance/PermanentRestriction.t.sol @@ -7,9 +7,9 @@ import {L2TransactionRequestTwoBridgesOuter, BridgehubBurnCTMAssetData} from "co import {Diamond} from "contracts/state-transition/libraries/Diamond.sol"; import {ChainTypeManager} from "contracts/state-transition/ChainTypeManager.sol"; import {DiamondInit} from "contracts/state-transition/chain-deps/DiamondInit.sol"; -import {PermanentRestriction} from "contracts/governance/PermanentRestriction.sol"; +import {PermanentRestriction, MIN_GAS_FOR_FALLABLE_CALL} from "contracts/governance/PermanentRestriction.sol"; import {IPermanentRestriction} from "contracts/governance/IPermanentRestriction.sol"; -import {InvalidAddress, UnsupportedEncodingVersion, InvalidSelector, NotBridgehub, ZeroAddress, ChainZeroAddress, NotAnAdmin, UnallowedImplementation, RemovingPermanentRestriction, CallNotAllowed} from "contracts/common/L1ContractErrors.sol"; +import {NotAllowed, NotEnoughGas, InvalidAddress, UnsupportedEncodingVersion, InvalidSelector, NotBridgehub, ZeroAddress, ChainZeroAddress, NotAnAdmin, UnallowedImplementation, RemovingPermanentRestriction, CallNotAllowed} from "contracts/common/L1ContractErrors.sol"; import {Call} from "contracts/governance/Common.sol"; import {IZKChain} from "contracts/state-transition/chain-interfaces/IZKChain.sol"; import {VerifierParams, FeeParams, PubdataPricingMode} from "contracts/state-transition/chain-deps/ZKChainStorage.sol"; @@ -25,6 +25,7 @@ import {MessageRoot} from "contracts/bridgehub/MessageRoot.sol"; import {IL1AssetRouter} from "contracts/bridge/asset-router/IL1AssetRouter.sol"; import {IL1Nullifier} from "contracts/bridge/interfaces/IL1Nullifier.sol"; import {IBridgehub} from "contracts/bridgehub/IBridgehub.sol"; +import {L2ContractHelper} from "contracts/common/libraries/L2ContractHelper.sol"; contract PermanentRestrictionTest is ChainTypeManagerTest { ChainAdmin internal chainAdmin; @@ -361,6 +362,64 @@ contract PermanentRestrictionTest is ChainTypeManagerTest { assertEq(result, l2Addr); } + function test_validateMigrationToL2RevertNotAllowed() public { + Call memory call = _encodeMigraationCall( + true, + true, + true, + true, + true, + address(0) + ); + + vm.expectRevert(abi.encodeWithSelector(NotAllowed.selector, address(0))); + permRestriction.validateCall(call, owner); + } + + function test_validateMigrationToL2() public { + address expectedAddress = L2ContractHelper.computeCreate2Address( + L2_FACTORY_ADDR, + bytes32(0), + bytes32(0), + bytes32(0) + ); + + vm.expectEmit(true, false, false, true); + emit IPermanentRestriction.AllowL2Admin(expectedAddress); + permRestriction.allowL2Admin( + bytes32(0), + bytes32(0), + bytes32(0) + ); + + Call memory call = _encodeMigraationCall( + true, + true, + true, + true, + true, + expectedAddress + ); + + // Should not fail + permRestriction.validateCall(call, owner); + } + + function test_validateNotEnoughGas() public { + address l2Addr = makeAddr("l2Addr"); + Call memory call = _encodeMigraationCall( + true, + true, + true, + true, + true, + l2Addr + ); + + vm.expectRevert(abi.encodeWithSelector(NotEnoughGas.selector)); + permRestriction.validateCall{gas: MIN_GAS_FOR_FALLABLE_CALL}(call, address(0)); + } + function createNewChainBridgehub() internal { bytes[] memory factoryDeps = new bytes[](0); vm.stopPrank(); From 950862e5852b375753bfecd014dd4847819e76c8 Mon Sep 17 00:00:00 2001 From: Stanislav Breadless Date: Wed, 25 Sep 2024 15:32:29 +0200 Subject: [PATCH 11/18] lint fix --- .../governance/PermanentRestriction.sol | 4 +- .../Governance/PermanentRestriction.t.sol | 110 ++++-------------- 2 files changed, 24 insertions(+), 90 deletions(-) diff --git a/l1-contracts/contracts/governance/PermanentRestriction.sol b/l1-contracts/contracts/governance/PermanentRestriction.sol index 5dda4f071..6e34e3ba3 100644 --- a/l1-contracts/contracts/governance/PermanentRestriction.sol +++ b/l1-contracts/contracts/governance/PermanentRestriction.sol @@ -19,9 +19,9 @@ import {IAdmin} from "../state-transition/chain-interfaces/IAdmin.sol"; import {IPermanentRestriction} from "./IPermanentRestriction.sol"; /// @dev We use try-catch to test whether some of the conditions should be checked. -/// To avoid attacks based on teh 63/64 gas limitations, we ensure that each such call +/// To avoid attacks based on teh 63/64 gas limitations, we ensure that each such call /// has at least this amount. -uint256 constant MIN_GAS_FOR_FALLABLE_CALL = 5_000_000; +uint256 constant MIN_GAS_FOR_FALLABLE_CALL = 5_000_000; /// @title PermanentRestriction contract /// @author Matter Labs diff --git a/l1-contracts/test/foundry/l1/unit/concrete/Governance/PermanentRestriction.t.sol b/l1-contracts/test/foundry/l1/unit/concrete/Governance/PermanentRestriction.t.sol index 84e4d4793..c7eede8de 100644 --- a/l1-contracts/test/foundry/l1/unit/concrete/Governance/PermanentRestriction.t.sol +++ b/l1-contracts/test/foundry/l1/unit/concrete/Governance/PermanentRestriction.t.sol @@ -222,7 +222,7 @@ contract PermanentRestrictionTest is ChainTypeManagerTest { } function _encodeMigraationCall( - bool correctTarget, + bool correctTarget, bool correctSelector, bool correctSecondBridge, bool correctEncodingVersion, @@ -258,9 +258,8 @@ contract PermanentRestrictionTest is ChainTypeManagerTest { } outer.secondBridgeAddress = sharedBridge; - uint8 encoding = correctEncodingVersion ? 1 : 12; - + bytes32 chainAssetId = correctAssetId ? bridgehub.ctmAssetIdFromChainId(chainId) : bytes32(0); bytes memory bridgehubData = abi.encode( @@ -277,100 +276,50 @@ contract PermanentRestrictionTest is ChainTypeManagerTest { } function test_tryGetNewAdminFromMigrationRevertWhenInvalidSelector() public { - Call memory call = _encodeMigraationCall( - false, - true, - true, - true, - true, - address(0) - ); + Call memory call = _encodeMigraationCall(false, true, true, true, true, address(0)); vm.expectRevert(abi.encodeWithSelector(NotBridgehub.selector, address(0))); - permRestriction.tryGetNewAdminFromMigration(call); + permRestriction.tryGetNewAdminFromMigration(call); } function test_tryGetNewAdminFromMigrationRevertWhenNotBridgehub() public { - Call memory call = _encodeMigraationCall( - true, - false, - true, - true, - true, - address(0) - ); + Call memory call = _encodeMigraationCall(true, false, true, true, true, address(0)); vm.expectRevert(abi.encodeWithSelector(InvalidSelector.selector, bytes4(0))); - permRestriction.tryGetNewAdminFromMigration(call); + permRestriction.tryGetNewAdminFromMigration(call); } - function test_tryGetNewAdminFromMigrationRevertWhenNotSharedBridge() public { - Call memory call = _encodeMigraationCall( - true, - true, - false, - true, - true, - address(0) - ); + Call memory call = _encodeMigraationCall(true, true, false, true, true, address(0)); vm.expectRevert(abi.encodeWithSelector(InvalidAddress.selector, address(sharedBridge), address(0))); - permRestriction.tryGetNewAdminFromMigration(call); + permRestriction.tryGetNewAdminFromMigration(call); } function test_tryGetNewAdminFromMigrationRevertWhenIncorrectEncoding() public { - Call memory call = _encodeMigraationCall( - true, - true, - true, - false, - true, - address(0) - ); + Call memory call = _encodeMigraationCall(true, true, true, false, true, address(0)); vm.expectRevert(abi.encodeWithSelector(UnsupportedEncodingVersion.selector)); - permRestriction.tryGetNewAdminFromMigration(call); + permRestriction.tryGetNewAdminFromMigration(call); } function test_tryGetNewAdminFromMigrationRevertWhenIncorrectAssetId() public { - Call memory call = _encodeMigraationCall( - true, - true, - true, - true, - false, - address(0) - ); + Call memory call = _encodeMigraationCall(true, true, true, true, false, address(0)); vm.expectRevert(abi.encodeWithSelector(ZeroAddress.selector)); - permRestriction.tryGetNewAdminFromMigration(call); + permRestriction.tryGetNewAdminFromMigration(call); } function test_tryGetNewAdminFromMigrationShouldWorkCorrectly() public { address l2Addr = makeAddr("l2Addr"); - Call memory call = _encodeMigraationCall( - true, - true, - true, - true, - true, - l2Addr - ); + Call memory call = _encodeMigraationCall(true, true, true, true, true, l2Addr); address result = permRestriction.tryGetNewAdminFromMigration(call); - assertEq(result, l2Addr); + assertEq(result, l2Addr); } function test_validateMigrationToL2RevertNotAllowed() public { - Call memory call = _encodeMigraationCall( - true, - true, - true, - true, - true, - address(0) - ); + Call memory call = _encodeMigraationCall(true, true, true, true, true, address(0)); vm.expectRevert(abi.encodeWithSelector(NotAllowed.selector, address(0))); permRestriction.validateCall(call, owner); @@ -386,20 +335,9 @@ contract PermanentRestrictionTest is ChainTypeManagerTest { vm.expectEmit(true, false, false, true); emit IPermanentRestriction.AllowL2Admin(expectedAddress); - permRestriction.allowL2Admin( - bytes32(0), - bytes32(0), - bytes32(0) - ); + permRestriction.allowL2Admin(bytes32(0), bytes32(0), bytes32(0)); - Call memory call = _encodeMigraationCall( - true, - true, - true, - true, - true, - expectedAddress - ); + Call memory call = _encodeMigraationCall(true, true, true, true, true, expectedAddress); // Should not fail permRestriction.validateCall(call, owner); @@ -407,14 +345,7 @@ contract PermanentRestrictionTest is ChainTypeManagerTest { function test_validateNotEnoughGas() public { address l2Addr = makeAddr("l2Addr"); - Call memory call = _encodeMigraationCall( - true, - true, - true, - true, - true, - l2Addr - ); + Call memory call = _encodeMigraationCall(true, true, true, true, true, l2Addr); vm.expectRevert(abi.encodeWithSelector(NotEnoughGas.selector)); permRestriction.validateCall{gas: MIN_GAS_FOR_FALLABLE_CALL}(call, address(0)); @@ -431,7 +362,10 @@ contract PermanentRestrictionTest is ChainTypeManagerTest { // ctm deployer address is 0 in this test vm.startPrank(address(0)); - bridgehub.setAssetHandlerAddress(bytes32(uint256(uint160(address(chainContractAddress)))), address(chainContractAddress)); + bridgehub.setAssetHandlerAddress( + bytes32(uint256(uint160(address(chainContractAddress)))), + address(chainContractAddress) + ); vm.stopPrank(); address l1Nullifier = makeAddr("l1Nullifier"); From 16a26bc5c357f62a588daae7a073f101e0b68342 Mon Sep 17 00:00:00 2001 From: Stanislav Breadless Date: Wed, 25 Sep 2024 15:34:04 +0200 Subject: [PATCH 12/18] comment --- l1-contracts/contracts/governance/PermanentRestriction.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/l1-contracts/contracts/governance/PermanentRestriction.sol b/l1-contracts/contracts/governance/PermanentRestriction.sol index 6e34e3ba3..81b472fee 100644 --- a/l1-contracts/contracts/governance/PermanentRestriction.sol +++ b/l1-contracts/contracts/governance/PermanentRestriction.sol @@ -19,7 +19,7 @@ import {IAdmin} from "../state-transition/chain-interfaces/IAdmin.sol"; import {IPermanentRestriction} from "./IPermanentRestriction.sol"; /// @dev We use try-catch to test whether some of the conditions should be checked. -/// To avoid attacks based on teh 63/64 gas limitations, we ensure that each such call +/// To avoid attacks based on the 63/64 gas limitations, we ensure that each such call /// has at least this amount. uint256 constant MIN_GAS_FOR_FALLABLE_CALL = 5_000_000; From 30f1e549b377adbb551d1db993bb8f4cedf708ae Mon Sep 17 00:00:00 2001 From: Stanislav Breadless Date: Wed, 25 Sep 2024 16:02:49 +0200 Subject: [PATCH 13/18] add tests for l2 admin factory --- .../unit/L2AdminFactory/L2AdminFactory.t.sol | 50 +++++++++++++++++++ 1 file changed, 50 insertions(+) create mode 100644 l1-contracts/test/foundry/l2/unit/L2AdminFactory/L2AdminFactory.t.sol diff --git a/l1-contracts/test/foundry/l2/unit/L2AdminFactory/L2AdminFactory.t.sol b/l1-contracts/test/foundry/l2/unit/L2AdminFactory/L2AdminFactory.t.sol new file mode 100644 index 000000000..23dbf5486 --- /dev/null +++ b/l1-contracts/test/foundry/l2/unit/L2AdminFactory/L2AdminFactory.t.sol @@ -0,0 +1,50 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.20; + +import {Test} from "forge-std/Test.sol"; + +import {IBridgehub} from "contracts/bridgehub/IBridgehub.sol"; +import { L2AdminFactory } from "contracts/governance/L2AdminFactory.sol"; +import { PermanentRestriction } from "contracts/governance/PermanentRestriction.sol"; +import { IPermanentRestriction } from "contracts/governance/IPermanentRestriction.sol"; + +contract L2AdminFactoryTest is Test { + function testL2AdminFactory() public { + address[] memory requiredRestrictions = new address[](1); + requiredRestrictions[0] = makeAddr("required"); + + L2AdminFactory factory = new L2AdminFactory(requiredRestrictions); + + address[] memory additionalRestrictions = new address[](1); + additionalRestrictions[0] = makeAddr("additional"); + + address[] memory allRestrictions = new address[](2); + allRestrictions[0] = requiredRestrictions[0]; + allRestrictions[1] = additionalRestrictions[0]; + + + bytes32 salt = keccak256("salt"); + + address admin = factory.deployAdmin(additionalRestrictions, salt); + + // Now, we need to check whether it would be able to accept such an admin + PermanentRestriction restriction = new PermanentRestriction( + IBridgehub(address(0)), + address(factory) + ); + + bytes32 codeHash; + assembly { + codeHash := extcodehash(admin) + } + + vm.expectEmit(true, false, false, true); + emit IPermanentRestriction.AllowL2Admin(admin); + restriction.allowL2Admin( + salt, + codeHash, + keccak256(abi.encode(allRestrictions)) + ); + } +} From 2c6d11637b0c3a24063ed1014862785b72b379c1 Mon Sep 17 00:00:00 2001 From: Stanislav Breadless Date: Wed, 25 Sep 2024 16:05:44 +0200 Subject: [PATCH 14/18] fix lint --- .../unit/L2AdminFactory/L2AdminFactory.t.sol | 18 +++++------------- 1 file changed, 5 insertions(+), 13 deletions(-) diff --git a/l1-contracts/test/foundry/l2/unit/L2AdminFactory/L2AdminFactory.t.sol b/l1-contracts/test/foundry/l2/unit/L2AdminFactory/L2AdminFactory.t.sol index 23dbf5486..7b85a8c54 100644 --- a/l1-contracts/test/foundry/l2/unit/L2AdminFactory/L2AdminFactory.t.sol +++ b/l1-contracts/test/foundry/l2/unit/L2AdminFactory/L2AdminFactory.t.sol @@ -5,9 +5,9 @@ pragma solidity ^0.8.20; import {Test} from "forge-std/Test.sol"; import {IBridgehub} from "contracts/bridgehub/IBridgehub.sol"; -import { L2AdminFactory } from "contracts/governance/L2AdminFactory.sol"; -import { PermanentRestriction } from "contracts/governance/PermanentRestriction.sol"; -import { IPermanentRestriction } from "contracts/governance/IPermanentRestriction.sol"; +import {L2AdminFactory} from "contracts/governance/L2AdminFactory.sol"; +import {PermanentRestriction} from "contracts/governance/PermanentRestriction.sol"; +import {IPermanentRestriction} from "contracts/governance/IPermanentRestriction.sol"; contract L2AdminFactoryTest is Test { function testL2AdminFactory() public { @@ -23,16 +23,12 @@ contract L2AdminFactoryTest is Test { allRestrictions[0] = requiredRestrictions[0]; allRestrictions[1] = additionalRestrictions[0]; - bytes32 salt = keccak256("salt"); address admin = factory.deployAdmin(additionalRestrictions, salt); // Now, we need to check whether it would be able to accept such an admin - PermanentRestriction restriction = new PermanentRestriction( - IBridgehub(address(0)), - address(factory) - ); + PermanentRestriction restriction = new PermanentRestriction(IBridgehub(address(0)), address(factory)); bytes32 codeHash; assembly { @@ -41,10 +37,6 @@ contract L2AdminFactoryTest is Test { vm.expectEmit(true, false, false, true); emit IPermanentRestriction.AllowL2Admin(admin); - restriction.allowL2Admin( - salt, - codeHash, - keccak256(abi.encode(allRestrictions)) - ); + restriction.allowL2Admin(salt, codeHash, keccak256(abi.encode(allRestrictions))); } } From 5bd14e18291115b841d5651683e105415822e886 Mon Sep 17 00:00:00 2001 From: Stanislav Breadless Date: Wed, 25 Sep 2024 16:22:07 +0200 Subject: [PATCH 15/18] exclude l2adminfactory as it is covered by l2 tests --- l1-contracts/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/l1-contracts/package.json b/l1-contracts/package.json index 6d68729e2..0c955188e 100644 --- a/l1-contracts/package.json +++ b/l1-contracts/package.json @@ -61,7 +61,7 @@ "test:foundry": "forge test --ffi --match-path 'test/foundry/l1/*'", "test:zkfoundry": "forge test --zksync --match-path 'test/foundry/l2/*'", "test:fork": "TEST_CONTRACTS_FORK=1 yarn run hardhat test test/unit_tests/*.fork.ts --network hardhat", - "coverage:foundry": "forge coverage --ffi --match-path 'test/foundry/l1/*' --no-match-coverage 'contracts/bridge/.*L2.*.sol'", + "coverage:foundry": "forge coverage --ffi --match-path 'test/foundry/l1/*' --no-match-coverage 'contracts/(bridge/.*L2.*\\.sol|governance/L2AdminFactory\\.sol)'", "deploy-no-build": "ts-node scripts/deploy.ts", "register-zk-chain": "ts-node scripts/register-zk-chain.ts", "deploy-weth-bridges": "ts-node scripts/deploy-weth-bridges.ts", From 61e8cda4b304a57c4d495b3a2c53e75b7a015dd8 Mon Sep 17 00:00:00 2001 From: Stanislav Breadless Date: Wed, 25 Sep 2024 17:48:28 +0200 Subject: [PATCH 16/18] use legacy functions --- l1-contracts/contracts/bridgehub/IBridgehub.sol | 4 ++++ l1-contracts/contracts/governance/PermanentRestriction.sol | 4 +++- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/l1-contracts/contracts/bridgehub/IBridgehub.sol b/l1-contracts/contracts/bridgehub/IBridgehub.sol index 5e2be03fc..dc6c23e7c 100644 --- a/l1-contracts/contracts/bridgehub/IBridgehub.sol +++ b/l1-contracts/contracts/bridgehub/IBridgehub.sol @@ -232,4 +232,8 @@ interface IBridgehub is IAssetHandler, IL1AssetHandler { function registerAlreadyDeployedZKChain(uint256 _chainId, address _hyperchain) external; function setLegacyChainAddress(uint256 _chainId) external; + + /// @notice return the ZK chain contract for a chainId + /// @dev It is a legacy method. Do not use! + function getHyperchain(uint256 _chainId) public view returns (address); } diff --git a/l1-contracts/contracts/governance/PermanentRestriction.sol b/l1-contracts/contracts/governance/PermanentRestriction.sol index 81b472fee..7d5c41c91 100644 --- a/l1-contracts/contracts/governance/PermanentRestriction.sol +++ b/l1-contracts/contracts/governance/PermanentRestriction.sol @@ -232,7 +232,9 @@ contract PermanentRestriction is IRestriction, IPermanentRestriction, Ownable2St // - Query the Bridgehub for the Hyperchain with the given `chainId`. // - We compare the corresponding addresses uint256 chainId = IZKChain(_chain).getChainId(); - if (BRIDGE_HUB.getZKChain(chainId) != _chain) { + // Note, that here it is important to use the legacy `getHyperchain` function, so that the contract + // is compatible with the legacy ones. + if (BRIDGE_HUB.getHyperchain(chainId) != _chain) { revert NotAHyperchain(_chain); } From a27fdeac7404394c7bc254e98b7dd43d630bc653 Mon Sep 17 00:00:00 2001 From: Stanislav Breadless Date: Wed, 25 Sep 2024 17:51:16 +0200 Subject: [PATCH 17/18] ensure that the function never panics --- .../contracts/governance/PermanentRestriction.sol | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/l1-contracts/contracts/governance/PermanentRestriction.sol b/l1-contracts/contracts/governance/PermanentRestriction.sol index 7d5c41c91..ee12b5ff0 100644 --- a/l1-contracts/contracts/governance/PermanentRestriction.sol +++ b/l1-contracts/contracts/governance/PermanentRestriction.sol @@ -231,7 +231,17 @@ contract PermanentRestriction is IRestriction, IPermanentRestriction, Ownable2St // - Query it for `chainId`. If it reverts, it is not a ZkSyncHyperchain. // - Query the Bridgehub for the Hyperchain with the given `chainId`. // - We compare the corresponding addresses - uint256 chainId = IZKChain(_chain).getChainId(); + + // Note, that we do not use an explicit call here to ensure that the function does not panic in case of + // incorrect `_chain` address. + (bool success, bytes memory data) = _chain.staticcall(abi.encodeWithSelector(IZKChain.getChainId.selector)); + if (!success || data.length < 32) { + revert NotAHyperchain(_chain); + } + + // Can not fail + uint256 chainId = abi.decode(data, (uint256)); + // Note, that here it is important to use the legacy `getHyperchain` function, so that the contract // is compatible with the legacy ones. if (BRIDGE_HUB.getHyperchain(chainId) != _chain) { From 8e4c1fc78406b87eafde5213627ee8ec0d374f62 Mon Sep 17 00:00:00 2001 From: Stanislav Breadless Date: Wed, 25 Sep 2024 17:55:23 +0200 Subject: [PATCH 18/18] fix things --- l1-contracts/contracts/bridgehub/IBridgehub.sol | 2 +- l1-contracts/contracts/governance/PermanentRestriction.sol | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/l1-contracts/contracts/bridgehub/IBridgehub.sol b/l1-contracts/contracts/bridgehub/IBridgehub.sol index dc6c23e7c..4e4931345 100644 --- a/l1-contracts/contracts/bridgehub/IBridgehub.sol +++ b/l1-contracts/contracts/bridgehub/IBridgehub.sol @@ -235,5 +235,5 @@ interface IBridgehub is IAssetHandler, IL1AssetHandler { /// @notice return the ZK chain contract for a chainId /// @dev It is a legacy method. Do not use! - function getHyperchain(uint256 _chainId) public view returns (address); + function getHyperchain(uint256 _chainId) external view returns (address); } diff --git a/l1-contracts/contracts/governance/PermanentRestriction.sol b/l1-contracts/contracts/governance/PermanentRestriction.sol index ee12b5ff0..153ce369e 100644 --- a/l1-contracts/contracts/governance/PermanentRestriction.sol +++ b/l1-contracts/contracts/governance/PermanentRestriction.sol @@ -14,6 +14,7 @@ import {IRestriction} from "./IRestriction.sol"; import {IChainAdmin} from "./IChainAdmin.sol"; import {IBridgehub} from "../bridgehub/IBridgehub.sol"; import {IZKChain} from "../state-transition/chain-interfaces/IZKChain.sol"; +import {IGetters} from "../state-transition/chain-interfaces/IGetters.sol"; import {IAdmin} from "../state-transition/chain-interfaces/IAdmin.sol"; import {IPermanentRestriction} from "./IPermanentRestriction.sol"; @@ -234,7 +235,7 @@ contract PermanentRestriction is IRestriction, IPermanentRestriction, Ownable2St // Note, that we do not use an explicit call here to ensure that the function does not panic in case of // incorrect `_chain` address. - (bool success, bytes memory data) = _chain.staticcall(abi.encodeWithSelector(IZKChain.getChainId.selector)); + (bool success, bytes memory data) = _chain.staticcall(abi.encodeWithSelector(IGetters.getChainId.selector)); if (!success || data.length < 32) { revert NotAHyperchain(_chain); }