Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: gateway for l2 native erc20 #40

Merged
merged 3 commits into from
Sep 25, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions foundry.toml
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,9 @@ libs = ["lib"]
remappings = [] # a list of remappings
libraries = [] # a list of deployed libraries to link against
cache = true # whether to cache builds or not
force = true # whether to ignore the cache (clean build)
# evm_version = 'london' # the evm version (by hardfork name)
solc_version = '0.8.24' # override for the solc version (setting this ignores `auto_detect_solc`)
# force = true # whether to ignore the cache (clean build)
colinlyguo marked this conversation as resolved.
Show resolved Hide resolved
evm_version = 'cancun' # the evm version (by hardfork name)
solc_version = '0.8.24' # override for the solc version (setting this ignores `auto_detect_solc`)
optimizer = true # enable or disable the solc optimizer
optimizer_runs = 200 # the number of optimizer runs
verbosity = 2 # the verbosity of tests
Expand Down
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);
}
}
15 changes: 8 additions & 7 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 @@ -92,7 +92,7 @@ contract L2CustomERC20Gateway is L2ERC20Gateway {
address _to,
uint256 _amount,
bytes calldata _data
) external payable override onlyCallByCounterpart nonReentrant {
) external payable virtual override onlyCallByCounterpart nonReentrant {
require(msg.value == 0, "nonzero msg.value");
require(_l1Token != address(0), "token address cannot be 0");
require(_l1Token == tokenMapping[_l2Token], "l1 token mismatch");
Expand All @@ -109,11 +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 {
require(_l1Token != address(0), "token address cannot be 0");
colinlyguo marked this conversation as resolved.
Show resolved Hide resolved

function updateTokenMapping(address _l2Token, address _l1Token) external onlyCallByCounterpart {
address _oldL1Token = tokenMapping[_l2Token];
tokenMapping[_l2Token] = _l1Token;

Expand Down Expand Up @@ -146,7 +147,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);
}
}
Loading
Loading