@@ -43,9 +43,6 @@ contract AnchorStateRegistry is Initializable, ISemver {
4343 /// @notice The starting anchor root.
4444 OutputRoot internal startingAnchorRoot;
4545
46- /// @notice Emitted when an anchor state is not updated.
47- event AnchorNotUpdated (IFaultDisputeGame indexed game , string reason );
48-
4946 /// @notice Emitted when an anchor state is updated.
5047 event AnchorUpdated (IFaultDisputeGame indexed game );
5148
@@ -74,6 +71,12 @@ contract AnchorStateRegistry is Initializable, ISemver {
7471 startingAnchorRoot = _startingAnchorRoot;
7572 }
7673
74+ /// @notice Returns the respected game type.
75+ /// @return The respected game type.
76+ function respectedGameType () public view returns (GameType) {
77+ return portal.respectedGameType ();
78+ }
79+
7780 /// @custom:legacy
7881 /// @notice Returns the anchor root. Note that this is a legacy deprecated function and will
7982 /// be removed in a future release. Use getAnchorRoot() instead. Anchor roots are no
@@ -114,7 +117,7 @@ contract AnchorStateRegistry is Initializable, ISemver {
114117 /// @param _game The game to check.
115118 /// @return Whether the game is of a respected game type.
116119 function isGameRespected (IDisputeGame _game ) public view returns (bool ) {
117- return _game.gameType (). raw () == portal. respectedGameType (). raw ();
120+ return _game.wasRespectedGameTypeWhenCreated ();
118121 }
119122
120123 /// @notice Determines whether a game is blacklisted.
@@ -131,6 +134,21 @@ contract AnchorStateRegistry is Initializable, ISemver {
131134 return _game.createdAt ().raw () < portal.respectedGameTypeUpdatedAt ();
132135 }
133136
137+ /// @notice Returns whether a game is resolved.
138+ /// @param _game The game to check.
139+ /// @return Whether the game is resolved.
140+ function isGameResolved (IDisputeGame _game ) public view returns (bool ) {
141+ return _game.resolvedAt ().raw () != 0
142+ && (_game.status () == GameStatus.DEFENDER_WINS || _game.status () == GameStatus.CHALLENGER_WINS);
143+ }
144+
145+ /// @notice Returns whether a game is beyond the airgap period.
146+ /// @param _game The game to check.
147+ /// @return Whether the game is beyond the airgap period.
148+ function isGameBeyondAirgap (IDisputeGame _game ) public view returns (bool ) {
149+ return block .timestamp - _game.resolvedAt ().raw () > portal.disputeGameFinalityDelaySeconds ();
150+ }
151+
134152 /// @notice Determines whether a game resolved properly and the game was not subject to any
135153 /// invalidation conditions. The root claim of a proper game IS NOT guaranteed to be
136154 /// valid. The root claim of a proper game CAN BE incorrect and still be a proper game.
@@ -160,56 +178,73 @@ contract AnchorStateRegistry is Initializable, ISemver {
160178 return (true , "" );
161179 }
162180
163- /// @notice Callable by FaultDisputeGame contracts to update the anchor state. Pulls the anchor state directly from
164- /// the FaultDisputeGame contract and stores it in the registry if the new anchor state is valid and the
165- /// state is newer than the current anchor state.
166- function tryUpdateAnchorState () external {
167- // Grab the game.
168- IFaultDisputeGame game = IFaultDisputeGame (msg .sender );
169-
170- // Check if the game is a proper game.
171- (bool isProper , string memory reason ) = isGameProper (game);
172- if (! isProper) {
173- emit AnchorNotUpdated (game, reason);
174- return ;
181+ /// @notice Returns whether a game is finalized.
182+ /// @param _game The game to check.
183+ /// @return Whether the game is finalized.
184+ /// @return Reason why the game is not finalized.
185+ function isGameFinalized (IDisputeGame _game ) public view returns (bool , string memory ) {
186+ // Game must be resolved.
187+ if (! isGameResolved (_game)) {
188+ return (false , "game not resolved " );
175189 }
176190
177- // Must be a game that resolved in favor of the state.
178- if (game.status () != GameStatus.DEFENDER_WINS) {
179- emit AnchorNotUpdated (game, "game not defender wins " );
180- return ;
191+ // Game must be beyond the airgap period.
192+ if (! isGameBeyondAirgap (_game)) {
193+ return (false , "game not beyond airgap period " );
181194 }
182195
183- // No need to update anything if the anchor state is already newer.
184- (, uint256 anchorL2BlockNumber ) = getAnchorRoot ();
185- if (game.l2BlockNumber () <= anchorL2BlockNumber) {
186- emit AnchorNotUpdated (game, "game not newer than anchor state " );
187- return ;
196+ return (true , "" );
197+ }
198+
199+ /// @notice Returns whether a game's root claim is valid.
200+ /// @param _game The game to check.
201+ /// @return Whether the game's root claim is valid.
202+ /// @return Reason why the game's root claim is not valid.
203+ function isGameClaimValid (IDisputeGame _game ) public view returns (bool , string memory ) {
204+ // Game must be a proper game.
205+ (bool properGame , string memory notProperGameReason ) = isGameProper (_game);
206+ if (! properGame) {
207+ return (false , notProperGameReason);
188208 }
189209
190- // Update the anchor game.
191- anchorGame = game;
192- emit AnchorUpdated (game);
193- }
210+ // Game must be finalized.
211+ (bool finalized , string memory notFinalizedReason ) = isGameFinalized (_game);
212+ if (! finalized) {
213+ return (false , notFinalizedReason);
214+ }
215+
216+ // Game must be resolved in favor of the defender.
217+ if (_game.status () != GameStatus.DEFENDER_WINS) {
218+ return (false , "game not defender wins " );
219+ }
194220
195- /// @notice Sets the anchor state given the game.
196- /// @param _game The game to set the anchor state for.
197- function setAnchorState (IFaultDisputeGame _game ) external {
198- if (msg .sender != superchainConfig.guardian ()) revert Unauthorized ();
221+ return (true , "" );
222+ }
199223
200- // Check if the game is a proper game.
201- (bool isProper , string memory reason ) = isGameProper (_game);
202- if (! isProper) {
224+ /// @notice Updates the anchor game.
225+ /// @param _game New candidate anchor game.
226+ function setAnchorState (IDisputeGame _game ) public {
227+ // Convert game to FaultDisputeGame.
228+ // We can't use FaultDisputeGame in the interface because this function is called from the
229+ // FaultDisputeGame contract which can't import IFaultDisputeGame by convention. We should
230+ // likely introduce a new interface (e.g., StateDisputeGame) that can act as a more useful
231+ // version of IDisputeGame in the future.
232+ IFaultDisputeGame game = IFaultDisputeGame (address (_game));
233+
234+ // Check if the candidate game is valid.
235+ (bool valid , string memory reason ) = isGameClaimValid (game);
236+ if (! valid) {
203237 revert InvalidAnchorGame (reason);
204238 }
205239
206- // The game must have resolved in favor of the root claim.
207- if (_game.status () != GameStatus.DEFENDER_WINS) {
208- revert InvalidAnchorGame ("game not defender wins " );
240+ // No need to update anything if the anchor state is already newer.
241+ (, uint256 anchorL2BlockNumber ) = getAnchorRoot ();
242+ if (game.l2BlockNumber () <= anchorL2BlockNumber) {
243+ revert InvalidAnchorGame ("game not newer than anchor state " );
209244 }
210245
211246 // Update the anchor game.
212- anchorGame = _game ;
213- emit AnchorUpdated (_game );
247+ anchorGame = game ;
248+ emit AnchorUpdated (game );
214249 }
215250}
0 commit comments