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