-
Notifications
You must be signed in to change notification settings - Fork 4k
feat: dispute mon helper #14743
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
feat: dispute mon helper #14743
Changes from all commits
Commits
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
89 changes: 89 additions & 0 deletions
89
packages/contracts-bedrock/snapshots/abi/DisputeMonitorHelper.json
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,89 @@ | ||
| [ | ||
| { | ||
| "inputs": [ | ||
| { | ||
| "internalType": "contract IDisputeGameFactory", | ||
| "name": "_factory", | ||
| "type": "address" | ||
| }, | ||
| { | ||
| "internalType": "uint256", | ||
| "name": "_creationRangeStart", | ||
| "type": "uint256" | ||
| }, | ||
| { | ||
| "internalType": "uint256", | ||
| "name": "_creationRangeEnd", | ||
| "type": "uint256" | ||
| } | ||
| ], | ||
| "name": "getUnresolvedGames", | ||
| "outputs": [ | ||
| { | ||
| "internalType": "contract IDisputeGame[]", | ||
| "name": "unresolvedGames_", | ||
| "type": "address[]" | ||
| } | ||
| ], | ||
| "stateMutability": "view", | ||
| "type": "function" | ||
| }, | ||
| { | ||
| "inputs": [ | ||
| { | ||
| "internalType": "contract IDisputeGameFactory", | ||
| "name": "_factory", | ||
| "type": "address" | ||
| }, | ||
| { | ||
| "internalType": "contract IDisputeGame", | ||
| "name": "_game", | ||
| "type": "address" | ||
| } | ||
| ], | ||
| "name": "isGameRegistered", | ||
| "outputs": [ | ||
| { | ||
| "internalType": "bool", | ||
| "name": "isValid_", | ||
| "type": "bool" | ||
| } | ||
| ], | ||
| "stateMutability": "view", | ||
| "type": "function" | ||
| }, | ||
| { | ||
| "inputs": [ | ||
| { | ||
| "internalType": "contract IDisputeGameFactory", | ||
| "name": "_factory", | ||
| "type": "address" | ||
| }, | ||
| { | ||
| "internalType": "uint256", | ||
| "name": "_targetTimestamp", | ||
| "type": "uint256" | ||
| }, | ||
| { | ||
| "internalType": "enum DisputeMonitorHelper.SearchDirection", | ||
| "name": "_direction", | ||
| "type": "uint8" | ||
| } | ||
| ], | ||
| "name": "search", | ||
| "outputs": [ | ||
| { | ||
| "internalType": "uint256", | ||
| "name": "index_", | ||
| "type": "uint256" | ||
| } | ||
| ], | ||
| "stateMutability": "view", | ||
| "type": "function" | ||
| }, | ||
| { | ||
| "inputs": [], | ||
| "name": "DisputeMonitorHelper_InvalidSearchRange", | ||
| "type": "error" | ||
| } | ||
| ] |
1 change: 1 addition & 0 deletions
1
packages/contracts-bedrock/snapshots/storageLayout/DisputeMonitorHelper.json
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1 @@ | ||
| [] |
157 changes: 157 additions & 0 deletions
157
packages/contracts-bedrock/src/periphery/monitoring/DisputeMonitorHelper.sol
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,157 @@ | ||
| // SPDX-License-Identifier: MIT | ||
| pragma solidity ^0.8.15; | ||
|
|
||
| // Interfaces | ||
| import { IDisputeGameFactory } from "interfaces/dispute/IDisputeGameFactory.sol"; | ||
| import { IDisputeGame } from "interfaces/dispute/IDisputeGame.sol"; | ||
| import { Timestamp, GameType, Claim } from "src/dispute/lib/Types.sol"; | ||
|
|
||
| /// @title DisputeMonitorHelper | ||
| /// @notice Peripheral contract that can help to monitor dispute games. Supplements offchain tools | ||
| /// by simplifying certain queries about dispute games. | ||
| contract DisputeMonitorHelper { | ||
| /// @notice Thrown when the end index is less than the start index. | ||
| error DisputeMonitorHelper_InvalidSearchRange(); | ||
|
|
||
| /// @notice Enum representing the direction of the search. | ||
| enum SearchDirection { | ||
| OLDER_THAN_OR_EQ, | ||
| NEWER_THAN_OR_EQ | ||
| } | ||
|
|
||
| /// @notice Checks if a game was created by the provided factory. | ||
| /// @param _factory The factory of the dispute games. | ||
| /// @param _game The game to check. | ||
| /// @return isValid_ True if the game was created by the factory, false otherwise. | ||
| function isGameRegistered(IDisputeGameFactory _factory, IDisputeGame _game) public view returns (bool isValid_) { | ||
| // Grab the game and game data. | ||
| (GameType gameType, Claim rootClaim, bytes memory extraData) = _game.gameData(); | ||
|
|
||
| // Grab the verified address of the game based on the game data. | ||
| (IDisputeGame _factoryRegisteredGame,) = | ||
| _factory.games({ _gameType: gameType, _rootClaim: rootClaim, _extraData: extraData }); | ||
|
|
||
| // Return whether the game is factory registered. | ||
| isValid_ = address(_factoryRegisteredGame) == address(_game); | ||
| } | ||
|
|
||
| /// @notice Finds all unresolved games in a given time range. | ||
| /// @param _factory The factory of the dispute games. | ||
| /// @param _creationRangeStart Start of the range of game creation timestamps. | ||
| /// @param _creationRangeEnd End of the range of game creation timestamps. | ||
| /// @return unresolvedGames_ The array of unresolved games. | ||
| function getUnresolvedGames( | ||
| IDisputeGameFactory _factory, | ||
| uint256 _creationRangeStart, | ||
| uint256 _creationRangeEnd | ||
| ) | ||
| public | ||
| view | ||
| returns (IDisputeGame[] memory unresolvedGames_) | ||
| { | ||
| // Check that the max | ||
| if (_creationRangeEnd < _creationRangeStart) { | ||
| revert DisputeMonitorHelper_InvalidSearchRange(); | ||
| } | ||
|
|
||
| // If there are no games, return an empty array. In theory we could error here too but it's | ||
| // easier for offchain tooling if this case just returns empty. Either is fine, but this is | ||
| // likely to be a common standard case and it'd be nicer if it didn't error. | ||
| if (_factory.gameCount() == 0) { | ||
| return new IDisputeGame[](0); | ||
| } | ||
|
|
||
| // Try to find a suitable start and end index. If startIdx is type(uint256).max then we did | ||
| // not find any newer games and the creation range start must be after the timestamp of the | ||
| // latest game. Similarly, if endIdx is type(uint256).max then we did not find any older | ||
| // games and the creation range end must be before the timestamp of the earliest game. In | ||
| // either case, we can return an empty array. | ||
| uint256 startIdx = search(_factory, _creationRangeStart, SearchDirection.NEWER_THAN_OR_EQ); | ||
| uint256 endIdx = search(_factory, _creationRangeEnd, SearchDirection.OLDER_THAN_OR_EQ); | ||
| if (startIdx == type(uint256).max || endIdx == type(uint256).max) { | ||
| return new IDisputeGame[](0); | ||
| } | ||
|
|
||
| // Additionally, if the end index is less than the start index, then the range is between | ||
| // two dispute games. We return an empty array in this case. | ||
| if (endIdx < startIdx) { | ||
| return new IDisputeGame[](0); | ||
| } | ||
|
|
||
| // Allocate the array and fill it | ||
| unresolvedGames_ = new IDisputeGame[](endIdx - startIdx + 1); | ||
| uint256 unresolvedGameCount = 0; | ||
| for (uint256 i = startIdx; i <= endIdx; i++) { | ||
| (,, IDisputeGame game) = _factory.gameAtIndex(i); | ||
| if (game.resolvedAt().raw() == 0) { | ||
| unresolvedGames_[unresolvedGameCount] = game; | ||
| unresolvedGameCount++; | ||
| } | ||
| } | ||
|
|
||
| // Clobber the size of the array to return the right size. | ||
| assembly { | ||
| mstore(unresolvedGames_, unresolvedGameCount) | ||
| } | ||
| } | ||
|
|
||
| /// @notice Searches for a game by timestamp, returning the index of the game that best matches the | ||
| /// given search direction. | ||
| /// @param _factory The factory of the dispute games. | ||
| /// @param _targetTimestamp The timestamp to search for. | ||
| /// @param _direction The direction to search in (older or newer). | ||
| /// @return index_ The index of the matching game, if it exists. | ||
| function search( | ||
| IDisputeGameFactory _factory, | ||
| uint256 _targetTimestamp, | ||
| SearchDirection _direction | ||
| ) | ||
| public | ||
| view | ||
| returns (uint256 index_) | ||
| { | ||
| uint256 gameCount = _factory.gameCount(); | ||
| // If there are no games, return max to indicate "not found." | ||
| if (gameCount == 0) { | ||
| return type(uint256).max; | ||
| } | ||
|
|
||
| uint256 left = 0; | ||
| uint256 right = gameCount - 1; | ||
|
|
||
| // We'll store the candidate here. If it remains max, no suitable game was found. | ||
| index_ = type(uint256).max; | ||
|
|
||
| while (left <= right) { | ||
|
ControlCplusControlV marked this conversation as resolved.
|
||
| uint256 mid = left + (right - left) / 2; | ||
| (, Timestamp timestamp,) = _factory.gameAtIndex(mid); | ||
| uint256 gameTimestamp = uint64(timestamp.raw()); | ||
|
|
||
| if (_direction == SearchDirection.OLDER_THAN_OR_EQ) { | ||
| // Rightmost index where timestamp <= _targetTimestamp | ||
| if (gameTimestamp <= _targetTimestamp) { | ||
| index_ = mid; | ||
| left = mid + 1; | ||
| } else { | ||
| if (mid == 0) { | ||
| // Prevent underflow | ||
| break; | ||
| } | ||
| right = mid - 1; | ||
| } | ||
| } else { | ||
| // Leftmost index where timestamp >= _targetTimestamp | ||
| if (gameTimestamp >= _targetTimestamp) { | ||
| index_ = mid; | ||
| if (mid == 0) { | ||
| // Prevent underflow | ||
| break; | ||
| } | ||
| right = mid - 1; | ||
| } else { | ||
| left = mid + 1; | ||
| } | ||
| } | ||
| } | ||
| } | ||
| } | ||
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.