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 }