|
| 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 | +} |
0 commit comments