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
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
// SPDX-License-Identifier: MIT
pragma solidity 0.8.25;
// Testing utilities
import { Test } from "forge-std/Test.sol";
// Libraries
import { Predeploys } from "src/libraries/Predeploys.sol";
import { TransientContext } from "src/libraries/TransientContext.sol";
// Target contracts
import { CrossL2Inbox, NotEntered, InvalidTimestamp, InvalidChainId, TargetCallFailed } from "src/L2/CrossL2Inbox.sol";
import { ICrossL2Inbox } from "src/L2/ICrossL2Inbox.sol";
/// @title CrossL2InboxWithModifiableTransientStorage
/// @dev CrossL2Inbox contract with methods to modify the transient storage.
/// This is used to test the transient storage of CrossL2Inbox.
contract CrossL2InboxWithModifiableTransientStorage is CrossL2Inbox {
/// @dev Increments call depth in transient storage.
function increment() external {
TransientContext.increment();
}
/// @dev Sets origin in transient storage.
/// @param _origin Origin to set.
function setOrigin(address _origin) external {
TransientContext.set(ORIGIN_SLOT, uint160(_origin));
}
/// @dev Sets block number in transient storage.
/// @param _blockNumber Block number to set.
function setBlockNumber(uint256 _blockNumber) external {
TransientContext.set(BLOCK_NUMBER_SLOT, _blockNumber);
}
/// @dev Sets log index in transient storage.
/// @param _logIndex Log index to set.
function setLogIndex(uint256 _logIndex) external {
TransientContext.set(LOG_INDEX_SLOT, _logIndex);
}
/// @dev Sets timestamp in transient storage.
/// @param _timestamp Timestamp to set.
function setTimestamp(uint256 _timestamp) external {
TransientContext.set(TIMESTAMP_SLOT, _timestamp);
}
/// @dev Sets chain ID in transient storage.
/// @param _chainId Chain ID to set.
function setChainId(uint256 _chainId) external {
TransientContext.set(CHAINID_SLOT, _chainId);
}
}
/// @title CrossL2InboxTest
/// @dev Contract for testing the CrossL2Inbox contract.
contract CrossL2InboxTest is Test {
/// @dev Selector for the `isInDependencySet` method of the L1Block contract.
bytes4 constant L1BlockIsInDependencySetSelector = bytes4(keccak256("isInDependencySet(uint256)"));
/// @dev CrossL2Inbox contract instance.
CrossL2Inbox crossL2Inbox;
/// @dev Sets up the test suite.
function setUp() public {
// Deploy the L2ToL2CrossDomainMessenger contract
vm.etch(Predeploys.CROSS_L2_INBOX, address(new CrossL2InboxWithModifiableTransientStorage()).code);
crossL2Inbox = CrossL2Inbox(Predeploys.CROSS_L2_INBOX);
}
/// @dev Tests that the `executeMessage` function succeeds.
function testFuzz_executeMessage_succeeds(
ICrossL2Inbox.Identifier memory _id,
address _target,
bytes calldata _message,
uint256 _value
)
external
payable
{
// Ensure that the id's timestamp is valid (less than or equal to the current block timestamp)
_id.timestamp = bound(_id.timestamp, 0, block.timestamp);
// Ensure that the target call is payable if value is sent
if (_value > 0) assumePayable(_target);
// Ensure that the target call does not revert
vm.mockCall({ callee: _target, msgValue: _value, data: _message, returnData: abi.encode(true) });
// Ensure that the chain ID is in the dependency set
vm.mockCall({
callee: Predeploys.L1_BLOCK_ATTRIBUTES,
data: abi.encodeWithSelector(L1BlockIsInDependencySetSelector, _id.chainId),
returnData: abi.encode(true)
});
// Ensure that the contract has enough balance to send with value
vm.deal(address(this), _value);
// Look for the call to the target contract
vm.expectCall({ callee: _target, msgValue: _value, data: _message });
// Look for the emit ExecutingMessage event
vm.expectEmit(Predeploys.CROSS_L2_INBOX);
emit CrossL2Inbox.ExecutingMessage(keccak256(_message), _id);
// Call the executeMessage function
crossL2Inbox.executeMessage{ value: _value }({ _id: _id, _target: _target, _message: _message });
// Check that the Identifier was stored correctly, but first we have to increment. This is because
// `executeMessage` increments + decrements the transient call depth, so we need to increment to have the
// getters use the right call depth.
CrossL2InboxWithModifiableTransientStorage(Predeploys.CROSS_L2_INBOX).increment();
assertEq(crossL2Inbox.origin(), _id.origin);
assertEq(crossL2Inbox.blockNumber(), _id.blockNumber);
assertEq(crossL2Inbox.logIndex(), _id.logIndex);
assertEq(crossL2Inbox.timestamp(), _id.timestamp);
assertEq(crossL2Inbox.chainId(), _id.chainId);
}
/// @dev Mock reentrant function that calls the `executeMessage` function.
/// @param _id Identifier to pass to the `executeMessage` function.
function mockReentrant(ICrossL2Inbox.Identifier calldata _id) external payable {
crossL2Inbox.executeMessage({ _id: _id, _target: address(0), _message: "" });
}
/// @dev Tests that the `executeMessage` function successfully handles reentrant calls.
function testFuzz_executeMessage_reentrant_succeeds(
ICrossL2Inbox.Identifier memory _id1, // identifier passed to `executeMessage` by the initial call.
ICrossL2Inbox.Identifier memory _id2, // identifier passed to `executeMessage` by the reentrant call.
uint256 _value
)
external
payable
{
// Ensure that the ids' timestamp are valid (less than or equal to the current block timestamp)
_id1.timestamp = bound(_id1.timestamp, 0, block.timestamp);
_id2.timestamp = bound(_id2.timestamp, 0, block.timestamp);
// Ensure that id1's chain ID is in the dependency set
vm.mockCall({
callee: Predeploys.L1_BLOCK_ATTRIBUTES,
data: abi.encodeWithSelector(L1BlockIsInDependencySetSelector, _id1.chainId),
returnData: abi.encode(true)
});
// Ensure that id2's chain ID is in the dependency set
vm.mockCall({
callee: Predeploys.L1_BLOCK_ATTRIBUTES,
data: abi.encodeWithSelector(L1BlockIsInDependencySetSelector, _id2.chainId),
returnData: abi.encode(true)
});
// Set the target and message for the reentrant call
address target = address(this);
bytes memory message = abi.encodeWithSelector(this.mockReentrant.selector, _id2);
// Ensure that the contract has enough balance to send with value
vm.deal(address(this), _value);
// Look for the call to the target contract
vm.expectCall({ callee: target, msgValue: _value, data: message });
// Call the executeMessage function
crossL2Inbox.executeMessage{ value: _value }({ _id: _id1, _target: target, _message: message });
// Check that the reentrant function didn't update Identifier in transient storage at first call's call depth
CrossL2InboxWithModifiableTransientStorage(Predeploys.CROSS_L2_INBOX).increment();
assertEq(crossL2Inbox.origin(), _id1.origin);
assertEq(crossL2Inbox.blockNumber(), _id1.blockNumber);
assertEq(crossL2Inbox.logIndex(), _id1.logIndex);
assertEq(crossL2Inbox.timestamp(), _id1.timestamp);
assertEq(crossL2Inbox.chainId(), _id1.chainId);
// Check that the reentrant function updated the Identifier at deeper call depth
CrossL2InboxWithModifiableTransientStorage(Predeploys.CROSS_L2_INBOX).increment();
assertEq(crossL2Inbox.origin(), _id2.origin);
assertEq(crossL2Inbox.blockNumber(), _id2.blockNumber);
assertEq(crossL2Inbox.logIndex(), _id2.logIndex);
assertEq(crossL2Inbox.timestamp(), _id2.timestamp);
assertEq(crossL2Inbox.chainId(), _id2.chainId);
}
/// @dev Tests that the `executeMessage` function reverts when called with an identifier with an invalid timestamp.
function testFuzz_executeMessage_invalidTimestamp_reverts(
ICrossL2Inbox.Identifier calldata _id,
address _target,
bytes calldata _message,
uint256 _value
)
external
{
// Ensure that the id's timestamp is invalid (greater than the current block timestamp)
vm.assume(_id.timestamp > block.timestamp);
// Ensure that the contract has enough balance to send with value
vm.deal(address(this), _value);
// Expect a revert with the InvalidTimestamp selector
vm.expectRevert(InvalidTimestamp.selector);
// Call the executeMessage function
crossL2Inbox.executeMessage{ value: _value }({ _id: _id, _target: _target, _message: _message });
}
/// @dev Tests that the `executeMessage` function reverts when called with an identifier with a chain ID not in
/// dependency set.
function testFuzz_executeMessage_invalidChainId_reverts(
ICrossL2Inbox.Identifier memory _id,
address _target,
bytes calldata _message,
uint256 _value
)
external
{
// Ensure that the id's timestamp is valid (less than or equal to the current block timestamp)
_id.timestamp = bound(_id.timestamp, 0, block.timestamp);
// Ensure that the chain ID is NOT in the dependency set
vm.mockCall({
callee: Predeploys.L1_BLOCK_ATTRIBUTES,
data: abi.encodeWithSelector(L1BlockIsInDependencySetSelector, _id.chainId),
returnData: abi.encode(false)
});
// Ensure that the contract has enough balance to send with value
vm.deal(address(this), _value);
// Expect a revert with the InvalidChainId selector
vm.expectRevert(InvalidChainId.selector);
// Call the executeMessage function
crossL2Inbox.executeMessage{ value: _value }({ _id: _id, _target: _target, _message: _message });
}
/// @dev Tests that the `executeMessage` function reverts when the target call fails.
function testFuzz_executeMessage_targetCallFailed_reverts(
ICrossL2Inbox.Identifier memory _id,
address _target,
bytes calldata _message,
uint256 _value
)
external
{
// Ensure that the id's timestamp is valid (less than or equal to the current block timestamp)
_id.timestamp = bound(_id.timestamp, 0, block.timestamp);
// Ensure that the target call is payable if value is sent
if (_value > 0) assumePayable(_target);
// Ensure that the target call reverts
vm.mockCallRevert({ callee: _target, msgValue: _value, data: _message, revertData: abi.encode(false) });
// Ensure that the chain ID is in the dependency set
vm.mockCall({
callee: Predeploys.L1_BLOCK_ATTRIBUTES,
data: abi.encodeWithSelector(L1BlockIsInDependencySetSelector, _id.chainId),
returnData: abi.encode(true)
});
// Ensure that the contract has enough balance to send with value
vm.deal(address(this), _value);
// Look for the call to the target contract
vm.expectCall({ callee: _target, msgValue: _value, data: _message });
// Expect a revert with the TargetCallFailed selector
vm.expectRevert(TargetCallFailed.selector);
// Call the executeMessage function
crossL2Inbox.executeMessage{ value: _value }({ _id: _id, _target: _target, _message: _message });
}
function testFuzz_validateMessage_succeeds(ICrossL2Inbox.Identifier memory _id, bytes32 _messageHash) external {
// Ensure that the id's timestamp is valid (less than or equal to the current block timestamp)
_id.timestamp = bound(_id.timestamp, 1, block.timestamp);
// Ensure that the chain ID is in the dependency set
vm.mockCall({
callee: Predeploys.L1_BLOCK_ATTRIBUTES,
data: abi.encodeWithSelector(L1BlockIsInDependencySetSelector, _id.chainId),
returnData: abi.encode(true)
});
// Look for the emit ExecutingMessage event
vm.expectEmit(Predeploys.CROSS_L2_INBOX);
emit CrossL2Inbox.ExecutingMessage(_messageHash, _id);
// Call the validateMessage function
crossL2Inbox.validateMessage(_id, _messageHash);
}
/// @dev Tests that the `validateMessage` function reverts when called with an identifier with an invalid timestamp.
function testFuzz_validateMessage_invalidTimestamp_reverts(
ICrossL2Inbox.Identifier calldata _id,
bytes32 _messageHash
)
external
{
// Ensure that the id's timestamp is invalid (greater thsan the current block timestamp)
vm.assume(_id.timestamp > block.timestamp);
// Expect a revert with the InvalidTimestamp selector
vm.expectRevert(InvalidTimestamp.selector);
// Call the validateMessage function
crossL2Inbox.validateMessage(_id, _messageHash);
}
/// @dev Tests that the `validateMessage` function reverts when called with an identifier with a chain ID not in the
/// dependency set.
function testFuzz_validateMessage_invalidChainId_reverts(
ICrossL2Inbox.Identifier memory _id,
bytes32 _messageHash
)
external
{
// Ensure that the timestamp is valid (less than or equal to the current block timestamp)
_id.timestamp = bound(_id.timestamp, 0, block.timestamp);
// Ensure that the chain ID is NOT in the dependency set.
vm.mockCall({
callee: Predeploys.L1_BLOCK_ATTRIBUTES,
data: abi.encodeWithSelector(L1BlockIsInDependencySetSelector, _id.chainId),
returnData: abi.encode(false)
});
// Expect a revert with the InvalidChainId selector
vm.expectRevert(InvalidChainId.selector);
// Call the validateMessage function
crossL2Inbox.validateMessage(_id, _messageHash);
}
/// @dev Tests that the `origin` function returns the correct value.
function testFuzz_origin_succeeds(address _origin) external {
// Increment the call depth to prevent NotEntered revert
CrossL2InboxWithModifiableTransientStorage(Predeploys.CROSS_L2_INBOX).increment();
// Set origin in the transient storage
CrossL2InboxWithModifiableTransientStorage(Predeploys.CROSS_L2_INBOX).setOrigin(_origin);
// Check that the `origin` function returns the correct value
assertEq(crossL2Inbox.origin(), _origin);
}
/// @dev Tests that the `origin` function reverts when not entered.
function test_origin_notEntered_reverts() external {
// Expect a revert with the NotEntered selector
vm.expectRevert(NotEntered.selector);
// Call the `origin` function
crossL2Inbox.origin();
}
/// @dev Tests that the `blockNumber` function returns the correct value.
function testFuzz_blockNumber_succeeds(uint256 _blockNumber) external {
// Increment the call depth to prevent NotEntered revert
CrossL2InboxWithModifiableTransientStorage(Predeploys.CROSS_L2_INBOX).increment();
// Set blockNumber in the transient storage
CrossL2InboxWithModifiableTransientStorage(Predeploys.CROSS_L2_INBOX).setBlockNumber(_blockNumber);
// Check that the `blockNumber` function returns the correct value
assertEq(crossL2Inbox.blockNumber(), _blockNumber);
}
/// @dev Tests that the `blockNumber` function reverts when not entered.
function test_blockNumber_notEntered_reverts() external {
// Expect a revert with the NotEntered selector
vm.expectRevert(NotEntered.selector);
// Call the `blockNumber` function
crossL2Inbox.blockNumber();
}
/// @dev Tests that the `logIndex` function returns the correct value.
function testFuzz_logIndex_succeeds(uint256 _logIndex) external {
// Increment the call depth to prevent NotEntered revert
CrossL2InboxWithModifiableTransientStorage(Predeploys.CROSS_L2_INBOX).increment();
// Set logIndex in the transient storage
CrossL2InboxWithModifiableTransientStorage(Predeploys.CROSS_L2_INBOX).setLogIndex(_logIndex);
// Check that the `logIndex` function returns the correct value
assertEq(crossL2Inbox.logIndex(), _logIndex);
}
/// @dev Tests that the `logIndex` function reverts when not entered.
function test_logIndex_notEntered_reverts() external {
// Expect a revert with the NotEntered selector
vm.expectRevert(NotEntered.selector);
// Call the `logIndex` function
crossL2Inbox.logIndex();
}
/// @dev Tests that the `timestamp` function returns the correct value.
function testFuzz_timestamp_succeeds(uint256 _timestamp) external {
// Increment the call depth to prevent NotEntered revert
CrossL2InboxWithModifiableTransientStorage(Predeploys.CROSS_L2_INBOX).increment();
// Set timestamp in the transient storage
CrossL2InboxWithModifiableTransientStorage(Predeploys.CROSS_L2_INBOX).setTimestamp(_timestamp);
// Check that the `timestamp` function returns the correct value
assertEq(crossL2Inbox.timestamp(), _timestamp);
}
/// @dev Tests that the `timestamp` function reverts when not entered.
function test_timestamp_notEntered_reverts() external {
// Expect a revert with the NotEntered selector
vm.expectRevert(NotEntered.selector);
// Call the `timestamp` function
crossL2Inbox.timestamp();
}
/// @dev Tests that the `chainId` function returns the correct value.
function testFuzz_chainId_succeeds(uint256 _chainId) external {
// Increment the call depth to prevent NotEntered revert
CrossL2InboxWithModifiableTransientStorage(Predeploys.CROSS_L2_INBOX).increment();
// Set chainId in the transient storage
CrossL2InboxWithModifiableTransientStorage(Predeploys.CROSS_L2_INBOX).setChainId(_chainId);
// Check that the `chainId` function returns the correct value
assertEq(crossL2Inbox.chainId(), _chainId);
}
/// @dev Tests that the `chainId` function reverts when not entered.
function test_chainId_notEntered_reverts() external {
// Expect a revert with the NotEntered selector
vm.expectRevert(NotEntered.selector);
// Call the `chainId` function
crossL2Inbox.chainId();
}
}