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

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 (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
  }