@arbitrum/nitro-contracts 1.0.0-beta.5 → 1.0.0-beta.6

Sign up to get free protection for your applications and to get access to all the features.
Files changed (54) hide show
  1. package/package.json +2 -1
  2. package/src/bridge/Bridge.sol +127 -32
  3. package/src/bridge/IBridge.sol +40 -6
  4. package/src/bridge/{IMessageProvider.sol → IDelayedMessageProvider.sol} +2 -3
  5. package/src/bridge/IInbox.sol +23 -2
  6. package/src/bridge/IOwnable.sol +9 -0
  7. package/src/bridge/ISequencerInbox.sol +36 -9
  8. package/src/bridge/Inbox.sol +131 -45
  9. package/src/bridge/Outbox.sol +134 -31
  10. package/src/bridge/SequencerInbox.sol +159 -60
  11. package/src/challenge/ChallengeLib.sol +0 -2
  12. package/src/challenge/ChallengeManager.sol +4 -8
  13. package/src/challenge/IChallengeManager.sol +1 -1
  14. package/src/libraries/Error.sol +6 -0
  15. package/src/libraries/IGasRefunder.sol +12 -13
  16. package/src/libraries/MerkleLib.sol +11 -2
  17. package/src/libraries/MessageTypes.sol +1 -0
  18. package/src/mocks/BridgeStub.sol +67 -21
  19. package/src/mocks/InboxStub.sol +3 -9
  20. package/src/mocks/SequencerInboxStub.sol +10 -8
  21. package/src/mocks/Simple.sol +8 -0
  22. package/src/node-interface/NodeInterface.sol +32 -5
  23. package/src/osp/IOneStepProver.sol +1 -2
  24. package/src/osp/OneStepProver0.sol +1 -87
  25. package/src/osp/OneStepProverHostIo.sol +5 -6
  26. package/src/osp/OneStepProverMath.sol +37 -27
  27. package/src/osp/OneStepProverMemory.sol +3 -4
  28. package/src/precompiles/ArbAggregator.sol +23 -33
  29. package/src/precompiles/ArbBLS.sol +1 -43
  30. package/src/precompiles/ArbGasInfo.sol +1 -19
  31. package/src/precompiles/ArbOwner.sol +12 -15
  32. package/src/precompiles/ArbRetryableTx.sol +10 -1
  33. package/src/precompiles/ArbosActs.sol +9 -2
  34. package/src/rollup/BridgeCreator.sol +23 -28
  35. package/src/rollup/IRollupCore.sol +3 -3
  36. package/src/rollup/{IRollupEventBridge.sol → IRollupEventInbox.sol} +2 -2
  37. package/src/rollup/IRollupLogic.sol +21 -18
  38. package/src/rollup/RollupAdminLogic.sol +30 -34
  39. package/src/rollup/RollupCore.sol +15 -7
  40. package/src/rollup/RollupCreator.sol +21 -11
  41. package/src/rollup/{RollupEventBridge.sol → RollupEventInbox.sol} +10 -10
  42. package/src/rollup/RollupLib.sol +20 -5
  43. package/src/rollup/RollupUserLogic.sol +9 -18
  44. package/src/rollup/ValidatorWallet.sol +124 -8
  45. package/src/rollup/ValidatorWalletCreator.sol +11 -6
  46. package/src/state/Deserialize.sol +3 -22
  47. package/src/state/Instructions.sol +2 -10
  48. package/src/state/Machine.sol +0 -4
  49. package/src/state/ModuleMemory.sol +2 -1
  50. package/src/state/Value.sol +2 -3
  51. package/src/test-helpers/BridgeTester.sol +223 -0
  52. package/src/test-helpers/OutboxWithoutOptTester.sol +188 -0
  53. package/src/test-helpers/RollupMock.sol +21 -0
  54. package/src/state/PcStack.sol +0 -32
