Skip to content

Commit fa19dae

Browse files
feat: incident response improvements
First half of the original incident response improvements PR.
1 parent a622ff3 commit fa19dae

17 files changed

+559
-450
lines changed

packages/contracts-bedrock/interfaces/dispute/IAnchorStateRegistry.sol

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,7 @@ import { GameType, Hash, OutputRoot } from "src/dispute/lib/Types.sol";
1010

1111
interface IAnchorStateRegistry {
1212
error InvalidAnchorGame(string reason);
13-
error Unauthorized();
1413

15-
event AnchorNotUpdated(IFaultDisputeGame indexed game, string reason);
1614
event AnchorUpdated(IFaultDisputeGame indexed game);
1715
event Initialized(uint8 version);
1816

@@ -25,11 +23,15 @@ interface IAnchorStateRegistry {
2523
function isGameBlacklisted(IDisputeGame _game) external view returns (bool);
2624
function isGameRespected(IDisputeGame _game) external view returns (bool);
2725
function isGameRetired(IDisputeGame _game) external view returns (bool);
26+
function isGameResolved(IDisputeGame _game) external view returns (bool);
27+
function isGameBeyondAirgap(IDisputeGame _game) external view returns (bool);
2828
function isGameProper(IDisputeGame _game) external view returns (bool, string memory);
29+
function isGameFinalized(IDisputeGame _game) external view returns (bool, string memory);
30+
function isGameClaimValid(IDisputeGame _game) external view returns (bool, string memory);
2931
function portal() external view returns (IOptimismPortal2);
30-
function setAnchorState(IFaultDisputeGame _game) external;
32+
function respectedGameType() external view returns (GameType);
33+
function setAnchorState(IDisputeGame _game) external;
3134
function superchainConfig() external view returns (ISuperchainConfig);
32-
function tryUpdateAnchorState() external;
3335
function version() external view returns (string memory);
3436

3537
function __constructor__() external;

packages/contracts-bedrock/interfaces/dispute/IDelayedWETH.sol

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,13 +11,13 @@ interface IDelayedWETH {
1111

1212
event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);
1313
event Initialized(uint8 version);
14-
event Unwrap(address indexed src, uint256 wad);
1514

1615
fallback() external payable;
1716
receive() external payable;
1817

1918
function config() external view returns (ISuperchainConfig);
2019
function delay() external view returns (uint256);
20+
function hold(address _guy) external;
2121
function hold(address _guy, uint256 _wad) external;
2222
function initialize(address _owner, ISuperchainConfig _config) external;
2323
function owner() external view returns (address);

packages/contracts-bedrock/interfaces/dispute/IDisputeGame.sol

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,9 @@ interface IDisputeGame is IInitializable {
1414
function gameCreator() external pure returns (address creator_);
1515
function rootClaim() external pure returns (Claim rootClaim_);
1616
function l1Head() external pure returns (Hash l1Head_);
17+
function l2BlockNumber() external pure returns (uint256 l2BlockNumber_);
1718
function extraData() external pure returns (bytes memory extraData_);
1819
function resolve() external returns (GameStatus status_);
1920
function gameData() external view returns (GameType gameType_, Claim rootClaim_, bytes memory extraData_);
21+
function wasRespectedGameTypeWhenCreated() external view returns (bool);
2022
}

packages/contracts-bedrock/interfaces/dispute/IFaultDisputeGame.sol

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import { IDelayedWETH } from "interfaces/dispute/IDelayedWETH.sol";
66
import { IAnchorStateRegistry } from "interfaces/dispute/IAnchorStateRegistry.sol";
77
import { IBigStepper } from "interfaces/dispute/IBigStepper.sol";
88
import { Types } from "src/libraries/Types.sol";
9-
import { GameType, Claim, Position, Clock, Hash, Duration } from "src/dispute/lib/Types.sol";
9+
import { GameType, Claim, Position, Clock, Hash, Duration, BondDistributionMode } from "src/dispute/lib/Types.sol";
1010

1111
interface IFaultDisputeGame is IDisputeGame {
1212
struct ClaimData {
@@ -74,13 +74,19 @@ interface IFaultDisputeGame is IDisputeGame {
7474
error UnexpectedRootClaim(Claim rootClaim);
7575
error UnexpectedString();
7676
error ValidStep();
77+
error InvalidBondDistributionMode();
78+
error GameNotFinalized(string reason);
79+
error GameNotResolved();
80+
error ReservedGameType();
7781

7882
event Move(uint256 indexed parentIndex, Claim indexed claim, address indexed claimant);
83+
event GameClosed(BondDistributionMode bondDistributionMode);
7984

8085
function absolutePrestate() external view returns (Claim absolutePrestate_);
8186
function addLocalData(uint256 _ident, uint256 _execLeafIdx, uint256 _partOffset) external;
8287
function anchorStateRegistry() external view returns (IAnchorStateRegistry registry_);
8388
function attack(Claim _disputed, uint256 _parentIndex, Claim _claim) external payable;
89+
function bondDistributionMode() external view returns (BondDistributionMode);
8490
function challengeRootL2Block(Types.OutputRootProof memory _outputRootProof, bytes memory _headerRLP) external;
8591
function claimCredit(address _recipient) external;
8692
function claimData(uint256)
@@ -98,18 +104,21 @@ interface IFaultDisputeGame is IDisputeGame {
98104
function claimDataLen() external view returns (uint256 len_);
99105
function claims(Hash) external view returns (bool);
100106
function clockExtension() external view returns (Duration clockExtension_);
107+
function closeGame() external;
101108
function credit(address) external view returns (uint256);
102109
function defend(Claim _disputed, uint256 _parentIndex, Claim _claim) external payable;
103110
function getChallengerDuration(uint256 _claimIndex) external view returns (Duration duration_);
104111
function getNumToResolve(uint256 _claimIndex) external view returns (uint256 numRemainingChildren_);
105112
function getRequiredBond(Position _position) external view returns (uint256 requiredBond_);
113+
function hasUnlockedCredit(address) external view returns (bool);
106114
function l2BlockNumber() external pure returns (uint256 l2BlockNumber_);
107115
function l2BlockNumberChallenged() external view returns (bool);
108116
function l2BlockNumberChallenger() external view returns (address);
109117
function l2ChainId() external view returns (uint256 l2ChainId_);
110118
function maxClockDuration() external view returns (Duration maxClockDuration_);
111119
function maxGameDepth() external view returns (uint256 maxGameDepth_);
112120
function move(Claim _disputed, uint256 _challengeIndex, Claim _claim, bool _isAttack) external payable;
121+
function refundModeCredit(address) external view returns (uint256);
113122
function resolutionCheckpoints(uint256)
114123
external
115124
view
@@ -124,6 +133,7 @@ interface IFaultDisputeGame is IDisputeGame {
124133
function subgames(uint256, uint256) external view returns (uint256);
125134
function version() external view returns (string memory);
126135
function vm() external view returns (IBigStepper vm_);
136+
function wasRespectedGameTypeWhenCreated() external view returns (bool);
127137
function weth() external view returns (IDelayedWETH weth_);
128138

129139
function __constructor__(GameConstructorParams memory _params) external;

packages/contracts-bedrock/interfaces/dispute/IPermissionedDisputeGame.sol

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
pragma solidity ^0.8.0;
33

44
import { Types } from "src/libraries/Types.sol";
5-
import { Claim, Position, Clock, Hash, Duration } from "src/dispute/lib/Types.sol";
5+
import { Claim, Position, Clock, Hash, Duration, BondDistributionMode } from "src/dispute/lib/Types.sol";
66

77
import { IAnchorStateRegistry } from "interfaces/dispute/IAnchorStateRegistry.sol";
88
import { IDelayedWETH } from "interfaces/dispute/IDelayedWETH.sol";
@@ -63,13 +63,19 @@ interface IPermissionedDisputeGame is IDisputeGame {
6363
error UnexpectedRootClaim(Claim rootClaim);
6464
error UnexpectedString();
6565
error ValidStep();
66+
error InvalidBondDistributionMode();
67+
error GameNotFinalized(string reason);
68+
error GameNotResolved();
69+
error ReservedGameType();
6670

6771
event Move(uint256 indexed parentIndex, Claim indexed claim, address indexed claimant);
72+
event GameClosed(BondDistributionMode bondDistributionMode);
6873

6974
function absolutePrestate() external view returns (Claim absolutePrestate_);
7075
function addLocalData(uint256 _ident, uint256 _execLeafIdx, uint256 _partOffset) external;
7176
function anchorStateRegistry() external view returns (IAnchorStateRegistry registry_);
7277
function attack(Claim _disputed, uint256 _parentIndex, Claim _claim) external payable;
78+
function bondDistributionMode() external view returns (BondDistributionMode);
7379
function challengeRootL2Block(Types.OutputRootProof memory _outputRootProof, bytes memory _headerRLP) external;
7480
function claimCredit(address _recipient) external;
7581
function claimData(uint256)
@@ -87,18 +93,21 @@ interface IPermissionedDisputeGame is IDisputeGame {
8793
function claimDataLen() external view returns (uint256 len_);
8894
function claims(Hash) external view returns (bool);
8995
function clockExtension() external view returns (Duration clockExtension_);
96+
function closeGame() external;
9097
function credit(address) external view returns (uint256);
9198
function defend(Claim _disputed, uint256 _parentIndex, Claim _claim) external payable;
9299
function getChallengerDuration(uint256 _claimIndex) external view returns (Duration duration_);
93100
function getNumToResolve(uint256 _claimIndex) external view returns (uint256 numRemainingChildren_);
94101
function getRequiredBond(Position _position) external view returns (uint256 requiredBond_);
102+
function hasUnlockedCredit(address) external view returns (bool);
95103
function l2BlockNumber() external pure returns (uint256 l2BlockNumber_);
96104
function l2BlockNumberChallenged() external view returns (bool);
97105
function l2BlockNumberChallenger() external view returns (address);
98106
function l2ChainId() external view returns (uint256 l2ChainId_);
99107
function maxClockDuration() external view returns (Duration maxClockDuration_);
100108
function maxGameDepth() external view returns (uint256 maxGameDepth_);
101109
function move(Claim _disputed, uint256 _challengeIndex, Claim _claim, bool _isAttack) external payable;
110+
function refundModeCredit(address) external view returns (uint256);
102111
function resolutionCheckpoints(uint256)
103112
external
104113
view
@@ -113,6 +122,7 @@ interface IPermissionedDisputeGame is IDisputeGame {
113122
function subgames(uint256, uint256) external view returns (uint256);
114123
function version() external view returns (string memory);
115124
function vm() external view returns (IBigStepper vm_);
125+
function wasRespectedGameTypeWhenCreated() external view returns (bool);
116126
function weth() external view returns (IDelayedWETH weth_);
117127

118128
error BadAuth();

packages/contracts-bedrock/src/L1/OptimismPortal2.sol

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -657,9 +657,16 @@ contract OptimismPortal2 is Initializable, ResourceMetering, ISemver {
657657
/// @param _gameType The game type to consult for output proposals.
658658
function setRespectedGameType(GameType _gameType) external {
659659
if (msg.sender != guardian()) revert Unauthorized();
660-
respectedGameType = _gameType;
661-
respectedGameTypeUpdatedAt = uint64(block.timestamp);
662-
emit RespectedGameTypeSet(_gameType, Timestamp.wrap(respectedGameTypeUpdatedAt));
660+
// respectedGameTypeUpdatedAt is now no longer set by default. We want to avoid modifying
661+
// this function's signature as that would result in changes to the DeputyGuardianModule.
662+
// We use type(uint32).max as a temporary solution to allow us to update the
663+
// respectedGameTypeUpdatedAt timestamp without modifying this function's signature.
664+
if (_gameType.raw() == type(uint32).max) {
665+
respectedGameTypeUpdatedAt = uint64(block.timestamp);
666+
} else {
667+
respectedGameType = _gameType;
668+
emit RespectedGameTypeSet(_gameType, Timestamp.wrap(respectedGameTypeUpdatedAt));
669+
}
663670
}
664671

665672
/// @notice Checks if a withdrawal can be finalized. This function will revert if the withdrawal cannot be
@@ -704,6 +711,12 @@ contract OptimismPortal2 is Initializable, ResourceMetering, ISemver {
704711
// the withdrawal was proven.
705712
if (disputeGameProxy.gameType().raw() != respectedGameType.raw()) revert InvalidGameType();
706713

714+
// The game type of the dispute game must have been the respected game type at creation
715+
// time. We check that the game type is the respected game type at proving time, but it's
716+
// possible that the respected game type has since changed. Users can still use this game
717+
// to finalize a withdrawal as long as it has not been otherwise invalidated.
718+
if (!disputeGameProxy.wasRespectedGameTypeWhenCreated()) revert InvalidGameType();
719+
707720
// The game must have been created after `respectedGameTypeUpdatedAt`. This is to prevent users from creating
708721
// invalid disputes against a deployed game type while the off-chain challenge agents are not watching.
709722
require(

packages/contracts-bedrock/src/dispute/AnchorStateRegistry.sol

Lines changed: 76 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -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

Comments
 (0)