Skip to content

Commit

Permalink
Merge branch 'kl/l2-native-token' of ssh://github.com/matter-labs/era…
Browse files Browse the repository at this point in the history
…-contracts into kl/interop
  • Loading branch information
kelemeno committed Oct 3, 2024
2 parents 5b4eff0 + 37dc6cd commit a271fc5
Show file tree
Hide file tree
Showing 51 changed files with 1,955 additions and 868 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
4 changes: 2 additions & 2 deletions l1-contracts/contracts/bridge/L1Nullifier.sol
Original file line number Diff line number Diff line change
Expand Up @@ -648,7 +648,7 @@ contract L1Nullifier is IL1Nullifier, ReentrancyGuard, Ownable2StepUpgradeable,
uint16 _l2TxNumberInBatch,
bytes32[] calldata _merkleProof
) external override {
bytes32 assetId = INativeTokenVault(address(l1NativeTokenVault)).getAssetId(block.chainid, _l1Token);
bytes32 assetId = INativeTokenVault(address(l1NativeTokenVault)).calculateAssetId(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 +702,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);
}
}
15 changes: 14 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 token. The wrapped token does not have its own assetId.
bytes32 public override assetId;

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;
assetId = _baseTokenAssetId;

// Set decoded values for name and symbol.
__ERC20_init_unchained(name_, symbol_);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -306,6 +306,12 @@ abstract contract AssetRouterBase is IAssetRouterBase, Ownable2StepUpgradeable,
bytes32 _assetId,
bytes memory _assetData
) public view virtual returns (bytes memory);

