diff --git a/contracts/optimism/L1ERC20ExtendedTokensBridge.sol b/contracts/optimism/L1ERC20ExtendedTokensBridge.sol index 7e303a9..97046a9 100644 --- a/contracts/optimism/L1ERC20ExtendedTokensBridge.sol +++ b/contracts/optimism/L1ERC20ExtendedTokensBridge.sol @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: 2022 Lido +// SPDX-FileCopyrightText: 2024 Lido // SPDX-License-Identifier: GPL-3.0 pragma solidity 0.8.10; @@ -6,15 +6,13 @@ pragma solidity 0.8.10; import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; import {Address} from "@openzeppelin/contracts/utils/Address.sol"; import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; - import {IL1ERC20Bridge} from "./interfaces/IL1ERC20Bridge.sol"; import {IL2ERC20Bridge} from "./interfaces/IL2ERC20Bridge.sol"; import {IERC20Wrapper} from "../token/interfaces/IERC20Wrapper.sol"; import {BridgingManager} from "../BridgingManager.sol"; import {RebasableAndNonRebasableTokens} from "./RebasableAndNonRebasableTokens.sol"; import {CrossDomainEnabled} from "./CrossDomainEnabled.sol"; -import {DepositDataCodec} from "./DepositDataCodec.sol"; -import {IERC20WstETH} from "../token/interfaces/IERC20WstETH.sol"; +import {DepositDataCodec} from "../lib//DepositDataCodec.sol"; /// @author psirex, kovalgek /// @notice The L1 ERC20 token bridge locks bridged tokens on the L1 side, sends deposit messages @@ -24,12 +22,11 @@ abstract contract L1ERC20ExtendedTokensBridge is IL1ERC20Bridge, BridgingManager, RebasableAndNonRebasableTokens, - CrossDomainEnabled, - DepositDataCodec + CrossDomainEnabled { using SafeERC20 for IERC20; - address public immutable L2_TOKEN_BRIDGE; + address private immutable L2_TOKEN_BRIDGE; /// @param messenger_ L1 messenger address being used for cross-chain communications /// @param l2TokenBridge_ Address of the corresponding L2 bridge @@ -53,8 +50,24 @@ abstract contract L1ERC20ExtendedTokensBridge is L2_TOKEN_BRIDGE = l2TokenBridge_; } + function initialize2( + address admin_, + address l1TokenNonRebasable_, + address l1TokenRebasable_, + address l2TokenNonRebasable_, + address l2TokenRebasable_ + ) external { + BridgingManager.initialize(admin_); + RebasableAndNonRebasableTokens.initialize( + l1TokenNonRebasable_, + l1TokenRebasable_, + l2TokenNonRebasable_, + l2TokenRebasable_ + ); + } + /// @notice required to abstact a way token rate is requested. - function tokenRate() virtual internal view returns (uint256); + function tokenRate(address l1NonRebasableToken) virtual internal view returns (uint256); /// @inheritdoc IL1ERC20Bridge function l2TokenBridge() external view returns (address) { @@ -71,14 +84,19 @@ abstract contract L1ERC20ExtendedTokensBridge is ) external whenDepositsEnabled - onlySupportedL1Token(l1Token_) - onlySupportedL2Token(l2Token_) + onlySupportedL1L2TokensPair(l1Token_, l2Token_) { if (Address.isContract(msg.sender)) { revert ErrorSenderNotEOA(); } - - _depositERC20To(l1Token_, l2Token_, msg.sender, amount_, l2Gas_, data_); + uint256 rate = tokenRate(_l1NonRebasableToken(l1Token_)); + bytes memory encodedDepositData = DepositDataCodec.encodeDepositData(DepositDataCodec.DepositData({ + rate: uint96(rate), + timestamp: uint40(block.timestamp), + data: data_ + })); + _depositERC20To(l1Token_, l2Token_, msg.sender, msg.sender, amount_, l2Gas_, data_); + emit ERC20DepositInitiated(l1Token_, l2Token_, msg.sender, msg.sender, amount_, encodedDepositData); } /// @inheritdoc IL1ERC20Bridge @@ -93,10 +111,18 @@ abstract contract L1ERC20ExtendedTokensBridge is external whenDepositsEnabled onlyNonZeroAccount(to_) - onlySupportedL1Token(l1Token_) - onlySupportedL2Token(l2Token_) + // onlySupportedL1Token(l1Token_) + // onlySupportedL2Token(l2Token_) + onlySupportedL1L2TokensPair(l1Token_, l2Token_) { - _depositERC20To(l1Token_, l2Token_, to_, amount_, l2Gas_, data_); + uint256 rate = tokenRate(_l1NonRebasableToken(l1Token_)); + bytes memory encodedDepositData = DepositDataCodec.encodeDepositData(DepositDataCodec.DepositData({ + rate: uint96(rate), + timestamp: uint40(block.timestamp), + data: data_ + })); + _depositERC20To(l1Token_, l2Token_, msg.sender, to_, amount_, l2Gas_, encodedDepositData); + emit ERC20DepositInitiated(l1Token_, l2Token_, msg.sender, to_, amount_, encodedDepositData); } /// @inheritdoc IL1ERC20Bridge @@ -110,154 +136,71 @@ abstract contract L1ERC20ExtendedTokensBridge is ) external whenWithdrawalsEnabled - onlySupportedL1Token(l1Token_) - onlySupportedL2Token(l2Token_) + onlySupportedL1L2TokensPair(l1Token_, l2Token_) onlyFromCrossDomainAccount(L2_TOKEN_BRIDGE) { - if (_isRebasableTokenFlow(l1Token_, l2Token_)) { - uint256 rebasableTokenAmount = IERC20Wrapper(L1_TOKEN_NON_REBASABLE).unwrap(amount_); - IERC20(L1_TOKEN_REBASABLE).safeTransfer(to_, rebasableTokenAmount); - - emit ERC20WithdrawalFinalized( - L1_TOKEN_REBASABLE, - L2_TOKEN_REBASABLE, - from_, - to_, - rebasableTokenAmount, - data_ - ); - } else if (_isNonRebasableTokenFlow(l1Token_, l2Token_)) { - IERC20(L1_TOKEN_NON_REBASABLE).safeTransfer(to_, amount_); - - emit ERC20WithdrawalFinalized( - L1_TOKEN_NON_REBASABLE, - L2_TOKEN_NON_REBASABLE, - from_, - to_, - amount_, - data_ - ); - } - } - - function _depositERC20To( - address l1Token_, - address l2Token_, - address to_, - uint256 amount_, - uint32 l2Gas_, - bytes memory data_ - ) internal { - if (_isRebasableTokenFlow(l1Token_, l2Token_)) { - DepositData memory depositData = DepositData({ - rate: uint96(tokenRate()), - timestamp: uint40(block.timestamp), - data: data_ - }); - bytes memory encodedDepositData = encodeDepositData(depositData); - - if (amount_ == 0) { - _initiateERC20Deposit( - L1_TOKEN_REBASABLE, - L2_TOKEN_REBASABLE, - msg.sender, - to_, - 0, - l2Gas_, - encodedDepositData - ); - - emit ERC20DepositInitiated( - L1_TOKEN_REBASABLE, - L2_TOKEN_REBASABLE, - msg.sender, - to_, - 0, - encodedDepositData - ); - - return; - } - - IERC20(L1_TOKEN_REBASABLE).safeTransferFrom(msg.sender, address(this), amount_); - if(!IERC20(L1_TOKEN_REBASABLE).approve(L1_TOKEN_NON_REBASABLE, amount_)) { - revert ErrorRebasableTokenApprove(); - } - uint256 nonRebasableTokenAmount = IERC20Wrapper(L1_TOKEN_NON_REBASABLE).wrap(amount_); - - _initiateERC20Deposit( - L1_TOKEN_REBASABLE, - L2_TOKEN_REBASABLE, - msg.sender, - to_, - nonRebasableTokenAmount, - l2Gas_, - encodedDepositData - ); - - emit ERC20DepositInitiated( - L1_TOKEN_REBASABLE, - L2_TOKEN_REBASABLE, - msg.sender, - to_, - amount_, - encodedDepositData - ); - } else if (_isNonRebasableTokenFlow(l1Token_, l2Token_)) { - IERC20(L1_TOKEN_NON_REBASABLE).safeTransferFrom(msg.sender, address(this), amount_); - - _initiateERC20Deposit( - L1_TOKEN_NON_REBASABLE, - L2_TOKEN_NON_REBASABLE, - msg.sender, - to_, - amount_, - l2Gas_, - data_ - ); - - emit ERC20DepositInitiated( - L1_TOKEN_NON_REBASABLE, - L2_TOKEN_NON_REBASABLE, - msg.sender, - to_, - amount_, - data_ - ); + if(_isRebasable(l1Token_)) { + address l1NonRebasableToken = _getRebasableTokens()[l1Token_].pairedToken; + uint256 rebasableTokenAmount = IERC20Wrapper(l1NonRebasableToken).unwrap(amount_); + IERC20(l1Token_).safeTransfer(to_, rebasableTokenAmount); + emit ERC20WithdrawalFinalized(l1Token_, l2Token_, from_, to_, rebasableTokenAmount, data_); + } else { + IERC20(l1Token_).safeTransfer(to_, amount_); + emit ERC20WithdrawalFinalized(l1Token_, l2Token_, from_, to_, amount_, data_); } } /// @dev Performs the logic for deposits by informing the L2 token bridge contract /// of the deposit and calling safeTransferFrom to lock the L1 funds. + + /// @param l1Token_ Address of the L1 ERC20 we are depositing + /// @param l2Token_ Address of the L1 respective L2 ERC20 /// @param from_ Account to pull the deposit from on L1 /// @param to_ Account to give the deposit to on L2 /// @param amount_ Amount of the ERC20 to deposit. /// @param l2Gas_ Gas limit required to complete the deposit on L2. - /// @param data_ Optional data to forward to L2. This data is provided + + /// @param encodedDepositData_ Optional data to forward to L2. This data is provided /// solely as a convenience for external contracts. Aside from enforcing a maximum /// length, these contracts provide no guarantees about its content. - function _initiateERC20Deposit( + function _depositERC20To( address l1Token_, address l2Token_, address from_, address to_, uint256 amount_, uint32 l2Gas_, - bytes memory data_ + bytes memory encodedDepositData_ ) internal { + uint256 amountToDeposit = _transferToBridge(l1Token_, from_, amount_); + bytes memory message = abi.encodeWithSelector( IL2ERC20Bridge.finalizeDeposit.selector, - l1Token_, - l2Token_, - from_, - to_, - amount_, - data_ + l1Token_, l2Token_, from_, to_, amountToDeposit, encodedDepositData_ ); sendCrossDomainMessage(L2_TOKEN_BRIDGE, l2Gas_, message); } + function _transferToBridge( + address l1Token_, + address from_, + uint256 amount_ + ) internal returns (uint256) { + + if (amount_ == 0) { + return amount_; + } + + IERC20(l1Token_).safeTransferFrom(from_, address(this), amount_); + if(_isRebasable(l1Token_)) { + address l1NonRebasableToken = _getRebasableTokens()[l1Token_].pairedToken; + if(!IERC20(l1Token_).approve(l1NonRebasableToken, amount_)) revert ErrorRebasableTokenApprove(); + return IERC20Wrapper(l1NonRebasableToken).wrap(amount_); + } + return amount_; + } + error ErrorSenderNotEOA(); error ErrorRebasableTokenApprove(); } diff --git a/contracts/optimism/L1LidoTokensBridge.sol b/contracts/optimism/L1LidoTokensBridge.sol index 491ff28..8580978 100644 --- a/contracts/optimism/L1LidoTokensBridge.sol +++ b/contracts/optimism/L1LidoTokensBridge.sol @@ -27,7 +27,7 @@ contract L1LidoTokensBridge is L1ERC20ExtendedTokensBridge { ) { } - function tokenRate() override internal view returns (uint256) { - return IERC20WstETH(L1_TOKEN_NON_REBASABLE).stEthPerToken(); + function tokenRate(address l1NonRebasableToken) override internal view returns (uint256) { + return IERC20WstETH(l1NonRebasableToken).stEthPerToken(); } } diff --git a/contracts/optimism/L2ERC20ExtendedTokensBridge.sol b/contracts/optimism/L2ERC20ExtendedTokensBridge.sol index f2b630b..88caab8 100644 --- a/contracts/optimism/L2ERC20ExtendedTokensBridge.sol +++ b/contracts/optimism/L2ERC20ExtendedTokensBridge.sol @@ -5,18 +5,16 @@ pragma solidity 0.8.10; import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; - import {IL1ERC20Bridge} from "./interfaces/IL1ERC20Bridge.sol"; import {IL2ERC20Bridge} from "./interfaces/IL2ERC20Bridge.sol"; import {IERC20Bridged} from "../token/interfaces/IERC20Bridged.sol"; import {ITokenRateOracle} from "../token/interfaces/ITokenRateOracle.sol"; import {IERC20Wrapper} from "../token/interfaces/IERC20Wrapper.sol"; - import {ERC20Rebasable} from "../token/ERC20Rebasable.sol"; import {BridgingManager} from "../BridgingManager.sol"; import {RebasableAndNonRebasableTokens} from "./RebasableAndNonRebasableTokens.sol"; import {CrossDomainEnabled} from "./CrossDomainEnabled.sol"; -import {DepositDataCodec} from "./DepositDataCodec.sol"; +import {DepositDataCodec} from "../lib/DepositDataCodec.sol"; /// @author psirex /// @notice The L2 token bridge works with the L1 token bridge to enable ERC20 token bridging @@ -28,8 +26,7 @@ contract L2ERC20ExtendedTokensBridge is IL2ERC20Bridge, BridgingManager, RebasableAndNonRebasableTokens, - CrossDomainEnabled, - DepositDataCodec + CrossDomainEnabled { using SafeERC20 for IERC20; @@ -68,8 +65,12 @@ contract L2ERC20ExtendedTokensBridge is uint256 amount_, uint32 l1Gas_, bytes calldata data_ - ) external whenWithdrawalsEnabled onlySupportedL2Token(l2Token_) { - _withdrawTo(l2Token_, msg.sender, amount_, l1Gas_, data_); + ) external + whenWithdrawalsEnabled + onlySupportedL2Token(l2Token_) + { + _withdrawTo(l2Token_, msg.sender, msg.sender, amount_, l1Gas_, data_); + emit WithdrawalInitiated(_l1Token(l2Token_), l2Token_, msg.sender, msg.sender, amount_, data_); } /// @inheritdoc IL2ERC20Bridge @@ -79,8 +80,12 @@ contract L2ERC20ExtendedTokensBridge is uint256 amount_, uint32 l1Gas_, bytes calldata data_ - ) external whenWithdrawalsEnabled onlySupportedL2Token(l2Token_) { - _withdrawTo(l2Token_, to_, amount_, l1Gas_, data_); + ) external + whenWithdrawalsEnabled + onlySupportedL2Token(l2Token_) + { + _withdrawTo(l2Token_, msg.sender, to_, amount_, l1Gas_, data_); + emit WithdrawalInitiated(_l1Token(l2Token_), l2Token_, msg.sender, to_, amount_, data_); } /// @inheritdoc IL2ERC20Bridge @@ -93,90 +98,16 @@ contract L2ERC20ExtendedTokensBridge is bytes calldata data_ ) external - whenDepositsEnabled - onlySupportedL1Token(l1Token_) - onlySupportedL2Token(l2Token_) + whenDepositsEnabled() + onlySupportedL1L2TokensPair(l1Token_, l2Token_) onlyFromCrossDomainAccount(L1_TOKEN_BRIDGE) { - if (_isRebasableTokenFlow(l1Token_, l2Token_)) { - DepositData memory depositData = decodeDepositData(data_); - - ITokenRateOracle tokenRateOracle = ERC20Rebasable(L2_TOKEN_REBASABLE).TOKEN_RATE_ORACLE(); - tokenRateOracle.updateRate(depositData.rate, depositData.timestamp); - - ERC20Rebasable(L2_TOKEN_REBASABLE).bridgeMintShares(to_, amount_); - - uint256 rebasableTokenAmount = ERC20Rebasable(L2_TOKEN_REBASABLE).getTokensByShares(amount_); - emit DepositFinalized( - L1_TOKEN_REBASABLE, - L2_TOKEN_REBASABLE, - from_, - to_, - rebasableTokenAmount, - depositData.data - ); - } else if (_isNonRebasableTokenFlow(l1Token_, l2Token_)) { - IERC20Bridged(L2_TOKEN_NON_REBASABLE).bridgeMint(to_, amount_); - emit DepositFinalized( - L1_TOKEN_NON_REBASABLE, - L2_TOKEN_NON_REBASABLE, - from_, - to_, - amount_, - data_ - ); - } - } + DepositDataCodec.DepositData memory depositData = DepositDataCodec.decodeDepositData(data_); + ITokenRateOracle tokenRateOracle = ERC20Rebasable(l2Token_).TOKEN_RATE_ORACLE(); + tokenRateOracle.updateRate(depositData.rate, depositData.timestamp); - function _withdrawTo( - address l2Token_, - address to_, - uint256 amount_, - uint32 l1Gas_, - bytes calldata data_ - ) internal { - if (l2Token_ == L2_TOKEN_REBASABLE) { - uint256 shares = ERC20Rebasable(L2_TOKEN_REBASABLE).getSharesByTokens(amount_); - ERC20Rebasable(L2_TOKEN_REBASABLE).bridgeBurnShares(msg.sender, shares); - - _initiateWithdrawal( - L1_TOKEN_REBASABLE, - L2_TOKEN_REBASABLE, - msg.sender, - to_, - shares, - l1Gas_, - data_ - ); - emit WithdrawalInitiated( - L1_TOKEN_REBASABLE, - L2_TOKEN_REBASABLE, - msg.sender, - to_, - amount_, - data_ - ); - } else if (l2Token_ == L2_TOKEN_NON_REBASABLE) { - IERC20Bridged(L2_TOKEN_NON_REBASABLE).bridgeBurn(msg.sender, amount_); - - _initiateWithdrawal( - L1_TOKEN_NON_REBASABLE, - L2_TOKEN_NON_REBASABLE, - msg.sender, - to_, - amount_, - l1Gas_, - data_ - ); - emit WithdrawalInitiated( - L1_TOKEN_NON_REBASABLE, - L2_TOKEN_NON_REBASABLE, - msg.sender, - to_, - amount_, - data_ - ); - } + uint256 depositedAmount = _mintTokens(l1Token_, l2Token_, to_, amount_); + emit DepositFinalized(l1Token_, l2Token_, from_, to_, depositedAmount, depositData.data); } /// @notice Performs the logic for withdrawals by burning the token and informing @@ -188,25 +119,50 @@ contract L2ERC20ExtendedTokensBridge is /// @param data_ Optional data to forward to L1. This data is provided /// solely as a convenience for external contracts. Aside from enforcing a maximum /// length, these contracts provide no guarantees about its content - function _initiateWithdrawal( - address l1Token_, + function _withdrawTo( address l2Token_, address from_, address to_, uint256 amount_, uint32 l1Gas_, - bytes memory data_ + bytes calldata data_ ) internal { + uint256 amountToWithdraw = _burnTokens(l2Token_, from_, amount_); + bytes memory message = abi.encodeWithSelector( IL1ERC20Bridge.finalizeERC20Withdrawal.selector, - l1Token_, - l2Token_, - from_, - to_, - amount_, - data_ + _l1Token(l2Token_), l2Token_, from_, to_, amountToWithdraw, data_ ); - sendCrossDomainMessage(L1_TOKEN_BRIDGE, l1Gas_, message); } + + function _mintTokens( + address l1Token_, + address l2Token_, + address to_, + uint256 amount_ + ) internal returns (uint256) { + if(_isRebasable(l1Token_)) { + ERC20Rebasable(l2Token_).bridgeMintShares(to_, amount_); + return ERC20Rebasable(l2Token_).getTokensByShares(amount_); + } + + IERC20Bridged(l2Token_).bridgeMint(to_, amount_); + return amount_; + } + + function _burnTokens( + address l2Token_, + address from_, + uint256 amount_ + ) internal returns (uint256) { + if(_isRebasable(l2Token_)) { + uint256 shares = ERC20Rebasable(l2Token_).getSharesByTokens(amount_); + ERC20Rebasable(l2Token_).bridgeBurnShares(from_, shares); + return shares; + } + + IERC20Bridged(l2Token_).bridgeBurn(from_, amount_); + return amount_; + } } diff --git a/contracts/optimism/RebasableAndNonRebasableTokens.sol b/contracts/optimism/RebasableAndNonRebasableTokens.sol index bffff15..45313fc 100644 --- a/contracts/optimism/RebasableAndNonRebasableTokens.sol +++ b/contracts/optimism/RebasableAndNonRebasableTokens.sol @@ -1,37 +1,113 @@ -// SPDX-FileCopyrightText: 2022 Lido +// SPDX-FileCopyrightText: 2024 Lido // SPDX-License-Identifier: GPL-3.0 pragma solidity 0.8.10; -/// @author psirex +import {UnstructuredRefStorage} from "../token/UnstructuredRefStorage.sol"; + +/// @author psirex, kovalgek /// @notice Contains the logic for validation of tokens used in the bridging process contract RebasableAndNonRebasableTokens { - /// @notice Address of the bridged non rebasable token in the L1 chain - address public immutable L1_TOKEN_NON_REBASABLE; + using UnstructuredRefStorage for bytes32; + + /// @dev Servers for pairing tokens by one-layer and wrapping. + /// @param `oppositeLayerToken` token representation on opposite layer. + /// @param `pairedToken` paired token address on the same domain. + struct TokenInfo { + address oppositeLayerToken; + address pairedToken; + } - /// @notice Address of the bridged rebasable token in the L1 chain - address public immutable L1_TOKEN_REBASABLE; + bytes32 internal constant REBASABLE_TOKENS_POSITION = keccak256("RebasableAndNonRebasableTokens.REBASABLE_TOKENS_POSITION"); + bytes32 internal constant NON_REBASABLE_TOKENS_POSITION = keccak256("RebasableAndNonRebasableTokens.NON_REBASABLE_TOKENS_POSITION"); + + function _getRebasableTokens() internal pure returns (mapping(address => TokenInfo) storage) { + return _storageMapAddressTokenInfo(REBASABLE_TOKENS_POSITION); + } - /// @notice Address of the non rebasable token minted on the L2 chain when token bridged - address public immutable L2_TOKEN_NON_REBASABLE; + function _getNonRebasableTokens() internal pure returns (mapping(address => TokenInfo) storage) { + return _storageMapAddressTokenInfo(REBASABLE_TOKENS_POSITION); + } - /// @notice Address of the rebasable token minted on the L2 chain when token bridged - address public immutable L2_TOKEN_REBASABLE; + function _storageMapAddressTokenInfo(bytes32 _position) internal pure returns ( + mapping(address => TokenInfo) storage result + ) { + assembly { result.slot := _position } + } /// @param l1TokenNonRebasable_ Address of the bridged non rebasable token in the L1 chain /// @param l1TokenRebasable_ Address of the bridged rebasable token in the L1 chain /// @param l2TokenNonRebasable_ Address of the non rebasable token minted on the L2 chain when token bridged /// @param l2TokenRebasable_ Address of the rebasable token minted on the L2 chain when token bridged - constructor(address l1TokenNonRebasable_, address l1TokenRebasable_, address l2TokenNonRebasable_, address l2TokenRebasable_) { - L1_TOKEN_NON_REBASABLE = l1TokenNonRebasable_; - L1_TOKEN_REBASABLE = l1TokenRebasable_; - L2_TOKEN_NON_REBASABLE = l2TokenNonRebasable_; - L2_TOKEN_REBASABLE = l2TokenRebasable_; + constructor( + address l1TokenNonRebasable_, + address l1TokenRebasable_, + address l2TokenNonRebasable_, + address l2TokenRebasable_ + ) { + _getRebasableTokens()[l1TokenRebasable_] = TokenInfo({ + oppositeLayerToken: l2TokenRebasable_, + pairedToken: l1TokenNonRebasable_ + }); + _getRebasableTokens()[l2TokenRebasable_] = TokenInfo({ + oppositeLayerToken: l1TokenRebasable_, + pairedToken: l2TokenNonRebasable_ + }); + _getNonRebasableTokens()[l1TokenNonRebasable_] = TokenInfo({ + oppositeLayerToken: l2TokenNonRebasable_, + pairedToken: l1TokenRebasable_ + }); + _getNonRebasableTokens()[l2TokenNonRebasable_] = TokenInfo({ + oppositeLayerToken: l1TokenNonRebasable_, + pairedToken: l2TokenRebasable_ + }); + } + + function initialize( + address l1TokenNonRebasable_, + address l1TokenRebasable_, + address l2TokenNonRebasable_, + address l2TokenRebasable_ + ) public { + _getRebasableTokens()[l1TokenRebasable_] = TokenInfo({ + oppositeLayerToken: l2TokenRebasable_, + pairedToken: l1TokenNonRebasable_ + }); + _getRebasableTokens()[l2TokenRebasable_] = TokenInfo({ + oppositeLayerToken: l1TokenRebasable_, + pairedToken: l2TokenNonRebasable_ + }); + _getNonRebasableTokens()[l1TokenNonRebasable_] = TokenInfo({ + oppositeLayerToken: l2TokenNonRebasable_, + pairedToken: l1TokenRebasable_ + }); + _getNonRebasableTokens()[l2TokenNonRebasable_] = TokenInfo({ + oppositeLayerToken: l1TokenNonRebasable_, + pairedToken: l2TokenRebasable_ + }); + } + + /// @dev Validates that passed l1Token_ and l2Token_ tokens pair is supported by the bridge. + modifier onlySupportedL1L2TokensPair(address l1Token_, address l2Token_) { + if (_getRebasableTokens()[l1Token_].oppositeLayerToken == address(0) && + _getNonRebasableTokens()[l1Token_].oppositeLayerToken == address(0)) { + revert ErrorUnsupportedL1Token(); + } + if (_getRebasableTokens()[l2Token_].oppositeLayerToken == address(0) && + _getNonRebasableTokens()[l2Token_].oppositeLayerToken == address(0)) { + revert ErrorUnsupportedL2Token(); + } + if (_getRebasableTokens()[l1Token_].oppositeLayerToken != l2Token_ && + _getNonRebasableTokens()[l2Token_].oppositeLayerToken != l1Token_) { + revert ErrorUnsupportedL1L2TokensPair(); + } + _; } /// @dev Validates that passed l1Token_ is supported by the bridge modifier onlySupportedL1Token(address l1Token_) { - if (l1Token_ != L1_TOKEN_NON_REBASABLE && l1Token_ != L1_TOKEN_REBASABLE) { + if (_getRebasableTokens()[l1Token_].oppositeLayerToken == address(0) && + _getNonRebasableTokens()[l1Token_].oppositeLayerToken == address(0)) { revert ErrorUnsupportedL1Token(); } _; @@ -39,7 +115,8 @@ contract RebasableAndNonRebasableTokens { /// @dev Validates that passed l2Token_ is supported by the bridge modifier onlySupportedL2Token(address l2Token_) { - if (l2Token_ != L2_TOKEN_NON_REBASABLE && l2Token_ != L2_TOKEN_REBASABLE) { + if (_getRebasableTokens()[l2Token_].oppositeLayerToken == address(0) && + _getNonRebasableTokens()[l2Token_].oppositeLayerToken == address(0)) { revert ErrorUnsupportedL2Token(); } _; @@ -53,15 +130,22 @@ contract RebasableAndNonRebasableTokens { _; } - function _isRebasableTokenFlow(address l1Token_, address l2Token_) internal view returns (bool) { - return l1Token_ == L1_TOKEN_REBASABLE && l2Token_ == L2_TOKEN_REBASABLE; + function _isRebasable(address token_) internal view returns (bool) { + return _getRebasableTokens()[token_].oppositeLayerToken != address(0); + } + + function _l1Token(address l2Token_) internal view returns (address) { + return _isRebasable(l2Token_) ? + _getRebasableTokens()[l2Token_].oppositeLayerToken : + _getNonRebasableTokens()[l2Token_].oppositeLayerToken; } - function _isNonRebasableTokenFlow(address l1Token_, address l2Token_) internal view returns (bool) { - return l1Token_ == L1_TOKEN_NON_REBASABLE && l2Token_ == L2_TOKEN_NON_REBASABLE; + function _l1NonRebasableToken(address l1Token_) internal view returns (address) { + return _isRebasable(l1Token_) ? _getRebasableTokens()[l1Token_].pairedToken : l1Token_; } error ErrorUnsupportedL1Token(); error ErrorUnsupportedL2Token(); + error ErrorUnsupportedL1L2TokensPair(); error ErrorAccountIsZeroAddress(); }