Skip to content

Commit eedc79a

Browse files
authored
feat: gateway for l2 native erc20 (#40)
1 parent 7bb751f commit eedc79a

10 files changed

+1153
-55
lines changed

foundry.toml

+3-3
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,9 @@ libs = ["lib"]
77
remappings = [] # a list of remappings
88
libraries = [] # a list of deployed libraries to link against
99
cache = true # whether to cache builds or not
10-
force = true # whether to ignore the cache (clean build)
11-
# evm_version = 'london' # the evm version (by hardfork name)
12-
solc_version = '0.8.24' # override for the solc version (setting this ignores `auto_detect_solc`)
10+
# force = true # whether to ignore the cache (clean build)
11+
evm_version = 'cancun' # the evm version (by hardfork name)
12+
solc_version = '0.8.24' # override for the solc version (setting this ignores `auto_detect_solc`)
1313
optimizer = true # enable or disable the solc optimizer
1414
optimizer_runs = 200 # the number of optimizer runs
1515
verbosity = 2 # the verbosity of tests

src/L1/gateways/L1CustomERC20Gateway.sol

+6-2
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ contract L1CustomERC20Gateway is L1ERC20Gateway {
4343

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

9292
address _oldL2Token = tokenMapping[_l1Token];
9393
tokenMapping[_l1Token] = _l2Token;
9494

9595
emit UpdateTokenMapping(_l1Token, _oldL2Token, _l2Token);
96+
97+
// update corresponding mapping in L2, 1000000 gas limit should be enough
98+
bytes memory _message = abi.encodeCall(L1CustomERC20Gateway.updateTokenMapping, (_l2Token, _l1Token));
99+
IL1ScrollMessenger(messenger).sendMessage{value: msg.value}(counterpart, 0, _message, 1000000, _msgSender());
96100
}
97101

98102
/**********************
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
// SPDX-License-Identifier: MIT
2+
3+
pragma solidity =0.8.24;
4+
5+
import {IL2ERC20Gateway} from "../../L2/gateways/IL2ERC20Gateway.sol";
6+
import {IScrollERC20Upgradeable} from "../../libraries/token/IScrollERC20Upgradeable.sol";
7+
import {IL1ScrollMessenger} from "../IL1ScrollMessenger.sol";
8+
import {L1CustomERC20Gateway} from "./L1CustomERC20Gateway.sol";
9+
import {L1ERC20Gateway} from "./L1ERC20Gateway.sol";
10+
11+
/// @title L1ReverseCustomERC20Gateway
12+
/// @notice The `L1ReverseCustomERC20Gateway` is used to deposit layer 2 native ERC20 tokens on layer 1 and
13+
/// finalize withdraw the tokens from layer 2.
14+
/// @dev The deposited tokens are transferred to this gateway and then burned. On finalizing withdraw, the corresponding
15+
/// tokens will be minted and transfer to the recipient.
16+
contract L1ReverseCustomERC20Gateway is L1CustomERC20Gateway {
17+
/**********
18+
* Errors *
19+
**********/
20+
21+
/// @dev Thrown when no l2 token exists.
22+
error ErrorNoCorrespondingL2Token();
23+
24+
/***************
25+
* Constructor *
26+
***************/
27+
28+
/// @notice Constructor for `L1ReverseCustomERC20Gateway` implementation contract.
29+
///
30+
/// @param _counterpart The address of `L2ReverseCustomERC20Gateway` contract in L2.
31+
/// @param _router The address of `L1GatewayRouter` contract in L1.
32+
/// @param _messenger The address of `L1ScrollMessenger` contract L1.
33+
constructor(
34+
address _counterpart,
35+
address _router,
36+
address _messenger
37+
) L1CustomERC20Gateway(_counterpart, _router, _messenger) {
38+
_disableInitializers();
39+
}
40+
41+
/**********************
42+
* Internal Functions *
43+
**********************/
44+
45+
/// @inheritdoc L1ERC20Gateway
46+
function _beforeFinalizeWithdrawERC20(
47+
address _l1Token,
48+
address _l2Token,
49+
address _from,
50+
address _to,
51+
uint256 _amount,
52+
bytes calldata _data
53+
) internal virtual override {
54+
super._beforeFinalizeWithdrawERC20(_l1Token, _l2Token, _from, _to, _amount, _data);
55+
56+
IScrollERC20Upgradeable(_l1Token).mint(address(this), _amount);
57+
}
58+
59+
/// @inheritdoc L1ERC20Gateway
60+
function _beforeDropMessage(
61+
address _token,
62+
address _receiver,
63+
uint256 _amount
64+
) internal virtual override {
65+
super._beforeDropMessage(_token, _receiver, _amount);
66+
67+
IScrollERC20Upgradeable(_token).mint(address(this), _amount);
68+
}
69+
70+
/// @inheritdoc L1ERC20Gateway
71+
function _deposit(
72+
address _token,
73+
address _to,
74+
uint256 _amount,
75+
bytes memory _data,
76+
uint256 _gasLimit
77+
) internal virtual override {
78+
super._deposit(_token, _to, _amount, _data, _gasLimit);
79+
80+
IScrollERC20Upgradeable(_token).burn(address(this), _amount);
81+
}
82+
}

