Skip to content
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -204,7 +204,7 @@ abstract contract DisputeKitClassicBase is IDisputeKit, Initializable, UUPSProxi
uint256 _numberOfChoices,
bytes calldata _extraData,
uint256 /*_nbVotes*/
) external override onlyByCore {
) public virtual override onlyByCore {
uint256 localDisputeID;
Dispute storage dispute;
Active storage active = coreDisputeIDToActive[_coreDisputeID];
Expand Down
47 changes: 45 additions & 2 deletions contracts/src/arbitration/dispute-kits/DisputeKitGated.sol
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,14 @@ interface IBalanceHolderERC1155 {
contract DisputeKitGated is DisputeKitClassicBase {
string public constant override version = "2.0.0";

address private constant NO_TOKEN_GATE = address(0);

// ************************************* //
// * Storage * //
// ************************************* //

mapping(address token => bool supported) public supportedTokens; // Whether the token is supported or not.

// ************************************* //
// * Constructor * //
// ************************************* //
Expand All @@ -50,6 +58,7 @@ contract DisputeKitGated is DisputeKitClassicBase {
uint256 _jumpDisputeKitID
) external initializer {
__DisputeKitClassicBase_initialize(_owner, _core, _wNative, _jumpDisputeKitID);
supportedTokens[NO_TOKEN_GATE] = true; // Allows disputes without token gating
}

// ************************ //
Expand All @@ -62,6 +71,34 @@ contract DisputeKitGated is DisputeKitClassicBase {
// NOP
}

/// @notice Changes the supported tokens.
/// @param _tokens The tokens to support.
/// @param _supported Whether the tokens are supported or not.
function changeSupportedTokens(address[] memory _tokens, bool _supported) external onlyByOwner {
for (uint256 i = 0; i < _tokens.length; i++) {
supportedTokens[_tokens[i]] = _supported;
}
}

// ************************************* //
// * State Modifiers * //
// ************************************* //

/// @inheritdoc DisputeKitClassicBase
function createDispute(
uint256 _coreDisputeID,
uint256 _coreRoundID,
uint256 _numberOfChoices,
bytes calldata _extraData,
uint256 _nbVotes
) public override {
(address tokenGate, , ) = _extraDataToTokenInfo(_extraData);
if (!supportedTokens[tokenGate]) revert TokenNotSupported(tokenGate);

// super.createDispute() ensures access control onlyByCore.
super.createDispute(_coreDisputeID, _coreRoundID, _numberOfChoices, _extraData, _nbVotes);
}

// ************************************* //
// * Internal * //
// ************************************* //
Expand All @@ -78,7 +115,7 @@ contract DisputeKitGated is DisputeKitClassicBase {
/// @return tokenId The token ID for ERC-1155 tokens (ignored for ERC-20/ERC-721).
function _extraDataToTokenInfo(
bytes memory _extraData
) public pure returns (address tokenGate, bool isERC1155, uint256 tokenId) {
) internal pure returns (address tokenGate, bool isERC1155, uint256 tokenId) {
// Need at least 160 bytes to safely read the parameters
if (_extraData.length < 160) return (address(0), false, 0);

Expand Down Expand Up @@ -107,7 +144,7 @@ contract DisputeKitGated is DisputeKitClassicBase {
(address tokenGate, bool isERC1155, uint256 tokenId) = _extraDataToTokenInfo(dispute.extraData);

// If no token gate is specified, allow all jurors
if (tokenGate == address(0)) return true;
if (tokenGate == NO_TOKEN_GATE) return true;

// Check juror's token balance
if (isERC1155) {
Expand All @@ -116,4 +153,10 @@ contract DisputeKitGated is DisputeKitClassicBase {
return IBalanceHolder(tokenGate).balanceOf(_juror) > 0;
}
}

// ************************************* //
// * Errors * //
// ************************************* //

error TokenNotSupported(address tokenGate);
}
Original file line number Diff line number Diff line change
Expand Up @@ -30,10 +30,13 @@ interface IBalanceHolderERC1155 {
contract DisputeKitGatedShutter is DisputeKitClassicBase {
string public constant override version = "2.0.0";

address private constant NO_TOKEN_GATE = address(0);

// ************************************* //
// * Storage * //
// ************************************* //

mapping(address token => bool supported) public supportedTokens; // Whether the token is supported or not.
mapping(uint256 localDisputeID => mapping(uint256 localRoundID => mapping(uint256 voteID => bytes32 recoveryCommitment)))
public recoveryCommitments;

Expand Down Expand Up @@ -84,6 +87,7 @@ contract DisputeKitGatedShutter is DisputeKitClassicBase {
uint256 _jumpDisputeKitID
) external initializer {
__DisputeKitClassicBase_initialize(_owner, _core, _wNative, _jumpDisputeKitID);
supportedTokens[NO_TOKEN_GATE] = true; // Allows disputes without token gating
}

// ************************ //
Expand All @@ -96,10 +100,34 @@ contract DisputeKitGatedShutter is DisputeKitClassicBase {
// NOP
}

/// @notice Changes the supported tokens.
/// @param _tokens The tokens to support.
/// @param _supported Whether the tokens are supported or not.
function changeSupportedTokens(address[] memory _tokens, bool _supported) external onlyByOwner {
for (uint256 i = 0; i < _tokens.length; i++) {
supportedTokens[_tokens[i]] = _supported;
}
}

// ************************************* //
// * State Modifiers * //
// ************************************* //

/// @inheritdoc DisputeKitClassicBase
function createDispute(
uint256 _coreDisputeID,
uint256 _coreRoundID,
uint256 _numberOfChoices,
bytes calldata _extraData,
uint256 _nbVotes
) public override {
(address tokenGate, , ) = _extraDataToTokenInfo(_extraData);
if (!supportedTokens[tokenGate]) revert TokenNotSupported(tokenGate);

// super.createDispute() ensures access control onlyByCore.
super.createDispute(_coreDisputeID, _coreRoundID, _numberOfChoices, _extraData, _nbVotes);
}

/// @notice Sets the caller's commit for the specified votes.
///
/// @dev It can be called multiple times during the commit period, each call overrides the commits of the previous one.
Expand Down Expand Up @@ -209,7 +237,7 @@ contract DisputeKitGatedShutter is DisputeKitClassicBase {
/// @return tokenGate The address of the token contract used for gating access.
/// @return isERC1155 True if the token is an ERC-1155, false for ERC-20/ERC-721.
/// @return tokenId The token ID for ERC-1155 tokens (ignored for ERC-20/ERC-721).
function __extraDataToTokenInfo(
function _extraDataToTokenInfo(
bytes memory _extraData
) internal pure returns (address tokenGate, bool isERC1155, uint256 tokenId) {
// Need at least 160 bytes to safely read the parameters
Expand Down Expand Up @@ -237,10 +265,10 @@ contract DisputeKitGatedShutter is DisputeKitClassicBase {
// Get the local dispute and extract token info from extraData
uint256 localDisputeID = coreDisputeIDToLocal[_coreDisputeID];
Dispute storage dispute = disputes[localDisputeID];
(address tokenGate, bool isERC1155, uint256 tokenId) = __extraDataToTokenInfo(dispute.extraData);
(address tokenGate, bool isERC1155, uint256 tokenId) = _extraDataToTokenInfo(dispute.extraData);

// If no token gate is specified, allow all jurors
if (tokenGate == address(0)) return true;
if (tokenGate == NO_TOKEN_GATE) return true;

// Check juror's token balance
if (isERC1155) {
Expand All @@ -254,5 +282,6 @@ contract DisputeKitGatedShutter is DisputeKitClassicBase {
// * Errors * //
// ************************************* //

error TokenNotSupported(address tokenGate);
error EmptyRecoveryCommit();
}
15 changes: 15 additions & 0 deletions contracts/src/test/DisputeKitGatedMock.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
// SPDX-License-Identifier: MIT

pragma solidity ^0.8.24;

import "../arbitration/dispute-kits/DisputeKitGated.sol";

/// @title KlerosCoreMock
/// KlerosCore with view functions to use in Foundry tests.
contract DisputeKitGatedMock is DisputeKitGated {
function extraDataToTokenInfo(
bytes memory _extraData
) public pure returns (address tokenGate, bool isERC1155, uint256 tokenId) {
(tokenGate, isERC1155, tokenId) = _extraDataToTokenInfo(_extraData);
}
}
15 changes: 15 additions & 0 deletions contracts/src/test/DisputeKitGatedShutterMock.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
// SPDX-License-Identifier: MIT

pragma solidity ^0.8.24;

import "../arbitration/dispute-kits/DisputeKitGatedShutter.sol";

/// @title DisputeKitGatedShutterMock
/// DisputeKitGatedShutter with view functions to use in Foundry tests.
contract DisputeKitGatedShutterMock is DisputeKitGatedShutter {
function extraDataToTokenInfo(
bytes memory _extraData
) public pure returns (address tokenGate, bool isERC1155, uint256 tokenId) {
(tokenGate, isERC1155, tokenId) = _extraDataToTokenInfo(_extraData);
}
}
74 changes: 74 additions & 0 deletions contracts/test/arbitration/dispute-kit-gated-shutter.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
import {
setupTokenGatedTest,
testTokenWhitelistManagement,
testAccessControl,
testUnsupportedTokenErrors,
testERC20Gating,
testERC721Gating,
testERC1155Gating,
testWhitelistIntegration,
testNoTokenGateAddress,
TokenGatedTestContext,
} from "./helpers/dispute-kit-gated-common";
import {
setupShutterTest,
testCommitPhase,
testNormalFlowBotReveals,
testRecoveryFlowJurorReveals,
testHashFunctionBehavior,
testEdgeCasesAndSecurity,
ShutterTestContext,
} from "./helpers/dispute-kit-shutter-common";

/* eslint-disable no-unused-vars */
/* eslint-disable no-unused-expressions */

/**
* Test suite for DisputeKitGatedShutter - a dispute kit that requires jurors to hold
* specific tokens (ERC20, ERC721, or ERC1155) to participate in disputes, with additional
* Shutter functionality for commit-reveal voting.
*
* Tests cover:
* - All DisputeKitGated functionality (via shared tests)
* - Shutter-specific commit/reveal mechanism
* - Recovery commits for juror vote recovery
* - Integration between token gating and Shutter features
*/
describe("DisputeKitGatedShutter", async () => {
describe("Token Gating Features", async () => {
let tokenContext: TokenGatedTestContext;

beforeEach("Setup", async () => {
tokenContext = await setupTokenGatedTest({ contractName: "DisputeKitGatedShutterMock" });
});

// Run all shared token gating tests
testTokenWhitelistManagement(() => tokenContext);
testAccessControl(() => tokenContext);
testUnsupportedTokenErrors(() => tokenContext);
testERC20Gating(() => tokenContext);
testERC721Gating(() => tokenContext);
testERC1155Gating(() => tokenContext);
testWhitelistIntegration(() => tokenContext);
testNoTokenGateAddress(() => tokenContext);
});

describe("Shutter Features", async () => {
let shutterContext: ShutterTestContext;

beforeEach("Setup", async () => {
// Setup DisputeKitGatedShutter with token gating enabled
shutterContext = await setupShutterTest({
contractName: "DisputeKitGatedShutter",
isGated: true, // Enable token gating for DAI
});
});

// Run all shared Shutter tests
testCommitPhase(() => shutterContext);
testNormalFlowBotReveals(() => shutterContext);
testRecoveryFlowJurorReveals(() => shutterContext);
testHashFunctionBehavior(() => shutterContext);
testEdgeCasesAndSecurity(() => shutterContext);
});
});
Loading
Loading