diff --git a/.gitignore b/.gitignore index a2a7a4d05..2128686e0 100644 --- a/.gitignore +++ b/.gitignore @@ -33,4 +33,5 @@ l1-contracts/test/foundry/l1/integration/deploy-scripts/script-out/*.toml l1-contracts/test/foundry/l1/integration/deploy-scripts/script-out/* l1-contracts/test/foundry/l1/integration/deploy-scripts/script-config/config-deploy-zk-chain-*.toml l1-contracts/test/foundry/integration/deploy-scripts/script-out/* +l1-contracts/test/foundry/l1/integration/upgrade-envs/script-out/*.toml l1-contracts/zkout/* diff --git a/l1-contracts/contracts/bridge/L1Nullifier.sol b/l1-contracts/contracts/bridge/L1Nullifier.sol index 6dda093c9..a4447e085 100644 --- a/l1-contracts/contracts/bridge/L1Nullifier.sol +++ b/l1-contracts/contracts/bridge/L1Nullifier.sol @@ -100,6 +100,12 @@ contract L1Nullifier is IL1Nullifier, ReentrancyGuard, Ownable2StepUpgradeable, /// NOTE: this function may be removed in the future, don't rely on it! mapping(uint256 chainId => mapping(address l1Token => uint256 balance)) public __DEPRECATED_chainBalance; + /// @dev Admin has the ability to register new chains within the shared bridge. + address public __DEPRECATED_admin; + + /// @dev The pending admin, i.e. the candidate to the admin role. + address public __DEPRECATED_pendingAdmin; + /// @dev Address of L1 asset router. IL1AssetRouter public l1AssetRouter; diff --git a/l1-contracts/contracts/bridge/L2SharedBridgeLegacy.sol b/l1-contracts/contracts/bridge/L2SharedBridgeLegacy.sol index 9ea29cf07..df5bda67c 100644 --- a/l1-contracts/contracts/bridge/L2SharedBridgeLegacy.sol +++ b/l1-contracts/contracts/bridge/L2SharedBridgeLegacy.sol @@ -10,17 +10,20 @@ import {BridgedStandardERC20} from "./BridgedStandardERC20.sol"; import {DEPLOYER_SYSTEM_CONTRACT, L2_ASSET_ROUTER_ADDR, L2_NATIVE_TOKEN_VAULT_ADDR} from "../common/L2ContractAddresses.sol"; import {SystemContractsCaller} from "../common/libraries/SystemContractsCaller.sol"; import {L2ContractHelper, IContractDeployer} from "../common/libraries/L2ContractHelper.sol"; +import {AddressAliasHelper} from "../vendor/AddressAliasHelper.sol"; import {IL2AssetRouter} from "./asset-router/IL2AssetRouter.sol"; import {IL2NativeTokenVault} from "./ntv/IL2NativeTokenVault.sol"; import {IL2SharedBridgeLegacy} from "./interfaces/IL2SharedBridgeLegacy.sol"; -import {ZeroAddress, EmptyBytes32, Unauthorized, AmountMustBeGreaterThanZero, DeployFailed} from "../common/L1ContractErrors.sol"; +import {InvalidCaller, ZeroAddress, EmptyBytes32, Unauthorized, AmountMustBeGreaterThanZero, DeployFailed} from "../common/L1ContractErrors.sol"; /// @author Matter Labs /// @custom:security-contact security@matterlabs.dev /// @notice The "default" bridge implementation for the ERC20 tokens. Note, that it does not /// support any custom token logic, i.e. rebase tokens' functionality is not supported. +/// @dev Note, that this contract should be compatible with its previous version as it will be +/// the primary bridge to be used during migration. contract L2SharedBridgeLegacy is IL2SharedBridgeLegacy, Initializable { /// @dev The address of the L1 shared bridge counterpart. address public override l1SharedBridge; @@ -37,6 +40,7 @@ contract L2SharedBridgeLegacy is IL2SharedBridgeLegacy, Initializable { /// @dev The address of the legacy L1 erc20 bridge counterpart. /// This is non-zero only on Era, and should not be renamed for backward compatibility with the SDKs. + // slither-disable-next-line uninitialized-state address public override l1Bridge; modifier onlyNTV() { @@ -103,6 +107,44 @@ contract L2SharedBridgeLegacy is IL2SharedBridgeLegacy, Initializable { IL2AssetRouter(L2_ASSET_ROUTER_ADDR).withdrawLegacyBridge(_l1Receiver, _l2Token, _amount, msg.sender); } + /// @notice Finalize the deposit and mint funds + /// @param _l1Sender The account address that initiated the deposit on L1 + /// @param _l2Receiver The account address that would receive minted ether + /// @param _l1Token The address of the token that was locked on the L1 + /// @param _amount Total amount of tokens deposited from L1 + /// @param _data The additional data that user can pass with the deposit + function finalizeDeposit( + address _l1Sender, + address _l2Receiver, + address _l1Token, + uint256 _amount, + bytes calldata _data + ) external { + // Only the L1 bridge counterpart can initiate and finalize the deposit. + if ( + AddressAliasHelper.undoL1ToL2Alias(msg.sender) != l1Bridge && + AddressAliasHelper.undoL1ToL2Alias(msg.sender) != l1SharedBridge + ) { + revert InvalidCaller(msg.sender); + } + + IL2AssetRouter(L2_ASSET_ROUTER_ADDR).finalizeDepositLegacyBridge({ + _l1Sender: _l1Sender, + _l2Receiver: _l2Receiver, + _l1Token: _l1Token, + _amount: _amount, + _data: _data + }); + + address l2Token = IL2NativeTokenVault(L2_NATIVE_TOKEN_VAULT_ADDR).l2TokenAddress(_l1Token); + + if (l1TokenAddress[l2Token] == address(0)) { + l1TokenAddress[l2Token] = _l1Token; + } + + emit FinalizeDeposit(_l1Sender, _l2Receiver, l2Token, _amount); + } + /// @return Address of an L2 token counterpart function l2TokenAddress(address _l1Token) public view override returns (address) { address token = IL2NativeTokenVault(L2_NATIVE_TOKEN_VAULT_ADDR).l2TokenAddress(_l1Token); diff --git a/l1-contracts/contracts/bridge/asset-router/IL1AssetRouter.sol b/l1-contracts/contracts/bridge/asset-router/IL1AssetRouter.sol index 0e0a058da..e729df510 100644 --- a/l1-contracts/contracts/bridge/asset-router/IL1AssetRouter.sol +++ b/l1-contracts/contracts/bridge/asset-router/IL1AssetRouter.sol @@ -5,11 +5,13 @@ pragma solidity ^0.8.21; import {IL1Nullifier} from "../interfaces/IL1Nullifier.sol"; import {INativeTokenVault} from "../ntv/INativeTokenVault.sol"; import {IAssetRouterBase} from "./IAssetRouterBase.sol"; +import {L2TransactionRequestTwoBridgesInner} from "../../bridgehub/IBridgehub.sol"; +import {IL1SharedBridgeLegacy} from "../interfaces/IL1SharedBridgeLegacy.sol"; /// @title L1 Bridge contract interface /// @author Matter Labs /// @custom:security-contact security@matterlabs.dev -interface IL1AssetRouter is IAssetRouterBase { +interface IL1AssetRouter is IAssetRouterBase, IL1SharedBridgeLegacy { event BridgehubMintData(bytes bridgeMintData); event BridgehubDepositFinalized( diff --git a/l1-contracts/contracts/bridge/asset-router/IL2AssetRouter.sol b/l1-contracts/contracts/bridge/asset-router/IL2AssetRouter.sol index 7cb2bd1ce..232811708 100644 --- a/l1-contracts/contracts/bridge/asset-router/IL2AssetRouter.sol +++ b/l1-contracts/contracts/bridge/asset-router/IL2AssetRouter.sol @@ -20,6 +20,14 @@ interface IL2AssetRouter is IAssetRouterBase { function withdrawLegacyBridge(address _l1Receiver, address _l2Token, uint256 _amount, address _sender) external; + function finalizeDepositLegacyBridge( + address _l1Sender, + address _l2Receiver, + address _l1Token, + uint256 _amount, + bytes calldata _data + ) external; + /// @dev Used to set the assedAddress for a given assetId. /// @dev Will be used by ZK Gateway function setAssetHandlerAddress(uint256 _originChainId, bytes32 _assetId, address _assetAddress) external; diff --git a/l1-contracts/contracts/bridge/asset-router/L1AssetRouter.sol b/l1-contracts/contracts/bridge/asset-router/L1AssetRouter.sol index 344a9bfd6..27d2c4e8d 100644 --- a/l1-contracts/contracts/bridge/asset-router/L1AssetRouter.sol +++ b/l1-contracts/contracts/bridge/asset-router/L1AssetRouter.sol @@ -491,4 +491,11 @@ contract L1AssetRouter is AssetRouterBase, IL1AssetRouter, ReentrancyGuard { _merkleProof: _merkleProof }); } + + /// @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. + function l2BridgeAddress(uint256 _chainId) external view override returns (address) { + return L1_NULLIFIER.l2BridgeAddress(_chainId); + } } diff --git a/l1-contracts/contracts/bridge/asset-router/L2AssetRouter.sol b/l1-contracts/contracts/bridge/asset-router/L2AssetRouter.sol index 78997201e..395b80d52 100644 --- a/l1-contracts/contracts/bridge/asset-router/L2AssetRouter.sol +++ b/l1-contracts/contracts/bridge/asset-router/L2AssetRouter.sol @@ -288,7 +288,39 @@ contract L2AssetRouter is AssetRouterBase, IL2AssetRouter { uint256 _amount, bytes calldata _data ) external { - // onlyAssetRouterCounterpart(L1_CHAIN_ID) { + // onlyAssetRouterCounterpart(L1_CHAIN_ID) { + _translateLegacyFinalizeDeposit({ + _l1Sender: _l1Sender, + _l2Receiver: _l2Receiver, + _l1Token: _l1Token, + _amount: _amount, + _data: _data + }); + } + + function finalizeDepositLegacyBridge( + address _l1Sender, + address _l2Receiver, + address _l1Token, + uint256 _amount, + bytes calldata _data + ) external onlyLegacyBridge { + _translateLegacyFinalizeDeposit({ + _l1Sender: _l1Sender, + _l2Receiver: _l2Receiver, + _l1Token: _l1Token, + _amount: _amount, + _data: _data + }); + } + + function _translateLegacyFinalizeDeposit( + address _l1Sender, + address _l2Receiver, + address _l1Token, + uint256 _amount, + bytes calldata _data + ) internal { bytes32 assetId = DataEncoding.encodeNTVAssetId(L1_CHAIN_ID, _l1Token); // solhint-disable-next-line func-named-parameters bytes memory data = DataEncoding.encodeBridgeMintData(_l1Sender, _l2Receiver, _l1Token, _amount, _data); diff --git a/l1-contracts/contracts/bridge/interfaces/IL1SharedBridgeLegacy.sol b/l1-contracts/contracts/bridge/interfaces/IL1SharedBridgeLegacy.sol index 627048f75..43fca83a3 100644 --- a/l1-contracts/contracts/bridge/interfaces/IL1SharedBridgeLegacy.sol +++ b/l1-contracts/contracts/bridge/interfaces/IL1SharedBridgeLegacy.sol @@ -7,13 +7,4 @@ pragma solidity 0.8.24; /// @custom:security-contact security@matterlabs.dev interface IL1SharedBridgeLegacy { function l2BridgeAddress(uint256 _chainId) external view returns (address); - - event LegacyDepositInitiated( - uint256 indexed chainId, - bytes32 indexed l2DepositTxHash, - address indexed from, - address to, - address l1Asset, - uint256 amount - ); } diff --git a/l1-contracts/contracts/bridge/interfaces/IL2SharedBridgeLegacy.sol b/l1-contracts/contracts/bridge/interfaces/IL2SharedBridgeLegacy.sol index 00a762447..05d86757e 100644 --- a/l1-contracts/contracts/bridge/interfaces/IL2SharedBridgeLegacy.sol +++ b/l1-contracts/contracts/bridge/interfaces/IL2SharedBridgeLegacy.sol @@ -2,9 +2,20 @@ pragma solidity ^0.8.20; +import {UpgradeableBeacon} from "@openzeppelin/contracts-v4/proxy/beacon/UpgradeableBeacon.sol"; + /// @author Matter Labs /// @custom:security-contact security@matterlabs.dev interface IL2SharedBridgeLegacy { + event FinalizeDeposit( + address indexed l1Sender, + address indexed l2Receiver, + address indexed l2Token, + uint256 amount + ); + + function l2TokenBeacon() external returns (UpgradeableBeacon); + function withdraw(address _l1Receiver, address _l2Token, uint256 _amount) external; function l1TokenAddress(address _l2Token) external view returns (address); diff --git a/l1-contracts/contracts/bridge/ntv/IL1NativeTokenVault.sol b/l1-contracts/contracts/bridge/ntv/IL1NativeTokenVault.sol index 1d16f48fb..a3fcbe917 100644 --- a/l1-contracts/contracts/bridge/ntv/IL1NativeTokenVault.sol +++ b/l1-contracts/contracts/bridge/ntv/IL1NativeTokenVault.sol @@ -4,13 +4,14 @@ pragma solidity 0.8.24; import {IL1Nullifier} from "../interfaces/IL1Nullifier.sol"; import {INativeTokenVault} from "./INativeTokenVault.sol"; +import {IL1AssetDeploymentTracker} from "../interfaces/IL1AssetDeploymentTracker.sol"; /// @title L1 Native token vault contract interface /// @author Matter Labs /// @custom:security-contact security@matterlabs.dev /// @notice The NTV is an Asset Handler for the L1AssetRouter to handle native tokens // is IL1AssetHandler, IL1BaseTokenAssetHandler { -interface IL1NativeTokenVault is INativeTokenVault { +interface IL1NativeTokenVault is INativeTokenVault, IL1AssetDeploymentTracker { /// @notice The L1Nullifier contract function L1_NULLIFIER() external view returns (IL1Nullifier); diff --git a/l1-contracts/contracts/bridge/ntv/L1NativeTokenVault.sol b/l1-contracts/contracts/bridge/ntv/L1NativeTokenVault.sol index f1d14834d..9fe19b2f4 100644 --- a/l1-contracts/contracts/bridge/ntv/L1NativeTokenVault.sol +++ b/l1-contracts/contracts/bridge/ntv/L1NativeTokenVault.sol @@ -20,6 +20,7 @@ import {IL1Nullifier} from "../interfaces/IL1Nullifier.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"; @@ -123,6 +124,17 @@ contract L1NativeTokenVault is IL1NativeTokenVault, IL1AssetHandler, NativeToken L1_NULLIFIER.nullifyChainBalanceByNTV(_targetChainId, _token); } + /// @notice Used to register the Asset Handler asset in L2 AssetRouter. + /// @param _assetHandlerAddressOnCounterpart the address of the asset handler on the counterpart chain. + function bridgeCheckCounterpartAddress( + uint256, + bytes32, + address, + address _assetHandlerAddressOnCounterpart + ) external view override onlyAssetRouter { + require(_assetHandlerAddressOnCounterpart == L2_NATIVE_TOKEN_VAULT_ADDR, "NTV: wrong counterpart"); + } + /*////////////////////////////////////////////////////////////// Start transaction Functions //////////////////////////////////////////////////////////////*/ diff --git a/l1-contracts/contracts/bridge/ntv/NativeTokenVault.sol b/l1-contracts/contracts/bridge/ntv/NativeTokenVault.sol index 02e865d5d..c636f1a91 100644 --- a/l1-contracts/contracts/bridge/ntv/NativeTokenVault.sol +++ b/l1-contracts/contracts/bridge/ntv/NativeTokenVault.sol @@ -252,7 +252,7 @@ abstract contract NativeTokenVault is INativeTokenVault, IAssetHandler, Ownable2 } _handleChainBalanceIncrease(_chainId, _assetId, amount, true); if (_depositAmount != amount) { - revert ValueMismatch(amount, msg.value); + revert ValueMismatch(_depositAmount, amount); } } else { // The Bridgehub also checks this, but we want to be sure diff --git a/l1-contracts/contracts/bridgehub/Bridgehub.sol b/l1-contracts/contracts/bridgehub/Bridgehub.sol index 8af6c35f0..8b0e2bde9 100644 --- a/l1-contracts/contracts/bridgehub/Bridgehub.sol +++ b/l1-contracts/contracts/bridgehub/Bridgehub.sol @@ -162,14 +162,24 @@ contract Bridgehub is IBridgehub, ReentrancyGuard, Ownable2StepUpgradeable, Paus // We will change this with interop. ETH_TOKEN_ASSET_ID = DataEncoding.encodeNTVAssetId(L1_CHAIN_ID, ETH_TOKEN_ADDRESS); _transferOwnership(_owner); - whitelistedSettlementLayers[_l1ChainId] = true; + _initializeInner(); } /// @notice used to initialize the contract /// @notice this contract is also deployed on L2 as a system contract there the owner and the related functions will not be used /// @param _owner the owner of the contract - function initialize(address _owner) external reentrancyGuardInitializer { + function initialize(address _owner) external reentrancyGuardInitializer onlyL1 { _transferOwnership(_owner); + _initializeInner(); + } + + /// @notice Used to initialize the contract on L1 + function initializeV2() external initializer onlyL1 { + _initializeInner(); + } + + /// @notice Initializes the contract + function _initializeInner() internal { assetIdIsRegistered[ETH_TOKEN_ASSET_ID] = true; whitelistedSettlementLayers[L1_CHAIN_ID] = true; } @@ -366,7 +376,6 @@ contract Bridgehub is IBridgehub, ReentrancyGuard, Ownable2StepUpgradeable, Paus address chainAddress = IChainTypeManager(_chainTypeManager).createNewChain({ _chainId: _chainId, _baseTokenAssetId: _baseTokenAssetId, - _assetRouter: assetRouter, _admin: _admin, _initData: _initData, _factoryDeps: _factoryDeps diff --git a/l1-contracts/contracts/bridgehub/IBridgehub.sol b/l1-contracts/contracts/bridgehub/IBridgehub.sol index 9230dd514..f83cefa3f 100644 --- a/l1-contracts/contracts/bridgehub/IBridgehub.sol +++ b/l1-contracts/contracts/bridgehub/IBridgehub.sol @@ -234,4 +234,8 @@ interface IBridgehub is IAssetHandler, IL1AssetHandler { function registerAlreadyDeployedZKChain(uint256 _chainId, address _hyperchain) external; function setLegacyChainAddress(uint256 _chainId) external; + + /// @notice return the ZK chain contract for a chainId + /// @dev It is a legacy method. Do not use! + function getHyperchain(uint256 _chainId) external view returns (address); } diff --git a/l1-contracts/contracts/common/L1ContractErrors.sol b/l1-contracts/contracts/common/L1ContractErrors.sol index 8a3dd6801..d7405040e 100644 --- a/l1-contracts/contracts/common/L1ContractErrors.sol +++ b/l1-contracts/contracts/common/L1ContractErrors.sol @@ -99,8 +99,6 @@ error DepositDoesNotExist(); error DepositExists(); // 0x79cacff1 error DepositFailed(); -// 0xae08e4af -error DepositIncorrectAmount(uint256 expectedAmt, uint256 providedAmt); // 0x0e7ee319 error DiamondAlreadyFrozen(); // 0x682dabb4 @@ -421,6 +419,12 @@ error IncorrectBatchBounds( ); // 0x64107968 error AssetHandlerNotRegistered(bytes32 assetId); +// 0x10f30e75 +error NotBridgehub(address addr); +// 0x2554babc +error InvalidAddress(address expected, address actual); +// 0xfa5cd00f +error NotAllowed(address addr); enum SharedBridgeKey { PostUpgradeFirstBatch, diff --git a/l1-contracts/contracts/dev-contracts/test/DummyBridgehub.sol b/l1-contracts/contracts/dev-contracts/test/DummyBridgehub.sol index 5038f5f66..f178fc0ed 100644 --- a/l1-contracts/contracts/dev-contracts/test/DummyBridgehub.sol +++ b/l1-contracts/contracts/dev-contracts/test/DummyBridgehub.sol @@ -15,6 +15,8 @@ contract DummyBridgehub { address public zkChain; + address public sharedBridge; + // add this to be excluded from coverage report function test() internal virtual {} @@ -41,4 +43,8 @@ contract DummyBridgehub { function getZKChain(uint256) external view returns (address) { return address(0); } + + function setSharedBridge(address addr) external { + sharedBridge = addr; + } } diff --git a/l1-contracts/contracts/governance/IPermanentRestriction.sol b/l1-contracts/contracts/governance/IPermanentRestriction.sol index 548866b9f..5fb015e33 100644 --- a/l1-contracts/contracts/governance/IPermanentRestriction.sol +++ b/l1-contracts/contracts/governance/IPermanentRestriction.sol @@ -14,4 +14,7 @@ interface IPermanentRestriction { /// @notice Emitted when the selector is labeled as validated or not. event SelectorValidationChanged(bytes4 indexed selector, bool isValidated); + + /// @notice Emitted when the L2 admin is whitelisted or not. + event AllowL2Admin(address indexed adminAddress); } diff --git a/l1-contracts/contracts/governance/L2AdminFactory.sol b/l1-contracts/contracts/governance/L2AdminFactory.sol new file mode 100644 index 000000000..d4fe4637c --- /dev/null +++ b/l1-contracts/contracts/governance/L2AdminFactory.sol @@ -0,0 +1,42 @@ +// SPDX-License-Identifier: MIT + +pragma solidity 0.8.24; + +import {ChainAdmin} from "./ChainAdmin.sol"; + +/// @author Matter Labs +/// @custom:security-contact security@matterlabs.dev +/// @dev Contract used to deploy ChainAdmin contracts on L2. +/// @dev It can be used to ensure that certain L2 admins are deployed with +/// predefined restrictions. E.g. it can be used to deploy admins that ensure that +/// a chain is a permanent rollup. +/// @dev This contract is expected to be deployed in zkEVM (L2) environment. +/// @dev The contract is immutable, in case the restrictions need to be changed, +/// a new contract should be deployed. +contract L2AdminFactory { + event AdminDeployed(address admin); + + /// @dev We use storage instead of immutable variables due to the + /// specifics of the zkEVM environment, where storage is actually cheaper. + address[] public requiredRestrictions; + + constructor(address[] memory _requiredRestrictions) { + requiredRestrictions = _requiredRestrictions; + } + + /// @notice Deploys a new L2 admin contract. + /// @return admin The address of the deployed admin contract. + function deployAdmin(address[] calldata _additionalRestrictions, bytes32 _salt) external returns (address admin) { + address[] memory restrictions = new address[](requiredRestrictions.length + _additionalRestrictions.length); + uint256 cachedRequired = requiredRestrictions.length; + for (uint256 i = 0; i < cachedRequired; ++i) { + restrictions[i] = requiredRestrictions[i]; + } + uint256 cachedAdditional = _additionalRestrictions.length; + for (uint256 i = 0; i < cachedAdditional; ++i) { + restrictions[requiredRestrictions.length + i] = _additionalRestrictions[i]; + } + + admin = address(new ChainAdmin{salt: _salt}(restrictions)); + } +} diff --git a/l1-contracts/contracts/governance/L2ProxyAdminDeployer.sol b/l1-contracts/contracts/governance/L2ProxyAdminDeployer.sol new file mode 100644 index 000000000..144f951bf --- /dev/null +++ b/l1-contracts/contracts/governance/L2ProxyAdminDeployer.sol @@ -0,0 +1,22 @@ +// SPDX-License-Identifier: MIT + +pragma solidity 0.8.24; + +// solhint-disable gas-length-in-loops + +import {ProxyAdmin} from "@openzeppelin/contracts-v4/proxy/transparent/ProxyAdmin.sol"; + +/// @author Matter Labs +/// @custom:security-contact security@matterlabs.dev +/// @notice The contract that deterministically deploys a ProxyAdmin, while +/// ensuring that its owner is the aliased governance contract +contract L2ProxyAdminDeployer { + address public immutable PROXY_ADMIN_ADDRESS; + + constructor(address _aliasedGovernance) { + ProxyAdmin admin = new ProxyAdmin{salt: bytes32(0)}(); + admin.transferOwnership(_aliasedGovernance); + + PROXY_ADMIN_ADDRESS = address(admin); + } +} diff --git a/l1-contracts/contracts/governance/PermanentRestriction.sol b/l1-contracts/contracts/governance/PermanentRestriction.sol index d013a4de6..153ce369e 100644 --- a/l1-contracts/contracts/governance/PermanentRestriction.sol +++ b/l1-contracts/contracts/governance/PermanentRestriction.sol @@ -2,19 +2,28 @@ pragma solidity 0.8.24; -import {CallNotAllowed, ChainZeroAddress, NotAHyperchain, NotAnAdmin, RemovingPermanentRestriction, ZeroAddress, UnallowedImplementation} from "../common/L1ContractErrors.sol"; +import {UnsupportedEncodingVersion, CallNotAllowed, ChainZeroAddress, NotAHyperchain, NotAnAdmin, RemovingPermanentRestriction, ZeroAddress, UnallowedImplementation, AlreadyWhitelisted, NotAllowed, NotBridgehub, InvalidSelector, InvalidAddress, NotEnoughGas} from "../common/L1ContractErrors.sol"; -import {Ownable2Step} from "@openzeppelin/contracts-v4/access/Ownable2Step.sol"; +import {L2TransactionRequestTwoBridgesOuter, BridgehubBurnCTMAssetData} from "../bridgehub/IBridgehub.sol"; +import {Ownable2StepUpgradeable} from "@openzeppelin/contracts-upgradeable-v4/access/Ownable2StepUpgradeable.sol"; +import {L2ContractHelper} from "../common/libraries/L2ContractHelper.sol"; +import {NEW_ENCODING_VERSION} from "../bridge/asset-router/IAssetRouterBase.sol"; import {Call} from "./Common.sol"; import {IRestriction} from "./IRestriction.sol"; import {IChainAdmin} from "./IChainAdmin.sol"; import {IBridgehub} from "../bridgehub/IBridgehub.sol"; import {IZKChain} from "../state-transition/chain-interfaces/IZKChain.sol"; +import {IGetters} from "../state-transition/chain-interfaces/IGetters.sol"; import {IAdmin} from "../state-transition/chain-interfaces/IAdmin.sol"; import {IPermanentRestriction} from "./IPermanentRestriction.sol"; +/// @dev We use try-catch to test whether some of the conditions should be checked. +/// To avoid attacks based on the 63/64 gas limitations, we ensure that each such call +/// has at least this amount. +uint256 constant MIN_GAS_FOR_FALLABLE_CALL = 5_000_000; + /// @title PermanentRestriction contract /// @author Matter Labs /// @custom:security-contact security@matterlabs.dev @@ -22,10 +31,16 @@ import {IPermanentRestriction} from "./IPermanentRestriction.sol"; /// properties are preserved forever. /// @dev To be deployed as a transparent upgradable proxy, owned by a trusted decentralized governance. /// @dev Once of the instances of such contract is to ensure that a ZkSyncHyperchain is a rollup forever. -contract PermanentRestriction is IRestriction, IPermanentRestriction, Ownable2Step { +contract PermanentRestriction is IRestriction, IPermanentRestriction, Ownable2StepUpgradeable { /// @notice The address of the Bridgehub contract. IBridgehub public immutable BRIDGE_HUB; + /// @notice The address of the L2 admin factory that should be used to deploy the chain admins + /// for chains that migrated on top of an L2 settlement layer. + /// @dev If this contract is deployed on L2, this address is 0. + /// @dev This address is expected to be the same on all L2 chains. + address public immutable L2_ADMIN_FACTORY; + /// @notice The mapping of the allowed admin implementations. mapping(bytes32 implementationCodeHash => bool isAllowed) public allowedAdminImplementations; @@ -35,9 +50,15 @@ contract PermanentRestriction is IRestriction, IPermanentRestriction, Ownable2St /// @notice The mapping of the validated selectors. mapping(bytes4 selector => bool isValidated) public validatedSelectors; - constructor(address _initialOwner, IBridgehub _bridgehub) { + /// @notice The mapping of whitelisted L2 admins. + mapping(address adminAddress => bool isWhitelisted) public allowedL2Admins; + + constructor(IBridgehub _bridgehub, address _l2AdminFactory) { BRIDGE_HUB = _bridgehub; + L2_ADMIN_FACTORY = _l2AdminFactory; + } + function initialize(address _initialOwner) external initializer { // solhint-disable-next-line gas-custom-errors, reason-string if (_initialOwner == address(0)) { revert ZeroAddress(); @@ -72,15 +93,53 @@ contract PermanentRestriction is IRestriction, IPermanentRestriction, Ownable2St emit SelectorValidationChanged(_selector, _isValidated); } + /// @notice Whitelists a certain L2 admin. + /// @param deploymentSalt The salt for the deployment. + /// @param l2BytecodeHash The hash of the L2 bytecode. + /// @param constructorInputHash The hash of the constructor data for the deployment. + function allowL2Admin(bytes32 deploymentSalt, bytes32 l2BytecodeHash, bytes32 constructorInputHash) external { + // We do not do any additional validations for constructor data or the bytecode, + // we expect that only admins of the allowed format are to be deployed. + address expectedAddress = L2ContractHelper.computeCreate2Address( + L2_ADMIN_FACTORY, + deploymentSalt, + l2BytecodeHash, + constructorInputHash + ); + + if (allowedL2Admins[expectedAddress]) { + revert AlreadyWhitelisted(expectedAddress); + } + + allowedL2Admins[expectedAddress] = true; + emit AllowL2Admin(expectedAddress); + } + /// @inheritdoc IRestriction function validateCall( Call calldata _call, address // _invoker ) external view override { _validateAsChainAdmin(_call); + _validateMigrationToL2(_call); _validateRemoveRestriction(_call); } + /// @notice Validates the migration to an L2 settlement layer. + /// @param _call The call data. + /// @dev Note that we do not need to validate the migration to the L1 layer as the admin + /// is not changed in this case. + function _validateMigrationToL2(Call calldata _call) internal view { + _ensureEnoughGas(); + try this.tryGetNewAdminFromMigration(_call) returns (address admin) { + if (!allowedL2Admins[admin]) { + revert NotAllowed(admin); + } + } catch { + // It was not the migration call, so we do nothing + } + } + /// @notice Validates the call as the chain admin /// @param _call The call data. function _validateAsChainAdmin(Call calldata _call) internal view { @@ -153,6 +212,7 @@ contract PermanentRestriction is IRestriction, IPermanentRestriction, Ownable2St /// @notice Checks if the `msg.sender` is an admin of a certain ZkSyncHyperchain. /// @param _chain The address of the chain. function _isAdminOfAChain(address _chain) internal view returns (bool) { + _ensureEnoughGas(); (bool success, ) = address(this).staticcall(abi.encodeCall(this.tryCompareAdminOfAChain, (_chain, msg.sender))); return success; } @@ -172,8 +232,20 @@ contract PermanentRestriction is IRestriction, IPermanentRestriction, Ownable2St // - Query it for `chainId`. If it reverts, it is not a ZkSyncHyperchain. // - Query the Bridgehub for the Hyperchain with the given `chainId`. // - We compare the corresponding addresses - uint256 chainId = IZKChain(_chain).getChainId(); - if (BRIDGE_HUB.getZKChain(chainId) != _chain) { + + // Note, that we do not use an explicit call here to ensure that the function does not panic in case of + // incorrect `_chain` address. + (bool success, bytes memory data) = _chain.staticcall(abi.encodeWithSelector(IGetters.getChainId.selector)); + if (!success || data.length < 32) { + revert NotAHyperchain(_chain); + } + + // Can not fail + uint256 chainId = abi.decode(data, (uint256)); + + // Note, that here it is important to use the legacy `getHyperchain` function, so that the contract + // is compatible with the legacy ones. + if (BRIDGE_HUB.getHyperchain(chainId) != _chain) { revert NotAHyperchain(_chain); } @@ -183,4 +255,57 @@ contract PermanentRestriction is IRestriction, IPermanentRestriction, Ownable2St revert NotAnAdmin(admin, _potentialAdmin); } } + + /// @notice Tries to get the new admin from the migration. + /// @param _call The call data. + /// @dev This function reverts if the provided call was not a migration call. + function tryGetNewAdminFromMigration(Call calldata _call) external view returns (address) { + if (_call.target != address(BRIDGE_HUB)) { + revert NotBridgehub(_call.target); + } + + if (bytes4(_call.data[:4]) != IBridgehub.requestL2TransactionTwoBridges.selector) { + revert InvalidSelector(bytes4(_call.data[:4])); + } + + address sharedBridge = BRIDGE_HUB.sharedBridge(); + + L2TransactionRequestTwoBridgesOuter memory request = abi.decode( + _call.data[4:], + (L2TransactionRequestTwoBridgesOuter) + ); + + if (request.secondBridgeAddress != sharedBridge) { + revert InvalidAddress(sharedBridge, request.secondBridgeAddress); + } + + bytes memory secondBridgeData = request.secondBridgeCalldata; + if (secondBridgeData[0] != NEW_ENCODING_VERSION) { + revert UnsupportedEncodingVersion(); + } + bytes memory encodedData = new bytes(secondBridgeData.length - 1); + assembly { + mcopy(add(encodedData, 0x20), add(secondBridgeData, 0x21), mload(encodedData)) + } + + (bytes32 chainAssetId, bytes memory bridgehubData) = abi.decode(encodedData, (bytes32, bytes)); + // We will just check that the chainAssetId is a valid chainAssetId. + // For now, for simplicity, we do not check that the admin is exactly the admin + // of this chain. + address ctmAddress = BRIDGE_HUB.ctmAssetIdToAddress(chainAssetId); + if (ctmAddress == address(0)) { + revert ZeroAddress(); + } + + BridgehubBurnCTMAssetData memory burnData = abi.decode(bridgehubData, (BridgehubBurnCTMAssetData)); + (address l2Admin, ) = abi.decode(burnData.ctmData, (address, bytes)); + + return l2Admin; + } + + function _ensureEnoughGas() internal view { + if (gasleft() < MIN_GAS_FOR_FALLABLE_CALL) { + revert NotEnoughGas(); + } + } } diff --git a/l1-contracts/contracts/governance/TransitionaryOwner.sol b/l1-contracts/contracts/governance/TransitionaryOwner.sol new file mode 100644 index 000000000..9248204bf --- /dev/null +++ b/l1-contracts/contracts/governance/TransitionaryOwner.sol @@ -0,0 +1,25 @@ +// SPDX-License-Identifier: MIT + +pragma solidity 0.8.24; + +// solhint-disable gas-length-in-loops + +import {Ownable2Step} from "@openzeppelin/contracts-v4/access/Ownable2Step.sol"; + +/// @author Matter Labs +/// @custom:security-contact security@matterlabs.dev +/// @notice The contract that is used a temporary owner for Ownable2Step contracts until the +/// governance can accept the ownership +contract TransitionaryOwner { + address public immutable GOVERNANCE_ADDRESS; + + constructor(address _governanceAddress) { + GOVERNANCE_ADDRESS = _governanceAddress; + } + + /// @notice Claims that ownership of a contract and transfers it to the governance + function claimOwnershipAndGiveToGovernance(address target) external { + Ownable2Step(target).acceptOwnership(); + Ownable2Step(target).transferOwnership(GOVERNANCE_ADDRESS); + } +} diff --git a/l1-contracts/contracts/state-transition/ChainTypeManager.sol b/l1-contracts/contracts/state-transition/ChainTypeManager.sol index 62df92419..2760e36c3 100644 --- a/l1-contracts/contracts/state-transition/ChainTypeManager.sol +++ b/l1-contracts/contracts/state-transition/ChainTypeManager.sol @@ -344,13 +344,11 @@ contract ChainTypeManager is IChainTypeManager, ReentrancyGuard, Ownable2StepUpg /// @notice deploys a full set of chains contracts /// @param _chainId the chain's id /// @param _baseTokenAssetId the base token asset id used to pay for gas fees - /// @param _sharedBridge the shared bridge address, used as base token bridge /// @param _admin the chain's admin address /// @param _diamondCut the diamond cut data that initializes the chains Diamond Proxy function _deployNewChain( uint256 _chainId, bytes32 _baseTokenAssetId, - address _sharedBridge, address _admin, bytes memory _diamondCut ) internal returns (address zkChainAddress) { @@ -383,7 +381,6 @@ contract ChainTypeManager is IChainTypeManager, ReentrancyGuard, Ownable2StepUpg bytes32(uint256(uint160(_admin))), bytes32(uint256(uint160(validatorTimelock))), _baseTokenAssetId, - bytes32(uint256(uint160(_sharedBridge))), storedBatchZero, diamondCut.initCalldata ); @@ -400,7 +397,6 @@ contract ChainTypeManager is IChainTypeManager, ReentrancyGuard, Ownable2StepUpg /// @notice called by Bridgehub when a chain registers /// @param _chainId the chain's id /// @param _baseTokenAssetId the base token asset id used to pay for gas fees - /// @param _assetRouter the shared bridge address, used as base token bridge /// @param _admin the chain's admin address /// @param _initData the diamond cut data, force deployments and factoryDeps encoded /// @param _factoryDeps the factory dependencies used for the genesis upgrade @@ -408,7 +404,6 @@ contract ChainTypeManager is IChainTypeManager, ReentrancyGuard, Ownable2StepUpg function createNewChain( uint256 _chainId, bytes32 _baseTokenAssetId, - address _assetRouter, address _admin, bytes calldata _initData, bytes[] calldata _factoryDeps @@ -416,7 +411,7 @@ contract ChainTypeManager is IChainTypeManager, ReentrancyGuard, Ownable2StepUpg (bytes memory _diamondCut, bytes memory _forceDeploymentData) = abi.decode(_initData, (bytes, bytes)); // solhint-disable-next-line func-named-parameters - zkChainAddress = _deployNewChain(_chainId, _baseTokenAssetId, _assetRouter, _admin, _diamondCut); + zkChainAddress = _deployNewChain(_chainId, _baseTokenAssetId, _admin, _diamondCut); { // check input @@ -492,7 +487,6 @@ contract ChainTypeManager is IChainTypeManager, ReentrancyGuard, Ownable2StepUpg chainAddress = _deployNewChain({ _chainId: _chainId, _baseTokenAssetId: _baseTokenAssetId, - _sharedBridge: address(IBridgehub(BRIDGE_HUB).sharedBridge()), _admin: _admin, _diamondCut: _diamondCut }); diff --git a/l1-contracts/contracts/state-transition/IChainTypeManager.sol b/l1-contracts/contracts/state-transition/IChainTypeManager.sol index b5202e975..90b500b28 100644 --- a/l1-contracts/contracts/state-transition/IChainTypeManager.sol +++ b/l1-contracts/contracts/state-transition/IChainTypeManager.sol @@ -115,7 +115,6 @@ interface IChainTypeManager { function createNewChain( uint256 _chainId, bytes32 _baseTokenAssetId, - address _assetRouter, address _admin, bytes calldata _initData, bytes[] calldata _factoryDeps diff --git a/l1-contracts/contracts/state-transition/chain-deps/DiamondInit.sol b/l1-contracts/contracts/state-transition/chain-deps/DiamondInit.sol index 3be7dc2b1..69a646308 100644 --- a/l1-contracts/contracts/state-transition/chain-deps/DiamondInit.sol +++ b/l1-contracts/contracts/state-transition/chain-deps/DiamondInit.sol @@ -45,9 +45,6 @@ contract DiamondInit is ZKChainBase, IDiamondInit { if (_initializeData.baseTokenAssetId == bytes32(0)) { revert ZeroAddress(); } - if (_initializeData.baseTokenBridge == address(0)) { - revert ZeroAddress(); - } if (_initializeData.blobVersionedHashRetriever == address(0)) { revert ZeroAddress(); } @@ -56,7 +53,6 @@ contract DiamondInit is ZKChainBase, IDiamondInit { s.bridgehub = _initializeData.bridgehub; s.chainTypeManager = _initializeData.chainTypeManager; s.baseTokenAssetId = _initializeData.baseTokenAssetId; - s.baseTokenBridge = _initializeData.baseTokenBridge; s.protocolVersion = _initializeData.protocolVersion; s.verifier = _initializeData.verifier; diff --git a/l1-contracts/contracts/state-transition/chain-deps/ZKChainStorage.sol b/l1-contracts/contracts/state-transition/chain-deps/ZKChainStorage.sol index 3205a229e..5f19aecd4 100644 --- a/l1-contracts/contracts/state-transition/chain-deps/ZKChainStorage.sol +++ b/l1-contracts/contracts/state-transition/chain-deps/ZKChainStorage.sol @@ -149,7 +149,7 @@ struct ZKChainStorage { /// @dev The address of the baseToken contract. Eth is address(1) address __DEPRECATED_baseToken; /// @dev The address of the baseTokenbridge. Eth also uses the shared bridge - address baseTokenBridge; + address __DEPRECATED_baseTokenBridge; /// @notice gasPriceMultiplier for each baseToken, so that each L1->L2 transaction pays for its transaction on the destination /// we multiply by the nominator, and divide by the denominator uint128 baseTokenGasPriceMultiplierNominator; diff --git a/l1-contracts/contracts/state-transition/chain-deps/facets/Getters.sol b/l1-contracts/contracts/state-transition/chain-deps/facets/Getters.sol index 1ffdb5b0c..b5eda6d19 100644 --- a/l1-contracts/contracts/state-transition/chain-deps/facets/Getters.sol +++ b/l1-contracts/contracts/state-transition/chain-deps/facets/Getters.sol @@ -75,11 +75,6 @@ contract GettersFacet is ZKChainBase, IGetters, ILegacyGetters { return s.baseTokenAssetId; } - /// @inheritdoc IGetters - function getBaseTokenBridge() external view returns (address) { - return s.baseTokenBridge; - } - /// @inheritdoc IGetters function baseTokenGasPriceMultiplierNominator() external view returns (uint128) { return s.baseTokenGasPriceMultiplierNominator; diff --git a/l1-contracts/contracts/state-transition/chain-deps/facets/Mailbox.sol b/l1-contracts/contracts/state-transition/chain-deps/facets/Mailbox.sol index c9572c0e3..d4244f7aa 100644 --- a/l1-contracts/contracts/state-transition/chain-deps/facets/Mailbox.sol +++ b/l1-contracts/contracts/state-transition/chain-deps/facets/Mailbox.sol @@ -498,7 +498,8 @@ contract MailboxFacet is ZKChainBase, IMailbox { if (s.chainId != ERA_CHAIN_ID) { revert OnlyEraSupported(); } - IL1AssetRouter(s.baseTokenBridge).finalizeWithdrawal({ + address sharedBridge = IBridgehub(s.bridgehub).sharedBridge(); + IL1AssetRouter(sharedBridge).finalizeWithdrawal({ _chainId: ERA_CHAIN_ID, _l2BatchNumber: _l2BatchNumber, _l2MessageIndex: _l2MessageIndex, @@ -534,7 +535,8 @@ contract MailboxFacet is ZKChainBase, IMailbox { refundRecipient: _refundRecipient }) ); - IL1AssetRouter(s.baseTokenBridge).bridgehubDepositBaseToken{value: msg.value}( + address sharedBridge = IBridgehub(s.bridgehub).sharedBridge(); + IL1AssetRouter(sharedBridge).bridgehubDepositBaseToken{value: msg.value}( s.chainId, s.baseTokenAssetId, msg.sender, diff --git a/l1-contracts/contracts/state-transition/chain-deps/facets/ZKChainBase.sol b/l1-contracts/contracts/state-transition/chain-deps/facets/ZKChainBase.sol index 45c360197..6c8a08657 100644 --- a/l1-contracts/contracts/state-transition/chain-deps/facets/ZKChainBase.sol +++ b/l1-contracts/contracts/state-transition/chain-deps/facets/ZKChainBase.sol @@ -62,13 +62,6 @@ contract ZKChainBase is ReentrancyGuard { _; } - modifier onlyBaseTokenBridge() { - if (msg.sender != s.baseTokenBridge) { - revert Unauthorized(msg.sender); - } - _; - } - function _getTotalPriorityTxs() internal view returns (uint256) { if (s.priorityQueue.getFirstUnprocessedPriorityTx() >= s.priorityTree.startIndex) { return s.priorityTree.getTotalPriorityTxs(); diff --git a/l1-contracts/contracts/state-transition/chain-interfaces/IDiamondInit.sol b/l1-contracts/contracts/state-transition/chain-interfaces/IDiamondInit.sol index c5f2bbc90..e175ac91f 100644 --- a/l1-contracts/contracts/state-transition/chain-interfaces/IDiamondInit.sol +++ b/l1-contracts/contracts/state-transition/chain-interfaces/IDiamondInit.sol @@ -12,7 +12,6 @@ import {FeeParams} from "../chain-deps/ZKChainStorage.sol"; /// @param validatorTimelock address of the validator timelock that delays execution /// @param admin address who can manage the contract /// @param baseTokenAssetId asset id of the base token of the chain -/// @param baseTokenBridge address of the L1 shared bridge contract /// @param storedBatchZero hash of the initial genesis batch /// @param verifier address of Verifier contract /// @param verifierParams Verifier config parameters that describes the circuit to be verified @@ -30,7 +29,6 @@ struct InitializeData { address admin; address validatorTimelock; bytes32 baseTokenAssetId; - address baseTokenBridge; bytes32 storedBatchZero; IVerifier verifier; VerifierParams verifierParams; diff --git a/l1-contracts/contracts/state-transition/chain-interfaces/IGetters.sol b/l1-contracts/contracts/state-transition/chain-interfaces/IGetters.sol index 5dfd600ca..d2ee2b3d0 100644 --- a/l1-contracts/contracts/state-transition/chain-interfaces/IGetters.sol +++ b/l1-contracts/contracts/state-transition/chain-interfaces/IGetters.sol @@ -41,9 +41,6 @@ interface IGetters is IZKChainBase { /// @return The address of the base token function getBaseTokenAssetId() external view returns (bytes32); - /// @return The address of the base token bridge - function getBaseTokenBridge() external view returns (address); - /// @return The total number of batches that were committed function getTotalBatchesCommitted() external view returns (uint256); diff --git a/l1-contracts/contracts/state-transition/l2-deps/IL2GatewayUpgrade.sol b/l1-contracts/contracts/state-transition/l2-deps/IL2GatewayUpgrade.sol new file mode 100644 index 000000000..fdafe2807 --- /dev/null +++ b/l1-contracts/contracts/state-transition/l2-deps/IL2GatewayUpgrade.sol @@ -0,0 +1,13 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.24; + +import {IL2ContractDeployer} from "../../common/interfaces/IL2ContractDeployer.sol"; + +interface IL2GatewayUpgrade { + function upgrade( + IL2ContractDeployer.ForceDeployment[] calldata _forceDeployments, + address _ctmDeployer, + bytes calldata _fixedForceDeploymentsData, + bytes calldata _additionalForceDeploymentsData + ) external payable; +} diff --git a/l1-contracts/contracts/upgrades/GatewayHelper.sol b/l1-contracts/contracts/upgrades/GatewayHelper.sol new file mode 100644 index 000000000..5ae02b9a0 --- /dev/null +++ b/l1-contracts/contracts/upgrades/GatewayHelper.sol @@ -0,0 +1,24 @@ +// SPDX-License-Identifier: MIT + +pragma solidity 0.8.24; + +import {IL1SharedBridgeLegacy} from "../bridge/interfaces/IL1SharedBridgeLegacy.sol"; +import {IBridgehub} from "../bridgehub/IBridgehub.sol"; + +import {ZKChainSpecificForceDeploymentsData} from "../state-transition/l2-deps/IL2GenesisUpgrade.sol"; + +import {ZKChainStorage} from "../state-transition/chain-deps/ZKChainStorage.sol"; + +library GatewayHelper { + function getZKChainSpecificForceDeploymentsData(ZKChainStorage storage s) internal view returns (bytes memory) { + address sharedBridge = IBridgehub(s.bridgehub).sharedBridge(); + address legacySharedBridge = IL1SharedBridgeLegacy(sharedBridge).l2BridgeAddress(s.chainId); + ZKChainSpecificForceDeploymentsData + memory additionalForceDeploymentsData = ZKChainSpecificForceDeploymentsData({ + baseTokenAssetId: s.baseTokenAssetId, + l2LegacySharedBridge: legacySharedBridge, + l2Weth: address(0) // kl todo + }); + return abi.encode(additionalForceDeploymentsData); + } +} diff --git a/l1-contracts/contracts/upgrades/GatewayUpgrade.sol b/l1-contracts/contracts/upgrades/GatewayUpgrade.sol index 08d05989e..3420d81ae 100644 --- a/l1-contracts/contracts/upgrades/GatewayUpgrade.sol +++ b/l1-contracts/contracts/upgrades/GatewayUpgrade.sol @@ -2,8 +2,6 @@ pragma solidity 0.8.24; -import {Initializable} from "@openzeppelin/contracts-upgradeable-v4/proxy/utils/Initializable.sol"; - import {BaseZkSyncUpgrade, ProposedUpgrade} from "./BaseZkSyncUpgrade.sol"; import {DataEncoding} from "../common/libraries/DataEncoding.sol"; @@ -13,14 +11,27 @@ import {PriorityQueue} from "../state-transition/libraries/PriorityQueue.sol"; import {PriorityTree} from "../state-transition/libraries/PriorityTree.sol"; import {IGatewayUpgrade} from "./IGatewayUpgrade.sol"; -import {IL1SharedBridgeLegacy} from "../bridge/interfaces/IL1SharedBridgeLegacy.sol"; +import {IComplexUpgrader} from "../state-transition/l2-deps/IComplexUpgrader.sol"; +import {IL2GatewayUpgrade} from "../state-transition/l2-deps/IL2GatewayUpgrade.sol"; + +import {IL2ContractDeployer} from "../common/interfaces/IL2ContractDeployer.sol"; -import {IBridgehub} from "../bridgehub/IBridgehub.sol"; +import {GatewayHelper} from "./GatewayHelper.sol"; + +// solhint-disable-next-line gas-struct-packing +struct GatewayUpgradeEncodedInput { + IL2ContractDeployer.ForceDeployment[] baseForceDeployments; + bytes fixedForceDeploymentsData; + address ctmDeployer; + address l2GatewayUpgrade; + address oldValidatorTimelock; + address newValidatorTimelock; +} /// @author Matter Labs /// @custom:security-contact security@matterlabs.dev /// @notice This upgrade will be used to migrate Era to be part of the ZK chain ecosystem contracts. -contract GatewayUpgrade is BaseZkSyncUpgrade, Initializable { +contract GatewayUpgrade is BaseZkSyncUpgrade { using PriorityQueue for PriorityQueue.Queue; using PriorityTree for PriorityTree.Tree; @@ -33,23 +44,34 @@ contract GatewayUpgrade is BaseZkSyncUpgrade, Initializable { /// @notice The main function that will be called by the upgrade proxy. /// @param _proposedUpgrade The upgrade to be executed. function upgrade(ProposedUpgrade calldata _proposedUpgrade) public override returns (bytes32) { - (bytes memory l2TxDataStart, bytes memory l2TxDataFinish) = abi.decode( + GatewayUpgradeEncodedInput memory encodedInput = abi.decode( _proposedUpgrade.postUpgradeCalldata, - (bytes, bytes) + (GatewayUpgradeEncodedInput) ); - s.baseTokenAssetId = DataEncoding.encodeNTVAssetId(block.chainid, s.__DEPRECATED_baseToken); + bytes32 baseTokenAssetId = DataEncoding.encodeNTVAssetId(block.chainid, s.__DEPRECATED_baseToken); + + s.baseTokenAssetId = baseTokenAssetId; s.priorityTree.setup(s.priorityQueue.getTotalPriorityTxs()); - IBridgehub bridgehub = IBridgehub(s.bridgehub); - s.baseTokenBridge = bridgehub.sharedBridge(); // we change the assetRouter - bridgehub.setLegacyBaseTokenAssetId(s.chainId); + s.validators[encodedInput.oldValidatorTimelock] = false; + s.validators[encodedInput.newValidatorTimelock] = true; ProposedUpgrade memory proposedUpgrade = _proposedUpgrade; - address l2LegacyBridge = IL1SharedBridgeLegacy(s.baseTokenBridge).l2BridgeAddress(s.chainId); - proposedUpgrade.l2ProtocolUpgradeTx.data = bytes.concat( - l2TxDataStart, - bytes32(uint256(uint160(l2LegacyBridge))), - l2TxDataFinish + + bytes memory gatewayUpgradeCalldata = abi.encodeCall( + IL2GatewayUpgrade.upgrade, + ( + encodedInput.baseForceDeployments, + encodedInput.ctmDeployer, + encodedInput.fixedForceDeploymentsData, + GatewayHelper.getZKChainSpecificForceDeploymentsData(s) + ) + ); + + proposedUpgrade.l2ProtocolUpgradeTx.data = abi.encodeCall( + IComplexUpgrader.upgrade, + (encodedInput.l2GatewayUpgrade, gatewayUpgradeCalldata) ); + // slither-disable-next-line controlled-delegatecall (bool success, ) = THIS_ADDRESS.delegatecall( abi.encodeWithSelector(IGatewayUpgrade.upgradeExternal.selector, proposedUpgrade) @@ -61,8 +83,6 @@ contract GatewayUpgrade is BaseZkSyncUpgrade, Initializable { /// @notice The function that will be called from this same contract, we need an external call to be able to modify _proposedUpgrade (memory/calldata). function upgradeExternal(ProposedUpgrade calldata _proposedUpgrade) external { - // solhint-disable-next-line gas-custom-errors - require(msg.sender == address(this), "GatewayUpgrade: upgradeExternal"); super.upgrade(_proposedUpgrade); } } diff --git a/l1-contracts/contracts/upgrades/L1GenesisUpgrade.sol b/l1-contracts/contracts/upgrades/L1GenesisUpgrade.sol index d6cb769c0..a6475f59a 100644 --- a/l1-contracts/contracts/upgrades/L1GenesisUpgrade.sol +++ b/l1-contracts/contracts/upgrades/L1GenesisUpgrade.sol @@ -8,15 +8,18 @@ import {Diamond} from "../state-transition/libraries/Diamond.sol"; import {BaseZkSyncUpgradeGenesis} from "./BaseZkSyncUpgradeGenesis.sol"; import {ProposedUpgrade} from "./IDefaultUpgrade.sol"; import {L2CanonicalTransaction} from "../common/Messaging.sol"; -import {IL2GenesisUpgrade, ZKChainSpecificForceDeploymentsData} from "../state-transition/l2-deps/IL2GenesisUpgrade.sol"; +import {IL2GenesisUpgrade} from "../state-transition/l2-deps/IL2GenesisUpgrade.sol"; import {IL1GenesisUpgrade} from "./IL1GenesisUpgrade.sol"; -import {IL1Nullifier} from "../bridge/interfaces/IL1Nullifier.sol"; -import {IL1AssetRouter} from "../bridge/asset-router/IL1AssetRouter.sol"; import {IComplexUpgrader} from "../state-transition/l2-deps/IComplexUpgrader.sol"; import {L2_FORCE_DEPLOYER_ADDR, L2_COMPLEX_UPGRADER_ADDR, L2_GENESIS_UPGRADE_ADDR} from "../common/L2ContractAddresses.sol"; //, COMPLEX_UPGRADER_ADDR, GENESIS_UPGRADE_ADDR import {REQUIRED_L2_GAS_PRICE_PER_PUBDATA, SYSTEM_UPGRADE_L2_TX_TYPE, PRIORITY_TX_MAX_GAS_LIMIT} from "../common/Config.sol"; import {SemVer} from "../common/libraries/SemVer.sol"; +import {IL1SharedBridgeLegacy} from "../bridge/interfaces/IL1SharedBridgeLegacy.sol"; +import {IBridgehub} from "../bridgehub/IBridgehub.sol"; + +import {ZKChainSpecificForceDeploymentsData} from "../state-transition/l2-deps/IL2GenesisUpgrade.sol"; + import {VerifierParams} from "../state-transition/chain-interfaces/IVerifier.sol"; import {L2ContractHelper} from "../common/libraries/L2ContractHelper.sol"; @@ -106,8 +109,8 @@ contract L1GenesisUpgrade is IL1GenesisUpgrade, BaseZkSyncUpgradeGenesis { } function _getZKChainSpecificForceDeploymentsData() internal view returns (bytes memory) { - IL1Nullifier l1Nullifier = IL1AssetRouter(s.baseTokenBridge).L1_NULLIFIER(); - address legacySharedBridge = l1Nullifier.l2BridgeAddress(s.chainId); + address sharedBridge = IBridgehub(s.bridgehub).sharedBridge(); + address legacySharedBridge = IL1SharedBridgeLegacy(sharedBridge).l2BridgeAddress(s.chainId); ZKChainSpecificForceDeploymentsData memory additionalForceDeploymentsData = ZKChainSpecificForceDeploymentsData({ baseTokenAssetId: s.baseTokenAssetId, diff --git a/l1-contracts/deploy-scripts/DeployL1.s.sol b/l1-contracts/deploy-scripts/DeployL1.s.sol index 1112d917e..26bd82396 100644 --- a/l1-contracts/deploy-scripts/DeployL1.s.sol +++ b/l1-contracts/deploy-scripts/DeployL1.s.sol @@ -67,6 +67,10 @@ struct FixedForceDeploymentsData { bytes32 l2AssetRouterBytecodeHash; bytes32 l2NtvBytecodeHash; bytes32 messageRootBytecodeHash; + address l2SharedBridgeLegacyImpl; + address l2BridgedStandardERC20Impl; + address l2BridgeProxyOwnerAddress; + address l2BridgedStandardERC20ProxyOwnerAddress; } contract DeployL1Script is Script { @@ -1102,7 +1106,12 @@ contract DeployL1Script is Script { l2NtvBytecodeHash: L2ContractHelper.hashL2Bytecode( L2ContractsBytecodesLib.readL2NativeTokenVaultBytecode() ), - messageRootBytecodeHash: L2ContractHelper.hashL2Bytecode(L2ContractsBytecodesLib.readMessageRootBytecode()) + messageRootBytecodeHash: L2ContractHelper.hashL2Bytecode(L2ContractsBytecodesLib.readMessageRootBytecode()), + // For newly created chains it it is expected that the following bridges are not present + l2SharedBridgeLegacyImpl: address(0), + l2BridgedStandardERC20Impl: address(0), + l2BridgeProxyOwnerAddress: address(0), + l2BridgedStandardERC20ProxyOwnerAddress: address(0) }); return abi.encode(data); diff --git a/l1-contracts/deploy-scripts/L2ContractsBytecodesLib.sol b/l1-contracts/deploy-scripts/L2ContractsBytecodesLib.sol index b9481c578..9b672deb2 100644 --- a/l1-contracts/deploy-scripts/L2ContractsBytecodesLib.sol +++ b/l1-contracts/deploy-scripts/L2ContractsBytecodesLib.sol @@ -259,4 +259,47 @@ library L2ContractsBytecodesLib { "/../l1-contracts/artifacts-zk/contracts/bridge/L2SharedBridgeLegacy.sol/L2SharedBridgeLegacy.json" ); } + + /// @notice Reads the bytecode of the L2GatewayUpgrade contract. + /// @return The bytecode of the L2GatewayUpgrade contract. + function readGatewayUpgradeBytecode() internal view returns (bytes memory) { + return + Utils.readHardhatBytecode( + "/../system-contracts/artifacts-zk/contracts-preprocessed/L2GatewayUpgrade.sol/L2GatewayUpgrade.json" + ); + } + + /// @notice Reads the bytecode of the L2GatewayUpgrade contract. + /// @return The bytecode of the L2GatewayUpgrade contract. + function readL2AdminFactoryBytecode() internal view returns (bytes memory) { + return + Utils.readHardhatBytecode( + "/../l1-contracts/artifacts-zk/contracts/governance/L2AdminFactory.sol/L2AdminFactory.json" + ); + } + + function readProxyAdminBytecode() internal view returns (bytes memory) { + return + Utils.readHardhatBytecode( + "/../l1-contracts/artifacts-zk/@openzeppelin/contracts-v4/proxy/transparent/ProxyAdmin.sol/ProxyAdmin.json" + ); + } + + /// @notice Reads the bytecode of the L2GatewayUpgrade contract. + /// @return The bytecode of the L2GatewayUpgrade contract. + function readPermanentRestrictionBytecode() internal view returns (bytes memory) { + return + Utils.readHardhatBytecode( + "/../l1-contracts/artifacts-zk/contracts/governance/PermanentRestriction.sol/PermanentRestriction.json" + ); + } + + /// @notice Reads the bytecode of the L2ProxyAdminDeployer contract. + /// @return The bytecode of the L2ProxyAdminDeployer contract. + function readProxyAdminDeployerBytecode() internal view returns (bytes memory) { + return + Utils.readHardhatBytecode( + "/../l1-contracts/artifacts-zk/contracts/governance/L2ProxyAdminDeployer.sol/L2ProxyAdminDeployer.json" + ); + } } diff --git a/l1-contracts/deploy-scripts/Utils.sol b/l1-contracts/deploy-scripts/Utils.sol index e71871298..ea75a45ad 100644 --- a/l1-contracts/deploy-scripts/Utils.sol +++ b/l1-contracts/deploy-scripts/Utils.sol @@ -17,7 +17,6 @@ import {L2_DEPLOYER_SYSTEM_CONTRACT_ADDR} from "contracts/common/L2ContractAddre import {L2ContractHelper} from "contracts/common/libraries/L2ContractHelper.sol"; import {IChainAdmin} from "contracts/governance/IChainAdmin.sol"; import {AccessControlRestriction} from "contracts/governance/AccessControlRestriction.sol"; -import {Call} from "contracts/governance/Common.sol"; /// @dev The offset from which the built-in, but user space contracts are located. uint160 constant USER_CONTRACTS_OFFSET = 0x10000; // 2^16 @@ -28,6 +27,8 @@ address constant L2_ASSET_ROUTER_ADDRESS = address(USER_CONTRACTS_OFFSET + 0x03) address constant L2_NATIVE_TOKEN_VAULT_ADDRESS = address(USER_CONTRACTS_OFFSET + 0x04); address constant L2_MESSAGE_ROOT_ADDRESS = address(USER_CONTRACTS_OFFSET + 0x05); +address constant L2_CREATE2_FACTORY_ADDRESS = address(USER_CONTRACTS_OFFSET); + // solhint-disable-next-line gas-struct-packing struct StateTransitionDeployedAddresses { address chainTypeManagerProxy; @@ -244,14 +245,7 @@ library Utils { address bridgehubAddress, address l1SharedBridgeProxy ) internal returns (address) { - bytes32 bytecodeHash = L2ContractHelper.hashL2Bytecode(bytecode); - - bytes memory deployData = abi.encodeWithSignature( - "create2(bytes32,bytes32,bytes)", - create2salt, - bytecodeHash, - constructorargs - ); + (bytes32 bytecodeHash, bytes memory deployData) = getDeploymentCalldata(create2salt, bytecode, constructorargs); address contractAddress = L2ContractHelper.computeCreate2Address( msg.sender, @@ -260,21 +254,80 @@ library Utils { keccak256(constructorargs) ); - uint256 factoryDepsLength = factoryDeps.length; + bytes[] memory _factoryDeps = appendArray(factoryDeps, bytecode); - bytes[] memory _factoryDeps = new bytes[](factoryDepsLength + 1); + runL1L2Transaction({ + l2Calldata: deployData, + l2GasLimit: l2GasLimit, + l2Value: 0, + factoryDeps: _factoryDeps, + dstAddress: L2_DEPLOYER_SYSTEM_CONTRACT_ADDR, + chainId: chainId, + bridgehubAddress: bridgehubAddress, + l1SharedBridgeProxy: l1SharedBridgeProxy + }); + return contractAddress; + } + + function getL2AddressViaCreate2Factory( + bytes32 create2Salt, + bytes32 bytecodeHash, + bytes memory constructorArgs + ) internal view returns (address) { + return + L2ContractHelper.computeCreate2Address( + L2_CREATE2_FACTORY_ADDRESS, + create2Salt, + bytecodeHash, + keccak256(constructorArgs) + ); + } - for (uint256 i = 0; i < factoryDepsLength; ++i) { - _factoryDeps[i] = factoryDeps[i]; + function getDeploymentCalldata( + bytes32 create2Salt, + bytes memory bytecode, + bytes memory constructorArgs + ) internal view returns (bytes32 bytecodeHash, bytes memory data) { + bytecodeHash = L2ContractHelper.hashL2Bytecode(bytecode); + + data = abi.encodeWithSignature("create2(bytes32,bytes32,bytes)", create2Salt, bytecodeHash, constructorArgs); + } + + function appendArray(bytes[] memory array, bytes memory element) internal pure returns (bytes[] memory) { + uint256 arrayLength = array.length; + bytes[] memory newArray = new bytes[](arrayLength + 1); + for (uint256 i = 0; i < arrayLength; ++i) { + newArray[i] = array[i]; } - _factoryDeps[factoryDepsLength] = bytecode; + newArray[arrayLength] = element; + return newArray; + } + + /** + * @dev Deploy l2 contracts through l1, while using built-in L2 Create2Factory contract. + */ + function deployThroughL1Deterministic( + bytes memory bytecode, + bytes memory constructorargs, + bytes32 create2salt, + uint256 l2GasLimit, + bytes[] memory factoryDeps, + uint256 chainId, + address bridgehubAddress, + address l1SharedBridgeProxy + ) internal returns (address) { + (bytes32 bytecodeHash, bytes memory deployData) = getDeploymentCalldata(create2salt, bytecode, constructorargs); + + address contractAddress = getL2AddressViaCreate2Factory(create2salt, bytecodeHash, constructorargs); + + bytes[] memory _factoryDeps = appendArray(factoryDeps, bytecode); runL1L2Transaction({ l2Calldata: deployData, l2GasLimit: l2GasLimit, l2Value: 0, factoryDeps: _factoryDeps, - dstAddress: L2_DEPLOYER_SYSTEM_CONTRACT_ADDR, + dstAddress: L2_CREATE2_FACTORY_ADDRESS, chainId: chainId, bridgehubAddress: bridgehubAddress, l1SharedBridgeProxy: l1SharedBridgeProxy diff --git a/l1-contracts/deploy-scripts/upgrade/ChainUpgrade.s.sol b/l1-contracts/deploy-scripts/upgrade/ChainUpgrade.s.sol new file mode 100644 index 000000000..7df6863d3 --- /dev/null +++ b/l1-contracts/deploy-scripts/upgrade/ChainUpgrade.s.sol @@ -0,0 +1,212 @@ +// 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 {Utils, L2_BRIDGEHUB_ADDRESS, L2_ASSET_ROUTER_ADDRESS, L2_NATIVE_TOKEN_VAULT_ADDRESS, L2_MESSAGE_ROOT_ADDRESS} from "../Utils.sol"; +import {L2ContractHelper} from "contracts/common/libraries/L2ContractHelper.sol"; +import {L2ContractsBytecodesLib} from "../L2ContractsBytecodesLib.sol"; +import {IZKChain} from "contracts/state-transition/chain-interfaces/IZKChain.sol"; +import {IAdmin} from "contracts/state-transition/chain-interfaces/IAdmin.sol"; +import {AccessControlRestriction} from "contracts/governance/AccessControlRestriction.sol"; +import {ChainAdmin} from "contracts/governance/ChainAdmin.sol"; +import {Call} from "contracts/governance/Common.sol"; +import {Diamond} from "contracts/state-transition/libraries/Diamond.sol"; + +interface LegacyChainAdmin { + function owner() external view returns (address); +} + +contract ChainUpgrade is Script { + using stdToml for string; + + struct ChainConfig { + address deployerAddress; + address ownerAddress; + uint256 chainChainId; + address chainDiamondProxyAddress; + bool validiumMode; + bool permanentRollup; + // FIXME: From ecosystem, maybe move to a different struct + address expectedRollupL2DAValidator; + address expectedL2GatewayUpgrade; + address expectedValidiumL2DAValidator; + address permanentRollupRestriction; + address bridgehubProxyAddress; + address oldSharedBridgeProxyAddress; + } + + struct Output { + address l2DAValidator; + address accessControlRestriction; + address chainAdmin; + } + + address currentChainAdmin; + ChainConfig config; + Output output; + + function prepareChain( + string memory ecosystemInputPath, + string memory ecosystemOutputPath, + string memory configPath, + string memory outputPath + ) public { + string memory root = vm.projectRoot(); + ecosystemInputPath = string.concat(root, ecosystemInputPath); + ecosystemOutputPath = string.concat(root, ecosystemOutputPath); + configPath = string.concat(root, configPath); + outputPath = string.concat(root, outputPath); + + initializeConfig(configPath, ecosystemInputPath, ecosystemOutputPath); + + checkCorrectOwnerAddress(); + // Preparation of chain consists of two parts: + // - Deploying l2 da validator + // - Deploying new chain admin + + deployNewL2DAValidator(); + deployL2GatewayUpgrade(); + deployNewChainAdmin(); + governanceMoveToNewChainAdmin(); + + saveOutput(outputPath); + } + + function upgradeChain(uint256 oldProtocolVersion, Diamond.DiamondCutData memory upgradeCutData) public { + Utils.adminExecute( + output.chainAdmin, + output.accessControlRestriction, + config.chainDiamondProxyAddress, + abi.encodeCall(IAdmin.upgradeChainFromVersion, (oldProtocolVersion, upgradeCutData)), + 0 + ); + } + + function initializeConfig( + string memory configPath, + string memory ecosystemInputPath, + string memory ecosystemOutputPath + ) internal { + config.deployerAddress = msg.sender; + + // Grab config from output of l1 deployment + string memory toml = vm.readFile(configPath); + + // 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.ownerAddress = toml.readAddress("$.owner_address"); + config.chainChainId = toml.readUint("$.chain.chain_id"); + config.validiumMode = toml.readBool("$.chain.validium_mode"); + config.chainDiamondProxyAddress = toml.readAddress("$.chain.diamond_proxy_address"); + config.permanentRollup = toml.readBool("$.chain.permanent_rollup"); + + toml = vm.readFile(ecosystemOutputPath); + + config.expectedRollupL2DAValidator = toml.readAddress("$.contracts_config.expected_rollup_l2_da_validator"); + config.expectedValidiumL2DAValidator = toml.readAddress("$.contracts_config.expected_validium_l2_da_validator"); + config.expectedL2GatewayUpgrade = toml.readAddress("$.contracts_config.expected_l2_gateway_upgrade"); + config.permanentRollupRestriction = toml.readAddress("$.deployed_addresses.permanent_rollup_restriction"); + + toml = vm.readFile(ecosystemInputPath); + + config.bridgehubProxyAddress = toml.readAddress("$.contracts.bridgehub_proxy_address"); + config.oldSharedBridgeProxyAddress = toml.readAddress("$.contracts.old_shared_bridge_proxy_address"); + } + + function checkCorrectOwnerAddress() internal { + currentChainAdmin = address(IZKChain(config.chainDiamondProxyAddress).getAdmin()); + address currentAdminOwner = LegacyChainAdmin(currentChainAdmin).owner(); + + require(currentAdminOwner == config.ownerAddress, "Only the owner of the chain admin can call this function"); + } + + function deployNewL2DAValidator() internal { + address expectedL2DAValidator = Utils.deployThroughL1Deterministic({ + // FIXME: for now this script only works with rollup chains + bytecode: L2ContractsBytecodesLib.readRollupL2DAValidatorBytecode(), + constructorargs: hex"", + create2salt: bytes32(0), + l2GasLimit: Utils.MAX_PRIORITY_TX_GAS, + factoryDeps: new bytes[](0), + chainId: config.chainChainId, + bridgehubAddress: config.bridgehubProxyAddress, + l1SharedBridgeProxy: config.oldSharedBridgeProxyAddress + }); + // FIXME: for now this script only works with rollup chains + require(expectedL2DAValidator == config.expectedRollupL2DAValidator, "Invalid L2DAValidator address"); + + output.l2DAValidator = expectedL2DAValidator; + } + + function deployL2GatewayUpgrade() internal { + address expectedGatewayUpgrade = Utils.deployThroughL1Deterministic({ + bytecode: L2ContractsBytecodesLib.readGatewayUpgradeBytecode(), + constructorargs: hex"", + create2salt: bytes32(0), + l2GasLimit: Utils.MAX_PRIORITY_TX_GAS, + factoryDeps: new bytes[](0), + chainId: config.chainChainId, + bridgehubAddress: config.bridgehubProxyAddress, + l1SharedBridgeProxy: config.oldSharedBridgeProxyAddress + }); + require(expectedGatewayUpgrade == config.expectedL2GatewayUpgrade, "Invalid L2Gateway address"); + } + + function deployNewChainAdmin() internal { + AccessControlRestriction accessControlRestriction = new AccessControlRestriction(0, config.ownerAddress); + + address[] memory restrictions; + if (config.permanentRollup) { + restrictions = new address[](2); + restrictions[0] = address(accessControlRestriction); + restrictions[1] = config.permanentRollupRestriction; + } else { + restrictions = new address[](1); + restrictions[0] = address(accessControlRestriction); + } + + ChainAdmin newChainAdmin = new ChainAdmin(restrictions); + output.chainAdmin = address(newChainAdmin); + output.accessControlRestriction = address(accessControlRestriction); + } + + /// @dev The caller of this function needs to be the owner of the chain admin + /// of the + function governanceMoveToNewChainAdmin() internal { + // Firstly, we need to call the legacy chain admin to transfer the ownership to the new chain admin + Call[] memory calls = new Call[](1); + calls[0] = Call({ + target: config.chainDiamondProxyAddress, + value: 0, + data: abi.encodeCall(IAdmin.setPendingAdmin, (output.chainAdmin)) + }); + + vm.startBroadcast(config.ownerAddress); + ChainAdmin(payable(currentChainAdmin)).multicall(calls, true); + vm.stopBroadcast(); + + // Now we need to accept the adminship + Utils.adminExecute({ + _admin: output.chainAdmin, + _accessControlRestriction: output.accessControlRestriction, + _target: config.chainDiamondProxyAddress, + _data: abi.encodeCall(IAdmin.acceptAdmin, ()), + _value: 0 + }); + } + + function saveOutput(string memory outputPath) internal { + vm.serializeAddress("root", "l2_da_validator_addr", output.l2DAValidator); + vm.serializeAddress("root", "chain_admin_addr", output.chainAdmin); + + string memory toml = vm.serializeAddress("root", "access_control_restriction", output.accessControlRestriction); + string memory root = vm.projectRoot(); + vm.writeToml(toml, outputPath); + console.log("Output saved at:", outputPath); + } +} diff --git a/l1-contracts/deploy-scripts/upgrade/EcosystemUpgrade.s.sol b/l1-contracts/deploy-scripts/upgrade/EcosystemUpgrade.s.sol new file mode 100644 index 000000000..124d33294 --- /dev/null +++ b/l1-contracts/deploy-scripts/upgrade/EcosystemUpgrade.s.sol @@ -0,0 +1,1348 @@ +// 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, ITransparentUpgradeableProxy} from "@openzeppelin/contracts-v4/proxy/transparent/TransparentUpgradeableProxy.sol"; +import {UpgradeableBeacon} from "@openzeppelin/contracts-v4/proxy/beacon/UpgradeableBeacon.sol"; +import {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 {GatewayUpgrade} from "contracts/upgrades/GatewayUpgrade.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 {PermanentRestriction} from "contracts/governance/PermanentRestriction.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"; +import {ValidiumL1DAValidator} from "contracts/state-transition/data-availability/ValidiumL1DAValidator.sol"; +import {Call} from "contracts/governance/Common.sol"; +import {Ownable2StepUpgradeable} from "@openzeppelin/contracts-upgradeable-v4/access/Ownable2StepUpgradeable.sol"; +import {IZKChain} from "contracts/state-transition/chain-interfaces/IZKChain.sol"; +import {ProposedUpgrade} from "contracts/upgrades/BaseZkSyncUpgrade.sol"; + +import {L2CanonicalTransaction} from "contracts/common/Messaging.sol"; + +import {L2_FORCE_DEPLOYER_ADDR, L2_COMPLEX_UPGRADER_ADDR} from "contracts/common/L2ContractAddresses.sol"; +import {IComplexUpgrader} from "contracts/state-transition/l2-deps/IComplexUpgrader.sol"; +import {GatewayUpgradeEncodedInput} from "contracts/upgrades/GatewayUpgrade.sol"; +import {TransitionaryOwner} from "contracts/governance/TransitionaryOwner.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; +} + +// A subset of the ones used for tests +struct StateTransitionDeployedAddresses { + address chainTypeManagerImplementation; + address verifier; + address adminFacet; + address mailboxFacet; + address executorFacet; + address gettersFacet; + address diamondInit; + address genesisUpgrade; + address defaultUpgrade; + address validatorTimelock; +} + +contract EcosystemUpgrade is Script { + 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; + ExpectedL2Addresses expectedL2Addresses; + address chainAdmin; + address accessControlRestrictionAddress; + address permanentRollupRestriction; + address validatorTimelock; + address gatewayUpgrade; + address create2Factory; + address transitionaryOwner; + } + + struct ExpectedL2Addresses { + address expectedRollupL2DAValidator; + address expectedValidiumL2DAValidator; + address expectedL2GatewayUpgrade; + address l2SharedBridgeLegacyImpl; + address l2BridgedStandardERC20Impl; + // In reality, the following addresses need to be + // deployed only on a settlement layer, i.e. the Gateway. + address expectedL2ProxyAdminDeployer; + address expectedL2ProxyAdmin; + address expectedL2AdminFactory; + address expectedL2PermanentRestrictionImpl; + address expectedL2PermanentRestrictionProxy; + } + + // 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 ctmDeploymentTrackerImplementation; + address ctmDeploymentTrackerProxy; + address messageRootImplementation; + address messageRootProxy; + } + + // solhint-disable-next-line gas-struct-packing + struct BridgesDeployedAddresses { + address erc20BridgeImplementation; + address sharedBridgeProxy; + address sharedBridgeImplementation; + address l1NullifierImplementation; + 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; + bytes diamondCutData; + } + + // solhint-disable-next-line gas-struct-packing + struct ContractsConfig { + bytes32 create2FactorySalt; + address create2FactoryAddr; + 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; + uint256 maxNumberOfChains; + bytes32 bootloaderHash; + bytes32 defaultAAHash; + address oldValidatorTimelock; + address legacyErc20BridgeAddress; + address bridgehubProxyAddress; + address oldSharedBridgeProxyAddress; + address stateTransitionManagerAddress; + address transparentProxyAdmin; + address eraDiamondProxy; + address blobVersionedHashRetriever; + address l2BridgeProxyOwnerAddress; + address l2BridgedStandardERC20ProxyOwnerAddress; + } + + struct TokensConfig { + address tokenWethAddress; + } + + Config internal config; + GeneratedData internal generatedData; + DeployedAddresses internal addresses; + + function prepareEcosystemContracts(string memory configPath, string memory outputPath) public { + string memory root = vm.projectRoot(); + configPath = string.concat(root, configPath); + outputPath = string.concat(root, outputPath); + + initializeConfig(configPath); + + instantiateCreate2Factory(); + + deployVerifier(); + deployDefaultUpgrade(); + deployGenesisUpgrade(); + deployGatewayUpgrade(); + + deployDAValidators(); + deployValidatorTimelock(); + + // TODO: restore + // deployChainAdmin(); + deployBridgehubImplementation(); + deployMessageRootContract(); + + deployL1NullifierContracts(); + deploySharedBridgeContracts(); + deployBridgedStandardERC20Implementation(); + deployBridgedTokenBeacon(); + deployL1NativeTokenVaultImplementation(); + deployL1NativeTokenVaultProxy(); + deployErc20BridgeImplementation(); + + deployCTMDeploymentTracker(); + + initializeGeneratedData(); + initializeExpectedL2Addresses(); + + deployChainTypeManagerContract(); + setChainTypeManagerInValidatorTimelock(); + + deployPermanentRollupRestriction(); + + deployTransitionaryOwner(); + + updateOwners(); + + saveOutput(outputPath); + } + + function run() public { + console.log("Deploying L1 contracts"); + } + + function provideAcceptOwnershipCalls() public returns (Call[] memory calls) { + console.log("Providing accept ownership calls"); + + calls = new Call[](4); + calls[0] = Call({ + target: addresses.permanentRollupRestriction, + data: abi.encodeCall(Ownable2StepUpgradeable.acceptOwnership, ()), + value: 0 + }); + calls[1] = Call({ + target: addresses.validatorTimelock, + data: abi.encodeCall(Ownable2StepUpgradeable.acceptOwnership, ()), + value: 0 + }); + calls[2] = Call({ + target: addresses.bridges.sharedBridgeProxy, + data: abi.encodeCall(Ownable2StepUpgradeable.acceptOwnership, ()), + value: 0 + }); + calls[3] = Call({ + target: addresses.bridgehub.ctmDeploymentTrackerProxy, + data: abi.encodeCall(Ownable2StepUpgradeable.acceptOwnership, ()), + value: 0 + }); + } + + function getOwnerAddress() public returns (address) { + return config.ownerAddress; + } + + function _getFacetCutsForDeletion() internal returns (Diamond.FacetCut[] memory facetCuts) { + IZKChain.Facet[] memory facets = IZKChain(config.contracts.eraDiamondProxy).facets(); + + // Freezability does not matter when deleting, so we just put false everywhere + facetCuts = new Diamond.FacetCut[](facets.length); + for (uint i = 0; i < facets.length; i++) { + facetCuts[i] = Diamond.FacetCut({ + facet: address(0), + action: Diamond.Action.Remove, + isFreezable: false, + selectors: facets[i].selectors + }); + } + } + + function _composeUpgradeTx() internal returns (L2CanonicalTransaction memory transaction) { + transaction = L2CanonicalTransaction({ + // FIXME: dont use hardcoded values + txType: 254, + from: uint256(uint160(L2_FORCE_DEPLOYER_ADDR)), + to: uint256(uint160(address(L2_COMPLEX_UPGRADER_ADDR))), + gasLimit: 72_000_000, + gasPerPubdataByteLimit: 800, + maxFeePerGas: 0, + maxPriorityFeePerGas: 0, + paymaster: uint256(uint160(address(0))), + nonce: 25, + value: 0, + reserved: [uint256(0), uint256(0), uint256(0), uint256(0)], + // Note, that the data is empty, it will be fully composed inside the `GatewayUpgrade` contract + data: new bytes(0), + signature: new bytes(0), + // All factory deps should've been published before + factoryDeps: new uint256[](0), + paymasterInput: new bytes(0), + // Reserved dynamic type for the future use-case. Using it should be avoided, + // But it is still here, just in case we want to enable some additional functionality + reservedDynamic: new bytes(0) + }); + } + + function getNewProtocolVersion() public returns (uint256) { + return 0x1900000000; + } + + function getOldProtocolDeadline() public returns (uint256) { + return 7 days; + } + + function getOldProtocolVersion() public returns (uint256) { + return 0x1800000002; + } + + function provideSetNewVersionUpgradeCall() public returns (Call[] memory calls) { + // Just retrieved it from the contract + uint256 PREVIOUS_PROTOCOL_VERSION = getOldProtocolVersion(); + uint256 DEADLINE = getOldProtocolDeadline(); + uint256 NEW_PROTOCOL_VERSION = getNewProtocolVersion(); + Call memory call = Call({ + target: config.contracts.stateTransitionManagerAddress, + data: abi.encodeCall( + ChainTypeManager.setNewVersionUpgrade, + (getChainUpgradeInfo(), PREVIOUS_PROTOCOL_VERSION, DEADLINE, NEW_PROTOCOL_VERSION) + ), + value: 0 + }); + + calls = new Call[](1); + calls[0] = call; + } + + function getChainUpgradeInfo() public returns (Diamond.DiamondCutData memory upgradeCutData) { + Diamond.FacetCut[] memory deletedFacets = _getFacetCutsForDeletion(); + + Diamond.FacetCut[] memory facetCuts = new Diamond.FacetCut[](deletedFacets.length + 4); + for (uint i = 0; i < deletedFacets.length; i++) { + facetCuts[i] = deletedFacets[i]; + } + facetCuts[deletedFacets.length] = Diamond.FacetCut({ + facet: addresses.stateTransition.adminFacet, + action: Diamond.Action.Add, + isFreezable: false, + selectors: Utils.getAllSelectors(addresses.stateTransition.adminFacet.code) + }); + facetCuts[deletedFacets.length + 1] = Diamond.FacetCut({ + facet: addresses.stateTransition.gettersFacet, + action: Diamond.Action.Add, + isFreezable: false, + selectors: Utils.getAllSelectors(addresses.stateTransition.gettersFacet.code) + }); + facetCuts[deletedFacets.length + 2] = Diamond.FacetCut({ + facet: addresses.stateTransition.mailboxFacet, + action: Diamond.Action.Add, + isFreezable: true, + selectors: Utils.getAllSelectors(addresses.stateTransition.mailboxFacet.code) + }); + facetCuts[deletedFacets.length + 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 + }); + + // TODO: we should fill this one up completely, but it is straightforward + IL2ContractDeployer.ForceDeployment[] memory baseForceDeployments = new IL2ContractDeployer.ForceDeployment[]( + 0 + ); + address ctmDeployer = addresses.bridgehub.ctmDeploymentTrackerProxy; + + GatewayUpgradeEncodedInput memory gateUpgradeInput = GatewayUpgradeEncodedInput({ + baseForceDeployments: baseForceDeployments, + ctmDeployer: ctmDeployer, + fixedForceDeploymentsData: generatedData.forceDeploymentsData, + l2GatewayUpgrade: addresses.expectedL2Addresses.expectedL2GatewayUpgrade, + oldValidatorTimelock: config.contracts.oldValidatorTimelock, + newValidatorTimelock: addresses.validatorTimelock + }); + + bytes memory postUpgradeCalldata = abi.encode(gateUpgradeInput); + + ProposedUpgrade memory proposedUpgrade = ProposedUpgrade({ + l2ProtocolUpgradeTx: _composeUpgradeTx(), + factoryDeps: new bytes[](0), + bootloaderHash: config.contracts.bootloaderHash, + defaultAccountHash: config.contracts.defaultAAHash, + verifier: addresses.stateTransition.verifier, + verifierParams: verifierParams, + l1ContractsUpgradeCalldata: new bytes(0), + postUpgradeCalldata: postUpgradeCalldata, + // FIXME: TBH, I am not sure if even should even put any time there, + // but we may + upgradeTimestamp: 0, + newProtocolVersion: getNewProtocolVersion() + }); + + upgradeCutData = Diamond.DiamondCutData({ + facetCuts: facetCuts, + initAddress: addresses.gatewayUpgrade, + initCalldata: abi.encodeCall(GatewayUpgrade.upgrade, (proposedUpgrade)) + }); + } + + function getStage2UpgradeCalls() public returns (Call[] memory calls) { + calls = new Call[](9); + + // We need to firstly update all the contracts + calls[0] = Call({ + target: config.contracts.transparentProxyAdmin, + data: abi.encodeCall( + ProxyAdmin.upgrade, + ( + ITransparentUpgradeableProxy(payable(config.contracts.stateTransitionManagerAddress)), + addresses.stateTransition.chainTypeManagerImplementation + ) + ), + value: 0 + }); + calls[1] = Call({ + target: config.contracts.transparentProxyAdmin, + data: abi.encodeCall( + ProxyAdmin.upgradeAndCall, + ( + ITransparentUpgradeableProxy(payable(config.contracts.bridgehubProxyAddress)), + addresses.bridgehub.bridgehubImplementation, + abi.encodeCall(Bridgehub.initializeV2, ()) + ) + ), + value: 0 + }); + calls[2] = Call({ + target: config.contracts.transparentProxyAdmin, + data: abi.encodeCall( + ProxyAdmin.upgrade, + ( + ITransparentUpgradeableProxy(payable(config.contracts.oldSharedBridgeProxyAddress)), + addresses.bridges.l1NullifierImplementation + ) + ), + value: 0 + }); + calls[3] = Call({ + target: config.contracts.transparentProxyAdmin, + data: abi.encodeCall( + ProxyAdmin.upgrade, + ( + ITransparentUpgradeableProxy(payable(config.contracts.legacyErc20BridgeAddress)), + addresses.bridges.erc20BridgeImplementation + ) + ), + value: 0 + }); + + // Now, updating chain creation params + calls[4] = Call({ + target: config.contracts.stateTransitionManagerAddress, + data: abi.encodeCall(ChainTypeManager.setChainCreationParams, (prepareNewChainCreationParams())), + value: 0 + }); + calls[5] = Call({ + target: config.contracts.stateTransitionManagerAddress, + data: abi.encodeCall(ChainTypeManager.setValidatorTimelock, (addresses.validatorTimelock)), + value: 0 + }); + + // Now, we need to update the bridgehub + calls[6] = Call({ + target: config.contracts.bridgehubProxyAddress, + data: abi.encodeCall( + Bridgehub.setAddresses, + ( + addresses.bridges.sharedBridgeProxy, + CTMDeploymentTracker(addresses.bridgehub.ctmDeploymentTrackerProxy), + MessageRoot(addresses.bridgehub.messageRootProxy) + ) + ), + value: 0 + }); + + // Setting the necessary params for the L1Nullifier contract + calls[7] = Call({ + target: config.contracts.oldSharedBridgeProxyAddress, + data: abi.encodeCall( + L1Nullifier.setL1NativeTokenVault, + (L1NativeTokenVault(payable(addresses.vaults.l1NativeTokenVaultProxy))) + ), + value: 0 + }); + calls[8] = Call({ + target: config.contracts.oldSharedBridgeProxyAddress, + data: abi.encodeCall(L1Nullifier.setL1AssetRouter, (addresses.bridges.sharedBridgeProxy)), + value: 0 + }); + } + + 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.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.contracts.stateTransitionManagerAddress = toml.readAddress( + "$.contracts.state_transition_manager_address" + ); + config.contracts.bridgehubProxyAddress = toml.readAddress("$.contracts.bridgehub_proxy_address"); + config.contracts.oldSharedBridgeProxyAddress = toml.readAddress("$.contracts.old_shared_bridge_proxy_address"); + config.contracts.transparentProxyAdmin = toml.readAddress("$.contracts.transparent_proxy_admin"); + config.contracts.eraDiamondProxy = toml.readAddress("$.contracts.era_diamond_proxy"); + config.contracts.legacyErc20BridgeAddress = toml.readAddress("$.contracts.legacy_erc20_bridge_address"); + config.contracts.oldValidatorTimelock = toml.readAddress("$.contracts.old_validator_timelock"); + // FIXME: value stored there is incorrect at the moment, figure out the correct value + config.contracts.blobVersionedHashRetriever = toml.readAddress("$.contracts.blob_versioned_hash_retriever"); + config.contracts.l2BridgeProxyOwnerAddress = toml.readAddress("$.contracts.l2_bridge_proxy_owner_address"); + config.contracts.l2BridgedStandardERC20ProxyOwnerAddress = toml.readAddress( + "$.contracts.l2_bridged_standard_erc20_proxy_owner_address" + ); + + config.tokens.tokenWethAddress = toml.readAddress("$.tokens.token_weth_address"); + } + + function initializeGeneratedData() internal { + generatedData.forceDeploymentsData = prepareForceDeploymentsData(); + } + + function initializeExpectedL2Addresses() internal { + address aliasedGovernance = AddressAliasHelper.applyL1ToL2Alias(config.ownerAddress); + + address expectedL2ProxyAdminDeployer = Utils.getL2AddressViaCreate2Factory( + bytes32(0), + L2ContractHelper.hashL2Bytecode(L2ContractsBytecodesLib.readProxyAdminDeployerBytecode()), + abi.encode(aliasedGovernance) + ); + address expectedL2ProxyAdmin = L2ContractHelper.computeCreate2Address( + expectedL2ProxyAdminDeployer, + bytes32(0), + L2ContractHelper.hashL2Bytecode(L2ContractsBytecodesLib.readProxyAdminBytecode()), + keccak256(hex"") + ); + + address permanentRestrictionImpl = Utils.getL2AddressViaCreate2Factory( + bytes32(0), + L2ContractHelper.hashL2Bytecode(L2ContractsBytecodesLib.readPermanentRestrictionBytecode()), + // Note that for L2 deployments the L2AdminFactory is 0. + abi.encode(L2_BRIDGEHUB_ADDRESS, address(0)) + ); + + address permanentRestrictionProxy = Utils.getL2AddressViaCreate2Factory( + bytes32(0), + L2ContractHelper.hashL2Bytecode(L2ContractsBytecodesLib.readTransparentUpgradeableProxyBytecode()), + abi.encode( + permanentRestrictionImpl, + expectedL2ProxyAdmin, + abi.encodeCall(PermanentRestriction.initialize, (aliasedGovernance)) + ) + ); + + address[] memory requiredL2Restrictions = new address[](1); + requiredL2Restrictions[0] = permanentRestrictionProxy; + + addresses.expectedL2Addresses = ExpectedL2Addresses({ + expectedRollupL2DAValidator: Utils.getL2AddressViaCreate2Factory( + bytes32(0), + L2ContractHelper.hashL2Bytecode(L2ContractsBytecodesLib.readRollupL2DAValidatorBytecode()), + hex"" + ), + expectedValidiumL2DAValidator: Utils.getL2AddressViaCreate2Factory( + bytes32(0), + L2ContractHelper.hashL2Bytecode(L2ContractsBytecodesLib.readValidiumL2DAValidatorBytecode()), + hex"" + ), + expectedL2GatewayUpgrade: Utils.getL2AddressViaCreate2Factory( + bytes32(0), + L2ContractHelper.hashL2Bytecode(L2ContractsBytecodesLib.readGatewayUpgradeBytecode()), + hex"" + ), + l2SharedBridgeLegacyImpl: Utils.getL2AddressViaCreate2Factory( + bytes32(0), + L2ContractHelper.hashL2Bytecode(L2ContractsBytecodesLib.readL2LegacySharedBridgeBytecode()), + hex"" + ), + l2BridgedStandardERC20Impl: Utils.getL2AddressViaCreate2Factory( + bytes32(0), + L2ContractHelper.hashL2Bytecode(L2ContractsBytecodesLib.readStandardERC20Bytecode()), + hex"" + ), + expectedL2ProxyAdminDeployer: expectedL2ProxyAdminDeployer, + expectedL2ProxyAdmin: expectedL2ProxyAdmin, + expectedL2AdminFactory: Utils.getL2AddressViaCreate2Factory( + bytes32(0), + L2ContractHelper.hashL2Bytecode(L2ContractsBytecodesLib.readL2AdminFactoryBytecode()), + abi.encode(requiredL2Restrictions) + ), + expectedL2PermanentRestrictionImpl: permanentRestrictionImpl, + expectedL2PermanentRestrictionProxy: permanentRestrictionProxy + }); + } + + 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 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 deployGatewayUpgrade() internal { + bytes memory bytecode = abi.encodePacked(type(GatewayUpgrade).creationCode); + address contractAddress = deployViaCreate2(bytecode); + console.log("GatewayUpgrade deployed at:", contractAddress); + addresses.gatewayUpgrade = contractAddress; + } + + function deployDAValidators() internal { + address contractAddress = deployViaCreate2(Utils.readRollupDAValidatorBytecode()); + console.log("L1RollupDAValidator deployed at:", contractAddress); + addresses.daAddresses.l1RollupDAValidator = contractAddress; + + contractAddress = deployViaCreate2(type(ValidiumL1DAValidator).creationCode); + console.log("L1ValidiumDAValidator deployed at:", contractAddress); + addresses.daAddresses.l1ValidiumDAValidator = contractAddress; + } + + function deployPermanentRollupRestriction() internal { + bytes memory bytecode = abi.encodePacked( + type(PermanentRestriction).creationCode, + abi.encode(config.contracts.bridgehubProxyAddress, addresses.expectedL2Addresses.expectedL2AdminFactory) + ); + address implementationAddress = deployViaCreate2(bytecode); + + bytes memory proxyBytecode = abi.encodePacked( + type(TransparentUpgradeableProxy).creationCode, + abi.encode( + implementationAddress, + config.contracts.transparentProxyAdmin, + abi.encodeCall(PermanentRestriction.initialize, (config.deployerAddress)) + ) + ); + + address proxyAddress = deployViaCreate2(proxyBytecode); + addresses.permanentRollupRestriction = proxyAddress; + // FIXME: supply restrictions + } + + 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 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 deployBridgehubImplementation() internal { + bytes memory bridgeHubBytecode = abi.encodePacked( + 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; + } + + function deployMessageRootContract() internal { + bytes memory messageRootBytecode = abi.encodePacked( + type(MessageRoot).creationCode, + abi.encode(config.contracts.bridgehubProxyAddress) + ); + address messageRootImplementation = deployViaCreate2(messageRootBytecode); + console.log("MessageRoot Implementation deployed at:", messageRootImplementation); + addresses.bridgehub.messageRootImplementation = messageRootImplementation; + + bytes memory bytecode = abi.encodePacked( + type(TransparentUpgradeableProxy).creationCode, + abi.encode( + messageRootImplementation, + config.contracts.transparentProxyAdmin, + 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( + type(CTMDeploymentTracker).creationCode, + abi.encode(config.contracts.bridgehubProxyAddress, 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( + type(TransparentUpgradeableProxy).creationCode, + abi.encode( + ctmDTImplementation, + config.contracts.transparentProxyAdmin, + abi.encodeCall(CTMDeploymentTracker.initialize, (config.deployerAddress)) + ) + ); + address ctmDTProxy = deployViaCreate2(bytecode); + console.log("CTM Deployment Tracker Proxy deployed at:", ctmDTProxy); + addresses.bridgehub.ctmDeploymentTrackerProxy = ctmDTProxy; + } + + function deployChainTypeManagerContract() internal { + deployStateTransitionDiamondFacets(); + deployChainTypeManagerImplementation(); + // 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(config.contracts.bridgehubProxyAddress) + ); + address contractAddress = deployViaCreate2(bytecode); + console.log("ChainTypeManagerImplementation deployed at:", contractAddress); + addresses.stateTransition.chainTypeManagerImplementation = contractAddress; + } + + function setChainTypeManagerInValidatorTimelock() internal { + ValidatorTimelock validatorTimelock = ValidatorTimelock(addresses.validatorTimelock); + vm.broadcast(msg.sender); + validatorTimelock.setChainTypeManager(IChainTypeManager(config.contracts.stateTransitionManagerAddress)); + console.log("ChainTypeManager set in ValidatorTimelock"); + } + + function deploySharedBridgeContracts() internal { + deploySharedBridgeImplementation(); + deploySharedBridgeProxy(); + setL1LegacyBridge(); + } + + function deployL1NullifierContracts() internal { + deployL1NullifierImplementation(); + } + + function deployL1NullifierImplementation() internal { + // TODO(EVM-743): allow non-dev nullifier in the local deployment + bytes memory bytecode = abi.encodePacked( + type(L1NullifierDev).creationCode, + // solhint-disable-next-line func-named-parameters + abi.encode(config.contracts.bridgehubProxyAddress, config.eraChainId, config.contracts.eraDiamondProxy) + ); + address contractAddress = deployViaCreate2(bytecode); + console.log("L1NullifierImplementation deployed at:", contractAddress); + addresses.bridges.l1NullifierImplementation = contractAddress; + } + + function deploySharedBridgeImplementation() internal { + bytes memory bytecode = abi.encodePacked( + type(L1AssetRouter).creationCode, + // solhint-disable-next-line func-named-parameters + abi.encode( + config.tokens.tokenWethAddress, + config.contracts.bridgehubProxyAddress, + config.contracts.oldSharedBridgeProxyAddress, + config.eraChainId, + config.contracts.eraDiamondProxy + ) + ); + 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( + type(TransparentUpgradeableProxy).creationCode, + abi.encode( + addresses.bridges.sharedBridgeImplementation, + config.contracts.transparentProxyAdmin, + initCalldata + ) + ); + address contractAddress = deployViaCreate2(bytecode); + console.log("SharedBridgeProxy deployed at:", contractAddress); + addresses.bridges.sharedBridgeProxy = contractAddress; + } + + function setL1LegacyBridge() internal { + vm.broadcast(msg.sender); + L1AssetRouter(addresses.bridges.sharedBridgeProxy).setL1Erc20Bridge( + L1ERC20Bridge(config.contracts.legacyErc20BridgeAddress) + ); + } + + function deployErc20BridgeImplementation() internal { + bytes memory bytecode = abi.encodePacked( + type(L1ERC20Bridge).creationCode, + abi.encode( + config.contracts.oldSharedBridgeProxyAddress, + addresses.bridges.sharedBridgeProxy, + addresses.vaults.l1NativeTokenVaultProxy, + config.eraChainId + ) + ); + address contractAddress = deployViaCreate2(bytecode); + console.log("Erc20BridgeImplementation deployed at:", contractAddress); + addresses.bridges.erc20BridgeImplementation = contractAddress; + } + + function deployBridgedStandardERC20Implementation() internal { + bytes memory bytecode = abi.encodePacked( + 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) + ); + UpgradeableBeacon beacon = new UpgradeableBeacon(addresses.bridges.bridgedStandardERC20Implementation); + address contractAddress = address(beacon); + beacon.transferOwnership(config.ownerAddress); + console.log("BridgedTokenBeacon deployed at:", contractAddress); + addresses.bridges.bridgedTokenBeacon = contractAddress; + } + + function deployL1NativeTokenVaultImplementation() internal { + bytes memory bytecode = abi.encodePacked( + type(L1NativeTokenVault).creationCode, + // solhint-disable-next-line func-named-parameters + abi.encode( + config.tokens.tokenWethAddress, + addresses.bridges.sharedBridgeProxy, + config.eraChainId, + config.contracts.oldSharedBridgeProxyAddress + ) + ); + address contractAddress = deployViaCreate2(bytecode); + console.log("L1NativeTokenVaultImplementation deployed at:", contractAddress); + addresses.vaults.l1NativeTokenVaultImplementation = contractAddress; + } + + function deployL1NativeTokenVaultProxy() internal { + bytes memory initCalldata = abi.encodeCall( + L1NativeTokenVault.initialize, + (config.ownerAddress, addresses.bridges.bridgedTokenBeacon) + ); + bytes memory bytecode = abi.encodePacked( + type(TransparentUpgradeableProxy).creationCode, + abi.encode( + addresses.vaults.l1NativeTokenVaultImplementation, + config.contracts.transparentProxyAdmin, + initCalldata + ) + ); + address contractAddress = deployViaCreate2(bytecode); + console.log("L1NativeTokenVaultProxy deployed at:", contractAddress); + addresses.vaults.l1NativeTokenVaultProxy = contractAddress; + + IL1AssetRouter sharedBridge = IL1AssetRouter(addresses.bridges.sharedBridgeProxy); + IL1Nullifier l1Nullifier = IL1Nullifier(config.contracts.oldSharedBridgeProxyAddress); + // Ownable ownable = Ownable(addresses.bridges.sharedBridgeProxy); + + vm.broadcast(msg.sender); + sharedBridge.setNativeTokenVault(INativeTokenVault(addresses.vaults.l1NativeTokenVaultProxy)); + vm.broadcast(msg.sender); + IL1NativeTokenVault(addresses.vaults.l1NativeTokenVaultProxy).registerEthToken(); + } + + function deployTransitionaryOwner() internal { + bytes memory bytecode = abi.encodePacked( + type(TransitionaryOwner).creationCode, + abi.encode(config.ownerAddress) + ); + + addresses.transitionaryOwner = deployViaCreate2(bytecode); + } + + function _moveGovernanceToOwner(address target) internal { + Ownable2StepUpgradeable(target).transferOwnership(addresses.transitionaryOwner); + TransitionaryOwner(addresses.transitionaryOwner).claimOwnershipAndGiveToGovernance(target); + } + + function updateOwners() internal { + vm.startBroadcast(msg.sender); + + // Note, that it will take some time for the governance to sign the "acceptOwnership" transaction, + // in order to avoid any possibility of the front-run, we will temporarily give the ownership to the + // contract that can only transfer ownership to the governance. + _moveGovernanceToOwner(addresses.validatorTimelock); + _moveGovernanceToOwner(addresses.bridges.sharedBridgeProxy); + _moveGovernanceToOwner(addresses.bridgehub.ctmDeploymentTrackerProxy); + _moveGovernanceToOwner(addresses.permanentRollupRestriction); + + vm.stopBroadcast(); + console.log("Owners updated"); + } + + function prepareNewChainCreationParams() internal returns (ChainCreationParams memory chainCreationParams) { + 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: config.contracts.blobVersionedHashRetriever + }); + + Diamond.DiamondCutData memory diamondCut = Diamond.DiamondCutData({ + facetCuts: facetCuts, + initAddress: addresses.stateTransition.diamondInit, + initCalldata: abi.encode(initializeData) + }); + + chainCreationParams = ChainCreationParams({ + genesisUpgrade: addresses.stateTransition.genesisUpgrade, + genesisBatchHash: config.contracts.genesisRoot, + genesisIndexRepeatedStorageChanges: uint64(config.contracts.genesisRollupLeafIndex), + genesisBatchCommitment: config.contracts.genesisBatchCommitment, + diamondCut: diamondCut, + forceDeploymentsData: generatedData.forceDeploymentsData + }); + } + + function saveOutput(string memory outputPath) internal { + vm.serializeAddress("bridgehub", "bridgehub_implementation_addr", addresses.bridgehub.bridgehubImplementation); + vm.serializeAddress( + "bridgehub", + "ctm_deployment_tracker_proxy_addr", + addresses.bridgehub.ctmDeploymentTrackerProxy + ); + vm.serializeAddress( + "bridgehub", + "ctm_deployment_tracker_implementation_addr", + addresses.bridgehub.ctmDeploymentTrackerImplementation + ); + vm.serializeAddress("bridgehub", "message_root_proxy_addr", addresses.bridgehub.messageRootProxy); + string memory bridgehub = vm.serializeAddress( + "bridgehub", + "message_root_implementation_addr", + addresses.bridgehub.messageRootImplementation + ); + + // TODO(EVM-744): this has to be renamed to chain type manager + vm.serializeAddress( + "state_transition", + "state_transition_implementation_addr", + addresses.stateTransition.chainTypeManagerImplementation + ); + vm.serializeAddress("state_transition", "verifier_addr", addresses.stateTransition.verifier); + vm.serializeAddress("state_transition", "admin_facet_addr", addresses.stateTransition.adminFacet); + vm.serializeAddress("state_transition", "mailbox_facet_addr", addresses.stateTransition.mailboxFacet); + vm.serializeAddress("state_transition", "executor_facet_addr", addresses.stateTransition.executorFacet); + vm.serializeAddress("state_transition", "getters_facet_addr", addresses.stateTransition.gettersFacet); + vm.serializeAddress("state_transition", "diamond_init_addr", addresses.stateTransition.diamondInit); + vm.serializeAddress("state_transition", "genesis_upgrade_addr", addresses.stateTransition.genesisUpgrade); + string memory stateTransition = vm.serializeAddress( + "state_transition", + "default_upgrade_addr", + addresses.stateTransition.defaultUpgrade + ); + + vm.serializeAddress("bridges", "erc20_bridge_implementation_addr", addresses.bridges.erc20BridgeImplementation); + vm.serializeAddress("bridges", "l1_nullifier_implementation_addr", addresses.bridges.l1NullifierImplementation); + vm.serializeAddress( + "bridges", + "shared_bridge_implementation_addr", + addresses.bridges.sharedBridgeImplementation + ); + string memory bridges = vm.serializeAddress( + "bridges", + "shared_bridge_proxy_addr", + addresses.bridges.sharedBridgeProxy + ); + + vm.serializeUint( + "contracts_config", + "diamond_init_max_l2_gas_per_batch", + config.contracts.diamondInitMaxL2GasPerBatch + ); + vm.serializeUint( + "contracts_config", + "diamond_init_batch_overhead_l1_gas", + config.contracts.diamondInitBatchOverheadL1Gas + ); + vm.serializeUint( + "contracts_config", + "diamond_init_max_pubdata_per_batch", + config.contracts.diamondInitMaxPubdataPerBatch + ); + vm.serializeUint( + "contracts_config", + "diamond_init_minimal_l2_gas_price", + config.contracts.diamondInitMinimalL2GasPrice + ); + vm.serializeUint( + "contracts_config", + "diamond_init_priority_tx_max_pubdata", + config.contracts.diamondInitPriorityTxMaxPubdata + ); + vm.serializeUint( + "contracts_config", + "diamond_init_pubdata_pricing_mode", + uint256(config.contracts.diamondInitPubdataPricingMode) + ); + vm.serializeUint("contracts_config", "priority_tx_max_gas_limit", config.contracts.priorityTxMaxGasLimit); + vm.serializeBytes32( + "contracts_config", + "recursion_circuits_set_vks_hash", + config.contracts.recursionCircuitsSetVksHash + ); + vm.serializeBytes32( + "contracts_config", + "recursion_leaf_level_vk_hash", + config.contracts.recursionLeafLevelVkHash + ); + vm.serializeBytes32( + "contracts_config", + "recursion_node_level_vk_hash", + config.contracts.recursionNodeLevelVkHash + ); + + vm.serializeAddress( + "contracts_config", + "expected_rollup_l2_da_validator", + addresses.expectedL2Addresses.expectedRollupL2DAValidator + ); + vm.serializeAddress( + "contracts_config", + "expected_validium_l2_da_validator", + addresses.expectedL2Addresses.expectedValidiumL2DAValidator + ); + vm.serializeAddress( + "contracts_config", + "expected_l2_gateway_upgrade", + addresses.expectedL2Addresses.expectedL2GatewayUpgrade + ); + vm.serializeBytes("contracts_config", "diamond_cut_data", generatedData.diamondCutData); + + string memory contractsConfig = vm.serializeBytes( + "contracts_config", + "force_deployments_data", + generatedData.forceDeploymentsData + ); + + vm.serializeAddress("deployed_addresses", "validator_timelock_addr", addresses.validatorTimelock); + vm.serializeAddress("deployed_addresses", "chain_admin", addresses.chainAdmin); + vm.serializeAddress( + "deployed_addresses", + "access_control_restriction_addr", + addresses.accessControlRestrictionAddress + ); + vm.serializeAddress("deployed_addresses", "permanent_rollup_restriction", addresses.permanentRollupRestriction); + vm.serializeString("deployed_addresses", "bridgehub", bridgehub); + vm.serializeString("deployed_addresses", "bridges", bridges); + vm.serializeString("deployed_addresses", "state_transition", stateTransition); + + vm.serializeAddress( + "deployed_addresses", + "rollup_l1_da_validator_addr", + addresses.daAddresses.l1RollupDAValidator + ); + vm.serializeAddress( + "deployed_addresses", + "validium_l1_da_validator_addr", + addresses.daAddresses.l1ValidiumDAValidator + ); + + string memory deployedAddresses = vm.serializeAddress( + "deployed_addresses", + "native_token_vault_addr", + addresses.vaults.l1NativeTokenVaultProxy + ); + + vm.serializeAddress("root", "create2_factory_addr", addresses.create2Factory); + vm.serializeBytes32("root", "create2_factory_salt", config.contracts.create2FactorySalt); + vm.serializeUint("root", "l1_chain_id", config.l1ChainId); + vm.serializeUint("root", "era_chain_id", config.eraChainId); + vm.serializeAddress("root", "deployer_addr", config.deployerAddress); + vm.serializeString("root", "deployed_addresses", deployedAddresses); + vm.serializeString("root", "contracts_config", contractsConfig); + string memory toml = vm.serializeAddress("root", "owner_address", config.ownerAddress); + + 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(config.ownerAddress != address(0), "owner not set"); + + FixedForceDeploymentsData memory data = FixedForceDeploymentsData({ + l1ChainId: config.l1ChainId, + eraChainId: config.eraChainId, + l1AssetRouter: addresses.bridges.sharedBridgeProxy, + l2TokenProxyBytecodeHash: L2ContractHelper.hashL2Bytecode( + L2ContractsBytecodesLib.readBeaconProxyBytecode() + ), + aliasedL1Governance: AddressAliasHelper.applyL1ToL2Alias(config.ownerAddress), + maxNumberOfZKChains: config.contracts.maxNumberOfChains, + bridgehubBytecodeHash: L2ContractHelper.hashL2Bytecode(L2ContractsBytecodesLib.readBridgehubBytecode()), + l2AssetRouterBytecodeHash: L2ContractHelper.hashL2Bytecode( + L2ContractsBytecodesLib.readL2AssetRouterBytecode() + ), + l2NtvBytecodeHash: L2ContractHelper.hashL2Bytecode( + L2ContractsBytecodesLib.readL2NativeTokenVaultBytecode() + ), + messageRootBytecodeHash: L2ContractHelper.hashL2Bytecode(L2ContractsBytecodesLib.readMessageRootBytecode()), + l2SharedBridgeLegacyImpl: addresses.expectedL2Addresses.l2SharedBridgeLegacyImpl, + l2BridgedStandardERC20Impl: addresses.expectedL2Addresses.l2BridgedStandardERC20Impl, + l2BridgeProxyOwnerAddress: config.contracts.l2BridgeProxyOwnerAddress, + l2BridgedStandardERC20ProxyOwnerAddress: config.contracts.l2BridgedStandardERC20ProxyOwnerAddress + }); + + return abi.encode(data); + } + + // add this to be excluded from coverage report + function test() internal {} +} diff --git a/l1-contracts/foundry.toml b/l1-contracts/foundry.toml index 8c9a3a52f..b4dc2ca3b 100644 --- a/l1-contracts/foundry.toml +++ b/l1-contracts/foundry.toml @@ -19,6 +19,8 @@ fs_permissions = [ { access = "read", path = "./out" }, { access = "read-write", path = "./test/foundry/l1/integration/deploy-scripts/script-config/" }, { access = "read-write", path = "./test/foundry/l1/integration/deploy-scripts/script-out/" }, + { access = "read-write", path = "./test/foundry/l1/integration/upgrade-envs/script-config/" }, + { access = "read-write", path = "./test/foundry/l1/integration/upgrade-envs/script-out/" }, { access = "read", path = "zkout" }, ] ignored_error_codes = ["missing-receive-ether", "code-size"] @@ -34,7 +36,7 @@ remappings = [ "@openzeppelin/contracts-upgradeable-v4/=lib/openzeppelin-contracts-upgradeable-v4/contracts/", ] optimizer = true -optimizer_runs = 9999 +optimizer_runs = 200 [profile.default.zksync] enable_eravm_extensions = true zksolc = "1.5.3" diff --git a/l1-contracts/package.json b/l1-contracts/package.json index 6d68729e2..effe54841 100644 --- a/l1-contracts/package.json +++ b/l1-contracts/package.json @@ -58,10 +58,11 @@ "clean": "hardhat clean && CONTRACTS_BASE_NETWORK_ZKSYNC=true hardhat clean", "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/*'", + "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: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'", + "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", "deploy-no-build": "ts-node scripts/deploy.ts", "register-zk-chain": "ts-node scripts/register-zk-chain.ts", "deploy-weth-bridges": "ts-node scripts/deploy-weth-bridges.ts", diff --git a/l1-contracts/src.ts/deploy.ts b/l1-contracts/src.ts/deploy.ts index 088aeb016..b624044c7 100644 --- a/l1-contracts/src.ts/deploy.ts +++ b/l1-contracts/src.ts/deploy.ts @@ -213,6 +213,10 @@ export class Deployer { l2AssetRouterBytecodeHash: ethers.utils.hexlify(hashL2Bytecode(assetRouterZKBytecode)), l2NtvBytecodeHash: ethers.utils.hexlify(hashL2Bytecode(nativeTokenVaultZKBytecode)), messageRootBytecodeHash: ethers.utils.hexlify(hashL2Bytecode(messageRootZKBytecode)), + l2SharedBridgeLegacyImpl: ethers.constants.AddressZero, + l2BridgedStandardERC20Impl: ethers.constants.AddressZero, + l2BridgeProxyOwnerAddress: ethers.constants.AddressZero, + l2BridgedStandardERC20ProxyOwnerAddress: ethers.constants.AddressZero, }; return ethers.utils.defaultAbiCoder.encode([FIXED_FORCE_DEPLOYMENTS_DATA_ABI_STRING], [fixedForceDeploymentsData]); diff --git a/l1-contracts/src.ts/utils.ts b/l1-contracts/src.ts/utils.ts index 408dc4e90..06f3511af 100644 --- a/l1-contracts/src.ts/utils.ts +++ b/l1-contracts/src.ts/utils.ts @@ -17,6 +17,20 @@ const CREATE2_PREFIX = ethers.utils.solidityKeccak256(["string"], ["zksyncCreate export const priorityTxMaxGasLimit = getNumberFromEnv("CONTRACTS_PRIORITY_TX_MAX_GAS_LIMIT"); const ADDRESS_MODULO = ethers.BigNumber.from(2).pow(160); +export const STORED_BATCH_INFO_ABI_STRING = + "tuple(uint64 batchNumber, bytes32 batchHash, uint64 indexRepeatedStorageChanges, uint256 numberOfLayer1Txs, bytes32 priorityOperationsHash, bytes32 l2LogsTreeRoot, uint256 timestamp, bytes32 commitment)"; +export const COMMIT_BATCH_INFO_ABI_STRING = + "tuple(uint64 batchNumber, uint64 timestamp, uint64 indexRepeatedStorageChanges, bytes32 newStateRoot, uint256 numberOfLayer1Txs, bytes32 priorityOperationsHash, bytes32 bootloaderHeapInitialContentsHash, bytes32 eventsQueueStateHash, bytes systemLogs, bytes operatorDAInput)"; +export const PRIORITY_OPS_BATCH_INFO_ABI_STRING = + "tuple(bytes32[] leftPath, bytes32[] rightPath, bytes32[] itemHashes)"; +export const DIAMOND_CUT_DATA_ABI_STRING = + "tuple(tuple(address facet, uint8 action, bool isFreezable, bytes4[] selectors)[] facetCuts, address initAddress, bytes initCalldata)"; +export const FORCE_DEPLOYMENT_ABI_STRING = + "tuple(bytes32 bytecodeHash, address newAddress, bool callConstructor, uint256 value, bytes input)[]"; +export const BRIDGEHUB_CTM_ASSET_DATA_ABI_STRING = "tuple(uint256 chainId, bytes ctmData, bytes chainData)"; +export const FIXED_FORCE_DEPLOYMENTS_DATA_ABI_STRING = + "tuple(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)"; +export const ADDITIONAL_FORCE_DEPLOYMENTS_DATA_ABI_STRING = "tuple(bytes32 baseTokenAssetId, address l2Weth)"; export function applyL1ToL2Alias(address: string): string { return ethers.utils.hexlify(ethers.BigNumber.from(address).add(L1_TO_L2_ALIAS_OFFSET).mod(ADDRESS_MODULO)); @@ -289,7 +303,6 @@ export function compileInitialCutHash( admin: "0x0000000000000000000000000000000000003234", validatorTimelock: "0x0000000000000000000000000000000000004234", baseTokenAssetId: "0x0000000000000000000000000000000000000000000000000000000000004234", - baseTokenBridge: "0x0000000000000000000000000000000000004234", storedBatchZero: "0x0000000000000000000000000000000000000000000000000000000000005432", verifier, verifierParams, @@ -301,7 +314,7 @@ export function compileInitialCutHash( }, ]); - return diamondCut(facetCuts, diamondInit, "0x" + diamondInitCalldata.slice(2 + (4 + 9 * 32) * 2)); + return diamondCut(facetCuts, diamondInit, "0x" + diamondInitCalldata.slice(2 + (4 + 8 * 32) * 2)); } export enum PubdataSource { diff --git a/l1-contracts/test/foundry/l1/integration/GatewayTests.t.sol b/l1-contracts/test/foundry/l1/integration/GatewayTests.t.sol index 0cfcbf049..d4aa1ba0a 100644 --- a/l1-contracts/test/foundry/l1/integration/GatewayTests.t.sol +++ b/l1-contracts/test/foundry/l1/integration/GatewayTests.t.sol @@ -258,7 +258,6 @@ contract GatewayTests is L1ContractDeployer, ZKChainDeployer, TokenDeployer, L2T address chain = _deployZkChain( chainId, baseTokenAssetId, - address(bridgehub.sharedBridge()), owner, stm.protocolVersion(), stm.storedBatchZero(), @@ -290,7 +289,6 @@ contract GatewayTests is L1ContractDeployer, ZKChainDeployer, TokenDeployer, L2T address chain = _deployZkChain( chainId, baseTokenAssetId, - address(bridgehub.sharedBridge()), owner, stm.protocolVersion(), stm.storedBatchZero(), diff --git a/l1-contracts/test/foundry/l1/integration/UpgradeTest.t.sol b/l1-contracts/test/foundry/l1/integration/UpgradeTest.t.sol new file mode 100644 index 000000000..3cbcf454b --- /dev/null +++ b/l1-contracts/test/foundry/l1/integration/UpgradeTest.t.sol @@ -0,0 +1,105 @@ +// 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 {EcosystemUpgrade} from "deploy-scripts/upgrade/EcosystemUpgrade.s.sol"; +import {ChainUpgrade} from "deploy-scripts/upgrade/ChainUpgrade.s.sol"; +import {Call} from "contracts/governance/Common.sol"; +import {Test} from "forge-std/Test.sol"; + +string constant ECOSYSTEM_INPUT = "/test/foundry/l1/integration/upgrade-envs/script-config/mainnet.toml"; +string constant ECOSYSTEM_OUTPUT = "/test/foundry/l1/integration/upgrade-envs/script-out/mainnet.toml"; +string constant CHAIN_INPUT = "/test/foundry/l1/integration/upgrade-envs/script-config/mainnet-era.toml"; +string constant CHAIN_OUTPUT = "/test/foundry/l1/integration/upgrade-envs/script-out/mainnet-era.toml"; + +contract UpgradeTest is Test { + EcosystemUpgrade generateUpgradeData; + ChainUpgrade chainUpgrade; + + function setUp() public { + generateUpgradeData = new EcosystemUpgrade(); + chainUpgrade = new ChainUpgrade(); + } + + function test_MainnetFork() public { + console.log("Preparing ecosystem contracts"); + // Firstly, we deploy all the contracts. + generateUpgradeData.prepareEcosystemContracts(ECOSYSTEM_INPUT, ECOSYSTEM_OUTPUT); + + // For chain, we have deployed the DA validator contracts + // and also updated the chain admin. + // IMPORTANT: for erc20-based chains with token multiplier setter + // this should be coordinated with the server. + console.log("Preparing chain for the upgrade"); + chainUpgrade.prepareChain(ECOSYSTEM_INPUT, ECOSYSTEM_OUTPUT, CHAIN_INPUT, CHAIN_OUTPUT); + + console.log("Starting stage1 of the upgrade!"); + // Now, some time has passed and we are ready to start the upgrade of the + // ecosystem. + // Stage 1 of the upgrade: + // - accept all the ownerships of the contracts + // - set the new upgrade data for chains + update validator timelock. + Call[] memory stage1Calls = mergeCalls( + generateUpgradeData.provideAcceptOwnershipCalls(), + generateUpgradeData.provideSetNewVersionUpgradeCall() + ); + + governanceMulticall(generateUpgradeData.getOwnerAddress(), stage1Calls); + + console.log("Stage1 is done, now all the chains have to upgrade to the new version"); + + console.log("Upgrading Era"); + + // Now, the admin of the Era needs to call the upgrade function. + // Note, that the step below also updated ValidatorTimelock so the server needs to be ready for that. + // TODO: We do not include calls that ensure that the server is ready for the sake of brevity. + chainUpgrade.upgradeChain( + generateUpgradeData.getOldProtocolVersion(), + generateUpgradeData.getChainUpgradeInfo() + ); + + // TODO: here we should include tests that depoists work for upgraded chains + // including era specific deposit/withdraw functions + // We also may need to test that normal flow of block commit / verify / execute works (but it is hard) + + vm.warp(generateUpgradeData.getOldProtocolDeadline()); + + console.log("Starting stage2 of the upgrade!"); + governanceMulticall(generateUpgradeData.getOwnerAddress(), generateUpgradeData.getStage2UpgradeCalls()); + + // TODO: here we should have tests that the bridging works for the previously deployed chains + // and that it does not work for those that did not upgrade. + // TODO: test that creation of new chains works under new conditions. + // TODO: if not hard, include test for deploying a gateway and migrating Era to it. + } + + /// @dev This is a contract that is used for additional visibility of transactions + /// that the decentralized governance should do. + function governanceMulticall(address governanceAddr, Call[] memory calls) internal { + // How the governance is implemented is out of scope here + vm.startBroadcast(governanceAddr); + + for (uint256 i = 0; i < calls.length; i++) { + Call memory call = calls[i]; + + (bool success, bytes memory data) = payable(call.target).call{value: call.value}(call.data); + require(success, "Multicall failed"); + } + + vm.stopBroadcast(); + } + + function mergeCalls(Call[] memory a, Call[] memory b) internal pure returns (Call[] memory result) { + result = new Call[](a.length + b.length); + for (uint256 i = 0; i < a.length; i++) { + result[i] = a[i]; + } + for (uint256 i = 0; i < b.length; i++) { + result[a.length + i] = b[i]; + } + } +} diff --git a/l1-contracts/test/foundry/l1/integration/_SharedZKChainDeployer.t.sol b/l1-contracts/test/foundry/l1/integration/_SharedZKChainDeployer.t.sol index 6220818ef..74a2f86b6 100644 --- a/l1-contracts/test/foundry/l1/integration/_SharedZKChainDeployer.t.sol +++ b/l1-contracts/test/foundry/l1/integration/_SharedZKChainDeployer.t.sol @@ -156,7 +156,6 @@ contract ZKChainDeployer is L1ContractDeployer { function _deployZkChain( uint256 _chainId, bytes32 _baseTokenAssetId, - address _sharedBridge, address _admin, uint256 _protocolVersion, bytes32 _storedBatchZero, @@ -178,7 +177,6 @@ contract ZKChainDeployer is L1ContractDeployer { bytes32(uint256(uint160(_admin))), bytes32(uint256(uint160(address(0x1337)))), _baseTokenAssetId, - bytes32(uint256(uint160(_sharedBridge))), _storedBatchZero, diamondCut.initCalldata ); diff --git a/l1-contracts/test/foundry/l1/integration/upgrade-envs/script-config/mainnet-era.toml b/l1-contracts/test/foundry/l1/integration/upgrade-envs/script-config/mainnet-era.toml new file mode 100644 index 000000000..d48013384 --- /dev/null +++ b/l1-contracts/test/foundry/l1/integration/upgrade-envs/script-config/mainnet-era.toml @@ -0,0 +1,7 @@ +owner_address = "0x4e4943346848c4867f81dfb37c4ca9c5715a7828" + +[chain] +chain_id = 324 +diamond_proxy_address = "0x32400084c286cf3e17e7b677ea9583e60a000324" +validium_mode = false +permanent_rollup = true diff --git a/l1-contracts/test/foundry/l1/integration/upgrade-envs/script-config/mainnet.toml b/l1-contracts/test/foundry/l1/integration/upgrade-envs/script-config/mainnet.toml new file mode 100644 index 000000000..abf681e37 --- /dev/null +++ b/l1-contracts/test/foundry/l1/integration/upgrade-envs/script-config/mainnet.toml @@ -0,0 +1,37 @@ +era_chain_id = 324 +owner_address = "8f7a9912416e8adc4d9c21fae1415d3318a11897" +testnet_verifier = false + +[contracts] +max_number_of_chains = 100 +create2_factory_salt = "0xde6b9c610417de5c775c1601c947f482e4f4e30c0f7b848c6d2b0554d76f607e" +validator_timelock_execution_delay = 0 +genesis_root = "0xf9030b78c5bf5ac997a76962aa32c90a6d8e8ebce9838c8eeb388d73e1f7659a" +genesis_rollup_leaf_index = 64 +genesis_batch_commitment = "0x34c1b220363e0cde7eaf10fe95754d61de097e0f9d9a1dc56c8026562e395259" +latest_protocol_version = "0x1900000000" +recursion_node_level_vk_hash = "0x0000000000000000000000000000000000000000000000000000000000000000" +recursion_leaf_level_vk_hash = "0x0000000000000000000000000000000000000000000000000000000000000000" +recursion_circuits_set_vks_hash = "0x0000000000000000000000000000000000000000000000000000000000000000" +priority_tx_max_gas_limit = 72000000 +diamond_init_pubdata_pricing_mode = 0 +diamond_init_batch_overhead_l1_gas = 1000000 +diamond_init_max_pubdata_per_batch = 120000 +diamond_init_max_l2_gas_per_batch = 80000000 +diamond_init_priority_tx_max_pubdata = 99000 +diamond_init_minimal_l2_gas_price = 250000000 +bootloader_hash = "0x010008c753336bc8d1ddca235602b9f31d346412b2d463cd342899f7bfb73baf" +default_aa_hash = "0x0100055d760f11a3d737e7fd1816e600a4cd874a9f17f7a225d1f1c537c51a1e" +bridgehub_proxy_address = "0x303a465B659cBB0ab36eE643eA362c509EEb5213" +old_shared_bridge_proxy_address = "0xD7f9f54194C633F36CCD5F3da84ad4a1c38cB2cB" +state_transition_manager_address = "0xc2eE6b6af7d616f6e27ce7F4A451Aedc2b0F5f5C" +transparent_proxy_admin = "0xC2a36181fB524a6bEfE639aFEd37A67e77d62cf1" +era_diamond_proxy = "0x32400084c286cf3e17e7b677ea9583e60a000324" +blob_versioned_hash_retriever = "0x0000000000000000000000000000000000000001" +legacy_erc20_bridge_address = "0x57891966931eb4bb6fb81430e6ce0a03aabde063" +old_validator_timelock = "0x5D8ba173Dc6C3c90C8f7C04C9288BeF5FDbAd06E" +l2_bridge_proxy_owner_address = "0x0000000000000000000000000000000000000001" +l2_bridged_standard_erc20_proxy_owner_address = "0x0000000000000000000000000000000000000001" + +[tokens] +token_weth_address = "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2" diff --git a/l1-contracts/test/foundry/l1/integration/upgrade-envs/script-out/.gitkeep b/l1-contracts/test/foundry/l1/integration/upgrade-envs/script-out/.gitkeep new file mode 100644 index 000000000..e69de29bb diff --git a/l1-contracts/test/foundry/l1/unit/concrete/Bridgehub/experimental_bridge.t.sol b/l1-contracts/test/foundry/l1/unit/concrete/Bridgehub/experimental_bridge.t.sol index a6f8d1ef6..7cc353b5e 100644 --- a/l1-contracts/test/foundry/l1/unit/concrete/Bridgehub/experimental_bridge.t.sol +++ b/l1-contracts/test/foundry/l1/unit/concrete/Bridgehub/experimental_bridge.t.sol @@ -439,9 +439,10 @@ contract ExperimentalBridgeTest is Test { ); if (randomAddress != address(testTokenAddress)) { + assetId = DataEncoding.encodeNTVAssetId(block.chainid, address(randomAddress)); + vm.assume(!bridgeHub.assetIdIsRegistered(assetId)); // Testing to see if a random address can also be added or not vm.prank(bridgeOwner); - assetId = DataEncoding.encodeNTVAssetId(block.chainid, address(randomAddress)); bridgeHub.addTokenAssetId(assetId); assertTrue(bridgeHub.assetIdIsRegistered(assetId)); } @@ -787,7 +788,6 @@ contract ExperimentalBridgeTest is Test { mockCTM.createNewChain.selector, chainId, tokenAssetId, - sharedBridgeAddress, admin, mockInitCalldata, factoryDeps diff --git a/l1-contracts/test/foundry/l1/unit/concrete/Bridges/L1SharedBridge/L1SharedBridgeFails.t.sol b/l1-contracts/test/foundry/l1/unit/concrete/Bridges/L1SharedBridge/L1SharedBridgeFails.t.sol index 0131721a0..0603a34dc 100644 --- a/l1-contracts/test/foundry/l1/unit/concrete/Bridges/L1SharedBridge/L1SharedBridgeFails.t.sol +++ b/l1-contracts/test/foundry/l1/unit/concrete/Bridges/L1SharedBridge/L1SharedBridgeFails.t.sol @@ -8,6 +8,7 @@ import {L1AssetRouterTest} from "./_L1SharedBridge_Shared.t.sol"; import {TransparentUpgradeableProxy} from "@openzeppelin/contracts-v4/proxy/transparent/TransparentUpgradeableProxy.sol"; import {IERC20} from "@openzeppelin/contracts-v4/token/ERC20/IERC20.sol"; +import {SET_ASSET_HANDLER_COUNTERPART_ENCODING_VERSION} from "contracts/bridge/asset-router/IAssetRouterBase.sol"; import {L1AssetRouter} from "contracts/bridge/asset-router/L1AssetRouter.sol"; import {L1NativeTokenVault} from "contracts/bridge/ntv/L1NativeTokenVault.sol"; import {ETH_TOKEN_ADDRESS} from "contracts/common/Config.sol"; @@ -20,7 +21,7 @@ import {INativeTokenVault} from "contracts/bridge/ntv/INativeTokenVault.sol"; import {L1NativeTokenVault} from "contracts/bridge/ntv/L1NativeTokenVault.sol"; import {L2_BASE_TOKEN_SYSTEM_CONTRACT_ADDR} from "contracts/common/L2ContractAddresses.sol"; import {IGetters} from "contracts/state-transition/chain-interfaces/IGetters.sol"; -import {AddressAlreadyUsed, WithdrawFailed, Unauthorized, AssetIdNotSupported, SharedBridgeKey, SharedBridgeValueNotSet, L2WithdrawalMessageWrongLength, InsufficientChainBalance, ZeroAddress, ValueMismatch, NonEmptyMsgValue, DepositExists, ValueMismatch, NonEmptyMsgValue, TokenNotSupported, EmptyDeposit, L2BridgeNotDeployed, DepositIncorrectAmount, InvalidProof, NoFundsTransferred, InsufficientFunds, DepositDoesNotExist, WithdrawalAlreadyFinalized, InsufficientFunds, MalformedMessage, InvalidSelector, TokensWithFeesNotSupported} from "contracts/common/L1ContractErrors.sol"; +import {AddressAlreadyUsed, WithdrawFailed, Unauthorized, AssetIdNotSupported, SharedBridgeKey, SharedBridgeValueNotSet, L2WithdrawalMessageWrongLength, InsufficientChainBalance, ZeroAddress, ValueMismatch, NonEmptyMsgValue, DepositExists, ValueMismatch, NonEmptyMsgValue, TokenNotSupported, EmptyDeposit, L2BridgeNotDeployed, InvalidProof, NoFundsTransferred, InsufficientFunds, DepositDoesNotExist, WithdrawalAlreadyFinalized, InsufficientFunds, MalformedMessage, InvalidSelector, TokensWithFeesNotSupported} from "contracts/common/L1ContractErrors.sol"; import {StdStorage, stdStorage} from "forge-std/Test.sol"; /// We are testing all the specified revert and require cases. @@ -96,37 +97,23 @@ contract L1AssetRouterFailTest is L1AssetRouterTest { sharedBridge.setNativeTokenVault(INativeTokenVault(address(0))); } - // function test_setAssetHandlerAddressOnCounterpart_notOwnerOrADT() public { - // uint256 l2TxGasLimit = 100000; - // uint256 l2TxGasPerPubdataByte = 100; - // address refundRecipient = address(0); - - // vm.prank(alice); - // vm.expectRevert("L1N: only ADT or owner"); - // sharedBridge.setAssetHandlerAddressOnCounterpart( - // eraChainId, - // mintValue, - // l2TxGasLimit, - // l2TxGasPerPubdataByte, - // refundRecipient, - // tokenAssetId, - // address(token) - // ); - // } - - // function test_transferFundsToSharedBridge_Eth_CallFailed() public { - // vm.mockCall(address(nativeTokenVault), "0x", abi.encode("")); - // vm.prank(address(nativeTokenVault)); - // vm.expectRevert("L1N: eth transfer failed"); - // nativeTokenVault.transferFundsFromSharedBridge(ETH_TOKEN_ADDRESS); - // } - - // function test_transferFundsToSharedBridge_Eth_CallFailed() public { - // vm.mockCall(address(nativeTokenVault), "0x", abi.encode("")); - // vm.prank(address(nativeTokenVault)); - // vm.expectRevert("L1N: eth transfer failed"); - // nativeTokenVault.transferFundsFromSharedBridge(ETH_TOKEN_ADDRESS); - // } + function test_setAssetHandlerAddressOnCounterpart_wrongCounterPartAddress() public { + bytes memory data = bytes.concat( + SET_ASSET_HANDLER_COUNTERPART_ENCODING_VERSION, + abi.encode(tokenAssetId, address(token)) + ); + + vm.prank(bridgehubAddress); + vm.expectRevert("NTV: wrong counterpart"); + sharedBridge.bridgehubDeposit(eraChainId, owner, 0, data); + } + + function test_transferFundsToSharedBridge_Eth_CallFailed() public { + vm.mockCallRevert(address(nativeTokenVault), "", "eth transfer failed"); + vm.prank(address(nativeTokenVault)); + vm.expectRevert("L1N: eth transfer failed"); + l1Nullifier.transferTokenToNTV(ETH_TOKEN_ADDRESS); + } function test_transferFundsToSharedBridge_Eth_0_AmountTransferred() public { vm.deal(address(l1Nullifier), 0); @@ -155,12 +142,12 @@ contract L1AssetRouterFailTest is L1AssetRouterTest { sharedBridge.bridgehubDepositBaseToken{value: amount}(chainId, ETH_TOKEN_ASSET_ID, alice, amount); } - // function test_bridgehubDepositBaseToken_EthwrongMsgValue() public { - // vm.deal(bridgehubAddress, amount); - // vm.prank(bridgehubAddress); - // vm.expectRevert(abi.encodeWithSelector(ValueMismatch.selector, amount, uint256(1))); - // sharedBridge.bridgehubDepositBaseToken(chainId, ETH_TOKEN_ASSET_ID, alice, amount); - // } + function test_bridgehubDepositBaseToken_EthwrongMsgValue() public { + vm.deal(bridgehubAddress, amount); + vm.prank(bridgehubAddress); + vm.expectRevert(abi.encodeWithSelector(ValueMismatch.selector, amount, uint256(1))); + sharedBridge.bridgehubDepositBaseToken{value: 1}(chainId, ETH_TOKEN_ASSET_ID, alice, amount); + } function test_bridgehubDepositBaseToken_ErcWrongMsgValue() public { vm.deal(bridgehubAddress, amount); @@ -199,18 +186,18 @@ contract L1AssetRouterFailTest is L1AssetRouterTest { sharedBridge.bridgehubDeposit(chainId, alice, 0, abi.encode(ETH_TOKEN_ADDRESS, 0, bob)); } - // function test_bridgehubDeposit_Eth_wrongDepositAmount() public { - // _setBaseTokenAssetId(tokenAssetId); - // vm.prank(bridgehubAddress); - // vm.mockCall( - // bridgehubAddress, - // abi.encodeWithSelector(IBridgehub.baseTokenAssetId.selector), - // abi.encode(tokenAssetId) - // ); - // vm.expectRevert(abi.encodeWithSelector(DepositIncorrectAmount.selector, 0, amount)); - // // solhint-disable-next-line func-named-parameters - // sharedBridge.bridgehubDeposit(chainId, alice, 0, abi.encode(ETH_TOKEN_ADDRESS, amount, bob)); - // } + function test_bridgehubDeposit_Eth_wrongDepositAmount() public { + _setBaseTokenAssetId(tokenAssetId); + vm.prank(bridgehubAddress); + vm.mockCall( + bridgehubAddress, + abi.encodeWithSelector(IBridgehub.baseTokenAssetId.selector), + abi.encode(tokenAssetId) + ); + vm.expectRevert(abi.encodeWithSelector(ValueMismatch.selector, amount, 0)); + // solhint-disable-next-line func-named-parameters + sharedBridge.bridgehubDeposit(chainId, alice, 0, abi.encode(ETH_TOKEN_ADDRESS, amount, bob)); + } function test_bridgehubDeposit_Erc_msgValue() public { vm.prank(bridgehubAddress); @@ -623,27 +610,31 @@ contract L1AssetRouterFailTest is L1AssetRouterTest { }); } - // function test_finalizeWithdrawal_TokenOnEth_legacyUpgradeFirstBatchNotSet() public { - // vm.store(address(sharedBridge), bytes32(isWithdrawalFinalizedStorageLocation - 6), bytes32(uint256(0))); - // vm.deal(address(sharedBridge), amount); - - // bytes memory message = abi.encodePacked( - // IL1ERC20Bridge.finalizeWithdrawal.selector, - // alice, - // address(token), - // amount - // ); - - // vm.expectRevert(abi.encodeWithSelector(SharedBridgeValueNotSet.selector, SharedBridgeKey.PostUpgradeFirstBatch)); - // sharedBridge.finalizeWithdrawal({ - // _chainId: eraChainId, - // _l2BatchNumber: l2BatchNumber, - // _l2MessageIndex: l2MessageIndex, - // _l2TxNumberInBatch: l2TxNumberInBatch, - // _message: message, - // _merkleProof: merkleProof - // }); - // } + function test_finalizeWithdrawal_TokenOnEth_legacyUpgradeFirstBatchNotSet() public { + vm.store(address(l1Nullifier), bytes32(isWithdrawalFinalizedStorageLocation - 7), bytes32(uint256(0))); + vm.deal(address(nativeTokenVault), amount); + + bytes memory message = abi.encodePacked( + IL1ERC20Bridge.finalizeWithdrawal.selector, + alice, + address(token), + amount + ); + + vm.mockCall(bridgehubAddress, abi.encode(IBridgehub.proveL2MessageInclusion.selector), abi.encode(true)); + + vm.expectRevert( + abi.encodeWithSelector(SharedBridgeValueNotSet.selector, SharedBridgeKey.PostUpgradeFirstBatch) + ); + sharedBridge.finalizeWithdrawal({ + _chainId: eraChainId, + _l2BatchNumber: l2BatchNumber, + _l2MessageIndex: l2MessageIndex, + _l2TxNumberInBatch: l2TxNumberInBatch, + _message: message, + _merkleProof: merkleProof + }); + } function test_finalizeWithdrawal_chainBalance() public { bytes memory message = abi.encodePacked(IMailbox.finalizeEthWithdrawal.selector, alice, amount); diff --git a/l1-contracts/test/foundry/l1/unit/concrete/DiamondCut/UpgradeLogic.t.sol b/l1-contracts/test/foundry/l1/unit/concrete/DiamondCut/UpgradeLogic.t.sol index 4645bcb2b..8823f20b9 100644 --- a/l1-contracts/test/foundry/l1/unit/concrete/DiamondCut/UpgradeLogic.t.sol +++ b/l1-contracts/test/foundry/l1/unit/concrete/DiamondCut/UpgradeLogic.t.sol @@ -84,7 +84,6 @@ contract UpgradeLogicTest is DiamondCutTest { admin: admin, validatorTimelock: makeAddr("validatorTimelock"), baseTokenAssetId: DataEncoding.encodeNTVAssetId(1, (makeAddr("baseToken"))), - baseTokenBridge: makeAddr("baseTokenBridge"), storedBatchZero: bytes32(0), // genesisBatchHash: 0x02c775f0a90abf7a0e8043f2fdc38f0580ca9f9996a895d05a501bfeaa3b2e21, // genesisIndexRepeatedStorageChanges: 0, diff --git a/l1-contracts/test/foundry/l1/unit/concrete/Executor/ExecutorProof.t.sol b/l1-contracts/test/foundry/l1/unit/concrete/Executor/ExecutorProof.t.sol index af6e9f3a5..9f4530cc4 100644 --- a/l1-contracts/test/foundry/l1/unit/concrete/Executor/ExecutorProof.t.sol +++ b/l1-contracts/test/foundry/l1/unit/concrete/Executor/ExecutorProof.t.sol @@ -72,60 +72,55 @@ contract ExecutorProofTest is Test { utilsFacet = UtilsFacet(diamondProxy); } // todo - /// This test is based on a block generated in a local system. - // function test_Hashes() public { - // utilsFacet.util_setL2DefaultAccountBytecodeHash( - // 0x0100065d134a862a777e50059f5e0fbe68b583f3617a67820f7edda0d7f253a0 - // ); - // utilsFacet.util_setL2BootloaderBytecodeHash(0x010009416e909e0819593a9806bbc841d25c5cdfed3f4a1523497c6814e5194a); - // utilsFacet.util_setZkPorterAvailability(false); - - // IExecutor.CommitBatchInfo memory nextBatch = IExecutor.CommitBatchInfo({ - // // ignored - // batchNumber: 1, - // // ignored - // timestamp: 100, - // indexRepeatedStorageChanges: 84, - // newStateRoot: 0x9cf7bb72401a56039ca097cabed20a72221c944ed9b0e515c083c04663ab45a6, - // // ignored - // numberOfLayer1Txs: 10, - // // ignored - // priorityOperationsHash: 0x167f4ca80269c9520ad951eeeda28dd3deb0715e9e2917461e81a60120a14183, - // bootloaderHeapInitialContentsHash: 0x540442e48142fa061a81822184f7790e7b69dea92153d38ef623802c6f0411c0, - // eventsQueueStateHash: 0xda42ab7994d4695a25f4ea8a9a485a592b7a31c20d5dae6363828de86d8826ea, - // systemLogs: abi.encodePacked( - // hex"00000000000000000000000000000000000000000000800b000000000000000000000000000000000000000000000000000000000000000416914ac26bb9cafa0f1dfaeaab10745a9094e1b60c7076fedf21651d6a25b5740000000a000000000000000000000000000000000000800b0000000000000000000000000000000000000000000000000000000000000003000000000000000000000000651bcde0000000000000000000000000651bcde20001000a00000000000000000000000000000000000080010000000000000000000000000000000000000000000000000000000000000005167f4ca80269c9520ad951eeeda28dd3deb0715e9e2917461e81a60120a141830001000a00000000000000000000000000000000000080010000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000a0001000a00000000000000000000000000000000000080080000000000000000000000000000000000000000000000000000000000000000ee6ee8f50659bd8be3d86c32efb02baa5571cf3b46dd7ea3db733ae181747b8b0001000a0000000000000000000000000000000000008008000000000000000000000000000000000000000000000000000000000000000160fc5fb513ca8e6f6232a7410797954dcb6edbf9081768da24b483aca91c54db0001000a000000000000000000000000000000000000800800000000000000000000000000000000000000000000000000000000000000029a67073c2df8f53087fcfc32d82c98bba591da35df6ce1fb55a23b677d37f9fc000000000000000000000000000000000000000000008011000000000000000000000000000000000000000000000000000000000000000700000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000080110000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000801100000000000000000000000000000000000000000000000000000000000000090000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008011000000000000000000000000000000000000000000000000000000000000000a0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008011000000000000000000000000000000000000000000000000000000000000000b0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008011000000000000000000000000000000000000000000000000000000000000000c0000000000000000000000000000000000000000000000000000000000000000" - // ), - // operatorDAInput: abi.encodePacked( - // hex"000000000a000100000000000000000000000000000000000000008001760f6100ddbd86c4d5a58532923e7424d33ffb44145a26171d9b2595a349450b0000000000000000000000000000000000000000000000000000000000000001000100010000000000000000000000000000000000008001a789fe4e2a955eee45d44f408f86203c8f643910bf4888d1fd1465cdbc6376d800000000000000000000000000000000000000000000000000000000000000010001000200000000000000000000000000000000000080016ba43e7c7df11e5a655f22c9bce1b37434afd2bf8fcdb10100a460e6a2c0cc83000000000000000000000000000000000000000000000000000000000000000100010003000000000000000000000000000000000000800156e569838658c17c756aa9f6e40de8f1c41b1a67fea5214ec47869882ecda9bd0000000000000000000000000000000000000000000000000000000000000001000100040000000000000000000000000000000000008001ab5d064ba75c02635fd6e4de7fd8420eda54c4bda05bd61edabe201f2066d38f00000000000000000000000000000000000000000000000000000000000000010001000500000000000000000000000000000000000080015bcb6d7c735023e0884297db5016a6c704e3490ed0671417639313ecea86795b00000000000000000000000000000000000000000000000000000000000000010001000600000000000000000000000000000000000080015ee51b5b7d47fae5811a9f777174bb08d81d78098c8bd9430a7618756a0ceb8b00000000000000000000000000000000000000000000000000000000000000010001000700000000000000000000000000000000000080011ea63171021b9ab0846efbe0a06f7882d76e24a4900c74c14fa1e0bdf313ed560000000000000000000000000000000000000000000000000000000000000001000100080000000000000000000000000000000000008001574537f1665cd9c894d8d9834d32ed291f49ae1165a0e12a79a4937f2425bf70000000000000000000000000000000000000000000000000000000000000000100010009000000000000000000000000000000000000800190558033c8a3f7c20c81e613e00a9d0e678a7a14923e94e7cb99c8621c7918090000000000000000000000000000000000000000000000000000000000000001000000000000000001000c3104003d1291725c657fe486d0e626f562842175a705a9704c0980b40e3d716b95bbf9e8000100005dd96deb789fbc05264165795bf652190645bfae1ce253ce1db17087a898fb1e240ebf0d53563011198fddab33312923ba20f3c56cf1ba18ca5be9c053000100022bd65a924da61271d1dd5080fc640601185125830805e0ceb42f4185e5118fb454a12a3d9e0c1fbb89230f67044cc191e4f18459261233f659c9e2ba5e000100008b9feb52993729436da78b2863dd56d8d757e19c01a2cdcf1940e45ca9979941fa93f5f699afeab75e8b25cfea22004a8d2ea49f057741c2f2b910996d00010001bdf9205fb9bd185829f2c6bec2a6f100b86eff579da4fc2a8f1a15ea4afee3cea48e96b9bddb544b4569e60736a1f1fe919e223fcc08f74acf3513be1200010001bdf9205fb9bd185829f2c6bec2a6f100b86eff579da4fc2a8f1a15ea4a8755061217b6a78f5d5f8af6e326e482ebdc57f7144108662d122252ddcc27e7000100045dddc527887dc39b9cd189d6f183f16217393a5d3d3165fead2daeaf4f2d6916280c572561a809555de4a87d7a56d5bcca2c246a389dbb2a24c5639bdb0001000153c0f36532563ba2a10f52b865e558cd1a5eef9a9edd01c1cb23b74aa772beb4f3e3b784609f4e205a09863c0587e63b4b47664022cb34896a1711416b00010003e7842b0b4f4fd8e665883fe9c158ba8d38347840f1da0a75aca1fc284ce2428454b48df9f5551500fc50b63af4741b1cd21d4cfddc69aa46cb78eff45b00010000f183703a165afed04326ad5786316f6fc65b27f1cf17459a52bd1f57f27f896b7429e070ca76e3e33165ec75f6c9f439ee37f3b58822494b1251c8247500010001bdf9205fb9bd185829f2c6bec2a6f100b86eff579da4fc2a8f1a15ea4a05ea3d0bb218598c42b2e25ae5f6cbc9369b273ee6610450cade89775646b2a08902000000000000000000000000000000008b71d4a184058d07fccac4348ae02a1f663403231b0a40fa2c8c0ff73bdca092890200000000000000000000000000000000ab63c4cebbd508a7d7184f0b9134453eea7a09ca749610d5576f8046241b9cde890200000000000000000000000000000000e58af14be53d8ac56f58ff3e5b07c239bfb549149f067597e9d028f35e3c2b77890200000000000000000000000000000000b78e94980fec3a5f68aa25d0d934084907688e537e82c2942af905aab21413ab890200000000000000000000000000000000c4db460819691e825328b532024bbecdc40394c74307a00bd245fc658b1bd34f0901908827f2052a14b24a10cae1f9e259ead06a89a1d74ff736a54f54ebcf05eeb30901d32d07305b87debd25698d4dfac4c2f986693a4e9d9baff7da37a7b5ca8d01cb0901e73042e5dacff2ce20a720c9c6d694576e4afa7bbbafdc4d409c63b7ca8027b70901760a7405795441aceea3be649a53d02785cb3487b7bd23e3b4888a935cee010d09011f3acf5d6d7bfeab8a7112771866e28c3714e0c315a81ec6a58ab4ad1c3d6eb10901c207b49d14deb3af9bc960d57074e27386285c73248abc5fa1d72aa6e8664fa40901644f0c4e15446d7e5ff363c944b55bd6801a1f38afd984c3427569530cb663210901743be0243628b8e7e8f04c00fc4f88efae001250a7482b31e6a0ec87ee3598e7090171e91721f9918576d760f02f03cac47c6f4003316031848e3c1d99e6e83a47434102d84e69f2f480002d5a6962cccee5d4adb48a36bbbf443a531721484381125937f3001ac5ff875b41022f496efbbdb2007b727eb806c926fb20c8ad087c57422977cebd06373e26d19b640e5fe32c85ff39a904faf736ce00a25420c1d9d705358c134cc601d9d184cb4dfdde7e1cac2bc3d4d38bf9ec44e6210ee6b280123bafc586f77764488cd24c6a77546e5a0fe8bdfb4fa203cfaffc36cce4dd5b8901000000000000000000000000651bcde08e7dd06ac5b73b473be6bc5a51030f4c7437657cb7b29bf376c564b8d1675a5e8903000000000000000000000000651bcde24ba84e1f37d041bc6e55ba396826cc494e84d4815b6db52690422eea7386314f00e8e77626586f73b955364c7b4bbf0bb7f7685ebd40e852b164633a4acbd3244c3de2202ccb626ad387d70722e64fbe44562e2f231a290c08532b8d6aba402ff50025fe002039e87b424de2772b82d935f14e2b657429a1bcc04612391ea0330c90ebddefdda48eb2aa7f66ecf7940a280e9ef3fb2e95db0995538440a62af79861004434720529e816fd2e40f8031a8d7471ebcd00351db0787346bcfe8dfad8d2b479093588d0e847efa73a10ce20e4799fb1e46642d65617c7e5213fa04989d92d8903000000000000000000000000651bcde287ded247e1660f827071c7f1371934589751085384fc9f4462c1f1897c5c3eef890100000000000000000000000000000001911dd2ad743ff237d411648a0fe32c6d74eec060716a2a74352f6b1c435b5d670016914ac26bb9cafa0f1dfaeaab10745a9094e1b60c7076fedf21651d6a25b574686a068c708f1bdbefd9e6e454ac2b520fd41c8dcf23ecd4cee978c22f1c1f5f09ff974fe8b575175cefa919a5ba1c0ddf4409be4b16695dc7bd12f6701b99bd2e70a152312ad6f01657413b2eae9287f6b9adad93d5fed1a0dd5e13ec74ce1163146509bfe426f2315a69cb452bf388cccd321eca2746a1adf793b489e5c8f61c40688b7ef3e53defc56c78facf513e511f9f5ba0eb50dbcc745afea3b860da75b394d2d1627b6e2ef54fb7b187d0af61e4532c238f387ecf9f0b466f1d54414100018e519b65c8901b344a480638beadb923fbd3462e475d39acebe559d65ed5cb11a1b25279f1918477c35eec1332ff07001d3f85cf854b70d7552f93ba8e88d581064ca4c0df6ac456c00a0e83898ccd464c63e5008aa1a498cc0646b78eb216d9eeeec76ed0eb0ee6c352f35ca5f0b2edc2ca17d211cc5cb905ba10142f042a6ac836d9cef9a6916635c9a1c1d2dc62a9fe83e2230b506b98e0fded46249008fe28b813907a05ae0d773d8f31e330200e9336e0159034c137ed645fb67ccca8a152312ad6f01657413b2eae9287f6b9adad93d5fee5d8f810abde496ccbeb45a4f3c06af828975163a006257cbf18cefebbfb4cd409025f40404a3d37bba024799ce32d7c2a833aec8474288a26b246afa32b07b4a3ce00577261707065642045746865720000000000000000000000000000000000001a09cf14f266dfe87c4b33e6d934de01f8f7242199fa8783178117218fa033f7ab005745544800000000000000000000000000000000000000000000000000000008289026c5fa173652bd62774824698a6848c63031f853d0e275174552f35df33000577261707065642045746865720000000000000000000000000000000000001a1e59309944cbc900ae848855e10bc929f78e86c2179d6e96cf52bfd520f039200031000000000000000000000000000000000000000000000000000000000000021653a735395136e5494c5426ba972b45e34d36ebcb86ac104c724ab375fcce90a18580ba6aeebc6e6b89d226c79be8927257a436ad11d9c0305b18e9d78cab8f75a3aec2096302b67e3815939e29476fb36a0d8299a1b25279f1918477c35eec1332ff07001d3f85cf85688525f98e4859a9c6939f2d2f92e6b1950ed57e56137d717aca1ccf9754f719a1c7ebe9226d26524400a8959a08f411a727ae7bb68f8febecd89ffe9d84708d24544d452de3e22e62b3b2b872e430839a15115818a152312ad6f01657413b2eae9287f6b9adad93d5fe3fb60af355125687beeb90c066ace76c442b0f963a6afd0e3316fcdd673ad22c09ff30c8a03ec44e5337a1f9d66763cf1b319fdc6d8bc4981e1f47edbd86210614b909ff0cbdceb634b81192417b64d114d535ad3bdba97d6d7e90ee2a79bf1c132d3c2d09ff5cd85060f4ff26eb5b68a6687aee76c1b7a77575fdc86ba49b4faf5041377a79b14de8989f2385a6e23f6bd05a80e0d9231870c15a000142e50adc0d84bff439d0086d9fbab9984f8b27aa208935238a60cc62e7c9bb2ea1709e94c96366b3c40ea4854837c18733e5ac1193b8d8e4070d2eca4441b0378b572bd949ab764fd71c002b759613c3e29d425cf4000100012730c940a81021004e899c6ee4bec02f0667757b9d75a8f0714ce6c157f5940b7664e4f69f01fc530db36965e33599a1348629f07ae2d724007ac36a71a16baac84db583d88e0f3a8c082e3632fcc0e15757f0dcf5234b87af41fdee4c0999c4fe698a8d824415979ab839e6913a975a3055a152312ad6f01657413b2eae9287f6b9adad93d5fe00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000" - // ) - // }); - // LogProcessingOutput memory logOutput = executor.processL2Logs( - // nextBatch, - // 0x0000000000000000000000000000000000000000000000000000000000000000 - // ); - // assertEq( - // logOutput.stateDiffHash, - // 0x9a67073c2df8f53087fcfc32d82c98bba591da35df6ce1fb55a23b677d37f9fc, - // "stateDiffHash computation failed" - // ); - - // bytes32 nextCommitment = executor.createBatchCommitment( - // nextBatch, - // logOutput.stateDiffHash, - // new bytes32[](6), - // new bytes32[](6) - // ); - // assertEq( - // nextCommitment, - // 0xa1dcde434352cda8e331e721232ff2d457d4074efae1e3d06ef5b10ffada0c9a, - // "nextCommitment computation failed" - // ); - - // bytes32 prevCommitment = 0x6ebf945305689a8c3ac993df7f002d41d311a762cd6bf39bb054ead8d1f54404; - // uint256 result = executor.getBatchProofPublicInput(prevCommitment, nextCommitment); - // assertEq(result, 0xAC7931F2C11013FC24963E41B86E5325A79F1150350CB41E4F0876A7, "getBatchProofPublicInput"); - // } + // This test is based on a block generated in a local system. + function test_Hashes() public { + utilsFacet.util_setL2DefaultAccountBytecodeHash( + 0x0100065d134a862a777e50059f5e0fbe68b583f3617a67820f7edda0d7f253a0 + ); + utilsFacet.util_setL2BootloaderBytecodeHash(0x010009416e909e0819593a9806bbc841d25c5cdfed3f4a1523497c6814e5194a); + utilsFacet.util_setZkPorterAvailability(false); + + bytes[] memory mockSystemLogs = Utils.createSystemLogsWithEmptyDAValidator(); + + IExecutor.CommitBatchInfo memory nextBatch = IExecutor.CommitBatchInfo({ + // ignored + batchNumber: 1, + // ignored + timestamp: 100, + indexRepeatedStorageChanges: 84, + newStateRoot: 0x9cf7bb72401a56039ca097cabed20a72221c944ed9b0e515c083c04663ab45a6, + // ignored + numberOfLayer1Txs: 10, + // ignored + priorityOperationsHash: 0x167f4ca80269c9520ad951eeeda28dd3deb0715e9e2917461e81a60120a14183, + bootloaderHeapInitialContentsHash: 0x540442e48142fa061a81822184f7790e7b69dea92153d38ef623802c6f0411c0, + eventsQueueStateHash: 0xda42ab7994d4695a25f4ea8a9a485a592b7a31c20d5dae6363828de86d8826ea, + systemLogs: Utils.encodePacked(mockSystemLogs), + operatorDAInput: abi.encodePacked( + hex"000000000a000100000000000000000000000000000000000000008001760f6100ddbd86c4d5a58532923e7424d33ffb44145a26171d9b2595a349450b0000000000000000000000000000000000000000000000000000000000000001000100010000000000000000000000000000000000008001a789fe4e2a955eee45d44f408f86203c8f643910bf4888d1fd1465cdbc6376d800000000000000000000000000000000000000000000000000000000000000010001000200000000000000000000000000000000000080016ba43e7c7df11e5a655f22c9bce1b37434afd2bf8fcdb10100a460e6a2c0cc83000000000000000000000000000000000000000000000000000000000000000100010003000000000000000000000000000000000000800156e569838658c17c756aa9f6e40de8f1c41b1a67fea5214ec47869882ecda9bd0000000000000000000000000000000000000000000000000000000000000001000100040000000000000000000000000000000000008001ab5d064ba75c02635fd6e4de7fd8420eda54c4bda05bd61edabe201f2066d38f00000000000000000000000000000000000000000000000000000000000000010001000500000000000000000000000000000000000080015bcb6d7c735023e0884297db5016a6c704e3490ed0671417639313ecea86795b00000000000000000000000000000000000000000000000000000000000000010001000600000000000000000000000000000000000080015ee51b5b7d47fae5811a9f777174bb08d81d78098c8bd9430a7618756a0ceb8b00000000000000000000000000000000000000000000000000000000000000010001000700000000000000000000000000000000000080011ea63171021b9ab0846efbe0a06f7882d76e24a4900c74c14fa1e0bdf313ed560000000000000000000000000000000000000000000000000000000000000001000100080000000000000000000000000000000000008001574537f1665cd9c894d8d9834d32ed291f49ae1165a0e12a79a4937f2425bf70000000000000000000000000000000000000000000000000000000000000000100010009000000000000000000000000000000000000800190558033c8a3f7c20c81e613e00a9d0e678a7a14923e94e7cb99c8621c7918090000000000000000000000000000000000000000000000000000000000000001000000000000000001000c3104003d1291725c657fe486d0e626f562842175a705a9704c0980b40e3d716b95bbf9e8000100005dd96deb789fbc05264165795bf652190645bfae1ce253ce1db17087a898fb1e240ebf0d53563011198fddab33312923ba20f3c56cf1ba18ca5be9c053000100022bd65a924da61271d1dd5080fc640601185125830805e0ceb42f4185e5118fb454a12a3d9e0c1fbb89230f67044cc191e4f18459261233f659c9e2ba5e000100008b9feb52993729436da78b2863dd56d8d757e19c01a2cdcf1940e45ca9979941fa93f5f699afeab75e8b25cfea22004a8d2ea49f057741c2f2b910996d00010001bdf9205fb9bd185829f2c6bec2a6f100b86eff579da4fc2a8f1a15ea4afee3cea48e96b9bddb544b4569e60736a1f1fe919e223fcc08f74acf3513be1200010001bdf9205fb9bd185829f2c6bec2a6f100b86eff579da4fc2a8f1a15ea4a8755061217b6a78f5d5f8af6e326e482ebdc57f7144108662d122252ddcc27e7000100045dddc527887dc39b9cd189d6f183f16217393a5d3d3165fead2daeaf4f2d6916280c572561a809555de4a87d7a56d5bcca2c246a389dbb2a24c5639bdb0001000153c0f36532563ba2a10f52b865e558cd1a5eef9a9edd01c1cb23b74aa772beb4f3e3b784609f4e205a09863c0587e63b4b47664022cb34896a1711416b00010003e7842b0b4f4fd8e665883fe9c158ba8d38347840f1da0a75aca1fc284ce2428454b48df9f5551500fc50b63af4741b1cd21d4cfddc69aa46cb78eff45b00010000f183703a165afed04326ad5786316f6fc65b27f1cf17459a52bd1f57f27f896b7429e070ca76e3e33165ec75f6c9f439ee37f3b58822494b1251c8247500010001bdf9205fb9bd185829f2c6bec2a6f100b86eff579da4fc2a8f1a15ea4a05ea3d0bb218598c42b2e25ae5f6cbc9369b273ee6610450cade89775646b2a08902000000000000000000000000000000008b71d4a184058d07fccac4348ae02a1f663403231b0a40fa2c8c0ff73bdca092890200000000000000000000000000000000ab63c4cebbd508a7d7184f0b9134453eea7a09ca749610d5576f8046241b9cde890200000000000000000000000000000000e58af14be53d8ac56f58ff3e5b07c239bfb549149f067597e9d028f35e3c2b77890200000000000000000000000000000000b78e94980fec3a5f68aa25d0d934084907688e537e82c2942af905aab21413ab890200000000000000000000000000000000c4db460819691e825328b532024bbecdc40394c74307a00bd245fc658b1bd34f0901908827f2052a14b24a10cae1f9e259ead06a89a1d74ff736a54f54ebcf05eeb30901d32d07305b87debd25698d4dfac4c2f986693a4e9d9baff7da37a7b5ca8d01cb0901e73042e5dacff2ce20a720c9c6d694576e4afa7bbbafdc4d409c63b7ca8027b70901760a7405795441aceea3be649a53d02785cb3487b7bd23e3b4888a935cee010d09011f3acf5d6d7bfeab8a7112771866e28c3714e0c315a81ec6a58ab4ad1c3d6eb10901c207b49d14deb3af9bc960d57074e27386285c73248abc5fa1d72aa6e8664fa40901644f0c4e15446d7e5ff363c944b55bd6801a1f38afd984c3427569530cb663210901743be0243628b8e7e8f04c00fc4f88efae001250a7482b31e6a0ec87ee3598e7090171e91721f9918576d760f02f03cac47c6f4003316031848e3c1d99e6e83a47434102d84e69f2f480002d5a6962cccee5d4adb48a36bbbf443a531721484381125937f3001ac5ff875b41022f496efbbdb2007b727eb806c926fb20c8ad087c57422977cebd06373e26d19b640e5fe32c85ff39a904faf736ce00a25420c1d9d705358c134cc601d9d184cb4dfdde7e1cac2bc3d4d38bf9ec44e6210ee6b280123bafc586f77764488cd24c6a77546e5a0fe8bdfb4fa203cfaffc36cce4dd5b8901000000000000000000000000651bcde08e7dd06ac5b73b473be6bc5a51030f4c7437657cb7b29bf376c564b8d1675a5e8903000000000000000000000000651bcde24ba84e1f37d041bc6e55ba396826cc494e84d4815b6db52690422eea7386314f00e8e77626586f73b955364c7b4bbf0bb7f7685ebd40e852b164633a4acbd3244c3de2202ccb626ad387d70722e64fbe44562e2f231a290c08532b8d6aba402ff50025fe002039e87b424de2772b82d935f14e2b657429a1bcc04612391ea0330c90ebddefdda48eb2aa7f66ecf7940a280e9ef3fb2e95db0995538440a62af79861004434720529e816fd2e40f8031a8d7471ebcd00351db0787346bcfe8dfad8d2b479093588d0e847efa73a10ce20e4799fb1e46642d65617c7e5213fa04989d92d8903000000000000000000000000651bcde287ded247e1660f827071c7f1371934589751085384fc9f4462c1f1897c5c3eef890100000000000000000000000000000001911dd2ad743ff237d411648a0fe32c6d74eec060716a2a74352f6b1c435b5d670016914ac26bb9cafa0f1dfaeaab10745a9094e1b60c7076fedf21651d6a25b574686a068c708f1bdbefd9e6e454ac2b520fd41c8dcf23ecd4cee978c22f1c1f5f09ff974fe8b575175cefa919a5ba1c0ddf4409be4b16695dc7bd12f6701b99bd2e70a152312ad6f01657413b2eae9287f6b9adad93d5fed1a0dd5e13ec74ce1163146509bfe426f2315a69cb452bf388cccd321eca2746a1adf793b489e5c8f61c40688b7ef3e53defc56c78facf513e511f9f5ba0eb50dbcc745afea3b860da75b394d2d1627b6e2ef54fb7b187d0af61e4532c238f387ecf9f0b466f1d54414100018e519b65c8901b344a480638beadb923fbd3462e475d39acebe559d65ed5cb11a1b25279f1918477c35eec1332ff07001d3f85cf854b70d7552f93ba8e88d581064ca4c0df6ac456c00a0e83898ccd464c63e5008aa1a498cc0646b78eb216d9eeeec76ed0eb0ee6c352f35ca5f0b2edc2ca17d211cc5cb905ba10142f042a6ac836d9cef9a6916635c9a1c1d2dc62a9fe83e2230b506b98e0fded46249008fe28b813907a05ae0d773d8f31e330200e9336e0159034c137ed645fb67ccca8a152312ad6f01657413b2eae9287f6b9adad93d5fee5d8f810abde496ccbeb45a4f3c06af828975163a006257cbf18cefebbfb4cd409025f40404a3d37bba024799ce32d7c2a833aec8474288a26b246afa32b07b4a3ce00577261707065642045746865720000000000000000000000000000000000001a09cf14f266dfe87c4b33e6d934de01f8f7242199fa8783178117218fa033f7ab005745544800000000000000000000000000000000000000000000000000000008289026c5fa173652bd62774824698a6848c63031f853d0e275174552f35df33000577261707065642045746865720000000000000000000000000000000000001a1e59309944cbc900ae848855e10bc929f78e86c2179d6e96cf52bfd520f039200031000000000000000000000000000000000000000000000000000000000000021653a735395136e5494c5426ba972b45e34d36ebcb86ac104c724ab375fcce90a18580ba6aeebc6e6b89d226c79be8927257a436ad11d9c0305b18e9d78cab8f75a3aec2096302b67e3815939e29476fb36a0d8299a1b25279f1918477c35eec1332ff07001d3f85cf85688525f98e4859a9c6939f2d2f92e6b1950ed57e56137d717aca1ccf9754f719a1c7ebe9226d26524400a8959a08f411a727ae7bb68f8febecd89ffe9d84708d24544d452de3e22e62b3b2b872e430839a15115818a152312ad6f01657413b2eae9287f6b9adad93d5fe3fb60af355125687beeb90c066ace76c442b0f963a6afd0e3316fcdd673ad22c09ff30c8a03ec44e5337a1f9d66763cf1b319fdc6d8bc4981e1f47edbd86210614b909ff0cbdceb634b81192417b64d114d535ad3bdba97d6d7e90ee2a79bf1c132d3c2d09ff5cd85060f4ff26eb5b68a6687aee76c1b7a77575fdc86ba49b4faf5041377a79b14de8989f2385a6e23f6bd05a80e0d9231870c15a000142e50adc0d84bff439d0086d9fbab9984f8b27aa208935238a60cc62e7c9bb2ea1709e94c96366b3c40ea4854837c18733e5ac1193b8d8e4070d2eca4441b0378b572bd949ab764fd71c002b759613c3e29d425cf4000100012730c940a81021004e899c6ee4bec02f0667757b9d75a8f0714ce6c157f5940b7664e4f69f01fc530db36965e33599a1348629f07ae2d724007ac36a71a16baac84db583d88e0f3a8c082e3632fcc0e15757f0dcf5234b87af41fdee4c0999c4fe698a8d824415979ab839e6913a975a3055a152312ad6f01657413b2eae9287f6b9adad93d5fe00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000" + ) + }); + LogProcessingOutput memory logOutput = executor.processL2Logs( + nextBatch, + 0x0000000000000000000000000000000000000000000000000000000000000000 + ); + + bytes32 nextCommitment = executor.createBatchCommitment( + nextBatch, + logOutput.stateDiffHash, + new bytes32[](16), + new bytes32[](16) + ); + assertEq( + nextCommitment, + 0x81e46ea22cdb4a0a6cb30b6c02170394703e9bdd101275d542a7c6c23c789898, + "nextCommitment computation failed" + ); + + bytes32 prevCommitment = 0x6ebf945305689a8c3ac993df7f002d41d311a762cd6bf39bb054ead8d1f54404; + uint256 result = executor.getBatchProofPublicInput(prevCommitment, nextCommitment); + assertEq(result, 0x7C854720CBA105B9E34DA6A28770B93AD384C1BF98C497CCBFA4DADB, "getBatchProofPublicInput"); + } // add this to be excluded from coverage report function test() internal {} diff --git a/l1-contracts/test/foundry/l1/unit/concrete/Executor/_Executor_Shared.t.sol b/l1-contracts/test/foundry/l1/unit/concrete/Executor/_Executor_Shared.t.sol index 64ef75b0b..6d331acee 100644 --- a/l1-contracts/test/foundry/l1/unit/concrete/Executor/_Executor_Shared.t.sol +++ b/l1-contracts/test/foundry/l1/unit/concrete/Executor/_Executor_Shared.t.sol @@ -150,6 +150,8 @@ contract ExecutorTest is Test { dummyBridgehub.setMessageRoot(address(messageRoot)); sharedBridge = new DummyEraBaseTokenBridge(); + dummyBridgehub.setSharedBridge(address(sharedBridge)); + vm.mockCall( address(messageRoot), abi.encodeWithSelector(MessageRoot.addChainBatchRoot.selector, 9, 1, bytes32(0)), @@ -197,7 +199,6 @@ contract ExecutorTest is Test { admin: owner, validatorTimelock: validator, baseTokenAssetId: DataEncoding.encodeNTVAssetId(block.chainid, ETH_TOKEN_ADDRESS), - baseTokenBridge: address(sharedBridge), storedBatchZero: keccak256(abi.encode(genesisStoredBatchInfo)), verifier: IVerifier(testnetVerifier), // verifier verifierParams: VerifierParams({ diff --git a/l1-contracts/test/foundry/l1/unit/concrete/Governance/PermanentRestriction.t.sol b/l1-contracts/test/foundry/l1/unit/concrete/Governance/PermanentRestriction.t.sol index f97ecc08a..bcfe6ae2c 100644 --- a/l1-contracts/test/foundry/l1/unit/concrete/Governance/PermanentRestriction.t.sol +++ b/l1-contracts/test/foundry/l1/unit/concrete/Governance/PermanentRestriction.t.sol @@ -1,13 +1,15 @@ pragma solidity 0.8.24; import "@openzeppelin/contracts-v4/utils/Strings.sol"; +import {TransparentUpgradeableProxy} from "@openzeppelin/contracts-v4/proxy/transparent/TransparentUpgradeableProxy.sol"; import {Bridgehub} from "contracts/bridgehub/Bridgehub.sol"; +import {L2TransactionRequestTwoBridgesOuter, BridgehubBurnCTMAssetData} from "contracts/bridgehub/IBridgehub.sol"; import {Diamond} from "contracts/state-transition/libraries/Diamond.sol"; import {ChainTypeManager} from "contracts/state-transition/ChainTypeManager.sol"; import {DiamondInit} from "contracts/state-transition/chain-deps/DiamondInit.sol"; -import {PermanentRestriction} from "contracts/governance/PermanentRestriction.sol"; +import {PermanentRestriction, MIN_GAS_FOR_FALLABLE_CALL} from "contracts/governance/PermanentRestriction.sol"; import {IPermanentRestriction} from "contracts/governance/IPermanentRestriction.sol"; -import {ZeroAddress, ChainZeroAddress, NotAnAdmin, UnallowedImplementation, RemovingPermanentRestriction, CallNotAllowed} from "contracts/common/L1ContractErrors.sol"; +import {NotAllowed, NotEnoughGas, InvalidAddress, UnsupportedEncodingVersion, InvalidSelector, NotBridgehub, ZeroAddress, ChainZeroAddress, NotAnAdmin, UnallowedImplementation, RemovingPermanentRestriction, CallNotAllowed} from "contracts/common/L1ContractErrors.sol"; import {Call} from "contracts/governance/Common.sol"; import {IZKChain} from "contracts/state-transition/chain-interfaces/IZKChain.sol"; import {VerifierParams, FeeParams, PubdataPricingMode} from "contracts/state-transition/chain-deps/ZKChainStorage.sol"; @@ -22,12 +24,16 @@ import {IMessageRoot} from "contracts/bridgehub/IMessageRoot.sol"; import {MessageRoot} from "contracts/bridgehub/MessageRoot.sol"; import {IL1AssetRouter} from "contracts/bridge/asset-router/IL1AssetRouter.sol"; import {IL1Nullifier} from "contracts/bridge/interfaces/IL1Nullifier.sol"; +import {IBridgehub} from "contracts/bridgehub/IBridgehub.sol"; +import {L2ContractHelper} from "contracts/common/libraries/L2ContractHelper.sol"; contract PermanentRestrictionTest is ChainTypeManagerTest { ChainAdmin internal chainAdmin; AccessControlRestriction internal restriction; PermanentRestriction internal permRestriction; + address constant L2_FACTORY_ADDR = address(0); + address internal owner; address internal hyperchain; @@ -36,20 +42,38 @@ contract PermanentRestrictionTest is ChainTypeManagerTest { createNewChainBridgehub(); - vm.stopPrank(); - owner = makeAddr("owner"); hyperchain = chainContractAddress.getHyperchain(chainId); - permRestriction = new PermanentRestriction(owner, bridgehub); + (permRestriction, ) = _deployPermRestriction(bridgehub, L2_FACTORY_ADDR, owner); restriction = new AccessControlRestriction(0, owner); address[] memory restrictions = new address[](1); restrictions[0] = address(restriction); chainAdmin = new ChainAdmin(restrictions); } + function _deployPermRestriction( + IBridgehub _bridgehub, + address _l2AdminFactory, + address _owner + ) internal returns (PermanentRestriction proxy, PermanentRestriction impl) { + impl = new PermanentRestriction(_bridgehub, _l2AdminFactory); + TransparentUpgradeableProxy tup = new TransparentUpgradeableProxy( + address(impl), + address(uint160(1)), + abi.encodeCall(PermanentRestriction.initialize, (_owner)) + ); + + proxy = PermanentRestriction(address(tup)); + } + function test_ownerAsAddressZero() public { + PermanentRestriction impl = new PermanentRestriction(bridgehub, L2_FACTORY_ADDR); vm.expectRevert(ZeroAddress.selector); - permRestriction = new PermanentRestriction(address(0), bridgehub); + new TransparentUpgradeableProxy( + address(impl), + address(uint160(1)), + abi.encodeCall(PermanentRestriction.initialize, (address(0))) + ); } function test_allowAdminImplementation(bytes32 implementationHash) public { @@ -197,6 +221,136 @@ contract PermanentRestrictionTest is ChainTypeManagerTest { vm.stopPrank(); } + function _encodeMigraationCall( + bool correctTarget, + bool correctSelector, + bool correctSecondBridge, + bool correctEncodingVersion, + bool correctAssetId, + address l2Admin + ) internal returns (Call memory call) { + if (!correctTarget) { + call.target = address(0); + return call; + } + call.target = address(bridgehub); + + if (!correctSelector) { + call.data = hex"00000000"; + return call; + } + + L2TransactionRequestTwoBridgesOuter memory outer = L2TransactionRequestTwoBridgesOuter({ + chainId: chainId, + mintValue: 0, + l2Value: 0, + l2GasLimit: 0, + l2GasPerPubdataByteLimit: 0, + refundRecipient: address(0), + secondBridgeAddress: address(0), + secondBridgeValue: 0, + secondBridgeCalldata: hex"" + }); + if (!correctSecondBridge) { + call.data = abi.encodeCall(Bridgehub.requestL2TransactionTwoBridges, (outer)); + // 0 is not correct second bridge + return call; + } + outer.secondBridgeAddress = sharedBridge; + + uint8 encoding = correctEncodingVersion ? 1 : 12; + + bytes32 chainAssetId = correctAssetId ? bridgehub.ctmAssetIdFromChainId(chainId) : bytes32(0); + + bytes memory bridgehubData = abi.encode( + BridgehubBurnCTMAssetData({ + // Gateway chain id, we do not need it + chainId: 0, + ctmData: abi.encode(l2Admin, hex""), + chainData: abi.encode(IZKChain(IBridgehub(bridgehub).getZKChain(chainId)).getProtocolVersion()) + }) + ); + outer.secondBridgeCalldata = abi.encodePacked(bytes1(encoding), abi.encode(chainAssetId, bridgehubData)); + + call.data = abi.encodeCall(Bridgehub.requestL2TransactionTwoBridges, (outer)); + } + + function test_tryGetNewAdminFromMigrationRevertWhenInvalidSelector() public { + Call memory call = _encodeMigraationCall(false, true, true, true, true, address(0)); + + vm.expectRevert(abi.encodeWithSelector(NotBridgehub.selector, address(0))); + permRestriction.tryGetNewAdminFromMigration(call); + } + + function test_tryGetNewAdminFromMigrationRevertWhenNotBridgehub() public { + Call memory call = _encodeMigraationCall(true, false, true, true, true, address(0)); + + vm.expectRevert(abi.encodeWithSelector(InvalidSelector.selector, bytes4(0))); + permRestriction.tryGetNewAdminFromMigration(call); + } + + function test_tryGetNewAdminFromMigrationRevertWhenNotSharedBridge() public { + Call memory call = _encodeMigraationCall(true, true, false, true, true, address(0)); + + vm.expectRevert(abi.encodeWithSelector(InvalidAddress.selector, address(sharedBridge), address(0))); + permRestriction.tryGetNewAdminFromMigration(call); + } + + function test_tryGetNewAdminFromMigrationRevertWhenIncorrectEncoding() public { + Call memory call = _encodeMigraationCall(true, true, true, false, true, address(0)); + + vm.expectRevert(abi.encodeWithSelector(UnsupportedEncodingVersion.selector)); + permRestriction.tryGetNewAdminFromMigration(call); + } + + function test_tryGetNewAdminFromMigrationRevertWhenIncorrectAssetId() public { + Call memory call = _encodeMigraationCall(true, true, true, true, false, address(0)); + + vm.expectRevert(abi.encodeWithSelector(ZeroAddress.selector)); + permRestriction.tryGetNewAdminFromMigration(call); + } + + function test_tryGetNewAdminFromMigrationShouldWorkCorrectly() public { + address l2Addr = makeAddr("l2Addr"); + Call memory call = _encodeMigraationCall(true, true, true, true, true, l2Addr); + + address result = permRestriction.tryGetNewAdminFromMigration(call); + assertEq(result, l2Addr); + } + + function test_validateMigrationToL2RevertNotAllowed() public { + Call memory call = _encodeMigraationCall(true, true, true, true, true, address(0)); + + vm.expectRevert(abi.encodeWithSelector(NotAllowed.selector, address(0))); + permRestriction.validateCall(call, owner); + } + + function test_validateMigrationToL2() public { + address expectedAddress = L2ContractHelper.computeCreate2Address( + L2_FACTORY_ADDR, + bytes32(0), + bytes32(0), + bytes32(0) + ); + + vm.expectEmit(true, false, false, true); + emit IPermanentRestriction.AllowL2Admin(expectedAddress); + permRestriction.allowL2Admin(bytes32(0), bytes32(0), bytes32(0)); + + Call memory call = _encodeMigraationCall(true, true, true, true, true, expectedAddress); + + // Should not fail + permRestriction.validateCall(call, owner); + } + + function test_validateNotEnoughGas() public { + address l2Addr = makeAddr("l2Addr"); + Call memory call = _encodeMigraationCall(true, true, true, true, true, l2Addr); + + vm.expectRevert(abi.encodeWithSelector(NotEnoughGas.selector)); + permRestriction.validateCall{gas: MIN_GAS_FOR_FALLABLE_CALL}(call, address(0)); + } + function createNewChainBridgehub() internal { bytes[] memory factoryDeps = new bytes[](0); vm.stopPrank(); @@ -204,18 +358,23 @@ contract PermanentRestrictionTest is ChainTypeManagerTest { bridgehub.addChainTypeManager(address(chainContractAddress)); bridgehub.addTokenAssetId(DataEncoding.encodeNTVAssetId(block.chainid, baseToken)); bridgehub.setAddresses(sharedBridge, ICTMDeploymentTracker(address(0)), new MessageRoot(bridgehub)); + vm.stopPrank(); + + // ctm deployer address is 0 in this test + vm.startPrank(address(0)); + bridgehub.setAssetHandlerAddress( + bytes32(uint256(uint160(address(chainContractAddress)))), + address(chainContractAddress) + ); + vm.stopPrank(); + address l1Nullifier = makeAddr("l1Nullifier"); - address l2LegacySharedBridge = makeAddr("l2LegacySharedBridge"); vm.mockCall( address(sharedBridge), abi.encodeWithSelector(IL1AssetRouter.L1_NULLIFIER.selector), abi.encode(l1Nullifier) ); - vm.mockCall( - address(l1Nullifier), - abi.encodeWithSelector(IL1Nullifier.l2BridgeAddress.selector), - abi.encode(l2LegacySharedBridge) - ); + vm.startPrank(governor); bridgehub.createNewChain({ _chainId: chainId, _chainTypeManager: address(chainContractAddress), @@ -225,5 +384,6 @@ contract PermanentRestrictionTest is ChainTypeManagerTest { _initData: getCTMInitData(), _factoryDeps: factoryDeps }); + vm.stopPrank(); } } diff --git a/l1-contracts/test/foundry/l1/unit/concrete/Utils/Utils.sol b/l1-contracts/test/foundry/l1/unit/concrete/Utils/Utils.sol index 8ab52c976..3347e19ef 100644 --- a/l1-contracts/test/foundry/l1/unit/concrete/Utils/Utils.sol +++ b/l1-contracts/test/foundry/l1/unit/concrete/Utils/Utils.sol @@ -108,6 +108,18 @@ library Utils { return logs; } + function createSystemLogsWithEmptyDAValidator() public returns (bytes[] memory) { + bytes[] memory systemLogs = createSystemLogs(bytes32(0)); + systemLogs[uint256(SystemLogKey.USED_L2_DA_VALIDATOR_ADDRESS_KEY)] = constructL2Log( + true, + L2_TO_L1_MESSENGER, + uint256(SystemLogKey.USED_L2_DA_VALIDATOR_ADDRESS_KEY), + bytes32(uint256(0)) + ); + + return systemLogs; + } + function createSystemLogsWithUpgradeTransaction( bytes32 _expectedSystemContractUpgradeTxHash ) public returns (bytes[] memory) { @@ -125,6 +137,30 @@ library Utils { return logs; } + function createSystemLogsWithUpgradeTransactionForCTM( + bytes32 _expectedSystemContractUpgradeTxHash, + bytes32 _outputHash + ) public returns (bytes[] memory) { + bytes[] memory logsWithoutUpgradeTx = createSystemLogs(_outputHash); + bytes[] memory logs = new bytes[](logsWithoutUpgradeTx.length + 1); + for (uint256 i = 0; i < logsWithoutUpgradeTx.length; i++) { + logs[i] = logsWithoutUpgradeTx[i]; + } + logs[uint256(SystemLogKey.PREV_BATCH_HASH_KEY)] = constructL2Log( + true, + L2_SYSTEM_CONTEXT_ADDRESS, + uint256(SystemLogKey.PREV_BATCH_HASH_KEY), + bytes32(uint256(0x01)) + ); + logs[logsWithoutUpgradeTx.length] = constructL2Log( + true, + L2_BOOTLOADER_ADDRESS, + uint256(SystemLogKey.EXPECTED_SYSTEM_CONTRACT_UPGRADE_TX_HASH_KEY), + _expectedSystemContractUpgradeTxHash + ); + return logs; + } + function createStoredBatchInfo() public pure returns (IExecutor.StoredBatchInfo memory) { return IExecutor.StoredBatchInfo({ @@ -198,7 +234,7 @@ library Utils { } function getAdminSelectors() public pure returns (bytes4[] memory) { - bytes4[] memory selectors = new bytes4[](12); + bytes4[] memory selectors = new bytes4[](13); selectors[0] = AdminFacet.setPendingAdmin.selector; selectors[1] = AdminFacet.acceptAdmin.selector; selectors[2] = AdminFacet.setValidator.selector; @@ -211,6 +247,7 @@ library Utils { selectors[9] = AdminFacet.freezeDiamond.selector; selectors[10] = AdminFacet.unfreezeDiamond.selector; selectors[11] = AdminFacet.genesisUpgrade.selector; + selectors[12] = AdminFacet.setDAValidatorPair.selector; return selectors; } @@ -272,48 +309,47 @@ library Utils { } function getUtilsFacetSelectors() public pure returns (bytes4[] memory) { - bytes4[] memory selectors = new bytes4[](41); + bytes4[] memory selectors = new bytes4[](39); selectors[0] = UtilsFacet.util_setChainId.selector; selectors[1] = UtilsFacet.util_getChainId.selector; selectors[2] = UtilsFacet.util_setBridgehub.selector; selectors[3] = UtilsFacet.util_getBridgehub.selector; selectors[4] = UtilsFacet.util_setBaseToken.selector; selectors[5] = UtilsFacet.util_getBaseTokenAssetId.selector; - selectors[6] = UtilsFacet.util_setBaseTokenBridge.selector; - selectors[7] = UtilsFacet.util_getBaseTokenBridge.selector; - selectors[8] = UtilsFacet.util_setVerifier.selector; - selectors[9] = UtilsFacet.util_getVerifier.selector; - selectors[10] = UtilsFacet.util_setStoredBatchHashes.selector; - selectors[11] = UtilsFacet.util_getStoredBatchHashes.selector; - selectors[12] = UtilsFacet.util_setVerifierParams.selector; - selectors[13] = UtilsFacet.util_getVerifierParams.selector; - selectors[14] = UtilsFacet.util_setL2BootloaderBytecodeHash.selector; - selectors[15] = UtilsFacet.util_getL2BootloaderBytecodeHash.selector; - selectors[16] = UtilsFacet.util_setL2DefaultAccountBytecodeHash.selector; - selectors[17] = UtilsFacet.util_getL2DefaultAccountBytecodeHash.selector; - selectors[18] = UtilsFacet.util_setPendingAdmin.selector; - selectors[19] = UtilsFacet.util_getPendingAdmin.selector; - selectors[20] = UtilsFacet.util_setAdmin.selector; - selectors[21] = UtilsFacet.util_getAdmin.selector; - selectors[22] = UtilsFacet.util_setValidator.selector; - selectors[23] = UtilsFacet.util_getValidator.selector; - selectors[24] = UtilsFacet.util_setZkPorterAvailability.selector; - selectors[25] = UtilsFacet.util_getZkPorterAvailability.selector; - selectors[26] = UtilsFacet.util_setChainTypeManager.selector; - selectors[27] = UtilsFacet.util_getChainTypeManager.selector; - selectors[28] = UtilsFacet.util_setPriorityTxMaxGasLimit.selector; - selectors[29] = UtilsFacet.util_getPriorityTxMaxGasLimit.selector; - selectors[30] = UtilsFacet.util_setFeeParams.selector; - selectors[31] = UtilsFacet.util_getFeeParams.selector; - selectors[32] = UtilsFacet.util_setProtocolVersion.selector; - selectors[33] = UtilsFacet.util_getProtocolVersion.selector; - selectors[34] = UtilsFacet.util_setIsFrozen.selector; - selectors[35] = UtilsFacet.util_getIsFrozen.selector; - selectors[36] = UtilsFacet.util_setTransactionFilterer.selector; - selectors[37] = UtilsFacet.util_setBaseTokenGasPriceMultiplierDenominator.selector; - selectors[38] = UtilsFacet.util_setTotalBatchesExecuted.selector; - selectors[39] = UtilsFacet.util_setL2LogsRootHash.selector; - selectors[40] = UtilsFacet.util_setBaseTokenGasPriceMultiplierNominator.selector; + selectors[6] = UtilsFacet.util_setVerifier.selector; + selectors[7] = UtilsFacet.util_getVerifier.selector; + selectors[8] = UtilsFacet.util_setStoredBatchHashes.selector; + selectors[9] = UtilsFacet.util_getStoredBatchHashes.selector; + selectors[10] = UtilsFacet.util_setVerifierParams.selector; + selectors[11] = UtilsFacet.util_getVerifierParams.selector; + selectors[12] = UtilsFacet.util_setL2BootloaderBytecodeHash.selector; + selectors[13] = UtilsFacet.util_getL2BootloaderBytecodeHash.selector; + selectors[14] = UtilsFacet.util_setL2DefaultAccountBytecodeHash.selector; + selectors[15] = UtilsFacet.util_getL2DefaultAccountBytecodeHash.selector; + selectors[16] = UtilsFacet.util_setPendingAdmin.selector; + selectors[17] = UtilsFacet.util_getPendingAdmin.selector; + selectors[18] = UtilsFacet.util_setAdmin.selector; + selectors[19] = UtilsFacet.util_getAdmin.selector; + selectors[20] = UtilsFacet.util_setValidator.selector; + selectors[21] = UtilsFacet.util_getValidator.selector; + selectors[22] = UtilsFacet.util_setZkPorterAvailability.selector; + selectors[23] = UtilsFacet.util_getZkPorterAvailability.selector; + selectors[24] = UtilsFacet.util_setChainTypeManager.selector; + selectors[25] = UtilsFacet.util_getChainTypeManager.selector; + selectors[26] = UtilsFacet.util_setPriorityTxMaxGasLimit.selector; + selectors[27] = UtilsFacet.util_getPriorityTxMaxGasLimit.selector; + selectors[28] = UtilsFacet.util_setFeeParams.selector; + selectors[29] = UtilsFacet.util_getFeeParams.selector; + selectors[30] = UtilsFacet.util_setProtocolVersion.selector; + selectors[31] = UtilsFacet.util_getProtocolVersion.selector; + selectors[32] = UtilsFacet.util_setIsFrozen.selector; + selectors[33] = UtilsFacet.util_getIsFrozen.selector; + selectors[34] = UtilsFacet.util_setTransactionFilterer.selector; + selectors[35] = UtilsFacet.util_setBaseTokenGasPriceMultiplierDenominator.selector; + selectors[36] = UtilsFacet.util_setTotalBatchesExecuted.selector; + selectors[37] = UtilsFacet.util_setL2LogsRootHash.selector; + selectors[38] = UtilsFacet.util_setBaseTokenGasPriceMultiplierNominator.selector; + return selectors; } @@ -350,7 +386,6 @@ library Utils { admin: address(0x32149872498357874258787), validatorTimelock: address(0x85430237648403822345345), baseTokenAssetId: bytes32(uint256(0x923645439232223445)), - baseTokenBridge: address(0x23746765237749923040872834), storedBatchZero: bytes32(0), verifier: makeVerifier(testnetVerifier), verifierParams: makeVerifierParams(), diff --git a/l1-contracts/test/foundry/l1/unit/concrete/Utils/UtilsFacet.sol b/l1-contracts/test/foundry/l1/unit/concrete/Utils/UtilsFacet.sol index 0d141ce1f..08afae8c9 100644 --- a/l1-contracts/test/foundry/l1/unit/concrete/Utils/UtilsFacet.sol +++ b/l1-contracts/test/foundry/l1/unit/concrete/Utils/UtilsFacet.sol @@ -32,14 +32,6 @@ contract UtilsFacet is ZKChainBase { return s.baseTokenAssetId; } - function util_setBaseTokenBridge(address _baseTokenBridge) external { - s.baseTokenBridge = _baseTokenBridge; - } - - function util_getBaseTokenBridge() external view returns (address) { - return s.baseTokenBridge; - } - function util_setVerifier(IVerifier _verifier) external { s.verifier = _verifier; } diff --git a/l1-contracts/test/foundry/l1/unit/concrete/state-transition/ChainTypeManager/CreateNewChain.t.sol b/l1-contracts/test/foundry/l1/unit/concrete/state-transition/ChainTypeManager/CreateNewChain.t.sol index 81659b682..c422dca99 100644 --- a/l1-contracts/test/foundry/l1/unit/concrete/state-transition/ChainTypeManager/CreateNewChain.t.sol +++ b/l1-contracts/test/foundry/l1/unit/concrete/state-transition/ChainTypeManager/CreateNewChain.t.sol @@ -5,6 +5,7 @@ import {ChainTypeManagerTest} from "./_ChainTypeManager_Shared.t.sol"; import {Diamond} from "contracts/state-transition/libraries/Diamond.sol"; import {DataEncoding} from "contracts/common/libraries/DataEncoding.sol"; import {Unauthorized, HashMismatch} from "contracts/common/L1ContractErrors.sol"; +import {IZKChain} from "contracts/state-transition/chain-interfaces/IZKChain.sol"; contract createNewChainTest is ChainTypeManagerTest { function setUp() public { @@ -32,10 +33,18 @@ contract createNewChainTest is ChainTypeManagerTest { chainContractAddress.createNewChain({ _chainId: chainId, _baseTokenAssetId: DataEncoding.encodeNTVAssetId(block.chainid, baseToken), - _assetRouter: sharedBridge, _admin: admin, _initData: abi.encode(abi.encode(initialDiamondCutData), bytes("")), _factoryDeps: new bytes[](0) }); } + + function test_SuccessfulCreationOfNewChain() public { + address newChainAddress = createNewChain(getDiamondCutData(diamondInit)); + + address admin = IZKChain(newChainAddress).getAdmin(); + + assertEq(newChainAdmin, admin); + assertNotEq(newChainAddress, address(0)); + } } diff --git a/l1-contracts/test/foundry/l1/unit/concrete/state-transition/ChainTypeManager/FreezeChain.t.sol b/l1-contracts/test/foundry/l1/unit/concrete/state-transition/ChainTypeManager/FreezeChain.t.sol index d92349a61..73a2dc498 100644 --- a/l1-contracts/test/foundry/l1/unit/concrete/state-transition/ChainTypeManager/FreezeChain.t.sol +++ b/l1-contracts/test/foundry/l1/unit/concrete/state-transition/ChainTypeManager/FreezeChain.t.sol @@ -5,22 +5,31 @@ import {ChainTypeManagerTest} from "./_ChainTypeManager_Shared.t.sol"; import {GettersFacet} from "contracts/state-transition/chain-deps/facets/Getters.sol"; import {IAdmin} from "contracts/state-transition/chain-interfaces/IAdmin.sol"; import {FacetIsFrozen} from "contracts/common/L1ContractErrors.sol"; +import {IBridgehub} from "contracts/bridgehub/IBridgehub.sol"; contract freezeChainTest is ChainTypeManagerTest { - // function test_FreezingChain() public { - // createNewChain(getDiamondCutData(diamondInit)); - // address newChainAddress = chainContractAddress.getZKChain(chainId); - // GettersFacet gettersFacet = GettersFacet(newChainAddress); - // bool isChainFrozen = gettersFacet.isDiamondStorageFrozen(); - // assertEq(isChainFrozen, false); - // vm.stopPrank(); - // vm.startPrank(governor); - // chainContractAddress.freezeChain(block.chainid); - // // Repeated call should revert - // vm.expectRevert(bytes("q1")); // storage frozen - // chainContractAddress.freezeChain(block.chainid); - // // Call fails as storage is frozen - // vm.expectRevert(bytes("q1")); - // isChainFrozen = gettersFacet.isDiamondStorageFrozen(); - // } + function setUp() public { + deploy(); + } + + function test_FreezingChain() public { + address newChainAddress = createNewChain(getDiamondCutData(diamondInit)); + vm.mockCall( + address(bridgehub), + abi.encodeWithSelector(IBridgehub.getZKChain.selector), + abi.encode(newChainAddress) + ); + GettersFacet gettersFacet = GettersFacet(newChainAddress); + bool isChainFrozen = gettersFacet.isDiamondStorageFrozen(); + assertEq(isChainFrozen, false); + vm.stopPrank(); + vm.startPrank(governor); + chainContractAddress.freezeChain(block.chainid); + // Repeated call should revert + vm.expectRevert(bytes("q1")); // storage frozen + chainContractAddress.freezeChain(block.chainid); + // Call fails as storage is frozen + vm.expectRevert(bytes("q1")); + isChainFrozen = gettersFacet.isDiamondStorageFrozen(); + } } diff --git a/l1-contracts/test/foundry/l1/unit/concrete/state-transition/ChainTypeManager/RevertBatches.t.sol b/l1-contracts/test/foundry/l1/unit/concrete/state-transition/ChainTypeManager/RevertBatches.t.sol index cdac3e776..610c6c1a3 100644 --- a/l1-contracts/test/foundry/l1/unit/concrete/state-transition/ChainTypeManager/RevertBatches.t.sol +++ b/l1-contracts/test/foundry/l1/unit/concrete/state-transition/ChainTypeManager/RevertBatches.t.sol @@ -3,15 +3,24 @@ pragma solidity 0.8.24; import {Vm} from "forge-std/Test.sol"; -import {Utils, L2_SYSTEM_CONTEXT_ADDRESS} from "../../Utils/Utils.sol"; +import {SafeCast} from "@openzeppelin/contracts-v4/utils/math/SafeCast.sol"; + +import {Utils, L2_SYSTEM_CONTEXT_ADDRESS, L2_DA_VALIDATOR_ADDRESS} from "../../Utils/Utils.sol"; import {ChainTypeManagerTest} from "./_ChainTypeManager_Shared.t.sol"; -import {COMMIT_TIMESTAMP_NOT_OLDER, DEFAULT_L2_LOGS_TREE_ROOT_HASH, EMPTY_STRING_KECCAK} from "contracts/common/Config.sol"; -import {IExecutor, SystemLogKey} from "contracts/state-transition/chain-interfaces/IExecutor.sol"; +import {COMMIT_TIMESTAMP_NOT_OLDER, DEFAULT_L2_LOGS_TREE_ROOT_HASH, EMPTY_STRING_KECCAK, POINT_EVALUATION_PRECOMPILE_ADDR, REQUIRED_L2_GAS_PRICE_PER_PUBDATA, SYSTEM_UPGRADE_L2_TX_TYPE, PRIORITY_TX_MAX_GAS_LIMIT} from "contracts/common/Config.sol"; +import {L2_FORCE_DEPLOYER_ADDR, L2_COMPLEX_UPGRADER_ADDR, L2_GENESIS_UPGRADE_ADDR} from "contracts/common/L2ContractAddresses.sol"; //, COMPLEX_UPGRADER_ADDR, GENESIS_UPGRADE_ADDR +import {SemVer} from "contracts/common/libraries/SemVer.sol"; +import {L2ContractHelper} from "contracts/common/libraries/L2ContractHelper.sol"; +import {L2CanonicalTransaction} from "contracts/common/Messaging.sol"; +import {IExecutor, SystemLogKey, TOTAL_BLOBS_IN_COMMITMENT} from "contracts/state-transition/chain-interfaces/IExecutor.sol"; import {GettersFacet} from "contracts/state-transition/chain-deps/facets/Getters.sol"; import {AdminFacet} from "contracts/state-transition/chain-deps/facets/Admin.sol"; import {ExecutorFacet} from "contracts/state-transition/chain-deps/facets/Executor.sol"; import {IExecutor} from "contracts/state-transition/chain-interfaces/IExecutor.sol"; +import {IL2GenesisUpgrade} from "contracts/state-transition/l2-deps/IL2GenesisUpgrade.sol"; +import {IComplexUpgrader} from "contracts/state-transition/l2-deps/IComplexUpgrader.sol"; +import {IBridgehub} from "contracts/bridgehub/IBridgehub.sol"; contract revertBatchesTest is ChainTypeManagerTest { // Items for logs & commits @@ -20,129 +29,221 @@ contract revertBatchesTest is ChainTypeManagerTest { IExecutor.StoredBatchInfo internal newStoredBatchInfo; IExecutor.StoredBatchInfo internal genesisStoredBatchInfo; uint256[] internal proofInput; + bytes32 l2DAValidatorOutputHash; + bytes operatorDAInput; + bytes defaultBlobCommitment; + bytes32[] defaultBlobVersionedHashes; + bytes16 defaultBlobOpeningPoint = 0x7142c5851421a2dc03dde0aabdb0ffdb; + bytes32 defaultBlobClaimedValue = 0x1e5eea3bbb85517461c1d1c7b84c7c2cec050662a5e81a71d5d7e2766eaff2f0; + bytes l2Logs; + address newChainAddress; + + bytes32 constant EMPTY_PREPUBLISHED_COMMITMENT = 0x0000000000000000000000000000000000000000000000000000000000000000; + bytes constant POINT_EVALUATION_PRECOMPILE_RESULT = + hex"000000000000000000000000000000000000000000000000000000000000100073eda753299d7d483339d80809a1d80553bda402fffe5bfeffffffff00000001"; // Facets exposing the diamond AdminFacet internal adminFacet; ExecutorFacet internal executorFacet; GettersFacet internal gettersFacet; - // function test_SuccessfulBatchReverting() public { - // createNewChain(getDiamondCutData(diamondInit)); - - // address newChainAddress = chainContractAddress.getZKChain(chainId); - - // executorFacet = ExecutorFacet(address(newChainAddress)); - // gettersFacet = GettersFacet(address(newChainAddress)); - // adminFacet = AdminFacet(address(newChainAddress)); - - // // Initial setup for logs & commits - // vm.stopPrank(); - // vm.startPrank(newChainAdmin); - - // genesisStoredBatchInfo = IExecutor.StoredBatchInfo({ - // batchNumber: 0, - // batchHash: bytes32(uint256(0x01)), - // indexRepeatedStorageChanges: 1, - // numberOfLayer1Txs: 0, - // priorityOperationsHash: EMPTY_STRING_KECCAK, - // l2LogsTreeRoot: DEFAULT_L2_LOGS_TREE_ROOT_HASH, - // timestamp: 0, - // commitment: bytes32(uint256(0x01)) - // }); - - // adminFacet.setTokenMultiplier(1, 1); - - // uint256[] memory recursiveAggregationInput; - // uint256[] memory serializedProof; - // proofInput = IExecutor.ProofInput(recursiveAggregationInput, serializedProof); - - // // foundry's default value is 1 for the block's timestamp, it is expected - // // that block.timestamp > COMMIT_TIMESTAMP_NOT_OLDER + 1 - // vm.warp(COMMIT_TIMESTAMP_NOT_OLDER + 1 + 1); - // currentTimestamp = block.timestamp; - - // bytes memory l2Logs = Utils.encodePacked(Utils.createSystemLogs()); - // newCommitBatchInfo = IExecutor.CommitBatchInfo({ - // batchNumber: 1, - // timestamp: uint64(currentTimestamp), - // indexRepeatedStorageChanges: 1, - // newStateRoot: Utils.randomBytes32("newStateRoot"), - // numberOfLayer1Txs: 0, - // priorityOperationsHash: keccak256(""), - // bootloaderHeapInitialContentsHash: Utils.randomBytes32("bootloaderHeapInitialContentsHash"), - // eventsQueueStateHash: Utils.randomBytes32("eventsQueueStateHash"), - // systemLogs: l2Logs, - // pubdataCommitments: "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" - // }); - - // // Commit & prove batches - // vm.warp(COMMIT_TIMESTAMP_NOT_OLDER + 1); - // currentTimestamp = block.timestamp; - - // bytes32 expectedSystemContractUpgradeTxHash = gettersFacet.getL2SystemContractsUpgradeTxHash(); - // bytes[] memory correctL2Logs = Utils.createSystemLogsWithUpgradeTransaction( - // expectedSystemContractUpgradeTxHash - // ); - - // correctL2Logs[uint256(uint256(SystemLogKey.PACKED_BATCH_AND_L2_BLOCK_TIMESTAMP_KEY))] = Utils.constructL2Log( - // true, - // L2_SYSTEM_CONTEXT_ADDRESS, - // uint256(SystemLogKey.PACKED_BATCH_AND_L2_BLOCK_TIMESTAMP_KEY), - // Utils.packBatchTimestampAndBlockTimestamp(currentTimestamp, currentTimestamp) - // ); - - // correctL2Logs[uint256(uint256(SystemLogKey.PREV_BATCH_HASH_KEY))] = Utils.constructL2Log( - // true, - // L2_SYSTEM_CONTEXT_ADDRESS, - // uint256(SystemLogKey.PREV_BATCH_HASH_KEY), - // bytes32(uint256(0x01)) - // ); - - // l2Logs = Utils.encodePacked(correctL2Logs); - // newCommitBatchInfo.timestamp = uint64(currentTimestamp); - // newCommitBatchInfo.systemLogs = l2Logs; - - // IExecutor.CommitBatchInfo[] memory commitBatchInfoArray = new IExecutor.CommitBatchInfo[](1); - // commitBatchInfoArray[0] = newCommitBatchInfo; - - // vm.stopPrank(); - // vm.startPrank(validator); - // vm.recordLogs(); - // executorFacet.commitBatchesSharedBridge(uint256(0), genesisStoredBatchInfo, commitBatchInfoArray); - // Vm.Log[] memory entries = vm.getRecordedLogs(); - - // newStoredBatchInfo = IExecutor.StoredBatchInfo({ - // batchNumber: 1, - // batchHash: entries[0].topics[2], - // indexRepeatedStorageChanges: 1, - // numberOfLayer1Txs: 0, - // priorityOperationsHash: keccak256(""), - // l2LogsTreeRoot: 0, - // timestamp: currentTimestamp, - // commitment: entries[0].topics[3] - // }); - - // IExecutor.StoredBatchInfo[] memory storedBatchInfoArray = new IExecutor.StoredBatchInfo[](1); - // storedBatchInfoArray[0] = newStoredBatchInfo; - - // executorFacet.proveBatches(genesisStoredBatchInfo, storedBatchInfoArray, proofInput); - - // // Test batch revert triggered from CTM - // vm.stopPrank(); - // vm.startPrank(governor); - - // uint256 totalBlocksCommittedBefore = gettersFacet.getTotalBlocksCommitted(); - // assertEq(totalBlocksCommittedBefore, 1, "totalBlocksCommittedBefore"); - - // uint256 totalBlocksVerifiedBefore = gettersFacet.getTotalBlocksVerified(); - // assertEq(totalBlocksVerifiedBefore, 1, "totalBlocksVerifiedBefore"); - - // chainContractAddress.revertBatches(chainId, 0); - - // uint256 totalBlocksCommitted = gettersFacet.getTotalBlocksCommitted(); - // assertEq(totalBlocksCommitted, 0, "totalBlocksCommitted"); - - // uint256 totalBlocksVerified = gettersFacet.getTotalBlocksVerified(); - // assertEq(totalBlocksVerified, 0, "totalBlocksVerified"); - // } + function setUp() public { + deploy(); + + defaultBlobCommitment = Utils.getDefaultBlobCommitment(); + defaultBlobVersionedHashes = new bytes32[](1); + defaultBlobVersionedHashes[0] = 0x01c024b4740620a5849f95930cefe298933bdf588123ea897cdf0f2462f6d2d5; + + bytes memory precompileInput = Utils.defaultPointEvaluationPrecompileInput(defaultBlobVersionedHashes[0]); + vm.mockCall(POINT_EVALUATION_PRECOMPILE_ADDR, precompileInput, POINT_EVALUATION_PRECOMPILE_RESULT); + + l2Logs = Utils.encodePacked(Utils.createSystemLogs(bytes32(0))); + genesisStoredBatchInfo = IExecutor.StoredBatchInfo({ + batchNumber: 0, + batchHash: bytes32(uint256(0x01)), + indexRepeatedStorageChanges: 0x01, + numberOfLayer1Txs: 0, + priorityOperationsHash: keccak256(""), + l2LogsTreeRoot: DEFAULT_L2_LOGS_TREE_ROOT_HASH, + timestamp: 0, + commitment: bytes32(uint256(0x01)) + }); + vm.warp(COMMIT_TIMESTAMP_NOT_OLDER + 1 + 1); + currentTimestamp = block.timestamp; + newCommitBatchInfo = IExecutor.CommitBatchInfo({ + batchNumber: 1, + timestamp: uint64(currentTimestamp), + indexRepeatedStorageChanges: 0, + newStateRoot: Utils.randomBytes32("newStateRoot"), + numberOfLayer1Txs: 0, + priorityOperationsHash: keccak256(""), + bootloaderHeapInitialContentsHash: Utils.randomBytes32("bootloaderHeapInitialContentsHash"), + eventsQueueStateHash: Utils.randomBytes32("eventsQueueStateHash"), + systemLogs: l2Logs, + operatorDAInput: "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" + }); + + { + bytes memory complexUpgraderCalldata; + address l1CtmDeployer = address(bridgehub.l1CtmDeployer()); + { + bytes memory l2GenesisUpgradeCalldata = abi.encodeCall( + IL2GenesisUpgrade.genesisUpgrade, + (chainId, l1CtmDeployer, forceDeploymentsData, "0x") + ); + complexUpgraderCalldata = abi.encodeCall( + IComplexUpgrader.upgrade, + (L2_GENESIS_UPGRADE_ADDR, l2GenesisUpgradeCalldata) + ); + } + + // slither-disable-next-line unused-return + (, uint32 minorVersion, ) = SemVer.unpackSemVer(SafeCast.toUint96(0)); + } + + newChainAddress = createNewChain(getDiamondCutData(diamondInit)); + vm.mockCall( + address(bridgehub), + abi.encodeWithSelector(IBridgehub.getZKChain.selector), + abi.encode(newChainAddress) + ); + + executorFacet = ExecutorFacet(address(newChainAddress)); + gettersFacet = GettersFacet(address(newChainAddress)); + adminFacet = AdminFacet(address(newChainAddress)); + + vm.stopPrank(); + vm.prank(newChainAdmin); + adminFacet.setDAValidatorPair(address(rollupL1DAValidator), L2_DA_VALIDATOR_ADDRESS); + } + + function test_SuccessfulBatchReverting() public { + vm.startPrank(governor); + + bytes32 uncompressedStateDiffHash = Utils.randomBytes32("uncompressedStateDiffHash"); + bytes32 totalL2PubdataHash = Utils.randomBytes32("totalL2PubdataHash"); + uint8 numberOfBlobs = 1; + bytes32[] memory blobsLinearHashes = new bytes32[](1); + blobsLinearHashes[0] = Utils.randomBytes32("blobsLinearHashes"); + + operatorDAInput = abi.encodePacked( + uncompressedStateDiffHash, + totalL2PubdataHash, + numberOfBlobs, + blobsLinearHashes, + bytes1(0x01), + defaultBlobCommitment, + EMPTY_PREPUBLISHED_COMMITMENT + ); + + l2DAValidatorOutputHash = Utils.constructRollupL2DAValidatorOutputHash( + uncompressedStateDiffHash, + totalL2PubdataHash, + uint8(numberOfBlobs), + blobsLinearHashes + ); + + vm.warp(COMMIT_TIMESTAMP_NOT_OLDER + 1); + currentTimestamp = block.timestamp; + bytes32 expectedSystemContractUpgradeTxHash = gettersFacet.getL2SystemContractsUpgradeTxHash(); + bytes[] memory correctL2Logs = Utils.createSystemLogsWithUpgradeTransactionForCTM( + expectedSystemContractUpgradeTxHash, + l2DAValidatorOutputHash + ); + correctL2Logs[uint256(SystemLogKey.PACKED_BATCH_AND_L2_BLOCK_TIMESTAMP_KEY)] = Utils.constructL2Log( + true, + L2_SYSTEM_CONTEXT_ADDRESS, + uint256(SystemLogKey.PACKED_BATCH_AND_L2_BLOCK_TIMESTAMP_KEY), + Utils.packBatchTimestampAndBlockTimestamp(currentTimestamp, currentTimestamp) + ); + + IExecutor.CommitBatchInfo memory correctNewCommitBatchInfo = newCommitBatchInfo; + correctNewCommitBatchInfo.timestamp = uint64(currentTimestamp); + correctNewCommitBatchInfo.systemLogs = Utils.encodePacked(correctL2Logs); + correctNewCommitBatchInfo.operatorDAInput = operatorDAInput; + + bytes32[] memory blobHashes = new bytes32[](TOTAL_BLOBS_IN_COMMITMENT); + blobHashes[0] = blobsLinearHashes[0]; + + bytes32[] memory blobCommitments = new bytes32[](TOTAL_BLOBS_IN_COMMITMENT); + blobCommitments[0] = keccak256( + abi.encodePacked( + defaultBlobVersionedHashes[0], + abi.encodePacked(defaultBlobOpeningPoint, defaultBlobClaimedValue) + ) + ); + + bytes32 expectedBatchCommitment = Utils.createBatchCommitment( + correctNewCommitBatchInfo, + uncompressedStateDiffHash, + blobCommitments, + blobHashes + ); + + IExecutor.CommitBatchInfo[] memory correctCommitBatchInfoArray = new IExecutor.CommitBatchInfo[](1); + correctCommitBatchInfoArray[0] = correctNewCommitBatchInfo; + correctCommitBatchInfoArray[0].operatorDAInput = operatorDAInput; + + vm.stopPrank(); + vm.startPrank(validator); + vm.blobhashes(defaultBlobVersionedHashes); + vm.recordLogs(); + (uint256 commitBatchFrom, uint256 commitBatchTo, bytes memory commitData) = Utils.encodeCommitBatchesData( + genesisStoredBatchInfo, + correctCommitBatchInfoArray + ); + executorFacet.commitBatchesSharedBridge(uint256(0), commitBatchFrom, commitBatchTo, commitData); + + Vm.Log[] memory entries = vm.getRecordedLogs(); + + assertEq(entries.length, 1); + assertEq(entries[0].topics[0], keccak256("BlockCommit(uint256,bytes32,bytes32)")); + assertEq(entries[0].topics[1], bytes32(uint256(1))); // batchNumber + assertEq(entries[0].topics[2], correctNewCommitBatchInfo.newStateRoot); // batchHash + + uint256 totalBatchesCommitted = gettersFacet.getTotalBatchesCommitted(); + assertEq(totalBatchesCommitted, 1); + + newStoredBatchInfo = IExecutor.StoredBatchInfo({ + batchNumber: 1, + batchHash: entries[0].topics[2], + indexRepeatedStorageChanges: 0, + numberOfLayer1Txs: 0, + priorityOperationsHash: keccak256(""), + l2LogsTreeRoot: DEFAULT_L2_LOGS_TREE_ROOT_HASH, + timestamp: currentTimestamp, + commitment: entries[0].topics[3] + }); + + IExecutor.StoredBatchInfo[] memory storedBatchInfoArray = new IExecutor.StoredBatchInfo[](1); + storedBatchInfoArray[0] = newStoredBatchInfo; + + (uint256 proveBatchFrom, uint256 proveBatchTo, bytes memory proveData) = Utils.encodeProveBatchesData( + genesisStoredBatchInfo, + storedBatchInfoArray, + proofInput + ); + + executorFacet.proveBatchesSharedBridge(uint256(0), proveBatchFrom, proveBatchTo, proveData); + + // Test batch revert triggered from CTM + vm.stopPrank(); + vm.prank(address(chainContractAddress)); + adminFacet.setValidator(address(chainContractAddress), true); + vm.startPrank(governor); + + uint256 totalBlocksCommittedBefore = gettersFacet.getTotalBlocksCommitted(); + assertEq(totalBlocksCommittedBefore, 1, "totalBlocksCommittedBefore"); + + uint256 totalBlocksVerifiedBefore = gettersFacet.getTotalBlocksVerified(); + assertEq(totalBlocksVerifiedBefore, 1, "totalBlocksVerifiedBefore"); + + chainContractAddress.revertBatches(chainId, 0); + + uint256 totalBlocksCommitted = gettersFacet.getTotalBlocksCommitted(); + assertEq(totalBlocksCommitted, 0, "totalBlocksCommitted"); + + uint256 totalBlocksVerified = gettersFacet.getTotalBlocksVerified(); + assertEq(totalBlocksVerified, 0, "totalBlocksVerified"); + } } diff --git a/l1-contracts/test/foundry/l1/unit/concrete/state-transition/ChainTypeManager/_ChainTypeManager_Shared.t.sol b/l1-contracts/test/foundry/l1/unit/concrete/state-transition/ChainTypeManager/_ChainTypeManager_Shared.t.sol index 99d8c9859..5ecaa7407 100644 --- a/l1-contracts/test/foundry/l1/unit/concrete/state-transition/ChainTypeManager/_ChainTypeManager_Shared.t.sol +++ b/l1-contracts/test/foundry/l1/unit/concrete/state-transition/ChainTypeManager/_ChainTypeManager_Shared.t.sol @@ -3,6 +3,7 @@ pragma solidity ^0.8.21; import {Test} from "forge-std/Test.sol"; +import {console2 as console} from "forge-std/Script.sol"; import {TransparentUpgradeableProxy} from "@openzeppelin/contracts-v4/proxy/transparent/TransparentUpgradeableProxy.sol"; @@ -10,6 +11,8 @@ import {IBridgehub} from "contracts/bridgehub/IBridgehub.sol"; import {Utils} from "foundry-test/l1/unit/concrete/Utils/Utils.sol"; import {Bridgehub} from "contracts/bridgehub/Bridgehub.sol"; +import {IL1AssetRouter} from "contracts/bridge/asset-router/IL1AssetRouter.sol"; +import {IL1Nullifier} from "contracts/bridge/interfaces/IL1Nullifier.sol"; import {UtilsFacet} from "foundry-test/l1/unit/concrete/Utils/UtilsFacet.sol"; import {AdminFacet} from "contracts/state-transition/chain-deps/facets/Admin.sol"; import {ExecutorFacet} from "contracts/state-transition/chain-deps/facets/Executor.sol"; @@ -24,18 +27,24 @@ import {TestnetVerifier} from "contracts/state-transition/TestnetVerifier.sol"; import {DummyBridgehub} from "contracts/dev-contracts/test/DummyBridgehub.sol"; import {DataEncoding} from "contracts/common/libraries/DataEncoding.sol"; import {ZeroAddress} from "contracts/common/L1ContractErrors.sol"; +import {ICTMDeploymentTracker} from "contracts/bridgehub/ICTMDeploymentTracker.sol"; +import {IMessageRoot} from "contracts/bridgehub/IMessageRoot.sol"; +import {L1AssetRouter} from "contracts/bridge/asset-router/L1AssetRouter.sol"; +import {RollupL1DAValidator} from "da-contracts/RollupL1DAValidator.sol"; contract ChainTypeManagerTest is Test { ChainTypeManager internal chainTypeManager; ChainTypeManager internal chainContractAddress; L1GenesisUpgrade internal genesisUpgradeContract; Bridgehub internal bridgehub; + RollupL1DAValidator internal rollupL1DAValidator; address internal diamondInit; address internal constant governor = address(0x1010101); address internal constant admin = address(0x2020202); address internal constant baseToken = address(0x3030303); address internal constant sharedBridge = address(0x4040404); address internal constant validator = address(0x5050505); + address internal constant l1Nullifier = address(0x6060606); address internal newChainAdmin; uint256 chainId = 112; address internal testnetVerifier = address(new TestnetVerifier()); @@ -46,6 +55,15 @@ contract ChainTypeManagerTest is Test { function deploy() public { bridgehub = new Bridgehub(block.chainid, governor, type(uint256).max); + vm.prank(governor); + bridgehub.setAddresses(sharedBridge, ICTMDeploymentTracker(address(0)), IMessageRoot(address(0))); + + vm.mockCall( + address(sharedBridge), + abi.encodeCall(L1AssetRouter.l2BridgeAddress, (chainId)), + abi.encode(makeAddr("l2BridgeAddress")) + ); + newChainAdmin = makeAddr("chainadmin"); vm.startPrank(address(bridgehub)); @@ -123,6 +141,8 @@ contract ChainTypeManagerTest is Test { ); chainContractAddress = ChainTypeManager(address(transparentUpgradeableProxy)); + rollupL1DAValidator = new RollupL1DAValidator(); + vm.stopPrank(); vm.startPrank(governor); } @@ -143,11 +163,22 @@ contract ChainTypeManagerTest is Test { vm.stopPrank(); vm.startPrank(address(bridgehub)); + vm.mockCall( + address(sharedBridge), + abi.encodeWithSelector(IL1AssetRouter.L1_NULLIFIER.selector), + abi.encode(l1Nullifier) + ); + + vm.mockCall( + address(l1Nullifier), + abi.encodeWithSelector(IL1Nullifier.l2BridgeAddress.selector), + abi.encode(l1Nullifier) + ); + return chainContractAddress.createNewChain({ _chainId: chainId, _baseTokenAssetId: DataEncoding.encodeNTVAssetId(block.chainid, baseToken), - _assetRouter: sharedBridge, _admin: newChainAdmin, _initData: abi.encode(abi.encode(_diamondCut), bytes("")), _factoryDeps: new bytes[](0) diff --git a/l1-contracts/test/foundry/l1/unit/concrete/state-transition/chain-deps/DiamondInit/Initialize.t.sol b/l1-contracts/test/foundry/l1/unit/concrete/state-transition/chain-deps/DiamondInit/Initialize.t.sol index cfc826fa5..0b7dfbbe9 100644 --- a/l1-contracts/test/foundry/l1/unit/concrete/state-transition/chain-deps/DiamondInit/Initialize.t.sol +++ b/l1-contracts/test/foundry/l1/unit/concrete/state-transition/chain-deps/DiamondInit/Initialize.t.sol @@ -86,7 +86,6 @@ contract InitializeTest is DiamondInitTest { assertEq(utilsFacet.util_getBridgehub(), initializeData.bridgehub); assertEq(utilsFacet.util_getChainTypeManager(), initializeData.chainTypeManager); assertEq(utilsFacet.util_getBaseTokenAssetId(), initializeData.baseTokenAssetId); - assertEq(utilsFacet.util_getBaseTokenBridge(), initializeData.baseTokenBridge); assertEq(utilsFacet.util_getProtocolVersion(), initializeData.protocolVersion); assertEq(address(utilsFacet.util_getVerifier()), address(initializeData.verifier)); diff --git a/l1-contracts/test/foundry/l1/unit/concrete/state-transition/chain-deps/facets/Getters/GetBaseTokenBridge.t.sol b/l1-contracts/test/foundry/l1/unit/concrete/state-transition/chain-deps/facets/Getters/GetBaseTokenBridge.t.sol deleted file mode 100644 index db32ca6bd..000000000 --- a/l1-contracts/test/foundry/l1/unit/concrete/state-transition/chain-deps/facets/Getters/GetBaseTokenBridge.t.sol +++ /dev/null @@ -1,16 +0,0 @@ -// SPDX-License-Identifier: MIT - -pragma solidity 0.8.24; - -import {GettersFacetTest} from "./_Getters_Shared.t.sol"; - -contract GetBaseTokenBridgeTest is GettersFacetTest { - function test() public { - address expected = makeAddr("baseTokenBride"); - gettersFacetWrapper.util_setBaseTokenBridge(expected); - - address received = gettersFacet.getBaseTokenBridge(); - - assertEq(expected, received, "BaseTokenBridge address is incorrect"); - } -} diff --git a/l1-contracts/test/foundry/l1/unit/concrete/state-transition/chain-deps/facets/Getters/_Getters_Shared.t.sol b/l1-contracts/test/foundry/l1/unit/concrete/state-transition/chain-deps/facets/Getters/_Getters_Shared.t.sol index 557378c63..9f66926e7 100644 --- a/l1-contracts/test/foundry/l1/unit/concrete/state-transition/chain-deps/facets/Getters/_Getters_Shared.t.sol +++ b/l1-contracts/test/foundry/l1/unit/concrete/state-transition/chain-deps/facets/Getters/_Getters_Shared.t.sol @@ -36,10 +36,6 @@ contract GettersFacetWrapper is GettersFacet { s.baseTokenAssetId = _baseTokenAssetId; } - function util_setBaseTokenBridge(address _baseTokenBridge) external { - s.baseTokenBridge = _baseTokenBridge; - } - function util_setTotalBatchesCommitted(uint256 _totalBatchesCommitted) external { s.totalBatchesCommitted = _totalBatchesCommitted; } diff --git a/l1-contracts/test/foundry/l1/unit/concrete/state-transition/chain-deps/facets/Mailbox/FinalizeWithdrawal.t.sol b/l1-contracts/test/foundry/l1/unit/concrete/state-transition/chain-deps/facets/Mailbox/FinalizeWithdrawal.t.sol index c71721c79..5e7fa27f6 100644 --- a/l1-contracts/test/foundry/l1/unit/concrete/state-transition/chain-deps/facets/Mailbox/FinalizeWithdrawal.t.sol +++ b/l1-contracts/test/foundry/l1/unit/concrete/state-transition/chain-deps/facets/Mailbox/FinalizeWithdrawal.t.sol @@ -9,6 +9,7 @@ import {IBridgehub} from "contracts/bridgehub/IBridgehub.sol"; import {IL1AssetRouter} from "contracts/bridge/asset-router/IL1AssetRouter.sol"; import {DummySharedBridge} from "contracts/dev-contracts/test/DummySharedBridge.sol"; import {OnlyEraSupported} from "contracts/common/L1ContractErrors.sol"; +import {Bridgehub} from "contracts/bridgehub/Bridgehub.sol"; contract MailboxFinalizeWithdrawal is MailboxTest { bytes32[] proof; @@ -22,6 +23,8 @@ contract MailboxFinalizeWithdrawal is MailboxTest { L1AssetRouter = new DummySharedBridge(keccak256("dummyDepositHash")); baseTokenBridgeAddress = address(L1AssetRouter); + vm.mockCall(bridgehub, abi.encodeCall(Bridgehub.sharedBridge, ()), abi.encode(baseTokenBridgeAddress)); + proof = new bytes32[](0); message = "message"; } @@ -40,9 +43,7 @@ contract MailboxFinalizeWithdrawal is MailboxTest { } function test_success_withdrawal(uint256 amount) public { - address baseTokenBridge = makeAddr("baseTokenBridge"); utilsFacet.util_setChainId(eraChainId); - utilsFacet.util_setBaseTokenBridge(baseTokenBridgeAddress); address l1Receiver = makeAddr("receiver"); address l1Token = address(1); diff --git a/l1-contracts/test/foundry/l1/unit/concrete/state-transition/chain-deps/facets/Mailbox/RequestL2Transaction.t.sol b/l1-contracts/test/foundry/l1/unit/concrete/state-transition/chain-deps/facets/Mailbox/RequestL2Transaction.t.sol index 85bcd8be8..0ea16b46c 100644 --- a/l1-contracts/test/foundry/l1/unit/concrete/state-transition/chain-deps/facets/Mailbox/RequestL2Transaction.t.sol +++ b/l1-contracts/test/foundry/l1/unit/concrete/state-transition/chain-deps/facets/Mailbox/RequestL2Transaction.t.sol @@ -11,6 +11,7 @@ import {FeeParams, PubdataPricingMode} from "contracts/state-transition/chain-de import {IL1AssetRouter} from "contracts/bridge/asset-router/IL1AssetRouter.sol"; import {DummySharedBridge} from "contracts/dev-contracts/test/DummySharedBridge.sol"; import {OnlyEraSupported, TooManyFactoryDeps, MsgValueTooLow, GasPerPubdataMismatch} from "contracts/common/L1ContractErrors.sol"; +import {Bridgehub} from "contracts/bridgehub/Bridgehub.sol"; contract MailboxRequestL2TransactionTest is MailboxTest { address tempAddress; @@ -24,6 +25,7 @@ contract MailboxRequestL2TransactionTest is MailboxTest { l1SharedBridge = new DummySharedBridge(keccak256("dummyDepositHash")); baseTokenBridgeAddress = address(l1SharedBridge); + vm.mockCall(bridgehub, abi.encodeCall(Bridgehub.sharedBridge, ()), abi.encode(baseTokenBridgeAddress)); tempAddress = makeAddr("temp"); tempBytesArr = new bytes[](0); @@ -122,7 +124,6 @@ contract MailboxRequestL2TransactionTest is MailboxTest { function test_RevertWhen_bridgePaused(uint256 randomValue) public { utilsFacet.util_setBaseTokenGasPriceMultiplierDenominator(1); utilsFacet.util_setPriorityTxMaxGasLimit(100000000); - utilsFacet.util_setBaseTokenBridge(baseTokenBridgeAddress); uint256 l2GasLimit = 1000000; uint256 baseCost = mailboxFacet.l2TransactionBaseCost(10000000, l2GasLimit, REQUIRED_L2_GAS_PRICE_PER_PUBDATA); @@ -137,7 +138,6 @@ contract MailboxRequestL2TransactionTest is MailboxTest { function test_success_requestL2Transaction(uint256 randomValue) public { utilsFacet.util_setBaseTokenGasPriceMultiplierDenominator(1); utilsFacet.util_setPriorityTxMaxGasLimit(100000000); - utilsFacet.util_setBaseTokenBridge(baseTokenBridgeAddress); uint256 l2GasLimit = 1000000; uint256 baseCost = mailboxFacet.l2TransactionBaseCost(10000000, l2GasLimit, REQUIRED_L2_GAS_PRICE_PER_PUBDATA); diff --git a/l1-contracts/test/foundry/l1/unit/concrete/state-transition/chain-deps/facets/Mailbox/_Mailbox_Shared.t.sol b/l1-contracts/test/foundry/l1/unit/concrete/state-transition/chain-deps/facets/Mailbox/_Mailbox_Shared.t.sol index b1ef215d8..37755b08e 100644 --- a/l1-contracts/test/foundry/l1/unit/concrete/state-transition/chain-deps/facets/Mailbox/_Mailbox_Shared.t.sol +++ b/l1-contracts/test/foundry/l1/unit/concrete/state-transition/chain-deps/facets/Mailbox/_Mailbox_Shared.t.sol @@ -21,9 +21,11 @@ contract MailboxTest is Test { uint256 constant eraChainId = 9; address internal testnetVerifier = address(new TestnetVerifier()); address diamondProxy; + address bridgehub; function setupDiamondProxy() public virtual { sender = makeAddr("sender"); + bridgehub = makeAddr("bridgehub"); vm.deal(sender, 100 ether); Diamond.FacetCut[] memory facetCuts = new Diamond.FacetCut[](3); @@ -50,6 +52,8 @@ contract MailboxTest is Test { mailboxFacet = IMailbox(diamondProxy); utilsFacet = UtilsFacet(diamondProxy); gettersFacet = IGetters(diamondProxy); + + utilsFacet.util_setBridgehub(bridgehub); } // add this to be excluded from coverage report diff --git a/l1-contracts/test/foundry/l2/unit/L2AdminFactory/L2AdminFactory.t.sol b/l1-contracts/test/foundry/l2/unit/L2AdminFactory/L2AdminFactory.t.sol new file mode 100644 index 000000000..7b85a8c54 --- /dev/null +++ b/l1-contracts/test/foundry/l2/unit/L2AdminFactory/L2AdminFactory.t.sol @@ -0,0 +1,42 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.20; + +import {Test} from "forge-std/Test.sol"; + +import {IBridgehub} from "contracts/bridgehub/IBridgehub.sol"; +import {L2AdminFactory} from "contracts/governance/L2AdminFactory.sol"; +import {PermanentRestriction} from "contracts/governance/PermanentRestriction.sol"; +import {IPermanentRestriction} from "contracts/governance/IPermanentRestriction.sol"; + +contract L2AdminFactoryTest is Test { + function testL2AdminFactory() public { + address[] memory requiredRestrictions = new address[](1); + requiredRestrictions[0] = makeAddr("required"); + + L2AdminFactory factory = new L2AdminFactory(requiredRestrictions); + + address[] memory additionalRestrictions = new address[](1); + additionalRestrictions[0] = makeAddr("additional"); + + address[] memory allRestrictions = new address[](2); + allRestrictions[0] = requiredRestrictions[0]; + allRestrictions[1] = additionalRestrictions[0]; + + bytes32 salt = keccak256("salt"); + + address admin = factory.deployAdmin(additionalRestrictions, salt); + + // Now, we need to check whether it would be able to accept such an admin + PermanentRestriction restriction = new PermanentRestriction(IBridgehub(address(0)), address(factory)); + + bytes32 codeHash; + assembly { + codeHash := extcodehash(admin) + } + + vm.expectEmit(true, false, false, true); + emit IPermanentRestriction.AllowL2Admin(admin); + restriction.allowL2Admin(salt, codeHash, keccak256(abi.encode(allRestrictions))); + } +} diff --git a/system-contracts/SystemContractsHashes.json b/system-contracts/SystemContractsHashes.json index 8b0feaf78..3f351ca43 100644 --- a/system-contracts/SystemContractsHashes.json +++ b/system-contracts/SystemContractsHashes.json @@ -3,49 +3,49 @@ "contractName": "AccountCodeStorage", "bytecodePath": "artifacts-zk/contracts-preprocessed/AccountCodeStorage.sol/AccountCodeStorage.json", "sourceCodePath": "contracts-preprocessed/AccountCodeStorage.sol", - "bytecodeHash": "0x0100005d7a6c264bffa97a66a5ba8daff43b8a34e5ab7fab4e2ddf74f0a10e4c", + "bytecodeHash": "0x0100005d3ae95fb62791ed4693e614755bd780011ffc3d2dea8344fb1284f9df", "sourceCodeHash": "0x2e0e09d57a04bd1e722d8bf8c6423fdf3f8bca44e5e8c4f6684f987794be066e" }, { "contractName": "BootloaderUtilities", "bytecodePath": "artifacts-zk/contracts-preprocessed/BootloaderUtilities.sol/BootloaderUtilities.json", "sourceCodePath": "contracts-preprocessed/BootloaderUtilities.sol", - "bytecodeHash": "0x010007c7b6bd43d607e55f594e743394b7ae6288ac7f6caad8a7904b6c990e32", + "bytecodeHash": "0x010007c7daac9a547e1e20ed650b09b21668c3fb49e23cea5113dd8cb224b9ac", "sourceCodeHash": "0x0f1213c4b95acb71f4ab5d4082cc1aeb2bd5017e1cccd46afc66e53268609d85" }, { "contractName": "ComplexUpgrader", "bytecodePath": "artifacts-zk/contracts-preprocessed/ComplexUpgrader.sol/ComplexUpgrader.json", "sourceCodePath": "contracts-preprocessed/ComplexUpgrader.sol", - "bytecodeHash": "0x0100004d8c1520bc212778de21b5df751968cbbf1690ddd362ea7ab844ec0b1d", + "bytecodeHash": "0x0100004de3ddc8c5296fed145323ba1d6874af80469e40657bf0ff79d113ccb5", "sourceCodeHash": "0x796046a914fb676ba2bbd337b2924311ee2177ce54571c18a2c3945755c83614" }, { "contractName": "Compressor", "bytecodePath": "artifacts-zk/contracts-preprocessed/Compressor.sol/Compressor.json", "sourceCodePath": "contracts-preprocessed/Compressor.sol", - "bytecodeHash": "0x0100014ba4bf3056bc1d10e64986c71972db9056c879eed689163f7c91bb596f", + "bytecodeHash": "0x0100014b3784efd0fbc6825fa84f3dcf9fc1dcbed37a681c57098c347527ba21", "sourceCodeHash": "0x7240b5fb2ea8e184522e731fb14f764ebae52b8a69d1870a55daedac9a3ed617" }, { "contractName": "ContractDeployer", "bytecodePath": "artifacts-zk/contracts-preprocessed/ContractDeployer.sol/ContractDeployer.json", "sourceCodePath": "contracts-preprocessed/ContractDeployer.sol", - "bytecodeHash": "0x010004e5ad1de716de961c9e52e3b1d6219709891024e5b28d8cde96fe7fdc69", + "bytecodeHash": "0x010004e522c95733920e0a1f072b5dc36dd3d6a1b30515de48423575c10a8f7e", "sourceCodeHash": "0x92bc09da23ed9d86ba7a84f0dbf48503c99582ae58cdbebbdcc5f14ea1fcf014" }, { "contractName": "Create2Factory", "bytecodePath": "artifacts-zk/contracts-preprocessed/Create2Factory.sol/Create2Factory.json", "sourceCodePath": "contracts-preprocessed/Create2Factory.sol", - "bytecodeHash": "0x01000049bae223f356480d01ad05099bad8cfc3e0a91e206ae5dd72abb187cb1", + "bytecodeHash": "0x01000049a4e4beb07895adcdcf34186af3d28a9f3c1d9b56c72c8464730755f1", "sourceCodeHash": "0x114d9322a9ca654989f3e0b3b21f1311dbc4db84f443d054cd414f6414d84de3" }, { "contractName": "DefaultAccount", "bytecodePath": "artifacts-zk/contracts-preprocessed/DefaultAccount.sol/DefaultAccount.json", "sourceCodePath": "contracts-preprocessed/DefaultAccount.sol", - "bytecodeHash": "0x0100055d760f11a3d737e7fd1816e600a4cd874a9f17f7a225d1f1c537c51a1e", + "bytecodeHash": "0x0100055d3993e14104994ca4d8cfa91beb9b544ee86894b45708b4824d832ff2", "sourceCodeHash": "0xebffe840ebbd9329edb1ebff8ca50f6935e7dabcc67194a896fcc2e968d46dfb" }, { @@ -59,63 +59,77 @@ "contractName": "ImmutableSimulator", "bytecodePath": "artifacts-zk/contracts-preprocessed/ImmutableSimulator.sol/ImmutableSimulator.json", "sourceCodePath": "contracts-preprocessed/ImmutableSimulator.sol", - "bytecodeHash": "0x01000039fcb0afdc4480b9a83e0a5cfc2dbc55dce4f6f1d6363778b4e9371ca9", + "bytecodeHash": "0x010000394846da43b9adfe72f0820c19d39daaf861e2eae55d6fe248840f641e", "sourceCodeHash": "0x9659e69f7db09e8f60a8bb95314b1ed26afcc689851665cf27f5408122f60c98" }, { "contractName": "KnownCodesStorage", "bytecodePath": "artifacts-zk/contracts-preprocessed/KnownCodesStorage.sol/KnownCodesStorage.json", "sourceCodePath": "contracts-preprocessed/KnownCodesStorage.sol", - "bytecodeHash": "0x0100006f2889f3200b41f87f8e0835f970e47e513548bbf68239577f8bd97816", + "bytecodeHash": "0x0100006f1fa761c40d5b3325482c8bc9a577ac65278b624523b67eb99cf7e51c", "sourceCodeHash": "0xb39b5b81168653e0c5062f7b8e1d6d15a4e186df3317f192f0cb2fc3a74f5448" }, { "contractName": "L1Messenger", "bytecodePath": "artifacts-zk/contracts-preprocessed/L1Messenger.sol/L1Messenger.json", "sourceCodePath": "contracts-preprocessed/L1Messenger.sol", - "bytecodeHash": "0x010001f735b209b666e9e3d377cf7b5c0483a57dff7b1cd035b58ac10c2e0771", + "bytecodeHash": "0x010001f74edfe69d83816cc586cbb42b2a37d2649dcd1cab88052a37dffaadf3", "sourceCodeHash": "0x8d22a4019347a45cb0c27bed9e98f7033637a7bdcd90fafb1922caa48f2b05de" }, { "contractName": "L2BaseToken", "bytecodePath": "artifacts-zk/contracts-preprocessed/L2BaseToken.sol/L2BaseToken.json", "sourceCodePath": "contracts-preprocessed/L2BaseToken.sol", - "bytecodeHash": "0x010001036ff04ddfde50fac4cf41bf9a34df472373e8b2769938cb35d293f7a7", + "bytecodeHash": "0x0100010395d69e52583bf981c6eb16a7f45a4e930671c69df04e24c58dba3648", "sourceCodeHash": "0x8bdd2b4d0b53dba84c9f0af250bbaa2aad10b3de6747bba957f0bd3721090dfa" }, + { + "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" + }, { "contractName": "L2GenesisUpgrade", "bytecodePath": "artifacts-zk/contracts-preprocessed/L2GenesisUpgrade.sol/L2GenesisUpgrade.json", "sourceCodePath": "contracts-preprocessed/L2GenesisUpgrade.sol", - "bytecodeHash": "0x01000109946aaf60780aa88277365bdd6bc2719f5a2592079a91aae7058cee58", - "sourceCodeHash": "0xbfe430d992d5740c4befdc7adbac2bb9a33c25a45c30ed9fe86c2b4e0263778a" + "bytecodeHash": "0x010000fb0d40ab01a12ce7bad289cb44ab49563e51e6e1d889d431492a4b320f", + "sourceCodeHash": "0xdd7a89c11b624282aed07ef9d985d2fd006015a0aa0b11aec8e445695f7dd689" }, { "contractName": "MsgValueSimulator", "bytecodePath": "artifacts-zk/contracts-preprocessed/MsgValueSimulator.sol/MsgValueSimulator.json", "sourceCodePath": "contracts-preprocessed/MsgValueSimulator.sol", - "bytecodeHash": "0x0100005dc9890cf9aca1c56e823e2147619ea5058c70e123a6a76e51bcee8956", + "bytecodeHash": "0x0100005d895eb5ad625c93a99c3796c18e8fda2e34e9af6997c5208aea197cc2", "sourceCodeHash": "0x082f3dcbc2fe4d93706c86aae85faa683387097d1b676e7ebd00f71ee0f13b71" }, { "contractName": "NonceHolder", "bytecodePath": "artifacts-zk/contracts-preprocessed/NonceHolder.sol/NonceHolder.json", "sourceCodePath": "contracts-preprocessed/NonceHolder.sol", - "bytecodeHash": "0x010000d9ff72782330c259f50fc3ff63e5f8b0a2277e6d652c2a60c56e60efec", + "bytecodeHash": "0x010000d9515266d6a161d41fe0789fe8e75a31a3d8c0ce915ed09fa4ffcd7c61", "sourceCodeHash": "0xcd0c0366effebf2c98c58cf96322cc242a2d1c675620ef5514b7ed1f0a869edc" }, { "contractName": "PubdataChunkPublisher", "bytecodePath": "artifacts-zk/contracts-preprocessed/PubdataChunkPublisher.sol/PubdataChunkPublisher.json", "sourceCodePath": "contracts-preprocessed/PubdataChunkPublisher.sol", - "bytecodeHash": "0x0100004994ca7f560b82e531240c2bac414d02180c33f75363de4edc00796c15", + "bytecodeHash": "0x010000491ab9335e1a112a136580f1f1b2c2929d11be12aed00fd94925bc3fc1", "sourceCodeHash": "0x04d3d2e4019081c87aae5c22a060d84ae2e9d631ebce59801ecce37b9c87e4c7" }, { "contractName": "SystemContext", "bytecodePath": "artifacts-zk/contracts-preprocessed/SystemContext.sol/SystemContext.json", "sourceCodePath": "contracts-preprocessed/SystemContext.sol", - "bytecodeHash": "0x010001a7e10cfa64a3b326897067f582f992db9fbb9bf50304d5db6525de961d", + "bytecodeHash": "0x010001a7baaabeaf80186c0dec134f606186fa9f73f91e753806b424ca8f171f", "sourceCodeHash": "0xb3b8c1f57928938ac590984442bc96c2c888282793014845d5ce2f90bbf2677f" }, { diff --git a/system-contracts/contracts/L2GatewayUpgrade.sol b/system-contracts/contracts/L2GatewayUpgrade.sol new file mode 100644 index 000000000..59d96102d --- /dev/null +++ b/system-contracts/contracts/L2GatewayUpgrade.sol @@ -0,0 +1,78 @@ +// SPDX-License-Identifier: MIT + +pragma solidity 0.8.24; + +import {DEPLOYER_SYSTEM_CONTRACT} from "./Constants.sol"; +import {IContractDeployer, ForceDeployment} from "./interfaces/IContractDeployer.sol"; +import {SystemContractHelper} from "./libraries/SystemContractHelper.sol"; +import {FixedForceDeploymentsData, ZKChainSpecificForceDeploymentsData} from "./interfaces/IL2GenesisUpgrade.sol"; + +import {L2GatewayUpgradeHelper} from "./L2GatewayUpgradeHelper.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"; + +/// @custom:security-contact security@matterlabs.dev +/// @author Matter Labs +/// @notice The contract that is used for facilitating the upgrade of the L2 +/// to the protocol version that supports gateway +/// @dev This contract is neither predeployed nor a system contract. It is located +/// in this folder due to very overlapping functionality with `L2GenesisUpgrade` and +/// facilitating reusage of the code. +/// @dev During the upgrade, it will be delegate-called by the `ComplexUpgrader` contract. +contract L2GatewayUpgrade { + function upgrade( + ForceDeployment[] calldata _forceDeployments, + address _ctmDeployer, + bytes calldata _fixedForceDeploymentsData, + bytes calldata _additionalForceDeploymentsData + ) external payable { + // Firstly, we force deploy the main set of contracts. + // Those will be deployed without any contract invocation. + IContractDeployer(DEPLOYER_SYSTEM_CONTRACT).forceDeployOnAddresses{value: msg.value}(_forceDeployments); + + // Secondly, we perform the more complex deployment of the gateway contracts. + L2GatewayUpgradeHelper.performGatewayContractsInit( + _ctmDeployer, + _fixedForceDeploymentsData, + _additionalForceDeploymentsData + ); + + ZKChainSpecificForceDeploymentsData memory additionalForceDeploymentsData = abi.decode( + _additionalForceDeploymentsData, + (ZKChainSpecificForceDeploymentsData) + ); + + address l2LegacyBridgeAddress = additionalForceDeploymentsData.l2LegacySharedBridge; + + if (l2LegacyBridgeAddress != address(0)) { + FixedForceDeploymentsData memory fixedForceDeploymentsData = abi.decode( + _fixedForceDeploymentsData, + (FixedForceDeploymentsData) + ); + + // Firstly, upgrade the legacy L2SharedBridge + bytes memory bridgeUpgradeData = abi.encodeCall( + ITransparentUpgradeableProxy.upgradeTo, + (fixedForceDeploymentsData.l2SharedBridgeLegacyImpl) + ); + SystemContractHelper.mimicCallWithPropagatedRevert( + l2LegacyBridgeAddress, + fixedForceDeploymentsData.l2BridgeProxyOwnerAddress, + bridgeUpgradeData + ); + + // Secondly, upgrade the tokens + UpgradeableBeacon upgradableBeacon = IL2SharedBridgeLegacy(l2LegacyBridgeAddress).l2TokenBeacon(); + bytes memory beaconUpgradeData = abi.encodeCall( + UpgradeableBeacon.upgradeTo, + (fixedForceDeploymentsData.l2BridgedStandardERC20Impl) + ); + SystemContractHelper.mimicCallWithPropagatedRevert( + address(upgradableBeacon), + fixedForceDeploymentsData.l2BridgedStandardERC20ProxyOwnerAddress, + beaconUpgradeData + ); + } + } +} diff --git a/system-contracts/contracts/L2GatewayUpgradeHelper.sol b/system-contracts/contracts/L2GatewayUpgradeHelper.sol new file mode 100644 index 000000000..e5004bc8a --- /dev/null +++ b/system-contracts/contracts/L2GatewayUpgradeHelper.sol @@ -0,0 +1,117 @@ +// SPDX-License-Identifier: MIT + +pragma solidity 0.8.24; + +import {DEPLOYER_SYSTEM_CONTRACT, L2_BRIDGE_HUB, L2_ASSET_ROUTER, L2_MESSAGE_ROOT, L2_NATIVE_TOKEN_VAULT_ADDR} from "./Constants.sol"; +import {IContractDeployer, ForceDeployment} from "./interfaces/IContractDeployer.sol"; +import {SystemContractHelper} from "./libraries/SystemContractHelper.sol"; +import {FixedForceDeploymentsData, ZKChainSpecificForceDeploymentsData} from "./interfaces/IL2GenesisUpgrade.sol"; + +library L2GatewayUpgradeHelper { + function performGatewayContractsInit( + address _ctmDeployer, + bytes calldata _fixedForceDeploymentsData, + bytes calldata _additionalForceDeploymentsData + ) internal { + ForceDeployment[] memory forceDeployments = _getForceDeploymentsData( + _fixedForceDeploymentsData, + _additionalForceDeploymentsData + ); + IContractDeployer(DEPLOYER_SYSTEM_CONTRACT).forceDeployOnAddresses{value: msg.value}(forceDeployments); + + // It is expected that either via to the force deployments above + // or upon init both the L2 deployment of Bridgehub, AssetRouter and MessageRoot are deployed. + // (The comment does not mention the exact order in case it changes) + // However, there is still some follow up finalization that needs to be done. + + address bridgehubOwner = L2_BRIDGE_HUB.owner(); + + bytes memory data = abi.encodeCall( + L2_BRIDGE_HUB.setAddresses, + (L2_ASSET_ROUTER, _ctmDeployer, address(L2_MESSAGE_ROOT)) + ); + + (bool success, bytes memory returnData) = SystemContractHelper.mimicCall( + address(L2_BRIDGE_HUB), + bridgehubOwner, + data + ); + if (!success) { + // Progapatate revert reason + assembly { + revert(add(returnData, 0x20), returndatasize()) + } + } + } + + function _getForceDeploymentsData( + bytes calldata _fixedForceDeploymentsData, + bytes calldata _additionalForceDeploymentsData + ) internal view returns (ForceDeployment[] memory forceDeployments) { + FixedForceDeploymentsData memory fixedForceDeploymentsData = abi.decode( + _fixedForceDeploymentsData, + (FixedForceDeploymentsData) + ); + ZKChainSpecificForceDeploymentsData memory additionalForceDeploymentsData = abi.decode( + _additionalForceDeploymentsData, + (ZKChainSpecificForceDeploymentsData) + ); + + forceDeployments = new ForceDeployment[](4); + + forceDeployments[0] = ForceDeployment({ + bytecodeHash: fixedForceDeploymentsData.messageRootBytecodeHash, + newAddress: address(L2_MESSAGE_ROOT), + callConstructor: true, + value: 0, + // solhint-disable-next-line func-named-parameters + input: abi.encode(address(L2_BRIDGE_HUB)) + }); + + forceDeployments[1] = ForceDeployment({ + bytecodeHash: fixedForceDeploymentsData.bridgehubBytecodeHash, + newAddress: address(L2_BRIDGE_HUB), + callConstructor: true, + value: 0, + input: abi.encode( + fixedForceDeploymentsData.l1ChainId, + fixedForceDeploymentsData.aliasedL1Governance, + fixedForceDeploymentsData.maxNumberOfZKChains + ) + }); + + forceDeployments[2] = ForceDeployment({ + bytecodeHash: fixedForceDeploymentsData.l2AssetRouterBytecodeHash, + newAddress: address(L2_ASSET_ROUTER), + callConstructor: true, + value: 0, + // solhint-disable-next-line func-named-parameters + input: abi.encode( + fixedForceDeploymentsData.l1ChainId, + fixedForceDeploymentsData.eraChainId, + fixedForceDeploymentsData.l1AssetRouter, + additionalForceDeploymentsData.l2LegacySharedBridge, + additionalForceDeploymentsData.baseTokenAssetId, + fixedForceDeploymentsData.aliasedL1Governance + ) + }); + + forceDeployments[3] = ForceDeployment({ + bytecodeHash: fixedForceDeploymentsData.l2NtvBytecodeHash, + newAddress: L2_NATIVE_TOKEN_VAULT_ADDR, + callConstructor: true, + value: 0, + // solhint-disable-next-line func-named-parameters + input: abi.encode( + fixedForceDeploymentsData.l1ChainId, + fixedForceDeploymentsData.aliasedL1Governance, + fixedForceDeploymentsData.l2TokenProxyBytecodeHash, + additionalForceDeploymentsData.l2LegacySharedBridge, + address(0), // this is used if the contract were already deployed, so for the migration of Era. + false, + additionalForceDeploymentsData.l2Weth, + additionalForceDeploymentsData.baseTokenAssetId + ) + }); + } +} diff --git a/system-contracts/contracts/L2GenesisUpgrade.sol b/system-contracts/contracts/L2GenesisUpgrade.sol index 35d03b648..4d5592529 100644 --- a/system-contracts/contracts/L2GenesisUpgrade.sol +++ b/system-contracts/contracts/L2GenesisUpgrade.sol @@ -2,11 +2,11 @@ pragma solidity 0.8.24; -import {DEPLOYER_SYSTEM_CONTRACT, SYSTEM_CONTEXT_CONTRACT, L2_BRIDGE_HUB, L2_ASSET_ROUTER, L2_MESSAGE_ROOT, L2_NATIVE_TOKEN_VAULT_ADDR} from "./Constants.sol"; -import {IContractDeployer, ForceDeployment} from "./interfaces/IContractDeployer.sol"; -import {SystemContractHelper} from "./libraries/SystemContractHelper.sol"; +import {SYSTEM_CONTEXT_CONTRACT} from "./Constants.sol"; import {ISystemContext} from "./interfaces/ISystemContext.sol"; -import {IL2GenesisUpgrade, FixedForceDeploymentsData, ZKChainSpecificForceDeploymentsData} from "./interfaces/IL2GenesisUpgrade.sol"; +import {IL2GenesisUpgrade} from "./interfaces/IL2GenesisUpgrade.sol"; + +import {L2GatewayUpgradeHelper} from "./L2GatewayUpgradeHelper.sol"; /// @custom:security-contact security@matterlabs.dev /// @author Matter Labs @@ -21,107 +21,13 @@ contract L2GenesisUpgrade is IL2GenesisUpgrade { // solhint-disable-next-line gas-custom-errors require(_chainId != 0, "Invalid chainId"); ISystemContext(SYSTEM_CONTEXT_CONTRACT).setChainId(_chainId); - ForceDeployment[] memory forceDeployments = _getForceDeploymentsData( + + L2GatewayUpgradeHelper.performGatewayContractsInit( + _ctmDeployer, _fixedForceDeploymentsData, _additionalForceDeploymentsData ); - IContractDeployer(DEPLOYER_SYSTEM_CONTRACT).forceDeployOnAddresses{value: msg.value}(forceDeployments); - - // It is expected that either via to the force deployments above - // or upon init both the L2 deployment of Bridgehub, AssetRouter and MessageRoot are deployed. - // (The comment does not mention the exact order in case it changes) - // However, there is still some follow up finalization that needs to be done. - - address bridgehubOwner = L2_BRIDGE_HUB.owner(); - - bytes memory data = abi.encodeCall( - L2_BRIDGE_HUB.setAddresses, - (L2_ASSET_ROUTER, _ctmDeployer, address(L2_MESSAGE_ROOT)) - ); - - (bool success, bytes memory returnData) = SystemContractHelper.mimicCall( - address(L2_BRIDGE_HUB), - bridgehubOwner, - data - ); - if (!success) { - // Progapatate revert reason - assembly { - revert(add(returnData, 0x20), returndatasize()) - } - } emit UpgradeComplete(_chainId); } - - function _getForceDeploymentsData( - bytes calldata _fixedForceDeploymentsData, - bytes calldata _additionalForceDeploymentsData - ) internal view returns (ForceDeployment[] memory forceDeployments) { - FixedForceDeploymentsData memory fixedForceDeploymentsData = abi.decode( - _fixedForceDeploymentsData, - (FixedForceDeploymentsData) - ); - ZKChainSpecificForceDeploymentsData memory additionalForceDeploymentsData = abi.decode( - _additionalForceDeploymentsData, - (ZKChainSpecificForceDeploymentsData) - ); - - forceDeployments = new ForceDeployment[](4); - - forceDeployments[0] = ForceDeployment({ - bytecodeHash: fixedForceDeploymentsData.messageRootBytecodeHash, - newAddress: address(L2_MESSAGE_ROOT), - callConstructor: true, - value: 0, - // solhint-disable-next-line func-named-parameters - input: abi.encode(address(L2_BRIDGE_HUB)) - }); - - forceDeployments[1] = ForceDeployment({ - bytecodeHash: fixedForceDeploymentsData.bridgehubBytecodeHash, - newAddress: address(L2_BRIDGE_HUB), - callConstructor: true, - value: 0, - input: abi.encode( - fixedForceDeploymentsData.l1ChainId, - fixedForceDeploymentsData.aliasedL1Governance, - fixedForceDeploymentsData.maxNumberOfZKChains - ) - }); - - forceDeployments[2] = ForceDeployment({ - bytecodeHash: fixedForceDeploymentsData.l2AssetRouterBytecodeHash, - newAddress: address(L2_ASSET_ROUTER), - callConstructor: true, - value: 0, - // solhint-disable-next-line func-named-parameters - input: abi.encode( - fixedForceDeploymentsData.l1ChainId, - fixedForceDeploymentsData.eraChainId, - fixedForceDeploymentsData.l1AssetRouter, - additionalForceDeploymentsData.l2LegacySharedBridge, - additionalForceDeploymentsData.baseTokenAssetId, - fixedForceDeploymentsData.aliasedL1Governance - ) - }); - - forceDeployments[3] = ForceDeployment({ - bytecodeHash: fixedForceDeploymentsData.l2NtvBytecodeHash, - newAddress: L2_NATIVE_TOKEN_VAULT_ADDR, - callConstructor: true, - value: 0, - // solhint-disable-next-line func-named-parameters - input: abi.encode( - fixedForceDeploymentsData.l1ChainId, - fixedForceDeploymentsData.aliasedL1Governance, - fixedForceDeploymentsData.l2TokenProxyBytecodeHash, - additionalForceDeploymentsData.l2LegacySharedBridge, - address(0), // this is used if the contract were already deployed, so for the migration of Era. - false, - additionalForceDeploymentsData.l2Weth, - additionalForceDeploymentsData.baseTokenAssetId - ) - }); - } } diff --git a/system-contracts/contracts/interfaces/IL2GenesisUpgrade.sol b/system-contracts/contracts/interfaces/IL2GenesisUpgrade.sol index 88566d5d8..8752202a5 100644 --- a/system-contracts/contracts/interfaces/IL2GenesisUpgrade.sol +++ b/system-contracts/contracts/interfaces/IL2GenesisUpgrade.sol @@ -8,6 +8,7 @@ struct ZKChainSpecificForceDeploymentsData { address l2Weth; } +// solhint-disable-next-line gas-struct-packing struct FixedForceDeploymentsData { uint256 l1ChainId; uint256 eraChainId; @@ -19,6 +20,10 @@ struct FixedForceDeploymentsData { bytes32 l2AssetRouterBytecodeHash; bytes32 l2NtvBytecodeHash; bytes32 messageRootBytecodeHash; + address l2SharedBridgeLegacyImpl; + address l2BridgedStandardERC20Impl; + address l2BridgeProxyOwnerAddress; + address l2BridgedStandardERC20ProxyOwnerAddress; } interface IL2GenesisUpgrade { diff --git a/system-contracts/contracts/interfaces/IL2SharedBridgeLegacy.sol b/system-contracts/contracts/interfaces/IL2SharedBridgeLegacy.sol new file mode 100644 index 000000000..05d86757e --- /dev/null +++ b/system-contracts/contracts/interfaces/IL2SharedBridgeLegacy.sol @@ -0,0 +1,32 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.20; + +import {UpgradeableBeacon} from "@openzeppelin/contracts-v4/proxy/beacon/UpgradeableBeacon.sol"; + +/// @author Matter Labs +/// @custom:security-contact security@matterlabs.dev +interface IL2SharedBridgeLegacy { + event FinalizeDeposit( + address indexed l1Sender, + address indexed l2Receiver, + address indexed l2Token, + uint256 amount + ); + + function l2TokenBeacon() external returns (UpgradeableBeacon); + + function withdraw(address _l1Receiver, address _l2Token, uint256 _amount) external; + + function l1TokenAddress(address _l2Token) external view returns (address); + + function l2TokenAddress(address _l1Token) external view returns (address); + + function l1Bridge() external view returns (address); + + function l1SharedBridge() external view returns (address); + + function deployBeaconProxy(bytes32 _salt) external returns (address); + + function sendMessageToL1(bytes calldata _message) external; +} diff --git a/system-contracts/contracts/libraries/SystemContractHelper.sol b/system-contracts/contracts/libraries/SystemContractHelper.sol index 77407b2cd..d3d5e7536 100644 --- a/system-contracts/contracts/libraries/SystemContractHelper.sol +++ b/system-contracts/contracts/libraries/SystemContractHelper.sol @@ -401,4 +401,19 @@ library SystemContractHelper { returndatacopy(add(returndata, 0x20), 0, rtSize) } } + + /// @notice Performs a `mimicCall` to an address, while ensuring that the call + /// was successful + /// @param _to The address to call. + /// @param _whoToMimic The address to mimic. + /// @param _data The data to pass to the call. + function mimicCallWithPropagatedRevert(address _to, address _whoToMimic, bytes memory _data) internal { + (bool success, bytes memory returnData) = mimicCall(_to, _whoToMimic, _data); + if (!success) { + // Propagate revert reason + assembly { + revert(add(returnData, 0x20), returndatasize()) + } + } + } } diff --git a/system-contracts/package.json b/system-contracts/package.json index ab9f8d60e..b1010c7ab 100644 --- a/system-contracts/package.json +++ b/system-contracts/package.json @@ -15,7 +15,9 @@ "fast-glob": "^3.3.2", "hardhat": "=2.22.2", "preprocess": "^3.2.0", - "zksync-ethers": "^5.9.0" + "zksync-ethers": "^5.9.0", + "@openzeppelin/contracts-upgradeable-v4": "npm:@openzeppelin/contracts-upgradeable@4.9.5", + "@openzeppelin/contracts-v4": "npm:@openzeppelin/contracts@4.9.5" }, "devDependencies": { "@matterlabs/hardhat-zksync-chai-matchers": "^0.2.0", diff --git a/system-contracts/test/L2GenesisUpgrade.spec.ts b/system-contracts/test/L2GenesisUpgrade.spec.ts index 916807c99..a7914f705 100644 --- a/system-contracts/test/L2GenesisUpgrade.spec.ts +++ b/system-contracts/test/L2GenesisUpgrade.spec.ts @@ -90,7 +90,7 @@ describe("L2GenesisUpgrade tests", function () { fixedForceDeploymentsData = ethers.utils.defaultAbiCoder.encode( [ - "tuple(uint256 l1ChainId, uint256 eraChainId, address l1AssetRouter, bytes32 l2TokenProxyBytecodeHash, address aliasedL1Governance, uint256 maxNumberOfZKChains, bytes32 bridgehubBytecodeHash, bytes32 l2AssetRouterBytecodeHash, bytes32 l2NtvBytecodeHash, bytes32 messageRootBytecodeHash)", + "tuple(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)", ], [ { @@ -104,6 +104,11 @@ describe("L2GenesisUpgrade tests", function () { l2AssetRouterBytecodeHash: l2AssetRouterBytecodeHash, l2NtvBytecodeHash: ntvBytecodeHash, messageRootBytecodeHash: messageRootBytecodeHash, + // For genesis upgrade these values will always be zero + l2SharedBridgeLegacyImpl: ethers.constants.AddressZero, + l2BridgedStandardERC20Impl: ethers.constants.AddressZero, + l2BridgeProxyOwnerAddress: ethers.constants.AddressZero, + l2BridgedStandardERC20ProxyOwnerAddress: ethers.constants.AddressZero, }, ] );