/
L2ScrollMessenger.sol
343 lines (284 loc) · 13 KB
/
L2ScrollMessenger.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
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import {PausableUpgradeable} from "@openzeppelin/contracts-upgradeable/security/PausableUpgradeable.sol";
import {IL2ScrollMessenger} from "./IL2ScrollMessenger.sol";
import {L2MessageQueue} from "./predeploys/L2MessageQueue.sol";
import {IL1BlockContainer} from "./predeploys/IL1BlockContainer.sol";
import {IL1GasPriceOracle} from "./predeploys/IL1GasPriceOracle.sol";
import {PatriciaMerkleTrieVerifier} from "../libraries/verifier/PatriciaMerkleTrieVerifier.sol";
import {ScrollConstants} from "../libraries/constants/ScrollConstants.sol";
import {AddressAliasHelper} from "../libraries/common/AddressAliasHelper.sol";
import {IScrollMessenger} from "../libraries/IScrollMessenger.sol";
import {ScrollMessengerBase} from "../libraries/ScrollMessengerBase.sol";
/// @title L2ScrollMessenger
/// @notice The `L2ScrollMessenger` contract can:
///
/// 1. send messages from layer 2 to layer 1;
/// 2. relay messages from layer 1 layer 2;
/// 3. drop expired message due to sequencer problems.
///
/// @dev It should be a predeployed contract in layer 2 and should hold infinite amount
/// of Ether (Specifically, `uint256(-1)`), which can be initialized in Genesis Block.
contract L2ScrollMessenger is ScrollMessengerBase, PausableUpgradeable, IL2ScrollMessenger {
/**********
* Events *
**********/
/// @notice Emitted when the maximum number of times each message can fail in L2 is updated.
/// @param maxFailedExecutionTimes The new maximum number of times each message can fail in L2.
event UpdateMaxFailedExecutionTimes(uint256 maxFailedExecutionTimes);
/*************
* Constants *
*************/
/// @notice The contract contains the list of L1 blocks.
address public immutable blockContainer;
/// @notice The address of L2MessageQueue.
address public immutable gasOracle;
/// @notice The address of L2MessageQueue.
address public immutable messageQueue;
/*************
* Variables *
*************/
/// @notice Mapping from L2 message hash to sent status.
mapping(bytes32 => bool) public isL2MessageSent;
/// @notice Mapping from L1 message hash to a boolean value indicating if the message has been successfully executed.
mapping(bytes32 => bool) public isL1MessageExecuted;
/// @notice Mapping from L1 message hash to the number of failure times.
mapping(bytes32 => uint256) public l1MessageFailedTimes;
/// @notice The maximum number of times each L1 message can fail on L2.
uint256 public maxFailedExecutionTimes;
// @note move to ScrollMessengerBase in next big refactor
/// @dev The status of for non-reentrant check.
uint256 private _lock_status;
/**********************
* Function Modifiers *
**********************/
modifier nonReentrant() {
// On the first call to nonReentrant, _notEntered will be true
require(_lock_status != _ENTERED, "ReentrancyGuard: reentrant call");
// Any calls to nonReentrant after this point will fail
_lock_status = _ENTERED;
_;
// By storing the original value once again, a refund is triggered (see
// https://eips.ethereum.org/EIPS/eip-2200)
_lock_status = _NOT_ENTERED;
}
/***************
* Constructor *
***************/
constructor(
address _blockContainer,
address _gasOracle,
address _messageQueue
) {
blockContainer = _blockContainer;
gasOracle = _gasOracle;
messageQueue = _messageQueue;
}
function initialize(address _counterpart, address _feeVault) external initializer {
PausableUpgradeable.__Pausable_init();
ScrollMessengerBase._initialize(_counterpart, _feeVault);
maxFailedExecutionTimes = 3;
// initialize to a nonzero value
xDomainMessageSender = ScrollConstants.DEFAULT_XDOMAIN_MESSAGE_SENDER;
}
/*************************
* Public View Functions *
*************************/
/// @notice Check whether the l1 message is included in the corresponding L1 block.
/// @param _blockHash The block hash where the message should in.
/// @param _msgHash The hash of the message to check.
/// @param _proof The encoded storage proof from eth_getProof.
/// @return bool Return true is the message is included in L1, otherwise return false.
function verifyMessageInclusionStatus(
bytes32 _blockHash,
bytes32 _msgHash,
bytes calldata _proof
) public view returns (bool) {
bytes32 _expectedStateRoot = IL1BlockContainer(blockContainer).getStateRoot(_blockHash);
require(_expectedStateRoot != bytes32(0), "Block is not imported");
bytes32 _storageKey;
// `mapping(bytes32 => bool) public isL1MessageSent` is the 105-nd slot of contract `L1ScrollMessenger`.
// + 50 from `OwnableUpgradeable`
// + 4 from `ScrollMessengerBase`
// + 50 from `PausableUpgradeable`
// + 2-nd in `L1ScrollMessenger`
assembly {
mstore(0x00, _msgHash)
mstore(0x20, 105)
_storageKey := keccak256(0x00, 0x40)
}
(bytes32 _computedStateRoot, bytes32 _storageValue) = PatriciaMerkleTrieVerifier.verifyPatriciaProof(
counterpart,
_storageKey,
_proof
);
require(_computedStateRoot == _expectedStateRoot, "State roots mismatch");
return uint256(_storageValue) == 1;
}
/// @notice Check whether the message is executed in the corresponding L1 block.
/// @param _blockHash The block hash where the message should in.
/// @param _msgHash The hash of the message to check.
/// @param _proof The encoded storage proof from eth_getProof.
/// @return bool Return true is the message is executed in L1, otherwise return false.
function verifyMessageExecutionStatus(
bytes32 _blockHash,
bytes32 _msgHash,
bytes calldata _proof
) external view returns (bool) {
bytes32 _expectedStateRoot = IL1BlockContainer(blockContainer).getStateRoot(_blockHash);
require(_expectedStateRoot != bytes32(0), "Block not imported");
bytes32 _storageKey;
// `mapping(bytes32 => bool) public isL2MessageExecuted` is the 106-th slot of contract `L1ScrollMessenger`.
// + 50 from `OwnableUpgradeable`
// + 4 from `ScrollMessengerBase`
// + 50 from `PausableUpgradeable`
// + 3-rd in `L1ScrollMessenger`
assembly {
mstore(0x00, _msgHash)
mstore(0x20, 106)
_storageKey := keccak256(0x00, 0x40)
}
(bytes32 _computedStateRoot, bytes32 _storageValue) = PatriciaMerkleTrieVerifier.verifyPatriciaProof(
counterpart,
_storageKey,
_proof
);
require(_computedStateRoot == _expectedStateRoot, "State root mismatch");
return uint256(_storageValue) == 1;
}
/*****************************
* Public Mutating Functions *
*****************************/
/// @inheritdoc IScrollMessenger
function sendMessage(
address _to,
uint256 _value,
bytes memory _message,
uint256 _gasLimit
) external payable override whenNotPaused {
_sendMessage(_to, _value, _message, _gasLimit);
}
/// @inheritdoc IScrollMessenger
function sendMessage(
address _to,
uint256 _value,
bytes calldata _message,
uint256 _gasLimit,
address
) external payable override whenNotPaused {
_sendMessage(_to, _value, _message, _gasLimit);
}
/// @inheritdoc IL2ScrollMessenger
function relayMessage(
address _from,
address _to,
uint256 _value,
uint256 _nonce,
bytes memory _message
) external override whenNotPaused {
// It is impossible to deploy a contract with the same address, reentrance is prevented in nature.
require(AddressAliasHelper.undoL1ToL2Alias(msg.sender) == counterpart, "Caller is not L1ScrollMessenger");
bytes32 _xDomainCalldataHash = keccak256(_encodeXDomainCalldata(_from, _to, _value, _nonce, _message));
require(!isL1MessageExecuted[_xDomainCalldataHash], "Message was already successfully executed");
_executeMessage(_from, _to, _value, _message, _xDomainCalldataHash);
}
/// @inheritdoc IL2ScrollMessenger
function retryMessageWithProof(
address _from,
address _to,
uint256 _value,
uint256 _nonce,
bytes memory _message,
L1MessageProof calldata _proof
) external override whenNotPaused {
// anti reentrance
require(xDomainMessageSender == ScrollConstants.DEFAULT_XDOMAIN_MESSAGE_SENDER, "Already in execution");
// check message status
bytes32 _xDomainCalldataHash = keccak256(_encodeXDomainCalldata(_from, _to, _value, _nonce, _message));
require(!isL1MessageExecuted[_xDomainCalldataHash], "Message successfully executed");
require(l1MessageFailedTimes[_xDomainCalldataHash] > 0, "Message not relayed before");
require(
verifyMessageInclusionStatus(_proof.blockHash, _xDomainCalldataHash, _proof.stateRootProof),
"Message not included"
);
_executeMessage(_from, _to, _value, _message, _xDomainCalldataHash);
}
/************************
* Restricted Functions *
************************/
/// @notice Pause the contract
/// @dev This function can only called by contract owner.
/// @param _status The pause status to update.
function setPause(bool _status) external onlyOwner {
if (_status) {
_pause();
} else {
_unpause();
}
}
/// @notice Update max failed execution times.
/// @dev This function can only called by contract owner.
/// @param _maxFailedExecutionTimes The new max failed execution times.
function updateMaxFailedExecutionTimes(uint256 _maxFailedExecutionTimes) external onlyOwner {
maxFailedExecutionTimes = _maxFailedExecutionTimes;
emit UpdateMaxFailedExecutionTimes(_maxFailedExecutionTimes);
}
/**********************
* Internal Functions *
**********************/
/// @dev Internal function to send cross domain message.
/// @param _to The address of account who receive the message.
/// @param _value The amount of ether passed when call target contract.
/// @param _message The content of the message.
/// @param _gasLimit Optional gas limit to complete the message relay on corresponding chain.
function _sendMessage(
address _to,
uint256 _value,
bytes memory _message,
uint256 _gasLimit
) internal nonReentrant {
require(msg.value == _value, "msg.value mismatch");
uint256 _nonce = L2MessageQueue(messageQueue).nextMessageIndex();
bytes32 _xDomainCalldataHash = keccak256(_encodeXDomainCalldata(msg.sender, _to, _value, _nonce, _message));
// normally this won't happen, since each message has different nonce, but just in case.
require(!isL2MessageSent[_xDomainCalldataHash], "Duplicated message");
isL2MessageSent[_xDomainCalldataHash] = true;
L2MessageQueue(messageQueue).appendMessage(_xDomainCalldataHash);
emit SentMessage(msg.sender, _to, _value, _nonce, _gasLimit, _message);
}
/// @dev Internal function to execute a L1 => L2 message.
/// @param _from The address of the sender of the message.
/// @param _to The address of the recipient of the message.
/// @param _value The msg.value passed to the message call.
/// @param _message The content of the message.
/// @param _xDomainCalldataHash The hash of the message.
function _executeMessage(
address _from,
address _to,
uint256 _value,
bytes memory _message,
bytes32 _xDomainCalldataHash
) internal {
// @note check more `_to` address to avoid attack in the future when we add more gateways.
require(_to != messageQueue, "Forbid to call message queue");
require(_to != address(this), "Forbid to call self");
// @note This usually will never happen, just in case.
require(_from != xDomainMessageSender, "Invalid message sender");
xDomainMessageSender = _from;
// solhint-disable-next-line avoid-low-level-calls
(bool success, ) = _to.call{value: _value}(_message);
// reset value to refund gas.
xDomainMessageSender = ScrollConstants.DEFAULT_XDOMAIN_MESSAGE_SENDER;
if (success) {
isL1MessageExecuted[_xDomainCalldataHash] = true;
emit RelayedMessage(_xDomainCalldataHash);
} else {
unchecked {
uint256 _failedTimes = l1MessageFailedTimes[_xDomainCalldataHash] + 1;
require(_failedTimes <= maxFailedExecutionTimes, "Exceed maximum failure times");
l1MessageFailedTimes[_xDomainCalldataHash] = _failedTimes;
}
emit FailedRelayedMessage(_xDomainCalldataHash);
}
}
}