src/L2/gateways/L2CustomERC20Gateway.sol

+8-7
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,8 @@ import {IL1ERC20Gateway} from "../../L1/gateways/IL1ERC20Gateway.sol";
88
import {ScrollGatewayBase} from "../../libraries/gateway/ScrollGatewayBase.sol";
99
import {IScrollERC20Upgradeable} from "../../libraries/token/IScrollERC20Upgradeable.sol";
1010

11-
/// @title L2ERC20Gateway
12-
/// @notice The `L2ERC20Gateway` is used to withdraw custom ERC20 compatible tokens on layer 2 and
11+
/// @title L2CustomERC20Gateway
12+
/// @notice The `L2CustomERC20Gateway` is used to withdraw custom ERC20 compatible tokens on layer 2 and
1313
/// finalize deposit the tokens from layer 1.
1414
/// @dev The withdrawn tokens will be burned directly. On finalizing deposit, the corresponding
1515
/// tokens will be minted and transferred to the recipient.
@@ -92,7 +92,7 @@ contract L2CustomERC20Gateway is L2ERC20Gateway {
9292
address _to,
9393
uint256 _amount,
9494
bytes calldata _data
95-
) external payable override onlyCallByCounterpart nonReentrant {
95+
) external payable virtual override onlyCallByCounterpart nonReentrant {
9696
require(msg.value == 0, "nonzero msg.value");
9797
require(_l1Token != address(0), "token address cannot be 0");
9898
require(_l1Token == tokenMapping[_l2Token], "l1 token mismatch");
@@ -109,11 +109,12 @@ contract L2CustomERC20Gateway is L2ERC20Gateway {
109109
************************/
110110

111111
/// @notice Update layer 2 to layer 1 token mapping.
112+
///
113+
/// @dev To make the token mapping consistent with L1, this should be called from L1.
114+
///
112115
/// @param _l2Token The address of corresponding ERC20 token on layer 2.
113116
/// @param _l1Token The address of ERC20 token on layer 1.
114-
function updateTokenMapping(address _l2Token, address _l1Token) external onlyOwner {
115-
require(_l1Token != address(0), "token address cannot be 0");
116-
117+
function updateTokenMapping(address _l2Token, address _l1Token) external onlyCallByCounterpart {
117118
address _oldL1Token = tokenMapping[_l2Token];
118119
tokenMapping[_l2Token] = _l1Token;
119120

@@ -146,7 +147,7 @@ contract L2CustomERC20Gateway is L2ERC20Gateway {
146147
// 2. Burn token.
147148
IScrollERC20Upgradeable(_token).burn(_from, _amount);
148149

149-
// 3. Generate message passed to L1StandardERC20Gateway.
150+
// 3. Generate message passed to L1CustomERC20Gateway.
150151
bytes memory _message = abi.encodeCall(
151152
IL1ERC20Gateway.finalizeWithdrawERC20,
152153
(_l1Token, _token, _from, _to, _amount, _data)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,120 @@
1+
// SPDX-License-Identifier: MIT
2+
3+
pragma solidity =0.8.24;
4+
5+
import {IERC20Upgradeable} from "@openzeppelin/contracts-upgradeable/token/ERC20/IERC20Upgradeable.sol";
6+
import {SafeERC20Upgradeable} from "@openzeppelin/contracts-upgradeable/token/ERC20/utils/SafeERC20Upgradeable.sol";
7+
import {AddressUpgradeable} from "@openzeppelin/contracts-upgradeable/utils/AddressUpgradeable.sol";
8+
9+
import {IL1ERC20Gateway} from "../../L1/gateways/IL1ERC20Gateway.sol";
10+
import {IL2ScrollMessenger} from "../IL2ScrollMessenger.sol";
11+
import {IL2ERC20Gateway, L2ERC20Gateway} from "./L2ERC20Gateway.sol";
12+
import {L2CustomERC20Gateway} from "./L2CustomERC20Gateway.sol";
13+
14+
/// @title L2ReverseCustomERC20Gateway
15+
/// @notice The `L2ReverseCustomERC20Gateway` is used to withdraw native ERC20 tokens on layer 2 and
16+
/// finalize deposit the tokens from layer 1.
17+
/// @dev The withdrawn ERC20 tokens are holed in this contract. On finalizing deposit, the corresponding
18+
/// token will be transferred to the recipient.
19+
contract L2ReverseCustomERC20Gateway is L2CustomERC20Gateway {
20+
using SafeERC20Upgradeable for IERC20Upgradeable;
21+
22+
/**********
23+
* Errors *
24+
**********/
25+
26+
/// @dev Thrown when the message value is not zero.
27+
error ErrorNonzeroMsgValue();
28+
29+
/// @dev Thrown when the given l1 token address is zero.
30+
error ErrorL1TokenAddressIsZero();
31+
32+
/// @dev Thrown when the given l1 token address not match stored one.
33+
error ErrorL1TokenAddressMismatch();
34+
35+
/// @dev Thrown when no l1 token exists.
36+
error ErrorNoCorrespondingL1Token();
37+
38+
/// @dev Thrown when withdraw zero amount token.
39+
error ErrorWithdrawZeroAmount();
40+
41+
/***************
42+
* Constructor *
43+
***************/
44+
45+
/// @notice Constructor for `L2ReverseCustomERC20Gateway` implementation contract.
46+
///
47+
/// @param _counterpart The address of `L1ReverseCustomERC20Gateway` contract in L1.
48+
/// @param _router The address of `L2GatewayRouter` contract in L2.
49+
/// @param _messenger The address of `L2ScrollMessenger` contract in L2.
50+
constructor(
51+
address _counterpart,
52+
address _router,
53+
address _messenger
54+
) L2CustomERC20Gateway(_counterpart, _router, _messenger) {
55+
_disableInitializers();
56+
}
57+
58+
/*****************************
59+
* Public Mutating Functions *
60+
*****************************/
61+
62+
/// @inheritdoc IL2ERC20Gateway
63+
function finalizeDepositERC20(
64+
address _l1Token,
65+
address _l2Token,
66+
address _from,
67+
address _to,
68+
uint256 _amount,
69+
bytes calldata _data
70+
) external payable override onlyCallByCounterpart nonReentrant {
71+
if (msg.value != 0) revert ErrorNonzeroMsgValue();
72+
if (_l1Token == address(0)) revert ErrorL1TokenAddressIsZero();
73+
if (_l1Token != tokenMapping[_l2Token]) revert ErrorL1TokenAddressMismatch();
74+
75+
IERC20Upgradeable(_l2Token).safeTransfer(_to, _amount);
76+
77+
_doCallback(_to, _data);
78+
79+
emit FinalizeDepositERC20(_l1Token, _l2Token, _from, _to, _amount, _data);
80+
}
81+
82+
/**********************
83+
* Internal Functions *
84+
**********************/
85+
86+
/// @inheritdoc L2ERC20Gateway
87+
function _withdraw(
88+
address _token,
89+
address _to,
90+
uint256 _amount,
91+
bytes memory _data,
92+
uint256 _gasLimit
93+
) internal virtual override nonReentrant {
94+
address _l1Token = tokenMapping[_token];
95+
if (_l1Token == address(0)) revert ErrorNoCorrespondingL1Token();
96+
if (_amount == 0) revert ErrorWithdrawZeroAmount();
97+
98+
// 1. Extract real sender if this call is from L2GatewayRouter.
99+
address _from = _msgSender();
100+
if (router == _from) {
101+
(_from, _data) = abi.decode(_data, (address, bytes));
102+
}
103+
104+
// 2. transfer token to this contract
105+
uint256 balance = IERC20Upgradeable(_token).balanceOf(address(this));
106+
IERC20Upgradeable(_token).safeTransferFrom(_from, address(this), _amount);
107+
_amount = IERC20Upgradeable(_token).balanceOf(address(this)) - balance;
108+
109+
// 3. Generate message passed to L1ReverseCustomERC20Gateway.
110+
bytes memory _message = abi.encodeCall(
111+
IL1ERC20Gateway.finalizeWithdrawERC20,
112+
(_l1Token, _token, _from, _to, _amount, _data)
113+
);
114+
115+
// 4. send message to L2ScrollMessenger
116+
IL2ScrollMessenger(messenger).sendMessage{value: msg.value}(counterpart, 0, _message, _gasLimit);
117+
118+
emit WithdrawERC20(_l1Token, _token, _from, _to, _amount, _data);
119+
}
120+
}

0 commit comments

Comments
 (0)