@@ -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,72 @@ 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
- modifier onlyOwner() {
36
- // whoevever owns the Bridge, also owns the Inbox. this is usually the rollup contract
37
- address bridgeOwner = OwnableUpgradeable(address(bridge)).owner();
38
- if (msg.sender != bridgeOwner) revert NotOwner(msg.sender, bridgeOwner);
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
+ if (allowListEnabled && !isAllowed[tx.origin]) revert NotAllowedOrigin(tx.origin);
65
+ _;
66
+ }
67
+
68
+ /// ------------------------------------ allow list end ------------------------------------ ///
69
+
70
+ modifier onlyRollupOrOwner() {
71
+ IOwnable rollup = bridge.rollup();
72
+ if (msg.sender != address(rollup)) {
73
+ address rollupOwner = rollup.owner();
74
+ if (msg.sender != rollupOwner) {
75
+ revert NotRollupOrOwner(msg.sender, address(rollup), rollupOwner);
76
+ }
77
+ }
39
78
  _;
40
79
  }
41
80
 
42
81
  /// @notice pauses all inbox functionality
43
- function pause() external onlyOwner {
82
+ function pause() external onlyRollupOrOwner {
44
83
  _pause();
45
84
  }
46
85
 
47
86
  /// @notice unpauses all inbox functionality
48
- function unpause() external onlyOwner {
87
+ function unpause() external onlyRollupOrOwner {
49
88
  _unpause();
50
89
  }
51
90
 
52
- function initialize(IBridge _bridge) external initializer onlyDelegated {
91
+ function initialize(IBridge _bridge, ISequencerInbox _sequencerInbox)
92
+ external
93
+ initializer
94
+ onlyDelegated
95
+ {
53
96
  if (address(bridge) != address(0)) revert AlreadyInit();
54
97
  bridge = _bridge;
98
+ sequencerInbox = _sequencerInbox;
99
+ allowListEnabled = false;
55
100
  __Pausable_init();
56
101
  }
57
102
 
@@ -64,26 +109,19 @@ contract Inbox is DelegateCallAware, PausableUpgradeable, IInbox {
64
109
  sstore(i, 0)
65
110
  }
66
111
  }
112
+ allowListEnabled = false;
67
113
  bridge = _bridge;
68
114
  }
69
115
 
70
116
  /**
71
- * @notice Send a generic L2 message to the chain
72
- * @dev This method is an optimization to avoid having to emit the entirety of the messageData in a log. Instead validators are expected to be able to parse the data from the transaction's input
117
+ * @dev DEPRECATED in favor of sendL2Message
73
118
  * @param messageData Data of the message being sent
74
119
  */
75
120
  function sendL2MessageFromOrigin(bytes calldata messageData)
76
121
  external
77
- whenNotPaused
78
122
  returns (uint256)
79
123
  {
80
- // solhint-disable-next-line avoid-tx-origin
81
- if (msg.sender != tx.origin) revert NotOrigin();
82
- if (messageData.length > MAX_DATA_SIZE)
83
- revert DataTooLarge(messageData.length, MAX_DATA_SIZE);
84
- uint256 msgNum = deliverToBridge(L2_MSG, msg.sender, keccak256(messageData));
85
- emit InboxMessageDeliveredFromOrigin(msgNum);
86
- return msgNum;
124
+ return sendL2Message(messageData);
87
125
  }
88
126
 
89
127
  /**
@@ -92,9 +130,10 @@ contract Inbox is DelegateCallAware, PausableUpgradeable, IInbox {
92
130
  * @param messageData Data of the message being sent
93
131
  */
94
132
  function sendL2Message(bytes calldata messageData)
95
- external
133
+ public
96
134
  override
97
135
  whenNotPaused
136
+ onlyAllowed
98
137
  returns (uint256)
99
138
  {
100
139
  return _deliverMessage(L2_MSG, msg.sender, messageData);
@@ -106,7 +145,7 @@ contract Inbox is DelegateCallAware, PausableUpgradeable, IInbox {
106
145
  uint256 nonce,
107
146
  address to,
108
147
  bytes calldata data
109
- ) external payable virtual override whenNotPaused returns (uint256) {
148
+ ) external payable virtual override whenNotPaused onlyAllowed returns (uint256) {
110
149
  return
111
150
  _deliverMessage(
112
151
  L1MessageType_L2FundedByL1,
@@ -128,7 +167,7 @@ contract Inbox is DelegateCallAware, PausableUpgradeable, IInbox {
128
167
  uint256 maxFeePerGas,
129
168
  address to,
130
169
  bytes calldata data
131
- ) external payable virtual override whenNotPaused returns (uint256) {
170
+ ) external payable virtual override whenNotPaused onlyAllowed returns (uint256) {
132
171
  return
133
172
  _deliverMessage(
134
173
  L1MessageType_L2FundedByL1,
@@ -151,7 +190,7 @@ contract Inbox is DelegateCallAware, PausableUpgradeable, IInbox {
151
190
  address to,
152
191
  uint256 value,
153
192
  bytes calldata data
154
- ) external virtual override whenNotPaused returns (uint256) {
193
+ ) external virtual override whenNotPaused onlyAllowed returns (uint256) {
155
194
  return
156
195
  _deliverMessage(
157
196
  L2_MSG,
@@ -174,7 +213,7 @@ contract Inbox is DelegateCallAware, PausableUpgradeable, IInbox {
174
213
  address to,
175
214
  uint256 value,
176
215
  bytes calldata data
177
- ) external virtual override whenNotPaused returns (uint256) {
216
+ ) external virtual override whenNotPaused onlyAllowed returns (uint256) {
178
217
  return
179
218
  _deliverMessage(
180
219
  L2_MSG,
@@ -209,43 +248,51 @@ contract Inbox is DelegateCallAware, PausableUpgradeable, IInbox {
209
248
  /// @dev this does not trigger the fallback function when receiving in the L2 side.
210
249
  /// Look into retryable tickets if you are interested in this functionality.
211
250
  /// @dev this function should not be called inside contract constructors
212
- function depositEth() public payable override whenNotPaused returns (uint256) {
213
- address sender = msg.sender;
251
+ function depositEth() public payable override whenNotPaused onlyAllowed returns (uint256) {
252
+ address dest = msg.sender;
214
253
 
215
254
  // solhint-disable-next-line avoid-tx-origin
216
- if (!AddressUpgradeable.isContract(sender) && tx.origin == msg.sender) {
255
+ if (AddressUpgradeable.isContract(msg.sender) || tx.origin != msg.sender) {
217
256
  // isContract check fails if this function is called during a contract's constructor.
218
257
  // We don't adjust the address for calls coming from L1 contracts since their addresses get remapped
219
258
  // If the caller is an EOA, we adjust the address.
220
259
  // This is needed because unsigned messages to the L2 (such as retryables)
221
260
  // have the L1 sender address mapped.
222
- // Here we preemptively reverse the mapping for EOAs so deposits work as expected
223
- sender = AddressAliasHelper.undoL1ToL2Alias(sender);
261
+ dest = AddressAliasHelper.applyL1ToL2Alias(msg.sender);
224
262
  }
225
263
 
226
264
  return
227
265
  _deliverMessage(
228
266
  L1MessageType_ethDeposit,
229
- sender, // arb-os will add the alias to this value
230
- abi.encodePacked(msg.value)
267
+ msg.sender,
268
+ abi.encodePacked(dest, msg.value)
231
269
  );
232
270
  }
233
271
 
234
272
  /// @notice deprecated in favour of depositEth with no parameters
235
- function depositEth(uint256) external payable virtual override whenNotPaused returns (uint256) {
273
+ function depositEth(uint256)
274
+ external
275
+ payable
276
+ virtual
277
+ override
278
+ whenNotPaused
279
+ onlyAllowed
280
+ returns (uint256)
281
+ {
236
282
  return depositEth();
237
283
  }
238
284
 
239
285
  /**
240
286
  * @notice deprecated in favour of unsafeCreateRetryableTicket
241
287
  * @dev deprecated in favour of unsafeCreateRetryableTicket
288
+ * @dev Gas limit and maxFeePerGas should not be set to 1 as that is used to trigger the RetryableData error
242
289
  * @param to destination L2 contract address
243
290
  * @param l2CallValue call value for retryable L2 message
244
291
  * @param maxSubmissionCost Max gas deducted from user's L2 balance to cover base submission fee
245
292
  * @param excessFeeRefundAddress gasLimit x maxFeePerGas - execution cost gets credited here on L2 balance
246
293
  * @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
294
+ * @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)
295
+ * @param maxFeePerGas price bid for L2 execution. Should not be set to 1 (magic value used to trigger the RetryableData error)
249
296
  * @param data ABI encoded data of L2 message
250
297
  * @return unique id for retryable transaction (keccak256(requestID, uint(0) )
251
298
  */
@@ -258,7 +305,7 @@ contract Inbox is DelegateCallAware, PausableUpgradeable, IInbox {
258
305
  uint256 gasLimit,
259
306
  uint256 maxFeePerGas,
260
307
  bytes calldata data
261
- ) external payable virtual whenNotPaused returns (uint256) {
308
+ ) external payable virtual whenNotPaused onlyAllowed returns (uint256) {
262
309
  return
263
310
  unsafeCreateRetryableTicket(
264
311
  to,
@@ -275,13 +322,14 @@ contract Inbox is DelegateCallAware, PausableUpgradeable, IInbox {
275
322
  /**
276
323
  * @notice Put a message in the L2 inbox that can be reexecuted for some fixed amount of time if it reverts
277
324
  * @dev all msg.value will deposited to callValueRefundAddress on L2
325
+ * @dev Gas limit and maxFeePerGas should not be set to 1 as that is used to trigger the RetryableData error
278
326
  * @param to destination L2 contract address
279
327
  * @param l2CallValue call value for retryable L2 message
280
328
  * @param maxSubmissionCost Max gas deducted from user's L2 balance to cover base submission fee
281
329
  * @param excessFeeRefundAddress gasLimit x maxFeePerGas - execution cost gets credited here on L2 balance
282
330
  * @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
331
+ * @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)
332
+ * @param maxFeePerGas price bid for L2 execution. Should not be set to 1 (magic value used to trigger the RetryableData error)
285
333
  * @param data ABI encoded data of L2 message
286
334
  * @return unique id for retryable transaction (keccak256(requestID, uint(0) )
287
335
  */
@@ -294,10 +342,14 @@ contract Inbox is DelegateCallAware, PausableUpgradeable, IInbox {
294
342
  uint256 gasLimit,
295
343
  uint256 maxFeePerGas,
296
344
  bytes calldata data
297
- ) external payable virtual override whenNotPaused returns (uint256) {
345
+ ) external payable virtual override whenNotPaused onlyAllowed returns (uint256) {
298
346
  // ensure the user's deposit alone will make submission succeed
299
- if (msg.value < maxSubmissionCost + l2CallValue)
300
- revert InsufficientValue(maxSubmissionCost + l2CallValue, msg.value);
347
+ if (msg.value < (maxSubmissionCost + l2CallValue + gasLimit * maxFeePerGas)) {
348
+ revert InsufficientValue(
349
+ maxSubmissionCost + l2CallValue + gasLimit * maxFeePerGas,
350
+ msg.value
351
+ );
352
+ }
301
353
 
302
354
  // if a refund address is a contract, we apply the alias to it
303
355
  // so that it can access its funds on the L2
@@ -311,7 +363,7 @@ contract Inbox is DelegateCallAware, PausableUpgradeable, IInbox {
311
363
  }
312
364
 
313
365
  return
314
- unsafeCreateRetryableTicket(
366
+ unsafeCreateRetryableTicketInternal(
315
367
  to,
316
368
  l2CallValue,
317
369
  maxSubmissionCost,
@@ -329,17 +381,18 @@ contract Inbox is DelegateCallAware, PausableUpgradeable, IInbox {
329
381
  * come from the deposit alone, rather than falling back on the user's L2 balance
330
382
  * @dev Advanced usage only (does not rewrite aliases for excessFeeRefundAddress and callValueRefundAddress).
331
383
  * createRetryableTicket method is the recommended standard.
384
+ * @dev Gas limit and maxFeePerGas should not be set to 1 as that is used to trigger the RetryableData error
332
385
  * @param to destination L2 contract address
333
386
  * @param l2CallValue call value for retryable L2 message
334
387
  * @param maxSubmissionCost Max gas deducted from user's L2 balance to cover base submission fee
335
388
  * @param excessFeeRefundAddress gasLimit x maxFeePerGas - execution cost gets credited here on L2 balance
336
389
  * @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
390
+ * @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)
391
+ * @param maxFeePerGas price bid for L2 execution. Should not be set to 1 (magic value used to trigger the RetryableData error)
339
392
  * @param data ABI encoded data of L2 message
340
393
  * @return unique id for retryable transaction (keccak256(requestID, uint(0) )
341
394
  */
342
- function unsafeCreateRetryableTicket(
395
+ function unsafeCreateRetryableTicketInternal(
343
396
  address to,
344
397
  uint256 l2CallValue,
345
398
  uint256 maxSubmissionCost,
@@ -348,7 +401,23 @@ contract Inbox is DelegateCallAware, PausableUpgradeable, IInbox {
348
401
  uint256 gasLimit,
349
402
  uint256 maxFeePerGas,
350
403
  bytes calldata data
351
- ) public payable virtual override whenNotPaused returns (uint256) {
404
+ ) internal virtual whenNotPaused onlyAllowed returns (uint256) {
405
+ // gas price and limit of 1 should never be a valid input, so instead they are used as
406
+ // magic values to trigger a revert in eth calls that surface data without requiring a tx trace
407
+ if (gasLimit == 1 || maxFeePerGas == 1)
408
+ revert RetryableData(
409
+ msg.sender,
410
+ to,
411
+ l2CallValue,
412
+ msg.value,
413
+ maxSubmissionCost,
414
+ excessFeeRefundAddress,
415
+ callValueRefundAddress,
416
+ gasLimit,
417
+ maxFeePerGas,
418
+ data
419
+ );
420
+
352
421
  uint256 submissionFee = calculateRetryableSubmissionFee(data.length, block.basefee);
353
422
  if (maxSubmissionCost < submissionFee)
354
423
  revert InsufficientSubmissionCost(submissionFee, maxSubmissionCost);
@@ -372,6 +441,19 @@ contract Inbox is DelegateCallAware, PausableUpgradeable, IInbox {
372
441
  );
373
442
  }
374
443
 
444
+ function unsafeCreateRetryableTicket(
445
+ address,
446
+ uint256,
447
+ uint256,
448
+ address,
449
+ address,
450
+ uint256,
451
+ uint256,
452
+ bytes calldata
453
+ ) public payable override returns (uint256) {
454
+ revert("UNSAFE_RETRYABLES_TEMPORARILY_DISABLED");
455
+ }
456
+
375
457
  function _deliverMessage(
376
458
  uint8 _kind,
377
459
  address _sender,
@@ -379,7 +461,11 @@ contract Inbox is DelegateCallAware, PausableUpgradeable, IInbox {
379
461
  ) internal returns (uint256) {
380
462
  if (_messageData.length > MAX_DATA_SIZE)
381
463
  revert DataTooLarge(_messageData.length, MAX_DATA_SIZE);
382
- uint256 msgNum = deliverToBridge(_kind, _sender, keccak256(_messageData));
464
+ uint256 msgNum = deliverToBridge(
465
+ _kind,
466
+ AddressAliasHelper.applyL1ToL2Alias(_sender),
467
+ keccak256(_messageData)
468
+ );
383
469
  emit InboxMessageDelivered(msgNum, _messageData);
384
470
  return msgNum;
385
471
  }
@@ -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 => bool) public spent; // maps leaf number => if spent
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(address _rollup, IBridge _bridge) external onlyDelegated {
33
- if (rollup != address(0)) revert AlreadyInit();
34
- rollup = _rollup;
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
- return context.sender;
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
- return uint256(context.l2Block);
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
- return uint256(context.l1Block);
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
- return uint256(context.timestamp);
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
- // @deprecated batch number is now always 0
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
- return context.outputId;
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 virtual {
97
- bytes32 outputId;
98
- {
99
- bytes32 userTx = calculateItemHash(
100
- l2Sender,
101
- to,
102
- l2Block,
103
- l1Block,
104
- l2Timestamp,
105
- value,
106
- data
107
- );
108
-
109
- outputId = recordOutputAsSpent(proof, index, userTx);
110
- emit OutBoxTransactionExecuted(to, l2Sender, 0, index);
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 returns (bytes32) {
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
- if (spent[index]) revert AlreadySpent(index);
144
- spent[index] = true;
246
+ (uint256 spentIndex, uint256 bitOffset, bytes32 replay) = _calcSpentIndexOffset(index);
145
247
 
146
- return bytes32(index);
248
+ if (_isSpent(bitOffset, replay)) revert AlreadySpent(index);
249
+ spent[spentIndex] = (replay | bytes32(1 << bitOffset));
147
250
  }
148
251
 
149
252
  function executeBridgeCall(