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

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