Skip to content

Commit

Permalink
feat: add L1ReverseCustomERC20Gateway and L2ReverseCustomERC20Gateway…
Browse files Browse the repository at this point in the history
… for l2 native erc20
  • Loading branch information
zimpha committed Sep 23, 2024
1 parent 7bb751f commit 9ec3594
Show file tree
Hide file tree
Showing 4 changed files with 215 additions and 6 deletions.
8 changes: 6 additions & 2 deletions src/L1/gateways/L1CustomERC20Gateway.sol
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ contract L1CustomERC20Gateway is L1ERC20Gateway {

/// @notice Constructor for `L1CustomERC20Gateway` implementation contract.
///
/// @param _counterpart The address of `L2USDCGateway` contract in L2.
/// @param _counterpart The address of `L2CustomERC20Gateway` contract in L2.
/// @param _router The address of `L1GatewayRouter` contract in L1.
/// @param _messenger The address of `L1ScrollMessenger` contract L1.
constructor(
Expand Down Expand Up @@ -86,13 +86,17 @@ contract L1CustomERC20Gateway is L1ERC20Gateway {
/// @notice Update layer 1 to layer 2 token mapping.
/// @param _l1Token The address of ERC20 token on layer 1.
/// @param _l2Token The address of corresponding ERC20 token on layer 2.
function updateTokenMapping(address _l1Token, address _l2Token) external onlyOwner {
function updateTokenMapping(address _l1Token, address _l2Token) external payable onlyOwner {
require(_l2Token != address(0), "token address cannot be 0");

address _oldL2Token = tokenMapping[_l1Token];
tokenMapping[_l1Token] = _l2Token;

emit UpdateTokenMapping(_l1Token, _oldL2Token, _l2Token);

// update corresponding mapping in L2, 1000000 gas limit should be enough
bytes memory _message = abi.encodeCall(L1CustomERC20Gateway.updateTokenMapping, (_l2Token, _l1Token));
IL1ScrollMessenger(messenger).sendMessage{value: msg.value}(counterpart, 0, _message, 1000000, _msgSender());
}

/**********************
Expand Down
82 changes: 82 additions & 0 deletions src/L1/gateways/L1ReverseCustomERC20Gateway.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
// SPDX-License-Identifier: MIT

pragma solidity =0.8.24;

import {IL2ERC20Gateway} from "../../L2/gateways/IL2ERC20Gateway.sol";
import {IScrollERC20Upgradeable} from "../../libraries/token/IScrollERC20Upgradeable.sol";
import {IL1ScrollMessenger} from "../IL1ScrollMessenger.sol";
import {L1CustomERC20Gateway} from "./L1CustomERC20Gateway.sol";
import {L1ERC20Gateway} from "./L1ERC20Gateway.sol";

/// @title L1ReverseCustomERC20Gateway
/// @notice The `L1ReverseCustomERC20Gateway` is used to deposit layer 2 native ERC20 tokens on layer 1 and
/// finalize withdraw the tokens from layer 2.
/// @dev The deposited tokens are transferred to this gateway and then burned. On finalizing withdraw, the corresponding
/// tokens will be minted and transfer to the recipient.
contract L1ReverseCustomERC20Gateway is L1CustomERC20Gateway {
/**********
* Errors *
**********/

/// @dev Thrown when no l2 token exists.
error ErrorNoCorrespondingL2Token();

/***************
* Constructor *
***************/

/// @notice Constructor for `L1ReverseCustomERC20Gateway` implementation contract.
///
/// @param _counterpart The address of `L2ReverseCustomERC20Gateway` contract in L2.
/// @param _router The address of `L1GatewayRouter` contract in L1.
/// @param _messenger The address of `L1ScrollMessenger` contract L1.
constructor(
address _counterpart,
address _router,
address _messenger
) L1CustomERC20Gateway(_counterpart, _router, _messenger) {
_disableInitializers();
}

/**********************
* Internal Functions *
**********************/

/// @inheritdoc L1ERC20Gateway
function _beforeFinalizeWithdrawERC20(
address _l1Token,
address _l2Token,
address _from,
address _to,
uint256 _amount,
bytes calldata _data
) internal virtual override {
super._beforeFinalizeWithdrawERC20(_l1Token, _l2Token, _from, _to, _amount, _data);

IScrollERC20Upgradeable(_l1Token).mint(address(this), _amount);
}

/// @inheritdoc L1ERC20Gateway
function _beforeDropMessage(
address _token,
address _receiver,
uint256 _amount
) internal virtual override {
super._beforeDropMessage(_token, _receiver, _amount);

IScrollERC20Upgradeable(_token).mint(address(this), _amount);
}

/// @inheritdoc L1ERC20Gateway
function _deposit(
address _token,
address _to,
uint256 _amount,
bytes memory _data,
uint256 _gasLimit
) internal virtual override {
super._deposit(_token, _to, _amount, _data, _gasLimit);

IScrollERC20Upgradeable(_token).burn(address(this), _amount);
}
}
11 changes: 7 additions & 4 deletions src/L2/gateways/L2CustomERC20Gateway.sol
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,8 @@ import {IL1ERC20Gateway} from "../../L1/gateways/IL1ERC20Gateway.sol";
import {ScrollGatewayBase} from "../../libraries/gateway/ScrollGatewayBase.sol";
import {IScrollERC20Upgradeable} from "../../libraries/token/IScrollERC20Upgradeable.sol";

/// @title L2ERC20Gateway
/// @notice The `L2ERC20Gateway` is used to withdraw custom ERC20 compatible tokens on layer 2 and
/// @title L2CustomERC20Gateway
/// @notice The `L2CustomERC20Gateway` is used to withdraw custom ERC20 compatible tokens on layer 2 and
/// finalize deposit the tokens from layer 1.
/// @dev The withdrawn tokens will be burned directly. On finalizing deposit, the corresponding
/// tokens will be minted and transferred to the recipient.
Expand Down Expand Up @@ -109,9 +109,12 @@ contract L2CustomERC20Gateway is L2ERC20Gateway {
************************/

/// @notice Update layer 2 to layer 1 token mapping.
///
/// @dev To make the token mapping consistent with L1, this should be called from L1.
///
/// @param _l2Token The address of corresponding ERC20 token on layer 2.
/// @param _l1Token The address of ERC20 token on layer 1.
function updateTokenMapping(address _l2Token, address _l1Token) external onlyOwner {
function updateTokenMapping(address _l2Token, address _l1Token) external onlyCallByCounterpart {
require(_l1Token != address(0), "token address cannot be 0");

address _oldL1Token = tokenMapping[_l2Token];
Expand Down Expand Up @@ -146,7 +149,7 @@ contract L2CustomERC20Gateway is L2ERC20Gateway {
// 2. Burn token.
IScrollERC20Upgradeable(_token).burn(_from, _amount);

// 3. Generate message passed to L1StandardERC20Gateway.
// 3. Generate message passed to L1CustomERC20Gateway.
bytes memory _message = abi.encodeCall(
IL1ERC20Gateway.finalizeWithdrawERC20,
(_l1Token, _token, _from, _to, _amount, _data)
Expand Down
120 changes: 120 additions & 0 deletions src/L2/gateways/L2ReverseCustomERC20Gateway.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
// SPDX-License-Identifier: MIT

pragma solidity =0.8.24;

import {IERC20Upgradeable} from "@openzeppelin/contracts-upgradeable/token/ERC20/IERC20Upgradeable.sol";
import {SafeERC20Upgradeable} from "@openzeppelin/contracts-upgradeable/token/ERC20/utils/SafeERC20Upgradeable.sol";
import {AddressUpgradeable} from "@openzeppelin/contracts-upgradeable/utils/AddressUpgradeable.sol";

import {IL1ERC20Gateway} from "../../L1/gateways/IL1ERC20Gateway.sol";
import {IL2ScrollMessenger} from "../IL2ScrollMessenger.sol";
import {IL2ERC20Gateway, L2ERC20Gateway} from "./L2ERC20Gateway.sol";
import {L2CustomERC20Gateway} from "./L2CustomERC20Gateway.sol";

/// @title L2ReverseCustomERC20Gateway
/// @notice The `L2ReverseCustomERC20Gateway` is used to withdraw native ERC20 tokens on layer 2 and
/// finalize deposit the tokens from layer 1.
/// @dev The withdrawn ERC20 tokens are holed in this contract. On finalizing deposit, the corresponding
/// token will be transferred to the recipient.
contract L2ReverseCustomERC20Gateway is L2CustomERC20Gateway {
using SafeERC20Upgradeable for IERC20Upgradeable;

/**********
* Errors *
**********/

/// @dev Thrown when the message value is not zero.
error ErrorNonzeroMsgValue();

/// @dev Thrown when the given l1 token address is zero.
error ErrorL1TokenAddressIsZero();

/// @dev Thrown when the given l1 token address not match stored one.
error ErrorL1TokenAddressMismatch();

/// @dev Thrown when no l1 token exists.
error ErrorNoCorrespondingL1Token();

/// @dev Thrown when withdraw zero amount token.
error ErrorWithdrawZeroAmount();

/***************
* Constructor *
***************/

/// @notice Constructor for `L2ReverseCustomERC20Gateway` implementation contract.
///
/// @param _counterpart The address of `L1ReverseCustomERC20Gateway` contract in L1.
/// @param _router The address of `L2GatewayRouter` contract in L2.
/// @param _messenger The address of `L2ScrollMessenger` contract in L2.
constructor(
address _counterpart,
address _router,
address _messenger
) L2CustomERC20Gateway(_counterpart, _router, _messenger) {
_disableInitializers();
}

/*****************************
* Public Mutating Functions *
*****************************/

/// @inheritdoc IL2ERC20Gateway
function finalizeDepositERC20(
address _l1Token,
address _l2Token,
address _from,
address _to,
uint256 _amount,
bytes calldata _data
) external payable override onlyCallByCounterpart nonReentrant {
if (msg.value != 0) revert ErrorNonzeroMsgValue();
if (_l1Token == address(0)) revert ErrorL1TokenAddressIsZero();
if (_l1Token != tokenMapping[_l2Token]) revert ErrorL1TokenAddressMismatch();

IERC20Upgradeable(_l2Token).safeTransfer(_to, _amount);

_doCallback(_to, _data);

emit FinalizeDepositERC20(_l1Token, _l2Token, _from, _to, _amount, _data);
}

/**********************
* Internal Functions *
**********************/

/// @inheritdoc L2ERC20Gateway
function _withdraw(
address _token,
address _to,
uint256 _amount,
bytes memory _data,
uint256 _gasLimit
) internal virtual override nonReentrant {
address _l1Token = tokenMapping[_token];
if (_l1Token == address(0)) revert ErrorNoCorrespondingL1Token();
if (_amount == 0) revert ErrorWithdrawZeroAmount();

// 1. Extract real sender if this call is from L2GatewayRouter.
address _from = _msgSender();
if (router == _from) {
(_from, _data) = abi.decode(_data, (address, bytes));
}

// 2. transfer token to this contract
uint256 balance = IERC20Upgradeable(_token).balanceOf(address(this));
IERC20Upgradeable(_token).safeTransferFrom(_from, address(this), _amount);
_amount = IERC20Upgradeable(_token).balanceOf(address(this)) - balance;

// 3. Generate message passed to L1ReverseCustomERC20Gateway.
bytes memory _message = abi.encodeCall(
IL1ERC20Gateway.finalizeWithdrawERC20,
(_l1Token, _token, _from, _to, _amount, _data)
);

// 4. send message to L2ScrollMessenger
IL2ScrollMessenger(messenger).sendMessage{value: msg.value}(counterpart, 0, _message, _gasLimit);

emit WithdrawERC20(_l1Token, _token, _from, _to, _amount, _data);
}
}

0 comments on commit 9ec3594

Please sign in to comment.