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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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(