diff --git a/l1-contracts/.env b/l1-contracts/.env index 25ec2b87f..0cbe2dbd1 100644 --- a/l1-contracts/.env +++ b/l1-contracts/.env @@ -44,3 +44,4 @@ TOKENS_CONFIG=/script-config/config-deploy-erc20.toml ZK_CHAIN_CONFIG=/script-config/register-zk-chain.toml ZK_CHAIN_OUTPUT=/script-out/output-deploy-zk-chain-era.toml FORCE_DEPLOYMENTS_CONFIG=/script-config/generate-force-deployments-data.toml +GATEWAY_PREPARATION_L1_CONFIG=/script-config/gateway-preparation-l1.toml diff --git a/l1-contracts/contracts/bridge/BridgedStandardERC20.sol b/l1-contracts/contracts/bridge/BridgedStandardERC20.sol index bd8d01110..d848dcbac 100644 --- a/l1-contracts/contracts/bridge/BridgedStandardERC20.sol +++ b/l1-contracts/contracts/bridge/BridgedStandardERC20.sol @@ -10,6 +10,7 @@ import {IBridgedStandardToken} from "./interfaces/IBridgedStandardToken.sol"; import {Unauthorized, NonSequentialVersion, ZeroAddress} from "../common/L1ContractErrors.sol"; import {L2_NATIVE_TOKEN_VAULT_ADDR} from "../common/L2ContractAddresses.sol"; import {DataEncoding} from "../common/libraries/DataEncoding.sol"; +import {INativeTokenVault} from "../bridge/ntv/INativeTokenVault.sol"; /// @author Matter Labs /// @custom:security-contact security@matterlabs.dev @@ -43,6 +44,9 @@ contract BridgedStandardERC20 is ERC20PermitUpgradeable, IBridgedStandardToken, /// @dev Address of the native token vault that is used as trustee who can mint/burn tokens address public nativeTokenVault; + /// @dev The assetId of the token. + bytes32 public assetId; + /// @dev This also sets the native token vault to the default value if it is not set. /// It is not set only on the L2s for legacy tokens. modifier onlyNTV() { @@ -50,6 +54,10 @@ contract BridgedStandardERC20 is ERC20PermitUpgradeable, IBridgedStandardToken, if (ntv == address(0)) { ntv = L2_NATIVE_TOKEN_VAULT_ADDR; nativeTokenVault = L2_NATIVE_TOKEN_VAULT_ADDR; + assetId = DataEncoding.encodeNTVAssetId( + INativeTokenVault(L2_NATIVE_TOKEN_VAULT_ADDR).L1_CHAIN_ID(), + originToken + ); } if (msg.sender != ntv) { revert Unauthorized(msg.sender); @@ -74,14 +82,20 @@ contract BridgedStandardERC20 is ERC20PermitUpgradeable, IBridgedStandardToken, /// @notice Initializes a contract token for later use. Expected to be used in the proxy. /// @dev Stores the L1 address of the bridge and set `name`/`symbol`/`decimals` getters that L1 token has. + /// @param _assetId The assetId of the token. /// @param _originToken Address of the origin token that can be deposited to mint this bridged token /// @param _data The additional data that the L1 bridge provide for initialization. /// In this case, it is packed `name`/`symbol`/`decimals` of the L1 token. - function bridgeInitialize(address _originToken, bytes calldata _data) external initializer returns (uint256) { + function bridgeInitialize( + bytes32 _assetId, + address _originToken, + bytes calldata _data + ) external initializer returns (uint256) { if (_originToken == address(0)) { revert ZeroAddress(); } originToken = _originToken; + assetId = _assetId; nativeTokenVault = msg.sender; diff --git a/l1-contracts/contracts/bridge/L1Nullifier.sol b/l1-contracts/contracts/bridge/L1Nullifier.sol index a4447e085..6e624d723 100644 --- a/l1-contracts/contracts/bridge/L1Nullifier.sol +++ b/l1-contracts/contracts/bridge/L1Nullifier.sol @@ -16,7 +16,6 @@ import {IL1NativeTokenVault} from "./ntv/IL1NativeTokenVault.sol"; import {IL1ERC20Bridge} from "./interfaces/IL1ERC20Bridge.sol"; import {IL1AssetRouter} from "./asset-router/IL1AssetRouter.sol"; import {IAssetRouterBase} from "./asset-router/IAssetRouterBase.sol"; -import {INativeTokenVault} from "./ntv/INativeTokenVault.sol"; import {IL1Nullifier, FinalizeL1DepositParams} from "./interfaces/IL1Nullifier.sol"; @@ -511,7 +510,7 @@ contract L1Nullifier is IL1Nullifier, ReentrancyGuard, Ownable2StepUpgradeable, /// @return transferData The transfer data used to finalize withdawal. function _verifyWithdrawal( FinalizeL1DepositParams calldata _finalizeWithdrawalParams - ) internal view returns (bytes32 assetId, bytes memory transferData) { + ) internal returns (bytes32 assetId, bytes memory transferData) { (assetId, transferData) = _parseL2WithdrawalMessage( _finalizeWithdrawalParams.chainId, _finalizeWithdrawalParams.message @@ -560,7 +559,7 @@ contract L1Nullifier is IL1Nullifier, ReentrancyGuard, Ownable2StepUpgradeable, function _parseL2WithdrawalMessage( uint256 _chainId, bytes memory _l2ToL1message - ) internal view returns (bytes32 assetId, bytes memory transferData) { + ) internal returns (bytes32 assetId, bytes memory transferData) { // Please note that there are three versions of the message: // 1. The message that is sent from `L2BaseToken` to withdraw base token. // 2. The message that is sent from L2 Legacy Shared Bridge to withdraw ERC20 tokens or base token. @@ -604,6 +603,7 @@ contract L1Nullifier is IL1Nullifier, ReentrancyGuard, Ownable2StepUpgradeable, // slither-disable-next-line unused-return (amount, ) = UnsafeBytes.readUint256(_l2ToL1message, offset); + l1NativeTokenVault.ensureTokenIsRegistered(l1Token); assetId = DataEncoding.encodeNTVAssetId(block.chainid, l1Token); transferData = DataEncoding.encodeBridgeMintData({ _originalCaller: address(0), @@ -616,7 +616,7 @@ contract L1Nullifier is IL1Nullifier, ReentrancyGuard, Ownable2StepUpgradeable, // The data is expected to be at least 36 bytes long to contain assetId. require(_l2ToL1message.length >= 36, "L1N: wrong msg len"); // wrong message length // slither-disable-next-line unused-return - (, offset) = UnsafeBytes.readBytes32(_l2ToL1message, offset); // originChainId, not used for L2->L1 txs + (, offset) = UnsafeBytes.readUint256(_l2ToL1message, offset); // originChainId, not used for L2->L1 txs (assetId, offset) = UnsafeBytes.readBytes32(_l2ToL1message, offset); transferData = UnsafeBytes.readRemainingBytes(_l2ToL1message, offset); } else { @@ -648,7 +648,10 @@ contract L1Nullifier is IL1Nullifier, ReentrancyGuard, Ownable2StepUpgradeable, uint16 _l2TxNumberInBatch, bytes32[] calldata _merkleProof ) external override { - bytes32 assetId = INativeTokenVault(address(l1NativeTokenVault)).getAssetId(block.chainid, _l1Token); + bytes32 assetId = l1NativeTokenVault.assetId(_l1Token); + if (assetId == bytes32(0)) { + assetId = DataEncoding.encodeNTVAssetId(block.chainid, _l1Token); + } // For legacy deposits, the l2 receiver is not required to check tx data hash // bytes memory transferData = abi.encode(_amount, _depositSender); bytes memory assetData = abi.encode(_amount, address(0)); @@ -702,7 +705,7 @@ contract L1Nullifier is IL1Nullifier, ReentrancyGuard, Ownable2StepUpgradeable, ) external override onlyLegacyBridge { bytes memory assetData = abi.encode(_amount, _depositSender); /// the legacy bridge can only be used with L1 native tokens. - bytes32 assetId = INativeTokenVault(address(l1NativeTokenVault)).getAssetId(block.chainid, _l1Asset); + bytes32 assetId = DataEncoding.encodeNTVAssetId(block.chainid, _l1Asset); _verifyAndClearFailedTransfer({ _checkedInLegacyBridge: true, diff --git a/l1-contracts/contracts/bridge/L2SharedBridgeLegacy.sol b/l1-contracts/contracts/bridge/L2SharedBridgeLegacy.sol index df5bda67c..4ae901593 100644 --- a/l1-contracts/contracts/bridge/L2SharedBridgeLegacy.sol +++ b/l1-contracts/contracts/bridge/L2SharedBridgeLegacy.sol @@ -190,8 +190,8 @@ contract L2SharedBridgeLegacy is IL2SharedBridgeLegacy, Initializable { proxy = abi.decode(returndata, (address)); } - function sendMessageToL1(bytes calldata _message) external override onlyAssetRouter { + function sendMessageToL1(bytes calldata _message) external override onlyAssetRouter returns (bytes32) { // slither-disable-next-line unused-return - L2ContractHelper.sendMessageToL1(_message); + return L2ContractHelper.sendMessageToL1(_message); } } diff --git a/l1-contracts/contracts/bridge/L2WrappedBaseToken.sol b/l1-contracts/contracts/bridge/L2WrappedBaseToken.sol index 4319a8b7c..1c430cacd 100644 --- a/l1-contracts/contracts/bridge/L2WrappedBaseToken.sol +++ b/l1-contracts/contracts/bridge/L2WrappedBaseToken.sol @@ -6,6 +6,7 @@ import {ERC20PermitUpgradeable} from "@openzeppelin/contracts-upgradeable-v4/tok import {IL2WrappedBaseToken} from "./interfaces/IL2WrappedBaseToken.sol"; import {IBridgedStandardToken} from "./interfaces/IBridgedStandardToken.sol"; +import {L2_NATIVE_TOKEN_VAULT_ADDR} from "../common/L2ContractAddresses.sol"; import {ZeroAddress, Unauthorized, BridgeMintNotImplemented, WithdrawFailed} from "../common/L1ContractErrors.sol"; @@ -29,6 +30,12 @@ contract L2WrappedBaseToken is ERC20PermitUpgradeable, IL2WrappedBaseToken, IBri /// @dev Address of the L1 base token. It can be deposited to mint this L2 token. address public override l1Address; + /// @dev Address of the native token vault. + address public override nativeTokenVault; + + /// @dev The assetId of the base token. The wrapped token does not have its own assetId. + bytes32 public baseTokenAssetId; + modifier onlyBridge() { if (msg.sender != l2Bridge) { revert Unauthorized(msg.sender); @@ -59,7 +66,8 @@ contract L2WrappedBaseToken is ERC20PermitUpgradeable, IL2WrappedBaseToken, IBri string calldata name_, string calldata symbol_, address _l2Bridge, - address _l1Address + address _l1Address, + bytes32 _baseTokenAssetId ) external reinitializer(2) { if (_l2Bridge == address(0)) { revert ZeroAddress(); @@ -68,8 +76,13 @@ contract L2WrappedBaseToken is ERC20PermitUpgradeable, IL2WrappedBaseToken, IBri if (_l1Address == address(0)) { revert ZeroAddress(); } + if (_baseTokenAssetId == bytes32(0)) { + revert ZeroAddress(); + } l2Bridge = _l2Bridge; l1Address = _l1Address; + nativeTokenVault = L2_NATIVE_TOKEN_VAULT_ADDR; + baseTokenAssetId = _baseTokenAssetId; // Set decoded values for name and symbol. __ERC20_init_unchained(name_, symbol_); @@ -131,4 +144,8 @@ contract L2WrappedBaseToken is ERC20PermitUpgradeable, IL2WrappedBaseToken, IBri function originToken() external view override returns (address) { return l1Address; } + + function assetId() external view override returns (bytes32) { + return baseTokenAssetId; + } } diff --git a/l1-contracts/contracts/bridge/asset-router/AssetRouterBase.sol b/l1-contracts/contracts/bridge/asset-router/AssetRouterBase.sol index 1a27e825f..d6ca41bdf 100644 --- a/l1-contracts/contracts/bridge/asset-router/AssetRouterBase.sol +++ b/l1-contracts/contracts/bridge/asset-router/AssetRouterBase.sol @@ -149,6 +149,12 @@ abstract contract AssetRouterBase is IAssetRouterBase, Ownable2StepUpgradeable, }); } + /// @notice Ensures that token is registered with native token vault. + /// @dev Only used when deposit is made with legacy data encoding format. + /// @param _token The native token address which should be registered with native token vault. + /// @return assetId The asset ID of the token provided. + function _ensureTokenRegisteredWithNTV(address _token) internal virtual returns (bytes32 assetId); + /*////////////////////////////////////////////////////////////// PAUSE //////////////////////////////////////////////////////////////*/ diff --git a/l1-contracts/contracts/bridge/asset-router/IL1AssetRouter.sol b/l1-contracts/contracts/bridge/asset-router/IL1AssetRouter.sol index 8c02ea00e..5d4c4fc5f 100644 --- a/l1-contracts/contracts/bridge/asset-router/IL1AssetRouter.sol +++ b/l1-contracts/contracts/bridge/asset-router/IL1AssetRouter.sol @@ -95,6 +95,29 @@ interface IL1AssetRouter is IAssetRouterBase, IL1SharedBridgeLegacy { bytes calldata _assetData ) external; + /// @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 + ) external; + /// @notice Transfers funds 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) /// @param _amount The asset amount to be transferred to native token vault. @@ -169,4 +192,10 @@ interface IL1AssetRouter is IAssetRouterBase, IL1SharedBridgeLegacy { /// @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; + + function isWithdrawalFinalized( + uint256 _chainId, + uint256 _l2BatchNumber, + uint256 _l2MessageIndex + ) external view returns (bool); } diff --git a/l1-contracts/contracts/bridge/asset-router/IL2AssetRouter.sol b/l1-contracts/contracts/bridge/asset-router/IL2AssetRouter.sol index 2613a75eb..81b1bc995 100644 --- a/l1-contracts/contracts/bridge/asset-router/IL2AssetRouter.sol +++ b/l1-contracts/contracts/bridge/asset-router/IL2AssetRouter.sol @@ -2,9 +2,11 @@ pragma solidity ^0.8.20; +import {IAssetRouterBase} from "./IAssetRouterBase.sol"; + /// @author Matter Labs /// @custom:security-contact security@matterlabs.dev -interface IL2AssetRouter { +interface IL2AssetRouter is IAssetRouterBase { event WithdrawalInitiatedAssetRouter( uint256 chainId, address indexed l2Sender, @@ -12,7 +14,7 @@ interface IL2AssetRouter { bytes assetData ); - function withdraw(bytes32 _assetId, bytes calldata _transferData) external; + function withdraw(bytes32 _assetId, bytes calldata _transferData) external returns (bytes32); function l1AssetRouter() external view returns (address); diff --git a/l1-contracts/contracts/bridge/asset-router/L1AssetRouter.sol b/l1-contracts/contracts/bridge/asset-router/L1AssetRouter.sol index 16b14f2ea..ff94d0db5 100644 --- a/l1-contracts/contracts/bridge/asset-router/L1AssetRouter.sol +++ b/l1-contracts/contracts/bridge/asset-router/L1AssetRouter.sol @@ -335,6 +335,31 @@ contract L1AssetRouter is AssetRouterBase, IL1AssetRouter, ReentrancyGuard { emit ClaimedFailedDepositAssetRouter(_chainId, _assetId, _assetData); } + + function bridgeRecoverFailedTransfer( + uint256 _chainId, + address _depositSender, + bytes32 _assetId, + bytes calldata _assetData, + bytes32 _l2TxHash, + uint256 _l2BatchNumber, + uint256 _l2MessageIndex, + uint16 _l2TxNumberInBatch, + bytes32[] calldata _merkleProof + ) external { + L1_NULLIFIER.bridgeRecoverFailedTransfer({ + _chainId: _chainId, + _depositSender: _depositSender, + _assetId: _assetId, + _assetData: _assetData, + _l2TxHash: _l2TxHash, + _l2BatchNumber: _l2BatchNumber, + _l2MessageIndex: _l2MessageIndex, + _l2TxNumberInBatch: _l2TxNumberInBatch, + _merkleProof: _merkleProof + }); + } + /*////////////////////////////////////////////////////////////// Internal & Helpers //////////////////////////////////////////////////////////////*/ @@ -354,13 +379,15 @@ contract L1AssetRouter is AssetRouterBase, IL1AssetRouter, ReentrancyGuard { /// @notice Ensures that token is registered with native token vault. /// @dev Only used when deposit is made with legacy data encoding format. - /// @param _token The L1 token address which should be registered with native token vault. + /// @param _token The native token address which should be registered with native token vault. /// @return assetId The asset ID of the token provided. - function _ensureTokenRegisteredWithNTV(address _token) internal returns (bytes32 assetId) { - assetId = nativeTokenVault.getAssetId(block.chainid, _token); - if (nativeTokenVault.tokenAddress(assetId) == address(0)) { - nativeTokenVault.registerToken(_token); + function _ensureTokenRegisteredWithNTV(address _token) internal override returns (bytes32 assetId) { + assetId = nativeTokenVault.assetId(_token); + if (assetId != bytes32(0)) { + return assetId; } + nativeTokenVault.ensureTokenIsRegistered(_token); + assetId = nativeTokenVault.assetId(_token); } /// @inheritdoc IL1AssetRouter @@ -586,6 +613,15 @@ contract L1AssetRouter is AssetRouterBase, IL1AssetRouter, ReentrancyGuard { }); } + /// @notice Legacy read method, which forwards the call to L1Nullifier to check if withdrawal was finalized + function isWithdrawalFinalized( + uint256 _chainId, + uint256 _l2BatchNumber, + uint256 _l2MessageIndex + ) external view returns (bool) { + return L1_NULLIFIER.isWithdrawalFinalized(_chainId, _l2BatchNumber, _l2MessageIndex); + } + /// @notice Legacy function to get the L2 shared bridge address for a chain. /// @dev In case the chain has been deployed after the gateway release, /// the returned value is 0. diff --git a/l1-contracts/contracts/bridge/asset-router/L2AssetRouter.sol b/l1-contracts/contracts/bridge/asset-router/L2AssetRouter.sol index b22b3f5b9..0a10822f4 100644 --- a/l1-contracts/contracts/bridge/asset-router/L2AssetRouter.sol +++ b/l1-contracts/contracts/bridge/asset-router/L2AssetRouter.sol @@ -7,6 +7,7 @@ import {IAssetRouterBase} from "./IAssetRouterBase.sol"; import {AssetRouterBase} from "./AssetRouterBase.sol"; import {IL2NativeTokenVault} from "../ntv/IL2NativeTokenVault.sol"; +import {INativeTokenVault} from "../ntv/INativeTokenVault.sol"; import {IL2SharedBridgeLegacy} from "../interfaces/IL2SharedBridgeLegacy.sol"; import {IAssetHandler} from "../interfaces/IAssetHandler.sol"; import {IBridgedStandardToken} from "../interfaces/IBridgedStandardToken.sol"; @@ -101,7 +102,7 @@ contract L2AssetRouter is AssetRouterBase, IL2AssetRouter { function setAssetHandlerAddressThisChain( bytes32 _assetRegistrationData, address _assetHandlerAddress - ) external override(AssetRouterBase) { + ) external override(AssetRouterBase, IAssetRouterBase) { _setAssetHandlerAddressThisChain(L2_NATIVE_TOKEN_VAULT_ADDR, _assetRegistrationData, _assetHandlerAddress); } @@ -117,7 +118,7 @@ contract L2AssetRouter is AssetRouterBase, IL2AssetRouter { uint256, bytes32 _assetId, bytes calldata _transferData - ) public override onlyAssetRouterCounterpartOrSelf(L1_CHAIN_ID) { + ) public override(AssetRouterBase, IAssetRouterBase) onlyAssetRouterCounterpartOrSelf(L1_CHAIN_ID) { if (_assetId == BASE_TOKEN_ASSET_ID) { revert AssetIdNotSupported(BASE_TOKEN_ASSET_ID); } @@ -126,6 +127,16 @@ contract L2AssetRouter is AssetRouterBase, IL2AssetRouter { emit DepositFinalizedAssetRouter(L1_CHAIN_ID, _assetId, _transferData); } + /*////////////////////////////////////////////////////////////// + Internal & Helpers + //////////////////////////////////////////////////////////////*/ + + /// @inheritdoc AssetRouterBase + function _ensureTokenRegisteredWithNTV(address _token) internal override returns (bytes32 assetId) { + IL2NativeTokenVault nativeTokenVault = IL2NativeTokenVault(L2_NATIVE_TOKEN_VAULT_ADDR); + nativeTokenVault.ensureTokenIsRegistered(_token); + } + /*////////////////////////////////////////////////////////////// LEGACY FUNCTIONS //////////////////////////////////////////////////////////////*/ @@ -135,8 +146,18 @@ contract L2AssetRouter is AssetRouterBase, IL2AssetRouter { /// @dev do not rely on this function, it will be deprecated in the future /// @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 { - _withdrawSender(_assetId, _assetData, msg.sender, true); + function withdraw(bytes32 _assetId, bytes memory _assetData) public override returns (bytes32) { + return _withdrawSender(_assetId, _assetData, msg.sender, true); + } + + function withdrawToken(address _l2NativeToken, bytes memory _assetData) public returns (bytes32) { + bytes32 recordedAssetId = INativeTokenVault(L2_NATIVE_TOKEN_VAULT_ADDR).assetId(_l2NativeToken); + uint256 recordedOriginChainId = INativeTokenVault(L2_NATIVE_TOKEN_VAULT_ADDR).originChainId(recordedAssetId); + if (recordedOriginChainId == L1_CHAIN_ID) { + revert AssetIdNotSupported(recordedAssetId); + } + bytes32 assetId = _ensureTokenRegisteredWithNTV(_l2NativeToken); + return _withdrawSender(assetId, _assetData, msg.sender, true); } /// @notice Initiates a withdrawal by burning funds on the contract and sending the message to L1 @@ -150,7 +171,7 @@ contract L2AssetRouter is AssetRouterBase, IL2AssetRouter { bytes memory _assetData, address _sender, bool _alwaysNewMessageFormat - ) internal { + ) internal returns (bytes32 txHash) { address assetHandler = assetHandlerAddress[_assetId]; bytes memory _l1bridgeMintData = IAssetHandler(assetHandler).bridgeBurn({ _chainId: L1_CHAIN_ID, @@ -164,7 +185,7 @@ contract L2AssetRouter is AssetRouterBase, IL2AssetRouter { if (_alwaysNewMessageFormat || L2_LEGACY_SHARED_BRIDGE == address(0)) { message = _getAssetRouterWithdrawMessage(_assetId, _l1bridgeMintData); // slither-disable-next-line unused-return - L2ContractHelper.sendMessageToL1(message); + txHash = L2ContractHelper.sendMessageToL1(message); } else { address l1Token = IBridgedStandardToken( IL2NativeTokenVault(L2_NATIVE_TOKEN_VAULT_ADDR).tokenAddress(_assetId) @@ -174,7 +195,7 @@ contract L2AssetRouter is AssetRouterBase, IL2AssetRouter { } (uint256 amount, address l1Receiver) = abi.decode(_assetData, (uint256, address)); message = _getSharedBridgeWithdrawMessage(l1Receiver, l1Token, amount); - IL2SharedBridgeLegacy(L2_LEGACY_SHARED_BRIDGE).sendMessageToL1(message); + txHash = IL2SharedBridgeLegacy(L2_LEGACY_SHARED_BRIDGE).sendMessageToL1(message); } emit WithdrawalInitiatedAssetRouter(L1_CHAIN_ID, _sender, _assetId, _assetData); @@ -186,9 +207,9 @@ contract L2AssetRouter is AssetRouterBase, IL2AssetRouter { function _getAssetRouterWithdrawMessage( bytes32 _assetId, bytes memory _l1bridgeMintData - ) internal pure returns (bytes memory) { + ) internal view returns (bytes memory) { // solhint-disable-next-line func-named-parameters - return abi.encodePacked(IAssetRouterBase.finalizeDeposit.selector, _assetId, _l1bridgeMintData); + return abi.encodePacked(IAssetRouterBase.finalizeDeposit.selector, block.chainid, _assetId, _l1bridgeMintData); } /// @notice Encodes the message for l2ToL1log sent during withdraw initialization. @@ -282,7 +303,7 @@ contract L2AssetRouter is AssetRouterBase, IL2AssetRouter { } function _withdrawLegacy(address _l1Receiver, address _l2Token, uint256 _amount, address _sender) internal { - bytes32 assetId = DataEncoding.encodeNTVAssetId(L1_CHAIN_ID, getL1TokenAddress(_l2Token)); + bytes32 assetId = DataEncoding.encodeNTVAssetId(L1_CHAIN_ID, l1TokenAddress(_l2Token)); bytes memory data = abi.encode(_amount, _l1Receiver); _withdrawSender(assetId, data, _sender, false); } @@ -290,7 +311,7 @@ contract L2AssetRouter is AssetRouterBase, IL2AssetRouter { /// @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) { + function l1TokenAddress(address _l2Token) public view returns (address) { return IBridgedStandardToken(_l2Token).l1Address(); } diff --git a/l1-contracts/contracts/bridge/interfaces/IBridgedStandardToken.sol b/l1-contracts/contracts/bridge/interfaces/IBridgedStandardToken.sol index 952bc1871..2ba2a081b 100644 --- a/l1-contracts/contracts/bridge/interfaces/IBridgedStandardToken.sol +++ b/l1-contracts/contracts/bridge/interfaces/IBridgedStandardToken.sol @@ -18,4 +18,8 @@ interface IBridgedStandardToken { function originToken() external view returns (address); function l2Bridge() external view returns (address); + + function assetId() external view returns (bytes32); + + function nativeTokenVault() external view returns (address); } diff --git a/l1-contracts/contracts/bridge/interfaces/IL2SharedBridgeLegacy.sol b/l1-contracts/contracts/bridge/interfaces/IL2SharedBridgeLegacy.sol index 05d86757e..71c7a46c5 100644 --- a/l1-contracts/contracts/bridge/interfaces/IL2SharedBridgeLegacy.sol +++ b/l1-contracts/contracts/bridge/interfaces/IL2SharedBridgeLegacy.sol @@ -28,5 +28,5 @@ interface IL2SharedBridgeLegacy { function deployBeaconProxy(bytes32 _salt) external returns (address); - function sendMessageToL1(bytes calldata _message) external; + function sendMessageToL1(bytes calldata _message) external returns (bytes32); } diff --git a/l1-contracts/contracts/bridge/ntv/INativeTokenVault.sol b/l1-contracts/contracts/bridge/ntv/INativeTokenVault.sol index 0cd78cf54..12718bd6f 100644 --- a/l1-contracts/contracts/bridge/ntv/INativeTokenVault.sol +++ b/l1-contracts/contracts/bridge/ntv/INativeTokenVault.sol @@ -16,6 +16,10 @@ interface INativeTokenVault { /// @notice The AssetRouter contract function ASSET_ROUTER() external view returns (IAssetRouterBase); + + /// @notice The chain ID of the L1 chain + function L1_CHAIN_ID() external view returns (uint256); + /// @notice Returns the chain ID of the origin chain for a given asset ID function originChainId(bytes32 assetId) external view returns (uint256); @@ -25,8 +29,9 @@ interface INativeTokenVault { /// @notice No access control is ok, since the bridging of tokens should be permissionless. This requires permissionless registration. function registerToken(address _l1Token) external; - /// @notice Used to get the assetId of a token - function getAssetId(uint256 _chainId, address _tokenAddress) external view returns (bytes32); + /// @notice Ensures that the native token is registered with the NTV. + /// @dev This function is used to ensure that the token is registered with the NTV. + function ensureTokenIsRegistered(address _nativeToken) external; /// @notice Used to get the the ERC20 data for a token function getERC20Getters(address _token, uint256 _originChainId) external view returns (bytes memory); @@ -34,6 +39,9 @@ interface INativeTokenVault { /// @notice Used to get the token address of an assetId function tokenAddress(bytes32 assetId) external view returns (address); + /// @notice Used to get the assetId of a token + function assetId(address token) external view returns (bytes32); + /// @notice Used to get the expected bridged token address corresponding to its native counterpart function calculateCreate2TokenAddress(uint256 _originChainId, address _originToken) external view returns (address); } diff --git a/l1-contracts/contracts/bridge/ntv/L1NativeTokenVault.sol b/l1-contracts/contracts/bridge/ntv/L1NativeTokenVault.sol index 9fe19b2f4..d5b059ae6 100644 --- a/l1-contracts/contracts/bridge/ntv/L1NativeTokenVault.sol +++ b/l1-contracts/contracts/bridge/ntv/L1NativeTokenVault.sol @@ -17,13 +17,14 @@ import {NativeTokenVault} from "./NativeTokenVault.sol"; import {IL1AssetHandler} from "../interfaces/IL1AssetHandler.sol"; import {IL1Nullifier} from "../interfaces/IL1Nullifier.sol"; +import {IBridgedStandardToken} from "../interfaces/IBridgedStandardToken.sol"; import {IL1AssetRouter} from "../asset-router/IL1AssetRouter.sol"; import {ETH_TOKEN_ADDRESS} from "../../common/Config.sol"; import {L2_NATIVE_TOKEN_VAULT_ADDR} from "../../common/L2ContractAddresses.sol"; import {DataEncoding} from "../../common/libraries/DataEncoding.sol"; -import {Unauthorized, ZeroAddress, NoFundsTransferred, InsufficientChainBalance, WithdrawFailed} from "../../common/L1ContractErrors.sol"; +import {Unauthorized, ZeroAddress, NoFundsTransferred, InsufficientChainBalance, WithdrawFailed, OriginChainIdNotFound} from "../../common/L1ContractErrors.sol"; /// @author Matter Labs /// @custom:security-contact security@matterlabs.dev @@ -95,6 +96,7 @@ contract L1NativeTokenVault is IL1NativeTokenVault, IL1AssetHandler, NativeToken /// @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 { + ensureTokenIsRegistered(_token); if (_token == ETH_TOKEN_ADDRESS) { uint256 balanceBefore = address(this).balance; L1_NULLIFIER.transferTokenToNTV(_token); @@ -135,6 +137,24 @@ contract L1NativeTokenVault is IL1NativeTokenVault, IL1AssetHandler, NativeToken require(_assetHandlerAddressOnCounterpart == L2_NATIVE_TOKEN_VAULT_ADDR, "NTV: wrong counterpart"); } + function _getOriginChainId(bytes32 _assetId) internal view returns (uint256) { + uint256 chainId = originChainId[_assetId]; + if (chainId != 0) { + return chainId; + } else { + address token = tokenAddress[_assetId]; + if (token == ETH_TOKEN_ADDRESS) { + return block.chainid; + } else if (IERC20(token).balanceOf(address(this)) > 0) { + return block.chainid; + } else if (IERC20(token).balanceOf(address(L1_NULLIFIER)) > 0) { + return block.chainid; + } else { + return 0; + } + } + } + /*////////////////////////////////////////////////////////////// Start transaction Functions //////////////////////////////////////////////////////////////*/ @@ -190,7 +210,14 @@ contract L1NativeTokenVault is IL1NativeTokenVault, IL1AssetHandler, NativeToken } require(callSuccess, "NTV: claimFailedDeposit failed, no funds or cannot transfer to receiver"); } else { - IERC20(l1Token).safeTransfer(_depositSender, _amount); + uint256 originChainId = _getOriginChainId(_assetId); + if (originChainId == block.chainid) { + IERC20(l1Token).safeTransfer(_depositSender, _amount); + } else if (originChainId != 0) { + IBridgedStandardToken(l1Token).bridgeMint(_depositSender, _amount); + } else { + revert OriginChainIdNotFound(); + } // 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. } diff --git a/l1-contracts/contracts/bridge/ntv/L2NativeTokenVault.sol b/l1-contracts/contracts/bridge/ntv/L2NativeTokenVault.sol index e96a6d289..4b83037a7 100644 --- a/l1-contracts/contracts/bridge/ntv/L2NativeTokenVault.sol +++ b/l1-contracts/contracts/bridge/ntv/L2NativeTokenVault.sol @@ -85,9 +85,10 @@ contract L2NativeTokenVault is IL2NativeTokenVault, NativeTokenVault { /// @notice Sets the legacy token asset ID for the given L2 token address. function setLegacyTokenAssetId(address _l2TokenAddress) public { address l1TokenAddress = L2_LEGACY_SHARED_BRIDGE.l1TokenAddress(_l2TokenAddress); - bytes32 assetId = DataEncoding.encodeNTVAssetId(L1_CHAIN_ID, l1TokenAddress); - tokenAddress[assetId] = _l2TokenAddress; - originChainId[assetId] = L1_CHAIN_ID; + bytes32 newAssetId = DataEncoding.encodeNTVAssetId(L1_CHAIN_ID, l1TokenAddress); + tokenAddress[newAssetId] = _l2TokenAddress; + assetId[_l2TokenAddress] = newAssetId; + originChainId[newAssetId] = L1_CHAIN_ID; } /// @notice Ensures that the token is deployed. @@ -114,6 +115,7 @@ contract L2NativeTokenVault is IL2NativeTokenVault, NativeTokenVault { revert AddressMismatch(_originToken, l1LegacyToken); } tokenAddress[_assetId] = expectedToken; + assetId[expectedToken] = _assetId; } else { super._ensureTokenDeployedInner({ _originChainId: _originChainId, @@ -130,7 +132,7 @@ contract L2NativeTokenVault is IL2NativeTokenVault, NativeTokenVault { /// for the code of the proxy. /// @param _salt The salt used for beacon proxy deployment of L2 bridged token. /// @return proxy The beacon proxy, i.e. L2 bridged token. - function _deployBeaconProxy(bytes32 _salt) internal override returns (BeaconProxy proxy) { + function _deployBeaconProxy(bytes32 _salt) internal virtual override returns (BeaconProxy proxy) { if (address(L2_LEGACY_SHARED_BRIDGE) == address(0)) { // Deploy the beacon proxy for the L2 token @@ -175,7 +177,7 @@ contract L2NativeTokenVault is IL2NativeTokenVault, NativeTokenVault { function calculateCreate2TokenAddress( uint256 _originChainId, address _l1Token - ) public view override(INativeTokenVault, NativeTokenVault) returns (address) { + ) public view virtual override(INativeTokenVault, NativeTokenVault) returns (address) { bytes32 constructorInputHash = keccak256(abi.encode(address(bridgedTokenBeacon), "")); bytes32 salt = _getCreate2Salt(_originChainId, _l1Token); if (address(L2_LEGACY_SHARED_BRIDGE) != address(0)) { diff --git a/l1-contracts/contracts/bridge/ntv/NativeTokenVault.sol b/l1-contracts/contracts/bridge/ntv/NativeTokenVault.sol index c636f1a91..c4d11e827 100644 --- a/l1-contracts/contracts/bridge/ntv/NativeTokenVault.sol +++ b/l1-contracts/contracts/bridge/ntv/NativeTokenVault.sol @@ -21,7 +21,7 @@ import {DataEncoding} from "../../common/libraries/DataEncoding.sol"; import {BridgedStandardERC20} from "../BridgedStandardERC20.sol"; import {BridgeHelper} from "../BridgeHelper.sol"; -import {EmptyDeposit, Unauthorized, TokensWithFeesNotSupported, TokenNotSupported, NonEmptyMsgValue, ValueMismatch, AddressMismatch, AssetIdMismatch, AmountMustBeGreaterThanZero, ZeroAddress} from "../../common/L1ContractErrors.sol"; +import {EmptyDeposit, Unauthorized, TokensWithFeesNotSupported, TokenNotSupported, NonEmptyMsgValue, ValueMismatch, AddressMismatch, AssetIdMismatch, AmountMustBeGreaterThanZero, ZeroAddress, L1TokenDeploymentWithZeroChainId, DeployingBridgedTokenForNativeToken} from "../../common/L1ContractErrors.sol"; /// @author Matter Labs /// @custom:security-contact security@matterlabs.dev @@ -52,12 +52,15 @@ abstract contract NativeTokenVault is INativeTokenVault, IAssetHandler, Ownable2 /// @dev A mapping assetId => tokenAddress mapping(bytes32 assetId => address tokenAddress) public tokenAddress; + /// @dev A mapping tokenAddress => assetId + mapping(address tokenAddress => bytes32 assetId) public assetId; + /** * @dev This empty reserved space is put in place to allow future versions to add new * variables without shifting down storage in the inheritance chain. * See https://docs.openzeppelin.com/contracts/4.x/upgradeable#storage_gaps */ - uint256[47] private __gap; + uint256[46] private __gap; /// @notice Checks that the message sender is the bridgehub. modifier onlyAssetRouter() { @@ -92,6 +95,13 @@ abstract contract NativeTokenVault is INativeTokenVault, IAssetHandler, Ownable2 _unsafeRegisterNativeToken(_nativeToken); } + /// @inheritdoc INativeTokenVault + function ensureTokenIsRegistered(address _nativeToken) public { + if (assetId[_nativeToken] == bytes32(0)) { + _registerToken(_nativeToken); + } + } + /*////////////////////////////////////////////////////////////// FINISH TRANSACTION FUNCTIONS //////////////////////////////////////////////////////////////*/ @@ -319,21 +329,15 @@ abstract contract NativeTokenVault is INativeTokenVault, IAssetHandler, Ownable2 return BridgeHelper.getERC20Getters(_token, _originChainId); } - /// @notice Returns the parsed assetId. - /// @param _nativeToken The address of the token to be parsed. - /// @dev Shows the assetId for a given chain and token address - function getAssetId(uint256 _chainId, address _nativeToken) external pure override returns (bytes32) { - return DataEncoding.encodeNTVAssetId(_chainId, _nativeToken); - } - /// @notice Registers a native token address for the vault. /// @dev It does not perform any checks for the correctnesss of the token contract. /// @param _nativeToken The address of the token to be registered. function _unsafeRegisterNativeToken(address _nativeToken) internal { - bytes32 assetId = DataEncoding.encodeNTVAssetId(block.chainid, _nativeToken); + bytes32 newAssetId = DataEncoding.encodeNTVAssetId(block.chainid, _nativeToken); ASSET_ROUTER.setAssetHandlerAddressThisChain(bytes32(uint256(uint160(_nativeToken))), address(this)); - tokenAddress[assetId] = _nativeToken; - originChainId[assetId] = block.chainid; + tokenAddress[newAssetId] = _nativeToken; + assetId[_nativeToken] = newAssetId; + originChainId[newAssetId] = block.chainid; } function _handleChainBalanceIncrease( @@ -390,12 +394,13 @@ abstract contract NativeTokenVault is INativeTokenVault, IAssetHandler, Ownable2 bytes memory _erc20Data, address _expectedToken ) internal { - address deployedToken = _deployBridgedToken(_originChainId, _originToken, _erc20Data); + address deployedToken = _deployBridgedToken(_originChainId, _assetId, _originToken, _erc20Data); if (deployedToken != _expectedToken) { revert AddressMismatch(_expectedToken, deployedToken); } tokenAddress[_assetId] = _expectedToken; + assetId[_expectedToken] = _assetId; } /// @notice Calculates the bridged token address corresponding to native token counterpart. @@ -412,14 +417,26 @@ abstract contract NativeTokenVault is INativeTokenVault, IAssetHandler, Ownable2 /// @return The address of the beacon proxy (bridged token). function _deployBridgedToken( uint256 _originChainId, + bytes32 _assetId, address _originToken, bytes memory _erc20Data ) internal returns (address) { bytes32 salt = _getCreate2Salt(_originChainId, _originToken); BeaconProxy l2Token = _deployBeaconProxy(salt); - uint256 tokenOriginChainId = BridgedStandardERC20(address(l2Token)).bridgeInitialize(_originToken, _erc20Data); + uint256 tokenOriginChainId = BridgedStandardERC20(address(l2Token)).bridgeInitialize( + _assetId, + _originToken, + _erc20Data + ); + // an extra check for legacy tokens on L1, they might not be registered i.e. + if (block.chainid == L1_CHAIN_ID && tokenOriginChainId == 0) { + revert L1TokenDeploymentWithZeroChainId(_assetId); + } tokenOriginChainId = tokenOriginChainId == 0 ? L1_CHAIN_ID : tokenOriginChainId; + if (tokenOriginChainId == block.chainid) { + revert DeployingBridgedTokenForNativeToken(); + } originChainId[DataEncoding.encodeNTVAssetId(tokenOriginChainId, _originToken)] = tokenOriginChainId; return address(l2Token); } diff --git a/l1-contracts/contracts/bridgehub/Bridgehub.sol b/l1-contracts/contracts/bridgehub/Bridgehub.sol index 4df126f9b..e084bc3d1 100644 --- a/l1-contracts/contracts/bridgehub/Bridgehub.sol +++ b/l1-contracts/contracts/bridgehub/Bridgehub.sol @@ -18,7 +18,7 @@ import {ReentrancyGuard} from "../common/ReentrancyGuard.sol"; import {DataEncoding} from "../common/libraries/DataEncoding.sol"; import {IZKChain} from "../state-transition/chain-interfaces/IZKChain.sol"; -import {ETH_TOKEN_ADDRESS, TWO_BRIDGES_MAGIC_VALUE, BRIDGEHUB_MIN_SECOND_BRIDGE_ADDRESS, SETTLEMENT_LAYER_RELAY_SENDER} from "../common/Config.sol"; +import {ETH_TOKEN_ADDRESS, TWO_BRIDGES_MAGIC_VALUE, BRIDGEHUB_MIN_SECOND_BRIDGE_ADDRESS, SETTLEMENT_LAYER_RELAY_SENDER, L1_SETTLEMENT_LAYER_VIRTUAL_ADDRESS} from "../common/Config.sol"; import {BridgehubL2TransactionRequest, L2Message, L2Log, TxStatus} from "../common/Messaging.sol"; import {AddressAliasHelper} from "../vendor/AddressAliasHelper.sol"; import {IMessageRoot} from "./IMessageRoot.sol"; @@ -86,6 +86,9 @@ contract Bridgehub is IBridgehub, ReentrancyGuard, Ownable2StepUpgradeable, Paus /// @dev asset info used to identify chains in the Shared Bridge mapping(bytes32 ctmAssetId => address ctmAddress) public ctmAssetIdToAddress; + /// @dev ctmAddress to ctmAssetId + mapping(address ctmAddress => bytes32 ctmAssetId) public ctmAssetIdFromAddress; + /// @dev used to indicate the currently active settlement layer for a given chainId mapping(uint256 chainId => uint256 activeSettlementLayerChainId) public settlementLayer; @@ -330,6 +333,7 @@ contract Bridgehub is IBridgehub, ReentrancyGuard, Ownable2StepUpgradeable, Paus bytes32 assetInfo = keccak256(abi.encode(L1_CHAIN_ID, sender, _additionalData)); ctmAssetIdToAddress[assetInfo] = _assetAddress; + ctmAssetIdFromAddress[_assetAddress] = assetInfo; emit AssetRegistered(assetInfo, _assetAddress, _additionalData, msg.sender); } @@ -431,10 +435,10 @@ contract Bridgehub is IBridgehub, ReentrancyGuard, Ownable2StepUpgradeable, Paus if (ctmAddress == address(0)) { revert ChainIdNotRegistered(_chainId); } - return ctmAssetId(chainTypeManager[_chainId]); + return ctmAssetIdFromAddress[chainTypeManager[_chainId]]; } - function ctmAssetId(address _ctmAddress) public view override returns (bytes32) { + function calculateCtmAssetId(address _ctmAddress) internal view returns (bytes32) { return keccak256(abi.encode(L1_CHAIN_ID, address(l1CtmDeployer), bytes32(uint256(uint160(_ctmAddress))))); } @@ -709,7 +713,9 @@ contract Bridgehub is IBridgehub, ReentrancyGuard, Ownable2StepUpgradeable, Paus bridgehubData.ctmData ); bytes memory chainMintData = IZKChain(zkChain).forwardedBridgeBurn( - zkChainMap.get(_settlementChainId), + _settlementChainId == L1_CHAIN_ID + ? L1_SETTLEMENT_LAYER_VIRTUAL_ADDRESS + : zkChainMap.get(_settlementChainId), _originalCaller, bridgehubData.chainData ); diff --git a/l1-contracts/contracts/bridgehub/CTMDeploymentTracker.sol b/l1-contracts/contracts/bridgehub/CTMDeploymentTracker.sol index 6a7717ed5..b82ad213b 100644 --- a/l1-contracts/contracts/bridgehub/CTMDeploymentTracker.sol +++ b/l1-contracts/contracts/bridgehub/CTMDeploymentTracker.sol @@ -115,7 +115,7 @@ contract CTMDeploymentTracker is ICTMDeploymentTracker, ReentrancyGuard, Ownable require(_assetHandlerAddressOnCounterpart == L2_BRIDGEHUB_ADDR, "CTMDT: wrong counter part"); } - function getAssetId(address _l1CTM) public view override returns (bytes32) { + function calculateAssetId(address _l1CTM) public view override returns (bytes32) { return keccak256(abi.encode(block.chainid, address(this), bytes32(uint256(uint160(_l1CTM))))); } diff --git a/l1-contracts/contracts/bridgehub/IBridgehub.sol b/l1-contracts/contracts/bridgehub/IBridgehub.sol index 4e4931345..3f05bba35 100644 --- a/l1-contracts/contracts/bridgehub/IBridgehub.sol +++ b/l1-contracts/contracts/bridgehub/IBridgehub.sol @@ -217,7 +217,7 @@ interface IBridgehub is IAssetHandler, IL1AssetHandler { function ctmAssetIdFromChainId(uint256 _chainId) external view returns (bytes32); - function ctmAssetId(address _ctmAddress) external view returns (bytes32); + function ctmAssetIdFromAddress(address _ctmAddress) external view returns (bytes32); function l1CtmDeployer() external view returns (ICTMDeploymentTracker); diff --git a/l1-contracts/contracts/bridgehub/ICTMDeploymentTracker.sol b/l1-contracts/contracts/bridgehub/ICTMDeploymentTracker.sol index 5f75aa990..1b7558f29 100644 --- a/l1-contracts/contracts/bridgehub/ICTMDeploymentTracker.sol +++ b/l1-contracts/contracts/bridgehub/ICTMDeploymentTracker.sol @@ -22,5 +22,5 @@ interface ICTMDeploymentTracker is IL1AssetDeploymentTracker { function registerCTMAssetOnL1(address _ctmAddress) external; - function getAssetId(address _l1CTM) external view returns (bytes32); + function calculateAssetId(address _l1CTM) external view returns (bytes32); } diff --git a/l1-contracts/contracts/common/Config.sol b/l1-contracts/contracts/common/Config.sol index beebcd00c..a1e58f464 100644 --- a/l1-contracts/contracts/common/Config.sol +++ b/l1-contracts/contracts/common/Config.sol @@ -120,6 +120,11 @@ address constant SETTLEMENT_LAYER_RELAY_SENDER = address(uint160(0x1111111111111 /// @dev The metadata version that is supported by the ZK Chains to prove that an L2->L1 log was included in a batch. uint256 constant SUPPORTED_PROOF_METADATA_VERSION = 1; +/// @dev The virtual address of the L1 settlement layer. +address constant L1_SETTLEMENT_LAYER_VIRTUAL_ADDRESS = address( + uint160(uint256(keccak256("L1_SETTLEMENT_LAYER_VIRTUAL_ADDRESS")) - 1) +); + struct PriorityTreeCommitment { uint256 nextLeafIndex; uint256 startIndex; diff --git a/l1-contracts/contracts/common/L1ContractErrors.sol b/l1-contracts/contracts/common/L1ContractErrors.sol index 00812e606..ab1320968 100644 --- a/l1-contracts/contracts/common/L1ContractErrors.sol +++ b/l1-contracts/contracts/common/L1ContractErrors.sol @@ -39,7 +39,7 @@ error AmountMustBeGreaterThanZero(); error AssetHandlerDoesNotExist(bytes32 assetId); // 0x1294e9e1 error AssetIdMismatch(bytes32 expected, bytes32 supplied); -// +// 0xfe919e28 error AssetIdAlreadyRegistered(); // 0x0bfcef28 error AlreadyWhitelisted(address); @@ -59,7 +59,7 @@ error BatchNumberMismatch(uint256 expectedBatchNumber, uint256 providedBatchNumb error BlobHashCommitmentError(uint256 index, bool blobHashEmpty, bool blobCommitmentEmpty); // 0x6cf12312 error BridgeHubAlreadyRegistered(); -// +// 0xdb538614 error BridgeMintNotImplemented(); // 0xcf102c5a error CalldataLengthTooBig(); @@ -75,9 +75,9 @@ error ChainIdAlreadyExists(); error ChainIdCantBeCurrentChain(); // 0xa179f8c9 error ChainIdMismatch(); -// +// 0x23f3c357 error ChainIdNotRegistered(uint256 chainId); -// +// 0x5de72107 error ChainNotLegacy(); // 0x78d2ed02 error ChainAlreadyLive(); @@ -87,8 +87,10 @@ error ChainIdTooBig(); error DelegateCallFailed(bytes returnData); // 0x0a8ed92c error DenominatorIsZero(); -// +// 0xb4f54111 error DeployFailed(); +// 0x138ee1a3 +error DeployingBridgedTokenForNativeToken(); // 0xc7c9660f error DepositDoesNotExist(); // 0xad2fa98e @@ -101,17 +103,17 @@ error DiamondAlreadyFrozen(); error DiamondFreezeIncorrectState(); // 0xa7151b9a error DiamondNotFrozen(); -// +// 0x7138356f error EmptyAddress(); // 0x2d4d012f error EmptyAssetId(); // 0xfc7ab1d3 error EmptyBlobVersionHash(uint256 index); -// +// 0x1c25715b error EmptyBytes32(); // 0x95b66fe9 error EmptyDeposit(); -// +// 0x627e0872 error ETHDepositNotSupported(); // error FailedToTransferTokens(address tokenContract, address to, uint256 amount); @@ -119,6 +121,7 @@ error FailedToTransferTokens(address tokenContract, address to, uint256 amount); error FacetExists(bytes4 selector, address); // 0x79e12cc3 error FacetIsFrozen(bytes4 func); +/// error FunctionNotSupported(); // 0xc91cf3b1 error GasPerPubdataMismatch(); @@ -144,7 +147,7 @@ error IncorrectBridgeHubAddress(address bridgehub); error InsufficientChainBalance(); // 0x356680b7 error InsufficientFunds(); -// +// 0xcbd9d2e0 error InvalidCaller(address); // 0x7a47c9a2 error InvalidChainId(); @@ -180,6 +183,8 @@ error InvalidTxType(uint256 txType); error InvalidUpgradeTxn(UpgradeTxVerifyParam); // 0xaa7feadc error InvalidValue(); +// 0x888b2f09 +error L1TokenDeploymentWithZeroChainId(bytes32 assetId); // 0xa4f62e33 error L2BridgeNotDeployed(uint256 chainId); // 0xff8811ff @@ -210,7 +215,7 @@ error MerkleIndexOutOfBounds(); error MerklePathEmpty(); // 0x1c500385 error MerklePathOutOfBounds(); -// +// 0x3312a450 error MigrationPaused(); // 0xfa44b527 error MissingSystemLogs(uint256 expected, uint256 actual); @@ -236,7 +241,7 @@ error NonEmptyMsgValue(); error NonIncreasingTimestamp(); // 0x0105f9c0 error NonSequentialBatch(); -// +// 0x0ac76f01 error NonSequentialVersion(); // 0x4ef79e5a error NonZeroAddress(address); @@ -254,6 +259,8 @@ error OperationExists(); error OperationMustBePending(); // 0xe1c1ff37 error OperationMustBeReady(); +// 0xb926450e +error OriginChainIdNotFound(); // 0xd7f50a9d error PatchCantSetUpgradeTxn(); // 0x962fd7d0 @@ -360,7 +367,7 @@ error UnexpectedSystemLog(uint256 logKey); error UnimplementedMessage(string); // 0xf093c2e5 error UpgradeBatchNumberIsNotZero(); -// +// 0x084a1449 error UnsupportedEncodingVersion(); // error UnsupportedPaymasterFlow(); diff --git a/l1-contracts/contracts/dev-contracts/test/L2NativeTokenVaultDev.sol b/l1-contracts/contracts/dev-contracts/test/L2NativeTokenVaultDev.sol new file mode 100644 index 000000000..fc1991503 --- /dev/null +++ b/l1-contracts/contracts/dev-contracts/test/L2NativeTokenVaultDev.sol @@ -0,0 +1,78 @@ +// SPDX-License-Identifier: MIT + +pragma solidity 0.8.24; + +import {BeaconProxy} from "@openzeppelin/contracts-v4/proxy/beacon/BeaconProxy.sol"; +import {Create2} from "@openzeppelin/contracts-v4/utils/Create2.sol"; +import {IBeacon} from "@openzeppelin/contracts-v4/proxy/beacon/IBeacon.sol"; +import {UpgradeableBeacon} from "@openzeppelin/contracts-v4/proxy/beacon/UpgradeableBeacon.sol"; + +import {INativeTokenVault} from "contracts/bridge/ntv/INativeTokenVault.sol"; +import {NativeTokenVault} from "contracts/bridge/ntv/NativeTokenVault.sol"; +import {L2NativeTokenVault} from "contracts/bridge/ntv/L2NativeTokenVault.sol"; +import {BridgedStandardERC20} from "contracts/bridge/BridgedStandardERC20.sol"; + +/// @author Matter Labs +/// @notice This is used for fast debugging of the L2NTV by running it in L1 context, i.e. normal foundry instead of foundry --zksync. +contract L2NativeTokenVaultDev is L2NativeTokenVault { + constructor( + uint256 _l1ChainId, + address _aliasedOwner, + bytes32 _l2TokenProxyBytecodeHash, + address _legacySharedBridge, + address _bridgedTokenBeacon, + bool _contractsDeployedAlready, + address _wethToken, + bytes32 _baseTokenAssetId + ) + L2NativeTokenVault( + _l1ChainId, + _aliasedOwner, + _l2TokenProxyBytecodeHash, + _legacySharedBridge, + _bridgedTokenBeacon, + _contractsDeployedAlready, + _wethToken, + _baseTokenAssetId + ) + {} + + /// @notice copied from L1NTV for L1 compilation + function calculateCreate2TokenAddress( + uint256 _originChainId, + address _l1Token + ) public view override(L2NativeTokenVault) returns (address) { + bytes32 salt = _getCreate2Salt(_originChainId, _l1Token); + return + Create2.computeAddress( + salt, + keccak256(abi.encodePacked(type(BeaconProxy).creationCode, abi.encode(bridgedTokenBeacon, ""))) + ); + } + + function deployBridgedStandardERC20(address _owner) external { + _transferOwnership(_owner); + + address l2StandardToken = address(new BridgedStandardERC20{salt: bytes32(0)}()); + + UpgradeableBeacon tokenBeacon = new UpgradeableBeacon{salt: bytes32(0)}(l2StandardToken); + + tokenBeacon.transferOwnership(owner()); + bridgedTokenBeacon = IBeacon(address(tokenBeacon)); + emit L2TokenBeaconUpdated(address(bridgedTokenBeacon), l2TokenProxyBytecodeHash); + } + + function test() external pure { + // test + } + + function _deployBeaconProxy(bytes32 _salt) internal virtual override returns (BeaconProxy proxy) { + // Use CREATE2 to deploy the BeaconProxy + address proxyAddress = Create2.deploy( + 0, + _salt, + abi.encodePacked(type(BeaconProxy).creationCode, abi.encode(bridgedTokenBeacon, "")) + ); + return BeaconProxy(payable(proxyAddress)); + } +} diff --git a/l1-contracts/contracts/state-transition/chain-interfaces/IAdmin.sol b/l1-contracts/contracts/state-transition/chain-interfaces/IAdmin.sol index 4a2ad7170..daf155e3a 100644 --- a/l1-contracts/contracts/state-transition/chain-interfaces/IAdmin.sol +++ b/l1-contracts/contracts/state-transition/chain-interfaces/IAdmin.sol @@ -6,6 +6,7 @@ import {IZKChainBase} from "../chain-interfaces/IZKChainBase.sol"; import {Diamond} from "../libraries/Diamond.sol"; import {FeeParams, PubdataPricingMode} from "../chain-deps/ZKChainStorage.sol"; +import {ZKChainCommitment} from "../../common/Config.sol"; /// @title The interface of the Admin Contract that controls access rights for contract management. /// @author Matter Labs @@ -149,4 +150,6 @@ interface IAdmin is IZKChainBase { /// @dev Similar to IL1AssetHandler interface, used to receive chains. function forwardedBridgeMint(bytes calldata _data, bool _contractAlreadyDeployed) external payable; + + function prepareChainCommitment() external view returns (ZKChainCommitment memory commitment); } diff --git a/l1-contracts/deploy-scripts/DeployL1.s.sol b/l1-contracts/deploy-scripts/DeployL1.s.sol index 26bd82396..8a10c0017 100644 --- a/l1-contracts/deploy-scripts/DeployL1.s.sol +++ b/l1-contracts/deploy-scripts/DeployL1.s.sol @@ -56,129 +56,12 @@ import {IAssetRouterBase} from "contracts/bridge/asset-router/IAssetRouterBase.s import {L2ContractsBytecodesLib} from "./L2ContractsBytecodesLib.sol"; import {ValidiumL1DAValidator} from "contracts/state-transition/data-availability/ValidiumL1DAValidator.sol"; -struct FixedForceDeploymentsData { - uint256 l1ChainId; - uint256 eraChainId; - address l1AssetRouter; - bytes32 l2TokenProxyBytecodeHash; - address aliasedL1Governance; - uint256 maxNumberOfZKChains; - bytes32 bridgehubBytecodeHash; - bytes32 l2AssetRouterBytecodeHash; - bytes32 l2NtvBytecodeHash; - bytes32 messageRootBytecodeHash; - address l2SharedBridgeLegacyImpl; - address l2BridgedStandardERC20Impl; - address l2BridgeProxyOwnerAddress; - address l2BridgedStandardERC20ProxyOwnerAddress; -} +import {DeployUtils, GeneratedData, Config, DeployedAddresses, FixedForceDeploymentsData} from "./DeployUtils.s.sol"; -contract DeployL1Script is Script { +contract DeployL1Script is Script, DeployUtils { using stdToml for string; address internal constant ADDRESS_ONE = 0x0000000000000000000000000000000000000001; - address internal constant DETERMINISTIC_CREATE2_ADDRESS = 0x4e59b44847b379578588920cA78FbF26c0B4956C; - - // solhint-disable-next-line gas-struct-packing - struct DeployedAddresses { - BridgehubDeployedAddresses bridgehub; - StateTransitionDeployedAddresses stateTransition; - BridgesDeployedAddresses bridges; - L1NativeTokenVaultAddresses vaults; - DataAvailabilityDeployedAddresses daAddresses; - address transparentProxyAdmin; - address governance; - address chainAdmin; - address accessControlRestrictionAddress; - address blobVersionedHashRetriever; - address validatorTimelock; - address create2Factory; - } - - // solhint-disable-next-line gas-struct-packing - struct L1NativeTokenVaultAddresses { - address l1NativeTokenVaultImplementation; - address l1NativeTokenVaultProxy; - } - - struct DataAvailabilityDeployedAddresses { - address l1RollupDAValidator; - address l1ValidiumDAValidator; - } - - // solhint-disable-next-line gas-struct-packing - struct BridgehubDeployedAddresses { - address bridgehubImplementation; - address bridgehubProxy; - address ctmDeploymentTrackerImplementation; - address ctmDeploymentTrackerProxy; - address messageRootImplementation; - address messageRootProxy; - } - - // solhint-disable-next-line gas-struct-packing - struct BridgesDeployedAddresses { - address erc20BridgeImplementation; - address erc20BridgeProxy; - address sharedBridgeImplementation; - address sharedBridgeProxy; - address l1NullifierImplementation; - address l1NullifierProxy; - address bridgedStandardERC20Implementation; - address bridgedTokenBeacon; - } - - // solhint-disable-next-line gas-struct-packing - struct Config { - uint256 l1ChainId; - address deployerAddress; - uint256 eraChainId; - address ownerAddress; - bool testnetVerifier; - ContractsConfig contracts; - TokensConfig tokens; - } - - // solhint-disable-next-line gas-struct-packing - struct GeneratedData { - bytes forceDeploymentsData; - } - - // solhint-disable-next-line gas-struct-packing - struct ContractsConfig { - bytes32 create2FactorySalt; - address create2FactoryAddr; - address multicall3Addr; - uint256 validatorTimelockExecutionDelay; - bytes32 genesisRoot; - uint256 genesisRollupLeafIndex; - bytes32 genesisBatchCommitment; - uint256 latestProtocolVersion; - bytes32 recursionNodeLevelVkHash; - bytes32 recursionLeafLevelVkHash; - bytes32 recursionCircuitsSetVksHash; - uint256 priorityTxMaxGasLimit; - PubdataPricingMode diamondInitPubdataPricingMode; - uint256 diamondInitBatchOverheadL1Gas; - uint256 diamondInitMaxPubdataPerBatch; - uint256 diamondInitMaxL2GasPerBatch; - uint256 diamondInitPriorityTxMaxPubdata; - uint256 diamondInitMinimalL2GasPrice; - address governanceSecurityCouncilAddress; - uint256 governanceMinDelay; - uint256 maxNumberOfChains; - bytes diamondCutData; - bytes32 bootloaderHash; - bytes32 defaultAAHash; - } - - struct TokensConfig { - address tokenWethAddress; - } - - Config internal config; - GeneratedData internal generatedData; - DeployedAddresses internal addresses; function run() public { console.log("Deploying L1 contracts"); @@ -190,11 +73,20 @@ contract DeployL1Script is Script { runInner(vm.envString("L1_CONFIG"), vm.envString("L1_OUTPUT")); } + function getAddresses() public view returns (DeployedAddresses memory) { + return addresses; + } + + function getConfig() public view returns (Config memory) { + return config; + } + function runInner(string memory inputPath, string memory outputPath) internal { string memory root = vm.projectRoot(); inputPath = string.concat(root, inputPath); outputPath = string.concat(root, outputPath); + saveDiamondSelectors(); initializeConfig(inputPath); instantiateCreate2Factory(); @@ -229,6 +121,7 @@ contract DeployL1Script is Script { deployBlobVersionedHashRetriever(); deployChainTypeManagerContract(); + registerChainTypeManager(); setChainTypeManagerInValidatorTimelock(); updateOwners(); @@ -236,226 +129,38 @@ contract DeployL1Script is Script { saveOutput(outputPath); } - function getBridgehubProxyAddress() public view returns (address) { - return addresses.bridgehub.bridgehubProxy; - } - - function getSharedBridgeProxyAddress() public view returns (address) { - return addresses.bridges.sharedBridgeProxy; - } - - function getNativeTokenVaultProxyAddress() public view returns (address) { - return addresses.vaults.l1NativeTokenVaultProxy; - } - - function getL1NullifierProxyAddress() public view returns (address) { - return addresses.bridges.l1NullifierProxy; - } - - function getOwnerAddress() public view returns (address) { - return config.ownerAddress; - } - - function getCTM() public view returns (address) { - return addresses.stateTransition.chainTypeManagerProxy; - } - - function getInitialDiamondCutData() public view returns (bytes memory) { - return config.contracts.diamondCutData; - } - - function getCTMDeploymentTrackerAddress() public view returns (address) { - return addresses.bridgehub.ctmDeploymentTrackerProxy; - } - - function initializeConfig(string memory configPath) internal { - string memory toml = vm.readFile(configPath); - - config.l1ChainId = block.chainid; - config.deployerAddress = msg.sender; - - // Config file must be parsed key by key, otherwise values returned - // are parsed alfabetically and not by key. - // https://book.getfoundry.sh/cheatcodes/parse-toml - config.eraChainId = toml.readUint("$.era_chain_id"); - config.ownerAddress = toml.readAddress("$.owner_address"); - config.testnetVerifier = toml.readBool("$.testnet_verifier"); - - config.contracts.governanceSecurityCouncilAddress = toml.readAddress( - "$.contracts.governance_security_council_address" - ); - config.contracts.governanceMinDelay = toml.readUint("$.contracts.governance_min_delay"); - config.contracts.maxNumberOfChains = toml.readUint("$.contracts.max_number_of_chains"); - config.contracts.create2FactorySalt = toml.readBytes32("$.contracts.create2_factory_salt"); - if (vm.keyExistsToml(toml, "$.contracts.create2_factory_addr")) { - config.contracts.create2FactoryAddr = toml.readAddress("$.contracts.create2_factory_addr"); - } - config.contracts.validatorTimelockExecutionDelay = toml.readUint( - "$.contracts.validator_timelock_execution_delay" - ); - config.contracts.genesisRoot = toml.readBytes32("$.contracts.genesis_root"); - config.contracts.genesisRollupLeafIndex = toml.readUint("$.contracts.genesis_rollup_leaf_index"); - config.contracts.genesisBatchCommitment = toml.readBytes32("$.contracts.genesis_batch_commitment"); - config.contracts.latestProtocolVersion = toml.readUint("$.contracts.latest_protocol_version"); - config.contracts.recursionNodeLevelVkHash = toml.readBytes32("$.contracts.recursion_node_level_vk_hash"); - config.contracts.recursionLeafLevelVkHash = toml.readBytes32("$.contracts.recursion_leaf_level_vk_hash"); - config.contracts.recursionCircuitsSetVksHash = toml.readBytes32("$.contracts.recursion_circuits_set_vks_hash"); - config.contracts.priorityTxMaxGasLimit = toml.readUint("$.contracts.priority_tx_max_gas_limit"); - config.contracts.diamondInitPubdataPricingMode = PubdataPricingMode( - toml.readUint("$.contracts.diamond_init_pubdata_pricing_mode") - ); - config.contracts.diamondInitBatchOverheadL1Gas = toml.readUint( - "$.contracts.diamond_init_batch_overhead_l1_gas" - ); - config.contracts.diamondInitMaxPubdataPerBatch = toml.readUint( - "$.contracts.diamond_init_max_pubdata_per_batch" - ); - config.contracts.diamondInitMaxL2GasPerBatch = toml.readUint("$.contracts.diamond_init_max_l2_gas_per_batch"); - config.contracts.diamondInitPriorityTxMaxPubdata = toml.readUint( - "$.contracts.diamond_init_priority_tx_max_pubdata" - ); - config.contracts.diamondInitMinimalL2GasPrice = toml.readUint("$.contracts.diamond_init_minimal_l2_gas_price"); - config.contracts.defaultAAHash = toml.readBytes32("$.contracts.default_aa_hash"); - config.contracts.bootloaderHash = toml.readBytes32("$.contracts.bootloader_hash"); - - config.tokens.tokenWethAddress = toml.readAddress("$.tokens.token_weth_address"); - } - function initializeGeneratedData() internal { generatedData.forceDeploymentsData = prepareForceDeploymentsData(); } - function instantiateCreate2Factory() internal { - address contractAddress; - - bool isDeterministicDeployed = DETERMINISTIC_CREATE2_ADDRESS.code.length > 0; - bool isConfigured = config.contracts.create2FactoryAddr != address(0); - - if (isConfigured) { - if (config.contracts.create2FactoryAddr.code.length == 0) { - revert AddressHasNoCode(config.contracts.create2FactoryAddr); - } - contractAddress = config.contracts.create2FactoryAddr; - console.log("Using configured Create2Factory address:", contractAddress); - } else if (isDeterministicDeployed) { - contractAddress = DETERMINISTIC_CREATE2_ADDRESS; - console.log("Using deterministic Create2Factory address:", contractAddress); - } else { - contractAddress = Utils.deployCreate2Factory(); - console.log("Create2Factory deployed at:", contractAddress); - } - - addresses.create2Factory = contractAddress; - } - function deployIfNeededMulticall3() internal { // Multicall3 is already deployed on public networks if (MULTICALL3_ADDRESS.code.length == 0) { - address contractAddress = deployViaCreate2(type(Multicall3).creationCode); + address contractAddress = deployViaCreate2(type(Multicall3).creationCode, ""); console.log("Multicall3 deployed at:", contractAddress); config.contracts.multicall3Addr = contractAddress; } else { config.contracts.multicall3Addr = MULTICALL3_ADDRESS; } } - - function deployVerifier() internal { - bytes memory code; - if (config.testnetVerifier) { - code = type(TestnetVerifier).creationCode; - } else { - code = type(Verifier).creationCode; - } - address contractAddress = deployViaCreate2(code); - console.log("Verifier deployed at:", contractAddress); - addresses.stateTransition.verifier = contractAddress; - } - - function deployDefaultUpgrade() internal { - address contractAddress = deployViaCreate2(type(DefaultUpgrade).creationCode); - console.log("DefaultUpgrade deployed at:", contractAddress); - addresses.stateTransition.defaultUpgrade = contractAddress; - } - - function deployGenesisUpgrade() internal { - bytes memory bytecode = abi.encodePacked(type(L1GenesisUpgrade).creationCode); - address contractAddress = deployViaCreate2(bytecode); - console.log("GenesisUpgrade deployed at:", contractAddress); - addresses.stateTransition.genesisUpgrade = contractAddress; - } - function deployDAValidators() internal { - address contractAddress = deployViaCreate2(Utils.readRollupDAValidatorBytecode()); + address contractAddress = deployViaCreate2(Utils.readRollupDAValidatorBytecode(), ""); console.log("L1RollupDAValidator deployed at:", contractAddress); addresses.daAddresses.l1RollupDAValidator = contractAddress; - contractAddress = deployViaCreate2(type(ValidiumL1DAValidator).creationCode); + contractAddress = deployViaCreate2(type(ValidiumL1DAValidator).creationCode, ""); console.log("L1ValidiumDAValidator deployed at:", contractAddress); addresses.daAddresses.l1ValidiumDAValidator = contractAddress; } - - function deployValidatorTimelock() internal { - uint32 executionDelay = uint32(config.contracts.validatorTimelockExecutionDelay); - bytes memory bytecode = abi.encodePacked( - type(ValidatorTimelock).creationCode, - abi.encode(config.deployerAddress, executionDelay, config.eraChainId) - ); - address contractAddress = deployViaCreate2(bytecode); - console.log("ValidatorTimelock deployed at:", contractAddress); - addresses.validatorTimelock = contractAddress; - } - - function deployGovernance() internal { - bytes memory bytecode = abi.encodePacked( - type(Governance).creationCode, - abi.encode( - config.ownerAddress, - config.contracts.governanceSecurityCouncilAddress, - config.contracts.governanceMinDelay - ) - ); - address contractAddress = deployViaCreate2(bytecode); - console.log("Governance deployed at:", contractAddress); - addresses.governance = contractAddress; - } - - function deployChainAdmin() internal { - bytes memory accessControlRestrictionBytecode = abi.encodePacked( - type(AccessControlRestriction).creationCode, - abi.encode(uint256(0), config.ownerAddress) - ); - - address accessControlRestriction = deployViaCreate2(accessControlRestrictionBytecode); - console.log("Access control restriction deployed at:", accessControlRestriction); - address[] memory restrictions = new address[](1); - restrictions[0] = accessControlRestriction; - addresses.accessControlRestrictionAddress = accessControlRestriction; - - bytes memory bytecode = abi.encodePacked(type(ChainAdmin).creationCode, abi.encode(restrictions)); - address contractAddress = deployViaCreate2(bytecode); - console.log("ChainAdmin deployed at:", contractAddress); - addresses.chainAdmin = contractAddress; - } - - function deployTransparentProxyAdmin() internal { - vm.startBroadcast(); - ProxyAdmin proxyAdmin = new ProxyAdmin(); - proxyAdmin.transferOwnership(addresses.governance); - vm.stopBroadcast(); - console.log("Transparent Proxy Admin deployed at:", address(proxyAdmin)); - addresses.transparentProxyAdmin = address(proxyAdmin); - } - function deployBridgehubContract() internal { - bytes memory bridgeHubBytecode = abi.encodePacked( + address bridgehubImplementation = deployViaCreate2( type(Bridgehub).creationCode, abi.encode(config.l1ChainId, config.ownerAddress, (config.contracts.maxNumberOfChains)) ); - address bridgehubImplementation = deployViaCreate2(bridgeHubBytecode); console.log("Bridgehub Implementation deployed at:", bridgehubImplementation); addresses.bridgehub.bridgehubImplementation = bridgehubImplementation; - bytes memory bytecode = abi.encodePacked( + address bridgehubProxy = deployViaCreate2( type(TransparentUpgradeableProxy).creationCode, abi.encode( bridgehubImplementation, @@ -463,21 +168,19 @@ contract DeployL1Script is Script { abi.encodeCall(Bridgehub.initialize, (config.deployerAddress)) ) ); - address bridgehubProxy = deployViaCreate2(bytecode); console.log("Bridgehub Proxy deployed at:", bridgehubProxy); addresses.bridgehub.bridgehubProxy = bridgehubProxy; } function deployMessageRootContract() internal { - bytes memory messageRootBytecode = abi.encodePacked( + address messageRootImplementation = deployViaCreate2( type(MessageRoot).creationCode, abi.encode(addresses.bridgehub.bridgehubProxy) ); - address messageRootImplementation = deployViaCreate2(messageRootBytecode); console.log("MessageRoot Implementation deployed at:", messageRootImplementation); addresses.bridgehub.messageRootImplementation = messageRootImplementation; - bytes memory bytecode = abi.encodePacked( + address messageRootProxy = deployViaCreate2( type(TransparentUpgradeableProxy).creationCode, abi.encode( messageRootImplementation, @@ -485,21 +188,19 @@ contract DeployL1Script is Script { abi.encodeCall(MessageRoot.initialize, ()) ) ); - address messageRootProxy = deployViaCreate2(bytecode); console.log("Message Root Proxy deployed at:", messageRootProxy); addresses.bridgehub.messageRootProxy = messageRootProxy; } function deployCTMDeploymentTracker() internal { - bytes memory ctmDTBytecode = abi.encodePacked( + address ctmDTImplementation = deployViaCreate2( type(CTMDeploymentTracker).creationCode, abi.encode(addresses.bridgehub.bridgehubProxy, addresses.bridges.sharedBridgeProxy) ); - address ctmDTImplementation = deployViaCreate2(ctmDTBytecode); console.log("CTM Deployment Tracker Implementation deployed at:", ctmDTImplementation); addresses.bridgehub.ctmDeploymentTrackerImplementation = ctmDTImplementation; - bytes memory bytecode = abi.encodePacked( + address ctmDTProxy = deployViaCreate2( type(TransparentUpgradeableProxy).creationCode, abi.encode( ctmDTImplementation, @@ -507,7 +208,6 @@ contract DeployL1Script is Script { abi.encodeCall(CTMDeploymentTracker.initialize, (config.deployerAddress)) ) ); - address ctmDTProxy = deployViaCreate2(bytecode); console.log("CTM Deployment Tracker Proxy deployed at:", ctmDTProxy); addresses.bridgehub.ctmDeploymentTrackerProxy = ctmDTProxy; } @@ -515,144 +215,10 @@ contract DeployL1Script is Script { function deployBlobVersionedHashRetriever() internal { // solc contracts/state-transition/utils/blobVersionedHashRetriever.yul --strict-assembly --bin bytes memory bytecode = hex"600b600b5f39600b5ff3fe5f358049805f5260205ff3"; - address contractAddress = deployViaCreate2(bytecode); + address contractAddress = deployViaCreate2(bytecode, ""); console.log("BlobVersionedHashRetriever deployed at:", contractAddress); addresses.blobVersionedHashRetriever = contractAddress; } - - function deployChainTypeManagerContract() internal { - deployStateTransitionDiamondFacets(); - deployChainTypeManagerImplementation(); - deployChainTypeManagerProxy(); - registerChainTypeManager(); - } - - function deployStateTransitionDiamondFacets() internal { - address executorFacet = deployViaCreate2(type(ExecutorFacet).creationCode); - console.log("ExecutorFacet deployed at:", executorFacet); - addresses.stateTransition.executorFacet = executorFacet; - - address adminFacet = deployViaCreate2( - abi.encodePacked(type(AdminFacet).creationCode, abi.encode(config.l1ChainId)) - ); - console.log("AdminFacet deployed at:", adminFacet); - addresses.stateTransition.adminFacet = adminFacet; - - address mailboxFacet = deployViaCreate2( - abi.encodePacked(type(MailboxFacet).creationCode, abi.encode(config.eraChainId, config.l1ChainId)) - ); - console.log("MailboxFacet deployed at:", mailboxFacet); - addresses.stateTransition.mailboxFacet = mailboxFacet; - - address gettersFacet = deployViaCreate2(type(GettersFacet).creationCode); - console.log("GettersFacet deployed at:", gettersFacet); - addresses.stateTransition.gettersFacet = gettersFacet; - - address diamondInit = deployViaCreate2(type(DiamondInit).creationCode); - console.log("DiamondInit deployed at:", diamondInit); - addresses.stateTransition.diamondInit = diamondInit; - } - - function deployChainTypeManagerImplementation() internal { - bytes memory bytecode = abi.encodePacked( - type(ChainTypeManager).creationCode, - abi.encode(addresses.bridgehub.bridgehubProxy) - ); - address contractAddress = deployViaCreate2(bytecode); - console.log("ChainTypeManagerImplementation deployed at:", contractAddress); - addresses.stateTransition.chainTypeManagerImplementation = contractAddress; - } - - function deployChainTypeManagerProxy() internal { - Diamond.FacetCut[] memory facetCuts = new Diamond.FacetCut[](4); - facetCuts[0] = Diamond.FacetCut({ - facet: addresses.stateTransition.adminFacet, - action: Diamond.Action.Add, - isFreezable: false, - selectors: Utils.getAllSelectors(addresses.stateTransition.adminFacet.code) - }); - facetCuts[1] = Diamond.FacetCut({ - facet: addresses.stateTransition.gettersFacet, - action: Diamond.Action.Add, - isFreezable: false, - selectors: Utils.getAllSelectors(addresses.stateTransition.gettersFacet.code) - }); - facetCuts[2] = Diamond.FacetCut({ - facet: addresses.stateTransition.mailboxFacet, - action: Diamond.Action.Add, - isFreezable: true, - selectors: Utils.getAllSelectors(addresses.stateTransition.mailboxFacet.code) - }); - facetCuts[3] = Diamond.FacetCut({ - facet: addresses.stateTransition.executorFacet, - action: Diamond.Action.Add, - isFreezable: true, - selectors: Utils.getAllSelectors(addresses.stateTransition.executorFacet.code) - }); - - VerifierParams memory verifierParams = VerifierParams({ - recursionNodeLevelVkHash: config.contracts.recursionNodeLevelVkHash, - recursionLeafLevelVkHash: config.contracts.recursionLeafLevelVkHash, - recursionCircuitsSetVksHash: config.contracts.recursionCircuitsSetVksHash - }); - - FeeParams memory feeParams = FeeParams({ - pubdataPricingMode: config.contracts.diamondInitPubdataPricingMode, - batchOverheadL1Gas: uint32(config.contracts.diamondInitBatchOverheadL1Gas), - maxPubdataPerBatch: uint32(config.contracts.diamondInitMaxPubdataPerBatch), - maxL2GasPerBatch: uint32(config.contracts.diamondInitMaxL2GasPerBatch), - priorityTxMaxPubdata: uint32(config.contracts.diamondInitPriorityTxMaxPubdata), - minimalL2GasPrice: uint64(config.contracts.diamondInitMinimalL2GasPrice) - }); - - DiamondInitializeDataNewChain memory initializeData = DiamondInitializeDataNewChain({ - verifier: IVerifier(addresses.stateTransition.verifier), - verifierParams: verifierParams, - l2BootloaderBytecodeHash: config.contracts.bootloaderHash, - l2DefaultAccountBytecodeHash: config.contracts.defaultAAHash, - priorityTxMaxGasLimit: config.contracts.priorityTxMaxGasLimit, - feeParams: feeParams, - blobVersionedHashRetriever: addresses.blobVersionedHashRetriever - }); - - Diamond.DiamondCutData memory diamondCut = Diamond.DiamondCutData({ - facetCuts: facetCuts, - initAddress: addresses.stateTransition.diamondInit, - initCalldata: abi.encode(initializeData) - }); - - config.contracts.diamondCutData = abi.encode(diamondCut); - - ChainCreationParams memory chainCreationParams = ChainCreationParams({ - genesisUpgrade: addresses.stateTransition.genesisUpgrade, - genesisBatchHash: config.contracts.genesisRoot, - genesisIndexRepeatedStorageChanges: uint64(config.contracts.genesisRollupLeafIndex), - genesisBatchCommitment: config.contracts.genesisBatchCommitment, - diamondCut: diamondCut, - forceDeploymentsData: generatedData.forceDeploymentsData - }); - - ChainTypeManagerInitializeData memory diamondInitData = ChainTypeManagerInitializeData({ - owner: msg.sender, - validatorTimelock: addresses.validatorTimelock, - chainCreationParams: chainCreationParams, - protocolVersion: config.contracts.latestProtocolVersion - }); - - address contractAddress = deployViaCreate2( - abi.encodePacked( - type(TransparentUpgradeableProxy).creationCode, - abi.encode( - addresses.stateTransition.chainTypeManagerImplementation, - addresses.transparentProxyAdmin, - abi.encodeCall(ChainTypeManager.initialize, (diamondInitData)) - ) - ) - ); - console.log("ChainTypeManagerProxy deployed at:", contractAddress); - addresses.stateTransition.chainTypeManagerProxy = contractAddress; - } - function registerChainTypeManager() internal { Bridgehub bridgehub = Bridgehub(addresses.bridgehub.bridgehubProxy); vm.startBroadcast(msg.sender); @@ -671,7 +237,7 @@ contract DeployL1Script is Script { vm.stopBroadcast(); console.log("CTM registered in CTMDeploymentTracker"); - bytes32 assetId = bridgehub.ctmAssetId(addresses.stateTransition.chainTypeManagerProxy); + bytes32 assetId = bridgehub.ctmAssetIdFromAddress(addresses.stateTransition.chainTypeManagerProxy); // console.log(address(bridgehub.ctmDeployer()), addresses.bridgehub.ctmDeploymentTrackerProxy); // console.log(address(bridgehub.ctmDeployer().BRIDGE_HUB()), addresses.bridgehub.bridgehubProxy); console.log( @@ -701,11 +267,10 @@ contract DeployL1Script is Script { initAddress: address(0), initCalldata: "" }); - bytes memory bytecode = abi.encodePacked( + address contractAddress = deployViaCreate2( type(DiamondProxy).creationCode, abi.encode(config.l1ChainId, diamondCut) ); - address contractAddress = deployViaCreate2(bytecode); console.log("DiamondProxy deployed at:", contractAddress); addresses.stateTransition.diamondProxy = contractAddress; } @@ -722,29 +287,27 @@ contract DeployL1Script is Script { function deployL1NullifierImplementation() internal { // TODO(EVM-743): allow non-dev nullifier in the local deployment - bytes memory bytecode = abi.encodePacked( + address contractAddress = deployViaCreate2( type(L1NullifierDev).creationCode, // solhint-disable-next-line func-named-parameters abi.encode(addresses.bridgehub.bridgehubProxy, config.eraChainId, addresses.stateTransition.diamondProxy) ); - address contractAddress = deployViaCreate2(bytecode); console.log("L1NullifierImplementation deployed at:", contractAddress); addresses.bridges.l1NullifierImplementation = contractAddress; } function deployL1NullifierProxy() internal { bytes memory initCalldata = abi.encodeCall(L1Nullifier.initialize, (config.deployerAddress, 1, 1, 1, 0)); - bytes memory bytecode = abi.encodePacked( + address contractAddress = deployViaCreate2( type(TransparentUpgradeableProxy).creationCode, abi.encode(addresses.bridges.l1NullifierImplementation, addresses.transparentProxyAdmin, initCalldata) ); - address contractAddress = deployViaCreate2(bytecode); console.log("L1NullifierProxy deployed at:", contractAddress); addresses.bridges.l1NullifierProxy = contractAddress; } function deploySharedBridgeImplementation() internal { - bytes memory bytecode = abi.encodePacked( + address contractAddress = deployViaCreate2( type(L1AssetRouter).creationCode, // solhint-disable-next-line func-named-parameters abi.encode( @@ -755,18 +318,16 @@ contract DeployL1Script is Script { addresses.stateTransition.diamondProxy ) ); - address contractAddress = deployViaCreate2(bytecode); console.log("SharedBridgeImplementation deployed at:", contractAddress); addresses.bridges.sharedBridgeImplementation = contractAddress; } function deploySharedBridgeProxy() internal { bytes memory initCalldata = abi.encodeCall(L1AssetRouter.initialize, (config.deployerAddress)); - bytes memory bytecode = abi.encodePacked( + address contractAddress = deployViaCreate2( type(TransparentUpgradeableProxy).creationCode, abi.encode(addresses.bridges.sharedBridgeImplementation, addresses.transparentProxyAdmin, initCalldata) ); - address contractAddress = deployViaCreate2(bytecode); console.log("SharedBridgeProxy deployed at:", contractAddress); addresses.bridges.sharedBridgeProxy = contractAddress; } @@ -786,7 +347,7 @@ contract DeployL1Script is Script { } function deployErc20BridgeImplementation() internal { - bytes memory bytecode = abi.encodePacked( + address contractAddress = deployViaCreate2( type(L1ERC20Bridge).creationCode, abi.encode( addresses.bridges.l1NullifierProxy, @@ -795,18 +356,16 @@ contract DeployL1Script is Script { config.eraChainId ) ); - address contractAddress = deployViaCreate2(bytecode); console.log("Erc20BridgeImplementation deployed at:", contractAddress); addresses.bridges.erc20BridgeImplementation = contractAddress; } function deployErc20BridgeProxy() internal { bytes memory initCalldata = abi.encodeCall(L1ERC20Bridge.initialize, ()); - bytes memory bytecode = abi.encodePacked( + address contractAddress = deployViaCreate2( type(TransparentUpgradeableProxy).creationCode, abi.encode(addresses.bridges.erc20BridgeImplementation, addresses.transparentProxyAdmin, initCalldata) ); - address contractAddress = deployViaCreate2(bytecode); console.log("Erc20BridgeProxy deployed at:", contractAddress); addresses.bridges.erc20BridgeProxy = contractAddress; } @@ -819,31 +378,28 @@ contract DeployL1Script is Script { } function deployBridgedStandardERC20Implementation() internal { - bytes memory bytecode = abi.encodePacked( + address contractAddress = deployViaCreate2( type(BridgedStandardERC20).creationCode, // solhint-disable-next-line func-named-parameters abi.encode() ); - address contractAddress = deployViaCreate2(bytecode); console.log("BridgedStandardERC20Implementation deployed at:", contractAddress); addresses.bridges.bridgedStandardERC20Implementation = contractAddress; } function deployBridgedTokenBeacon() internal { - bytes memory bytecode = abi.encodePacked( - type(UpgradeableBeacon).creationCode, - // solhint-disable-next-line func-named-parameters - abi.encode(addresses.bridges.bridgedStandardERC20Implementation) - ); + /// Note we cannot use create2 as the deployer is the owner. + vm.broadcast(); UpgradeableBeacon beacon = new UpgradeableBeacon(addresses.bridges.bridgedStandardERC20Implementation); address contractAddress = address(beacon); + vm.broadcast(); beacon.transferOwnership(config.ownerAddress); console.log("BridgedTokenBeacon deployed at:", contractAddress); addresses.bridges.bridgedTokenBeacon = contractAddress; } function deployL1NativeTokenVaultImplementation() internal { - bytes memory bytecode = abi.encodePacked( + address contractAddress = deployViaCreate2( type(L1NativeTokenVault).creationCode, // solhint-disable-next-line func-named-parameters abi.encode( @@ -853,7 +409,6 @@ contract DeployL1Script is Script { addresses.bridges.l1NullifierProxy ) ); - address contractAddress = deployViaCreate2(bytecode); console.log("L1NativeTokenVaultImplementation deployed at:", contractAddress); addresses.vaults.l1NativeTokenVaultImplementation = contractAddress; } @@ -863,11 +418,10 @@ contract DeployL1Script is Script { L1NativeTokenVault.initialize, (config.ownerAddress, addresses.bridges.bridgedTokenBeacon) ); - bytes memory bytecode = abi.encodePacked( + address contractAddress = deployViaCreate2( type(TransparentUpgradeableProxy).creationCode, abi.encode(addresses.vaults.l1NativeTokenVaultImplementation, addresses.transparentProxyAdmin, initCalldata) ); - address contractAddress = deployViaCreate2(bytecode); console.log("L1NativeTokenVaultProxy deployed at:", contractAddress); addresses.vaults.l1NativeTokenVaultProxy = contractAddress; @@ -920,6 +474,36 @@ contract DeployL1Script is Script { console.log("Owners updated"); } + function saveDiamondSelectors() public { + AdminFacet adminFacet = new AdminFacet(1); + GettersFacet gettersFacet = new GettersFacet(); + MailboxFacet mailboxFacet = new MailboxFacet(1, 1); + ExecutorFacet executorFacet = new ExecutorFacet(); + bytes4[] memory adminFacetSelectors = Utils.getAllSelectors(address(adminFacet).code); + bytes4[] memory gettersFacetSelectors = Utils.getAllSelectors(address(gettersFacet).code); + bytes4[] memory mailboxFacetSelectors = Utils.getAllSelectors(address(mailboxFacet).code); + bytes4[] memory executorFacetSelectors = Utils.getAllSelectors(address(executorFacet).code); + + string memory root = vm.projectRoot(); + string memory outputPath = string.concat(root, "/script-out/diamond-selectors.toml"); + + bytes memory adminFacetSelectorsBytes = abi.encode(adminFacetSelectors); + bytes memory gettersFacetSelectorsBytes = abi.encode(gettersFacetSelectors); + bytes memory mailboxFacetSelectorsBytes = abi.encode(mailboxFacetSelectors); + bytes memory executorFacetSelectorsBytes = abi.encode(executorFacetSelectors); + + vm.serializeBytes("diamond_selectors", "admin_facet_selectors", adminFacetSelectorsBytes); + vm.serializeBytes("diamond_selectors", "getters_facet_selectors", gettersFacetSelectorsBytes); + vm.serializeBytes("diamond_selectors", "mailbox_facet_selectors", mailboxFacetSelectorsBytes); + string memory toml = vm.serializeBytes( + "diamond_selectors", + "executor_facet_selectors", + executorFacetSelectorsBytes + ); + + vm.writeToml(toml, outputPath); + } + function saveOutput(string memory outputPath) internal { vm.serializeAddress("bridgehub", "bridgehub_proxy_addr", addresses.bridgehub.bridgehubProxy); vm.serializeAddress("bridgehub", "bridgehub_implementation_addr", addresses.bridgehub.bridgehubImplementation); @@ -1083,10 +667,6 @@ contract DeployL1Script is Script { vm.writeToml(toml, outputPath); } - function deployViaCreate2(bytes memory _bytecode) internal returns (address) { - return Utils.deployViaCreate2(_bytecode, config.contracts.create2FactorySalt, addresses.create2Factory); - } - function prepareForceDeploymentsData() internal view returns (bytes memory) { require(addresses.governance != address(0), "Governance address is not set"); @@ -1118,5 +698,5 @@ contract DeployL1Script is Script { } // add this to be excluded from coverage report - function test() internal {} + function test() internal virtual override {} } diff --git a/l1-contracts/deploy-scripts/DeployUtils.s.sol b/l1-contracts/deploy-scripts/DeployUtils.s.sol new file mode 100644 index 000000000..33f386cf9 --- /dev/null +++ b/l1-contracts/deploy-scripts/DeployUtils.s.sol @@ -0,0 +1,483 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.24; + +// solhint-disable no-console, gas-custom-errors + +import {Script, console2 as console} from "forge-std/Script.sol"; +import {stdToml} from "forge-std/StdToml.sol"; +import {ProxyAdmin} from "@openzeppelin/contracts-v4/proxy/transparent/ProxyAdmin.sol"; +import {TransparentUpgradeableProxy} from "@openzeppelin/contracts-v4/proxy/transparent/TransparentUpgradeableProxy.sol"; +import {UpgradeableBeacon} from "@openzeppelin/contracts-v4/proxy/beacon/UpgradeableBeacon.sol"; +import {StateTransitionDeployedAddresses, Utils, L2_BRIDGEHUB_ADDRESS, L2_ASSET_ROUTER_ADDRESS, L2_NATIVE_TOKEN_VAULT_ADDRESS, L2_MESSAGE_ROOT_ADDRESS} from "./Utils.sol"; +import {Multicall3} from "contracts/dev-contracts/Multicall3.sol"; +import {Verifier} from "contracts/state-transition/Verifier.sol"; +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"; +import {L1GenesisUpgrade} from "contracts/upgrades/L1GenesisUpgrade.sol"; +import {ChainAdmin} from "contracts/governance/ChainAdmin.sol"; +import {ValidatorTimelock} from "contracts/state-transition/ValidatorTimelock.sol"; +import {Bridgehub} from "contracts/bridgehub/Bridgehub.sol"; +import {MessageRoot} from "contracts/bridgehub/MessageRoot.sol"; +import {CTMDeploymentTracker} from "contracts/bridgehub/CTMDeploymentTracker.sol"; +import {L1NativeTokenVault} from "contracts/bridge/ntv/L1NativeTokenVault.sol"; +import {ExecutorFacet} from "contracts/state-transition/chain-deps/facets/Executor.sol"; +import {AdminFacet} from "contracts/state-transition/chain-deps/facets/Admin.sol"; +import {MailboxFacet} from "contracts/state-transition/chain-deps/facets/Mailbox.sol"; +import {GettersFacet} from "contracts/state-transition/chain-deps/facets/Getters.sol"; +import {DiamondInit} from "contracts/state-transition/chain-deps/DiamondInit.sol"; +import {ChainTypeManager} from "contracts/state-transition/ChainTypeManager.sol"; +import {ChainTypeManagerInitializeData, ChainCreationParams} from "contracts/state-transition/IChainTypeManager.sol"; +import {IChainTypeManager} from "contracts/state-transition/IChainTypeManager.sol"; +import {Diamond} from "contracts/state-transition/libraries/Diamond.sol"; +import {InitializeDataNewChain as DiamondInitializeDataNewChain} from "contracts/state-transition/chain-interfaces/IDiamondInit.sol"; +import {FeeParams, PubdataPricingMode} from "contracts/state-transition/chain-deps/ZKChainStorage.sol"; +import {L1AssetRouter} from "contracts/bridge/asset-router/L1AssetRouter.sol"; +import {L1ERC20Bridge} from "contracts/bridge/L1ERC20Bridge.sol"; +import {L1Nullifier} from "contracts/bridge/L1Nullifier.sol"; +import {DiamondProxy} from "contracts/state-transition/chain-deps/DiamondProxy.sol"; +import {IL1AssetRouter} from "contracts/bridge/asset-router/IL1AssetRouter.sol"; +import {INativeTokenVault} from "contracts/bridge/ntv/INativeTokenVault.sol"; +import {BridgedStandardERC20} from "contracts/bridge/BridgedStandardERC20.sol"; +import {AddressHasNoCode} from "./ZkSyncScriptErrors.sol"; +import {ICTMDeploymentTracker} from "contracts/bridgehub/ICTMDeploymentTracker.sol"; +import {IMessageRoot} from "contracts/bridgehub/IMessageRoot.sol"; +import {IL2ContractDeployer} from "contracts/common/interfaces/IL2ContractDeployer.sol"; +import {L2ContractHelper} from "contracts/common/libraries/L2ContractHelper.sol"; +import {AddressAliasHelper} from "contracts/vendor/AddressAliasHelper.sol"; +import {IL1Nullifier} from "contracts/bridge/L1Nullifier.sol"; +import {IL1NativeTokenVault} from "contracts/bridge/ntv/IL1NativeTokenVault.sol"; +import {L1NullifierDev} from "contracts/dev-contracts/L1NullifierDev.sol"; +import {AccessControlRestriction} from "contracts/governance/AccessControlRestriction.sol"; +import {ICTMDeploymentTracker} from "contracts/bridgehub/ICTMDeploymentTracker.sol"; +import {IMessageRoot} from "contracts/bridgehub/IMessageRoot.sol"; +import {IAssetRouterBase} from "contracts/bridge/asset-router/IAssetRouterBase.sol"; +import {L2ContractsBytecodesLib} from "./L2ContractsBytecodesLib.sol"; + +struct FixedForceDeploymentsData { + uint256 l1ChainId; + uint256 eraChainId; + address l1AssetRouter; + bytes32 l2TokenProxyBytecodeHash; + address aliasedL1Governance; + uint256 maxNumberOfZKChains; + bytes32 bridgehubBytecodeHash; + bytes32 l2AssetRouterBytecodeHash; + bytes32 l2NtvBytecodeHash; + bytes32 messageRootBytecodeHash; + address l2SharedBridgeLegacyImpl; + address l2BridgedStandardERC20Impl; + address l2BridgeProxyOwnerAddress; + address l2BridgedStandardERC20ProxyOwnerAddress; +} + +// solhint-disable-next-line gas-struct-packing +struct DeployedAddresses { + BridgehubDeployedAddresses bridgehub; + StateTransitionDeployedAddresses stateTransition; + BridgesDeployedAddresses bridges; + L1NativeTokenVaultAddresses vaults; + DataAvailabilityDeployedAddresses daAddresses; + address transparentProxyAdmin; + address governance; + address chainAdmin; + address accessControlRestrictionAddress; + address blobVersionedHashRetriever; + address validatorTimelock; + address create2Factory; +} + +// solhint-disable-next-line gas-struct-packing +struct L1NativeTokenVaultAddresses { + address l1NativeTokenVaultImplementation; + address l1NativeTokenVaultProxy; +} + +struct DataAvailabilityDeployedAddresses { + address l1RollupDAValidator; + address l1ValidiumDAValidator; +} + +// solhint-disable-next-line gas-struct-packing +struct BridgehubDeployedAddresses { + address bridgehubImplementation; + address bridgehubProxy; + address ctmDeploymentTrackerImplementation; + address ctmDeploymentTrackerProxy; + address messageRootImplementation; + address messageRootProxy; +} + +// solhint-disable-next-line gas-struct-packing +struct BridgesDeployedAddresses { + address erc20BridgeImplementation; + address erc20BridgeProxy; + address sharedBridgeImplementation; + address sharedBridgeProxy; + address l1NullifierImplementation; + address l1NullifierProxy; + address bridgedStandardERC20Implementation; + address bridgedTokenBeacon; +} + +// solhint-disable-next-line gas-struct-packing +struct Config { + uint256 l1ChainId; + address deployerAddress; + uint256 eraChainId; + address ownerAddress; + bool testnetVerifier; + ContractsConfig contracts; + TokensConfig tokens; +} + +// solhint-disable-next-line gas-struct-packing +struct ContractsConfig { + bytes32 create2FactorySalt; + address create2FactoryAddr; + address multicall3Addr; + uint256 validatorTimelockExecutionDelay; + bytes32 genesisRoot; + uint256 genesisRollupLeafIndex; + bytes32 genesisBatchCommitment; + uint256 latestProtocolVersion; + bytes32 recursionNodeLevelVkHash; + bytes32 recursionLeafLevelVkHash; + bytes32 recursionCircuitsSetVksHash; + uint256 priorityTxMaxGasLimit; + PubdataPricingMode diamondInitPubdataPricingMode; + uint256 diamondInitBatchOverheadL1Gas; + uint256 diamondInitMaxPubdataPerBatch; + uint256 diamondInitMaxL2GasPerBatch; + uint256 diamondInitPriorityTxMaxPubdata; + uint256 diamondInitMinimalL2GasPrice; + address governanceSecurityCouncilAddress; + uint256 governanceMinDelay; + uint256 maxNumberOfChains; + bytes diamondCutData; + bytes32 bootloaderHash; + bytes32 defaultAAHash; +} + +struct TokensConfig { + address tokenWethAddress; +} + +// solhint-disable-next-line gas-struct-packing +struct GeneratedData { + bytes forceDeploymentsData; +} + +contract DeployUtils is Script { + using stdToml for string; + + address internal constant DETERMINISTIC_CREATE2_ADDRESS = 0x4e59b44847b379578588920cA78FbF26c0B4956C; + + Config public config; + GeneratedData internal generatedData; + DeployedAddresses internal addresses; + + function initializeConfig(string memory configPath) internal { + string memory toml = vm.readFile(configPath); + + config.l1ChainId = block.chainid; + config.deployerAddress = msg.sender; + + // Config file must be parsed key by key, otherwise values returned + // are parsed alfabetically and not by key. + // https://book.getfoundry.sh/cheatcodes/parse-toml + config.eraChainId = toml.readUint("$.era_chain_id"); + config.ownerAddress = toml.readAddress("$.owner_address"); + config.testnetVerifier = toml.readBool("$.testnet_verifier"); + + config.contracts.governanceSecurityCouncilAddress = toml.readAddress( + "$.contracts.governance_security_council_address" + ); + config.contracts.governanceMinDelay = toml.readUint("$.contracts.governance_min_delay"); + config.contracts.maxNumberOfChains = toml.readUint("$.contracts.max_number_of_chains"); + config.contracts.create2FactorySalt = toml.readBytes32("$.contracts.create2_factory_salt"); + if (vm.keyExistsToml(toml, "$.contracts.create2_factory_addr")) { + config.contracts.create2FactoryAddr = toml.readAddress("$.contracts.create2_factory_addr"); + } + config.contracts.validatorTimelockExecutionDelay = toml.readUint( + "$.contracts.validator_timelock_execution_delay" + ); + config.contracts.genesisRoot = toml.readBytes32("$.contracts.genesis_root"); + config.contracts.genesisRollupLeafIndex = toml.readUint("$.contracts.genesis_rollup_leaf_index"); + config.contracts.genesisBatchCommitment = toml.readBytes32("$.contracts.genesis_batch_commitment"); + config.contracts.latestProtocolVersion = toml.readUint("$.contracts.latest_protocol_version"); + config.contracts.recursionNodeLevelVkHash = toml.readBytes32("$.contracts.recursion_node_level_vk_hash"); + config.contracts.recursionLeafLevelVkHash = toml.readBytes32("$.contracts.recursion_leaf_level_vk_hash"); + config.contracts.recursionCircuitsSetVksHash = toml.readBytes32("$.contracts.recursion_circuits_set_vks_hash"); + config.contracts.priorityTxMaxGasLimit = toml.readUint("$.contracts.priority_tx_max_gas_limit"); + config.contracts.diamondInitPubdataPricingMode = PubdataPricingMode( + toml.readUint("$.contracts.diamond_init_pubdata_pricing_mode") + ); + config.contracts.diamondInitBatchOverheadL1Gas = toml.readUint( + "$.contracts.diamond_init_batch_overhead_l1_gas" + ); + config.contracts.diamondInitMaxPubdataPerBatch = toml.readUint( + "$.contracts.diamond_init_max_pubdata_per_batch" + ); + config.contracts.diamondInitMaxL2GasPerBatch = toml.readUint("$.contracts.diamond_init_max_l2_gas_per_batch"); + config.contracts.diamondInitPriorityTxMaxPubdata = toml.readUint( + "$.contracts.diamond_init_priority_tx_max_pubdata" + ); + config.contracts.diamondInitMinimalL2GasPrice = toml.readUint("$.contracts.diamond_init_minimal_l2_gas_price"); + config.contracts.defaultAAHash = toml.readBytes32("$.contracts.default_aa_hash"); + config.contracts.bootloaderHash = toml.readBytes32("$.contracts.bootloader_hash"); + + config.tokens.tokenWethAddress = toml.readAddress("$.tokens.token_weth_address"); + } + + function instantiateCreate2Factory() internal { + address contractAddress; + + bool isDeterministicDeployed = DETERMINISTIC_CREATE2_ADDRESS.code.length > 0; + bool isConfigured = config.contracts.create2FactoryAddr != address(0); + + if (isConfigured) { + if (config.contracts.create2FactoryAddr.code.length == 0) { + revert AddressHasNoCode(config.contracts.create2FactoryAddr); + } + contractAddress = config.contracts.create2FactoryAddr; + console.log("Using configured Create2Factory address:", contractAddress); + } else if (isDeterministicDeployed) { + contractAddress = DETERMINISTIC_CREATE2_ADDRESS; + console.log("Using deterministic Create2Factory address:", contractAddress); + } else { + contractAddress = Utils.deployCreate2Factory(); + console.log("Create2Factory deployed at:", contractAddress); + } + + addresses.create2Factory = contractAddress; + } + + function deployViaCreate2( + bytes memory creationCode, + bytes memory constructorArgs + ) internal virtual returns (address) { + return + Utils.deployViaCreate2( + abi.encodePacked(creationCode, constructorArgs), + config.contracts.create2FactorySalt, + addresses.create2Factory + ); + } + + function deployVerifier() internal { + bytes memory code; + if (config.testnetVerifier) { + code = type(TestnetVerifier).creationCode; + } else { + code = type(Verifier).creationCode; + } + address contractAddress = deployViaCreate2(code, ""); + console.log("Verifier deployed at:", contractAddress); + addresses.stateTransition.verifier = contractAddress; + } + + function deployDefaultUpgrade() internal { + address contractAddress = deployViaCreate2(type(DefaultUpgrade).creationCode, ""); + console.log("DefaultUpgrade deployed at:", contractAddress); + addresses.stateTransition.defaultUpgrade = contractAddress; + } + + function deployGenesisUpgrade() internal { + address contractAddress = deployViaCreate2(type(L1GenesisUpgrade).creationCode, ""); + console.log("GenesisUpgrade deployed at:", contractAddress); + addresses.stateTransition.genesisUpgrade = contractAddress; + } + + function deployValidatorTimelock() internal { + uint32 executionDelay = uint32(config.contracts.validatorTimelockExecutionDelay); + address contractAddress = deployViaCreate2( + type(ValidatorTimelock).creationCode, + abi.encode(config.deployerAddress, executionDelay, config.eraChainId) + ); + console.log("ValidatorTimelock deployed at:", contractAddress); + addresses.validatorTimelock = contractAddress; + } + + function deployGovernance() internal { + address contractAddress = deployViaCreate2( + type(Governance).creationCode, + abi.encode( + config.ownerAddress, + config.contracts.governanceSecurityCouncilAddress, + config.contracts.governanceMinDelay + ) + ); + console.log("Governance deployed at:", contractAddress); + addresses.governance = contractAddress; + } + + function deployChainAdmin() internal { + address accessControlRestriction = deployViaCreate2( + type(AccessControlRestriction).creationCode, + abi.encode(uint256(0), config.ownerAddress) + ); + + console.log("Access control restriction deployed at:", accessControlRestriction); + address[] memory restrictions = new address[](1); + restrictions[0] = accessControlRestriction; + addresses.accessControlRestrictionAddress = accessControlRestriction; + + address contractAddress = deployViaCreate2(type(ChainAdmin).creationCode, abi.encode(restrictions)); + console.log("ChainAdmin deployed at:", contractAddress); + addresses.chainAdmin = contractAddress; + } + + function deployTransparentProxyAdmin() internal { + vm.startBroadcast(); + ProxyAdmin proxyAdmin = new ProxyAdmin(); + proxyAdmin.transferOwnership(addresses.governance); + vm.stopBroadcast(); + console.log("Transparent Proxy Admin deployed at:", address(proxyAdmin)); + addresses.transparentProxyAdmin = address(proxyAdmin); + } + + function deployChainTypeManagerContract() internal { + deployStateTransitionDiamondFacets(); + deployChainTypeManagerImplementation(); + deployChainTypeManagerProxy(); + } + + function deployStateTransitionDiamondFacets() internal { + address executorFacet = deployViaCreate2(type(ExecutorFacet).creationCode, abi.encode()); + console.log("ExecutorFacet deployed at:", executorFacet); + addresses.stateTransition.executorFacet = executorFacet; + + address adminFacet = deployViaCreate2(type(AdminFacet).creationCode, abi.encode(config.l1ChainId)); + console.log("AdminFacet deployed at:", adminFacet); + addresses.stateTransition.adminFacet = adminFacet; + + address mailboxFacet = deployViaCreate2( + type(MailboxFacet).creationCode, + abi.encode(config.eraChainId, config.l1ChainId) + ); + console.log("MailboxFacet deployed at:", mailboxFacet); + addresses.stateTransition.mailboxFacet = mailboxFacet; + + address gettersFacet = deployViaCreate2(type(GettersFacet).creationCode, ""); + console.log("GettersFacet deployed at:", gettersFacet); + addresses.stateTransition.gettersFacet = gettersFacet; + + address diamondInit = deployViaCreate2(type(DiamondInit).creationCode, ""); + console.log("DiamondInit deployed at:", diamondInit); + addresses.stateTransition.diamondInit = diamondInit; + } + + function deployChainTypeManagerImplementation() internal { + bytes memory bytecode = type(ChainTypeManager).creationCode; + bytes memory constructorArgs = abi.encode(addresses.bridgehub.bridgehubProxy); + address contractAddress = deployViaCreate2(bytecode, constructorArgs); + console.log("ChainTypeManagerImplementation deployed at:", contractAddress); + addresses.stateTransition.chainTypeManagerImplementation = contractAddress; + } + + function deployChainTypeManagerProxy() internal { + string memory root = vm.projectRoot(); + string memory inputPath = string.concat(root, "/script-out/diamond-selectors.toml"); + string memory toml = vm.readFile(inputPath); + + bytes memory adminFacetSelectors = toml.readBytes("$.admin_facet_selectors"); + bytes memory gettersFacetSelectors = toml.readBytes("$.getters_facet_selectors"); + bytes memory mailboxFacetSelectors = toml.readBytes("$.mailbox_facet_selectors"); + bytes memory executorFacetSelectors = toml.readBytes("$.executor_facet_selectors"); + + bytes4[] memory adminFacetSelectorsArray = abi.decode(adminFacetSelectors, (bytes4[])); + bytes4[] memory gettersFacetSelectorsArray = abi.decode(gettersFacetSelectors, (bytes4[])); + bytes4[] memory mailboxFacetSelectorsArray = abi.decode(mailboxFacetSelectors, (bytes4[])); + bytes4[] memory executorFacetSelectorsArray = abi.decode(executorFacetSelectors, (bytes4[])); + + Diamond.FacetCut[] memory facetCuts = new Diamond.FacetCut[](4); + facetCuts[0] = Diamond.FacetCut({ + facet: addresses.stateTransition.adminFacet, + action: Diamond.Action.Add, + isFreezable: false, + selectors: adminFacetSelectorsArray + }); + facetCuts[1] = Diamond.FacetCut({ + facet: addresses.stateTransition.gettersFacet, + action: Diamond.Action.Add, + isFreezable: false, + selectors: gettersFacetSelectorsArray + }); + facetCuts[2] = Diamond.FacetCut({ + facet: addresses.stateTransition.mailboxFacet, + action: Diamond.Action.Add, + isFreezable: true, + selectors: mailboxFacetSelectorsArray + }); + facetCuts[3] = Diamond.FacetCut({ + facet: addresses.stateTransition.executorFacet, + action: Diamond.Action.Add, + isFreezable: true, + selectors: executorFacetSelectorsArray + }); + + VerifierParams memory verifierParams = VerifierParams({ + recursionNodeLevelVkHash: config.contracts.recursionNodeLevelVkHash, + recursionLeafLevelVkHash: config.contracts.recursionLeafLevelVkHash, + recursionCircuitsSetVksHash: config.contracts.recursionCircuitsSetVksHash + }); + + FeeParams memory feeParams = FeeParams({ + pubdataPricingMode: config.contracts.diamondInitPubdataPricingMode, + batchOverheadL1Gas: uint32(config.contracts.diamondInitBatchOverheadL1Gas), + maxPubdataPerBatch: uint32(config.contracts.diamondInitMaxPubdataPerBatch), + maxL2GasPerBatch: uint32(config.contracts.diamondInitMaxL2GasPerBatch), + priorityTxMaxPubdata: uint32(config.contracts.diamondInitPriorityTxMaxPubdata), + minimalL2GasPrice: uint64(config.contracts.diamondInitMinimalL2GasPrice) + }); + + DiamondInitializeDataNewChain memory initializeData = DiamondInitializeDataNewChain({ + verifier: IVerifier(addresses.stateTransition.verifier), + verifierParams: verifierParams, + l2BootloaderBytecodeHash: config.contracts.bootloaderHash, + l2DefaultAccountBytecodeHash: config.contracts.defaultAAHash, + priorityTxMaxGasLimit: config.contracts.priorityTxMaxGasLimit, + feeParams: feeParams, + blobVersionedHashRetriever: addresses.blobVersionedHashRetriever + }); + + Diamond.DiamondCutData memory diamondCut = Diamond.DiamondCutData({ + facetCuts: facetCuts, + initAddress: addresses.stateTransition.diamondInit, + initCalldata: abi.encode(initializeData) + }); + + config.contracts.diamondCutData = abi.encode(diamondCut); + + ChainCreationParams memory chainCreationParams = ChainCreationParams({ + genesisUpgrade: addresses.stateTransition.genesisUpgrade, + genesisBatchHash: config.contracts.genesisRoot, + genesisIndexRepeatedStorageChanges: uint64(config.contracts.genesisRollupLeafIndex), + genesisBatchCommitment: config.contracts.genesisBatchCommitment, + diamondCut: diamondCut, + forceDeploymentsData: generatedData.forceDeploymentsData + }); + + ChainTypeManagerInitializeData memory diamondInitData = ChainTypeManagerInitializeData({ + owner: msg.sender, + validatorTimelock: addresses.validatorTimelock, + chainCreationParams: chainCreationParams, + protocolVersion: config.contracts.latestProtocolVersion + }); + + address contractAddress = deployViaCreate2( + type(TransparentUpgradeableProxy).creationCode, + abi.encode( + addresses.stateTransition.chainTypeManagerImplementation, + addresses.transparentProxyAdmin, + abi.encodeCall(ChainTypeManager.initialize, (diamondInitData)) + ) + ); + console.log("ChainTypeManagerProxy deployed at:", contractAddress); + addresses.stateTransition.chainTypeManagerProxy = contractAddress; + } + + function test() internal virtual {} +} diff --git a/l1-contracts/deploy-scripts/GatewayPreparation.s.sol b/l1-contracts/deploy-scripts/GatewayPreparation.s.sol index 91601d9bd..1c68dd82d 100644 --- a/l1-contracts/deploy-scripts/GatewayPreparation.s.sol +++ b/l1-contracts/deploy-scripts/GatewayPreparation.s.sol @@ -22,6 +22,31 @@ import {GatewayTransactionFilterer} from "contracts/transactionFilterer/GatewayT import {TransparentUpgradeableProxy} from "@openzeppelin/contracts-v4/proxy/transparent/TransparentUpgradeableProxy.sol"; import {SET_ASSET_HANDLER_COUNTERPART_ENCODING_VERSION} from "contracts/bridge/asset-router/IAssetRouterBase.sol"; import {CTM_DEPLOYMENT_TRACKER_ENCODING_VERSION} from "contracts/bridgehub/CTMDeploymentTracker.sol"; +import {L2AssetRouter, IL2AssetRouter} from "contracts/bridge/asset-router/L2AssetRouter.sol"; +import {L1Nullifier} from "contracts/bridge/L1Nullifier.sol"; +import {BridgehubMintCTMAssetData} from "contracts/bridgehub/IBridgehub.sol"; +import {IAssetRouterBase} from "contracts/bridge/asset-router/IAssetRouterBase.sol"; +import {L2_ASSET_ROUTER_ADDR} from "contracts/common/L2ContractAddresses.sol"; +import {IAdmin} from "contracts/state-transition/chain-interfaces/IAdmin.sol"; +import {FinalizeL1DepositParams} from "contracts/bridge/interfaces/IL1Nullifier.sol"; + +import {IChainTypeManager} from "contracts/state-transition/IChainTypeManager.sol"; + +// solhint-disable-next-line gas-struct-packing +struct Config { + address bridgehub; + address ctmDeploymentTracker; + address chainTypeManagerProxy; + address sharedBridgeProxy; + address governance; + uint256 gatewayChainId; + address gatewayChainAdmin; + address gatewayAccessControlRestriction; + address gatewayChainProxyAdmin; + address l1NullifierProxy; + bytes gatewayDiamondCutData; + bytes l1DiamondCutData; +} /// @notice Scripts that is responsible for preparing the chain to become a gateway contract GatewayPreparation is Script { @@ -33,20 +58,6 @@ contract GatewayPreparation is Script { address deployerAddress; uint256 l1ChainId; - // solhint-disable-next-line gas-struct-packing - struct Config { - address bridgehub; - address ctmDeploymentTracker; - address chainTypeManagerProxy; - address sharedBridgeProxy; - address governance; - uint256 gatewayChainId; - address gatewayChainAdmin; - address gatewayAccessControlRestriction; - address gatewayChainProxyAdmin; - bytes gatewayDiamondCutData; - } - struct Output { bytes32 governanceL2TxHash; address gatewayTransactionFiltererImplementation; @@ -70,7 +81,7 @@ contract GatewayPreparation is Script { l1ChainId = block.chainid; string memory root = vm.projectRoot(); - string memory path = string.concat(root, "/script-config/gateway-preparation-l1.toml"); + string memory path = string.concat(root, vm.envString("GATEWAY_PREPARATION_L1_CONFIG")); string memory toml = vm.readFile(path); // Config file must be parsed key by key, otherwise values returned @@ -87,9 +98,11 @@ contract GatewayPreparation is Script { gatewayChainId: toml.readUint("$.chain_chain_id"), governance: toml.readAddress("$.governance"), gatewayDiamondCutData: toml.readBytes("$.gateway_diamond_cut_data"), + l1DiamondCutData: toml.readBytes("$.l1_diamond_cut_data"), gatewayChainAdmin: toml.readAddress("$.chain_admin"), gatewayAccessControlRestriction: toml.readAddress("$.access_control_restriction"), - gatewayChainProxyAdmin: toml.readAddress("$.chain_proxy_admin") + gatewayChainProxyAdmin: toml.readAddress("$.chain_proxy_admin"), + l1NullifierProxy: toml.readAddress("$.l1_nullifier_proxy_addr") }); } @@ -177,7 +190,7 @@ contract GatewayPreparation is Script { function governanceSetCTMAssetHandler(bytes32 governanoceOperationSalt) public { initializeConfig(); - bytes32 assetId = IBridgehub(config.bridgehub).ctmAssetId(config.chainTypeManagerProxy); + bytes32 assetId = IBridgehub(config.bridgehub).ctmAssetIdFromAddress(config.chainTypeManagerProxy); // This should be equivalent to `config.chainTypeManagerProxy`, but we just double checking to ensure that // bridgehub was initialized correctly @@ -240,7 +253,7 @@ contract GatewayPreparation is Script { uint256 currentSettlementLayer = IBridgehub(config.bridgehub).settlementLayer(chainId); if (currentSettlementLayer == config.gatewayChainId) { - console.log("Chain already whitelisted as settlement layer"); + console.log("Chain already using gateway as its settlement layer"); saveOutput(bytes32(0)); return; } @@ -272,6 +285,77 @@ contract GatewayPreparation is Script { saveOutput(l2TxHash); } + /// @dev Calling this function requires private key to the admin of the chain + function startMigrateChainFromGateway( + address chainAdmin, + address accessControlRestriction, + uint256 chainId + ) public { + initializeConfig(); + IBridgehub bridgehub = IBridgehub(config.bridgehub); + + uint256 currentSettlementLayer = bridgehub.settlementLayer(chainId); + if (currentSettlementLayer != config.gatewayChainId) { + console.log("Chain not using Gateway as settlement layer"); + saveOutput(bytes32(0)); + return; + } + + bytes memory bridgehubBurnData = abi.encode( + BridgehubBurnCTMAssetData({ + chainId: chainId, + ctmData: abi.encode(chainAdmin, config.l1DiamondCutData), + chainData: abi.encode(IChainTypeManager(config.chainTypeManagerProxy).getProtocolVersion(chainId)) + }) + ); + + bytes32 ctmAssetId = bridgehub.ctmAssetIdFromChainId(chainId); + L2AssetRouter l2AssetRouter = L2AssetRouter(L2_ASSET_ROUTER_ADDR); + bytes memory l2Calldata = abi.encodeCall(IL2AssetRouter.withdraw, (ctmAssetId, bridgehubBurnData)); + bytes32 l2TxHash = Utils.runAdminL1L2DirectTransaction( + _getL1GasPrice(), + chainAdmin, + accessControlRestriction, + l2Calldata, + Utils.MAX_PRIORITY_TX_GAS, + new bytes[](0), + L2_ASSET_ROUTER_ADDR, + config.gatewayChainId, + config.bridgehub, + config.sharedBridgeProxy + ); + + saveOutput(l2TxHash); + } + + function finishMigrateChainFromGateway( + uint256 migratingChainId, + uint256 gatewayChainId, + uint256 l2BatchNumber, + uint256 l2MessageIndex, + uint16 l2TxNumberInBatch, + bytes memory message, + bytes32[] memory merkleProof + ) public { + initializeConfig(); + + L1Nullifier l1Nullifier = L1Nullifier(config.l1NullifierProxy); + IBridgehub bridgehub = IBridgehub(config.bridgehub); + bytes32 assetId = bridgehub.ctmAssetIdFromChainId(migratingChainId); + vm.broadcast(); + l1Nullifier.finalizeDeposit( + FinalizeL1DepositParams({ + chainId: gatewayChainId, + l2BatchNumber: l2BatchNumber, + l2MessageIndex: l2MessageIndex, + l2Sender: L2_ASSET_ROUTER_ADDR, + l2TxNumberInBatch: l2TxNumberInBatch, + message: message, + merkleProof: merkleProof + }) + ); + } + /// @dev Calling this function requires private key to the admin of the chain function setDAValidatorPair( address chainAdmin, diff --git a/l1-contracts/deploy-scripts/RegisterZKChain.s.sol b/l1-contracts/deploy-scripts/RegisterZKChain.s.sol index 3a374e674..b1c8ae292 100644 --- a/l1-contracts/deploy-scripts/RegisterZKChain.s.sol +++ b/l1-contracts/deploy-scripts/RegisterZKChain.s.sol @@ -28,38 +28,38 @@ import {AddressAliasHelper} from "contracts/vendor/AddressAliasHelper.sol"; import {ETH_TOKEN_ADDRESS} from "contracts/common/Config.sol"; +// solhint-disable-next-line gas-struct-packing +struct Config { + address deployerAddress; + address ownerAddress; + uint256 chainChainId; + bool validiumMode; + uint256 bridgehubCreateNewChainSalt; + address validatorSenderOperatorCommitEth; + address validatorSenderOperatorBlobsEth; + address baseToken; + bytes32 baseTokenAssetId; + uint128 baseTokenGasPriceMultiplierNominator; + uint128 baseTokenGasPriceMultiplierDenominator; + address bridgehub; + // TODO(EVM-744): maybe rename to asset router + address sharedBridgeProxy; + address nativeTokenVault; + address chainTypeManagerProxy; + address validatorTimelock; + bytes diamondCutData; + bytes forceDeployments; + address governanceSecurityCouncilAddress; + uint256 governanceMinDelay; + address l1Nullifier; +} + contract RegisterZKChainScript is Script { using stdToml for string; address internal constant ADDRESS_ONE = 0x0000000000000000000000000000000000000001; bytes32 internal constant STATE_TRANSITION_NEW_CHAIN_HASH = keccak256("NewZKChain(uint256,address)"); - // solhint-disable-next-line gas-struct-packing - struct Config { - address deployerAddress; - address ownerAddress; - uint256 chainChainId; - bool validiumMode; - uint256 bridgehubCreateNewChainSalt; - address validatorSenderOperatorCommitEth; - address validatorSenderOperatorBlobsEth; - address baseToken; - bytes32 baseTokenAssetId; - uint128 baseTokenGasPriceMultiplierNominator; - uint128 baseTokenGasPriceMultiplierDenominator; - address bridgehub; - // TODO(EVM-744): maybe rename to asset router - address sharedBridgeProxy; - address nativeTokenVault; - address chainTypeManagerProxy; - address validatorTimelock; - bytes diamondCutData; - bytes forceDeployments; - address governanceSecurityCouncilAddress; - uint256 governanceMinDelay; - address l1Nullifier; - } - struct Output { address governance; address diamondProxy; @@ -167,6 +167,10 @@ contract RegisterZKChainScript is Script { config.validatorSenderOperatorBlobsEth = toml.readAddress("$.chain.validator_sender_operator_blobs_eth"); } + function getConfig() public view returns (Config memory) { + return config; + } + function initializeConfigTest() internal { // Grab config from output of l1 deployment string memory root = vm.projectRoot(); diff --git a/l1-contracts/package.json b/l1-contracts/package.json index effe54841..8a6896e86 100644 --- a/l1-contracts/package.json +++ b/l1-contracts/package.json @@ -59,7 +59,7 @@ "clean:foundry": "forge clean", "test": "yarn workspace da-contracts build && hardhat test test/unit_tests/*.spec.ts --network hardhat", "test:foundry": "forge test --ffi --match-path 'test/foundry/l1/*' --no-match-test 'test_MainnetFork'", - "test:zkfoundry": "forge test --zksync --match-path 'test/foundry/l2/*'", + "test:zkfoundry": "forge script --sig 0x2dd0ebe3 DeployL1Script --ffi && forge test --zksync --match-path 'test/foundry/l2/*'", "test:mainnet-upgrade-fork": "forge test --match-test test_MainnetFork --ffi --rpc-url $INFURA_MAINNET", "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|governance/L2AdminFactory\\.sol)' --no-match-test test_MainnetFork", diff --git a/l1-contracts/src.ts/deploy.ts b/l1-contracts/src.ts/deploy.ts index 7083bbb24..0c8ccc2cc 100644 --- a/l1-contracts/src.ts/deploy.ts +++ b/l1-contracts/src.ts/deploy.ts @@ -1354,7 +1354,9 @@ export class Deployer { const alreadyRegisteredInCTM = (await chainTypeManager.getZKChain(inputChainId)) != ethers.constants.AddressZero; if (l2LegacySharedBridge) { - console.log("Setting L2 legacy shared bridge in L1Nullifier"); + if (this.verbose) { + console.log("Setting L2 legacy shared bridge in L1Nullifier"); + } await this.setL2LegacySharedBridgeInL1Nullifier(inputChainId); nonce++; } diff --git a/l1-contracts/test/foundry/l1/integration/AssetRouterTest.t.sol b/l1-contracts/test/foundry/l1/integration/AssetRouterTest.t.sol index 83913d2a4..7d122638d 100644 --- a/l1-contracts/test/foundry/l1/integration/AssetRouterTest.t.sol +++ b/l1-contracts/test/foundry/l1/integration/AssetRouterTest.t.sol @@ -3,6 +3,7 @@ pragma solidity 0.8.24; import {Test} from "forge-std/Test.sol"; import {Vm} from "forge-std/Vm.sol"; +import {console2 as console} from "forge-std/console2.sol"; import {L2TransactionRequestDirect, L2TransactionRequestTwoBridgesOuter} from "contracts/bridgehub/IBridgehub.sol"; import {TestnetERC20Token} from "contracts/dev-contracts/TestnetERC20Token.sol"; @@ -24,6 +25,7 @@ import {IZKChain} from "contracts/state-transition/chain-interfaces/IZKChain.sol import {IChainTypeManager} from "contracts/state-transition/IChainTypeManager.sol"; import {IL1AssetRouter} from "contracts/bridge/asset-router/IL1AssetRouter.sol"; import {IL1NativeTokenVault} from "contracts/bridge/ntv/IL1NativeTokenVault.sol"; +import {INativeTokenVault} from "contracts/bridge/ntv/INativeTokenVault.sol"; import {IL1Nullifier, FinalizeL1DepositParams} from "contracts/bridge/interfaces/IL1Nullifier.sol"; import {IL1AssetRouter} from "contracts/bridge/asset-router/IL1AssetRouter.sol"; import {IAssetRouterBase, LEGACY_ENCODING_VERSION, NEW_ENCODING_VERSION} from "contracts/bridge/asset-router/IAssetRouterBase.sol"; @@ -79,12 +81,12 @@ contract AssetRouterTest is L1ContractDeployer, ZKChainDeployer, TokenDeployer, function depositToL1(address _tokenAddress) public { vm.mockCall( - address(bridgeHub), + address(bridgehub), abi.encodeWithSelector(IBridgehub.proveL2MessageInclusion.selector), abi.encode(true) ); uint256 chainId = eraZKChainId; - l2TokenAssetId = DataEncoding.encodeNTVAssetId(chainId, address(1)); + l2TokenAssetId = DataEncoding.encodeNTVAssetId(chainId, _tokenAddress); bytes memory transferData = DataEncoding.encodeBridgeMintData({ _originalCaller: ETH_TOKEN_ADDRESS, _l2Receiver: address(this), @@ -152,7 +154,13 @@ contract AssetRouterTest is L1ContractDeployer, ZKChainDeployer, TokenDeployer, function test_BridgeTokenBurn() public { depositToL1(ETH_TOKEN_ADDRESS); BridgedStandardERC20 bridgedToken = BridgedStandardERC20(l1NativeTokenVault.tokenAddress(l2TokenAssetId)); + // setting nativeTokenVault to zero address. vm.store(address(bridgedToken), bytes32(uint256(207)), bytes32(0)); + vm.mockCall( + address(L2_NATIVE_TOKEN_VAULT_ADDR), + abi.encodeWithSelector(INativeTokenVault.L1_CHAIN_ID.selector), + abi.encode(block.chainid) + ); vm.broadcast(L2_NATIVE_TOKEN_VAULT_ADDR); // kl todo call ntv, or even assetRouter/bridgehub bridgedToken.bridgeBurn(address(this), 100); } @@ -164,7 +172,7 @@ contract AssetRouterTest is L1ContractDeployer, ZKChainDeployer, TokenDeployer, abi.encode(l2TokenAssetId, abi.encode(uint256(100), address(this))) ); IERC20(tokenL1Address).approve(address(l1NativeTokenVault), 100); - bridgeHub.requestL2TransactionTwoBridges{value: 250000000000100}( + bridgehub.requestL2TransactionTwoBridges{value: 250000000000100}( L2TransactionRequestTwoBridgesOuter({ chainId: eraZKChainId, mintValue: 250000000000100, diff --git a/l1-contracts/test/foundry/l1/integration/BridgeHubInvariantTests.t.sol b/l1-contracts/test/foundry/l1/integration/BridgeHubInvariantTests.t.sol index 6eb78944d..b4fec78de 100644 --- a/l1-contracts/test/foundry/l1/integration/BridgeHubInvariantTests.t.sol +++ b/l1-contracts/test/foundry/l1/integration/BridgeHubInvariantTests.t.sol @@ -270,7 +270,7 @@ contract BridgeHubInvariantTests is L1ContractDeployer, ZKChainDeployer, TokenDe }); vm.recordLogs(); - bytes32 resultantHash = bridgeHub.requestL2TransactionTwoBridges{value: mintValue}(requestTx); + bytes32 resultantHash = bridgehub.requestL2TransactionTwoBridges{value: mintValue}(requestTx); Vm.Log[] memory logs = vm.getRecordedLogs(); NewPriorityRequest memory request = _getNewPriorityQueueFromLogs(logs); @@ -320,7 +320,7 @@ contract BridgeHubInvariantTests is L1ContractDeployer, ZKChainDeployer, TokenDe }); vm.recordLogs(); - bytes32 resultantHash = bridgeHub.requestL2TransactionTwoBridges{value: l2Value}(requestTx); + bytes32 resultantHash = bridgehub.requestL2TransactionTwoBridges{value: l2Value}(requestTx); Vm.Log[] memory logs = vm.getRecordedLogs(); NewPriorityRequest memory request = _getNewPriorityQueueFromLogs(logs); @@ -375,7 +375,7 @@ contract BridgeHubInvariantTests is L1ContractDeployer, ZKChainDeployer, TokenDe }); vm.recordLogs(); - bytes32 resultantHash = bridgeHub.requestL2TransactionTwoBridges(requestTx); + bytes32 resultantHash = bridgehub.requestL2TransactionTwoBridges(requestTx); Vm.Log[] memory logs = vm.getRecordedLogs(); NewPriorityRequest memory request = _getNewPriorityQueueFromLogs(logs); @@ -420,7 +420,7 @@ contract BridgeHubInvariantTests is L1ContractDeployer, ZKChainDeployer, TokenDe }); vm.recordLogs(); - bytes32 resultantHash = bridgeHub.requestL2TransactionDirect{value: mintValue}(txRequest); + bytes32 resultantHash = bridgehub.requestL2TransactionDirect{value: mintValue}(txRequest); Vm.Log[] memory logs = vm.getRecordedLogs(); NewPriorityRequest memory request = _getNewPriorityQueueFromLogs(logs); @@ -464,7 +464,7 @@ contract BridgeHubInvariantTests is L1ContractDeployer, ZKChainDeployer, TokenDe }); vm.recordLogs(); - bytes32 resultantHash = bridgeHub.requestL2TransactionDirect(txRequest); + bytes32 resultantHash = bridgehub.requestL2TransactionDirect(txRequest); Vm.Log[] memory logs = vm.getRecordedLogs(); NewPriorityRequest memory request = _getNewPriorityQueueFromLogs(logs); diff --git a/l1-contracts/test/foundry/l1/integration/BridgehubTests.t.sol b/l1-contracts/test/foundry/l1/integration/BridgehubTests.t.sol index 09b07cca0..a99790839 100644 --- a/l1-contracts/test/foundry/l1/integration/BridgehubTests.t.sol +++ b/l1-contracts/test/foundry/l1/integration/BridgehubTests.t.sol @@ -270,7 +270,7 @@ contract BridgeHubInvariantTests is L1ContractDeployer, ZKChainDeployer, TokenDe }); vm.recordLogs(); - bytes32 resultantHash = bridgeHub.requestL2TransactionTwoBridges{value: mintValue}(requestTx); + bytes32 resultantHash = bridgehub.requestL2TransactionTwoBridges{value: mintValue}(requestTx); Vm.Log[] memory logs = vm.getRecordedLogs(); NewPriorityRequest memory request = _getNewPriorityQueueFromLogs(logs); @@ -320,7 +320,7 @@ contract BridgeHubInvariantTests is L1ContractDeployer, ZKChainDeployer, TokenDe }); vm.recordLogs(); - bytes32 resultantHash = bridgeHub.requestL2TransactionTwoBridges{value: l2Value}(requestTx); + bytes32 resultantHash = bridgehub.requestL2TransactionTwoBridges{value: l2Value}(requestTx); Vm.Log[] memory logs = vm.getRecordedLogs(); NewPriorityRequest memory request = _getNewPriorityQueueFromLogs(logs); @@ -375,7 +375,7 @@ contract BridgeHubInvariantTests is L1ContractDeployer, ZKChainDeployer, TokenDe }); vm.recordLogs(); - bytes32 resultantHash = bridgeHub.requestL2TransactionTwoBridges(requestTx); + bytes32 resultantHash = bridgehub.requestL2TransactionTwoBridges(requestTx); Vm.Log[] memory logs = vm.getRecordedLogs(); NewPriorityRequest memory request = _getNewPriorityQueueFromLogs(logs); @@ -420,7 +420,7 @@ contract BridgeHubInvariantTests is L1ContractDeployer, ZKChainDeployer, TokenDe }); vm.recordLogs(); - bytes32 resultantHash = bridgeHub.requestL2TransactionDirect{value: mintValue}(txRequest); + bytes32 resultantHash = bridgehub.requestL2TransactionDirect{value: mintValue}(txRequest); Vm.Log[] memory logs = vm.getRecordedLogs(); NewPriorityRequest memory request = _getNewPriorityQueueFromLogs(logs); @@ -464,7 +464,7 @@ contract BridgeHubInvariantTests is L1ContractDeployer, ZKChainDeployer, TokenDe }); vm.recordLogs(); - bytes32 resultantHash = bridgeHub.requestL2TransactionDirect(txRequest); + bytes32 resultantHash = bridgehub.requestL2TransactionDirect(txRequest); Vm.Log[] memory logs = vm.getRecordedLogs(); NewPriorityRequest memory request = _getNewPriorityQueueFromLogs(logs); diff --git a/l1-contracts/test/foundry/l1/integration/DeploymentTest.t.sol b/l1-contracts/test/foundry/l1/integration/DeploymentTest.t.sol index 9224eea05..b4cca3bb1 100644 --- a/l1-contracts/test/foundry/l1/integration/DeploymentTest.t.sol +++ b/l1-contracts/test/foundry/l1/integration/DeploymentTest.t.sol @@ -4,6 +4,8 @@ pragma solidity 0.8.24; import {Test} from "forge-std/Test.sol"; import {Vm} from "forge-std/Vm.sol"; +import {Ownable} from "@openzeppelin/contracts-v4/access/Ownable.sol"; + import {L2TransactionRequestDirect, L2TransactionRequestTwoBridgesOuter} from "contracts/bridgehub/IBridgehub.sol"; import {TestnetERC20Token} from "contracts/dev-contracts/TestnetERC20Token.sol"; import {MailboxFacet} from "contracts/state-transition/chain-deps/facets/Mailbox.sol"; @@ -22,6 +24,8 @@ import {L2_BASE_TOKEN_SYSTEM_CONTRACT_ADDR} from "contracts/common/L2ContractAdd import {IL1ERC20Bridge} from "contracts/bridge/interfaces/IL1ERC20Bridge.sol"; import {IZKChain} from "contracts/state-transition/chain-interfaces/IZKChain.sol"; import {IChainTypeManager} from "contracts/state-transition/IChainTypeManager.sol"; +import {DataEncoding} from "contracts/common/libraries/DataEncoding.sol"; +import {IncorrectBridgeHubAddress} from "contracts/common/L1ContractErrors.sol"; contract DeploymentTests is L1ContractDeployer, ZKChainDeployer, TokenDeployer, L2TxMocker { uint256 constant TEST_USERS_COUNT = 10; @@ -69,10 +73,8 @@ contract DeploymentTests is L1ContractDeployer, ZKChainDeployer, TokenDeployer, // equals the balance of L1Shared bridge. function test_initialDeployment() public { uint256 chainId = zkChainIds[0]; - IBridgehub bridgehub = IBridgehub(l1Script.getBridgehubProxyAddress()); address newChainAddress = bridgehub.getZKChain(chainId); address admin = IZKChain(bridgehub.getZKChain(chainId)).getAdmin(); - IChainTypeManager ctm = IChainTypeManager(bridgehub.chainTypeManager(chainId)); assertNotEq(admin, address(0)); assertNotEq(newChainAddress, address(0)); @@ -85,14 +87,12 @@ contract DeploymentTests is L1ContractDeployer, ZKChainDeployer, TokenDeployer, assertEq(chainIds.length, 1); assertEq(chainIds[0], chainId); - uint256 protocolVersion = ctm.getProtocolVersion(chainId); - assertEq(protocolVersion, 0); + uint256 protocolVersion = chainTypeManager.getProtocolVersion(chainId); + assertEq(protocolVersion, 25); } function test_bridgehubSetter() public { - IBridgehub bridgehub = IBridgehub(l1Script.getBridgehubProxyAddress()); uint256 chainId = zkChainIds[0]; - IChainTypeManager chainTypeManager = IChainTypeManager(bridgehub.chainTypeManager(chainId)); uint256 randomChainId = 123456; vm.mockCall( @@ -110,6 +110,65 @@ contract DeploymentTests is L1ContractDeployer, ZKChainDeployer, TokenDeployer, bridgehub.setLegacyChainAddress(randomChainId); } + function test_registerAlreadyDeployedZKChain() public { + address owner = Ownable(address(bridgehub)).owner(); + + { + uint256 chainId = currentZKChainId++; + bytes32 baseTokenAssetId = DataEncoding.encodeNTVAssetId(chainId, ETH_TOKEN_ADDRESS); + + address chain = _deployZkChain( + chainId, + baseTokenAssetId, + owner, + chainTypeManager.protocolVersion(), + chainTypeManager.storedBatchZero(), + address(bridgehub) + ); + + address stmAddr = IZKChain(chain).getChainTypeManager(); + + vm.startBroadcast(owner); + bridgehub.addChainTypeManager(stmAddr); + bridgehub.addTokenAssetId(baseTokenAssetId); + bridgehub.registerAlreadyDeployedZKChain(chainId, chain); + vm.stopBroadcast(); + + address bridgehubStmForChain = bridgehub.chainTypeManager(chainId); + bytes32 bridgehubBaseAssetIdForChain = bridgehub.baseTokenAssetId(chainId); + address bridgehubChainAddressdForChain = bridgehub.getZKChain(chainId); + address bhAddr = IZKChain(chain).getBridgehub(); + + assertEq(bridgehubStmForChain, stmAddr); + assertEq(bridgehubBaseAssetIdForChain, baseTokenAssetId); + assertEq(bridgehubChainAddressdForChain, chain); + assertEq(bhAddr, address(bridgehub)); + } + + { + uint256 chainId = currentZKChainId++; + bytes32 baseTokenAssetId = DataEncoding.encodeNTVAssetId(chainId, ETH_TOKEN_ADDRESS); + address chain = _deployZkChain( + chainId, + baseTokenAssetId, + owner, + chainTypeManager.protocolVersion(), + chainTypeManager.storedBatchZero(), + address(bridgehub.sharedBridge()) + ); + + address stmAddr = IZKChain(chain).getChainTypeManager(); + + vm.startBroadcast(owner); + bridgehub.addTokenAssetId(baseTokenAssetId); + vm.expectRevert( + abi.encodeWithSelector(IncorrectBridgeHubAddress.selector, address(bridgehub.sharedBridge())) + ); + bridgehub.registerAlreadyDeployedZKChain(chainId, chain); + vm.stopBroadcast(); + } + } + // add this to be excluded from coverage report function test() internal override {} } diff --git a/l1-contracts/test/foundry/l1/integration/GatewayTests.t.sol b/l1-contracts/test/foundry/l1/integration/L1GatewayTests.t.sol similarity index 65% rename from l1-contracts/test/foundry/l1/integration/GatewayTests.t.sol rename to l1-contracts/test/foundry/l1/integration/L1GatewayTests.t.sol index d4aa1ba0a..9f67d7973 100644 --- a/l1-contracts/test/foundry/l1/integration/GatewayTests.t.sol +++ b/l1-contracts/test/foundry/l1/integration/L1GatewayTests.t.sol @@ -5,6 +5,8 @@ import {Test} from "forge-std/Test.sol"; import {Vm} from "forge-std/Vm.sol"; import "forge-std/console.sol"; +import {Ownable} from "@openzeppelin/contracts-v4/access/Ownable.sol"; + import {L2TransactionRequestDirect, L2TransactionRequestTwoBridgesOuter, BridgehubMintCTMAssetData, BridgehubBurnCTMAssetData} from "contracts/bridgehub/IBridgehub.sol"; import {TestnetERC20Token} from "contracts/dev-contracts/TestnetERC20Token.sol"; import {MailboxFacet} from "contracts/state-transition/chain-deps/facets/Mailbox.sol"; @@ -21,11 +23,13 @@ import {REQUIRED_L2_GAS_PRICE_PER_PUBDATA, DEFAULT_L2_LOGS_TREE_ROOT_HASH, EMPTY import {L2CanonicalTransaction} from "contracts/common/Messaging.sol"; import {L2Message} from "contracts/common/Messaging.sol"; import {IBridgehub} from "contracts/bridgehub/IBridgehub.sol"; -import {L2_BASE_TOKEN_SYSTEM_CONTRACT_ADDR} from "contracts/common/L2ContractAddresses.sol"; +import {L2_BASE_TOKEN_SYSTEM_CONTRACT_ADDR, L2_ASSET_ROUTER_ADDR} from "contracts/common/L2ContractAddresses.sol"; import {IL1ERC20Bridge} from "contracts/bridge/interfaces/IL1ERC20Bridge.sol"; import {IL1AssetRouter} from "contracts/bridge/asset-router/IL1AssetRouter.sol"; +import {IAssetRouterBase} from "contracts/bridge/asset-router/IAssetRouterBase.sol"; +import {L1Nullifier} from "contracts/bridge/L1Nullifier.sol"; +import {FinalizeL1DepositParams} from "contracts/bridge/L1Nullifier.sol"; -import {Ownable} from "@openzeppelin/contracts-v4/access/Ownable.sol"; import {IZKChain} from "contracts/state-transition/chain-interfaces/IZKChain.sol"; import {IChainTypeManager} from "contracts/state-transition/IChainTypeManager.sol"; import {AdminFacet} from "contracts/state-transition/chain-deps/facets/Admin.sol"; @@ -34,8 +38,9 @@ import {TxStatus} from "contracts/common/Messaging.sol"; import {DataEncoding} from "contracts/common/libraries/DataEncoding.sol"; import {IncorrectBridgeHubAddress} from "contracts/common/L1ContractErrors.sol"; import {ChainAdmin} from "contracts/governance/ChainAdmin.sol"; +import {IAdmin} from "contracts/state-transition/chain-interfaces/IAdmin.sol"; -contract GatewayTests is L1ContractDeployer, ZKChainDeployer, TokenDeployer, L2TxMocker, GatewayDeployer { +contract L1GatewayTests is L1ContractDeployer, ZKChainDeployer, TokenDeployer, L2TxMocker, GatewayDeployer { uint256 constant TEST_USERS_COUNT = 10; address[] public users; address[] public l2ContractAddresses; @@ -85,15 +90,14 @@ contract GatewayTests is L1ContractDeployer, ZKChainDeployer, TokenDeployer, L2T _initializeGatewayScript(); - vm.deal(Ownable(l1Script.getBridgehubProxyAddress()).owner(), 100000000000000000000000000000000000); - vm.deal(l1Script.getOwnerAddress(), 100000000000000000000000000000000000); - migratingChain = IZKChain(IBridgehub(l1Script.getBridgehubProxyAddress()).getZKChain(migratingChainId)); - gatewayChain = IZKChain(IBridgehub(l1Script.getBridgehubProxyAddress()).getZKChain(gatewayChainId)); + vm.deal(ecosystemConfig.ownerAddress, 100000000000000000000000000000000000); + migratingChain = IZKChain(IBridgehub(bridgehub).getZKChain(migratingChainId)); + gatewayChain = IZKChain(IBridgehub(bridgehub).getZKChain(gatewayChainId)); vm.deal(migratingChain.getAdmin(), 100000000000000000000000000000000000); vm.deal(gatewayChain.getAdmin(), 100000000000000000000000000000000000); // vm.deal(msg.sender, 100000000000000000000000000000000000); - // vm.deal(l1Script.getBridgehubProxyAddress(), 100000000000000000000000000000000000); + // vm.deal(bridgehub, 100000000000000000000000000000000000); } // This is a method to simplify porting the tests for now. @@ -124,7 +128,7 @@ contract GatewayTests is L1ContractDeployer, ZKChainDeployer, TokenDeployer, L2T _extractAccessControlRestriction(migratingChain.getAdmin()), migratingChainId ); - // require(bridgehub.settlementLayer()) + require(bridgehub.settlementLayer(migratingChainId) == gatewayChainId, "Migration failed"); } function test_l2Registration() public { @@ -138,13 +142,14 @@ contract GatewayTests is L1ContractDeployer, ZKChainDeployer, TokenDeployer, L2T gatewayScript.registerAssetIdInBridgehub(address(0x01), bytes32(0)); } - function test_finishMoveChain() public { - finishMoveChain(); - } - function test_startMessageToL3() public { - finishMoveChain(); - IBridgehub bridgehub = IBridgehub(l1Script.getBridgehubProxyAddress()); + _setUpGatewayWithFilterer(); + gatewayScript.migrateChainToGateway( + migratingChain.getAdmin(), + _extractAccessControlRestriction(migratingChain.getAdmin()), + migratingChainId + ); + IBridgehub bridgehub = IBridgehub(bridgehub); uint256 expectedValue = 1000000000000000000000; L2TransactionRequestDirect memory request = _createL2TransactionRequestDirect( @@ -158,16 +163,6 @@ contract GatewayTests is L1ContractDeployer, ZKChainDeployer, TokenDeployer, L2T bridgehub.requestL2TransactionDirect{value: expectedValue}(request); } - function test_forwardToL3OnGateway() public { - finishMoveChain(); - - IBridgehub bridgehub = IBridgehub(l1Script.getBridgehubProxyAddress()); - vm.chainId(12345); - vm.startBroadcast(SETTLEMENT_LAYER_RELAY_SENDER); - bridgehub.forwardTransactionOnGateway(mintChainId, bytes32(0), 0); - vm.stopBroadcast(); - } - function test_recoverFromFailedChainMigration() public { _setUpGatewayWithFilterer(); gatewayScript.migrateChainToGateway( @@ -177,16 +172,19 @@ contract GatewayTests is L1ContractDeployer, ZKChainDeployer, TokenDeployer, L2T ); // Setup - IBridgehub bridgehub = IBridgehub(l1Script.getBridgehubProxyAddress()); - IChainTypeManager ctm = IChainTypeManager(l1Script.getCTM()); + IBridgehub bridgehub = IBridgehub(bridgehub); bytes32 assetId = bridgehub.ctmAssetIdFromChainId(migratingChainId); bytes memory transferData; { IZKChain chain = IZKChain(bridgehub.getZKChain(migratingChainId)); - bytes memory initialDiamondCut = l1Script.getInitialDiamondCutData(); bytes memory chainData = abi.encode(chain.getProtocolVersion()); - bytes memory ctmData = abi.encode(address(1), msg.sender, ctm.protocolVersion(), initialDiamondCut); + bytes memory ctmData = abi.encode( + address(1), + msg.sender, + chainTypeManager.protocolVersion(), + ecosystemConfig.contracts.diamondCutData + ); BridgehubBurnCTMAssetData memory data = BridgehubBurnCTMAssetData({ chainId: migratingChainId, ctmData: ctmData, @@ -221,7 +219,7 @@ contract GatewayTests is L1ContractDeployer, ZKChainDeployer, TokenDeployer, L2T ); // Set Deposit Happened - vm.startBroadcast(address(bridgeHub)); + vm.startBroadcast(address(bridgehub)); assetRouter.bridgehubConfirmL2Transaction({ _chainId: migratingChainId, _txDataHash: txDataHash, @@ -244,72 +242,18 @@ contract GatewayTests is L1ContractDeployer, ZKChainDeployer, TokenDeployer, L2T vm.stopBroadcast(); } - function test_registerAlreadyDeployedZKChain() public { - gatewayScript.governanceRegisterGateway(); - - IChainTypeManager stm = IChainTypeManager(l1Script.getCTM()); - IBridgehub bridgehub = IBridgehub(l1Script.getBridgehubProxyAddress()); - address owner = Ownable(address(bridgeHub)).owner(); - - { - uint256 chainId = currentZKChainId++; - bytes32 baseTokenAssetId = DataEncoding.encodeNTVAssetId(chainId, ETH_TOKEN_ADDRESS); - - address chain = _deployZkChain( - chainId, - baseTokenAssetId, - owner, - stm.protocolVersion(), - stm.storedBatchZero(), - address(bridgehub) - ); - - address stmAddr = IZKChain(chain).getChainTypeManager(); - - vm.startBroadcast(owner); - bridgeHub.addChainTypeManager(stmAddr); - bridgeHub.addTokenAssetId(baseTokenAssetId); - bridgeHub.registerAlreadyDeployedZKChain(chainId, chain); - vm.stopBroadcast(); - - address bridgeHubStmForChain = bridgeHub.chainTypeManager(chainId); - bytes32 bridgeHubBaseAssetIdForChain = bridgeHub.baseTokenAssetId(chainId); - address bridgeHubChainAddressdForChain = bridgeHub.getZKChain(chainId); - address bhAddr = IZKChain(chain).getBridgehub(); - - assertEq(bridgeHubStmForChain, stmAddr); - assertEq(bridgeHubBaseAssetIdForChain, baseTokenAssetId); - assertEq(bridgeHubChainAddressdForChain, chain); - assertEq(bhAddr, address(bridgeHub)); - } - - { - uint256 chainId = currentZKChainId++; - bytes32 baseTokenAssetId = DataEncoding.encodeNTVAssetId(chainId, ETH_TOKEN_ADDRESS); - address chain = _deployZkChain( - chainId, - baseTokenAssetId, - owner, - stm.protocolVersion(), - stm.storedBatchZero(), - address(bridgehub.sharedBridge()) - ); - - address stmAddr = IZKChain(chain).getChainTypeManager(); - - vm.startBroadcast(owner); - bridgeHub.addTokenAssetId(baseTokenAssetId); - vm.expectRevert( - abi.encodeWithSelector(IncorrectBridgeHubAddress.selector, address(bridgehub.sharedBridge())) - ); - bridgeHub.registerAlreadyDeployedZKChain(chainId, chain); - vm.stopBroadcast(); - } + function test_finishMigrateBackChain() public { + _setUpGatewayWithFilterer(); + gatewayScript.migrateChainToGateway( + migratingChain.getAdmin(), + _extractAccessControlRestriction(migratingChain.getAdmin()), + migratingChainId + ); + migrateBackChain(); } - function finishMoveChain() public { - IBridgehub bridgehub = IBridgehub(l1Script.getBridgehubProxyAddress()); - IChainTypeManager ctm = IChainTypeManager(l1Script.getCTM()); + function migrateBackChain() public { + IBridgehub bridgehub = IBridgehub(bridgehub); IZKChain migratingChain = IZKChain(bridgehub.getZKChain(migratingChainId)); bytes32 assetId = bridgehub.ctmAssetIdFromChainId(migratingChainId); @@ -317,27 +261,73 @@ contract GatewayTests is L1ContractDeployer, ZKChainDeployer, TokenDeployer, L2T bridgehub.registerSettlementLayer(gatewayChainId, true); vm.stopBroadcast(); - bytes32 baseTokenAssetId = keccak256("baseTokenAssetId"); - bytes memory initialDiamondCut = l1Script.getInitialDiamondCutData(); - bytes memory chainData = abi.encode(AdminFacet(address(migratingChain)).prepareChainCommitment()); - bytes memory ctmData = abi.encode(baseTokenAssetId, msg.sender, ctm.protocolVersion(), initialDiamondCut); + bytes32 baseTokenAssetId = eraConfig.baseTokenAssetId; + + uint256 currentChainId = block.chainid; + // we are already on L1, so we have to set another chain id, it cannot be GW or mintChainId. + vm.chainId(migratingChainId); + vm.mockCall( + address(bridgehub), + abi.encodeWithSelector(IBridgehub.proveL2MessageInclusion.selector), + abi.encode(true) + ); + vm.mockCall( + address(bridgehub), + abi.encodeWithSelector(IBridgehub.ctmAssetIdFromChainId.selector), + abi.encode(assetId) + ); + vm.mockCall( + address(chainTypeManager), + abi.encodeWithSelector(IChainTypeManager.protocolVersion.selector), + abi.encode(chainTypeManager.protocolVersion()) + ); + + uint256 protocolVersion = chainTypeManager.getProtocolVersion(migratingChainId); + + bytes memory chainData = abi.encode(IAdmin(address(migratingChain)).prepareChainCommitment()); + bytes memory ctmData = abi.encode( + baseTokenAssetId, + msg.sender, + protocolVersion, + ecosystemConfig.contracts.diamondCutData + ); BridgehubMintCTMAssetData memory data = BridgehubMintCTMAssetData({ - chainId: mintChainId, + chainId: migratingChainId, baseTokenAssetId: baseTokenAssetId, ctmData: ctmData, chainData: chainData }); bytes memory bridgehubMintData = abi.encode(data); - vm.startBroadcast(address(bridgehub.sharedBridge())); - uint256 currentChainId = block.chainid; - vm.chainId(migratingChainId); - bridgehub.bridgeMint(gatewayChainId, assetId, bridgehubMintData); - vm.stopBroadcast(); + bytes memory message = abi.encodePacked( + IAssetRouterBase.finalizeDeposit.selector, + gatewayChainId, + assetId, + bridgehubMintData + ); + gatewayScript.finishMigrateChainFromGateway( + migratingChainId, + gatewayChainId, + 0, + 0, + 0, + message, + new bytes32[](0) + ); + vm.chainId(currentChainId); - assertEq(bridgehub.baseTokenAssetId(mintChainId), baseTokenAssetId); - IZKChain mintedZKChain = IZKChain(bridgehub.getZKChain(mintChainId)); - assertEq(mintedZKChain.getBaseTokenAssetId(), baseTokenAssetId); + assertEq(bridgehub.baseTokenAssetId(migratingChainId), baseTokenAssetId); + IZKChain migratingChainContract = IZKChain(bridgehub.getZKChain(migratingChainId)); + assertEq(migratingChainContract.getBaseTokenAssetId(), baseTokenAssetId); + } + + /// to increase coverage, properly tested in L2GatewayTests + function test_forwardToL3OnGateway() public { + _setUpGatewayWithFilterer(); + vm.chainId(12345); + vm.startBroadcast(SETTLEMENT_LAYER_RELAY_SENDER); + bridgehub.forwardTransactionOnGateway(migratingChainId, bytes32(0), 0); + vm.stopBroadcast(); } // add this to be excluded from coverage report diff --git a/l1-contracts/test/foundry/l1/integration/GatewayPreparationForTests.sol b/l1-contracts/test/foundry/l1/integration/_GatewayPreparationForTests.sol similarity index 87% rename from l1-contracts/test/foundry/l1/integration/GatewayPreparationForTests.sol rename to l1-contracts/test/foundry/l1/integration/_GatewayPreparationForTests.sol index 271dfdf0c..26e754d9f 100644 --- a/l1-contracts/test/foundry/l1/integration/GatewayPreparationForTests.sol +++ b/l1-contracts/test/foundry/l1/integration/_GatewayPreparationForTests.sol @@ -22,12 +22,12 @@ contract GatewayPreparationForTests is GatewayPreparation { ); config.governance = toml.readAddress("$.deployed_addresses.governance_addr"); - path = string.concat(root, vm.envString("GATEWAY_CONFIG")); + path = string.concat(root, vm.envString("GATEWAY_AS_CHAIN_CONFIG")); toml = vm.readFile(path); config.gatewayChainId = toml.readUint("$.chain.chain_chain_id"); - path = string.concat(root, vm.envString("GATEWAY_OUTPUT")); + path = string.concat(root, vm.envString("GATEWAY_AS_CHAIN_OUTPUT")); toml = vm.readFile(path); config.gatewayChainAdmin = toml.readAddress("$.chain_admin_addr"); @@ -35,6 +35,7 @@ contract GatewayPreparationForTests is GatewayPreparation { config.gatewayAccessControlRestriction = toml.readAddress( "$.deployed_addresses.access_control_restriction_addr" ); + config.l1NullifierProxy = toml.readAddress("$.deployed_addresses.bridges.l1_nullifier_proxy_addr"); console.log("chain chain id = ", config.gatewayChainId); diff --git a/l1-contracts/test/foundry/l1/integration/_SharedGatewayDeployer.t.sol b/l1-contracts/test/foundry/l1/integration/_SharedGatewayDeployer.t.sol index 66e0aa628..bd72835af 100644 --- a/l1-contracts/test/foundry/l1/integration/_SharedGatewayDeployer.t.sol +++ b/l1-contracts/test/foundry/l1/integration/_SharedGatewayDeployer.t.sol @@ -2,7 +2,7 @@ pragma solidity 0.8.24; import {L1ContractDeployer} from "./_SharedL1ContractDeployer.t.sol"; -import {GatewayPreparationForTests} from "./GatewayPreparationForTests.sol"; +import {GatewayPreparationForTests} from "./_GatewayPreparationForTests.sol"; import {ETH_TOKEN_ADDRESS} from "contracts/common/Config.sol"; import "@openzeppelin/contracts-v4/utils/Strings.sol"; @@ -17,11 +17,11 @@ contract GatewayDeployer is L1ContractDeployer { "/test/foundry/l1/integration/deploy-scripts/script-config/config-deploy-zk-chain-10.toml" ); vm.setEnv( - "GATEWAY_CONFIG", + "GATEWAY_AS_CHAIN_CONFIG", "/test/foundry/l1/integration/deploy-scripts/script-config/config-deploy-zk-chain-11.toml" ); vm.setEnv( - "GATEWAY_OUTPUT", + "GATEWAY_AS_CHAIN_OUTPUT", "/test/foundry/l1/integration/deploy-scripts/script-out/output-deploy-zk-chain-11.toml" ); diff --git a/l1-contracts/test/foundry/l1/integration/_SharedL1ContractDeployer.t.sol b/l1-contracts/test/foundry/l1/integration/_SharedL1ContractDeployer.t.sol index b180f6090..e4cb7e690 100644 --- a/l1-contracts/test/foundry/l1/integration/_SharedL1ContractDeployer.t.sol +++ b/l1-contracts/test/foundry/l1/integration/_SharedL1ContractDeployer.t.sol @@ -11,13 +11,19 @@ import {L1Nullifier} from "contracts/bridge/L1Nullifier.sol"; import {L1NativeTokenVault} from "contracts/bridge/ntv/L1NativeTokenVault.sol"; import {DataEncoding} from "contracts/common/libraries/DataEncoding.sol"; import {CTMDeploymentTracker} from "contracts/bridgehub/CTMDeploymentTracker.sol"; +import {IChainTypeManager} from "contracts/state-transition/IChainTypeManager.sol"; +import {DeployedAddresses, Config} from "deploy-scripts/DeployUtils.s.sol"; contract L1ContractDeployer is Test { using stdStorage for StdStorage; + DeployL1Script l1Script; + DeployedAddresses public ecosystemAddresses; + Config public ecosystemConfig; + address bridgehubProxyAddress; address bridgehubOwnerAddress; - Bridgehub bridgeHub; + Bridgehub bridgehub; CTMDeploymentTracker ctmDeploymentTracker; @@ -25,7 +31,7 @@ contract L1ContractDeployer is Test { L1Nullifier public l1Nullifier; L1NativeTokenVault public l1NativeTokenVault; - DeployL1Script l1Script; + IChainTypeManager chainTypeManager; function _deployL1Contracts() internal { vm.setEnv("L1_CONFIG", "/test/foundry/l1/integration/deploy-scripts/script-config/config-deploy-l1.toml"); @@ -38,33 +44,34 @@ contract L1ContractDeployer is Test { "ZK_CHAIN_OUT", "/test/foundry/l1/integration/deploy-scripts/script-out/output-deploy-zk-chain-era.toml" ); + vm.setEnv( + "GATEWAY_PREPARATION_L1_CONFIG", + "/test/foundry/l1/integration/deploy-scripts/script-config/gateway-preparation-l1.toml" + ); l1Script = new DeployL1Script(); l1Script.runForTest(); - bridgehubProxyAddress = l1Script.getBridgehubProxyAddress(); - bridgeHub = Bridgehub(bridgehubProxyAddress); - - address sharedBridgeProxyAddress = l1Script.getSharedBridgeProxyAddress(); - sharedBridge = L1AssetRouter(sharedBridgeProxyAddress); - - address l1NullifierProxyAddress = l1Script.getL1NullifierProxyAddress(); - l1Nullifier = L1Nullifier(l1NullifierProxyAddress); + ecosystemAddresses = l1Script.getAddresses(); + ecosystemConfig = l1Script.getConfig(); - address l1NativeTokenVaultProxyAddress = l1Script.getNativeTokenVaultProxyAddress(); - l1NativeTokenVault = L1NativeTokenVault(payable(l1NativeTokenVaultProxyAddress)); + bridgehub = Bridgehub(ecosystemAddresses.bridgehub.bridgehubProxy); + chainTypeManager = IChainTypeManager(ecosystemAddresses.stateTransition.chainTypeManagerProxy); + ctmDeploymentTracker = CTMDeploymentTracker(ecosystemAddresses.bridgehub.ctmDeploymentTrackerProxy); - ctmDeploymentTracker = CTMDeploymentTracker(l1Script.getCTMDeploymentTrackerAddress()); + sharedBridge = L1AssetRouter(ecosystemAddresses.bridges.sharedBridgeProxy); + l1Nullifier = L1Nullifier(ecosystemAddresses.bridges.l1NullifierProxy); + l1NativeTokenVault = L1NativeTokenVault(payable(ecosystemAddresses.vaults.l1NativeTokenVaultProxy)); _acceptOwnership(); _setEraBatch(); - bridgehubOwnerAddress = bridgeHub.owner(); + bridgehubOwnerAddress = bridgehub.owner(); } function _acceptOwnership() private { - vm.startPrank(bridgeHub.pendingOwner()); - bridgeHub.acceptOwnership(); + vm.startPrank(bridgehub.pendingOwner()); + bridgehub.acceptOwnership(); sharedBridge.acceptOwnership(); ctmDeploymentTracker.acceptOwnership(); vm.stopPrank(); @@ -79,9 +86,9 @@ contract L1ContractDeployer is Test { function _registerNewToken(address _tokenAddress) internal { bytes32 tokenAssetId = DataEncoding.encodeNTVAssetId(block.chainid, _tokenAddress); - if (!bridgeHub.assetIdIsRegistered(tokenAssetId)) { + if (!bridgehub.assetIdIsRegistered(tokenAssetId)) { vm.prank(bridgehubOwnerAddress); - bridgeHub.addTokenAssetId(tokenAssetId); + bridgehub.addTokenAssetId(tokenAssetId); } } diff --git a/l1-contracts/test/foundry/l1/integration/_SharedZKChainDeployer.t.sol b/l1-contracts/test/foundry/l1/integration/_SharedZKChainDeployer.t.sol index 74a2f86b6..8836fec99 100644 --- a/l1-contracts/test/foundry/l1/integration/_SharedZKChainDeployer.t.sol +++ b/l1-contracts/test/foundry/l1/integration/_SharedZKChainDeployer.t.sol @@ -13,6 +13,8 @@ import {Diamond} from "contracts/state-transition/libraries/Diamond.sol"; import {DiamondProxy} from "contracts/state-transition/chain-deps/DiamondProxy.sol"; import {IDiamondInit} from "contracts/state-transition/chain-interfaces/IDiamondInit.sol"; +import {Config as ChainConfig} from "deploy-scripts/RegisterZKChain.s.sol"; + contract ZKChainDeployer is L1ContractDeployer { using stdStorage for StdStorage; @@ -29,6 +31,8 @@ contract ZKChainDeployer is L1ContractDeployer { uint128 baseTokenGasPriceMultiplierDenominator; } + ChainConfig internal eraConfig; + uint256 currentZKChainId = 10; uint256 eraZKChainId = 9; uint256[] public zkChainIds; @@ -47,6 +51,7 @@ contract ZKChainDeployer is L1ContractDeployer { vm.warp(100); deployScript.runForTest(); zkChainIds.push(eraZKChainId); + eraConfig = deployScript.getConfig(); } function _deployZKChain(address _baseToken) internal { @@ -134,15 +139,15 @@ contract ZKChainDeployer is L1ContractDeployer { } function getZKChainAddress(uint256 _chainId) public view returns (address) { - return bridgeHub.getZKChain(_chainId); + return bridgehub.getZKChain(_chainId); } function getZKChainBaseToken(uint256 _chainId) public view returns (address) { - return bridgeHub.baseToken(_chainId); + return bridgehub.baseToken(_chainId); } function acceptPendingAdmin() public { - IZKChain chain = IZKChain(bridgeHub.getZKChain(currentZKChainId - 1)); + IZKChain chain = IZKChain(bridgehub.getZKChain(currentZKChainId - 1)); address admin = chain.getPendingAdmin(); vm.startBroadcast(admin); chain.acceptAdmin(); @@ -159,10 +164,10 @@ contract ZKChainDeployer is L1ContractDeployer { address _admin, uint256 _protocolVersion, bytes32 _storedBatchZero, - address _bridgeHub + address _bridgehub ) internal returns (address) { Diamond.DiamondCutData memory diamondCut = abi.decode( - l1Script.getInitialDiamondCutData(), + ecosystemConfig.contracts.diamondCutData, (Diamond.DiamondCutData) ); bytes memory initData; @@ -171,7 +176,7 @@ contract ZKChainDeployer is L1ContractDeployer { initData = bytes.concat( IDiamondInit.initialize.selector, bytes32(_chainId), - bytes32(uint256(uint160(address(_bridgeHub)))), + bytes32(uint256(uint160(address(_bridgehub)))), bytes32(uint256(uint160(address(this)))), bytes32(_protocolVersion), bytes32(uint256(uint160(_admin))), diff --git a/l1-contracts/test/foundry/l1/integration/deploy-scripts/script-config/config-deploy-l1.toml b/l1-contracts/test/foundry/l1/integration/deploy-scripts/script-config/config-deploy-l1.toml index 5683d081a..d52a6b704 100644 --- a/l1-contracts/test/foundry/l1/integration/deploy-scripts/script-config/config-deploy-l1.toml +++ b/l1-contracts/test/foundry/l1/integration/deploy-scripts/script-config/config-deploy-l1.toml @@ -12,7 +12,7 @@ validator_timelock_execution_delay = 0 genesis_root = "0x1000000000000000000000000000000000000000000000000000000000000000" genesis_rollup_leaf_index = 1 genesis_batch_commitment = "0x1000000000000000000000000000000000000000000000000000000000000000" -latest_protocol_version = 0 +latest_protocol_version = 25 recursion_node_level_vk_hash = "0x0000000000000000000000000000000000000000000000000000000000000000" recursion_leaf_level_vk_hash = "0x0000000000000000000000000000000000000000000000000000000000000000" recursion_circuits_set_vks_hash = "0x0000000000000000000000000000000000000000000000000000000000000000" diff --git a/l1-contracts/test/foundry/l1/integration/l2-tests-in-l1-context/L2Erc20L1Test.t.sol b/l1-contracts/test/foundry/l1/integration/l2-tests-in-l1-context/L2Erc20L1Test.t.sol new file mode 100644 index 000000000..b6346a09d --- /dev/null +++ b/l1-contracts/test/foundry/l1/integration/l2-tests-in-l1-context/L2Erc20L1Test.t.sol @@ -0,0 +1,51 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.20; + +// solhint-disable gas-custom-errors + +import {Test} from "forge-std/Test.sol"; +import "forge-std/console.sol"; + +import {BridgedStandardERC20} from "contracts/bridge/BridgedStandardERC20.sol"; +import {L2AssetRouter} from "contracts/bridge/asset-router/L2AssetRouter.sol"; +import {IL2NativeTokenVault} from "contracts/bridge/ntv/IL2NativeTokenVault.sol"; + +import {UpgradeableBeacon} from "@openzeppelin/contracts-v4/proxy/beacon/UpgradeableBeacon.sol"; +import {BeaconProxy} from "@openzeppelin/contracts-v4/proxy/beacon/BeaconProxy.sol"; + +import {L2_ASSET_ROUTER_ADDR, L2_NATIVE_TOKEN_VAULT_ADDR, L2_BRIDGEHUB_ADDR} from "contracts/common/L2ContractAddresses.sol"; +import {ETH_TOKEN_ADDRESS, SETTLEMENT_LAYER_RELAY_SENDER} from "contracts/common/Config.sol"; + +import {AddressAliasHelper} from "contracts/vendor/AddressAliasHelper.sol"; +import {BridgehubMintCTMAssetData} from "contracts/bridgehub/IBridgehub.sol"; +import {IAdmin} from "contracts/state-transition/chain-interfaces/IAdmin.sol"; +import {IL2AssetRouter} from "contracts/bridge/asset-router/IL2AssetRouter.sol"; +import {IL1Nullifier} from "contracts/bridge/interfaces/IL1Nullifier.sol"; +import {IL1AssetRouter} from "contracts/bridge/asset-router/IL1AssetRouter.sol"; +import {IBridgehub} from "contracts/bridgehub/IBridgehub.sol"; + +import {SharedL2ContractDeployer} from "./_SharedL2ContractDeployer.sol"; +import {IChainTypeManager} from "contracts/state-transition/IChainTypeManager.sol"; +import {IZKChain} from "contracts/state-transition/chain-interfaces/IZKChain.sol"; +import {SystemContractsArgs} from "./_SharedL2ContractL1DeployerUtils.sol"; + +import {DeployUtils} from "deploy-scripts/DeployUtils.s.sol"; +import {L2Erc20TestAbstract} from "./L2Erc20TestAbstract.t.sol"; +import {SharedL2ContractL1DeployerUtils} from "./_SharedL2ContractL1DeployerUtils.sol"; + +contract L2Erc20L1Test is Test, SharedL2ContractL1DeployerUtils, SharedL2ContractDeployer, L2Erc20TestAbstract { + function test() internal virtual override(DeployUtils, SharedL2ContractL1DeployerUtils) {} + + function initSystemContracts( + SystemContractsArgs memory _args + ) internal virtual override(SharedL2ContractDeployer, SharedL2ContractL1DeployerUtils) { + super.initSystemContracts(_args); + } + + function deployL2Contracts( + uint256 _l1ChainId + ) public virtual override(SharedL2ContractDeployer, SharedL2ContractL1DeployerUtils) { + super.deployL2Contracts(_l1ChainId); + } +} diff --git a/l1-contracts/test/foundry/l2/unit/erc20/L2Erc20BridgeTest.t.sol b/l1-contracts/test/foundry/l1/integration/l2-tests-in-l1-context/L2Erc20TestAbstract.t.sol similarity index 58% rename from l1-contracts/test/foundry/l2/unit/erc20/L2Erc20BridgeTest.t.sol rename to l1-contracts/test/foundry/l1/integration/l2-tests-in-l1-context/L2Erc20TestAbstract.t.sol index 5e195353e..fcabf0d01 100644 --- a/l1-contracts/test/foundry/l2/unit/erc20/L2Erc20BridgeTest.t.sol +++ b/l1-contracts/test/foundry/l1/integration/l2-tests-in-l1-context/L2Erc20TestAbstract.t.sol @@ -14,71 +14,33 @@ import {IL2NativeTokenVault} from "contracts/bridge/ntv/IL2NativeTokenVault.sol" import {UpgradeableBeacon} from "@openzeppelin/contracts-v4/proxy/beacon/UpgradeableBeacon.sol"; import {BeaconProxy} from "@openzeppelin/contracts-v4/proxy/beacon/BeaconProxy.sol"; -import {L2_ASSET_ROUTER_ADDR, L2_NATIVE_TOKEN_VAULT_ADDR} from "contracts/common/L2ContractAddresses.sol"; +import {L2_ASSET_ROUTER_ADDR, L2_NATIVE_TOKEN_VAULT_ADDR, L2_BRIDGEHUB_ADDR} from "contracts/common/L2ContractAddresses.sol"; +import {ETH_TOKEN_ADDRESS, SETTLEMENT_LAYER_RELAY_SENDER} from "contracts/common/Config.sol"; import {AddressAliasHelper} from "contracts/vendor/AddressAliasHelper.sol"; +import {BridgehubMintCTMAssetData} from "contracts/bridgehub/IBridgehub.sol"; +import {IAdmin} from "contracts/state-transition/chain-interfaces/IAdmin.sol"; +import {IL2AssetRouter} from "contracts/bridge/asset-router/IL2AssetRouter.sol"; +import {IL1Nullifier} from "contracts/bridge/interfaces/IL1Nullifier.sol"; +import {IL1AssetRouter} from "contracts/bridge/asset-router/IL1AssetRouter.sol"; +import {IBridgehub} from "contracts/bridgehub/IBridgehub.sol"; -import {L2Utils} from "../utils/L2Utils.sol"; +import {SharedL2ContractDeployer} from "./_SharedL2ContractDeployer.sol"; +import {IChainTypeManager} from "contracts/state-transition/IChainTypeManager.sol"; +import {IZKChain} from "contracts/state-transition/chain-interfaces/IZKChain.sol"; +import {SystemContractsArgs} from "./_SharedL2ContractL1DeployerUtils.sol"; -contract L2Erc20BridgeTest is Test { - // 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. - address internal l1BridgeWallet = address(1); - address internal aliasedL1BridgeWallet; - - // The owner of the beacon and the native token vault - address internal ownerWallet = address(2); - - BridgedStandardERC20 internal standardErc20Impl; - - UpgradeableBeacon internal beacon; - BeaconProxy internal proxy; - - uint256 internal constant L1_CHAIN_ID = 9; - uint256 internal ERA_CHAIN_ID = 270; - - // We won't actually deploy an L1 token in these tests, but we need some address for it. - address internal L1_TOKEN_ADDRESS = 0x1111100000000000000000000000000000011111; - - string internal constant TOKEN_DEFAULT_NAME = "TestnetERC20Token"; - string internal constant TOKEN_DEFAULT_SYMBOL = "TET"; - uint8 internal constant TOKEN_DEFAULT_DECIMALS = 18; - - function setUp() public { - aliasedL1BridgeWallet = AddressAliasHelper.applyL1ToL2Alias(l1BridgeWallet); - - standardErc20Impl = new BridgedStandardERC20(); - beacon = new UpgradeableBeacon(address(standardErc20Impl)); - beacon.transferOwnership(ownerWallet); - - // One of the purposes of deploying it here is to publish its bytecode - BeaconProxy beaconProxy = new BeaconProxy(address(beacon), new bytes(0)); - proxy = beaconProxy; - bytes32 beaconProxyBytecodeHash; - assembly { - beaconProxyBytecodeHash := extcodehash(beaconProxy) - } - - L2Utils.initSystemContracts(); - L2Utils.forceDeployAssetRouter(L1_CHAIN_ID, ERA_CHAIN_ID, ownerWallet, l1BridgeWallet, address(0)); - L2Utils.forceDeployNativeTokenVault({ - _l1ChainId: L1_CHAIN_ID, - _aliasedOwner: ownerWallet, - _l2TokenProxyBytecodeHash: beaconProxyBytecodeHash, - _legacySharedBridge: address(0), - _l2TokenBeacon: address(beacon), - _contractsDeployedAlready: true - }); - } +import {DeployUtils} from "deploy-scripts/DeployUtils.s.sol"; +abstract contract L2Erc20TestAbstract is Test, SharedL2ContractDeployer { function performDeposit(address depositor, address receiver, uint256 amount) internal { - vm.prank(aliasedL1BridgeWallet); + vm.prank(aliasedL1AssetRouter); L2AssetRouter(L2_ASSET_ROUTER_ADDR).finalizeDeposit({ _l1Sender: depositor, _l2Receiver: receiver, _l1Token: L1_TOKEN_ADDRESS, _amount: amount, - _data: L2Utils.encodeTokenData(TOKEN_DEFAULT_NAME, TOKEN_DEFAULT_SYMBOL, TOKEN_DEFAULT_DECIMALS) + _data: encodeTokenData(TOKEN_DEFAULT_NAME, TOKEN_DEFAULT_SYMBOL, TOKEN_DEFAULT_DECIMALS) }); } diff --git a/l1-contracts/test/foundry/l1/integration/l2-tests-in-l1-context/L2GatewayL1Test.t.sol b/l1-contracts/test/foundry/l1/integration/l2-tests-in-l1-context/L2GatewayL1Test.t.sol new file mode 100644 index 000000000..3e8b04e42 --- /dev/null +++ b/l1-contracts/test/foundry/l1/integration/l2-tests-in-l1-context/L2GatewayL1Test.t.sol @@ -0,0 +1,51 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.20; + +// solhint-disable gas-custom-errors + +import {Test} from "forge-std/Test.sol"; +import "forge-std/console.sol"; + +import {BridgedStandardERC20} from "contracts/bridge/BridgedStandardERC20.sol"; +import {L2AssetRouter} from "contracts/bridge/asset-router/L2AssetRouter.sol"; +import {IL2NativeTokenVault} from "contracts/bridge/ntv/IL2NativeTokenVault.sol"; + +import {UpgradeableBeacon} from "@openzeppelin/contracts-v4/proxy/beacon/UpgradeableBeacon.sol"; +import {BeaconProxy} from "@openzeppelin/contracts-v4/proxy/beacon/BeaconProxy.sol"; + +import {L2_ASSET_ROUTER_ADDR, L2_NATIVE_TOKEN_VAULT_ADDR, L2_BRIDGEHUB_ADDR} from "contracts/common/L2ContractAddresses.sol"; +import {ETH_TOKEN_ADDRESS, SETTLEMENT_LAYER_RELAY_SENDER} from "contracts/common/Config.sol"; + +import {AddressAliasHelper} from "contracts/vendor/AddressAliasHelper.sol"; +import {BridgehubMintCTMAssetData} from "contracts/bridgehub/IBridgehub.sol"; +import {IAdmin} from "contracts/state-transition/chain-interfaces/IAdmin.sol"; +import {IL2AssetRouter} from "contracts/bridge/asset-router/IL2AssetRouter.sol"; +import {IL1Nullifier} from "contracts/bridge/interfaces/IL1Nullifier.sol"; +import {IL1AssetRouter} from "contracts/bridge/asset-router/IL1AssetRouter.sol"; +import {IBridgehub} from "contracts/bridgehub/IBridgehub.sol"; + +import {SharedL2ContractDeployer} from "./_SharedL2ContractDeployer.sol"; +import {IChainTypeManager} from "contracts/state-transition/IChainTypeManager.sol"; +import {IZKChain} from "contracts/state-transition/chain-interfaces/IZKChain.sol"; +import {SystemContractsArgs} from "./_SharedL2ContractL1DeployerUtils.sol"; + +import {DeployUtils} from "deploy-scripts/DeployUtils.s.sol"; +import {L2GatewayTestAbstract} from "./L2GatewayTestAbstract.t.sol"; +import {SharedL2ContractL1DeployerUtils} from "./_SharedL2ContractL1DeployerUtils.sol"; + +contract L2GatewayL1Test is Test, SharedL2ContractL1DeployerUtils, SharedL2ContractDeployer, L2GatewayTestAbstract { + function test() internal virtual override(DeployUtils, SharedL2ContractL1DeployerUtils) {} + + function initSystemContracts( + SystemContractsArgs memory _args + ) internal virtual override(SharedL2ContractDeployer, SharedL2ContractL1DeployerUtils) { + super.initSystemContracts(_args); + } + + function deployL2Contracts( + uint256 _l1ChainId + ) public virtual override(SharedL2ContractDeployer, SharedL2ContractL1DeployerUtils) { + super.deployL2Contracts(_l1ChainId); + } +} diff --git a/l1-contracts/test/foundry/l1/integration/l2-tests-in-l1-context/L2GatewayTestAbstract.t.sol b/l1-contracts/test/foundry/l1/integration/l2-tests-in-l1-context/L2GatewayTestAbstract.t.sol new file mode 100644 index 000000000..e97c2017b --- /dev/null +++ b/l1-contracts/test/foundry/l1/integration/l2-tests-in-l1-context/L2GatewayTestAbstract.t.sol @@ -0,0 +1,85 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.20; + +// solhint-disable gas-custom-errors + +import {Test} from "forge-std/Test.sol"; +import "forge-std/console.sol"; + +import {BridgedStandardERC20} from "contracts/bridge/BridgedStandardERC20.sol"; +import {L2AssetRouter} from "contracts/bridge/asset-router/L2AssetRouter.sol"; +import {IL2NativeTokenVault} from "contracts/bridge/ntv/IL2NativeTokenVault.sol"; + +import {UpgradeableBeacon} from "@openzeppelin/contracts-v4/proxy/beacon/UpgradeableBeacon.sol"; +import {BeaconProxy} from "@openzeppelin/contracts-v4/proxy/beacon/BeaconProxy.sol"; + +import {L2_ASSET_ROUTER_ADDR, L2_NATIVE_TOKEN_VAULT_ADDR, L2_BRIDGEHUB_ADDR, L2_MESSENGER} from "contracts/common/L2ContractAddresses.sol"; +import {ETH_TOKEN_ADDRESS, SETTLEMENT_LAYER_RELAY_SENDER} from "contracts/common/Config.sol"; + +import {AddressAliasHelper} from "contracts/vendor/AddressAliasHelper.sol"; +import {BridgehubMintCTMAssetData, BridgehubBurnCTMAssetData} from "contracts/bridgehub/IBridgehub.sol"; +import {IAdmin} from "contracts/state-transition/chain-interfaces/IAdmin.sol"; +import {IL2AssetRouter} from "contracts/bridge/asset-router/IL2AssetRouter.sol"; +import {IL1Nullifier} from "contracts/bridge/interfaces/IL1Nullifier.sol"; +import {IL1AssetRouter} from "contracts/bridge/asset-router/IL1AssetRouter.sol"; +import {IBridgehub} from "contracts/bridgehub/IBridgehub.sol"; + +import {SharedL2ContractDeployer} from "./_SharedL2ContractDeployer.sol"; +import {IChainTypeManager} from "contracts/state-transition/IChainTypeManager.sol"; +import {IZKChain} from "contracts/state-transition/chain-interfaces/IZKChain.sol"; +import {SystemContractsArgs} from "./_SharedL2ContractL1DeployerUtils.sol"; + +import {DeployUtils} from "deploy-scripts/DeployUtils.s.sol"; + +abstract contract L2GatewayTestAbstract is Test, SharedL2ContractDeployer { + function test_gatewayShouldFinalizeDeposit() public { + finalizeDeposit(); + require(l2Bridgehub.ctmAssetIdFromAddress(address(chainTypeManager)) == ctmAssetId, "ctmAssetId mismatch"); + require(l2Bridgehub.ctmAssetIdFromChainId(mintChainId) == ctmAssetId, "ctmAssetIdFromChainId mismatch"); + } + + function test_forwardToL3OnGateway() public { + // todo fix this test + finalizeDeposit(); + vm.prank(SETTLEMENT_LAYER_RELAY_SENDER); + l2Bridgehub.forwardTransactionOnGateway(mintChainId, bytes32(0), 0); + } + + function test_withdrawFromGateway() public { + // todo fix this test + finalizeDeposit(); + address newAdmin = address(0x1); + bytes memory newDiamondCut = abi.encode(); + BridgehubBurnCTMAssetData memory data = BridgehubBurnCTMAssetData({ + chainId: mintChainId, + ctmData: abi.encode(newAdmin, config.contracts.diamondCutData), + chainData: abi.encode(chainTypeManager.protocolVersion()) + }); + vm.prank(ownerWallet); + vm.mockCall( + address(L2_MESSENGER), + abi.encodeWithSelector(L2_MESSENGER.sendToL1.selector), + abi.encode(bytes("")) + ); + l2AssetRouter.withdraw(ctmAssetId, abi.encode(data)); + } + + function finalizeDeposit() public { + bytes memory chainData = exampleChainCommitment; + bytes memory ctmData = abi.encode( + baseTokenAssetId, + ownerWallet, + chainTypeManager.protocolVersion(), + config.contracts.diamondCutData + ); + BridgehubMintCTMAssetData memory data = BridgehubMintCTMAssetData({ + chainId: mintChainId, + baseTokenAssetId: baseTokenAssetId, + ctmData: ctmData, + chainData: chainData + }); + vm.prank(aliasedL1AssetRouter); + l2AssetRouter.finalizeDeposit(L1_CHAIN_ID, ctmAssetId, abi.encode(data)); + } +} diff --git a/l1-contracts/test/foundry/l2/unit/weth/WETH.t.sol b/l1-contracts/test/foundry/l1/integration/l2-tests-in-l1-context/L2WethTestAbstract.t.sol similarity index 61% rename from l1-contracts/test/foundry/l2/unit/weth/WETH.t.sol rename to l1-contracts/test/foundry/l1/integration/l2-tests-in-l1-context/L2WethTestAbstract.t.sol index 6cbc44fa7..d2a43be2e 100644 --- a/l1-contracts/test/foundry/l2/unit/weth/WETH.t.sol +++ b/l1-contracts/test/foundry/l1/integration/l2-tests-in-l1-context/L2WethTestAbstract.t.sol @@ -2,33 +2,38 @@ pragma solidity ^0.8.20; -import {Test} from "forge-std/Test.sol"; - -import {L2WrappedBaseToken} from "contracts/bridge/L2WrappedBaseToken.sol"; -import {TransparentUpgradeableProxy} from "@openzeppelin/contracts-v4/proxy/transparent/TransparentUpgradeableProxy.sol"; - -import {Unauthorized, UnimplementedMessage, BridgeMintNotImplemented} from "contracts/common/L1ContractErrors.sol"; +// solhint-disable gas-custom-errors -contract WethTest is Test { - L2WrappedBaseToken internal weth; +import {Test} from "forge-std/Test.sol"; +import "forge-std/console.sol"; - // The owner of the proxy - address internal ownerWallet = address(2); +import {BridgedStandardERC20} from "contracts/bridge/BridgedStandardERC20.sol"; +import {L2AssetRouter} from "contracts/bridge/asset-router/L2AssetRouter.sol"; +import {IL2NativeTokenVault} from "contracts/bridge/ntv/IL2NativeTokenVault.sol"; - address internal l2BridgeAddress = address(3); - address internal l1Address = address(4); +import {UpgradeableBeacon} from "@openzeppelin/contracts-v4/proxy/beacon/UpgradeableBeacon.sol"; +import {BeaconProxy} from "@openzeppelin/contracts-v4/proxy/beacon/BeaconProxy.sol"; - function setUp() public { - ownerWallet = makeAddr("owner"); - L2WrappedBaseToken impl = new L2WrappedBaseToken(); +import {L2_ASSET_ROUTER_ADDR, L2_NATIVE_TOKEN_VAULT_ADDR, L2_BRIDGEHUB_ADDR} from "contracts/common/L2ContractAddresses.sol"; +import {ETH_TOKEN_ADDRESS, SETTLEMENT_LAYER_RELAY_SENDER} from "contracts/common/Config.sol"; - TransparentUpgradeableProxy proxy = new TransparentUpgradeableProxy(address(impl), ownerWallet, ""); +import {AddressAliasHelper} from "contracts/vendor/AddressAliasHelper.sol"; +import {BridgehubMintCTMAssetData} from "contracts/bridgehub/IBridgehub.sol"; +import {IAdmin} from "contracts/state-transition/chain-interfaces/IAdmin.sol"; +import {IL2AssetRouter} from "contracts/bridge/asset-router/IL2AssetRouter.sol"; +import {IL1Nullifier} from "contracts/bridge/interfaces/IL1Nullifier.sol"; +import {IL1AssetRouter} from "contracts/bridge/asset-router/IL1AssetRouter.sol"; +import {IBridgehub} from "contracts/bridgehub/IBridgehub.sol"; - weth = L2WrappedBaseToken(payable(proxy)); +import {SharedL2ContractDeployer} from "./_SharedL2ContractDeployer.sol"; +import {IChainTypeManager} from "contracts/state-transition/IChainTypeManager.sol"; +import {IZKChain} from "contracts/state-transition/chain-interfaces/IZKChain.sol"; +import {SystemContractsArgs} from "./_SharedL2ContractL1DeployerUtils.sol"; - weth.initializeV2("Wrapped Ether", "WETH", l2BridgeAddress, l1Address); - } +import {DeployUtils} from "deploy-scripts/DeployUtils.s.sol"; +import {Unauthorized, UnimplementedMessage, BridgeMintNotImplemented} from "contracts/common/L1ContractErrors.sol"; +abstract contract L2WethTestAbstract is Test, SharedL2ContractDeployer { function test_shouldDepositWethByCallingDeposit() public { uint256 amount = 100; weth.deposit{value: amount}(); @@ -102,7 +107,7 @@ contract WethTest is Test { function test_revertWhenCallingBridgeMint() public { vm.expectRevert(abi.encodeWithSelector(BridgeMintNotImplemented.selector)); - vm.prank(l2BridgeAddress); + vm.prank(L2_ASSET_ROUTER_ADDR); weth.bridgeMint(address(1), 1); } diff --git a/l1-contracts/test/foundry/l1/integration/l2-tests-in-l1-context/_SharedL2ContractDeployer.sol b/l1-contracts/test/foundry/l1/integration/l2-tests-in-l1-context/_SharedL2ContractDeployer.sol new file mode 100644 index 000000000..83d8def98 --- /dev/null +++ b/l1-contracts/test/foundry/l1/integration/l2-tests-in-l1-context/_SharedL2ContractDeployer.sol @@ -0,0 +1,202 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.20; + +// solhint-disable gas-custom-errors + +import {Test} from "forge-std/Test.sol"; +import "forge-std/console.sol"; + +import {TransparentUpgradeableProxy} from "@openzeppelin/contracts-v4/proxy/transparent/TransparentUpgradeableProxy.sol"; + +import {BridgedStandardERC20} from "contracts/bridge/BridgedStandardERC20.sol"; +import {L2AssetRouter} from "contracts/bridge/asset-router/L2AssetRouter.sol"; +import {IL2NativeTokenVault} from "contracts/bridge/ntv/IL2NativeTokenVault.sol"; + +import {UpgradeableBeacon} from "@openzeppelin/contracts-v4/proxy/beacon/UpgradeableBeacon.sol"; +import {BeaconProxy} from "@openzeppelin/contracts-v4/proxy/beacon/BeaconProxy.sol"; + +import {L2_ASSET_ROUTER_ADDR, L2_NATIVE_TOKEN_VAULT_ADDR, L2_BRIDGEHUB_ADDR} from "contracts/common/L2ContractAddresses.sol"; +import {ETH_TOKEN_ADDRESS, SETTLEMENT_LAYER_RELAY_SENDER} from "contracts/common/Config.sol"; + +import {AddressAliasHelper} from "contracts/vendor/AddressAliasHelper.sol"; +import {BridgehubMintCTMAssetData} from "contracts/bridgehub/IBridgehub.sol"; +import {IAdmin} from "contracts/state-transition/chain-interfaces/IAdmin.sol"; +import {IL2AssetRouter} from "contracts/bridge/asset-router/IL2AssetRouter.sol"; +import {IL1Nullifier} from "contracts/bridge/interfaces/IL1Nullifier.sol"; +import {IL1AssetRouter} from "contracts/bridge/asset-router/IL1AssetRouter.sol"; +import {IBridgehub} from "contracts/bridgehub/IBridgehub.sol"; +import {L2WrappedBaseToken} from "contracts/bridge/L2WrappedBaseToken.sol"; +import {L2SharedBridgeLegacy} from "contracts/bridge/L2SharedBridgeLegacy.sol"; +import {DataEncoding} from "contracts/common/libraries/DataEncoding.sol"; + +import {IChainTypeManager} from "contracts/state-transition/IChainTypeManager.sol"; +import {IZKChain} from "contracts/state-transition/chain-interfaces/IZKChain.sol"; +import {SystemContractsArgs} from "./_SharedL2ContractL1DeployerUtils.sol"; + +import {DeployUtils} from "deploy-scripts/DeployUtils.s.sol"; + +abstract contract SharedL2ContractDeployer is Test, DeployUtils { + L2WrappedBaseToken internal weth; + address internal l1WethAddress = address(4); + + // The owner of the beacon and the native token vault + address internal ownerWallet = address(2); + + BridgedStandardERC20 internal standardErc20Impl; + + UpgradeableBeacon internal beacon; + BeaconProxy internal proxy; + + IL2AssetRouter l2AssetRouter = IL2AssetRouter(L2_ASSET_ROUTER_ADDR); + IBridgehub l2Bridgehub = IBridgehub(L2_BRIDGEHUB_ADDR); + + uint256 internal constant L1_CHAIN_ID = 10; // it cannot be 9, the default block.chainid + uint256 internal ERA_CHAIN_ID = 270; + uint256 internal mintChainId = 300; + address internal l1AssetRouter = makeAddr("l1AssetRouter"); + address internal aliasedL1AssetRouter = AddressAliasHelper.applyL1ToL2Alias(l1AssetRouter); + + // We won't actually deploy an L1 token in these tests, but we need some address for it. + address internal L1_TOKEN_ADDRESS = 0x1111100000000000000000000000000000011111; + + string internal constant TOKEN_DEFAULT_NAME = "TestnetERC20Token"; + string internal constant TOKEN_DEFAULT_SYMBOL = "TET"; + uint8 internal constant TOKEN_DEFAULT_DECIMALS = 18; + address internal l1CTMDeployer = makeAddr("l1CTMDeployer"); + address internal l1CTM = makeAddr("l1CTM"); + bytes32 internal ctmAssetId = keccak256(abi.encode(L1_CHAIN_ID, l1CTMDeployer, bytes32(uint256(uint160(l1CTM))))); + + bytes32 internal baseTokenAssetId = + keccak256(abi.encode(L1_CHAIN_ID, L2_NATIVE_TOKEN_VAULT_ADDR, abi.encode(ETH_TOKEN_ADDRESS))); + + bytes internal exampleChainCommitment; + + IChainTypeManager internal chainTypeManager; + + function setUp() public { + standardErc20Impl = new BridgedStandardERC20(); + beacon = new UpgradeableBeacon(address(standardErc20Impl)); + beacon.transferOwnership(ownerWallet); + + // One of the purposes of deploying it here is to publish its bytecode + BeaconProxy beaconProxy = new BeaconProxy(address(beacon), new bytes(0)); + proxy = beaconProxy; + bytes32 beaconProxyBytecodeHash; + assembly { + beaconProxyBytecodeHash := extcodehash(beaconProxy) + } + + address l2SharedBridge = deployL2SharedBridgeLegacy( + L1_CHAIN_ID, + ERA_CHAIN_ID, + ownerWallet, + l1AssetRouter, + beaconProxyBytecodeHash + ); + + L2WrappedBaseToken weth = deployL2Weth(); + + initSystemContracts( + SystemContractsArgs({ + l1ChainId: L1_CHAIN_ID, + eraChainId: ERA_CHAIN_ID, + l1AssetRouter: l1AssetRouter, + legacySharedBridge: l2SharedBridge, + l2TokenBeacon: address(beacon), + l2TokenProxyBytecodeHash: beaconProxyBytecodeHash, + aliasedOwner: ownerWallet, + contractsDeployedAlready: false, + l1CtmDeployer: l1CTMDeployer + }) + ); + deployL2Contracts(L1_CHAIN_ID); + + vm.prank(aliasedL1AssetRouter); + l2AssetRouter.setAssetHandlerAddress(L1_CHAIN_ID, ctmAssetId, L2_BRIDGEHUB_ADDR); + vm.prank(ownerWallet); + l2Bridgehub.addChainTypeManager(address(addresses.stateTransition.chainTypeManagerProxy)); + vm.prank(AddressAliasHelper.applyL1ToL2Alias(l1CTMDeployer)); + l2Bridgehub.setAssetHandlerAddress( + bytes32(uint256(uint160(l1CTM))), + address(addresses.stateTransition.chainTypeManagerProxy) + ); + chainTypeManager = IChainTypeManager(address(addresses.stateTransition.chainTypeManagerProxy)); + getExampleChainCommitment(); + } + + function getExampleChainCommitment() internal returns (bytes memory) { + vm.mockCall( + L2_ASSET_ROUTER_ADDR, + abi.encodeWithSelector(IL1AssetRouter.L1_NULLIFIER.selector), + abi.encode(L2_ASSET_ROUTER_ADDR) + ); + vm.mockCall( + L2_ASSET_ROUTER_ADDR, + abi.encodeWithSelector(IL1Nullifier.l2BridgeAddress.selector), + abi.encode(address(0)) + ); + vm.prank(L2_BRIDGEHUB_ADDR); + address chainAddress = chainTypeManager.createNewChain( + ERA_CHAIN_ID + 1, + baseTokenAssetId, + address(0x1), + abi.encode(config.contracts.diamondCutData, generatedData.forceDeploymentsData), + new bytes[](0) + ); + exampleChainCommitment = abi.encode(IZKChain(chainAddress).prepareChainCommitment()); + } + + /// @notice Encodes the token data. + /// @param name The name of the token. + /// @param symbol The symbol of the token. + /// @param decimals The decimals of the token. + function encodeTokenData( + string memory name, + string memory symbol, + uint8 decimals + ) internal pure returns (bytes memory) { + bytes memory encodedName = abi.encode(name); + bytes memory encodedSymbol = abi.encode(symbol); + bytes memory encodedDecimals = abi.encode(decimals); + + return abi.encode(encodedName, encodedSymbol, encodedDecimals); + } + + function deployL2SharedBridgeLegacy( + uint256 _l1ChainId, + uint256 _eraChainId, + address _aliasedOwner, + address _l1SharedBridge, + bytes32 _l2TokenProxyBytecodeHash + ) internal returns (address) { + bytes32 ethAssetId = DataEncoding.encodeNTVAssetId(_l1ChainId, ETH_TOKEN_ADDRESS); + + L2SharedBridgeLegacy bridge = new L2SharedBridgeLegacy(); + console.log("bridge", address(bridge)); + address proxyAdmin = address(0x1); + TransparentUpgradeableProxy proxy = new TransparentUpgradeableProxy( + address(bridge), + proxyAdmin, + abi.encodeWithSelector( + L2SharedBridgeLegacy.initialize.selector, + _l1SharedBridge, + _l2TokenProxyBytecodeHash, + _aliasedOwner + ) + ); + console.log("proxy", address(proxy)); + return address(proxy); + } + + function deployL2Weth() internal returns (L2WrappedBaseToken) { + L2WrappedBaseToken wethImpl = new L2WrappedBaseToken(); + TransparentUpgradeableProxy wethProxy = new TransparentUpgradeableProxy(address(wethImpl), ownerWallet, ""); + weth = L2WrappedBaseToken(payable(wethProxy)); + weth.initializeV2("Wrapped Ether", "WETH", L2_ASSET_ROUTER_ADDR, l1WethAddress, baseTokenAssetId); + return weth; + } + + function initSystemContracts(SystemContractsArgs memory _args) internal virtual; + function deployL2Contracts(uint256 _l1ChainId) public virtual; +} diff --git a/l1-contracts/test/foundry/l1/integration/l2-tests-in-l1-context/_SharedL2ContractL1DeployerUtils.sol b/l1-contracts/test/foundry/l1/integration/l2-tests-in-l1-context/_SharedL2ContractL1DeployerUtils.sol new file mode 100644 index 000000000..9dd125055 --- /dev/null +++ b/l1-contracts/test/foundry/l1/integration/l2-tests-in-l1-context/_SharedL2ContractL1DeployerUtils.sol @@ -0,0 +1,129 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.24; + +import {Test} from "forge-std/Test.sol"; +import {StdStorage, stdStorage, stdToml} from "forge-std/Test.sol"; +import {Script, console2 as console} from "forge-std/Script.sol"; + +import {Bridgehub} from "contracts/bridgehub/Bridgehub.sol"; +import {L1AssetRouter} from "contracts/bridge/asset-router/L1AssetRouter.sol"; +import {L1Nullifier} from "contracts/bridge/L1Nullifier.sol"; +import {L1NativeTokenVault} from "contracts/bridge/ntv/L1NativeTokenVault.sol"; +import {DataEncoding} from "contracts/common/libraries/DataEncoding.sol"; +import {CTMDeploymentTracker} from "contracts/bridgehub/CTMDeploymentTracker.sol"; +import {IChainTypeManager} from "contracts/state-transition/IChainTypeManager.sol"; +import {DeployedAddresses, Config} from "deploy-scripts/DeployUtils.s.sol"; + +import {DeployUtils} from "deploy-scripts/DeployUtils.s.sol"; + +import {L2_MESSAGE_ROOT_ADDR, L2_BRIDGEHUB_ADDR, L2_ASSET_ROUTER_ADDR, L2_NATIVE_TOKEN_VAULT_ADDR} from "contracts/common/L2ContractAddresses.sol"; + +import {MessageRoot} from "contracts/bridgehub/MessageRoot.sol"; +import {IBridgehub} from "contracts/bridgehub/IBridgehub.sol"; +import {L2AssetRouter} from "contracts/bridge/asset-router/L2AssetRouter.sol"; +import {L2NativeTokenVault} from "contracts/bridge/ntv/L2NativeTokenVault.sol"; +import {L2NativeTokenVaultDev} from "contracts/dev-contracts/test/L2NativeTokenVaultDev.sol"; +import {ETH_TOKEN_ADDRESS} from "contracts/common/Config.sol"; +import {IMessageRoot} from "contracts/bridgehub/IMessageRoot.sol"; +import {ICTMDeploymentTracker} from "contracts/bridgehub/ICTMDeploymentTracker.sol"; + +struct SystemContractsArgs { + uint256 l1ChainId; + uint256 eraChainId; + address l1AssetRouter; + address legacySharedBridge; + address l2TokenBeacon; + bytes32 l2TokenProxyBytecodeHash; + address aliasedOwner; + bool contractsDeployedAlready; + address l1CtmDeployer; +} + +contract SharedL2ContractL1DeployerUtils is DeployUtils { + using stdToml for string; + using stdStorage for StdStorage; + + /// @dev We provide a fast form of debugging the L2 contracts using L1 foundry. We also test using zk foundry. + function initSystemContracts(SystemContractsArgs memory _args) internal virtual { + bytes32 baseTokenAssetId = DataEncoding.encodeNTVAssetId(_args.l1ChainId, ETH_TOKEN_ADDRESS); + address wethToken = address(0x1); + // we deploy the code to get the contract code with immutables which we then vm.etch + address messageRoot = address(new MessageRoot(IBridgehub(L2_BRIDGEHUB_ADDR))); + address bridgehub = address(new Bridgehub(_args.l1ChainId, _args.aliasedOwner, 100)); + address assetRouter = address( + new L2AssetRouter( + _args.l1ChainId, + _args.eraChainId, + _args.l1AssetRouter, + _args.legacySharedBridge, + baseTokenAssetId, + _args.aliasedOwner + ) + ); + address ntv = address( + new L2NativeTokenVaultDev( + _args.l1ChainId, + _args.aliasedOwner, + _args.l2TokenProxyBytecodeHash, + _args.legacySharedBridge, + _args.l2TokenBeacon, + _args.contractsDeployedAlready, + wethToken, + baseTokenAssetId + ) + ); + + vm.etch(L2_MESSAGE_ROOT_ADDR, messageRoot.code); + MessageRoot(L2_MESSAGE_ROOT_ADDR).initialize(); + + vm.etch(L2_BRIDGEHUB_ADDR, bridgehub.code); + uint256 prevChainId = block.chainid; + vm.chainId(_args.l1ChainId); + Bridgehub(L2_BRIDGEHUB_ADDR).initialize(_args.aliasedOwner); + vm.chainId(prevChainId); + vm.prank(_args.aliasedOwner); + Bridgehub(L2_BRIDGEHUB_ADDR).setAddresses( + L2_ASSET_ROUTER_ADDR, + ICTMDeploymentTracker(_args.l1CtmDeployer), + IMessageRoot(L2_MESSAGE_ROOT_ADDR) + ); + + vm.etch(L2_ASSET_ROUTER_ADDR, assetRouter.code); + stdstore.target(address(L2_ASSET_ROUTER_ADDR)).sig("l1AssetRouter()").checked_write(_args.l1AssetRouter); + + stdstore + .target(L2_ASSET_ROUTER_ADDR) + .sig("assetHandlerAddress(bytes32)") + .with_key(baseTokenAssetId) + .checked_write(bytes32(uint256(uint160(L2_NATIVE_TOKEN_VAULT_ADDR)))); + + vm.etch(L2_NATIVE_TOKEN_VAULT_ADDR, ntv.code); + + vm.store(L2_NATIVE_TOKEN_VAULT_ADDR, bytes32(uint256(251)), bytes32(uint256(_args.l2TokenProxyBytecodeHash))); + L2NativeTokenVaultDev(L2_NATIVE_TOKEN_VAULT_ADDR).deployBridgedStandardERC20(_args.aliasedOwner); + } + + function deployL2Contracts(uint256 _l1ChainId) public virtual { + string memory root = vm.projectRoot(); + string memory inputPath = string.concat( + root, + "/test/foundry/l1/integration/deploy-scripts/script-config/config-deploy-l1.toml" + ); + initializeConfig(inputPath); + addresses.transparentProxyAdmin = address(0x1); + addresses.bridgehub.bridgehubProxy = L2_BRIDGEHUB_ADDR; + addresses.bridges.sharedBridgeProxy = L2_ASSET_ROUTER_ADDR; + addresses.vaults.l1NativeTokenVaultProxy = L2_NATIVE_TOKEN_VAULT_ADDR; + addresses.blobVersionedHashRetriever = address(0x1); + config.l1ChainId = _l1ChainId; + console.log("Deploying L2 contracts"); + instantiateCreate2Factory(); + deployGenesisUpgrade(); + deployVerifier(); + deployValidatorTimelock(); + deployChainTypeManagerContract(); + } + + // add this to be excluded from coverage report + function test() internal virtual override {} +} diff --git a/l1-contracts/test/foundry/l1/unit/concrete/Bridges/L1SharedBridge/_L1SharedBridge_Shared.t.sol b/l1-contracts/test/foundry/l1/unit/concrete/Bridges/L1SharedBridge/_L1SharedBridge_Shared.t.sol index c67fa6a36..de655e7ee 100644 --- a/l1-contracts/test/foundry/l1/unit/concrete/Bridges/L1SharedBridge/_L1SharedBridge_Shared.t.sol +++ b/l1-contracts/test/foundry/l1/unit/concrete/Bridges/L1SharedBridge/_L1SharedBridge_Shared.t.sol @@ -208,11 +208,6 @@ contract L1AssetRouterTest is Test { abi.encodeWithSelector(IBridgehub.requestL2TransactionDirect.selector), abi.encode(txHash) ); - // vm.mockCall( - // address(bridgehubAddress), - // abi.encodeWithSelector(IBridgehub.baseTokenAssetId.selector, address(token)), - // abi.encode(nativeTokenVault.getAssetId(address(token))) - // ); token.mint(address(nativeTokenVault), amount); diff --git a/l1-contracts/test/foundry/l2/integration/L2ERC20BridgeTest.t.sol b/l1-contracts/test/foundry/l2/integration/L2ERC20BridgeTest.t.sol new file mode 100644 index 000000000..8fe0ffcc8 --- /dev/null +++ b/l1-contracts/test/foundry/l2/integration/L2ERC20BridgeTest.t.sol @@ -0,0 +1,49 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.20; + +// solhint-disable gas-custom-errors + +import {Test} from "forge-std/Test.sol"; +import "forge-std/console.sol"; + +import {BridgedStandardERC20} from "contracts/bridge/BridgedStandardERC20.sol"; +import {L2AssetRouter} from "contracts/bridge/asset-router/L2AssetRouter.sol"; +import {IL2NativeTokenVault} from "contracts/bridge/ntv/IL2NativeTokenVault.sol"; + +import {UpgradeableBeacon} from "@openzeppelin/contracts-v4/proxy/beacon/UpgradeableBeacon.sol"; +import {BeaconProxy} from "@openzeppelin/contracts-v4/proxy/beacon/BeaconProxy.sol"; + +import {L2_ASSET_ROUTER_ADDR, L2_NATIVE_TOKEN_VAULT_ADDR} from "contracts/common/L2ContractAddresses.sol"; + +import {AddressAliasHelper} from "contracts/vendor/AddressAliasHelper.sol"; + +import {DeployUtils} from "deploy-scripts/DeployUtils.s.sol"; +import {SharedL2ContractL1DeployerUtils} from "../../l1/integration/l2-tests-in-l1-context/_SharedL2ContractL1DeployerUtils.sol"; +import {L2Utils, SystemContractsArgs} from "./L2Utils.sol"; +import {SharedL2ContractL2DeployerUtils} from "./_SharedL2ContractL2DeployerUtils.sol"; +import {L2Erc20TestAbstract} from "../../l1/integration/l2-tests-in-l1-context/L2Erc20TestAbstract.t.sol"; +import {SharedL2ContractDeployer} from "../../l1/integration/l2-tests-in-l1-context/_SharedL2ContractDeployer.sol"; + +contract L2Erc20Test is Test, L2Erc20TestAbstract, SharedL2ContractL2DeployerUtils { + function test() internal virtual override(DeployUtils, SharedL2ContractL2DeployerUtils) {} + + function initSystemContracts( + SystemContractsArgs memory _args + ) internal override(SharedL2ContractDeployer, SharedL2ContractL2DeployerUtils) { + super.initSystemContracts(_args); + } + + function deployViaCreate2( + bytes memory creationCode, + bytes memory constructorArgs + ) internal override(DeployUtils, SharedL2ContractL2DeployerUtils) returns (address) { + return super.deployViaCreate2(creationCode, constructorArgs); + } + + function deployL2Contracts( + uint256 _l1ChainId + ) public override(SharedL2ContractL1DeployerUtils, SharedL2ContractDeployer) { + super.deployL2Contracts(_l1ChainId); + } +} diff --git a/l1-contracts/test/foundry/l2/integration/L2GatewayTests.t.sol b/l1-contracts/test/foundry/l2/integration/L2GatewayTests.t.sol new file mode 100644 index 000000000..b1ae7bb5f --- /dev/null +++ b/l1-contracts/test/foundry/l2/integration/L2GatewayTests.t.sol @@ -0,0 +1,60 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.20; + +// solhint-disable gas-custom-errors + +import {Test} from "forge-std/Test.sol"; +import "forge-std/console.sol"; + +import {L2AssetRouter} from "contracts/bridge/asset-router/L2AssetRouter.sol"; +import {IL2NativeTokenVault} from "contracts/bridge/ntv/IL2NativeTokenVault.sol"; + +import {UpgradeableBeacon} from "@openzeppelin/contracts-v4/proxy/beacon/UpgradeableBeacon.sol"; + +import {L2_ASSET_ROUTER_ADDR, L2_NATIVE_TOKEN_VAULT_ADDR, L2_BRIDGEHUB_ADDR} from "contracts/common/L2ContractAddresses.sol"; +import {ETH_TOKEN_ADDRESS, SETTLEMENT_LAYER_RELAY_SENDER} from "contracts/common/Config.sol"; + +import {AddressAliasHelper} from "contracts/vendor/AddressAliasHelper.sol"; +import {BridgehubMintCTMAssetData} from "contracts/bridgehub/IBridgehub.sol"; +import {IAdmin} from "contracts/state-transition/chain-interfaces/IAdmin.sol"; +import {IL2AssetRouter} from "contracts/bridge/asset-router/IL2AssetRouter.sol"; +import {IL1Nullifier} from "contracts/bridge/interfaces/IL1Nullifier.sol"; +import {IL1AssetRouter} from "contracts/bridge/asset-router/IL1AssetRouter.sol"; +import {IBridgehub} from "contracts/bridgehub/IBridgehub.sol"; + +import {L2Utils, SystemContractsArgs} from "./L2Utils.sol"; + +import {SharedL2ContractL2DeployerUtils} from "./_SharedL2ContractL2DeployerUtils.sol"; +import {IChainTypeManager} from "contracts/state-transition/IChainTypeManager.sol"; +import {IZKChain} from "contracts/state-transition/chain-interfaces/IZKChain.sol"; + +import {DeployUtils} from "deploy-scripts/DeployUtils.s.sol"; +import {SharedL2ContractL1DeployerUtils} from "../../l1/integration/l2-tests-in-l1-context/_SharedL2ContractL1DeployerUtils.sol"; +import {L2GatewayTestAbstract} from "../../l1/integration/l2-tests-in-l1-context/L2GatewayTestAbstract.t.sol"; +import {SharedL2ContractDeployer} from "../../l1/integration/l2-tests-in-l1-context/_SharedL2ContractDeployer.sol"; + +contract L2GatewayTests is Test, L2GatewayTestAbstract, SharedL2ContractL2DeployerUtils { + // 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. + function test() internal virtual override(DeployUtils, SharedL2ContractL2DeployerUtils) {} + + function initSystemContracts( + SystemContractsArgs memory _args + ) internal override(SharedL2ContractDeployer, SharedL2ContractL2DeployerUtils) { + super.initSystemContracts(_args); + } + + function deployViaCreate2( + bytes memory creationCode, + bytes memory constructorArgs + ) internal override(DeployUtils, SharedL2ContractL2DeployerUtils) returns (address) { + return super.deployViaCreate2(creationCode, constructorArgs); + } + + function deployL2Contracts( + uint256 _l1ChainId + ) public override(SharedL2ContractL1DeployerUtils, SharedL2ContractDeployer) { + super.deployL2Contracts(_l1ChainId); + } +} diff --git a/l1-contracts/test/foundry/l2/unit/utils/L2Utils.sol b/l1-contracts/test/foundry/l2/integration/L2Utils.sol similarity index 55% rename from l1-contracts/test/foundry/l2/unit/utils/L2Utils.sol rename to l1-contracts/test/foundry/l2/integration/L2Utils.sol index 9da81fe5d..d10105734 100644 --- a/l1-contracts/test/foundry/l2/unit/utils/L2Utils.sol +++ b/l1-contracts/test/foundry/l2/integration/L2Utils.sol @@ -5,15 +5,28 @@ pragma solidity ^0.8.20; import {Vm} from "forge-std/Vm.sol"; import "forge-std/console.sol"; -import {DEPLOYER_SYSTEM_CONTRACT, L2_ASSET_ROUTER_ADDR, L2_NATIVE_TOKEN_VAULT_ADDR} from "contracts/common/L2ContractAddresses.sol"; +import {UpgradeableBeacon} from "@openzeppelin/contracts-v4/proxy/beacon/UpgradeableBeacon.sol"; +import {BeaconProxy} from "@openzeppelin/contracts-v4/proxy/beacon/BeaconProxy.sol"; +import {DEPLOYER_SYSTEM_CONTRACT, L2_ASSET_ROUTER_ADDR, L2_NATIVE_TOKEN_VAULT_ADDR, L2_BRIDGEHUB_ADDR, L2_MESSAGE_ROOT_ADDR} from "contracts/common/L2ContractAddresses.sol"; import {IContractDeployer, L2ContractHelper} from "contracts/common/libraries/L2ContractHelper.sol"; +import {TransparentUpgradeableProxy} from "@openzeppelin/contracts-v4/proxy/transparent/TransparentUpgradeableProxy.sol"; import {L2AssetRouter} from "contracts/bridge/asset-router/L2AssetRouter.sol"; import {L2NativeTokenVault} from "contracts/bridge/ntv/L2NativeTokenVault.sol"; +import {L2SharedBridgeLegacy} from "contracts/bridge/L2SharedBridgeLegacy.sol"; +import {IMessageRoot} from "contracts/bridgehub/IMessageRoot.sol"; +import {ICTMDeploymentTracker} from "contracts/bridgehub/ICTMDeploymentTracker.sol"; +import {Bridgehub, IBridgehub} from "contracts/bridgehub/Bridgehub.sol"; +import {MessageRoot} from "contracts/bridgehub/MessageRoot.sol"; import {ETH_TOKEN_ADDRESS} from "contracts/common/Config.sol"; import {DataEncoding} from "contracts/common/libraries/DataEncoding.sol"; +import {BridgedStandardERC20} from "contracts/bridge/BridgedStandardERC20.sol"; + +import {SystemContractsCaller} from "contracts/common/libraries/SystemContractsCaller.sol"; +import {DeployFailed} from "contracts/common/L1ContractErrors.sol"; +import {SystemContractsArgs} from "../../l1/integration/l2-tests-in-l1-context/_SharedL2ContractDeployer.sol"; library L2Utils { address internal constant VM_ADDRESS = address(uint160(uint256(keccak256("hevm cheat code")))); @@ -23,6 +36,7 @@ library L2Utils { string internal constant L2_ASSET_ROUTER_PATH = "./zkout/L2AssetRouter.sol/L2AssetRouter.json"; string internal constant L2_NATIVE_TOKEN_VAULT_PATH = "./zkout/L2NativeTokenVault.sol/L2NativeTokenVault.json"; + string internal constant BRIDGEHUB_PATH = "./zkout/Bridgehub.sol/Bridgehub.json"; /// @notice Returns the bytecode of a given era contract from a `zkout` folder. function readEraBytecode(string memory _filename) internal returns (bytes memory bytecode) { @@ -54,9 +68,61 @@ library L2Utils { * @dev Initializes the system contracts. * @dev It is a hack needed to make the tests be able to call system contracts directly. */ - function initSystemContracts() internal { + function initSystemContracts(SystemContractsArgs memory _args) internal { bytes memory contractDeployerBytecode = readSystemContractsBytecode("ContractDeployer"); vm.etch(DEPLOYER_SYSTEM_CONTRACT, contractDeployerBytecode); + forceDeploySystemContracts(_args); + } + + function forceDeploySystemContracts(SystemContractsArgs memory _args) internal { + forceDeployMessageRoot(); + forceDeployBridgehub( + _args.l1ChainId, + _args.eraChainId, + _args.aliasedOwner, + _args.l1AssetRouter, + _args.legacySharedBridge, + _args.l1CtmDeployer + ); + forceDeployAssetRouter( + _args.l1ChainId, + _args.eraChainId, + _args.aliasedOwner, + _args.l1AssetRouter, + _args.legacySharedBridge + ); + forceDeployNativeTokenVault( + _args.l1ChainId, + _args.aliasedOwner, + _args.l2TokenProxyBytecodeHash, + _args.legacySharedBridge, + _args.l2TokenBeacon, + _args.contractsDeployedAlready + ); + } + + function forceDeployMessageRoot() internal { + new MessageRoot(IBridgehub(L2_BRIDGEHUB_ADDR)); + forceDeployWithConstructor("MessageRoot", L2_MESSAGE_ROOT_ADDR, abi.encode(L2_BRIDGEHUB_ADDR)); + } + + function forceDeployBridgehub( + uint256 _l1ChainId, + uint256 _eraChainId, + address _aliasedOwner, + address _l1AssetRouter, + address _legacySharedBridge, + address _l1CtmDeployer + ) internal { + new Bridgehub(_l1ChainId, _aliasedOwner, 100); + forceDeployWithConstructor("Bridgehub", L2_BRIDGEHUB_ADDR, abi.encode(_l1ChainId, _aliasedOwner, 100)); + Bridgehub bridgehub = Bridgehub(L2_BRIDGEHUB_ADDR); + vm.prank(_aliasedOwner); + bridgehub.setAddresses( + L2_ASSET_ROUTER_ADDR, + ICTMDeploymentTracker(_l1CtmDeployer), + IMessageRoot(L2_MESSAGE_ROOT_ADDR) + ); } /// @notice Deploys the L2AssetRouter contract. @@ -76,22 +142,11 @@ library L2Utils { { new L2AssetRouter(_l1ChainId, _eraChainId, _l1AssetRouter, _legacySharedBridge, ethAssetId, _aliasedOwner); } - - bytes memory bytecode = readEraBytecode("L2AssetRouter"); - - bytes32 bytecodehash = L2ContractHelper.hashL2Bytecode(bytecode); - - IContractDeployer.ForceDeployment[] memory deployments = new IContractDeployer.ForceDeployment[](1); - deployments[0] = IContractDeployer.ForceDeployment({ - bytecodeHash: bytecodehash, - newAddress: L2_ASSET_ROUTER_ADDR, - callConstructor: true, - value: 0, - input: abi.encode(_l1ChainId, _eraChainId, _l1AssetRouter, _legacySharedBridge, ethAssetId, _aliasedOwner) - }); - - vm.prank(L2_FORCE_DEPLOYER_ADDR); - IContractDeployer(DEPLOYER_SYSTEM_CONTRACT).forceDeployOnAddresses(deployments); + forceDeployWithConstructor( + "L2AssetRouter", + L2_ASSET_ROUTER_ADDR, + abi.encode(_l1ChainId, _eraChainId, _l1AssetRouter, _legacySharedBridge, ethAssetId, _aliasedOwner) + ); } /// @notice Deploys the L2NativeTokenVault contract. @@ -123,19 +178,10 @@ library L2Utils { _baseTokenAssetId: ethAssetId }); } - - bytes memory bytecode = readEraBytecode("L2NativeTokenVault"); - - bytes32 bytecodehash = L2ContractHelper.hashL2Bytecode(bytecode); - - IContractDeployer.ForceDeployment[] memory deployments = new IContractDeployer.ForceDeployment[](1); - deployments[0] = IContractDeployer.ForceDeployment({ - bytecodeHash: bytecodehash, - newAddress: L2_NATIVE_TOKEN_VAULT_ADDR, - callConstructor: true, - value: 0, - // solhint-disable-next-line func-named-parameters - input: abi.encode( + forceDeployWithConstructor( + "L2NativeTokenVault", + L2_NATIVE_TOKEN_VAULT_ADDR, + abi.encode( _l1ChainId, _aliasedOwner, _l2TokenProxyBytecodeHash, @@ -145,25 +191,48 @@ library L2Utils { address(0), ethAssetId ) + ); + } + + function forceDeployWithConstructor( + string memory _contractName, + address _address, + bytes memory _constructorArgs + ) public { + bytes memory bytecode = readEraBytecode(_contractName); + + bytes32 bytecodehash = L2ContractHelper.hashL2Bytecode(bytecode); + + IContractDeployer.ForceDeployment[] memory deployments = new IContractDeployer.ForceDeployment[](1); + deployments[0] = IContractDeployer.ForceDeployment({ + bytecodeHash: bytecodehash, + newAddress: _address, + callConstructor: true, + value: 0, + input: _constructorArgs }); vm.prank(L2_FORCE_DEPLOYER_ADDR); IContractDeployer(DEPLOYER_SYSTEM_CONTRACT).forceDeployOnAddresses(deployments); } - /// @notice Encodes the token data. - /// @param name The name of the token. - /// @param symbol The symbol of the token. - /// @param decimals The decimals of the token. - function encodeTokenData( - string memory name, - string memory symbol, - uint8 decimals - ) internal pure returns (bytes memory) { - bytes memory encodedName = abi.encode(name); - bytes memory encodedSymbol = abi.encode(symbol); - bytes memory encodedDecimals = abi.encode(decimals); - - return abi.encode(encodedName, encodedSymbol, encodedDecimals); + function deployViaCreat2L2( + bytes memory creationCode, + bytes memory constructorargs, + bytes32 create2salt + ) internal returns (address) { + bytes memory bytecode = abi.encodePacked(creationCode, constructorargs); + address contractAddress; + assembly { + contractAddress := create2(0, add(bytecode, 0x20), mload(bytecode), create2salt) + } + uint32 size; + assembly { + size := extcodesize(contractAddress) + } + if (size == 0) { + revert DeployFailed(); + } + return contractAddress; } } diff --git a/l1-contracts/test/foundry/l2/integration/WETH.t.sol b/l1-contracts/test/foundry/l2/integration/WETH.t.sol new file mode 100644 index 000000000..f7932b4eb --- /dev/null +++ b/l1-contracts/test/foundry/l2/integration/WETH.t.sol @@ -0,0 +1,36 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.20; + +import {Test} from "forge-std/Test.sol"; + +import {DeployUtils} from "deploy-scripts/DeployUtils.s.sol"; + +import {SharedL2ContractDeployer} from "../../l1/integration/l2-tests-in-l1-context/_SharedL2ContractDeployer.sol"; +import {SharedL2ContractL1DeployerUtils} from "../../l1/integration/l2-tests-in-l1-context/_SharedL2ContractL1DeployerUtils.sol"; +import {L2WethTestAbstract} from "../../l1/integration/l2-tests-in-l1-context/L2WethTestAbstract.t.sol"; + +import {SharedL2ContractL2DeployerUtils, SystemContractsArgs} from "./_SharedL2ContractL2DeployerUtils.sol"; + +contract WethTest is Test, L2WethTestAbstract, SharedL2ContractL2DeployerUtils { + function test() internal virtual override(DeployUtils, SharedL2ContractL2DeployerUtils) {} + + function initSystemContracts( + SystemContractsArgs memory _args + ) internal override(SharedL2ContractDeployer, SharedL2ContractL2DeployerUtils) { + super.initSystemContracts(_args); + } + + function deployViaCreate2( + bytes memory creationCode, + bytes memory constructorArgs + ) internal override(DeployUtils, SharedL2ContractL2DeployerUtils) returns (address) { + return super.deployViaCreate2(creationCode, constructorArgs); + } + + function deployL2Contracts( + uint256 _l1ChainId + ) public override(SharedL2ContractL1DeployerUtils, SharedL2ContractDeployer) { + super.deployL2Contracts(_l1ChainId); + } +} diff --git a/l1-contracts/test/foundry/l2/integration/_SharedL2ContractL2DeployerUtils.sol b/l1-contracts/test/foundry/l2/integration/_SharedL2ContractL2DeployerUtils.sol new file mode 100644 index 000000000..0b42255b5 --- /dev/null +++ b/l1-contracts/test/foundry/l2/integration/_SharedL2ContractL2DeployerUtils.sol @@ -0,0 +1,41 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.24; + +import {Test} from "forge-std/Test.sol"; +import {StdStorage, stdStorage, stdToml} from "forge-std/Test.sol"; +import {Script, console2 as console} from "forge-std/Script.sol"; + +import {Bridgehub} from "contracts/bridgehub/Bridgehub.sol"; +import {L1AssetRouter} from "contracts/bridge/asset-router/L1AssetRouter.sol"; +import {L1Nullifier} from "contracts/bridge/L1Nullifier.sol"; +import {L1NativeTokenVault} from "contracts/bridge/ntv/L1NativeTokenVault.sol"; +import {DataEncoding} from "contracts/common/libraries/DataEncoding.sol"; +import {CTMDeploymentTracker} from "contracts/bridgehub/CTMDeploymentTracker.sol"; +import {IChainTypeManager} from "contracts/state-transition/IChainTypeManager.sol"; +import {DeployedAddresses, Config} from "deploy-scripts/DeployUtils.s.sol"; + +import {DeployUtils} from "deploy-scripts/DeployUtils.s.sol"; + +import {L2_BRIDGEHUB_ADDR, L2_ASSET_ROUTER_ADDR, L2_NATIVE_TOKEN_VAULT_ADDR} from "contracts/common/L2ContractAddresses.sol"; + +import {L2Utils} from "./L2Utils.sol"; +import {SharedL2ContractL1DeployerUtils, SystemContractsArgs} from "../../l1/integration/l2-tests-in-l1-context/_SharedL2ContractL1DeployerUtils.sol"; + +contract SharedL2ContractL2DeployerUtils is DeployUtils, SharedL2ContractL1DeployerUtils { + using stdToml for string; + + function initSystemContracts(SystemContractsArgs memory _args) internal virtual override { + L2Utils.initSystemContracts(_args); + } + + function deployViaCreate2( + bytes memory creationCode, + bytes memory constructorArgs + ) internal virtual override returns (address) { + console.log("Deploying via create2 L2"); + return L2Utils.deployViaCreat2L2(creationCode, constructorArgs, config.contracts.create2FactorySalt); + } + + // add this to be excluded from coverage report + function test() internal virtual override(DeployUtils, SharedL2ContractL1DeployerUtils) {} +} diff --git a/l2-contracts/test/foundry/unit/data-availability/ValidiumL2DAValidator.t.sol b/l2-contracts/test/foundry/unit/data-availability/ValidiumL2DAValidator.t.sol index c54367295..3374e1acc 100644 --- a/l2-contracts/test/foundry/unit/data-availability/ValidiumL2DAValidator.t.sol +++ b/l2-contracts/test/foundry/unit/data-availability/ValidiumL2DAValidator.t.sol @@ -8,7 +8,7 @@ import {Test} from "forge-std/Test.sol"; import {ValidiumL2DAValidator} from "contracts/data-availability/ValidiumL2DAValidator.sol"; -contract L2Erc20BridgeTest is Test { +contract L2ValidiumDAValidatorTest is Test { function test_callValidiumDAValidator(address depositor, address receiver, uint256 amount) internal { ValidiumL2DAValidator validator = new ValidiumL2DAValidator(); diff --git a/system-contracts/SystemContractsHashes.json b/system-contracts/SystemContractsHashes.json index 3f351ca43..163458f1f 100644 --- a/system-contracts/SystemContractsHashes.json +++ b/system-contracts/SystemContractsHashes.json @@ -87,22 +87,22 @@ "contractName": "L2GatewayUpgrade", "bytecodePath": "artifacts-zk/contracts-preprocessed/L2GatewayUpgrade.sol/L2GatewayUpgrade.json", "sourceCodePath": "contracts-preprocessed/L2GatewayUpgrade.sol", - "bytecodeHash": "0x0100019d6f999ca8393f86be0b24f494bf647cc3ec56e01eed6aa233c6a0e6c0", - "sourceCodeHash": "0x3b85c44fc4fdc422c43d8bf8a2b8229665698996a034bf37c895e963f6c839bf" - }, - { - "contractName": "L2GatewayUpgradeHelper", - "bytecodePath": "artifacts-zk/contracts-preprocessed/L2GatewayUpgradeHelper.sol/L2GatewayUpgradeHelper.json", - "sourceCodePath": "contracts-preprocessed/L2GatewayUpgradeHelper.sol", - "bytecodeHash": "0x01000007a010cd0e6cd1a9fcb802ccc92eb7a1acfb6566864ff6429a2c0ddf0a", - "sourceCodeHash": "0xe7bdc91f70cd5c395435423c25dd9cd27a99e19e12965854106cdc1385bcf14d" + "bytecodeHash": "0x0100019db8d039fb6ef02a7aea5eb8cbfd917afdf3dc88659f28e18b6a43e5df", + "sourceCodeHash": "0xc69d0c1819a366fc91a43a9ad2d747540a7b8ee2071b48e4adfd301ec40e8600" }, { "contractName": "L2GenesisUpgrade", "bytecodePath": "artifacts-zk/contracts-preprocessed/L2GenesisUpgrade.sol/L2GenesisUpgrade.json", "sourceCodePath": "contracts-preprocessed/L2GenesisUpgrade.sol", - "bytecodeHash": "0x010000fb0d40ab01a12ce7bad289cb44ab49563e51e6e1d889d431492a4b320f", - "sourceCodeHash": "0xdd7a89c11b624282aed07ef9d985d2fd006015a0aa0b11aec8e445695f7dd689" + "bytecodeHash": "0x010000fbf45473bbd4c0ef708beb28c5f40c10b22b0a289feb4ccbce9b7ae825", + "sourceCodeHash": "0xe21a58d96b2727020958e95605764b84f7f2f3cc4e0c01c4cdf2854f882d41ca" + }, + { + "contractName": "L2GenesisUpgradeHelper", + "bytecodePath": "artifacts-zk/contracts-preprocessed/L2GenesisUpgradeHelper.sol/L2GenesisUpgradeHelper.json", + "sourceCodePath": "contracts-preprocessed/L2GenesisUpgradeHelper.sol", + "bytecodeHash": "0x01000007e61dc52f7aff33c2c209b51ab7fbaf9b4668980f076fdabb24934f97", + "sourceCodeHash": "0xc2fd787a23e7935f8109f3eb35b71853fe3a9acf5cd96138afcb0e5b65002ba3" }, { "contractName": "MsgValueSimulator", diff --git a/system-contracts/contracts/L2GatewayUpgrade.sol b/system-contracts/contracts/L2GatewayUpgrade.sol index 59d96102d..bef6009ae 100644 --- a/system-contracts/contracts/L2GatewayUpgrade.sol +++ b/system-contracts/contracts/L2GatewayUpgrade.sol @@ -7,7 +7,7 @@ import {IContractDeployer, ForceDeployment} from "./interfaces/IContractDeployer import {SystemContractHelper} from "./libraries/SystemContractHelper.sol"; import {FixedForceDeploymentsData, ZKChainSpecificForceDeploymentsData} from "./interfaces/IL2GenesisUpgrade.sol"; -import {L2GatewayUpgradeHelper} from "./L2GatewayUpgradeHelper.sol"; +import {L2GenesisUpgradeHelper} from "./L2GenesisUpgradeHelper.sol"; import {ITransparentUpgradeableProxy} from "@openzeppelin/contracts-v4/proxy/transparent/TransparentUpgradeableProxy.sol"; import {IL2SharedBridgeLegacy} from "./interfaces/IL2SharedBridgeLegacy.sol"; import {UpgradeableBeacon} from "@openzeppelin/contracts-v4/proxy/beacon/UpgradeableBeacon.sol"; @@ -32,7 +32,7 @@ contract L2GatewayUpgrade { IContractDeployer(DEPLOYER_SYSTEM_CONTRACT).forceDeployOnAddresses{value: msg.value}(_forceDeployments); // Secondly, we perform the more complex deployment of the gateway contracts. - L2GatewayUpgradeHelper.performGatewayContractsInit( + L2GenesisUpgradeHelper.performForceDeployedContractsInit( _ctmDeployer, _fixedForceDeploymentsData, _additionalForceDeploymentsData diff --git a/system-contracts/contracts/L2GenesisUpgrade.sol b/system-contracts/contracts/L2GenesisUpgrade.sol index 4d5592529..585ff14f1 100644 --- a/system-contracts/contracts/L2GenesisUpgrade.sol +++ b/system-contracts/contracts/L2GenesisUpgrade.sol @@ -6,7 +6,7 @@ import {SYSTEM_CONTEXT_CONTRACT} from "./Constants.sol"; import {ISystemContext} from "./interfaces/ISystemContext.sol"; import {IL2GenesisUpgrade} from "./interfaces/IL2GenesisUpgrade.sol"; -import {L2GatewayUpgradeHelper} from "./L2GatewayUpgradeHelper.sol"; +import {L2GenesisUpgradeHelper} from "./L2GenesisUpgradeHelper.sol"; /// @custom:security-contact security@matterlabs.dev /// @author Matter Labs @@ -22,7 +22,7 @@ contract L2GenesisUpgrade is IL2GenesisUpgrade { require(_chainId != 0, "Invalid chainId"); ISystemContext(SYSTEM_CONTEXT_CONTRACT).setChainId(_chainId); - L2GatewayUpgradeHelper.performGatewayContractsInit( + L2GenesisUpgradeHelper.performForceDeployedContractsInit( _ctmDeployer, _fixedForceDeploymentsData, _additionalForceDeploymentsData diff --git a/system-contracts/contracts/L2GatewayUpgradeHelper.sol b/system-contracts/contracts/L2GenesisUpgradeHelper.sol similarity index 98% rename from system-contracts/contracts/L2GatewayUpgradeHelper.sol rename to system-contracts/contracts/L2GenesisUpgradeHelper.sol index e5004bc8a..30752c948 100644 --- a/system-contracts/contracts/L2GatewayUpgradeHelper.sol +++ b/system-contracts/contracts/L2GenesisUpgradeHelper.sol @@ -7,8 +7,8 @@ import {IContractDeployer, ForceDeployment} from "./interfaces/IContractDeployer import {SystemContractHelper} from "./libraries/SystemContractHelper.sol"; import {FixedForceDeploymentsData, ZKChainSpecificForceDeploymentsData} from "./interfaces/IL2GenesisUpgrade.sol"; -library L2GatewayUpgradeHelper { - function performGatewayContractsInit( +library L2GenesisUpgradeHelper { + function performForceDeployedContractsInit( address _ctmDeployer, bytes calldata _fixedForceDeploymentsData, bytes calldata _additionalForceDeploymentsData