-
Notifications
You must be signed in to change notification settings - Fork 25
/
Copy pathL1StandardERC20Gateway.sol
183 lines (154 loc) · 7.24 KB
/
L1StandardERC20Gateway.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
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
// SPDX-License-Identifier: MIT
pragma solidity =0.8.24;
import {ClonesUpgradeable} from "@openzeppelin/contracts-upgradeable/proxy/ClonesUpgradeable.sol";
import {IERC20MetadataUpgradeable} from "@openzeppelin/contracts-upgradeable/token/ERC20/extensions/IERC20MetadataUpgradeable.sol";
import {IL2ERC20Gateway} from "../../L2/gateways/IL2ERC20Gateway.sol";
import {IL1ScrollMessenger} from "../IL1ScrollMessenger.sol";
import {IL1ERC20Gateway} from "./IL1ERC20Gateway.sol";
import {ScrollGatewayBase} from "../../libraries/gateway/ScrollGatewayBase.sol";
import {L1ERC20Gateway} from "./L1ERC20Gateway.sol";
/// @title L1StandardERC20Gateway
/// @notice The `L1StandardERC20Gateway` is used to deposit standard ERC20 tokens on layer 1 and
/// finalize withdraw the tokens from layer 2.
/// @dev The deposited ERC20 tokens are held in this gateway. On finalizing withdraw, the corresponding
/// token will be transfer to the recipient directly. Any ERC20 that requires non-standard functionality
/// should use a separate gateway.
contract L1StandardERC20Gateway is L1ERC20Gateway {
/*************
* Constants *
*************/
/// @notice The address of ScrollStandardERC20 implementation in L2.
address public immutable l2TokenImplementation;
/// @notice The address of ScrollStandardERC20Factory contract in L2.
address public immutable l2TokenFactory;
/*************
* Variables *
*************/
/// @dev The storage slot used as ScrollStandardERC20 implementation in L2, which is deprecated now.
address private __l2TokenImplementation;
/// @dev The storage slot used as ScrollStandardERC20Factory contract in L2, which is deprecated now.
address private __l2TokenFactory;
/// @notice Mapping from l1 token address to l2 token address.
/// @dev This is not necessary, since we can compute the address directly. But, we use this mapping
/// to keep track on whether we have deployed the token in L2 using the L2ScrollStandardERC20Factory and
/// pass deploy data on first call to the token.
mapping(address => address) private tokenMapping;
/***************
* Constructor *
***************/
/// @notice Constructor for `L1StandardERC20Gateway` implementation contract.
///
/// @param _counterpart The address of `L2StandardERC20Gateway` contract in L2.
/// @param _router The address of `L1GatewayRouter` contract in L1.
/// @param _messenger The address of `L1ScrollMessenger` contract in L1.
/// @param _l2TokenImplementation The address of `ScrollStandardERC20` implementation in L2.
/// @param _l2TokenFactory The address of `ScrollStandardERC20Factory` contract in L2.
constructor(
address _counterpart,
address _router,
address _messenger,
address _l2TokenImplementation,
address _l2TokenFactory
) ScrollGatewayBase(_counterpart, _router, _messenger) {
if (_router == address(0) || _l2TokenImplementation == address(0) || _l2TokenFactory == address(0)) {
revert ErrorZeroAddress();
}
_disableInitializers();
l2TokenImplementation = _l2TokenImplementation;
l2TokenFactory = _l2TokenFactory;
}
/// @notice Initialize the storage of L1StandardERC20Gateway.
///
/// @dev The parameters `_counterpart`, `_router`, `_messenger`, `_l2TokenImplementation` and
/// `_l2TokenFactory` are no longer used.
///
/// @param _counterpart The address of L2StandardERC20Gateway in L2.
/// @param _router The address of L1GatewayRouter in L1.
/// @param _messenger The address of L1ScrollMessenger in L1.
function initialize(
address _counterpart,
address _router,
address _messenger,
address, /*_l2TokenImplementation*/
address /*_l2TokenFactory*/
) external initializer {
ScrollGatewayBase._initialize(_counterpart, _router, _messenger);
}
/*************************
* Public View Functions *
*************************/
/// @inheritdoc IL1ERC20Gateway
function getL2ERC20Address(address _l1Token) public view override returns (address) {
// In StandardERC20Gateway, all corresponding l2 tokens are depoyed by Create2 with salt,
// we can calculate the l2 address directly.
bytes32 _salt = keccak256(abi.encodePacked(counterpart, keccak256(abi.encodePacked(_l1Token))));
return ClonesUpgradeable.predictDeterministicAddress(l2TokenImplementation, _salt, l2TokenFactory);
}
/**********************
* Internal Functions *
**********************/
/// @inheritdoc L1ERC20Gateway
function _beforeFinalizeWithdrawERC20(
address _l1Token,
address _l2Token,
address,
address,
uint256,
bytes calldata
) internal virtual override {
require(msg.value == 0, "nonzero msg.value");
require(_l2Token != address(0), "token address cannot be 0");
require(getL2ERC20Address(_l1Token) == _l2Token, "l2 token mismatch");
// update `tokenMapping` on first withdraw
address _storedL2Token = tokenMapping[_l1Token];
if (_storedL2Token == address(0)) {
tokenMapping[_l1Token] = _l2Token;
} else {
require(_storedL2Token == _l2Token, "l2 token mismatch");
}
}
/// @inheritdoc L1ERC20Gateway
function _beforeDropMessage(
address,
address,
uint256
) internal virtual override {
require(msg.value == 0, "nonzero msg.value");
}
/// @inheritdoc L1ERC20Gateway
function _deposit(
address _token,
address _to,
uint256 _amount,
bytes memory _data,
uint256 _gasLimit
) internal virtual override nonReentrant {
require(_amount > 0, "deposit zero amount");
// 1. Transfer token into this contract.
address _from;
(_from, _amount, _data) = _transferERC20In(_token, _amount, _data);
// 2. Generate message passed to L2StandardERC20Gateway.
address _l2Token = tokenMapping[_token];
bytes memory _l2Data;
if (_l2Token == address(0)) {
// @note we won't update `tokenMapping` here but update the `tokenMapping` on
// first successful withdraw. This will prevent user to set arbitrary token
// metadata by setting a very small `_gasLimit` on the first tx.
_l2Token = getL2ERC20Address(_token);
// passing symbol/name/decimal in order to deploy in L2.
string memory _symbol = IERC20MetadataUpgradeable(_token).symbol();
string memory _name = IERC20MetadataUpgradeable(_token).name();
uint8 _decimals = IERC20MetadataUpgradeable(_token).decimals();
_l2Data = abi.encode(true, abi.encode(_data, abi.encode(_symbol, _name, _decimals)));
} else {
_l2Data = abi.encode(false, _data);
}
bytes memory _message = abi.encodeCall(
IL2ERC20Gateway.finalizeDepositERC20,
(_token, _l2Token, _from, _to, _amount, _l2Data)
);
// 3. Send message to L1ScrollMessenger.
IL1ScrollMessenger(messenger).sendMessage{value: msg.value}(counterpart, 0, _message, _gasLimit, _from);
emit DepositERC20(_token, _l2Token, _from, _to, _amount, _data);
}
}