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