Skip to content

Commit b6ca2ad

Browse files
committed
feat: new dispute kit with specialized gating logic
For accredited Argentinian consumer protection lawyers
1 parent f8536e6 commit b6ca2ad

File tree

5 files changed

+162
-5
lines changed

5 files changed

+162
-5
lines changed

contracts/src/arbitration/KlerosCore.sol

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -753,7 +753,11 @@ contract KlerosCore is IArbitratorV2, Initializable, UUPSProxiable {
753753
uint256 startIndex = round.drawIterations; // for gas: less storage reads
754754
uint256 i;
755755
while (i < _iterations && round.drawnJurors.length < round.nbVotes) {
756-
(address drawnAddress, uint96 fromSubcourtID) = disputeKit.draw(_disputeID, startIndex + i++);
756+
(address drawnAddress, uint96 fromSubcourtID) = disputeKit.draw(
757+
_disputeID,
758+
startIndex + i++,
759+
round.nbVotes
760+
);
757761
if (drawnAddress == address(0)) {
758762
continue;
759763
}

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

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -251,8 +251,16 @@ abstract contract DisputeKitClassicBase is IDisputeKit, Initializable, UUPSProxi
251251
/// @inheritdoc IDisputeKit
252252
function draw(
253253
uint256 _coreDisputeID,
254-
uint256 _nonce
255-
) external override onlyByCore isActive(_coreDisputeID) returns (address drawnAddress, uint96 fromSubcourtID) {
254+
uint256 _nonce,
255+
uint256 /*_roundNbVotes*/
256+
)
257+
public
258+
virtual
259+
override
260+
onlyByCore
261+
isActive(_coreDisputeID)
262+
returns (address drawnAddress, uint96 fromSubcourtID)
263+
{
256264
uint256 localDisputeID = coreDisputeIDToLocal[_coreDisputeID];
257265
Dispute storage dispute = disputes[localDisputeID];
258266
uint256 localRoundID = dispute.rounds.length - 1;
Lines changed: 143 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,143 @@
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+
}

contracts/src/arbitration/interfaces/IDisputeKit.sol

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,11 +50,13 @@ interface IDisputeKit {
5050
/// @dev Access restricted to Kleros Core only.
5151
/// @param _coreDisputeID The ID of the dispute in Kleros Core, not in the Dispute Kit.
5252
/// @param _nonce Nonce.
53+
/// @param _roundNbVotes The number of votes in the round, including already drawn and yet to be drawn.
5354
/// @return drawnAddress The drawn address.
5455
/// @return fromSubcourtID The subcourt ID from which the juror was drawn.
5556
function draw(
5657
uint256 _coreDisputeID,
57-
uint256 _nonce
58+
uint256 _nonce,
59+
uint256 _roundNbVotes
5860
) external returns (address drawnAddress, uint96 fromSubcourtID);
5961

6062
// ************************************* //

contracts/src/arbitration/university/KlerosCoreUniversity.sol

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -602,7 +602,7 @@ contract KlerosCoreUniversity is IArbitratorV2, UUPSProxiable, Initializable {
602602
{
603603
IDisputeKit disputeKit = disputeKits[round.disputeKitID];
604604
uint256 iteration = round.drawIterations + 1;
605-
(address drawnAddress, uint96 fromSubcourtID) = disputeKit.draw(_disputeID, iteration);
605+
(address drawnAddress, uint96 fromSubcourtID) = disputeKit.draw(_disputeID, iteration, round.nbVotes);
606606
if (drawnAddress == address(0)) {
607607
revert NoJurorDrawn();
608608
}

0 commit comments

Comments
 (0)