Skip to content

Commit

Permalink
Merge pull request #842 from matter-labs/kl/sync-layer-reorg
Browse files Browse the repository at this point in the history
feat: periodic merge with multiple fixes
  • Loading branch information
kelemeno authored Oct 8, 2024
2 parents a3ba4b3 + 53b0283 commit 9e4a192
Show file tree
Hide file tree
Showing 58 changed files with 2,146 additions and 902 deletions.
1 change: 1 addition & 0 deletions l1-contracts/.env
Original file line number Diff line number Diff line change
Expand Up @@ -44,3 +44,4 @@ TOKENS_CONFIG=/script-config/config-deploy-erc20.toml
ZK_CHAIN_CONFIG=/script-config/register-zk-chain.toml
ZK_CHAIN_OUTPUT=/script-out/output-deploy-zk-chain-era.toml
FORCE_DEPLOYMENTS_CONFIG=/script-config/generate-force-deployments-data.toml
GATEWAY_PREPARATION_L1_CONFIG=/script-config/gateway-preparation-l1.toml
16 changes: 15 additions & 1 deletion l1-contracts/contracts/bridge/BridgedStandardERC20.sol
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import {IBridgedStandardToken} from "./interfaces/IBridgedStandardToken.sol";
import {Unauthorized, NonSequentialVersion, ZeroAddress} from "../common/L1ContractErrors.sol";
import {L2_NATIVE_TOKEN_VAULT_ADDR} from "../common/L2ContractAddresses.sol";
import {DataEncoding} from "../common/libraries/DataEncoding.sol";
import {INativeTokenVault} from "../bridge/ntv/INativeTokenVault.sol";

/// @author Matter Labs
/// @custom:security-contact [email protected]
Expand Down Expand Up @@ -43,13 +44,20 @@ contract BridgedStandardERC20 is ERC20PermitUpgradeable, IBridgedStandardToken,
/// @dev Address of the native token vault that is used as trustee who can mint/burn tokens
address public nativeTokenVault;

/// @dev The assetId of the token.
bytes32 public assetId;

