Skip to content

Commit 358a621

Browse files
authored
Merge pull request #2045 from kleros/feat/universal-gated-dk
Universal Gated Dispute Kit and Shutter Gated Dispute Kit
2 parents 2116239 + 3bca47c commit 358a621

File tree

11 files changed

+659
-65
lines changed

11 files changed

+659
-65
lines changed

contracts/deploy/00-home-chain-arbitration.ts

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import { DeployFunction } from "hardhat-deploy/types";
33
import { getContractAddress } from "./utils/getContractAddress";
44
import { deployUpgradable } from "./utils/deployUpgradable";
55
import { changeCurrencyRate } from "./utils/klerosCoreHelper";
6-
import { HomeChains, isSkipped, isDevnet, PNK, ETH } from "./utils";
6+
import { HomeChains, isSkipped, isDevnet, PNK, ETH, Courts } from "./utils";
77
import { getContractOrDeploy, getContractOrDeployUpgradable } from "./utils/getContractOrDeploy";
88
import { deployERC20AndFaucet } from "./utils/deployTokens";
99
import { ChainlinkRNG, DisputeKitClassic, KlerosCore } from "../typechain-types";
@@ -103,8 +103,25 @@ const deployArbitration: DeployFunction = async (hre: HardhatRuntimeEnvironment)
103103
log: true,
104104
});
105105
await core.addNewDisputeKit(disputeKitShutter.address);
106-
await core.enableDisputeKits(1, [2], true); // enable disputeKitShutter on the General Court
106+
await core.enableDisputeKits(Courts.GENERAL, [2], true); // enable disputeKitShutter on the General Court
107107

108+
const disputeKitGated = await deployUpgradable(deployments, "DisputeKitGated", {
109+
from: deployer,
110+
args: [deployer, core.target],
111+
log: true,
112+
});
113+
await core.addNewDisputeKit(disputeKitGated.address);
114+
await core.enableDisputeKits(Courts.GENERAL, [3], true); // enable disputeKitGated on the General Court
115+
116+
const disputeKitGatedShutter = await deployUpgradable(deployments, "DisputeKitGatedShutter", {
117+
from: deployer,
118+
args: [deployer, core.target],
119+
log: true,
120+
});
121+
await core.addNewDisputeKit(disputeKitGatedShutter.address);
122+
await core.enableDisputeKits(Courts.GENERAL, [4], true); // enable disputeKitGatedShutter on the General Court
123+
124+
// Snapshot proxy
108125
await deploy("KlerosCoreSnapshotProxy", {
109126
from: deployer,
110127
args: [deployer, core.target],

contracts/deploy/utils/deployTokens.ts

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,3 +65,17 @@ export const deployERC721 = async (
6565
log: true,
6666
});
6767
};
68+
69+
export const deployERC1155 = async (
70+
hre: HardhatRuntimeEnvironment,
71+
deployer: string,
72+
name: string,
73+
ticker: string
74+
): Promise<Contract> => {
75+
return getContractOrDeploy(hre, ticker, {
76+
from: deployer,
77+
contract: "TestERC1155",
78+
args: [],
79+
log: true,
80+
});
81+
};

contracts/hardhat.config.ts

