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