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

     1  // SPDX-License-Identifier: MIT
     2  pragma solidity 0.8.15;
     3  
     4  import { ClonesWithImmutableArgs } from "@cwia/ClonesWithImmutableArgs.sol";
     5  import { OwnableUpgradeable } from "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol";
     6  import { ISemver } from "src/universal/ISemver.sol";
     7  
     8  import { IDisputeGame } from "src/dispute/interfaces/IDisputeGame.sol";
     9  import { IDisputeGameFactory } from "src/dispute/interfaces/IDisputeGameFactory.sol";
    10  
    11  import { LibGameId } from "src/dispute/lib/LibGameId.sol";
    12  
    13  import "src/libraries/DisputeTypes.sol";
    14  import "src/libraries/DisputeErrors.sol";
    15  
    16  /// @title DisputeGameFactory
    17  /// @notice A factory contract for creating `IDisputeGame` contracts. All created dispute games are stored in both a
    18  ///         mapping and an append only array. The timestamp of the creation time of the dispute game is packed tightly
    19  ///         into the storage slot with the address of the dispute game to make offchain discoverability of playable
    20  ///         dispute games easier.
    21  contract DisputeGameFactory is OwnableUpgradeable, IDisputeGameFactory, ISemver {
    22      /// @dev Allows for the creation of clone proxies with immutable arguments.
    23      using ClonesWithImmutableArgs for address;
    24  
    25      /// @notice Semantic version.
    26      /// @custom:semver 0.2.0
    27      string public constant version = "0.2.0";
    28  
    29      /// @inheritdoc IDisputeGameFactory
    30      mapping(GameType => IDisputeGame) public gameImpls;
    31  
    32      /// @inheritdoc IDisputeGameFactory
    33      mapping(GameType => uint256) public initBonds;
    34  
    35      /// @notice Mapping of a hash of `gameType || rootClaim || extraData` to the deployed `IDisputeGame` clone (where
    36      //          `||` denotes concatenation).
    37      mapping(Hash => GameId) internal _disputeGames;
    38  
    39      /// @notice An append-only array of disputeGames that have been created. Used by offchain game solvers to
    40      ///         efficiently track dispute games.
    41      GameId[] internal _disputeGameList;
    42  
    43      /// @notice Constructs a new DisputeGameFactory contract.
    44      constructor() OwnableUpgradeable() {
    45          initialize(address(0));
    46      }
    47  
    48      /// @notice Initializes the contract.
    49      /// @param _owner The owner of the contract.
    50      function initialize(address _owner) public initializer {
    51          __Ownable_init();
    52          _transferOwnership(_owner);
    53      }
    54  
    55      /// @inheritdoc IDisputeGameFactory
    56      function gameCount() external view returns (uint256 gameCount_) {
    57          gameCount_ = _disputeGameList.length;
    58      }
    59  
    60      /// @inheritdoc IDisputeGameFactory
    61      function games(
    62          GameType _gameType,
    63          Claim _rootClaim,
    64          bytes calldata _extraData
    65      )
    66          external
    67          view
    68          returns (IDisputeGame proxy_, Timestamp timestamp_)
    69      {
    70          Hash uuid = getGameUUID(_gameType, _rootClaim, _extraData);
    71          (, timestamp_, proxy_) = _disputeGames[uuid].unpack();
    72      }
    73  
    74      /// @inheritdoc IDisputeGameFactory
    75      function gameAtIndex(uint256 _index)
    76          external
    77          view
    78          returns (GameType gameType_, Timestamp timestamp_, IDisputeGame proxy_)
    79      {
    80          (gameType_, timestamp_, proxy_) = _disputeGameList[_index].unpack();
    81      }
    82  
    83      /// @inheritdoc IDisputeGameFactory
    84      function create(
    85          GameType _gameType,
    86          Claim _rootClaim,
    87          bytes calldata _extraData
    88      )
    89          external
    90          payable
    91          returns (IDisputeGame proxy_)
    92      {
    93          // Grab the implementation contract for the given `GameType`.
    94          IDisputeGame impl = gameImpls[_gameType];
    95  
    96          // If there is no implementation to clone for the given `GameType`, revert.
    97          if (address(impl) == address(0)) revert NoImplementation(_gameType);
    98  
    99          // If the required initialization bond is not met, revert.
   100          if (msg.value < initBonds[_gameType]) revert InsufficientBond();
   101  
   102          // Get the hash of the parent block.
   103          bytes32 parentHash = blockhash(block.number - 1);
   104  
   105          // Clone the implementation contract and initialize it with the given parameters.
   106          proxy_ = IDisputeGame(address(impl).clone(abi.encodePacked(_rootClaim, parentHash, _extraData)));
   107          proxy_.initialize{ value: msg.value }();
   108  
   109          // Compute the unique identifier for the dispute game.
   110          Hash uuid = getGameUUID(_gameType, _rootClaim, _extraData);
   111  
   112          // If a dispute game with the same UUID already exists, revert.
   113          if (GameId.unwrap(_disputeGames[uuid]) != bytes32(0)) revert GameAlreadyExists(uuid);
   114  
   115          // Pack the game ID.
   116          GameId id = LibGameId.pack(_gameType, Timestamp.wrap(uint64(block.timestamp)), proxy_);
   117  
   118          // Store the dispute game id in the mapping & emit the `DisputeGameCreated` event.
   119          _disputeGames[uuid] = id;
   120          _disputeGameList.push(id);
   121          emit DisputeGameCreated(address(proxy_), _gameType, _rootClaim);
   122      }
   123  
   124      /// @inheritdoc IDisputeGameFactory
   125      function getGameUUID(
   126          GameType _gameType,
   127          Claim _rootClaim,
   128          bytes calldata _extraData
   129      )
   130          public
   131          pure
   132          returns (Hash uuid_)
   133      {
   134          uuid_ = Hash.wrap(keccak256(abi.encode(_gameType, _rootClaim, _extraData)));
   135      }
   136  
   137      /// @inheritdoc IDisputeGameFactory
   138      function findLatestGames(
   139          GameType _gameType,
   140          uint256 _start,
   141          uint256 _n
   142      )
   143          external
   144          view
   145          returns (GameSearchResult[] memory games_)
   146      {
   147          // If the `_start` index is greater than or equal to the game array length or `_n == 0`, return an empty array.
   148          if (_start >= _disputeGameList.length || _n == 0) return games_;
   149  
   150          // Allocate enough memory for the full array, but start the array's length at `0`. We may not use all of the
   151          // memory allocated, but we don't know ahead of time the final size of the array.
   152          assembly {
   153              games_ := mload(0x40)
   154              mstore(0x40, add(games_, add(0x20, shl(0x05, _n))))
   155          }
   156  
   157          // Perform a reverse linear search for the `_n` most recent games of type `_gameType`.
   158          for (uint256 i = _start; i >= 0 && i <= _start;) {
   159              GameId id = _disputeGameList[i];
   160              (GameType gameType, Timestamp timestamp, IDisputeGame proxy) = id.unpack();
   161  
   162              if (gameType.raw() == _gameType.raw()) {
   163                  // Increase the size of the `games_` array by 1.
   164                  // SAFETY: We can safely lazily allocate memory here because we pre-allocated enough memory for the max
   165                  //         possible size of the array.
   166                  assembly {
   167                      mstore(games_, add(mload(games_), 0x01))
   168                  }
   169  
   170                  bytes memory extraData = proxy.extraData();
   171                  Claim rootClaim = proxy.rootClaim();
   172                  games_[games_.length - 1] = GameSearchResult({
   173                      index: i,
   174                      metadata: id,
   175                      timestamp: timestamp,
   176                      rootClaim: rootClaim,
   177                      extraData: extraData
   178                  });
   179                  if (games_.length >= _n) break;
   180              }
   181  
   182              unchecked {
   183                  i--;
   184              }
   185          }
   186      }
   187  
   188      /// @inheritdoc IDisputeGameFactory
   189      function setImplementation(GameType _gameType, IDisputeGame _impl) external onlyOwner {
   190          gameImpls[_gameType] = _impl;
   191          emit ImplementationSet(address(_impl), _gameType);
   192      }
   193  
   194      /// @inheritdoc IDisputeGameFactory
   195      function setInitBond(GameType _gameType, uint256 _initBond) external onlyOwner {
   196          initBonds[_gameType] = _initBond;
   197          emit InitBondUpdated(_gameType, _initBond);
   198      }
   199  }