/
L2CustomERC20Gateway.sol
147 lines (117 loc) · 5 KB
/
L2CustomERC20Gateway.sol
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import {OwnableUpgradeable} from "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol";
import {IERC20Upgradeable} from "@openzeppelin/contracts-upgradeable/token/ERC20/IERC20Upgradeable.sol";
import {IL2ERC20Gateway, L2ERC20Gateway} from "./L2ERC20Gateway.sol";
import {IL2ScrollMessenger} from "../IL2ScrollMessenger.sol";
import {IL1ERC20Gateway} from "../../L1/gateways/IL1ERC20Gateway.sol";
import {ScrollGatewayBase, IScrollGateway} from "../../libraries/gateway/ScrollGatewayBase.sol";
import {IScrollERC20} from "../../libraries/token/IScrollERC20.sol";
/// @title L2ERC20Gateway
/// @notice The `L2ERC20Gateway` is used to withdraw custom ERC20 compatible tokens in layer 2 and
/// finalize deposit the tokens from layer 1.
/// @dev The withdrawn tokens tokens will be burned directly. On finalizing deposit, the corresponding
/// tokens will be minted and transfered to the recipient.
contract L2CustomERC20Gateway is OwnableUpgradeable, ScrollGatewayBase, L2ERC20Gateway {
/**********
* Events *
**********/
/// @notice Emitted when token mapping for ERC20 token is updated.
/// @param _l2Token The address of corresponding ERC20 token in layer 2.
/// @param _l1Token The address of ERC20 token in layer 1.
event UpdateTokenMapping(address _l2Token, address _l1Token);
/*************
* Variables *
*************/
/// @notice Mapping from layer 2 token address to layer 1 token address for ERC20 token.
// solhint-disable-next-line var-name-mixedcase
mapping(address => address) public tokenMapping;
/***************
* Constructor *
***************/
function initialize(
address _counterpart,
address _router,
address _messenger
) external initializer {
require(_router != address(0), "zero router address");
OwnableUpgradeable.__Ownable_init();
ScrollGatewayBase._initialize(_counterpart, _router, _messenger);
}
/*************************
* Public View Functions *
*************************/
/// @inheritdoc IL2ERC20Gateway
function getL1ERC20Address(address _l2Token) external view override returns (address) {
return tokenMapping[_l2Token];
}
/// @inheritdoc IL2ERC20Gateway
function getL2ERC20Address(address) public pure override returns (address) {
revert("unimplemented");
}
/*****************************
* 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 {
require(msg.value == 0, "nonzero msg.value");
require(_l1Token != address(0), "token address cannot be 0");
require(_l1Token == tokenMapping[_l2Token], "l1 token mismatch");
IScrollERC20(_l2Token).mint(_to, _amount);
_doCallback(_to, _data);
emit FinalizeDepositERC20(_l1Token, _l2Token, _from, _to, _amount, _data);
}
/************************
* Restricted Functions *
************************/
/// @notice Update layer 2 to layer 1 token mapping.
/// @param _l2Token The address of corresponding ERC20 token in layer 2.
/// @param _l1Token The address of ERC20 token in layer 1.
function updateTokenMapping(address _l2Token, address _l1Token) external onlyOwner {
require(_l1Token != address(0), "token address cannot be 0");
tokenMapping[_l2Token] = _l1Token;
emit UpdateTokenMapping(_l2Token, _l1Token);
}
/**********************
* 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];
require(_l1Token != address(0), "no corresponding l1 token");
require(_amount > 0, "withdraw zero amount");
// 1. Extract real sender if this call is from L2GatewayRouter.
address _from = msg.sender;
if (router == msg.sender) {
(_from, _data) = abi.decode(_data, (address, bytes));
}
// 2. Burn token.
IScrollERC20(_token).burn(_from, _amount);
// 3. Generate message passed to L1StandardERC20Gateway.
bytes memory _message = abi.encodeWithSelector(
IL1ERC20Gateway.finalizeWithdrawERC20.selector,
_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);
}
}