Commit 8478fbfe authored by clabby's avatar clabby

Change comment style for `dispute` contracts

parent 72c6fb0a
......@@ -13,73 +13,53 @@ import { IDisputeGame } from "./interfaces/IDisputeGame.sol";
import { IDisputeGameFactory } from "./interfaces/IDisputeGameFactory.sol";
import { IVersioned } from "./interfaces/IVersioned.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.
*/
/// @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, IVersioned {
/**
* @dev Allows for the creation of clone proxies with immutable arguments.
*/
/// @dev Allows for the creation of clone proxies with immutable arguments.
using ClonesWithImmutableArgs for address;
/**
* @inheritdoc IDisputeGameFactory
*/
/// @inheritdoc IDisputeGameFactory
mapping(GameType => IDisputeGame) public gameImpls;
/**
* @notice Mapping of a hash of `gameType || rootClaim || extraData` to
* the deployed `IDisputeGame` clone.
* @dev Note: `||` denotes concatenation.
*/
/// @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
*/
/// @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.
*/
/// @notice constructs a new DisputeGameFactory contract.
constructor() OwnableUpgradeable() {
initialize(address(0));
}
/**
* @notice Initializes the contract.
* @param _owner The owner of the contract.
*/
/// @notice Initializes the contract.
/// @param _owner The owner of the contract.
function initialize(address _owner) public initializer {
__Ownable_init();
_transferOwnership(_owner);
}
/**
* @inheritdoc IVersioned
* @custom:semver 0.0.2
*/
/// @inheritdoc IVersioned
/// @custom:semver 0.0.2
function version() external pure returns (string memory) {
return "0.0.2";
}
/**
* @inheritdoc IDisputeGameFactory
*/
/// @inheritdoc IDisputeGameFactory
function gameCount() external view returns (uint256 gameCount_) {
gameCount_ = _disputeGameList.length;
}
/**
* @inheritdoc IDisputeGameFactory
*/
/// @inheritdoc IDisputeGameFactory
function games(
GameType _gameType,
Claim _rootClaim,
......@@ -92,9 +72,7 @@ contract DisputeGameFactory is OwnableUpgradeable, IDisputeGameFactory, IVersion
timestamp_ = timestamp;
}
/**
* @inheritdoc IDisputeGameFactory
*/
/// @inheritdoc IDisputeGameFactory
function gameAtIndex(uint256 _index)
external
view
......@@ -106,9 +84,7 @@ contract DisputeGameFactory is OwnableUpgradeable, IDisputeGameFactory, IVersion
timestamp_ = timestamp;
}
/**
* @inheritdoc IDisputeGameFactory
*/
/// @inheritdoc IDisputeGameFactory
function create(
GameType gameType,
Claim rootClaim,
......@@ -142,9 +118,7 @@ contract DisputeGameFactory is OwnableUpgradeable, IDisputeGameFactory, IVersion
emit DisputeGameCreated(address(proxy), gameType, rootClaim);
}
/**
* @inheritdoc IDisputeGameFactory
*/
/// @inheritdoc IDisputeGameFactory
function getGameUUID(
GameType gameType,
Claim rootClaim,
......@@ -181,27 +155,21 @@ contract DisputeGameFactory is OwnableUpgradeable, IDisputeGameFactory, IVersion
}
}
/**
* @inheritdoc IDisputeGameFactory
*/
/// @inheritdoc IDisputeGameFactory
function setImplementation(GameType gameType, IDisputeGame impl) external onlyOwner {
gameImpls[gameType] = impl;
emit ImplementationSet(address(impl), gameType);
}
/**
* @dev Packs an address and a uint256 into a single bytes32 slot. This
* is only safe for up to uint96 values.
*/
/// @dev Packs an address and a uint256 into a single bytes32 slot. This
/// is only safe for up to uint96 values.
function _packSlot(address _addr, uint256 _num) internal pure returns (GameId slot_) {
assembly {
slot_ := or(shl(0xa0, _num), _addr)
}
}
/**
* @dev Unpacks an address and a uint256 from a single bytes32 slot.
*/
/// @dev Unpacks an address and a uint256 from a single bytes32 slot.
function _unpackSlot(GameId _slot) internal pure returns (address addr_, uint256 num_) {
assembly {
addr_ := and(_slot, 0xffffffffffffffffffffffffffffffffffffffff)
......
......@@ -15,70 +15,47 @@ import { LibClock } from "./lib/LibClock.sol";
import "../libraries/DisputeTypes.sol";
import "../libraries/DisputeErrors.sol";
/**
* @title FaultDisputeGame
* @notice An implementation of the `IFaultDisputeGame` interface.
*/
/// @title FaultDisputeGame
/// @notice An implementation of the `IFaultDisputeGame` interface.
contract FaultDisputeGame is IFaultDisputeGame, Clone {
////////////////////////////////////////////////////////////////
// State Vars //
////////////////////////////////////////////////////////////////
/**
* @notice The current Semver of the FaultDisputeGame implementation.
*/
string internal constant VERSION = "0.0.2";
/// @notice The absolute prestate of the instruction trace. This is a constant that is defined
/// by the program that is being used to execute the trace.
Claim public immutable ABSOLUTE_PRESTATE;
/// @notice The max depth of the game.
uint256 public immutable MAX_GAME_DEPTH;
/**
* @notice The duration of the game.
* @dev TODO: Account for resolution buffer. (?)
*/
/// @notice The duration of the game.
/// @dev TODO: Account for resolution buffer. (?)
Duration internal constant GAME_DURATION = Duration.wrap(7 days);
/**
* @notice The root claim's position is always at gindex 1.
*/
/// @notice The root claim's position is always at gindex 1.
Position internal constant ROOT_POSITION = Position.wrap(1);
/**
* @notice The absolute prestate of the instruction trace. This is a constant that is defined
* by the program that is being used to execute the trace.
*/
Claim public immutable ABSOLUTE_PRESTATE;
/**
* @notice The max depth of the game.
*/
uint256 public immutable MAX_GAME_DEPTH;
/// @notice The current Semver of the FaultDisputeGame implementation.
string internal constant VERSION = "0.0.2";
/**
* @notice The starting timestamp of the game
*/
/// @notice The starting timestamp of the game
Timestamp public gameStart;
/**
* @inheritdoc IDisputeGame
*/
/// @inheritdoc IDisputeGame
GameStatus public status;
/**
* @inheritdoc IDisputeGame
*/
/// @inheritdoc IDisputeGame
IBondManager public bondManager;
/**
* @notice An append-only array of all claims made during the dispute game.
*/
/// @notice An append-only array of all claims made during the dispute game.
ClaimData[] public claimData;
/**
* @notice An internal mapping to allow for constant-time lookups of existing claims.
*/
/// @notice An internal mapping to allow for constant-time lookups of existing claims.
mapping(ClaimHash => bool) internal claims;
/**
* @param _absolutePrestate The absolute prestate of the instruction trace.
*/
/// @param _absolutePrestate The absolute prestate of the instruction trace.
constructor(Claim _absolutePrestate, uint256 _maxGameDepth) {
ABSOLUTE_PRESTATE = _absolutePrestate;
MAX_GAME_DEPTH = _maxGameDepth;
......@@ -88,23 +65,17 @@ contract FaultDisputeGame is IFaultDisputeGame, Clone {
// External Logic //
////////////////////////////////////////////////////////////////
/**
* @inheritdoc IFaultDisputeGame
*/
/// @inheritdoc IFaultDisputeGame
function attack(uint256 _parentIndex, Claim _pivot) external payable {
_move(_parentIndex, _pivot, true);
}
/**
* @inheritdoc IFaultDisputeGame
*/
/// @inheritdoc IFaultDisputeGame
function defend(uint256 _parentIndex, Claim _pivot) external payable {
_move(_parentIndex, _pivot, false);
}
/**
* @inheritdoc IFaultDisputeGame
*/
/// @inheritdoc IFaultDisputeGame
function step(
uint256 _stateIndex,
uint256 _claimIndex,
......@@ -182,12 +153,10 @@ contract FaultDisputeGame is IFaultDisputeGame, Clone {
// Internal Logic //
////////////////////////////////////////////////////////////////
/**
* @notice Internal move function, used by both `attack` and `defend`.
* @param _challengeIndex The index of the claim being moved against.
* @param _pivot The claim at the next logical position in the game.
* @param _isAttack Whether or not the move is an attack or defense.
*/
/// @notice Internal move function, used by both `attack` and `defend`.
/// @param _challengeIndex The index of the claim being moved against.
/// @param _pivot The claim at the next logical position in the game.
/// @param _isAttack Whether or not the move is an attack or defense.
function _move(
uint256 _challengeIndex,
Claim _pivot,
......@@ -275,9 +244,7 @@ contract FaultDisputeGame is IFaultDisputeGame, Clone {
emit Move(_challengeIndex, _pivot, msg.sender);
}
/**
* @inheritdoc IFaultDisputeGame
*/
/// @inheritdoc IFaultDisputeGame
function l2BlockNumber() public pure returns (uint256 l2BlockNumber_) {
l2BlockNumber_ = _getArgUint256(0x20);
}
......@@ -286,23 +253,17 @@ contract FaultDisputeGame is IFaultDisputeGame, Clone {
// `IDisputeGame` impl //
////////////////////////////////////////////////////////////////
/**
* @inheritdoc IDisputeGame
*/
/// @inheritdoc IDisputeGame
function gameType() public pure override returns (GameType gameType_) {
gameType_ = GameTypes.FAULT;
}
/**
* @inheritdoc IDisputeGame
*/
/// @inheritdoc IDisputeGame
function createdAt() external view returns (Timestamp createdAt_) {
createdAt_ = gameStart;
}
/**
* @inheritdoc IDisputeGame
*/
/// @inheritdoc IDisputeGame
function resolve() external returns (GameStatus status_) {
// TODO: Do not allow resolution before clocks run out.
......@@ -361,16 +322,12 @@ contract FaultDisputeGame is IFaultDisputeGame, Clone {
emit Resolved(status_);
}
/**
* @inheritdoc IDisputeGame
*/
/// @inheritdoc IDisputeGame
function rootClaim() public pure returns (Claim rootClaim_) {
rootClaim_ = Claim.wrap(_getArgFixedBytes(0x00));
}
/**
* @inheritdoc IDisputeGame
*/
/// @inheritdoc IDisputeGame
function extraData() public pure returns (bytes memory extraData_) {
// The extra data starts at the second word within the cwia calldata.
// TODO: What data do we need to pass along to this contract from the factory?
......@@ -378,9 +335,7 @@ contract FaultDisputeGame is IFaultDisputeGame, Clone {
extraData_ = _getArgDynBytes(0x20, 0x20);
}
/**
* @inheritdoc IDisputeGame
*/
/// @inheritdoc IDisputeGame
function gameData()
external
pure
......@@ -395,9 +350,7 @@ contract FaultDisputeGame is IFaultDisputeGame, Clone {
extraData_ = extraData();
}
/**
* @inheritdoc IInitializable
*/
/// @inheritdoc IInitializable
function initialize() external {
// Set the game start
gameStart = Timestamp.wrap(uint64(block.timestamp));
......@@ -416,9 +369,7 @@ contract FaultDisputeGame is IFaultDisputeGame, Clone {
);
}
/**
* @inheritdoc IVersioned
*/
/// @inheritdoc IVersioned
function version() external pure override returns (string memory version_) {
version_ = VERSION;
}
......
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.15;
/**
* @title IBondManager
* @notice The Bond Manager holds ether posted as a bond for a bond id.
*/
/// @title IBondManager
/// @notice The Bond Manager holds ether posted as a bond for a bond id.
interface IBondManager {
/**
* @notice BondPosted is emitted when a bond is posted.
* @param bondId is the id of the bond.
* @param owner is the address that owns the bond.
* @param expiration is the time at which the bond expires.
* @param amount is the amount of the bond.
*/
/// @notice BondPosted is emitted when a bond is posted.
/// @param bondId is the id of the bond.
/// @param owner is the address that owns the bond.
/// @param expiration is the time at which the bond expires.
/// @param amount is the amount of the bond.
event BondPosted(bytes32 bondId, address owner, uint256 expiration, uint256 amount);
/**
* @notice BondSeized is emitted when a bond is seized.
* @param bondId is the id of the bond.
* @param owner is the address that owns the bond.
* @param seizer is the address that seized the bond.
* @param amount is the amount of the bond.
*/
/// @notice BondSeized is emitted when a bond is seized.
/// @param bondId is the id of the bond.
/// @param owner is the address that owns the bond.
/// @param seizer is the address that seized the bond.
/// @param amount is the amount of the bond.
event BondSeized(bytes32 bondId, address owner, address seizer, uint256 amount);
/**
* @notice BondReclaimed is emitted when a bond is reclaimed by the owner.
* @param bondId is the id of the bond.
* @param claiment is the address that reclaimed the bond.
* @param amount is the amount of the bond.
*/
/// @notice BondReclaimed is emitted when a bond is reclaimed by the owner.
/// @param bondId is the id of the bond.
/// @param claiment is the address that reclaimed the bond.
/// @param amount is the amount of the bond.
event BondReclaimed(bytes32 bondId, address claiment, uint256 amount);
/**
* @notice Post a bond with a given id and owner.
* @dev This function will revert if the provided bondId is already in use.
* @param _bondId is the id of the bond.
* @param _bondOwner is the address that owns the bond.
* @param _minClaimHold is the minimum amount of time the owner
* must wait before reclaiming their bond.
*/
/// @notice Post a bond with a given id and owner.
/// @dev This function will revert if the provided bondId is already in use.
/// @param _bondId is the id of the bond.
/// @param _bondOwner is the address that owns the bond.
/// @param _minClaimHold is the minimum amount of time the owner
/// must wait before reclaiming their bond.
function post(
bytes32 _bondId,
address _bondOwner,
uint128 _minClaimHold
) external payable;
/**
* @notice Seizes the bond with the given id.
* @dev This function will revert if there is no bond at the given id.
* @param _bondId is the id of the bond.
*/
/// @notice Seizes the bond with the given id.
/// @dev This function will revert if there is no bond at the given id.
/// @param _bondId is the id of the bond.
function seize(bytes32 _bondId) external;
/**
* @notice Seizes the bond with the given id and distributes it to recipients.
* @dev This function will revert if there is no bond at the given id.
* @param _bondId is the id of the bond.
* @param _claimRecipients is a set of addresses to split the bond amongst.
*/
/// @notice Seizes the bond with the given id and distributes it to recipients.
/// @dev This function will revert if there is no bond at the given id.
/// @param _bondId is the id of the bond.
/// @param _claimRecipients is a set of addresses to split the bond amongst.
///
function seizeAndSplit(bytes32 _bondId, address[] calldata _claimRecipients) external;
/**
* @notice Reclaims the bond of the bond owner.
* @dev This function will revert if there is no bond at the given id.
* @param _bondId is the id of the bond.
*/
/// @notice Reclaims the bond of the bond owner.
/// @dev This function will revert if there is no bond at the given id.
/// @param _bondId is the id of the bond.
function reclaim(bytes32 _bondId) external;
}
......@@ -7,77 +7,57 @@ import { IVersioned } from "./IVersioned.sol";
import { IBondManager } from "./IBondManager.sol";
import { IInitializable } from "./IInitializable.sol";
/**
* @title IDisputeGame
* @notice The generic interface for a DisputeGame contract.
*/
/// @title IDisputeGame
/// @notice The generic interface for a DisputeGame contract.
interface IDisputeGame is IInitializable, IVersioned {
/**
* @notice Emitted when the game is resolved.
* @param status The status of the game after resolution.
*/
/// @notice Emitted when the game is resolved.
/// @param status The status of the game after resolution.
event Resolved(GameStatus indexed status);
/**
* @notice Returns the timestamp that the DisputeGame contract was created at.
* @return createdAt_ The timestamp that the DisputeGame contract was created at.
*/
/// @notice Returns the timestamp that the DisputeGame contract was created at.
/// @return createdAt_ The timestamp that the DisputeGame contract was created at.
function createdAt() external view returns (Timestamp createdAt_);
/**
* @notice Returns the current status of the game.
* @return status_ The current status of the game.
*/
/// @notice Returns the current status of the game.
/// @return status_ The current status of the game.
function status() external view returns (GameStatus status_);
/**
* @notice Getter for the game type.
* @dev `clones-with-immutable-args` argument #1
* @dev The reference impl should be entirely different depending on the type (fault, validity)
* i.e. The game type should indicate the security model.
* @return gameType_ The type of proof system being used.
*/
/// @notice Getter for the game type.
/// @dev `clones-with-immutable-args` argument #1
/// @dev The reference impl should be entirely different depending on the type (fault, validity)
/// i.e. The game type should indicate the security model.
/// @return gameType_ The type of proof system being used.
function gameType() external pure returns (GameType gameType_);
/**
* @notice Getter for the root claim.
* @dev `clones-with-immutable-args` argument #2
* @return rootClaim_ The root claim of the DisputeGame.
*/
/// @notice Getter for the root claim.
/// @dev `clones-with-immutable-args` argument #2
/// @return rootClaim_ The root claim of the DisputeGame.
function rootClaim() external pure returns (Claim rootClaim_);
/**
* @notice Getter for the extra data.
* @dev `clones-with-immutable-args` argument #3
* @return extraData_ Any extra data supplied to the dispute game contract by the creator.
*/
/// @notice Getter for the extra data.
/// @dev `clones-with-immutable-args` argument #3
/// @return extraData_ Any extra data supplied to the dispute game contract by the creator.
function extraData() external pure returns (bytes memory extraData_);
/**
* @notice Returns the address of the `BondManager` used.
* @return bondManager_ The address of the `BondManager` used.
*/
/// @notice Returns the address of the `BondManager` used.
/// @return bondManager_ The address of the `BondManager` used.
function bondManager() external view returns (IBondManager bondManager_);
/**
* @notice If all necessary information has been gathered, this function should mark the game
* status as either `CHALLENGER_WINS` or `DEFENDER_WINS` and return the status of
* the resolved game. It is at this stage that the bonds should be awarded to the
* necessary parties.
* @dev May only be called if the `status` is `IN_PROGRESS`.
* @return status_ The status of the game after resolution.
*/
/// @notice If all necessary information has been gathered, this function should mark the game
/// status as either `CHALLENGER_WINS` or `DEFENDER_WINS` and return the status of
/// the resolved game. It is at this stage that the bonds should be awarded to the
/// necessary parties.
/// @dev May only be called if the `status` is `IN_PROGRESS`.
/// @return status_ The status of the game after resolution.
function resolve() external returns (GameStatus status_);
/**
* @notice A compliant implementation of this interface should return the components of the
* game UUID's preimage provided in the cwia payload. The preimage of the UUID is
* constructed as `keccak256(gameType . rootClaim . extraData)` where `.` denotes
* concatenation.
* @return gameType_ The type of proof system being used.
* @return rootClaim_ The root claim of the DisputeGame.
* @return extraData_ Any extra data supplied to the dispute game contract by the creator.
*/
/// @notice A compliant implementation of this interface should return the components of the
/// game UUID's preimage provided in the cwia payload. The preimage of the UUID is
/// constructed as `keccak256(gameType . rootClaim . extraData)` where `.` denotes
/// concatenation.
/// @return gameType_ The type of proof system being used.
/// @return rootClaim_ The root claim of the DisputeGame.
/// @return extraData_ Any extra data supplied to the dispute game contract by the creator.
function gameData()
external
pure
......
......@@ -5,105 +5,87 @@ import "../../libraries/DisputeTypes.sol";
import { IDisputeGame } from "./IDisputeGame.sol";
/**
* @title IDisputeGameFactory
* @notice The interface for a DisputeGameFactory contract.
*/
/// @title IDisputeGameFactory
/// @notice The interface for a DisputeGameFactory contract.
interface IDisputeGameFactory {
/**
* @notice Emitted when a new dispute game is created
* @param disputeProxy The address of the dispute game proxy
* @param gameType The type of the dispute game proxy's implementation
* @param rootClaim The root claim of the dispute game
*/
/// @notice Emitted when a new dispute game is created
/// @param disputeProxy The address of the dispute game proxy
/// @param gameType The type of the dispute game proxy's implementation
/// @param rootClaim The root claim of the dispute game
event DisputeGameCreated(
address indexed disputeProxy,
GameType indexed gameType,
Claim indexed rootClaim
);
/**
* @notice Emitted when a new game implementation added to the factory
* @param impl The implementation contract for the given `GameType`.
* @param gameType The type of the DisputeGame.
*/
/// @notice Emitted when a new game implementation added to the factory
/// @param impl The implementation contract for the given `GameType`.
/// @param gameType The type of the DisputeGame.
event ImplementationSet(address indexed impl, GameType indexed gameType);
/**
* @notice the total number of dispute games created by this factory.
* @return _gameCount The total number of dispute games created by this factory.
*/
/// @notice the total number of dispute games created by this factory.
/// @return _gameCount The total number of dispute games created by this factory.
function gameCount() external view returns (uint256 _gameCount);
/**
* @notice `games` queries an internal a mapping that maps the hash of
* `gameType ++ rootClaim ++ extraData` to the deployed `DisputeGame` clone.
* @dev `++` equates to concatenation.
* @param gameType The type of the DisputeGame - used to decide the proxy implementation
* @param rootClaim The root claim of the DisputeGame.
* @param extraData Any extra data that should be provided to the created dispute game.
* @return _proxy The clone of the `DisputeGame` created with the given parameters.
* Returns `address(0)` if nonexistent.
* @return _timestamp The timestamp of the creation of the dispute game.
*/
/// @notice `games` queries an internal a mapping that maps the hash of
/// `gameType ++ rootClaim ++ extraData` to the deployed `DisputeGame` clone.
/// @dev `++` equates to concatenation.
/// @param gameType The type of the DisputeGame - used to decide the proxy implementation
/// @param rootClaim The root claim of the DisputeGame.
/// @param extraData Any extra data that should be provided to the created dispute game.
/// @return _proxy The clone of the `DisputeGame` created with the given parameters.
/// Returns `address(0)` if nonexistent.
/// @return _timestamp The timestamp of the creation of the dispute game.
function games(
GameType gameType,
Claim rootClaim,
bytes calldata extraData
) external view returns (IDisputeGame _proxy, uint256 _timestamp);
/**
* @notice `gameAtIndex` returns the dispute game contract address and its creation timestamp
* at the given index. Each created dispute game increments the underlying index.
* @param _index The index of the dispute game.
* @return _proxy The clone of the `DisputeGame` created with the given parameters.
* Returns `address(0)` if nonexistent.
* @return _timestamp The timestamp of the creation of the dispute game.
*/
/// @notice `gameAtIndex` returns the dispute game contract address and its creation timestamp
/// at the given index. Each created dispute game increments the underlying index.
/// @param _index The index of the dispute game.
/// @return _proxy The clone of the `DisputeGame` created with the given parameters.
/// Returns `address(0)` if nonexistent.
/// @return _timestamp The timestamp of the creation of the dispute game.
function gameAtIndex(uint256 _index)
external
view
returns (IDisputeGame _proxy, uint256 _timestamp);
/**
* @notice `gameImpls` is a mapping that maps `GameType`s to their respective
* `IDisputeGame` implementations.
* @param gameType The type of the dispute game.
* @return _impl The address of the implementation of the game type.
* Will be cloned on creation of a new dispute game with the given `gameType`.
*/
/// @notice `gameImpls` is a mapping that maps `GameType`s to their respective
/// `IDisputeGame` implementations.
/// @param gameType The type of the dispute game.
/// @return _impl The address of the implementation of the game type.
/// Will be cloned on creation of a new dispute game with the given `gameType`.
function gameImpls(GameType gameType) external view returns (IDisputeGame _impl);
/**
* @notice Creates a new DisputeGame proxy contract.
* @param gameType The type of the DisputeGame - used to decide the proxy implementation
* @param rootClaim The root claim of the DisputeGame.
* @param extraData Any extra data that should be provided to the created dispute game.
* @return proxy The address of the created DisputeGame proxy.
*/
/// @notice Creates a new DisputeGame proxy contract.
/// @param gameType The type of the DisputeGame - used to decide the proxy implementation
/// @param rootClaim The root claim of the DisputeGame.
/// @param extraData Any extra data that should be provided to the created dispute game.
/// @return proxy The address of the created DisputeGame proxy.
function create(
GameType gameType,
Claim rootClaim,
bytes calldata extraData
) external returns (IDisputeGame proxy);
/**
* @notice Sets the implementation contract for a specific `GameType`.
* @dev May only be called by the `owner`.
* @param gameType The type of the DisputeGame.
* @param impl The implementation contract for the given `GameType`.
*/
///
/// @notice Sets the implementation contract for a specific `GameType`.
/// @dev May only be called by the `owner`.
/// @param gameType The type of the DisputeGame.
/// @param impl The implementation contract for the given `GameType`.
///
function setImplementation(GameType gameType, IDisputeGame impl) external;
/**
* @notice Returns a unique identifier for the given dispute game parameters.
* @dev Hashes the concatenation of `gameType . rootClaim . extraData`
* without expanding memory.
* @param gameType The type of the DisputeGame.
* @param rootClaim The root claim of the DisputeGame.
* @param extraData Any extra data that should be provided to the created dispute game.
* @return _uuid The unique identifier for the given dispute game parameters.
*/
/// @notice Returns a unique identifier for the given dispute game parameters.
/// @dev Hashes the concatenation of `gameType . rootClaim . extraData`
/// without expanding memory.
/// @param gameType The type of the DisputeGame.
/// @param rootClaim The root claim of the DisputeGame.
/// @param extraData Any extra data that should be provided to the created dispute game.
/// @return _uuid The unique identifier for the given dispute game parameters.
function getGameUUID(
GameType gameType,
Claim rootClaim,
......
......@@ -5,16 +5,11 @@ import "../../libraries/DisputeTypes.sol";
import { IDisputeGame } from "./IDisputeGame.sol";
/**
* @title IFaultDisputeGame
* @notice The interface for a fault proof backed dispute game.
*/
/// @title IFaultDisputeGame
/// @notice The interface for a fault proof backed dispute game.
interface IFaultDisputeGame is IDisputeGame {
/**
* @notice The `ClaimData` struct represents the data associated with a Claim.
* @dev TODO: Pack `Clock` and `Position` into the same slot. Should require 4 64 bit arms.
* @dev TODO: Add bond ID information.
*/
/// @notice The `ClaimData` struct represents the data associated with a Claim.
/// @dev TODO: Add bond ID information.
struct ClaimData {
uint32 parentIndex;
bool countered;
......@@ -23,40 +18,32 @@ interface IFaultDisputeGame is IDisputeGame {
Clock clock;
}
/**
* @notice Emitted when a new claim is added to the DAG by `claimant`
* @param parentIndex The index within the `claimData` array of the parent claim
* @param pivot The claim being added
* @param claimant The address of the claimant
*/
/// @notice Emitted when a new claim is added to the DAG by `claimant`
/// @param parentIndex The index within the `claimData` array of the parent claim
/// @param pivot The claim being added
/// @param claimant The address of the claimant
event Move(uint256 indexed parentIndex, Claim indexed pivot, address indexed claimant);
/**
* @notice Attack a disagreed upon `Claim`.
* @param _parentIndex Index of the `Claim` to attack in `claimData`.
* @param _pivot The `Claim` at the relative attack position.
*/
/// @notice Attack a disagreed upon `Claim`.
/// @param _parentIndex Index of the `Claim` to attack in `claimData`.
/// @param _pivot The `Claim` at the relative attack position.
function attack(uint256 _parentIndex, Claim _pivot) external payable;
/**
* @notice Defend an agreed upon `Claim`.
* @param _parentIndex Index of the claim to defend in `claimData`.
* @param _pivot The `Claim` at the relative defense position.
*/
/// @notice Defend an agreed upon `Claim`.
/// @param _parentIndex Index of the claim to defend in `claimData`.
/// @param _pivot The `Claim` at the relative defense position.
function defend(uint256 _parentIndex, Claim _pivot) external payable;
/**
* @notice Perform the final step via an on-chain fault proof processor
* @dev This function should point to a fault proof processor in order to execute
* a step in the fault proof program on-chain. The interface of the fault proof
* processor contract should be generic enough such that we can use different
* fault proof VMs (MIPS, RiscV5, etc.)
* @param _stateIndex The index of the pre/post state of the step within `claimData`.
* @param _claimIndex The index of the challenged claim within `claimData`.
* @param _isAttack Whether or not the step is an attack or a defense.
* @param _stateData The stateData of the step is the preimage of the claim @ `prestateIndex`
* @param _proof Proof to access memory leaf nodes in the VM.
*/
/// @notice Perform the final step via an on-chain fault proof processor
/// @dev This function should point to a fault proof processor in order to execute
/// a step in the fault proof program on-chain. The interface of the fault proof
/// processor contract should be generic enough such that we can use different
/// fault proof VMs (MIPS, RiscV5, etc.)
/// @param _stateIndex The index of the pre/post state of the step within `claimData`.
/// @param _claimIndex The index of the challenged claim within `claimData`.
/// @param _isAttack Whether or not the step is an attack or a defense.
/// @param _stateData The stateData of the step is the preimage of the claim @ `prestateIndex`
/// @param _proof Proof to access memory leaf nodes in the VM.
function step(
uint256 _stateIndex,
uint256 _claimIndex,
......@@ -65,10 +52,8 @@ interface IFaultDisputeGame is IDisputeGame {
bytes calldata _proof
) external;
/**
* @notice The l2BlockNumber that the `rootClaim` commits to. The trace being bisected within
* the game is from `l2BlockNumber - 1` -> `l2BlockNumber`.
* @return l2BlockNumber_ The l2BlockNumber that the `rootClaim` commits to.
*/
/// @notice The l2BlockNumber that the `rootClaim` commits to. The trace being bisected within
/// the game is from `l2BlockNumber - 1` -> `l2BlockNumber`.
/// @return l2BlockNumber_ The l2BlockNumber that the `rootClaim` commits to.
function l2BlockNumber() external view returns (uint256 l2BlockNumber_);
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.15;
/**
* @title IInitializable
* @notice An interface for initializable contracts.
*/
/// @title IInitializable
/// @notice An interface for initializable contracts.
interface IInitializable {
/**
* @notice Initializes the contract.
* @dev This function may only be called once.
*/
/// @notice Initializes the contract.
/// @dev This function may only be called once.
function initialize() external;
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.15;
/**
* @title IVersioned
* @notice An interface for semantically versioned contracts.
*/
/// @title IVersioned
/// @notice An interface for semantically versioned contracts.
interface IVersioned {
/**
* @notice Returns the semantic version of the contract
* @return _version The semantic version of the contract
*/
/// @notice Returns the semantic version of the contract
/// @return _version The semantic version of the contract
function version() external pure returns (string memory _version);
}
......@@ -3,28 +3,22 @@ pragma solidity ^0.8.15;
import "../../libraries/DisputeTypes.sol";
/**
* @title LibClock
* @notice This library contains helper functions for working with the `Clock` type.
*/
/// @title LibClock
/// @notice This library contains helper functions for working with the `Clock` type.
library LibClock {
/**
* @notice Packs a `Duration` and `Timestamp` into a `Clock` type.
* @param _duration The `Duration` to pack into the `Clock` type.
* @param _timestamp The `Timestamp` to pack into the `Clock` type.
* @return clock_ The `Clock` containing the `_duration` and `_timestamp`.
*/
/// @notice Packs a `Duration` and `Timestamp` into a `Clock` type.
/// @param _duration The `Duration` to pack into the `Clock` type.
/// @param _timestamp The `Timestamp` to pack into the `Clock` type.
/// @return clock_ The `Clock` containing the `_duration` and `_timestamp`.
function wrap(Duration _duration, Timestamp _timestamp) internal pure returns (Clock clock_) {
assembly {
clock_ := or(shl(0x40, _duration), _timestamp)
}
}
/**
* @notice Pull the `Duration` out of a `Clock` type.
* @param _clock The `Clock` type to pull the `Duration` out of.
* @return duration_ The `Duration` pulled out of `_clock`.
*/
/// @notice Pull the `Duration` out of a `Clock` type.
/// @param _clock The `Clock` type to pull the `Duration` out of.
/// @return duration_ The `Duration` pulled out of `_clock`.
function duration(Clock _clock) internal pure returns (Duration duration_) {
// Shift the high-order 64 bits into the low-order 64 bits, leaving only the `duration`.
assembly {
......@@ -32,11 +26,9 @@ library LibClock {
}
}
/**
* @notice Pull the `Timestamp` out of a `Clock` type.
* @param _clock The `Clock` type to pull the `Timestamp` out of.
* @return timestamp_ The `Timestamp` pulled out of `_clock`.
*/
/// @notice Pull the `Timestamp` out of a `Clock` type.
/// @param _clock The `Clock` type to pull the `Timestamp` out of.
/// @return timestamp_ The `Timestamp` pulled out of `_clock`.
function timestamp(Clock _clock) internal pure returns (Timestamp timestamp_) {
// Clean the high-order 192 bits by shifting the clock left and then right again, leaving
// only the `timestamp`.
......
......@@ -3,17 +3,14 @@ pragma solidity ^0.8.15;
import "../../libraries/DisputeTypes.sol";
/**
* @title Hashing
* @notice This library contains all of the hashing utilities used in the Cannon contracts.
*/
/// @title Hashing
/// @notice This library contains all of the hashing utilities used in the Cannon contracts.
library LibHashing {
/**
* @notice Hashes a claim and a position together.
* @param _claim A Claim type.
* @param _position The position of `claim`.
* @return claimHash_ A hash of abi.encodePacked(claim, position);
*/
/// @notice Hashes a claim and a position together.
/// @param _claim A Claim type.
/// @param _position The position of `claim`.
/// @return claimHash_ A hash of abi.encodePacked(claim, position);
function hashClaimPos(Claim _claim, Position _position) internal pure returns (ClaimHash claimHash_) {
assembly {
mstore(0x00, _claim)
......
......@@ -3,17 +3,14 @@ pragma solidity ^0.8.15;
import "../../libraries/DisputeTypes.sol";
/**
* @title LibPosition
* @notice This library contains helper functions for working with the `Position` type.
*/
/// @title LibPosition
/// @notice This library contains helper functions for working with the `Position` type.
library LibPosition {
/**
* @notice Computes a generalized index (2^{depth} + indexAtDepth).
* @param _depth The depth of the position.
* @param _indexAtDepth The index at the depth of the position.
* @return position_ The computed generalized index.
*/
/// @notice Computes a generalized index (2^{depth} + indexAtDepth).
/// @param _depth The depth of the position.
/// @param _indexAtDepth The index at the depth of the position.
/// @return position_ The computed generalized index.
function wrap(uint64 _depth, uint64 _indexAtDepth) internal pure returns (Position position_) {
assembly {
// gindex = 2^{_depth} + _indexAtDepth
......@@ -21,12 +18,10 @@ library LibPosition {
}
}
/**
* @notice Pulls the `depth` out of a `Position` type.
* @param _position The generalized index to get the `depth` of.
* @return depth_ The `depth` of the `position` gindex.
* @custom:attribution Solady <https://github.com/Vectorized/Solady>
*/
/// @notice Pulls the `depth` out of a `Position` type.
/// @param _position The generalized index to get the `depth` of.
/// @return depth_ The `depth` of the `position` gindex.
/// @custom:attribution Solady <https://github.com/Vectorized/Solady>
function depth(Position _position) internal pure returns (uint64 depth_) {
// Return the most significant bit offset, which signifies the depth of the gindex.
assembly {
......@@ -51,14 +46,12 @@ library LibPosition {
}
}
/**
* @notice Pulls the `indexAtDepth` out of a `Position` type.
* The `indexAtDepth` is the left/right index of a position at a specific depth within
* the binary tree, starting from index 0. For example, at gindex 2, the `depth` = 1
* and the `indexAtDepth` = 0.
* @param _position The generalized index to get the `indexAtDepth` of.
* @return indexAtDepth_ The `indexAtDepth` of the `position` gindex.
*/
/// @notice Pulls the `indexAtDepth` out of a `Position` type.
/// The `indexAtDepth` is the left/right index of a position at a specific depth within
/// the binary tree, starting from index 0. For example, at gindex 2, the `depth` = 1
/// and the `indexAtDepth` = 0.
/// @param _position The generalized index to get the `indexAtDepth` of.
/// @return indexAtDepth_ The `indexAtDepth` of the `position` gindex.
function indexAtDepth(Position _position) internal pure returns (uint64 indexAtDepth_) {
// Return bits p_{msb-1}...p_{0}. This effectively pulls the 2^{depth} out of the gindex,
// leaving only the `indexAtDepth`.
......@@ -68,46 +61,38 @@ library LibPosition {
}
}
/**
* @notice Get the left child of `_position`.
* @param _position The position to get the left position of.
* @return left_ The position to the left of `position`.
*/
/// @notice Get the left child of `_position`.
/// @param _position The position to get the left position of.
/// @return left_ The position to the left of `position`.
function left(Position _position) internal pure returns (Position left_) {
assembly {
left_ := shl(1, _position)
}
}
/**
* @notice Get the right child of `_position`
* @param _position The position to get the right position of.
* @return right_ The position to the right of `position`.
*/
/// @notice Get the right child of `_position`
/// @param _position The position to get the right position of.
/// @return right_ The position to the right of `position`.
function right(Position _position) internal pure returns (Position right_) {
assembly {
right_ := or(1, shl(1, _position))
}
}
/**
* @notice Get the parent position of `_position`.
* @param _position The position to get the parent position of.
* @return parent_ The parent position of `position`.
*/
/// @notice Get the parent position of `_position`.
/// @param _position The position to get the parent position of.
/// @return parent_ The parent position of `position`.
function parent(Position _position) internal pure returns (Position parent_) {
assembly {
parent_ := shr(1, _position)
}
}
/**
* @notice Get the deepest, right most gindex relative to the `position`. This is equivalent to
* calling `right` on a position until the maximum depth is reached.
* @param _position The position to get the relative deepest, right most gindex of.
* @param _maxDepth The maximum depth of the game.
* @return rightIndex_ The deepest, right most gindex relative to the `position`.
*/
/// @notice Get the deepest, right most gindex relative to the `position`. This is equivalent to
/// calling `right` on a position until the maximum depth is reached.
/// @param _position The position to get the relative deepest, right most gindex of.
/// @param _maxDepth The maximum depth of the game.
/// @return rightIndex_ The deepest, right most gindex relative to the `position`.
function rightIndex(
Position _position,
uint256 _maxDepth
......@@ -119,14 +104,12 @@ library LibPosition {
}
}
/**
* @notice Get the move position of `_position`, which is the left child of:
* 1. `_position + 1` if `_isAttack` is true.
* 1. `_position` if `_isAttack` is false.
* @param _position The position to get the relative attack/defense position of.
* @param _isAttack Whether or not the move is an attack move.
* @return move_ The move position relative to `position`.
*/
/// @notice Get the move position of `_position`, which is the left child of:
/// 1. `_position + 1` if `_isAttack` is true.
/// 1. `_position` if `_isAttack` is false.
/// @param _position The position to get the relative attack/defense position of.
/// @param _isAttack Whether or not the move is an attack move.
/// @return move_ The move position relative to `position`.
function move(Position _position, bool _isAttack) internal pure returns (Position move_) {
assembly {
move_ := shl(1, or(iszero(_isAttack), _position))
......
......@@ -7,95 +7,65 @@ import "./DisputeTypes.sol";
// `DisputeGameFactory` Errors //
////////////////////////////////////////////////////////////////
/**
* @notice Thrown when a dispute game is attempted to be created with an unsupported game type.
* @param gameType The unsupported game type.
*/
/// @notice Thrown when a dispute game is attempted to be created with an unsupported game type.
/// @param gameType The unsupported game type.
error NoImplementation(GameType gameType);
/**
* @notice Thrown when a dispute game that already exists is attempted to be created.
* @param uuid The UUID of the dispute game that already exists.
*/
/// @notice Thrown when a dispute game that already exists is attempted to be created.
/// @param uuid The UUID of the dispute game that already exists.
error GameAlreadyExists(Hash uuid);
////////////////////////////////////////////////////////////////
// `DisputeGame_Fault.sol` Errors //
////////////////////////////////////////////////////////////////
/**
* @notice Thrown when a supplied bond is too low to cover the
* cost of the next possible counter claim.
*/
/// @notice Thrown when a supplied bond is too low to cover the
/// cost of the next possible counter claim.
error BondTooLow();
/**
* @notice Thrown when a defense against the root claim is attempted.
*/
/// @notice Thrown when a defense against the root claim is attempted.
error CannotDefendRootClaim();
/**
* @notice Thrown when a claim is attempting to be made that already exists.
*/
/// @notice Thrown when a claim is attempting to be made that already exists.
error ClaimAlreadyExists();
/**
* @notice Thrown when a given claim is invalid (0).
*/
/// @notice Thrown when a given claim is invalid (0).
error InvalidClaim();
/**
* @notice Thrown when an action that requires the game to be `IN_PROGRESS` is invoked when
* the game is not in progress.
*/
/// @notice Thrown when an action that requires the game to be `IN_PROGRESS` is invoked when
/// the game is not in progress.
error GameNotInProgress();
/**
* @notice Thrown when a move is attempted to be made after the clock has timed out.
*/
/// @notice Thrown when a move is attempted to be made after the clock has timed out.
error ClockTimeExceeded();
/**
* @notice Thrown when a move is attempted to be made at or greater than the max depth of the game.
*/
/// @notice Thrown when a move is attempted to be made at or greater than the max depth of the game.
error GameDepthExceeded();
/**
* @notice Thrown when a step is attempted above the maximum game depth.
*/
/// @notice Thrown when a step is attempted above the maximum game depth.
error InvalidParent();
/**
* @notice Thrown when an invalid prestate is supplied to `step`.
*/
/// @notice Thrown when an invalid prestate is supplied to `step`.
error InvalidPrestate();
/**
* @notice Thrown when a step is made that computes the expected post state correctly.
*/
/// @notice Thrown when a step is made that computes the expected post state correctly.
error ValidStep();
////////////////////////////////////////////////////////////////
// `AttestationDisputeGame` Errors //
////////////////////////////////////////////////////////////////
/**
* @notice Thrown when an invalid signature is submitted to `challenge`.
*/
/// @notice Thrown when an invalid signature is submitted to `challenge`.
error InvalidSignature();
/**
* @notice Thrown when a signature that has already been used to support the
* `rootClaim` is submitted to `challenge`.
*/
/// @notice Thrown when a signature that has already been used to support the
/// `rootClaim` is submitted to `challenge`.
error AlreadyChallenged();
////////////////////////////////////////////////////////////////
// `Ownable` Errors //
////////////////////////////////////////////////////////////////
/**
* @notice Thrown when a function that is protected by the `onlyOwner` modifier
* is called from an account other than the owner.
*/
/// @notice Thrown when a function that is protected by the `onlyOwner` modifier
/// is called from an account other than the owner.
error NotOwner();
......@@ -9,79 +9,57 @@ using LibHashing for Claim global;
using LibPosition for Position global;
using LibClock for Clock global;
/**
* @notice A custom type for a generic hash.
*/
/// @notice A custom type for a generic hash.
type Hash is bytes32;
/**
* @notice A claim represents an MPT root representing the state of the fault proof program.
*/
/// @notice A claim represents an MPT root representing the state of the fault proof program.
type Claim is bytes32;
/**
* @notice A claim hash represents a hash of a claim and a position within the game tree.
* @dev Keccak hash of abi.encodePacked(Claim, Position);
*/
/// @notice A claim hash represents a hash of a claim and a position within the game tree.
/// @dev Keccak hash of abi.encodePacked(Claim, Position);
type ClaimHash is bytes32;
/**
* @notice A bondamount represents the amount of collateral that a user has locked up in a claim.
*/
/// @notice A bondamount represents the amount of collateral that a user has locked up in a claim.
type BondAmount is uint256;
/**
* @notice A dedicated timestamp type.
*/
/// @notice A dedicated timestamp type.
type Timestamp is uint64;
/**
* @notice A dedicated duration type.
* @dev Unit: seconds
*/
/// @notice A dedicated duration type.
/// @dev Unit: seconds
type Duration is uint64;
/**
* @notice A `GameId` represents a packed 12 byte timestamp and a 20 byte address.
* @dev The packed layout of this type is as follows:
* ┌────────────┬────────────────┐
* │ Bits │ Value │
* ├────────────┼────────────────┤
* │ [0, 96) │ Timestamp │
* │ [96, 256) │ Address │
* └────────────┴────────────────┘
*/
/// @notice A `GameId` represents a packed 12 byte timestamp and a 20 byte address.
/// @dev The packed layout of this type is as follows:
/// ┌────────────┬────────────────┐
/// │ Bits │ Value │
/// ├────────────┼────────────────┤
/// │ [0, 96) │ Timestamp │
/// │ [96, 256) │ Address │
/// └────────────┴────────────────┘
type GameId is bytes32;
/**
* @notice A `Clock` represents a packed `Duration` and `Timestamp`
* @dev The packed layout of this type is as follows:
* ┌────────────┬────────────────┐
* │ Bits │ Value │
* ├────────────┼────────────────┤
* │ [0, 64) │ Duration │
* │ [64, 128) │ Timestamp │
* └────────────┴────────────────┘
*/
/// @notice A `Clock` represents a packed `Duration` and `Timestamp`
/// @dev The packed layout of this type is as follows:
/// ┌────────────┬────────────────┐
/// │ Bits │ Value │
/// ├────────────┼────────────────┤
/// │ [0, 64) │ Duration │
/// │ [64, 128) │ Timestamp │
/// └────────────┴────────────────┘
type Clock is uint128;
/**
* @notice A `Position` represents a position of a claim within the game tree.
* @dev This is represented as a "generalized index" where the high-order bit
* is the level in the tree and the remaining bits is a unique bit pattern, allowing
* a unique identifier for each node in the tree. Mathematically, it is calculated
* as 2^{depth} + indexAtDepth.
*/
/// @notice A `Position` represents a position of a claim within the game tree.
/// @dev This is represented as a "generalized index" where the high-order bit
/// is the level in the tree and the remaining bits is a unique bit pattern, allowing
/// a unique identifier for each node in the tree. Mathematically, it is calculated
/// as 2^{depth} + indexAtDepth.
type Position is uint128;
/**
* @notice A `GameType` represents the type of game being played.
*/
/// @notice A `GameType` represents the type of game being played.
type GameType is uint8;
/**
* @notice The current status of the dispute game.
*/
/// @notice The current status of the dispute game.
enum GameStatus {
// The game is currently in progress, and has not been resolved.
IN_PROGRESS,
......@@ -91,23 +69,15 @@ enum GameStatus {
DEFENDER_WINS
}
/**
* @title GameTypes
* @notice A library that defines the IDs of games that can be played.
*/
/// @title GameTypes
/// @notice A library that defines the IDs of games that can be played.
library GameTypes {
/**
* @dev The game will use a `IDisputeGame` implementation that utilizes fault proofs.
*/
/// @dev The game will use a `IDisputeGame` implementation that utilizes fault proofs.
GameType internal constant FAULT = GameType.wrap(0);
/**
* @dev The game will use a `IDisputeGame` implementation that utilizes validity proofs.
*/
/// @dev The game will use a `IDisputeGame` implementation that utilizes validity proofs.
GameType internal constant VALIDITY = GameType.wrap(1);
/**
* @dev The game will use a `IDisputeGame` implementation that utilizes attestation proofs.
*/
/// @dev The game will use a `IDisputeGame` implementation that utilizes attestation proofs.
GameType internal constant ATTESTATION = GameType.wrap(2);
}
......@@ -37,10 +37,8 @@ contract DisputeGameFactory_Init is Test {
}
contract DisputeGameFactory_Create_Test is DisputeGameFactory_Init {
/**
* @dev Tests that the `create` function succeeds when creating a new dispute game
* with a `GameType` that has an implementation set.
*/
/// @dev Tests that the `create` function succeeds when creating a new dispute game
/// with a `GameType` that has an implementation set.
function testFuzz_create_succeeds(
uint8 gameType,
Claim rootClaim,
......@@ -70,10 +68,8 @@ contract DisputeGameFactory_Create_Test is DisputeGameFactory_Init {
assertEq(timestamp2, block.timestamp);
}
/**
* @dev Tests that the `create` function reverts when there is no implementation
* set for the given `GameType`.
*/
/// @dev Tests that the `create` function reverts when there is no implementation
/// set for the given `GameType`.
function testFuzz_create_noImpl_reverts(
uint8 gameType,
Claim rootClaim,
......@@ -86,9 +82,7 @@ contract DisputeGameFactory_Create_Test is DisputeGameFactory_Init {
factory.create(gt, rootClaim, extraData);
}
/**
* @dev Tests that the `create` function reverts when there exists a dispute game with the same UUID.
*/
/// @dev Tests that the `create` function reverts when there exists a dispute game with the same UUID.
function testFuzz_create_sameUUID_reverts(
uint8 gameType,
Claim rootClaim,
......@@ -124,9 +118,7 @@ contract DisputeGameFactory_Create_Test is DisputeGameFactory_Init {
}
contract DisputeGameFactory_SetImplementation_Test is DisputeGameFactory_Init {
/**
* @dev Tests that the `setImplementation` function properly sets the implementation for a given `GameType`.
*/
/// @dev Tests that the `setImplementation` function properly sets the implementation for a given `GameType`.
function test_setImplementation_succeeds() public {
// There should be no implementation for the `GameTypes.FAULT` enum value, it has not been set.
assertEq(address(factory.gameImpls(GameTypes.FAULT)), address(0));
......@@ -141,9 +133,7 @@ contract DisputeGameFactory_SetImplementation_Test is DisputeGameFactory_Init {
assertEq(address(factory.gameImpls(GameTypes.FAULT)), address(1));
}
/**
* @dev Tests that the `setImplementation` function reverts when called by a non-owner.
*/
/// @dev Tests that the `setImplementation` function reverts when called by a non-owner.
function test_setImplementation_notOwner_reverts() public {
// Ensure that the `setImplementation` function reverts when called by a non-owner.
vm.prank(address(0));
......@@ -153,10 +143,8 @@ contract DisputeGameFactory_SetImplementation_Test is DisputeGameFactory_Init {
}
contract DisputeGameFactory_GetGameUUID_Test is DisputeGameFactory_Init {
/**
* @dev Tests that the `getGameUUID` function returns the correct hash when comparing
* against the keccak256 hash of the abi-encoded parameters.
*/
/// @dev Tests that the `getGameUUID` function returns the correct hash when comparing
/// against the keccak256 hash of the abi-encoded parameters.
function testDiff_getGameUUID_succeeds(
uint8 gameType,
Claim rootClaim,
......@@ -173,26 +161,20 @@ contract DisputeGameFactory_GetGameUUID_Test is DisputeGameFactory_Init {
}
contract DisputeGameFactory_Owner_Test is DisputeGameFactory_Init {
/**
* @dev Tests that the `owner` function returns the correct address after deployment.
*/
/// @dev Tests that the `owner` function returns the correct address after deployment.
function test_owner_succeeds() public {
assertEq(factory.owner(), address(this));
}
}
contract DisputeGameFactory_TransferOwnership_Test is DisputeGameFactory_Init {
/**
* @dev Tests that the `transferOwnership` function succeeds when called by the owner.
*/
/// @dev Tests that the `transferOwnership` function succeeds when called by the owner.
function test_transferOwnership_succeeds() public {
factory.transferOwnership(address(1));
assertEq(factory.owner(), address(1));
}
/**
* @dev Tests that the `transferOwnership` function reverts when called by a non-owner.
*/
/// @dev Tests that the `transferOwnership` function reverts when called by a non-owner.
function test_transferOwnership_notOwner_reverts() public {
vm.prank(address(0));
vm.expectRevert("Ownable: caller is not the owner");
......@@ -200,11 +182,9 @@ contract DisputeGameFactory_TransferOwnership_Test is DisputeGameFactory_Init {
}
}
/**
* @title PackingTester
* @notice Exposes the internal packing functions so that they can be fuzzed
* in a roundtrip manner.
*/
/// @title PackingTester
/// @notice Exposes the internal packing functions so that they can be fuzzed
/// in a roundtrip manner.
contract PackingTester is DisputeGameFactory {
function packSlot(address _addr, uint256 _num) external pure returns (GameId) {
return _packSlot(_addr, _num);
......@@ -215,10 +195,8 @@ contract PackingTester is DisputeGameFactory {
}
}
/**
* @title DisputeGameFactory_PackSlot_Test
* @notice Fuzzes the PackingTester contract
*/
/// @title DisputeGameFactory_PackSlot_Test
/// @notice Fuzzes the PackingTester contract
contract DisputeGameFactory_PackSlot_Test is Test {
PackingTester tester;
......@@ -226,9 +204,7 @@ contract DisputeGameFactory_PackSlot_Test is Test {
tester = new PackingTester();
}
/**
* @dev Tests that the `packSlot` and `unpackSlot` functions roundtrip correctly.
*/
/// @dev Tests that the `packSlot` and `unpackSlot` functions roundtrip correctly.
function testFuzz_packSlot_succeeds(address _addr, uint96 _num) public {
GameId slot = tester.packSlot(_addr, uint256(_num));
(address addr, uint256 num) = tester.unpackSlot(slot);
......@@ -237,9 +213,7 @@ contract DisputeGameFactory_PackSlot_Test is Test {
}
}
/**
* @dev A fake clone used for testing the `DisputeGameFactory` contract's `create` function.
*/
/// @dev A fake clone used for testing the `DisputeGameFactory` contract's `create` function.
contract FakeClone {
function initialize() external {
// noop
......
......@@ -13,22 +13,14 @@ import { LibClock } from "../dispute/lib/LibClock.sol";
import { LibPosition } from "../dispute/lib/LibPosition.sol";
contract FaultDisputeGame_Init is DisputeGameFactory_Init {
/**
* @dev The extra data passed to the game for initialization.
*/
/// @dev The extra data passed to the game for initialization.
bytes internal constant EXTRA_DATA = abi.encode(1);
/**
* @dev The type of the game being tested.
*/
/// @dev The type of the game being tested.
GameType internal constant GAME_TYPE = GameType.wrap(0);
/**
* @dev The implementation of the game.
*/
/// @dev The implementation of the game.
FaultDisputeGame internal gameImpl;
/**
* @dev The `Clone` proxy of the game.
*/
/// @dev The `Clone` proxy of the game.
FaultDisputeGame internal gameProxy;
event Move(uint256 indexed parentIndex, Claim indexed pivot, address indexed claimant);
......@@ -48,13 +40,9 @@ contract FaultDisputeGame_Init is DisputeGameFactory_Init {
}
contract FaultDisputeGame_Test is FaultDisputeGame_Init {
/**
* @dev The root claim of the game.
*/
/// @dev The root claim of the game.
Claim internal constant ROOT_CLAIM = Claim.wrap(bytes32(uint256(10)));
/**
* @dev The absolute prestate of the trace.
*/
/// @dev The absolute prestate of the trace.
Claim internal constant ABSOLUTE_PRESTATE = Claim.wrap(bytes32(uint256(0)));
function setUp() public override {
......@@ -65,37 +53,27 @@ contract FaultDisputeGame_Test is FaultDisputeGame_Init {
// `IDisputeGame` Implementation Tests //
////////////////////////////////////////////////////////////////
/**
* @dev Tests that the game's root claim is set correctly.
*/
/// @dev Tests that the game's root claim is set correctly.
function test_rootClaim_succeeds() public {
assertEq(Claim.unwrap(gameProxy.rootClaim()), Claim.unwrap(ROOT_CLAIM));
}
/**
* @dev Tests that the game's extra data is set correctly.
*/
/// @dev Tests that the game's extra data is set correctly.
function test_extraData_succeeds() public {
assertEq(gameProxy.extraData(), EXTRA_DATA);
}
/**
* @dev Tests that the game's status is set correctly.
*/
/// @dev Tests that the game's status is set correctly.
function test_gameStart_succeeds() public {
assertEq(Timestamp.unwrap(gameProxy.gameStart()), block.timestamp);
}
/**
* @dev Tests that the game's type is set correctly.
*/
/// @dev Tests that the game's type is set correctly.
function test_gameType_succeeds() public {
assertEq(GameType.unwrap(gameProxy.gameType()), GameType.unwrap(GAME_TYPE));
}
/**
* @dev Tests that the game's data is set correctly.
*/
/// @dev Tests that the game's data is set correctly.
function test_gameData_succeeds() public {
(GameType gameType, Claim rootClaim, bytes memory extraData) = gameProxy.gameData();
......@@ -108,9 +86,7 @@ contract FaultDisputeGame_Test is FaultDisputeGame_Init {
// `IFaultDisputeGame` Implementation Tests //
////////////////////////////////////////////////////////////////
/**
* @dev Tests that the root claim's data is set correctly when the game is initialized.
*/
/// @dev Tests that the root claim's data is set correctly when the game is initialized.
function test_initialRootClaimData_succeeds() public {
(
uint32 parentIndex,
......@@ -130,10 +106,8 @@ contract FaultDisputeGame_Test is FaultDisputeGame_Init {
);
}
/**
* @dev Tests that a move while the game status is not `IN_PROGRESS` causes the call to revert
* with the `GameNotInProgress` error
*/
/// @dev Tests that a move while the game status is not `IN_PROGRESS` causes the call to revert
/// with the `GameNotInProgress` error
function test_move_gameNotInProgress_reverts() public {
uint256 chalWins = uint256(GameStatus.CHALLENGER_WINS);
......@@ -154,18 +128,14 @@ contract FaultDisputeGame_Test is FaultDisputeGame_Init {
gameProxy.attack(0, Claim.wrap(0));
}
/**
* @dev Tests that an attempt to defend the root claim reverts with the `CannotDefendRootClaim` error.
*/
/// @dev Tests that an attempt to defend the root claim reverts with the `CannotDefendRootClaim` error.
function test_defendRoot_invalidMove_reverts() public {
vm.expectRevert(CannotDefendRootClaim.selector);
gameProxy.defend(0, Claim.wrap(bytes32(uint256(5))));
}
/**
* @dev Tests that an attempt to move against a claim that does not exist reverts with the
* `ParentDoesNotExist` error.
*/
/// @dev Tests that an attempt to move against a claim that does not exist reverts with the
/// `ParentDoesNotExist` error.
function test_move_nonExistentParent_reverts() public {
Claim claim = Claim.wrap(bytes32(uint256(5)));
......@@ -178,10 +148,8 @@ contract FaultDisputeGame_Test is FaultDisputeGame_Init {
gameProxy.defend(1, claim);
}
/**
* @dev Tests that an attempt to move at the maximum game depth reverts with the
* `GameDepthExceeded` error.
*/
/// @dev Tests that an attempt to move at the maximum game depth reverts with the
/// `GameDepthExceeded` error.
function test_move_gameDepthExceeded_reverts() public {
Claim claim = Claim.wrap(bytes32(uint256(5)));
......@@ -197,10 +165,8 @@ contract FaultDisputeGame_Test is FaultDisputeGame_Init {
}
}
/**
* @dev Tests that a move made after the clock time has exceeded reverts with the
* `ClockTimeExceeded` error.
*/
/// @dev Tests that a move made after the clock time has exceeded reverts with the
/// `ClockTimeExceeded` error.
function test_move_clockTimeExceeded_reverts() public {
// Warp ahead past the clock time for the first move (3 1/2 days)
vm.warp(block.timestamp + 3 days + 12 hours + 1);
......@@ -208,10 +174,8 @@ contract FaultDisputeGame_Test is FaultDisputeGame_Init {
gameProxy.attack(0, Claim.wrap(bytes32(uint256(5))));
}
/**
* @dev Tests that an identical claim cannot be made twice. The duplicate claim attempt should
* revert with the `ClaimAlreadyExists` error.
*/
/// @dev Tests that an identical claim cannot be made twice. The duplicate claim attempt should
/// revert with the `ClaimAlreadyExists` error.
function test_move_duplicateClaim_reverts() public {
Claim claim = Claim.wrap(bytes32(uint256(5)));
......@@ -223,9 +187,7 @@ contract FaultDisputeGame_Test is FaultDisputeGame_Init {
gameProxy.attack(0, claim);
}
/**
* @dev Static unit test for the correctness of an opening attack.
*/
/// @dev Static unit test for the correctness of an opening attack.
function test_simpleAttack_succeeds() public {
// Warp ahead 5 seconds.
vm.warp(block.timestamp + 5);
......@@ -272,19 +234,15 @@ contract FaultDisputeGame_Test is FaultDisputeGame_Init {
);
}
/**
* @dev Static unit test for the correctness an uncontested root resolution.
*/
/// @dev Static unit test for the correctness an uncontested root resolution.
function test_resolve_rootUncontested() public {
GameStatus status = gameProxy.resolve();
assertEq(uint8(status), uint8(GameStatus.DEFENDER_WINS));
assertEq(uint8(gameProxy.status()), uint8(GameStatus.DEFENDER_WINS));
}
/**
* @dev Static unit test asserting that resolve reverts when the game state is
* not in progress.
*/
/// @dev Static unit test asserting that resolve reverts when the game state is
/// not in progress.
function test_resolve_notInProgress_reverts() public {
uint256 chalWins = uint256(GameStatus.CHALLENGER_WINS);
......@@ -300,9 +258,7 @@ contract FaultDisputeGame_Test is FaultDisputeGame_Init {
gameProxy.resolve();
}
/**
* @dev Static unit test for the correctness of resolving a single attack game state.
*/
/// @dev Static unit test for the correctness of resolving a single attack game state.
function test_resolve_rootContested() public {
gameProxy.attack(0, Claim.wrap(bytes32(uint256(5))));
......@@ -311,9 +267,7 @@ contract FaultDisputeGame_Test is FaultDisputeGame_Init {
assertEq(uint8(gameProxy.status()), uint8(GameStatus.CHALLENGER_WINS));
}
/**
* @dev Static unit test for the correctness of resolving a game with a contested challenge claim.
*/
/// @dev Static unit test for the correctness of resolving a game with a contested challenge claim.
function test_resolve_challengeContested() public {
gameProxy.attack(0, Claim.wrap(bytes32(uint256(5))));
gameProxy.defend(1, Claim.wrap(bytes32(uint256(6))));
......@@ -323,9 +277,7 @@ contract FaultDisputeGame_Test is FaultDisputeGame_Init {
assertEq(uint8(gameProxy.status()), uint8(GameStatus.DEFENDER_WINS));
}
/**
* @dev Static unit test for the correctness of resolving a game with multiplayer moves.
*/
/// @dev Static unit test for the correctness of resolving a game with multiplayer moves.
function test_resolve_teamDeathmatch() public {
gameProxy.attack(0, Claim.wrap(bytes32(uint256(5))));
gameProxy.attack(0, Claim.wrap(bytes32(uint256(4))));
......@@ -338,11 +290,9 @@ contract FaultDisputeGame_Test is FaultDisputeGame_Init {
}
}
/**
* @notice A generic game player actor with a configurable trace.
* @dev This actor always responds rationally with respect to their trace. The
* `play` function can be overridden to change this behavior.
*/
/// @notice A generic game player actor with a configurable trace.
/// @dev This actor always responds rationally with respect to their trace. The
/// `play` function can be overridden to change this behavior.
contract GamePlayer {
bool public failedToStep;
FaultDisputeGame public gameProxy;
......@@ -352,9 +302,7 @@ contract GamePlayer {
bytes internal trace;
uint256 internal maxDepth;
/**
* @notice Initializes the player
*/
/// @notice Initializes the player
function init(
FaultDisputeGame _gameProxy,
GamePlayer _counterParty,
......@@ -366,9 +314,7 @@ contract GamePlayer {
maxDepth = _gameProxy.MAX_GAME_DEPTH();
}
/**
* @notice Perform the next move in the game.
*/
/// @notice Perform the next move in the game.
function play(uint256 _parentIndex) public virtual {
// Grab the claim data at the parent index.
(uint32 grandparentIndex, , Claim parentClaim, Position parentPos, ) = gameProxy.claimData(
......@@ -488,40 +434,28 @@ contract GamePlayer {
}
}
/**
* @notice Returns the length of the claim data array.
*/
/// @notice Returns the length of the claim data array.
function claimDataLen() internal view returns (uint256 len_) {
return uint256(vm.load(address(gameProxy), bytes32(uint256(1))));
}
/**
* @notice Returns the player's claim that commits to a given gindex.
*/
/// @notice Returns the player's claim that commits to a given gindex.
function claimAt(Position _position) internal view returns (Claim claim_) {
return claimAt(_position.rightIndex(maxDepth).indexAtDepth());
}
/**
* @notice Returns the player's claim that commits to a given trace index.
*/
/// @notice Returns the player's claim that commits to a given trace index.
function claimAt(uint256 _traceIndex) public view returns (Claim claim_) {
return Claim.wrap(bytes32(uint256(bytes32(trace[_traceIndex]) >> 248)));
}
}
contract OneVsOne_Arena is FaultDisputeGame_Init {
/**
* @dev The absolute prestate of the trace.
*/
/// @dev The absolute prestate of the trace.
Claim internal constant ABSOLUTE_PRESTATE = Claim.wrap(bytes32(uint256(15)));
/**
* @dev The honest participant.
*/
/// @dev The honest participant.
GamePlayer internal honest;
/**
* @dev The dishonest participant.
*/
/// @dev The dishonest participant.
GamePlayer internal dishonest;
function init(
......
......@@ -5,21 +5,15 @@ import { Test } from "forge-std/Test.sol";
import { LibClock } from "../dispute/lib/LibClock.sol";
import "../libraries/DisputeTypes.sol";
/**
* @notice Tests for `LibClock`
*/
/// @notice Tests for `LibClock`
contract LibClock_Test is Test {
/**
* @notice Tests that the `duration` function correctly shifts out the `Duration` from a packed `Clock` type.
*/
/// @notice Tests that the `duration` function correctly shifts out the `Duration` from a packed `Clock` type.
function testFuzz_duration_succeeds(Duration _duration, Timestamp _timestamp) public {
Clock clock = LibClock.wrap(_duration, _timestamp);
assertEq(Duration.unwrap(clock.duration()), Duration.unwrap(_duration));
}
/**
* @notice Tests that the `timestamp` function correctly shifts out the `Timestamp` from a packed `Clock` type.
*/
/// @notice Tests that the `timestamp` function correctly shifts out the `Timestamp` from a packed `Clock` type.
function testFuzz_timestamp_succeeds(Duration _duration, Timestamp _timestamp) public {
Clock clock = LibClock.wrap(_duration, _timestamp);
assertEq(Timestamp.unwrap(clock.timestamp()), Timestamp.unwrap(_timestamp));
......
......@@ -5,15 +5,11 @@ import { Test } from "forge-std/Test.sol";
import { LibPosition } from "../dispute/lib/LibPosition.sol";
import "../libraries/DisputeTypes.sol";
/**
* @notice Tests for `LibPosition`
*/
/// @notice Tests for `LibPosition`
contract LibPosition_Test is Test {
/**
* @dev Assumes a MAX depth of 63 for the Position type. Any greater depth can cause overflows.
* @dev At the lowest level of the tree, this allows for 2 ** 63 leaves. In reality, the max game depth
* will likely be much lower.
*/
/// @dev Assumes a MAX depth of 63 for the Position type. Any greater depth can cause overflows.
/// @dev At the lowest level of the tree, this allows for 2 ** 63 leaves. In reality, the max game depth
/// will likely be much lower.
uint8 internal constant MAX_DEPTH = 63;
function boundIndexAtDepth(uint8 _depth, uint64 _indexAtDepth) internal view returns (uint64) {
......@@ -25,9 +21,7 @@ contract LibPosition_Test is Test {
}
}
/**
* @notice Tests that the `depth` function correctly shifts out the `depth` from a packed `Position` type.
*/
/// @notice Tests that the `depth` function correctly shifts out the `depth` from a packed `Position` type.
function testFuzz_depth_correctness_suceeds(uint8 _depth, uint64 _indexAtDepth) public {
_depth = uint8(bound(_depth, 0, MAX_DEPTH));
_indexAtDepth = boundIndexAtDepth(_depth, _indexAtDepth);
......@@ -35,9 +29,7 @@ contract LibPosition_Test is Test {
assertEq(position.depth(), _depth);
}
/**
* @notice Tests that the `indexAtDepth` function correctly shifts out the `indexAtDepth` from a packed `Position` type.
*/
/// @notice Tests that the `indexAtDepth` function correctly shifts out the `indexAtDepth` from a packed `Position` type.
function testFuzz_indexAtDepth_correctness_suceeds(uint8 _depth, uint64 _indexAtDepth) public {
_depth = uint8(bound(_depth, 0, MAX_DEPTH));
_indexAtDepth = boundIndexAtDepth(_depth, _indexAtDepth);
......@@ -45,9 +37,7 @@ contract LibPosition_Test is Test {
assertEq(position.indexAtDepth(), _indexAtDepth);
}
/**
* @notice Tests that the `left` function correctly computes the position of the left child.
*/
/// @notice Tests that the `left` function correctly computes the position of the left child.
function testFuzz_left_correctness_suceeds(uint8 _depth, uint64 _indexAtDepth) public {
_depth = uint8(bound(_depth, 0, MAX_DEPTH));
_indexAtDepth = boundIndexAtDepth(_depth, _indexAtDepth);
......@@ -59,9 +49,7 @@ contract LibPosition_Test is Test {
assertEq(left.indexAtDepth(), _indexAtDepth * 2);
}
/**
* @notice Tests that the `right` function correctly computes the position of the right child.
*/
/// @notice Tests that the `right` function correctly computes the position of the right child.
function testFuzz_right_correctness_suceeds(uint8 _depth, uint64 _indexAtDepth) public {
// Depth bound: [0, 63]
_depth = uint8(bound(_depth, 0, MAX_DEPTH));
......@@ -74,9 +62,7 @@ contract LibPosition_Test is Test {
assertEq(right.indexAtDepth(), _indexAtDepth * 2 + 1);
}
/**
* @notice Tests that the `parent` function correctly computes the position of the parent.
*/
/// @notice Tests that the `parent` function correctly computes the position of the parent.
function testFuzz_parent_correctness_suceeds(uint8 _depth, uint64 _indexAtDepth) public {
_depth = uint8(bound(_depth, 1, MAX_DEPTH));
_indexAtDepth = boundIndexAtDepth(_depth, _indexAtDepth);
......@@ -88,10 +74,8 @@ contract LibPosition_Test is Test {
assertEq(parent.indexAtDepth(), _indexAtDepth / 2);
}
/**
* @notice Tests that the `rightIndex` function correctly computes the deepest, right most index relative
* to a given position.
*/
/// @notice Tests that the `rightIndex` function correctly computes the deepest, right most index relative
/// to a given position.
function testFuzz_rightIndex_correctness_suceeds(
uint64 _maxDepth,
uint8 _depth,
......@@ -115,11 +99,9 @@ contract LibPosition_Test is Test {
assertEq(Position.unwrap(rightIndex), Position.unwrap(position));
}
/**
* @notice Tests that the `attack` function correctly computes the position of the attack relative to
* a given position.
* @dev `attack` is an alias for `left`, but we test it separately for completeness.
*/
/// @notice Tests that the `attack` function correctly computes the position of the attack relative to
/// a given position.
/// @dev `attack` is an alias for `left`, but we test it separately for completeness.
function testFuzz_attack_correctness_suceeds(uint8 _depth, uint64 _indexAtDepth) public {
// Depth bound: [0, 63]
_depth = uint8(bound(_depth, 0, MAX_DEPTH));
......@@ -132,12 +114,10 @@ contract LibPosition_Test is Test {
assertEq(attack.indexAtDepth(), _indexAtDepth * 2);
}
/**
* @notice Tests that the `defend` function correctly computes the position of the defense relative to
* a given position.
* @dev A defense can only be given if the position does not belong to the root claim, hence the bound of [1, 127]
* on the depth.
*/
/// @notice Tests that the `defend` function correctly computes the position of the defense relative to
/// a given position.
/// @dev A defense can only be given if the position does not belong to the root claim, hence the bound of [1, 127]
/// on the depth.
function testFuzz_defend_correctness_suceeds(uint8 _depth, uint64 _indexAtDepth) public {
// Depth bound: [1, 63]
_depth = uint8(bound(_depth, 1, MAX_DEPTH));
......@@ -150,10 +130,8 @@ contract LibPosition_Test is Test {
assertEq(defend.indexAtDepth(), ((_indexAtDepth / 2) * 2 + 1) * 2);
}
/**
* @notice A static unit test for the correctness of all gindicies, (depth, index) combos,
* and the trace index in a tree of max depth = 4.
*/
/// @notice A static unit test for the correctness of all gindicies, (depth, index) combos,
/// and the trace index in a tree of max depth = 4.
function test_pos_correctness_succeeds() public {
uint256 maxDepth = 4;
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment