Commit 5a13203d authored by clabby's avatar clabby

Dispute contract cleanups

parent 747876d0
This diff is collapsed.
This diff is collapsed.
...@@ -87,37 +87,37 @@ FaucetTest:test_nonAdmin_drip_fails() (gas: 262520) ...@@ -87,37 +87,37 @@ FaucetTest:test_nonAdmin_drip_fails() (gas: 262520)
FaucetTest:test_receive_succeeds() (gas: 17401) FaucetTest:test_receive_succeeds() (gas: 17401)
FaucetTest:test_withdraw_nonAdmin_reverts() (gas: 13145) FaucetTest:test_withdraw_nonAdmin_reverts() (gas: 13145)
FaucetTest:test_withdraw_succeeds() (gas: 78359) FaucetTest:test_withdraw_succeeds() (gas: 78359)
FaultDisputeGame_ResolvesCorrectly_CorrectRoot1:test_resolvesCorrectly_succeeds() (gas: 498844) FaultDisputeGame_ResolvesCorrectly_CorrectRoot1:test_resolvesCorrectly_succeeds() (gas: 498906)
FaultDisputeGame_ResolvesCorrectly_CorrectRoot2:test_resolvesCorrectly_succeeds() (gas: 505718) FaultDisputeGame_ResolvesCorrectly_CorrectRoot2:test_resolvesCorrectly_succeeds() (gas: 505746)
FaultDisputeGame_ResolvesCorrectly_CorrectRoot3:test_resolvesCorrectly_succeeds() (gas: 502387) FaultDisputeGame_ResolvesCorrectly_CorrectRoot3:test_resolvesCorrectly_succeeds() (gas: 502447)
FaultDisputeGame_ResolvesCorrectly_CorrectRoot4:test_resolvesCorrectly_succeeds() (gas: 505594) FaultDisputeGame_ResolvesCorrectly_CorrectRoot4:test_resolvesCorrectly_succeeds() (gas: 505644)
FaultDisputeGame_ResolvesCorrectly_CorrectRoot5:test_resolvesCorrectly_succeeds() (gas: 504883) FaultDisputeGame_ResolvesCorrectly_CorrectRoot5:test_resolvesCorrectly_succeeds() (gas: 504933)
FaultDisputeGame_ResolvesCorrectly_IncorrectRoot1:test_resolvesCorrectly_succeeds() (gas: 497609) FaultDisputeGame_ResolvesCorrectly_IncorrectRoot1:test_resolvesCorrectly_succeeds() (gas: 497671)
FaultDisputeGame_ResolvesCorrectly_IncorrectRoot2:test_resolvesCorrectly_succeeds() (gas: 504483) FaultDisputeGame_ResolvesCorrectly_IncorrectRoot2:test_resolvesCorrectly_succeeds() (gas: 504511)
FaultDisputeGame_ResolvesCorrectly_IncorrectRoot3:test_resolvesCorrectly_succeeds() (gas: 501152) FaultDisputeGame_ResolvesCorrectly_IncorrectRoot3:test_resolvesCorrectly_succeeds() (gas: 501212)
FaultDisputeGame_ResolvesCorrectly_IncorrectRoot4:test_resolvesCorrectly_succeeds() (gas: 502359) FaultDisputeGame_ResolvesCorrectly_IncorrectRoot4:test_resolvesCorrectly_succeeds() (gas: 502409)
FaultDisputeGame_ResolvesCorrectly_IncorrectRoot5:test_resolvesCorrectly_succeeds() (gas: 501648) FaultDisputeGame_ResolvesCorrectly_IncorrectRoot5:test_resolvesCorrectly_succeeds() (gas: 501698)
FaultDisputeGame_Test:test_createdAt_succeeds() (gas: 10364)
FaultDisputeGame_Test:test_extraData_succeeds() (gas: 32354) FaultDisputeGame_Test:test_extraData_succeeds() (gas: 32354)
FaultDisputeGame_Test:test_gameData_succeeds() (gas: 32806) FaultDisputeGame_Test:test_gameData_succeeds() (gas: 32806)
FaultDisputeGame_Test:test_gameStart_succeeds() (gas: 10388)
FaultDisputeGame_Test:test_gameType_succeeds() (gas: 8272) FaultDisputeGame_Test:test_gameType_succeeds() (gas: 8272)
FaultDisputeGame_Test:test_initialize_correctData_succeeds() (gas: 57716) FaultDisputeGame_Test:test_initialize_correctData_succeeds() (gas: 57716)
FaultDisputeGame_Test:test_initialize_firstOutput_reverts() (gas: 210532) FaultDisputeGame_Test:test_initialize_firstOutput_reverts() (gas: 210532)
FaultDisputeGame_Test:test_initialize_l1HeadTooOld_reverts() (gas: 228402) FaultDisputeGame_Test:test_initialize_l1HeadTooOld_reverts() (gas: 228402)
FaultDisputeGame_Test:test_move_clockCorrectness_succeeds() (gas: 415921) FaultDisputeGame_Test:test_move_clockCorrectness_succeeds() (gas: 415993)
FaultDisputeGame_Test:test_move_clockTimeExceeded_reverts() (gas: 26428) FaultDisputeGame_Test:test_move_clockTimeExceeded_reverts() (gas: 23175)
FaultDisputeGame_Test:test_move_defendRoot_reverts() (gas: 13388) FaultDisputeGame_Test:test_move_defendRoot_reverts() (gas: 13366)
FaultDisputeGame_Test:test_move_duplicateClaim_reverts() (gas: 103377) FaultDisputeGame_Test:test_move_duplicateClaim_reverts() (gas: 102942)
FaultDisputeGame_Test:test_move_gameDepthExceeded_reverts() (gas: 408294) FaultDisputeGame_Test:test_move_gameDepthExceeded_reverts() (gas: 407913)
FaultDisputeGame_Test:test_move_gameNotInProgress_reverts() (gas: 11024) FaultDisputeGame_Test:test_move_gameNotInProgress_reverts() (gas: 11024)
FaultDisputeGame_Test:test_move_nonExistentParent_reverts() (gas: 24689) FaultDisputeGame_Test:test_move_nonExistentParent_reverts() (gas: 24667)
FaultDisputeGame_Test:test_move_simpleAttack_succeeds() (gas: 107300) FaultDisputeGame_Test:test_move_simpleAttack_succeeds() (gas: 107318)
FaultDisputeGame_Test:test_resolve_challengeContested_succeeds() (gas: 224869) FaultDisputeGame_Test:test_resolve_challengeContested_succeeds() (gas: 224883)
FaultDisputeGame_Test:test_resolve_notInProgress_reverts() (gas: 9664) FaultDisputeGame_Test:test_resolve_notInProgress_reverts() (gas: 9664)
FaultDisputeGame_Test:test_resolve_rootContested_succeeds() (gas: 109838) FaultDisputeGame_Test:test_resolve_rootContested_succeeds() (gas: 109856)
FaultDisputeGame_Test:test_resolve_rootUncontestedClockNotExpired_succeeds() (gas: 21443) FaultDisputeGame_Test:test_resolve_rootUncontestedClockNotExpired_succeeds() (gas: 21443)
FaultDisputeGame_Test:test_resolve_rootUncontested_succeeds() (gas: 27278) FaultDisputeGame_Test:test_resolve_rootUncontested_succeeds() (gas: 27278)
FaultDisputeGame_Test:test_resolve_teamDeathmatch_succeeds() (gas: 395607) FaultDisputeGame_Test:test_resolve_teamDeathmatch_succeeds() (gas: 395635)
FaultDisputeGame_Test:test_rootClaim_succeeds() (gas: 8253) FaultDisputeGame_Test:test_rootClaim_succeeds() (gas: 8253)
FeeVault_Test:test_constructor_succeeds() (gas: 18185) FeeVault_Test:test_constructor_succeeds() (gas: 18185)
GasBenchMark_L1CrossDomainMessenger:test_sendMessage_benchmark_0() (gas: 352113) GasBenchMark_L1CrossDomainMessenger:test_sendMessage_benchmark_0() (gas: 352113)
......
...@@ -14,7 +14,7 @@ ...@@ -14,7 +14,7 @@
"src/L2/L2StandardBridge.sol": "0x73a4fea3dca8ac7d7ba32e38aadeb69bd344042666a40a75e8c28849f01999e5", "src/L2/L2StandardBridge.sol": "0x73a4fea3dca8ac7d7ba32e38aadeb69bd344042666a40a75e8c28849f01999e5",
"src/L2/L2ToL1MessagePasser.sol": "0xed800b600cb3f67e18a1ab10750e3934a8b3e42178f422bcacfde770a6e8e8bd", "src/L2/L2ToL1MessagePasser.sol": "0xed800b600cb3f67e18a1ab10750e3934a8b3e42178f422bcacfde770a6e8e8bd",
"src/L2/SequencerFeeVault.sol": "0xd57c143b1f042400430b991b806bf971628e6980406c751e82d19ae80eeb4e8d", "src/L2/SequencerFeeVault.sol": "0xd57c143b1f042400430b991b806bf971628e6980406c751e82d19ae80eeb4e8d",
"src/dispute/FaultDisputeGame.sol": "0x01b3770e4d056550e71b4b9e9ada5401de98668d0a1e0ff336ed8f73a0127e99", "src/dispute/FaultDisputeGame.sol": "0xc191d9caad8498c46035e9bceb1ee372be24f9d7f6cb9f8fda8f646bc875d338",
"src/legacy/DeployerWhitelist.sol": "0x5e80f7b13ef73f06c63bd9b118a49da1ff06a5c0fcf8067b5a3365d731c23765", "src/legacy/DeployerWhitelist.sol": "0x5e80f7b13ef73f06c63bd9b118a49da1ff06a5c0fcf8067b5a3365d731c23765",
"src/legacy/L1BlockNumber.sol": "0x84cc587148de5920dfcd19da44d28e769f0e4d08ca2bcc93f18aa78c6cc2ebe6", "src/legacy/L1BlockNumber.sol": "0x84cc587148de5920dfcd19da44d28e769f0e4d08ca2bcc93f18aa78c6cc2ebe6",
"src/legacy/LegacyMessagePasser.sol": "0x2692b50b227e5f75a53439c0cf303498edfd4fc087555b3fc9bc4bceb518229b", "src/legacy/LegacyMessagePasser.sol": "0x2692b50b227e5f75a53439c0cf303498edfd4fc087555b3fc9bc4bceb518229b",
......
// SPDX-License-Identifier: MIT // SPDX-License-Identifier: MIT
pragma solidity ^0.8.15; pragma solidity ^0.8.15;
import "../libraries/DisputeTypes.sol";
import "../libraries/DisputeErrors.sol";
import { ClonesWithImmutableArgs } from "@cwia/ClonesWithImmutableArgs.sol"; import { ClonesWithImmutableArgs } from "@cwia/ClonesWithImmutableArgs.sol";
import { import {
OwnableUpgradeable OwnableUpgradeable
} from "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol"; } from "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol";
import { Semver } from "../universal/Semver.sol"; import { Semver } from "src/universal/Semver.sol";
import { IDisputeGame } from "./interfaces/IDisputeGame.sol"; import { IDisputeGame } from "./interfaces/IDisputeGame.sol";
import { IDisputeGameFactory } from "./interfaces/IDisputeGameFactory.sol"; import { IDisputeGameFactory } from "./interfaces/IDisputeGameFactory.sol";
import "src/libraries/DisputeTypes.sol";
import "src/libraries/DisputeErrors.sol";
/// @title DisputeGameFactory /// @title DisputeGameFactory
/// @notice A factory contract for creating `IDisputeGame` contracts. All created dispute games /// @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 /// are stored in both a mapping and an append only array. The timestamp of the creation
...@@ -37,7 +37,7 @@ contract DisputeGameFactory is OwnableUpgradeable, IDisputeGameFactory, Semver { ...@@ -37,7 +37,7 @@ contract DisputeGameFactory is OwnableUpgradeable, IDisputeGameFactory, Semver {
GameId[] internal _disputeGameList; GameId[] internal _disputeGameList;
/// @notice constructs a new DisputeGameFactory contract. /// @notice constructs a new DisputeGameFactory contract.
constructor() OwnableUpgradeable() Semver(0, 0, 2) { constructor() OwnableUpgradeable() Semver(0, 0, 3) {
initialize(address(0)); initialize(address(0));
} }
...@@ -88,9 +88,7 @@ contract DisputeGameFactory is OwnableUpgradeable, IDisputeGameFactory, Semver { ...@@ -88,9 +88,7 @@ contract DisputeGameFactory is OwnableUpgradeable, IDisputeGameFactory, Semver {
IDisputeGame impl = gameImpls[gameType]; IDisputeGame impl = gameImpls[gameType];
// If there is no implementation to clone for the given `GameType`, revert. // If there is no implementation to clone for the given `GameType`, revert.
if (address(impl) == address(0)) { if (address(impl) == address(0)) revert NoImplementation(gameType);
revert NoImplementation(gameType);
}
// Clone the implementation contract and initialize it with the given parameters. // Clone the implementation contract and initialize it with the given parameters.
proxy = IDisputeGame(address(impl).clone(abi.encodePacked(rootClaim, extraData))); proxy = IDisputeGame(address(impl).clone(abi.encodePacked(rootClaim, extraData)));
...@@ -100,9 +98,7 @@ contract DisputeGameFactory is OwnableUpgradeable, IDisputeGameFactory, Semver { ...@@ -100,9 +98,7 @@ contract DisputeGameFactory is OwnableUpgradeable, IDisputeGameFactory, Semver {
Hash uuid = getGameUUID(gameType, rootClaim, extraData); Hash uuid = getGameUUID(gameType, rootClaim, extraData);
// If a dispute game with the same UUID already exists, revert. // If a dispute game with the same UUID already exists, revert.
if (GameId.unwrap(_disputeGames[uuid]) != bytes32(0)) { if (GameId.unwrap(_disputeGames[uuid]) != bytes32(0)) revert GameAlreadyExists(uuid);
revert GameAlreadyExists(uuid);
}
GameId slot = _packSlot(address(proxy), block.timestamp); GameId slot = _packSlot(address(proxy), block.timestamp);
......
// SPDX-License-Identifier: MIT // SPDX-License-Identifier: MIT
pragma solidity ^0.8.15; pragma solidity ^0.8.15;
import { IPreimageOracle } from "../../cannon/interfaces/IPreimageOracle.sol"; import { IPreimageOracle } from "src/cannon/interfaces/IPreimageOracle.sol";
/// @title IBigStepper /// @title IBigStepper
/// @notice An interface for a contract with a state transition function that /// @notice An interface for a contract with a state transition function that
......
// SPDX-License-Identifier: MIT // SPDX-License-Identifier: MIT
pragma solidity ^0.8.15; pragma solidity ^0.8.15;
import "../../libraries/DisputeTypes.sol";
import { IBondManager } from "./IBondManager.sol"; import { IBondManager } from "./IBondManager.sol";
import { IInitializable } from "./IInitializable.sol"; import { IInitializable } from "./IInitializable.sol";
import "src/libraries/DisputeTypes.sol";
/// @title IDisputeGame /// @title IDisputeGame
/// @notice The generic interface for a DisputeGame contract. /// @notice The generic interface for a DisputeGame contract.
interface IDisputeGame is IInitializable { interface IDisputeGame is IInitializable {
......
// SPDX-License-Identifier: MIT // SPDX-License-Identifier: MIT
pragma solidity ^0.8.15; pragma solidity ^0.8.15;
import "../../libraries/DisputeTypes.sol";
import { IDisputeGame } from "./IDisputeGame.sol"; import { IDisputeGame } from "./IDisputeGame.sol";
import "src/libraries/DisputeTypes.sol";
/// @title IDisputeGameFactory /// @title IDisputeGameFactory
/// @notice The interface for a DisputeGameFactory contract. /// @notice The interface for a DisputeGameFactory contract.
interface IDisputeGameFactory { interface IDisputeGameFactory {
......
// SPDX-License-Identifier: MIT // SPDX-License-Identifier: MIT
pragma solidity ^0.8.15; pragma solidity ^0.8.15;
import "../../libraries/DisputeTypes.sol";
import { IDisputeGame } from "./IDisputeGame.sol"; import { IDisputeGame } from "./IDisputeGame.sol";
import "src/libraries/DisputeTypes.sol";
/// @title IFaultDisputeGame /// @title IFaultDisputeGame
/// @notice The interface for a fault proof backed dispute game. /// @notice The interface for a fault proof backed dispute game.
interface IFaultDisputeGame is IDisputeGame { interface IFaultDisputeGame is IDisputeGame {
/// @notice The `ClaimData` struct represents the data associated with a Claim. /// @notice The `ClaimData` struct represents the data associated with a Claim.
/// @dev TODO: Add bond ID information. /// @dev TODO(clabby): Add bond ID information.
struct ClaimData { struct ClaimData {
uint32 parentIndex; uint32 parentIndex;
bool countered; bool countered;
...@@ -26,7 +26,14 @@ interface IFaultDisputeGame is IDisputeGame { ...@@ -26,7 +26,14 @@ interface IFaultDisputeGame is IDisputeGame {
Hash outputRoot; Hash outputRoot;
} }
/// @notice A container for two consecutive `OutputProposal`s. /// @notice A container for two consecutive `OutputProposal`s, used to store the starting
/// and disputed output proposals for a given dispute game. The starting output
/// proposal will be used to determine where the off chain agents should begin
/// running their fault proof program, and the disputed output proposal will be
/// fed into the program and treated as disputed state. The program's exit code
/// expresses its opinion on the validity of the state transition from the starting,
/// trusted output proposal to the disputed output proposal, and ultimately resolves
/// the dispute.
struct OutputProposals { struct OutputProposals {
OutputProposal starting; OutputProposal starting;
OutputProposal disputed; OutputProposal disputed;
...@@ -39,27 +46,26 @@ interface IFaultDisputeGame is IDisputeGame { ...@@ -39,27 +46,26 @@ interface IFaultDisputeGame is IDisputeGame {
event Move(uint256 indexed parentIndex, Claim indexed claim, address indexed claimant); event Move(uint256 indexed parentIndex, Claim indexed claim, address indexed claimant);
/// @notice Attack a disagreed upon `Claim`. /// @notice Attack a disagreed upon `Claim`.
/// @param _parentIndex Index of the `Claim` to attack in `claimData`. /// @param _parentIndex Index of the `Claim` to attack in the `claimData` array.
/// @param _claim The `Claim` at the relative attack position. /// @param _claim The `Claim` at the relative attack position.
function attack(uint256 _parentIndex, Claim _claim) external payable; function attack(uint256 _parentIndex, Claim _claim) external payable;
/// @notice Defend an agreed upon `Claim`. /// @notice Defend an agreed upon `Claim`.
/// @param _parentIndex Index of the claim to defend in `claimData`. /// @param _parentIndex Index of the claim to defend in the `claimData` array.
/// @param _claim The `Claim` at the relative defense position. /// @param _claim The `Claim` at the relative defense position.
function defend(uint256 _parentIndex, Claim _claim) external payable; function defend(uint256 _parentIndex, Claim _claim) external payable;
/// @notice Perform the final step via an on-chain fault proof processor /// @notice Perform an instruction step via an on-chain fault proof processor.
/// @dev This function should point to a fault proof processor in order to execute /// @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 /// 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 /// processor contract should adhere to the `IBigStepper` interface.
/// fault proof VMs (MIPS, RiscV5, etc.)
/// @param _claimIndex The index of the challenged claim 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 _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 at the given /// @param _stateData The stateData of the step is the preimage of the claim at the given
/// prestate, which is at `_stateIndex` if the move is an attack and `_claimIndex` if /// prestate, which is at `_stateIndex` if the move is an attack and `_claimIndex` if
/// the move is a defense. If the step is an attack on the first instruction, it is /// the move is a defense. If the step is an attack on the first instruction, it is
/// the absolute prestate of the fault proof VM. /// the absolute prestate of the fault proof VM.
/// @param _proof Proof to access memory leaf nodes in the VM. /// @param _proof Proof to access memory nodes in the VM's merkle state tree.
function step( function step(
uint256 _claimIndex, uint256 _claimIndex,
bool _isAttack, bool _isAttack,
...@@ -72,11 +78,13 @@ interface IFaultDisputeGame is IDisputeGame { ...@@ -72,11 +78,13 @@ interface IFaultDisputeGame is IDisputeGame {
/// @param _partOffset The offset of the data to post. /// @param _partOffset The offset of the data to post.
function addLocalData(uint256 _ident, uint256 _partOffset) external; function addLocalData(uint256 _ident, uint256 _partOffset) external;
/// @notice Returns the L1 block hash at the time of the game's creation. /// @notice An L1 block hash that contains the disputed output root, fetched from the
/// `BlockOracle` and verified by referencing the timestamp associated with the
/// first L2 Output Proposal in the `L2OutputOracle` that contains the disputed
/// L2 block number.
function l1Head() external view returns (Hash l1Head_); function l1Head() external view returns (Hash l1Head_);
/// @notice The l2BlockNumber that the `rootClaim` commits to. The trace being bisected within /// @notice The l2BlockNumber of the disputed output root in the `L2OutputOracle`.
/// the game is from `l2BlockNumber - 1` -> `l2BlockNumber`.
function l2BlockNumber() external view returns (uint256 l2BlockNumber_); function l2BlockNumber() external view returns (uint256 l2BlockNumber_);
/// @notice The l1BlockNumber that Cannon was ran from to generate the root claim. /// @notice The l1BlockNumber that Cannon was ran from to generate the root claim.
......
// SPDX-License-Identifier: MIT // SPDX-License-Identifier: MIT
pragma solidity ^0.8.15; pragma solidity ^0.8.15;
import "../../libraries/DisputeTypes.sol"; import "src/libraries/DisputeTypes.sol";
/// @title LibClock /// @title LibClock
/// @notice This library contains helper functions for working with the `Clock` type. /// @notice This library contains helper functions for working with the `Clock` type.
......
// SPDX-License-Identifier: MIT // SPDX-License-Identifier: MIT
pragma solidity ^0.8.15; pragma solidity ^0.8.15;
import "../../libraries/DisputeTypes.sol"; import "src/libraries/DisputeTypes.sol";
/// @title Hashing /// @title Hashing
/// @notice This library contains all of the hashing utilities used in the Cannon contracts. /// @notice This library contains all of the hashing utilities used in the Cannon contracts.
......
// SPDX-License-Identifier: MIT // SPDX-License-Identifier: MIT
pragma solidity ^0.8.15; pragma solidity ^0.8.15;
import "../../libraries/DisputeTypes.sol"; import "src/libraries/DisputeTypes.sol";
/// @title LibPosition /// @title LibPosition
/// @notice This library contains helper functions for working with the `Position` type. /// @notice This library contains helper functions for working with the `Position` type.
......
...@@ -58,6 +58,9 @@ error ValidStep(); ...@@ -58,6 +58,9 @@ error ValidStep();
/// not contain the disputed output root. /// not contain the disputed output root.
error L1HeadTooOld(); error L1HeadTooOld();
/// @notice Thrown when an invalid local identifier is passed to the `addLocalData` function.
error InvalidLocalIdent();
//////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////
// `AttestationDisputeGame` Errors // // `AttestationDisputeGame` Errors //
//////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////
......
...@@ -102,9 +102,9 @@ contract FaultDisputeGame_Test is FaultDisputeGame_Init { ...@@ -102,9 +102,9 @@ contract FaultDisputeGame_Test is FaultDisputeGame_Init {
assertEq(gameProxy.extraData(), extraData); assertEq(gameProxy.extraData(), extraData);
} }
/// @dev Tests that the game's status is set correctly. /// @dev Tests that the game's starting timestamp is set correctly.
function test_gameStart_succeeds() public { function test_createdAt_succeeds() public {
assertEq(Timestamp.unwrap(gameProxy.gameStart()), block.timestamp); assertEq(Timestamp.unwrap(gameProxy.createdAt()), block.timestamp);
} }
/// @dev Tests that the game's type is set correctly. /// @dev Tests that the game's type is set correctly.
......
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