@arbitrum/nitro-contracts 1.0.0-beta.4 → 1.0.0-beta.7
Sign up to get free protection for your applications and to get access to all the features.
- package/package.json +2 -1
- package/src/bridge/Bridge.sol +127 -32
- package/src/bridge/IBridge.sol +40 -6
- package/src/bridge/{IMessageProvider.sol → IDelayedMessageProvider.sol} +4 -1
- package/src/bridge/IInbox.sol +23 -2
- package/src/bridge/IOwnable.sol +9 -0
- package/src/bridge/ISequencerInbox.sol +36 -9
- package/src/bridge/Inbox.sol +131 -34
- package/src/bridge/Outbox.sol +134 -31
- package/src/bridge/SequencerInbox.sol +159 -60
- package/src/challenge/ChallengeLib.sol +0 -2
- package/src/challenge/ChallengeManager.sol +4 -8
- package/src/challenge/IChallengeManager.sol +1 -1
- package/src/libraries/Error.sol +6 -0
- package/src/libraries/IGasRefunder.sol +13 -13
- package/src/libraries/MerkleLib.sol +11 -2
- package/src/libraries/MessageTypes.sol +1 -0
- package/src/mocks/BridgeStub.sol +67 -21
- package/src/mocks/SequencerInboxStub.sol +10 -8
- package/src/mocks/Simple.sol +8 -0
- package/src/node-interface/NodeInterface.sol +32 -5
- package/src/osp/IOneStepProver.sol +1 -2
- package/src/osp/OneStepProver0.sol +1 -87
- package/src/osp/OneStepProverHostIo.sol +5 -6
- package/src/osp/OneStepProverMath.sol +37 -27
- package/src/osp/OneStepProverMemory.sol +3 -4
- package/src/precompiles/ArbAggregator.sol +23 -33
- package/src/precompiles/ArbBLS.sol +1 -43
- package/src/precompiles/ArbGasInfo.sol +9 -18
- package/src/precompiles/ArbOwner.sol +18 -15
- package/src/precompiles/ArbRetryableTx.sol +13 -1
- package/src/precompiles/ArbSys.sol +15 -2
- package/src/precompiles/ArbosActs.sol +9 -2
- package/src/rollup/BridgeCreator.sol +23 -28
- package/src/rollup/IRollupCore.sol +3 -3
- package/src/rollup/{IRollupEventBridge.sol → IRollupEventInbox.sol} +2 -2
- package/src/rollup/IRollupLogic.sol +21 -18
- package/src/rollup/RollupAdminLogic.sol +30 -34
- package/src/rollup/RollupCore.sol +15 -7
- package/src/rollup/RollupCreator.sol +21 -11
- package/src/rollup/{RollupEventBridge.sol → RollupEventInbox.sol} +10 -10
- package/src/rollup/RollupLib.sol +20 -5
- package/src/rollup/RollupUserLogic.sol +10 -18
- package/src/rollup/ValidatorWallet.sol +125 -8
- package/src/rollup/ValidatorWalletCreator.sol +11 -6
- package/src/state/Deserialize.sol +3 -22
- package/src/state/Instructions.sol +2 -10
- package/src/state/Machine.sol +0 -4
- package/src/state/ModuleMemory.sol +2 -1
- package/src/state/Value.sol +2 -3
- package/src/test-helpers/BridgeTester.sol +223 -0
- package/src/test-helpers/OutboxWithoutOptTester.sol +188 -0
- package/src/test-helpers/RollupMock.sol +21 -0
- package/src/state/PcStack.sol +0 -32
package/src/bridge/Inbox.sol
CHANGED
@@ -5,6 +5,7 @@
|
|
5
5
|
pragma solidity ^0.8.4;
|
6
6
|
|
7
7
|
import "./IInbox.sol";
|
8
|
+
import "./ISequencerInbox.sol";
|
8
9
|
import "./IBridge.sol";
|
9
10
|
|
10
11
|
import "./Messages.sol";
|
@@ -20,7 +21,6 @@ import {
|
|
20
21
|
} from "../libraries/MessageTypes.sol";
|
21
22
|
import {MAX_DATA_SIZE} from "../libraries/Constants.sol";
|
22
23
|
|
23
|
-
import "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol";
|
24
24
|
import "@openzeppelin/contracts-upgradeable/utils/AddressUpgradeable.sol";
|
25
25
|
import "@openzeppelin/contracts-upgradeable/security/PausableUpgradeable.sol";
|
26
26
|
|
@@ -31,27 +31,73 @@ import "@openzeppelin/contracts-upgradeable/security/PausableUpgradeable.sol";
|
|
31
31
|
*/
|
32
32
|
contract Inbox is DelegateCallAware, PausableUpgradeable, IInbox {
|
33
33
|
IBridge public override bridge;
|
34
|
+
ISequencerInbox public sequencerInbox;
|
34
35
|
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
36
|
+
/// ------------------------------------ allow list start ------------------------------------ ///
|
37
|
+
|
38
|
+
bool public allowListEnabled;
|
39
|
+
mapping(address => bool) public isAllowed;
|
40
|
+
|
41
|
+
event AllowListAddressSet(address indexed user, bool val);
|
42
|
+
event AllowListEnabledUpdated(bool isEnabled);
|
43
|
+
|
44
|
+
function setAllowList(address[] memory user, bool[] memory val) external onlyRollupOrOwner {
|
45
|
+
require(user.length == val.length, "INVALID_INPUT");
|
46
|
+
|
47
|
+
for (uint256 i = 0; i < user.length; i++) {
|
48
|
+
isAllowed[user[i]] = val[i];
|
49
|
+
emit AllowListAddressSet(user[i], val[i]);
|
50
|
+
}
|
51
|
+
}
|
52
|
+
|
53
|
+
function setAllowListEnabled(bool _allowListEnabled) external onlyRollupOrOwner {
|
54
|
+
require(_allowListEnabled != allowListEnabled, "ALREADY_SET");
|
55
|
+
allowListEnabled = _allowListEnabled;
|
56
|
+
emit AllowListEnabledUpdated(_allowListEnabled);
|
57
|
+
}
|
58
|
+
|
59
|
+
/// @dev this modifier checks the tx.origin instead of msg.sender for convenience (ie it allows
|
60
|
+
/// allowed users to interact with the token bridge without needing the token bridge to be allowList aware).
|
61
|
+
/// this modifier is not intended to use to be used for security (since this opens the allowList to
|
62
|
+
/// a smart contract phishing risk).
|
63
|
+
modifier onlyAllowed() {
|
64
|
+
// solhint-disable-next-line avoid-tx-origin
|
65
|
+
if (allowListEnabled && !isAllowed[tx.origin]) revert NotAllowedOrigin(tx.origin);
|
66
|
+
_;
|
67
|
+
}
|
68
|
+
|
69
|
+
/// ------------------------------------ allow list end ------------------------------------ ///
|
70
|
+
|
71
|
+
modifier onlyRollupOrOwner() {
|
72
|
+
IOwnable rollup = bridge.rollup();
|
73
|
+
if (msg.sender != address(rollup)) {
|
74
|
+
address rollupOwner = rollup.owner();
|
75
|
+
if (msg.sender != rollupOwner) {
|
76
|
+
revert NotRollupOrOwner(msg.sender, address(rollup), rollupOwner);
|
77
|
+
}
|
78
|
+
}
|
39
79
|
_;
|
40
80
|
}
|
41
81
|
|
42
82
|
/// @notice pauses all inbox functionality
|
43
|
-
function pause() external
|
83
|
+
function pause() external onlyRollupOrOwner {
|
44
84
|
_pause();
|
45
85
|
}
|
46
86
|
|
47
87
|
/// @notice unpauses all inbox functionality
|
48
|
-
function unpause() external
|
88
|
+
function unpause() external onlyRollupOrOwner {
|
49
89
|
_unpause();
|
50
90
|
}
|
51
91
|
|
52
|
-
function initialize(IBridge _bridge
|
92
|
+
function initialize(IBridge _bridge, ISequencerInbox _sequencerInbox)
|
93
|
+
external
|
94
|
+
initializer
|
95
|
+
onlyDelegated
|
96
|
+
{
|
53
97
|
if (address(bridge) != address(0)) revert AlreadyInit();
|
54
98
|
bridge = _bridge;
|
99
|
+
sequencerInbox = _sequencerInbox;
|
100
|
+
allowListEnabled = false;
|
55
101
|
__Pausable_init();
|
56
102
|
}
|
57
103
|
|
@@ -64,6 +110,7 @@ contract Inbox is DelegateCallAware, PausableUpgradeable, IInbox {
|
|
64
110
|
sstore(i, 0)
|
65
111
|
}
|
66
112
|
}
|
113
|
+
allowListEnabled = false;
|
67
114
|
bridge = _bridge;
|
68
115
|
}
|
69
116
|
|
@@ -75,6 +122,7 @@ contract Inbox is DelegateCallAware, PausableUpgradeable, IInbox {
|
|
75
122
|
function sendL2MessageFromOrigin(bytes calldata messageData)
|
76
123
|
external
|
77
124
|
whenNotPaused
|
125
|
+
onlyAllowed
|
78
126
|
returns (uint256)
|
79
127
|
{
|
80
128
|
// solhint-disable-next-line avoid-tx-origin
|
@@ -95,6 +143,7 @@ contract Inbox is DelegateCallAware, PausableUpgradeable, IInbox {
|
|
95
143
|
external
|
96
144
|
override
|
97
145
|
whenNotPaused
|
146
|
+
onlyAllowed
|
98
147
|
returns (uint256)
|
99
148
|
{
|
100
149
|
return _deliverMessage(L2_MSG, msg.sender, messageData);
|
@@ -106,7 +155,7 @@ contract Inbox is DelegateCallAware, PausableUpgradeable, IInbox {
|
|
106
155
|
uint256 nonce,
|
107
156
|
address to,
|
108
157
|
bytes calldata data
|
109
|
-
) external payable virtual override whenNotPaused returns (uint256) {
|
158
|
+
) external payable virtual override whenNotPaused onlyAllowed returns (uint256) {
|
110
159
|
return
|
111
160
|
_deliverMessage(
|
112
161
|
L1MessageType_L2FundedByL1,
|
@@ -128,7 +177,7 @@ contract Inbox is DelegateCallAware, PausableUpgradeable, IInbox {
|
|
128
177
|
uint256 maxFeePerGas,
|
129
178
|
address to,
|
130
179
|
bytes calldata data
|
131
|
-
) external payable virtual override whenNotPaused returns (uint256) {
|
180
|
+
) external payable virtual override whenNotPaused onlyAllowed returns (uint256) {
|
132
181
|
return
|
133
182
|
_deliverMessage(
|
134
183
|
L1MessageType_L2FundedByL1,
|
@@ -151,7 +200,7 @@ contract Inbox is DelegateCallAware, PausableUpgradeable, IInbox {
|
|
151
200
|
address to,
|
152
201
|
uint256 value,
|
153
202
|
bytes calldata data
|
154
|
-
) external virtual override whenNotPaused returns (uint256) {
|
203
|
+
) external virtual override whenNotPaused onlyAllowed returns (uint256) {
|
155
204
|
return
|
156
205
|
_deliverMessage(
|
157
206
|
L2_MSG,
|
@@ -174,7 +223,7 @@ contract Inbox is DelegateCallAware, PausableUpgradeable, IInbox {
|
|
174
223
|
address to,
|
175
224
|
uint256 value,
|
176
225
|
bytes calldata data
|
177
|
-
) external virtual override whenNotPaused returns (uint256) {
|
226
|
+
) external virtual override whenNotPaused onlyAllowed returns (uint256) {
|
178
227
|
return
|
179
228
|
_deliverMessage(
|
180
229
|
L2_MSG,
|
@@ -209,43 +258,51 @@ contract Inbox is DelegateCallAware, PausableUpgradeable, IInbox {
|
|
209
258
|
/// @dev this does not trigger the fallback function when receiving in the L2 side.
|
210
259
|
/// Look into retryable tickets if you are interested in this functionality.
|
211
260
|
/// @dev this function should not be called inside contract constructors
|
212
|
-
function depositEth() public payable override whenNotPaused returns (uint256) {
|
213
|
-
address
|
261
|
+
function depositEth() public payable override whenNotPaused onlyAllowed returns (uint256) {
|
262
|
+
address dest = msg.sender;
|
214
263
|
|
215
264
|
// solhint-disable-next-line avoid-tx-origin
|
216
|
-
if (
|
265
|
+
if (AddressUpgradeable.isContract(msg.sender) || tx.origin != msg.sender) {
|
217
266
|
// isContract check fails if this function is called during a contract's constructor.
|
218
267
|
// We don't adjust the address for calls coming from L1 contracts since their addresses get remapped
|
219
268
|
// If the caller is an EOA, we adjust the address.
|
220
269
|
// This is needed because unsigned messages to the L2 (such as retryables)
|
221
270
|
// have the L1 sender address mapped.
|
222
|
-
|
223
|
-
sender = AddressAliasHelper.undoL1ToL2Alias(sender);
|
271
|
+
dest = AddressAliasHelper.applyL1ToL2Alias(msg.sender);
|
224
272
|
}
|
225
273
|
|
226
274
|
return
|
227
275
|
_deliverMessage(
|
228
276
|
L1MessageType_ethDeposit,
|
229
|
-
sender,
|
230
|
-
abi.encodePacked(msg.value)
|
277
|
+
msg.sender,
|
278
|
+
abi.encodePacked(dest, msg.value)
|
231
279
|
);
|
232
280
|
}
|
233
281
|
|
234
282
|
/// @notice deprecated in favour of depositEth with no parameters
|
235
|
-
function depositEth(uint256)
|
283
|
+
function depositEth(uint256)
|
284
|
+
external
|
285
|
+
payable
|
286
|
+
virtual
|
287
|
+
override
|
288
|
+
whenNotPaused
|
289
|
+
onlyAllowed
|
290
|
+
returns (uint256)
|
291
|
+
{
|
236
292
|
return depositEth();
|
237
293
|
}
|
238
294
|
|
239
295
|
/**
|
240
296
|
* @notice deprecated in favour of unsafeCreateRetryableTicket
|
241
297
|
* @dev deprecated in favour of unsafeCreateRetryableTicket
|
298
|
+
* @dev Gas limit and maxFeePerGas should not be set to 1 as that is used to trigger the RetryableData error
|
242
299
|
* @param to destination L2 contract address
|
243
300
|
* @param l2CallValue call value for retryable L2 message
|
244
301
|
* @param maxSubmissionCost Max gas deducted from user's L2 balance to cover base submission fee
|
245
302
|
* @param excessFeeRefundAddress gasLimit x maxFeePerGas - execution cost gets credited here on L2 balance
|
246
303
|
* @param callValueRefundAddress l2Callvalue gets credited here on L2 if retryable txn times out or gets cancelled
|
247
|
-
* @param gasLimit Max gas deducted from user's L2 balance to cover L2 execution
|
248
|
-
* @param maxFeePerGas price bid for L2 execution
|
304
|
+
* @param gasLimit Max gas deducted from user's L2 balance to cover L2 execution. Should not be set to 1 (magic value used to trigger the RetryableData error)
|
305
|
+
* @param maxFeePerGas price bid for L2 execution. Should not be set to 1 (magic value used to trigger the RetryableData error)
|
249
306
|
* @param data ABI encoded data of L2 message
|
250
307
|
* @return unique id for retryable transaction (keccak256(requestID, uint(0) )
|
251
308
|
*/
|
@@ -258,7 +315,7 @@ contract Inbox is DelegateCallAware, PausableUpgradeable, IInbox {
|
|
258
315
|
uint256 gasLimit,
|
259
316
|
uint256 maxFeePerGas,
|
260
317
|
bytes calldata data
|
261
|
-
) external payable virtual whenNotPaused returns (uint256) {
|
318
|
+
) external payable virtual whenNotPaused onlyAllowed returns (uint256) {
|
262
319
|
return
|
263
320
|
unsafeCreateRetryableTicket(
|
264
321
|
to,
|
@@ -275,13 +332,14 @@ contract Inbox is DelegateCallAware, PausableUpgradeable, IInbox {
|
|
275
332
|
/**
|
276
333
|
* @notice Put a message in the L2 inbox that can be reexecuted for some fixed amount of time if it reverts
|
277
334
|
* @dev all msg.value will deposited to callValueRefundAddress on L2
|
335
|
+
* @dev Gas limit and maxFeePerGas should not be set to 1 as that is used to trigger the RetryableData error
|
278
336
|
* @param to destination L2 contract address
|
279
337
|
* @param l2CallValue call value for retryable L2 message
|
280
338
|
* @param maxSubmissionCost Max gas deducted from user's L2 balance to cover base submission fee
|
281
339
|
* @param excessFeeRefundAddress gasLimit x maxFeePerGas - execution cost gets credited here on L2 balance
|
282
340
|
* @param callValueRefundAddress l2Callvalue gets credited here on L2 if retryable txn times out or gets cancelled
|
283
|
-
* @param gasLimit Max gas deducted from user's L2 balance to cover L2 execution
|
284
|
-
* @param maxFeePerGas price bid for L2 execution
|
341
|
+
* @param gasLimit Max gas deducted from user's L2 balance to cover L2 execution. Should not be set to 1 (magic value used to trigger the RetryableData error)
|
342
|
+
* @param maxFeePerGas price bid for L2 execution. Should not be set to 1 (magic value used to trigger the RetryableData error)
|
285
343
|
* @param data ABI encoded data of L2 message
|
286
344
|
* @return unique id for retryable transaction (keccak256(requestID, uint(0) )
|
287
345
|
*/
|
@@ -294,10 +352,14 @@ contract Inbox is DelegateCallAware, PausableUpgradeable, IInbox {
|
|
294
352
|
uint256 gasLimit,
|
295
353
|
uint256 maxFeePerGas,
|
296
354
|
bytes calldata data
|
297
|
-
) external payable virtual override whenNotPaused returns (uint256) {
|
355
|
+
) external payable virtual override whenNotPaused onlyAllowed returns (uint256) {
|
298
356
|
// ensure the user's deposit alone will make submission succeed
|
299
|
-
if (msg.value < maxSubmissionCost + l2CallValue)
|
300
|
-
revert InsufficientValue(
|
357
|
+
if (msg.value < (maxSubmissionCost + l2CallValue + gasLimit * maxFeePerGas)) {
|
358
|
+
revert InsufficientValue(
|
359
|
+
maxSubmissionCost + l2CallValue + gasLimit * maxFeePerGas,
|
360
|
+
msg.value
|
361
|
+
);
|
362
|
+
}
|
301
363
|
|
302
364
|
// if a refund address is a contract, we apply the alias to it
|
303
365
|
// so that it can access its funds on the L2
|
@@ -311,7 +373,7 @@ contract Inbox is DelegateCallAware, PausableUpgradeable, IInbox {
|
|
311
373
|
}
|
312
374
|
|
313
375
|
return
|
314
|
-
|
376
|
+
unsafeCreateRetryableTicketInternal(
|
315
377
|
to,
|
316
378
|
l2CallValue,
|
317
379
|
maxSubmissionCost,
|
@@ -329,17 +391,18 @@ contract Inbox is DelegateCallAware, PausableUpgradeable, IInbox {
|
|
329
391
|
* come from the deposit alone, rather than falling back on the user's L2 balance
|
330
392
|
* @dev Advanced usage only (does not rewrite aliases for excessFeeRefundAddress and callValueRefundAddress).
|
331
393
|
* createRetryableTicket method is the recommended standard.
|
394
|
+
* @dev Gas limit and maxFeePerGas should not be set to 1 as that is used to trigger the RetryableData error
|
332
395
|
* @param to destination L2 contract address
|
333
396
|
* @param l2CallValue call value for retryable L2 message
|
334
397
|
* @param maxSubmissionCost Max gas deducted from user's L2 balance to cover base submission fee
|
335
398
|
* @param excessFeeRefundAddress gasLimit x maxFeePerGas - execution cost gets credited here on L2 balance
|
336
399
|
* @param callValueRefundAddress l2Callvalue gets credited here on L2 if retryable txn times out or gets cancelled
|
337
|
-
* @param gasLimit Max gas deducted from user's L2 balance to cover L2 execution
|
338
|
-
* @param maxFeePerGas price bid for L2 execution
|
400
|
+
* @param gasLimit Max gas deducted from user's L2 balance to cover L2 execution. Should not be set to 1 (magic value used to trigger the RetryableData error)
|
401
|
+
* @param maxFeePerGas price bid for L2 execution. Should not be set to 1 (magic value used to trigger the RetryableData error)
|
339
402
|
* @param data ABI encoded data of L2 message
|
340
403
|
* @return unique id for retryable transaction (keccak256(requestID, uint(0) )
|
341
404
|
*/
|
342
|
-
function
|
405
|
+
function unsafeCreateRetryableTicketInternal(
|
343
406
|
address to,
|
344
407
|
uint256 l2CallValue,
|
345
408
|
uint256 maxSubmissionCost,
|
@@ -348,7 +411,23 @@ contract Inbox is DelegateCallAware, PausableUpgradeable, IInbox {
|
|
348
411
|
uint256 gasLimit,
|
349
412
|
uint256 maxFeePerGas,
|
350
413
|
bytes calldata data
|
351
|
-
)
|
414
|
+
) internal virtual whenNotPaused onlyAllowed returns (uint256) {
|
415
|
+
// gas price and limit of 1 should never be a valid input, so instead they are used as
|
416
|
+
// magic values to trigger a revert in eth calls that surface data without requiring a tx trace
|
417
|
+
if (gasLimit == 1 || maxFeePerGas == 1)
|
418
|
+
revert RetryableData(
|
419
|
+
msg.sender,
|
420
|
+
to,
|
421
|
+
l2CallValue,
|
422
|
+
msg.value,
|
423
|
+
maxSubmissionCost,
|
424
|
+
excessFeeRefundAddress,
|
425
|
+
callValueRefundAddress,
|
426
|
+
gasLimit,
|
427
|
+
maxFeePerGas,
|
428
|
+
data
|
429
|
+
);
|
430
|
+
|
352
431
|
uint256 submissionFee = calculateRetryableSubmissionFee(data.length, block.basefee);
|
353
432
|
if (maxSubmissionCost < submissionFee)
|
354
433
|
revert InsufficientSubmissionCost(submissionFee, maxSubmissionCost);
|
@@ -372,6 +451,19 @@ contract Inbox is DelegateCallAware, PausableUpgradeable, IInbox {
|
|
372
451
|
);
|
373
452
|
}
|
374
453
|
|
454
|
+
function unsafeCreateRetryableTicket(
|
455
|
+
address,
|
456
|
+
uint256,
|
457
|
+
uint256,
|
458
|
+
address,
|
459
|
+
address,
|
460
|
+
uint256,
|
461
|
+
uint256,
|
462
|
+
bytes calldata
|
463
|
+
) public payable override returns (uint256) {
|
464
|
+
revert("UNSAFE_RETRYABLES_TEMPORARILY_DISABLED");
|
465
|
+
}
|
466
|
+
|
375
467
|
function _deliverMessage(
|
376
468
|
uint8 _kind,
|
377
469
|
address _sender,
|
@@ -389,6 +481,11 @@ contract Inbox is DelegateCallAware, PausableUpgradeable, IInbox {
|
|
389
481
|
address sender,
|
390
482
|
bytes32 messageDataHash
|
391
483
|
) internal returns (uint256) {
|
392
|
-
return
|
484
|
+
return
|
485
|
+
bridge.enqueueDelayedMessage{value: msg.value}(
|
486
|
+
kind,
|
487
|
+
AddressAliasHelper.applyL1ToL2Alias(sender),
|
488
|
+
messageDataHash
|
489
|
+
);
|
393
490
|
}
|
394
491
|
}
|
package/src/bridge/Outbox.sol
CHANGED
@@ -9,11 +9,14 @@ import "./IOutbox.sol";
|
|
9
9
|
import "../libraries/MerkleLib.sol";
|
10
10
|
import "../libraries/DelegateCallAware.sol";
|
11
11
|
|
12
|
+
/// @dev this error is thrown since certain functions are only expected to be used in simulations, not in actual txs
|
13
|
+
error SimulationOnlyEntrypoint();
|
14
|
+
|
12
15
|
contract Outbox is DelegateCallAware, IOutbox {
|
13
16
|
address public rollup; // the rollup contract
|
14
17
|
IBridge public bridge; // the bridge contract
|
15
18
|
|
16
|
-
mapping(uint256 =>
|
19
|
+
mapping(uint256 => bytes32) public spent; // packed spent bitmap
|
17
20
|
mapping(bytes32 => bytes32) public roots; // maps root hashes => L2 block hash
|
18
21
|
|
19
22
|
struct L2ToL1Context {
|
@@ -27,12 +30,31 @@ contract Outbox is DelegateCallAware, IOutbox {
|
|
27
30
|
// Therefore their values don't need to be maintained, and their slots will
|
28
31
|
// be empty outside of transactions
|
29
32
|
L2ToL1Context internal context;
|
33
|
+
|
34
|
+
// default context values to be used in storage instead of zero, to save on storage refunds
|
35
|
+
// it is assumed that arb-os never assigns these values to a valid leaf to be redeemed
|
36
|
+
uint128 private constant L2BLOCK_DEFAULT_CONTEXT = type(uint128).max;
|
37
|
+
uint128 private constant L1BLOCK_DEFAULT_CONTEXT = type(uint128).max;
|
38
|
+
uint128 private constant TIMESTAMP_DEFAULT_CONTEXT = type(uint128).max;
|
39
|
+
bytes32 private constant OUTPUTID_DEFAULT_CONTEXT = bytes32(type(uint256).max);
|
40
|
+
address private constant SENDER_DEFAULT_CONTEXT = address(type(uint160).max);
|
41
|
+
|
30
42
|
uint128 public constant OUTBOX_VERSION = 2;
|
31
43
|
|
32
|
-
function initialize(
|
33
|
-
if (
|
34
|
-
|
44
|
+
function initialize(IBridge _bridge) external onlyDelegated {
|
45
|
+
if (address(bridge) != address(0)) revert AlreadyInit();
|
46
|
+
// address zero is returned if no context is set, but the values used in storage
|
47
|
+
// are non-zero to save users some gas (as storage refunds are usually maxed out)
|
48
|
+
// EIP-1153 would help here
|
49
|
+
context = L2ToL1Context({
|
50
|
+
l2Block: L2BLOCK_DEFAULT_CONTEXT,
|
51
|
+
l1Block: L1BLOCK_DEFAULT_CONTEXT,
|
52
|
+
timestamp: TIMESTAMP_DEFAULT_CONTEXT,
|
53
|
+
outputId: OUTPUTID_DEFAULT_CONTEXT,
|
54
|
+
sender: SENDER_DEFAULT_CONTEXT
|
55
|
+
});
|
35
56
|
bridge = _bridge;
|
57
|
+
rollup = address(_bridge.rollup());
|
36
58
|
}
|
37
59
|
|
38
60
|
function updateSendRoot(bytes32 root, bytes32 l2BlockHash) external override {
|
@@ -45,28 +67,51 @@ contract Outbox is DelegateCallAware, IOutbox {
|
|
45
67
|
/// When the return value is zero, that means this is a system message
|
46
68
|
/// @dev the l2ToL1Sender behaves as the tx.origin, the msg.sender should be validated to protect against reentrancies
|
47
69
|
function l2ToL1Sender() external view override returns (address) {
|
48
|
-
|
70
|
+
address sender = context.sender;
|
71
|
+
// we don't return the default context value to avoid a breaking change in the API
|
72
|
+
if (sender == SENDER_DEFAULT_CONTEXT) return address(0);
|
73
|
+
return sender;
|
49
74
|
}
|
50
75
|
|
76
|
+
/// @return l2Block return L2 block when the L2 tx was initiated or zero
|
77
|
+
/// if no L2 to L1 transaction is active
|
51
78
|
function l2ToL1Block() external view override returns (uint256) {
|
52
|
-
|
79
|
+
uint128 l2Block = context.l2Block;
|
80
|
+
// we don't return the default context value to avoid a breaking change in the API
|
81
|
+
if (l2Block == L1BLOCK_DEFAULT_CONTEXT) return uint256(0);
|
82
|
+
return uint256(l2Block);
|
53
83
|
}
|
54
84
|
|
85
|
+
/// @return l1Block return L1 block when the L2 tx was initiated or zero
|
86
|
+
/// if no L2 to L1 transaction is active
|
55
87
|
function l2ToL1EthBlock() external view override returns (uint256) {
|
56
|
-
|
88
|
+
uint128 l1Block = context.l1Block;
|
89
|
+
// we don't return the default context value to avoid a breaking change in the API
|
90
|
+
if (l1Block == L1BLOCK_DEFAULT_CONTEXT) return uint256(0);
|
91
|
+
return uint256(l1Block);
|
57
92
|
}
|
58
93
|
|
94
|
+
/// @return timestamp return L2 timestamp when the L2 tx was initiated or zero
|
95
|
+
/// if no L2 to L1 transaction is active
|
59
96
|
function l2ToL1Timestamp() external view override returns (uint256) {
|
60
|
-
|
97
|
+
uint128 timestamp = context.timestamp;
|
98
|
+
// we don't return the default context value to avoid a breaking change in the API
|
99
|
+
if (timestamp == TIMESTAMP_DEFAULT_CONTEXT) return uint256(0);
|
100
|
+
return uint256(timestamp);
|
61
101
|
}
|
62
102
|
|
63
|
-
|
103
|
+
/// @notice batch number is deprecated and now always returns 0
|
64
104
|
function l2ToL1BatchNum() external pure override returns (uint256) {
|
65
105
|
return 0;
|
66
106
|
}
|
67
107
|
|
108
|
+
/// @return outputId returns the unique output identifier of the L2 to L1 tx or
|
109
|
+
/// zero if no L2 to L1 transaction is active
|
68
110
|
function l2ToL1OutputId() external view override returns (bytes32) {
|
69
|
-
|
111
|
+
bytes32 outputId = context.outputId;
|
112
|
+
// we don't return the default context value to avoid a breaking change in the API
|
113
|
+
if (outputId == OUTPUTID_DEFAULT_CONTEXT) return bytes32(0);
|
114
|
+
return outputId;
|
70
115
|
}
|
71
116
|
|
72
117
|
/**
|
@@ -93,22 +138,56 @@ contract Outbox is DelegateCallAware, IOutbox {
|
|
93
138
|
uint256 l2Timestamp,
|
94
139
|
uint256 value,
|
95
140
|
bytes calldata data
|
96
|
-
) external
|
97
|
-
bytes32
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
141
|
+
) external {
|
142
|
+
bytes32 userTx = calculateItemHash(
|
143
|
+
l2Sender,
|
144
|
+
to,
|
145
|
+
l2Block,
|
146
|
+
l1Block,
|
147
|
+
l2Timestamp,
|
148
|
+
value,
|
149
|
+
data
|
150
|
+
);
|
151
|
+
|
152
|
+
recordOutputAsSpent(proof, index, userTx);
|
153
|
+
|
154
|
+
executeTransactionImpl(index, l2Sender, to, l2Block, l1Block, l2Timestamp, value, data);
|
155
|
+
}
|
156
|
+
|
157
|
+
/// @dev function used to simulate the result of a particular function call from the outbox
|
158
|
+
/// it is useful for things such as gas estimates. This function includes all costs except for
|
159
|
+
/// proof validation (which can be considered offchain as a somewhat of a fixed cost - it's
|
160
|
+
/// not really a fixed cost, but can be treated as so with a fixed overhead for gas estimation).
|
161
|
+
/// We can't include the cost of proof validation since this is intended to be used to simulate txs
|
162
|
+
/// that are included in yet-to-be confirmed merkle roots. The simulation entrypoint could instead pretend
|
163
|
+
/// to confirm a pending merkle root, but that would be less pratical for integrating with tooling.
|
164
|
+
/// It is only possible to trigger it when the msg sender is address zero, which should be impossible
|
165
|
+
/// unless under simulation in an eth_call or eth_estimateGas
|
166
|
+
function executeTransactionSimulation(
|
167
|
+
uint256 index,
|
168
|
+
address l2Sender,
|
169
|
+
address to,
|
170
|
+
uint256 l2Block,
|
171
|
+
uint256 l1Block,
|
172
|
+
uint256 l2Timestamp,
|
173
|
+
uint256 value,
|
174
|
+
bytes calldata data
|
175
|
+
) external {
|
176
|
+
if (msg.sender != address(0)) revert SimulationOnlyEntrypoint();
|
177
|
+
executeTransactionImpl(index, l2Sender, to, l2Block, l1Block, l2Timestamp, value, data);
|
178
|
+
}
|
179
|
+
|
180
|
+
function executeTransactionImpl(
|
181
|
+
uint256 outputId,
|
182
|
+
address l2Sender,
|
183
|
+
address to,
|
184
|
+
uint256 l2Block,
|
185
|
+
uint256 l1Block,
|
186
|
+
uint256 l2Timestamp,
|
187
|
+
uint256 value,
|
188
|
+
bytes calldata data
|
189
|
+
) internal {
|
190
|
+
emit OutBoxTransactionExecuted(to, l2Sender, 0, outputId);
|
112
191
|
|
113
192
|
// we temporarily store the previous values so the outbox can naturally
|
114
193
|
// unwind itself when there are nested calls to `executeTransaction`
|
@@ -119,7 +198,7 @@ contract Outbox is DelegateCallAware, IOutbox {
|
|
119
198
|
l2Block: uint128(l2Block),
|
120
199
|
l1Block: uint128(l1Block),
|
121
200
|
timestamp: uint128(l2Timestamp),
|
122
|
-
outputId: outputId
|
201
|
+
outputId: bytes32(outputId)
|
123
202
|
});
|
124
203
|
|
125
204
|
// set and reset vars around execution so they remain valid during call
|
@@ -128,11 +207,35 @@ contract Outbox is DelegateCallAware, IOutbox {
|
|
128
207
|
context = prevContext;
|
129
208
|
}
|
130
209
|
|
210
|
+
function _calcSpentIndexOffset(uint256 index)
|
211
|
+
internal
|
212
|
+
view
|
213
|
+
returns (
|
214
|
+
uint256,
|
215
|
+
uint256,
|
216
|
+
bytes32
|
217
|
+
)
|
218
|
+
{
|
219
|
+
uint256 spentIndex = index / 255; // Note: Reserves the MSB.
|
220
|
+
uint256 bitOffset = index % 255;
|
221
|
+
bytes32 replay = spent[spentIndex];
|
222
|
+
return (spentIndex, bitOffset, replay);
|
223
|
+
}
|
224
|
+
|
225
|
+
function _isSpent(uint256 bitOffset, bytes32 replay) internal pure returns (bool) {
|
226
|
+
return ((replay >> bitOffset) & bytes32(uint256(1))) != bytes32(0);
|
227
|
+
}
|
228
|
+
|
229
|
+
function isSpent(uint256 index) external view returns (bool) {
|
230
|
+
(, uint256 bitOffset, bytes32 replay) = _calcSpentIndexOffset(index);
|
231
|
+
return _isSpent(bitOffset, replay);
|
232
|
+
}
|
233
|
+
|
131
234
|
function recordOutputAsSpent(
|
132
235
|
bytes32[] memory proof,
|
133
236
|
uint256 index,
|
134
237
|
bytes32 item
|
135
|
-
) internal
|
238
|
+
) internal {
|
136
239
|
if (proof.length >= 256) revert ProofTooLong(proof.length);
|
137
240
|
if (index >= 2**proof.length) revert PathNotMinimal(index, 2**proof.length);
|
138
241
|
|
@@ -140,10 +243,10 @@ contract Outbox is DelegateCallAware, IOutbox {
|
|
140
243
|
bytes32 calcRoot = calculateMerkleRoot(proof, index, item);
|
141
244
|
if (roots[calcRoot] == bytes32(0)) revert UnknownRoot(calcRoot);
|
142
245
|
|
143
|
-
|
144
|
-
spent[index] = true;
|
246
|
+
(uint256 spentIndex, uint256 bitOffset, bytes32 replay) = _calcSpentIndexOffset(index);
|
145
247
|
|
146
|
-
|
248
|
+
if (_isSpent(bitOffset, replay)) revert AlreadySpent(index);
|
249
|
+
spent[spentIndex] = (replay | bytes32(1 << bitOffset));
|
147
250
|
}
|
148
251
|
|
149
252
|
function executeBridgeCall(
|