github.com/ethereum-optimism/optimism@v1.7.2/packages/contracts-bedrock/src/L1/OptimismPortal.sol (about)

     1  // SPDX-License-Identifier: MIT
     2  pragma solidity 0.8.15;
     3  
     4  import { Initializable } from "@openzeppelin/contracts/proxy/utils/Initializable.sol";
     5  import { SafeCall } from "src/libraries/SafeCall.sol";
     6  import { L2OutputOracle } from "src/L1/L2OutputOracle.sol";
     7  import { SystemConfig } from "src/L1/SystemConfig.sol";
     8  import { SuperchainConfig } from "src/L1/SuperchainConfig.sol";
     9  import { Constants } from "src/libraries/Constants.sol";
    10  import { Types } from "src/libraries/Types.sol";
    11  import { Hashing } from "src/libraries/Hashing.sol";
    12  import { SecureMerkleTrie } from "src/libraries/trie/SecureMerkleTrie.sol";
    13  import { AddressAliasHelper } from "src/vendor/AddressAliasHelper.sol";
    14  import { ResourceMetering } from "src/L1/ResourceMetering.sol";
    15  import { ISemver } from "src/universal/ISemver.sol";
    16  import { Constants } from "src/libraries/Constants.sol";
    17  
    18  /// @custom:proxied
    19  /// @title OptimismPortal
    20  /// @notice The OptimismPortal is a low-level contract responsible for passing messages between L1
    21  ///         and L2. Messages sent directly to the OptimismPortal have no form of replayability.
    22  ///         Users are encouraged to use the L1CrossDomainMessenger for a higher-level interface.
    23  contract OptimismPortal is Initializable, ResourceMetering, ISemver {
    24      /// @notice Represents a proven withdrawal.
    25      /// @custom:field outputRoot    Root of the L2 output this was proven against.
    26      /// @custom:field timestamp     Timestamp at whcih the withdrawal was proven.
    27      /// @custom:field l2OutputIndex Index of the output this was proven against.
    28      struct ProvenWithdrawal {
    29          bytes32 outputRoot;
    30          uint128 timestamp;
    31          uint128 l2OutputIndex;
    32      }
    33  
    34      /// @notice Version of the deposit event.
    35      uint256 internal constant DEPOSIT_VERSION = 0;
    36  
    37      /// @notice The L2 gas limit set when eth is deposited using the receive() function.
    38      uint64 internal constant RECEIVE_DEFAULT_GAS_LIMIT = 100_000;
    39  
    40      /// @notice Address of the L2 account which initiated a withdrawal in this transaction.
    41      ///         If the of this variable is the default L2 sender address, then we are NOT inside of
    42      ///         a call to finalizeWithdrawalTransaction.
    43      address public l2Sender;
    44  
    45      /// @notice A list of withdrawal hashes which have been successfully finalized.
    46      mapping(bytes32 => bool) public finalizedWithdrawals;
    47  
    48      /// @notice A mapping of withdrawal hashes to `ProvenWithdrawal` data.
    49      mapping(bytes32 => ProvenWithdrawal) public provenWithdrawals;
    50  
    51      /// @custom:legacy
    52      /// @custom:spacer paused
    53      /// @notice Spacer for backwards compatibility.
    54      bool private spacer_53_0_1;
    55  
    56      /// @notice Contract of the Superchain Config.
    57      SuperchainConfig public superchainConfig;
    58  
    59      /// @notice Contract of the L2OutputOracle.
    60      /// @custom:network-specific
    61      L2OutputOracle public l2Oracle;
    62  
    63      /// @notice Contract of the SystemConfig.
    64      /// @custom:network-specific
    65      SystemConfig public systemConfig;
    66  
    67      /// @notice Emitted when a transaction is deposited from L1 to L2.
    68      ///         The parameters of this event are read by the rollup node and used to derive deposit
    69      ///         transactions on L2.
    70      /// @param from       Address that triggered the deposit transaction.
    71      /// @param to         Address that the deposit transaction is directed to.
    72      /// @param version    Version of this deposit transaction event.
    73      /// @param opaqueData ABI encoded deposit data to be parsed off-chain.
    74      event TransactionDeposited(address indexed from, address indexed to, uint256 indexed version, bytes opaqueData);
    75  
    76      /// @notice Emitted when a withdrawal transaction is proven.
    77      /// @param withdrawalHash Hash of the withdrawal transaction.
    78      /// @param from           Address that triggered the withdrawal transaction.
    79      /// @param to             Address that the withdrawal transaction is directed to.
    80      event WithdrawalProven(bytes32 indexed withdrawalHash, address indexed from, address indexed to);
    81  
    82      /// @notice Emitted when a withdrawal transaction is finalized.
    83      /// @param withdrawalHash Hash of the withdrawal transaction.
    84      /// @param success        Whether the withdrawal transaction was successful.
    85      event WithdrawalFinalized(bytes32 indexed withdrawalHash, bool success);
    86  
    87      /// @notice Reverts when paused.
    88      modifier whenNotPaused() {
    89          require(paused() == false, "OptimismPortal: paused");
    90          _;
    91      }
    92  
    93      /// @notice Semantic version.
    94      /// @custom:semver 2.5.0
    95      string public constant version = "2.5.0";
    96  
    97      /// @notice Constructs the OptimismPortal contract.
    98      constructor() {
    99          initialize({
   100              _l2Oracle: L2OutputOracle(address(0)),
   101              _systemConfig: SystemConfig(address(0)),
   102              _superchainConfig: SuperchainConfig(address(0))
   103          });
   104      }
   105  
   106      /// @notice Initializer.
   107      /// @param _l2Oracle Contract of the L2OutputOracle.
   108      /// @param _systemConfig Contract of the SystemConfig.
   109      /// @param _superchainConfig Contract of the SuperchainConfig.
   110      function initialize(
   111          L2OutputOracle _l2Oracle,
   112          SystemConfig _systemConfig,
   113          SuperchainConfig _superchainConfig
   114      )
   115          public
   116          initializer
   117      {
   118          l2Oracle = _l2Oracle;
   119          systemConfig = _systemConfig;
   120          superchainConfig = _superchainConfig;
   121          if (l2Sender == address(0)) {
   122              l2Sender = Constants.DEFAULT_L2_SENDER;
   123          }
   124          __ResourceMetering_init();
   125      }
   126  
   127      /// @notice Getter function for the contract of the L2OutputOracle on this chain.
   128      ///         Public getter is legacy and will be removed in the future. Use `l2Oracle()` instead.
   129      /// @return Contract of the L2OutputOracle on this chain.
   130      /// @custom:legacy
   131      function L2_ORACLE() external view returns (L2OutputOracle) {
   132          return l2Oracle;
   133      }
   134  
   135      /// @notice Getter function for the contract of the SystemConfig on this chain.
   136      ///         Public getter is legacy and will be removed in the future. Use `systemConfig()` instead.
   137      /// @return Contract of the SystemConfig on this chain.
   138      /// @custom:legacy
   139      function SYSTEM_CONFIG() external view returns (SystemConfig) {
   140          return systemConfig;
   141      }
   142  
   143      /// @notice Getter function for the address of the guardian.
   144      ///         Public getter is legacy and will be removed in the future. Use `SuperchainConfig.guardian()` instead.
   145      /// @return Address of the guardian.
   146      /// @custom:legacy
   147      function GUARDIAN() external view returns (address) {
   148          return guardian();
   149      }
   150  
   151      /// @notice Getter function for the address of the guardian.
   152      ///         Public getter is legacy and will be removed in the future. Use `SuperchainConfig.guardian()` instead.
   153      /// @return Address of the guardian.
   154      /// @custom:legacy
   155      function guardian() public view returns (address) {
   156          return superchainConfig.guardian();
   157      }
   158  
   159      /// @notice Getter for the current paused status.
   160      /// @return paused_ Whether or not the contract is paused.
   161      function paused() public view returns (bool paused_) {
   162          paused_ = superchainConfig.paused();
   163      }
   164  
   165      /// @notice Computes the minimum gas limit for a deposit.
   166      ///         The minimum gas limit linearly increases based on the size of the calldata.
   167      ///         This is to prevent users from creating L2 resource usage without paying for it.
   168      ///         This function can be used when interacting with the portal to ensure forwards
   169      ///         compatibility.
   170      /// @param _byteCount Number of bytes in the calldata.
   171      /// @return The minimum gas limit for a deposit.
   172      function minimumGasLimit(uint64 _byteCount) public pure returns (uint64) {
   173          return _byteCount * 16 + 21000;
   174      }
   175  
   176      /// @notice Accepts value so that users can send ETH directly to this contract and have the
   177      ///         funds be deposited to their address on L2. This is intended as a convenience
   178      ///         function for EOAs. Contracts should call the depositTransaction() function directly
   179      ///         otherwise any deposited funds will be lost due to address aliasing.
   180      receive() external payable {
   181          depositTransaction(msg.sender, msg.value, RECEIVE_DEFAULT_GAS_LIMIT, false, bytes(""));
   182      }
   183  
   184      /// @notice Accepts ETH value without triggering a deposit to L2.
   185      ///         This function mainly exists for the sake of the migration between the legacy
   186      ///         Optimism system and Bedrock.
   187      function donateETH() external payable {
   188          // Intentionally empty.
   189      }
   190  
   191      /// @notice Getter for the resource config.
   192      ///         Used internally by the ResourceMetering contract.
   193      ///         The SystemConfig is the source of truth for the resource config.
   194      /// @return ResourceMetering ResourceConfig
   195      function _resourceConfig() internal view override returns (ResourceMetering.ResourceConfig memory) {
   196          return systemConfig.resourceConfig();
   197      }
   198  
   199      /// @notice Proves a withdrawal transaction.
   200      /// @param _tx              Withdrawal transaction to finalize.
   201      /// @param _l2OutputIndex   L2 output index to prove against.
   202      /// @param _outputRootProof Inclusion proof of the L2ToL1MessagePasser contract's storage root.
   203      /// @param _withdrawalProof Inclusion proof of the withdrawal in L2ToL1MessagePasser contract.
   204      function proveWithdrawalTransaction(
   205          Types.WithdrawalTransaction memory _tx,
   206          uint256 _l2OutputIndex,
   207          Types.OutputRootProof calldata _outputRootProof,
   208          bytes[] calldata _withdrawalProof
   209      )
   210          external
   211          whenNotPaused
   212      {
   213          // Prevent users from creating a deposit transaction where this address is the message
   214          // sender on L2. Because this is checked here, we do not need to check again in
   215          // `finalizeWithdrawalTransaction`.
   216          require(_tx.target != address(this), "OptimismPortal: you cannot send messages to the portal contract");
   217  
   218          // Get the output root and load onto the stack to prevent multiple mloads. This will
   219          // revert if there is no output root for the given block number.
   220          bytes32 outputRoot = l2Oracle.getL2Output(_l2OutputIndex).outputRoot;
   221  
   222          // Verify that the output root can be generated with the elements in the proof.
   223          require(
   224              outputRoot == Hashing.hashOutputRootProof(_outputRootProof), "OptimismPortal: invalid output root proof"
   225          );
   226  
   227          // Load the ProvenWithdrawal into memory, using the withdrawal hash as a unique identifier.
   228          bytes32 withdrawalHash = Hashing.hashWithdrawal(_tx);
   229          ProvenWithdrawal memory provenWithdrawal = provenWithdrawals[withdrawalHash];
   230  
   231          // We generally want to prevent users from proving the same withdrawal multiple times
   232          // because each successive proof will update the timestamp. A malicious user can take
   233          // advantage of this to prevent other users from finalizing their withdrawal. However,
   234          // since withdrawals are proven before an output root is finalized, we need to allow users
   235          // to re-prove their withdrawal only in the case that the output root for their specified
   236          // output index has been updated.
   237          require(
   238              provenWithdrawal.timestamp == 0
   239                  || l2Oracle.getL2Output(provenWithdrawal.l2OutputIndex).outputRoot != provenWithdrawal.outputRoot,
   240              "OptimismPortal: withdrawal hash has already been proven"
   241          );
   242  
   243          // Compute the storage slot of the withdrawal hash in the L2ToL1MessagePasser contract.
   244          // Refer to the Solidity documentation for more information on how storage layouts are
   245          // computed for mappings.
   246          bytes32 storageKey = keccak256(
   247              abi.encode(
   248                  withdrawalHash,
   249                  uint256(0) // The withdrawals mapping is at the first slot in the layout.
   250              )
   251          );
   252  
   253          // Verify that the hash of this withdrawal was stored in the L2toL1MessagePasser contract
   254          // on L2. If this is true, under the assumption that the SecureMerkleTrie does not have
   255          // bugs, then we know that this withdrawal was actually triggered on L2 and can therefore
   256          // be relayed on L1.
   257          require(
   258              SecureMerkleTrie.verifyInclusionProof(
   259                  abi.encode(storageKey), hex"01", _withdrawalProof, _outputRootProof.messagePasserStorageRoot
   260              ),
   261              "OptimismPortal: invalid withdrawal inclusion proof"
   262          );
   263  
   264          // Designate the withdrawalHash as proven by storing the `outputRoot`, `timestamp`, and
   265          // `l2BlockNumber` in the `provenWithdrawals` mapping. A `withdrawalHash` can only be
   266          // proven once unless it is submitted again with a different outputRoot.
   267          provenWithdrawals[withdrawalHash] = ProvenWithdrawal({
   268              outputRoot: outputRoot,
   269              timestamp: uint128(block.timestamp),
   270              l2OutputIndex: uint128(_l2OutputIndex)
   271          });
   272  
   273          // Emit a `WithdrawalProven` event.
   274          emit WithdrawalProven(withdrawalHash, _tx.sender, _tx.target);
   275      }
   276  
   277      /// @notice Finalizes a withdrawal transaction.
   278      /// @param _tx Withdrawal transaction to finalize.
   279      function finalizeWithdrawalTransaction(Types.WithdrawalTransaction memory _tx) external whenNotPaused {
   280          // Make sure that the l2Sender has not yet been set. The l2Sender is set to a value other
   281          // than the default value when a withdrawal transaction is being finalized. This check is
   282          // a defacto reentrancy guard.
   283          require(
   284              l2Sender == Constants.DEFAULT_L2_SENDER, "OptimismPortal: can only trigger one withdrawal per transaction"
   285          );
   286  
   287          // Grab the proven withdrawal from the `provenWithdrawals` map.
   288          bytes32 withdrawalHash = Hashing.hashWithdrawal(_tx);
   289          ProvenWithdrawal memory provenWithdrawal = provenWithdrawals[withdrawalHash];
   290  
   291          // A withdrawal can only be finalized if it has been proven. We know that a withdrawal has
   292          // been proven at least once when its timestamp is non-zero. Unproven withdrawals will have
   293          // a timestamp of zero.
   294          require(provenWithdrawal.timestamp != 0, "OptimismPortal: withdrawal has not been proven yet");
   295  
   296          // As a sanity check, we make sure that the proven withdrawal's timestamp is greater than
   297          // starting timestamp inside the L2OutputOracle. Not strictly necessary but extra layer of
   298          // safety against weird bugs in the proving step.
   299          require(
   300              provenWithdrawal.timestamp >= l2Oracle.startingTimestamp(),
   301              "OptimismPortal: withdrawal timestamp less than L2 Oracle starting timestamp"
   302          );
   303  
   304          // A proven withdrawal must wait at least the finalization period before it can be
   305          // finalized. This waiting period can elapse in parallel with the waiting period for the
   306          // output the withdrawal was proven against. In effect, this means that the minimum
   307          // withdrawal time is proposal submission time + finalization period.
   308          require(
   309              _isFinalizationPeriodElapsed(provenWithdrawal.timestamp),
   310              "OptimismPortal: proven withdrawal finalization period has not elapsed"
   311          );
   312  
   313          // Grab the OutputProposal from the L2OutputOracle, will revert if the output that
   314          // corresponds to the given index has not been proposed yet.
   315          Types.OutputProposal memory proposal = l2Oracle.getL2Output(provenWithdrawal.l2OutputIndex);
   316  
   317          // Check that the output root that was used to prove the withdrawal is the same as the
   318          // current output root for the given output index. An output root may change if it is
   319          // deleted by the challenger address and then re-proposed.
   320          require(
   321              proposal.outputRoot == provenWithdrawal.outputRoot,
   322              "OptimismPortal: output root proven is not the same as current output root"
   323          );
   324  
   325          // Check that the output proposal has also been finalized.
   326          require(
   327              _isFinalizationPeriodElapsed(proposal.timestamp),
   328              "OptimismPortal: output proposal finalization period has not elapsed"
   329          );
   330  
   331          // Check that this withdrawal has not already been finalized, this is replay protection.
   332          require(finalizedWithdrawals[withdrawalHash] == false, "OptimismPortal: withdrawal has already been finalized");
   333  
   334          // Mark the withdrawal as finalized so it can't be replayed.
   335          finalizedWithdrawals[withdrawalHash] = true;
   336  
   337          // Set the l2Sender so contracts know who triggered this withdrawal on L2.
   338          l2Sender = _tx.sender;
   339  
   340          // Trigger the call to the target contract. We use a custom low level method
   341          // SafeCall.callWithMinGas to ensure two key properties
   342          //   1. Target contracts cannot force this call to run out of gas by returning a very large
   343          //      amount of data (and this is OK because we don't care about the returndata here).
   344          //   2. The amount of gas provided to the execution context of the target is at least the
   345          //      gas limit specified by the user. If there is not enough gas in the current context
   346          //      to accomplish this, `callWithMinGas` will revert.
   347          bool success = SafeCall.callWithMinGas(_tx.target, _tx.gasLimit, _tx.value, _tx.data);
   348  
   349          // Reset the l2Sender back to the default value.
   350          l2Sender = Constants.DEFAULT_L2_SENDER;
   351  
   352          // All withdrawals are immediately finalized. Replayability can
   353          // be achieved through contracts built on top of this contract
   354          emit WithdrawalFinalized(withdrawalHash, success);
   355  
   356          // Reverting here is useful for determining the exact gas cost to successfully execute the
   357          // sub call to the target contract if the minimum gas limit specified by the user would not
   358          // be sufficient to execute the sub call.
   359          if (success == false && tx.origin == Constants.ESTIMATION_ADDRESS) {
   360              revert("OptimismPortal: withdrawal failed");
   361          }
   362      }
   363  
   364      /// @notice Accepts deposits of ETH and data, and emits a TransactionDeposited event for use in
   365      ///         deriving deposit transactions. Note that if a deposit is made by a contract, its
   366      ///         address will be aliased when retrieved using `tx.origin` or `msg.sender`. Consider
   367      ///         using the CrossDomainMessenger contracts for a simpler developer experience.
   368      /// @param _to         Target address on L2.
   369      /// @param _value      ETH value to send to the recipient.
   370      /// @param _gasLimit   Amount of L2 gas to purchase by burning gas on L1.
   371      /// @param _isCreation Whether or not the transaction is a contract creation.
   372      /// @param _data       Data to trigger the recipient with.
   373      function depositTransaction(
   374          address _to,
   375          uint256 _value,
   376          uint64 _gasLimit,
   377          bool _isCreation,
   378          bytes memory _data
   379      )
   380          public
   381          payable
   382          metered(_gasLimit)
   383      {
   384          // Just to be safe, make sure that people specify address(0) as the target when doing
   385          // contract creations.
   386          if (_isCreation) {
   387              require(_to == address(0), "OptimismPortal: must send to address(0) when creating a contract");
   388          }
   389  
   390          // Prevent depositing transactions that have too small of a gas limit. Users should pay
   391          // more for more resource usage.
   392          require(_gasLimit >= minimumGasLimit(uint64(_data.length)), "OptimismPortal: gas limit too small");
   393  
   394          // Prevent the creation of deposit transactions that have too much calldata. This gives an
   395          // upper limit on the size of unsafe blocks over the p2p network. 120kb is chosen to ensure
   396          // that the transaction can fit into the p2p network policy of 128kb even though deposit
   397          // transactions are not gossipped over the p2p network.
   398          require(_data.length <= 120_000, "OptimismPortal: data too large");
   399  
   400          // Transform the from-address to its alias if the caller is a contract.
   401          address from = msg.sender;
   402          if (msg.sender != tx.origin) {
   403              from = AddressAliasHelper.applyL1ToL2Alias(msg.sender);
   404          }
   405  
   406          // Compute the opaque data that will be emitted as part of the TransactionDeposited event.
   407          // We use opaque data so that we can update the TransactionDeposited event in the future
   408          // without breaking the current interface.
   409          bytes memory opaqueData = abi.encodePacked(msg.value, _value, _gasLimit, _isCreation, _data);
   410  
   411          // Emit a TransactionDeposited event so that the rollup node can derive a deposit
   412          // transaction for this deposit.
   413          emit TransactionDeposited(from, _to, DEPOSIT_VERSION, opaqueData);
   414      }
   415  
   416      /// @notice Determine if a given output is finalized.
   417      ///         Reverts if the call to l2Oracle.getL2Output reverts.
   418      ///         Returns a boolean otherwise.
   419      /// @param _l2OutputIndex Index of the L2 output to check.
   420      /// @return Whether or not the output is finalized.
   421      function isOutputFinalized(uint256 _l2OutputIndex) external view returns (bool) {
   422          return _isFinalizationPeriodElapsed(l2Oracle.getL2Output(_l2OutputIndex).timestamp);
   423      }
   424  
   425      /// @notice Determines whether the finalization period has elapsed with respect to
   426      ///         the provided block timestamp.
   427      /// @param _timestamp Timestamp to check.
   428      /// @return Whether or not the finalization period has elapsed.
   429      function _isFinalizationPeriodElapsed(uint256 _timestamp) internal view returns (bool) {
   430          return block.timestamp > _timestamp + l2Oracle.FINALIZATION_PERIOD_SECONDS();
   431      }
   432  }