-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathOptimismPortal2.sol
652 lines (555 loc) · 30.5 KB
/
OptimismPortal2.sol
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
// SPDX-License-Identifier: MIT
pragma solidity 0.8.15;
import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import { SafeERC20 } from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
import { Initializable } from "@openzeppelin/contracts/proxy/utils/Initializable.sol";
import { SafeCall } from "src/libraries/SafeCall.sol";
import { DisputeGameFactory, IDisputeGame } from "src/dispute/DisputeGameFactory.sol";
import { SystemConfig } from "src/L1/SystemConfig.sol";
import { SuperchainConfig } from "src/L1/SuperchainConfig.sol";
import { Constants } from "src/libraries/Constants.sol";
import { Types } from "src/libraries/Types.sol";
import { Hashing } from "src/libraries/Hashing.sol";
import { SecureMerkleTrie } from "src/libraries/trie/SecureMerkleTrie.sol";
import { AddressAliasHelper } from "src/vendor/AddressAliasHelper.sol";
import { ResourceMetering } from "src/L1/ResourceMetering.sol";
import { ISemver } from "src/universal/ISemver.sol";
import { Constants } from "src/libraries/Constants.sol";
import { OnApprove } from "./OnApprove.sol";
import "src/libraries/PortalErrors.sol";
import "src/dispute/lib/Types.sol";
/// @custom:proxied
/// @title OptimismPortal2
/// @notice The OptimismPortal2 is a low-level contract responsible for passing messages between L1
/// and L2. Messages sent directly to the OptimismPortal have no form of replayability.
/// Users are encouraged to use the L1CrossDomainMessenger for a higher-level interface.
/// It is implemented for supporting permissionless fault proofs to help OP Stack chains reach Stage 1.
contract OptimismPortal2 is Initializable, ResourceMetering, OnApprove, ISemver {
using SafeERC20 for IERC20;
/// @notice Represents a proven withdrawal.
/// @custom:field disputeGameProxy The address of the dispute game proxy that the withdrawal was proven against.
/// @custom:field timestamp Timestamp at whcih the withdrawal was proven.
struct ProvenWithdrawal {
IDisputeGame disputeGameProxy;
uint64 timestamp;
}
/// @notice The delay between when a withdrawal transaction is proven and when it may be finalized.
uint256 internal immutable PROOF_MATURITY_DELAY_SECONDS;
/// @notice The delay between when a dispute game is resolved and when a withdrawal proven against it may be
/// finalized.
uint256 internal immutable DISPUTE_GAME_FINALITY_DELAY_SECONDS;
/// @notice Version of the deposit event.
uint256 internal constant DEPOSIT_VERSION = 0;
/// @notice The L2 gas limit set when eth is deposited using the receive() function.
uint64 internal constant RECEIVE_DEFAULT_GAS_LIMIT = 100_000;
/// @notice Address of the L2 account which initiated a withdrawal in this transaction.
/// If the of this variable is the default L2 sender address, then we are NOT inside of
/// a call to finalizeWithdrawalTransaction.
address public l2Sender;
/// @notice A list of withdrawal hashes which have been successfully finalized.
mapping(bytes32 => bool) public finalizedWithdrawals;
/// @custom:legacy
/// @custom:spacer provenWithdrawals
/// @notice Spacer taking up the legacy `provenWithdrawals` mapping slot.
bytes32 private spacer_52_0_32;
/// @custom:legacy
/// @custom:spacer paused
/// @notice Spacer for backwards compatibility.
bool private spacer_53_0_1;
/// @notice Contract of the Superchain Config.
SuperchainConfig public superchainConfig;
/// @custom:legacy
/// @custom:spacer l2Oracle
/// @notice Spacer taking up the legacy `l2Oracle` address slot.
address private spacer_54_0_20;
/// @notice Contract of the SystemConfig.
/// @custom:network-specific
SystemConfig public systemConfig;
/// @notice Address of the DisputeGameFactory.
/// @custom:network-specific
DisputeGameFactory public disputeGameFactory;
/// @notice A mapping of withdrawal hashes to proof submitters to `ProvenWithdrawal` data.
mapping(bytes32 => mapping(address => ProvenWithdrawal)) public provenWithdrawals;
/// @notice A mapping of dispute game addresses to whether or not they are blacklisted.
mapping(IDisputeGame => bool) public disputeGameBlacklist;
/// @notice The game type that the OptimismPortal consults for output proposals.
GameType public respectedGameType;
/// @notice The timestamp at which the respected game type was last updated.
uint64 public respectedGameTypeUpdatedAt;
/// @notice Mapping of withdrawal hashes to addresses that have submitted a proof for the withdrawal.
mapping(bytes32 => address[]) public proofSubmitters;
/// @custom:spacer _balance (custom gas token)
/// @notice Spacer for forwards compatibility.
bytes32 private spacer_61_0_32;
/// @notice Emitted when a transaction is deposited from L1 to L2.
/// The parameters of this event are read by the rollup node and used to derive deposit
/// transactions on L2.
/// @param from Address that triggered the deposit transaction.
/// @param to Address that the deposit transaction is directed to.
/// @param version Version of this deposit transaction event.
/// @param opaqueData ABI encoded deposit data to be parsed off-chain.
event TransactionDeposited(address indexed from, address indexed to, uint256 indexed version, bytes opaqueData);
/// @notice Emitted when a withdrawal transaction is proven.
/// @param withdrawalHash Hash of the withdrawal transaction.
/// @param from Address that triggered the withdrawal transaction.
/// @param to Address that the withdrawal transaction is directed to.
event WithdrawalProven(bytes32 indexed withdrawalHash, address indexed from, address indexed to);
/// @notice Emitted when a withdrawal transaction is proven. Exists as a separate event to allow for backwards
/// compatibility for tooling that observes the `WithdrawalProven` event.
/// @param withdrawalHash Hash of the withdrawal transaction.
/// @param proofSubmitter Address of the proof submitter.
event WithdrawalProvenExtension1(bytes32 indexed withdrawalHash, address indexed proofSubmitter);
/// @notice Emitted when a withdrawal transaction is finalized.
/// @param withdrawalHash Hash of the withdrawal transaction.
/// @param success Whether the withdrawal transaction was successful.
event WithdrawalFinalized(bytes32 indexed withdrawalHash, bool success);
/// @notice Emitted when a dispute game is blacklisted by the Guardian.
/// @param disputeGame Address of the dispute game that was blacklisted.
event DisputeGameBlacklisted(IDisputeGame indexed disputeGame);
/// @notice Emitted when the Guardian changes the respected game type in the portal.
/// @param newGameType The new respected game type.
/// @param updatedAt The timestamp at which the respected game type was updated.
event RespectedGameTypeSet(GameType indexed newGameType, Timestamp indexed updatedAt);
/// @notice Reverts when paused.
modifier whenNotPaused() {
if (paused()) revert CallPaused();
_;
}
/// @notice Semantic version.
/// @custom:semver 3.10.0
string public constant version = "3.10.0";
/// @notice Constructs the OptimismPortal contract.
constructor(uint256 _proofMaturityDelaySeconds, uint256 _disputeGameFinalityDelaySeconds) {
PROOF_MATURITY_DELAY_SECONDS = _proofMaturityDelaySeconds;
DISPUTE_GAME_FINALITY_DELAY_SECONDS = _disputeGameFinalityDelaySeconds;
initialize({
_disputeGameFactory: DisputeGameFactory(address(0)),
_systemConfig: SystemConfig(address(0)),
_superchainConfig: SuperchainConfig(address(0)),
_initialRespectedGameType: GameType.wrap(0)
});
}
/// @notice Initializer.
/// @param _disputeGameFactory Contract of the DisputeGameFactory.
/// @param _systemConfig Contract of the SystemConfig.
/// @param _superchainConfig Contract of the SuperchainConfig.
function initialize(
DisputeGameFactory _disputeGameFactory,
SystemConfig _systemConfig,
SuperchainConfig _superchainConfig,
GameType _initialRespectedGameType
)
public
initializer
{
disputeGameFactory = _disputeGameFactory;
systemConfig = _systemConfig;
superchainConfig = _superchainConfig;
// Set the `l2Sender` slot, only if it is currently empty. This signals the first initialization of the
// contract.
if (l2Sender == address(0)) {
l2Sender = Constants.DEFAULT_L2_SENDER;
// Set the `respectedGameTypeUpdatedAt` timestamp, to ignore all games of the respected type prior
// to this operation.
respectedGameTypeUpdatedAt = uint64(block.timestamp);
// Set the initial respected game type
respectedGameType = _initialRespectedGameType;
}
__ResourceMetering_init();
}
/// @notice Getter function for the address of the guardian.
/// Public getter is legacy and will be removed in the future. Use `SuperchainConfig.guardian()` instead.
/// @return Address of the guardian.
/// @custom:legacy
function guardian() public view returns (address) {
return superchainConfig.guardian();
}
/// @notice Getter for the current paused status.
function paused() public view returns (bool) {
return superchainConfig.paused();
}
/// @notice Returns the address of the native token.
/// @dev This function provides an external interface to retrieve the native token address
/// by calling the internal `_nativeToken()` function, which fetches the address from `systemConfig`.
/// @return The address of the native token in the current system.
function nativeTokenAddress() external view returns (address) {
return _nativeToken();
}
/// @notice Getter for the native token address
function _nativeToken() internal view returns (address) {
return systemConfig.nativeTokenAddress();
}
/// @notice Getter for the proof maturity delay.
function proofMaturityDelaySeconds() public view returns (uint256) {
return PROOF_MATURITY_DELAY_SECONDS;
}
/// @notice Getter for the dispute game finality delay.
function disputeGameFinalityDelaySeconds() public view returns (uint256) {
return DISPUTE_GAME_FINALITY_DELAY_SECONDS;
}
/// @notice Computes the minimum gas limit for a deposit.
/// The minimum gas limit linearly increases based on the size of the calldata.
/// This is to prevent users from creating L2 resource usage without paying for it.
/// This function can be used when interacting with the portal to ensure forwards
/// compatibility.
/// @param _byteCount Number of bytes in the calldata.
/// @return The minimum gas limit for a deposit.
function minimumGasLimit(uint64 _byteCount) public pure returns (uint64) {
return _byteCount * 16 + 21000;
}
/// @notice Not accept value so that users can send ETH directly to this contract
receive() external payable {
revert("Only allow native token");
// depositTransaction(msg.sender, msg.value, RECEIVE_DEFAULT_GAS_LIMIT, false, bytes(""));
}
/// @notice Accepts ETH value without triggering a deposit to L2.
/// This function mainly exists for the sake of the migration between the legacy
/// Optimism system and Bedrock.
function donateETH() external payable {
// Intentionally empty.
}
/// @notice Getter for the resource config.
/// Used internally by the ResourceMetering contract.
/// The SystemConfig is the source of truth for the resource config.
/// @return ResourceMetering ResourceConfig
function _resourceConfig() internal view override returns (ResourceMetering.ResourceConfig memory) {
return systemConfig.resourceConfig();
}
/// @notice Proves a withdrawal transaction.
/// @param _tx Withdrawal transaction to finalize.
/// @param _disputeGameIndex Index of the dispute game to prove the withdrawal against.
/// @param _outputRootProof Inclusion proof of the L2ToL1MessagePasser contract's storage root.
/// @param _withdrawalProof Inclusion proof of the withdrawal in L2ToL1MessagePasser contract.
function proveWithdrawalTransaction(
Types.WithdrawalTransaction memory _tx,
uint256 _disputeGameIndex,
Types.OutputRootProof calldata _outputRootProof,
bytes[] calldata _withdrawalProof
)
external
whenNotPaused
{
// Prevent users from creating a deposit transaction where this address is the message
// sender on L2. Because this is checked here, we do not need to check again in
// `finalizeWithdrawalTransaction`.
require(_tx.target != address(this), "OptimismPortal: you cannot send messages to the portal contract");
// Fetch the dispute game proxy from the `DisputeGameFactory` contract.
(GameType gameType,, IDisputeGame gameProxy) = disputeGameFactory.gameAtIndex(_disputeGameIndex);
Claim outputRoot = gameProxy.rootClaim();
// The game type of the dispute game must be the respected game type.
require(gameType.raw() == respectedGameType.raw(), "OptimismPortal: invalid game type");
// Verify that the output root can be generated with the elements in the proof.
require(
outputRoot.raw() == Hashing.hashOutputRootProof(_outputRootProof),
"OptimismPortal: invalid output root proof"
);
// Load the ProvenWithdrawal into memory, using the withdrawal hash as a unique identifier.
bytes32 withdrawalHash = Hashing.hashWithdrawal(_tx);
// We do not allow for proving withdrawals against dispute games that have resolved against the favor
// of the root claim.
require(
gameProxy.status() != GameStatus.CHALLENGER_WINS,
"OptimismPortal: cannot prove against invalid dispute games"
);
// Compute the storage slot of the withdrawal hash in the L2ToL1MessagePasser contract.
// Refer to the Solidity documentation for more information on how storage layouts are
// computed for mappings.
bytes32 storageKey = keccak256(
abi.encode(
withdrawalHash,
uint256(0) // The withdrawals mapping is at the first slot in the layout.
)
);
// Verify that the hash of this withdrawal was stored in the L2toL1MessagePasser contract
// on L2. If this is true, under the assumption that the SecureMerkleTrie does not have
// bugs, then we know that this withdrawal was actually triggered on L2 and can therefore
// be relayed on L1.
require(
SecureMerkleTrie.verifyInclusionProof({
_key: abi.encode(storageKey),
_value: hex"01",
_proof: _withdrawalProof,
_root: _outputRootProof.messagePasserStorageRoot
}),
"OptimismPortal: invalid withdrawal inclusion proof"
);
// Designate the withdrawalHash as proven by storing the `disputeGameProxy` & `timestamp` in the
// `provenWithdrawals` mapping. A `withdrawalHash` can only be proven once unless the dispute game it proved
// against resolves against the favor of the root claim.
provenWithdrawals[withdrawalHash][msg.sender] =
ProvenWithdrawal({ disputeGameProxy: gameProxy, timestamp: uint64(block.timestamp) });
// Emit a `WithdrawalProven` event.
emit WithdrawalProven(withdrawalHash, _tx.sender, _tx.target);
// Emit a `WithdrawalProvenExtension1` event.
emit WithdrawalProvenExtension1(withdrawalHash, msg.sender);
// Add the proof submitter to the list of proof submitters for this withdrawal hash.
proofSubmitters[withdrawalHash].push(msg.sender);
}
/// @notice Finalizes a withdrawal transaction.
/// @param _tx Withdrawal transaction to finalize.
function finalizeWithdrawalTransaction(Types.WithdrawalTransaction memory _tx) external whenNotPaused {
finalizeWithdrawalTransactionExternalProof(_tx, msg.sender);
}
/// @notice Finalizes a withdrawal transaction, using an external proof submitter.
/// @param _tx Withdrawal transaction to finalize.
/// @param _proofSubmitter Address of the proof submitter.
function finalizeWithdrawalTransactionExternalProof(
Types.WithdrawalTransaction memory _tx,
address _proofSubmitter
)
public
whenNotPaused
{
// Make sure that the l2Sender has not yet been set. The l2Sender is set to a value other
// than the default value when a withdrawal transaction is being finalized. This check is
// a defacto reentrancy guard.
require(
l2Sender == Constants.DEFAULT_L2_SENDER, "OptimismPortal: can only trigger one withdrawal per transaction"
);
// Compute the withdrawal hash.
bytes32 withdrawalHash = Hashing.hashWithdrawal(_tx);
// Check that the withdrawal can be finalized.
checkWithdrawal(withdrawalHash, _proofSubmitter);
// Mark the withdrawal as finalized so it can't be replayed.
finalizedWithdrawals[withdrawalHash] = true;
address _nativeTokenAddress = _nativeToken();
// Not allow to call native token contract because users can transfer all token out of the contract
require(
_tx.target != _nativeTokenAddress, "Optimism Portal: cannot make a direct call to native token contract"
);
// Set the l2Sender so contracts know who triggered this withdrawal on L2.
l2Sender = _tx.sender;
if (_tx.value != 0) {
if (_tx.data.length != 0) {
IERC20(_nativeTokenAddress).approve(_tx.target, _tx.value);
} else {
IERC20(_nativeTokenAddress).safeTransfer(_tx.target, _tx.value);
}
}
// Trigger the call to the target contract. We use a custom low level method
// SafeCall.callWithMinGas to ensure two key properties
// 1. Target contracts cannot force this call to run out of gas by returning a very large
// amount of data (and this is OK because we don't care about the returndata here).
// 2. The amount of gas provided to the execution context of the target is at least the
// gas limit specified by the user. If there is not enough gas in the current context
// to accomplish this, `callWithMinGas` will revert.
bool success;
if (_tx.data.length != 0) {
success = SafeCall.callWithMinGas(_tx.target, _tx.gasLimit, 0, _tx.data);
} else {
success = true;
}
// Reset approval after a call
if (_tx.data.length != 0 && _tx.value != 0) {
IERC20(_nativeTokenAddress).approve(_tx.target, 0);
}
// Reset the l2Sender back to the default value.
l2Sender = Constants.DEFAULT_L2_SENDER;
// All withdrawals are immediately finalized. Replayability can
// be achieved through contracts built on top of this contract
emit WithdrawalFinalized(withdrawalHash, success);
// Reverting here is useful for determining the exact gas cost to successfully execute the
// sub call to the target contract if the minimum gas limit specified by the user would not
// be sufficient to execute the sub call.
if (!success && tx.origin == Constants.ESTIMATION_ADDRESS) {
revert GasEstimation();
}
}
/// @notice unpack onApprove data
/// @param _data Data used in OnApprove contract
function unpackOnApproveData(bytes calldata _data)
internal
pure
returns (address _to, uint256 _value, uint32 _gasLimit, bytes calldata _message)
{
require(_data.length >= 56, "invalid onApprove data");
assembly {
// The layout of a "bytes calldata" is:
// The first 20 bytes: _to
// The next 32 bytes: _value
// The next 4 bytes: _gasLimit
// The rest: _message
_to := shr(96, calldataload(_data.offset))
_value := calldataload(add(_data.offset, 20))
_gasLimit := shr(224, calldataload(add(_data.offset, 52)))
_message.offset := add(_data.offset, 56)
_message.length := sub(_data.length, 56)
}
}
/// @notice ERC20 onApprove callback
/// @param _owner Account that called approveAndCall
/// @param _amount Approved amount
/// @param _data Data used in OnApprove contract
function onApprove(
address _owner,
address,
uint256 _amount,
bytes calldata _data
)
external
override
returns (bool)
{
(address to, uint256 value, uint32 gasLimit, bytes calldata message) = unpackOnApproveData(_data);
if (msg.sender == _nativeToken()) {
_depositTransaction(_owner, to, _amount, value, gasLimit, to == address(0), message, true);
return true;
} else {
return false;
}
}
// @notice Public interface for Accepting deposits of L1's ERC20 as L2's native token and data, and emits a
// TransactionDeposited event for use
// in
/// deriving deposit transactions. Note that if a deposit is made by a contract, its
/// address will be aliased when retrieved using `tx.origin` or `msg.sender`. Consider
/// using the CrossDomainMessenger contracts for a simpler developer experience.
/// @param _to Target address on L2.
/// @param _mint Native token value to deposit into L2.
/// @param _value Native token value to send to the recipient.
/// @param _gasLimit Amount of L2 gas to purchase by burning gas on L1.
/// @param _isCreation Whether or not the transaction is a contract creation.
/// @param _data Data to trigger the recipient with.
function depositTransaction(
address _to,
uint256 _mint,
uint256 _value,
uint64 _gasLimit,
bool _isCreation,
bytes calldata _data
)
external
{
_depositTransaction(msg.sender, _to, _mint, _value, _gasLimit, _isCreation, _data, false);
}
/// @notice Accepts deposits of L2's native token and data, and emits a TransactionDeposited event for
/// use in deriving deposit transactions. Note that if a deposit is made by a contract, its
/// address will be aliased when retrieved using `tx.origin` or `msg.sender`. Consider
/// using the CrossDomainMessenger contracts for a simpler developer experience.
/// @param _sender Sender address
/// @param _to Target address on L2.
/// @param _mint Native token value to deposit into L2's recipient.
/// @param _value Callvalue for L2's transaction
/// @param _gasLimit Amount of L2 gas to purchase by burning gas on L1.
/// @param _isCreation Whether or not the transaction is a contract creation.
/// @param _data Data to trigger the recipient with.
/// @param _isOnApproveTrigger Whether or not the transaction is trigger from approveAndCall.
function _depositTransaction(
address _sender,
address _to,
uint256 _mint,
uint256 _value,
uint64 _gasLimit,
bool _isCreation,
bytes calldata _data,
bool _isOnApproveTrigger
)
internal
metered(_gasLimit)
{
address _nativeTokenAddress = _nativeToken();
// Lock token in this contract
if (_mint > 0) {
IERC20(_nativeTokenAddress).safeTransferFrom(_sender, address(this), _mint);
}
if (_isCreation) {
require(_to == address(0), "OptimismPortal: must send to address(0) when creating a contract");
}
// Prevent depositing transactions that have too small of a gas limit. Users should pay
// more for more resource usage.
require(_gasLimit >= minimumGasLimit(uint64(_data.length)), "OptimismPortal: gas limit too small");
// Prevent the creation of deposit transactions that have too much calldata. This gives an
// upper limit on the size of unsafe blocks over the p2p network. 120kb is chosen to ensure
// that the transaction can fit into the p2p network policy of 128kb even though deposit
// transactions are not gossipped over the p2p network.
require(_data.length <= 120_000, "OptimismPortal: data too large");
// Transform the from-address to its alias if the caller is a contract.
address from =
((_sender != tx.origin) && !_isOnApproveTrigger) ? AddressAliasHelper.applyL1ToL2Alias(_sender) : _sender;
// Compute the opaque data that will be emitted as part of the TransactionDeposited event.
// We use opaque data so that we can update the TransactionDeposited event in the future
// without breaking the current interface.
bytes memory opaqueData = abi.encodePacked(_mint, _value, _gasLimit, _isCreation, _data);
// Emit a TransactionDeposited event so that the rollup node can derive a deposit
// transaction for this deposit.
emit TransactionDeposited(from, _to, DEPOSIT_VERSION, opaqueData);
}
/// @notice Blacklists a dispute game. Should only be used in the event that a dispute game resolves incorrectly.
/// @param _disputeGame Dispute game to blacklist.
function blacklistDisputeGame(IDisputeGame _disputeGame) external {
if (msg.sender != guardian()) revert Unauthorized();
disputeGameBlacklist[_disputeGame] = true;
emit DisputeGameBlacklisted(_disputeGame);
}
/// @notice Sets the respected game type. Changing this value can alter the security properties of the system,
/// depending on the new game's behavior.
/// @param _gameType The game type to consult for output proposals.
function setRespectedGameType(GameType _gameType) external {
if (msg.sender != guardian()) revert Unauthorized();
respectedGameType = _gameType;
respectedGameTypeUpdatedAt = uint64(block.timestamp);
emit RespectedGameTypeSet(_gameType, Timestamp.wrap(respectedGameTypeUpdatedAt));
}
/// @notice Checks if a withdrawal can be finalized. This function will revert if the withdrawal cannot be
/// finalized, and otherwise has no side-effects.
/// @param _withdrawalHash Hash of the withdrawal to check.
/// @param _proofSubmitter The submitter of the proof for the withdrawal hash
function checkWithdrawal(bytes32 _withdrawalHash, address _proofSubmitter) public view {
ProvenWithdrawal memory provenWithdrawal = provenWithdrawals[_withdrawalHash][_proofSubmitter];
IDisputeGame disputeGameProxy = provenWithdrawal.disputeGameProxy;
// The dispute game must not be blacklisted.
require(!disputeGameBlacklist[disputeGameProxy], "OptimismPortal: dispute game has been blacklisted");
// A withdrawal can only be finalized if it has been proven. We know that a withdrawal has
// been proven at least once when its timestamp is non-zero. Unproven withdrawals will have
// a timestamp of zero.
require(
provenWithdrawal.timestamp != 0,
"OptimismPortal: withdrawal has not been proven by proof submitter address yet"
);
uint64 createdAt = disputeGameProxy.createdAt().raw();
// As a sanity check, we make sure that the proven withdrawal's timestamp is greater than
// starting timestamp inside the Dispute Game. Not strictly necessary but extra layer of
// safety against weird bugs in the proving step.
require(
provenWithdrawal.timestamp > createdAt,
"OptimismPortal: withdrawal timestamp less than dispute game creation timestamp"
);
// A proven withdrawal must wait at least `PROOF_MATURITY_DELAY_SECONDS` before finalizing.
require(
block.timestamp - provenWithdrawal.timestamp > PROOF_MATURITY_DELAY_SECONDS,
"OptimismPortal: proven withdrawal has not matured yet"
);
// A proven withdrawal must wait until the dispute game it was proven against has been
// resolved in favor of the root claim (the output proposal). This is to prevent users
// from finalizing withdrawals proven against non-finalized output roots.
require(
disputeGameProxy.status() == GameStatus.DEFENDER_WINS,
"OptimismPortal: output proposal has not been validated"
);
// The game type of the dispute game must be the respected game type. This was also checked in
// `proveWithdrawalTransaction`, but we check it again in case the respected game type has changed since
// the withdrawal was proven.
require(disputeGameProxy.gameType().raw() == respectedGameType.raw(), "OptimismPortal: invalid game type");
// The game must have been created after `respectedGameTypeUpdatedAt`. This is to prevent users from creating
// invalid disputes against a deployed game type while the off-chain challenge agents are not watching.
require(
createdAt >= respectedGameTypeUpdatedAt,
"OptimismPortal: dispute game created before respected game type was updated"
);
// Before a withdrawal can be finalized, the dispute game it was proven against must have been
// resolved for at least `DISPUTE_GAME_FINALITY_DELAY_SECONDS`. This is to allow for manual
// intervention in the event that a dispute game is resolved incorrectly.
require(
block.timestamp - disputeGameProxy.resolvedAt().raw() > DISPUTE_GAME_FINALITY_DELAY_SECONDS,
"OptimismPortal: output proposal in air-gap"
);
// Check that this withdrawal has not already been finalized, this is replay protection.
require(!finalizedWithdrawals[_withdrawalHash], "OptimismPortal: withdrawal has already been finalized");
}
/// @notice External getter for the number of proof submitters for a withdrawal hash.
/// @param _withdrawalHash Hash of the withdrawal.
/// @return The number of proof submitters for the withdrawal hash.
function numProofSubmitters(bytes32 _withdrawalHash) external view returns (uint256) {
return proofSubmitters[_withdrawalHash].length;
}
}