@arbitrum/nitro-contracts 1.0.0-beta.5 → 1.0.0-beta.8
Sign up to get free protection for your applications and to get access to all the features.
- package/package.json +6 -2
- package/src/bridge/Bridge.sol +138 -32
- package/src/bridge/IBridge.sol +34 -14
- package/src/bridge/IDelayedMessageProvider.sol +15 -0
- package/src/bridge/IInbox.sol +8 -19
- package/src/bridge/IOutbox.sol +43 -23
- package/src/bridge/IOwnable.sol +10 -0
- package/src/bridge/ISequencerInbox.sol +30 -32
- package/src/bridge/Inbox.sol +133 -35
- package/src/bridge/Outbox.sol +145 -33
- package/src/bridge/SequencerInbox.sol +179 -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 +113 -0
- package/src/libraries/IGasRefunder.sol +15 -14
- package/src/libraries/MerkleLib.sol +11 -2
- package/src/libraries/MessageTypes.sol +1 -0
- package/src/mocks/BridgeStub.sol +69 -21
- package/src/mocks/InboxStub.sol +2 -0
- package/src/mocks/SequencerInboxStub.sol +10 -8
- package/src/mocks/Simple.sol +8 -0
- package/src/node-interface/NodeInterface.sol +62 -4
- 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 +10 -19
- package/src/precompiles/ArbOwner.sol +21 -15
- package/src/precompiles/ArbRetryableTx.sol +10 -1
- package/src/precompiles/ArbSys.sol +4 -4
- 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 +72 -34
- package/src/rollup/RollupCore.sol +20 -9
- package/src/rollup/RollupCreator.sol +21 -11
- package/src/rollup/{RollupEventBridge.sol → RollupEventInbox.sol} +10 -10
- package/src/rollup/RollupLib.sol +21 -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/GlobalState.sol +7 -0
- 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 +233 -0
- package/src/test-helpers/InterfaceCompatibilityTester.sol +11 -0
- package/src/test-helpers/OutboxWithoutOptTester.sol +214 -0
- package/src/test-helpers/RollupMock.sol +21 -0
- package/src/bridge/IMessageProvider.sol +0 -11
- package/src/state/PcStack.sol +0 -32
package/src/bridge/Inbox.sol
CHANGED
@@ -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
|
36
|
-
|
37
|
-
|
38
|
-
|
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
|
96
|
+
function pause() external onlyRollupOrOwner {
|
44
97
|
_pause();
|
45
98
|
}
|
46
99
|
|
47
100
|
/// @notice unpauses all inbox functionality
|
48
|
-
function unpause() external
|
101
|
+
function unpause() external onlyRollupOrOwner {
|
49
102
|
_unpause();
|
50
103
|
}
|
51
104
|
|
52
|
-
function initialize(IBridge _bridge
|
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
|
-
|
264
|
+
view
|
203
265
|
returns (uint256)
|
204
266
|
{
|
205
|
-
|
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
|
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 (
|
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
|
-
|
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,
|
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)
|
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(
|
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
|
485
|
+
return
|
486
|
+
bridge.enqueueDelayedMessage{value: msg.value}(
|
487
|
+
kind,
|
488
|
+
AddressAliasHelper.applyL1ToL2Alias(sender),
|
489
|
+
messageDataHash
|
490
|
+
);
|
393
491
|
}
|
394
492
|
}
|
package/src/bridge/Outbox.sol
CHANGED
@@ -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 =>
|
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(
|
33
|
-
if (
|
34
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
97
|
-
bytes32
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
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
|
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
|
-
|
144
|
-
spent[index] = true;
|
255
|
+
(uint256 spentIndex, uint256 bitOffset, bytes32 replay) = _calcSpentIndexOffset(index);
|
145
256
|
|
146
|
-
|
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
|
}
|