Lines changed: 2 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -105,15 +105,7 @@ const config: HardhatUserConfig = {
105105
arbitrumSepolia: {
106106
chainId: 421614,
107107
url: process.env.ARBITRUM_SEPOLIA_RPC ?? `https://arbitrum-sepolia.infura.io/v3/${process.env.INFURA_API_KEY}`,
108-
accounts:
109-
(process.env.ARB_GOERLI_PRIVATE_KEY_WALLET_1 && [
110-
process.env.ARB_GOERLI_PRIVATE_KEY_WALLET_1 as string,
111-
process.env.ARB_GOERLI_PRIVATE_KEY_WALLET_2 as string,
112-
process.env.ARB_GOERLI_PRIVATE_KEY_WALLET_3 as string,
113-
process.env.ARB_GOERLI_PRIVATE_KEY_WALLET_4 as string,
114-
process.env.ARB_GOERLI_PRIVATE_KEY_WALLET_5 as string,
115-
]) ||
116-
(process.env.PRIVATE_KEY !== undefined ? [process.env.PRIVATE_KEY] : []),
108+
accounts: process.env.PRIVATE_KEY !== undefined ? [process.env.PRIVATE_KEY] : [],
117109
live: true,
118110
saveDeployments: true,
119111
tags: ["staging", "home", "layer2"],
@@ -131,15 +123,7 @@ const config: HardhatUserConfig = {
131123
arbitrumSepoliaDevnet: {
132124
chainId: 421614,
133125
url: process.env.ARBITRUM_SEPOLIA_RPC ?? `https://arbitrum-sepolia.infura.io/v3/${process.env.INFURA_API_KEY}`,
134-
accounts:
135-
(process.env.ARB_GOERLI_PRIVATE_KEY_WALLET_1 && [
136-
process.env.ARB_GOERLI_PRIVATE_KEY_WALLET_1 as string,
137-
process.env.ARB_GOERLI_PRIVATE_KEY_WALLET_2 as string,
138-
process.env.ARB_GOERLI_PRIVATE_KEY_WALLET_3 as string,
139-
process.env.ARB_GOERLI_PRIVATE_KEY_WALLET_4 as string,
140-
process.env.ARB_GOERLI_PRIVATE_KEY_WALLET_5 as string,
141-
]) ||
142-
(process.env.PRIVATE_KEY !== undefined ? [process.env.PRIVATE_KEY] : []),
126+
accounts: process.env.PRIVATE_KEY !== undefined ? [process.env.PRIVATE_KEY] : [],
143127
live: true,
144128
saveDeployments: true,
145129
tags: ["staging", "home", "layer2"],

contracts/src/arbitration/dispute-kits/DisputeKitGated.sol

Lines changed: 37 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -29,14 +29,6 @@ interface IBalanceHolderERC1155 {
2929
contract DisputeKitGated is DisputeKitClassicBase {
3030
string public constant override version = "0.10.0";
3131

32-
// ************************************* //
33-
// * Storage * //
34-
// ************************************* //
35-
36-
address public tokenGate; // The token used for gating access.
37-
uint256 public tokenId; // Only used for ERC-1155
38-
bool public isERC1155; // True if the tokenGate is an ERC-1155, false otherwise.
39-
4032
// ************************************* //
4133
// * Constructor * //
4234
// ************************************* //
@@ -49,20 +41,8 @@ contract DisputeKitGated is DisputeKitClassicBase {
4941
/// @dev Initializer.
5042
/// @param _governor The governor's address.
5143
/// @param _core The KlerosCore arbitrator.
52-
/// @param _tokenGate The token used for gating access.
53-
/// @param _tokenId The token ID for ERC-1155 (ignored for other token types)
54-
/// @param _isERC1155 Whether the token is an ERC-1155
55-
function initialize(
56-
address _governor,
57-
KlerosCore _core,
58-
address _tokenGate,
59-
uint256 _tokenId,
60-
bool _isERC1155
61-
) external reinitializer(1) {
44+
function initialize(address _governor, KlerosCore _core) external reinitializer(1) {
6245
__DisputeKitClassicBase_initialize(_governor, _core);
63-
tokenGate = _tokenGate;
64-
tokenId = _tokenId;
65-
isERC1155 = _isERC1155;
6646
}
6747

6848
// ************************ //
@@ -75,26 +55,37 @@ contract DisputeKitGated is DisputeKitClassicBase {
7555
// NOP
7656
}
7757

78-
/// @dev Changes the `tokenGate` to an ERC-20 or ERC-721 token.
79-
/// @param _tokenGate The new value for the `tokenGate` storage variable.
80-
function changeTokenGateERC20OrERC721(address _tokenGate) external onlyByGovernor {
81-
tokenGate = _tokenGate;
82-
isERC1155 = false;
83-
}
84-
85-
/// @dev Changes the `tokenGate` to an ERC-1155 token.
86-
/// @param _tokenGate The new value for the `tokenGate` storage variable.
87-
/// @param _tokenId The new value for the `tokenId` storage variable.
88-
function changeTokenGateERC1155(address _tokenGate, uint256 _tokenId) external onlyByGovernor {
89-
tokenGate = _tokenGate;
90-
tokenId = _tokenId;
91-
isERC1155 = true;
92-
}
93-
9458
// ************************************* //
9559
// * Internal * //
9660
// ************************************* //
9761

62+
/// @dev Extracts token gating information from the extra data.
63+
/// @param _extraData The extra data bytes array with the following encoding:
64+
/// - bytes 0-31: uint96 courtID, not used here
65+
/// - bytes 32-63: uint256 minJurors, not used here
66+
/// - bytes 64-95: uint256 disputeKitID, not used here
67+
/// - bytes 96-127: uint256 packedTokenGateAndFlag (address tokenGate in bits 0-159, bool isERC1155 in bit 160)
68+
/// - bytes 128-159: uint256 tokenId
69+
/// @return tokenGate The address of the token contract used for gating access.
70+
/// @return isERC1155 True if the token is an ERC-1155, false for ERC-20/ERC-721.
71+
/// @return tokenId The token ID for ERC-1155 tokens (ignored for ERC-20/ERC-721).
72+
function extraDataToTokenInfo(
73+
bytes memory _extraData
74+
) public pure returns (address tokenGate, bool isERC1155, uint256 tokenId) {
75+
// Need at least 160 bytes to safely read the parameters
76+
if (_extraData.length < 160) return (address(0), false, 0);
77+
78+
assembly {
79+
// solium-disable-line security/no-inline-assembly
80+
let packedTokenGateIsERC1155 := mload(add(_extraData, 0x80)) // 4th parameter at offset 128
81+
tokenId := mload(add(_extraData, 0xA0)) // 5th parameter at offset 160 (moved up)
82+
83+
// Unpack address from lower 160 bits and bool from bit 160
84+
tokenGate := and(packedTokenGateIsERC1155, 0xffffffffffffffffffffffffffffffffffffffff)
85+
isERC1155 := and(shr(160, packedTokenGateIsERC1155), 1)
86+
}
87+
}
88+
9889
/// @inheritdoc DisputeKitClassicBase
9990
function _postDrawCheck(
10091
Round storage _round,
@@ -103,6 +94,15 @@ contract DisputeKitGated is DisputeKitClassicBase {
10394
) internal view override returns (bool) {
10495
if (!super._postDrawCheck(_round, _coreDisputeID, _juror)) return false;
10596

97+
// Get the local dispute and extract token info from extraData
98+
uint256 localDisputeID = coreDisputeIDToLocal[_coreDisputeID];
99+
Dispute storage dispute = disputes[localDisputeID];
100+
(address tokenGate, bool isERC1155, uint256 tokenId) = extraDataToTokenInfo(dispute.extraData);
101+
102+
// If no token gate is specified, allow all jurors
103+
if (tokenGate == address(0)) return true;
104+
105+
// Check juror's token balance
106106
if (isERC1155) {
107107
return IBalanceHolderERC1155(tokenGate).balanceOf(_juror, tokenId) > 0;
108108
} else {
Lines changed: 189 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,189 @@
1+
// SPDX-License-Identifier: MIT
2+
3+
pragma solidity 0.8.24;
4+
5+
import {DisputeKitClassicBase, KlerosCore} from "./DisputeKitClassicBase.sol";
6+
7+
interface IBalanceHolder {
8+
/// @dev Returns the number of tokens in `owner` account.
9+
/// @dev Compatible with ERC-20 and ERC-721.
10+
/// @param owner The address of the owner.
11+
/// @return balance The number of tokens in `owner` account.
12+
function balanceOf(address owner) external view returns (uint256 balance);
13+
}
14+
15+
interface IBalanceHolderERC1155 {
16+
/// @dev Returns the balance of an ERC-1155 token.
17+
/// @param account The address of the token holder
18+
/// @param id ID of the token
19+
/// @return The token balance
20+
function balanceOf(address account, uint256 id) external view returns (uint256);
21+
}
22+
23+
/// @title DisputeKitGatedShutter
24+
/// Added functionality: shielded voting.
25+
/// Dispute kit implementation adapted from DisputeKitClassic
26+
/// - a drawing system: proportional to staked PNK with a non-zero balance of `tokenGate` where `tokenGate` is an ERC20, ERC721 or ERC1155
27+
/// - a vote aggregation system: plurality,
28+
/// - an incentive system: equal split between coherent votes,
29+
/// - an appeal system: fund 2 choices only, vote on any choice.
30+
contract DisputeKitGatedShutter is DisputeKitClassicBase {
31+
string public constant override version = "0.10.0";
32+
33+
// ************************************* //
34+
// * Events * //
35+
// ************************************* //
36+
37+
/// @dev Emitted when a vote is cast.
38+
/// @param _coreDisputeID The identifier of the dispute in the Arbitrator contract.
39+
/// @param _juror The address of the juror casting the vote commitment.
40+
/// @param _commit The commitment hash.
41+
/// @param _identity The Shutter identity used for encryption.
42+
/// @param _encryptedVote The Shutter encrypted vote.
43+
event CommitCastShutter(
44+
uint256 indexed _coreDisputeID,
45+
address indexed _juror,
46+
bytes32 indexed _commit,
47+
bytes32 _identity,
48+
bytes _encryptedVote
49+
);
50+
51+
// ************************************* //
52+
// * Constructor * //
53+
// ************************************* //
54+
55+
/// @custom:oz-upgrades-unsafe-allow constructor
56+
constructor() {
57+
_disableInitializers();
58+
}
59+
60+
/// @dev Initializer.
61+
/// @param _governor The governor's address.
62+
/// @param _core The KlerosCore arbitrator.
63+
function initialize(address _governor, KlerosCore _core) external reinitializer(1) {
64+
__DisputeKitClassicBase_initialize(_governor, _core);
65+
}
66+
67+
// ************************ //
68+
// * Governance * //
69+
// ************************ //
70+
71+
/// @dev Access Control to perform implementation upgrades (UUPS Proxiable)
72+
/// Only the governor can perform upgrades (`onlyByGovernor`)
73+
function _authorizeUpgrade(address) internal view override onlyByGovernor {
74+
// NOP
75+
}
76+
77+
// ************************************* //
78+
// * State Modifiers * //
79+
// ************************************* //
80+
81+
/// @dev Sets the caller's commit for the specified votes. It can be called multiple times during the
82+
/// commit period, each call overrides the commits of the previous one.
83+
/// `O(n)` where
84+
/// `n` is the number of votes.
85+
/// @param _coreDisputeID The ID of the dispute in Kleros Core.
86+
/// @param _voteIDs The IDs of the votes.
87+
/// @param _commit The commitment hash including the justification.
88+
/// @param _identity The Shutter identity used for encryption.
89+
/// @param _encryptedVote The Shutter encrypted vote.
90+
function castCommitShutter(
91+
uint256 _coreDisputeID,
92+
uint256[] calldata _voteIDs,
93+
bytes32 _commit,
94+
bytes32 _identity,
95+
bytes calldata _encryptedVote
96+
) external notJumped(_coreDisputeID) {
97+
_castCommit(_coreDisputeID, _voteIDs, _commit);
98+
emit CommitCastShutter(_coreDisputeID, msg.sender, _commit, _identity, _encryptedVote);
99+
}
100+
101+
function castVoteShutter(
102+
uint256 _coreDisputeID,
103+
uint256[] calldata _voteIDs,
104+
uint256 _choice,
105+
uint256 _salt,
106+
string memory _justification
107+
) external {
108+
Dispute storage dispute = disputes[coreDisputeIDToLocal[_coreDisputeID]];
109+
address juror = dispute.rounds[dispute.rounds.length - 1].votes[_voteIDs[0]].account;
110+
111+
// _castVote() ensures that all the _voteIDs do belong to `juror`
112+
_castVote(_coreDisputeID, _voteIDs, _choice, _salt, _justification, juror);
113+
}
114+
115+
// ************************************* //
116+
// * Public Views * //
117+
// ************************************* //
118+
119+
/**
120+
* @dev Computes the hash of a vote using ABI encoding
121+
* @param _choice The choice being voted for
122+
* @param _justification The justification for the vote
123+
* @param _salt A random salt for commitment
124+
* @return bytes32 The hash of the encoded vote parameters
125+
*/
126+
function hashVote(
127+
uint256 _choice,
128+
uint256 _salt,
129+
string memory _justification
130+
) public pure override returns (bytes32) {
131+
bytes32 justificationHash = keccak256(bytes(_justification));
132+
return keccak256(abi.encode(_choice, _salt, justificationHash));
133+
}
134+
135+
// ************************************* //
136+
// * Internal * //
137+
// ************************************* //
138+
139+
/// @dev Extracts token gating information from the extra data.
140+
/// @param _extraData The extra data bytes array with the following encoding:
141+
/// - bytes 0-31: uint96 courtID, not used here
142+
/// - bytes 32-63: uint256 minJurors, not used here
143+
/// - bytes 64-95: uint256 disputeKitID, not used here
144+
/// - bytes 96-127: uint256 packedTokenGateAndFlag (address tokenGate in bits 0-159, bool isERC1155 in bit 160)
145+
/// - bytes 128-159: uint256 tokenId
146+
/// @return tokenGate The address of the token contract used for gating access.
147+
/// @return isERC1155 True if the token is an ERC-1155, false for ERC-20/ERC-721.
148+
/// @return tokenId The token ID for ERC-1155 tokens (ignored for ERC-20/ERC-721).
149+
function _extraDataToTokenInfo(
150+
bytes memory _extraData
151+
) internal pure returns (address tokenGate, bool isERC1155, uint256 tokenId) {
152+
// Need at least 160 bytes to safely read the parameters
153+
if (_extraData.length < 160) return (address(0), false, 0);
154+
155+
assembly {
156+
// solium-disable-line security/no-inline-assembly
157+
let packedTokenGateIsERC1155 := mload(add(_extraData, 0x80)) // 4th parameter at offset 128
158+
tokenId := mload(add(_extraData, 0xA0)) // 5th parameter at offset 160 (moved up)
159+
160+
// Unpack address from lower 160 bits and bool from bit 160
161+
tokenGate := and(packedTokenGateIsERC1155, 0xffffffffffffffffffffffffffffffffffffffff)
162+
isERC1155 := and(shr(160, packedTokenGateIsERC1155), 1)
163+
}
164+
}
165+
166+
/// @inheritdoc DisputeKitClassicBase
167+
function _postDrawCheck(
168+
Round storage _round,
169+
uint256 _coreDisputeID,
170+
address _juror
171+
) internal view override returns (bool) {
172+
if (!super._postDrawCheck(_round, _coreDisputeID, _juror)) return false;
173+
174+
// Get the local dispute and extract token info from extraData
175+
uint256 localDisputeID = coreDisputeIDToLocal[_coreDisputeID];
176+
Dispute storage dispute = disputes[localDisputeID];
177+
(address tokenGate, bool isERC1155, uint256 tokenId) = _extraDataToTokenInfo(dispute.extraData);
178+
179+
// If no token gate is specified, allow all jurors
180+
if (tokenGate == address(0)) return true;
181+
182+
// Check juror's token balance
183+
if (isERC1155) {
184+
return IBalanceHolderERC1155(tokenGate).balanceOf(_juror, tokenId) > 0;
185+
} else {
186+
return IBalanceHolder(tokenGate).balanceOf(_juror) > 0;
187+
}
188+
}
189+
}

contracts/src/arbitration/dispute-kits/DisputeKitShutter.sol

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,12 @@ pragma solidity 0.8.24;
55
import {DisputeKitClassicBase, KlerosCore} from "./DisputeKitClassicBase.sol";
66

77
/// @title DisputeKitShutter
8+
/// Added functionality: shielded voting.
89
/// Dispute kit implementation of the Kleros v1 features including:
910
/// - a drawing system: proportional to staked PNK,
1011
/// - a vote aggregation system: plurality,
1112
/// - an incentive system: equal split between coherent votes,
1213
/// - an appeal system: fund 2 choices only, vote on any choice.
13-
/// Added functionality: an Shutter-specific event emitted when a vote is cast.
1414
contract DisputeKitShutter is DisputeKitClassicBase {
1515
string public constant override version = "0.11.1";
1616

0 commit comments

Comments
 (0)