/// @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
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ interface IL2AssetRouter is IAssetRouterBase {
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
6 changes: 3 additions & 3 deletions l1-contracts/contracts/bridge/asset-router/L1AssetRouter.sol
Original file line number Diff line number Diff line change
Expand Up @@ -283,10 +283,10 @@ 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);
function _ensureTokenRegisteredWithNTV(address _token) internal override returns (bytes32 assetId) {
assetId = DataEncoding.encodeNTVAssetId(block.chainid, _token);
if (nativeTokenVault.tokenAddress(assetId) == address(0)) {
nativeTokenVault.registerToken(_token);
}
Expand Down
39 changes: 30 additions & 9 deletions l1-contracts/contracts/bridge/asset-router/L2AssetRouter.sol
Original file line number Diff line number Diff line change
Expand Up @@ -199,6 +199,22 @@ contract L2AssetRouter is AssetRouterBase, IL2AssetRouter {
return abi.encodeCall(IAssetRouterBase.finalizeDeposit, (block.chainid, _assetId, _assetData));
}

/*//////////////////////////////////////////////////////////////
Internal & Helpers
//////////////////////////////////////////////////////////////*/

// kl todo add handle Legaacy data here, which calls esureTokenRegisteredWithNTV
// have handleLegacyData called from somewhere.

/// @inheritdoc AssetRouterBase
function _ensureTokenRegisteredWithNTV(address _token) internal override returns (bytes32 assetId) {
IL2NativeTokenVault nativeTokenVault = IL2NativeTokenVault(L2_NATIVE_TOKEN_VAULT_ADDR);
assetId = nativeTokenVault.calculateAssetId(block.chainid, _token);
if (nativeTokenVault.tokenAddress(assetId) == address(0)) {
nativeTokenVault.registerToken(_token);
}
}

/*//////////////////////////////////////////////////////////////
LEGACY FUNCTIONS
//////////////////////////////////////////////////////////////*/
Expand All @@ -208,8 +224,13 @@ contract L2AssetRouter is AssetRouterBase, IL2AssetRouter {
/// @dev do not rely on this function, it will be deprecated in the future
/// @param _assetId The asset id of the withdrawn asset
/// @param _assetData The data that is passed to the asset handler contract
function withdraw(bytes32 _assetId, bytes memory _assetData) public override {
_withdrawSender(_assetId, _assetData, msg.sender, true);
function withdraw(bytes32 _assetId, bytes memory _assetData) public override returns (bytes32) {
return _withdrawSender(_assetId, _assetData, msg.sender, true);
}

function withdrawToken(address _l2NativeToken, bytes memory _assetData) public returns (bytes32) {
bytes32 assetId = _ensureTokenRegisteredWithNTV(_l2NativeToken);
return _withdrawSender(assetId, _assetData, msg.sender, true);
}

/// @notice Initiates a withdrawal by burning funds on the contract and sending the message to L1
Expand All @@ -223,7 +244,7 @@ contract L2AssetRouter is AssetRouterBase, IL2AssetRouter {
bytes memory _assetData,
address _sender,
bool _alwaysNewMessageFormat
) internal {
) internal returns (bytes32 txHash) {
address assetHandler = assetHandlerAddress[_assetId];
bytes memory _l1bridgeMintData = IAssetHandler(assetHandler).bridgeBurn({
_chainId: L1_CHAIN_ID,
Expand All @@ -237,7 +258,7 @@ contract L2AssetRouter is AssetRouterBase, IL2AssetRouter {
if (_alwaysNewMessageFormat || L2_LEGACY_SHARED_BRIDGE == address(0)) {
message = _getAssetRouterWithdrawMessage(_assetId, _l1bridgeMintData);
// slither-disable-next-line unused-return
L2ContractHelper.sendMessageToL1(message);
txHash = L2ContractHelper.sendMessageToL1(message);
} else {
address l1Token = IBridgedStandardToken(
IL2NativeTokenVault(L2_NATIVE_TOKEN_VAULT_ADDR).tokenAddress(_assetId)
Expand All @@ -247,7 +268,7 @@ contract L2AssetRouter is AssetRouterBase, IL2AssetRouter {
}
(uint256 amount, address l1Receiver) = abi.decode(_assetData, (uint256, address));
message = _getSharedBridgeWithdrawMessage(l1Receiver, l1Token, amount);
IL2SharedBridgeLegacy(L2_LEGACY_SHARED_BRIDGE).sendMessageToL1(message);
txHash = IL2SharedBridgeLegacy(L2_LEGACY_SHARED_BRIDGE).sendMessageToL1(message);
}

emit WithdrawalInitiatedAssetRouter(L1_CHAIN_ID, _sender, _assetId, _assetData);
Expand All @@ -259,9 +280,9 @@ contract L2AssetRouter is AssetRouterBase, IL2AssetRouter {
function _getAssetRouterWithdrawMessage(
bytes32 _assetId,
bytes memory _l1bridgeMintData
) internal pure returns (bytes memory) {
) internal view returns (bytes memory) {
// solhint-disable-next-line func-named-parameters
return abi.encodePacked(IAssetRouterBase.finalizeDeposit.selector, _assetId, _l1bridgeMintData);
return abi.encodePacked(IAssetRouterBase.finalizeDeposit.selector, block.chainid, _assetId, _l1bridgeMintData);
}

/// @notice Encodes the message for l2ToL1log sent during withdraw initialization.
Expand Down Expand Up @@ -356,15 +377,15 @@ contract L2AssetRouter is AssetRouterBase, IL2AssetRouter {
}

function _withdrawLegacy(address _l1Receiver, address _l2Token, uint256 _amount, address _sender) internal {
bytes32 assetId = DataEncoding.encodeNTVAssetId(L1_CHAIN_ID, getL1TokenAddress(_l2Token));
bytes32 assetId = DataEncoding.encodeNTVAssetId(L1_CHAIN_ID, l1TokenAddress(_l2Token));
bytes memory data = abi.encode(_amount, _l1Receiver);
_withdrawSender(assetId, data, _sender, false);
}

/// @notice Legacy getL1TokenAddress.
/// @param _l2Token The address of token on L2.
/// @return The address of token on L1.
function getL1TokenAddress(address _l2Token) public view returns (address) {
function l1TokenAddress(address _l2Token) public view returns (address) {
return IBridgedStandardToken(_l2Token).l1Address();
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,4 +18,8 @@ interface IBridgedStandardToken {
function originToken() external view returns (address);

function l2Bridge() external view returns (address);

function assetId() external view returns (bytes32);

function nativeTokenVault() external view returns (address);
}
Original file line number Diff line number Diff line change
Expand Up @@ -28,5 +28,5 @@ interface IL2SharedBridgeLegacy {

function deployBeaconProxy(bytes32 _salt) external returns (address);

function sendMessageToL1(bytes calldata _message) external;
function sendMessageToL1(bytes calldata _message) external returns (bytes32);
}
11 changes: 9 additions & 2 deletions l1-contracts/contracts/bridge/ntv/INativeTokenVault.sol
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,10 @@ interface INativeTokenVault {

/// @notice The AssetRouter contract
function ASSET_ROUTER() external view returns (IAssetRouterBase);

/// @notice The chain ID of the L1 chain
function L1_CHAIN_ID() external view returns (uint256);

/// @notice Returns the chain ID of the origin chain for a given asset ID
function originChainId(bytes32 assetId) external view returns (uint256);

Expand All @@ -25,15 +29,18 @@ interface INativeTokenVault {
/// @notice No access control is ok, since the bridging of tokens should be permissionless. This requires permissionless registration.
function registerToken(address _l1Token) external;

/// @notice Used to get the assetId of a token
function getAssetId(uint256 _chainId, address _tokenAddress) external view returns (bytes32);
/// @notice Used to calculate the assetId of a token
function calculateAssetId(uint256 _chainId, address _tokenAddress) external view returns (bytes32);

/// @notice Used to get the the ERC20 data for a token
function getERC20Getters(address _token, uint256 _originChainId) external view returns (bytes memory);

/// @notice Used to get the token address of an assetId
function tokenAddress(bytes32 assetId) external view returns (address);

/// @notice Used to get the assetId of a token
function assetId(address token) external view returns (bytes32);

/// @notice Used to get the expected bridged token address corresponding to its native counterpart
function calculateCreate2TokenAddress(uint256 _originChainId, address _originToken) external view returns (address);
}
12 changes: 7 additions & 5 deletions l1-contracts/contracts/bridge/ntv/L2NativeTokenVault.sol
Original file line number Diff line number Diff line change
Expand Up @@ -85,9 +85,10 @@ contract L2NativeTokenVault is IL2NativeTokenVault, NativeTokenVault {
/// @notice Sets the legacy token asset ID for the given L2 token address.
function setLegacyTokenAssetId(address _l2TokenAddress) public {
address l1TokenAddress = L2_LEGACY_SHARED_BRIDGE.l1TokenAddress(_l2TokenAddress);
bytes32 assetId = DataEncoding.encodeNTVAssetId(L1_CHAIN_ID, l1TokenAddress);
tokenAddress[assetId] = _l2TokenAddress;
originChainId[assetId] = L1_CHAIN_ID;
bytes32 newAssetId = DataEncoding.encodeNTVAssetId(L1_CHAIN_ID, l1TokenAddress);
tokenAddress[newAssetId] = _l2TokenAddress;
assetId[_l2TokenAddress] = newAssetId;
originChainId[newAssetId] = L1_CHAIN_ID;
}

/// @notice Ensures that the token is deployed.
Expand All @@ -114,6 +115,7 @@ contract L2NativeTokenVault is IL2NativeTokenVault, NativeTokenVault {
revert AddressMismatch(_originToken, l1LegacyToken);
}
tokenAddress[_assetId] = expectedToken;
assetId[expectedToken] = _assetId;
} else {
super._ensureTokenDeployedInner({
_originChainId: _originChainId,
Expand All @@ -130,7 +132,7 @@ contract L2NativeTokenVault is IL2NativeTokenVault, NativeTokenVault {
/// for the code of the proxy.
/// @param _salt The salt used for beacon proxy deployment of L2 bridged token.
/// @return proxy The beacon proxy, i.e. L2 bridged token.
function _deployBeaconProxy(bytes32 _salt) internal override returns (BeaconProxy proxy) {
function _deployBeaconProxy(bytes32 _salt) internal virtual override returns (BeaconProxy proxy) {
if (address(L2_LEGACY_SHARED_BRIDGE) == address(0)) {
// Deploy the beacon proxy for the L2 token

Expand Down Expand Up @@ -175,7 +177,7 @@ contract L2NativeTokenVault is IL2NativeTokenVault, NativeTokenVault {
function calculateCreate2TokenAddress(
uint256 _originChainId,
address _l1Token
) public view override(INativeTokenVault, NativeTokenVault) returns (address) {
) public view virtual override(INativeTokenVault, NativeTokenVault) returns (address) {
bytes32 constructorInputHash = keccak256(abi.encode(address(bridgedTokenBeacon), ""));
bytes32 salt = _getCreate2Salt(_originChainId, _l1Token);
if (address(L2_LEGACY_SHARED_BRIDGE) != address(0)) {
Expand Down
Loading

0 comments on commit a271fc5

Please sign in to comment.