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