Skip to content

Commit

Permalink
Merge pull request #920 from matter-labs/kl/h01-2gw
Browse files Browse the repository at this point in the history
Kl/h01 2gw
  • Loading branch information
kelemeno authored Oct 15, 2024
2 parents 8208402 + 15ef6d8 commit 7e719c5
Show file tree
Hide file tree
Showing 7 changed files with 181 additions and 52 deletions.
5 changes: 2 additions & 3 deletions l1-contracts/contracts/bridge/BridgedStandardERC20.sol
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ contract BridgedStandardERC20 is ERC20PermitUpgradeable, IBridgedStandardToken,
/// @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(address _originToken, bytes calldata _data) external initializer {
if (_originToken == address(0)) {
revert ZeroAddress();
}
Expand All @@ -86,7 +86,7 @@ contract BridgedStandardERC20 is ERC20PermitUpgradeable, IBridgedStandardToken,
nativeTokenVault = msg.sender;

// We parse the data exactly as they were created on the L1 bridge
(uint256 chainId, bytes memory nameBytes, bytes memory symbolBytes, bytes memory decimalsBytes) = DataEncoding
(uint256, bytes memory nameBytes, bytes memory symbolBytes, bytes memory decimalsBytes) = DataEncoding
.decodeTokenData(_data);

ERC20Getters memory getters;
Expand Down Expand Up @@ -129,7 +129,6 @@ contract BridgedStandardERC20 is ERC20PermitUpgradeable, IBridgedStandardToken,

availableGetters = getters;
emit BridgeInitialize(_originToken, decodedName, decodedSymbol, decimals_);
return chainId;
}

/// @notice A method to be called by the governor to update the token's metadata.
Expand Down
2 changes: 1 addition & 1 deletion l1-contracts/contracts/bridge/ntv/L1NativeTokenVault.sol
Original file line number Diff line number Diff line change
Expand Up @@ -234,7 +234,7 @@ contract L1NativeTokenVault is IL1NativeTokenVault, IL1AssetHandler, NativeToken
}
}