/// @dev This also sets the native token vault to the default value if it is not set.
/// It is not set only on the L2s for legacy tokens.
modifier onlyNTV() {
address ntv = nativeTokenVault;
if (ntv == address(0)) {
ntv = L2_NATIVE_TOKEN_VAULT_ADDR;
nativeTokenVault = L2_NATIVE_TOKEN_VAULT_ADDR;
assetId = DataEncoding.encodeNTVAssetId(
INativeTokenVault(L2_NATIVE_TOKEN_VAULT_ADDR).L1_CHAIN_ID(),
originToken
);
}
if (msg.sender != ntv) {
revert Unauthorized(msg.sender);
Expand All @@ -74,14 +82,20 @@ contract BridgedStandardERC20 is ERC20PermitUpgradeable, IBridgedStandardToken,

/// @notice Initializes a contract token for later use. Expected to be used in the proxy.
/// @dev Stores the L1 address of the bridge and set `name`/`symbol`/`decimals` getters that L1 token has.
/// @param _assetId The assetId of the token.
/// @param _originToken Address of the origin token that can be deposited to mint this bridged token
/// @param _data The additional data that the L1 bridge provide for initialization.
/// In this case, it is packed `name`/`symbol`/`decimals` of the L1 token.
function bridgeInitialize(address _originToken, bytes calldata _data) external initializer returns (uint256) {
function bridgeInitialize(
bytes32 _assetId,
address _originToken,
bytes calldata _data
) external initializer returns (uint256) {
if (_originToken == address(0)) {
revert ZeroAddress();
}
originToken = _originToken;
assetId = _assetId;

nativeTokenVault = msg.sender;

Expand Down
15 changes: 9 additions & 6 deletions l1-contracts/contracts/bridge/L1Nullifier.sol
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@ import {IL1NativeTokenVault} from "./ntv/IL1NativeTokenVault.sol";
import {IL1ERC20Bridge} from "./interfaces/IL1ERC20Bridge.sol";
import {IL1AssetRouter} from "./asset-router/IL1AssetRouter.sol";
import {IAssetRouterBase} from "./asset-router/IAssetRouterBase.sol";
import {INativeTokenVault} from "./ntv/INativeTokenVault.sol";

import {IL1Nullifier, FinalizeL1DepositParams} from "./interfaces/IL1Nullifier.sol";

Expand Down Expand Up @@ -511,7 +510,7 @@ contract L1Nullifier is IL1Nullifier, ReentrancyGuard, Ownable2StepUpgradeable,
/// @return transferData The transfer data used to finalize withdawal.
function _verifyWithdrawal(
FinalizeL1DepositParams calldata _finalizeWithdrawalParams
) internal view returns (bytes32 assetId, bytes memory transferData) {
) internal returns (bytes32 assetId, bytes memory transferData) {
(assetId, transferData) = _parseL2WithdrawalMessage(
_finalizeWithdrawalParams.chainId,
_finalizeWithdrawalParams.message
Expand Down Expand Up @@ -560,7 +559,7 @@ contract L1Nullifier is IL1Nullifier, ReentrancyGuard, Ownable2StepUpgradeable,
function _parseL2WithdrawalMessage(
uint256 _chainId,
bytes memory _l2ToL1message
) internal view returns (bytes32 assetId, bytes memory transferData) {
) internal returns (bytes32 assetId, bytes memory transferData) {
// Please note that there are three versions of the message:
// 1. The message that is sent from `L2BaseToken` to withdraw base token.
// 2. The message that is sent from L2 Legacy Shared Bridge to withdraw ERC20 tokens or base token.
Expand Down Expand Up @@ -604,6 +603,7 @@ contract L1Nullifier is IL1Nullifier, ReentrancyGuard, Ownable2StepUpgradeable,
// slither-disable-next-line unused-return
(amount, ) = UnsafeBytes.readUint256(_l2ToL1message, offset);

l1NativeTokenVault.ensureTokenIsRegistered(l1Token);
assetId = DataEncoding.encodeNTVAssetId(block.chainid, l1Token);
transferData = DataEncoding.encodeBridgeMintData({
_originalCaller: address(0),
Expand All @@ -616,7 +616,7 @@ contract L1Nullifier is IL1Nullifier, ReentrancyGuard, Ownable2StepUpgradeable,
// The data is expected to be at least 36 bytes long to contain assetId.
require(_l2ToL1message.length >= 36, "L1N: wrong msg len"); // wrong message length
// slither-disable-next-line unused-return
(, offset) = UnsafeBytes.readBytes32(_l2ToL1message, offset); // originChainId, not used for L2->L1 txs
(, offset) = UnsafeBytes.readUint256(_l2ToL1message, offset); // originChainId, not used for L2->L1 txs
(assetId, offset) = UnsafeBytes.readBytes32(_l2ToL1message, offset);
transferData = UnsafeBytes.readRemainingBytes(_l2ToL1message, offset);
} else {
Expand Down Expand Up @@ -648,7 +648,10 @@ contract L1Nullifier is IL1Nullifier, ReentrancyGuard, Ownable2StepUpgradeable,
uint16 _l2TxNumberInBatch,
bytes32[] calldata _merkleProof
) external override {
bytes32 assetId = INativeTokenVault(address(l1NativeTokenVault)).getAssetId(block.chainid, _l1Token);
bytes32 assetId = l1NativeTokenVault.assetId(_l1Token);
if (assetId == bytes32(0)) {
assetId = DataEncoding.encodeNTVAssetId(block.chainid, _l1Token);
}
// For legacy deposits, the l2 receiver is not required to check tx data hash
// bytes memory transferData = abi.encode(_amount, _depositSender);
bytes memory assetData = abi.encode(_amount, address(0));
Expand Down Expand Up @@ -702,7 +705,7 @@ contract L1Nullifier is IL1Nullifier, ReentrancyGuard, Ownable2StepUpgradeable,
) external override onlyLegacyBridge {
bytes memory assetData = abi.encode(_amount, _depositSender);
/// the legacy bridge can only be used with L1 native tokens.
bytes32 assetId = INativeTokenVault(address(l1NativeTokenVault)).getAssetId(block.chainid, _l1Asset);
bytes32 assetId = DataEncoding.encodeNTVAssetId(block.chainid, _l1Asset);

_verifyAndClearFailedTransfer({
_checkedInLegacyBridge: true,
Expand Down
4 changes: 2 additions & 2 deletions l1-contracts/contracts/bridge/L2SharedBridgeLegacy.sol
Original file line number Diff line number Diff line change
Expand Up @@ -190,8 +190,8 @@ contract L2SharedBridgeLegacy is IL2SharedBridgeLegacy, Initializable {
proxy = abi.decode(returndata, (address));
}

function sendMessageToL1(bytes calldata _message) external override onlyAssetRouter {
function sendMessageToL1(bytes calldata _message) external override onlyAssetRouter returns (bytes32) {
// slither-disable-next-line unused-return
L2ContractHelper.sendMessageToL1(_message);
return L2ContractHelper.sendMessageToL1(_message);
}
}
19 changes: 18 additions & 1 deletion l1-contracts/contracts/bridge/L2WrappedBaseToken.sol
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import {ERC20PermitUpgradeable} from "@openzeppelin/contracts-upgradeable-v4/tok

import {IL2WrappedBaseToken} from "./interfaces/IL2WrappedBaseToken.sol";
import {IBridgedStandardToken} from "./interfaces/IBridgedStandardToken.sol";
import {L2_NATIVE_TOKEN_VAULT_ADDR} from "../common/L2ContractAddresses.sol";

import {ZeroAddress, Unauthorized, BridgeMintNotImplemented, WithdrawFailed} from "../common/L1ContractErrors.sol";

Expand All @@ -29,6 +30,12 @@ contract L2WrappedBaseToken is ERC20PermitUpgradeable, IL2WrappedBaseToken, IBri
/// @dev Address of the L1 base token. It can be deposited to mint this L2 token.
address public override l1Address;

/// @dev Address of the native token vault.
address public override nativeTokenVault;

/// @dev The assetId of the base token. The wrapped token does not have its own assetId.
bytes32 public baseTokenAssetId;

modifier onlyBridge() {
if (msg.sender != l2Bridge) {
revert Unauthorized(msg.sender);
Expand Down Expand Up @@ -59,7 +66,8 @@ contract L2WrappedBaseToken is ERC20PermitUpgradeable, IL2WrappedBaseToken, IBri
string calldata name_,
string calldata symbol_,
address _l2Bridge,
address _l1Address
address _l1Address,
bytes32 _baseTokenAssetId
) external reinitializer(2) {
if (_l2Bridge == address(0)) {
revert ZeroAddress();
Expand All @@ -68,8 +76,13 @@ contract L2WrappedBaseToken is ERC20PermitUpgradeable, IL2WrappedBaseToken, IBri
if (_l1Address == address(0)) {
revert ZeroAddress();
}
if (_baseTokenAssetId == bytes32(0)) {
revert ZeroAddress();
}
l2Bridge = _l2Bridge;
l1Address = _l1Address;
nativeTokenVault = L2_NATIVE_TOKEN_VAULT_ADDR;
baseTokenAssetId = _baseTokenAssetId;

// Set decoded values for name and symbol.
__ERC20_init_unchained(name_, symbol_);
Expand Down Expand Up @@ -131,4 +144,8 @@ contract L2WrappedBaseToken is ERC20PermitUpgradeable, IL2WrappedBaseToken, IBri
function originToken() external view override returns (address) {
return l1Address;
}

function assetId() external view override returns (bytes32) {
return baseTokenAssetId;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -149,6 +149,12 @@ abstract contract AssetRouterBase is IAssetRouterBase, Ownable2StepUpgradeable,
});
}

/// @notice Ensures that token is registered with native token vault.
/// @dev Only used when deposit is made with legacy data encoding format.
/// @param _token The native token address which should be registered with native token vault.
/// @return assetId The asset ID of the token provided.
function _ensureTokenRegisteredWithNTV(address _token) internal virtual returns (bytes32 assetId);

/*//////////////////////////////////////////////////////////////
PAUSE
//////////////////////////////////////////////////////////////*/
Expand Down
29 changes: 29 additions & 0 deletions l1-contracts/contracts/bridge/asset-router/IL1AssetRouter.sol
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,29 @@ interface IL1AssetRouter is IAssetRouterBase, IL1SharedBridgeLegacy {
bytes calldata _assetData
) external;

/// @dev Withdraw funds from the initiated deposit, that failed when finalizing on L2.
/// @param _chainId The ZK chain id to which deposit was initiated.
/// @param _depositSender The address of the entity that initiated the deposit.
/// @param _assetId The unique identifier of the deposited L1 token.
/// @param _assetData The encoded transfer data, which includes both the deposit amount and the address of the L2 receiver. Might include extra information.
/// @param _l2TxHash The L2 transaction hash of the failed deposit finalization.
/// @param _l2BatchNumber The L2 batch number where the deposit finalization was processed.
/// @param _l2MessageIndex The position in the L2 logs Merkle tree of the l2Log that was sent with the message.
/// @param _l2TxNumberInBatch The L2 transaction number in a batch, in which the log was sent.
/// @param _merkleProof The Merkle proof of the processing L1 -> L2 transaction with deposit finalization.
/// @dev Processes claims of failed deposit, whether they originated from the legacy bridge or the current system.
function bridgeRecoverFailedTransfer(
uint256 _chainId,
address _depositSender,
bytes32 _assetId,
bytes memory _assetData,
bytes32 _l2TxHash,
uint256 _l2BatchNumber,
uint256 _l2MessageIndex,
uint16 _l2TxNumberInBatch,
bytes32[] calldata _merkleProof
) external;

/// @notice Transfers funds to Native Token Vault, if the asset is registered with it. Does nothing for ETH or non-registered tokens.
/// @dev assetId is not the padded address, but the correct encoded id (NTV stores respective format for IDs)
/// @param _amount The asset amount to be transferred to native token vault.
Expand Down Expand Up @@ -169,4 +192,10 @@ interface IL1AssetRouter is IAssetRouterBase, IL1SharedBridgeLegacy {
/// @param _txDataHash The keccak256 hash of 0x01 || abi.encode(bytes32, bytes) to identify deposits.
/// @param _txHash The hash of the L1->L2 transaction to confirm the deposit.
function bridgehubConfirmL2Transaction(uint256 _chainId, bytes32 _txDataHash, bytes32 _txHash) external;

function isWithdrawalFinalized(
uint256 _chainId,
uint256 _l2BatchNumber,
uint256 _l2MessageIndex
) external view returns (bool);
}
6 changes: 4 additions & 2 deletions l1-contracts/contracts/bridge/asset-router/IL2AssetRouter.sol
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,19 @@

pragma solidity ^0.8.20;

import {IAssetRouterBase} from "./IAssetRouterBase.sol";

/// @author Matter Labs
/// @custom:security-contact [email protected]
interface IL2AssetRouter {
interface IL2AssetRouter is IAssetRouterBase {
event WithdrawalInitiatedAssetRouter(
uint256 chainId,
address indexed l2Sender,
bytes32 indexed assetId,
bytes assetData
);

function withdraw(bytes32 _assetId, bytes calldata _transferData) external;
function withdraw(bytes32 _assetId, bytes calldata _transferData) external returns (bytes32);

function l1AssetRouter() external view returns (address);

Expand Down
46 changes: 41 additions & 5 deletions l1-contracts/contracts/bridge/asset-router/L1AssetRouter.sol
Original file line number Diff line number Diff line change
Expand Up @@ -335,6 +335,31 @@ contract L1AssetRouter is AssetRouterBase, IL1AssetRouter, ReentrancyGuard {

emit ClaimedFailedDepositAssetRouter(_chainId, _assetId, _assetData);
}

function bridgeRecoverFailedTransfer(
uint256 _chainId,
address _depositSender,
bytes32 _assetId,
bytes calldata _assetData,
bytes32 _l2TxHash,
uint256 _l2BatchNumber,
uint256 _l2MessageIndex,
uint16 _l2TxNumberInBatch,
bytes32[] calldata _merkleProof
) external {
L1_NULLIFIER.bridgeRecoverFailedTransfer({
_chainId: _chainId,
_depositSender: _depositSender,
_assetId: _assetId,
_assetData: _assetData,
_l2TxHash: _l2TxHash,
_l2BatchNumber: _l2BatchNumber,
_l2MessageIndex: _l2MessageIndex,
_l2TxNumberInBatch: _l2TxNumberInBatch,
_merkleProof: _merkleProof
});
}

/*//////////////////////////////////////////////////////////////
Internal & Helpers
//////////////////////////////////////////////////////////////*/
Expand All @@ -354,13 +379,15 @@ contract L1AssetRouter is AssetRouterBase, IL1AssetRouter, ReentrancyGuard {

/// @notice Ensures that token is registered with native token vault.
/// @dev Only used when deposit is made with legacy data encoding format.
/// @param _token The L1 token address which should be registered with native token vault.
/// @param _token The native token address which should be registered with native token vault.
/// @return assetId The asset ID of the token provided.
function _ensureTokenRegisteredWithNTV(address _token) internal returns (bytes32 assetId) {
assetId = nativeTokenVault.getAssetId(block.chainid, _token);
if (nativeTokenVault.tokenAddress(assetId) == address(0)) {
nativeTokenVault.registerToken(_token);
function _ensureTokenRegisteredWithNTV(address _token) internal override returns (bytes32 assetId) {
assetId = nativeTokenVault.assetId(_token);
if (assetId != bytes32(0)) {
return assetId;
}
nativeTokenVault.ensureTokenIsRegistered(_token);
assetId = nativeTokenVault.assetId(_token);
}

/// @inheritdoc IL1AssetRouter
Expand Down Expand Up @@ -586,6 +613,15 @@ contract L1AssetRouter is AssetRouterBase, IL1AssetRouter, ReentrancyGuard {
});
}

/// @notice Legacy read method, which forwards the call to L1Nullifier to check if withdrawal was finalized
function isWithdrawalFinalized(
uint256 _chainId,
uint256 _l2BatchNumber,
uint256 _l2MessageIndex
) external view returns (bool) {
return L1_NULLIFIER.isWithdrawalFinalized(_chainId, _l2BatchNumber, _l2MessageIndex);
}

/// @notice Legacy function to get the L2 shared bridge address for a chain.
/// @dev In case the chain has been deployed after the gateway release,
/// the returned value is 0.
Expand Down
Loading

0 comments on commit 9e4a192

Please sign in to comment.