diff --git a/.github/workflows/l1-contracts-ci.yaml b/.github/workflows/l1-contracts-ci.yaml index d761b63ed..181e6f5cb 100644 --- a/.github/workflows/l1-contracts-ci.yaml +++ b/.github/workflows/l1-contracts-ci.yaml @@ -215,3 +215,56 @@ jobs: coverage-files: ./l1-contracts/lcov.info working-directory: l1-contracts minimum-coverage: 85 # Set coverage threshold. + + gas-report: + needs: [build, lint] + runs-on: ubuntu-latest + + steps: + - name: Checkout the repository + uses: actions/checkout@v4 + with: + submodules: recursive + + - name: Use Foundry + uses: foundry-rs/foundry-toolchain@v1 + + - name: Use Node.js + uses: actions/setup-node@v3 + with: + node-version: 18.18.0 + cache: yarn + + - name: Install dependencies + run: yarn + + - name: Restore artifacts cache + uses: actions/cache/restore@v3 + with: + fail-on-cache-miss: true + key: artifacts-l1-${{ github.sha }} + path: | + l1-contracts/artifacts + l1-contracts/cache + l1-contracts/typechain + + # Add any step generating a gas report to a temporary file named gasreport.ansi. For example: + - name: Run tests + run: yarn l1 test:foundry --gas-report | tee gasreport.ansi # <- this file name should be unique in your repository! + + - name: Compare gas reports + uses: Rubilmax/foundry-gas-diff@v3.18 + with: + summaryQuantile: 0.0 # only display the 10% most significant gas diffs in the summary (defaults to 20%) + sortCriteria: avg,max # sort diff rows by criteria + sortOrders: desc,asc # and directions + ignore: test-foundry/**/*,l1-contracts/contracts/dev-contracts/**/*,l1-contracts/lib/**/*,l1-contracts/contracts/common/Dependencies.sol + id: gas_diff + + - name: Add gas diff to sticky comment + if: github.event_name == 'pull_request' || github.event_name == 'pull_request_target' + uses: marocchino/sticky-pull-request-comment@v2 + with: + # delete the comment in case changes no longer impact gas costs + delete: ${{ !steps.gas_diff.outputs.markdown }} + message: ${{ steps.gas_diff.outputs.markdown }} diff --git a/da-contracts/contracts/CalldataDA.sol b/da-contracts/contracts/CalldataDA.sol index 91ef71db9..c434e540d 100644 --- a/da-contracts/contracts/CalldataDA.sol +++ b/da-contracts/contracts/CalldataDA.sol @@ -4,14 +4,21 @@ pragma solidity 0.8.24; // solhint-disable gas-custom-errors, reason-string -import {BLOB_SIZE_BYTES} from "./DAUtils.sol"; +/// @dev Total number of bytes in a blob. Blob = 4096 field elements * 31 bytes per field element +/// @dev EIP-4844 defines it as 131_072 but we use 4096 * 31 within our circuits to always fit within a field element +/// @dev Our circuits will prove that a EIP-4844 blob and our internal blob are the same. +uint256 constant BLOB_SIZE_BYTES = 126_976; -uint256 constant BLOBS_SUPPORTED = 6; +/// @dev The state diff hash, hash of pubdata + the number of blobs. +uint256 constant BLOB_DATA_OFFSET = 65; + +/// @dev The size of the commitment for a single blob. +uint256 constant BLOB_COMMITMENT_SIZE = 32; /// @notice Contract that contains the functionality for process the calldata DA. /// @dev The expected l2DAValidator that should be used with it `RollupL2DAValidator`. abstract contract CalldataDA { - /// @notice Parses the input that the l2 Da validator has provided to the contract. + /// @notice Parses the input that the L2 DA validator has provided to the contract. /// @param _l2DAValidatorOutputHash The hash of the output of the L2 DA validator. /// @param _maxBlobsSupported The maximal number of blobs supported by the chain. /// @param _operatorDAInput The DA input by the operator provided on L1. @@ -30,14 +37,14 @@ abstract contract CalldataDA { bytes calldata l1DaInput ) { - // The preimage under the hash `l2DAValidatorOutputHash` is expected to be in the following format: + // The preimage under the hash `_l2DAValidatorOutputHash` is expected to be in the following format: // - First 32 bytes are the hash of the uncompressed state diff. // - Then, there is a 32-byte hash of the full pubdata. // - Then, there is the 1-byte number of blobs published. // - Then, there are linear hashes of the published blobs, 32 bytes each. // Check that it accommodates enough pubdata for the state diff hash, hash of pubdata + the number of blobs. - require(_operatorDAInput.length >= 32 + 32 + 1, "too small"); + require(_operatorDAInput.length >= BLOB_DATA_OFFSET, "too small"); stateDiffHash = bytes32(_operatorDAInput[:32]); fullPubdataHash = bytes32(_operatorDAInput[32:64]); @@ -49,43 +56,56 @@ abstract contract CalldataDA { // the `_maxBlobsSupported` blobsLinearHashes = new bytes32[](_maxBlobsSupported); - require(_operatorDAInput.length >= 65 + 32 * blobsProvided, "invalid blobs hashes"); + require(_operatorDAInput.length >= BLOB_DATA_OFFSET + 32 * blobsProvided, "invalid blobs hashes"); - uint256 ptr = 65; + cloneCalldata(blobsLinearHashes, _operatorDAInput[BLOB_DATA_OFFSET:], blobsProvided); - for (uint256 i = 0; i < blobsProvided; ++i) { - // Take the 32 bytes of the blob linear hash - blobsLinearHashes[i] = bytes32(_operatorDAInput[ptr:ptr + 32]); - ptr += 32; - } + uint256 ptr = BLOB_DATA_OFFSET + 32 * blobsProvided; - // Now, we need to double check that the provided input was indeed retutned by the L2 DA validator. + // Now, we need to double check that the provided input was indeed returned by the L2 DA validator. require(keccak256(_operatorDAInput[:ptr]) == _l2DAValidatorOutputHash, "invalid l2 DA output hash"); - // The rest of the output were provided specifically by the operator + // The rest of the output was provided specifically by the operator l1DaInput = _operatorDAInput[ptr:]; } /// @notice Verify that the calldata DA was correctly provided. - /// todo: better doc comments + /// @param _blobsProvided The number of blobs provided. + /// @param _fullPubdataHash Hash of the pubdata preimage. + /// @param _maxBlobsSupported Maximum number of blobs supported. + /// @param _pubdataInput Full pubdata + an additional 32 bytes containing the blob commitment for the pubdata. + /// @dev We supply the blob commitment as part of the pubdata because even with calldata the prover will check these values. function _processCalldataDA( uint256 _blobsProvided, bytes32 _fullPubdataHash, uint256 _maxBlobsSupported, bytes calldata _pubdataInput - ) internal pure returns (bytes32[] memory blobCommitments, bytes calldata _pubdata) { + ) internal pure virtual returns (bytes32[] memory blobCommitments, bytes calldata _pubdata) { + require(_blobsProvided == 1, "one blob with calldata"); + require(_pubdataInput.length >= BLOB_COMMITMENT_SIZE, "pubdata too small"); + // We typically do not know whether we'll use calldata or blobs at the time when // we start proving the batch. That's why the blob commitment for a single blob is still present in the case of calldata. blobCommitments = new bytes32[](_maxBlobsSupported); - require(_blobsProvided == 1, "one one blob with calldata"); - - _pubdata = _pubdataInput[:_pubdataInput.length - 32]; + _pubdata = _pubdataInput[:_pubdataInput.length - BLOB_COMMITMENT_SIZE]; - // FIXME: allow larger lengths for SyncLayer-based chains. require(_pubdata.length <= BLOB_SIZE_BYTES, "cz"); require(_fullPubdataHash == keccak256(_pubdata), "wp"); - blobCommitments[0] = bytes32(_pubdataInput[_pubdataInput.length - 32:_pubdataInput.length]); + blobCommitments[0] = bytes32(_pubdataInput[_pubdataInput.length - BLOB_COMMITMENT_SIZE:_pubdataInput.length]); + } + + /// @notice Method that clones a slice of calldata into a bytes32[] memory array. + /// @param _dst The destination array. + /// @param _input The input calldata. + /// @param _len The length of the slice in 32-byte words to clone. + function cloneCalldata(bytes32[] memory _dst, bytes calldata _input, uint256 _len) internal pure { + assembly { + // The pointer to the allocated memory above. We skip 32 bytes to avoid overwriting the length. + let dstPtr := add(_dst, 0x20) + let inputPtr := _input.offset + calldatacopy(dstPtr, inputPtr, mul(_len, 32)) + } } } diff --git a/da-contracts/contracts/IL1DAValidator.sol b/da-contracts/contracts/IL1DAValidator.sol index 3b4c339b8..c22e9c557 100644 --- a/da-contracts/contracts/IL1DAValidator.sol +++ b/da-contracts/contracts/IL1DAValidator.sol @@ -14,21 +14,22 @@ struct L1DAValidatorOutput { bytes32[] blobsOpeningCommitments; } -// TODO: require EIP165 support as this will allow changes for future compatibility. interface IL1DAValidator { /// @notice The function that checks the data availability for the given batch input. - /// @param chainId The chain id of the chain that is being committed. - /// @param l2DAValidatorOutputHash The hash of that was returned by the l2DAValidator. - /// @param operatorDAInput The DA input by the operator provided on L1. - /// @param maxBlobsSupported The maximal number of blobs supported by the chain. + /// @param _chainId The chain id of the chain that is being committed. + /// @param _chainId The batch number for which the data availability is being checked. + /// @param _l2DAValidatorOutputHash The hash of that was returned by the l2DAValidator. + /// @param _operatorDAInput The DA input by the operator provided on L1. + /// @param _maxBlobsSupported The maximal number of blobs supported by the chain. /// We provide this value for future compatibility. /// This is needed because the corresponding `blobsLinearHashes`/`blobsOpeningCommitments` /// in the `L1DAValidatorOutput` struct will have to have this length as it is required /// to be static by the circuits. function checkDA( - uint256 chainId, - bytes32 l2DAValidatorOutputHash, - bytes calldata operatorDAInput, - uint256 maxBlobsSupported + uint256 _chainId, + uint256 _batchNumber, + bytes32 _l2DAValidatorOutputHash, + bytes calldata _operatorDAInput, + uint256 _maxBlobsSupported ) external returns (L1DAValidatorOutput memory output); } diff --git a/da-contracts/contracts/RollupL1DAValidator.sol b/da-contracts/contracts/RollupL1DAValidator.sol index c50097906..d9077b20b 100644 --- a/da-contracts/contracts/RollupL1DAValidator.sol +++ b/da-contracts/contracts/RollupL1DAValidator.sol @@ -14,7 +14,7 @@ uint256 constant BLOBS_SUPPORTED = 6; contract RollupL1DAValidator is IL1DAValidator, CalldataDA { /// @dev The published blob commitments. Note, that the correctness of blob commitment with relation to the linear hash - /// is *not* checked in this contract, but is expected to be checked at the veriifcation stage of the ZK contract. + /// is *not* checked in this contract, but is expected to be checked at the verification stage of the ZK contract. mapping(bytes32 blobCommitment => bool isPublished) public publishedBlobCommitments; /// @notice Publishes certain blobs, marking commitments to them as published. @@ -37,6 +37,49 @@ contract RollupL1DAValidator is IL1DAValidator, CalldataDA { } } + /// @inheritdoc IL1DAValidator + function checkDA( + uint256, // _chainId + uint256, // _batchNumber + bytes32 _l2DAValidatorOutputHash, + bytes calldata _operatorDAInput, + uint256 _maxBlobsSupported + ) external view returns (L1DAValidatorOutput memory output) { + ( + bytes32 stateDiffHash, + bytes32 fullPubdataHash, + bytes32[] memory blobsLinearHashes, + uint256 blobsProvided, + bytes calldata l1DaInput + ) = _processL2RollupDAValidatorOutputHash(_l2DAValidatorOutputHash, _maxBlobsSupported, _operatorDAInput); + + uint8 pubdataSource = uint8(l1DaInput[0]); + bytes32[] memory blobCommitments; + + if (pubdataSource == uint8(PubdataSource.Blob)) { + blobCommitments = _processBlobDA(blobsProvided, _maxBlobsSupported, l1DaInput[1:]); + } else if (pubdataSource == uint8(PubdataSource.Calldata)) { + (blobCommitments, ) = _processCalldataDA(blobsProvided, fullPubdataHash, _maxBlobsSupported, l1DaInput[1:]); + } else { + revert("l1-da-validator/invalid-pubdata-source"); + } + + // We verify that for each set of blobHash/blobCommitment are either both empty + // or there are values for both. + // This is mostly a sanity check and it is not strictly required. + for (uint256 i = 0; i < _maxBlobsSupported; ++i) { + require( + (blobsLinearHashes[i] == bytes32(0) && blobCommitments[i] == bytes32(0)) || + (blobsLinearHashes[i] != bytes32(0) && blobCommitments[i] != bytes32(0)), + "bh" + ); + } + + output.stateDiffHash = stateDiffHash; + output.blobsLinearHashes = blobsLinearHashes; + output.blobsOpeningCommitments = blobCommitments; + } + /// @notice Generated the blob commitemnt to be used in the cryptographic proof by calling the point evaluation precompile. /// @param _index The index of the blob in this transaction. /// @param _commitment The packed: opening point (16 bytes) || claimed value (32 bytes) || commitment (48 bytes) || proof (48 bytes)) = 144 bytes @@ -81,7 +124,7 @@ contract RollupL1DAValidator is IL1DAValidator, CalldataDA { uint256 versionedHashIndex = 0; - // we iterate over the `_operatorDAInput`, while advacning the pointer by `BLOB_DA_INPUT_SIZE` each time + // we iterate over the `_operatorDAInput`, while advancing the pointer by `BLOB_DA_INPUT_SIZE` each time for (uint256 i = 0; i < _blobsProvided; ++i) { bytes calldata commitmentData = _operatorDAInput[:PUBDATA_COMMITMENT_SIZE]; bytes32 prepublishedCommitment = bytes32( @@ -109,48 +152,6 @@ contract RollupL1DAValidator is IL1DAValidator, CalldataDA { require(versionedHash == bytes32(0), "lh"); } - /// @inheritdoc IL1DAValidator - function checkDA( - uint256, // _chainId - bytes32 _l2DAValidatorOutputHash, - bytes calldata _operatorDAInput, - uint256 _maxBlobsSupported - ) external returns (L1DAValidatorOutput memory output) { - ( - bytes32 stateDiffHash, - bytes32 fullPubdataHash, - bytes32[] memory blobsLinearHashes, - uint256 blobsProvided, - bytes calldata l1DaInput - ) = _processL2RollupDAValidatorOutputHash(_l2DAValidatorOutputHash, _maxBlobsSupported, _operatorDAInput); - - uint8 pubdataSource = uint8(l1DaInput[0]); - bytes32[] memory blobCommitments; - - if (pubdataSource == uint8(PubdataSource.Blob)) { - blobCommitments = _processBlobDA(blobsProvided, _maxBlobsSupported, l1DaInput[1:]); - } else if (pubdataSource == uint8(PubdataSource.Calldata)) { - (blobCommitments, ) = _processCalldataDA(blobsProvided, fullPubdataHash, _maxBlobsSupported, l1DaInput[1:]); - } else { - revert("l1-da-validator/invalid-pubdata-source"); - } - - // We verify that for each set of blobHash/blobCommitment are either both empty - // or there are values for both. - // This is mostly a sanity check and it is not strictly required. - for (uint256 i = 0; i < _maxBlobsSupported; ++i) { - require( - (blobsLinearHashes[i] == bytes32(0) && blobCommitments[i] == bytes32(0)) || - (blobsLinearHashes[i] != bytes32(0) && blobCommitments[i] != bytes32(0)), - "bh" - ); - } - - output.stateDiffHash = stateDiffHash; - output.blobsLinearHashes = blobsLinearHashes; - output.blobsOpeningCommitments = blobCommitments; - } - /// @notice Calls the point evaluation precompile and verifies the output /// Verify p(z) = y given commitment that corresponds to the polynomial p(x) and a KZG proof. /// Also verify that the provided commitment matches the provided versioned_hash. diff --git a/da-contracts/contracts/ValidiumL1DAValidator.sol b/da-contracts/contracts/ValidiumL1DAValidator.sol index b63ff5a3a..501a56879 100644 --- a/da-contracts/contracts/ValidiumL1DAValidator.sol +++ b/da-contracts/contracts/ValidiumL1DAValidator.sol @@ -9,13 +9,14 @@ import {IL1DAValidator, L1DAValidatorOutput} from "./IL1DAValidator.sol"; contract ValidiumL1DAValidator is IL1DAValidator { function checkDA( uint256, // _chainId + uint256, // _batchNumber bytes32, // _l2DAValidatorOutputHash bytes calldata _operatorDAInput, uint256 // maxBlobsSupported ) external override returns (L1DAValidatorOutput memory output) { // For Validiums, we expect the operator to just provide the data for us. // We don't need to do any checks with regard to the l2DAValidatorOutputHash. - require(_operatorDAInput.length == 32); + require(_operatorDAInput.length == 32, "ValL1DA wrong input length"); bytes32 stateDiffHash = abi.decode(_operatorDAInput, (bytes32)); @@ -24,6 +25,6 @@ contract ValidiumL1DAValidator is IL1DAValidator { } function supportsInterface(bytes4 interfaceId) external pure returns (bool) { - return interfaceId == type(IL1DAValidator).interfaceId; + return (interfaceId == this.supportsInterface.selector) || (interfaceId == type(IL1DAValidator).interfaceId); } } diff --git a/docs/gateway/contracts-review-gateway.md b/docs/gateway/contracts-review-gateway.md index e4a34126c..1b2980c37 100644 --- a/docs/gateway/contracts-review-gateway.md +++ b/docs/gateway/contracts-review-gateway.md @@ -26,7 +26,7 @@ Known issues, and features that still need to be implemented: - Upgrade process, how do we upgrade to CAB bridge, to the new system contracts. - We had the syncLayer internal name previously for the Gateway. This has not been replaced everywhere yet. - permissions for some functions are not properly restricted yet, mostly they are missing a modifier. -- Bridgehub setAssetHandlerAddressInitial `address sender` might be an issue. +- Bridgehub setAssetHandlerAddress `address sender` might be an issue. - MessageRoot should be renamed to MessageRootAggregator ![Untitled](./Hyperchain-scheme.png) diff --git a/gas-bound-caller/contracts/test-contracts/SystemContractsCaller.sol b/gas-bound-caller/contracts/test-contracts/SystemContractsCaller.sol index ca7c870c7..1f154e270 100644 --- a/gas-bound-caller/contracts/test-contracts/SystemContractsCaller.sol +++ b/gas-bound-caller/contracts/test-contracts/SystemContractsCaller.sol @@ -6,7 +6,7 @@ import {MSG_VALUE_SYSTEM_CONTRACT, MSG_VALUE_SIMULATOR_IS_SYSTEM_BIT} from "@mat import {Utils} from "@matterlabs/zksync-contracts/l2/system-contracts/libraries/Utils.sol"; // Addresses used for the compiler to be replaced with the -// zkSync-specific opcodes during the compilation. +// ZKsync-specific opcodes during the compilation. // IMPORTANT: these are just compile-time constants and are used // only if used in-place by Yul optimizer. address constant TO_L1_CALL_ADDRESS = address((1 << 16) - 1); diff --git a/l1-contracts/contracts/bridge/BridgeHelper.sol b/l1-contracts/contracts/bridge/BridgeHelper.sol new file mode 100644 index 000000000..9fc9b7cfc --- /dev/null +++ b/l1-contracts/contracts/bridge/BridgeHelper.sol @@ -0,0 +1,29 @@ +// SPDX-License-Identifier: MIT + +pragma solidity 0.8.24; + +// solhint-disable gas-custom-errors + +import {IERC20Metadata} from "@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol"; + +/** + * @author Matter Labs + * @custom:security-contact security@matterlabs.dev + * @notice Helper library for working with L2 contracts on L1. + */ +library BridgeHelper { + /// @dev Receives and parses (name, symbol, decimals) from the token contract + function getERC20Getters(address _token, address _ethTokenAddress) internal view returns (bytes memory) { + if (_token == _ethTokenAddress) { + bytes memory name = abi.encode("Ether"); + bytes memory symbol = abi.encode("ETH"); + bytes memory decimals = abi.encode(uint8(18)); + return abi.encode(name, symbol, decimals); // when depositing eth to a non-eth based chain it is an ERC20 + } + + (, bytes memory data1) = _token.staticcall(abi.encodeCall(IERC20Metadata.name, ())); + (, bytes memory data2) = _token.staticcall(abi.encodeCall(IERC20Metadata.symbol, ())); + (, bytes memory data3) = _token.staticcall(abi.encodeCall(IERC20Metadata.decimals, ())); + return abi.encode(data1, data2, data3); + } +} diff --git a/l1-contracts/contracts/bridge/L1AssetRouter.sol b/l1-contracts/contracts/bridge/L1AssetRouter.sol index 3b4730a4c..6dff9cc36 100644 --- a/l1-contracts/contracts/bridge/L1AssetRouter.sol +++ b/l1-contracts/contracts/bridge/L1AssetRouter.sol @@ -7,7 +7,6 @@ pragma solidity 0.8.24; import {Ownable2StepUpgradeable} from "@openzeppelin/contracts-upgradeable/access/Ownable2StepUpgradeable.sol"; import {PausableUpgradeable} from "@openzeppelin/contracts-upgradeable/security/PausableUpgradeable.sol"; -import {IERC20Metadata} from "@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol"; import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; @@ -22,6 +21,7 @@ import {IMailbox} from "../state-transition/chain-interfaces/IMailbox.sol"; import {L2Message, TxStatus} from "../common/Messaging.sol"; import {UnsafeBytes} from "../common/libraries/UnsafeBytes.sol"; import {ReentrancyGuard} from "../common/ReentrancyGuard.sol"; +import {DataEncoding} from "../common/libraries/DataEncoding.sol"; import {AddressAliasHelper} from "../vendor/AddressAliasHelper.sol"; import {TWO_BRIDGES_MAGIC_VALUE, ETH_TOKEN_ADDRESS} from "../common/Config.sol"; import {L2_NATIVE_TOKEN_VAULT_ADDRESS} from "../common/L2ContractAddresses.sol"; @@ -29,6 +29,8 @@ import {L2_NATIVE_TOKEN_VAULT_ADDRESS} from "../common/L2ContractAddresses.sol"; import {IBridgehub, L2TransactionRequestTwoBridgesInner, L2TransactionRequestDirect} from "../bridgehub/IBridgehub.sol"; import {L2_BASE_TOKEN_SYSTEM_CONTRACT_ADDR, L2_ASSET_ROUTER_ADDR} from "../common/L2ContractAddresses.sol"; +import {BridgeHelper} from "./BridgeHelper.sol"; + /// @author Matter Labs /// @custom:security-contact security@matterlabs.dev /// @dev Bridges assets between L1 and ZK chain, supporting both ETH and ERC20 tokens. @@ -45,20 +47,20 @@ contract L1AssetRouter is IL1AssetRouter, ReentrancyGuard, Ownable2StepUpgradeab /// @dev Era's chainID uint256 internal immutable ERA_CHAIN_ID; - /// @dev The address of zkSync Era diamond proxy contract. + /// @dev The address of ZKsync Era diamond proxy contract. address internal immutable ERA_DIAMOND_PROXY; - /// @dev Stores the first batch number on the zkSync Era Diamond Proxy that was settled after Diamond proxy upgrade. + /// @dev Stores the first batch number on the ZKsync Era Diamond Proxy that was settled after Diamond proxy upgrade. /// This variable is used to differentiate between pre-upgrade and post-upgrade Eth withdrawals. Withdrawals from batches older /// than this value are considered to have been finalized prior to the upgrade and handled separately. uint256 internal eraPostDiamondUpgradeFirstBatch; - /// @dev Stores the first batch number on the zkSync Era Diamond Proxy that was settled after L1ERC20 Bridge upgrade. + /// @dev Stores the first batch number on the ZKsync Era Diamond Proxy that was settled after L1ERC20 Bridge upgrade. /// This variable is used to differentiate between pre-upgrade and post-upgrade ERC20 withdrawals. Withdrawals from batches older /// than this value are considered to have been finalized prior to the upgrade and handled separately. uint256 internal eraPostLegacyBridgeUpgradeFirstBatch; - /// @dev Stores the zkSync Era batch number that processes the last deposit tx initiated by the legacy bridge. + /// @dev Stores the ZKsync Era batch number that processes the last deposit tx initiated by the legacy bridge /// This variable (together with eraLegacyBridgeLastDepositTxNumber) is used to differentiate between pre-upgrade and post-upgrade deposits. Deposits processed in older batches /// than this value are considered to have been processed prior to the upgrade and handled separately. /// We use this both for Eth and erc20 token deposits, so we need to update the diamond and bridge simultaneously. @@ -76,8 +78,10 @@ contract L1AssetRouter is IL1AssetRouter, ReentrancyGuard, Ownable2StepUpgradeab /// @dev A mapping chainId => bridgeProxy. Used to store the bridge proxy's address, and to see if it has been deployed yet. mapping(uint256 chainId => address l2Bridge) public __DEPRECATED_l2BridgeAddress; - /// @dev A mapping chainId => L2 deposit transaction hash => keccak256(abi.encode(account, tokenAddress, amount)). - /// @dev Tracks deposit transactions from L2 to enable users to claim their funds if a deposit fails. + /// @dev A mapping chainId => L2 deposit transaction hash => dataHash + // keccak256(abi.encode(account, tokenAddress, amount)) for legacy transfers + // keccak256(abi.encode(_prevMsgSender, assetId, transferData)) for new transfers + /// @dev Tracks deposit transactions to L2 to enable users to claim their funds if a deposit fails. mapping(uint256 chainId => mapping(bytes32 l2DepositTxHash => bytes32 depositDataHash)) public override depositHappened; @@ -86,6 +90,7 @@ contract L1AssetRouter is IL1AssetRouter, ReentrancyGuard, Ownable2StepUpgradeab mapping(uint256 chainId => mapping(uint256 l2BatchNumber => mapping(uint256 l2ToL1MessageNumber => bool isFinalized))) public isWithdrawalFinalized; + /// @notice Deprecated. Kept for backwards compatibility. /// @dev Indicates whether the hyperbridging is enabled for a given chain. // slither-disable-next-line uninitialized-state mapping(uint256 chainId => bool enabled) public hyperbridgingEnabled; @@ -110,7 +115,7 @@ contract L1AssetRouter is IL1AssetRouter, ReentrancyGuard, Ownable2StepUpgradeab /// @notice Checks that the message sender is the bridgehub. modifier onlyBridgehub() { - require(msg.sender == address(BRIDGE_HUB), "ShB not BH"); + require(msg.sender == address(BRIDGE_HUB), "L1AR: not BH"); _; } @@ -118,14 +123,14 @@ contract L1AssetRouter is IL1AssetRouter, ReentrancyGuard, Ownable2StepUpgradeab modifier onlyBridgehubOrEra(uint256 _chainId) { require( msg.sender == address(BRIDGE_HUB) || (_chainId == ERA_CHAIN_ID && msg.sender == ERA_DIAMOND_PROXY), - "L1AssetRouter: msg.sender not equal to bridgehub or era chain" + "L1AR: msg.sender not equal to bridgehub or era chain" ); _; } /// @notice Checks that the message sender is the legacy bridge. modifier onlyLegacyBridge() { - require(msg.sender == address(legacyBridge), "ShB not legacy bridge"); + require(msg.sender == address(legacyBridge), "L1AR: not legacy bridge"); _; } @@ -159,7 +164,7 @@ contract L1AssetRouter is IL1AssetRouter, ReentrancyGuard, Ownable2StepUpgradeab uint256 _eraLegacyBridgeLastDepositBatch, uint256 _eraLegacyBridgeLastDepositTxNumber ) external reentrancyGuardInitializer initializer { - require(_owner != address(0), "ShB owner 0"); + require(_owner != address(0), "L1AR: owner 0"); _transferOwnership(_owner); if (eraPostDiamondUpgradeFirstBatch == 0) { eraPostDiamondUpgradeFirstBatch = _eraPostDiamondUpgradeFirstBatch; @@ -174,7 +179,7 @@ contract L1AssetRouter is IL1AssetRouter, ReentrancyGuard, Ownable2StepUpgradeab /// @param _token The address of the token to be transferred to NTV. function transferTokenToNTV(address _token) external { address ntvAddress = address(nativeTokenVault); - require(msg.sender == ntvAddress, "ShB: not NTV"); + require(msg.sender == ntvAddress, "L1AR: not NTV"); if (ETH_TOKEN_ADDRESS == _token) { uint256 amount = address(this).balance; bool callSuccess; @@ -182,7 +187,7 @@ contract L1AssetRouter is IL1AssetRouter, ReentrancyGuard, Ownable2StepUpgradeab assembly { callSuccess := call(gas(), ntvAddress, amount, 0, 0, 0, 0) } - require(callSuccess, "ShB: eth transfer failed"); + require(callSuccess, "L1AR: eth transfer failed"); } else { IERC20(_token).safeTransfer(ntvAddress, IERC20(_token).balanceOf(address(this))); } @@ -192,8 +197,8 @@ contract L1AssetRouter is IL1AssetRouter, ReentrancyGuard, Ownable2StepUpgradeab /// @dev This function is part of the upgrade process used to nullify chain balances once they are credited to NTV. /// @param _chainId The ID of the ZK chain. /// @param _token The address of the token which was previously deposit to shared bridge. - function clearChainBalance(uint256 _chainId, address _token) external { - require(msg.sender == address(nativeTokenVault), "ShB: not NTV"); + function nullifyChainBalanceByNTV(uint256 _chainId, address _token) external { + require(msg.sender == address(nativeTokenVault), "L1AR: not NTV"); chainBalance[_chainId][_token] = 0; } @@ -201,30 +206,50 @@ contract L1AssetRouter is IL1AssetRouter, ReentrancyGuard, Ownable2StepUpgradeab /// @dev Should be called only once by the owner. /// @param _legacyBridge The address of the legacy bridge. function setL1Erc20Bridge(address _legacyBridge) external onlyOwner { - require(address(legacyBridge) == address(0), "ShB: legacy bridge already set"); - require(_legacyBridge != address(0), "ShB: legacy bridge 0"); + require(address(legacyBridge) == address(0), "L1AR: legacy bridge already set"); + require(_legacyBridge != address(0), "L1AR: legacy bridge 0"); legacyBridge = IL1ERC20Bridge(_legacyBridge); } - /// @notice Sets the L1ERC20Bridge contract address. + /// @notice Sets the nativeTokenVault contract address. /// @dev Should be called only once by the owner. /// @param _nativeTokenVault The address of the native token vault. function setNativeTokenVault(IL1NativeTokenVault _nativeTokenVault) external onlyOwner { - require(address(nativeTokenVault) == address(0), "ShB: native token vault already set"); - require(address(_nativeTokenVault) != address(0), "ShB: native token vault 0"); + require(address(nativeTokenVault) == address(0), "L1AR: native token vault already set"); + require(address(_nativeTokenVault) != address(0), "L1AR: native token vault 0"); nativeTokenVault = _nativeTokenVault; } - /// @notice Sets the asset handler address for a given asset ID. - /// @dev No access control on the caller, as msg.sender is encoded in the assetId. - /// @param _assetData In most cases this parameter is bytes32 encoded token address. However, it can include extra information used by custom asset handlers. - /// @param _assetHandlerAddress The address of the asset handler, which will hold the token of interest. - function setAssetHandlerAddressInitial(bytes32 _assetData, address _assetHandlerAddress) external { - address sender = msg.sender == address(nativeTokenVault) ? L2_NATIVE_TOKEN_VAULT_ADDRESS : msg.sender; - bytes32 assetId = keccak256(abi.encode(uint256(block.chainid), sender, _assetData)); + /// @notice Used to set the assed deployment tracker address for given asset data. + /// @param _assetRegistrationData The asset data which may include the asset address and any additional required data or encodings. + /// @param _assetDeploymentTracker The whitelisted address of asset deployment tracker for provided asset. + function setAssetDeploymentTracker( + bytes32 _assetRegistrationData, + address _assetDeploymentTracker + ) external onlyOwner { + bytes32 assetId = keccak256( + abi.encode(uint256(block.chainid), _assetDeploymentTracker, _assetRegistrationData) + ); + assetDeploymentTracker[assetId] = _assetDeploymentTracker; + emit AssetDeploymentTrackerSet(assetId, _assetDeploymentTracker, _assetRegistrationData); + } + + /// @notice Sets the asset handler address for a specified asset ID on the chain of the asset deployment tracker. + /// @dev The caller of this function is encoded within the `assetId`, therefore, it should be invoked by the asset deployment tracker contract. + /// @dev Typically, for most tokens, ADT is the native token vault. However, custom tokens may have their own specific asset deployment trackers. + /// @dev `setAssetHandlerAddressOnCounterPart` should be called on L1 to set asset handlers on L2 chains for a specific asset ID. + /// @param _assetRegistrationData The asset data which may include the asset address and any additional required data or encodings. + /// @param _assetHandlerAddress The address of the asset handler to be set for the provided asset. + function setAssetHandlerAddressThisChain(bytes32 _assetRegistrationData, address _assetHandlerAddress) external { + bool senderIsNTV = msg.sender == address(nativeTokenVault); + address sender = senderIsNTV ? L2_NATIVE_TOKEN_VAULT_ADDRESS : msg.sender; + bytes32 assetId = DataEncoding.encodeAssetId(block.chainid, _assetRegistrationData, sender); + require(senderIsNTV || msg.sender == assetDeploymentTracker[assetId], "ShB: not NTV or ADT"); assetHandlerAddress[assetId] = _assetHandlerAddress; - assetDeploymentTracker[assetId] = msg.sender; - emit AssetHandlerRegisteredInitial(assetId, _assetHandlerAddress, _assetData, sender); + if (senderIsNTV) { + assetDeploymentTracker[assetId] = msg.sender; + } + emit AssetHandlerRegisteredInitial(assetId, _assetHandlerAddress, _assetRegistrationData, sender); } /// @notice Used to set the asset handler address for a given asset ID on a remote ZK chain @@ -235,8 +260,8 @@ contract L1AssetRouter is IL1AssetRouter, ReentrancyGuard, Ownable2StepUpgradeab /// @param _l2TxGasPerPubdataByte The gasPerPubdataByteLimit to be used in the corresponding L2 transaction. /// @param _refundRecipient The address on L2 that will receive the refund for the transaction. /// @param _assetId The encoding of asset ID. - /// @param _assetAddressOnCounterPart The address of the asset handler, which will hold the token of interest. - /// @return l2TxHash The L2 transaction hash of setting asset handler on remote chain. + /// @param _assetHandlerAddressOnCounterPart The address of the asset handler, which will hold the token of interest. + /// @return txHash The L2 transaction hash of setting asset handler on remote chain. function setAssetHandlerAddressOnCounterPart( uint256 _chainId, uint256 _mintValue, @@ -244,27 +269,27 @@ contract L1AssetRouter is IL1AssetRouter, ReentrancyGuard, Ownable2StepUpgradeab uint256 _l2TxGasPerPubdataByte, address _refundRecipient, bytes32 _assetId, - address _assetAddressOnCounterPart - ) external payable returns (bytes32 l2TxHash) { - require(msg.sender == assetDeploymentTracker[_assetId] || msg.sender == owner(), "ShB: only ADT or owner"); + address _assetHandlerAddressOnCounterPart + ) external payable returns (bytes32 txHash) { + require(msg.sender == assetDeploymentTracker[_assetId] || msg.sender == owner(), "L1AR: only ADT or owner"); bytes memory l2Calldata = abi.encodeCall( IL2Bridge.setAssetHandlerAddress, - (_assetId, _assetAddressOnCounterPart) + (_assetId, _assetHandlerAddressOnCounterPart) ); L2TransactionRequestDirect memory request = L2TransactionRequestDirect({ chainId: _chainId, l2Contract: L2_ASSET_ROUTER_ADDR, - mintValue: _mintValue, // l2 gas + l2 msg.Value the bridgehub will withdraw the mintValue from the base token bridge for gas - l2Value: 0, // L2 msg.value, this contract doesn't support base token deposits or wrapping functionality, for direct deposits use bridgehub + mintValue: _mintValue, // l2 gas + l2 msg.value the bridgehub will withdraw the mintValue from the base token bridge for gas + l2Value: 0, // For base token deposits, there is no msg.value during the call, as the base token is minted to the recipient address l2Calldata: l2Calldata, l2GasLimit: _l2TxGasLimit, l2GasPerPubdataByteLimit: _l2TxGasPerPubdataByte, factoryDeps: new bytes[](0), refundRecipient: _refundRecipient }); - l2TxHash = BRIDGE_HUB.requestL2TransactionDirect{value: msg.value}(request); + txHash = BRIDGE_HUB.requestL2TransactionDirect{value: msg.value}(request); } /// @notice Allows bridgehub to acquire mintValue for L1->L2 transactions. @@ -278,13 +303,15 @@ contract L1AssetRouter is IL1AssetRouter, ReentrancyGuard, Ownable2StepUpgradeab bytes32 _assetId, address _prevMsgSender, uint256 _amount - ) external payable virtual onlyBridgehubOrEra(_chainId) whenNotPaused { - address l1AssetHandler = _getAssetHandler(_assetId); + ) external payable onlyBridgehubOrEra(_chainId) whenNotPaused { + address l1AssetHandler = assetHandlerAddress[_assetId]; + require(l1AssetHandler != address(0), "ShB: asset handler not set"); + _transferAllowanceToNTV(_assetId, _amount, _prevMsgSender); // slither-disable-next-line unused-return IL1AssetHandler(l1AssetHandler).bridgeBurn{value: msg.value}({ _chainId: _chainId, - _mintValue: _amount, + _l2Value: 0, _assetId: _assetId, _prevMsgSender: _prevMsgSender, _data: abi.encode(_amount, address(0)) @@ -294,70 +321,6 @@ contract L1AssetRouter is IL1AssetRouter, ReentrancyGuard, Ownable2StepUpgradeab emit BridgehubDepositBaseTokenInitiated(_chainId, _prevMsgSender, _assetId, _amount); } - /// @notice Returns the address of asset handler. - /// @dev If asset handler is not set for the asset, the asset is registered. - /// @param _assetId The encoding of asset ID. - /// @return l1AssetHandler The address of asset handler for provided asset ID. - function _getAssetHandler(bytes32 _assetId) internal returns (address l1AssetHandler) { - l1AssetHandler = assetHandlerAddress[_assetId]; - // Check if no asset handler is set - if (l1AssetHandler == address(0)) { - require(uint256(_assetId) <= type(uint160).max, "ShB: only address can be registered"); - l1AssetHandler = address(nativeTokenVault); - nativeTokenVault.registerToken(address(uint160(uint256(_assetId)))); - } - } - - /// @notice Decodes the transfer input for legacy data and transfers allowance to NTV. - /// @dev Is not applicable for custom asset handlers. - /// @param _data The encoded transfer data (address _l1Token, uint256 _depositAmount, address _l2Receiver). - /// @param _prevMsgSender The address of the deposit initiator. - /// @return Tuple of asset ID and encoded transfer data to conform with new encoding standard. - function _handleLegacyData(bytes calldata _data, address _prevMsgSender) internal returns (bytes32, bytes memory) { - (address _l1Token, uint256 _depositAmount, address _l2Receiver) = abi.decode( - _data, - (address, uint256, address) - ); - bytes32 assetId = _ensureTokenRegisteredWithNTV(_l1Token); - _transferAllowanceToNTV(assetId, _depositAmount, _prevMsgSender); - return (assetId, abi.encode(_depositAmount, _l2Receiver)); - } - - /// @notice Ensures that token is registered with native token vault. - /// @dev Only used when deposit is made with legacy data encoding format. - /// @param _l1Token The L1 token address which should be registered with native token vault. - /// @return assetId The asset ID of the token provided. - function _ensureTokenRegisteredWithNTV(address _l1Token) internal returns (bytes32 assetId) { - assetId = nativeTokenVault.getAssetId(_l1Token); - if (nativeTokenVault.tokenAddress(assetId) == address(0)) { - nativeTokenVault.registerToken(_l1Token); - } - } - - /// @notice Transfers allowance to Native Token Vault, if the asset is registered with it. Does nothing for ETH or non-registered tokens. - /// @dev assetId is not the padded address, but the correct encoded ID (NTV stores respective format for IDs). - /// @param _assetId The encoding of asset ID. - /// @param _amount The asset amount to be transferred to native token vault. - /// @param _prevMsgSender The `msg.sender` address from the external call that initiated current one. - function _transferAllowanceToNTV(bytes32 _assetId, uint256 _amount, address _prevMsgSender) internal { - address l1TokenAddress = nativeTokenVault.tokenAddress(_assetId); - if (l1TokenAddress == address(0) || l1TokenAddress == ETH_TOKEN_ADDRESS) { - return; - } - IERC20 l1Token = IERC20(l1TokenAddress); - - // Do the transfer if allowance to Shared bridge is bigger than amount - // And if there is not enough allowance for the NTV - if ( - l1Token.allowance(_prevMsgSender, address(this)) >= _amount && - l1Token.allowance(_prevMsgSender, address(nativeTokenVault)) < _amount - ) { - // slither-disable-next-line arbitrary-send-erc20 - l1Token.safeTransferFrom(_prevMsgSender, address(this), _amount); - l1Token.safeIncreaseAllowance(address(nativeTokenVault), _amount); - } - } - /// @notice Initiates a deposit transaction within Bridgehub, used by `requestL2TransactionTwoBridges`. /// @param _chainId The chain ID of the ZK chain to which deposit. /// @param _prevMsgSender The `msg.sender` address from the external call that initiated current one. @@ -382,36 +345,35 @@ contract L1AssetRouter is IL1AssetRouter, ReentrancyGuard, Ownable2StepUpgradeab bool legacyDeposit = false; bytes1 encodingVersion = _data[0]; + // The new encoding ensures that the calldata is collision-resistant with respect to the legacy format. + // In the legacy calldata, the first input was the address, meaning the most significant byte was always `0x00`. if (encodingVersion == 0x01) { (assetId, transferData) = abi.decode(_data[1:], (bytes32, bytes)); + require( + assetHandlerAddress[assetId] != address(nativeTokenVault), + "ShB: new encoding format not yet supported for NTV" + ); } else { (assetId, transferData) = _handleLegacyData(_data, _prevMsgSender); legacyDeposit = true; } - require(BRIDGE_HUB.baseTokenAssetId(_chainId) != assetId, "ShB: baseToken deposit not supported"); + require(BRIDGE_HUB.baseTokenAssetId(_chainId) != assetId, "L1AR: baseToken deposit not supported"); - bytes memory l2BridgeMintCalldata = _burn({ + bytes memory bridgeMintCalldata = _burn({ _chainId: _chainId, _l2Value: _l2Value, _assetId: assetId, _prevMsgSender: _prevMsgSender, - _transferData: transferData + _transferData: transferData, + _passValue: true }); - bytes32 txDataHash; - - if (legacyDeposit) { - (uint256 _depositAmount, ) = abi.decode(transferData, (uint256, address)); - txDataHash = keccak256(abi.encode(_prevMsgSender, nativeTokenVault.tokenAddress(assetId), _depositAmount)); - } else { - txDataHash = keccak256(bytes.concat(bytes1(0x01), abi.encode(_prevMsgSender, assetId, transferData))); - } + bytes32 txDataHash = this.encodeTxDataHash(legacyDeposit, _prevMsgSender, assetId, transferData); request = _requestToBridge({ - _chainId: _chainId, _prevMsgSender: _prevMsgSender, _assetId: assetId, - _l2BridgeMintCalldata: l2BridgeMintCalldata, + _bridgeMintCalldata: bridgeMintCalldata, _txDataHash: txDataHash }); @@ -420,58 +382,7 @@ contract L1AssetRouter is IL1AssetRouter, ReentrancyGuard, Ownable2StepUpgradeab txDataHash: txDataHash, from: _prevMsgSender, assetId: assetId, - l2BridgeMintCalldata: l2BridgeMintCalldata - }); - } - - /// @notice Forwards the burn request for specific asset to respective asset handler. - /// @param _chainId The chain ID of the ZK chain to which deposit. - /// @param _l2Value The L2 `msg.value` from the L1 -> L2 deposit transaction. - /// @param _assetId The deposited asset ID. - /// @param _prevMsgSender The `msg.sender` address from the external call that initiated current one. - /// @param _transferData The encoded data, which is used by the asset handler to determine L2 recipient and amount. Might include extra information. - /// @return l2BridgeMintCalldata The calldata used by remote asset handler to mint tokens for recipient. - function _burn( - uint256 _chainId, - uint256 _l2Value, - bytes32 _assetId, - address _prevMsgSender, - bytes memory _transferData - ) internal returns (bytes memory l2BridgeMintCalldata) { - address l1AssetHandler = assetHandlerAddress[_assetId]; - l2BridgeMintCalldata = IL1AssetHandler(l1AssetHandler).bridgeBurn{value: msg.value}({ - _chainId: _chainId, - _mintValue: _l2Value, - _assetId: _assetId, - _prevMsgSender: _prevMsgSender, - _data: _transferData - }); - } - - /// @dev The request data that is passed to the bridgehub. - /// @param _chainId The chain ID of the ZK chain to which deposit. - /// @param _prevMsgSender The `msg.sender` address from the external call that initiated current one. - /// @param _assetId The deposited asset ID. - /// @param _l2BridgeMintCalldata The calldata used by remote asset handler to mint tokens for recipient. - /// @param _txDataHash The keccak256 hash of 0x01 || abi.encode(bytes32, bytes) to identify deposits. - /// @return request The data used by the bridgehub to create L2 transaction request to specific ZK chain. - function _requestToBridge( - // solhint-disable-next-line no-unused-vars - uint256 _chainId, - address _prevMsgSender, - bytes32 _assetId, - bytes memory _l2BridgeMintCalldata, - bytes32 _txDataHash - ) internal view returns (L2TransactionRequestTwoBridgesInner memory request) { - // Request the finalization of the deposit on the L2 side - bytes memory l2TxCalldata = _getDepositL2Calldata(_prevMsgSender, _assetId, _l2BridgeMintCalldata); - - request = L2TransactionRequestTwoBridgesInner({ - magicValue: TWO_BRIDGES_MAGIC_VALUE, - l2Contract: L2_ASSET_ROUTER_ADDR, - l2Calldata: l2TxCalldata, - factoryDeps: new bytes[](0), - txDataHash: _txDataHash + bridgeMintCalldata: bridgeMintCalldata }); } @@ -485,42 +396,56 @@ contract L1AssetRouter is IL1AssetRouter, ReentrancyGuard, Ownable2StepUpgradeab bytes32 _txDataHash, bytes32 _txHash ) external override onlyBridgehub whenNotPaused { - require(depositHappened[_chainId][_txHash] == 0x00, "ShB tx hap"); + require(depositHappened[_chainId][_txHash] == 0x00, "L1AR: tx hap"); depositHappened[_chainId][_txHash] = _txDataHash; emit BridgehubDepositFinalized(_chainId, _txDataHash, _txHash); } - /// @notice Generates a calldata for calling the deposit finalization on the L2 native token contract. - /// @param _l1Sender The address of the deposit initiator. - /// @param _assetId The deposited asset ID. - /// @param _transferData The encoded data, which is used by the asset handler to determine L2 recipient and amount. Might include extra information. - /// @return Returns calldata used on ZK chain. - function _getDepositL2Calldata( - address _l1Sender, + /// @notice Finalize the withdrawal and release funds + /// @param _chainId The chain ID of the transaction to check + /// @param _l2BatchNumber The L2 batch number where the withdrawal was processed + /// @param _l2MessageIndex The position in the L2 logs Merkle tree of the l2Log that was sent with the message + /// @param _l2TxNumberInBatch The L2 transaction number in the batch, in which the log was sent + /// @param _message The L2 withdraw data, stored in an L2 -> L1 message + /// @param _merkleProof The Merkle proof of the inclusion L2 -> L1 message about withdrawal initialization + function finalizeWithdrawal( + uint256 _chainId, + uint256 _l2BatchNumber, + uint256 _l2MessageIndex, + uint16 _l2TxNumberInBatch, + bytes calldata _message, + bytes32[] calldata _merkleProof + ) external override { + _finalizeWithdrawal({ + _chainId: _chainId, + _l2BatchNumber: _l2BatchNumber, + _l2MessageIndex: _l2MessageIndex, + _l2TxNumberInBatch: _l2TxNumberInBatch, + _message: _message, + _merkleProof: _merkleProof + }); + } + + /// @dev Calls the internal `_encodeTxDataHash`. Used as a wrapped for try / catch case. + /// @param _isLegacyEncoding Boolean flag indicating whether to use the legacy encoding standard (true) or the latest encoding standard (false). + /// @param _prevMsgSender The address of the entity that initiated the deposit. + /// @param _assetId The unique identifier of the deposited L1 token. + /// @param _transferData The encoded transfer data, which includes both the deposit amount and the address of the L2 receiver. + /// @return txDataHash The resulting encoded transaction data hash. + function encodeTxDataHash( + bool _isLegacyEncoding, + address _prevMsgSender, bytes32 _assetId, - bytes memory _transferData - ) internal view returns (bytes memory) { - // First branch covers the case when asset is not registered with NTV (custom asset handler) - // Second branch handles tokens registered with NTV and uses legacy calldata encoding - if (nativeTokenVault.tokenAddress(_assetId) == address(0)) { - return abi.encodeCall(IL2Bridge.finalizeDeposit, (_assetId, _transferData)); - } else { - (uint256 _amount, , address _l2Receiver, bytes memory _gettersData, address _parsedL1Token) = abi.decode( - _transferData, - (uint256, address, address, bytes, address) - ); - return - abi.encodeCall( - IL2BridgeLegacy.finalizeDeposit, - (_l1Sender, _l2Receiver, _parsedL1Token, _amount, _gettersData) - ); - } + bytes calldata _transferData + ) external view returns (bytes32 txDataHash) { + return _encodeTxDataHash(_isLegacyEncoding, _prevMsgSender, _assetId, _transferData); } /// @dev Withdraw funds from the initiated deposit, that failed when finalizing on L2. - /// @param _depositSender The address of the deposit initiator. - /// @param _assetId The address of the deposited L1 ERC20 token. - /// @param _transferData The encoded data, which is used by the asset handler to determine L2 recipient and amount. Might include extra information. + /// @param _chainId The ZK chain id to which deposit was initiated. + /// @param _depositSender The address of the entity that initiated the deposit. + /// @param _assetId The unique identifier of the deposited L1 token. + /// @param _assetData The encoded transfer data, which includes both the deposit amount and the address of the L2 receiver. Might include extra information. /// @param _l2TxHash The L2 transaction hash of the failed deposit finalization. /// @param _l2BatchNumber The L2 batch number where the deposit finalization was processed. /// @param _l2MessageIndex The position in the L2 logs Merkle tree of the l2Log that was sent with the message. @@ -531,7 +456,7 @@ contract L1AssetRouter is IL1AssetRouter, ReentrancyGuard, Ownable2StepUpgradeab uint256 _chainId, address _depositSender, bytes32 _assetId, - bytes memory _transferData, + bytes memory _assetData, bytes32 _l2TxHash, uint256 _l2BatchNumber, uint256 _l2MessageIndex, @@ -551,85 +476,62 @@ contract L1AssetRouter is IL1AssetRouter, ReentrancyGuard, Ownable2StepUpgradeab require(proofValid, "yn"); } - require(!_isEraLegacyDeposit(_chainId, _l2BatchNumber, _l2TxNumberInBatch), "ShB: legacy cFD"); + require(!_isEraLegacyDeposit(_chainId, _l2BatchNumber, _l2TxNumberInBatch), "L1AR: legacy cFD"); { bytes32 dataHash = depositHappened[_chainId][_l2TxHash]; - address l1Token = nativeTokenVault.tokenAddress(_assetId); - (uint256 amount, address prevMsgSender) = abi.decode(_transferData, (uint256, address)); - bytes32 txDataHash = keccak256(abi.encode(prevMsgSender, l1Token, amount)); - require(dataHash == txDataHash, "ShB: d.it not hap"); + // Determine if the given dataHash matches the calculated legacy transaction hash. + bool isLegacyTxDataHash = _isLegacyTxDataHash(_depositSender, _assetId, _assetData, dataHash); + // If the dataHash matches the legacy transaction hash, skip the next step. + // Otherwise, perform the check using the new transaction data hash encoding. + if (!isLegacyTxDataHash) { + bytes32 txDataHash = _encodeTxDataHash(false, _depositSender, _assetId, _assetData); + require(dataHash == txDataHash, "L1AR: d.it not hap"); + } } delete depositHappened[_chainId][_l2TxHash]; - IL1AssetHandler(assetHandlerAddress[_assetId]).bridgeRecoverFailedTransfer(_chainId, _assetId, _transferData); - - emit ClaimedFailedDepositSharedBridge(_chainId, _depositSender, _assetId, _transferData); - } + IL1AssetHandler(assetHandlerAddress[_assetId]).bridgeRecoverFailedTransfer( + _chainId, + _assetId, + _depositSender, + _assetData + ); - /// @dev Determines if an eth withdrawal was initiated on zkSync Era before the upgrade to the Shared Bridge. - /// @param _chainId The chain ID of the transaction to check. - /// @param _l2BatchNumber The L2 batch number for the withdrawal. - /// @return Whether withdrawal was initiated on zkSync Era before diamond proxy upgrade. - function _isEraLegacyEthWithdrawal(uint256 _chainId, uint256 _l2BatchNumber) internal view returns (bool) { - require((_chainId != ERA_CHAIN_ID) || eraPostDiamondUpgradeFirstBatch != 0, "ShB: diamondUFB not set for Era"); - return (_chainId == ERA_CHAIN_ID) && (_l2BatchNumber < eraPostDiamondUpgradeFirstBatch); + emit ClaimedFailedDepositSharedBridge(_chainId, _depositSender, _assetId, _assetData); } - /// @dev Determines if a token withdrawal was initiated on zkSync Era before the upgrade to the Shared Bridge. - /// @param _chainId The chain ID of the transaction to check. - /// @param _l2BatchNumber The L2 batch number for the withdrawal. - /// @return Whether withdrawal was initiated on zkSync Era before Legacy Bridge upgrade. - function _isEraLegacyTokenWithdrawal(uint256 _chainId, uint256 _l2BatchNumber) internal view returns (bool) { - require( - (_chainId != ERA_CHAIN_ID) || eraPostLegacyBridgeUpgradeFirstBatch != 0, - "ShB: LegacyUFB not set for Era" - ); - return (_chainId == ERA_CHAIN_ID) && (_l2BatchNumber < eraPostLegacyBridgeUpgradeFirstBatch); + /// @dev Receives and parses (name, symbol, decimals) from the token contract + function getERC20Getters(address _token) public view returns (bytes memory) { + return BridgeHelper.getERC20Getters(_token, ETH_TOKEN_ADDRESS); } - /// @dev Determines if a deposit was initiated on zkSync Era before the upgrade to the Shared Bridge. - /// @param _chainId The chain ID of the transaction to check. - /// @param _l2BatchNumber The L2 batch number for the deposit where it was processed. - /// @param _l2TxNumberInBatch The L2 transaction number in the batch, in which the deposit was processed. - /// @return Whether deposit was initiated on zkSync Era before Shared Bridge upgrade. - function _isEraLegacyDeposit( + /// @dev send the burn message to the asset + /// @notice Forwards the burn request for specific asset to respective asset handler + /// @param _chainId The chain ID of the ZK chain to which deposit. + /// @param _l2Value The L2 `msg.value` from the L1 -> L2 deposit transaction. + /// @param _assetId The deposited asset ID. + /// @param _prevMsgSender The `msg.sender` address from the external call that initiated current one. + /// @param _transferData The encoded data, which is used by the asset handler to determine L2 recipient and amount. Might include extra information. + /// @param _passValue Boolean indicating whether to pass msg.value in the call. + /// @return bridgeMintCalldata The calldata used by remote asset handler to mint tokens for recipient. + function _burn( uint256 _chainId, - uint256 _l2BatchNumber, - uint256 _l2TxNumberInBatch - ) internal view returns (bool) { - require( - (_chainId != ERA_CHAIN_ID) || (eraLegacyBridgeLastDepositBatch != 0), - "ShB: last deposit time not set for Era" - ); - return - (_chainId == ERA_CHAIN_ID) && - (_l2BatchNumber < eraLegacyBridgeLastDepositBatch || - (_l2TxNumberInBatch < eraLegacyBridgeLastDepositTxNumber && - _l2BatchNumber == eraLegacyBridgeLastDepositBatch)); - } + uint256 _l2Value, + bytes32 _assetId, + address _prevMsgSender, + bytes memory _transferData, + bool _passValue + ) internal returns (bytes memory bridgeMintCalldata) { + address l1AssetHandler = assetHandlerAddress[_assetId]; + require(l1AssetHandler != address(0), "ShB: asset handler does not exist for assetId"); - /// @notice Finalize the withdrawal and release funds. - /// @param _chainId The chain ID of the transaction to check. - /// @param _l2BatchNumber The L2 batch number where the withdrawal was processed. - /// @param _l2MessageIndex The position in the L2 logs Merkle tree of the l2Log that was sent with the message. - /// @param _l2TxNumberInBatch The L2 transaction number in the batch, in which the log was sent. - /// @param _message The L2 withdraw data, stored in an L2 -> L1 message. - /// @param _merkleProof The Merkle proof of the inclusion L2 -> L1 message about withdrawal initialization. - function finalizeWithdrawal( - uint256 _chainId, - uint256 _l2BatchNumber, - uint256 _l2MessageIndex, - uint16 _l2TxNumberInBatch, - bytes calldata _message, - bytes32[] calldata _merkleProof - ) external override { - _finalizeWithdrawal({ + uint256 msgValue = _passValue ? msg.value : 0; + bridgeMintCalldata = IL1AssetHandler(l1AssetHandler).bridgeBurn{value: msgValue}({ _chainId: _chainId, - _l2BatchNumber: _l2BatchNumber, - _l2MessageIndex: _l2MessageIndex, - _l2TxNumberInBatch: _l2TxNumberInBatch, - _message: _message, - _merkleProof: _merkleProof + _l2Value: _l2Value, + _assetId: _assetId, + _prevMsgSender: _prevMsgSender, + _data: _transferData }); } @@ -657,12 +559,16 @@ contract L1AssetRouter is IL1AssetRouter, ReentrancyGuard, Ownable2StepUpgradeab bytes calldata _message, bytes32[] calldata _merkleProof ) internal nonReentrant whenNotPaused returns (address l1Receiver, bytes32 assetId, uint256 amount) { - require(!isWithdrawalFinalized[_chainId][_l2BatchNumber][_l2MessageIndex], "Withdrawal is already finalized"); + require( + !isWithdrawalFinalized[_chainId][_l2BatchNumber][_l2MessageIndex], + "L1AR: Withdrawal is already finalized" + ); isWithdrawalFinalized[_chainId][_l2BatchNumber][_l2MessageIndex] = true; - // Handling special case for withdrawal from zkSync Era initiated before Shared Bridge. - require(!_isEraLegacyEthWithdrawal(_chainId, _l2BatchNumber), "ShB: legacy eth withdrawal"); - require(!_isEraLegacyTokenWithdrawal(_chainId, _l2BatchNumber), "ShB: legacy token withdrawal"); + // Handling special case for withdrawal from ZKsync Era initiated before Shared Bridge. + require(!_isEraLegacyEthWithdrawal(_chainId, _l2BatchNumber), "L1AR: legacy eth withdrawal"); + require(!_isEraLegacyTokenWithdrawal(_chainId, _l2BatchNumber), "L1AR: legacy token withdrawal"); + bytes memory transferData; { MessageParams memory messageParams = MessageParams({ @@ -680,6 +586,172 @@ contract L1AssetRouter is IL1AssetRouter, ReentrancyGuard, Ownable2StepUpgradeab emit WithdrawalFinalizedSharedBridge(_chainId, l1Receiver, assetId, amount); } + /// @notice Decodes the transfer input for legacy data and transfers allowance to NTV + /// @dev Is not applicable for custom asset handlers + /// @param _data encoded transfer data (address _l1Token, uint256 _depositAmount, address _l2Receiver) + /// @param _prevMsgSender address of the deposit initiator + function _handleLegacyData(bytes calldata _data, address _prevMsgSender) internal returns (bytes32, bytes memory) { + (address _l1Token, uint256 _depositAmount, address _l2Receiver) = abi.decode( + _data, + (address, uint256, address) + ); + bytes32 assetId = _ensureTokenRegisteredWithNTV(_l1Token); + _transferAllowanceToNTV(assetId, _depositAmount, _prevMsgSender); + return (assetId, abi.encode(_depositAmount, _l2Receiver)); + } + + function _ensureTokenRegisteredWithNTV(address _l1Token) internal returns (bytes32 assetId) { + assetId = DataEncoding.encodeNTVAssetId(block.chainid, _l1Token); + if (nativeTokenVault.tokenAddress(assetId) == address(0)) { + nativeTokenVault.registerToken(_l1Token); + } + } + + /// @notice Transfers allowance to Native Token Vault, if the asset is registered with it. Does nothing for ETH or non-registered tokens. + /// @dev assetId is not the padded address, but the correct encoded id (NTV stores respective format for IDs) + function _transferAllowanceToNTV(bytes32 _assetId, uint256 _amount, address _prevMsgSender) internal { + address l1TokenAddress = nativeTokenVault.tokenAddress(_assetId); + if (l1TokenAddress == address(0) || l1TokenAddress == ETH_TOKEN_ADDRESS) { + return; + } + IERC20 l1Token = IERC20(l1TokenAddress); + + // Do the transfer if allowance to Shared bridge is bigger than amount + // And if there is not enough allowance for the NTV + if ( + l1Token.allowance(_prevMsgSender, address(this)) >= _amount && + l1Token.allowance(_prevMsgSender, address(nativeTokenVault)) < _amount + ) { + // slither-disable-next-line arbitrary-send-erc20 + l1Token.safeTransferFrom(_prevMsgSender, address(this), _amount); + l1Token.forceApprove(address(nativeTokenVault), _amount); + } + } + + /// @dev The request data that is passed to the bridgehub + function _requestToBridge( + address _prevMsgSender, + bytes32 _assetId, + bytes memory _bridgeMintCalldata, + bytes32 _txDataHash + ) internal view returns (L2TransactionRequestTwoBridgesInner memory request) { + // Request the finalization of the deposit on the L2 side + bytes memory l2TxCalldata = _getDepositL2Calldata(_prevMsgSender, _assetId, _bridgeMintCalldata); + + request = L2TransactionRequestTwoBridgesInner({ + magicValue: TWO_BRIDGES_MAGIC_VALUE, + l2Contract: L2_ASSET_ROUTER_ADDR, + l2Calldata: l2TxCalldata, + factoryDeps: new bytes[](0), + txDataHash: _txDataHash + }); + } + + /// @dev Generate a calldata for calling the deposit finalization on the L2 bridge contract + function _getDepositL2Calldata( + address _l1Sender, + bytes32 _assetId, + bytes memory _assetData + ) internal view returns (bytes memory) { + // First branch covers the case when asset is not registered with NTV (custom asset handler) + // Second branch handles tokens registered with NTV and uses legacy calldata encoding + if (nativeTokenVault.tokenAddress(_assetId) == address(0)) { + return abi.encodeCall(IL2Bridge.finalizeDeposit, (_assetId, _assetData)); + } else { + // slither-disable-next-line unused-return + (, address _l2Receiver, address _parsedL1Token, uint256 _amount, bytes memory _gettersData) = DataEncoding + .decodeBridgeMintData(_assetData); + return + abi.encodeCall( + IL2BridgeLegacy.finalizeDeposit, + (_l1Sender, _l2Receiver, _parsedL1Token, _amount, _gettersData) + ); + } + } + + /// @dev Determines if an eth withdrawal was initiated on zkSync Era before the upgrade to the Shared Bridge. + /// @param _chainId The chain ID of the transaction to check. + /// @param _l2BatchNumber The L2 batch number for the withdrawal. + /// @return Whether withdrawal was initiated on ZKsync Era before diamond proxy upgrade. + function _isEraLegacyEthWithdrawal(uint256 _chainId, uint256 _l2BatchNumber) internal view returns (bool) { + require((_chainId != ERA_CHAIN_ID) || eraPostDiamondUpgradeFirstBatch != 0, "L1AR: diamondUFB not set for Era"); + return (_chainId == ERA_CHAIN_ID) && (_l2BatchNumber < eraPostDiamondUpgradeFirstBatch); + } + + /// @dev Determines if a token withdrawal was initiated on ZKsync Era before the upgrade to the Shared Bridge. + /// @param _chainId The chain ID of the transaction to check. + /// @param _l2BatchNumber The L2 batch number for the withdrawal. + /// @return Whether withdrawal was initiated on ZKsync Era before Legacy Bridge upgrade. + function _isEraLegacyTokenWithdrawal(uint256 _chainId, uint256 _l2BatchNumber) internal view returns (bool) { + require( + (_chainId != ERA_CHAIN_ID) || eraPostLegacyBridgeUpgradeFirstBatch != 0, + "L1AR: LegacyUFB not set for Era" + ); + return (_chainId == ERA_CHAIN_ID) && (_l2BatchNumber < eraPostLegacyBridgeUpgradeFirstBatch); + } + + /// @dev Determines if the provided data for a failed deposit corresponds to a legacy failed deposit. + /// @param _prevMsgSender The address of the entity that initiated the deposit. + /// @param _assetId The unique identifier of the deposited L1 token. + /// @param _transferData The encoded transfer data, which includes both the deposit amount and the address of the L2 receiver. + /// @param _expectedTxDataHash The nullifier data hash stored for the failed deposit. + /// @return isLegacyTxDataHash True if the transaction is legacy, false otherwise. + function _isLegacyTxDataHash( + address _prevMsgSender, + bytes32 _assetId, + bytes memory _transferData, + bytes32 _expectedTxDataHash + ) internal view returns (bool isLegacyTxDataHash) { + try this.encodeTxDataHash(true, _prevMsgSender, _assetId, _transferData) returns (bytes32 txDataHash) { + return txDataHash == _expectedTxDataHash; + } catch { + return false; + } + } + + /// @dev Encodes the transaction data hash using either the latest encoding standard or the legacy standard. + /// @param _isLegacyEncoding Boolean flag indicating whether to use the legacy encoding standard (true) or the latest encoding standard (false). + /// @param _prevMsgSender The address of the entity that initiated the deposit. + /// @param _assetId The unique identifier of the deposited L1 token. + /// @param _transferData The encoded transfer data, which includes both the deposit amount and the address of the L2 receiver. + /// @return txDataHash The resulting encoded transaction data hash. + function _encodeTxDataHash( + bool _isLegacyEncoding, + address _prevMsgSender, + bytes32 _assetId, + bytes memory _transferData + ) internal view returns (bytes32 txDataHash) { + if (_isLegacyEncoding) { + (uint256 depositAmount, ) = abi.decode(_transferData, (uint256, address)); + txDataHash = keccak256(abi.encode(_prevMsgSender, nativeTokenVault.tokenAddress(_assetId), depositAmount)); + } else { + // Similarly to calldata, the txDataHash is collision-resistant. + // In the legacy data hash, the first encoded variable was the address, which is padded with zeros during `abi.encode`. + txDataHash = keccak256(bytes.concat(bytes1(0x01), abi.encode(_prevMsgSender, _assetId, _transferData))); + } + } + + /// @dev Determines if a deposit was initiated on zkSync Era before the upgrade to the Shared Bridge. + /// @param _chainId The chain ID of the transaction to check. + /// @param _l2BatchNumber The L2 batch number for the deposit where it was processed. + /// @param _l2TxNumberInBatch The L2 transaction number in the batch, in which the deposit was processed. + /// @return Whether deposit was initiated on ZKsync Era before Shared Bridge upgrade. + function _isEraLegacyDeposit( + uint256 _chainId, + uint256 _l2BatchNumber, + uint256 _l2TxNumberInBatch + ) internal view returns (bool) { + require( + (_chainId != ERA_CHAIN_ID) || (eraLegacyBridgeLastDepositBatch != 0), + "L1AR: last deposit time not set for Era" + ); + return + (_chainId == ERA_CHAIN_ID) && + (_l2BatchNumber < eraLegacyBridgeLastDepositBatch || + (_l2TxNumberInBatch <= eraLegacyBridgeLastDepositTxNumber && + _l2BatchNumber == eraLegacyBridgeLastDepositBatch)); + } + /// @notice Verifies the validity of a withdrawal message from L2 and returns withdrawal details. /// @param _chainId The chain ID of the transaction to check. /// @param _messageParams The message params, which include batch number, message index, and L2 tx number in batch. @@ -713,7 +785,7 @@ contract L1AssetRouter is IL1AssetRouter, ReentrancyGuard, Ownable2StepUpgradeab _message: l2ToL1Message, _proof: _merkleProof }); - require(success, "ShB withd w proof"); // withdrawal wrong proof + require(success, "L1AR: withd w proof"); // withdrawal wrong proof } /// @notice Parses the withdrawal message and returns withdrawal details. @@ -728,72 +800,63 @@ contract L1AssetRouter is IL1AssetRouter, ReentrancyGuard, Ownable2StepUpgradeab bytes memory _l2ToL1message ) internal view returns (bytes32 assetId, bytes memory transferData) { // We check that the message is long enough to read the data. - // Please note that there are two versions of the message: - // 1. The message that is sent by `withdraw(address _l1Receiver)` + // Please note that there are three versions of the message: + // 1. The message that is sent by `withdraw(address _l1Receiver)` or `withdrawWithMessage`. In the second case, this function ignores the extra data // It should be equal to the length of the bytes4 function signature + address l1Receiver + uint256 amount = 4 + 20 + 32 = 56 (bytes). - // 2. The message that is encoded by `getL1WithdrawMessage(bytes32 _assetId, bytes memory _bridgeMintData)` - // No length is assume. The assetId is decoded and the mintData is passed to respective assetHandler - - // So the data is expected to be at least 56 bytes long. - require(_l2ToL1message.length >= 56, "ShB wrong msg len"); // wrong message length - uint256 amount; - address l1Receiver; + // 2. The legacy `getL1WithdrawMessage`, the length of the data is known. + // 3. The message that is encoded by `getL1WithdrawMessage(bytes32 _assetId, bytes memory _bridgeMintData)` + // No length is assumed. The assetId is decoded and the mintData is passed to respective assetHandler (uint32 functionSignature, uint256 offset) = UnsafeBytes.readUint32(_l2ToL1message, 0); if (bytes4(functionSignature) == IMailbox.finalizeEthWithdrawal.selector) { + uint256 amount; + address l1Receiver; + + // The data is expected to be at least 56 bytes long. + require(_l2ToL1message.length >= 56, "L1AR: wrong msg len"); // wrong message length // this message is a base token withdrawal (l1Receiver, offset) = UnsafeBytes.readAddress(_l2ToL1message, offset); - (amount, offset) = UnsafeBytes.readUint256(_l2ToL1message, offset); + // slither-disable-next-line unused-return + (amount, ) = UnsafeBytes.readUint256(_l2ToL1message, offset); assetId = BRIDGE_HUB.baseTokenAssetId(_chainId); transferData = abi.encode(amount, l1Receiver); } else if (bytes4(functionSignature) == IL1ERC20Bridge.finalizeWithdrawal.selector) { - // We use the IL1ERC20Bridge for backward compatibility with old withdrawals. address l1Token; - // this message is a token withdrawal + uint256 amount; + address l1Receiver; + // We use the IL1ERC20Bridge for backward compatibility with old withdrawals. + // This message is a token withdrawal // Check that the message length is correct. // It should be equal to the length of the function signature + address + address + uint256 = 4 + 20 + 20 + 32 = // 76 (bytes). - require(_l2ToL1message.length == 76, "ShB wrong msg len 2"); + require(_l2ToL1message.length == 76, "L1AR: wrong msg len 2"); (l1Receiver, offset) = UnsafeBytes.readAddress(_l2ToL1message, offset); (l1Token, offset) = UnsafeBytes.readAddress(_l2ToL1message, offset); - (amount, offset) = UnsafeBytes.readUint256(_l2ToL1message, offset); + // slither-disable-next-line unused-return + (amount, ) = UnsafeBytes.readUint256(_l2ToL1message, offset); - assetId = keccak256(abi.encode(block.chainid, L2_NATIVE_TOKEN_VAULT_ADDRESS, l1Token)); + assetId = DataEncoding.encodeNTVAssetId(block.chainid, l1Token); transferData = abi.encode(amount, l1Receiver); } else if (bytes4(functionSignature) == this.finalizeWithdrawal.selector) { - //todo + // The data is expected to be at least 36 bytes long to contain assetId. + require(_l2ToL1message.length >= 36, "L1AR: wrong msg len"); // wrong message length (assetId, offset) = UnsafeBytes.readBytes32(_l2ToL1message, offset); transferData = UnsafeBytes.readRemainingBytes(_l2ToL1message, offset); } else { - revert("ShB Incorrect message function selector"); - } - } - - /// @notice Receives and parses (name, symbol, decimals) from the token contract. - /// @param _token The address of token of interest. - /// @return Returns encoded name, symbol, and decimals for specific token. - function getERC20Getters(address _token) public view returns (bytes memory) { - if (_token == ETH_TOKEN_ADDRESS) { - bytes memory name = bytes("Ether"); - bytes memory symbol = bytes("ETH"); - bytes memory decimals = abi.encode(uint8(18)); - return abi.encode(name, symbol, decimals); // when depositing eth to a non-eth based chain it is an ERC20 + revert("L1AR: Incorrect message function selector"); } - - (, bytes memory data1) = _token.staticcall(abi.encodeCall(IERC20Metadata.name, ())); - (, bytes memory data2) = _token.staticcall(abi.encodeCall(IERC20Metadata.symbol, ())); - (, bytes memory data3) = _token.staticcall(abi.encodeCall(IERC20Metadata.decimals, ())); - return abi.encode(data1, data2, data3); } /*////////////////////////////////////////////////////////////// SHARED BRIDGE TOKEN BRIDGING LEGACY FUNCTIONS //////////////////////////////////////////////////////////////*/ - /// @dev Withdraw funds from the initiated deposit, that failed when finalizing on L2. + /// @notice Withdraw funds from the initiated deposit, that failed when finalizing on L2. + /// @dev Cannot be used to claim deposits made with new encoding. + /// @param _chainId The ZK chain id to which deposit was initiated. /// @param _depositSender The address of the deposit initiator. - /// @param _l1Token The address of the deposited L1 ERC20 token. + /// @param _l1Asset The address of the deposited L1 ERC20 token. /// @param _amount The amount of the deposit that failed. /// @param _l2TxHash The L2 transaction hash of the failed deposit finalization. /// @param _l2BatchNumber The L2 batch number where the deposit finalization was processed. @@ -803,7 +866,7 @@ contract L1AssetRouter is IL1AssetRouter, ReentrancyGuard, Ownable2StepUpgradeab function claimFailedDeposit( uint256 _chainId, address _depositSender, - address _l1Token, + address _l1Asset, uint256 _amount, bytes32 _l2TxHash, uint256 _l2BatchNumber, @@ -811,13 +874,14 @@ contract L1AssetRouter is IL1AssetRouter, ReentrancyGuard, Ownable2StepUpgradeab uint16 _l2TxNumberInBatch, bytes32[] calldata _merkleProof ) external override { - bytes32 assetId = nativeTokenVault.getAssetId(_l1Token); - bytes memory transferData = abi.encode(_amount, _depositSender); + bytes32 assetId = DataEncoding.encodeNTVAssetId(block.chainid, _l1Asset); + // For legacy deposits, the l2 receiver is not required to check tx data hash + bytes memory transferData = abi.encode(_amount, address(0)); bridgeRecoverFailedTransfer({ _chainId: _chainId, _depositSender: _depositSender, _assetId: assetId, - _transferData: transferData, + _assetData: transferData, _l2TxHash: _l2TxHash, _l2BatchNumber: _l2BatchNumber, _l2MessageIndex: _l2MessageIndex, @@ -854,7 +918,7 @@ contract L1AssetRouter is IL1AssetRouter, ReentrancyGuard, Ownable2StepUpgradeab /// L2 tx if the L1 msg.sender is a contract. Without address aliasing for L1 contracts as refund recipients they /// would not be able to make proper L2 tx requests through the Mailbox to use or withdraw the funds from L2, and /// the funds would be lost. - /// @return l2TxHash The L2 transaction hash of deposit finalization. + /// @return txHash The L2 transaction hash of deposit finalization. function depositLegacyErc20Bridge( address _prevMsgSender, address _l2Receiver, @@ -863,28 +927,31 @@ contract L1AssetRouter is IL1AssetRouter, ReentrancyGuard, Ownable2StepUpgradeab uint256 _l2TxGasLimit, uint256 _l2TxGasPerPubdataByte, address _refundRecipient - ) external payable override onlyLegacyBridge nonReentrant whenNotPaused returns (bytes32 l2TxHash) { - require(_l1Token != L1_WETH_TOKEN, "ShB: WETH deposit not supported 2"); + ) external payable override onlyLegacyBridge nonReentrant whenNotPaused returns (bytes32 txHash) { + require(_l1Token != L1_WETH_TOKEN, "L1AR: WETH deposit not supported 2"); bytes32 _assetId; - bytes memory l2BridgeMintCalldata; + bytes memory bridgeMintCalldata; { // Inner call to encode data to decrease local var numbers _assetId = _ensureTokenRegisteredWithNTV(_l1Token); + IERC20(_l1Token).forceApprove(address(nativeTokenVault), _amount); + } - // solhint-disable-next-line func-named-parameters - l2BridgeMintCalldata = abi.encode( - _amount, - _prevMsgSender, - _l2Receiver, - getERC20Getters(_l1Token), - _l1Token - ); + { + bridgeMintCalldata = _burn({ + _chainId: ERA_CHAIN_ID, + _l2Value: 0, + _assetId: _assetId, + _prevMsgSender: _prevMsgSender, + _transferData: abi.encode(_amount, _l2Receiver), + _passValue: false + }); } { - bytes memory l2TxCalldata = _getDepositL2Calldata(_prevMsgSender, _assetId, l2BridgeMintCalldata); + bytes memory l2TxCalldata = _getDepositL2Calldata(_prevMsgSender, _assetId, bridgeMintCalldata); // If the refund recipient is not specified, the refund will be sent to the sender of the transaction. // Otherwise, the refund will be sent to the specified address. @@ -894,7 +961,7 @@ contract L1AssetRouter is IL1AssetRouter, ReentrancyGuard, Ownable2StepUpgradeab L2TransactionRequestDirect memory request = L2TransactionRequestDirect({ chainId: ERA_CHAIN_ID, l2Contract: L2_ASSET_ROUTER_ADDR, - mintValue: msg.value, // l2 gas + l2 msg.Value the bridgehub will withdraw the mintValue from the base token bridge for gas + mintValue: msg.value, // l2 gas + l2 msg.Value the bridgehub will withdraw the mintValue from the shared bridge (base token bridge) for gas l2Value: 0, // L2 msg.value, this contract doesn't support base token deposits or wrapping functionality, for direct deposits use bridgehub l2Calldata: l2TxCalldata, l2GasLimit: _l2TxGasLimit, @@ -902,15 +969,15 @@ contract L1AssetRouter is IL1AssetRouter, ReentrancyGuard, Ownable2StepUpgradeab factoryDeps: new bytes[](0), refundRecipient: refundRecipient }); - l2TxHash = BRIDGE_HUB.requestL2TransactionDirect{value: msg.value}(request); + txHash = BRIDGE_HUB.requestL2TransactionDirect{value: msg.value}(request); } // Save the deposited amount to claim funds on L1 if the deposit failed on L2 - depositHappened[ERA_CHAIN_ID][l2TxHash] = keccak256(abi.encode(_prevMsgSender, _l1Token, _amount)); + depositHappened[ERA_CHAIN_ID][txHash] = keccak256(abi.encode(_prevMsgSender, _l1Token, _amount)); emit LegacyDepositInitiated({ chainId: ERA_CHAIN_ID, - l2DepositTxHash: l2TxHash, + l2DepositTxHash: txHash, from: _prevMsgSender, to: _l2Receiver, l1Asset: _l1Token, @@ -947,42 +1014,6 @@ contract L1AssetRouter is IL1AssetRouter, ReentrancyGuard, Ownable2StepUpgradeab l1Asset = nativeTokenVault.tokenAddress(assetId); } - /// @notice Withdraw funds from the initiated deposit, that failed when finalizing on zkSync Era chain. - /// This function is specifically designed for maintaining backward-compatibility with legacy `claimFailedDeposit` - /// method in `L1ERC20Bridge`. - /// - /// @param _depositSender The address of the deposit initiator. - /// @param _l1Asset The address of the deposited L1 ERC20 token. - /// @param _amount The amount of the deposit that failed. - /// @param _l2TxHash The L2 transaction hash of the failed deposit finalization. - /// @param _l2BatchNumber The L2 batch number where the deposit finalization was processed. - /// @param _l2MessageIndex The position in the L2 logs Merkle tree of the l2Log that was sent with the message. - /// @param _l2TxNumberInBatch The L2 transaction number in a batch, in which the log was sent. - /// @param _merkleProof The Merkle proof of the processing L1 -> L2 transaction with deposit finalization. - function claimFailedDepositLegacyErc20Bridge( - address _depositSender, - address _l1Asset, - uint256 _amount, - bytes32 _l2TxHash, - uint256 _l2BatchNumber, - uint256 _l2MessageIndex, - uint16 _l2TxNumberInBatch, - bytes32[] calldata _merkleProof - ) external override onlyLegacyBridge { - bytes memory transferData = abi.encode(_amount, _depositSender); - bridgeRecoverFailedTransfer({ - _chainId: ERA_CHAIN_ID, - _depositSender: _depositSender, - _assetId: nativeTokenVault.getAssetId(_l1Asset), - _transferData: transferData, - _l2TxHash: _l2TxHash, - _l2BatchNumber: _l2BatchNumber, - _l2MessageIndex: _l2MessageIndex, - _l2TxNumberInBatch: _l2TxNumberInBatch, - _merkleProof: _merkleProof - }); - } - /*////////////////////////////////////////////////////////////// PAUSE //////////////////////////////////////////////////////////////*/ diff --git a/l1-contracts/contracts/bridge/L1ERC20Bridge.sol b/l1-contracts/contracts/bridge/L1ERC20Bridge.sol index 2200d358a..c0065916a 100644 --- a/l1-contracts/contracts/bridge/L1ERC20Bridge.sol +++ b/l1-contracts/contracts/bridge/L1ERC20Bridge.sol @@ -17,7 +17,7 @@ import {ReentrancyGuard} from "../common/ReentrancyGuard.sol"; /// @author Matter Labs /// @custom:security-contact security@matterlabs.dev /// @notice Smart contract that allows depositing ERC20 tokens from Ethereum to ZK chains -/// @dev It is a legacy bridge from zkSync Era, that was deprecated in favour of shared bridge. +/// @dev It is a legacy bridge from ZKsync Era, that was deprecated in favour of shared bridge. /// It is needed for backward compatibility with already integrated projects. contract L1ERC20Bridge is IL1ERC20Bridge, ReentrancyGuard { using SafeERC20 for IERC20; @@ -28,26 +28,29 @@ contract L1ERC20Bridge is IL1ERC20Bridge, ReentrancyGuard { /// @dev The native token vault, which holds deposited tokens. IL1NativeTokenVault public immutable override NATIVE_TOKEN_VAULT; + /// @dev The chainId of Era + uint256 public immutable ERA_CHAIN_ID; + /// @dev A mapping L2 batch number => message number => flag. - /// @dev Used to indicate that L2 -> L1 message was already processed for zkSync Era withdrawals. + /// @dev Used to indicate that L2 -> L1 message was already processed for ZKsync Era withdrawals. // slither-disable-next-line uninitialized-state mapping(uint256 l2BatchNumber => mapping(uint256 l2ToL1MessageNumber => bool isFinalized)) public isWithdrawalFinalized; /// @dev A mapping account => L1 token address => L2 deposit transaction hash => amount. - /// @dev Used for saving the number of deposited funds, to claim them in case the deposit transaction will fail in zkSync Era. + /// @dev Used for saving the number of deposited funds, to claim them in case the deposit transaction will fail in ZKsync Era. mapping(address account => mapping(address l1Token => mapping(bytes32 depositL2TxHash => uint256 amount))) public depositAmount; - /// @dev The address that is used as a L2 native token vault in zkSync Era. + /// @dev The address that is used as a L2 Shared Bridge in ZKsync Era. // slither-disable-next-line uninitialized-state - address public l2NativeTokenVault; + address public l2Bridge; - /// @dev The address that is used as a beacon for L2 tokens in zkSync Era. + /// @dev The address that is used as a beacon for L2 tokens in ZKsync Era. // slither-disable-next-line uninitialized-state address public l2TokenBeacon; - /// @dev Stores the hash of the L2 token proxy contract's bytecode on zkSync Era. + /// @dev Stores the hash of the L2 token proxy contract's bytecode on ZKsync Era. // slither-disable-next-line uninitialized-state bytes32 public l2TokenProxyBytecodeHash; @@ -62,30 +65,52 @@ contract L1ERC20Bridge is IL1ERC20Bridge, ReentrancyGuard { /// @dev Contract is expected to be used as proxy implementation. /// @dev Initialize the implementation to prevent Parity hack. - constructor(IL1AssetRouter _sharedBridge, IL1NativeTokenVault _nativeTokenVault) reentrancyGuardInitializer { + constructor( + IL1AssetRouter _sharedBridge, + IL1NativeTokenVault _nativeTokenVault, + uint256 _eraChainId + ) reentrancyGuardInitializer { SHARED_BRIDGE = _sharedBridge; NATIVE_TOKEN_VAULT = _nativeTokenVault; + ERA_CHAIN_ID = _eraChainId; } /// @dev Initializes the reentrancy guard. Expected to be used in the proxy. function initialize() external reentrancyGuardInitializer {} - /*////////////////////////////////////////////////////////////// - ERA LEGACY GETTERS - //////////////////////////////////////////////////////////////*/ - - /// @return The L2 token address that would be minted for deposit of the given L1 token on zkSync Era. - function l2TokenAddress(address _l1Token) external view returns (address) { - bytes32 constructorInputHash = keccak256(abi.encode(l2TokenBeacon, "")); - bytes32 salt = bytes32(uint256(uint160(_l1Token))); + /// @dev Withdraw funds from the initiated deposit, that failed when finalizing on L2. + /// @param _depositSender The address of the deposit initiator + /// @param _l1Token The address of the deposited L1 ERC20 token + /// @param _l2TxHash The L2 transaction hash of the failed deposit finalization + /// @param _l2BatchNumber The L2 batch number where the deposit finalization was processed + /// @param _l2MessageIndex The position in the L2 logs Merkle tree of the l2Log that was sent with the message + /// @param _l2TxNumberInBatch The L2 transaction number in a batch, in which the log was sent + /// @param _merkleProof The Merkle proof of the processing L1 -> L2 transaction with deposit finalization + function claimFailedDeposit( + address _depositSender, + address _l1Token, + bytes32 _l2TxHash, + uint256 _l2BatchNumber, + uint256 _l2MessageIndex, + uint16 _l2TxNumberInBatch, + bytes32[] calldata _merkleProof + ) external nonReentrant { + uint256 amount = depositAmount[_depositSender][_l1Token][_l2TxHash]; + require(amount != 0, "2T"); // empty deposit + delete depositAmount[_depositSender][_l1Token][_l2TxHash]; - return - L2ContractHelper.computeCreate2Address( - l2NativeTokenVault, - salt, - l2TokenProxyBytecodeHash, - constructorInputHash - ); + SHARED_BRIDGE.claimFailedDeposit({ + _chainId: ERA_CHAIN_ID, + _depositSender: _depositSender, + _l1Token: _l1Token, + _amount: amount, + _l2TxHash: _l2TxHash, + _l2BatchNumber: _l2BatchNumber, + _l2MessageIndex: _l2MessageIndex, + _l2TxNumberInBatch: _l2TxNumberInBatch, + _merkleProof: _merkleProof + }); + emit ClaimedFailedDeposit(_depositSender, _l1Token, amount); } /*////////////////////////////////////////////////////////////// @@ -102,7 +127,7 @@ contract L1ERC20Bridge is IL1ERC20Bridge, ReentrancyGuard { /// @param _amount The total amount of tokens to be bridged /// @param _l2TxGasLimit The L2 gas limit to be used in the corresponding L2 transaction /// @param _l2TxGasPerPubdataByte The gasPerPubdataByteLimit to be used in the corresponding L2 transaction - /// @return l2TxHash The L2 transaction hash of deposit finalization + /// @return txHash The L2 transaction hash of deposit finalization /// NOTE: the function doesn't use `nonreentrant` modifier, because the inner method does. function deposit( address _l2Receiver, @@ -110,8 +135,8 @@ contract L1ERC20Bridge is IL1ERC20Bridge, ReentrancyGuard { uint256 _amount, uint256 _l2TxGasLimit, uint256 _l2TxGasPerPubdataByte - ) external payable returns (bytes32 l2TxHash) { - l2TxHash = deposit({ + ) external payable returns (bytes32 txHash) { + txHash = deposit({ _l2Receiver: _l2Receiver, _l1Token: _l1Token, _amount: _amount, @@ -121,6 +146,32 @@ contract L1ERC20Bridge is IL1ERC20Bridge, ReentrancyGuard { }); } + /// @notice Finalize the withdrawal and release funds + /// @param _l2BatchNumber The L2 batch number where the withdrawal was processed + /// @param _l2MessageIndex The position in the L2 logs Merkle tree of the l2Log that was sent with the message + /// @param _l2TxNumberInBatch The L2 transaction number in the batch, in which the log was sent + /// @param _message The L2 withdraw data, stored in an L2 -> L1 message + /// @param _merkleProof The Merkle proof of the inclusion L2 -> L1 message about withdrawal initialization + function finalizeWithdrawal( + uint256 _l2BatchNumber, + uint256 _l2MessageIndex, + uint16 _l2TxNumberInBatch, + bytes calldata _message, + bytes32[] calldata _merkleProof + ) external nonReentrant { + require(!isWithdrawalFinalized[_l2BatchNumber][_l2MessageIndex], "pw"); + // We don't need to set finalizeWithdrawal here, as we set it in the shared bridge + + (address l1Receiver, address l1Token, uint256 amount) = SHARED_BRIDGE.finalizeWithdrawalLegacyErc20Bridge({ + _l2BatchNumber: _l2BatchNumber, + _l2MessageIndex: _l2MessageIndex, + _l2TxNumberInBatch: _l2TxNumberInBatch, + _message: _message, + _merkleProof: _merkleProof + }); + emit WithdrawalFinalized(l1Receiver, l1Token, amount); + } + /// @notice Initiates a deposit by locking funds on the contract and sending the request /// @dev Initiates a deposit by locking funds on the contract and sending the request /// of processing an L2 transaction where tokens would be minted @@ -145,7 +196,7 @@ contract L1ERC20Bridge is IL1ERC20Bridge, ReentrancyGuard { /// L2 tx if the L1 msg.sender is a contract. Without address aliasing for L1 contracts as refund recipients they /// would not be able to make proper L2 tx requests through the Mailbox to use or withdraw the funds from L2, and /// the funds would be lost. - /// @return l2TxHash The L2 transaction hash of deposit finalization + /// @return txHash The L2 transaction hash of deposit finalization function deposit( address _l2Receiver, address _l1Token, @@ -153,13 +204,13 @@ contract L1ERC20Bridge is IL1ERC20Bridge, ReentrancyGuard { uint256 _l2TxGasLimit, uint256 _l2TxGasPerPubdataByte, address _refundRecipient - ) public payable nonReentrant returns (bytes32 l2TxHash) { + ) public payable nonReentrant returns (bytes32 txHash) { require(_amount != 0, "0T"); // empty deposit - uint256 amount = _depositFundsToNTV(msg.sender, IERC20(_l1Token), _amount); + uint256 amount = _depositFundsToSharedBridge(msg.sender, IERC20(_l1Token), _amount); require(amount == _amount, "3T"); // The token has non-standard transfer logic - l2TxHash = SHARED_BRIDGE.depositLegacyErc20Bridge{value: msg.value}({ - _msgSender: msg.sender, + txHash = SHARED_BRIDGE.depositLegacyErc20Bridge{value: msg.value}({ + _prevMsgSender: msg.sender, _l2Receiver: _l2Receiver, _l1Token: _l1Token, _amount: _amount, @@ -167,83 +218,34 @@ contract L1ERC20Bridge is IL1ERC20Bridge, ReentrancyGuard { _l2TxGasPerPubdataByte: _l2TxGasPerPubdataByte, _refundRecipient: _refundRecipient }); - depositAmount[msg.sender][_l1Token][l2TxHash] = _amount; - // solhint-disable-next-line func-named-parameters - emit DepositInitiated(l2TxHash, msg.sender, _l2Receiver, _l1Token, _amount); + depositAmount[msg.sender][_l1Token][txHash] = _amount; + emit DepositInitiated({ + l2DepositTxHash: txHash, + from: msg.sender, + to: _l2Receiver, + l1Token: _l1Token, + amount: _amount + }); } - /// @dev Transfers tokens from the depositor address to the native token vault address. + /// @dev Transfers tokens from the depositor address to the shared bridge address. /// @return The difference between the contract balance before and after the transferring of funds. - function _depositFundsToNTV(address _from, IERC20 _token, uint256 _amount) internal returns (uint256) { - uint256 balanceBefore = _token.balanceOf(address(NATIVE_TOKEN_VAULT)); - _token.safeTransferFrom(_from, address(NATIVE_TOKEN_VAULT), _amount); - uint256 balanceAfter = _token.balanceOf(address(NATIVE_TOKEN_VAULT)); - + function _depositFundsToSharedBridge(address _from, IERC20 _token, uint256 _amount) internal returns (uint256) { + uint256 balanceBefore = _token.balanceOf(address(SHARED_BRIDGE)); + _token.safeTransferFrom(_from, address(SHARED_BRIDGE), _amount); + uint256 balanceAfter = _token.balanceOf(address(SHARED_BRIDGE)); return balanceAfter - balanceBefore; } - /// @dev Withdraw funds from the initiated deposit, that failed when finalizing on L2. - /// @param _depositSender The address of the deposit initiator - /// @param _l1Token The address of the deposited L1 ERC20 token - /// @param _l2TxHash The L2 transaction hash of the failed deposit finalization - /// @param _l2BatchNumber The L2 batch number where the deposit finalization was processed - /// @param _l2MessageIndex The position in the L2 logs Merkle tree of the l2Log that was sent with the message - /// @param _l2TxNumberInBatch The L2 transaction number in a batch, in which the log was sent - /// @param _merkleProof The Merkle proof of the processing L1 -> L2 transaction with deposit finalization - function claimFailedDeposit( - address _depositSender, - address _l1Token, - bytes32 _l2TxHash, - uint256 _l2BatchNumber, - uint256 _l2MessageIndex, - uint16 _l2TxNumberInBatch, - bytes32[] calldata _merkleProof - ) external nonReentrant { - uint256 amount = depositAmount[_depositSender][_l1Token][_l2TxHash]; - require(amount != 0, "2T"); // empty deposit - delete depositAmount[_depositSender][_l1Token][_l2TxHash]; - - SHARED_BRIDGE.claimFailedDepositLegacyErc20Bridge({ - _depositSender: _depositSender, - _l1Token: _l1Token, - _amount: amount, - _l2TxHash: _l2TxHash, - _l2BatchNumber: _l2BatchNumber, - _l2MessageIndex: _l2MessageIndex, - _l2TxNumberInBatch: _l2TxNumberInBatch, - _merkleProof: _merkleProof - }); - emit ClaimedFailedDeposit(_depositSender, _l1Token, amount); - } - - /// @notice Finalize the withdrawal and release funds - /// @param _l2BatchNumber The L2 batch number where the withdrawal was processed - /// @param _l2MessageIndex The position in the L2 logs Merkle tree of the l2Log that was sent with the message - /// @param _l2TxNumberInBatch The L2 transaction number in the batch, in which the log was sent - /// @param _message The L2 withdraw data, stored in an L2 -> L1 message - /// @param _merkleProof The Merkle proof of the inclusion L2 -> L1 message about withdrawal initialization - function finalizeWithdrawal( - uint256 _l2BatchNumber, - uint256 _l2MessageIndex, - uint16 _l2TxNumberInBatch, - bytes calldata _message, - bytes32[] calldata _merkleProof - ) external nonReentrant { - require(!isWithdrawalFinalized[_l2BatchNumber][_l2MessageIndex], "pw"); - // We don't need to set finalizeWithdrawal here, as we set it in the shared bridge + /*////////////////////////////////////////////////////////////// + ERA LEGACY GETTERS + //////////////////////////////////////////////////////////////*/ - (address l1Receiver, address l1Token, uint256 amount) = SHARED_BRIDGE.finalizeWithdrawalLegacyErc20Bridge({ - _l2BatchNumber: _l2BatchNumber, - _l2MessageIndex: _l2MessageIndex, - _l2TxNumberInBatch: _l2TxNumberInBatch, - _message: _message, - _merkleProof: _merkleProof - }); - emit WithdrawalFinalized(l1Receiver, l1Token, amount); - } + /// @return The L2 token address that would be minted for deposit of the given L1 token on zkSync Era. + function l2TokenAddress(address _l1Token) external view returns (address) { + bytes32 constructorInputHash = keccak256(abi.encode(l2TokenBeacon, "")); + bytes32 salt = bytes32(uint256(uint160(_l1Token))); - /// @notice View-only function for backward compatibility - function l2Bridge() external view returns (address) { - return l2NativeTokenVault; + return L2ContractHelper.computeCreate2Address(l2Bridge, salt, l2TokenProxyBytecodeHash, constructorInputHash); } } diff --git a/l1-contracts/contracts/bridge/L1NativeTokenVault.sol b/l1-contracts/contracts/bridge/L1NativeTokenVault.sol index 12f6c1107..366bbf260 100644 --- a/l1-contracts/contracts/bridge/L1NativeTokenVault.sol +++ b/l1-contracts/contracts/bridge/L1NativeTokenVault.sol @@ -7,7 +7,6 @@ pragma solidity 0.8.24; import {Ownable2StepUpgradeable} from "@openzeppelin/contracts-upgradeable/access/Ownable2StepUpgradeable.sol"; import {PausableUpgradeable} from "@openzeppelin/contracts-upgradeable/security/PausableUpgradeable.sol"; -import {IERC20Metadata} from "@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol"; import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; @@ -16,7 +15,9 @@ import {IL1AssetHandler} from "./interfaces/IL1AssetHandler.sol"; import {IL1AssetRouter} from "./interfaces/IL1AssetRouter.sol"; import {ETH_TOKEN_ADDRESS} from "../common/Config.sol"; -import {L2_NATIVE_TOKEN_VAULT_ADDRESS} from "../common/L2ContractAddresses.sol"; +import {DataEncoding} from "../common/libraries/DataEncoding.sol"; + +import {BridgeHelper} from "./BridgeHelper.sol"; /// @author Matter Labs /// @custom:security-contact security@matterlabs.dev @@ -31,9 +32,6 @@ contract L1NativeTokenVault is IL1NativeTokenVault, IL1AssetHandler, Ownable2Ste /// @dev L1 Shared Bridge smart contract that handles communication with its counterparts on L2s IL1AssetRouter public immutable override L1_SHARED_BRIDGE; - /// @dev Era's chainID - uint256 public immutable ERA_CHAIN_ID; - /// @dev Maps token balances for each chain to prevent unauthorized spending across ZK chains. /// This serves as a security measure until hyperbridging is implemented. /// NOTE: this function may be removed in the future, don't rely on it! @@ -42,40 +40,33 @@ contract L1NativeTokenVault is IL1NativeTokenVault, IL1AssetHandler, Ownable2Ste /// @dev A mapping assetId => tokenAddress mapping(bytes32 assetId => address tokenAddress) public tokenAddress; - /// @notice Checks that the message sender is the bridgehub. + /// @notice Checks that the message sender is the bridge. modifier onlyBridge() { require(msg.sender == address(L1_SHARED_BRIDGE), "NTV not ShB"); _; } - /// @notice Checks that the message sender is the native token vault itself. - modifier onlySelf() { - require(msg.sender == address(this), "NTV only"); - _; - } - /// @dev Contract is expected to be used as proxy implementation. /// @dev Initialize the implementation to prevent Parity hack. - constructor(address _l1WethAddress, IL1AssetRouter _l1SharedBridge, uint256 _eraChainId) { + constructor(address _l1WethAddress, IL1AssetRouter _l1SharedBridge) { _disableInitializers(); L1_WETH_TOKEN = _l1WethAddress; - ERA_CHAIN_ID = _eraChainId; L1_SHARED_BRIDGE = _l1SharedBridge; } - /// @dev Initializes a contract for later use. Expected to be used in the proxy. - /// @param _owner Address which can change pause / unpause the NTV. + /// @dev Accepts ether only from the Shared Bridge. + receive() external payable { + require(address(L1_SHARED_BRIDGE) == msg.sender, "NTV: ETH only accepted from Shared Bridge"); + } + + /// @dev Initializes a contract for later use. Expected to be used in the proxy + /// @param _owner Address which can change pause / unpause the NTV /// implementation. The owner is the Governor and separate from the ProxyAdmin from now on, so that the Governor can call the bridge. function initialize(address _owner) external initializer { require(_owner != address(0), "NTV owner 0"); _transferOwnership(_owner); } - /// @dev Accepts ether only from the Shared Bridge. - receive() external payable { - require(address(L1_SHARED_BRIDGE) == msg.sender, "NTV: ETH only accepted from Shared Bridge"); - } - /// @notice Transfers tokens from shared bridge as part of the migration process. /// @dev Both ETH and ERC20 tokens can be transferred. Exhausts balance of shared bridge after the first call. /// @dev Calling second time for the same token will revert. @@ -100,10 +91,10 @@ contract L1NativeTokenVault is IL1NativeTokenVault, IL1AssetHandler, Ownable2Ste /// @dev Clears chain balance on the shared bridge after the first call. Subsequent calls will not affect the state. /// @param _token The address of token to be transferred (address(1) for ether and contract address for ERC20). /// @param _targetChainId The chain ID of the corresponding ZK chain. - function transferBalancesFromSharedBridge(address _token, uint256 _targetChainId) external { + function updateChainBalancesFromSharedBridge(address _token, uint256 _targetChainId) external { uint256 sharedBridgeChainBalance = L1_SHARED_BRIDGE.chainBalance(_targetChainId, _token); chainBalance[_targetChainId][_token] = chainBalance[_targetChainId][_token] + sharedBridgeChainBalance; - L1_SHARED_BRIDGE.clearChainBalance(_targetChainId, _token); + L1_SHARED_BRIDGE.nullifyChainBalanceByNTV(_targetChainId, _token); } /// @notice Registers tokens within the NTV. @@ -113,11 +104,39 @@ contract L1NativeTokenVault is IL1NativeTokenVault, IL1AssetHandler, Ownable2Ste function registerToken(address _l1Token) external { require(_l1Token != L1_WETH_TOKEN, "NTV: WETH deposit not supported"); require(_l1Token == ETH_TOKEN_ADDRESS || _l1Token.code.length > 0, "NTV: empty token"); - bytes32 assetId = getAssetId(_l1Token); - L1_SHARED_BRIDGE.setAssetHandlerAddressInitial(bytes32(uint256(uint160(_l1Token))), address(this)); + bytes32 assetId = DataEncoding.encodeNTVAssetId(block.chainid, _l1Token); + L1_SHARED_BRIDGE.setAssetHandlerAddressThisChain(bytes32(uint256(uint160(_l1Token))), address(this)); tokenAddress[assetId] = _l1Token; } + /// @inheritdoc IL1AssetHandler + function bridgeMint( + uint256 _chainId, + bytes32 _assetId, + bytes calldata _data + ) external payable override onlyBridge whenNotPaused returns (address l1Receiver) { + // here we are minting the tokens after the bridgeBurn has happened on an L2, so we can assume the l1Token is not zero + address l1Token = tokenAddress[_assetId]; + uint256 amount; + (amount, l1Receiver) = abi.decode(_data, (uint256, address)); + // Check that the chain has sufficient balance + require(chainBalance[_chainId][l1Token] >= amount, "NTV: not enough funds"); // not enough funds + chainBalance[_chainId][l1Token] -= amount; + + if (l1Token == ETH_TOKEN_ADDRESS) { + bool callSuccess; + // Low-level assembly call, to avoid any memory copying (save gas) + assembly { + callSuccess := call(gas(), l1Receiver, amount, 0, 0, 0, 0) + } + require(callSuccess, "NTV: withdrawal failed, no funds or cannot transfer to receiver"); + } else { + // Withdraw funds + IERC20(l1Token).safeTransfer(l1Receiver, amount); + } + emit BridgeMint(_chainId, _assetId, l1Receiver, amount); + } + /// @inheritdoc IL1AssetHandler /// @notice Allows bridgehub to acquire mintValue for L1->L2 transactions. /// @dev In case of native token vault _data is the tuple of _depositAmount and _l2Receiver. @@ -154,92 +173,36 @@ contract L1NativeTokenVault is IL1NativeTokenVault, IL1AssetHandler, Ownable2Ste chainBalance[_chainId][l1Token] += amount; - // solhint-disable-next-line func-named-parameters - _bridgeMintData = abi.encode(amount, _prevMsgSender, _l2Receiver, getERC20Getters(l1Token), l1Token); - // solhint-disable-next-line func-named-parameters - emit BridgeBurn(_chainId, _assetId, _prevMsgSender, _l2Receiver, amount); - } - - /// @notice Transfers tokens from the depositor address to the smart contract address. - /// @param _from The address of the depositor. - /// @param _token The ERC20 token to be transferred. - /// @param _amount The amount to be transferred. - /// @return The difference between the contract balance before and after the transferring of funds. - function _depositFunds(address _from, IERC20 _token, uint256 _amount) internal returns (uint256) { - uint256 balanceBefore = _token.balanceOf(address(this)); - address from = _from; - // in the legacy scenario the SharedBridge was granting the allowance, we have to transfer from them instead of the user - if ( - _token.allowance(address(L1_SHARED_BRIDGE), address(this)) >= _amount && - _token.allowance(_from, address(this)) < _amount - ) { - from = address(L1_SHARED_BRIDGE); - } - // slither-disable-next-line arbitrary-send-erc20 - _token.safeTransferFrom(from, address(this), _amount); - uint256 balanceAfter = _token.balanceOf(address(this)); - - return balanceAfter - balanceBefore; - } - - /// @notice Receives and parses (name, symbol, decimals) from the token contract. - /// @param _token The address of token of interest. - /// @return Returns encoded name, symbol, and decimals for specific token. - function getERC20Getters(address _token) public view returns (bytes memory) { - if (_token == ETH_TOKEN_ADDRESS) { - bytes memory name = bytes("Ether"); - bytes memory symbol = bytes("ETH"); - bytes memory decimals = abi.encode(uint8(18)); - return abi.encode(name, symbol, decimals); // when depositing eth to a non-eth based chain it is an ERC20 - } - - (, bytes memory data1) = _token.staticcall(abi.encodeCall(IERC20Metadata.name, ())); - (, bytes memory data2) = _token.staticcall(abi.encodeCall(IERC20Metadata.symbol, ())); - (, bytes memory data3) = _token.staticcall(abi.encodeCall(IERC20Metadata.decimals, ())); - return abi.encode(data1, data2, data3); - } - - /// @inheritdoc IL1AssetHandler - function bridgeMint( - uint256 _chainId, - bytes32 _assetId, - bytes calldata _data - ) external payable override onlyBridge whenNotPaused returns (address l1Receiver) { - // here we are minting the tokens after the bridgeBurn has happened on an L2, so we can assume the l1Token is not zero - address l1Token = tokenAddress[_assetId]; - uint256 amount; - (amount, l1Receiver) = abi.decode(_data, (uint256, address)); - // Check that the chain has sufficient balance - require(chainBalance[_chainId][l1Token] >= amount, "NTV not enough funds 2"); // not enough funds - chainBalance[_chainId][l1Token] -= amount; - - if (l1Token == ETH_TOKEN_ADDRESS) { - bool callSuccess; - // Low-level assembly call, to avoid any memory copying (save gas) - assembly { - callSuccess := call(gas(), l1Receiver, amount, 0, 0, 0, 0) - } - require(callSuccess, "NTV: withdrawal failed, no funds or cannot transfer to receiver"); - } else { - // Withdraw funds - IERC20(l1Token).safeTransfer(l1Receiver, amount); - } - // solhint-disable-next-line func-named-parameters - emit BridgeMint(_chainId, _assetId, l1Receiver, amount); + _bridgeMintData = DataEncoding.encodeBridgeMintData({ + _prevMsgSender: _prevMsgSender, + _l2Receiver: _l2Receiver, + _l1Token: l1Token, + _amount: amount, + _erc20Metadata: getERC20Getters(l1Token) + }); + + emit BridgeBurn({ + chainId: _chainId, + assetId: _assetId, + l1Sender: _prevMsgSender, + l2receiver: _l2Receiver, + amount: amount + }); } /// @inheritdoc IL1AssetHandler function bridgeRecoverFailedTransfer( uint256 _chainId, bytes32 _assetId, + address _depositSender, bytes calldata _data ) external payable override onlyBridge whenNotPaused { - (uint256 _amount, address _depositSender) = abi.decode(_data, (uint256, address)); + (uint256 _amount, ) = abi.decode(_data, (uint256, address)); address l1Token = tokenAddress[_assetId]; require(_amount > 0, "y1"); // check that the chain has sufficient balance - require(chainBalance[_chainId][l1Token] >= _amount, "NTV n funds"); + require(chainBalance[_chainId][l1Token] >= _amount, "NTV: not enough funds 2"); chainBalance[_chainId][l1Token] -= _amount; if (l1Token == ETH_TOKEN_ADDRESS) { @@ -256,11 +219,28 @@ contract L1NativeTokenVault is IL1NativeTokenVault, IL1AssetHandler, Ownable2Ste } } - /// @notice Returns the parsed assetId. - /// @param _l1TokenAddress The address of the token to be parsed. - /// @return The asset ID. - function getAssetId(address _l1TokenAddress) public view override returns (bytes32) { - return keccak256(abi.encode(block.chainid, L2_NATIVE_TOKEN_VAULT_ADDRESS, _l1TokenAddress)); + /// @dev Receives and parses (name, symbol, decimals) from the token contract + function getERC20Getters(address _token) public view returns (bytes memory) { + return BridgeHelper.getERC20Getters(_token, ETH_TOKEN_ADDRESS); + } + + /// @dev Transfers tokens from the depositor address to the smart contract address. + /// @return The difference between the contract balance before and after the transferring of funds. + function _depositFunds(address _from, IERC20 _token, uint256 _amount) internal returns (uint256) { + uint256 balanceBefore = _token.balanceOf(address(this)); + address from = _from; + // in the legacy scenario the SharedBridge was granting the allowance, we have to transfer from them instead of the user + if ( + _token.allowance(address(L1_SHARED_BRIDGE), address(this)) >= _amount && + _token.allowance(_from, address(this)) < _amount + ) { + from = address(L1_SHARED_BRIDGE); + } + // slither-disable-next-line arbitrary-send-erc20 + _token.safeTransferFrom(from, address(this), _amount); + uint256 balanceAfter = _token.balanceOf(address(this)); + + return balanceAfter - balanceBefore; } /*////////////////////////////////////////////////////////////// diff --git a/l1-contracts/contracts/bridge/interfaces/IL1AssetHandler.sol b/l1-contracts/contracts/bridge/interfaces/IL1AssetHandler.sol index 9df9a3b7a..a707da173 100644 --- a/l1-contracts/contracts/bridge/interfaces/IL1AssetHandler.sol +++ b/l1-contracts/contracts/bridge/interfaces/IL1AssetHandler.sol @@ -11,15 +11,15 @@ interface IL1AssetHandler { event BridgeInitialize(address indexed l1Token, string name, string symbol, uint8 decimals); /// @dev Emitted when a token is minted - event BridgeMint(uint256 indexed _chainId, bytes32 indexed _assetId, address _l1Receiver, uint256 _amount); + event BridgeMint(uint256 indexed chainId, bytes32 indexed assetId, address l1Receiver, uint256 amount); /// @dev Emitted when a token is burned event BridgeBurn( - uint256 indexed _chainId, - bytes32 indexed _assetId, + uint256 indexed chainId, + bytes32 indexed assetId, address indexed l1Sender, - address _l2receiver, - uint256 _amount + address l2receiver, + uint256 amount ); /// @param _chainId the chainId that the message is from @@ -32,13 +32,13 @@ interface IL1AssetHandler { ) external payable returns (address l1Receiver); /// @param _chainId the chainId that the message will be sent to - /// param mintValue the amount of base tokens to be minted on L2, will be used by Weth AssetHandler + /// @param _l2Value the msg.value of the L2 transaction /// @param _assetId the assetId of the asset being bridged /// @param _prevMsgSender the original caller of the Bridgehub, /// @param _data the actual data specified for the function function bridgeBurn( uint256 _chainId, - uint256 _mintValue, + uint256 _l2Value, bytes32 _assetId, address _prevMsgSender, bytes calldata _data @@ -46,6 +46,12 @@ interface IL1AssetHandler { /// @param _chainId the chainId that the message will be sent to /// @param _assetId the assetId of the asset being bridged + /// @param _depositSender the address of the entity that initiated the deposit. /// @param _data the actual data specified for the function - function bridgeRecoverFailedTransfer(uint256 _chainId, bytes32 _assetId, bytes calldata _data) external payable; + function bridgeRecoverFailedTransfer( + uint256 _chainId, + bytes32 _assetId, + address _depositSender, + bytes calldata _data + ) external payable; } diff --git a/l1-contracts/contracts/bridge/interfaces/IL1AssetRouter.sol b/l1-contracts/contracts/bridge/interfaces/IL1AssetRouter.sol index 9efd977fe..c5cbbc87f 100644 --- a/l1-contracts/contracts/bridge/interfaces/IL1AssetRouter.sol +++ b/l1-contracts/contracts/bridge/interfaces/IL1AssetRouter.sol @@ -25,7 +25,7 @@ interface IL1AssetRouter { bytes32 indexed txDataHash, address indexed from, bytes32 assetId, - bytes l2BridgeMintCalldata + bytes bridgeMintCalldata ); event BridgehubDepositBaseTokenInitiated( @@ -57,6 +57,12 @@ interface IL1AssetRouter { bytes assetData ); + event AssetDeploymentTrackerSet( + bytes32 indexed assetId, + address indexed assetDeploymentTracker, + bytes32 indexed additionalData + ); + event AssetHandlerRegisteredInitial( bytes32 indexed assetId, address indexed assetHandlerAddress, @@ -64,16 +70,14 @@ interface IL1AssetRouter { address sender ); - event AssetHandlerRegistered(bytes32 indexed assetId, address indexed assetHandlerAddress); - function isWithdrawalFinalized( uint256 _chainId, uint256 _l2BatchNumber, - uint256 _l2MessageIndex + uint256 _l2ToL1MessageNumber ) external view returns (bool); function depositLegacyErc20Bridge( - address _msgSender, + address _prevMsgSender, address _l2Receiver, address _l1Token, uint256 _amount, @@ -82,17 +86,6 @@ interface IL1AssetRouter { address _refundRecipient ) external payable returns (bytes32 txHash); - function claimFailedDepositLegacyErc20Bridge( - address _depositSender, - address _l1Token, - uint256 _amount, - bytes32 _l2TxHash, - uint256 _l2BatchNumber, - uint256 _l2MessageIndex, - uint16 _l2TxNumberInBatch, - bytes32[] calldata _merkleProof - ) external; - function claimFailedDeposit( uint256 _chainId, address _depositSender, @@ -111,7 +104,7 @@ interface IL1AssetRouter { uint16 _l2TxNumberInBatch, bytes calldata _message, bytes32[] calldata _merkleProof - ) external returns (address l1Receiver, address l1Token, uint256 amount); + ) external returns (address l1Receiver, address l1Asset, uint256 amount); function finalizeWithdrawal( uint256 _chainId, @@ -128,12 +121,15 @@ interface IL1AssetRouter { function legacyBridge() external view returns (IL1ERC20Bridge); - function depositHappened(uint256 _chainId, bytes32 _l2TxHash) external view returns (bytes32); + function depositHappened(uint256 _chainId, bytes32 _l2DepositTxHash) external view returns (bytes32); - /// data is abi encoded : + /// @dev Data has the following abi encoding for legacy deposits: /// address _l1Token, /// uint256 _amount, /// address _l2Receiver + /// for new deposits: + /// bytes32 _assetId, + /// bytes _transferData function bridgehubDeposit( uint256 _chainId, address _prevMsgSender, @@ -152,7 +148,9 @@ interface IL1AssetRouter { function hyperbridgingEnabled(uint256 _chainId) external view returns (bool); - function setAssetHandlerAddressInitial(bytes32 _additionalData, address _assetHandlerAddress) external; + function setAssetDeploymentTracker(bytes32 _assetRegistrationData, address _assetDeploymentTracker) external; + + function setAssetHandlerAddressThisChain(bytes32 _additionalData, address _assetHandlerAddress) external; function setAssetHandlerAddressOnCounterPart( uint256 _chainId, @@ -174,7 +172,7 @@ interface IL1AssetRouter { uint256 _chainId, address _depositSender, bytes32 _assetId, - bytes calldata _tokenData, + bytes calldata _assetData, bytes32 _l2TxHash, uint256 _l2BatchNumber, uint256 _l2MessageIndex, @@ -182,9 +180,9 @@ interface IL1AssetRouter { bytes32[] calldata _merkleProof ) external; - function chainBalance(uint256 _chainId, address _token) external view returns (uint256); + function chainBalance(uint256 _chainId, address _l1Token) external view returns (uint256); function transferTokenToNTV(address _token) external; - function clearChainBalance(uint256 _chainId, address _token) external; + function nullifyChainBalanceByNTV(uint256 _chainId, address _token) external; } diff --git a/l1-contracts/contracts/bridge/interfaces/IL1ERC20Bridge.sol b/l1-contracts/contracts/bridge/interfaces/IL1ERC20Bridge.sol index 1ecb95165..2fcdef189 100644 --- a/l1-contracts/contracts/bridge/interfaces/IL1ERC20Bridge.sol +++ b/l1-contracts/contracts/bridge/interfaces/IL1ERC20Bridge.sol @@ -8,7 +8,7 @@ import {IL1NativeTokenVault} from "./IL1NativeTokenVault.sol"; /// @title L1 Bridge contract legacy interface /// @author Matter Labs /// @custom:security-contact security@matterlabs.dev -/// @notice Legacy Bridge interface before ZK chain migration, used for backward compatibility with zkSync Era +/// @notice Legacy Bridge interface before ZK chain migration, used for backward compatibility with ZKsync Era interface IL1ERC20Bridge { event DepositInitiated( bytes32 indexed l2DepositTxHash, @@ -67,13 +67,11 @@ interface IL1ERC20Bridge { function l2TokenBeacon() external view returns (address); - function l2NativeTokenVault() external view returns (address); - function l2Bridge() external view returns (address); function depositAmount( address _account, address _l1Token, bytes32 _depositL2TxHash - ) external returns (uint256 amount); + ) external view returns (uint256 amount); } diff --git a/l1-contracts/contracts/bridge/interfaces/IL1NativeTokenVault.sol b/l1-contracts/contracts/bridge/interfaces/IL1NativeTokenVault.sol index 1f540ccf0..4572d8e01 100644 --- a/l1-contracts/contracts/bridge/interfaces/IL1NativeTokenVault.sol +++ b/l1-contracts/contracts/bridge/interfaces/IL1NativeTokenVault.sol @@ -18,12 +18,12 @@ interface IL1NativeTokenVault { /// @notice Used to register a token in the vault function registerToken(address _l1Token) external; - /// @notice Used to get the assetId of a token - function getAssetId(address l1TokenAddress) external view returns (bytes32); - - /// @notice Used to get the the ERC20 data for a token + /// @notice Used to get the ERC20 data for a token function getERC20Getters(address _token) external view returns (bytes memory); + /// @notice Used the get token balance for specific ZK chain in shared bridge + function chainBalance(uint256 _chainId, address _l1Token) external view returns (uint256); + /// @notice Used to get the token address of an assetId - function tokenAddress(bytes32 assetId) external view returns (address); + function tokenAddress(bytes32 _assetId) external view returns (address); } diff --git a/l1-contracts/contracts/bridge/interfaces/IL2Bridge.sol b/l1-contracts/contracts/bridge/interfaces/IL2Bridge.sol index 07afa8de2..c0f404a5b 100644 --- a/l1-contracts/contracts/bridge/interfaces/IL2Bridge.sol +++ b/l1-contracts/contracts/bridge/interfaces/IL2Bridge.sol @@ -3,14 +3,11 @@ pragma solidity 0.8.24; /// @author Matter Labs +/// @custom:security-contact security@matterlabs.dev interface IL2Bridge { - function finalizeDeposit(bytes32 _assetId, bytes calldata _data) external; + function withdraw(bytes32 _assetId, bytes memory _assetData) external; - 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 finalizeDeposit(bytes32 _assetId, bytes calldata _transferData) external; function l1Bridge() external view returns (address); diff --git a/l1-contracts/contracts/bridge/interfaces/IL2BridgeLegacy.sol b/l1-contracts/contracts/bridge/interfaces/IL2BridgeLegacy.sol index 111917acf..b163262c7 100644 --- a/l1-contracts/contracts/bridge/interfaces/IL2BridgeLegacy.sol +++ b/l1-contracts/contracts/bridge/interfaces/IL2BridgeLegacy.sol @@ -3,7 +3,10 @@ pragma solidity 0.8.24; /// @author Matter Labs +/// @custom:security-contact security@matterlabs.dev interface IL2BridgeLegacy { + function withdraw(address _l1Receiver, address _l2Token, uint256 _amount) external; + function finalizeDeposit( address _l1Sender, address _l2Receiver, @@ -11,4 +14,8 @@ interface IL2BridgeLegacy { uint256 _amount, bytes calldata _data ) external payable; + + function l1TokenAddress(address _l2Token) external view returns (address); + + function l2TokenAddress(address _l1Token) external view returns (address); } diff --git a/l1-contracts/contracts/bridgehub/Bridgehub.sol b/l1-contracts/contracts/bridgehub/Bridgehub.sol index 9f206697b..edee1af5b 100644 --- a/l1-contracts/contracts/bridgehub/Bridgehub.sol +++ b/l1-contracts/contracts/bridgehub/Bridgehub.sol @@ -11,9 +11,10 @@ import {IBridgehub, L2TransactionRequestDirect, L2TransactionRequestTwoBridgesOu import {IL1AssetRouter} from "../bridge/interfaces/IL1AssetRouter.sol"; import {IStateTransitionManager} from "../state-transition/IStateTransitionManager.sol"; import {ReentrancyGuard} from "../common/ReentrancyGuard.sol"; +import {DataEncoding} from "../common/libraries/DataEncoding.sol"; import {IZkSyncHyperchain} from "../state-transition/chain-interfaces/IZkSyncHyperchain.sol"; -import {ETH_TOKEN_ADDRESS, TWO_BRIDGES_MAGIC_VALUE, BRIDGEHUB_MIN_SECOND_BRIDGE_ADDRESS} from "../common/Config.sol"; -import {L2_NATIVE_TOKEN_VAULT_ADDRESS} from "../common/L2ContractAddresses.sol"; + +import {ETH_TOKEN_ADDRESS, TWO_BRIDGES_MAGIC_VALUE, BRIDGEHUB_MIN_SECOND_BRIDGE_ADDRESS, SETTLEMENT_LAYER_RELAY_SENDER} from "../common/Config.sol"; import {BridgehubL2TransactionRequest, L2Message, L2Log, TxStatus} from "../common/Messaging.sol"; import {AddressAliasHelper} from "../vendor/AddressAliasHelper.sol"; import {IMessageRoot} from "./IMessageRoot.sol"; @@ -25,26 +26,29 @@ import {L2CanonicalTransaction} from "../common/Messaging.sol"; /// @dev The Bridgehub contract serves as the primary entry point for L1<->L2 communication, /// facilitating interactions between end user and bridges. /// It also manages state transition managers, base tokens, and chain registrations. +/// Bridgehub is also an IL1AssetHandler for the chains themselves, which is used to migrate the chains +/// between different settlement layers (for example from L1 to Gateway). contract Bridgehub is IBridgehub, ReentrancyGuard, Ownable2StepUpgradeable, PausableUpgradeable { /// @notice the asset id of Eth bytes32 internal immutable ETH_TOKEN_ASSET_ID; - /// @dev The chain id of L1, this contract will be deployed on multiple layers. + /// @notice The chain id of L1. This contract can be deployed on multiple layers, but this value is still equal to the + /// L1 that is at the most base layer. uint256 public immutable L1_CHAIN_ID; - /// @notice all the ether is held by the weth bridge + /// @notice all the ether and ERC20 tokens are held by NativeVaultToken managed by this shared Bridge. IL1AssetRouter public sharedBridge; - /// @notice we store registered stateTransitionManagers - mapping(address stateTransitionManager => bool) public stateTransitionManagerIsRegistered; + /// @notice StateTransitionManagers that are registered, and ZKchains that use these STMs can use this bridgehub as settlement layer. + mapping(address _stateTransitionManager => bool) public stateTransitionManagerIsRegistered; /// @notice we store registered tokens (for arbitrary base token) - mapping(address token => bool) public tokenIsRegistered; + mapping(address _baseToken => bool) public tokenIsRegistered; - /// @notice chainID => StateTransitionManager contract address, storing StateTransitionManager - mapping(uint256 chainId => address) public stateTransitionManager; + /// @notice chainID => StateTransitionManager contract address, STM that is managing rules for a given ZKchain. + mapping(uint256 _chainId => address) public stateTransitionManager; - /// @notice chainID => baseToken contract address, storing baseToken - mapping(uint256 chainId => address) public baseToken; + /// @notice chainID => baseToken contract address, token that is used as 'base token' by a given child chain. + mapping(uint256 _chainId => address) public baseToken; /// @dev used to manage non critical updates address public admin; @@ -52,14 +56,15 @@ contract Bridgehub is IBridgehub, ReentrancyGuard, Ownable2StepUpgradeable, Paus /// @dev used to accept the admin role address private pendingAdmin; - // FIXME: `messageRoot` DOES NOT contain messages that come from the current layer and go to the settlement layer. - // it may make sense to store the final root somewhere for interop purposes. - // THough maybe it can be postponed. + /// @notice The contract that stores the cross-chain message root for each chain and the aggregated root. + /// @dev Note that the message root does not contain messages from the chain it is deployed on. It may + /// be added later on if needed. IMessageRoot public override messageRoot; /// @notice Mapping from chain id to encoding of the base token used for deposits / withdrawals - mapping(uint256 chainId => bytes32 baseTokenAssetId) public baseTokenAssetId; + mapping(uint256 _chainId => bytes32) public baseTokenAssetId; + /// @notice The deployment tracker for the state transition managers. ISTMDeploymentTracker public stmDeployer; /// @dev asset info used to identify chains in the Shared Bridge @@ -68,33 +73,55 @@ contract Bridgehub is IBridgehub, ReentrancyGuard, Ownable2StepUpgradeable, Paus /// @dev used to indicate the currently active settlement layer for a given chainId mapping(uint256 chainId => uint256 activeSettlementLayerChainId) public settlementLayer; + /// @notice shows whether the given chain can be used as a settlement layer. + /// @dev the Gateway will be one of the possible settlement layers. The L1 is also a settlement layer. /// @dev Sync layer chain is expected to have .. as the base token. - mapping(uint256 chainId => bool isWhitelistedSyncLayer) public whitelistedSettlementLayers; + mapping(uint256 chainId => bool isWhitelistedSettlementLayer) public whitelistedSettlementLayers; + + modifier onlyOwnerOrAdmin() { + require(msg.sender == admin || msg.sender == owner(), "BH: not owner or admin"); + _; + } + + modifier onlyChainSTM(uint256 _chainId) { + require(msg.sender == stateTransitionManager[_chainId], "BH: not chain STM"); + _; + } + + modifier onlyL1() { + require(L1_CHAIN_ID == block.chainid, "BH: not L1"); + _; + } + + modifier onlySettlementLayerRelayedSender() { + /// There is no sender for the wrapping, we use a virtual address. + require(msg.sender == SETTLEMENT_LAYER_RELAY_SENDER, "BH: not relayed senser"); + _; + } + + modifier onlyAssetRouter() { + require(msg.sender == address(sharedBridge), "BH: not asset router"); + _; + } /// @notice to avoid parity hack constructor(uint256 _l1ChainId, address _owner) reentrancyGuardInitializer { _disableInitializers(); L1_CHAIN_ID = _l1ChainId; - ETH_TOKEN_ASSET_ID = keccak256(abi.encode(block.chainid, L2_NATIVE_TOKEN_VAULT_ADDRESS, ETH_TOKEN_ADDRESS)); + + // Note that this assumes that the bridgehub only accepts transactions on chains with ETH base token only. + // This is indeed true, since the only methods where this immutable is used are the ones with `onlyL1` modifier. + ETH_TOKEN_ASSET_ID = DataEncoding.encodeNTVAssetId(block.chainid, ETH_TOKEN_ADDRESS); _transferOwnership(_owner); } /// @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 { _transferOwnership(_owner); } - modifier onlyOwnerOrAdmin() { - require(msg.sender == admin || msg.sender == owner(), "Bridgehub: not owner or admin"); - _; - } - - modifier onlyChainSTM(uint256 _chainId) { - require(msg.sender == stateTransitionManager[_chainId], "BH: not chain STM"); - _; - } - //// Initialization and registration /// @inheritdoc IBridgehub @@ -121,14 +148,11 @@ contract Bridgehub is IBridgehub, ReentrancyGuard, Ownable2StepUpgradeable, Paus emit NewAdmin(previousAdmin, currentPendingAdmin); } - /// @notice To set stmDeploymetTracker, only Owner. Not done in initialize, as - /// the order of deployment is Bridgehub, Shared bridge, and then we call this - function setSTMDeployer(ISTMDeploymentTracker _stmDeployer) external onlyOwner { - stmDeployer = _stmDeployer; - } - - /// @notice To set shared bridge, only Owner. Not done in initialize, as - /// the order of deployment is Bridgehub, Shared bridge, and then we call this + /// @notice To set the addresses of some of the ecosystem contracts, only Owner. Not done in initialize, as + /// the order of deployment is Bridgehub, other contracts, and then we call this. + /// @param _sharedBridge the shared bridge address + /// @param _stmDeployer the stm deployment tracker address + /// @param _messageRoot the message root address function setAddresses( address _sharedBridge, ISTMDeploymentTracker _stmDeployer, @@ -142,72 +166,89 @@ contract Bridgehub is IBridgehub, ReentrancyGuard, Ownable2StepUpgradeable, Paus //// Registry /// @notice State Transition can be any contract with the appropriate interface/functionality + /// @param _stateTransitionManager the state transition manager address to be added function addStateTransitionManager(address _stateTransitionManager) external onlyOwner { require( !stateTransitionManagerIsRegistered[_stateTransitionManager], - "Bridgehub: state transition already registered" + "BH: state transition already registered" ); stateTransitionManagerIsRegistered[_stateTransitionManager] = true; + + emit StateTransitionManagerAdded(_stateTransitionManager); } /// @notice State Transition can be any contract with the appropriate interface/functionality /// @notice this stops new Chains from using the STF, old chains are not affected + /// @param _stateTransitionManager the state transition manager address to be removed function removeStateTransitionManager(address _stateTransitionManager) external onlyOwner { - require( - stateTransitionManagerIsRegistered[_stateTransitionManager], - "Bridgehub: state transition not registered yet" - ); + require(stateTransitionManagerIsRegistered[_stateTransitionManager], "BH: state transition not registered yet"); stateTransitionManagerIsRegistered[_stateTransitionManager] = false; + + emit StateTransitionManagerRemoved(_stateTransitionManager); } /// @notice token can be any contract with the appropriate interface/functionality + /// @param _token address of base token to be registered function addToken(address _token) external onlyOwner { - require(!tokenIsRegistered[_token], "Bridgehub: token already registered"); + require(!tokenIsRegistered[_token], "BH: token already registered"); tokenIsRegistered[_token] = true; + + emit TokenRegistered(_token); } /// @notice To set shared bridge, only Owner. Not done in initialize, as /// the order of deployment is Bridgehub, Shared bridge, and then we call this function setSharedBridge(address _sharedBridge) external onlyOwner { sharedBridge = IL1AssetRouter(_sharedBridge); + + emit SharedBridgeUpdated(_sharedBridge); } - function registerSyncLayer( - uint256 _newSyncLayerChainId, + /// @notice Used to register a chain as a settlement layer. + /// @param _newSettlementLayerChainId the chainId of the chain + /// @param _isWhitelisted whether the chain is a whitelisted settlement layer + function registerSettlementLayer( + uint256 _newSettlementLayerChainId, bool _isWhitelisted - ) external onlyChainSTM(_newSyncLayerChainId) { - whitelistedSettlementLayers[_newSyncLayerChainId] = _isWhitelisted; - - // TODO: emit event + ) external onlyChainSTM(_newSettlementLayerChainId) onlyL1 { + whitelistedSettlementLayers[_newSettlementLayerChainId] = _isWhitelisted; + emit SettlementLayerRegistered(_newSettlementLayerChainId, _isWhitelisted); } - /// @dev Used to set the assedAddress for a given assetInfo. - function setAssetHandlerAddressInitial(bytes32 _additionalData, address _assetAddress) external { - address sender = L1_CHAIN_ID == block.chainid ? msg.sender : AddressAliasHelper.undoL1ToL2Alias(msg.sender); // Todo: this might be dangerous. We should decide based on the tx type. - bytes32 assetInfo = keccak256(abi.encode(L1_CHAIN_ID, sender, _additionalData)); /// todo make other asse + /// @dev Used to set the assetAddress for a given assetInfo. + /// @param _additionalData the additional data to identify the asset + /// @param _assetAddress the asset handler address + function setAssetHandlerAddress(bytes32 _additionalData, address _assetAddress) external { + // It is a simplified version of the logic used by the AssetRouter to manage asset handlers. + // STM's assetId is `keccak256(abi.encode(L1_CHAIN_ID, stmDeployer, stmAddress))`. + // And the STMDeployer is considered the deployment tracker for the STM asset. + // + // The STMDeployer will call this method to set the asset handler address for the assetId. + // If the chain is not the same as L1, we assume that it is done via L1->L2 communication and so we unalias the sender. + // + // For simpler handling we allow anyone to call this method. It is okay, since during bridging operations + // it is double checked that `assetId` is indeed derived from the `stmDeployer`. + // TODO(EVM-703): This logic should be revised once interchain communication is implemented. + + address sender = L1_CHAIN_ID == block.chainid ? msg.sender : AddressAliasHelper.undoL1ToL2Alias(msg.sender); + bytes32 assetInfo = keccak256(abi.encode(L1_CHAIN_ID, sender, _additionalData)); stmAssetIdToAddress[assetInfo] = _assetAddress; emit AssetRegistered(assetInfo, _assetAddress, _additionalData, msg.sender); } - ///// Getters - - /// @notice return the state transition chain contract for a chainId - function getHyperchain(uint256 _chainId) public view returns (address) { - return IStateTransitionManager(stateTransitionManager[_chainId]).getHyperchain(_chainId); - } - - function stmAssetIdFromChainId(uint256 _chainId) public view override returns (bytes32) { - return stmAssetId(stateTransitionManager[_chainId]); - } - - function stmAssetId(address _stmAddress) public view override returns (bytes32) { - return keccak256(abi.encode(L1_CHAIN_ID, address(stmDeployer), bytes32(uint256(uint160(_stmAddress))))); - } - - /// New chain + /*////////////////////////////////////////////////////////////// + Chain Registration + //////////////////////////////////////////////////////////////*/ - /// @notice register new chain + /// @notice register new chain. New chains can be only registered on Bridgehub deployed on L1. Later they can be moved to any other layer. /// @notice for Eth the baseToken address is 1 + /// @param _chainId the chainId of the chain + /// @param _stateTransitionManager the state transition manager address + /// @param _baseToken the base token of the chain + /// @param _salt the salt for the chainId, currently not used + /// @param _admin the admin of the chain + /// @param _initData the fixed initialization data for the chain + /// @param _factoryDeps the factory dependencies for the chain's deployment function createNewChain( uint256 _chainId, address _stateTransitionManager, @@ -217,20 +258,23 @@ contract Bridgehub is IBridgehub, ReentrancyGuard, Ownable2StepUpgradeable, Paus address _admin, bytes calldata _initData, bytes[] calldata _factoryDeps - ) external onlyOwnerOrAdmin nonReentrant whenNotPaused returns (uint256) { + ) external onlyOwnerOrAdmin nonReentrant whenNotPaused onlyL1 returns (uint256) { + require(L1_CHAIN_ID == block.chainid, "BH: New chain registration only allowed on L1"); require(_chainId != 0, "BH: chainId cannot be 0"); require(_chainId <= type(uint48).max, "BH: chainId too large"); + require(_chainId != block.chainid, "BH: chain id must not match current chainid"); require(stateTransitionManagerIsRegistered[_stateTransitionManager], "BH: state transition not registered"); require(tokenIsRegistered[_baseToken], "BH: token not registered"); - require(address(sharedBridge) != address(0), "BH: weth bridge not set"); + require(address(sharedBridge) != address(0), "BH: shared bridge not set"); require(stateTransitionManager[_chainId] == address(0), "BH: chainId already registered"); stateTransitionManager[_chainId] = _stateTransitionManager; baseToken[_chainId] = _baseToken; + /// For now all base tokens have to use the NTV. - baseTokenAssetId[_chainId] = sharedBridge.nativeTokenVault().getAssetId(_baseToken); + baseTokenAssetId[_chainId] = DataEncoding.encodeNTVAssetId(block.chainid, _baseToken); settlementLayer[_chainId] = block.chainid; IStateTransitionManager(_stateTransitionManager).createNewChain({ @@ -248,102 +292,42 @@ contract Bridgehub is IBridgehub, ReentrancyGuard, Ownable2StepUpgradeable, Paus } /*////////////////////////////////////////////////////////////// - Mailbox forwarder + Getters //////////////////////////////////////////////////////////////*/ - /// @notice forwards function call to Mailbox based on ChainId - /// @param _chainId The chain ID of the hyperchain where to prove L2 message inclusion. - /// @param _batchNumber The executed L2 batch number in which the message appeared - /// @param _index The position in the L2 logs Merkle tree of the l2Log that was sent with the message - /// @param _message Information about the sent message: sender address, the message itself, tx index in the L2 batch where the message was sent - /// @param _proof Merkle proof for inclusion of L2 log that was sent with the message - /// @return Whether the proof is valid - function proveL2MessageInclusion( - uint256 _chainId, - uint256 _batchNumber, - uint256 _index, - L2Message calldata _message, - bytes32[] calldata _proof - ) external view override returns (bool) { - address hyperchain = getHyperchain(_chainId); - return IZkSyncHyperchain(hyperchain).proveL2MessageInclusion(_batchNumber, _index, _message, _proof); + /// @notice return the state transition chain contract for a chainId + function getHyperchain(uint256 _chainId) public view returns (address) { + return IStateTransitionManager(stateTransitionManager[_chainId]).getHyperchain(_chainId); } - /// @notice forwards function call to Mailbox based on ChainId - /// @param _chainId The chain ID of the hyperchain where to prove L2 log inclusion. - /// @param _batchNumber The executed L2 batch number in which the log appeared - /// @param _index The position of the l2log in the L2 logs Merkle tree - /// @param _log Information about the sent log - /// @param _proof Merkle proof for inclusion of the L2 log - /// @return Whether the proof is correct and L2 log is included in batch - function proveL2LogInclusion( - uint256 _chainId, - uint256 _batchNumber, - uint256 _index, - L2Log calldata _log, - bytes32[] calldata _proof - ) external view override returns (bool) { - address hyperchain = getHyperchain(_chainId); - return IZkSyncHyperchain(hyperchain).proveL2LogInclusion(_batchNumber, _index, _log, _proof); + function stmAssetIdFromChainId(uint256 _chainId) public view override returns (bytes32) { + return stmAssetId(stateTransitionManager[_chainId]); } - /// @notice forwards function call to Mailbox based on ChainId - /// @param _chainId The chain ID of the hyperchain where to prove L1->L2 tx status. - /// @param _l2TxHash The L2 canonical transaction hash - /// @param _l2BatchNumber The L2 batch number where the transaction was processed - /// @param _l2MessageIndex The position in the L2 logs Merkle tree of the l2Log that was sent with the message - /// @param _l2TxNumberInBatch The L2 transaction number in the batch, in which the log was sent - /// @param _merkleProof The Merkle proof of the processing L1 -> L2 transaction - /// @param _status The execution status of the L1 -> L2 transaction (true - success & 0 - fail) - /// @return Whether the proof is correct and the transaction was actually executed with provided status - /// NOTE: It may return `false` for incorrect proof, but it doesn't mean that the L1 -> L2 transaction has an opposite status! - function proveL1ToL2TransactionStatus( - uint256 _chainId, - bytes32 _l2TxHash, - uint256 _l2BatchNumber, - uint256 _l2MessageIndex, - uint16 _l2TxNumberInBatch, - bytes32[] calldata _merkleProof, - TxStatus _status - ) external view override returns (bool) { - address hyperchain = getHyperchain(_chainId); - return - IZkSyncHyperchain(hyperchain).proveL1ToL2TransactionStatus({ - _l2TxHash: _l2TxHash, - _l2BatchNumber: _l2BatchNumber, - _l2MessageIndex: _l2MessageIndex, - _l2TxNumberInBatch: _l2TxNumberInBatch, - _merkleProof: _merkleProof, - _status: _status - }); + function stmAssetId(address _stmAddress) public view override returns (bytes32) { + return keccak256(abi.encode(L1_CHAIN_ID, address(stmDeployer), bytes32(uint256(uint160(_stmAddress))))); } - /// @notice forwards function call to Mailbox based on ChainId - function l2TransactionBaseCost( - uint256 _chainId, - uint256 _gasPrice, - uint256 _l2GasLimit, - uint256 _l2GasPerPubdataByteLimit - ) external view returns (uint256) { - address hyperchain = getHyperchain(_chainId); - return IZkSyncHyperchain(hyperchain).l2TransactionBaseCost(_gasPrice, _l2GasLimit, _l2GasPerPubdataByteLimit); - } + /*////////////////////////////////////////////////////////////// + Mailbox forwarder + //////////////////////////////////////////////////////////////*/ /// @notice the mailbox is called directly after the sharedBridge received the deposit /// this assumes that either ether is the base token or - /// the msg.sender has approved mintValue allowance for the sharedBridge. - /// This means this is not ideal for contract calls, as the contract would have to handle token allowance of the base Token + /// the msg.sender has approved mintValue allowance for the nativeTokenVault. + /// This means this is not ideal for contract calls, as the contract would have to handle token allowance of the base Token. + /// In case allowance is provided to the Shared Bridge, then it will be transferred to NTV. function requestL2TransactionDirect( L2TransactionRequestDirect calldata _request - ) external payable override nonReentrant whenNotPaused returns (bytes32 canonicalTxHash) { + ) external payable override nonReentrant whenNotPaused onlyL1 returns (bytes32 canonicalTxHash) { // Note: If the hyperchain with corresponding `chainId` is not yet created, // the transaction will revert on `bridgehubRequestL2Transaction` as call to zero address. { bytes32 tokenAssetId = baseTokenAssetId[_request.chainId]; if (tokenAssetId == ETH_TOKEN_ASSET_ID) { - require(msg.value == _request.mintValue, "Bridgehub: msg.value mismatch 1"); + require(msg.value == _request.mintValue, "BH: msg.value mismatch 1"); } else { - require(msg.value == 0, "Bridgehub: non-eth bridge with msg.value"); + require(msg.value == 0, "BH: non-eth bridge with msg.value"); } // slither-disable-next-line arbitrary-send-eth @@ -375,26 +359,30 @@ contract Bridgehub is IBridgehub, ReentrancyGuard, Ownable2StepUpgradeable, Paus /// @notice After depositing funds to the sharedBridge, the secondBridge is called /// to return the actual L2 message which is sent to the Mailbox. /// This assumes that either ether is the base token or - /// the msg.sender has approved the sharedBridge with the mintValue, + /// the msg.sender has approved the nativeTokenVault with the mintValue, /// and also the necessary approvals are given for the second bridge. + /// In case allowance is provided to the Shared Bridge, then it will be transferred to NTV. /// @notice The logic of this bridge is to allow easy depositing for bridges. /// Each contract that handles the users ERC20 tokens needs approvals from the user, this contract allows /// the user to approve for each token only its respective bridge /// @notice This function is great for contract calls to L2, the secondBridge can be any contract. + /// @param _request the request for the L2 transaction function requestL2TransactionTwoBridges( L2TransactionRequestTwoBridgesOuter calldata _request - ) external payable override nonReentrant whenNotPaused returns (bytes32 canonicalTxHash) { + ) external payable override nonReentrant whenNotPaused onlyL1 returns (bytes32 canonicalTxHash) { + require( + _request.secondBridgeAddress > BRIDGEHUB_MIN_SECOND_BRIDGE_ADDRESS, + "BH: second bridge address too low" + ); // to avoid calls to precompiles + { bytes32 tokenAssetId = baseTokenAssetId[_request.chainId]; uint256 baseTokenMsgValue; if (tokenAssetId == ETH_TOKEN_ASSET_ID) { - require( - msg.value == _request.mintValue + _request.secondBridgeValue, - "Bridgehub: msg.value mismatch 2" - ); + require(msg.value == _request.mintValue + _request.secondBridgeValue, "BH: msg.value mismatch 2"); baseTokenMsgValue = _request.mintValue; } else { - require(msg.value == _request.secondBridgeValue, "Bridgehub: msg.value mismatch 3"); + require(msg.value == _request.secondBridgeValue, "BH: msg.value mismatch 3"); baseTokenMsgValue = 0; } // slither-disable-next-line arbitrary-send-eth @@ -417,14 +405,10 @@ contract Bridgehub is IBridgehub, ReentrancyGuard, Ownable2StepUpgradeable, Paus _request.secondBridgeCalldata ); - require(outputRequest.magicValue == TWO_BRIDGES_MAGIC_VALUE, "Bridgehub: magic value mismatch"); + require(outputRequest.magicValue == TWO_BRIDGES_MAGIC_VALUE, "BH: magic value mismatch"); address refundRecipient = AddressAliasHelper.actualRefundRecipient(_request.refundRecipient, msg.sender); - require( - _request.secondBridgeAddress > BRIDGEHUB_MIN_SECOND_BRIDGE_ADDRESS, - "Bridgehub: second bridge address too low" - ); // to avoid calls to precompiles canonicalTxHash = IZkSyncHyperchain(hyperchain).bridgehubRequestL2Transaction( BridgehubL2TransactionRequest({ sender: _request.secondBridgeAddress, @@ -446,16 +430,22 @@ contract Bridgehub is IBridgehub, ReentrancyGuard, Ownable2StepUpgradeable, Paus ); } - function forwardTransactionOnSyncLayer( + /// @notice Used to forward a transaction on the gateway to the chains mailbox (from L1). + /// @param _chainId the chainId of the chain + /// @param _transaction the transaction to be forwarded + /// @param _factoryDeps the factory dependencies for the transaction + /// @param _canonicalTxHash the canonical transaction hash + /// @param _expirationTimestamp the expiration timestamp for the transaction + function forwardTransactionOnGateway( uint256 _chainId, L2CanonicalTransaction calldata _transaction, bytes[] calldata _factoryDeps, bytes32 _canonicalTxHash, uint64 _expirationTimestamp - ) external override { + ) external override onlySettlementLayerRelayedSender { require(L1_CHAIN_ID != block.chainid, "BH: not in sync layer mode"); address hyperchain = getHyperchain(_chainId); - IZkSyncHyperchain(hyperchain).bridgehubRequestL2TransactionOnSyncLayer( + IZkSyncHyperchain(hyperchain).bridgehubRequestL2TransactionOnGateway( _transaction, _factoryDeps, _canonicalTxHash, @@ -463,18 +453,100 @@ contract Bridgehub is IBridgehub, ReentrancyGuard, Ownable2StepUpgradeable, Paus ); } + /// @notice forwards function call to Mailbox based on ChainId + /// @param _chainId The chain ID of the hyperchain where to prove L2 message inclusion. + /// @param _batchNumber The executed L2 batch number in which the message appeared + /// @param _index The position in the L2 logs Merkle tree of the l2Log that was sent with the message + /// @param _message Information about the sent message: sender address, the message itself, tx index in the L2 batch where the message was sent + /// @param _proof Merkle proof for inclusion of L2 log that was sent with the message + /// @return Whether the proof is valid + function proveL2MessageInclusion( + uint256 _chainId, + uint256 _batchNumber, + uint256 _index, + L2Message calldata _message, + bytes32[] calldata _proof + ) external view override returns (bool) { + address hyperchain = getHyperchain(_chainId); + return IZkSyncHyperchain(hyperchain).proveL2MessageInclusion(_batchNumber, _index, _message, _proof); + } + + /// @notice forwards function call to Mailbox based on ChainId + /// @param _chainId The chain ID of the hyperchain where to prove L2 log inclusion. + /// @param _batchNumber The executed L2 batch number in which the log appeared + /// @param _index The position of the l2log in the L2 logs Merkle tree + /// @param _log Information about the sent log + /// @param _proof Merkle proof for inclusion of the L2 log + /// @return Whether the proof is correct and L2 log is included in batch + function proveL2LogInclusion( + uint256 _chainId, + uint256 _batchNumber, + uint256 _index, + L2Log calldata _log, + bytes32[] calldata _proof + ) external view override returns (bool) { + address hyperchain = getHyperchain(_chainId); + return IZkSyncHyperchain(hyperchain).proveL2LogInclusion(_batchNumber, _index, _log, _proof); + } + + /// @notice forwards function call to Mailbox based on ChainId + /// @param _chainId The chain ID of the hyperchain where to prove L1->L2 tx status. + /// @param _l2TxHash The L2 canonical transaction hash + /// @param _l2BatchNumber The L2 batch number where the transaction was processed + /// @param _l2MessageIndex The position in the L2 logs Merkle tree of the l2Log that was sent with the message + /// @param _l2TxNumberInBatch The L2 transaction number in the batch, in which the log was sent + /// @param _merkleProof The Merkle proof of the processing L1 -> L2 transaction + /// @param _status The execution status of the L1 -> L2 transaction (true - success & 0 - fail) + /// @return Whether the proof is correct and the transaction was actually executed with provided status + /// NOTE: It may return `false` for incorrect proof, but it doesn't mean that the L1 -> L2 transaction has an opposite status! + function proveL1ToL2TransactionStatus( + uint256 _chainId, + bytes32 _l2TxHash, + uint256 _l2BatchNumber, + uint256 _l2MessageIndex, + uint16 _l2TxNumberInBatch, + bytes32[] calldata _merkleProof, + TxStatus _status + ) external view override returns (bool) { + address hyperchain = getHyperchain(_chainId); + return + IZkSyncHyperchain(hyperchain).proveL1ToL2TransactionStatus({ + _l2TxHash: _l2TxHash, + _l2BatchNumber: _l2BatchNumber, + _l2MessageIndex: _l2MessageIndex, + _l2TxNumberInBatch: _l2TxNumberInBatch, + _merkleProof: _merkleProof, + _status: _status + }); + } + + /// @notice forwards function call to Mailbox based on ChainId + function l2TransactionBaseCost( + uint256 _chainId, + uint256 _gasPrice, + uint256 _l2GasLimit, + uint256 _l2GasPerPubdataByteLimit + ) external view returns (uint256) { + address hyperchain = getHyperchain(_chainId); + return IZkSyncHyperchain(hyperchain).l2TransactionBaseCost(_gasPrice, _l2GasLimit, _l2GasPerPubdataByteLimit); + } + /*////////////////////////////////////////////////////////////// Chain migration //////////////////////////////////////////////////////////////*/ - /// @dev we can move assets using these + /// @notice IL1AssetHandler interface, used to migrate (transfer) a chain to the settlement layer. + /// @param _settlementChainId the chainId of the settlement chain, i.e. where the message and the migrating chain is sent. + /// @param _assetId the assetId of the migrating chain's STM + /// @param _prevMsgSender the previous message sender + /// @param _data the data for the migration function bridgeBurn( uint256 _settlementChainId, - uint256, + uint256, // mintValue bytes32 _assetId, address _prevMsgSender, bytes calldata _data - ) external payable override returns (bytes memory bridgehubMintData) { + ) external payable override onlyAssetRouter onlyL1 returns (bytes memory bridgehubMintData) { require(whitelistedSettlementLayers[_settlementChainId], "BH: SL not whitelisted"); (uint256 _chainId, bytes memory _stmData, bytes memory _chainData) = abi.decode(_data, (uint256, bytes, bytes)); @@ -482,24 +554,30 @@ contract Bridgehub is IBridgehub, ReentrancyGuard, Ownable2StepUpgradeable, Paus require(settlementLayer[_chainId] == block.chainid, "BH: not current SL"); settlementLayer[_chainId] = _settlementChainId; + address hyperchain = getHyperchain(_chainId); + require(hyperchain != address(0), "BH: hyperchain not registered"); + require(_prevMsgSender == IZkSyncHyperchain(hyperchain).getAdmin(), "BH: incorrect sender"); + bytes memory stmMintData = IStateTransitionManager(stateTransitionManager[_chainId]).forwardedBridgeBurn( _chainId, _stmData ); - bytes memory chainMintData = IZkSyncHyperchain(getHyperchain(_chainId)).forwardedBridgeBurn( + bytes memory chainMintData = IZkSyncHyperchain(hyperchain).forwardedBridgeBurn( getHyperchain(_settlementChainId), _prevMsgSender, _chainData ); bridgehubMintData = abi.encode(_chainId, stmMintData, chainMintData); - // TODO: double check that get only returns when chain id is there. } + /// @dev IL1AssetHandler interface, used to receive a chain on the settlement layer. + /// @param _assetId the assetId of the chain's STM + /// @param _bridgehubMintData the data for the mint function bridgeMint( - uint256, + uint256, // chainId bytes32 _assetId, bytes calldata _bridgehubMintData - ) external payable override returns (address l1Receiver) { + ) external payable override onlyAssetRouter returns (address l1Receiver) { (uint256 _chainId, bytes memory _stmData, bytes memory _chainMintData) = abi.decode( _bridgehubMintData, (uint256, bytes, bytes) @@ -515,16 +593,21 @@ contract Bridgehub is IBridgehub, ReentrancyGuard, Ownable2StepUpgradeable, Paus hyperchain = IStateTransitionManager(stm).forwardedBridgeMint(_chainId, _stmData); } - IMessageRoot(messageRoot).addNewChainIfNeeded(_chainId); + messageRoot.addNewChainIfNeeded(_chainId); IZkSyncHyperchain(hyperchain).forwardedBridgeMint(_chainMintData); return address(0); } + /// @dev IL1AssetHandler interface, used to undo a failed migration of a chain. + /// @param _chainId the chainId of the chain + /// @param _assetId the assetId of the chain's STM + /// @param _data the data for the recovery function bridgeRecoverFailedTransfer( uint256 _chainId, bytes32 _assetId, + address _depositSender, bytes calldata _data - ) external payable override {} + ) external payable override onlyAssetRouter onlyL1 {} /*////////////////////////////////////////////////////////////// PAUSE diff --git a/l1-contracts/contracts/bridgehub/IBridgehub.sol b/l1-contracts/contracts/bridgehub/IBridgehub.sol index 98f9cf09c..37efceadd 100644 --- a/l1-contracts/contracts/bridgehub/IBridgehub.sol +++ b/l1-contracts/contracts/bridgehub/IBridgehub.sol @@ -40,6 +40,8 @@ struct L2TransactionRequestTwoBridgesInner { bytes32 txDataHash; } +/// @author Matter Labs +/// @custom:security-contact security@matterlabs.dev interface IBridgehub is IL1AssetHandler { /// @notice pendingAdmin is changed /// @dev Also emitted when new admin is accepted and in this case, `newPendingAdmin` would be zero address @@ -56,7 +58,9 @@ interface IBridgehub is IL1AssetHandler { address sender ); - /// @notice Starts the transfer of admin rights. Only the current admin can propose a new pending one. + event SettlementLayerRegistered(uint256 indexed chainId, bool indexed isWhitelisted); + + /// @notice Starts the transfer of admin rights. Only the current admin or owner can propose a new pending one. /// @notice New admin can accept admin rights by calling `acceptAdmin` function. /// @param _newPendingAdmin Address of the new admin function setPendingAdmin(address _newPendingAdmin) external; @@ -148,17 +152,21 @@ interface IBridgehub is IL1AssetHandler { IMessageRoot _messageRoot ) external; - // function relayTxThroughBH(uint256 _baseDestChainId, uint256 _destChainId, bytes calldata _dataToRelay) external; + event NewChain(uint256 indexed chainId, address stateTransitionManager, address indexed chainGovernance); - // function registerCounterpart(uint256 chainid, address _counterpart) external; + event StateTransitionManagerAdded(address indexed stateTransitionManager); - event NewChain(uint256 indexed chainId, address stateTransitionManager, address indexed chainGovernance); + event StateTransitionManagerRemoved(address indexed stateTransitionManager); + + event TokenRegistered(address indexed token); + + event SharedBridgeUpdated(address indexed sharedBridge); function whitelistedSettlementLayers(uint256 _chainId) external view returns (bool); - function registerSyncLayer(uint256 _newSyncLayerChainId, bool _isWhitelisted) external; + function registerSettlementLayer(uint256 _newSettlementLayerChainId, bool _isWhitelisted) external; - // function finalizeMigrationToSyncLayer( + // function finalizeMigrationToGateway( // uint256 _chainId, // address _baseToken, // address _sharedBridge, @@ -168,7 +176,7 @@ interface IBridgehub is IL1AssetHandler { // bytes calldata _diamondCut // ) external; - function forwardTransactionOnSyncLayer( + function forwardTransactionOnGateway( uint256 _chainId, L2CanonicalTransaction calldata _transaction, bytes[] calldata _factoryDeps, @@ -182,11 +190,9 @@ interface IBridgehub is IL1AssetHandler { function stmDeployer() external view returns (ISTMDeploymentTracker); - function setSTMDeployer(ISTMDeploymentTracker _stmDeployer) external; - function stmAssetIdToAddress(bytes32 _assetInfo) external view returns (address); - function setAssetHandlerAddressInitial(bytes32 _additionalData, address _assetAddress) external; + function setAssetHandlerAddress(bytes32 _additionalData, address _assetAddress) external; function L1_CHAIN_ID() external view returns (uint256); } diff --git a/l1-contracts/contracts/bridgehub/IMessageRoot.sol b/l1-contracts/contracts/bridgehub/IMessageRoot.sol index 25542bab7..a0791b922 100644 --- a/l1-contracts/contracts/bridgehub/IMessageRoot.sol +++ b/l1-contracts/contracts/bridgehub/IMessageRoot.sol @@ -4,6 +4,8 @@ pragma solidity 0.8.24; import {IBridgehub} from "./IBridgehub.sol"; +/// @author Matter Labs +/// @custom:security-contact security@matterlabs.dev interface IMessageRoot { function BRIDGE_HUB() external view returns (IBridgehub); @@ -11,7 +13,5 @@ interface IMessageRoot { function addChainBatchRoot(uint256 _chainId, uint256 _batchNumber, bytes32 _chainBatchRoot) external; - function clearTreeAndProvidePubdata() external returns (bytes memory pubdata); - function addNewChainIfNeeded(uint256 _chainId) external; } diff --git a/l1-contracts/contracts/bridgehub/ISTMDeploymentTracker.sol b/l1-contracts/contracts/bridgehub/ISTMDeploymentTracker.sol index 2f54c86c8..a1f71cdbf 100644 --- a/l1-contracts/contracts/bridgehub/ISTMDeploymentTracker.sol +++ b/l1-contracts/contracts/bridgehub/ISTMDeploymentTracker.sol @@ -5,6 +5,8 @@ pragma solidity 0.8.24; import {L2TransactionRequestTwoBridgesInner, IBridgehub} from "./IBridgehub.sol"; import {IL1AssetRouter} from "../bridge/interfaces/IL1AssetRouter.sol"; +/// @author Matter Labs +/// @custom:security-contact security@matterlabs.dev interface ISTMDeploymentTracker { function bridgehubDeposit( uint256 _chainId, diff --git a/l1-contracts/contracts/bridgehub/MessageRoot.sol b/l1-contracts/contracts/bridgehub/MessageRoot.sol index c98549229..9f70febd4 100644 --- a/l1-contracts/contracts/bridgehub/MessageRoot.sol +++ b/l1-contracts/contracts/bridgehub/MessageRoot.sol @@ -12,7 +12,7 @@ import {ReentrancyGuard} from "../common/ReentrancyGuard.sol"; import {FullMerkle} from "../common/libraries/FullMerkle.sol"; -import {Messaging} from "../common/libraries/Messaging.sol"; +import {MessageHashing} from "../common/libraries/MessageHashing.sol"; import {MAX_NUMBER_OF_HYPERCHAINS} from "../common/Config.sol"; @@ -26,31 +26,42 @@ bytes32 constant SHARED_ROOT_TREE_EMPTY_HASH = bytes32( 0x46700b4d40ac5c35af2c22dda2787a91eb567b06c924a8fb8ae9a05b20c08c21 ); +/// @author Matter Labs +/// @custom:security-contact security@matterlabs.dev +/// @dev The MessageRoot contract is responsible for storing the cross message roots of the chains and the aggregated root of all chains. contract MessageRoot is IMessageRoot, ReentrancyGuard { + using FullMerkle for FullMerkle.FullTree; + using DynamicIncrementalMerkle for DynamicIncrementalMerkle.Bytes32PushTree; + event AddedChain(uint256 indexed chainId, uint256 indexed chainIndex); event AppendedChainBatchRoot(uint256 indexed chainId, uint256 indexed batchNumber, bytes32 batchRoot); - using FullMerkle for FullMerkle.FullTree; - using DynamicIncrementalMerkle for DynamicIncrementalMerkle.Bytes32PushTree; + event Preimage(bytes32 one, bytes32 two); + /// @dev Bridgehub smart contract that is used to operate with L2 via asynchronous L2 <-> L1 communication. IBridgehub public immutable override BRIDGE_HUB; + /// @notice The number of chains that are registered. uint256 public chainCount; + /// @notice The mapping from chainId to chainIndex. mapping(uint256 chainId => uint256 chainIndex) public chainIndex; + /// @notice The mapping from chainIndex to chainId. mapping(uint256 chainIndex => uint256 chainId) public chainIndexToId; // There are two ways to distinguish chains: - // - Either by reserving the index 0 as a special value which denotes an unregistede chain + // - Either by reserving the index 0 as a special value which denotes an unregistered chain // - Use a separate mapping // The second approach is used due to explicitness. + /// @notice The mapping from chainId to whether the chain is registered. Used because the chainIndex can be 0. mapping(uint256 chainId => bool isRegistered) public chainRegistered; + /// @notice The shared full merkle tree storing the aggregate hash. FullMerkle.FullTree public sharedTree; - /// @dev the incremental merkle tree storing the chain message roots + /// @dev The incremental merkle tree storing the chain message roots. mapping(uint256 chainId => DynamicIncrementalMerkle.Bytes32PushTree tree) internal chainTree; /// @notice only the bridgehub can call @@ -60,12 +71,14 @@ contract MessageRoot is IMessageRoot, ReentrancyGuard { } /// @notice only the bridgehub can call + /// @param _chainId the chainId of the chain modifier onlyChain(uint256 _chainId) { require(msg.sender == BRIDGE_HUB.getHyperchain(_chainId), "MR: only chain"); _; } - /// @dev Contract is expected to be used as proxy implementation. + /// @dev Contract is expected to be used as proxy implementation on L1, but as a system contract on L2. + /// This means we call the _initialize in both the constructor and the initialize functions. /// @dev Initialize the implementation to prevent Parity hack. constructor(IBridgehub _bridgehub) reentrancyGuardInitializer { BRIDGE_HUB = _bridgehub; @@ -77,55 +90,19 @@ contract MessageRoot is IMessageRoot, ReentrancyGuard { _initialize(); } - function _initialize() internal { - // slither-disable-next-line unused-return - sharedTree.setup(SHARED_ROOT_TREE_EMPTY_HASH); - } - function addNewChain(uint256 _chainId) external onlyBridgehub { require(!chainRegistered[_chainId], "MR: chain exists"); _addNewChain(_chainId); } + /// @dev Adds a new chain to the message root if it has not been added yet. + /// @param _chainId the chainId of the chain function addNewChainIfNeeded(uint256 _chainId) external onlyBridgehub { if (!chainRegistered[_chainId]) { _addNewChain(_chainId); } } - function getAggregatedRoot() external view returns (bytes32) { - return sharedTree.root(); - } - - function getChainRoot(uint256 _chainId) external view returns (bytes32) { - return chainTree[_chainId].root(); - } - - function _addNewChain(uint256 _chainId) internal { - // The chain itself can not be the part of the message root. - // The message root will only aggregate chains that settle on it. - require(_chainId != block.chainid); - - chainRegistered[_chainId] = true; - - // We firstly increment `chainCount` and then apply it to ensure that `0` is reserved for chains that are not present. - uint256 cachedChainCount = chainCount; - require(cachedChainCount < MAX_NUMBER_OF_HYPERCHAINS, "MR: too many chains"); - - ++chainCount; - chainIndex[_chainId] = cachedChainCount; - chainIndexToId[cachedChainCount] = _chainId; - - // slither-disable-next-line unused-return - bytes32 initialHash = chainTree[_chainId].setup(CHAIN_TREE_EMPTY_ENTRY_HASH); - // slither-disable-next-line unused-return - sharedTree.pushNewLeaf(Messaging.chainIdLeafHash(initialHash, _chainId)); - - emit AddedChain(_chainId, cachedChainCount); - } - - event Preimage(bytes32 one, bytes32 two); - /// @dev add a new chainBatchRoot to the chainTree function addChainBatchRoot( uint256 _chainId, @@ -135,65 +112,64 @@ contract MessageRoot is IMessageRoot, ReentrancyGuard { require(chainRegistered[_chainId], "MR: not registered"); bytes32 chainRoot; // slither-disable-next-line unused-return - (, chainRoot) = chainTree[_chainId].push(Messaging.batchLeafHash(_chainBatchRoot, _batchNumber)); + (, chainRoot) = chainTree[_chainId].push(MessageHashing.batchLeafHash(_chainBatchRoot, _batchNumber)); // slither-disable-next-line unused-return - sharedTree.updateLeaf(chainIndex[_chainId], Messaging.chainIdLeafHash(chainRoot, _chainId)); + sharedTree.updateLeaf(chainIndex[_chainId], MessageHashing.chainIdLeafHash(chainRoot, _chainId)); - emit Preimage(chainRoot, Messaging.chainIdLeafHash(chainRoot, _chainId)); + emit Preimage(chainRoot, MessageHashing.chainIdLeafHash(chainRoot, _chainId)); emit AppendedChainBatchRoot(_chainId, _batchNumber, _chainBatchRoot); } + /// @dev Gets the aggregated root of all chains. + function getAggregatedRoot() external view returns (bytes32) { + return sharedTree.root(); + } + + /// @dev Gets the message root of a single chain. + /// @param _chainId the chainId of the chain + function getChainRoot(uint256 _chainId) external view returns (bytes32) { + return chainTree[_chainId].root(); + } + function updateFullTree() public { uint256 cachedChainCount = chainCount; bytes32[] memory newLeaves = new bytes32[](cachedChainCount); for (uint256 i = 0; i < cachedChainCount; ++i) { - newLeaves[i] = Messaging.chainIdLeafHash(chainTree[chainIndexToId[i]].root(), chainIndexToId[i]); + newLeaves[i] = MessageHashing.chainIdLeafHash(chainTree[chainIndexToId[i]].root(), chainIndexToId[i]); } // slither-disable-next-line unused-return sharedTree.updateAllLeaves(newLeaves); } - // It is expected that the root is present - // `_updateTree` should be false only if the caller ensures that it is followed by updating the entire tree. - function _unsafeResetChainRoot(uint256 _index, bool _updateTree) internal { - uint256 chainId = chainIndexToId[_index]; - bytes32 initialRoot = chainTree[chainId].setup(CHAIN_TREE_EMPTY_ENTRY_HASH); - - if (_updateTree) { - // slither-disable-next-line unused-return - sharedTree.updateLeaf(_index, Messaging.chainIdLeafHash(initialRoot, chainId)); - } + function _initialize() internal { + // slither-disable-next-line unused-return + sharedTree.setup(SHARED_ROOT_TREE_EMPTY_HASH); } - /// IMPORTANT FIXME!!!: split into two: provide pubdata and clear state. The "provide pubdata" part should be used by SL. - /// NO DA is provided here ATM !!! - /// @notice To be called by the bootloader by the L1Messenger at the end of the batch to produce the final root and send it to the underlying layer. - /// @return pubdata The pubdata to be relayed to the DA layer. - function clearTreeAndProvidePubdata() external returns (bytes memory) { - // FIXME: access control: only to be called by the l1 messenger. - // uint256 cachedChainCount = chainCount; - // // We will send the updated roots for all chains. - // // While it will mean that we'll pay even for unchanged roots: - // // - It is the simplest approach - // // - The alternative is to send pairs of (chainId, root), which is less efficient if at least half of the chains are active. - // // - // // There are of course ways to optimize it further, but it will be done in the future. - // bytes memory pubdata = new bytes(cachedChainCount * 32); - // for (uint256 i = 0; i < cachedChainCount; i++) { - // // It is the responsibility of each chain to provide the roots of its L2->L1 messages if it wants to see those. - // // However, for the security of the system as a whole, the chain roots need to be provided for all chains. - // bytes32 chainRoot = chainTree[chainIndexToId[i]].root(); - // assembly { - // mstore(add(pubdata, add(32, mul(i, 32))), chainRoot) - // } - // // Clearing up the state. - // // Note that it *does not* delete any storage slots, so in terms of pubdata savings, it is useless. - // // However, the chains paid for these changes anyway, so it is considered acceptable. - // // In the future, further optimizations will be available. - // _unsafeResetChainRoot(i, false); - // } - // updateFullTree(); + /// @dev Adds a single chain to the message root. + /// @param _chainId the chainId of the chain + function _addNewChain(uint256 _chainId) internal { + // The chain itself can not be the part of the message root. + // The message root will only aggregate chains that settle on it. + require(_chainId != block.chainid, "MR: chainId is this chain"); + + chainRegistered[_chainId] = true; + + // We firstly increment `chainCount` and then apply it to ensure that `0` is reserved for chains that are not present. + uint256 cachedChainCount = chainCount; + require(cachedChainCount < MAX_NUMBER_OF_HYPERCHAINS, "MR: too many chains"); + + ++chainCount; + chainIndex[_chainId] = cachedChainCount; + chainIndexToId[cachedChainCount] = _chainId; + + // slither-disable-next-line unused-return + bytes32 initialHash = chainTree[_chainId].setup(CHAIN_TREE_EMPTY_ENTRY_HASH); + // slither-disable-next-line unused-return + sharedTree.pushNewLeaf(MessageHashing.chainIdLeafHash(initialHash, _chainId)); + + emit AddedChain(_chainId, cachedChainCount); } } diff --git a/l1-contracts/contracts/bridgehub/STMDeploymentTracker.sol b/l1-contracts/contracts/bridgehub/STMDeploymentTracker.sol index a72dac000..3c99e61d7 100644 --- a/l1-contracts/contracts/bridgehub/STMDeploymentTracker.sol +++ b/l1-contracts/contracts/bridgehub/STMDeploymentTracker.sol @@ -32,7 +32,7 @@ contract STMDeploymentTracker is ISTMDeploymentTracker, ReentrancyGuard, Ownable _; } - /// @dev Contract is expected to be used as proxy implementation. + /// @dev Contract is expected to be used as proxy implementation on L1. /// @dev Initialize the implementation to prevent Parity hack. constructor(IBridgehub _bridgehub, IL1AssetRouter _sharedBridge) reentrancyGuardInitializer { _disableInitializers(); @@ -41,16 +41,19 @@ contract STMDeploymentTracker is ISTMDeploymentTracker, ReentrancyGuard, Ownable } /// @notice used to initialize the contract + /// @param _owner the owner of the contract function initialize(address _owner) external reentrancyGuardInitializer { _transferOwnership(_owner); } + /// @notice Used to register the stm asset in L1 contracts, AssetRouter and Bridgehub. + /// @param _stmAddress the address of the stm asset function registerSTMAssetOnL1(address _stmAddress) external onlyOwner { // solhint-disable-next-line gas-custom-errors require(BRIDGE_HUB.stateTransitionManagerIsRegistered(_stmAddress), "STMDT: stm not registered"); - SHARED_BRIDGE.setAssetHandlerAddressInitial(bytes32(uint256(uint160(_stmAddress))), address(BRIDGE_HUB)); - BRIDGE_HUB.setAssetHandlerAddressInitial(bytes32(uint256(uint160(_stmAddress))), _stmAddress); + SHARED_BRIDGE.setAssetHandlerAddressThisChain(bytes32(uint256(uint160(_stmAddress))), address(BRIDGE_HUB)); + BRIDGE_HUB.setAssetHandlerAddress(bytes32(uint256(uint160(_stmAddress))), _stmAddress); } /// @notice The function responsible for registering the L2 counterpart of an STM asset on the L2 Bridgehub. @@ -58,11 +61,14 @@ contract STMDeploymentTracker is ISTMDeploymentTracker, ReentrancyGuard, Ownable /// @dev Since the L2 settlement layers `_chainId` might potentially have ERC20 tokens as native assets, /// there are two ways to perform the L1->L2 transaction: /// - via the `Bridgehub.requestL2TransactionDirect`. However, this would require the STMDeploymentTracker to - /// hahndle the ERC20 balances to be used in the transaction. + /// handle the ERC20 balances to be used in the transaction. /// - via the `Bridgehub.requestL2TransactionTwoBridges`. This way it will be the sender that provides the funds /// for the L2 transaction. /// The second approach is used due to its simplicity even though it gives the sender slightly more control over the call: /// `gasLimit`, etc. + /// @param _chainId the chainId of the chain + /// @param _prevMsgSender the previous message sender + /// @param _data the data of the transaction // slither-disable-next-line locked-ether function bridgehubDeposit( uint256 _chainId, @@ -81,7 +87,13 @@ contract STMDeploymentTracker is ISTMDeploymentTracker, ReentrancyGuard, Ownable request = _registerSTMAssetOnL2Bridgehub(_chainId, _stmL1Address, _stmL2Address); } - // todo this has to be put in L1AssetRouter via TwoBridges. Hard, because we have to have multiple msg types + /// @notice The function called by the Bridgehub after the L2 transaction has been initiated. + /// @dev Not used in this contract. In case the transaction fails, we can just re-try it. + function bridgehubConfirmL2Transaction(uint256 _chainId, bytes32 _txDataHash, bytes32 _txHash) external {} + + // todo this has to be put in L1AssetRouter via TwoBridges for custom base tokens. Hard, because we have to have multiple msg types in bridgehubDeposit in the AssetRouter. + /// @notice Used to register the stm asset in L2 AssetRouter. + /// @param _chainId the chainId of the chain function registerSTMAssetOnL2SharedBridge( uint256 _chainId, address _stmL1Address, @@ -89,7 +101,7 @@ contract STMDeploymentTracker is ISTMDeploymentTracker, ReentrancyGuard, Ownable uint256 _l2TxGasLimit, uint256 _l2TxGasPerPubdataByteLimit, address _refundRecipient - ) public payable { + ) public payable onlyOwner { bytes32 assetId; { assetId = getAssetId(_stmL1Address); @@ -105,18 +117,22 @@ contract STMDeploymentTracker is ISTMDeploymentTracker, ReentrancyGuard, Ownable _assetAddressOnCounterPart: L2_BRIDGEHUB_ADDR }); } - // Todo this works for now, but it will not work in the future if we want to change STM DTs. Probably ok. + + function getAssetId(address _l1STM) public view override returns (bytes32) { + return keccak256(abi.encode(block.chainid, address(this), bytes32(uint256(uint160(_l1STM))))); + } + + /// @notice Used to register the stm asset in L2 Bridgehub. + /// @param _chainId the chainId of the chain function _registerSTMAssetOnL2Bridgehub( // solhint-disable-next-line no-unused-vars uint256 _chainId, address _stmL1Address, address _stmL2Address ) internal pure returns (L2TransactionRequestTwoBridgesInner memory request) { - bytes memory l2TxCalldata = abi.encodeWithSelector( - /// todo it should not be initial in setAssetHandlerAddressInitial - IBridgehub.setAssetHandlerAddressInitial.selector, - bytes32(uint256(uint160(_stmL1Address))), - _stmL2Address + bytes memory l2TxCalldata = abi.encodeCall( + IBridgehub.setAssetHandlerAddress, + (bytes32(uint256(uint160(_stmL1Address))), _stmL2Address) ); request = L2TransactionRequestTwoBridgesInner({ @@ -124,16 +140,11 @@ contract STMDeploymentTracker is ISTMDeploymentTracker, ReentrancyGuard, Ownable l2Contract: L2_BRIDGEHUB_ADDR, l2Calldata: l2TxCalldata, factoryDeps: new bytes[](0), + // The `txDataHash` is typically used in usual ERC20 bridges to commit to the transaction data + // so that the user can recover funds in case the bridging fails on L2. + // However, this contract uses the `requestL2TransactionTwoBridges` method just to perform an L1->L2 transaction. + // We do not need to recover anything and so `bytes32(0)` here is okay. txDataHash: bytes32(0) }); } - - /// @dev we need to implement this for the bridgehub for the TwoBridges logic - function bridgehubConfirmL2Transaction(uint256 _chainId, bytes32 _txDataHash, bytes32 _txHash) external { - // This function is typically used on bridges for e.g. - } - - function getAssetId(address _l1STM) public view override returns (bytes32) { - return keccak256(abi.encode(block.chainid, address(this), bytes32(uint256(uint160(_l1STM))))); - } } diff --git a/l1-contracts/contracts/common/Config.sol b/l1-contracts/contracts/common/Config.sol index 1b343ea09..d1b580182 100644 --- a/l1-contracts/contracts/common/Config.sol +++ b/l1-contracts/contracts/common/Config.sol @@ -21,10 +21,10 @@ bytes32 constant L2_L1_LOGS_TREE_DEFAULT_LEAF_HASH = 0x72abee45b59e344af8a6e5202 // TODO: change constant to the real root hash of empty Merkle tree (SMA-184) bytes32 constant DEFAULT_L2_LOGS_TREE_ROOT_HASH = bytes32(0); -/// @dev Denotes the type of the zkSync transaction that came from L1. +/// @dev Denotes the type of the ZKsync transaction that came from L1. uint256 constant PRIORITY_OPERATION_L2_TX_TYPE = 255; -/// @dev Denotes the type of the zkSync transaction that is used for system upgrades. +/// @dev Denotes the type of the ZKsync transaction that is used for system upgrades. uint256 constant SYSTEM_UPGRADE_L2_TX_TYPE = 254; /// @dev The maximal allowed difference between protocol minor versions in an upgrade. The 100 gap is needed @@ -102,21 +102,20 @@ uint256 constant MEMORY_OVERHEAD_GAS = 10; /// @dev The maximum gas limit for a priority transaction in L2. uint256 constant PRIORITY_TX_MAX_GAS_LIMIT = 72_000_000; +/// @dev the address used to identify eth as the base token for chains. address constant ETH_TOKEN_ADDRESS = address(1); +/// @dev the value returned in bridgehubDeposit in the TwoBridges function. bytes32 constant TWO_BRIDGES_MAGIC_VALUE = bytes32(uint256(keccak256("TWO_BRIDGES_MAGIC_VALUE")) - 1); /// @dev https://eips.ethereum.org/EIPS/eip-1352 address constant BRIDGEHUB_MIN_SECOND_BRIDGE_ADDRESS = address(uint160(type(uint16).max)); +/// @dev the maximum number of supported chains, this is an arbitrary limit. uint256 constant MAX_NUMBER_OF_HYPERCHAINS = 100; -/// FIXME: move to a different file - -struct StoredBatchHashInfo { - uint256 number; - bytes32 hash; -} +/// @dev Used as the `msg.sender` for transactions that relayed via a settlement layer. +address constant SETTLEMENT_LAYER_RELAY_SENDER = address(uint160(0x1111111111111111111111111111111111111111)); struct PriorityTreeCommitment { uint256 nextLeafIndex; diff --git a/l1-contracts/contracts/common/L2ContractAddresses.sol b/l1-contracts/contracts/common/L2ContractAddresses.sol index 1b7baf552..9ddb1635b 100644 --- a/l1-contracts/contracts/common/L2ContractAddresses.sol +++ b/l1-contracts/contracts/common/L2ContractAddresses.sol @@ -41,6 +41,7 @@ address constant L2_GENESIS_UPGRADE_ADDR = address(0x10001); /// @dev The address of the L2 bridge hub system contract, used to start L2<>L2 transactions address constant L2_BRIDGEHUB_ADDR = address(0x10002); +/// @dev the address of the l2 asse3t router. address constant L2_ASSET_ROUTER_ADDR = address(0x10003); /// @dev An l2 system contract address, used in the assetId calculation for native assets. diff --git a/l1-contracts/contracts/common/interfaces/IL2ContractDeployer.sol b/l1-contracts/contracts/common/interfaces/IL2ContractDeployer.sol index 31d796d45..08ddcd9e9 100644 --- a/l1-contracts/contracts/common/interfaces/IL2ContractDeployer.sol +++ b/l1-contracts/contracts/common/interfaces/IL2ContractDeployer.sol @@ -4,7 +4,7 @@ pragma solidity 0.8.24; /** * @author Matter Labs - * @notice System smart contract that is responsible for deploying other smart contracts on a zkSync hyperchain. + * @notice System smart contract that is responsible for deploying other smart contracts on a ZKsync hyperchain. */ interface IL2ContractDeployer { /// @notice A struct that describes a forced deployment on an address. diff --git a/l1-contracts/contracts/common/libraries/DataEncoding.sol b/l1-contracts/contracts/common/libraries/DataEncoding.sol new file mode 100644 index 000000000..39dcef4d5 --- /dev/null +++ b/l1-contracts/contracts/common/libraries/DataEncoding.sol @@ -0,0 +1,90 @@ +// SPDX-License-Identifier: MIT + +pragma solidity 0.8.24; + +import {L2_NATIVE_TOKEN_VAULT_ADDRESS} from "../L2ContractAddresses.sol"; + +/** + * @author Matter Labs + * @custom:security-contact security@matterlabs.dev + * @notice Helper library for transfer data encoding and decoding to reduce possibility of errors. + */ +library DataEncoding { + /// @notice Abi.encodes the data required for bridgeMint on remote chain. + /// @param _prevMsgSender The address which initiated the transfer. + /// @param _l2Receiver The address which to receive tokens on remote chain. + /// @param _l1Token The transferred token address. + /// @param _amount The amount of token to be transferred. + /// @param _erc20Metadata The transferred token metadata. + /// @return The encoded bridgeMint data + function encodeBridgeMintData( + address _prevMsgSender, + address _l2Receiver, + address _l1Token, + uint256 _amount, + bytes memory _erc20Metadata + ) internal pure returns (bytes memory) { + // solhint-disable-next-line func-named-parameters + return abi.encode(_prevMsgSender, _l2Receiver, _l1Token, _amount, _erc20Metadata); + } + + /// @notice Function decoding transfer data previously encoded with this library. + /// @param _bridgeMintData The encoded bridgeMint data + /// @return _prevMsgSender The address which initiated the transfer. + /// @return _l2Receiver The address which to receive tokens on remote chain. + /// @return _parsedL1Token The transferred token address. + /// @return _amount The amount of token to be transferred. + /// @return _erc20Metadata The transferred token metadata. + function decodeBridgeMintData( + bytes memory _bridgeMintData + ) + internal + pure + returns ( + address _prevMsgSender, + address _l2Receiver, + address _parsedL1Token, + uint256 _amount, + bytes memory _erc20Metadata + ) + { + (_prevMsgSender, _l2Receiver, _parsedL1Token, _amount, _erc20Metadata) = abi.decode( + _bridgeMintData, + (address, address, address, uint256, bytes) + ); + } + + /// @notice Encodes the asset data by combining chain id, asset deployment tracker and asset data. + /// @param _chainId The id of the chain token is native to. + /// @param _assetData The asset data that has to be encoded. + /// @param _sender The asset deployment tracker address. + /// @return The encoded asset data. + function encodeAssetId(uint256 _chainId, bytes32 _assetData, address _sender) internal pure returns (bytes32) { + return keccak256(abi.encode(_chainId, _sender, _assetData)); + } + + /// @notice Encodes the asset data by combining chain id, asset deployment tracker and asset data. + /// @param _chainId The id of the chain token is native to. + /// @param _tokenAaddress The address of token that has to be encoded (asset data is the address itself). + /// @param _sender The asset deployment tracker address. + /// @return The encoded asset data. + function encodeAssetId(uint256 _chainId, address _tokenAaddress, address _sender) internal pure returns (bytes32) { + return keccak256(abi.encode(_chainId, _sender, _tokenAaddress)); + } + + /// @notice Encodes the asset data by combining chain id, NTV as asset deployment tracker and asset data. + /// @param _chainId The id of the chain token is native to. + /// @param _assetData The asset data that has to be encoded. + /// @return The encoded asset data. + function encodeNTVAssetId(uint256 _chainId, bytes32 _assetData) internal pure returns (bytes32) { + return keccak256(abi.encode(_chainId, L2_NATIVE_TOKEN_VAULT_ADDRESS, _assetData)); + } + + /// @notice Encodes the asset data by combining chain id, NTV as asset deployment tracker and asset data. + /// @param _chainId The id of the chain token is native to. + /// @param _tokenAddress The address of token that has to be encoded (asset data is the address itself). + /// @return The encoded asset data. + function encodeNTVAssetId(uint256 _chainId, address _tokenAddress) internal pure returns (bytes32) { + return keccak256(abi.encode(_chainId, L2_NATIVE_TOKEN_VAULT_ADDRESS, _tokenAddress)); + } +} diff --git a/l1-contracts/contracts/common/libraries/DynamicIncrementalMerkle.sol b/l1-contracts/contracts/common/libraries/DynamicIncrementalMerkle.sol index f86f0858f..45f13cfaa 100644 --- a/l1-contracts/contracts/common/libraries/DynamicIncrementalMerkle.sol +++ b/l1-contracts/contracts/common/libraries/DynamicIncrementalMerkle.sol @@ -1,6 +1,6 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.0; +pragma solidity 0.8.24; import {Merkle} from "./Merkle.sol"; import {Arrays} from "@openzeppelin/contracts/utils/Arrays.sol"; @@ -47,8 +47,6 @@ library DynamicIncrementalMerkle { * @dev Initialize a {Bytes32PushTree} using {Hashes-Keccak256} to hash internal nodes. * The capacity of the tree (i.e. number of leaves) is set to `2**levels`. * - * Calling this function on MerkleTree that was already setup and used will reset it to a blank state. - * * IMPORTANT: The zero value should be carefully chosen since it will be stored in the tree representing * empty leaves. It should be a value that is not expected to be part of the tree. */ @@ -59,6 +57,27 @@ library DynamicIncrementalMerkle { return bytes32(0); } + /** + * @dev Resets the tree to a blank state. + * Calling this function on MerkleTree that was already setup and used will reset it to a blank state. + * @param zero The value that represents an empty leaf. + * @return initialRoot The initial root of the tree. + */ + function reset(Bytes32PushTree storage self, bytes32 zero) internal returns (bytes32 initialRoot) { + self._nextLeafIndex = 0; + uint256 length = self._zeros.length; + for (uint256 i = length; 0 < i; --i) { + self._zeros.pop(); + } + length = self._sides.length; + for (uint256 i = length; 0 < i; --i) { + self._sides.pop(); + } + self._zeros.push(zero); + self._sides.push(bytes32(0)); + return bytes32(0); + } + /** * @dev Insert a new leaf in the tree, and compute the new root. Returns the position of the inserted leaf in the * tree, and the resulting root. diff --git a/l1-contracts/contracts/common/libraries/FullMerkle.sol b/l1-contracts/contracts/common/libraries/FullMerkle.sol index 39f94fb22..d39ccde48 100644 --- a/l1-contracts/contracts/common/libraries/FullMerkle.sol +++ b/l1-contracts/contracts/common/libraries/FullMerkle.sol @@ -27,6 +27,7 @@ library FullMerkle { * * IMPORTANT: The zero value should be carefully chosen since it will be stored in the tree representing * empty leaves. It should be a value that is not expected to be part of the tree. + * @param zero The zero value to be used in the tree. */ function setup(FullTree storage self, bytes32 zero) internal returns (bytes32 initialRoot) { // Store depth in the dynamic array @@ -36,6 +37,10 @@ library FullMerkle { return zero; } + /** + * @dev Push a new leaf to the tree. + * @param _leaf The leaf to be added to the tree. + */ function pushNewLeaf(FullTree storage self, bytes32 _leaf) internal returns (bytes32 newRoot) { // solhint-disable-next-line gas-increment-by-one uint256 index = self._leafNumber++; @@ -52,10 +57,10 @@ library FullMerkle { uint256 oldMaxNodeNumber = index - 1; uint256 maxNodeNumber = index; for (uint256 i; i < self._height; i = i.uncheckedInc()) { - self._nodes[i].push(self._zeros[i]); if (oldMaxNodeNumber == maxNodeNumber) { break; } + self._nodes[i].push(self._zeros[i]); maxNodeNumber /= 2; oldMaxNodeNumber /= 2; } @@ -63,6 +68,11 @@ library FullMerkle { return updateLeaf(self, index, _leaf); } + /** + * @dev Update a leaf at index in the tree. + * @param _index The index of the leaf to be updated. + * @param _itemHash The new hash of the leaf. + */ function updateLeaf(FullTree storage self, uint256 _index, bytes32 _itemHash) internal returns (bytes32) { // solhint-disable-next-line gas-custom-errors uint256 maxNodeNumber = self._leafNumber - 1; @@ -85,12 +95,21 @@ library FullMerkle { return currentHash; } + /** + * @dev Updated all leaves in the tree. + * @param _newLeaves The new leaves to be added to the tree. + */ function updateAllLeaves(FullTree storage self, bytes32[] memory _newLeaves) internal returns (bytes32) { // solhint-disable-next-line gas-custom-errors require(_newLeaves.length == self._leafNumber, "FMT, wrong length"); return updateAllNodesAtHeight(self, 0, _newLeaves); } + /** + * @dev Update all nodes at a certain height in the tree. + * @param _height The height of the nodes to be updated. + * @param _newNodes The new nodes to be added to the tree. + */ function updateAllNodesAtHeight( FullTree storage self, uint256 _height, @@ -118,6 +137,9 @@ library FullMerkle { return updateAllNodesAtHeight(self, _height + 1, _newRow); } + /** + * @dev Returns the root of the tree. + */ function root(FullTree storage self) internal view returns (bytes32) { return self._nodes[self._height][0]; } diff --git a/l1-contracts/contracts/common/libraries/Messaging.sol b/l1-contracts/contracts/common/libraries/MessageHashing.sol similarity index 60% rename from l1-contracts/contracts/common/libraries/Messaging.sol rename to l1-contracts/contracts/common/libraries/MessageHashing.sol index 2cfdbcf7d..b7009482d 100644 --- a/l1-contracts/contracts/common/libraries/Messaging.sol +++ b/l1-contracts/contracts/common/libraries/MessageHashing.sol @@ -5,11 +5,17 @@ pragma solidity 0.8.24; bytes32 constant BATCH_LEAF_PADDING = keccak256("zkSync:BatchLeaf"); bytes32 constant CHAIN_ID_LEAF_PADDING = keccak256("zkSync:ChainIdLeaf"); -library Messaging { +library MessageHashing { + /// @dev Returns the leaf hash for a chain with batch number and batch root. + /// @param batchRoot The root hash of the batch. + /// @param batchNumber The number of the batch. function batchLeafHash(bytes32 batchRoot, uint256 batchNumber) internal pure returns (bytes32) { return keccak256(abi.encodePacked(BATCH_LEAF_PADDING, batchRoot, batchNumber)); } + /// @dev Returns the leaf hash for a chain with chain root and chain id. + /// @param chainIdRoot The root hash of the chain. + /// @param chainId The id of the chain. function chainIdLeafHash(bytes32 chainIdRoot, uint256 chainId) internal pure returns (bytes32) { return keccak256(abi.encodePacked(CHAIN_ID_LEAF_PADDING, chainIdRoot, chainId)); } diff --git a/l1-contracts/contracts/dev-contracts/DummyL1ERC20Bridge.sol b/l1-contracts/contracts/dev-contracts/DummyL1ERC20Bridge.sol index 75b7a155d..f3cf869cf 100644 --- a/l1-contracts/contracts/dev-contracts/DummyL1ERC20Bridge.sol +++ b/l1-contracts/contracts/dev-contracts/DummyL1ERC20Bridge.sol @@ -10,14 +10,10 @@ contract DummyL1ERC20Bridge is L1ERC20Bridge { constructor( IL1AssetRouter _l1SharedBridge, IL1NativeTokenVault _l1NativeTokenVault - ) L1ERC20Bridge(_l1SharedBridge, _l1NativeTokenVault) {} + ) L1ERC20Bridge(_l1SharedBridge, _l1NativeTokenVault, 1) {} - function setValues( - address _l2NativeTokenVault, - address _l2TokenBeacon, - bytes32 _l2TokenProxyBytecodeHash - ) external { - l2NativeTokenVault = _l2NativeTokenVault; + function setValues(address _l2SharedBridge, address _l2TokenBeacon, bytes32 _l2TokenProxyBytecodeHash) external { + l2Bridge = _l2SharedBridge; l2TokenBeacon = _l2TokenBeacon; l2TokenProxyBytecodeHash = _l2TokenProxyBytecodeHash; } diff --git a/l1-contracts/contracts/dev-contracts/RevertReceiveAccount.sol b/l1-contracts/contracts/dev-contracts/RevertReceiveAccount.sol index 31575de1b..663afdfdc 100644 --- a/l1-contracts/contracts/dev-contracts/RevertReceiveAccount.sol +++ b/l1-contracts/contracts/dev-contracts/RevertReceiveAccount.sol @@ -3,7 +3,7 @@ pragma solidity 0.8.24; /// @title RevertReceiveAccount - An account which reverts receiving funds depending on the flag -/// @dev Used for testing failed withdrawals from the zkSync smart contract +/// @dev Used for testing failed withdrawals from the ZKsync smart contract contract RevertReceiveAccount { // add this to be excluded from coverage report function test() internal virtual {} diff --git a/l1-contracts/contracts/dev-contracts/RevertTransferERC20.sol b/l1-contracts/contracts/dev-contracts/RevertTransferERC20.sol index bd018276d..dcc1f71f7 100644 --- a/l1-contracts/contracts/dev-contracts/RevertTransferERC20.sol +++ b/l1-contracts/contracts/dev-contracts/RevertTransferERC20.sol @@ -5,7 +5,7 @@ pragma solidity 0.8.24; import {TestnetERC20Token} from "./TestnetERC20Token.sol"; /// @title RevertTransferERC20Token - A ERC20 token contract which can revert transfers depending on a flag -/// @dev Used for testing failed ERC-20 withdrawals from the zkSync smart contract +/// @dev Used for testing failed ERC-20 withdrawals from the ZKsync smart contract contract RevertTransferERC20 is TestnetERC20Token { // add this to be excluded from coverage report function test() internal override {} diff --git a/l1-contracts/contracts/dev-contracts/test/DummyAdminFacetNoOverlap.sol b/l1-contracts/contracts/dev-contracts/test/DummyAdminFacetNoOverlap.sol index edc10d428..030006109 100644 --- a/l1-contracts/contracts/dev-contracts/test/DummyAdminFacetNoOverlap.sol +++ b/l1-contracts/contracts/dev-contracts/test/DummyAdminFacetNoOverlap.sol @@ -5,6 +5,7 @@ pragma solidity 0.8.24; import {Diamond} from "../../state-transition/libraries/Diamond.sol"; import {ZkSyncHyperchainBase} from "../../state-transition/chain-deps/facets/ZkSyncHyperchainBase.sol"; import {IL1AssetRouter} from "../../bridge/interfaces/IL1AssetRouter.sol"; +import {DataEncoding} from "../../common/libraries/DataEncoding.sol"; /// selectors do not overlap with normal facet selectors (getName does not count) contract DummyAdminFacetNoOverlap is ZkSyncHyperchainBase { @@ -17,7 +18,7 @@ contract DummyAdminFacetNoOverlap is ZkSyncHyperchainBase { function executeUpgradeNoOverlap(Diamond.DiamondCutData calldata _diamondCut) external { Diamond.diamondCut(_diamondCut); - s.baseTokenAssetId = IL1AssetRouter(s.baseTokenBridge).nativeTokenVault().getAssetId(s.baseToken); + s.baseTokenAssetId = DataEncoding.encodeNTVAssetId(block.chainid, s.baseToken); } function receiveEther() external payable {} diff --git a/l1-contracts/contracts/dev-contracts/test/DummySharedBridge.sol b/l1-contracts/contracts/dev-contracts/test/DummySharedBridge.sol index 6ea98fa15..2c3769ddc 100644 --- a/l1-contracts/contracts/dev-contracts/test/DummySharedBridge.sol +++ b/l1-contracts/contracts/dev-contracts/test/DummySharedBridge.sol @@ -70,6 +70,18 @@ contract DummySharedBridge { bytes32[] calldata // _merkleProof ) external {} + function claimFailedDeposit( + uint256, // _chainId, + address, // _depositSender, + address, // _l1Asset, + uint256, // _amount, + bytes32, // _l2TxHash, + uint256, // _l2BatchNumber, + uint256, // _l2MessageIndex, + uint16, // _l2TxNumberInBatch, + bytes32[] calldata //_merkleProof + ) external {} + function finalizeWithdrawalLegacyErc20Bridge( uint256, //_l2BatchNumber, uint256, //_l2MessageIndex, @@ -91,10 +103,10 @@ contract DummySharedBridge { uint256 _amount ) external payable { if (_l1Token == address(1)) { - require(msg.value == _amount, "L1AssetRouter: msg.value not equal to amount"); + require(msg.value == _amount, "L1AR: msg.value not equal to amount"); } else { // The Bridgehub also checks this, but we want to be sure - require(msg.value == 0, "ShB m.v > 0 b d.it"); + require(msg.value == 0, "L1AR: m.v > 0 b d.it"); uint256 amount = _depositFunds(_prevMsgSender, IERC20(_l1Token), _amount); // note if _prevMsgSender is this contract, this will return 0. This does not happen. require(amount == _amount, "5T"); // The token has non-standard transfer logic } @@ -139,13 +151,13 @@ contract DummySharedBridge { /// @dev Sets the L1ERC20Bridge contract address. Should be called only once. function setNativeTokenVault(IL1NativeTokenVault _nativeTokenVault) external { - require(address(nativeTokenVault) == address(0), "ShB: legacy bridge already set"); - require(address(_nativeTokenVault) != address(0), "ShB: legacy bridge 0"); + require(address(nativeTokenVault) == address(0), "L1AR: legacy bridge already set"); + require(address(_nativeTokenVault) != address(0), "L1AR: legacy bridge 0"); nativeTokenVault = _nativeTokenVault; } /// @dev Used to set the assedAddress for a given assetId. - function setAssetHandlerAddressInitial(bytes32 _additionalData, address _assetHandlerAddress) external { + function setAssetHandlerAddressThisChain(bytes32 _additionalData, address _assetHandlerAddress) external { address sender = msg.sender == address(nativeTokenVault) ? L2_NATIVE_TOKEN_VAULT_ADDRESS : msg.sender; bytes32 assetId = keccak256(abi.encode(uint256(block.chainid), sender, _additionalData)); assetHandlerAddress[assetId] = _assetHandlerAddress; diff --git a/l1-contracts/contracts/dev-contracts/test/L1ERC20BridgeTest.sol b/l1-contracts/contracts/dev-contracts/test/L1ERC20BridgeTest.sol index 350d5c8a9..ea65333c5 100644 --- a/l1-contracts/contracts/dev-contracts/test/L1ERC20BridgeTest.sol +++ b/l1-contracts/contracts/dev-contracts/test/L1ERC20BridgeTest.sol @@ -11,5 +11,5 @@ contract L1ERC20BridgeTest is L1ERC20Bridge { // add this to be excluded from coverage report function test() internal virtual {} - constructor(IBridgehub _zkSync) L1ERC20Bridge(IL1AssetRouter(address(0)), IL1NativeTokenVault(address(0))) {} + constructor(IBridgehub _zkSync) L1ERC20Bridge(IL1AssetRouter(address(0)), IL1NativeTokenVault(address(0)), 1) {} } diff --git a/l1-contracts/contracts/governance/Governance.sol b/l1-contracts/contracts/governance/Governance.sol index 3f40721e9..656cbeff4 100644 --- a/l1-contracts/contracts/governance/Governance.sol +++ b/l1-contracts/contracts/governance/Governance.sol @@ -13,7 +13,7 @@ import {IGovernance} from "./IGovernance.sol"; /// @notice This contract manages operations (calls with preconditions) for governance tasks. /// The contract allows for operations to be scheduled, executed, and canceled with /// appropriate permissions and delays. It is used for managing and coordinating upgrades -/// and changes in all zkSync hyperchain governed contracts. +/// and changes in all ZKsync hyperchain governed contracts. /// /// Operations can be proposed as either fully transparent upgrades with on-chain data, /// or "shadow" upgrades where upgrade data is not published on-chain before execution. Proposed operations diff --git a/l1-contracts/contracts/state-transition/IStateTransitionManager.sol b/l1-contracts/contracts/state-transition/IStateTransitionManager.sol index 33a531809..46771f1dc 100644 --- a/l1-contracts/contracts/state-transition/IStateTransitionManager.sol +++ b/l1-contracts/contracts/state-transition/IStateTransitionManager.sol @@ -156,7 +156,7 @@ interface IStateTransitionManager { function getSemverProtocolVersion() external view returns (uint32, uint32, uint32); - function registerSyncLayer(uint256 _newSyncLayerChainId, bool _isWhitelisted) external; + function registerSettlementLayer(uint256 _newSettlementLayerChainId, bool _isWhitelisted) external; event BridgeInitialize(address indexed l1Token, string name, string symbol, uint8 decimals); diff --git a/l1-contracts/contracts/state-transition/StateTransitionManager.sol b/l1-contracts/contracts/state-transition/StateTransitionManager.sol index f1cabbf22..f8baf7a98 100644 --- a/l1-contracts/contracts/state-transition/StateTransitionManager.sol +++ b/l1-contracts/contracts/state-transition/StateTransitionManager.sol @@ -102,7 +102,7 @@ contract StateTransitionManager is IStateTransitionManager, ReentrancyGuard, Own chainAddresses = new address[](keys.length); uint256 keysLength = keys.length; for (uint256 i = 0; i < keysLength; ++i) { - chainAddresses[i] = hyperchainMap.get(i); + chainAddresses[i] = hyperchainMap.get(keys[i]); } } @@ -112,12 +112,15 @@ contract StateTransitionManager is IStateTransitionManager, ReentrancyGuard, Own } /// @notice Returns the address of the hyperchain with the corresponding chainID + /// @param _chainId the chainId of the chain + /// @return chainAddress the address of the hyperchain function getHyperchain(uint256 _chainId) public view override returns (address chainAddress) { // slither-disable-next-line unused-return (, chainAddress) = hyperchainMap.tryGet(_chainId); } /// @notice Returns the address of the hyperchain admin with the corresponding chainID + /// @param _chainId the chainId of the chain function getChainAdmin(uint256 _chainId) external view override returns (address) { return IZkSyncHyperchain(hyperchainMap.get(_chainId)).getAdmin(); } @@ -209,6 +212,7 @@ contract StateTransitionManager is IStateTransitionManager, ReentrancyGuard, Own } /// @dev set validatorTimelock. Cannot do it during initialization, as validatorTimelock is deployed after STM + /// @param _validatorTimelock the new validatorTimelock address function setValidatorTimelock(address _validatorTimelock) external onlyOwnerOrAdmin { address oldValidatorTimelock = validatorTimelock; validatorTimelock = _validatorTimelock; @@ -216,6 +220,10 @@ contract StateTransitionManager is IStateTransitionManager, ReentrancyGuard, Own } /// @dev set New Version with upgrade from old version + /// @param _cutData the new diamond cut data + /// @param _oldProtocolVersion the old protocol version + /// @param _oldProtocolVersionDeadline the deadline for the old protocol version + /// @param _newProtocolVersion the new protocol version function setNewVersionUpgrade( Diamond.DiamondCutData calldata _cutData, uint256 _oldProtocolVersion, @@ -234,16 +242,21 @@ contract StateTransitionManager is IStateTransitionManager, ReentrancyGuard, Own } /// @dev check that the protocolVersion is active + /// @param _protocolVersion the protocol version to check function protocolVersionIsActive(uint256 _protocolVersion) external view override returns (bool) { return block.timestamp <= protocolVersionDeadline[_protocolVersion]; } /// @dev set the protocol version timestamp + /// @param _protocolVersion the protocol version + /// @param _timestamp the timestamp is the deadline function setProtocolVersionDeadline(uint256 _protocolVersion, uint256 _timestamp) external onlyOwner { protocolVersionDeadline[_protocolVersion] = _timestamp; } /// @dev set upgrade for some protocolVersion + /// @param _cutData the new diamond cut data + /// @param _oldProtocolVersion the old protocol version function setUpgradeDiamondCut( Diamond.DiamondCutData calldata _cutData, uint256 _oldProtocolVersion @@ -254,21 +267,28 @@ contract StateTransitionManager is IStateTransitionManager, ReentrancyGuard, Own } /// @dev freezes the specified chain + /// @param _chainId the chainId of the chain function freezeChain(uint256 _chainId) external onlyOwner { IZkSyncHyperchain(hyperchainMap.get(_chainId)).freezeDiamond(); } /// @dev freezes the specified chain + /// @param _chainId the chainId of the chain function unfreezeChain(uint256 _chainId) external onlyOwner { IZkSyncHyperchain(hyperchainMap.get(_chainId)).unfreezeDiamond(); } /// @dev reverts batches on the specified chain + /// @param _chainId the chainId of the chain + /// @param _newLastBatch the new last batch function revertBatches(uint256 _chainId, uint256 _newLastBatch) external onlyOwnerOrAdmin { IZkSyncHyperchain(hyperchainMap.get(_chainId)).revertBatches(_newLastBatch); } /// @dev execute predefined upgrade + /// @param _chainId the chainId of the chain + /// @param _oldProtocolVersion the old protocol version + /// @param _diamondCut the diamond cut data function upgradeChainFromVersion( uint256 _chainId, uint256 _oldProtocolVersion, @@ -278,37 +298,46 @@ contract StateTransitionManager is IStateTransitionManager, ReentrancyGuard, Own } /// @dev executes upgrade on chain + /// @param _chainId the chainId of the chain + /// @param _diamondCut the diamond cut data function executeUpgrade(uint256 _chainId, Diamond.DiamondCutData calldata _diamondCut) external onlyOwner { IZkSyncHyperchain(hyperchainMap.get(_chainId)).executeUpgrade(_diamondCut); } /// @dev setPriorityTxMaxGasLimit for the specified chain - function setPriorityTxMaxGasLimit(uint256 _chainId, uint256 _maxGasLimit) external { - // onlyOwner { + /// @param _chainId the chainId of the chain + /// @param _maxGasLimit the new max gas limit + function setPriorityTxMaxGasLimit(uint256 _chainId, uint256 _maxGasLimit) external onlyOwner { IZkSyncHyperchain(hyperchainMap.get(_chainId)).setPriorityTxMaxGasLimit(_maxGasLimit); } /// @dev setTokenMultiplier for the specified chain - function setTokenMultiplier(uint256 _chainId, uint128 _nominator, uint128 _denominator) external { - // onlyOwner { + /// @param _chainId the chainId of the chain + /// @param _nominator the new nominator of the token multiplier + /// @param _denominator the new denominator of the token multiplier + function setTokenMultiplier(uint256 _chainId, uint128 _nominator, uint128 _denominator) external onlyOwner { IZkSyncHyperchain(hyperchainMap.get(_chainId)).setTokenMultiplier(_nominator, _denominator); } /// @dev changeFeeParams for the specified chain - function changeFeeParams(uint256 _chainId, FeeParams calldata _newFeeParams) external { - // onlyOwner { + /// @param _chainId the chainId of the chain + /// @param _newFeeParams the new fee params + function changeFeeParams(uint256 _chainId, FeeParams calldata _newFeeParams) external onlyOwner { IZkSyncHyperchain(hyperchainMap.get(_chainId)).changeFeeParams(_newFeeParams); } /// @dev setValidator for the specified chain - function setValidator(uint256 _chainId, address _validator, bool _active) external { - // onlyOwnerOrAdmin { + /// @param _chainId the chainId of the chain + /// @param _validator the new validator + /// @param _active whether the validator is active + function setValidator(uint256 _chainId, address _validator, bool _active) external onlyOwnerOrAdmin { IZkSyncHyperchain(hyperchainMap.get(_chainId)).setValidator(_validator, _active); } /// @dev setPorterAvailability for the specified chain + /// @param _chainId the chainId of the chain + /// @param _zkPorterIsAvailable whether the zkPorter mode is available function setPorterAvailability(uint256 _chainId, bool _zkPorterIsAvailable) external onlyOwner { - // onlyOwner { IZkSyncHyperchain(hyperchainMap.get(_chainId)).setPorterAvailability(_zkPorterIsAvailable); } @@ -323,7 +352,7 @@ contract StateTransitionManager is IStateTransitionManager, ReentrancyGuard, Own _registerNewHyperchain(_chainId, _hyperchain); } - /// deploys a full set of chains contracts + /// @dev deploys a full set of chains contracts function _deployNewChain( uint256 _chainId, address _baseToken, @@ -349,20 +378,20 @@ contract StateTransitionManager is IStateTransitionManager, ReentrancyGuard, Own // solhint-disable-next-line func-named-parameters mandatoryInitData = bytes.concat( bytes32(_chainId), - bytes32(uint256(uint160(address(BRIDGE_HUB)))), + bytes32(uint256(uint160(BRIDGE_HUB))), bytes32(uint256(uint160(address(this)))), - bytes32(uint256(protocolVersion)), + bytes32(protocolVersion), bytes32(uint256(uint160(_admin))), bytes32(uint256(uint160(validatorTimelock))), bytes32(uint256(uint160(_baseToken))), bytes32(uint256(uint160(_sharedBridge))), - bytes32(storedBatchZero) + storedBatchZero ); } // construct init data bytes memory initData; - /// all together 4+9*32=292 bytes + /// all together 4+9*32=292 bytes for the selector + mandatory data // solhint-disable-next-line func-named-parameters initData = bytes.concat(IDiamondInit.initialize.selector, mandatoryInitData, diamondCut.initCalldata); @@ -382,6 +411,7 @@ contract StateTransitionManager is IStateTransitionManager, ReentrancyGuard, Own /// @param _sharedBridge 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 /// that initializes the chains Diamond Proxy function createNewChain( uint256 _chainId, @@ -392,7 +422,7 @@ contract StateTransitionManager is IStateTransitionManager, ReentrancyGuard, Own bytes[] calldata _factoryDeps ) external onlyBridgehub { (bytes memory _diamondCut, bytes memory _forceDeploymentData) = abi.decode(_initData, (bytes, bytes)); - // TODO: only allow on L1. + // solhint-disable-next-line func-named-parameters address hyperchainAddress = _deployNewChain(_chainId, _baseToken, _sharedBridge, _admin, _diamondCut); @@ -405,22 +435,20 @@ contract StateTransitionManager is IStateTransitionManager, ReentrancyGuard, Own IAdmin(hyperchainAddress).genesisUpgrade(l1GenesisUpgrade, _forceDeploymentData, _factoryDeps); } + /// @param _chainId the chainId of the chain function getProtocolVersion(uint256 _chainId) public view returns (uint256) { return IZkSyncHyperchain(hyperchainMap.get(_chainId)).getProtocolVersion(); } - function registerSyncLayer(uint256 _newSyncLayerChainId, bool _isWhitelisted) external onlyOwner { - require(_newSyncLayerChainId != 0, "Bad chain id"); + /// @param _newSettlementLayerChainId the chainId of the chain + /// @param _isWhitelisted whether the chain is whitelisted + function registerSettlementLayer(uint256 _newSettlementLayerChainId, bool _isWhitelisted) external onlyOwner { + require(_newSettlementLayerChainId != 0, "Bad chain id"); // Currently, we require that the sync layer is deployed by the same STM. - address syncLayerAddress = hyperchainMap.get(_newSyncLayerChainId); - - // TODO: Maybe `get` already ensured its existence. - require(syncLayerAddress != address(0), "STM: sync layer not registered"); - - IBridgehub(BRIDGE_HUB).registerSyncLayer(_newSyncLayerChainId, _isWhitelisted); + require(hyperchainMap.contains(_newSettlementLayerChainId), "STM: sync layer not registered"); - // TODO: emit event + IBridgehub(BRIDGE_HUB).registerSettlementLayer(_newSettlementLayerChainId, _isWhitelisted); } /// @notice Called by the bridgehub during the migration of a chain to another settlement layer. @@ -430,10 +458,17 @@ contract StateTransitionManager is IStateTransitionManager, ReentrancyGuard, Own uint256 _chainId, bytes calldata _data ) external view override onlyBridgehub returns (bytes memory stmForwardedBridgeMintData) { - (address _newSyncLayerAdmin, bytes memory _diamondCut) = abi.decode(_data, (address, bytes)); - require(_newSyncLayerAdmin != address(0), "STM: admin zero"); - // todo check protocol version - return abi.encode(IBridgehub(BRIDGE_HUB).baseToken(_chainId), _newSyncLayerAdmin, protocolVersion, _diamondCut); + // Note that the `_diamondCut` here is not for the current chain, for the chain where the migration + // happens. The correctness of it will be checked on the STM on the new settlement layer. + (address _newGatewayAdmin, bytes memory _diamondCut) = abi.decode(_data, (address, bytes)); + require(_newGatewayAdmin != address(0), "STM: admin zero"); + + // We ensure that the chain has the latest protocol version to avoid edge cases + // related to different protocol version support. + address hyperchain = hyperchainMap.get(_chainId); + require(IZkSyncHyperchain(hyperchain).getProtocolVersion() == protocolVersion, "STM: outdated pv"); + + return abi.encode(IBridgehub(BRIDGE_HUB).baseToken(_chainId), _newGatewayAdmin, protocolVersion, _diamondCut); } /// @notice Called by the bridgehub during the migration of a chain to the current settlement layer. @@ -447,8 +482,10 @@ contract StateTransitionManager is IStateTransitionManager, ReentrancyGuard, Own _stmData, (address, address, uint256, bytes) ); + + // We ensure that the chain has the latest protocol version to avoid edge cases + // related to different protocol version support. require(_protocolVersion == protocolVersion, "STM, outdated pv"); - // todo porotocl version check chainAddress = _deployNewChain({ _chainId: _chainId, _baseToken: _baseToken, @@ -458,6 +495,11 @@ contract StateTransitionManager is IStateTransitionManager, ReentrancyGuard, Own }); } + /// @notice Called by the bridgehub during the failed migration of a chain. + /// @param _chainId the chainId of the chain + /// @param _assetInfo the assetInfo of the chain + /// @param _prevMsgSender the previous message sender + /// @param _data the data of the migration function bridgeClaimFailedBurn( uint256 _chainId, bytes32 _assetInfo, diff --git a/l1-contracts/contracts/state-transition/ValidatorTimelock.sol b/l1-contracts/contracts/state-transition/ValidatorTimelock.sol index acba1f53a..a813e5d02 100644 --- a/l1-contracts/contracts/state-transition/ValidatorTimelock.sol +++ b/l1-contracts/contracts/state-transition/ValidatorTimelock.sol @@ -16,7 +16,7 @@ import {PriorityOpsBatchInfo} from "./libraries/PriorityTree.sol"; /// @dev The primary purpose of this contract is to provide a trustless means of delaying batch execution without /// modifying the main hyperchain diamond contract. As such, even if this contract is compromised, it will not impact the main /// contract. -/// @dev zkSync actively monitors the chain activity and reacts to any suspicious activity by freezing the chain. +/// @dev ZKsync actively monitors the chain activity and reacts to any suspicious activity by freezing the chain. /// This allows time for investigation and mitigation before resuming normal operations. /// @dev The contract overloads all of the 4 methods, that are used in state transition. When the batch is committed, /// the timestamp is stored for it. Later, when the owner calls the batch execution, the contract checks that batch @@ -207,7 +207,7 @@ contract ValidatorTimelock is IExecutor, Ownable2Step { // Note: if the `commitBatchTimestamp` is zero, that means either: // * The batch was committed, but not through this contract. - // * The batch wasn't committed at all, so execution will fail in the zkSync contract. + // * The batch wasn't committed at all, so execution will fail in the ZKsync contract. // We allow executing such batches. require(block.timestamp >= commitBatchTimestamp + delay, "5c"); // The delay is not passed diff --git a/l1-contracts/contracts/state-transition/Verifier.sol b/l1-contracts/contracts/state-transition/Verifier.sol index cfc1f848b..a74ecb12c 100644 --- a/l1-contracts/contracts/state-transition/Verifier.sol +++ b/l1-contracts/contracts/state-transition/Verifier.sol @@ -8,14 +8,14 @@ import {IVerifier} from "./chain-interfaces/IVerifier.sol"; /// @author Matter Labs /// @notice Modified version of the Permutations over Lagrange-bases for Oecumenical Noninteractive arguments of /// Knowledge (PLONK) verifier. -/// Modifications have been made to optimize the proof system for zkSync hyperchain circuits. +/// Modifications have been made to optimize the proof system for ZKsync hyperchain circuits. /// @dev Contract was generated from a verification key with a hash of 0x14f97b81e54b35fe673d8708cc1a19e1ea5b5e348e12d31e39824ed4f42bbca2 /// @dev It uses a custom memory layout inside the inline assembly block. Each reserved memory cell is declared in the /// constants below. /// @dev For a better understanding of the verifier algorithm please refer to the following papers: /// * Original Plonk Article: https://eprint.iacr.org/2019/953.pdf /// * Original LookUp Article: https://eprint.iacr.org/2020/315.pdf -/// * Plonk for zkSync v1.1: https://github.com/matter-labs/solidity_plonk_verifier/raw/recursive/bellman_vk_codegen_recursive/RecursivePlonkUnrolledForEthereum.pdf +/// * Plonk for ZKsync v1.1: https://github.com/matter-labs/solidity_plonk_verifier/raw/recursive/bellman_vk_codegen_recursive/RecursivePlonkUnrolledForEthereum.pdf /// The notation used in the code is the same as in the papers. /* solhint-enable max-line-length */ contract Verifier is IVerifier { diff --git a/l1-contracts/contracts/state-transition/chain-deps/ZkSyncHyperchainStorage.sol b/l1-contracts/contracts/state-transition/chain-deps/ZkSyncHyperchainStorage.sol index 45540177e..c4abf9007 100644 --- a/l1-contracts/contracts/state-transition/chain-deps/ZkSyncHyperchainStorage.sol +++ b/l1-contracts/contracts/state-transition/chain-deps/ZkSyncHyperchainStorage.sol @@ -161,8 +161,9 @@ struct ZkSyncHyperchainStorage { address l2DAValidator; /// @dev the Asset Id of the baseToken bytes32 baseTokenAssetId; - /// @dev address of the synclayer, only set on L1 if settling on it - address syncLayer; + /// @dev If this ZKchain settles on this chain, then this is zero. Otherwise it is the address of the ZKchain that is a + /// settlement layer for this ZKchain. (think about it as a 'forwarding' address for the chain that migrated away). + address settlementLayer; /// @dev Priority tree, the new data structure for priority queue PriorityTree.Tree priorityTree; } diff --git a/l1-contracts/contracts/state-transition/chain-deps/facets/Admin.sol b/l1-contracts/contracts/state-transition/chain-deps/facets/Admin.sol index 8fc1eeb56..e68bf5623 100644 --- a/l1-contracts/contracts/state-transition/chain-deps/facets/Admin.sol +++ b/l1-contracts/contracts/state-transition/chain-deps/facets/Admin.sol @@ -105,6 +105,7 @@ contract AdminFacet is ZkSyncHyperchainBase, IAdmin { emit ValidiumModeStatusUpdate(_pricingMode); } + /// @inheritdoc IAdmin function setTransactionFilterer(address _transactionFilterer) external onlyAdmin { address oldTransactionFilterer = s.transactionFilterer; s.transactionFilterer = _transactionFilterer; @@ -166,16 +167,13 @@ contract AdminFacet is ZkSyncHyperchainBase, IAdmin { bytes calldata _forceDeploymentData, bytes[] calldata _factoryDeps ) external onlyStateTransitionManager { - uint256 cachedProtocolVersion = s.protocolVersion; - uint256 chainId = s.chainId; - Diamond.FacetCut[] memory emptyArray; Diamond.DiamondCutData memory cutData = Diamond.DiamondCutData({ facetCuts: emptyArray, initAddress: _l1GenesisUpgrade, initCalldata: abi.encodeCall( IL1GenesisUpgrade.genesisUpgrade, - (_l1GenesisUpgrade, chainId, cachedProtocolVersion, _forceDeploymentData, _factoryDeps) + (_l1GenesisUpgrade, s.chainId, s.protocolVersion, _forceDeploymentData, _factoryDeps) ) }); @@ -210,14 +208,14 @@ contract AdminFacet is ZkSyncHyperchainBase, IAdmin { CHAIN MIGRATION //////////////////////////////////////////////////////////////*/ - /// @dev we can move assets using these + /// @inheritdoc IAdmin function forwardedBridgeBurn( - address _syncLayer, + address _settlementLayer, address _prevMsgSender, bytes calldata ) external payable override onlyBridgehub returns (bytes memory chainBridgeMintData) { - // (address _newSyncLayerAdmin, bytes memory _diamondCut) = abi.decode(_data, (address, bytes)); - require(s.syncLayer == address(0), "Af: already migrated"); + // (address _newSettlementLayerAdmin, bytes memory _diamondCut) = abi.decode(_data, (address, bytes)); + require(s.settlementLayer == address(0), "Af: already migrated"); require(_prevMsgSender == s.admin, "Af: not chainAdmin"); IStateTransitionManager stm = IStateTransitionManager(s.stateTransitionManager); @@ -227,11 +225,12 @@ contract AdminFacet is ZkSyncHyperchainBase, IAdmin { require(currentProtocolVersion == protocolVersion, "STM: protocolVersion not up to date"); - s.syncLayer = _syncLayer; - chainBridgeMintData = abi.encode(_prepareChainCommitment()); + s.settlementLayer = _settlementLayer; + chainBridgeMintData = abi.encode(prepareChainCommitment()); } - function forwardedBridgeMint(bytes calldata _data) external payable override { + /// @inheritdoc IAdmin + function forwardedBridgeMint(bytes calldata _data) external payable override onlyBridgehub { HyperchainCommitment memory _commitment = abi.decode(_data, (HyperchainCommitment)); uint256 batchesExecuted = _commitment.totalBatchesExecuted; @@ -266,20 +265,26 @@ contract AdminFacet is ZkSyncHyperchainBase, IAdmin { s.l2SystemContractsUpgradeTxHash = _commitment.l2SystemContractsUpgradeTxHash; s.l2SystemContractsUpgradeBatchNumber = _commitment.l2SystemContractsUpgradeBatchNumber; + // Set the settlement to 0 - as this is the current settlement chain. + s.settlementLayer = address(0); + _setDAValidatorPair(address(0), address(0)); emit MigrationComplete(); } + /// @inheritdoc IAdmin function forwardedBridgeClaimFailedBurn( uint256 _chainId, bytes32 _assetInfo, address _prevMsgSender, bytes calldata _data - ) external payable override {} + ) external payable override onlyBridgehub {} - // todo make internal. For now useful for testing - function _prepareChainCommitment() public view returns (HyperchainCommitment memory commitment) { + /// @notice Returns the commitment for a chain. + /// @dev Note, that this is a getter method helpful for debugging and should not be relied upon by clients. + /// @return commitment The commitment for the chain. + function prepareChainCommitment() public view returns (HyperchainCommitment memory commitment) { require(s.priorityQueue.getFirstUnprocessedPriorityTx() >= s.priorityTree.startIndex, "PQ not ready"); commitment.totalBatchesCommitted = s.totalBatchesCommitted; @@ -312,25 +317,21 @@ contract AdminFacet is ZkSyncHyperchainBase, IAdmin { commitment.batchHashes = batchHashes; } - function readChainCommitment() external view returns (bytes memory commitment) { - return abi.encode(_prepareChainCommitment()); - } - - // function recoverFromFailedMigrationToSyncLayer( - // uint256 _syncLayerChainId, + // function recoverFromFailedMigrationToGateway( + // uint256 _settlementLayerChainId, // uint256 _l2BatchNumber, // uint256 _l2MessageIndex, // uint16 _l2TxNumberInBatch, // bytes32[] calldata _merkleProof // ) external onlyAdmin { - // require(s.syncLayerState == SyncLayerState.MigratedFromL1, "not migrated L1"); + // require(s.settlementLayerState == SettlementLayerState.MigratedFromL1, "not migrated L1"); - // bytes32 migrationHash = s.syncLayerMigrationHash; + // bytes32 migrationHash = s.settlementLayerMigrationHash; // require(migrationHash != bytes32(0), "can not recover when there is no migration"); // require( // IBridgehub(s.bridgehub).proveL1ToL2TransactionStatus( - // _syncLayerChainId, + // _settlementLayerChainId, // migrationHash, // _l2BatchNumber, // _l2MessageIndex, @@ -341,9 +342,9 @@ contract AdminFacet is ZkSyncHyperchainBase, IAdmin { // "Migration not failed" // ); - // s.syncLayerState = SyncLayerState.ActiveOnL1; - // s.syncLayerChainId = 0; - // s.syncLayerMigrationHash = bytes32(0); + // s.settlementLayerState = SettlementLayerState.ActiveOnL1; + // s.settlementLayerChainId = 0; + // s.settlementLayerMigrationHash = bytes32(0); // // We do not need to perform any additional actions, since no changes related to the chain commitment can be performed // // while the chain is in the "migrated" state. diff --git a/l1-contracts/contracts/state-transition/chain-deps/facets/Executor.sol b/l1-contracts/contracts/state-transition/chain-deps/facets/Executor.sol index c92e0788e..57444cbac 100644 --- a/l1-contracts/contracts/state-transition/chain-deps/facets/Executor.sol +++ b/l1-contracts/contracts/state-transition/chain-deps/facets/Executor.sol @@ -20,7 +20,7 @@ import {IL1DAValidator, L1DAValidatorOutput} from "../../chain-interfaces/IL1DAV // While formally the following import is not used, it is needed to inherit documentation from it import {IZkSyncHyperchainBase} from "../../chain-interfaces/IZkSyncHyperchainBase.sol"; -/// @title zkSync hyperchain Executor contract capable of processing events emitted in the zkSync hyperchain protocol. +/// @title ZKsync hyperchain Executor contract capable of processing events emitted in the ZKsync hyperchain protocol. /// @author Matter Labs /// @custom:security-contact security@matterlabs.dev contract ExecutorFacet is ZkSyncHyperchainBase, IExecutor { @@ -31,6 +31,12 @@ contract ExecutorFacet is ZkSyncHyperchainBase, IExecutor { /// @inheritdoc IZkSyncHyperchainBase string public constant override getName = "ExecutorFacet"; + /// @dev Checks that the chain is connected to the current bridehub and not migrated away. + modifier chainOnCurrentBridgehub() { + require(s.settlementLayer == address(0), "Chain was migrated"); + _; + } + /// @dev Process one batch commit using the previous batch StoredBatchInfo /// @dev returns new batch StoredBatchInfo /// @notice Does not change storage @@ -39,18 +45,19 @@ contract ExecutorFacet is ZkSyncHyperchainBase, IExecutor { CommitBatchInfo calldata _newBatch, bytes32 _expectedSystemContractUpgradeTxHash ) internal returns (StoredBatchInfo memory) { - require(_newBatch.batchNumber == _previousBatch.batchNumber + 1, "f"); // only commit next batchs + require(_newBatch.batchNumber == _previousBatch.batchNumber + 1, "f"); // only commit next batch - // Check that batch contain all meta information for L2 logs. + // Check that batch contains all meta information for L2 logs. // Get the chained hash of priority transaction hashes. LogProcessingOutput memory logOutput = _processL2Logs(_newBatch, _expectedSystemContractUpgradeTxHash); - L1DAValidatorOutput memory daOutput = IL1DAValidator(s.l1DAValidator).checkDA( - s.chainId, - logOutput.l2DAValidatorOutputHash, - _newBatch.operatorDAInput, - TOTAL_BLOBS_IN_COMMITMENT - ); + L1DAValidatorOutput memory daOutput = IL1DAValidator(s.l1DAValidator).checkDA({ + _chainId: s.chainId, + _batchNumber: uint256(_newBatch.batchNumber), + _l2DAValidatorOutputHash: logOutput.l2DAValidatorOutputHash, + _operatorDAInput: _newBatch.operatorDAInput, + _maxBlobsSupported: TOTAL_BLOBS_IN_COMMITMENT + }); require(_previousBatch.batchHash == logOutput.previousBatchHash, "l"); // Check that the priority operation hash in the L2 logs is as expected @@ -169,13 +176,13 @@ contract ExecutorFacet is ZkSyncHyperchainBase, IExecutor { } } - // FIXME: temporarily old logs were kept for backwards compaitibility. This check can not work now. + // FIXME: temporarily old logs were kept for backwards compatibility. This check cannot work now. // - // We only require 13 logs to be checked, the 14th is if we are expecting a protocol upgrade - // Without the protocol upgrade we expect 13 logs: 2^13 - 1 = 8191 - // With the protocol upgrade we expect 14 logs: 2^14 - 1 = 16383 + // We only require 8 logs to be checked, the 9th is if we are expecting a protocol upgrade + // Without the protocol upgrade we expect 8 logs: 2^8 - 1 = 255 + // With the protocol upgrade we expect 9 logs: 2^9 - 1 = 511 if (_expectedSystemContractUpgradeTxHash == bytes32(0)) { - // require(processedLogs == 127, "b7"); + // require(processedLogs == 255, "b7"); } else { // FIXME: do restore this code to the one that was before require(_checkBit(processedLogs, uint8(SystemLogKey.EXPECTED_SYSTEM_CONTRACT_UPGRADE_TX_HASH_KEY)), "b8"); @@ -202,7 +209,7 @@ contract ExecutorFacet is ZkSyncHyperchainBase, IExecutor { function _commitBatches( StoredBatchInfo memory _lastCommittedBatchData, CommitBatchInfo[] calldata _newBatchesData - ) internal { + ) internal chainOnCurrentBridgehub { // check that we have the right protocol version // three comments: // 1. A chain has to keep their protocol version up to date, as processing a block requires the latest or previous protocol version @@ -336,11 +343,6 @@ contract ExecutorFacet is ZkSyncHyperchainBase, IExecutor { bytes32 priorityOperationsHash = _collectOperationsFromPriorityQueue(_storedBatch.numberOfLayer1Txs); _checkBatchData(_storedBatch, _executedBatchIdx, priorityOperationsHash); - uint256 firstUnprocessed = s.priorityQueue.getFirstUnprocessedPriorityTx(); - uint256 treeStartIndex = s.priorityTree.startIndex; - if (firstUnprocessed > treeStartIndex) { - s.priorityTree.unprocessedIndex = firstUnprocessed - treeStartIndex; - } uint256 currentBatchNumber = _storedBatch.batchNumber; // Save root hash of L2 -> L1 logs tree @@ -378,6 +380,7 @@ contract ExecutorFacet is ZkSyncHyperchainBase, IExecutor { messageRootContract.addChainBatchRoot(s.chainId, currentBatchNumber, _storedBatch.l2LogsTreeRoot); } + /// @inheritdoc IExecutor function executeBatchesSharedBridge( uint256, StoredBatchInfo[] calldata _batchesData, @@ -386,6 +389,7 @@ contract ExecutorFacet is ZkSyncHyperchainBase, IExecutor { _executeBatches(_batchesData, _priorityOpsData); } + /// @inheritdoc IExecutor function executeBatches( StoredBatchInfo[] calldata _batchesData, PriorityOpsBatchInfo[] calldata _priorityOpsData @@ -396,7 +400,7 @@ contract ExecutorFacet is ZkSyncHyperchainBase, IExecutor { function _executeBatches( StoredBatchInfo[] calldata _batchesData, PriorityOpsBatchInfo[] calldata _priorityOpsData - ) internal { + ) internal chainOnCurrentBridgehub { uint256 nBatches = _batchesData.length; require(_batchesData.length == _priorityOpsData.length, "bp"); @@ -446,7 +450,7 @@ contract ExecutorFacet is ZkSyncHyperchainBase, IExecutor { StoredBatchInfo calldata _prevBatch, StoredBatchInfo[] calldata _committedBatches, ProofInput calldata _proof - ) internal { + ) internal chainOnCurrentBridgehub { // Save the variables into the stack to save gas on reading them later uint256 currentTotalBatchesVerified = s.totalBatchesVerified; uint256 committedBatchesLength = _committedBatches.length; @@ -509,7 +513,7 @@ contract ExecutorFacet is ZkSyncHyperchainBase, IExecutor { _revertBatches(_newLastBatch); } - function _revertBatches(uint256 _newLastBatch) internal { + function _revertBatches(uint256 _newLastBatch) internal chainOnCurrentBridgehub { require(s.totalBatchesCommitted > _newLastBatch, "v1"); // The last committed batch is less than new last batch require(_newLastBatch >= s.totalBatchesExecuted, "v2"); // Already executed batches cannot be reverted 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 23144c0a5..04c34d103 100644 --- a/l1-contracts/contracts/state-transition/chain-deps/facets/Getters.sol +++ b/l1-contracts/contracts/state-transition/chain-deps/facets/Getters.sol @@ -9,7 +9,7 @@ import {ZkSyncHyperchainBase} from "./ZkSyncHyperchainBase.sol"; import {PubdataPricingMode} from "../ZkSyncHyperchainStorage.sol"; import {VerifierParams} from "../../../state-transition/chain-interfaces/IVerifier.sol"; import {Diamond} from "../../libraries/Diamond.sol"; -import {PriorityQueue, PriorityOperation} from "../../../state-transition/libraries/PriorityQueue.sol"; +import {PriorityQueue} from "../../../state-transition/libraries/PriorityQueue.sol"; import {PriorityTree} from "../../../state-transition/libraries/PriorityTree.sol"; import {UncheckedMath} from "../../../common/libraries/UncheckedMath.sol"; import {IGetters} from "../../chain-interfaces/IGetters.sol"; @@ -131,9 +131,6 @@ contract GettersFacet is ZkSyncHyperchainBase, IGetters, ILegacyGetters { } } - /// @inheritdoc IGetters - function priorityQueueFrontOperation() external view returns (PriorityOperation memory op) {} - /// @inheritdoc IGetters function isValidator(address _address) external view returns (bool) { return s.validators[_address]; @@ -227,9 +224,9 @@ contract GettersFacet is ZkSyncHyperchainBase, IGetters, ILegacyGetters { } /// @inheritdoc IGetters - function getSyncLayer() external view returns (address) { + function getSettlementLayer() external view returns (address) { // TODO: consider making private so that no one relies on it - return s.syncLayer; + return s.settlementLayer; } function getDAValidatorPair() external view returns (address, address) { 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 824e6ec3b..32642fcba 100644 --- a/l1-contracts/contracts/state-transition/chain-deps/facets/Mailbox.sol +++ b/l1-contracts/contracts/state-transition/chain-deps/facets/Mailbox.sol @@ -16,24 +16,21 @@ import {PriorityQueue, PriorityOperation} from "../../libraries/PriorityQueue.so import {PriorityTree} from "../../libraries/PriorityTree.sol"; import {TransactionValidator} from "../../libraries/TransactionValidator.sol"; import {WritePriorityOpParams, L2CanonicalTransaction, L2Message, L2Log, TxStatus, BridgehubL2TransactionRequest} from "../../../common/Messaging.sol"; -import {Messaging} from "../../../common/libraries/Messaging.sol"; +import {MessageHashing} from "../../../common/libraries/MessageHashing.sol"; import {FeeParams, PubdataPricingMode} from "../ZkSyncHyperchainStorage.sol"; import {UncheckedMath} from "../../../common/libraries/UncheckedMath.sol"; import {L2ContractHelper} from "../../../common/libraries/L2ContractHelper.sol"; import {AddressAliasHelper} from "../../../vendor/AddressAliasHelper.sol"; import {ZkSyncHyperchainBase} from "./ZkSyncHyperchainBase.sol"; -import {REQUIRED_L2_GAS_PRICE_PER_PUBDATA, L1_GAS_PER_PUBDATA_BYTE, L2_L1_LOGS_TREE_DEFAULT_LEAF_HASH, PRIORITY_OPERATION_L2_TX_TYPE, PRIORITY_EXPIRATION, MAX_NEW_FACTORY_DEPS} from "../../../common/Config.sol"; +import {REQUIRED_L2_GAS_PRICE_PER_PUBDATA, L1_GAS_PER_PUBDATA_BYTE, L2_L1_LOGS_TREE_DEFAULT_LEAF_HASH, PRIORITY_OPERATION_L2_TX_TYPE, PRIORITY_EXPIRATION, MAX_NEW_FACTORY_DEPS, SETTLEMENT_LAYER_RELAY_SENDER} from "../../../common/Config.sol"; import {L2_BOOTLOADER_ADDRESS, L2_TO_L1_MESSENGER_SYSTEM_CONTRACT_ADDR, L2_BRIDGEHUB_ADDR} from "../../../common/L2ContractAddresses.sol"; import {IL1AssetRouter} from "../../../bridge/interfaces/IL1AssetRouter.sol"; -import {IBridgehub} from "../../../bridgehub/IBridgehub.sol"; - -import {IStateTransitionManager} from "../../IStateTransitionManager.sol"; // While formally the following import is not used, it is needed to inherit documentation from it import {IZkSyncHyperchainBase} from "../../chain-interfaces/IZkSyncHyperchainBase.sol"; -/// @title zkSync Mailbox contract providing interfaces for L1 <-> L2 interaction. +/// @title ZKsync Mailbox contract providing interfaces for L1 <-> L2 interaction. /// @author Matter Labs /// @custom:security-contact security@matterlabs.dev contract MailboxFacet is ZkSyncHyperchainBase, IMailbox { @@ -51,7 +48,7 @@ contract MailboxFacet is ZkSyncHyperchainBase, IMailbox { ERA_CHAIN_ID = _eraChainId; } - /// @notice when requesting transactions through the bridgehub + /// @inheritdoc IMailbox function bridgehubRequestL2Transaction( BridgehubL2TransactionRequest calldata _request ) external onlyBridgehub returns (bytes32 canonicalTxHash) { @@ -109,7 +106,7 @@ contract MailboxFacet is ZkSyncHyperchainBase, IMailbox { } // /// @inheritdoc IMailbox - function proveL1ToL2TransactionStatusViaSyncLayer( + function proveL1ToL2TransactionStatusViaGateway( bytes32 _l2TxHash, uint256 _l2BatchNumber, uint256 _l2MessageIndex, @@ -120,7 +117,7 @@ contract MailboxFacet is ZkSyncHyperchainBase, IMailbox { function _parseProofMetadata( bytes32 _proofMetadata - ) internal view returns (uint256 logLeafProofLen, uint256 batchLeafProofLen) { + ) internal pure returns (uint256 logLeafProofLen, uint256 batchLeafProofLen) { bytes1 metadataVersion = bytes1(_proofMetadata[0]); require(metadataVersion == 0x01, "Mailbox: unsupported proof metadata version"); @@ -139,16 +136,16 @@ contract MailboxFacet is ZkSyncHyperchainBase, IMailbox { } } + /// @inheritdoc IMailbox function proveL2LeafInclusion( uint256 _batchNumber, uint256 _leafProofMask, bytes32 _leaf, bytes32[] calldata _proof - ) external view returns (bool) { + ) external view override returns (bool) { return _proveL2LeafInclusion(_batchNumber, _leafProofMask, _leaf, _proof); } - /// Proves that a certain leaf was included as part of the log merkle tree. function _proveL2LeafInclusion( uint256 _batchNumber, uint256 _leafProofMask, @@ -172,16 +169,16 @@ contract MailboxFacet is ZkSyncHyperchainBase, IMailbox { // Note that this logic works only for chains that do not migrate away from the synclayer back to L1. // Support for chains that migrate back to L1 will be added in the future. - if (s.syncLayer == address(0)) { + if (s.settlementLayer == address(0)) { bytes32 correctBatchRoot = s.l2LogsRootHashes[_batchNumber]; require(correctBatchRoot != bytes32(0), "local root is 0"); return correctBatchRoot == batchSettlementRoot; - } else { - require(s.l2LogsRootHashes[_batchNumber] == bytes32(0), "local root must be 0"); } - // Now, we'll have to check that the SyncLayer included the message. - bytes32 batchLeafHash = Messaging.batchLeafHash(batchSettlementRoot, _batchNumber); + require(s.l2LogsRootHashes[_batchNumber] == bytes32(0), "local root must be 0"); + + // Now, we'll have to check that the Gateway included the message. + bytes32 batchLeafHash = MessageHashing.batchLeafHash(batchSettlementRoot, _batchNumber); uint256 batchLeafProofMask = uint256(bytes32(_proof[ptr])); ++ptr; @@ -193,25 +190,25 @@ contract MailboxFacet is ZkSyncHyperchainBase, IMailbox { ); ptr += batchLeafProofLen; - chainIdLeaf = Messaging.chainIdLeafHash(chainIdRoot, s.chainId); + chainIdLeaf = MessageHashing.chainIdLeafHash(chainIdRoot, s.chainId); } - uint256 syncLayerBatchNumber; - uint256 syncLayerBatchRootMask; + uint256 settlementLayerBatchNumber; + uint256 settlementLayerBatchRootMask; // Preventing stack too deep error { // Now, we just need to double check whether this chainId leaf was present in the tree. - uint256 syncLayerPackedBatchInfo = uint256(_proof[ptr]); + uint256 settlementLayerPackedBatchInfo = uint256(_proof[ptr]); ++ptr; - syncLayerBatchNumber = uint256(syncLayerPackedBatchInfo >> 128); - syncLayerBatchRootMask = uint256(syncLayerPackedBatchInfo & ((1 << 128) - 1)); + settlementLayerBatchNumber = uint256(settlementLayerPackedBatchInfo >> 128); + settlementLayerBatchRootMask = uint256(settlementLayerPackedBatchInfo & ((1 << 128) - 1)); } return - IMailbox(s.syncLayer).proveL2LeafInclusion( - syncLayerBatchNumber, - syncLayerBatchRootMask, + IMailbox(s.settlementLayer).proveL2LeafInclusion( + settlementLayerBatchNumber, + settlementLayerBatchRootMask, chainIdLeaf, extractSlice(_proof, ptr, _proof.length) ); @@ -293,14 +290,14 @@ contract MailboxFacet is ZkSyncHyperchainBase, IMailbox { return Math.max(l2GasPrice, minL2GasPriceBaseToken); } - /// @dev On L1 we have to forward to SyncLayer mailbox - function requestL2TransactionToSyncLayerMailbox( + /// @inheritdoc IMailbox + function requestL2TransactionToGatewayMailbox( uint256 _chainId, L2CanonicalTransaction calldata _transaction, bytes[] calldata _factoryDeps, bytes32 _canonicalTxHash, uint64 _expirationTimestamp - ) external returns (bytes32 canonicalTxHash) { + ) external override returns (bytes32 canonicalTxHash) { require(IBridgehub(s.bridgehub).whitelistedSettlementLayers(s.chainId), "Mailbox SL: not SL"); require( IStateTransitionManager(s.stateTransitionManager).getHyperchain(_chainId) == msg.sender, @@ -314,16 +311,16 @@ contract MailboxFacet is ZkSyncHyperchainBase, IMailbox { _canonicalTxHash: _canonicalTxHash, _expirationTimestamp: _expirationTimestamp }); - canonicalTxHash = _requestL2TransactionToSyncLayerFree(wrappedRequest); + canonicalTxHash = _requestL2TransactionToGatewayFree(wrappedRequest); } - /// @dev On SL the chain's mailbox - function bridgehubRequestL2TransactionOnSyncLayer( + /// @inheritdoc IMailbox + function bridgehubRequestL2TransactionOnGateway( L2CanonicalTransaction calldata _transaction, bytes[] calldata _factoryDeps, bytes32 _canonicalTxHash, uint64 _expirationTimestamp - ) external { + ) external override onlyBridgehub { _writePriorityOp(_transaction, _factoryDeps, _canonicalTxHash, _expirationTimestamp); } @@ -332,20 +329,17 @@ contract MailboxFacet is ZkSyncHyperchainBase, IMailbox { L2CanonicalTransaction calldata _transaction, bytes[] calldata _factoryDeps, bytes32 _canonicalTxHash, - uint256 _expirationTimestamp + uint64 _expirationTimestamp ) internal view returns (BridgehubL2TransactionRequest memory) { // solhint-disable-next-line func-named-parameters - bytes memory data = abi.encodeWithSelector( - IBridgehub(s.bridgehub).forwardTransactionOnSyncLayer.selector, - _chainId, - _transaction, - _factoryDeps, - _canonicalTxHash, - _expirationTimestamp + bytes memory data = abi.encodeCall( + IBridgehub(s.bridgehub).forwardTransactionOnGateway, + (_chainId, _transaction, _factoryDeps, _canonicalTxHash, _expirationTimestamp) ); return BridgehubL2TransactionRequest({ - sender: s.bridgehub, + /// There is no sender for the wrapping, we use a virtual address. + sender: SETTLEMENT_LAYER_RELAY_SENDER, contractL2: L2_BRIDGEHUB_ADDR, mintValue: 0, l2Value: 0, @@ -403,7 +397,7 @@ contract MailboxFacet is ZkSyncHyperchainBase, IMailbox { request.refundRecipient = AddressAliasHelper.actualRefundRecipient(request.refundRecipient, request.sender); // Change the sender address if it is a smart contract to prevent address collision between L1 and L2. - // Please note, currently zkSync address derivation is different from Ethereum one, but it may be changed in the future. + // Please note, currently ZKsync address derivation is different from Ethereum one, but it may be changed in the future. // slither-disable-next-line tx-origin if (request.sender != tx.origin) { request.sender = AddressAliasHelper.applyL1ToL2Alias(request.sender); @@ -416,9 +410,9 @@ contract MailboxFacet is ZkSyncHyperchainBase, IMailbox { (transaction, canonicalTxHash) = _validateTx(_params); _writePriorityOp(transaction, _params.request.factoryDeps, canonicalTxHash, _params.expirationTimestamp); - if (s.syncLayer != address(0)) { + if (s.settlementLayer != address(0)) { // slither-disable-next-line unused-return - IMailbox(s.syncLayer).requestL2TransactionToSyncLayerMailbox({ + IMailbox(s.settlementLayer).requestL2TransactionToGatewayMailbox({ _chainId: s.chainId, _transaction: transaction, _factoryDeps: _params.request.factoryDeps, @@ -436,7 +430,7 @@ contract MailboxFacet is ZkSyncHyperchainBase, IMailbox { } } - function _requestL2TransactionToSyncLayerFree( + function _requestL2TransactionToGatewayFree( BridgehubL2TransactionRequest memory _request ) internal nonReentrant returns (bytes32 canonicalTxHash) { WritePriorityOpParams memory params = WritePriorityOpParams({ @@ -478,7 +472,7 @@ contract MailboxFacet is ZkSyncHyperchainBase, IMailbox { function _validateTx( WritePriorityOpParams memory _priorityOpParams - ) internal returns (L2CanonicalTransaction memory transaction, bytes32 canonicalTxHash) { + ) internal view returns (L2CanonicalTransaction memory transaction, bytes32 canonicalTxHash) { transaction = _serializeL2Transaction(_priorityOpParams); bytes memory transactionEncoding = abi.encode(transaction); TransactionValidator.validateL1ToL2Transaction( diff --git a/l1-contracts/contracts/state-transition/chain-interfaces/IAdmin.sol b/l1-contracts/contracts/state-transition/chain-interfaces/IAdmin.sol index f0d1f2303..a8ebbbc3c 100644 --- a/l1-contracts/contracts/state-transition/chain-interfaces/IAdmin.sol +++ b/l1-contracts/contracts/state-transition/chain-interfaces/IAdmin.sol @@ -3,7 +3,6 @@ pragma solidity 0.8.24; import {IZkSyncHyperchainBase} from "../chain-interfaces/IZkSyncHyperchainBase.sol"; -import {L2CanonicalTransaction} from "../../common/Messaging.sol"; import {Diamond} from "../libraries/Diamond.sol"; import {FeeParams, PubdataPricingMode} from "../chain-deps/ZkSyncHyperchainStorage.sol"; @@ -74,7 +73,7 @@ interface IAdmin is IZkSyncHyperchainBase { /// @notice Set the L1 DA validator address as well as the L2 DA validator address. /// @dev While in principle it is possible that updating only one of the addresses is needed, /// usually these should work in pair and L1 validator typically expects a specific input from the L2 Validator. - /// That's why we change those together to prevent shooting admins of chains from shooting themselves in the foot. + /// That's why we change those together to prevent admins of chains from shooting themselves in the foot. /// @param _l1DAValidator The address of the L1 DA validator /// @param _l2DAValidator The address of the L2 DA validator function setDAValidatorPair(address _l1DAValidator, address _l2DAValidator) external; @@ -127,23 +126,19 @@ interface IAdmin is IZkSyncHyperchainBase { /// @notice New pair of DA validators set event NewL2DAValidator(address indexed oldL2DAValidator, address indexed newL2DAValidator); event NewL1DAValidator(address indexed oldL1DAValidator, address indexed newL1DAValidator); - /// @dev emitted when an chain registers and a SetChainIdUpgrade happens - event SetChainIdUpgrade( - address indexed _hyperchain, - L2CanonicalTransaction _l2Transaction, - uint256 indexed _protocolVersion - ); event BridgeInitialize(address indexed l1Token, string name, string symbol, uint8 decimals); event BridgeMint(address indexed _account, uint256 _amount); + /// @dev Similar to IL1AssetHandler interface, used to send chains. function forwardedBridgeBurn( - address _syncLayer, + address _settlementLayer, address _prevMsgSender, bytes calldata _data ) external payable returns (bytes memory _bridgeMintData); + /// @dev Similar to IL1AssetHandler interface, used to claim failed chain transfers. function forwardedBridgeClaimFailedBurn( uint256 _chainId, bytes32 _assetInfo, @@ -151,5 +146,6 @@ interface IAdmin is IZkSyncHyperchainBase { bytes calldata _data ) external payable; + /// @dev Similar to IL1AssetHandler interface, used to receive chains. function forwardedBridgeMint(bytes calldata _data) external payable; } diff --git a/l1-contracts/contracts/state-transition/chain-interfaces/IExecutor.sol b/l1-contracts/contracts/state-transition/chain-interfaces/IExecutor.sol index 6f3930372..41ce9d33b 100644 --- a/l1-contracts/contracts/state-transition/chain-interfaces/IExecutor.sol +++ b/l1-contracts/contracts/state-transition/chain-interfaces/IExecutor.sol @@ -47,7 +47,7 @@ uint256 constant MAX_NUMBER_OF_BLOBS = 6; /// than the maximal number of blobs supported by the contract (`MAX_NUMBER_OF_BLOBS`). uint256 constant TOTAL_BLOBS_IN_COMMITMENT = 16; -/// @title The interface of the zkSync Executor contract capable of processing events emitted in the zkSync protocol. +/// @title The interface of the ZKsync Executor contract capable of processing events emitted in the ZKsync protocol. /// @author Matter Labs /// @custom:security-contact security@matterlabs.dev interface IExecutor is IZkSyncHyperchainBase { @@ -59,7 +59,7 @@ interface IExecutor is IZkSyncHyperchainBase { /// @param priorityOperationsHash Hash of all priority operations from this batch /// @param l2LogsTreeRoot Root hash of tree that contains L2 -> L1 messages from this batch /// @param timestamp Rollup batch timestamp, have the same format as Ethereum batch constant - /// @param commitment Verified input for the zkSync circuit + /// @param commitment Verified input for the ZKsync circuit // solhint-disable-next-line gas-struct-packing struct StoredBatchInfo { uint64 batchNumber; @@ -175,7 +175,7 @@ interface IExecutor is IZkSyncHyperchainBase { /// @notice Event emitted when a batch is committed /// @param batchNumber Number of the batch committed /// @param batchHash Hash of the L2 batch - /// @param commitment Calculated input for the zkSync circuit + /// @param commitment Calculated input for the ZKsync circuit /// @dev It has the name "BlockCommit" and not "BatchCommit" due to backward compatibility considerations event BlockCommit(uint256 indexed batchNumber, bytes32 indexed batchHash, bytes32 indexed commitment); @@ -188,7 +188,7 @@ interface IExecutor is IZkSyncHyperchainBase { /// @notice Event emitted when a batch is executed /// @param batchNumber Number of the batch executed /// @param batchHash Hash of the L2 batch - /// @param commitment Verified input for the zkSync circuit + /// @param commitment Verified input for the ZKsync circuit /// @dev It has the name "BlockExecution" and not "BatchExecution" due to backward compatibility considerations event BlockExecution(uint256 indexed batchNumber, bytes32 indexed batchHash, bytes32 indexed commitment); diff --git a/l1-contracts/contracts/state-transition/chain-interfaces/IGetters.sol b/l1-contracts/contracts/state-transition/chain-interfaces/IGetters.sol index 38b328452..c5d73cdb6 100644 --- a/l1-contracts/contracts/state-transition/chain-interfaces/IGetters.sol +++ b/l1-contracts/contracts/state-transition/chain-interfaces/IGetters.sol @@ -2,7 +2,6 @@ pragma solidity 0.8.24; -import {PriorityOperation} from "../libraries/PriorityQueue.sol"; import {VerifierParams} from "../chain-interfaces/IVerifier.sol"; import {PubdataPricingMode} from "../chain-deps/ZkSyncHyperchainStorage.sol"; import {IZkSyncHyperchainBase} from "./IZkSyncHyperchainBase.sol"; @@ -64,10 +63,6 @@ interface IGetters is IZkSyncHyperchainBase { /// @return The number of priority operations currently in the queue function getPriorityQueueSize() external view returns (uint256); - /// @notice This function is deprecated and will return an empty priority operation. - /// @return Empty priority operation - function priorityQueueFrontOperation() external view returns (PriorityOperation memory); - /// @return Whether the address has a validator access function isValidator(address _address) external view returns (bool); @@ -156,5 +151,5 @@ interface IGetters is IZkSyncHyperchainBase { function isFacetFreezable(address _facet) external view returns (bool isFreezable); /// TODO - function getSyncLayer() external view returns (address); + function getSettlementLayer() external view returns (address); } diff --git a/l1-contracts/contracts/state-transition/chain-interfaces/IL1DAValidator.sol b/l1-contracts/contracts/state-transition/chain-interfaces/IL1DAValidator.sol index 9abb301ca..a4fe56b01 100644 --- a/l1-contracts/contracts/state-transition/chain-interfaces/IL1DAValidator.sol +++ b/l1-contracts/contracts/state-transition/chain-interfaces/IL1DAValidator.sol @@ -22,18 +22,20 @@ struct L1DAValidatorOutput { interface IL1DAValidator { /// @notice The function that checks the data availability for the given batch input. - /// @param chainId The chain id of the chain that is being committed. - /// @param l2DAValidatorOutputHash The hash of that was returned by the l2DAValidator. - /// @param operatorDAInput The DA input by the operator provided on L1. - /// @param maxBlobsSupported The maximal number of blobs supported by the chain. + /// @param _chainId The chain id of the chain that is being committed. + /// @param _chainId The batch number for which the data availability is being checked. + /// @param _l2DAValidatorOutputHash The hash of that was returned by the l2DAValidator. + /// @param _operatorDAInput The DA input by the operator provided on L1. + /// @param _maxBlobsSupported The maximal number of blobs supported by the chain. /// We provide this value for future compatibility. /// This is needed because the corresponding `blobsLinearHashes`/`blobsOpeningCommitments` /// in the `L1DAValidatorOutput` struct will have to have this length as it is required /// to be static by the circuits. function checkDA( - uint256 chainId, - bytes32 l2DAValidatorOutputHash, - bytes calldata operatorDAInput, - uint256 maxBlobsSupported + uint256 _chainId, + uint256 _batchNumber, + bytes32 _l2DAValidatorOutputHash, + bytes calldata _operatorDAInput, + uint256 _maxBlobsSupported ) external returns (L1DAValidatorOutput memory output); } diff --git a/l1-contracts/contracts/state-transition/chain-interfaces/ILegacyGetters.sol b/l1-contracts/contracts/state-transition/chain-interfaces/ILegacyGetters.sol index 5d3c36094..5f758b6c2 100644 --- a/l1-contracts/contracts/state-transition/chain-interfaces/ILegacyGetters.sol +++ b/l1-contracts/contracts/state-transition/chain-interfaces/ILegacyGetters.sol @@ -5,7 +5,7 @@ pragma solidity 0.8.24; import {IZkSyncHyperchainBase} from "./IZkSyncHyperchainBase.sol"; /// @author Matter Labs -/// @dev This interface contains getters for the zkSync contract that should not be used, +/// @dev This interface contains getters for the ZKsync contract that should not be used, /// but still are kept for backward compatibility. /// @custom:security-contact security@matterlabs.dev interface ILegacyGetters is IZkSyncHyperchainBase { diff --git a/l1-contracts/contracts/state-transition/chain-interfaces/IMailbox.sol b/l1-contracts/contracts/state-transition/chain-interfaces/IMailbox.sol index db1fd0b2d..b6fc837f2 100644 --- a/l1-contracts/contracts/state-transition/chain-interfaces/IMailbox.sol +++ b/l1-contracts/contracts/state-transition/chain-interfaces/IMailbox.sol @@ -5,7 +5,7 @@ pragma solidity 0.8.24; import {IZkSyncHyperchainBase} from "./IZkSyncHyperchainBase.sol"; import {L2CanonicalTransaction, L2Log, L2Message, TxStatus, BridgehubL2TransactionRequest} from "../../common/Messaging.sol"; -/// @title The interface of the zkSync Mailbox contract that provides interfaces for L1 <-> L2 interaction. +/// @title The interface of the ZKsync Mailbox contract that provides interfaces for L1 <-> L2 interaction. /// @author Matter Labs /// @custom:security-contact security@matterlabs.dev interface IMailbox is IZkSyncHyperchainBase { @@ -95,18 +95,26 @@ interface IMailbox is IZkSyncHyperchainBase { address _refundRecipient ) external payable returns (bytes32 canonicalTxHash); + /// @notice when requesting transactions through the bridgehub function bridgehubRequestL2Transaction( BridgehubL2TransactionRequest calldata _request ) external returns (bytes32 canonicalTxHash); - function bridgehubRequestL2TransactionOnSyncLayer( + /// @dev On the Gateway the chain's mailbox receives the tx from the bridgehub. + function bridgehubRequestL2TransactionOnGateway( L2CanonicalTransaction calldata _transaction, bytes[] calldata _factoryDeps, bytes32 _canonicalTxHash, uint64 _expirationTimestamp ) external; - function requestL2TransactionToSyncLayerMailbox( + /// @dev On L1 we have to forward to the Gateway's mailbox which sends to the Bridgehub on the Gw + /// @param _chainId the chainId of the chain + /// @param _transaction the transaction to be relayed + /// @param _factoryDeps the factory dependencies + /// @param _canonicalTxHash the canonical transaction hash + /// @param _expirationTimestamp the expiration timestamp + function requestL2TransactionToGatewayMailbox( uint256 _chainId, L2CanonicalTransaction calldata _transaction, bytes[] calldata _factoryDeps, @@ -125,6 +133,7 @@ interface IMailbox is IZkSyncHyperchainBase { uint256 _l2GasPerPubdataByteLimit ) external view returns (uint256); + /// Proves that a certain leaf was included as part of the log merkle tree. function proveL2LeafInclusion( uint256 _batchNumber, uint256 _batchRootMask, diff --git a/l1-contracts/contracts/state-transition/chain-interfaces/IZkSyncHyperchainBase.sol b/l1-contracts/contracts/state-transition/chain-interfaces/IZkSyncHyperchainBase.sol index 1b6629f7d..a598821fb 100644 --- a/l1-contracts/contracts/state-transition/chain-interfaces/IZkSyncHyperchainBase.sol +++ b/l1-contracts/contracts/state-transition/chain-interfaces/IZkSyncHyperchainBase.sol @@ -1,7 +1,7 @@ -// SPDX-License-Identifier: UNLICENSED +// SPDX-License-Identifier: MIT pragma solidity 0.8.24; -/// @title The interface of the zkSync contract, responsible for the main zkSync logic. +/// @title The interface of the ZKsync contract, responsible for the main ZKsync logic. /// @author Matter Labs /// @custom:security-contact security@matterlabs.dev interface IZkSyncHyperchainBase { diff --git a/l1-contracts/contracts/state-transition/data-availability/CalldataDA.sol b/l1-contracts/contracts/state-transition/data-availability/CalldataDA.sol index 91c1cdc60..1d618310b 100644 --- a/l1-contracts/contracts/state-transition/data-availability/CalldataDA.sol +++ b/l1-contracts/contracts/state-transition/data-availability/CalldataDA.sol @@ -9,10 +9,16 @@ pragma solidity 0.8.24; /// @dev Our circuits will prove that a EIP-4844 blob and our internal blob are the same. uint256 constant BLOB_SIZE_BYTES = 126_976; +/// @dev The state diff hash, hash of pubdata + the number of blobs. +uint256 constant BLOB_DATA_OFFSET = 65; + +/// @dev The size of the commitment for a single blob. +uint256 constant BLOB_COMMITMENT_SIZE = 32; + /// @notice Contract that contains the functionality for process the calldata DA. /// @dev The expected l2DAValidator that should be used with it `RollupL2DAValidator`. abstract contract CalldataDA { - /// @notice Parses the input that the l2 Da validator has provided to the contract. + /// @notice Parses the input that the L2 DA validator has provided to the contract. /// @param _l2DAValidatorOutputHash The hash of the output of the L2 DA validator. /// @param _maxBlobsSupported The maximal number of blobs supported by the chain. /// @param _operatorDAInput The DA input by the operator provided on L1. @@ -31,14 +37,14 @@ abstract contract CalldataDA { bytes calldata l1DaInput ) { - // The preimage under the hash `l2DAValidatorOutputHash` is expected to be in the following format: + // The preimage under the hash `_l2DAValidatorOutputHash` is expected to be in the following format: // - First 32 bytes are the hash of the uncompressed state diff. // - Then, there is a 32-byte hash of the full pubdata. // - Then, there is the 1-byte number of blobs published. // - Then, there are linear hashes of the published blobs, 32 bytes each. // Check that it accommodates enough pubdata for the state diff hash, hash of pubdata + the number of blobs. - require(_operatorDAInput.length >= 32 + 32 + 1, "too small"); + require(_operatorDAInput.length >= BLOB_DATA_OFFSET, "too small"); stateDiffHash = bytes32(_operatorDAInput[:32]); fullPubdataHash = bytes32(_operatorDAInput[32:64]); @@ -50,20 +56,16 @@ abstract contract CalldataDA { // the `_maxBlobsSupported` blobsLinearHashes = new bytes32[](_maxBlobsSupported); - require(_operatorDAInput.length >= 65 + 32 * blobsProvided, "invalid blobs hashes"); + require(_operatorDAInput.length >= BLOB_DATA_OFFSET + 32 * blobsProvided, "invalid blobs hashes"); - uint256 ptr = 65; + cloneCalldata(blobsLinearHashes, _operatorDAInput[BLOB_DATA_OFFSET:], blobsProvided); - for (uint256 i = 0; i < blobsProvided; ++i) { - // Take the 32 bytes of the blob linear hash - blobsLinearHashes[i] = bytes32(_operatorDAInput[ptr:ptr + 32]); - ptr += 32; - } + uint256 ptr = BLOB_DATA_OFFSET + 32 * blobsProvided; - // Now, we need to double check that the provided input was indeed retutned by the L2 DA validator. + // Now, we need to double check that the provided input was indeed returned by the L2 DA validator. require(keccak256(_operatorDAInput[:ptr]) == _l2DAValidatorOutputHash, "invalid l2 DA output hash"); - // The rest of the output were provided specifically by the operator + // The rest of the output was provided specifically by the operator l1DaInput = _operatorDAInput[ptr:]; } @@ -78,21 +80,32 @@ abstract contract CalldataDA { bytes32 _fullPubdataHash, uint256 _maxBlobsSupported, bytes calldata _pubdataInput - ) internal pure returns (bytes32[] memory blobCommitments, bytes calldata _pubdata) { + ) internal pure virtual returns (bytes32[] memory blobCommitments, bytes calldata _pubdata) { + require(_blobsProvided == 1, "only one blob with calldata"); + require(_pubdataInput.length >= BLOB_COMMITMENT_SIZE, "pubdata too small"); + // We typically do not know whether we'll use calldata or blobs at the time when // we start proving the batch. That's why the blob commitment for a single blob is still present in the case of calldata. blobCommitments = new bytes32[](_maxBlobsSupported); - require(_blobsProvided == 1, "only one blob with calldata"); + _pubdata = _pubdataInput[:_pubdataInput.length - BLOB_COMMITMENT_SIZE]; - require(_pubdataInput.length >= 32, "pubdata too small"); - - _pubdata = _pubdataInput[:_pubdataInput.length - 32]; - - // FIXME: allow larger lengths for SyncLayer-based chains. require(_pubdata.length <= BLOB_SIZE_BYTES, "cz"); require(_fullPubdataHash == keccak256(_pubdata), "wp"); - blobCommitments[0] = bytes32(_pubdataInput[_pubdataInput.length - 32:_pubdataInput.length]); + blobCommitments[0] = bytes32(_pubdataInput[_pubdataInput.length - BLOB_COMMITMENT_SIZE:_pubdataInput.length]); + } + + /// @notice Method that clones a slice of calldata into a bytes32[] memory array. + /// @param _dst The destination array. + /// @param _input The input calldata. + /// @param _len The length of the slice in 32-byte words to clone. + function cloneCalldata(bytes32[] memory _dst, bytes calldata _input, uint256 _len) internal pure { + assembly { + // The pointer to the allocated memory above. We skip 32 bytes to avoid overwriting the length. + let dstPtr := add(_dst, 0x20) + let inputPtr := _input.offset + calldatacopy(dstPtr, inputPtr, mul(_len, 32)) + } } } diff --git a/l1-contracts/contracts/state-transition/data-availability/CalldataDAGateway.sol b/l1-contracts/contracts/state-transition/data-availability/CalldataDAGateway.sol new file mode 100644 index 000000000..0525cefd8 --- /dev/null +++ b/l1-contracts/contracts/state-transition/data-availability/CalldataDAGateway.sol @@ -0,0 +1,36 @@ +// SPDX-License-Identifier: MIT + +pragma solidity 0.8.24; + +import {CalldataDA, BLOB_COMMITMENT_SIZE, BLOB_SIZE_BYTES} from "./CalldataDA.sol"; + +// solhint-disable gas-custom-errors, reason-string + +/// @notice Contract that contains the functionality for process the calldata DA. +/// @dev The expected l2DAValidator that should be used with it `RollupL2DAValidator`. +abstract contract CalldataDAGateway is CalldataDA { + /// @inheritdoc CalldataDA + function _processCalldataDA( + uint256 _blobsProvided, + bytes32 _fullPubdataHash, + uint256 _maxBlobsSupported, + bytes calldata _pubdataInput + ) internal pure override returns (bytes32[] memory blobCommitments, bytes calldata _pubdata) { + require(_pubdataInput.length >= _blobsProvided * BLOB_COMMITMENT_SIZE, "pubdata too small"); + + // We typically do not know whether we'll use calldata or blobs at the time when + // we start proving the batch. That's why the blob commitment for a single blob is still present in the case of calldata. + blobCommitments = new bytes32[](_maxBlobsSupported); + + _pubdata = _pubdataInput[:_pubdataInput.length - _blobsProvided * BLOB_COMMITMENT_SIZE]; + + require(_pubdata.length <= _blobsProvided * BLOB_SIZE_BYTES, "cz"); + require(_fullPubdataHash == keccak256(_pubdata), "wp"); + + bytes calldata providedCommitments = _pubdataInput[_pubdataInput.length - + _blobsProvided * + BLOB_COMMITMENT_SIZE:]; + + cloneCalldata(blobCommitments, providedCommitments, _blobsProvided); + } +} diff --git a/l1-contracts/contracts/state-transition/data-availability/RelayedSLDAValidator.sol b/l1-contracts/contracts/state-transition/data-availability/RelayedSLDAValidator.sol index 2f1f9b470..a528d162d 100644 --- a/l1-contracts/contracts/state-transition/data-availability/RelayedSLDAValidator.sol +++ b/l1-contracts/contracts/state-transition/data-availability/RelayedSLDAValidator.sol @@ -7,22 +7,46 @@ pragma solidity 0.8.24; import {IL1DAValidator, L1DAValidatorOutput, PubdataSource} from "../chain-interfaces/IL1DAValidator.sol"; import {IL1Messenger} from "../../common/interfaces/IL1Messenger.sol"; -import {CalldataDA} from "./CalldataDA.sol"; +import {CalldataDAGateway} from "./CalldataDAGateway.sol"; -import {L2_TO_L1_MESSENGER_SYSTEM_CONTRACT_ADDR} from "../../common/L2ContractAddresses.sol"; +import {IBridgehub} from "../../bridgehub/IBridgehub.sol"; +import {L2_TO_L1_MESSENGER_SYSTEM_CONTRACT_ADDR, L2_BRIDGEHUB_ADDR} from "../../common/L2ContractAddresses.sol"; /// @notice The DA validator intended to be used in Era-environment. -/// @dev For compaitbility reasons it accepts calldata in the same format as the `RollupL1DAValidator`, but unlike the latter it +/// @dev For compatibility reasons it accepts calldata in the same format as the `RollupL1DAValidator`, but unlike the latter it /// does not support blobs. /// @dev Note that it does not provide any compression whatsoever. -contract RelayedSLDAValidator is IL1DAValidator, CalldataDA { +contract RelayedSLDAValidator is IL1DAValidator, CalldataDAGateway { + /// @dev Ensures that the sender is the chain that is supposed to send the message. + /// @param _chainId The chain id of the chain that is supposed to send the message. + function _ensureOnlyChainSender(uint256 _chainId) internal view { + // Note that this contract is only supposed to be deployed on L2, where the + // bridgehub is predeployed at `L2_BRIDGEHUB_ADDR` address. + require(IBridgehub(L2_BRIDGEHUB_ADDR).getHyperchain(_chainId) == msg.sender, "l1-da-validator/invalid-sender"); + } + + /// @dev Relays the calldata to L1. + /// @param _chainId The chain id of the chain that is supposed to send the message. + /// @param _batchNumber The batch number for which the data availability is being checked. + /// @param _pubdata The pubdata to be relayed to L1. + function _relayCalldata(uint256 _chainId, uint256 _batchNumber, bytes calldata _pubdata) internal { + // Re-sending all the pubdata in pure form to L1. + // slither-disable-next-line unused-return + IL1Messenger(L2_TO_L1_MESSENGER_SYSTEM_CONTRACT_ADDR).sendToL1(abi.encode(_chainId, _batchNumber, _pubdata)); + } + /// @inheritdoc IL1DAValidator function checkDA( uint256 _chainId, + uint256 _batchNumber, bytes32 _l2DAValidatorOutputHash, bytes calldata _operatorDAInput, uint256 _maxBlobsSupported ) external returns (L1DAValidatorOutput memory output) { + // Unfortunately we have to use a method call instead of a modifier + // because of the stack-too-deep error caused by it. + _ensureOnlyChainSender(_chainId); + // Preventing "stack too deep" error uint256 blobsProvided; bytes32 fullPubdataHash; @@ -56,10 +80,7 @@ contract RelayedSLDAValidator is IL1DAValidator, CalldataDA { l1DaInput[1:] ); - // Re-sending all the pubdata in pure form to L1. - // FIXME: we should also supply batch number, this is needed for logs to work. - // slither-disable-next-line unused-return - IL1Messenger(L2_TO_L1_MESSENGER_SYSTEM_CONTRACT_ADDR).sendToL1(abi.encode(_chainId, pubdata)); + _relayCalldata(_chainId, _batchNumber, pubdata); output.blobsOpeningCommitments = blobCommitments; } else { diff --git a/l1-contracts/contracts/state-transition/l2-deps/IComplexUpgrader.sol b/l1-contracts/contracts/state-transition/l2-deps/IComplexUpgrader.sol index 905215db9..f07b879e7 100644 --- a/l1-contracts/contracts/state-transition/l2-deps/IComplexUpgrader.sol +++ b/l1-contracts/contracts/state-transition/l2-deps/IComplexUpgrader.sol @@ -1,6 +1,8 @@ -// SPDX-License-Identifier: UNLICENSED -pragma solidity ^0.8.0; +// SPDX-License-Identifier: MIT +pragma solidity 0.8.24; +/// @author Matter Labs +/// @custom:security-contact security@matterlabs.dev interface IComplexUpgrader { function upgrade(address _delegateTo, bytes calldata _calldata) external payable; } diff --git a/l1-contracts/contracts/state-transition/l2-deps/IL2GenesisUpgrade.sol b/l1-contracts/contracts/state-transition/l2-deps/IL2GenesisUpgrade.sol index 8873affef..2effd48cd 100644 --- a/l1-contracts/contracts/state-transition/l2-deps/IL2GenesisUpgrade.sol +++ b/l1-contracts/contracts/state-transition/l2-deps/IL2GenesisUpgrade.sol @@ -1,5 +1,5 @@ -// SPDX-License-Identifier: UNLICENSED -pragma solidity ^0.8.0; +// SPDX-License-Identifier: MIT +pragma solidity 0.8.24; /// @notice A struct that describes a forced deployment on an address struct ForceDeployment { @@ -15,6 +15,8 @@ struct ForceDeployment { bytes input; } +/// @author Matter Labs +/// @custom:security-contact security@matterlabs.dev interface IL2GenesisUpgrade { function genesisUpgrade(uint256 _chainId, bytes calldata _forceDeploymentsData) external payable; } diff --git a/l1-contracts/contracts/state-transition/l2-deps/ISystemContext.sol b/l1-contracts/contracts/state-transition/l2-deps/ISystemContext.sol index fded0f4d2..b8319f7c4 100644 --- a/l1-contracts/contracts/state-transition/l2-deps/ISystemContext.sol +++ b/l1-contracts/contracts/state-transition/l2-deps/ISystemContext.sol @@ -1,6 +1,9 @@ -// SPDX-License-Identifier: UNLICENSED -pragma solidity ^0.8.0; +// SPDX-License-Identifier: MIT +// We use a floating point pragma here so it can be used within other projects that interact with the zkSync ecosystem without using our exact pragma version. +pragma solidity ^0.8.21; +/// @author Matter Labs +/// @custom:security-contact security@matterlabs.dev interface ISystemContext { function setChainId(uint256 _newChainId) external; } diff --git a/l1-contracts/contracts/state-transition/libraries/PriorityTree.sol b/l1-contracts/contracts/state-transition/libraries/PriorityTree.sol index e71a662fc..6bf3649e5 100644 --- a/l1-contracts/contracts/state-transition/libraries/PriorityTree.sol +++ b/l1-contracts/contracts/state-transition/libraries/PriorityTree.sol @@ -1,6 +1,6 @@ // SPDX-License-Identifier: MIT - -pragma solidity ^0.8.24; +// We use a floating point pragma here so it can be used within other projects that interact with the zkSync ecosystem without using our exact pragma version. +pragma solidity ^0.8.21; // solhint-disable gas-custom-errors @@ -40,7 +40,7 @@ library PriorityTree { /// @return The total number of unprocessed priority operations in a priority queue function getSize(Tree storage _tree) internal view returns (uint256) { - return uint256(_tree.tree._nextLeafIndex - _tree.unprocessedIndex); + return _tree.tree._nextLeafIndex - _tree.unprocessedIndex; } /// @notice Add the priority operation to the end of the priority queue @@ -49,15 +49,18 @@ library PriorityTree { _tree.historicalRoots[newRoot] = true; } + /// @notice Set up the tree function setup(Tree storage _tree, uint256 _startIndex) internal { _tree.tree.setup(ZERO_LEAF_HASH); _tree.startIndex = _startIndex; } + /// @return Returns the tree root. function getRoot(Tree storage _tree) internal view returns (bytes32) { return _tree.tree.root(); } + /// @notice Process the priority operations of a batch. function processBatch(Tree storage _tree, PriorityOpsBatchInfo calldata _priorityOpsData) internal { if (_priorityOpsData.itemHashes.length > 0) { bytes32 expectedRoot = Merkle.calculateRootPaths( @@ -71,6 +74,7 @@ library PriorityTree { } } + /// @notice Initialize a chain from a commitment. function initFromCommitment(Tree storage _tree, PriorityTreeCommitment memory _commitment) internal { uint256 height = _commitment.sides.length; // Height, including the root node. require(height > 0, "PT: invalid commitment"); @@ -87,6 +91,7 @@ library PriorityTree { _tree.historicalRoots[_tree.tree.root()] = true; } + /// @notice Returns the commitment to the priority tree. function getCommitment(Tree storage _tree) internal view returns (PriorityTreeCommitment memory commitment) { commitment.nextLeafIndex = _tree.tree._nextLeafIndex; commitment.startIndex = _tree.startIndex; diff --git a/l1-contracts/contracts/state-transition/libraries/TransactionValidator.sol b/l1-contracts/contracts/state-transition/libraries/TransactionValidator.sol index 86bdb4294..781c74303 100644 --- a/l1-contracts/contracts/state-transition/libraries/TransactionValidator.sol +++ b/l1-contracts/contracts/state-transition/libraries/TransactionValidator.sol @@ -9,7 +9,7 @@ import {Math} from "@openzeppelin/contracts/utils/math/Math.sol"; import {L2CanonicalTransaction} from "../../common/Messaging.sol"; import {TX_SLOT_OVERHEAD_L2_GAS, MEMORY_OVERHEAD_GAS, L1_TX_INTRINSIC_L2_GAS, L1_TX_DELTA_544_ENCODING_BYTES, L1_TX_DELTA_FACTORY_DEPS_L2_GAS, L1_TX_MIN_L2_GAS_BASE, L1_TX_INTRINSIC_PUBDATA, L1_TX_DELTA_FACTORY_DEPS_PUBDATA} from "../../common/Config.sol"; -/// @title zkSync Library for validating L1 -> L2 transactions +/// @title ZKsync Library for validating L1 -> L2 transactions /// @author Matter Labs /// @custom:security-contact security@matterlabs.dev library TransactionValidator { @@ -124,7 +124,7 @@ library TransactionValidator { /// @notice Based on the total L2 gas limit and several other parameters of the transaction /// returns the part of the L2 gas that will be spent on the batch's overhead. /// @dev The details of how this function works can be checked in the documentation - /// of the fee model of zkSync. The appropriate comments are also present + /// of the fee model of ZKsync. The appropriate comments are also present /// in the Rust implementation description of function `get_maximal_allowed_overhead`. /// @param _encodingLength The length of the binary encoding of the transaction in bytes function getOverheadForTransaction( diff --git a/l1-contracts/contracts/upgrades/IL1GenesisUpgrade.sol b/l1-contracts/contracts/upgrades/IL1GenesisUpgrade.sol index 1f7affaa0..345c70cbe 100644 --- a/l1-contracts/contracts/upgrades/IL1GenesisUpgrade.sol +++ b/l1-contracts/contracts/upgrades/IL1GenesisUpgrade.sol @@ -2,11 +2,10 @@ pragma solidity 0.8.24; -import {ProposedUpgrade} from "./BaseZkSyncUpgrade.sol"; import {L2CanonicalTransaction} from "../common/Messaging.sol"; interface IL1GenesisUpgrade { - /// @dev emitted when an chain registers and a GenesisUpgrade happens + /// @dev emitted when a chain registers and a GenesisUpgrade happens event GenesisUpgrade( address indexed _hyperchain, L2CanonicalTransaction _l2Transaction, @@ -21,6 +20,4 @@ interface IL1GenesisUpgrade { bytes calldata _forceDeployments, bytes[] calldata _factoryDeps ) external returns (bytes32); - - function upgradeInner(ProposedUpgrade calldata _proposedUpgrade) external returns (bytes32); } diff --git a/l1-contracts/contracts/upgrades/L1GenesisUpgrade.sol b/l1-contracts/contracts/upgrades/L1GenesisUpgrade.sol index d4220bcff..5c20aa56b 100644 --- a/l1-contracts/contracts/upgrades/L1GenesisUpgrade.sol +++ b/l1-contracts/contracts/upgrades/L1GenesisUpgrade.sol @@ -87,7 +87,7 @@ contract L1GenesisUpgrade is IL1GenesisUpgrade, BaseZkSyncUpgradeGenesis { Diamond.DiamondCutData memory cutData = Diamond.DiamondCutData({ facetCuts: emptyArray, initAddress: _l1GenesisUpgrade, - initCalldata: abi.encodeCall(this.upgradeInner, (proposedUpgrade)) + initCalldata: abi.encodeCall(this.upgrade, (proposedUpgrade)) }); Diamond.diamondCut(cutData); @@ -95,7 +95,8 @@ contract L1GenesisUpgrade is IL1GenesisUpgrade, BaseZkSyncUpgradeGenesis { return Diamond.DIAMOND_INIT_SUCCESS_RETURN_VALUE; } - function upgradeInner(ProposedUpgrade calldata _proposedUpgrade) external override returns (bytes32) { + /// @notice the upgrade function. + function upgrade(ProposedUpgrade calldata _proposedUpgrade) public override returns (bytes32) { super.upgrade(_proposedUpgrade); return Diamond.DIAMOND_INIT_SUCCESS_RETURN_VALUE; } diff --git a/l1-contracts/deploy-scripts/AcceptAdmin.s.sol b/l1-contracts/deploy-scripts/AcceptAdmin.s.sol index f12cf14f7..4fdfb5582 100644 --- a/l1-contracts/deploy-scripts/AcceptAdmin.s.sol +++ b/l1-contracts/deploy-scripts/AcceptAdmin.s.sol @@ -1,5 +1,6 @@ // SPDX-License-Identifier: UNLICENSED -pragma solidity ^0.8.0; +// We use a floating point pragma here so it can be used within other projects that interact with the zkSync ecosystem without using our exact pragma version. +pragma solidity ^0.8.21; import {Script} from "forge-std/Script.sol"; diff --git a/l1-contracts/deploy-scripts/DeployL1.s.sol b/l1-contracts/deploy-scripts/DeployL1.s.sol index c44bd03c4..0af406abb 100644 --- a/l1-contracts/deploy-scripts/DeployL1.s.sol +++ b/l1-contracts/deploy-scripts/DeployL1.s.sol @@ -577,13 +577,19 @@ contract DeployL1Script is Script { vm.startBroadcast(msg.sender); bridgehub.addStateTransitionManager(addresses.stateTransition.stateTransitionProxy); console.log("StateTransitionManager registered"); - STMDeploymentTracker stmDT = STMDeploymentTracker(addresses.bridgehub.stmDeploymentTrackerProxy); // vm.startBroadcast(msg.sender); + L1AssetRouter sharedBridge = L1AssetRouter(addresses.bridges.sharedBridgeProxy); + sharedBridge.setAssetDeploymentTracker( + bytes32(uint256(uint160(addresses.stateTransition.stateTransitionProxy))), + address(stmDT) + ); + console.log("STM DT whitelisted"); + stmDT.registerSTMAssetOnL1(addresses.stateTransition.stateTransitionProxy); vm.stopBroadcast(); console.log("STM registered in STMDeploymentTracker"); - L1AssetRouter sharedBridge = L1AssetRouter(addresses.bridges.sharedBridgeProxy); + bytes32 assetId = bridgehub.stmAssetId(addresses.stateTransition.stateTransitionProxy); // console.log(address(bridgehub.stmDeployer()), addresses.bridgehub.stmDeploymentTrackerProxy); // console.log(address(bridgehub.stmDeployer().BRIDGE_HUB()), addresses.bridgehub.bridgehubProxy); @@ -674,7 +680,7 @@ contract DeployL1Script is Script { function deployErc20BridgeImplementation() internal { bytes memory bytecode = abi.encodePacked( type(L1ERC20Bridge).creationCode, - abi.encode(addresses.bridges.sharedBridgeProxy, addresses.vaults.l1NativeTokenVaultProxy) + abi.encode(addresses.bridges.sharedBridgeProxy, addresses.vaults.l1NativeTokenVaultProxy, config.eraChainId) ); address contractAddress = deployViaCreate2(bytecode); console.log("Erc20BridgeImplementation deployed at:", contractAddress); diff --git a/l1-contracts/deploy-scripts/Gateway.s.sol b/l1-contracts/deploy-scripts/Gateway.s.sol index b32ee0926..84bc3909c 100644 --- a/l1-contracts/deploy-scripts/Gateway.s.sol +++ b/l1-contracts/deploy-scripts/Gateway.s.sol @@ -119,8 +119,8 @@ contract GatewayScript is Script { IStateTransitionManager stm = IStateTransitionManager(config.stateTransitionProxy); Ownable ownable = Ownable(config.stateTransitionProxy); vm.prank(ownable.owner()); - stm.registerSyncLayer(config.gatewayChainId, true); - // bytes memory data = abi.encodeCall(stm.registerSyncLayer, (config.chainChainId, true)); + stm.registerSettlementLayer(config.gatewayChainId, true); + // bytes memory data = abi.encodeCall(stm.registerSettlementLayer, (config.chainChainId, true)); // Utils.executeUpgrade({ // _governor: ownable.owner(), // _salt: bytes32(config.bridgehubCreateNewChainSalt), diff --git a/l1-contracts/deploy-scripts/RegisterHyperchain.s.sol b/l1-contracts/deploy-scripts/RegisterHyperchain.s.sol index 5365c2b78..1c2980737 100644 --- a/l1-contracts/deploy-scripts/RegisterHyperchain.s.sol +++ b/l1-contracts/deploy-scripts/RegisterHyperchain.s.sol @@ -16,6 +16,7 @@ import {ChainAdmin} from "contracts/governance/ChainAdmin.sol"; import {Utils} from "./Utils.sol"; import {PubdataPricingMode} from "contracts/state-transition/chain-deps/ZkSyncHyperchainStorage.sol"; import {IL1NativeTokenVault} from "contracts/bridge/interfaces/IL1NativeTokenVault.sol"; +import {DataEncoding} from "contracts/common/libraries/DataEncoding.sol"; contract RegisterHyperchainScript is Script { using stdToml for string; @@ -150,7 +151,7 @@ contract RegisterHyperchainScript is Script { function registerTokenOnNTV() internal { IL1NativeTokenVault ntv = IL1NativeTokenVault(config.nativeTokenVault); // Ownable ownable = Ownable(config.nativeTokenVault); - bytes32 assetId = ntv.getAssetId(config.baseToken); + bytes32 assetId = DataEncoding.encodeNTVAssetId(block.chainid, config.baseToken); if (ntv.tokenAddress(assetId) != address(0)) { console.log("Token already registered on NTV"); } else { diff --git a/l1-contracts/scripts/sync-layer.ts b/l1-contracts/scripts/sync-layer.ts index 7c440f89c..d6f790573 100644 --- a/l1-contracts/scripts/sync-layer.ts +++ b/l1-contracts/scripts/sync-layer.ts @@ -138,7 +138,7 @@ async function main() { verbose: true, }); - const syncLayerChainId = getNumberFromEnv("SYNC_LAYER_CHAIN_ID"); + const gatewayChainId = getNumberFromEnv("GATEWAY_CHAIN_ID"); const gasPrice = cmd.gasPrice ? parseUnits(cmd.gasPrice, "gwei") : (await provider.getGasPrice()).mul(GAS_MULTIPLIER); @@ -147,37 +147,35 @@ async function main() { const stm = deployer.stateTransitionManagerContract(deployer.deployWallet); - const counterPart = getAddressFromEnv("SYNC_LAYER_STATE_TRANSITION_PROXY_ADDR"); + const counterPart = getAddressFromEnv("GATEWAY_STATE_TRANSITION_PROXY_ADDR"); // FIXME: do it more gracefully - deployer.addresses.StateTransition.AdminFacet = getAddressFromEnv("SYNC_LAYER_ADMIN_FACET_ADDR"); - deployer.addresses.StateTransition.MailboxFacet = getAddressFromEnv("SYNC_LAYER_MAILBOX_FACET_ADDR"); - deployer.addresses.StateTransition.ExecutorFacet = getAddressFromEnv("SYNC_LAYER_EXECUTOR_FACET_ADDR"); - deployer.addresses.StateTransition.GettersFacet = getAddressFromEnv("SYNC_LAYER_GETTERS_FACET_ADDR"); - deployer.addresses.StateTransition.Verifier = getAddressFromEnv("SYNC_LAYER_VERIFIER_ADDR"); - deployer.addresses.BlobVersionedHashRetriever = getAddressFromEnv( - "SYNC_LAYER_BLOB_VERSIONED_HASH_RETRIEVER_ADDR" - ); - deployer.addresses.StateTransition.DiamondInit = getAddressFromEnv("SYNC_LAYER_DIAMOND_INIT_ADDR"); + deployer.addresses.StateTransition.AdminFacet = getAddressFromEnv("GATEWAY_ADMIN_FACET_ADDR"); + deployer.addresses.StateTransition.MailboxFacet = getAddressFromEnv("GATEWAY_MAILBOX_FACET_ADDR"); + deployer.addresses.StateTransition.ExecutorFacet = getAddressFromEnv("GATEWAY_EXECUTOR_FACET_ADDR"); + deployer.addresses.StateTransition.GettersFacet = getAddressFromEnv("GATEWAY_GETTERS_FACET_ADDR"); + deployer.addresses.StateTransition.Verifier = getAddressFromEnv("GATEWAY_VERIFIER_ADDR"); + deployer.addresses.BlobVersionedHashRetriever = getAddressFromEnv("GATEWAY_BLOB_VERSIONED_HASH_RETRIEVER_ADDR"); + deployer.addresses.StateTransition.DiamondInit = getAddressFromEnv("GATEWAY_DIAMOND_INIT_ADDR"); - const receipt = await deployer.moveChainToSyncLayer(syncLayerChainId, gasPrice); + const receipt = await deployer.moveChainToGateway(gatewayChainId, gasPrice); - const syncLayerAddress = await stm.getHyperchain(syncLayerChainId); + const gatewayAddress = await stm.getHyperchain(gatewayChainId); - const l2TxHash = zkUtils.getL2HashFromPriorityOp(receipt, syncLayerAddress); + const l2TxHash = zkUtils.getL2HashFromPriorityOp(receipt, gatewayAddress); console.log("Hash of the transaction on SL chain: ", l2TxHash); - const syncLayerProvider = new ZkProvider(process.env.SYNC_LAYER_API_WEB3_JSON_RPC_HTTP_URL); + const gatewayProvider = new ZkProvider(process.env.GATEWAY_API_WEB3_JSON_RPC_HTTP_URL); - const txL2Handle = syncLayerProvider.getL2TransactionFromPriorityOp( + const txL2Handle = gatewayProvider.getL2TransactionFromPriorityOp( await deployWallet.provider.getTransaction(receipt.transactionHash) ); const receiptOnSL = await (await txL2Handle).wait(); console.log("Finalized on SL with hash:", receiptOnSL.transactionHash); - const stmOnSL = IStateTransitionManagerFactory.connect(counterPart, syncLayerProvider); + const stmOnSL = IStateTransitionManagerFactory.connect(counterPart, gatewayProvider); const hyperchainAddress = await stmOnSL.getHyperchain(currentChainId); console.log(`CONTRACTS_DIAMOND_PROXY_ADDR=${hyperchainAddress}`); @@ -195,10 +193,10 @@ async function main() { .option("--diamond-upgrade-init ") .option("--only-verifier") .action(async (cmd) => { - const syncLayerChainId = getNumberFromEnv("SYNC_LAYER_CHAIN_ID"); - const syncLayerProvider = new ZkProvider(process.env.SYNC_LAYER_API_WEB3_JSON_RPC_HTTP_URL); + const gatewayChainId = getNumberFromEnv("GATEWAY_CHAIN_ID"); + const gatewayProvider = new ZkProvider(process.env.GATEWAY_API_WEB3_JSON_RPC_HTTP_URL); console.log("Obtaining proof..."); - const proof = await getTxFailureProof(syncLayerProvider, cmd.failedTxL2Hash); + const proof = await getTxFailureProof(gatewayProvider, cmd.failedTxL2Hash); const deployWallet = cmd.privateKey ? new Wallet(cmd.privateKey, provider) @@ -222,8 +220,8 @@ async function main() { console.log("Executing recovery..."); await ( - await hyperchain.recoverFromFailedMigrationToSyncLayer( - syncLayerChainId, + await hyperchain.recoverFromFailedMigrationToGateway( + gatewayChainId, proof.l2BatchNumber, proof.l2MessageIndex, proof.l2TxNumberInBatch, @@ -244,16 +242,16 @@ async function main() { .option("--diamond-upgrade-init ") .option("--only-verifier") .action(async (cmd) => { - const syncLayerProvider = new ZkProvider(process.env.SYNC_LAYER_API_WEB3_JSON_RPC_HTTP_URL); + const gatewayProvider = new ZkProvider(process.env.GATEWAY_API_WEB3_JSON_RPC_HTTP_URL); const currentChainId = getNumberFromEnv("CHAIN_ETH_ZKSYNC_NETWORK_ID"); // Right now the new admin is the wallet itself. const adminWallet = cmd.privateKey - ? new ZkWallet(cmd.privateKey, syncLayerProvider) + ? new ZkWallet(cmd.privateKey, gatewayProvider) : ZkWallet.fromMnemonic( process.env.MNEMONIC ? process.env.MNEMONIC : ethTestConfig.mnemonic, "m/44'/60'/0'/0/1" - ).connect(syncLayerProvider); + ).connect(gatewayProvider); const operators = [ process.env.ETH_SENDER_SENDER_OPERATOR_COMMIT_ETH_ADDR, @@ -270,7 +268,7 @@ async function main() { console.log("Enabling validators"); // FIXME: do it in cleaner way - deployer.addresses.ValidatorTimeLock = getAddressFromEnv("SYNC_LAYER_VALIDATOR_TIMELOCK_ADDR"); + deployer.addresses.ValidatorTimeLock = getAddressFromEnv("GATEWAY_VALIDATOR_TIMELOCK_ADDR"); const timelock = deployer.validatorTimelock(deployer.deployWallet); for (const operator of operators) { @@ -284,9 +282,9 @@ async function main() { // FIXME: this method includes bridgehub manipulation, but in the future it won't. deployer.addresses.StateTransition.StateTransitionProxy = getAddressFromEnv( - "SYNC_LAYER_STATE_TRANSITION_PROXY_ADDR" + "GATEWAY_STATE_TRANSITION_PROXY_ADDR" ); - deployer.addresses.Bridgehub.BridgehubProxy = getAddressFromEnv("SYNC_LAYER_BRIDGEHUB_PROXY_ADDR"); + deployer.addresses.Bridgehub.BridgehubProxy = getAddressFromEnv("GATEWAY_BRIDGEHUB_PROXY_ADDR"); // FIXME? Do we want to console.log("Setting default token multiplier"); @@ -298,7 +296,7 @@ async function main() { console.log("Setting SL DA validators"); // This logic should be distinctive between Validium and Rollup - const l1DaValidator = getAddressFromEnv("SYNC_LAYER_L1_RELAYED_SL_DA_VALIDATOR"); + const l1DaValidator = getAddressFromEnv("GATEWAY_L1_RELAYED_SL_DA_VALIDATOR"); const l2DaValidator = getAddressFromEnv("CONTRACTS_L2_DA_VALIDATOR_ADDR"); await (await hyperchain.setDAValidatorPair(l1DaValidator, l2DaValidator)).wait(); @@ -311,32 +309,32 @@ async function main() { async function registerSLContractsOnL1(deployer: Deployer) { /// STM asset info /// l2Bridgehub in L1Bridghub - const bridgehubOnSyncLayer = getAddressFromEnv("SYNC_LAYER_BRIDGEHUB_PROXY_ADDR"); + const bridgehubOnGateway = getAddressFromEnv("GATEWAY_BRIDGEHUB_PROXY_ADDR"); const chainId = getNumberFromEnv("CHAIN_ETH_ZKSYNC_NETWORK_ID"); - console.log(`Bridghub on SyncLayer: ${bridgehubOnSyncLayer}`); - console.log(`SyncLayer chain Id: ${chainId}`); + console.log(`Bridghub on Gateway: ${bridgehubOnGateway}`); + console.log(`Gateway chain Id: ${chainId}`); const l1STM = deployer.stateTransitionManagerContract(deployer.deployWallet); const l1Bridgehub = deployer.bridgehubContract(deployer.deployWallet); console.log(deployer.addresses.StateTransition.StateTransitionProxy); - const syncLayerAddress = await l1STM.getHyperchain(chainId); + const gatewayAddress = await l1STM.getHyperchain(chainId); // this script only works when owner is the deployer - console.log("Registering SyncLayer chain id on the STM"); + console.log("Registering Gateway chain id on the STM"); const receipt1 = await deployer.executeUpgrade( l1STM.address, 0, - l1Bridgehub.interface.encodeFunctionData("registerSyncLayer", [chainId, true]) + l1Bridgehub.interface.encodeFunctionData("registerSettlementLayer", [chainId, true]) ); - console.log("Registering Bridgehub counter part on the SyncLayer", receipt1.transactionHash); + console.log("Registering Bridgehub counter part on the Gateway", receipt1.transactionHash); // await deployer.executeUpgrade( // l1Bridgehub.address, // kl todo fix. The BH has the counterpart, the BH needs to be deployed on L2, and the STM needs to be registered in the L2 BH. // 0, - // l1Bridgehub.interface.encodeFunctionData("registerCounterpart", [chainId, bridgehubOnSyncLayer]) + // l1Bridgehub.interface.encodeFunctionData("registerCounterpart", [chainId, bridgehubOnGateway]) // ); - // console.log("SyncLayer registration completed in L1 Bridgehub"); + // console.log("Gateway registration completed in L1 Bridgehub"); const gasPrice = (await deployer.deployWallet.provider.getGasPrice()).mul(GAS_MULTIPLIER); const value = ( @@ -355,18 +353,19 @@ async function registerSLContractsOnL1(deployer: Deployer) { } const stmDeploymentTracker = deployer.stmDeploymentTracker(deployer.deployWallet); - const receipt2 = await ( - await stmDeploymentTracker.registerSTMAssetOnL2SharedBridge( + const receipt2 = await deployer.executeUpgrade( + stmDeploymentTracker.address, + value, + stmDeploymentTracker.interface.encodeFunctionData("registerSTMAssetOnL2SharedBridge", [ chainId, l1STM.address, value, priorityTxMaxGasLimit, SYSTEM_CONFIG.requiredL2GasPricePerPubdata, deployer.deployWallet.address, - { value } - ) - ).wait(); - const l2TxHash = zkUtils.getL2HashFromPriorityOp(receipt2, syncLayerAddress); + ]) + ); + const l2TxHash = zkUtils.getL2HashFromPriorityOp(receipt2, gatewayAddress); console.log("STM asset registered in L2SharedBridge on SL l2 tx hash: ", l2TxHash); const receipt3 = await deployer.executeUpgrade( l1Bridgehub.address, @@ -383,12 +382,12 @@ async function registerSLContractsOnL1(deployer: Deployer) { secondBridgeValue: 0, secondBridgeCalldata: ethers.utils.defaultAbiCoder.encode( ["address", "address"], - [l1STM.address, getAddressFromEnv("SYNC_LAYER_STATE_TRANSITION_PROXY_ADDR")] + [l1STM.address, getAddressFromEnv("GATEWAY_STATE_TRANSITION_PROXY_ADDR")] ), }, ]) ); - const l2TxHash2 = zkUtils.getL2HashFromPriorityOp(receipt3, syncLayerAddress); + const l2TxHash2 = zkUtils.getL2HashFromPriorityOp(receipt3, gatewayAddress); console.log("STM asset registered in L2 Bridgehub on SL", l2TxHash2); const upgradeData = l1Bridgehub.interface.encodeFunctionData("addStateTransitionManager", [ @@ -396,7 +395,7 @@ async function registerSLContractsOnL1(deployer: Deployer) { ]); const receipt4 = await deployer.executeUpgradeOnL2( chainId, - getAddressFromEnv("SYNC_LAYER_BRIDGEHUB_PROXY_ADDR"), + getAddressFromEnv("GATEWAY_BRIDGEHUB_PROXY_ADDR"), gasPrice, upgradeData, priorityTxMaxGasLimit diff --git a/l1-contracts/src.ts/deploy.ts b/l1-contracts/src.ts/deploy.ts index ce8e45fe3..82c1e0ff5 100644 --- a/l1-contracts/src.ts/deploy.ts +++ b/l1-contracts/src.ts/deploy.ts @@ -48,7 +48,6 @@ import { // priorityTxMaxGasLimit, } from "./utils"; import type { ChainAdminCall } from "./utils"; -import { IBridgehubFactory } from "../typechain/IBridgehubFactory"; import { IGovernanceFactory } from "../typechain/IGovernanceFactory"; import { ITransparentUpgradeableProxyFactory } from "../typechain/ITransparentUpgradeableProxyFactory"; import { ProxyAdminFactory } from "../typechain/ProxyAdminFactory"; @@ -62,7 +61,7 @@ import { ValidatorTimelockFactory } from "../typechain/ValidatorTimelockFactory" import type { FacetCut } from "./diamondCut"; import { getCurrentFacetCutsForAdd } from "./diamondCut"; -import { ChainAdminFactory, ERC20Factory, StateTransitionManagerFactory } from "../typechain"; +import { BridgehubFactory, ChainAdminFactory, ERC20Factory, StateTransitionManagerFactory } from "../typechain"; import { IL1AssetRouterFactory } from "../typechain/IL1AssetRouterFactory"; import { IL1NativeTokenVaultFactory } from "../typechain/IL1NativeTokenVaultFactory"; @@ -215,8 +214,8 @@ export class Deployer { callConstructor: true, value: 0, input: ethers.utils.defaultAbiCoder.encode( - ["bytes32", "address", "bool"], - [l2TokenProxyBytecodeHash, this.addresses.Governance, false] + ["uint256", "bytes32", "address"], + [getNumberFromEnv("ETH_CLIENT_CHAIN_ID"), l2TokenProxyBytecodeHash, applyL1ToL2Alias(this.addresses.Governance)] ), }; @@ -387,9 +386,8 @@ export class Deployer { if (this.verbose) { console.log( - `Proxy admin deployed, gasUsed: ${rec.gasUsed.toString()}, tx hash ${rec.transactionHash}, expected address: ${ - proxyAdmin.address - }` + `Proxy admin deployed, gasUsed: ${rec.gasUsed.toString()}, tx hash ${rec.transactionHash}, expected address: + ${proxyAdmin.address}` ); console.log(`CONTRACTS_TRANSPARENT_PROXY_ADMIN_ADDR=${proxyAdmin.address}`); } @@ -401,9 +399,8 @@ export class Deployer { if (this.verbose) { console.log( - `ProxyAdmin ownership transferred to Governance in tx ${ - receipt.transactionHash - }, gas used: ${receipt.gasUsed.toString()}` + `ProxyAdmin ownership transferred to Governance in tx + ${receipt.transactionHash}, gas used: ${receipt.gasUsed.toString()}` ); } } @@ -611,9 +608,10 @@ export class Deployer { ethTxOptions: ethers.providers.TransactionRequest, dummy: boolean = false ) { + const eraChainId = getNumberFromEnv("CONTRACTS_ERA_CHAIN_ID"); const contractAddress = await this.deployViaCreate2( dummy ? "DummyL1ERC20Bridge" : "L1ERC20Bridge", - [this.addresses.Bridges.SharedBridgeProxy, this.addresses.Bridges.NativeTokenVaultProxy], + [this.addresses.Bridges.SharedBridgeProxy, this.addresses.Bridges.NativeTokenVaultProxy, eraChainId], create2Salt, ethTxOptions ); @@ -820,19 +818,19 @@ export class Deployer { create2Salt: string, ethTxOptions: ethers.providers.TransactionRequest ) { - const eraChainId = getNumberFromEnv("CONTRACTS_ERA_CHAIN_ID"); + // const eraChainId = getNumberFromEnv("CONTRACTS_ERA_CHAIN_ID"); const tokens = getTokens(); const l1WethToken = tokens.find((token: { symbol: string }) => token.symbol == "WETH")!.address; const contractAddress = await this.deployViaCreate2( "L1NativeTokenVault", - [l1WethToken, this.addresses.Bridges.SharedBridgeProxy, eraChainId], + [l1WethToken, this.addresses.Bridges.SharedBridgeProxy], create2Salt, ethTxOptions ); if (this.verbose) { - console.log(`With era chain id ${eraChainId}`); + // console.log(`With era chain id ${eraChainId}`); console.log(`CONTRACTS_L1_NATIVE_TOKEN_VAULT_IMPL_ADDR=${contractAddress}`); } @@ -925,22 +923,22 @@ export class Deployer { public async registerAddresses() { const bridgehub = this.bridgehubContract(this.deployWallet); - /// registering ETH as a valid token, with address 1. - const upgradeData1 = bridgehub.interface.encodeFunctionData("addToken", [ADDRESS_ONE]); - await this.executeUpgrade(this.addresses.Bridgehub.BridgehubProxy, 0, upgradeData1); - if (this.verbose) { - console.log("ETH token registered in Bridgehub"); - } - - const upgradeData2 = bridgehub.interface.encodeFunctionData("setAddresses", [ + const upgradeData1 = await bridgehub.interface.encodeFunctionData("setAddresses", [ this.addresses.Bridges.SharedBridgeProxy, this.addresses.Bridgehub.STMDeploymentTrackerProxy, this.addresses.Bridgehub.MessageRootProxy, ]); - await this.executeUpgrade(this.addresses.Bridgehub.BridgehubProxy, 0, upgradeData2); + await this.executeUpgrade(this.addresses.Bridgehub.BridgehubProxy, 0, upgradeData1); if (this.verbose) { console.log("Shared bridge was registered in Bridgehub"); } + + /// registering ETH as a valid token, with address 1. + const upgradeData2 = bridgehub.interface.encodeFunctionData("addToken", [ADDRESS_ONE]); + await this.executeUpgrade(this.addresses.Bridgehub.BridgehubProxy, 0, upgradeData2); + if (this.verbose) { + console.log("ETH token registered in Bridgehub"); + } } public async registerTokenInNativeTokenVault(token: string) { @@ -1043,39 +1041,58 @@ export class Deployer { if (this.verbose) { console.log(`StateTransition System registered, gas used: ${receipt1.gasUsed.toString()}`); } - } - const stmDeploymentTracker = this.stmDeploymentTracker(this.deployWallet); - const data1 = stmDeploymentTracker.interface.encodeFunctionData("registerSTMAssetOnL1", [ - this.addresses.StateTransition.StateTransitionProxy, - ]); - const receipt2 = await this.executeUpgrade(this.addresses.Bridgehub.STMDeploymentTrackerProxy, 0, data1); - if (this.verbose) { - console.log("STM asset registered in L1 Shared Bridge via STM Deployment Tracker", receipt2.gasUsed.toString()); - console.log( - `CONTRACTS_STM_ASSET_INFO=${await bridgehub.stmAssetId(this.addresses.StateTransition.StateTransitionProxy)}` - ); + const stmDeploymentTracker = this.stmDeploymentTracker(this.deployWallet); + + const l1AssetRouter = this.defaultSharedBridge(this.deployWallet); + const whitelistData = l1AssetRouter.interface.encodeFunctionData("setAssetDeploymentTracker", [ + ethers.utils.hexZeroPad(this.addresses.StateTransition.StateTransitionProxy, 32), + stmDeploymentTracker.address, + ]); + const receipt2 = await this.executeUpgrade(l1AssetRouter.address, 0, whitelistData); + if (this.verbose) { + console.log("STM deployment tracker whitelisted in L1 Shared Bridge", receipt2.gasUsed.toString()); + console.log( + `CONTRACTS_STM_ASSET_INFO=${await bridgehub.stmAssetId(this.addresses.StateTransition.StateTransitionProxy)}` + ); + } + + const data1 = stmDeploymentTracker.interface.encodeFunctionData("registerSTMAssetOnL1", [ + this.addresses.StateTransition.StateTransitionProxy, + ]); + const receipt3 = await this.executeUpgrade(this.addresses.Bridgehub.STMDeploymentTrackerProxy, 0, data1); + if (this.verbose) { + console.log( + "STM asset registered in L1 Shared Bridge via STM Deployment Tracker", + receipt3.gasUsed.toString() + ); + console.log( + `CONTRACTS_STM_ASSET_INFO=${await bridgehub.stmAssetId(this.addresses.StateTransition.StateTransitionProxy)}` + ); + } } } } - public async registerSyncLayer() { + public async registerSettlementLayer() { const stm = this.stateTransitionManagerContract(this.deployWallet); - const calldata = stm.interface.encodeFunctionData("registerSyncLayer", [this.chainId, true]); + const calldata = stm.interface.encodeFunctionData("registerSettlementLayer", [this.chainId, true]); await this.executeUpgrade(this.addresses.StateTransition.StateTransitionProxy, 0, calldata); if (this.verbose) { - console.log("SyncLayer registered"); + console.log("Gateway registered"); } } - public async moveChainToSyncLayer(syncLayerChainId: string, gasPrice: BigNumberish) { + // Main function to move the current chain (that is hooked to l1), on top of the syncLayer chain. + public async moveChainToGateway(gatewayChainId: string, gasPrice: BigNumberish) { const bridgehub = this.bridgehubContract(this.deployWallet); // Just some large gas limit that should always be enough const l2GasLimit = ethers.BigNumber.from(72_000_000); const expectedCost = ( - await bridgehub.l2TransactionBaseCost(syncLayerChainId, gasPrice, l2GasLimit, REQUIRED_L2_GAS_PRICE_PER_PUBDATA) + await bridgehub.l2TransactionBaseCost(gatewayChainId, gasPrice, l2GasLimit, REQUIRED_L2_GAS_PRICE_PER_PUBDATA) ).mul(5); + // We are creating the new DiamondProxy for our chain, to be deployed on top of sync Layer. const newAdmin = this.deployWallet.address; const diamondCutData = await this.initialZkSyncHyperchainDiamondCut(); const initialDiamondCut = new ethers.utils.AbiCoder().encode([DIAMOND_CUT_DATA_ABI_STRING], [diamondCutData]); @@ -1089,19 +1106,28 @@ export class Deployer { // console.log("bridgehubData", bridgehubData) // console.log("this.addresses.ChainAssetInfo", this.addresses.ChainAssetInfo) + + // The stmAssetIFromChainId gives us a unique 'asset' identifier for a given chain. + const chainAssetId = await bridgehub.stmAssetIdFromChainId(this.chainId); + console.log("Chain asset id is: ", chainAssetId); + let sharedBridgeData = ethers.utils.defaultAbiCoder.encode( ["bytes32", "bytes"], - [await bridgehub.stmAssetIdFromChainId(this.chainId), bridgehubData] + [chainAssetId, bridgehubData] ); + // The 0x01 is the encoding for the L1AssetRouter. sharedBridgeData = "0x01" + sharedBridgeData.slice(2); + // And now we 'transfer' the chain through the bridge (it behaves like a 'regular' asset, where we 'freeze' it in L1 + // and then create on SyncLayer). You can see these methods in Admin.sol (part of DiamondProxy). const receipt = await this.executeChainAdminMulticall([ { target: bridgehub.address, data: bridgehub.interface.encodeFunctionData("requestL2TransactionTwoBridges", [ + // These arguments must match L2TransactionRequestTwoBridgesOuter struct. { - chainId: syncLayerChainId, + chainId: gatewayChainId, mintValue: expectedCost, l2Value: 0, l2GasLimit: l2GasLimit, @@ -1241,9 +1267,8 @@ export class Deployer { const receiptRegisterValidator = await txRegisterValidator.wait(); if (this.verbose) { console.log( - `Validator registered, gas used: ${receiptRegisterValidator.gasUsed.toString()}, tx hash: ${ - txRegisterValidator.hash - }` + `Validator registered, gas used: ${receiptRegisterValidator.gasUsed.toString()}, tx hash: + ${txRegisterValidator.hash}` ); } @@ -1324,7 +1349,6 @@ export class Deployer { public async registerTokenBridgehub(tokenAddress: string, useGovernance: boolean = false) { const bridgehub = this.bridgehubContract(this.deployWallet); - const receipt = await this.executeDirectOrGovernance(useGovernance, bridgehub, "addToken", [tokenAddress], 0); if (this.verbose) { @@ -1461,7 +1485,7 @@ export class Deployer { } public bridgehubContract(signerOrProvider: Signer | providers.Provider) { - return IBridgehubFactory.connect(this.addresses.Bridgehub.BridgehubProxy, signerOrProvider); + return BridgehubFactory.connect(this.addresses.Bridgehub.BridgehubProxy, signerOrProvider); } public stateTransitionManagerContract(signerOrProvider: Signer | providers.Provider) { diff --git a/l1-contracts/src.ts/utils.ts b/l1-contracts/src.ts/utils.ts index cf5f6d4c9..f328c5759 100644 --- a/l1-contracts/src.ts/utils.ts +++ b/l1-contracts/src.ts/utils.ts @@ -22,6 +22,7 @@ export const REQUIRED_L2_GAS_PRICE_PER_PUBDATA = require("../../SystemConfig.jso export const SYSTEM_UPGRADE_L2_TX_TYPE = 254; export const ADDRESS_ONE = "0x0000000000000000000000000000000000000001"; +export const ADDRESS_TWO_NTV = "0x0000000000000000000000000000000000000002"; export const ETH_ADDRESS_IN_CONTRACTS = ADDRESS_ONE; export const L1_TO_L2_ALIAS_OFFSET = "0x1111000000000000000000000000000000001111"; export const L2_BRIDGEHUB_ADDRESS = "0x0000000000000000000000000000000000010002"; @@ -104,6 +105,12 @@ export function computeL2Create2Address( return ethers.utils.hexDataSlice(data, 12); } +export function encodeNTVAssetId(chainId: number, assetData: BytesLike) { + return ethers.utils.keccak256( + ethers.utils.defaultAbiCoder.encode(["uint256", "address", "bytes32"], [chainId, ADDRESS_TWO_NTV, assetData]) + ); +} + export function getAddressFromEnv(envName: string): string { const address = process.env[envName]; if (!/^0x[a-fA-F0-9]{40}$/.test(address)) { diff --git a/l1-contracts/test/foundry/integration/BridgeHubInvariantTests.t.sol b/l1-contracts/test/foundry/integration/BridgeHubInvariantTests.t.sol index e2e6f70f5..6a59364bc 100644 --- a/l1-contracts/test/foundry/integration/BridgeHubInvariantTests.t.sol +++ b/l1-contracts/test/foundry/integration/BridgeHubInvariantTests.t.sol @@ -489,7 +489,7 @@ contract BridgeHubInvariantTests is L1ContractDeployer, HyperchainDeployer, Toke uint256 beforeBalance = currentToken.balanceOf(sharedBridgeProxyAddress); if (beforeChainBalance < amountToWithdraw) { - vm.expectRevert("ShB not enough funds 2"); + vm.expectRevert("L1AR: not enough funds 2"); } else { tokenSumWithdrawal[currentTokenAddress] += amountToWithdraw; } @@ -551,7 +551,7 @@ contract BridgeHubInvariantTests is L1ContractDeployer, HyperchainDeployer, Toke uint256 beforeBalance = sharedBridgeProxyAddress.balance; if (beforeChainBalance < amountToWithdraw) { - vm.expectRevert("ShB not enough funds 2"); + vm.expectRevert("L1AR: not enough funds 2"); } else { tokenSumWithdrawal[currentTokenAddress] += amountToWithdraw; } diff --git a/l1-contracts/test/foundry/integration/BridgehubTests.t.sol b/l1-contracts/test/foundry/integration/BridgehubTests.t.sol index b349fbcfc..5074d6091 100644 --- a/l1-contracts/test/foundry/integration/BridgehubTests.t.sol +++ b/l1-contracts/test/foundry/integration/BridgehubTests.t.sol @@ -489,7 +489,7 @@ contract BridgeHubInvariantTests is L1ContractDeployer, HyperchainDeployer, Toke uint256 beforeBalance = currentToken.balanceOf(sharedBridgeProxyAddress); if (beforeChainBalance < amountToWithdraw) { - vm.expectRevert("ShB not enough funds 2"); + vm.expectRevert("L1AR: not enough funds 2"); } else { tokenSumWithdrawal[currentTokenAddress] += amountToWithdraw; } @@ -551,7 +551,7 @@ contract BridgeHubInvariantTests is L1ContractDeployer, HyperchainDeployer, Toke uint256 beforeBalance = sharedBridgeProxyAddress.balance; if (beforeChainBalance < amountToWithdraw) { - vm.expectRevert("ShB not enough funds 2"); + vm.expectRevert("L1AR: not enough funds 2"); } else { tokenSumWithdrawal[currentTokenAddress] += amountToWithdraw; } diff --git a/l1-contracts/test/foundry/integration/GatewayTests.t.sol b/l1-contracts/test/foundry/integration/GatewayTests.t.sol index 89d39ffef..dc5e23643 100644 --- a/l1-contracts/test/foundry/integration/GatewayTests.t.sol +++ b/l1-contracts/test/foundry/integration/GatewayTests.t.sol @@ -16,7 +16,7 @@ import {TokenDeployer} from "./_SharedTokenDeployer.t.sol"; import {HyperchainDeployer} from "./_SharedHyperchainDeployer.t.sol"; import {GatewayDeployer} from "./_SharedGatewayDeployer.t.sol"; import {L2TxMocker} from "./_SharedL2TxMocker.t.sol"; -import {ETH_TOKEN_ADDRESS} from "contracts/common/Config.sol"; +import {ETH_TOKEN_ADDRESS, SETTLEMENT_LAYER_RELAY_SENDER} from "contracts/common/Config.sol"; import {REQUIRED_L2_GAS_PRICE_PER_PUBDATA, DEFAULT_L2_LOGS_TREE_ROOT_HASH, EMPTY_STRING_KECCAK} from "contracts/common/Config.sol"; import {L2CanonicalTransaction} from "contracts/common/Messaging.sol"; import {L2Message} from "contracts/common/Messaging.sol"; @@ -28,6 +28,7 @@ import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol"; import {IZkSyncHyperchain} from "contracts/state-transition/chain-interfaces/IZkSyncHyperchain.sol"; import {IStateTransitionManager} from "contracts/state-transition/IStateTransitionManager.sol"; import {AdminFacet} from "contracts/state-transition/chain-deps/facets/Admin.sol"; +import {AddressAliasHelper} from "contracts/vendor/AddressAliasHelper.sol"; contract GatewayTests is L1ContractDeployer, HyperchainDeployer, TokenDeployer, L2TxMocker, GatewayDeployer { uint256 constant TEST_USERS_COUNT = 10; @@ -158,7 +159,9 @@ contract GatewayTests is L1ContractDeployer, HyperchainDeployer, TokenDeployer, reservedDynamic: "0x" }); vm.chainId(12345); - bridgehub.forwardTransactionOnSyncLayer(mintChainId, tx, new bytes[](0), bytes32(0), 0); + vm.startBroadcast(SETTLEMENT_LAYER_RELAY_SENDER); + bridgehub.forwardTransactionOnGateway(mintChainId, tx, new bytes[](0), bytes32(0), 0); + vm.stopBroadcast(); } function finishMoveChain() public { @@ -168,7 +171,7 @@ contract GatewayTests is L1ContractDeployer, HyperchainDeployer, TokenDeployer, bytes32 assetId = bridgehub.stmAssetIdFromChainId(migratingChainId); bytes memory initialDiamondCut = l1Script.getInitialDiamondCutData(); - bytes memory chainData = abi.encode(AdminFacet(address(chain))._prepareChainCommitment()); + bytes memory chainData = abi.encode(AdminFacet(address(chain)).prepareChainCommitment()); bytes memory stmData = abi.encode(address(1), msg.sender, stm.protocolVersion(), initialDiamondCut); bytes memory bridgehubMintData = abi.encode(mintChainId, stmData, chainData); vm.startBroadcast(address(bridgehub.sharedBridge())); diff --git a/l1-contracts/test/foundry/unit/concrete/Bridgehub/MessageRoot.t.sol b/l1-contracts/test/foundry/unit/concrete/Bridgehub/MessageRoot.t.sol new file mode 100644 index 000000000..497ec4731 --- /dev/null +++ b/l1-contracts/test/foundry/unit/concrete/Bridgehub/MessageRoot.t.sol @@ -0,0 +1,115 @@ +// SPDX-License-Identifier: MIT + +pragma solidity 0.8.24; + +import {Test} from "forge-std/Test.sol"; +import {MessageRoot} from "contracts/bridgehub/MessageRoot.sol"; +import {IBridgehub} from "contracts/bridgehub/IBridgehub.sol"; + +// Chain tree consists of batch commitments as their leaves. We use hash of "new bytes(96)" as the hash of an empty leaf. +bytes32 constant CHAIN_TREE_EMPTY_ENTRY_HASH = bytes32( + 0x46700b4d40ac5c35af2c22dda2787a91eb567b06c924a8fb8ae9a05b20c08c21 +); + +// Chain tree consists of batch commitments as their leaves. We use hash of "new bytes(96)" as the hash of an empty leaf. +bytes32 constant SHARED_ROOT_TREE_EMPTY_HASH = bytes32( + 0x46700b4d40ac5c35af2c22dda2787a91eb567b06c924a8fb8ae9a05b20c08c21 +); + +contract MessageRootTest is Test { + address bridgeHub; + MessageRoot messageRoot; + + function setUp() public { + bridgeHub = makeAddr("bridgeHub"); + messageRoot = new MessageRoot(IBridgehub(bridgeHub)); + } + + function test_init() public { + assertEq(messageRoot.getAggregatedRoot(), CHAIN_TREE_EMPTY_ENTRY_HASH); + } + + function test_RevertWhen_addChainNotBridgeHub() public { + uint256 alphaChainId = uint256(uint160(makeAddr("alphaChainId"))); + uint256 betaChainId = uint256(uint160(makeAddr("betaChainId"))); + + assertFalse(messageRoot.chainRegistered(alphaChainId), "alpha chain 1"); + + vm.expectRevert("MR: only bridgehub"); + messageRoot.addNewChain(alphaChainId); + + assertFalse(messageRoot.chainRegistered(alphaChainId), "alpha chain 2"); + } + + function test_addNewChain() public { + uint256 alphaChainId = uint256(uint160(makeAddr("alphaChainId"))); + uint256 betaChainId = uint256(uint160(makeAddr("betaChainId"))); + + assertFalse(messageRoot.chainRegistered(alphaChainId), "alpha chain 1"); + assertFalse(messageRoot.chainRegistered(betaChainId), "beta chain 1"); + + vm.prank(bridgeHub); + vm.expectEmit(true, false, false, false); + emit MessageRoot.AddedChain(alphaChainId, 0); + messageRoot.addNewChain(alphaChainId); + + assertTrue(messageRoot.chainRegistered(alphaChainId), "alpha chain 2"); + assertFalse(messageRoot.chainRegistered(betaChainId), "beta chain 2"); + + assertEq(messageRoot.getChainRoot(alphaChainId), bytes32(0)); + } + + function test_RevertWhen_ChainNotRegistered() public { + address alphaChainSender = makeAddr("alphaChainSender"); + uint256 alphaChainId = uint256(uint160(makeAddr("alphaChainId"))); + vm.mockCall( + bridgeHub, + abi.encodeWithSelector(IBridgehub.getHyperchain.selector, alphaChainId), + abi.encode(alphaChainSender) + ); + + vm.prank(alphaChainSender); + vm.expectRevert("MR: not registered"); + messageRoot.addChainBatchRoot(alphaChainId, 1, bytes32(alphaChainId)); + } + + function test_addChainBatchRoot() public { + address alphaChainSender = makeAddr("alphaChainSender"); + uint256 alphaChainId = uint256(uint160(makeAddr("alphaChainId"))); + vm.mockCall( + bridgeHub, + abi.encodeWithSelector(IBridgehub.getHyperchain.selector, alphaChainId), + abi.encode(alphaChainSender) + ); + + vm.prank(bridgeHub); + messageRoot.addNewChain(alphaChainId); + + vm.prank(alphaChainSender); + vm.expectEmit(true, false, false, false); + emit MessageRoot.Preimage(bytes32(0), bytes32(0)); + vm.expectEmit(true, false, false, false); + emit MessageRoot.AppendedChainBatchRoot(alphaChainId, 1, bytes32(alphaChainId)); + messageRoot.addChainBatchRoot(alphaChainId, 1, bytes32(alphaChainId)); + } + + function test_updateFullTree() public { + address alphaChainSender = makeAddr("alphaChainSender"); + uint256 alphaChainId = uint256(uint160(makeAddr("alphaChainId"))); + vm.mockCall( + bridgeHub, + abi.encodeWithSelector(IBridgehub.getHyperchain.selector, alphaChainId), + abi.encode(alphaChainSender) + ); + + vm.prank(bridgeHub); + messageRoot.addNewChain(alphaChainId); + + vm.prank(alphaChainSender); + messageRoot.addChainBatchRoot(alphaChainId, 1, bytes32(alphaChainId)); + + messageRoot.updateFullTree(); + + assertEq(messageRoot.getAggregatedRoot(), 0xbad7e1cf889e30252b8ce93820f79d50651b78587844bc1c588dea123effa4ea); + } +} diff --git a/l1-contracts/test/foundry/unit/concrete/Bridgehub/experimental_bridge.t.sol b/l1-contracts/test/foundry/unit/concrete/Bridgehub/experimental_bridge.t.sol index a3f01cf83..abc39a84d 100644 --- a/l1-contracts/test/foundry/unit/concrete/Bridgehub/experimental_bridge.t.sol +++ b/l1-contracts/test/foundry/unit/concrete/Bridgehub/experimental_bridge.t.sol @@ -19,12 +19,14 @@ import {L1NativeTokenVault} from "contracts/bridge/L1NativeTokenVault.sol"; import {L2Message, L2Log, TxStatus, BridgehubL2TransactionRequest} from "contracts/common/Messaging.sol"; import {ETH_TOKEN_ADDRESS, REQUIRED_L2_GAS_PRICE_PER_PUBDATA, MAX_NEW_FACTORY_DEPS} from "contracts/common/Config.sol"; import {L2_NATIVE_TOKEN_VAULT_ADDRESS} from "contracts/common/L2ContractAddresses.sol"; +import {DataEncoding} from "contracts/common/libraries/DataEncoding.sol"; contract ExperimentalBridgeTest is Test { using stdStorage for StdStorage; Bridgehub bridgeHub; address public bridgeOwner; + address public testTokenAddress; DummyStateTransitionManagerWBH mockSTM; DummyHyperchain mockChainContract; DummySharedBridge mockSharedBridge; @@ -50,14 +52,15 @@ contract ExperimentalBridgeTest is Test { mockChainContract = new DummyHyperchain(address(bridgeHub), eraChainId); mockSharedBridge = new DummySharedBridge(keccak256("0xabc")); mockSecondSharedBridge = new DummySharedBridge(keccak256("0xdef")); - ntv = new L1NativeTokenVault(weth, IL1AssetRouter(address(mockSharedBridge)), eraChainId); + ntv = new L1NativeTokenVault(weth, IL1AssetRouter(address(mockSharedBridge))); mockSharedBridge.setNativeTokenVault(ntv); mockSecondSharedBridge.setNativeTokenVault(ntv); testToken = new TestnetERC20Token("ZKSTT", "ZkSync Test Token", 18); + testTokenAddress = address(testToken); vm.prank(address(ntv)); ntv.registerToken(ETH_TOKEN_ADDRESS); ntv.registerToken(address(testToken)); - tokenAssetId = ntv.getAssetId(address(testToken)); + tokenAssetId = DataEncoding.encodeNTVAssetId(block.chainid, address(testToken)); // test if the ownership of the bridgeHub is set correctly or not address defaultOwner = bridgeHub.owner(); @@ -104,7 +107,7 @@ contract ExperimentalBridgeTest is Test { function test_randomCallerCannotSetDeployer(address randomCaller, address randomDeployer) public { if (randomCaller != bridgeHub.owner() && randomCaller != bridgeHub.admin()) { vm.prank(randomCaller); - vm.expectRevert(bytes("Bridgehub: not owner or admin")); + vm.expectRevert(bytes("BH: not owner or admin")); bridgeHub.setPendingAdmin(randomDeployer); // The deployer shouldn't have changed. @@ -124,7 +127,7 @@ contract ExperimentalBridgeTest is Test { // An address that has already been registered, cannot be registered again (at least not before calling `removeStateTransitionManager`). vm.prank(bridgeOwner); - vm.expectRevert(bytes("Bridgehub: state transition already registered")); + vm.expectRevert(bytes("BH: state transition already registered")); bridgeHub.addStateTransitionManager(randomAddressWithoutTheCorrectInterface); isSTMRegistered = bridgeHub.stateTransitionManagerIsRegistered(randomAddressWithoutTheCorrectInterface); @@ -153,7 +156,7 @@ contract ExperimentalBridgeTest is Test { // An address that has already been registered, cannot be registered again (at least not before calling `removeStateTransitionManager`). vm.prank(bridgeOwner); - vm.expectRevert(bytes("Bridgehub: state transition already registered")); + vm.expectRevert(bytes("BH: state transition already registered")); bridgeHub.addStateTransitionManager(randomAddressWithoutTheCorrectInterface); // Definitely not by a random caller @@ -173,7 +176,7 @@ contract ExperimentalBridgeTest is Test { // A non-existent STM cannot be removed vm.prank(bridgeOwner); - vm.expectRevert(bytes("Bridgehub: state transition not registered yet")); + vm.expectRevert(bytes("BH: state transition not registered yet")); bridgeHub.removeStateTransitionManager(randomAddressWithoutTheCorrectInterface); // Let's first register our particular stateTransitionManager @@ -192,7 +195,7 @@ contract ExperimentalBridgeTest is Test { // An already removed STM cannot be removed again vm.prank(bridgeOwner); - vm.expectRevert(bytes("Bridgehub: state transition not registered yet")); + vm.expectRevert(bytes("BH: state transition not registered yet")); bridgeHub.removeStateTransitionManager(randomAddressWithoutTheCorrectInterface); } @@ -212,7 +215,7 @@ contract ExperimentalBridgeTest is Test { // A non-existent STM cannot be removed vm.prank(bridgeOwner); - vm.expectRevert(bytes("Bridgehub: state transition not registered yet")); + vm.expectRevert(bytes("BH: state transition not registered yet")); bridgeHub.removeStateTransitionManager(randomAddressWithoutTheCorrectInterface); // Let's first register our particular stateTransitionManager @@ -231,7 +234,7 @@ contract ExperimentalBridgeTest is Test { // An already removed STM cannot be removed again vm.prank(bridgeOwner); - vm.expectRevert(bytes("Bridgehub: state transition not registered yet")); + vm.expectRevert(bytes("BH: state transition not registered yet")); bridgeHub.removeStateTransitionManager(randomAddressWithoutTheCorrectInterface); // Not possible by a randomcaller as well @@ -242,61 +245,60 @@ contract ExperimentalBridgeTest is Test { } } - function test_addToken(address, address randomAddress) public { - assertTrue(!bridgeHub.tokenIsRegistered(randomAddress), "This random address is not registered as a token"); + function test_addToken(address randomAddress) public { + vm.startPrank(bridgeOwner); + bridgeHub.setSharedBridge(address(mockSharedBridge)); + vm.stopPrank(); + + assertTrue(!bridgeHub.tokenIsRegistered(testTokenAddress), "This random address is not registered as a token"); vm.prank(bridgeOwner); - bridgeHub.addToken(randomAddress); + bridgeHub.addToken(testTokenAddress); assertTrue( - bridgeHub.tokenIsRegistered(randomAddress), + bridgeHub.tokenIsRegistered(testTokenAddress), "after call from the bridgeowner, this randomAddress should be a registered token" ); - if (randomAddress != address(testToken)) { - // Testing to see if an actual ERC20 implementation can also be added or not + if (randomAddress != address(testTokenAddress)) { + // Testing to see if a random address can also be added or not vm.prank(bridgeOwner); - bridgeHub.addToken(address(testToken)); - - assertTrue(bridgeHub.tokenIsRegistered(address(testToken))); + bridgeHub.addToken(address(randomAddress)); + assertTrue(bridgeHub.tokenIsRegistered(randomAddress)); } // An already registered token cannot be registered again vm.prank(bridgeOwner); - vm.expectRevert("Bridgehub: token already registered"); - bridgeHub.addToken(randomAddress); + vm.expectRevert("BH: token already registered"); + bridgeHub.addToken(testTokenAddress); } function test_addToken_cannotBeCalledByRandomAddress(address randomAddress, address randomCaller) public { + vm.startPrank(bridgeOwner); + bridgeHub.setSharedBridge(address(mockSharedBridge)); + vm.stopPrank(); + if (randomCaller != bridgeOwner) { vm.prank(randomCaller); vm.expectRevert(bytes("Ownable: caller is not the owner")); - bridgeHub.addToken(randomAddress); + bridgeHub.addToken(testTokenAddress); } - assertTrue(!bridgeHub.tokenIsRegistered(randomAddress), "This random address is not registered as a token"); + assertTrue(!bridgeHub.tokenIsRegistered(testTokenAddress), "This random address is not registered as a token"); vm.prank(bridgeOwner); - bridgeHub.addToken(randomAddress); + bridgeHub.addToken(testTokenAddress); assertTrue( - bridgeHub.tokenIsRegistered(randomAddress), - "after call from the bridgeowner, this randomAddress should be a registered token" + bridgeHub.tokenIsRegistered(testTokenAddress), + "after call from the bridgeowner, this testTokenAddress should be a registered token" ); - if (randomAddress != address(testToken)) { - // Testing to see if an actual ERC20 implementation can also be added or not - vm.prank(bridgeOwner); - bridgeHub.addToken(address(testToken)); - - assertTrue(bridgeHub.tokenIsRegistered(address(testToken))); - } - // An already registered token cannot be registered again by randomCaller if (randomCaller != bridgeOwner) { vm.prank(bridgeOwner); - vm.expectRevert("Bridgehub: token already registered"); - bridgeHub.addToken(randomAddress); + vm.expectRevert("BH: token already registered"); + bridgeHub.addToken(testTokenAddress); } } @@ -336,8 +338,8 @@ contract ExperimentalBridgeTest is Test { // ); // } - uint256 newChainId; - address admin; + // uint256 newChainId; + // address admin; // function test_createNewChain( // address randomCaller, @@ -351,27 +353,27 @@ contract ExperimentalBridgeTest is Test { // admin = makeAddr("NEW_CHAIN_ADMIN"); // // Diamond.DiamondCutData memory dcData; - // vm.prank(bridgeOwner); - // bridgeHub.setPendingAdmin(deployerAddress); - // vm.prank(deployerAddress); - // bridgeHub.acceptAdmin(); - // vm.startPrank(bridgeOwner); - // bridgeHub.addStateTransitionManager(address(mockSTM)); - // bridgeHub.addToken(address(testToken)); - // bridgeHub.setSharedBridge(address(mockSharedBridge)); - // vm.stopPrank(); + // vm.prank(bridgeOwner); + // bridgeHub.setPendingAdmin(deployerAddress); + // vm.prank(deployerAddress); + // bridgeHub.acceptAdmin(); + // vm.startPrank(bridgeOwner); + // bridgeHub.setSharedBridge(address(mockSharedBridge)); + // bridgeHub.addStateTransitionManager(address(mockSTM)); + // bridgeHub.addToken(testTokenAddress); + // bridgeHub.setSharedBridge(address(mockSharedBridge)); + // vm.stopPrank(); // if (randomCaller != deployerAddress && randomCaller != bridgeOwner) { // vm.prank(randomCaller); - // vm.expectRevert(bytes("Bridgehub: not owner or admin")); + // vm.expectRevert(bytes("BH: not owner or admin")); // bridgeHub.createNewChain({ // _chainId: chainId, // _stateTransitionManager: address(mockSTM), - // _baseToken: address(testToken), + // _baseToken: testTokenAddress, // _salt: uint256(123), // _admin: admin, - // _initData: abi.encode(bytes(""), bytes("")), - // _factoryDeps: new bytes[](0) + // _initData: bytes("") // }); // } @@ -389,36 +391,35 @@ contract ExperimentalBridgeTest is Test { // mockSTM.setHyperchain(chainId, address(mockChainContract)); // assertTrue(mockSTM.getHyperchain(chainId) == address(mockChainContract)); - // vm.startPrank(deployerAddress); - // vm.mockCall( - // address(mockSTM), - // // solhint-disable-next-line func-named-parameters - // abi.encodeWithSelector( - // mockSTM.createNewChain.selector, - // chainId, - // address(testToken), - // address(mockSharedBridge), - // admin, - // _newChainInitData - // ), - // bytes("") - // ); + // vm.startPrank(deployerAddress); + // vm.mockCall( + // address(mockSTM), + // // solhint-disable-next-line func-named-parameters + // abi.encodeWithSelector( + // mockSTM.createNewChain.selector, + // chainId, + // testTokenAddress, + // address(mockSharedBridge), + // admin, + // _newChainInitData + // ), + // bytes("") + // ); // newChainId = bridgeHub.createNewChain({ // _chainId: chainId, // _stateTransitionManager: address(mockSTM), - // _baseToken: address(testToken), + // _baseToken: testTokenAddress, // _salt: uint256(chainId * 2), // _admin: admin, - // _initData: _newChainInitData, - // _factoryDeps: new bytes[](0) + // _initData: _newChainInitData // }); // vm.stopPrank(); // vm.clearMockedCalls(); // assertTrue(bridgeHub.stateTransitionManager(newChainId) == address(mockSTM)); - // assertTrue(bridgeHub.baseToken(newChainId) == address(testToken)); + // assertTrue(bridgeHub.baseToken(newChainId) == testTokenAddress); // } // function test_getHyperchain(uint256 mockChainId) public { @@ -734,7 +735,7 @@ contract ExperimentalBridgeTest is Test { // vm.deal(randomCaller, 1 ether); // vm.prank(randomCaller); - // vm.expectRevert("Bridgehub: non-eth bridge with msg.value"); + // vm.expectRevert("BH: non-eth bridge with msg.value"); // bytes32 resultantHash = bridgeHub.requestL2TransactionDirect{value: randomCaller.balance}(l2TxnReqDirect); // // Now, let's call the same function with zero msg.value diff --git a/l1-contracts/test/foundry/unit/concrete/Bridges/L1Erc20Bridge/Getters.t.sol b/l1-contracts/test/foundry/unit/concrete/Bridges/L1Erc20Bridge/Getters.t.sol index a9913f344..679dd70dd 100644 --- a/l1-contracts/test/foundry/unit/concrete/Bridges/L1Erc20Bridge/Getters.t.sol +++ b/l1-contracts/test/foundry/unit/concrete/Bridges/L1Erc20Bridge/Getters.t.sol @@ -12,7 +12,7 @@ contract GettersTest is L1Erc20BridgeTest { address daiOnEthereum = 0x6B175474E89094C44Da98b954EedeAC495271d0F; address daiOnEra = 0x4B9eb6c0b6ea15176BBF62841C6B2A8a398cb656; - stdstore.target(address(bridge)).sig("l2NativeTokenVault()").checked_write( + stdstore.target(address(bridge)).sig("l2Bridge()").checked_write( address(0x11f943b2c77b743AB90f4A0Ae7d5A4e7FCA3E102) ); diff --git a/l1-contracts/test/foundry/unit/concrete/Bridges/L1Erc20Bridge/_L1Erc20Bridge_Shared.t.sol b/l1-contracts/test/foundry/unit/concrete/Bridges/L1Erc20Bridge/_L1Erc20Bridge_Shared.t.sol index 5be79002f..f88026e70 100644 --- a/l1-contracts/test/foundry/unit/concrete/Bridges/L1Erc20Bridge/_L1Erc20Bridge_Shared.t.sol +++ b/l1-contracts/test/foundry/unit/concrete/Bridges/L1Erc20Bridge/_L1Erc20Bridge_Shared.t.sol @@ -34,15 +34,20 @@ contract L1Erc20BridgeTest is Test { alice = makeAddr("alice"); dummySharedBridge = new DummySharedBridge(dummyL2DepositTxHash); - bridge = new L1ERC20Bridge(IL1AssetRouter(address(dummySharedBridge)), IL1NativeTokenVault(address(1))); - uint256 eraChainId = 1; + uint256 eraChainId = 9; + bridge = new L1ERC20Bridge( + IL1AssetRouter(address(dummySharedBridge)), + IL1NativeTokenVault(address(1)), + eraChainId + ); + address weth = makeAddr("weth"); - L1NativeTokenVault ntv = new L1NativeTokenVault(weth, IL1AssetRouter(address(dummySharedBridge)), eraChainId); + L1NativeTokenVault ntv = new L1NativeTokenVault(weth, IL1AssetRouter(address(dummySharedBridge))); vm.store(address(bridge), bytes32(uint256(212)), bytes32(0)); reenterL1ERC20Bridge = new ReenterL1ERC20Bridge(); - bridgeReenterItself = new L1ERC20Bridge(IL1AssetRouter(address(reenterL1ERC20Bridge)), ntv); + bridgeReenterItself = new L1ERC20Bridge(IL1AssetRouter(address(reenterL1ERC20Bridge)), ntv, eraChainId); reenterL1ERC20Bridge.setBridge(bridgeReenterItself); token = new TestnetERC20Token("TestnetERC20Token", "TET", 18); diff --git a/l1-contracts/test/foundry/unit/concrete/Bridges/L1SharedBridge/L1SharedBridgeBase.t.sol b/l1-contracts/test/foundry/unit/concrete/Bridges/L1SharedBridge/L1SharedBridgeBase.t.sol index 62430cad8..3ced5fb4e 100644 --- a/l1-contracts/test/foundry/unit/concrete/Bridges/L1SharedBridge/L1SharedBridgeBase.t.sol +++ b/l1-contracts/test/foundry/unit/concrete/Bridges/L1SharedBridge/L1SharedBridgeBase.t.sol @@ -84,36 +84,11 @@ contract L1AssetRouterTestBase is L1AssetRouterTest { bytes32 txDataHash = keccak256(abi.encode(alice, ETH_TOKEN_ADDRESS, amount)); bytes memory mintCalldata = abi.encode( - amount, alice, bob, - nativeTokenVault.getERC20Getters(address(ETH_TOKEN_ADDRESS)), - address(ETH_TOKEN_ADDRESS) - ); - // solhint-disable-next-line func-named-parameters - vm.expectEmit(true, true, true, true, address(sharedBridge)); - vm.prank(bridgehubAddress); - emit BridgehubDepositInitiated({ - chainId: chainId, - txDataHash: txDataHash, - from: alice, - assetId: ETH_TOKEN_ASSET_ID, - bridgeMintCalldata: mintCalldata - }); - sharedBridge.bridgehubDeposit{value: amount}(chainId, alice, 0, abi.encode(ETH_TOKEN_ADDRESS, amount, bob)); - } - - function test_bridgehubDeposit_Eth_NewEncoding() public { - _setBaseTokenAssetId(tokenAssetId); - - bytes memory transferData = abi.encode(amount, bob); - bytes32 txDataHash = keccak256(bytes.concat(bytes1(0x01), abi.encode(alice, ETH_TOKEN_ASSET_ID, transferData))); - bytes memory mintCalldata = abi.encode( + address(ETH_TOKEN_ADDRESS), amount, - alice, - bob, - nativeTokenVault.getERC20Getters(address(ETH_TOKEN_ADDRESS)), - address(ETH_TOKEN_ADDRESS) + nativeTokenVault.getERC20Getters(address(ETH_TOKEN_ADDRESS)) ); // solhint-disable-next-line func-named-parameters vm.expectEmit(true, true, true, true, address(sharedBridge)); @@ -125,12 +100,7 @@ contract L1AssetRouterTestBase is L1AssetRouterTest { assetId: ETH_TOKEN_ASSET_ID, bridgeMintCalldata: mintCalldata }); - sharedBridge.bridgehubDeposit{value: amount}( - chainId, - alice, - 0, - bytes.concat(bytes1(0x01), abi.encode(ETH_TOKEN_ASSET_ID, transferData)) - ); + sharedBridge.bridgehubDeposit{value: amount}(chainId, alice, 0, abi.encode(ETH_TOKEN_ADDRESS, amount, bob)); } function test_bridgehubDeposit_Erc() public { @@ -200,7 +170,7 @@ contract L1AssetRouterTestBase is L1AssetRouterTest { sharedBridge.claimFailedDeposit({ _chainId: chainId, _depositSender: alice, - _l1Token: address(token), + _l1Asset: address(token), _amount: amount, _l2TxHash: txHash, _l2BatchNumber: l2BatchNumber, @@ -242,7 +212,7 @@ contract L1AssetRouterTestBase is L1AssetRouterTest { sharedBridge.claimFailedDeposit({ _chainId: chainId, _depositSender: alice, - _l1Token: ETH_TOKEN_ADDRESS, + _l1Asset: ETH_TOKEN_ADDRESS, _amount: amount, _l2TxHash: txHash, _l2BatchNumber: l2BatchNumber, @@ -286,7 +256,7 @@ contract L1AssetRouterTestBase is L1AssetRouterTest { _chainId: chainId, _depositSender: alice, _assetId: ETH_TOKEN_ASSET_ID, - _transferData: transferData, + _assetData: transferData, _l2TxHash: txHash, _l2BatchNumber: l2BatchNumber, _l2MessageIndex: l2MessageIndex, @@ -508,7 +478,7 @@ contract L1AssetRouterTestBase is L1AssetRouterTest { vm.expectEmit(true, true, false, true, address(token)); emit IERC20.Transfer(address(sharedBridge), address(nativeTokenVault), amount); nativeTokenVault.transferFundsFromSharedBridge(address(token)); - nativeTokenVault.transferBalancesFromSharedBridge(address(token), chainId); + nativeTokenVault.updateChainBalancesFromSharedBridge(address(token), chainId); uint256 endBalanceNtv = nativeTokenVault.chainBalance(chainId, address(token)); assertEq(endBalanceNtv - startBalanceNtv, amount); } @@ -517,7 +487,7 @@ contract L1AssetRouterTestBase is L1AssetRouterTest { uint256 startEthBalanceNtv = address(nativeTokenVault).balance; uint256 startBalanceNtv = nativeTokenVault.chainBalance(chainId, ETH_TOKEN_ADDRESS); nativeTokenVault.transferFundsFromSharedBridge(ETH_TOKEN_ADDRESS); - nativeTokenVault.transferBalancesFromSharedBridge(ETH_TOKEN_ADDRESS, chainId); + nativeTokenVault.updateChainBalancesFromSharedBridge(ETH_TOKEN_ADDRESS, chainId); uint256 endBalanceNtv = nativeTokenVault.chainBalance(chainId, ETH_TOKEN_ADDRESS); uint256 endEthBalanceNtv = address(nativeTokenVault).balance; assertEq(endBalanceNtv - startBalanceNtv, amount); diff --git a/l1-contracts/test/foundry/unit/concrete/Bridges/L1SharedBridge/L1SharedBridgeFails.t.sol b/l1-contracts/test/foundry/unit/concrete/Bridges/L1SharedBridge/L1SharedBridgeFails.t.sol index d1d02a15c..ccfac0b39 100644 --- a/l1-contracts/test/foundry/unit/concrete/Bridges/L1SharedBridge/L1SharedBridgeFails.t.sol +++ b/l1-contracts/test/foundry/unit/concrete/Bridges/L1SharedBridge/L1SharedBridgeFails.t.sol @@ -26,7 +26,7 @@ contract L1AssetRouterFailTest is L1AssetRouterTest { using stdStorage for StdStorage; function test_initialize_WrongOwner() public { - vm.expectRevert("ShB owner 0"); + vm.expectRevert("L1AR: owner 0"); new TransparentUpgradeableProxy( address(sharedBridgeImpl), admin, @@ -53,13 +53,13 @@ contract L1AssetRouterFailTest is L1AssetRouterTest { } function test_transferTokenToNTV_wrongCaller() public { - vm.expectRevert("ShB: not NTV"); + vm.expectRevert("L1AR: not NTV"); sharedBridge.transferTokenToNTV(address(token)); } - function test_clearChainBalance_wrongCaller() public { - vm.expectRevert("ShB: not NTV"); - sharedBridge.clearChainBalance(chainId, address(token)); + function test_nullifyChainBalanceByNTV_wrongCaller() public { + vm.expectRevert("L1AR: not NTV"); + sharedBridge.nullifyChainBalanceByNTV(chainId, address(token)); } function test_registerToken_noCode() public { @@ -69,27 +69,27 @@ contract L1AssetRouterFailTest is L1AssetRouterTest { function test_setL1Erc20Bridge_alreadySet() public { vm.prank(owner); - vm.expectRevert("ShB: legacy bridge already set"); + vm.expectRevert("L1AR: legacy bridge already set"); sharedBridge.setL1Erc20Bridge(address(0)); } function test_setL1Erc20Bridge_emptyAddressProvided() public { stdstore.target(address(sharedBridge)).sig(sharedBridge.legacyBridge.selector).checked_write(address(0)); vm.prank(owner); - vm.expectRevert("ShB: legacy bridge 0"); + vm.expectRevert("L1AR: legacy bridge 0"); sharedBridge.setL1Erc20Bridge(address(0)); } function test_setNativeTokenVault_alreadySet() public { vm.prank(owner); - vm.expectRevert("ShB: native token vault already set"); + vm.expectRevert("L1AR: native token vault already set"); sharedBridge.setNativeTokenVault(IL1NativeTokenVault(address(0))); } function test_setNativeTokenVault_emptyAddressProvided() public { stdstore.target(address(sharedBridge)).sig(sharedBridge.nativeTokenVault.selector).checked_write(address(0)); vm.prank(owner); - vm.expectRevert("ShB: native token vault 0"); + vm.expectRevert("L1AR: native token vault 0"); sharedBridge.setNativeTokenVault(IL1NativeTokenVault(address(0))); } @@ -99,7 +99,7 @@ contract L1AssetRouterFailTest is L1AssetRouterTest { address refundRecipient = address(0); vm.prank(alice); - vm.expectRevert("ShB: only ADT or owner"); + vm.expectRevert("L1AR: only ADT or owner"); sharedBridge.setAssetHandlerAddressOnCounterPart( eraChainId, mintValue, @@ -114,14 +114,14 @@ contract L1AssetRouterFailTest is L1AssetRouterTest { // function test_transferFundsToSharedBridge_Eth_CallFailed() public { // vm.mockCall(address(nativeTokenVault), "0x", abi.encode("")); // vm.prank(address(nativeTokenVault)); - // vm.expectRevert("ShB: eth transfer failed"); + // vm.expectRevert("L1AR: eth transfer failed"); // nativeTokenVault.transferFundsFromSharedBridge(ETH_TOKEN_ADDRESS); // } // function test_transferFundsToSharedBridge_Eth_CallFailed() public { // vm.mockCall(address(nativeTokenVault), "0x", abi.encode("")); // vm.prank(address(nativeTokenVault)); - // vm.expectRevert("ShB: eth transfer failed"); + // vm.expectRevert("L1AR: eth transfer failed"); // nativeTokenVault.transferFundsFromSharedBridge(ETH_TOKEN_ADDRESS); // } @@ -147,20 +147,8 @@ contract L1AssetRouterFailTest is L1AssetRouterTest { nativeTokenVault.transferFundsFromSharedBridge(address(token)); } - function test_bridgehubDepositBaseToken_Eth_Token_notRegisteredTokenID() public { - // ToDo: Shall we do it properly instead of mocking? - stdstore - .target(address(sharedBridge)) - .sig("assetHandlerAddress(bytes32)") - .with_key(ETH_TOKEN_ASSET_ID) - .checked_write(address(0)); - vm.prank(bridgehubAddress); - vm.expectRevert("ShB: only address can be registered"); - sharedBridge.bridgehubDepositBaseToken{value: amount}(chainId, ETH_TOKEN_ASSET_ID, alice, amount); - } - function test_bridgehubDepositBaseToken_Eth_Token_incorrectSender() public { - vm.expectRevert("L1AssetRouter: msg.sender not equal to bridgehub or era chain"); + vm.expectRevert("L1AR: msg.sender not equal to bridgehub or era chain"); sharedBridge.bridgehubDepositBaseToken{value: amount}(chainId, ETH_TOKEN_ASSET_ID, alice, amount); } @@ -195,7 +183,7 @@ contract L1AssetRouterFailTest is L1AssetRouterTest { function test_bridgehubDeposit_Eth_baseToken() public { vm.prank(bridgehubAddress); - vm.expectRevert("ShB: baseToken deposit not supported"); + vm.expectRevert("L1AR: baseToken deposit not supported"); // solhint-disable-next-line func-named-parameters sharedBridge.bridgehubDeposit(chainId, alice, 0, abi.encode(ETH_TOKEN_ADDRESS, 0, bob)); } @@ -239,7 +227,7 @@ contract L1AssetRouterFailTest is L1AssetRouterTest { bytes32 txDataHash = keccak256(abi.encode(alice, address(token), amount)); _setSharedBridgeDepositHappened(chainId, txHash, txDataHash); vm.prank(bridgehubAddress); - vm.expectRevert("ShB tx hap"); + vm.expectRevert("L1AR: tx hap"); sharedBridge.bridgehubConfirmL2Transaction(chainId, txDataHash, txHash); } @@ -305,7 +293,7 @@ contract L1AssetRouterFailTest is L1AssetRouterTest { _chainId: chainId, _depositSender: alice, _assetId: ETH_TOKEN_ASSET_ID, - _transferData: transferData, + _assetData: transferData, _l2TxHash: txHash, _l2BatchNumber: l2BatchNumber, _l2MessageIndex: l2MessageIndex, @@ -338,12 +326,12 @@ contract L1AssetRouterFailTest is L1AssetRouterTest { abi.encode(true) ); - vm.expectRevert("ShB: last deposit time not set for Era"); + vm.expectRevert("L1AR: last deposit time not set for Era"); sharedBridge.bridgeRecoverFailedTransfer({ _chainId: eraChainId, _depositSender: alice, _assetId: ETH_TOKEN_ASSET_ID, - _transferData: transferData, + _assetData: transferData, _l2TxHash: txHash, _l2BatchNumber: l2BatchNumber, _l2MessageIndex: l2MessageIndex, @@ -377,12 +365,12 @@ contract L1AssetRouterFailTest is L1AssetRouterTest { abi.encode(true) ); - vm.expectRevert("ShB: legacy cFD"); + vm.expectRevert("L1AR: legacy cFD"); sharedBridge.bridgeRecoverFailedTransfer({ _chainId: eraChainId, _depositSender: alice, _assetId: ETH_TOKEN_ASSET_ID, - _transferData: transferData, + _assetData: transferData, _l2TxHash: txHash, _l2BatchNumber: l2BatchNumber, _l2MessageIndex: l2MessageIndex, @@ -403,7 +391,7 @@ contract L1AssetRouterFailTest is L1AssetRouterTest { sharedBridge.claimFailedDeposit({ _chainId: chainId, _depositSender: alice, - _l1Token: ETH_TOKEN_ADDRESS, + _l1Asset: ETH_TOKEN_ADDRESS, _amount: amount, _l2TxHash: txHash, _l2BatchNumber: l2BatchNumber, @@ -437,7 +425,7 @@ contract L1AssetRouterFailTest is L1AssetRouterTest { sharedBridge.claimFailedDeposit({ _chainId: chainId, _depositSender: alice, - _l1Token: ETH_TOKEN_ADDRESS, + _l1Asset: ETH_TOKEN_ADDRESS, _amount: 0, _l2TxHash: txHash, _l2BatchNumber: l2BatchNumber, @@ -466,11 +454,11 @@ contract L1AssetRouterFailTest is L1AssetRouterTest { abi.encode(true) ); - vm.expectRevert("ShB: d.it not hap"); + vm.expectRevert("L1AR: d.it not hap"); sharedBridge.claimFailedDeposit({ _chainId: chainId, _depositSender: alice, - _l1Token: ETH_TOKEN_ADDRESS, + _l1Asset: ETH_TOKEN_ADDRESS, _amount: amount, _l2TxHash: txHash, _l2BatchNumber: l2BatchNumber, @@ -503,11 +491,11 @@ contract L1AssetRouterFailTest is L1AssetRouterTest { abi.encode(true) ); - vm.expectRevert("NTV n funds"); + vm.expectRevert("NTV: not enough funds 2"); sharedBridge.claimFailedDeposit({ _chainId: chainId, _depositSender: alice, - _l1Token: ETH_TOKEN_ADDRESS, + _l1Asset: ETH_TOKEN_ADDRESS, _amount: amount, _l2TxHash: txHash, _l2BatchNumber: l2BatchNumber, @@ -534,7 +522,7 @@ contract L1AssetRouterFailTest is L1AssetRouterTest { amount ); - vm.expectRevert("ShB: legacy eth withdrawal"); + vm.expectRevert("L1AR: legacy eth withdrawal"); sharedBridge.finalizeWithdrawal({ _chainId: eraChainId, _l2BatchNumber: legacyBatchNumber, @@ -578,7 +566,7 @@ contract L1AssetRouterFailTest is L1AssetRouterTest { amount ); - vm.expectRevert("Withdrawal is already finalized"); + vm.expectRevert("L1AR: Withdrawal is already finalized"); sharedBridge.finalizeWithdrawal({ _chainId: eraChainId, _l2BatchNumber: legacyBatchNumber, @@ -599,7 +587,7 @@ contract L1AssetRouterFailTest is L1AssetRouterTest { address(token), amount ); - vm.expectRevert("ShB: legacy eth withdrawal"); + vm.expectRevert("L1AR: legacy eth withdrawal"); sharedBridge.finalizeWithdrawal({ _chainId: eraChainId, @@ -621,7 +609,7 @@ contract L1AssetRouterFailTest is L1AssetRouterTest { address(token), amount ); - vm.expectRevert("ShB: diamondUFB not set for Era"); + vm.expectRevert("L1AR: diamondUFB not set for Era"); sharedBridge.finalizeWithdrawal({ _chainId: eraChainId, @@ -643,7 +631,7 @@ contract L1AssetRouterFailTest is L1AssetRouterTest { address(token), amount ); - vm.expectRevert("ShB: legacy token withdrawal"); + vm.expectRevert("L1AR: legacy token withdrawal"); sharedBridge.finalizeWithdrawal({ _chainId: eraChainId, @@ -665,7 +653,7 @@ contract L1AssetRouterFailTest is L1AssetRouterTest { address(token), amount ); - vm.expectRevert("ShB: LegacyUFB not set for Era"); + vm.expectRevert("L1AR: LegacyUFB not set for Era"); sharedBridge.finalizeWithdrawal({ _chainId: eraChainId, @@ -700,7 +688,7 @@ contract L1AssetRouterFailTest is L1AssetRouterTest { ); _setNativeTokenVaultChainBalance(chainId, ETH_TOKEN_ADDRESS, 0); - vm.expectRevert("NTV not enough funds 2"); + vm.expectRevert("NTV: not enough funds"); sharedBridge.finalizeWithdrawal({ _chainId: chainId, @@ -734,7 +722,7 @@ contract L1AssetRouterFailTest is L1AssetRouterTest { abi.encode(false) ); - vm.expectRevert("ShB withd w proof"); + vm.expectRevert("L1AR: withd w proof"); sharedBridge.finalizeWithdrawal({ _chainId: chainId, @@ -749,7 +737,7 @@ contract L1AssetRouterFailTest is L1AssetRouterTest { function test_parseL2WithdrawalMessage_wrongMsgLength() public { bytes memory message = abi.encodePacked(IMailbox.finalizeEthWithdrawal.selector); - vm.expectRevert("ShB wrong msg len"); + vm.expectRevert("L1AR: wrong msg len"); sharedBridge.finalizeWithdrawal({ _chainId: chainId, _l2BatchNumber: l2BatchNumber, @@ -763,7 +751,7 @@ contract L1AssetRouterFailTest is L1AssetRouterTest { function test_parseL2WithdrawalMessage_wrongMsgLength2() public { bytes memory message = abi.encodePacked(IL1ERC20Bridge.finalizeWithdrawal.selector, abi.encode(amount, token)); - vm.expectRevert("ShB wrong msg len 2"); + vm.expectRevert("L1AR: wrong msg len 2"); sharedBridge.finalizeWithdrawal({ _chainId: chainId, _l2BatchNumber: l2BatchNumber, @@ -778,7 +766,7 @@ contract L1AssetRouterFailTest is L1AssetRouterTest { // notice that the selector is wrong bytes memory message = abi.encodePacked(IMailbox.proveL2LogInclusion.selector, alice, amount); - vm.expectRevert("ShB Incorrect message function selector"); + vm.expectRevert("L1AR: Incorrect message function selector"); sharedBridge.finalizeWithdrawal({ _chainId: eraChainId, _l2BatchNumber: l2BatchNumber, @@ -794,7 +782,7 @@ contract L1AssetRouterFailTest is L1AssetRouterTest { uint256 l2TxGasPerPubdataByte = 100; address refundRecipient = address(0); - vm.expectRevert("ShB: WETH deposit not supported 2"); + vm.expectRevert("L1AR: WETH deposit not supported 2"); vm.prank(l1ERC20BridgeAddress); sharedBridge.depositLegacyErc20Bridge({ _prevMsgSender: alice, diff --git a/l1-contracts/test/foundry/unit/concrete/Bridges/L1SharedBridge/L1SharedBridgeHyperEnabled.t.sol b/l1-contracts/test/foundry/unit/concrete/Bridges/L1SharedBridge/L1SharedBridgeHyperEnabled.t.sol index f2a08bdd6..c0a170689 100644 --- a/l1-contracts/test/foundry/unit/concrete/Bridges/L1SharedBridge/L1SharedBridgeHyperEnabled.t.sol +++ b/l1-contracts/test/foundry/unit/concrete/Bridges/L1SharedBridge/L1SharedBridgeHyperEnabled.t.sol @@ -123,7 +123,7 @@ contract L1AssetRouterHyperEnabledTest is L1AssetRouterTest { sharedBridge.claimFailedDeposit({ _chainId: chainId, _depositSender: alice, - _l1Token: address(token), + _l1Asset: address(token), _amount: amount, _l2TxHash: txHash, _l2BatchNumber: l2BatchNumber, @@ -141,7 +141,7 @@ contract L1AssetRouterHyperEnabledTest is L1AssetRouterTest { // Bridgehub bridgehub = new Bridgehub(); // vm.store(address(bridgehub), bytes32(uint256(5 +2)), bytes32(uint256(31337))); - // require(address(bridgehub.deployer()) == address(31337), "Bridgehub: deployer wrong"); + // require(address(bridgehub.deployer()) == address(31337), "BH: deployer wrong"); vm.mockCall( bridgehubAddress, @@ -166,7 +166,7 @@ contract L1AssetRouterHyperEnabledTest is L1AssetRouterTest { sharedBridge.claimFailedDeposit({ _chainId: chainId, _depositSender: alice, - _l1Token: ETH_TOKEN_ADDRESS, + _l1Asset: ETH_TOKEN_ADDRESS, _amount: amount, _l2TxHash: txHash, _l2BatchNumber: l2BatchNumber, diff --git a/l1-contracts/test/foundry/unit/concrete/Bridges/L1SharedBridge/L1SharedBridgeLegacy.t.sol b/l1-contracts/test/foundry/unit/concrete/Bridges/L1SharedBridge/L1SharedBridgeLegacy.t.sol index 4a3753ab4..84ab99ab7 100644 --- a/l1-contracts/test/foundry/unit/concrete/Bridges/L1SharedBridge/L1SharedBridgeLegacy.t.sol +++ b/l1-contracts/test/foundry/unit/concrete/Bridges/L1SharedBridge/L1SharedBridgeLegacy.t.sol @@ -155,7 +155,7 @@ contract L1AssetRouterLegacyTest is L1AssetRouterTest { // Bridgehub bridgehub = new Bridgehub(); // vm.store(address(bridgehub), bytes32(uint256(5 +2)), bytes32(uint256(31337))); - // require(address(bridgehub.deployer()) == address(31337), "Bridgehub: deployer wrong"); + // require(address(bridgehub.deployer()) == address(31337), "BH: deployer wrong"); vm.store( address(sharedBridge), keccak256(abi.encode(tokenAssetId, isWithdrawalFinalizedStorageLocation + 2)), @@ -187,7 +187,8 @@ contract L1AssetRouterLegacyTest is L1AssetRouterTest { emit ClaimedFailedDepositSharedBridge(eraChainId, alice, (tokenAssetId), abi.encode(bytes32(0))); vm.prank(l1ERC20BridgeAddress); - sharedBridge.claimFailedDepositLegacyErc20Bridge({ + sharedBridge.claimFailedDeposit({ + _chainId: eraChainId, _depositSender: alice, _l1Asset: address(token), _amount: amount, diff --git a/l1-contracts/test/foundry/unit/concrete/Bridges/L1SharedBridge/_L1SharedBridge_Shared.t.sol b/l1-contracts/test/foundry/unit/concrete/Bridges/L1SharedBridge/_L1SharedBridge_Shared.t.sol index 6f516a2d4..3c9b90e5b 100644 --- a/l1-contracts/test/foundry/unit/concrete/Bridges/L1SharedBridge/_L1SharedBridge_Shared.t.sol +++ b/l1-contracts/test/foundry/unit/concrete/Bridges/L1SharedBridge/_L1SharedBridge_Shared.t.sol @@ -15,6 +15,7 @@ import {L1NativeTokenVault} from "contracts/bridge/L1NativeTokenVault.sol"; import {IL1NativeTokenVault} from "contracts/bridge/interfaces/IL1NativeTokenVault.sol"; import {ETH_TOKEN_ADDRESS} from "contracts/common/Config.sol"; import {L2_NATIVE_TOKEN_VAULT_ADDRESS, L2_ASSET_ROUTER_ADDR} from "contracts/common/L2ContractAddresses.sol"; +import {DataEncoding} from "contracts/common/libraries/DataEncoding.sol"; contract L1AssetRouterTest is Test { using stdStorage for StdStorage; @@ -98,10 +99,7 @@ contract L1AssetRouterTest is Test { uint256 legacyBatchNumber = 0; uint256 isWithdrawalFinalizedStorageLocation = uint256(8 - 1 + (1 + 49) + 0 + (1 + 49) + 50 + 1 + 50); - bytes32 ETH_TOKEN_ASSET_ID = - keccak256( - abi.encode(block.chainid, L2_NATIVE_TOKEN_VAULT_ADDRESS, bytes32(uint256(uint160(ETH_TOKEN_ADDRESS)))) - ); + bytes32 ETH_TOKEN_ASSET_ID = keccak256(abi.encode(block.chainid, L2_NATIVE_TOKEN_VAULT_ADDRESS, ETH_TOKEN_ADDRESS)); function setUp() public { owner = makeAddr("owner"); @@ -142,8 +140,7 @@ contract L1AssetRouterTest is Test { sharedBridge = L1AssetRouter(payable(sharedBridgeProxy)); nativeTokenVaultImpl = new L1NativeTokenVault({ _l1WethAddress: l1WethAddress, - _l1SharedBridge: IL1AssetRouter(address(sharedBridge)), - _eraChainId: eraChainId + _l1SharedBridge: IL1AssetRouter(address(sharedBridge)) }); TransparentUpgradeableProxy nativeTokenVaultProxy = new TransparentUpgradeableProxy( address(nativeTokenVaultImpl), @@ -153,7 +150,7 @@ contract L1AssetRouterTest is Test { nativeTokenVault = L1NativeTokenVault(payable(nativeTokenVaultProxy)); vm.prank(owner); sharedBridge.setL1Erc20Bridge(l1ERC20BridgeAddress); - tokenAssetId = nativeTokenVault.getAssetId(address(token)); + tokenAssetId = DataEncoding.encodeNTVAssetId(block.chainid, address(token)); vm.prank(owner); sharedBridge.setNativeTokenVault(IL1NativeTokenVault(address(nativeTokenVault))); vm.prank(address(nativeTokenVault)); diff --git a/l1-contracts/test/foundry/unit/concrete/Executor/_Executor_Shared.t.sol b/l1-contracts/test/foundry/unit/concrete/Executor/_Executor_Shared.t.sol index 39a896b03..3325b9343 100644 --- a/l1-contracts/test/foundry/unit/concrete/Executor/_Executor_Shared.t.sol +++ b/l1-contracts/test/foundry/unit/concrete/Executor/_Executor_Shared.t.sol @@ -94,7 +94,7 @@ contract ExecutorTest is Test { selectors[6] = getters.getTotalPriorityTxs.selector; selectors[7] = getters.getFirstUnprocessedPriorityTx.selector; selectors[8] = getters.getPriorityQueueSize.selector; - selectors[9] = getters.priorityQueueFrontOperation.selector; + selectors[9] = getters.getTotalBatchesExecuted.selector; selectors[10] = getters.isValidator.selector; selectors[11] = getters.l2LogsRootHash.selector; selectors[12] = getters.storedBatchHash.selector; @@ -112,7 +112,6 @@ contract ExecutorTest is Test { selectors[24] = getters.isFacetFreezable.selector; selectors[25] = getters.getTotalBatchesCommitted.selector; selectors[26] = getters.getTotalBatchesVerified.selector; - selectors[27] = getters.getTotalBatchesExecuted.selector; return selectors; } diff --git a/l1-contracts/test/foundry/unit/concrete/Utils/Utils.sol b/l1-contracts/test/foundry/unit/concrete/Utils/Utils.sol index 7b7ad25be..f96ed4ed6 100644 --- a/l1-contracts/test/foundry/unit/concrete/Utils/Utils.sol +++ b/l1-contracts/test/foundry/unit/concrete/Utils/Utils.sol @@ -208,7 +208,7 @@ library Utils { } function getGettersSelectors() public pure returns (bytes4[] memory) { - bytes4[] memory selectors = new bytes4[](29); + bytes4[] memory selectors = new bytes4[](30); selectors[0] = GettersFacet.getVerifier.selector; selectors[1] = GettersFacet.getAdmin.selector; selectors[2] = GettersFacet.getPendingAdmin.selector; @@ -218,7 +218,7 @@ library Utils { selectors[6] = GettersFacet.getTotalPriorityTxs.selector; selectors[7] = GettersFacet.getFirstUnprocessedPriorityTx.selector; selectors[8] = GettersFacet.getPriorityQueueSize.selector; - selectors[9] = GettersFacet.priorityQueueFrontOperation.selector; + selectors[9] = GettersFacet.getL2SystemContractsUpgradeTxHash.selector; selectors[10] = GettersFacet.isValidator.selector; selectors[11] = GettersFacet.l2LogsRootHash.selector; selectors[12] = GettersFacet.storedBatchHash.selector; @@ -237,7 +237,7 @@ library Utils { selectors[25] = GettersFacet.getTotalBatchesCommitted.selector; selectors[26] = GettersFacet.getTotalBatchesVerified.selector; selectors[27] = GettersFacet.getTotalBatchesExecuted.selector; - selectors[28] = GettersFacet.getL2SystemContractsUpgradeTxHash.selector; + selectors[28] = GettersFacet.getProtocolVersion.selector; return selectors; } diff --git a/l1-contracts/test/foundry/unit/concrete/state-transition/StateTransitionManager/Admin.t.sol b/l1-contracts/test/foundry/unit/concrete/state-transition/StateTransitionManager/Admin.t.sol new file mode 100644 index 000000000..a214c0374 --- /dev/null +++ b/l1-contracts/test/foundry/unit/concrete/state-transition/StateTransitionManager/Admin.t.sol @@ -0,0 +1,34 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.24; + +import {IStateTransitionManager} from "contracts/state-transition/IStateTransitionManager.sol"; +import {StateTransitionManagerTest} from "./_StateTransitionManager_Shared.t.sol"; + +contract AdminTest is StateTransitionManagerTest { + function test_setPendingAdmin() public { + address newAdmin = makeAddr("newAdmin"); + + vm.expectEmit(true, true, true, false); + emit IStateTransitionManager.NewPendingAdmin(address(0), newAdmin); + chainContractAddress.setPendingAdmin(newAdmin); + } + + function test_acceptPendingAdmin() public { + address newAdmin = makeAddr("newAdmin"); + + chainContractAddress.setPendingAdmin(newAdmin); + + // Need this because in shared setup we start a prank as the governor + vm.stopPrank(); + vm.prank(newAdmin); + vm.expectEmit(true, true, true, false); + emit IStateTransitionManager.NewPendingAdmin(newAdmin, address(0)); + vm.expectEmit(true, true, true, false); + emit IStateTransitionManager.NewAdmin(address(0), newAdmin); + chainContractAddress.acceptAdmin(); + + address currentAdmin = chainContractAddress.admin(); + + assertEq(currentAdmin, newAdmin); + } +} diff --git a/l1-contracts/test/foundry/unit/concrete/state-transition/StateTransitionManager/CreateNewChain.t.sol b/l1-contracts/test/foundry/unit/concrete/state-transition/StateTransitionManager/CreateNewChain.t.sol index 86239cfd9..7eef43b79 100644 --- a/l1-contracts/test/foundry/unit/concrete/state-transition/StateTransitionManager/CreateNewChain.t.sol +++ b/l1-contracts/test/foundry/unit/concrete/state-transition/StateTransitionManager/CreateNewChain.t.sol @@ -28,13 +28,24 @@ contract createNewChainTest is StateTransitionManagerTest { }); } - // function test_SuccessfulCreationOfNewChain() public { - // createNewChain(getDiamondCutData(diamondInit)); + function test_SuccessfulCreationOfNewChain() public { + createNewChain(getDiamondCutData(diamondInit)); - // address admin = chainContractAddress.getChainAdmin(chainId); - // address newChainAddress = chainContractAddress.getHyperchain(chainId); + address admin = chainContractAddress.getChainAdmin(chainId); + address newChainAddress = chainContractAddress.getHyperchain(chainId); - // assertEq(newChainAdmin, admin); - // assertNotEq(newChainAddress, address(0)); - // } + assertEq(newChainAdmin, admin); + assertNotEq(newChainAddress, address(0)); + + address[] memory chainAddresses = chainContractAddress.getAllHyperchains(); + assertEq(chainAddresses.length, 1); + assertEq(chainAddresses[0], newChainAddress); + + uint256[] memory chainIds = chainContractAddress.getAllHyperchainChainIDs(); + assertEq(chainIds.length, 1); + assertEq(chainIds[0], chainId); + + uint256 protocolVersion = chainContractAddress.getProtocolVersion(chainId); + assertEq(protocolVersion, 0); + } } diff --git a/l1-contracts/test/foundry/unit/concrete/state-transition/StateTransitionManager/SetNewVersionUpgrade.t.sol b/l1-contracts/test/foundry/unit/concrete/state-transition/StateTransitionManager/SetNewVersionUpgrade.t.sol index ced7e3f7d..b1153a495 100644 --- a/l1-contracts/test/foundry/unit/concrete/state-transition/StateTransitionManager/SetNewVersionUpgrade.t.sol +++ b/l1-contracts/test/foundry/unit/concrete/state-transition/StateTransitionManager/SetNewVersionUpgrade.t.sol @@ -16,5 +16,11 @@ contract setNewVersionUpgradeTest is StateTransitionManagerTest { assertEq(chainContractAddress.upgradeCutHash(0), newCutHash, "Diamond cut upgrade was not successful"); assertEq(chainContractAddress.protocolVersion(), 1, "New protocol version is not correct"); + + (uint32 major, uint32 minor, uint32 patch) = chainContractAddress.getSemverProtocolVersion(); + + assertEq(major, 0); + assertEq(minor, 0); + assertEq(patch, 1); } } diff --git a/l1-contracts/test/foundry/unit/concrete/state-transition/StateTransitionManager/SetValidatorTimelock.t.sol b/l1-contracts/test/foundry/unit/concrete/state-transition/StateTransitionManager/SetValidatorTimelock.t.sol index d290a8767..85267cf41 100644 --- a/l1-contracts/test/foundry/unit/concrete/state-transition/StateTransitionManager/SetValidatorTimelock.t.sol +++ b/l1-contracts/test/foundry/unit/concrete/state-transition/StateTransitionManager/SetValidatorTimelock.t.sol @@ -20,4 +20,23 @@ contract setValidatorTimelockTest is StateTransitionManagerTest { "Validator timelock update was not successful" ); } + + function test_RevertWhen_NotOwner() public { + // Need this because in shared setup we start a prank as the governor + vm.stopPrank(); + + address notOwner = makeAddr("notOwner"); + assertEq( + chainContractAddress.validatorTimelock(), + validator, + "Initial validator timelock address is not correct" + ); + + vm.prank(notOwner); + vm.expectRevert("STM: not owner or admin"); + address newValidatorTimelock = address(0x0000000000000000000000000000000000004235); + chainContractAddress.setValidatorTimelock(newValidatorTimelock); + + assertEq(chainContractAddress.validatorTimelock(), validator, "Validator should not have been updated"); + } } diff --git a/l1-contracts/test/foundry/unit/concrete/state-transition/chain-deps/facets/Getters/PriorityQueueFrontOperation.t.sol b/l1-contracts/test/foundry/unit/concrete/state-transition/chain-deps/facets/Getters/PriorityQueueFrontOperation.t.sol deleted file mode 100644 index 91e23f2f3..000000000 --- a/l1-contracts/test/foundry/unit/concrete/state-transition/chain-deps/facets/Getters/PriorityQueueFrontOperation.t.sol +++ /dev/null @@ -1,16 +0,0 @@ -// SPDX-License-Identifier: MIT - -pragma solidity 0.8.24; - -import {GettersFacetTest} from "./_Getters_Shared.t.sol"; -import {PriorityOperation} from "contracts/state-transition/libraries/PriorityQueue.sol"; - -contract GetPriorityQueueFrontOperationTest is GettersFacetTest { - function test_empty() public { - PriorityOperation memory received = gettersFacet.priorityQueueFrontOperation(); - - assertEq(received.canonicalTxHash, bytes32(0), "Priority queue front operation is incorrect"); - assertEq(received.layer2Tip, 0, "Priority queue front operation is incorrect"); - assertEq(received.expirationTimestamp, 0, "Priority queue front operation is incorrect"); - } -} diff --git a/l1-contracts/test/foundry/unit/concrete/state-transition/data-availability/RelayedSLDAValidator.t.sol b/l1-contracts/test/foundry/unit/concrete/state-transition/data-availability/RelayedSLDAValidator.t.sol index d193d1e5e..a6fb8e570 100644 --- a/l1-contracts/test/foundry/unit/concrete/state-transition/data-availability/RelayedSLDAValidator.t.sol +++ b/l1-contracts/test/foundry/unit/concrete/state-transition/data-availability/RelayedSLDAValidator.t.sol @@ -8,9 +8,12 @@ import {RelayedSLDAValidator} from "contracts/state-transition/data-availability import {L1DAValidatorOutput, PubdataSource} from "contracts/state-transition/chain-interfaces/IL1DAValidator.sol"; import {L2_TO_L1_MESSENGER_SYSTEM_CONTRACT_ADDR} from "contracts/common/L2ContractAddresses.sol"; import {IL1Messenger} from "contracts/common/interfaces/IL1Messenger.sol"; +import {L2_BRIDGEHUB_ADDR} from "contracts/common/L2ContractAddresses.sol"; +import {IBridgehub} from "contracts/bridgehub/IBridgehub.sol"; contract RelayedSLDAValidatorTest is Test { uint256 constant CHAIN_ID = 193; + address constant CHAIN_ADDRESS = address(0x1234); RelayedSLDAValidator daValidator; function setUp() public { @@ -21,6 +24,11 @@ contract RelayedSLDAValidatorTest is Test { abi.encodeWithSelector(IL1Messenger.sendToL1.selector), abi.encode(bytes32(0)) ); + vm.mockCall( + L2_BRIDGEHUB_ADDR, + abi.encodeWithSelector(IBridgehub.getHyperchain.selector, (CHAIN_ID)), + abi.encode(CHAIN_ADDRESS) + ); } /*////////////////////////////////////////////////////////////////////////// @@ -41,8 +49,9 @@ contract RelayedSLDAValidatorTest is Test { bytes memory operatorDAInput = abi.encodePacked(daInput, l1DaInput); + vm.prank(CHAIN_ADDRESS); vm.expectRevert("l1-da-validator/invalid-pubdata-source"); - daValidator.checkDA(CHAIN_ID, l2DAValidatorOutputHash, operatorDAInput, maxBlobsSupported); + daValidator.checkDA(CHAIN_ID, 0, l2DAValidatorOutputHash, operatorDAInput, maxBlobsSupported); } function test_revertWhen_PubdataInputTooSmall() public { @@ -63,8 +72,31 @@ contract RelayedSLDAValidatorTest is Test { bytes memory operatorDAInput = abi.encodePacked(daInput, pubdataSource, l1DaInput); + vm.prank(CHAIN_ADDRESS); vm.expectRevert("pubdata too small"); - daValidator.checkDA(CHAIN_ID, l2DAValidatorOutputHash, operatorDAInput, maxBlobsSupported); + daValidator.checkDA(CHAIN_ID, 0, l2DAValidatorOutputHash, operatorDAInput, maxBlobsSupported); + } + + function test_revertWhenInvalidSender() public { + bytes memory pubdata = "verifydont"; + console.logBytes(pubdata); + + bytes32 stateDiffHash = Utils.randomBytes32("stateDiffHash"); + uint8 blobsProvided = 1; + uint256 maxBlobsSupported = 6; + bytes32 blobLinearHash = Utils.randomBytes32("blobLinearHash"); + uint8 pubdataSource = uint8(PubdataSource.Calldata); + bytes memory l1DaInput = "verifydonttrust"; + bytes32 fullPubdataHash = keccak256(pubdata); + + bytes memory daInput = abi.encodePacked(stateDiffHash, fullPubdataHash, blobsProvided, blobLinearHash); + + bytes32 l2DAValidatorOutputHash = keccak256(daInput); + + bytes memory operatorDAInput = abi.encodePacked(daInput, pubdataSource, l1DaInput); + + vm.expectRevert("l1-da-validator/invalid-sender"); + daValidator.checkDA(CHAIN_ID, 0, l2DAValidatorOutputHash, operatorDAInput, maxBlobsSupported); } function test_checkDA() public { @@ -85,8 +117,10 @@ contract RelayedSLDAValidatorTest is Test { bytes memory operatorDAInput = abi.encodePacked(daInput, pubdataSource, l1DaInput); + vm.prank(CHAIN_ADDRESS); L1DAValidatorOutput memory output = daValidator.checkDA( CHAIN_ID, + 0, l2DAValidatorOutputHash, operatorDAInput, maxBlobsSupported diff --git a/l1-contracts/test/unit_tests/custom_base_token.spec.ts b/l1-contracts/test/unit_tests/custom_base_token.spec.ts index 9b600fcc1..f413d0491 100644 --- a/l1-contracts/test/unit_tests/custom_base_token.spec.ts +++ b/l1-contracts/test/unit_tests/custom_base_token.spec.ts @@ -8,6 +8,8 @@ import type { IBridgehub } from "../../typechain/IBridgehub"; import { IBridgehubFactory } from "../../typechain/IBridgehubFactory"; import type { IL1AssetRouter } from "../../typechain/IL1AssetRouter"; import { IL1AssetRouterFactory } from "../../typechain/IL1AssetRouterFactory"; +import type { IL1NativeTokenVault } from "../../typechain/IL1NativeTokenVault"; +import { IL1NativeTokenVaultFactory } from "../../typechain/IL1NativeTokenVaultFactory"; import { getTokens } from "../../src.ts/deploy-token"; import type { Deployer } from "../../src.ts/deploy"; @@ -23,6 +25,7 @@ describe("Custom base token chain and bridge tests", () => { let deployer: Deployer; let l1SharedBridge: IL1AssetRouter; let bridgehub: IBridgehub; + let nativeTokenVault: IL1NativeTokenVault; let baseToken: TestnetERC20Token; let baseTokenAddress: string; let altTokenAddress: string; @@ -61,6 +64,11 @@ describe("Custom base token chain and bridge tests", () => { // prepare the bridge l1SharedBridge = IL1AssetRouterFactory.connect(deployer.addresses.Bridges.SharedBridgeProxy, deployWallet); + + nativeTokenVault = IL1NativeTokenVaultFactory.connect( + deployer.addresses.Bridges.NativeTokenVaultProxy, + deployWallet + ); }); it("Should have correct base token", async () => { @@ -84,7 +92,7 @@ describe("Custom base token chain and bridge tests", () => { ) ); - expect(revertReason).equal("ShB not legacy bridge"); + expect(revertReason).equal("L1AR: not legacy bridge"); }); it("Should deposit base token successfully direct via bridgehub", async () => { @@ -106,6 +114,7 @@ describe("Custom base token chain and bridge tests", () => { }); it("Should deposit alternative token successfully twoBridges method", async () => { + nativeTokenVault.registerToken(altTokenAddress); const altTokenAmount = ethers.utils.parseUnits("800", 18); const baseTokenAmount = ethers.utils.parseUnits("800", 18); @@ -135,16 +144,17 @@ describe("Custom base token chain and bridge tests", () => { }); it("Should revert on finalizing a withdrawal with wrong message length", async () => { + const mailboxFunctionSignature = "0x6c0960f9"; const revertReason = await getCallRevertReason( - l1SharedBridge.connect(randomSigner).finalizeWithdrawal(chainId, 0, 0, 0, "0x", []) + l1SharedBridge.connect(randomSigner).finalizeWithdrawal(chainId, 0, 0, 0, mailboxFunctionSignature, []) ); - expect(revertReason).equal("ShB wrong msg len"); + expect(revertReason).equal("L1AR: wrong msg len"); }); it("Should revert on finalizing a withdrawal with wrong function selector", async () => { const revertReason = await getCallRevertReason( l1SharedBridge.connect(randomSigner).finalizeWithdrawal(chainId, 0, 0, 0, ethers.utils.randomBytes(96), []) ); - expect(revertReason).equal("ShB Incorrect message function selector"); + expect(revertReason).equal("L1AR: Incorrect message function selector"); }); }); diff --git a/l1-contracts/test/unit_tests/l1_shared_bridge_test.spec.ts b/l1-contracts/test/unit_tests/l1_shared_bridge_test.spec.ts index e50187a3d..84aab5abd 100644 --- a/l1-contracts/test/unit_tests/l1_shared_bridge_test.spec.ts +++ b/l1-contracts/test/unit_tests/l1_shared_bridge_test.spec.ts @@ -28,7 +28,7 @@ describe("Shared Bridge tests", () => { let proxyAsMockExecutor: MockExecutorFacet; let l1SharedBridge: L1AssetRouter; let erc20TestToken: ethers.Contract; - const functionSignature = "0x6c0960f9"; + const mailboxFunctionSignature = "0x6c0960f9"; const ERC20functionSignature = "0x11a2ccc1"; const dummyProof = Array(9).fill(ethers.constants.HashZero); dummyProof[0] = DUMMY_MERKLE_PROOF_START; @@ -111,16 +111,10 @@ describe("Shared Bridge tests", () => { refundRecipient: ethers.constants.AddressZero, secondBridgeAddress: l1SharedBridge.address, secondBridgeValue: 0, - secondBridgeCalldata: ethers.utils.concat([ - ethers.utils.hexlify(1), - new ethers.utils.AbiCoder().encode( - ["bytes32", "bytes"], - [ - await l1NativeTokenVault.getAssetId(erc20TestToken.address), - new ethers.utils.AbiCoder().encode(["uint256", "address"], [0, await randomSigner.getAddress()]), - ] - ), - ]), + secondBridgeCalldata: new ethers.utils.AbiCoder().encode( + ["address", "uint256", "address"], + [erc20TestToken.address, 0, await randomSigner.getAddress()] + ), }, { value: mintValue } ) @@ -128,46 +122,6 @@ describe("Shared Bridge tests", () => { expect(revertReason).equal("6T"); }); - it("Should deposit successfully", async () => { - const amount = ethers.utils.parseEther("1"); - const mintValue = ethers.utils.parseEther("2"); - - await erc20TestToken.connect(randomSigner).mint(await randomSigner.getAddress(), amount.mul(10)); - - const balanceBefore = await erc20TestToken.balanceOf(await randomSigner.getAddress()); - const balanceNTVBefore = await erc20TestToken.balanceOf(l1NativeTokenVault.address); - - const assetId = await l1NativeTokenVault.getAssetId(erc20TestToken.address); - await (await erc20TestToken.connect(randomSigner).approve(l1NativeTokenVault.address, amount.mul(10))).wait(); - await bridgehub.connect(randomSigner).requestL2TransactionTwoBridges( - { - chainId, - mintValue, - l2Value: amount, - l2GasLimit: 1000000, - l2GasPerPubdataByteLimit: REQUIRED_L2_GAS_PRICE_PER_PUBDATA, - refundRecipient: ethers.constants.AddressZero, - secondBridgeAddress: l1SharedBridge.address, - secondBridgeValue: 0, - secondBridgeCalldata: ethers.utils.concat([ - ethers.utils.hexlify(1), - new ethers.utils.AbiCoder().encode( - ["bytes32", "bytes"], - [ - assetId, - new ethers.utils.AbiCoder().encode(["uint256", "address"], [amount, await randomSigner.getAddress()]), - ] - ), - ]), - }, - { value: mintValue } - ); - const balanceAfter = await erc20TestToken.balanceOf(await randomSigner.getAddress()); - expect(balanceAfter).equal(balanceBefore.sub(amount)); - const balanceNTVAfter = await erc20TestToken.balanceOf(l1NativeTokenVault.address); - expect(balanceNTVAfter).equal(balanceNTVBefore.add(amount)); - }); - it("Should deposit successfully legacy encoding", async () => { const amount = ethers.utils.parseEther("1"); const mintValue = ethers.utils.parseEther("2"); @@ -203,9 +157,11 @@ describe("Shared Bridge tests", () => { it("Should revert on finalizing a withdrawal with short message length", async () => { const revertReason = await getCallRevertReason( - l1SharedBridge.connect(randomSigner).finalizeWithdrawal(chainId, 0, 0, 0, "0x", [ethers.constants.HashZero]) + l1SharedBridge + .connect(randomSigner) + .finalizeWithdrawal(chainId, 0, 0, 0, mailboxFunctionSignature, [ethers.constants.HashZero]) ); - expect(revertReason).equal("ShB wrong msg len"); + expect(revertReason).equal("L1AR: wrong msg len"); }); it("Should revert on finalizing a withdrawal with wrong message length", async () => { @@ -217,25 +173,27 @@ describe("Shared Bridge tests", () => { 0, 0, 0, - ethers.utils.hexConcat([ERC20functionSignature, l1SharedBridge.address, ethers.utils.randomBytes(72)]), + ethers.utils.hexConcat([ERC20functionSignature, l1SharedBridge.address, mailboxFunctionSignature]), [ethers.constants.HashZero] ) ); - expect(revertReason).equal("ShB wrong msg len 2"); + expect(revertReason).equal("L1AR: wrong msg len 2"); }); it("Should revert on finalizing a withdrawal with wrong function selector", async () => { const revertReason = await getCallRevertReason( l1SharedBridge.connect(randomSigner).finalizeWithdrawal(chainId, 0, 0, 0, ethers.utils.randomBytes(96), []) ); - expect(revertReason).equal("ShB Incorrect message function selector"); + expect(revertReason).equal("L1AR: Incorrect message function selector"); }); it("Should revert on finalizing a withdrawal with wrong message length", async () => { const revertReason = await getCallRevertReason( - l1SharedBridge.connect(randomSigner).finalizeWithdrawal(chainId, 0, 0, 0, "0x", [ethers.constants.HashZero]) + l1SharedBridge + .connect(randomSigner) + .finalizeWithdrawal(chainId, 0, 0, 0, mailboxFunctionSignature, [ethers.constants.HashZero]) ); - expect(revertReason).equal("ShB wrong msg len"); + expect(revertReason).equal("L1AR: wrong msg len"); }); it("Should revert on finalizing a withdrawal with wrong function signature", async () => { @@ -244,13 +202,13 @@ describe("Shared Bridge tests", () => { .connect(randomSigner) .finalizeWithdrawal(chainId, 0, 0, 0, ethers.utils.randomBytes(76), [ethers.constants.HashZero]) ); - expect(revertReason).equal("ShB Incorrect message function selector"); + expect(revertReason).equal("L1AR: Incorrect message function selector"); }); it("Should revert on finalizing a withdrawal with wrong batch number", async () => { const l1Receiver = await randomSigner.getAddress(); const l2ToL1message = ethers.utils.hexConcat([ - functionSignature, + mailboxFunctionSignature, l1Receiver, erc20TestToken.address, ethers.constants.HashZero, @@ -264,7 +222,7 @@ describe("Shared Bridge tests", () => { it("Should revert on finalizing a withdrawal with wrong length of proof", async () => { const l1Receiver = await randomSigner.getAddress(); const l2ToL1message = ethers.utils.hexConcat([ - functionSignature, + mailboxFunctionSignature, l1Receiver, erc20TestToken.address, ethers.constants.HashZero, @@ -274,13 +232,13 @@ describe("Shared Bridge tests", () => { .connect(randomSigner) .finalizeWithdrawal(chainId, 0, 0, 0, l2ToL1message, [dummyProof[0], dummyProof[1]]) ); - expect(revertReason).equal("ShB withd w proof"); + expect(revertReason).equal("L1AR: withd w proof"); }); it("Should revert on finalizing a withdrawal with wrong proof", async () => { const l1Receiver = await randomSigner.getAddress(); const l2ToL1message = ethers.utils.hexConcat([ - functionSignature, + mailboxFunctionSignature, l1Receiver, erc20TestToken.address, ethers.constants.HashZero, @@ -288,6 +246,6 @@ describe("Shared Bridge tests", () => { const revertReason = await getCallRevertReason( l1SharedBridge.connect(randomSigner).finalizeWithdrawal(chainId, 0, 0, 0, l2ToL1message, dummyProof) ); - expect(revertReason).equal("ShB withd w proof"); + expect(revertReason).equal("L1AR: withd w proof"); }); }); diff --git a/l1-contracts/test/unit_tests/legacy_era_test.spec.ts b/l1-contracts/test/unit_tests/legacy_era_test.spec.ts index 497a38329..88361ed05 100644 --- a/l1-contracts/test/unit_tests/legacy_era_test.spec.ts +++ b/l1-contracts/test/unit_tests/legacy_era_test.spec.ts @@ -170,10 +170,13 @@ describe("Legacy Era tests", function () { }); it("Should revert on finalizing a withdrawal with wrong message length", async () => { + const mailboxFunctionSignature = "0x6c0960f9"; const revertReason = await getCallRevertReason( - l1ERC20Bridge.connect(randomSigner).finalizeWithdrawal(1, 0, 0, "0x", [ethers.constants.HashZero]) + l1ERC20Bridge + .connect(randomSigner) + .finalizeWithdrawal(1, 0, 0, mailboxFunctionSignature, [ethers.constants.HashZero]) ); - expect(revertReason).equal("ShB wrong msg len"); + expect(revertReason).equal("L1AR: wrong msg len"); }); it("Should revert on finalizing a withdrawal with wrong function signature", async () => { @@ -182,7 +185,7 @@ describe("Legacy Era tests", function () { .connect(randomSigner) .finalizeWithdrawal(1, 0, 0, ethers.utils.randomBytes(76), [ethers.constants.HashZero]) ); - expect(revertReason).equal("ShB Incorrect message function selector"); + expect(revertReason).equal("L1AR: Incorrect message function selector"); }); it("Should revert on finalizing a withdrawal with wrong batch number", async () => { @@ -196,7 +199,7 @@ describe("Legacy Era tests", function () { const revertReason = await getCallRevertReason( l1ERC20Bridge.connect(randomSigner).finalizeWithdrawal(1, 0, 0, l2ToL1message, dummyProof) ); - expect(revertReason).equal("ShB withd w proof"); + expect(revertReason).equal("L1AR: withd w proof"); }); /////////// Mailbox. Note we have these two together because we need to fix ERA Diamond proxy Address @@ -266,7 +269,7 @@ describe("Legacy Era tests", function () { const revertReason = await getCallRevertReason( mailbox.finalizeEthWithdrawal(BLOCK_NUMBER, MESSAGE_INDEX, TX_NUMBER_IN_BLOCK, MESSAGE, invalidProof) ); - expect(revertReason).equal("ShB withd w proof"); + expect(revertReason).equal("L1AR: withd w proof"); }); it("Successful deposit", async () => { @@ -297,7 +300,7 @@ describe("Legacy Era tests", function () { const revertReason = await getCallRevertReason( mailbox.finalizeEthWithdrawal(BLOCK_NUMBER, MESSAGE_INDEX, TX_NUMBER_IN_BLOCK, MESSAGE, MERKLE_PROOF) ); - expect(revertReason).equal("Withdrawal is already finalized"); + expect(revertReason).equal("L1AR: Withdrawal is already finalized"); }); }); }); diff --git a/l1-contracts/test/unit_tests/synclayer.spec.ts b/l1-contracts/test/unit_tests/synclayer.spec.ts index 8f0a6de0f..d5ee3fe43 100644 --- a/l1-contracts/test/unit_tests/synclayer.spec.ts +++ b/l1-contracts/test/unit_tests/synclayer.spec.ts @@ -3,32 +3,25 @@ import * as ethers from "ethers"; import { Wallet } from "ethers"; import * as hardhat from "hardhat"; -import type { Bridgehub, StateTransitionManager } from "../../typechain"; -import { AdminFacetFactory, BridgehubFactory, StateTransitionManagerFactory } from "../../typechain"; +import type { Bridgehub } from "../../typechain"; +import { BridgehubFactory } from "../../typechain"; import { initialTestnetDeploymentProcess, defaultDeployerForTests, registerHyperchainWithBridgeRegistration, } from "../../src.ts/deploy-test-process"; -import { - ethTestConfig, - DIAMOND_CUT_DATA_ABI_STRING, - HYPERCHAIN_COMMITMENT_ABI_STRING, - ADDRESS_ONE, - REQUIRED_L2_GAS_PRICE_PER_PUBDATA, - priorityTxMaxGasLimit, -} from "../../src.ts/utils"; +import { ethTestConfig, REQUIRED_L2_GAS_PRICE_PER_PUBDATA, priorityTxMaxGasLimit } from "../../src.ts/utils"; import { SYSTEM_CONFIG } from "../../scripts/utils"; import type { Deployer } from "../../src.ts/deploy"; describe("Synclayer", function () { let bridgehub: Bridgehub; - let stateTransition: StateTransitionManager; + // let stateTransition: StateTransitionManager; let owner: ethers.Signer; let migratingDeployer: Deployer; - let syncLayerDeployer: Deployer; + let gatewayDeployer: Deployer; // const MAX_CODE_LEN_WORDS = (1 << 16) - 1; // const MAX_CODE_LEN_BYTES = MAX_CODE_LEN_WORDS * 32; // let forwarder: Forwarder; @@ -61,20 +54,16 @@ describe("Synclayer", function () { chainId = migratingDeployer.chainId; bridgehub = BridgehubFactory.connect(migratingDeployer.addresses.Bridgehub.BridgehubProxy, deployWallet); - stateTransition = StateTransitionManagerFactory.connect( - migratingDeployer.addresses.StateTransition.StateTransitionProxy, - deployWallet - ); - syncLayerDeployer = await defaultDeployerForTests(deployWallet, ownerAddress); - syncLayerDeployer.chainId = 10; + gatewayDeployer = await defaultDeployerForTests(deployWallet, ownerAddress); + gatewayDeployer.chainId = 10; await registerHyperchainWithBridgeRegistration( - syncLayerDeployer, + gatewayDeployer, false, [], gasPrice, undefined, - syncLayerDeployer.chainId.toString() + gatewayDeployer.chainId.toString() ); // For tests, the chainId is 9 @@ -82,13 +71,13 @@ describe("Synclayer", function () { }); it("Check register synclayer", async () => { - await syncLayerDeployer.registerSyncLayer(); + await gatewayDeployer.registerSettlementLayer(); }); it("Check start move chain to synclayer", async () => { const gasPrice = await owner.provider.getGasPrice(); - await migratingDeployer.moveChainToSyncLayer(syncLayerDeployer.chainId.toString(), gasPrice, false); - expect(await bridgehub.settlementLayer(migratingDeployer.chainId)).to.equal(syncLayerDeployer.chainId); + await migratingDeployer.moveChainToGateway(gatewayDeployer.chainId.toString(), gasPrice, false); + expect(await bridgehub.settlementLayer(migratingDeployer.chainId)).to.equal(gatewayDeployer.chainId); }); it("Check l2 registration", async () => { @@ -99,20 +88,16 @@ describe("Synclayer", function () { ).mul(10); // const baseTokenAddress = await bridgehub.baseToken(chainId); // const ethIsBaseToken = baseTokenAddress == ADDRESS_ONE; - const stmDeploymentTracker = migratingDeployer.stmDeploymentTracker(migratingDeployer.deployWallet); - await ( - await stmDeploymentTracker.registerSTMAssetOnL2SharedBridge( - chainId, - syncLayerDeployer.addresses.StateTransition.StateTransitionProxy, - value, - priorityTxMaxGasLimit, - SYSTEM_CONFIG.requiredL2GasPricePerPubdata, - syncLayerDeployer.deployWallet.address, - { value: value } - ) - ).wait(); - // console.log("STM asset registered in L2SharedBridge on SL"); + const calldata = stmDeploymentTracker.interface.encodeFunctionData("registerSTMAssetOnL2SharedBridge", [ + chainId, + gatewayDeployer.addresses.StateTransition.StateTransitionProxy, + value, + priorityTxMaxGasLimit, + SYSTEM_CONFIG.requiredL2GasPricePerPubdata, + gatewayDeployer.deployWallet.address, + ]); + await migratingDeployer.executeUpgrade(stmDeploymentTracker.address, value, calldata); await migratingDeployer.executeUpgrade( bridgehub.address, value, @@ -133,48 +118,6 @@ describe("Synclayer", function () { // console.log("STM asset registered in L2 Bridgehub on SL"); }); - it("Check finish move chain", async () => { - const syncLayerChainId = syncLayerDeployer.chainId; - const assetInfo = await bridgehub.stmAssetId(migratingDeployer.addresses.StateTransition.StateTransitionProxy); - const diamondCutData = await migratingDeployer.initialZkSyncHyperchainDiamondCut(); - const initialDiamondCut = new ethers.utils.AbiCoder().encode([DIAMOND_CUT_DATA_ABI_STRING], [diamondCutData]); - - const adminFacet = AdminFacetFactory.connect( - migratingDeployer.addresses.StateTransition.DiamondProxy, - migratingDeployer.deployWallet - ); - - // const chainCommitment = { - // totalBatchesExecuted: 0, - // totalBatchesVerified: 0, - // totalBatchesCommitted:0, - // priorityQueueHead: 0, - // priorityQueueTxs: [ - // { - // canonicalTxHash: '0xea79e9b7c3c46a76174b3aea3760570a7e18b593d2b5a087fce52cee95d2d57e', - // expirationTimestamp: "1716557077", - // layer2Tip: 0 - // }], - // l2SystemContractsUpgradeTxHash: ethers.constants.HashZero, - // l2SystemContractsUpgradeBatchNumber:0 , - // batchHashes: ['0xcd4e278573a3b2076a81f91b97e2dd0c85882d9f735ad81dc34b509033671e7b']} - const chainData = ethers.utils.defaultAbiCoder.encode( - [HYPERCHAIN_COMMITMENT_ABI_STRING], - [await adminFacet._prepareChainCommitment()] - ); - // const chainData = await adminFacet.readChainCommitment(); - const stmData = ethers.utils.defaultAbiCoder.encode( - ["address", "address", "uint256", "bytes"], - [ADDRESS_ONE, migratingDeployer.deployWallet.address, await stateTransition.protocolVersion(), initialDiamondCut] - ); - const bridgehubMintData = ethers.utils.defaultAbiCoder.encode( - ["uint256", "bytes", "bytes"], - [mintChainId, stmData, chainData] - ); - await bridgehub.bridgeMint(syncLayerChainId, assetInfo, bridgehubMintData); - expect(await stateTransition.getHyperchain(mintChainId)).to.not.equal(ethers.constants.AddressZero); - }); - it("Check start message to L3 on L1", async () => { const amount = ethers.utils.parseEther("2"); await bridgehub.requestL2TransactionDirect( @@ -218,6 +161,6 @@ describe("Synclayer", function () { paymasterInput: "0x", reservedDynamic: "0x", }; - bridgehub.forwardTransactionOnSyncLayer(mintChainId, tx, [], ethers.constants.HashZero, 0); + bridgehub.forwardTransactionOnGateway(mintChainId, tx, [], ethers.constants.HashZero, 0); }); }); diff --git a/l2-contracts/contracts/L2ContractHelper.sol b/l2-contracts/contracts/L2ContractHelper.sol index a2fe86080..907060281 100644 --- a/l2-contracts/contracts/L2ContractHelper.sol +++ b/l2-contracts/contracts/L2ContractHelper.sol @@ -113,6 +113,8 @@ IL2AssetRouter constant L2_ASSET_ROUTER = IL2AssetRouter(address(USER_CONTRACTS_ /// @dev The contract responsible for handling tokens native to a single chain. IL2NativeTokenVault constant L2_NATIVE_TOKEN_VAULT = IL2NativeTokenVault(address(USER_CONTRACTS_OFFSET + 0x04)); +uint256 constant L1_CHAIN_ID = 1; + IL2Messenger constant L2_MESSENGER = IL2Messenger(address(SYSTEM_CONTRACTS_OFFSET + 0x08)); IBaseToken constant L2_BASE_TOKEN_ADDRESS = IBaseToken(address(SYSTEM_CONTRACTS_OFFSET + 0x0a)); @@ -193,7 +195,7 @@ library L2ContractHelper { } } -/// @notice Structure used to represent a zkSync transaction. +/// @notice Structure used to represent a ZKsync transaction. struct Transaction { // The type of the transaction. uint256 txType; diff --git a/l2-contracts/contracts/bridge/L2AssetRouter.sol b/l2-contracts/contracts/bridge/L2AssetRouter.sol index 4ed6f062c..20be7d767 100644 --- a/l2-contracts/contracts/bridge/L2AssetRouter.sol +++ b/l2-contracts/contracts/bridge/L2AssetRouter.sol @@ -9,11 +9,12 @@ import {IL2AssetRouter} from "./interfaces/IL2AssetRouter.sol"; import {IL1AssetRouter} from "./interfaces/IL1AssetRouter.sol"; import {ILegacyL2SharedBridge} from "./interfaces/ILegacyL2SharedBridge.sol"; import {IL2AssetHandler} from "./interfaces/IL2AssetHandler.sol"; -import {ILegacyL2SharedBridge} from "./interfaces/ILegacyL2SharedBridge.sol"; import {IL2StandardToken} from "./interfaces/IL2StandardToken.sol"; +import {IL2NativeTokenVault} from "./interfaces/IL2NativeTokenVault.sol"; import {AddressAliasHelper} from "../vendor/AddressAliasHelper.sol"; import {L2ContractHelper, L2_NATIVE_TOKEN_VAULT} from "../L2ContractHelper.sol"; +import {DataEncoding} from "../common/libraries/DataEncoding.sol"; import {EmptyAddress, InvalidCaller} from "../L2ContractErrors.sol"; @@ -38,14 +39,19 @@ contract L2AssetRouter is IL2AssetRouter, ILegacyL2SharedBridge, Initializable { /// @dev Bytecode hash of the proxy for tokens deployed by the bridge. bytes32 internal DEPRECATED_l2TokenProxyBytecodeHash; - /// @dev A mapping l2 token address => l1 token address. - mapping(address l2TokenAddress => address l1TokenAddress) public override l1TokenAddress; + /// @notice Deprecated. Kept for backwards compatibility. + /// @dev A mapping l2 token address => l1 token address + mapping(address l2Token => address l1Token) public override l1TokenAddress; + /// @notice Obsolete, as all calls are performed via L1 Shared Bridge. Kept for backwards compatibility. /// @dev The address of the legacy L1 erc20 bridge counterpart. /// This is non-zero only on Era, and should not be renamed for backward compatibility with the SDKs. address public override l1Bridge; - /// @dev A mapping l2 token address => l1 token address. + /// @dev The contract responsible for handling tokens native to a single chain. + IL2NativeTokenVault public nativeTokenVault; + + /// @dev A mapping of asset ID to asset handler address mapping(bytes32 assetId => address assetHandlerAddress) public override assetHandlerAddress; /// @notice Checks that the message sender is the legacy bridge. @@ -83,9 +89,16 @@ contract L2AssetRouter is IL2AssetRouter, ILegacyL2SharedBridge, Initializable { _disableInitializers(); } - /// @notice Finalizes the deposit and mint funds. - /// @param _assetId The encoding of the asset on L2. - /// @param _transferData The encoded data required for deposit (address _l1Sender, uint256 _amount, address _l2Receiver, bytes memory erc20Data, address originToken). + /// @dev Used to set the assedAddress for a given assetId. + /// @dev Will be used by ZK Gateway + function setAssetHandlerAddress(bytes32 _assetId, address _assetAddress) external onlyL1Bridge { + assetHandlerAddress[_assetId] = _assetAddress; + emit AssetHandlerRegistered(_assetId, _assetAddress); + } + + /// @notice Finalize the deposit and mint funds + /// @param _assetId The encoding of the asset on L2 + /// @param _transferData The encoded data required for deposit (address _l1Sender, uint256 _amount, address _l2Receiver, bytes memory erc20Data, address originToken) function finalizeDeposit(bytes32 _assetId, bytes memory _transferData) public override onlyL1Bridge { address assetHandler = assetHandlerAddress[_assetId]; if (assetHandler != address(0)) { @@ -95,27 +108,27 @@ contract L2AssetRouter is IL2AssetRouter, ILegacyL2SharedBridge, Initializable { assetHandlerAddress[_assetId] = address(L2_NATIVE_TOKEN_VAULT); } - emit FinalizeDepositSharedBridge(L1_CHAIN_ID, _assetId, keccak256(_transferData)); + emit FinalizeDepositSharedBridge(L1_CHAIN_ID, _assetId, _transferData); } /// @notice Initiates a withdrawal by burning funds on the contract and sending the message to L1 - /// where tokens would be unlocked. - /// @param _assetId The encoding of the asset on L2 which is withdrawn. - /// @param _transferData The data that is passed to the asset handler contract. - function withdraw(bytes32 _assetId, bytes memory _transferData) public override { + /// where tokens would be unlocked + /// @param _assetId The asset id of the withdrawn asset + /// @param _assetData The data that is passed to the asset handler contract + function withdraw(bytes32 _assetId, bytes memory _assetData) public override { address assetHandler = assetHandlerAddress[_assetId]; bytes memory _l1bridgeMintData = IL2AssetHandler(assetHandler).bridgeBurn({ _chainId: L1_CHAIN_ID, _mintValue: 0, _assetId: _assetId, _prevMsgSender: msg.sender, - _transferData: _transferData + _data: _assetData }); bytes memory message = _getL1WithdrawMessage(_assetId, _l1bridgeMintData); L2ContractHelper.sendMessageToL1(message); - emit WithdrawalInitiatedSharedBridge(L1_CHAIN_ID, msg.sender, _assetId, keccak256(_transferData)); + emit WithdrawalInitiatedSharedBridge(L1_CHAIN_ID, msg.sender, _assetId, _assetData); } /// @notice Encodes the message for l2ToL1log sent during withdraw initialization. @@ -125,42 +138,33 @@ contract L2AssetRouter is IL2AssetRouter, ILegacyL2SharedBridge, Initializable { bytes32 _assetId, bytes memory _l1bridgeMintData ) internal pure returns (bytes memory) { - // note we use the IL1ERC20Bridge.finalizeWithdrawal function selector to specify the selector for L1<>L2 messages, + // note we use the IL1SharedBridge.finalizeWithdrawal function selector to specify the selector for L1<>L2 messages, // and we use this interface so that when the switch happened the old messages could be processed // solhint-disable-next-line func-named-parameters return abi.encodePacked(IL1AssetRouter.finalizeWithdrawal.selector, _assetId, _l1bridgeMintData); } - /// @notice Sets the asset handler address for a given assetId. - /// @dev Will be called by ZK Gateway. - /// @param _assetId The encoding of the asset on L2. - /// @param _assetHandlerAddress The address of the asset handler, which will hold the token of interest. - function setAssetHandlerAddress(bytes32 _assetId, address _assetHandlerAddress) external onlyL1Bridge { - assetHandlerAddress[_assetId] = _assetHandlerAddress; - emit AssetHandlerRegistered(_assetId, _assetHandlerAddress); - } - /*////////////////////////////////////////////////////////////// LEGACY FUNCTIONS //////////////////////////////////////////////////////////////*/ - /// @notice Finalizes the deposit and mint funds. + /// @notice Legacy finalizeDeposit. + /// @dev Finalizes the deposit and mint funds. /// @param _l1Sender The address of token sender on L1. /// @param _l2Receiver The address of token receiver on L2. /// @param _l1Token The address of the token transferred. /// @param _amount The amount of the token transferred. - /// @param erc20Data The ERC20 metadata of the token transferred. + /// @param _data The metadata of the token transferred. function finalizeDeposit( address _l1Sender, address _l2Receiver, address _l1Token, uint256 _amount, - bytes calldata erc20Data + bytes calldata _data ) external override { - // onlyBridge { - bytes32 assetId = keccak256(abi.encode(L1_CHAIN_ID, address(L2_NATIVE_TOKEN_VAULT), _l1Token)); + bytes32 assetId = DataEncoding.encodeNTVAssetId(L1_CHAIN_ID, _l1Token); // solhint-disable-next-line func-named-parameters - bytes memory data = abi.encode(_l1Sender, _amount, _l2Receiver, erc20Data, _l1Token); + bytes memory data = DataEncoding.encodeBridgeMintData(_l1Sender, _l2Receiver, _l1Token, _amount, _data); finalizeDeposit(assetId, data); } @@ -170,23 +174,24 @@ contract L2AssetRouter is IL2AssetRouter, ILegacyL2SharedBridge, Initializable { /// @param _l2Token The address of the token transferred. /// @param _amount The amount of the token transferred. function withdraw(address _l1Receiver, address _l2Token, uint256 _amount) external { - bytes32 assetId = keccak256( - abi.encode(L1_CHAIN_ID, address(L2_NATIVE_TOKEN_VAULT), getL1TokenAddress(_l2Token)) - ); + bytes32 assetId = DataEncoding.encodeNTVAssetId(L1_CHAIN_ID, getL1TokenAddress(_l2Token)); bytes memory data = abi.encode(_amount, _l1Receiver); withdraw(assetId, data); } - /// @notice Retrieves L1 address corresponding to L2 wrapped token. + /// @notice Legacy getL1TokenAddress. /// @param _l2Token The address of token on L2. /// @return The address of token on L1. function getL1TokenAddress(address _l2Token) public view returns (address) { return IL2StandardToken(_l2Token).l1Address(); } - /// @notice Retrieves L2 wrapped token address corresponding to L1 token counterpart. + /// @notice Legacy function used for backward compatibility to return L2 wrapped token + /// @notice address corresponding to provided L1 token address and deployed through NTV. + /// @dev However, the shared bridge can use custom asset handlers such that L2 addresses differ, + /// @dev or an L1 token may not have an L2 counterpart. /// @param _l1Token The address of token on L1. - /// @return The address of token on L2. + /// @return Address of an L2 token counterpart function l2TokenAddress(address _l1Token) public view returns (address) { return L2_NATIVE_TOKEN_VAULT.l2TokenAddress(_l1Token); } diff --git a/l2-contracts/contracts/bridge/L2NativeTokenVault.sol b/l2-contracts/contracts/bridge/L2NativeTokenVault.sol index aa3f55d0c..1cab56ae3 100644 --- a/l2-contracts/contracts/bridge/L2NativeTokenVault.sol +++ b/l2-contracts/contracts/bridge/L2NativeTokenVault.sol @@ -10,8 +10,9 @@ import {IL2StandardToken} from "./interfaces/IL2StandardToken.sol"; import {IL2NativeTokenVault} from "./interfaces/IL2NativeTokenVault.sol"; import {L2StandardERC20} from "./L2StandardERC20.sol"; -import {L2ContractHelper, DEPLOYER_SYSTEM_CONTRACT, L2_NATIVE_TOKEN_VAULT, L2_ASSET_ROUTER, IContractDeployer} from "../L2ContractHelper.sol"; +import {L2ContractHelper, DEPLOYER_SYSTEM_CONTRACT, L2_ASSET_ROUTER, IContractDeployer} from "../L2ContractHelper.sol"; import {SystemContractsCaller} from "../SystemContractsCaller.sol"; +import {DataEncoding} from "../common/libraries/DataEncoding.sol"; import {EmptyAddress, EmptyBytes32, AddressMismatch, AssetIdMismatch, DeployFailed, AmountMustBeGreaterThanZero, InvalidCaller} from "../L2ContractErrors.sol"; @@ -20,15 +21,19 @@ import {EmptyAddress, EmptyBytes32, AddressMismatch, AssetIdMismatch, DeployFail /// @notice The "default" bridge implementation for the ERC20 tokens. Note, that it does not /// support any custom token logic, i.e. rebase tokens' functionality is not supported. contract L2NativeTokenVault is IL2NativeTokenVault, Ownable2StepUpgradeable { + /// @dev Chain ID of L1 for bridging reasons. + uint256 public immutable L1_CHAIN_ID; + + bytes32 internal l2TokenProxyBytecodeHash; + /// @dev Contract that stores the implementation address for token. /// @dev For more details see https://docs.openzeppelin.com/contracts/3.x/api/proxy#UpgradeableBeacon. UpgradeableBeacon public l2TokenBeacon; - /// @dev Bytecode hash of the proxy for tokens deployed by the bridge. - bytes32 internal l2TokenProxyBytecodeHash; - mapping(bytes32 assetId => address tokenAddress) public override tokenAddress; + /// @dev Bytecode hash of the proxy for tokens deployed by the bridge. + modifier onlyBridge() { if (msg.sender != address(L2_ASSET_ROUTER)) { revert InvalidCaller(msg.sender); @@ -37,12 +42,13 @@ contract L2NativeTokenVault is IL2NativeTokenVault, Ownable2StepUpgradeable { _; } - /// @dev Contract is expected to be used as proxy implementation. - /// @dev Disable the initialization to prevent Parity hack. + /// @notice Initializes the bridge contract for later use. Expected to be used in the proxy. + /// @param _l1ChainId The L1 chain id differs between mainnet and testnets. /// @param _l2TokenProxyBytecodeHash The bytecode hash of the proxy for tokens deployed by the bridge. /// @param _aliasedOwner The address of the governor contract. - /// @param _contractsDeployedAlready Ensures beacon proxy for standard ERC20 has not been deployed - constructor(bytes32 _l2TokenProxyBytecodeHash, address _aliasedOwner, bool _contractsDeployedAlready) { + constructor(uint256 _l1ChainId, bytes32 _l2TokenProxyBytecodeHash, address _aliasedOwner) { + L1_CHAIN_ID = _l1ChainId; + _disableInitializers(); if (_l2TokenProxyBytecodeHash == bytes32(0)) { revert EmptyBytes32(); @@ -51,71 +57,96 @@ contract L2NativeTokenVault is IL2NativeTokenVault, Ownable2StepUpgradeable { revert EmptyAddress(); } - if (!_contractsDeployedAlready) { - l2TokenProxyBytecodeHash = _l2TokenProxyBytecodeHash; - } - + l2TokenProxyBytecodeHash = _l2TokenProxyBytecodeHash; _transferOwnership(_aliasedOwner); } /// @notice Sets L2 token beacon used by wrapped ERC20 tokens deployed by NTV. - /// @dev we don't call this in the constructor, as we need to provide factory deps - function setL2TokenBeacon() external { + /// @dev Sets the l2TokenBeacon, called after initialize. + /// @param _l2TokenBeacon The address of L2 token beacon implementation. + /// @param _l2TokenProxyBytecodeHash The bytecode hash of the L2 token proxy that will be deployed for wrapped tokens. + function setL2TokenBeacon(address _l2TokenBeacon, bytes32 _l2TokenProxyBytecodeHash) external onlyOwner { + l2TokenBeacon = UpgradeableBeacon(_l2TokenBeacon); + l2TokenProxyBytecodeHash = _l2TokenProxyBytecodeHash; + emit L2TokenBeaconUpdated(_l2TokenBeacon, _l2TokenProxyBytecodeHash); + } + + /// @notice Configure L2 token beacon used by wrapped ERC20 tokens deployed by NTV. + /// @dev we don't call this in the constructor, as we need to provide factory deps. + /// @param _contractsDeployedAlready Ensures beacon proxy for standard ERC20 has not been deployed. + function configureL2TokenBeacon(bool _contractsDeployedAlready, address _l2TokenBeacon) external { if (address(l2TokenBeacon) != address(0)) { revert AddressMismatch(address(l2TokenBeacon), address(0)); } - address l2StandardToken = address(new L2StandardERC20{salt: bytes32(0)}()); - l2TokenBeacon = new UpgradeableBeacon{salt: bytes32(0)}(l2StandardToken); - l2TokenBeacon.transferOwnership(owner()); + if (_contractsDeployedAlready) { + if (_l2TokenBeacon == address(0)) { + revert EmptyAddress(); + } + l2TokenBeacon = UpgradeableBeacon(_l2TokenBeacon); + } else { + address l2StandardToken = address(new L2StandardERC20{salt: bytes32(0)}()); + l2TokenBeacon = new UpgradeableBeacon{salt: bytes32(0)}(l2StandardToken); + l2TokenBeacon.transferOwnership(owner()); + } } - /// @notice Mints the wrapped asset during shared bridge deposit finalization. + /// @notice Used when the chain receives a transfer from L1 Shared Bridge and correspondingly mints the asset. /// @param _chainId The chainId that the message is from. /// @param _assetId The assetId of the asset being bridged. - /// @param _transferData The abi.encoded transfer data. - function bridgeMint(uint256 _chainId, bytes32 _assetId, bytes calldata _transferData) external payable override { + /// @param _data The abi.encoded transfer data. + function bridgeMint(uint256 _chainId, bytes32 _assetId, bytes calldata _data) external payable override onlyBridge { address token = tokenAddress[_assetId]; - (address _l1Sender, uint256 _amount, address _l2Receiver, bytes memory erc20Data, address originToken) = abi - .decode(_transferData, (address, uint256, address, bytes, address)); - address expectedToken = l2TokenAddress(originToken); + ( + address _l1Sender, + address _l2Receiver, + address originToken, + uint256 _amount, + bytes memory erc20Data + ) = DataEncoding.decodeBridgeMintData(_data); + if (token == address(0)) { - bytes32 expectedAssetId = keccak256( - abi.encode(_chainId, address(L2_NATIVE_TOKEN_VAULT), bytes32(uint256(uint160(originToken)))) - ); + address expectedToken = _calculateCreate2TokenAddress(originToken); + bytes32 expectedAssetId = DataEncoding.encodeNTVAssetId(L1_CHAIN_ID, originToken); if (_assetId != expectedAssetId) { // Make sure that a NativeTokenVault sent the message - revert AssetIdMismatch(_assetId, expectedAssetId); + revert AssetIdMismatch(expectedAssetId, _assetId); } address deployedToken = _deployL2Token(originToken, erc20Data); if (deployedToken != expectedToken) { revert AddressMismatch(expectedToken, deployedToken); } tokenAddress[_assetId] = expectedToken; + token = expectedToken; } - IL2StandardToken(expectedToken).bridgeMint(_l2Receiver, _amount); + IL2StandardToken(token).bridgeMint(_l2Receiver, _amount); /// backwards compatible event - emit FinalizeDeposit(_l1Sender, _l2Receiver, expectedToken, _amount); - // solhint-disable-next-line func-named-parameters - emit BridgeMint(_chainId, _assetId, _l1Sender, _l2Receiver, _amount); + emit FinalizeDeposit(_l1Sender, _l2Receiver, token, _amount); + emit BridgeMint({ + chainId: _chainId, + assetId: _assetId, + sender: _l1Sender, + l2Receiver: _l2Receiver, + amount: _amount + }); } /// @notice Burns wrapped tokens and returns the calldata for L2 -> L1 message. - /// @dev In case of native token vault _transferData is the tuple of _depositAmount and _l2Receiver. + /// @dev In case of native token vault _data is the tuple of _depositAmount and _l2Receiver. /// @param _chainId The chainId that the message will be sent to. /// @param _mintValue The L1 base token value bridged. /// @param _assetId The L2 assetId of the asset being bridged. /// @param _prevMsgSender The original caller of the shared bridge. - /// @param _transferData The abi.encoded transfer data. + /// @param _data The abi.encoded transfer data. /// @return l1BridgeMintData The calldata used by l1 asset handler to unlock tokens for recipient. function bridgeBurn( uint256 _chainId, uint256 _mintValue, bytes32 _assetId, address _prevMsgSender, - bytes calldata _transferData + bytes calldata _data ) external payable override onlyBridge returns (bytes memory l1BridgeMintData) { - (uint256 _amount, address _l1Receiver) = abi.decode(_transferData, (uint256, address)); + (uint256 _amount, address _l1Receiver) = abi.decode(_data, (uint256, address)); if (_amount == 0) { // "Amount cannot be zero"); revert AmountMustBeGreaterThanZero(); @@ -126,9 +157,26 @@ contract L2NativeTokenVault is IL2NativeTokenVault, Ownable2StepUpgradeable { /// backwards compatible event emit WithdrawalInitiated(_prevMsgSender, _l1Receiver, l2Token, _amount); - // solhint-disable-next-line func-named-parameters - emit BridgeBurn(_chainId, _assetId, _prevMsgSender, _l1Receiver, _mintValue, _amount); - l1BridgeMintData = _transferData; + emit BridgeBurn({ + chainId: _chainId, + assetId: _assetId, + l2Sender: _prevMsgSender, + receiver: _l1Receiver, + mintValue: _mintValue, + amount: _amount + }); + l1BridgeMintData = _data; + } + + /// @notice Calculates L2 wrapped token address corresponding to L1 token counterpart. + /// @param _l1Token The address of token on L1. + /// @return expectedToken The address of token on L2. + function l2TokenAddress(address _l1Token) public view override returns (address expectedToken) { + bytes32 expectedAssetId = DataEncoding.encodeNTVAssetId(L1_CHAIN_ID, _l1Token); + expectedToken = tokenAddress[expectedAssetId]; + if (expectedToken == address(0)) { + expectedToken = _calculateCreate2TokenAddress(_l1Token); + } } /// @notice Deploys and initializes the L2 token for the L1 counterpart. @@ -167,20 +215,20 @@ contract L2NativeTokenVault is IL2NativeTokenVault, Ownable2StepUpgradeable { proxy = BeaconProxy(abi.decode(returndata, (address))); } - /// @notice Converts the L1 token address to the create2 salt of deployed L2 token. - /// @param _l1Token The address of token on L1. - /// @return salt The salt used to compute address of wrapped token on L2 and for beacon proxy deployment. - function _getCreate2Salt(address _l1Token) internal pure returns (bytes32 salt) { - salt = bytes32(uint256(uint160(_l1Token))); - } - - /// @notice Calculates L2 wrapped token address corresponding to L1 token counterpart. + /// @notice Calculates L2 wrapped token address given the currently stored beacon proxy bytecode hash and beacon address. /// @param _l1Token The address of token on L1. - /// @return The address of token on L2. - function l2TokenAddress(address _l1Token) public view override returns (address) { + /// @return Address of an L2 token counterpart. + function _calculateCreate2TokenAddress(address _l1Token) internal view returns (address) { bytes32 constructorInputHash = keccak256(abi.encode(address(l2TokenBeacon), "")); bytes32 salt = _getCreate2Salt(_l1Token); return L2ContractHelper.computeCreate2Address(address(this), salt, l2TokenProxyBytecodeHash, constructorInputHash); } + + /// @notice Converts the L1 token address to the create2 salt of deployed L2 token. + /// @param _l1Token The address of token on L1. + /// @return salt The salt used to compute address of wrapped token on L2 and for beacon proxy deployment. + function _getCreate2Salt(address _l1Token) internal pure returns (bytes32 salt) { + salt = bytes32(uint256(uint160(_l1Token))); + } } diff --git a/l2-contracts/contracts/bridge/L2StandardERC20.sol b/l2-contracts/contracts/bridge/L2StandardERC20.sol index 2104629f4..cb3f54977 100644 --- a/l2-contracts/contracts/bridge/L2StandardERC20.sol +++ b/l2-contracts/contracts/bridge/L2StandardERC20.sol @@ -37,6 +37,22 @@ contract L2StandardERC20 is ERC20PermitUpgradeable, IL2StandardToken, ERC1967Upg /// @dev Address of the L1 token that can be deposited to mint this L2 token address public override l1Address; + modifier onlyBridge() { + if (msg.sender != l2Bridge) { + revert Unauthorized(); + } + _; + } + + modifier onlyNextVersion(uint8 _version) { + // The version should be incremented by 1. Otherwise, the governor risks disabling + // future reinitialization of the token by providing too large a version. + if (_version != _getInitializedVersion() + 1) { + revert NonSequentialVersion(); + } + _; + } + /// @dev Contract is expected to be used as proxy implementation. constructor() { // Disable initialization to prevent Parity hack. @@ -131,22 +147,6 @@ contract L2StandardERC20 is ERC20PermitUpgradeable, IL2StandardToken, ERC1967Upg emit BridgeInitialize(l1Address, _newName, _newSymbol, decimals_); } - modifier onlyBridge() { - if (msg.sender != l2Bridge) { - revert Unauthorized(); - } - _; - } - - modifier onlyNextVersion(uint8 _version) { - // The version should be incremented by 1. Otherwise, the governor risks disabling - // future reinitialization of the token by providing too large a version. - if (_version != _getInitializedVersion() + 1) { - revert NonSequentialVersion(); - } - _; - } - /// @dev Mint tokens to a given account. /// @param _to The account that will receive the created tokens. /// @param _amount The amount that will be created. @@ -165,6 +165,16 @@ contract L2StandardERC20 is ERC20PermitUpgradeable, IL2StandardToken, ERC1967Upg emit BridgeBurn(_from, _amount); } + /// @dev External function to decode a string from bytes. + function decodeString(bytes calldata _input) external pure returns (string memory result) { + (result) = abi.decode(_input, (string)); + } + + /// @dev External function to decode a uint8 from bytes. + function decodeUint8(bytes calldata _input) external pure returns (uint8 result) { + (result) = abi.decode(_input, (uint8)); + } + function name() public view override returns (string memory) { // If method is not available, behave like a token that does not implement this method - revert on call. if (availableGetters.ignoreName) revert Unimplemented(); @@ -182,14 +192,4 @@ contract L2StandardERC20 is ERC20PermitUpgradeable, IL2StandardToken, ERC1967Upg if (availableGetters.ignoreDecimals) revert Unimplemented(); return decimals_; } - - /// @dev External function to decode a string from bytes. - function decodeString(bytes calldata _input) external pure returns (string memory result) { - (result) = abi.decode(_input, (string)); - } - - /// @dev External function to decode a uint8 from bytes. - function decodeUint8(bytes calldata _input) external pure returns (uint8 result) { - (result) = abi.decode(_input, (uint8)); - } } diff --git a/l2-contracts/contracts/bridge/L2WrappedBaseToken.sol b/l2-contracts/contracts/bridge/L2WrappedBaseToken.sol index 8fc3b05b9..03c4bd0c3 100644 --- a/l2-contracts/contracts/bridge/L2WrappedBaseToken.sol +++ b/l2-contracts/contracts/bridge/L2WrappedBaseToken.sol @@ -29,12 +29,24 @@ contract L2WrappedBaseToken is ERC20PermitUpgradeable, IL2WrappedBaseToken, IL2S /// @dev Address of the L1 base token. It can be deposited to mint this L2 token. address public override l1Address; + modifier onlyBridge() { + if (msg.sender != l2Bridge) { + revert Unauthorized(); + } + _; + } + /// @dev Contract is expected to be used as proxy implementation. constructor() { // Disable initialization to prevent Parity hack. _disableInitializers(); } + /// @dev Fallback function to allow receiving Ether. + receive() external payable { + depositTo(msg.sender); + } + /// @notice Initializes a contract token for later use. Expected to be used in the proxy. /// @notice This function is used to integrate the previously deployed WETH token with the bridge. /// @dev Sets up `name`/`symbol`/`decimals` getters. @@ -68,13 +80,6 @@ contract L2WrappedBaseToken is ERC20PermitUpgradeable, IL2WrappedBaseToken, IL2S emit Initialize(name_, symbol_, 18); } - modifier onlyBridge() { - if (msg.sender != l2Bridge) { - revert Unauthorized(); - } - _; - } - /// @notice Function for minting tokens on L2, implemented only to be compatible with IL2StandardToken interface. /// Always reverts instead of minting anything! /// Note: Use `deposit`/`depositTo` methods instead. @@ -122,9 +127,4 @@ contract L2WrappedBaseToken is ERC20PermitUpgradeable, IL2WrappedBaseToken, IL2S revert WithdrawFailed(); } } - - /// @dev Fallback function to allow receiving Ether. - receive() external payable { - depositTo(msg.sender); - } } diff --git a/l2-contracts/contracts/bridge/interfaces/IL1ERC20Bridge.sol b/l2-contracts/contracts/bridge/interfaces/IL1ERC20Bridge.sol index 407669613..4213d1939 100644 --- a/l2-contracts/contracts/bridge/interfaces/IL1ERC20Bridge.sol +++ b/l2-contracts/contracts/bridge/interfaces/IL1ERC20Bridge.sol @@ -3,6 +3,7 @@ pragma solidity 0.8.20; /// @author Matter Labs +/// @custom:security-contact security@matterlabs.dev // note we use the IL1ERC20Bridge only to send L1<>L2 messages, // and we use this interface so that when the switch happened the old messages could be processed interface IL1ERC20Bridge { diff --git a/l2-contracts/contracts/bridge/interfaces/IL2AssetHandler.sol b/l2-contracts/contracts/bridge/interfaces/IL2AssetHandler.sol index 30ee71528..53f6708d7 100644 --- a/l2-contracts/contracts/bridge/interfaces/IL2AssetHandler.sol +++ b/l2-contracts/contracts/bridge/interfaces/IL2AssetHandler.sol @@ -2,22 +2,24 @@ pragma solidity 0.8.20; +/// @author Matter Labs +/// @custom:security-contact security@matterlabs.dev interface IL2AssetHandler { event BridgeMint( - uint256 indexed _chainId, - bytes32 indexed _assetId, - address indexed _sender, - address _l2Receiver, - uint256 _amount + uint256 indexed chainId, + bytes32 indexed assetId, + address indexed sender, + address l2Receiver, + uint256 amount ); event BridgeBurn( - uint256 indexed _chainId, - bytes32 indexed _assetId, + uint256 indexed chainId, + bytes32 indexed assetId, address indexed l2Sender, - address _receiver, - uint256 _mintValue, - uint256 _amount + address receiver, + uint256 mintValue, + uint256 amount ); function bridgeMint(uint256 _chainId, bytes32 _assetId, bytes calldata _transferData) external payable; @@ -27,6 +29,6 @@ interface IL2AssetHandler { uint256 _mintValue, bytes32 _assetId, address _prevMsgSender, - bytes calldata _transferData + bytes calldata _data ) external payable returns (bytes memory _l1BridgeMintData); } diff --git a/l2-contracts/contracts/bridge/interfaces/IL2AssetRouter.sol b/l2-contracts/contracts/bridge/interfaces/IL2AssetRouter.sol index 1245fc3d6..edd677dc6 100644 --- a/l2-contracts/contracts/bridge/interfaces/IL2AssetRouter.sol +++ b/l2-contracts/contracts/bridge/interfaces/IL2AssetRouter.sol @@ -3,28 +3,29 @@ pragma solidity 0.8.20; /// @author Matter Labs +/// @custom:security-contact security@matterlabs.dev interface IL2AssetRouter { - event FinalizeDepositSharedBridge(uint256 chainId, bytes32 indexed assetId, bytes32 assetDataHash); + event FinalizeDepositSharedBridge(uint256 chainId, bytes32 indexed assetId, bytes assetData); event WithdrawalInitiatedSharedBridge( uint256 chainId, address indexed l2Sender, bytes32 indexed assetId, - bytes32 assetDataHash + bytes assetData ); event AssetHandlerRegisteredInitial( bytes32 indexed assetId, - address indexed _assetAddress, + address indexed assetAddress, bytes32 indexed additionalData, address sender ); event AssetHandlerRegistered(bytes32 indexed assetId, address indexed _assetAddress); - function finalizeDeposit(bytes32 _assetId, bytes calldata _data) external; + function finalizeDeposit(bytes32 _assetId, bytes calldata _transferData) external; - function withdraw(bytes32 _assetId, bytes calldata _data) external; + function withdraw(bytes32 _assetId, bytes calldata _transferData) external; function l1Bridge() external view returns (address); diff --git a/l2-contracts/contracts/bridge/interfaces/IL2NativeTokenVault.sol b/l2-contracts/contracts/bridge/interfaces/IL2NativeTokenVault.sol index 0c4e61e93..8b44eba55 100644 --- a/l2-contracts/contracts/bridge/interfaces/IL2NativeTokenVault.sol +++ b/l2-contracts/contracts/bridge/interfaces/IL2NativeTokenVault.sol @@ -6,6 +6,7 @@ pragma solidity 0.8.20; import {IL2AssetHandler} from "./IL2AssetHandler.sol"; /// @author Matter Labs +/// @custom:security-contact security@matterlabs.dev interface IL2NativeTokenVault is IL2AssetHandler { event FinalizeDeposit( address indexed l1Sender, @@ -21,11 +22,13 @@ interface IL2NativeTokenVault is IL2AssetHandler { uint256 amount ); - event L2TokenBeaconUpdated(address l2TokenBeacon, bytes32 l2TokenProxyBytecodeHash); + event L2TokenBeaconUpdated(address indexed l2TokenBeacon, bytes32 indexed l2TokenProxyBytecodeHash); function tokenAddress(bytes32 _assetId) external view returns (address); function l2TokenAddress(address _l1Token) external view returns (address); - function setL2TokenBeacon() external; + function setL2TokenBeacon(address _l2TokenBeacon, bytes32 _l2TokenProxyBytecodeHash) external; + + function configureL2TokenBeacon(bool _contractsDeployedAlready, address _l2TokenBeacon) external; } diff --git a/l2-contracts/contracts/bridge/interfaces/IL2SharedBridgeLegacy.sol b/l2-contracts/contracts/bridge/interfaces/IL2SharedBridgeLegacy.sol deleted file mode 100644 index ee944bb11..000000000 --- a/l2-contracts/contracts/bridge/interfaces/IL2SharedBridgeLegacy.sol +++ /dev/null @@ -1,10 +0,0 @@ -// SPDX-License-Identifier: MIT - -pragma solidity 0.8.20; - -/// @author Matter Labs -interface IL2SharedBridgeLegacy { - function l1TokenAddress(address _l2Token) external view returns (address); - - function l2TokenAddress(address _l1Token) external view returns (address); -} diff --git a/l2-contracts/contracts/bridge/interfaces/IL2StandardToken.sol b/l2-contracts/contracts/bridge/interfaces/IL2StandardToken.sol index 6ceb1ae80..b94c7abff 100644 --- a/l2-contracts/contracts/bridge/interfaces/IL2StandardToken.sol +++ b/l2-contracts/contracts/bridge/interfaces/IL2StandardToken.sol @@ -5,9 +5,9 @@ pragma solidity 0.8.20; interface IL2StandardToken { event BridgeInitialize(address indexed l1Token, string name, string symbol, uint8 decimals); - event BridgeMint(address indexed _account, uint256 _amount); + event BridgeMint(address indexed account, uint256 amount); - event BridgeBurn(address indexed _account, uint256 _amount); + event BridgeBurn(address indexed account, uint256 amount); function bridgeMint(address _account, uint256 _amount) external; diff --git a/l2-contracts/contracts/bridge/interfaces/ILegacyL2SharedBridge.sol b/l2-contracts/contracts/bridge/interfaces/ILegacyL2SharedBridge.sol index 8d9ed32eb..a73f888ab 100644 --- a/l2-contracts/contracts/bridge/interfaces/ILegacyL2SharedBridge.sol +++ b/l2-contracts/contracts/bridge/interfaces/ILegacyL2SharedBridge.sol @@ -3,6 +3,7 @@ pragma solidity 0.8.20; /// @author Matter Labs +/// @custom:security-contact security@matterlabs.dev interface ILegacyL2SharedBridge { function finalizeDeposit( address _l1Sender, @@ -14,5 +15,7 @@ interface ILegacyL2SharedBridge { function withdraw(address _l1Receiver, address _l2Token, uint256 _amount) external; + function getL1TokenAddress(address _l2Token) external view returns (address); + function l2TokenAddress(address _l1Token) external view returns (address); } diff --git a/l2-contracts/contracts/common/libraries/DataEncoding.sol b/l2-contracts/contracts/common/libraries/DataEncoding.sol new file mode 100644 index 000000000..16c97c11a --- /dev/null +++ b/l2-contracts/contracts/common/libraries/DataEncoding.sol @@ -0,0 +1,90 @@ +// SPDX-License-Identifier: MIT + +pragma solidity 0.8.20; + +import {L2_NATIVE_TOKEN_VAULT} from "../../L2ContractHelper.sol"; + +/** + * @author Matter Labs + * @custom:security-contact security@matterlabs.dev + * @notice Helper library for transfer data encoding and decoding to reduce possibility of errors. + */ +library DataEncoding { + /// @notice Abi.encodes the data required for bridgeMint on remote chain. + /// @param _prevMsgSender The address which initiated the transfer. + /// @param _l2Receiver The address which to receive tokens on remote chain. + /// @param _l1Token The transferred token address. + /// @param _amount The amount of token to be transferred. + /// @param _erc20Metadata The transferred token metadata. + /// @return The encoded bridgeMint data + function encodeBridgeMintData( + address _prevMsgSender, + address _l2Receiver, + address _l1Token, + uint256 _amount, + bytes memory _erc20Metadata + ) internal pure returns (bytes memory) { + // solhint-disable-next-line func-named-parameters + return abi.encode(_prevMsgSender, _l2Receiver, _l1Token, _amount, _erc20Metadata); + } + + /// @notice Function decoding transfer data previously encoded with this library. + /// @param _bridgeMintData The encoded bridgeMint data + /// @return _prevMsgSender The address which initiated the transfer. + /// @return _l2Receiver The address which to receive tokens on remote chain. + /// @return _parsedL1Token The transferred token address. + /// @return _amount The amount of token to be transferred. + /// @return _erc20Metadata The transferred token metadata. + function decodeBridgeMintData( + bytes memory _bridgeMintData + ) + internal + pure + returns ( + address _prevMsgSender, + address _l2Receiver, + address _parsedL1Token, + uint256 _amount, + bytes memory _erc20Metadata + ) + { + (_prevMsgSender, _l2Receiver, _parsedL1Token, _amount, _erc20Metadata) = abi.decode( + _bridgeMintData, + (address, address, address, uint256, bytes) + ); + } + + /// @notice Encodes the asset data by combining chain id, asset deployment tracker and asset data. + /// @param _chainId The id of the chain token is native to. + /// @param _assetData The asset data that has to be encoded. + /// @param _sender The asset deployment tracker address. + /// @return The encoded asset data. + function encodeAssetId(uint256 _chainId, bytes32 _assetData, address _sender) internal pure returns (bytes32) { + return keccak256(abi.encode(_chainId, _sender, _assetData)); + } + + /// @notice Encodes the asset data by combining chain id, asset deployment tracker and asset data. + /// @param _chainId The id of the chain token is native to. + /// @param _tokenAaddress The address of token that has to be encoded (asset data is the address itself). + /// @param _sender The asset deployment tracker address. + /// @return The encoded asset data. + function encodeAssetId(uint256 _chainId, address _tokenAaddress, address _sender) internal pure returns (bytes32) { + return keccak256(abi.encode(_chainId, _sender, _tokenAaddress)); + } + + /// @notice Encodes the asset data by combining chain id, NTV as asset deployment tracker and asset data. + /// @param _chainId The id of the chain token is native to. + /// @param _assetData The asset data that has to be encoded. + /// @return The encoded asset data. + function encodeNTVAssetId(uint256 _chainId, bytes32 _assetData) internal pure returns (bytes32) { + return keccak256(abi.encode(_chainId, L2_NATIVE_TOKEN_VAULT, _assetData)); + } + + /// @notice Encodes the asset data by combining chain id, NTV as asset deployment tracker and asset data. + /// @param _chainId The id of the chain token is native to. + /// @param _tokenAddress The address of token that has to be encoded (asset data is the address itself). + /// @return The encoded asset data. + function encodeNTVAssetId(uint256 _chainId, address _tokenAddress) internal pure returns (bytes32) { + return keccak256(abi.encode(_chainId, L2_NATIVE_TOKEN_VAULT, _tokenAddress)); + } +} diff --git a/l2-contracts/contracts/data-availability/RollupL2DAValidator.sol b/l2-contracts/contracts/data-availability/RollupL2DAValidator.sol index 3374c84cb..d81bea055 100644 --- a/l2-contracts/contracts/data-availability/RollupL2DAValidator.sol +++ b/l2-contracts/contracts/data-availability/RollupL2DAValidator.sol @@ -2,8 +2,6 @@ pragma solidity 0.8.20; -// solhint-disable gas-custom-errors, reason-string - import {IL2DAValidator} from "../interfaces/IL2DAValidator.sol"; import {StateDiffL2DAValidator} from "./StateDiffL2DAValidator.sol"; import {PUBDATA_CHUNK_PUBLISHER} from "../L2ContractHelper.sol"; @@ -17,30 +15,25 @@ import {ReconstructionMismatch, PubdataField} from "./DAErrors.sol"; contract RollupL2DAValidator is IL2DAValidator, StateDiffL2DAValidator { function validatePubdata( // The rolling hash of the user L2->L1 logs. - bytes32 _chainedLogsHash, + bytes32, // The root hash of the user L2->L1 logs. bytes32, // The chained hash of the L2->L1 messages bytes32 _chainedMessagesHash, // The chained hash of uncompressed bytecodes sent to L1 - bytes32 _chainedBytescodesHash, + bytes32 _chainedBytecodesHash, // Operator data, that is related to the DA itself bytes calldata _totalL2ToL1PubdataAndStateDiffs ) external returns (bytes32 outputHash) { (bytes32 stateDiffHash, bytes calldata _totalPubdata, bytes calldata leftover) = _produceStateDiffPubdata( - _chainedLogsHash, _chainedMessagesHash, - _chainedBytescodesHash, + _chainedBytecodesHash, _totalL2ToL1PubdataAndStateDiffs ); /// Check for calldata strict format if (leftover.length != 0) { - revert ReconstructionMismatch( - PubdataField.ExtraData, - bytes32(leftover.length + _totalL2ToL1PubdataAndStateDiffs.length), - bytes32(_totalL2ToL1PubdataAndStateDiffs.length) - ); + revert ReconstructionMismatch(PubdataField.ExtraData, bytes32(0), bytes32(leftover.length)); } // The preimage under the hash `outputHash` is expected to be in the following format: diff --git a/l2-contracts/contracts/data-availability/StateDiffL2DAValidator.sol b/l2-contracts/contracts/data-availability/StateDiffL2DAValidator.sol index 3cfe6ccb1..2102b5c28 100644 --- a/l2-contracts/contracts/data-availability/StateDiffL2DAValidator.sol +++ b/l2-contracts/contracts/data-availability/StateDiffL2DAValidator.sol @@ -2,8 +2,6 @@ pragma solidity 0.8.20; -// solhint-disable gas-custom-errors, reason-string - import {ReconstructionMismatch, PubdataField} from "./DAErrors.sol"; import {COMPRESSOR_CONTRACT, L2ContractHelper} from "../L2ContractHelper.sol"; @@ -20,16 +18,15 @@ uint256 constant STATE_DIFF_ENTRY_SIZE = 272; /// A library that could be used by any L2 DA validator to produce standard state-diff-based /// DA output. abstract contract StateDiffL2DAValidator { - /// @notice Validates, that the operator provided the correct preimages for los, messages, and bytecodes. + /// @notice Validates, that the operator provided the correct preimages for logs, messages, and bytecodes. /// @return uncompressedStateDiffHash the hash of the uncompressed state diffs /// @return totalL2Pubdata total pubdata that should be sent to L1. /// @return leftoverSuffix the suffix left after pubdata and uncompressed state diffs. /// On Era or other "vanilla" rollups it is empty, but it can be used for providing additional data by the operator, /// e.g. DA committee signatures, etc. function _produceStateDiffPubdata( - bytes32 _chainedLogsHash, bytes32 _chainedMessagesHash, - bytes32 _chainedBytescodesHash, + bytes32 _chainedBytecodesHash, bytes calldata _totalL2ToL1PubdataAndStateDiffs ) internal @@ -40,19 +37,7 @@ abstract contract StateDiffL2DAValidator { /// Check logs uint32 numberOfL2ToL1Logs = uint32(bytes4(_totalL2ToL1PubdataAndStateDiffs[calldataPtr:calldataPtr + 4])); - calldataPtr += 4; - - bytes32 reconstructedChainedLogsHash; - for (uint256 i = 0; i < numberOfL2ToL1Logs; ++i) { - bytes32 hashedLog = EfficientCall.keccak( - _totalL2ToL1PubdataAndStateDiffs[calldataPtr:calldataPtr + L2_TO_L1_LOG_SERIALIZE_SIZE] - ); - calldataPtr += L2_TO_L1_LOG_SERIALIZE_SIZE; - reconstructedChainedLogsHash = keccak256(abi.encode(reconstructedChainedLogsHash, hashedLog)); - } - if (reconstructedChainedLogsHash != _chainedLogsHash) { - revert ReconstructionMismatch(PubdataField.LogsHash, _chainedLogsHash, reconstructedChainedLogsHash); - } + calldataPtr += 4 + numberOfL2ToL1Logs * L2_TO_L1_LOG_SERIALIZE_SIZE; /// Check messages uint32 numberOfMessages = uint32(bytes4(_totalL2ToL1PubdataAndStateDiffs[calldataPtr:calldataPtr + 4])); @@ -90,10 +75,10 @@ abstract contract StateDiffL2DAValidator { ); calldataPtr += currentBytecodeLength; } - if (reconstructedChainedL1BytecodesRevealDataHash != _chainedBytescodesHash) { + if (reconstructedChainedL1BytecodesRevealDataHash != _chainedBytecodesHash) { revert ReconstructionMismatch( PubdataField.Bytecode, - _chainedBytescodesHash, + _chainedBytecodesHash, reconstructedChainedL1BytecodesRevealDataHash ); } diff --git a/l2-contracts/contracts/data-availability/ValidiumL2DAValidator.sol b/l2-contracts/contracts/data-availability/ValidiumL2DAValidator.sol index 0f7b8cb47..78a49aea8 100644 --- a/l2-contracts/contracts/data-availability/ValidiumL2DAValidator.sol +++ b/l2-contracts/contracts/data-availability/ValidiumL2DAValidator.sol @@ -2,8 +2,6 @@ pragma solidity 0.8.20; -// solhint-disable gas-custom-errors, reason-string - import {IL2DAValidator} from "../interfaces/IL2DAValidator.sol"; /// Rollup DA validator. It will publish data that would allow to use either calldata or blobs. diff --git a/l2-contracts/contracts/interfaces/IL2DAValidator.sol b/l2-contracts/contracts/interfaces/IL2DAValidator.sol index 37c6fdaf5..3289bfc54 100644 --- a/l2-contracts/contracts/interfaces/IL2DAValidator.sol +++ b/l2-contracts/contracts/interfaces/IL2DAValidator.sol @@ -5,14 +5,14 @@ pragma solidity 0.8.20; interface IL2DAValidator { function validatePubdata( // The rolling hash of the user L2->L1 logs. - bytes32 chainedLogsHash, + bytes32 _chainedLogsHash, // The root hash of the user L2->L1 logs. - bytes32 logsRootHash, + bytes32 _logsRootHash, // The chained hash of the L2->L1 messages - bytes32 chainedMessagesHash, + bytes32 _chainedMessagesHash, // The chained hash of uncompressed bytecodes sent to L1 - bytes32 chainedBytescodesHash, + bytes32 _chainedBytecodesHash, // Same operator input - bytes calldata operatorInput + bytes calldata _totalL2ToL1PubdataAndStateDiffs ) external returns (bytes32 outputHash); } diff --git a/l2-contracts/src/deploy-shared-bridge-on-l2-through-l1.ts b/l2-contracts/src/deploy-shared-bridge-on-l2-through-l1.ts index 09c7d1807..3ca81bf74 100644 --- a/l2-contracts/src/deploy-shared-bridge-on-l2-through-l1.ts +++ b/l2-contracts/src/deploy-shared-bridge-on-l2-through-l1.ts @@ -1,6 +1,6 @@ import { Command } from "commander"; import type { BigNumberish } from "ethers"; -import { Wallet } from "ethers"; +import { Wallet, ethers } from "ethers"; import { formatUnits, parseUnits } from "ethers/lib/utils"; import { provider, publishBytecodeFromL1, priorityTxMaxGasLimit } from "./utils"; @@ -63,7 +63,7 @@ async function setL2TokenBeacon(deployer: Deployer, chainId: string, gasPrice: B chainId, L2_NATIVE_TOKEN_VAULT_ADDRESS, gasPrice, - l2NTV.interface.encodeFunctionData("setL2TokenBeacon"), + l2NTV.interface.encodeFunctionData("configureL2TokenBeacon", [false, ethers.constants.AddressZero]), priorityTxMaxGasLimit ); if (deployer.verbose) { diff --git a/l2-contracts/test/erc20.test.ts b/l2-contracts/test/erc20.test.ts index c4138a8f6..ec531f7aa 100644 --- a/l2-contracts/test/erc20.test.ts +++ b/l2-contracts/test/erc20.test.ts @@ -39,6 +39,7 @@ describe("ERC20Bridge", function () { // We won't actually deploy an L1 token in these tests, but we need some address for it. const L1_TOKEN_ADDRESS = "0x1111000000000000000000000000000000001111"; + const L2_ASSET_ROUTER_ADDRESS = "0x0000000000000000000000000000000000010003"; const L2_NATIVE_TOKEN_VAULT_ADDRESS = "0x0000000000000000000000000000000000010004"; const testChainId = 9; @@ -58,18 +59,25 @@ describe("ERC20Bridge", function () { ]); await deployer.deploy(await deployer.loadArtifact("BeaconProxy"), [l2Erc20TokenBeacon.address, "0x"]); const beaconProxyBytecodeHash = hashBytecode((await deployer.loadArtifact("BeaconProxy")).bytecode); - const erc20BridgeContract = await deployer.deploy(await deployer.loadArtifact("L2AssetRouter"), [ - testChainId, - 1, - unapplyL1ToL2Alias(l1BridgeWallet.address), - unapplyL1ToL2Alias(l1BridgeWallet.address), - ]); - erc20Bridge = L2AssetRouterFactory.connect(erc20BridgeContract.address, deployerWallet); + let constructorArgs = ethers.utils.defaultAbiCoder.encode( + ["uint256", "uint256", "address", "address"], + /// note in real deployment we have to transfer ownership of standard deployer here + [testChainId, 1, unapplyL1ToL2Alias(l1BridgeWallet.address), unapplyL1ToL2Alias(l1BridgeWallet.address)] + ); + await setCode( + deployerWallet, + L2_ASSET_ROUTER_ADDRESS, + (await deployer.loadArtifact("L2AssetRouter")).bytecode, + true, + constructorArgs + ); + + erc20Bridge = L2AssetRouterFactory.connect(L2_ASSET_ROUTER_ADDRESS, deployerWallet); const l2NativeTokenVaultArtifact = await deployer.loadArtifact("L2NativeTokenVault"); - const constructorArgs = ethers.utils.defaultAbiCoder.encode( - ["bytes32", "address", "bool"], + constructorArgs = ethers.utils.defaultAbiCoder.encode( + ["uint256", "bytes32", "address", "bool"], /// note in real deployment we have to transfer ownership of standard deployer here - [beaconProxyBytecodeHash, governorWallet.address, contractsDeployedAlready] + [1, beaconProxyBytecodeHash, governorWallet.address, contractsDeployedAlready] ); await setCode( deployerWallet, @@ -80,12 +88,12 @@ describe("ERC20Bridge", function () { ); erc20NativeTokenVault = L2NativeTokenVaultFactory.connect(L2_NATIVE_TOKEN_VAULT_ADDRESS, l1BridgeWallet); - await erc20NativeTokenVault.setL2TokenBeacon(); + const governorNTV = L2NativeTokenVaultFactory.connect(L2_NATIVE_TOKEN_VAULT_ADDRESS, governorWallet); + await governorNTV.configureL2TokenBeacon(false, ethers.constants.AddressZero); }); it("Should finalize deposit ERC20 deposit", async function () { const erc20BridgeWithL1BridgeWallet = L2AssetRouterFactory.connect(erc20Bridge.address, proxyAdminWallet); - const l1Depositor = ethers.Wallet.createRandom(); const l2Receiver = ethers.Wallet.createRandom(); const l1Bridge = await hre.ethers.getImpersonatedSigner(l1BridgeWallet.address); diff --git a/system-contracts/SystemContractsHashes.json b/system-contracts/SystemContractsHashes.json index b263ada0c..049311271 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": "0x0100005d0d5963b3b09cf356e290f28e43e05db5d9c1469fac8c8dc7b3abf2af", - "sourceCodeHash": "0x69d2533e5481ff13e65f4442e650f4b90c46a48ac643cac9798bbbf421194353" + "bytecodeHash": "0x0100005d25ca74d6dd4855495f92b76a7542f3b9a1145129bef7c874d11de0cd", + "sourceCodeHash": "0xea3806fcaf7728463f559fe195d8acdc47a7659d58119e0a51efcf86a691b61b" }, { "contractName": "BootloaderUtilities", "bytecodePath": "artifacts-zk/contracts-preprocessed/BootloaderUtilities.sol/BootloaderUtilities.json", "sourceCodePath": "contracts-preprocessed/BootloaderUtilities.sol", - "bytecodeHash": "0x010007c76573d4e76a30b911b65daa0af4a643ec8f9414262521b2491b505e49", - "sourceCodeHash": "0x26060f33c7c63bd1f8a1a2f3b368b97ef8dd939bc53e95090f2c556248b99dce" + "bytecodeHash": "0x010007c7fe668eff6936a7eb0bfd603671014aaa47c52e4847ddc1f65e4940bf", + "sourceCodeHash": "0x9d2b7376c4cd9b143ddd5dfe001a9faae99b9125ccd45f2915c3ce0099643ed9" }, { "contractName": "ComplexUpgrader", "bytecodePath": "artifacts-zk/contracts-preprocessed/ComplexUpgrader.sol/ComplexUpgrader.json", "sourceCodePath": "contracts-preprocessed/ComplexUpgrader.sol", - "bytecodeHash": "0x0100004dd7c52ea918d3bab6d8850449b80692184d0297b3c042eaae751225fe", + "bytecodeHash": "0x0100004d0dcb8cebf6f81f46011cd8ec8984e7ee82448ddd99e2c15da45fcaa2", "sourceCodeHash": "0xdde7c49a94cc3cd34c3e7ced1b5ba45e4740df68d26243871edbe393e7298f7a" }, { "contractName": "Compressor", "bytecodePath": "artifacts-zk/contracts-preprocessed/Compressor.sol/Compressor.json", "sourceCodePath": "contracts-preprocessed/Compressor.sol", - "bytecodeHash": "0x0100013f1d46c6bcfb0caabd15a0dc907f91460e085260c2fe40a108a505513a", + "bytecodeHash": "0x0100013ffa6d46d34d3c93202665387eb170af768e5e40e4e98f4b3484dac3ca", "sourceCodeHash": "0xb0cec0016f481ce023478f71727fbc0d82e967ddc0508e4d47f5c52292a3f790" }, { "contractName": "ContractDeployer", "bytecodePath": "artifacts-zk/contracts-preprocessed/ContractDeployer.sol/ContractDeployer.json", "sourceCodePath": "contracts-preprocessed/ContractDeployer.sol", - "bytecodeHash": "0x010004e5a00bf6227b8b2203553a4557b7bc326647c20c1173f21707792f02ad", - "sourceCodeHash": "0x1657e45c2ff4d6a892afaf71dab71bb8dc7db2c6863e66240e83dfdd2535420d" + "bytecodeHash": "0x010004e5d1fbad7f0a7e656eaf78187f7c46b83122643853f132c61e8c431019", + "sourceCodeHash": "0xea9627fd5e6e905c268ba801e87bf2d9022bea036982d2b54425f2388b27e6b1" }, { "contractName": "Create2Factory", "bytecodePath": "artifacts-zk/contracts-preprocessed/Create2Factory.sol/Create2Factory.json", "sourceCodePath": "contracts-preprocessed/Create2Factory.sol", - "bytecodeHash": "0x01000049a7d2acae5204fb196e8eec1dde41171b0da737cea75dbd49e80f9be6", + "bytecodeHash": "0x0100004993b764833fd41d3324324b8a5408b83a79bca1c5f2218e25f6540e65", "sourceCodeHash": "0x217e65f55c8add77982171da65e0db8cc10141ba75159af582973b332a4e098a" }, { "contractName": "DefaultAccount", "bytecodePath": "artifacts-zk/contracts-preprocessed/DefaultAccount.sol/DefaultAccount.json", "sourceCodePath": "contracts-preprocessed/DefaultAccount.sol", - "bytecodeHash": "0x0100055de356de05b75c83195567a6688d9050a17b58ccc5c5c91d05cd2bfb6d", + "bytecodeHash": "0x0100055dbf6d8f56faa896e4cc8e987aa24aaaee5fe85fc17243d908b9b74f69", "sourceCodeHash": "0xeb5ac8fc83e1c8619db058a9b6973958bd6ed1b6f4938f8f4541d702f12e085d" }, { @@ -59,63 +59,63 @@ "contractName": "ImmutableSimulator", "bytecodePath": "artifacts-zk/contracts-preprocessed/ImmutableSimulator.sol/ImmutableSimulator.json", "sourceCodePath": "contracts-preprocessed/ImmutableSimulator.sol", - "bytecodeHash": "0x0100003bd47713cb1de6f8839bd2f1f8679a4360da7f423e2f3eaa6bb3e86028", + "bytecodeHash": "0x0100003b05ace5d6b4b0f296b19d72c048f79bdb8cb3ee647806b07f4e5bbcd8", "sourceCodeHash": "0x4212e99cbc1722887cfb5b4cb967f278ac8642834786f0e3c6f3b324a9316815" }, { "contractName": "KnownCodesStorage", "bytecodePath": "artifacts-zk/contracts-preprocessed/KnownCodesStorage.sol/KnownCodesStorage.json", "sourceCodePath": "contracts-preprocessed/KnownCodesStorage.sol", - "bytecodeHash": "0x0100006fc57f216e7189ec08828a4fe4b1e67cb7336ca0173e5fe6e44e830f7f", + "bytecodeHash": "0x0100006fa9bd44feb2af80203eb413026decdd7c1ccb52b539e7a8e8f2f42f29", "sourceCodeHash": "0x8da495a9fc5aa0d7d20a165a4fc8bc77012bec29c472015ea5ecc0a2bd706137" }, { "contractName": "L1Messenger", "bytecodePath": "artifacts-zk/contracts-preprocessed/L1Messenger.sol/L1Messenger.json", "sourceCodePath": "contracts-preprocessed/L1Messenger.sol", - "bytecodeHash": "0x010001e9ec59f6aad98b91b7853b805e6c5b935112247cf5267094d0d1785067", - "sourceCodeHash": "0x2bc0dd1f7c3a9e1c8b7f2045e4be3cbec84079b5ec3863a90f71741f30ea1a6c" + "bytecodeHash": "0x010001f5931d8a6310005972dbb01adb4211b29e5ad0a22c682d70f34c4b4e48", + "sourceCodeHash": "0xa275cd393320fba29e5c94f399c1ae6743b4221b05f13b395a00648dcedc2540" }, { "contractName": "L2BaseToken", "bytecodePath": "artifacts-zk/contracts-preprocessed/L2BaseToken.sol/L2BaseToken.json", "sourceCodePath": "contracts-preprocessed/L2BaseToken.sol", - "bytecodeHash": "0x01000105a82f8eec41fbe25de2a833b9816fb171de7e2f650a44ad085dc08bc9", + "bytecodeHash": "0x010001052a89f68a2a3a2e4b4c67b740859d3fd95a0cde8317441047cf60db63", "sourceCodeHash": "0x4cdafafd4cfdf410b31641e14487ea657be3af25e5ec1754fcd7ad67ec23d8be" }, { "contractName": "L2GenesisUpgrade", "bytecodePath": "artifacts-zk/contracts-preprocessed/L2GenesisUpgrade.sol/L2GenesisUpgrade.json", "sourceCodePath": "contracts-preprocessed/L2GenesisUpgrade.sol", - "bytecodeHash": "0x010000974222230beff5f3bdcfb4b58940fe547a2907fc7417d4dd26a7abe07f", + "bytecodeHash": "0x0100009732b021d0c301d499dde61446b021b62c0ea93c78f3e3cd8501bf09c5", "sourceCodeHash": "0xcb190d0dfd41bbc809409a8aa04a4847b86edfe010b1d75e23b4c8d07b13a9d0" }, { "contractName": "MsgValueSimulator", "bytecodePath": "artifacts-zk/contracts-preprocessed/MsgValueSimulator.sol/MsgValueSimulator.json", "sourceCodePath": "contracts-preprocessed/MsgValueSimulator.sol", - "bytecodeHash": "0x0100005dec0422e4514f78cc4ad2435ba011998d59b584c77438d9505eb03b4a", + "bytecodeHash": "0x0100005d2589089ef8e7f7af29293876bc496266bafaa2b704d09627bf271fbd", "sourceCodeHash": "0x4834adf62dbaefa1a1c15d36b5ad1bf2826e7d888a17be495f7ed4e4ea381aa8" }, { "contractName": "NonceHolder", "bytecodePath": "artifacts-zk/contracts-preprocessed/NonceHolder.sol/NonceHolder.json", "sourceCodePath": "contracts-preprocessed/NonceHolder.sol", - "bytecodeHash": "0x010000db7a7a40b0058bfc4d65c3067ecfc59be76f3e4e46a82197ed36dce36f", + "bytecodeHash": "0x010000db506ed94921e7f4145fdacc21063000067bfe50e8af0f061519c04b6c", "sourceCodeHash": "0xaa2ed3a26af30032c00a612ac327e0cdf5288b7c932ae903462355f863f950cb" }, { "contractName": "PubdataChunkPublisher", "bytecodePath": "artifacts-zk/contracts-preprocessed/PubdataChunkPublisher.sol/PubdataChunkPublisher.json", "sourceCodePath": "contracts-preprocessed/PubdataChunkPublisher.sol", - "bytecodeHash": "0x0100004bb2b5083d5de7af7f05491bbd733d3b90917168c461f25dd0e0eda0c0", - "sourceCodeHash": "0xc442fd1abb821c95e8229f4ef1575f789e89d60d1001489f5ac9250155cd11dc" + "bytecodeHash": "0x010000494900cd3cd761e2676208fc86bc197228ce615e4665e5223bc31a9bd8", + "sourceCodeHash": "0x0da0d1279f906147a40e278f52bf3e4d5d4f24225935e4611cc04f4b387b5286" }, { "contractName": "SystemContext", "bytecodePath": "artifacts-zk/contracts-preprocessed/SystemContext.sol/SystemContext.json", "sourceCodePath": "contracts-preprocessed/SystemContext.sol", - "bytecodeHash": "0x010001a7276ef9e99743b84cd8ba4d7f8bc15359c73fec896f68e3c9c458e5a4", + "bytecodeHash": "0x010001a7d4d930ab07e144e2b7cf47ec3042b7c84bdfd6122ed13662e5ad790d", "sourceCodeHash": "0x532a962209042f948e8a13e3f4cf12b6d53631e0fc5fa53083c7e2d8062771c0" }, { @@ -185,35 +185,35 @@ "contractName": "bootloader_test", "bytecodePath": "bootloader/build/artifacts/bootloader_test.yul.zbin", "sourceCodePath": "bootloader/build/bootloader_test.yul", - "bytecodeHash": "0x010003cb0d9a7fd2cd5a66c58de8f4a53b1b7e35eaaa18a73b6da4ac74e45f4e", - "sourceCodeHash": "0x492c3bae09bfa1016e848fcb8e914b56ac0638a21a55b3f13e403acb8f06ca38" + "bytecodeHash": "0x010003cbab1bb46695dfda825246f92120398b9573f355c1ff795c30184ac2b2", + "sourceCodeHash": "0x02b3e8d6c1d542a123d50aa6d04bc9736a92376e922c3304650001636e942a95" }, { "contractName": "fee_estimate", "bytecodePath": "bootloader/build/artifacts/fee_estimate.yul.zbin", "sourceCodePath": "bootloader/build/fee_estimate.yul", - "bytecodeHash": "0x010009554a9569ee48f845ec9b0068714ff68b99f65764db567d4d3f2922f734", - "sourceCodeHash": "0x52a157b3320e621fda2ee069dcecdc7df466263db343bb4af054e6647c282790" + "bytecodeHash": "0x0100095516027c64c3252e7193d9a6d6148100e2c7b9387f15d856e6713aa0e9", + "sourceCodeHash": "0x7a1d1d2a5534c659fcc343178e9f9d68ccb3f49ce8e54a81a6c20d15c46b2fd9" }, { "contractName": "gas_test", "bytecodePath": "bootloader/build/artifacts/gas_test.yul.zbin", "sourceCodePath": "bootloader/build/gas_test.yul", - "bytecodeHash": "0x010008db84b2abb44133921b7598dec788a7c8df0dc76a06da46e54a2ac0f422", - "sourceCodeHash": "0x027e26ad98372a45fae1d70ef9e1b4efcbf5bc51bd400de5c0ce5650d55b7b43" + "bytecodeHash": "0x010008db6f54b61ecdb2c6a8f7575fa87c205402839318f429f6fa2beb8b00c2", + "sourceCodeHash": "0x37f3b77504a53d38ce29c8521264c10dc6f9bbc80f15814862a0e4a29f0f1612" }, { "contractName": "playground_batch", "bytecodePath": "bootloader/build/artifacts/playground_batch.yul.zbin", "sourceCodePath": "bootloader/build/playground_batch.yul", - "bytecodeHash": "0x0100095b12c6e008ba4e00bc8bddb609d99125d2bbdd423bcb377afc344729a0", - "sourceCodeHash": "0x1f3a2b06df0a4a4ddd8164e0542cb07e309a51408bd21ff6ecfa1df3aea47cbb" + "bytecodeHash": "0x0100095b6cbccf0b0af548f8ccb027d02c90681ae9890a41a23ecf4b590e72c4", + "sourceCodeHash": "0x1c9b760bc9abd39183ecaf1397465c149c63c317e107214ac8e72737c49cd5da" }, { "contractName": "proved_batch", "bytecodePath": "bootloader/build/artifacts/proved_batch.yul.zbin", "sourceCodePath": "bootloader/build/proved_batch.yul", - "bytecodeHash": "0x010008eb70b467979695d3f240d8db04b1b179dd02c0d7fd45a027fb4bd9ecaf", - "sourceCodeHash": "0x310e9a53b96b1861fee5eff8f4e338f88005a7d0012068fb0ee7f1b5476e5d96" + "bytecodeHash": "0x010008eb2ba1ec6290b553a401b9609363d9cc89796b3dd1aad8fb24cd88702e", + "sourceCodeHash": "0x3230de3b65d823d6f5142d7217a0597948d0f8744a8c1f0a9271b7eb40cfdf5b" } ] diff --git a/system-contracts/bootloader/bootloader.yul b/system-contracts/bootloader/bootloader.yul index 87f3190d3..3c1c669bd 100644 --- a/system-contracts/bootloader/bootloader.yul +++ b/system-contracts/bootloader/bootloader.yul @@ -2744,7 +2744,7 @@ object "Bootloader" { // Third slot -- length of pubdata let len := mload(add(ptr, 96)) - // 4 bytes for selector, 32 bytes for ABI-encoded l2 DA validator address, + // 4 bytes for selector, 32 bytes for ABI-encoded L2 DA validator address, // 32 bytes for array offset and 32 bytes for array length let fullLen := add(len, 100) diff --git a/system-contracts/contracts/AccountCodeStorage.sol b/system-contracts/contracts/AccountCodeStorage.sol index 5df2fdd8b..820d57305 100644 --- a/system-contracts/contracts/AccountCodeStorage.sol +++ b/system-contracts/contracts/AccountCodeStorage.sol @@ -14,7 +14,7 @@ import {Unauthorized, InvalidCodeHash, CodeHashReason} from "./SystemContractErr * @dev Code hash is not strictly a hash, it's a structure where the first byte denotes the version of the hash, * the second byte denotes whether the contract is constructed, and the next two bytes denote the length in 32-byte words. * And then the next 28 bytes are the truncated hash. - * @dev In this version of zkSync, the first byte of the hash MUST be 1. + * @dev In this version of ZKsync, the first byte of the hash MUST be 1. * @dev The length of each bytecode MUST be odd. It's internal code format requirements, due to padding of SHA256 function. * @dev It is also assumed that all the bytecode hashes are *known*, i.e. the full bytecodes * were published on L1 as calldata. This contract trusts the ContractDeployer and the KnownCodesStorage diff --git a/system-contracts/contracts/BootloaderUtilities.sol b/system-contracts/contracts/BootloaderUtilities.sol index 0aafee7be..642f9641e 100644 --- a/system-contracts/contracts/BootloaderUtilities.sol +++ b/system-contracts/contracts/BootloaderUtilities.sol @@ -177,7 +177,7 @@ contract BootloaderUtilities is IBootloaderUtilities { // Otherwise the length is not encoded at all. } - // On zkSync, access lists are always zero length (at least for now). + // On ZKsync, access lists are always zero length (at least for now). bytes memory encodedAccessListLength = RLPEncoder.encodeListLen(0); bytes memory rEncoded; @@ -276,7 +276,7 @@ contract BootloaderUtilities is IBootloaderUtilities { // Otherwise the length is not encoded at all. } - // On zkSync, access lists are always zero length (at least for now). + // On ZKsync, access lists are always zero length (at least for now). bytes memory encodedAccessListLength = RLPEncoder.encodeListLen(0); bytes memory rEncoded; diff --git a/system-contracts/contracts/Constants.sol b/system-contracts/contracts/Constants.sol index e8a8db0a5..b253d1e61 100644 --- a/system-contracts/contracts/Constants.sol +++ b/system-contracts/contracts/Constants.sol @@ -16,7 +16,7 @@ import {IBootloaderUtilities} from "./interfaces/IBootloaderUtilities.sol"; import {IPubdataChunkPublisher} from "./interfaces/IPubdataChunkPublisher.sol"; import {IMessageRoot} from "./interfaces/IMessageRoot.sol"; -/// @dev All the system contracts introduced by zkSync have their addresses +/// @dev All the system contracts introduced by ZKsync have their addresses /// started from 2^15 in order to avoid collision with Ethereum precompiles. uint160 constant SYSTEM_CONTRACTS_OFFSET = {{SYSTEM_CONTRACTS_OFFSET}}; // 2^15 diff --git a/system-contracts/contracts/ContractDeployer.sol b/system-contracts/contracts/ContractDeployer.sol index b03109862..2584c3a52 100644 --- a/system-contracts/contracts/ContractDeployer.sol +++ b/system-contracts/contracts/ContractDeployer.sol @@ -15,7 +15,7 @@ import {Unauthorized, InvalidNonceOrderingChange, ValuesNotEqual, EmptyBytes32, /** * @author Matter Labs * @custom:security-contact security@matterlabs.dev - * @notice System smart contract that is responsible for deploying other smart contracts on zkSync. + * @notice System smart contract that is responsible for deploying other smart contracts on ZKsync. * @dev The contract is responsible for generating the address of the deployed smart contract, * incrementing the deployment nonce and making sure that the constructor is never called twice in a contract. * Note, contracts with bytecode that have already been published to L1 once diff --git a/system-contracts/contracts/L1Messenger.sol b/system-contracts/contracts/L1Messenger.sol index dc1d7d796..53d518bc8 100644 --- a/system-contracts/contracts/L1Messenger.sol +++ b/system-contracts/contracts/L1Messenger.sol @@ -184,9 +184,9 @@ contract L1Messenger is IL1Messenger, ISystemContract { emit BytecodeL1PublicationRequested(_bytecodeHash); } - /// @notice Verifies that the {_totalL2ToL1PubdataAndStateDiffs} reflects what occurred within the L1Batch and that + /// @notice Verifies that the {_operatorInput} reflects what occurred within the L1Batch and that /// the compressed statediffs are equivalent to the full state diffs. - /// @param _totalL2ToL1PubdataAndStateDiffs The total pubdata and uncompressed state diffs of transactions that were + /// @param _operatorInput The total pubdata and uncompressed state diffs of transactions that were /// processed in the current L1 Batch. Pubdata consists of L2 to L1 Logs, messages, deployed bytecode, and state diffs. /// @dev Function that should be called exactly once per L1 Batch by the bootloader. /// @dev Checks that totalL2ToL1Pubdata is strictly packed data that should to be published to L1. @@ -196,7 +196,7 @@ contract L1Messenger is IL1Messenger, ISystemContract { /// to L1 using low-level (VM) L2Log. function publishPubdataAndClearState( address _l2DAValidator, - bytes calldata _totalL2ToL1PubdataAndStateDiffs + bytes calldata _operatorInput ) external onlyCallFromBootloader { uint256 calldataPtr = 0; @@ -210,9 +210,7 @@ contract L1Messenger is IL1Messenger, ISystemContract { // Operator data: 32 bytes for offset // 32 bytes for length - bytes4 inputL2DAValidatePubdataFunctionSig = bytes4( - _totalL2ToL1PubdataAndStateDiffs[calldataPtr:calldataPtr + 4] - ); + bytes4 inputL2DAValidatePubdataFunctionSig = bytes4(_operatorInput[calldataPtr:calldataPtr + 4]); if (inputL2DAValidatePubdataFunctionSig != IL2DAValidator.validatePubdata.selector) { revert ReconstructionMismatch( PubdataField.InputDAFunctionSig, @@ -222,23 +220,23 @@ contract L1Messenger is IL1Messenger, ISystemContract { } calldataPtr += 4; - bytes32 inputChainedLogsHash = bytes32(_totalL2ToL1PubdataAndStateDiffs[calldataPtr:calldataPtr + 32]); + bytes32 inputChainedLogsHash = bytes32(_operatorInput[calldataPtr:calldataPtr + 32]); if (inputChainedLogsHash != chainedLogsHash) { revert ReconstructionMismatch(PubdataField.InputLogsHash, chainedLogsHash, inputChainedLogsHash); } calldataPtr += 32; // Check happens below after we reconstruct the logs root hash - bytes32 inputChainedLogsRootHash = bytes32(_totalL2ToL1PubdataAndStateDiffs[calldataPtr:calldataPtr + 32]); + bytes32 inputChainedLogsRootHash = bytes32(_operatorInput[calldataPtr:calldataPtr + 32]); calldataPtr += 32; - bytes32 inputChainedMsgsHash = bytes32(_totalL2ToL1PubdataAndStateDiffs[calldataPtr:calldataPtr + 32]); + bytes32 inputChainedMsgsHash = bytes32(_operatorInput[calldataPtr:calldataPtr + 32]); if (inputChainedMsgsHash != chainedMessagesHash) { revert ReconstructionMismatch(PubdataField.InputMsgsHash, chainedMessagesHash, inputChainedMsgsHash); } calldataPtr += 32; - bytes32 inputChainedBytecodesHash = bytes32(_totalL2ToL1PubdataAndStateDiffs[calldataPtr:calldataPtr + 32]); + bytes32 inputChainedBytecodesHash = bytes32(_operatorInput[calldataPtr:calldataPtr + 32]); if (inputChainedBytecodesHash != chainedL1BytecodesRevealDataHash) { revert ReconstructionMismatch( PubdataField.InputBytecodeHash, @@ -247,11 +245,21 @@ contract L1Messenger is IL1Messenger, ISystemContract { ); } calldataPtr += 32; + + uint256 offset = uint256(bytes32(_operatorInput[calldataPtr:calldataPtr + 32])); + // The length of the pubdata input should be stored right next to the calldata. + // We need to change offset by 32 - 4 = 28 bytes, since 32 bytes is the length of the offset + // itself and the 4 bytes are the selector which is not included inside the offset. + if (offset != calldataPtr + 28) { + revert ReconstructionMismatch(PubdataField.Offset, bytes32(calldataPtr + 28), bytes32(offset)); + } + uint256 length = uint256(bytes32(_operatorInput[calldataPtr + 32:calldataPtr + 64])); + // Shift calldata ptr past the pubdata offset and len calldataPtr += 64; /// Check logs - uint32 numberOfL2ToL1Logs = uint32(bytes4(_totalL2ToL1PubdataAndStateDiffs[calldataPtr:calldataPtr + 4])); + uint32 numberOfL2ToL1Logs = uint32(bytes4(_operatorInput[calldataPtr:calldataPtr + 4])); if (numberOfL2ToL1Logs > L2_TO_L1_LOGS_MERKLE_TREE_LEAVES) { revert ReconstructionMismatch( PubdataField.NumberOfLogs, @@ -261,11 +269,20 @@ contract L1Messenger is IL1Messenger, ISystemContract { } calldataPtr += 4; + // We need to ensure that length is enough to read all logs + if (length < 4 + numberOfL2ToL1Logs * L2_TO_L1_LOG_SERIALIZE_SIZE) { + revert ReconstructionMismatch( + PubdataField.Length, + bytes32(4 + numberOfL2ToL1Logs * L2_TO_L1_LOG_SERIALIZE_SIZE), + bytes32(length) + ); + } + bytes32[] memory l2ToL1LogsTreeArray = new bytes32[](L2_TO_L1_LOGS_MERKLE_TREE_LEAVES); bytes32 reconstructedChainedLogsHash; for (uint256 i = 0; i < numberOfL2ToL1Logs; ++i) { bytes32 hashedLog = EfficientCall.keccak( - _totalL2ToL1PubdataAndStateDiffs[calldataPtr:calldataPtr + L2_TO_L1_LOG_SERIALIZE_SIZE] + _operatorInput[calldataPtr:calldataPtr + L2_TO_L1_LOG_SERIALIZE_SIZE] ); calldataPtr += L2_TO_L1_LOG_SERIALIZE_SIZE; l2ToL1LogsTreeArray[i] = hashedLog; @@ -301,7 +318,7 @@ contract L1Messenger is IL1Messenger, ISystemContract { _gas: gasleft(), _address: _l2DAValidator, _value: 0, - _data: _totalL2ToL1PubdataAndStateDiffs, + _data: _operatorInput, _isSystem: false }); diff --git a/system-contracts/contracts/PubdataChunkPublisher.sol b/system-contracts/contracts/PubdataChunkPublisher.sol index 2e98e8b83..e0c0f02e1 100644 --- a/system-contracts/contracts/PubdataChunkPublisher.sol +++ b/system-contracts/contracts/PubdataChunkPublisher.sol @@ -25,7 +25,7 @@ contract PubdataChunkPublisher is IPubdataChunkPublisher, ISystemContract { blobLinearHashes = new bytes32[](blobCount); - // We allocate to the full size of blobLinearHashes * BLOB_SIZE_BYTES because we need to pad + // We allocate to the full size of blobCount * BLOB_SIZE_BYTES because we need to pad // the data on the right with 0s if it doesn't take up the full blob bytes memory totalBlobs = new bytes(BLOB_SIZE_BYTES * blobCount); @@ -38,12 +38,6 @@ contract PubdataChunkPublisher is IPubdataChunkPublisher, ISystemContract { for (uint256 i = 0; i < blobCount; ++i) { uint256 start = BLOB_SIZE_BYTES * i; - // We break if the pubdata isn't enough to cover all 6 blobs. On L1 it is expected that the hash - // will be bytes32(0) if a blob isn't going to be used. - if (start >= _pubdata.length) { - break; - } - bytes32 blobHash; assembly { // The pointer to the allocated memory above skipping the length. diff --git a/system-contracts/contracts/SystemContractErrors.sol b/system-contracts/contracts/SystemContractErrors.sol index fd553c3cb..aa4857c49 100644 --- a/system-contracts/contracts/SystemContractErrors.sol +++ b/system-contracts/contracts/SystemContractErrors.sol @@ -68,7 +68,9 @@ enum PubdataField { InputLogsHash, InputLogsRootHash, InputMsgsHash, - InputBytecodeHash + InputBytecodeHash, + Offset, + Length } enum BytecodeError { diff --git a/system-contracts/contracts/interfaces/IComplexUpgrader.sol b/system-contracts/contracts/interfaces/IComplexUpgrader.sol index 1b5e15182..8ed670a10 100644 --- a/system-contracts/contracts/interfaces/IComplexUpgrader.sol +++ b/system-contracts/contracts/interfaces/IComplexUpgrader.sol @@ -1,6 +1,6 @@ // SPDX-License-Identifier: MIT - -pragma solidity 0.8.20; +// We use a floating point pragma here so it can be used within other projects that interact with the zkSync ecosystem without using our exact pragma version. +pragma solidity ^0.8.20; /** * @author Matter Labs diff --git a/system-contracts/contracts/interfaces/IL2DAValidator.sol b/system-contracts/contracts/interfaces/IL2DAValidator.sol index 3e3d1239c..4c8c6d4c4 100644 --- a/system-contracts/contracts/interfaces/IL2DAValidator.sol +++ b/system-contracts/contracts/interfaces/IL2DAValidator.sol @@ -1,6 +1,6 @@ // SPDX-License-Identifier: MIT - -pragma solidity ^0.8.0; +// We use a floating point pragma here so it can be used within other projects that interact with the zkSync ecosystem without using our exact pragma version. +pragma solidity ^0.8.20; interface IL2DAValidator { function validatePubdata( @@ -11,8 +11,8 @@ interface IL2DAValidator { // The chained hash of the L2->L1 messages bytes32 _chainedMessagesHash, // The chained hash of uncompressed bytecodes sent to L1 - bytes32 _chainedBytescodesHash, + bytes32 _chainedBytecodesHash, // Same operator input - bytes calldata _operatorInput + bytes calldata _totalL2ToL1PubdataAndStateDiffs ) external returns (bytes32 outputHash); } diff --git a/system-contracts/contracts/interfaces/IL2GenesisUpgrade.sol b/system-contracts/contracts/interfaces/IL2GenesisUpgrade.sol index cf4568515..96744b152 100644 --- a/system-contracts/contracts/interfaces/IL2GenesisUpgrade.sol +++ b/system-contracts/contracts/interfaces/IL2GenesisUpgrade.sol @@ -1,4 +1,5 @@ -// SPDX-License-Identifier: UNLICENSED +// SPDX-License-Identifier: MIT +// We use a floating point pragma here so it can be used within other projects that interact with the zkSync ecosystem without using our exact pragma version. pragma solidity ^0.8.20; interface IL2GenesisUpgrade { diff --git a/system-contracts/contracts/interfaces/IMessageRoot.sol b/system-contracts/contracts/interfaces/IMessageRoot.sol index 0983b71a4..f158b4918 100644 --- a/system-contracts/contracts/interfaces/IMessageRoot.sol +++ b/system-contracts/contracts/interfaces/IMessageRoot.sol @@ -3,6 +3,5 @@ pragma solidity 0.8.20; interface IMessageRoot { - function clearTreeAndProvidePubdata() external returns (bytes memory pubdata); function getAggregatedRoot() external view returns (bytes32 aggregatedRoot); } diff --git a/system-contracts/contracts/interfaces/ISystemContract.sol b/system-contracts/contracts/interfaces/ISystemContract.sol index 1f383a823..d86ba49ea 100644 --- a/system-contracts/contracts/interfaces/ISystemContract.sol +++ b/system-contracts/contracts/interfaces/ISystemContract.sol @@ -3,8 +3,8 @@ pragma solidity 0.8.20; import {SystemContractHelper} from "../libraries/SystemContractHelper.sol"; -import {BOOTLOADER_FORMAL_ADDRESS, FORCE_DEPLOYER} from "../Constants.sol"; -import {SystemCallFlagRequired, Unauthorized, CallerMustBeSystemContract, CallerMustBeBootloader, CallerMustBeForceDeployer} from "../SystemContractErrors.sol"; +import {BOOTLOADER_FORMAL_ADDRESS} from "../Constants.sol"; +import {SystemCallFlagRequired, Unauthorized, CallerMustBeSystemContract, CallerMustBeBootloader} from "../SystemContractErrors.sol"; /** * @author Matter Labs @@ -51,13 +51,4 @@ abstract contract ISystemContract { } _; } - - /// @notice Modifier that makes sure that the method - /// can only be called from the L1 force deployer. - modifier onlyCallFromForceDeployer() { - if (msg.sender != FORCE_DEPLOYER) { - revert CallerMustBeForceDeployer(); - } - _; - } } diff --git a/system-contracts/contracts/libraries/SystemContractsCaller.sol b/system-contracts/contracts/libraries/SystemContractsCaller.sol index d964fbbe7..9364b68ec 100644 --- a/system-contracts/contracts/libraries/SystemContractsCaller.sol +++ b/system-contracts/contracts/libraries/SystemContractsCaller.sol @@ -6,7 +6,7 @@ import {MSG_VALUE_SYSTEM_CONTRACT, MSG_VALUE_SIMULATOR_IS_SYSTEM_BIT} from "../C import {Utils} from "./Utils.sol"; // Addresses used for the compiler to be replaced with the -// zkSync-specific opcodes during the compilation. +// ZKsync-specific opcodes during the compilation. // IMPORTANT: these are just compile-time constants and are used // only if used in-place by Yul optimizer. address constant TO_L1_CALL_ADDRESS = address((1 << 16) - 1); diff --git a/system-contracts/contracts/libraries/TransactionHelper.sol b/system-contracts/contracts/libraries/TransactionHelper.sol index fbdc57919..701fd9985 100644 --- a/system-contracts/contracts/libraries/TransactionHelper.sol +++ b/system-contracts/contracts/libraries/TransactionHelper.sol @@ -11,7 +11,7 @@ import {RLPEncoder} from "./RLPEncoder.sol"; import {EfficientCall} from "./EfficientCall.sol"; import {UnsupportedTxType, InvalidInput, UnsupportedPaymasterFlow} from "../SystemContractErrors.sol"; -/// @dev The type id of zkSync's EIP-712-signed transaction. +/// @dev The type id of ZKsync's EIP-712-signed transaction. uint8 constant EIP_712_TX_TYPE = 0x71; /// @dev The type id of legacy transactions. @@ -21,7 +21,7 @@ uint8 constant EIP_2930_TX_TYPE = 0x01; /// @dev The type id of EIP1559 transactions. uint8 constant EIP_1559_TX_TYPE = 0x02; -/// @notice Structure used to represent a zkSync transaction. +/// @notice Structure used to represent a ZKsync transaction. struct Transaction { // The type of the transaction. uint256 txType; @@ -114,7 +114,7 @@ library TransactionHelper { } } - /// @notice Encode hash of the zkSync native transaction type. + /// @notice Encode hash of the ZKsync native transaction type. /// @return keccak256 hash of the EIP-712 encoded representation of transaction function _encodeHashEIP712Transaction(Transaction calldata _transaction) private view returns (bytes32) { bytes32 structHash = keccak256( @@ -223,7 +223,7 @@ library TransactionHelper { // Hash of EIP2930 transactions is encoded the following way: // H(0x01 || RLP(chain_id, nonce, gas_price, gas_limit, destination, amount, data, access_list)) // - // Note, that on zkSync access lists are not supported and should always be empty. + // Note, that on ZKsync access lists are not supported and should always be empty. // Encode all fixed-length params to avoid "stack too deep error" bytes memory encodedFixedLengthParams; @@ -261,7 +261,7 @@ library TransactionHelper { // Otherwise the length is not encoded at all. } - // On zkSync, access lists are always zero length (at least for now). + // On ZKsync, access lists are always zero length (at least for now). bytes memory encodedAccessListLength = RLPEncoder.encodeListLen(0); bytes memory encodedListLength; @@ -295,7 +295,7 @@ library TransactionHelper { // Hash of EIP1559 transactions is encoded the following way: // H(0x02 || RLP(chain_id, nonce, max_priority_fee_per_gas, max_fee_per_gas, gas_limit, destination, amount, data, access_list)) // - // Note, that on zkSync access lists are not supported and should always be empty. + // Note, that on ZKsync access lists are not supported and should always be empty. // Encode all fixed-length params to avoid "stack too deep error" bytes memory encodedFixedLengthParams; @@ -335,7 +335,7 @@ library TransactionHelper { // Otherwise the length is not encoded at all. } - // On zkSync, access lists are always zero length (at least for now). + // On ZKsync, access lists are always zero length (at least for now). bytes memory encodedAccessListLength = RLPEncoder.encodeListLen(0); bytes memory encodedListLength; diff --git a/system-contracts/contracts/libraries/Utils.sol b/system-contracts/contracts/libraries/Utils.sol index 4f535e2c1..cfad32cf7 100644 --- a/system-contracts/contracts/libraries/Utils.sol +++ b/system-contracts/contracts/libraries/Utils.sol @@ -7,7 +7,7 @@ import {MalformedBytecode, BytecodeError, Overflow} from "../SystemContractError /** * @author Matter Labs * @custom:security-contact security@matterlabs.dev - * @dev Common utilities used in zkSync system contracts + * @dev Common utilities used in ZKsync system contracts */ library Utils { /// @dev Bit mask of bytecode hash "isConstructor" marker diff --git a/tools/data/verifier_contract_template.txt b/tools/data/verifier_contract_template.txt index 972123961..5ef32b2c5 100644 --- a/tools/data/verifier_contract_template.txt +++ b/tools/data/verifier_contract_template.txt @@ -8,14 +8,14 @@ import {IVerifier} from "./chain-interfaces/IVerifier.sol"; /// @author Matter Labs /// @notice Modified version of the Permutations over Lagrange-bases for Oecumenical Noninteractive arguments of /// Knowledge (PLONK) verifier. -/// Modifications have been made to optimize the proof system for zkSync hyperchain circuits. +/// Modifications have been made to optimize the proof system for ZKsync hyperchain circuits. /// @dev Contract was generated from a verification key with a hash of 0x{{vk_hash}} /// @dev It uses a custom memory layout inside the inline assembly block. Each reserved memory cell is declared in the /// constants below. /// @dev For a better understanding of the verifier algorithm please refer to the following papers: /// * Original Plonk Article: https://eprint.iacr.org/2019/953.pdf /// * Original LookUp Article: https://eprint.iacr.org/2020/315.pdf -/// * Plonk for zkSync v1.1: https://github.com/matter-labs/solidity_plonk_verifier/raw/recursive/bellman_vk_codegen_recursive/RecursivePlonkUnrolledForEthereum.pdf +/// * Plonk for ZKsync v1.1: https://github.com/matter-labs/solidity_plonk_verifier/raw/recursive/bellman_vk_codegen_recursive/RecursivePlonkUnrolledForEthereum.pdf /// The notation used in the code is the same as in the papers. /* solhint-enable max-line-length */ contract Verifier is IVerifier {