/
DisputeGameFactory.sol
147 lines (121 loc) · 5.37 KB
/
DisputeGameFactory.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
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.15;
import { ClonesWithImmutableArgs } from "@cwia/ClonesWithImmutableArgs.sol";
import { OwnableUpgradeable } from "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol";
import { ISemver } from "src/universal/ISemver.sol";
import { IDisputeGame } from "src/dispute/interfaces/IDisputeGame.sol";
import { IDisputeGameFactory } from "src/dispute/interfaces/IDisputeGameFactory.sol";
import { LibGameId } from "src/dispute/lib/LibGameId.sol";
import "src/libraries/DisputeTypes.sol";
import "src/libraries/DisputeErrors.sol";
/// @title DisputeGameFactory
/// @notice A factory contract for creating `IDisputeGame` contracts. All created dispute games
/// are stored in both a mapping and an append only array. The timestamp of the creation
/// time of the dispute game is packed tightly into the storage slot with the address of
/// the dispute game. This is to make offchain discoverability of playable dispute games
/// easier.
contract DisputeGameFactory is OwnableUpgradeable, IDisputeGameFactory, ISemver {
/// @dev Allows for the creation of clone proxies with immutable arguments.
using ClonesWithImmutableArgs for address;
/// @notice Semantic version.
/// @custom:semver 0.0.8
string public constant version = "0.0.8";
/// @inheritdoc IDisputeGameFactory
mapping(GameType => IDisputeGame) public gameImpls;
/// @inheritdoc IDisputeGameFactory
mapping(GameType => uint256) public initBonds;
/// @notice Mapping of a hash of `gameType || rootClaim || extraData` to
/// the deployed `IDisputeGame` clone.
/// @dev Note: `||` denotes concatenation.
mapping(Hash => GameId) internal _disputeGames;
/// @notice an append-only array of disputeGames that have been created.
/// @dev this accessor is used by offchain game solvers to efficiently
/// track dispute games
GameId[] internal _disputeGameList;
/// @notice constructs a new DisputeGameFactory contract.
constructor() OwnableUpgradeable() {
initialize(address(0));
}
/// @notice Initializes the contract.
/// @param _owner The owner of the contract.
function initialize(address _owner) public initializer {
__Ownable_init();
_transferOwnership(_owner);
}
/// @inheritdoc IDisputeGameFactory
function gameCount() external view returns (uint256 gameCount_) {
gameCount_ = _disputeGameList.length;
}
/// @inheritdoc IDisputeGameFactory
function games(
GameType _gameType,
Claim _rootClaim,
bytes calldata _extraData
)
external
view
returns (IDisputeGame proxy_, Timestamp timestamp_)
{
Hash uuid = getGameUUID(_gameType, _rootClaim, _extraData);
(, timestamp_, proxy_) = _disputeGames[uuid].unpack();
}
/// @inheritdoc IDisputeGameFactory
function gameAtIndex(uint256 _index)
external
view
returns (GameType gameType_, Timestamp timestamp_, IDisputeGame proxy_)
{
(gameType_, timestamp_, proxy_) = _disputeGameList[_index].unpack();
}
/// @inheritdoc IDisputeGameFactory
function create(
GameType _gameType,
Claim _rootClaim,
bytes calldata _extraData
)
external
payable
returns (IDisputeGame proxy_)
{
// Grab the implementation contract for the given `GameType`.
IDisputeGame impl = gameImpls[_gameType];
// If there is no implementation to clone for the given `GameType`, revert.
if (address(impl) == address(0)) revert NoImplementation(_gameType);
// If the required initialization bond is not met, revert.
if (msg.value < initBonds[_gameType]) revert InsufficientBond();
// Clone the implementation contract and initialize it with the given parameters.
proxy_ = IDisputeGame(address(impl).clone(abi.encodePacked(_rootClaim, _extraData)));
proxy_.initialize{ value: msg.value }();
// Compute the unique identifier for the dispute game.
Hash uuid = getGameUUID(_gameType, _rootClaim, _extraData);
// If a dispute game with the same UUID already exists, revert.
if (GameId.unwrap(_disputeGames[uuid]) != bytes32(0)) revert GameAlreadyExists(uuid);
GameId id = LibGameId.pack(_gameType, Timestamp.wrap(uint64(block.timestamp)), proxy_);
// Store the dispute game id in the mapping & emit the `DisputeGameCreated` event.
_disputeGames[uuid] = id;
_disputeGameList.push(id);
emit DisputeGameCreated(address(proxy_), _gameType, _rootClaim);
}
/// @inheritdoc IDisputeGameFactory
function getGameUUID(
GameType _gameType,
Claim _rootClaim,
bytes calldata _extraData
)
public
pure
returns (Hash uuid_)
{
uuid_ = Hash.wrap(keccak256(abi.encode(_gameType, _rootClaim, _extraData)));
}
/// @inheritdoc IDisputeGameFactory
function setImplementation(GameType _gameType, IDisputeGame _impl) external onlyOwner {
gameImpls[_gameType] = _impl;
emit ImplementationSet(address(_impl), _gameType);
}
/// @inheritdoc IDisputeGameFactory
function setInitBond(GameType _gameType, uint256 _initBond) external onlyOwner {
initBonds[_gameType] = _initBond;
emit InitBondUpdated(_gameType, _initBond);
}
}