|
| 1 | +// SPDX-License-Identifier: MIT |
| 2 | + |
| 3 | +pragma solidity ^0.8.24; |
| 4 | + |
| 5 | +import {DisputeKitClassicBase} from "./DisputeKitClassicBase.sol"; |
| 6 | +import {KlerosCore} from "../KlerosCore.sol"; |
| 7 | + |
| 8 | +interface IBalanceHolder { |
| 9 | + /// @notice Returns the number of tokens in `owner` account. |
| 10 | + /// @dev Compatible with ERC-20 and ERC-721. |
| 11 | + /// @param owner The address of the owner. |
| 12 | + /// @return balance The number of tokens in `owner` account. |
| 13 | + function balanceOf(address owner) external view returns (uint256 balance); |
| 14 | +} |
| 15 | + |
| 16 | +/// @title DisputeKitGatedArgentinaConsumerProtection |
| 17 | +/// @notice Dispute kit implementation adapted from DisputeKitClassic |
| 18 | +/// - a drawing system: proportional to staked PNK among the jurors holding a `accreditedLawyerToken` or a `accreditedConsumerProtectionLawyerToken` |
| 19 | +/// and at least one of the drawn jurors is holding a `accreditedConsumerProtectionLawyerToken`, |
| 20 | +/// - a vote aggregation system: plurality, |
| 21 | +/// - an incentive system: equal split between coherent votes, |
| 22 | +/// - an appeal system: fund 2 choices only, vote on any choice. |
| 23 | +contract DisputeKitGatedArgentinaConsumerProtection is DisputeKitClassicBase { |
| 24 | + string public constant override version = "2.0.0"; |
| 25 | + |
| 26 | + // ************************************* // |
| 27 | + // * Storage * // |
| 28 | + // ************************************* // |
| 29 | + |
| 30 | + address public accreditedLawyerToken; // The address of the accredited lawyer token. |
| 31 | + address public accreditedConsumerProtectionLawyerToken; // The address of the accredited consumer protection lawyer token. |
| 32 | + mapping(uint256 localDisputeID => mapping(uint256 localRoundID => bool)) public drawnConsumerProtectionLawyer; // Maps the local dispute and round ID to the boolean indicating if the consumer protection lawyer was drawn. |
| 33 | + |
| 34 | + // ************************************* // |
| 35 | + // * Constructor * // |
| 36 | + // ************************************* // |
| 37 | + |
| 38 | + /// @custom:oz-upgrades-unsafe-allow constructor |
| 39 | + constructor() { |
| 40 | + _disableInitializers(); |
| 41 | + } |
| 42 | + |
| 43 | + /// @notice Initializer. |
| 44 | + /// @param _owner The owner's address. |
| 45 | + /// @param _core The KlerosCore arbitrator. |
| 46 | + /// @param _wNative The wrapped native token address, typically wETH. |
| 47 | + /// @param _accreditedLawyerToken The address of the accredited lawyer token. |
| 48 | + /// @param _accreditedConsumerProtectionLawyerToken The address of the accredited consumer protection lawyer token. |
| 49 | + function initialize( |
| 50 | + address _owner, |
| 51 | + KlerosCore _core, |
| 52 | + address _wNative, |
| 53 | + address _accreditedLawyerToken, |
| 54 | + address _accreditedConsumerProtectionLawyerToken |
| 55 | + ) external initializer { |
| 56 | + __DisputeKitClassicBase_initialize(_owner, _core, _wNative); |
| 57 | + accreditedLawyerToken = _accreditedLawyerToken; |
| 58 | + accreditedConsumerProtectionLawyerToken = _accreditedConsumerProtectionLawyerToken; |
| 59 | + } |
| 60 | + |
| 61 | + // ************************ // |
| 62 | + // * Governance * // |
| 63 | + // ************************ // |
| 64 | + |
| 65 | + /// @dev Access Control to perform implementation upgrades (UUPS Proxiable) |
| 66 | + /// Only the owner can perform upgrades (`onlyByOwner`) |
| 67 | + function _authorizeUpgrade(address) internal view override onlyByOwner { |
| 68 | + // NOP |
| 69 | + } |
| 70 | + |
| 71 | + /// @notice Changes the accredited lawyer token. |
| 72 | + /// @param _accreditedLawyerToken The address of the accredited lawyer token. |
| 73 | + function changeAccreditedLawyerToken(address _accreditedLawyerToken) external onlyByOwner { |
| 74 | + accreditedLawyerToken = _accreditedLawyerToken; |
| 75 | + } |
| 76 | + |
| 77 | + /// @notice Changes the accredited consumer protection lawyer token. |
| 78 | + /// @param _accreditedConsumerProtectionLawyerToken The address of the accredited consumer protection lawyer token. |
| 79 | + function changeAccreditedConsumerProtectionLawyerToken( |
| 80 | + address _accreditedConsumerProtectionLawyerToken |
| 81 | + ) external onlyByOwner { |
| 82 | + accreditedConsumerProtectionLawyerToken = _accreditedConsumerProtectionLawyerToken; |
| 83 | + } |
| 84 | + |
| 85 | + // ************************************* // |
| 86 | + // * State Modifiers * // |
| 87 | + // ************************************* // |
| 88 | + |
| 89 | + /// @inheritdoc DisputeKitClassicBase |
| 90 | + function draw( |
| 91 | + uint256 _coreDisputeID, |
| 92 | + uint256 _nonce, |
| 93 | + uint256 _roundNbVotes |
| 94 | + ) public override onlyByCore isActive(_coreDisputeID) returns (address drawnAddress, uint96 fromSubcourtID) { |
| 95 | + (drawnAddress, fromSubcourtID) = super.draw(_coreDisputeID, _nonce, _roundNbVotes); |
| 96 | + |
| 97 | + if (drawnAddress == address(0)) return (drawnAddress, fromSubcourtID); |
| 98 | + |
| 99 | + uint256 localDisputeID = coreDisputeIDToLocal[_coreDisputeID]; |
| 100 | + Dispute storage dispute = disputes[localDisputeID]; |
| 101 | + uint256 localRoundID = dispute.rounds.length - 1; |
| 102 | + if (IBalanceHolder(accreditedConsumerProtectionLawyerToken).balanceOf(drawnAddress) > 0) { |
| 103 | + // The drawnAddress is a consumer protection lawyer. |
| 104 | + drawnConsumerProtectionLawyer[localDisputeID][localRoundID] = true; |
| 105 | + } else { |
| 106 | + // The drawnAddress is not a consumer protection lawyer. |
| 107 | + if ( |
| 108 | + dispute.rounds[localRoundID].votes.length == _roundNbVotes && |
| 109 | + !drawnConsumerProtectionLawyer[localDisputeID][localRoundID] |
| 110 | + ) { |
| 111 | + // This is the last draw iteration and we still have not drawn a consumer protection lawyer. |
| 112 | + // Drop the last round.votes pushed by super.draw(), so that another iteration can try again. |
| 113 | + drawnAddress = address(0); |
| 114 | + dispute.rounds[localRoundID].votes.pop(); |
| 115 | + // Note that round.alreadyDrawn[drawnAddress] is not cleared because we don't know if it has been drawn more than once. |
| 116 | + // It's fine because this DisputeKit does not enable singleDrawPerJuror. |
| 117 | + } |
| 118 | + } |
| 119 | + return (drawnAddress, fromSubcourtID); |
| 120 | + } |
| 121 | + |
| 122 | + // ************************************* // |
| 123 | + // * Internal * // |
| 124 | + // ************************************* // |
| 125 | + |
| 126 | + /// @inheritdoc DisputeKitClassicBase |
| 127 | + function _postDrawCheck( |
| 128 | + Round storage _round, |
| 129 | + uint256 _coreDisputeID, |
| 130 | + address _juror |
| 131 | + ) internal view override returns (bool) { |
| 132 | + if (!super._postDrawCheck(_round, _coreDisputeID, _juror)) return false; |
| 133 | + return |
| 134 | + (IBalanceHolder(accreditedLawyerToken).balanceOf(_juror) > 0) || |
| 135 | + (IBalanceHolder(accreditedConsumerProtectionLawyerToken).balanceOf(_juror) > 0); |
| 136 | + } |
| 137 | + |
| 138 | + // ************************************* // |
| 139 | + // * Errors * // |
| 140 | + // ************************************* // |
| 141 | + |
| 142 | + error TokenNotSupported(address tokenGate); |
| 143 | +} |
0 commit comments