function _deployBeaconProxy(bytes32 _salt) internal override returns (BeaconProxy proxy) {
function _deployBeaconProxy(bytes32 _salt, uint256) internal override returns (BeaconProxy proxy) {
// Use CREATE2 to deploy the BeaconProxy
address proxyAddress = Create2.deploy(
0,
Expand Down
69 changes: 47 additions & 22 deletions l1-contracts/contracts/bridge/ntv/L2NativeTokenVault.sol
Original file line number Diff line number Diff line change
Expand Up @@ -91,32 +91,32 @@ contract L2NativeTokenVault is IL2NativeTokenVault, NativeTokenVault {
}

/// @notice Ensures that the token is deployed.
/// @param _originChainId The chain ID of the origin chain.
/// @param _assetId The asset ID.
/// @param _originToken The origin token address.
/// @param _erc20Data The ERC20 data.
/// @return expectedToken The token address.
function _ensureTokenDeployed(
uint256 _originChainId,
function _ensureAndSaveTokenDeployed(
bytes32 _assetId,
address _originToken,
bytes memory _erc20Data
) internal override returns (address expectedToken) {
expectedToken = _assetIdCheck(_originChainId, _assetId, _originToken);
uint256 tokenOriginChainId;
(expectedToken, tokenOriginChainId) = _calculateExpectedTokenAddress(_originToken, _erc20Data);
address l1LegacyToken;
if (address(L2_LEGACY_SHARED_BRIDGE) != address(0)) {
l1LegacyToken = L2_LEGACY_SHARED_BRIDGE.l1TokenAddress(expectedToken);
}

if (l1LegacyToken != address(0)) {
/// token is a legacy token, no need to deploy
if (l1LegacyToken != _originToken) {
revert AddressMismatch(_originToken, l1LegacyToken);
}
tokenAddress[_assetId] = expectedToken;
_ensureAndSaveTokenDeployedInnerLegacyToken({
_assetId: _assetId,
_originToken: _originToken,
_expectedToken: expectedToken,
_l1LegacyToken: l1LegacyToken
});
} else {
super._ensureTokenDeployedInner({
_originChainId: _originChainId,
super._ensureAndSaveTokenDeployedInner({
_tokenOriginChainId: tokenOriginChainId,
_assetId: _assetId,
_originToken: _originToken,
_erc20Data: _erc20Data,
Expand All @@ -125,13 +125,34 @@ contract L2NativeTokenVault is IL2NativeTokenVault, NativeTokenVault {
}
}

/// @notice Deploys the beacon proxy for the L2 token, while using ContractDeployer system contract.
/// @notice Ensures that the token is deployed inner for legacy tokens.
function _ensureAndSaveTokenDeployedInnerLegacyToken(
bytes32 _assetId,
address _originToken,
address _expectedToken,
address _l1LegacyToken
) internal {
_assetIdCheck(L1_CHAIN_ID, _assetId, _originToken);

/// token is a legacy token, no need to deploy
if (_l1LegacyToken != _originToken) {
revert AddressMismatch(_originToken, _l1LegacyToken);
}

tokenAddress[_assetId] = _expectedToken;
}

/// @notice Deploys the beacon proxy for the L2 token, while using ContractDeployer system contract or the legacy shared bridge.
/// @dev This function uses raw call to ContractDeployer to make sure that exactly `l2TokenProxyBytecodeHash` is used
/// for the code of the proxy.
/// @param _salt The salt used for beacon proxy deployment of L2 bridged token.
/// @param _tokenOriginChainId The origin chain id of the token.
/// @return proxy The beacon proxy, i.e. L2 bridged token.
function _deployBeaconProxy(bytes32 _salt) internal override returns (BeaconProxy proxy) {
if (address(L2_LEGACY_SHARED_BRIDGE) == address(0)) {
function _deployBeaconProxy(
bytes32 _salt,
uint256 _tokenOriginChainId
) internal virtual override returns (BeaconProxy proxy) {
if (address(L2_LEGACY_SHARED_BRIDGE) == address(0) || _tokenOriginChainId != L1_CHAIN_ID) {
// Deploy the beacon proxy for the L2 token

(bool success, bytes memory returndata) = SystemContractsCaller.systemCallWithReturndata(
Expand Down Expand Up @@ -170,17 +191,18 @@ contract L2NativeTokenVault is IL2NativeTokenVault, NativeTokenVault {
//////////////////////////////////////////////////////////////*/

/// @notice Calculates L2 wrapped token address given the currently stored beacon proxy bytecode hash and beacon address.
/// @param _tokenOriginChainId The chain id of the origin token.
/// @param _l1Token The address of token on L1.
/// @return Address of an L2 token counterpart.
function calculateCreate2TokenAddress(
uint256 _originChainId,
uint256 _tokenOriginChainId,
address _l1Token
) public view override(INativeTokenVault, NativeTokenVault) returns (address) {
bytes32 constructorInputHash = keccak256(abi.encode(address(bridgedTokenBeacon), ""));
bytes32 salt = _getCreate2Salt(_originChainId, _l1Token);
if (address(L2_LEGACY_SHARED_BRIDGE) != address(0)) {
) public view virtual override(INativeTokenVault, NativeTokenVault) returns (address) {
if (address(L2_LEGACY_SHARED_BRIDGE) != address(0) && _tokenOriginChainId == L1_CHAIN_ID) {
return L2_LEGACY_SHARED_BRIDGE.l2TokenAddress(_l1Token);
} else {
bytes32 constructorInputHash = keccak256(abi.encode(address(bridgedTokenBeacon), ""));
bytes32 salt = _getCreate2Salt(_tokenOriginChainId, _l1Token);
return
L2ContractHelper.computeCreate2Address(
address(this),
Expand All @@ -192,10 +214,13 @@ contract L2NativeTokenVault is IL2NativeTokenVault, NativeTokenVault {
}

/// @notice Calculates the salt for the Create2 deployment of the L2 token.
function _getCreate2Salt(uint256 _originChainId, address _l1Token) internal view override returns (bytes32 salt) {
salt = _originChainId == L1_CHAIN_ID
function _getCreate2Salt(
uint256 _tokenOriginChainId,
address _l1Token
) internal view override returns (bytes32 salt) {
salt = _tokenOriginChainId == L1_CHAIN_ID
? bytes32(uint256(uint160(_l1Token)))
: keccak256(abi.encode(_originChainId, _l1Token));
: keccak256(abi.encode(_tokenOriginChainId, _l1Token));
}

function _handleChainBalanceIncrease(
Expand Down
75 changes: 50 additions & 25 deletions l1-contracts/contracts/bridge/ntv/NativeTokenVault.sol
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ import {DataEncoding} from "../../common/libraries/DataEncoding.sol";
import {BridgedStandardERC20} from "../BridgedStandardERC20.sol";
import {BridgeHelper} from "../BridgeHelper.sol";

import {EmptyDeposit, Unauthorized, TokensWithFeesNotSupported, TokenNotSupported, NonEmptyMsgValue, ValueMismatch, AddressMismatch, AssetIdMismatch, AmountMustBeGreaterThanZero, ZeroAddress} from "../../common/L1ContractErrors.sol";
import {EmptyDeposit, Unauthorized, TokensWithFeesNotSupported, TokenNotSupported, NonEmptyMsgValue, ValueMismatch, AddressMismatch, AssetIdMismatch, AmountMustBeGreaterThanZero, ZeroAddress, DeployingBridgedTokenForNativeToken} from "../../common/L1ContractErrors.sol";

/// @author Matter Labs
/// @custom:security-contact [email protected]
Expand Down Expand Up @@ -132,7 +132,7 @@ abstract contract NativeTokenVault is INativeTokenVault, IAssetHandler, Ownable2
(, receiver, originToken, amount, erc20Data) = DataEncoding.decodeBridgeMintData(_data);

if (token == address(0)) {
token = _ensureTokenDeployed(_originChainId, _assetId, originToken, erc20Data);
token = _ensureAndSaveTokenDeployed(_assetId, originToken, erc20Data);
}
_handleChainBalanceDecrease(_originChainId, _assetId, amount, false);
IBridgedStandardToken(token).bridgeMint(receiver, amount);
Expand Down Expand Up @@ -354,43 +354,59 @@ abstract contract NativeTokenVault is INativeTokenVault, IAssetHandler, Ownable2
TOKEN DEPLOYER FUNCTIONS
//////////////////////////////////////////////////////////////*/

function _ensureTokenDeployed(
uint256 _originChainId,
function _ensureAndSaveTokenDeployed(
bytes32 _assetId,
address _originToken,
bytes memory _erc20Data
) internal virtual returns (address expectedToken) {
expectedToken = _assetIdCheck(_originChainId, _assetId, _originToken);
_ensureTokenDeployedInner({
_originChainId: _originChainId,
uint256 tokenOriginChainId;
(expectedToken, tokenOriginChainId) = _calculateExpectedTokenAddress(_originToken, _erc20Data);
_ensureAndSaveTokenDeployedInner({
_tokenOriginChainId: tokenOriginChainId,
_assetId: _assetId,
_originToken: _originToken,
_erc20Data: _erc20Data,
_expectedToken: expectedToken
});
}

function _assetIdCheck(
uint256 _originChainId,
bytes32 _assetId,
address _originToken
) internal view returns (address expectedToken) {
expectedToken = calculateCreate2TokenAddress(_originChainId, _originToken);
bytes32 expectedAssetId = DataEncoding.encodeNTVAssetId(_originChainId, _originToken);
/// @notice Calculates the bridged token address corresponding to native token counterpart.
function _calculateExpectedTokenAddress(
address _originToken,
bytes memory _erc20Data
) internal view returns (address expectedToken, uint256 tokenOriginChainId) {
/// @dev calling externally to convert from memory to calldata
tokenOriginChainId = this.tokenDataOriginChainId(_erc20Data);
expectedToken = calculateCreate2TokenAddress(tokenOriginChainId, _originToken);
}

/// @notice Returns the origin chain id from the token data.
function tokenDataOriginChainId(bytes calldata _erc20Data) public view returns (uint256 tokenOriginChainId) {
(tokenOriginChainId, , , ) = DataEncoding.decodeTokenData(_erc20Data);
if (tokenOriginChainId == 0) {
tokenOriginChainId = L1_CHAIN_ID;
}
}

/// @notice Checks that the assetId is correct for the origin token and chain.
function _assetIdCheck(uint256 _tokenOriginChainId, bytes32 _assetId, address _originToken) internal view {
bytes32 expectedAssetId = DataEncoding.encodeNTVAssetId(_tokenOriginChainId, _originToken);
if (_assetId != expectedAssetId) {
// Make sure that a NativeTokenVault sent the message
revert AssetIdMismatch(_assetId, expectedAssetId);
}
}

function _ensureTokenDeployedInner(
uint256 _originChainId,
function _ensureAndSaveTokenDeployedInner(
uint256 _tokenOriginChainId,
bytes32 _assetId,
address _originToken,
bytes memory _erc20Data,
address _expectedToken
) internal {
address deployedToken = _deployBridgedToken(_originChainId, _originToken, _erc20Data);
_assetIdCheck(_tokenOriginChainId, _assetId, _originToken);

address deployedToken = _deployBridgedToken(_tokenOriginChainId, _assetId, _originToken, _erc20Data);
if (deployedToken != _expectedToken) {
revert AddressMismatch(_expectedToken, deployedToken);
}
Expand All @@ -399,28 +415,34 @@ abstract contract NativeTokenVault is INativeTokenVault, IAssetHandler, Ownable2
}

/// @notice Calculates the bridged token address corresponding to native token counterpart.
/// @param _tokenOriginChainId The chain id of the origin token.
/// @param _bridgeToken The address of native token.
/// @return The address of bridged token.
function calculateCreate2TokenAddress(
uint256 _originChainId,
uint256 _tokenOriginChainId,
address _bridgeToken
) public view virtual override returns (address);

/// @notice Deploys and initializes the bridged token for the native counterpart.
/// @param _tokenOriginChainId The chain id of the origin token.
/// @param _originToken The address of origin token.
/// @param _erc20Data The ERC20 metadata of the token deployed.
/// @return The address of the beacon proxy (bridged token).
function _deployBridgedToken(
uint256 _originChainId,
uint256 _tokenOriginChainId,
bytes32 _assetId,
address _originToken,
bytes memory _erc20Data
) internal returns (address) {
bytes32 salt = _getCreate2Salt(_originChainId, _originToken);
if (_tokenOriginChainId == block.chainid) {
revert DeployingBridgedTokenForNativeToken();
}
bytes32 salt = _getCreate2Salt(_tokenOriginChainId, _originToken);

BeaconProxy l2Token = _deployBeaconProxy(salt, _tokenOriginChainId);
BridgedStandardERC20(address(l2Token)).bridgeInitialize(_originToken, _erc20Data);

BeaconProxy l2Token = _deployBeaconProxy(salt);
uint256 tokenOriginChainId = BridgedStandardERC20(address(l2Token)).bridgeInitialize(_originToken, _erc20Data);
tokenOriginChainId = tokenOriginChainId == 0 ? L1_CHAIN_ID : tokenOriginChainId;
originChainId[DataEncoding.encodeNTVAssetId(tokenOriginChainId, _originToken)] = tokenOriginChainId;
originChainId[_assetId] = _tokenOriginChainId;
return address(l2Token);
}

Expand All @@ -436,7 +458,10 @@ abstract contract NativeTokenVault is INativeTokenVault, IAssetHandler, Ownable2
/// for the code of the proxy.
/// @param _salt The salt used for beacon proxy deployment of the bridged token (we pass the native token address).
/// @return proxy The beacon proxy, i.e. bridged token.
function _deployBeaconProxy(bytes32 _salt) internal virtual returns (BeaconProxy proxy);
function _deployBeaconProxy(
bytes32 _salt,
uint256 _tokenOriginChainId
) internal virtual returns (BeaconProxy proxy);

/*//////////////////////////////////////////////////////////////
PAUSE
Expand Down
2 changes: 2 additions & 0 deletions l1-contracts/contracts/common/L1ContractErrors.sol
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,8 @@ error ChainIdTooBig();
error DelegateCallFailed(bytes returnData);
// 0x0a8ed92c
error DenominatorIsZero();
// 0x138ee1a3
error DeployingBridgedTokenForNativeToken();
//
error DeployFailed();
// 0xc7c9660f
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
// SPDX-License-Identifier: MIT

pragma solidity 0.8.24;

import {BeaconProxy} from "@openzeppelin/contracts-v4/proxy/beacon/BeaconProxy.sol";
import {Create2} from "@openzeppelin/contracts-v4/utils/Create2.sol";
import {IBeacon} from "@openzeppelin/contracts-v4/proxy/beacon/IBeacon.sol";
import {UpgradeableBeacon} from "@openzeppelin/contracts-v4/proxy/beacon/UpgradeableBeacon.sol";

import {INativeTokenVault} from "contracts/bridge/ntv/INativeTokenVault.sol";
import {NativeTokenVault} from "contracts/bridge/ntv/NativeTokenVault.sol";
import {L2NativeTokenVault} from "contracts/bridge/ntv/L2NativeTokenVault.sol";
import {BridgedStandardERC20} from "contracts/bridge/BridgedStandardERC20.sol";

/// @author Matter Labs
/// @notice This is used for fast debugging of the L2NTV by running it in L1 context, i.e. normal foundry instead of foundry --zksync.
contract L2NativeTokenVaultDev is L2NativeTokenVault {
constructor(
uint256 _l1ChainId,
address _aliasedOwner,
bytes32 _l2TokenProxyBytecodeHash,
address _legacySharedBridge,
address _bridgedTokenBeacon,
bool _contractsDeployedAlready,
address _wethToken,
bytes32 _baseTokenAssetId
)
L2NativeTokenVault(
_l1ChainId,
_aliasedOwner,
_l2TokenProxyBytecodeHash,
_legacySharedBridge,
_bridgedTokenBeacon,
_contractsDeployedAlready,
_wethToken,
_baseTokenAssetId
)
{}

/// @notice copied from L1NTV for L1 compilation
function calculateCreate2TokenAddress(
uint256 _originChainId,
address _l1Token
) public view override(L2NativeTokenVault) returns (address) {
bytes32 salt = _getCreate2Salt(_originChainId, _l1Token);
return
Create2.computeAddress(
salt,
keccak256(abi.encodePacked(type(BeaconProxy).creationCode, abi.encode(bridgedTokenBeacon, "")))
);
}

function deployBridgedStandardERC20(address _owner) external {
_transferOwnership(_owner);

address l2StandardToken = address(new BridgedStandardERC20{salt: bytes32(0)}());

UpgradeableBeacon tokenBeacon = new UpgradeableBeacon{salt: bytes32(0)}(l2StandardToken);

tokenBeacon.transferOwnership(owner());
bridgedTokenBeacon = IBeacon(address(tokenBeacon));
emit L2TokenBeaconUpdated(address(bridgedTokenBeacon), l2TokenProxyBytecodeHash);
}

function test() external pure {
// test
}

function _deployBeaconProxy(bytes32 _salt, uint256) internal virtual override returns (BeaconProxy proxy) {
// Use CREATE2 to deploy the BeaconProxy
address proxyAddress = Create2.deploy(
0,
_salt,
abi.encodePacked(type(BeaconProxy).creationCode, abi.encode(bridgedTokenBeacon, ""))
);
return BeaconProxy(payable(proxyAddress));
}
}
Loading

0 comments on commit 7e719c5

Please sign in to comment.